Comment je me suis débarrassé de mille onglets ...

... et avait 3 ans de retard. Idéalement, cela devrait être comme ceci: l'utilisateur lance le navigateur et le navigateur montre ce dont l'utilisateur a besoin. Mais bien que cela ne soit pas implémenté, vous devez utiliser des moteurs de recherche. Idéalement, cela devrait être comme ceci: l'utilisateur ouvre un moteur de recherche, entre une requête de recherche et montre ce dont l'utilisateur a besoin. Mais alors que le bouton «Je me sens chanceux» ne fonctionne pas aussi bien (bien que récemment, il y ait eu un mouvement notable dans cette direction), il faut parfois aller à plusieurs adresses depuis la page des résultats de la recherche.

Le scénario d'utilisation des moteurs de recherche, apparemment, a été fixé historiquement (lorsque Internet était lent): en arrivant à la page des résultats de la recherche, j'ai ouvert plusieurs onglets en arrière-plan, et pendant que les autres se chargeaient, il était déjà possible de lire le premier onglet. Dans le cas où j'ai trouvé les informations nécessaires sur l'un des onglets, le reste a dû être fermé manuellement. S'il ne se fermait pas immédiatement, les onglets restaient suspendus, ce qui gonflait le nombre d'onglets ouverts dans le navigateur, qui, en règle générale, se fermait rarement après cela.

De plus, si vous cliquez sur la page avec les liens qui s'ouvrent dans une nouvelle fenêtre, plusieurs onglets (logiquement) à onglets sont créés. Lorsque vous trouvez les informations dont vous avez besoin, vous ne vous souvenez pas toujours des onglets connectés, vous ne pouvez pas tout fermer, ce qui conduit également à augmenter le nombre d'onglets ouverts.

J'ai toujours eu besoin du bouton «Trouvé» , qui nettoierait après moi les conséquences de la recherche (appelons-le «j'ai eu de la chance» ). Après avoir plongé dans le monde des extensions de navigateur, j'ai pensé que c'était quelque chose qui pourrait aider dans ce cas. Alors commençait à faiblement apparaître le désir d'écrire une extension qui résoudrait mes problèmes.

Je vais vous raconter mon histoire, je vais mener l'histoire par ordre chronologique, les conclusions peuvent être inattendues.

Premier pas vers


La première chose que j'ai faite a été de mettre en place l'infrastructure: webpack + babel . Et tout de suite, je n'ai pas aimé ce code dupliqué babel pour ses assistants dans chaque module. Il était possible de le configurer pour utiliser l'objet babelHelper, mais ensuite le fichier de code babelHelperdevait être connecté dans la configuration du webpack . Garder un tel fichier dans le projet et le pointer entryétait moche, j'ai fait un plug-in pour le webpack qui l'a fait automatiquement pour moi. Après avoir dépensé beaucoup d'efforts sur la première étape et écrit un peu plus de code pour l'extension elle-même, j'ai ralenti un peu.

Brancher

Fondation


Le temps a passé et seul un plug-in pour webpack était disponible, ce qui n'a pas résolu mes problèmes. Et chaque fois que je cherchais quelque chose et que je ne fermais pas les onglets, il y avait une pensée: "Ce serait bien de compléter cette extension ..." Le désir grandissait et grandissait, et maintenant, un beau jour, la quantité devenait qualité.

Il est temps de dire quelle était l'idée principale: l'
utilisateur accède à la page des résultats de la recherche - SICKLE , nous analysons les résultats, enregistrons les adresses des liens pour nous-mêmes, après que l'utilisateur a cliqué sur l'une des adresses, lui montre une notification avec le reste des adresses et le bouton "Trouvé" pour fermer les onglets.

Lorsque vous accédez à la page, il peut y avoir différentes options. Le plus simple: une demande - une réponse du serveur ( 200) Le plus difficile: une requête - plusieurs redirections de serveur ( 3xx ), après quoi la redirection client (en utilisant <meta/>ou javascript), l' API historique est également au top . Et les combinaisons entre eux, en règle générale, la plupart des sites entrent dans cette catégorie.

Cas de transition simple:

Le cas d'une transition simple (réponse 200)

Cas de transition complexe:

Cas de transition complexe (3xx + redirections client)

Autrement dit, enregistrer l'adresse de la page et la vérifier uniquement n'est pas toujours suffisant. Par conséquent, vous devez créer une transition logique, où écrire toutes les adresses rencontrées sur le chemin, puis vérifier que la transition logique contient l'adresse stockée. La tâche est claire, mais tout n'est pas aussi simple à exécuter.

Dans Chrome, il existe deux API liées à la navigation: webNavigation et webRequest - chacune avec ses propres événements. Le premier - connecte les transitions et l'interface utilisateur du navigateur, le second - les demandes de réseau sous-jacentes. Par conséquent, si le changement d'adresse sur la page s'est produit en raison de l' API historique, il n'y aura aucun événement pour ce dernier, et si des redirections se produisent lors d'une requête réseau, le premier ne le signale pas du tout. Par conséquent, il est nécessaire d'utiliser les deux API, en collectant une pincée de chaque événement de chaque API, pour former une transition logique.

Quelques détails
, webNavigation (wN) :

onBeforeNavigate -> onCommitted -> onDOMContentLoaded -> onCompleted

webRequest (wR):

onBeforeRequest -> [onBeforeRedirect -> onBeforeRequest]* -> onCompleted | onErrorOccurred

wR wN ( ), .. - wN.onBeforeNavigate wR.onBeforeRequest, - . .

, , .

Développement


... Revenons au moment où la quantité est devenue qualité. Un temps considérable s'est écoulé depuis le début du développement jusqu'à ce point: les navigateurs ont commencé à prendre en charge les modules es6 , le DOM fantôme et d'autres fonctionnalités modernes. Pour construire le projet, je suis passé à Rollup , cette fois je n'ai pas eu à écrire de plugin. Après avoir construit les bases - la possibilité d'obtenir des informations sur toute transition dans n'importe quel onglet, il reste à implémenter la logique d'analyse des SICKLES pris en charge et d'affichage des notifications sur les pages connexes.

La première tâche est assez primitive: nous connaissons l'adresse du SICKLE, montons dans le contenu de la page en utilisant le script de contenu, récupérons les données qui nous intéressent, enregistrez-les, attendez que l'utilisateur aille sur l'une des pages pour lui montrer une notification avec les autres pages.

Pour la deuxième tâche, vous devez implémenter la notification elle-même, que montrer à l'utilisateur sur la page. Et ici aussi, les scripts de contenu ne peuvent pas faire.

Initialement, il n'y avait qu'un seul gestionnaire (alias un contrôleur) qui était responsable de la logique lors de l'interaction de l'utilisateur avec les moteurs de recherche. Puis l'idée est venue, pourquoi ne pas afficher les notifications sur les onglets associés lorsque l'utilisateur clique simplement sur les liens qui s'ouvrent dans de nouveaux onglets. J'ai dû refaire la logique, la rendant plus universelle. Similaire au middleware React / Redux, vous pouvez connecter plusieurs gestionnaires de transition, ce qui vous permettra à l'avenir de mettre en œuvre la possibilité de désactiver / activer divers gestionnaires dans les paramètres d'extension.

Intimité


Étant donné que la notification est un panneau en bas de l'écran et qu'elle est ajoutée à la mise en page, le script de la page peut accéder à cet élément de la même manière que tout autre élément de cette page. Autrement dit, la page pourrait savoir quelle requête de recherche vous avez utilisée, dans quel moteur de recherche et quelles autres pages vous ont été proposées, ce qui n'est pas très bon.

Une technologie appelée shadow DOM vient à la rescousse . Il n'est pas recommandé de l'utiliser closed modesur le Web lors de sa création shadowRoot, car cela n'a pas beaucoup de sens (vous devez toujours stocker le lien vers l'élément shadowRootquelque part si vous souhaitez y accéder par programme; vous pouvez également redéfinir la fonction attachShadowà créershadowRooten mode ouvert, puis les scripts chargés après la redéfinition utiliseront déjà la nouvelle version de la fonction).

En cas d'expansion, ce n'est pas le cas. Les scripts de contenu et les scripts de page vivent dans des mondes parallèles. Les scripts de la page n'ont pas accès aux objets définis dans les scripts de contenu, tandis que les scripts de contenu fonctionnent avec l'implémentation native des fonctions DOM des objets (une fonction remplacée par un script de la page n'a aucun effet sur la fonction avec laquelle le script de contenu fonctionne). En combinant ces deux conditions, nous voyons qu'il est possible de créer un élément avec un élément privé shadowRooten stockant le lien vers celui-ci dans une variable.

Dans ce cas, le script de la page ne pourra accéder qu'à l'élément wrapper, qui sera vide pour lui. Il ne pourra pas recevoir le texte de la demande ni les pages proposées. Il faut veiller à ne pas donner de lien vers un élément de la notification ou du texte brut dans les événements générés. Par conséquent, dans l'extension, l'ID généré est utilisé dans les événements et le script d'arrière-plan comprend déjà ce qui est requis de lui par cet ID. Pour la page, cet identifiant n'a aucun sens.

Difficultés de traduction


Initialement, l'extension a été développée uniquement pour Google Chrome , mais depuis l' API WebExtensions , quelque part dans ma tête a gardé la possibilité de porter sur d'autres navigateurs. Et la présence de webextension-polyfill a inspiré la confiance. Mais peu importe comment. Le polyphil de cette extension n'a apporté que la possibilité d'utiliser l'API Chrome avec des promesses.

Firefox est devenu une déception de l'année. La non-concordance de l'API Chrome dans Firefox ( bug 1543647 , bug 1595621 ) s'est avérée critique pour que l'extension fonctionne, nous pouvons dire qu'elle ne fonctionne pas dans ce navigateur (comme prévu).

Vivaldi était le plus proche, mais non sans frais. un événementwN.onCreatedNavigationTargetCela ne se produit pas lorsque l'utilisateur ouvre le lien avec le bouton central de la souris ou via le bouton Shift|Ctrl+ gauche de la souris, au lieu de l'événement wN.onCommitted transitionType == 'start_page', qui ne se trouve pas dans l' API Chrome , pour cette raison, l'extension ne fonctionne pas correctement dans tous les cas. Également dans Vivaldi ne fonctionne pas les raccourcis clavier pour les extensions. Ce qui est une fonctionnalité tueur dans ce cas dans Chrome, vous permet de parcourir rapidement les onglets et de les fermer, sans avoir besoin d'utiliser une souris pour cela.

Conclusion


Lors de l'écriture du code, la logique d'affichage des notifications a changé plusieurs fois, se simplifiant à chaque fois. En conséquence, il s'est avéré qu'il était possible de ne pas clôturer le jardin avec des transitions logiques, mais d'attraper les «transitions liées» de l'utilisateur (dans le cas où wN.onCommittedil y a un indicateur transitionTypequi indique à quoi servait la transition, dans de nombreux cas, il s'agit de «lien», ce qui signifie que l'utilisateur est passé par référence), ce qui simplifierait considérablement le code et fonctionnerait dans de nombreux cas, mais pas dans tous.

De plus, n'étant pas dans le sujet, je m'attendais à plus de compatibilité en termes d' API webExtensions. Comme toujours, il fait bon vivre dans un monde de navigateurs modernes lorsque vous n'avez pas besoin de prise en charge pour les anciennes versions. Les animations CSS sont une chose merveilleuse: ce pour quoi vous utilisiez la bibliothèque js se fait maintenant en quelques lignes sur css. Les éléments personnalisés ne fonctionnent pas dans les extensions, mais le DOM fantôme fonctionne, vous permettant de profiter de toutes ses fonctionnalités.

Expansion
chrome web store: Handy Search

Source: https://habr.com/ru/post/undefined/


All Articles