Maison > interface Web > js tutoriel > Création d'une extension multi-navigateurs bloquant le site

Création d'une extension multi-navigateurs bloquant le site

Mary-Kate Olsen
Libérer: 2025-01-22 22:33:16
original
563 Les gens l'ont consulté

Dans cet article, je vais expliquer mon processus étape par étape de création d'une extension de navigateur pour bloquer les sites Web et décrire les défis que j'ai rencontrés et les solutions que j'ai proposées. Il ne s’agit pas d’un guide exhaustif. Je ne prétends pas être un expert en quoi que ce soit. Je veux juste partager mon processus de réflexion derrière la construction de ce projet. Alors prenez tout ici avec des pincettes. Je ne couvrirai pas chaque ligne mais me concentrerai plutôt sur les points clés du projet, les difficultés, les cas intéressants et les bizarreries du projet. Vous êtes invités à explorer le code source plus en détail par vous-même.


Table des matières :

  • Préface
  • Mise en place du projet
  • Création du formulaire de saisie principal
  • Gestion du blocage d'URL
  • Page d'options de création
  • Implémentation du mode strict
  • Conclusion

Préface

Comme beaucoup de gens, j'ai du mal à me concentrer sur différentes tâches, en particulier avec Internet qui est le distraction omniprésent. Heureusement, en tant que programmeur, j'ai développé de grandes compétences en matière de création de problèmes. J'ai donc décidé qu'au lieu de chercher une meilleure solution existante, je créerais ma propre extension de navigateur qui bloquerait les sites Web auxquels les utilisateurs souhaitent restreindre l'accès.
Tout d’abord, décrivons les exigences et les principales fonctionnalités. L'extension doit :

  • être multi-navigateur.
  • bloquer les sites Web de la liste noire.
  • permet de choisir une option de blocage : soit bloquer l'intégralité du domaine avec ses sous-domaines, soit bloquer uniquement l'URL sélectionnée.
  • offre la possibilité de désactiver un site Web bloqué sans le supprimer de la liste noire.
  • fournir une option pour restreindre automatiquement l'accès si l'utilisateur rechute ou oublie de réactiver les URL désactivées (utile pour les personnes atteintes de TDAH).

Mise en place du projet

Tout d'abord, voici la pile principale que j'ai choisie :

  • TypeScript : J'ai opté pour TS plutôt que pour JS en raison des nombreuses API peu familières permettant aux extensions de se passer de la fonctionnalité de saisie semi-automatique.
  • Webpack : Plus facile à utiliser dans ce contexte par rapport à tsc pour la compilation TS. De plus, j'ai rencontré des problèmes pour générer du JS compatible avec le navigateur avec tsc.
  • CSS : Vanilla CSS correspondait à mon objectif de simplicité, de taille de bundle plus petite et de dépendances minimales. De plus, je pensais que toute autre chose serait exagérée pour une extension de seulement quelques pages. Pour ces raisons, j'ai également décidé de ne pas utiliser d'outils comme React ou des frameworks spécifiques de création d'extensions.

La principale distinction entre le développement d'extensions et le développement Web classique est que les extensions s'appuient sur des techniciens de service qui gèrent la plupart des événements, des scripts de contenu et de la messagerie entre eux.

Création du manifeste

Pour prendre en charge la fonctionnalité multi-navigateurs, j'ai créé deux fichiers manifeste :

  • manifest.chrome.json : pour l'exigence Manifest v3 de Chrome.
  • manifest.firefox.json : Pour Firefox, qui prend mieux en charge Manifest v2. Voici les principales différences entre les 2 fichiers :

manifest.chrome.json :

{
  "manifest_version": 3,
  "action": {
    "default_title": "Click to show the form"
  },
  "incognito": "split",
  "permissions": [
    "activeTab",
    "declarativeNetRequestWithHostAccess",
    "scripting",
    "storage",
    "tabs"
  ],
  "host_permissions": ["*://*/"], // get access to all URLs
  "background": {
    "service_worker": "background.js"
  },
  "content_scripts": [{
    "matches": ["<all_urls>"]
  }],
  "web_accessible_resources": [
    {
      "resources": ["blocked.html", "options.html", "about.html", "icons/*.svg"],
      "matches": ["<all_urls>"]
    }
  ],
  "content_security_policy": {
    "extension_pages": "script-src 'self'; object-src 'self'"
  },
}
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

manifest.firefox.json :

{
  "manifest_version": 2,
  "browser_action": {
    "default_title": "Click to show the form"
  },
  "permissions": [
    "activeTab",
    "declarativeNetRequest",
    "declarativeNetRequestWithHostAccess",
    "scripting", 
    "storage",
    "tabs",
    "*://*/"
  ],
  "background": {
    "scripts": [
      "background.js"
    ],
    "persistent": false
  },
  "content_scripts": [{
    "matches": ["<all_urls>"],
    "js": [
      "options.js",
      "blocked.js",
      "about.js"
    ]
  }],
  "web_accessible_resources": [
    "blocked.html",
    "options.html", 
    "icons/*.svg"
  ],
  "content_security_policy": "script-src 'self'; object-src 'self'",
}
Copier après la connexion
Copier après la connexion
Copier après la connexion

Une chose intéressante ici est que Chrome nécessitait "incognito": "split", propriété spécifiée pour fonctionner correctement en mode navigation privée alors que Firefox fonctionnait bien sans elle.

Voici la structure de base des fichiers de l'extension :

dist/
node_modules/
src/
|-- background.tsc
|-- content.ts
static/
|-- manifest.chrome.json
|-- manifest.firefox.json
package.json
tsconfig.json
webpack.config.js
Copier après la connexion
Copier après la connexion
Copier après la connexion

Parlons maintenant de la façon dont l'extension est censée fonctionner. L'utilisateur devrait pouvoir déclencher une sorte de formulaire pour soumettre l'URL qu'il souhaite bloquer. Lorsqu'il accède à une URL, l'extension intercepte la demande et vérifie si elle doit être bloquée ou autorisée. Il a également besoin d'une sorte de page d'options où un utilisateur peut voir la liste de toutes les URL bloquées et pouvoir ajouter, modifier, désactiver ou supprimer une URL de la liste.

Création du formulaire de saisie principal

Le formulaire apparaît en injectant du HTML et du CSS dans la page actuelle lorsque l'utilisateur clique sur l'icône d'extension ou tape le raccourci clavier. Il existe différentes manières d'afficher un formulaire, comme appeler une fenêtre contextuelle, mais les options de personnalisation sont limitées à mon goût. Le script d'arrière-plan ressemble à ceci :

background.ts :

import browser, { DeclarativeNetRequest } from 'webextension-polyfill';

// on icon click
const action = chrome.action ?? browser.browserAction; // Manifest v2 only has browserAction method
action.onClicked.addListener(tab => {
  triggerPopup(tab as browser.Tabs.Tab);
});

// on shortcut key press 
browser.commands.onCommand.addListener(command => {
  if (command === 'trigger_form') {
    browser.tabs.query({ active: true, currentWindow: true })
      .then((tabs) => {
        const tab = tabs[0];
        if (tab) {
          triggerPopup(tab);
        }
      })
      .catch(error => console.error(error));
  }
});

function triggerPopup(tab: browser.Tabs.Tab) {
  if (tab.id) {
    const tabId = tab.id;
    browser.scripting.insertCSS(({
      target: { tabId },
      files: ['global.css', './popup.css'],
    }))
      .then(() => {
        browser.scripting.executeScript
          ? browser.scripting.executeScript({
            target: { tabId },
            files: ['./content.js'], // refer to the compiled JS files, not the original TS ones 
          })
          : browser.tabs.executeScript({
            file: './content.js',
          });
      })
      .catch(error => console.error(error));
  }
}
Copier après la connexion
Copier après la connexion
Copier après la connexion

Injecter du HTML dans chaque page peut conduire à des résultats imprévisibles car il est difficile de prédire comment les différents styles de pages Web vont affecter le formulaire. Une meilleure alternative semble utiliser Shadow DOM car il crée sa propre portée pour les styles. C'est certainement une amélioration potentielle sur laquelle j'aimerais travailler à l'avenir.

J'ai utilisé webextension-polyfill pour la compatibilité du navigateur. En l'utilisant, je n'ai pas eu besoin d'écrire des extensions distinctes pour différentes versions du manifeste. Vous pouvez en savoir plus sur ce qu'il fait ici. Pour que cela fonctionne, j'ai inclus le fichier browser-polyfill.js avant les autres scripts dans les fichiers manifestes.

manifest.chrome.json :

{
  "content_scripts": [{
    "js": ["browser-polyfill.js"]
  }],
}
Copier après la connexion
Copier après la connexion
Copier après la connexion

manifest.firefox.json :

{
  "background": {
    "scripts": [
      "browser-polyfill.js",
      // other scripts
    ],
  },
  "content_scripts": [{
    "js": [
      "browser-polyfill.js",
      // other scripts
    ]
  }],
}
Copier après la connexion
Copier après la connexion
Copier après la connexion

Le processus d'injection du formulaire est une manipulation simple du DOM, mais notez que chaque élément doit être créé individuellement au lieu d'appliquer un modèle littéral à un élément. Bien que plus verbeuse et fastidieuse, cette méthode évite les avertissements d'injection HTML non sécurisée que nous recevrions autrement en essayant d'exécuter le code compilé dans le navigateur.

content.ts :

import browser from 'webextension-polyfill';
import { maxUrlLength, minUrlLength } from "./globals";
import { GetCurrentUrl, ResToSend } from "./types";
import { handleFormSubmission } from './helpers';

async function showPopup() {
  const body = document.body;
  const formExists = document.getElementById('extension-popup-form');
  if (!formExists) {
    const msg: GetCurrentUrl = { action: 'getCurrentUrl' };

    try {
      const res: ResToSend = await browser.runtime.sendMessage(msg);

      if (res.success && res.url) {
        const currUrl: string = res.url;
        const popupForm = document.createElement('form');
        popupForm.classList.add('extension-popup-form');
        popupForm.id = 'extension-popup-form';

        /* Create every child element the same way as above */

        body.appendChild(popupForm);
        popupForm.addEventListener('submit', (e) => {
          e.preventDefault();
          handleFormSubmission(popupForm, handleSuccessfulSubmission); // we'll discuss form submission later
        });
        document.addEventListener('keydown', (e) => {
          if (e.key === 'Escape') {
            if (popupForm) {
              body.removeChild(popupForm);
            }
          }
        });
      }
    } catch (error) {
      console.error(error);
      alert('Something went wrong. Please try again.');
    }
  }
}

function handleSuccessfulSubmission() {
  hidePopup();
  setTimeout(() => {
    window.location.reload();
  }, 100); // need to wait a little bit in order to see the changes
}

function hidePopup() {
  const popup = document.getElementById('extension-popup-form');
  popup && document.body.removeChild(popup);
}
Copier après la connexion

Il est maintenant temps de vous assurer que le formulaire s'affiche dans le navigateur. Pour effectuer l'étape de compilation requise, j'ai configuré Webpack comme ceci :

webpack.config.ts :

{
  "manifest_version": 3,
  "action": {
    "default_title": "Click to show the form"
  },
  "incognito": "split",
  "permissions": [
    "activeTab",
    "declarativeNetRequestWithHostAccess",
    "scripting",
    "storage",
    "tabs"
  ],
  "host_permissions": ["*://*/"], // get access to all URLs
  "background": {
    "service_worker": "background.js"
  },
  "content_scripts": [{
    "matches": ["<all_urls>"]
  }],
  "web_accessible_resources": [
    {
      "resources": ["blocked.html", "options.html", "about.html", "icons/*.svg"],
      "matches": ["<all_urls>"]
    }
  ],
  "content_security_policy": {
    "extension_pages": "script-src 'self'; object-src 'self'"
  },
}
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Fondamentalement, il prend le nom du navigateur de la variable d'environnement des commandes que j'exécute pour choisir entre 2 des fichiers manifestes et compile le code TypeScript dans le répertoire dist/.

J'allais écrire des tests appropriés pour l'extension, mais j'ai découvert que Puppeteer ne prend pas en charge les tests de scripts de contenu, ce qui rend impossible le test de la plupart des fonctionnalités. Si vous connaissez des solutions de contournement pour les tests de scripts de contenu, j'aimerais les entendre dans les commentaires.

Mes commandes de build dans package.json sont :

{
  "manifest_version": 2,
  "browser_action": {
    "default_title": "Click to show the form"
  },
  "permissions": [
    "activeTab",
    "declarativeNetRequest",
    "declarativeNetRequestWithHostAccess",
    "scripting", 
    "storage",
    "tabs",
    "*://*/"
  ],
  "background": {
    "scripts": [
      "background.js"
    ],
    "persistent": false
  },
  "content_scripts": [{
    "matches": ["<all_urls>"],
    "js": [
      "options.js",
      "blocked.js",
      "about.js"
    ]
  }],
  "web_accessible_resources": [
    "blocked.html",
    "options.html", 
    "icons/*.svg"
  ],
  "content_security_policy": "script-src 'self'; object-src 'self'",
}
Copier après la connexion
Copier après la connexion
Copier après la connexion

Donc, par exemple, chaque fois que je cours

dist/
node_modules/
src/
|-- background.tsc
|-- content.ts
static/
|-- manifest.chrome.json
|-- manifest.firefox.json
package.json
tsconfig.json
webpack.config.js
Copier après la connexion
Copier après la connexion
Copier après la connexion

les fichiers pour Chrome sont compilés dans le répertoire dist/. Après avoir déclenché un formulaire sur n'importe quel site en cliquant sur l'icône d'action ou en appuyant sur le raccourci, le formulaire ressemble à ceci :

Main form display

Gestion du blocage d'URL

Maintenant que le formulaire principal est prêt, la tâche suivante consiste à le soumettre. Pour implémenter la fonctionnalité de blocage, j'ai exploité l'API déclarativeNetRequest et les règles dynamiques. Les règles vont être stockées dans le stockage de l'extension. La manipulation de règles dynamiques n'est possible que dans le fichier du service Worker, donc pour échanger des données entre le Service Worker et les scripts de contenu, j'enverrai des messages entre eux avec les données nécessaires. Comme de nombreux types d'opérations sont nécessaires pour cette extension, j'ai créé des types pour chaque action. Voici un exemple de type d'opération :

types.ts:

import browser, { DeclarativeNetRequest } from 'webextension-polyfill';

// on icon click
const action = chrome.action ?? browser.browserAction; // Manifest v2 only has browserAction method
action.onClicked.addListener(tab => {
  triggerPopup(tab as browser.Tabs.Tab);
});

// on shortcut key press 
browser.commands.onCommand.addListener(command => {
  if (command === 'trigger_form') {
    browser.tabs.query({ active: true, currentWindow: true })
      .then((tabs) => {
        const tab = tabs[0];
        if (tab) {
          triggerPopup(tab);
        }
      })
      .catch(error => console.error(error));
  }
});

function triggerPopup(tab: browser.Tabs.Tab) {
  if (tab.id) {
    const tabId = tab.id;
    browser.scripting.insertCSS(({
      target: { tabId },
      files: ['global.css', './popup.css'],
    }))
      .then(() => {
        browser.scripting.executeScript
          ? browser.scripting.executeScript({
            target: { tabId },
            files: ['./content.js'], // refer to the compiled JS files, not the original TS ones 
          })
          : browser.tabs.executeScript({
            file: './content.js',
          });
      })
      .catch(error => console.error(error));
  }
}
Copier après la connexion
Copier après la connexion
Copier après la connexion

Comme il est raisonnable de pouvoir ajouter de nouvelles URL à la fois depuis le formulaire principal et depuis la page d'options, la soumission a été exécutée par une fonction réutilisable dans un nouveau fichier :

helpers.ts:

{
  "content_scripts": [{
    "js": ["browser-polyfill.js"]
  }],
}
Copier après la connexion
Copier après la connexion
Copier après la connexion

J'appelle handleFormSubmission() dans content.ts qui valide l'URL fournie, puis l'envoie au service worker pour l'ajouter à la liste noire.

Les règles dynamiques ont fixé la taille maximale qui doit être prise en compte. La transmission d'une chaîne d'URL trop longue entraînera un comportement inattendu lorsque vous tenterez d'enregistrer la règle dynamique correspondante. J'ai découvert que dans mon cas, une URL de 75 caractères était une bonne longueur maximale pour une règle.

Voici comment le service worker va traiter le message reçu :

background.ts :

{
  "background": {
    "scripts": [
      "browser-polyfill.js",
      // other scripts
    ],
  },
  "content_scripts": [{
    "js": [
      "browser-polyfill.js",
      // other scripts
    ]
  }],
}
Copier après la connexion
Copier après la connexion
Copier après la connexion

Pour la soumission, je crée un nouvel objet de règle et mets à jour les règles dynamiques pour l'inclure. Une simple expression régulière conditionnelle me permet de choisir entre bloquer l'intégralité du domaine ou uniquement l'URL spécifiée.

Une fois terminé, je renvoie le message de réponse au script de contenu. La chose la plus intéressante dans cet extrait est l’utilisation du nanooïde. Par essais et erreurs, j'ai découvert qu'il existe une limite pour le nombre de règles dynamiques : 5 000 pour les anciens navigateurs et 30 000 pour les plus récents. J'ai découvert cela grâce à un bug lorsque j'ai essayé d'attribuer un identifiant à une règle supérieure à 5 000. Je ne pouvais pas créer de limite pour que mes identifiants soient inférieurs à 4 999, j'ai donc dû limiter mes identifiants à des nombres à 3 chiffres ( 0-999, soit 1000 identifiants uniques au total). Cela signifiait que j'avais réduit le nombre total de règles pour mon extension de 5 000 à 1 000, ce qui d'une part est assez significatif, mais d'autre part, la probabilité qu'un utilisateur ait autant d'URL à bloquer était assez faible, et j'ai donc a décidé de se contenter de cette solution pas si gracieuse.

L'utilisateur peut désormais ajouter de nouvelles URL à la liste noire et choisir le type de blocage qu'il souhaite leur attribuer. S'il tente d'accéder à une ressource bloquée, il sera redirigé vers une page de blocage :

Block page

Cependant, il y a un cas limite qui doit être résolu. L'extension bloquera toutes les URL indésirables si l'utilisateur y accède directement. Mais si le site Web est un SPA avec redirection côté client, l'extension n'y interceptera pas les URL interdites. Pour gérer ce cas, j'ai mis à jour mon background.ts pour écouter l'onglet actuel et voir si l'URL a changé. Lorsque cela se produit, je vérifie manuellement si l'URL est dans la liste noire, et si c'est le cas, je redirige l'utilisateur.

background.ts :

{
  "manifest_version": 3,
  "action": {
    "default_title": "Click to show the form"
  },
  "incognito": "split",
  "permissions": [
    "activeTab",
    "declarativeNetRequestWithHostAccess",
    "scripting",
    "storage",
    "tabs"
  ],
  "host_permissions": ["*://*/"], // get access to all URLs
  "background": {
    "service_worker": "background.js"
  },
  "content_scripts": [{
    "matches": ["<all_urls>"]
  }],
  "web_accessible_resources": [
    {
      "resources": ["blocked.html", "options.html", "about.html", "icons/*.svg"],
      "matches": ["<all_urls>"]
    }
  ],
  "content_security_policy": {
    "extension_pages": "script-src 'self'; object-src 'self'"
  },
}
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

getRules() est une fonction qui utilise la méthode declarativeNetRequest.getDynamicRules() pour récupérer la liste de toutes les règles dynamiques que je convertis dans un format plus lisible.

Désormais, l'extension bloque correctement les URL accessibles directement et via les SPA.

Création d'une page d'options

La page d'options a une interface simple, comme indiqué ci-dessous :

Options page

Il s'agit de la page contenant l'essentiel des fonctionnalités telles que l'édition, la suppression, la désactivation et l'application du mode strict. Voici comment je l'ai câblé.

Fonctionnalité de modification et de suppression

Le montage était probablement la tâche la plus complexe. Les utilisateurs peuvent modifier une URL en modifiant sa chaîne ou en changeant son type de blocage (bloquer le domaine entier ou uniquement un domaine spécifique). Lors de l'édition, je collecte les identifiants des URL modifiées dans un tableau. Lors de l'enregistrement, je crée des règles dynamiques mises à jour que je transmets au service worker pour appliquer les modifications. Après chaque modification ou rechargement enregistré, je récupère les règles dynamiques et les affiche dans le tableau. Vous en trouverez ci-dessous la version simplifiée :

options.ts :

{
  "manifest_version": 3,
  "action": {
    "default_title": "Click to show the form"
  },
  "incognito": "split",
  "permissions": [
    "activeTab",
    "declarativeNetRequestWithHostAccess",
    "scripting",
    "storage",
    "tabs"
  ],
  "host_permissions": ["*://*/"], // get access to all URLs
  "background": {
    "service_worker": "background.js"
  },
  "content_scripts": [{
    "matches": ["<all_urls>"]
  }],
  "web_accessible_resources": [
    {
      "resources": ["blocked.html", "options.html", "about.html", "icons/*.svg"],
      "matches": ["<all_urls>"]
    }
  ],
  "content_security_policy": {
    "extension_pages": "script-src 'self'; object-src 'self'"
  },
}
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

La façon dont je décide de bloquer ou d'autoriser une règle particulière consiste simplement à vérifier conditionnellement sa propriété isActive. Mettre à jour les règles et récupérer les règles - ce sont 2 opérations supplémentaires à ajouter à mon écouteur en arrière-plan :

background.ts :

{
  "manifest_version": 2,
  "browser_action": {
    "default_title": "Click to show the form"
  },
  "permissions": [
    "activeTab",
    "declarativeNetRequest",
    "declarativeNetRequestWithHostAccess",
    "scripting", 
    "storage",
    "tabs",
    "*://*/"
  ],
  "background": {
    "scripts": [
      "background.js"
    ],
    "persistent": false
  },
  "content_scripts": [{
    "matches": ["<all_urls>"],
    "js": [
      "options.js",
      "blocked.js",
      "about.js"
    ]
  }],
  "web_accessible_resources": [
    "blocked.html",
    "options.html", 
    "icons/*.svg"
  ],
  "content_security_policy": "script-src 'self'; object-src 'self'",
}
Copier après la connexion
Copier après la connexion
Copier après la connexion

La fonctionnalité de mise à jour était un peu difficile à mettre en œuvre car il existe un cas extrême où une URL modifiée devient un double d'une règle existante. A part ça, c'est le même baratin : mettez à jour les règles dynamiques et envoyez le message approprié une fois terminé.

La suppression d'URL était probablement la tâche la plus simple. Il existe 2 types de suppression dans cette extension : suppression d'une règle spécifique et suppression de toutes les règles.

options.ts :

dist/
node_modules/
src/
|-- background.tsc
|-- content.ts
static/
|-- manifest.chrome.json
|-- manifest.firefox.json
package.json
tsconfig.json
webpack.config.js
Copier après la connexion
Copier après la connexion
Copier après la connexion

Et, comme avant, j'ai ajouté 2 actions supplémentaires à l'auditeur du service worker :

background.ts :

import browser, { DeclarativeNetRequest } from 'webextension-polyfill';

// on icon click
const action = chrome.action ?? browser.browserAction; // Manifest v2 only has browserAction method
action.onClicked.addListener(tab => {
  triggerPopup(tab as browser.Tabs.Tab);
});

// on shortcut key press 
browser.commands.onCommand.addListener(command => {
  if (command === 'trigger_form') {
    browser.tabs.query({ active: true, currentWindow: true })
      .then((tabs) => {
        const tab = tabs[0];
        if (tab) {
          triggerPopup(tab);
        }
      })
      .catch(error => console.error(error));
  }
});

function triggerPopup(tab: browser.Tabs.Tab) {
  if (tab.id) {
    const tabId = tab.id;
    browser.scripting.insertCSS(({
      target: { tabId },
      files: ['global.css', './popup.css'],
    }))
      .then(() => {
        browser.scripting.executeScript
          ? browser.scripting.executeScript({
            target: { tabId },
            files: ['./content.js'], // refer to the compiled JS files, not the original TS ones 
          })
          : browser.tabs.executeScript({
            file: './content.js',
          });
      })
      .catch(error => console.error(error));
  }
}
Copier après la connexion
Copier après la connexion
Copier après la connexion

Implémentation du mode strict

La principale caractéristique de l'extension est probablement la possibilité d'appliquer automatiquement le blocage des règles désactivées (autorisations d'accès) pour les personnes qui ont besoin d'un contrôle plus rigide sur leurs habitudes de navigation. L'idée est que lorsque le mode strict est désactivé, toute URL désactivée par l'utilisateur restera désactivée jusqu'à ce que l'utilisateur la modifie. Avec le mode strict activé, toutes les règles désactivées seront automatiquement réactivées après 1 heure. Pour implémenter une telle fonctionnalité, j'ai utilisé le stockage local de l'extension pour stocker un tableau d'objets représentant chaque règle désactivée. Chaque objet comprend un ID de règle, une date de déblocage et l'URL elle-même. Chaque fois qu'un utilisateur accède à une nouvelle ressource ou actualise la liste noire, l'extension vérifie d'abord le stockage pour les règles expirées et les met à jour en conséquence.

options.ts :

{
  "content_scripts": [{
    "js": ["browser-polyfill.js"]
  }],
}
Copier après la connexion
Copier après la connexion
Copier après la connexion

isStrictModeOn boolean est également stocké dans le stockage. Si c'est vrai, je boucle sur toutes les règles et ajoute au stockage celles qui sont désactivées avec un temps de déblocage nouvellement créé pour elles. Ensuite, à chaque réponse, je vérifie le stockage pour les règles désactivées, supprime celles expirées si elles existent et les mets à jour :

background.ts :

{
  "background": {
    "scripts": [
      "browser-polyfill.js",
      // other scripts
    ],
  },
  "content_scripts": [{
    "js": [
      "browser-polyfill.js",
      // other scripts
    ]
  }],
}
Copier après la connexion
Copier après la connexion
Copier après la connexion

Une fois cela fait, l'extension de blocage de sites Web est terminée. Les utilisateurs peuvent ajouter, modifier, supprimer et désactiver toutes les URL de leur choix, appliquer des blocages de domaine partiels ou entiers et utiliser le mode strict pour les aider à maintenir plus de discipline dans leur navigation.

Extension work example


Conclusion

C'est l'aperçu de base de mon extension de blocage de sites. C'est ma première extension, et ce fut une expérience intéressante, surtout compte tenu du fait que le monde du développement Web peut parfois devenir banal. Il y a certainement place à l'amélioration et à de nouvelles fonctionnalités. Barre de recherche d'URL dans la liste noire, ajout de tests appropriés, durée personnalisée pour le mode strict, soumission de plusieurs URL à la fois - ce ne sont là que quelques éléments qui me préoccupent et que j'aimerais ajouter un jour à ce projet. J'avais également initialement prévu de rendre l'extension multiplateforme, mais je n'ai pas pu la faire fonctionner sur mon téléphone.
Si vous avez aimé lire cette procédure pas à pas, appris quelque chose de nouveau ou si vous avez d'autres commentaires, vos commentaires sont appréciés. Merci d'avoir lu.

Le code source
La version live

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal