Heim > Web-Frontend > js-Tutorial > Browserübergreifende Erweiterung zum Blockieren von Websites

Browserübergreifende Erweiterung zum Blockieren von Websites

Mary-Kate Olsen
Freigeben: 2025-01-22 22:33:16
Original
521 Leute haben es durchsucht

In diesem Artikel erkläre ich Schritt für Schritt meinen Prozess zum Erstellen einer Browser-Erweiterung zum Blockieren von Websites und beschreibe die Herausforderungen, denen ich begegnet bin, und die Lösungen, die ich gefunden habe. Dies ist kein erschöpfender Leitfaden. Ich behaupte nicht, in irgendetwas ein Experte zu sein. Ich möchte nur meinen Denkprozess hinter der Erstellung dieses Projekts teilen. Nehmen Sie also alles hier mit Vorsicht. Ich werde nicht jede Zeile abdecken, sondern mich stattdessen auf die Kernpunkte des Projekts, Schwierigkeiten, interessante Fälle und Eigenheiten des Projekts konzentrieren. Sie können den Quellcode gerne selbst genauer erkunden.


Inhaltsverzeichnis:

  • Vorwort
  • Projekt einrichten
  • Haupteingabeformular erstellen
  • URL-Blockierung behandeln
  • Optionsseite erstellen
  • Implementierung des strengen Modus
  • Fazit

Vorwort

Wie vielen Menschen fällt es mir schwer, mich auf verschiedene Aufgaben zu konzentrieren, insbesondere da das Internet der allgegenwärtige Ablenkungsfaktor ist. Glücklicherweise habe ich als Programmierer große Fähigkeiten zur Problemerstellung entwickelt und beschloss daher, statt nach einer besseren bestehenden Lösung zu suchen, eine eigene Browsererweiterung zu erstellen, die die Websites blockiert, auf die Benutzer den Zugriff beschränken möchten.
Lassen Sie uns zunächst die Anforderungen und Hauptfunktionen skizzieren. Die Erweiterung muss:

  • browserübergreifend sein.
  • Websites von der Blacklist blockieren.
  • Ermöglichen Sie die Auswahl einer Blockierungsoption: Blockieren Sie entweder die gesamte Domain mit ihren Subdomains oder blockieren Sie nur die ausgewählte URL.
  • bieten die Möglichkeit, eine blockierte Website zu deaktivieren, ohne sie von der Blacklist zu löschen.
  • stellen Sie eine Option bereit, um den Zugriff automatisch einzuschränken, wenn der Benutzer einen Rückfall erleidet oder vergisst, deaktivierte URLs wieder zu aktivieren (hilfreich für Menschen mit ADHS).

Einrichten des Projekts

Hier ist zunächst der Hauptstapel, den ich ausgewählt habe:

  • TypeScript: Ich habe mich für TS gegenüber JS entschieden, da es zahlreiche unbekannte APIs für Erweiterungen gibt, die ohne die Funktion zur automatischen Vervollständigung auskommen.
  • Webpack: In diesem Zusammenhang einfacher zu verwenden als tsc für die TS-Kompilierung. Außerdem hatte ich Probleme beim Generieren von browserkompatiblem JS mit tsc.
  • CSS: Vanilla CSS entsprach meinem Ziel hinsichtlich Einfachheit, kleinerer Bundle-Größe und minimaler Abhängigkeiten. Außerdem hatte ich das Gefühl, dass alles andere für eine Erweiterung mit nur ein paar Seiten übertrieben wäre. Aus diesen Gründen habe ich mich auch gegen die Verwendung von Tools wie React oder spezifischen Frameworks zum Aufbau von Erweiterungen entschieden.

Der Hauptunterschied zwischen der Erweiterungsentwicklung und der regulären Webentwicklung besteht darin, dass Erweiterungen auf Servicemitarbeitern basieren, die die meisten Ereignisse, Inhaltsskripte und Nachrichten zwischen ihnen verwalten.

Erstellen des Manifests

Um die browserübergreifende Funktionalität zu unterstützen, habe ich zwei Manifestdateien erstellt:

  • manifest.chrome.json: Für Chromes Manifest v3-Anforderung.
  • manifest.firefox.json: Für Firefox, das Manifest v2 besser unterstützt. Hier sind die Hauptunterschiede zwischen den beiden Dateien:

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'"
  },
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

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'",
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Eine interessante Sache hierbei ist, dass Chrome die Eigenschaft „incognito“: „split“ benötigte, damit sie ordnungsgemäß im Inkognito-Modus funktioniert, während Firefox ohne diese Eigenschaft einwandfrei funktionierte.

Hier ist die grundlegende Dateistruktur der Erweiterung:

dist/
node_modules/
src/
|-- background.tsc
|-- content.ts
static/
|-- manifest.chrome.json
|-- manifest.firefox.json
package.json
tsconfig.json
webpack.config.js
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Lassen Sie uns nun darüber sprechen, wie die Erweiterung funktionieren soll. Der Benutzer sollte in der Lage sein, eine Art Formular auszulösen, um die URL einzureichen, die er blockieren möchte. Wenn er auf eine URL zugreift, fängt die Erweiterung die Anfrage ab und prüft, ob sie blockiert oder zugelassen werden soll. Es benötigt außerdem eine Art Optionsseite, auf der ein Benutzer die Liste aller blockierten URLs sehen und eine URL zur Liste hinzufügen, bearbeiten, deaktivieren oder löschen kann.

Haupteingabeformular erstellen

Das Formular wird angezeigt, indem HTML und CSS in die aktuelle Seite eingefügt werden, wenn der Benutzer auf das Erweiterungssymbol klickt oder die Tastenkombination eingibt. Es gibt verschiedene Möglichkeiten, ein Formular anzuzeigen, z. B. das Aufrufen eines Popups, aber die Anpassungsmöglichkeiten sind für meinen Geschmack begrenzt. Das Hintergrundskript sieht so aus:

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));
  }
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Das Einfügen von HTML in jede Seite kann zu unvorhersehbaren Ergebnissen führen, da es schwer vorherzusagen ist, wie sich unterschiedliche Stile von Webseiten auf das Formular auswirken werden. Eine bessere Alternative scheint die Verwendung von Shadow DOM zu sein, da es einen eigenen Spielraum für Stile schafft. Auf jeden Fall eine potenzielle Verbesserung, an der ich in Zukunft gerne arbeiten würde.

Aus Gründen der Browserkompatibilität habe ich webextension-polyfill verwendet. Dadurch musste ich keine separaten Erweiterungen für verschiedene Manifestversionen schreiben. Mehr darüber, was es bewirkt, können Sie hier lesen. Damit es funktioniert, habe ich die Datei browser-polyfill.js vor anderen Skripten in die Manifestdateien eingefügt.

manifest.chrome.json:

{
  "content_scripts": [{
    "js": ["browser-polyfill.js"]
  }],
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

manifest.firefox.json:

{
  "background": {
    "scripts": [
      "browser-polyfill.js",
      // other scripts
    ],
  },
  "content_scripts": [{
    "js": [
      "browser-polyfill.js",
      // other scripts
    ]
  }],
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Der Prozess des Einfügens des Formulars ist eine einfache DOM-Manipulation. Beachten Sie jedoch, dass jedes Element einzeln erstellt werden muss, anstatt ein Vorlagenliteral auf ein Element anzuwenden. Obwohl ausführlicher und langwieriger, vermeidet diese Methode Warnungen wegen unsicherer HTML-Injektion, die wir andernfalls erhalten würden, wenn wir versuchen, den kompilierten Code im Browser auszuführen.

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);
}
Nach dem Login kopieren

Jetzt ist es an der Zeit, sicherzustellen, dass das Formular im Browser angezeigt wird. Um den erforderlichen Kompilierungsschritt durchzuführen, habe ich Webpack wie folgt konfiguriert:

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'"
  },
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Im Grunde nimmt es den Browsernamen aus der Umgebungsvariablen der Befehle, die ich ausführe, um zwischen zwei der Manifestdateien auszuwählen, und kompiliert den TypeScript-Code in das Verzeichnis dist/.

Ich wollte eigentlich richtige Tests für die Erweiterung schreiben, habe aber festgestellt, dass Puppeteer das Testen von Inhaltsskripten nicht unterstützt, sodass es unmöglich ist, die meisten Funktionen zu testen. Wenn Sie Problemumgehungen für das Testen von Inhaltsskripten kennen, würde ich sie gerne in den Kommentaren hören.

Meine Build-Befehle in package.json sind:

{
  "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'",
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Also zum Beispiel, wenn ich laufe

dist/
node_modules/
src/
|-- background.tsc
|-- content.ts
static/
|-- manifest.chrome.json
|-- manifest.firefox.json
package.json
tsconfig.json
webpack.config.js
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Die Dateien für Chrome werden im Verzeichnis dist/ kompiliert. Nach dem Auslösen eines Formulars auf einer beliebigen Website durch Klicken auf das Aktionssymbol oder Drücken der Verknüpfung sieht das Formular folgendermaßen aus:

Main form display

Umgang mit URL-Block

Da das Hauptformular nun fertig ist, besteht die nächste Aufgabe darin, es einzureichen. Um die Blockierungsfunktion zu implementieren, habe ich die declarativeNetRequest-API und dynamische Regeln genutzt. Die Regeln werden im Speicher der Erweiterung gespeichert. Das Bearbeiten dynamischer Regeln ist nur in der Service-Worker-Datei möglich. Um Daten zwischen dem Service-Worker und den Inhaltsskripten auszutauschen, sende ich zwischen ihnen Nachrichten mit den erforderlichen Daten. Da für diese Erweiterung eine ganze Reihe von Operationstypen erforderlich sind, habe ich für jede Aktion Typen erstellt. Hier ist ein Beispiel für einen Operationstyp:

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));
  }
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Da es sinnvoll ist, neue URLs sowohl vom Hauptformular als auch von der Optionsseite aus hinzufügen zu können, wurde die Übermittlung durch eine wiederverwendbare Funktion in einer neuen Datei ausgeführt:

helpers.ts:

{
  "content_scripts": [{
    "js": ["browser-polyfill.js"]
  }],
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Ich rufe handleFormSubmission() in content.ts auf, das die bereitgestellte URL validiert und sie dann an den Servicemitarbeiter sendet, um sie zur Blacklist hinzuzufügen.

Dynamische Regeln haben eine maximale Größe festgelegt, die berücksichtigt werden muss. Die Übergabe einer zu langen URL-Zeichenfolge führt zu unerwartetem Verhalten beim Versuch, die dynamische Regel dafür zu speichern. Ich habe herausgefunden, dass in meinem Fall eine 75 Zeichen lange URL eine gute maximale Länge für eine Regel war.

So wird der Servicemitarbeiter die empfangene Nachricht verarbeiten:

background.ts:

{
  "background": {
    "scripts": [
      "browser-polyfill.js",
      // other scripts
    ],
  },
  "content_scripts": [{
    "js": [
      "browser-polyfill.js",
      // other scripts
    ]
  }],
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Für die Übermittlung erstelle ich ein neues Regelobjekt und aktualisiere die dynamischen Regeln, um es einzuschließen. Mit einem einfachen bedingten regulären Ausdruck kann ich zwischen dem Blockieren der gesamten Domain oder nur der angegebenen URL wählen.

Nach Abschluss sende ich die Antwortnachricht an das Inhaltsskript zurück. Das Interessanteste an diesem Snippet ist die Verwendung von Nanoid. Durch Versuch und Irrtum habe ich herausgefunden, dass es eine Grenze für die Anzahl dynamischer Regeln gibt – 5.000 für ältere Browser und 30.000 für neuere. Ich habe das durch einen Fehler herausgefunden, als ich versuchte, einer Regel eine ID zuzuweisen, die größer als 5000 war. Ich konnte kein Limit für meine IDs auf unter 4999 erstellen, also musste ich meine IDs auf dreistellige Zahlen beschränken ( 0-999, also insgesamt 1000 eindeutige IDs). Das bedeutete, dass ich die Gesamtzahl der Regeln für meine Erweiterung von 5000 auf 1000 reduziert habe, was einerseits ziemlich wichtig ist, andererseits aber auch die Wahrscheinlichkeit, dass ein Benutzer so viele URLs zum Blockieren hatte, ziemlich gering war, und so habe ich Ich habe beschlossen, mich mit dieser nicht ganz so eleganten Lösung zufrieden zu geben.

Jetzt kann der Benutzer neue URLs zur Blacklist hinzufügen und den Blocktyp auswählen, den er ihnen zuweisen möchte. Wenn er versucht, auf eine blockierte Ressource zuzugreifen, wird er auf eine Blockierungsseite weitergeleitet:

Block page

Es gibt jedoch einen Randfall, der angegangen werden muss. Die Erweiterung blockiert alle unerwünschten URLs, wenn der Benutzer direkt darauf zugreift. Wenn es sich bei der Website jedoch um eine SPA mit clientseitiger Umleitung handelt, fängt die Erweiterung die dort verbotenen URLs nicht ab. Um diesen Fall zu lösen, habe ich meine Datei „background.ts“ aktualisiert, um den aktuellen Tab abzuhören und zu sehen, ob sich die URL geändert hat. Wenn es passiert, überprüfe ich manuell, ob die URL auf der Blacklist steht, und wenn ja, leite ich den Benutzer um.

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'"
  },
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

getRules() ist eine Funktion, die die Methode declarativeNetRequest.getDynamicRules() verwendet, um die Liste aller dynamischen Regeln abzurufen, die ich in ein besser lesbares Format konvertiere.

Jetzt blockiert die Erweiterung korrekt URLs, auf die direkt und über SPAs zugegriffen wird.

Optionsseite erstellen

Die Optionsseite verfügt über eine einfache Benutzeroberfläche, wie unten gezeigt:

Options page

Dies ist die Seite mit den meisten Funktionen wie Bearbeiten, Löschen, Deaktivieren und Anwenden des strengen Modus. So habe ich es verkabelt.

Funktionalität zum Bearbeiten und Löschen

Das Bearbeiten war wahrscheinlich die komplexeste Aufgabe. Benutzer können eine URL bearbeiten, indem sie ihre Zeichenfolge ändern oder ihren Blockierungstyp ändern (die gesamte Domain oder nur eine bestimmte blockieren). Beim Bearbeiten sammle ich die IDs der bearbeiteten URLs in einem Array. Beim Speichern erstelle ich aktualisierte dynamische Regeln, die ich an den Servicemitarbeiter übergebe, um Änderungen anzuwenden. Nach jeder gespeicherten Änderung oder jedem Neuladen rufe ich die dynamischen Regeln erneut ab und rendere sie in der Tabelle. Unten ist die vereinfachte Version davon:

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'"
  },
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Ich entscheide, ob eine bestimmte Regel blockiert oder zugelassen wird, indem ich einfach ihre isActive-Eigenschaft bedingt überprüfe. Die Regeln aktualisieren und die Regeln abrufen – das sind zwei weitere Vorgänge, die ich zu meinem Hintergrund-Listener hinzufügen kann:

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'",
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Es war etwas schwierig, die Aktualisierungsfunktion richtig hinzubekommen, da es einen Grenzfall gibt, wenn eine bearbeitete URL zu einem Duplikat einer vorhandenen Regel wird. Ansonsten ist es das gleiche Spiel – aktualisieren Sie die dynamischen Regeln und senden Sie nach Abschluss die entsprechende Nachricht.

URLs zu löschen war wahrscheinlich die einfachste Aufgabe. In dieser Erweiterung gibt es zwei Arten des Löschens: das Löschen einer bestimmten Regel und das Löschen aller Regeln.

options.ts:

dist/
node_modules/
src/
|-- background.tsc
|-- content.ts
static/
|-- manifest.chrome.json
|-- manifest.firefox.json
package.json
tsconfig.json
webpack.config.js
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Und wie zuvor habe ich dem Service-Worker-Listener zwei weitere Aktionen hinzugefügt:

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));
  }
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Implementierung des strikten Modus

Wahrscheinlich ist das Hauptmerkmal der Erweiterung die Möglichkeit, die Blockierung deaktivierter (Zugriffserlaubter) Regeln automatisch für Personen zu erzwingen, die eine strengere Kontrolle über ihre Surfgewohnheiten benötigen. Die Idee dahinter ist, dass bei Deaktivierung des strikten Modus jede vom Benutzer deaktivierte URL deaktiviert bleibt, bis der Benutzer sie ändert. Wenn der strikte Modus aktiviert ist, werden alle deaktivierten Regeln nach einer Stunde automatisch wieder aktiviert. Um eine solche Funktion zu implementieren, habe ich den lokalen Speicher der Erweiterung verwendet, um ein Array von Objekten zu speichern, die jede deaktivierte Regel darstellen. Jedes Objekt enthält eine Regel-ID, ein Entsperrdatum und die URL selbst. Jedes Mal, wenn ein Benutzer auf eine neue Ressource zugreift oder die Blacklist aktualisiert, überprüft die Erweiterung zunächst den Speicher auf abgelaufene Regeln und aktualisiert diese entsprechend.

options.ts:

{
  "content_scripts": [{
    "js": ["browser-polyfill.js"]
  }],
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

isStrictModeOn boolean wird ebenfalls im Speicher gespeichert. Wenn es wahr ist, durchlaufe ich alle Regeln und füge dem Speicher diejenigen hinzu, die deaktiviert sind, mit einer neu erstellten Entsperrungszeit für sie. Dann überprüfe ich bei jeder Antwort den Speicher auf deaktivierte Regeln, entferne die abgelaufenen Regeln, falls vorhanden, und aktualisiere sie:

background.ts:

{
  "background": {
    "scripts": [
      "browser-polyfill.js",
      // other scripts
    ],
  },
  "content_scripts": [{
    "js": [
      "browser-polyfill.js",
      // other scripts
    ]
  }],
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Damit ist die Website-Blockierungserweiterung abgeschlossen. Benutzer können beliebige URLs hinzufügen, bearbeiten, löschen und deaktivieren, teilweise oder ganze Domänensperren anwenden und den strikten Modus verwenden, um mehr Disziplin beim Surfen aufrechtzuerhalten.

Extension work example


Abschluss

Das ist der grundlegende Überblick über meine Erweiterung zum Blockieren von Websites. Es ist meine erste Erweiterung und es war eine interessante Erfahrung, insbesondere angesichts der Tatsache, dass die Welt der Webentwicklung manchmal alltäglich werden kann. Es gibt definitiv Raum für Verbesserungen und neue Funktionen. Suchleiste für URLs in der Blacklist, das Hinzufügen geeigneter Tests, die benutzerdefinierte Zeitdauer für den strikten Modus, die gleichzeitige Übermittlung mehrerer URLs – das sind nur einige Dinge, die mir durch den Kopf gehen und die ich eines Tages gerne zu diesem Projekt hinzufügen würde. Ich hatte ursprünglich auch geplant, die Erweiterung plattformübergreifend zu machen, konnte sie aber nicht auf meinem Telefon ausführen.
Wenn Ihnen die Lektüre dieser exemplarischen Vorgehensweise gefallen hat, Sie etwas Neues gelernt haben oder Sie sonstiges Feedback haben, freuen wir uns über Ihre Kommentare. Vielen Dank fürs Lesen.

Der Quellcode
Die Live-Version

Das obige ist der detaillierte Inhalt vonBrowserübergreifende Erweiterung zum Blockieren von Websites. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Quelle:dev.to
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Neueste Artikel des Autors
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage