Bericht overslaan

Omdat inhoudsscripts in de context van een webpagina worden uitgevoerd en niet in de extensie die ze uitvoert, hebben ze vaak manieren nodig om met de rest van de extensie te communiceren. Een RSS-lezerextensie kan bijvoorbeeld inhoudsscripts gebruiken om de aanwezigheid van een RSS-feed op een pagina te detecteren en vervolgens de servicemedewerker op de hoogte stellen om een ​​actiepictogram voor die pagina weer te geven.

Deze communicatie maakt gebruik van het doorgeven van berichten, waardoor zowel extensies als inhoudsscripts naar elkaars berichten kunnen luisteren en op hetzelfde kanaal kunnen reageren. Een bericht kan elk geldig JSON-object bevatten (null, boolean, number, string, array of object). Er zijn twee API's voor het doorgeven van berichten: één voor eenmalige verzoeken en een complexere API voor langlevende verbindingen waarmee meerdere berichten kunnen worden verzonden. Zie de sectie Berichten over meerdere extensies voor informatie over het verzenden van berichten tussen extensies.

Eenmalige aanvragen

Als u één bericht naar een ander deel van uw extensie wilt verzenden en optioneel een reactie wilt ontvangen, roept u runtime.sendMessage() of tabs.sendMessage() aan. Met deze methoden kunt u een eenmalig JSON-serialiseerbaar bericht verzenden van een inhoudsscript naar de extensie, of van de extensie naar een inhoudsscript. Gebruik de geretourneerde belofte om het antwoord af te handelen. Voor achterwaartse compatibiliteit met oudere extensies kunt u in plaats daarvan een callback als laatste argument doorgeven. U kunt niet tegelijkertijd een belofte en een terugbelactie gebruiken.

Zie de Manifest V3-migratiehandleiding voor informatie over het omzetten van callbacks in beloften en over het gebruik ervan in extensies.

Het verzenden van een verzoek vanuit een inhoudsscript ziet er als volgt uit:

inhoud-script.js:

(async () => {
  const response = await chrome.runtime.sendMessage({greeting: "hello"});
  // do something with response here, not outside the function
  console.log(response);
})();

Als u synchroon op een bericht wilt reageren, roept u gewoon sendResponse aan zodra u het antwoord heeft, en retourneert u false om aan te geven dat het klaar is. Om asynchroon te reageren, retourneert u true om de sendResponse callback actief te houden totdat u klaar bent om deze te gebruiken. Asynchrone functies worden niet ondersteund omdat ze een belofte retourneren, die niet wordt ondersteund.

Als u een verzoek naar een inhoudsscript wilt verzenden, geeft u op op welk tabblad het verzoek van toepassing is, zoals hieronder wordt weergegeven. Dit voorbeeld werkt in servicewerknemers, pop-ups en chrome-extension://-pagina's die als tabblad zijn geopend.

(async () => {
  const [tab] = await chrome.tabs.query({active: true, lastFocusedWindow: true});
  const response = await chrome.tabs.sendMessage(tab.id, {greeting: "hello"});
  // do something with response here, not outside the function
  console.log(response);
})();

Om het bericht te ontvangen, stelt u een runtime.onMessage -gebeurtenislistener in. Deze gebruiken dezelfde code in zowel extensies als inhoudsscripts:

content-script.js of service-worker.js:

chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    console.log(sender.tab ?
                "from a content script:" + sender.tab.url :
                "from the extension");
    if (request.greeting === "hello")
      sendResponse({farewell: "goodbye"});
  }
);

In het vorige voorbeeld werd sendResponse() synchroon aangeroepen. Om sendResponse() asynchroon te gebruiken, voegt u return true; naar de onMessage -gebeurtenishandler.

Als meerdere pagina's luisteren naar onMessage gebeurtenissen, zal alleen de eerste die sendResponse() aanroept voor een bepaalde gebeurtenis erin slagen het antwoord te verzenden. Alle andere reacties op die gebeurtenis worden genegeerd.

Verbindingen met een lange levensduur

Als u een herbruikbaar kanaal voor het doorgeven van berichten met een lange levensduur wilt maken, roept u runtime.connect() aan om berichten van een inhoudsscript door te geven aan een extensiepagina, of tabs.connect() om berichten van een extensiepagina door te geven aan een inhoudsscript. U kunt uw kanaal een naam geven om onderscheid te maken tussen verschillende soorten verbindingen.

Een mogelijke use case voor een langdurige verbinding is een automatische formulierinvulextensie. Het inhoudsscript kan voor een specifieke aanmelding een kanaal naar de extensiepagina openen en voor elk invoerelement op de pagina een bericht naar de extensie sturen met het verzoek om formuliergegevens in te vullen. Dankzij de gedeelde verbinding kan de extensie de status delen tussen extensies componenten.

Bij het tot stand brengen van een verbinding wordt aan elk uiteinde een runtime.Port -object toegewezen voor het verzenden en ontvangen van berichten via die verbinding.

Gebruik de volgende code om een ​​kanaal te openen vanuit een inhoudsscript en berichten te verzenden en te beluisteren:

inhoud-script.js:

var port = chrome.runtime.connect({name: "knockknock"});
port.postMessage({joke: "Knock knock"});
port.onMessage.addListener(function(msg) {
  if (msg.question === "Who's there?")
    port.postMessage({answer: "Madame"});
  else if (msg.question === "Madame who?")
    port.postMessage({answer: "Madame... Bovary"});
});

Als u een verzoek van de extensie naar een inhoudsscript wilt verzenden, vervangt u de aanroep van runtime.connect() in het vorige voorbeeld door tabs.connect() .

Om inkomende verbindingen voor een inhoudsscript of een extensiepagina af te handelen, stelt u een runtime.onConnect -gebeurtenislistener in. Wanneer een ander deel van uw extensie connect() aanroept, worden deze gebeurtenis en het runtime.Port object geactiveerd. De code voor het reageren op inkomende verbindingen ziet er als volgt uit:

service-werker.js:

chrome.runtime.onConnect.addListener(function(port) {
  console.assert(port.name === "knockknock");
  port.onMessage.addListener(function(msg) {
    if (msg.joke === "Knock knock")
      port.postMessage({question: "Who's there?"});
    else if (msg.answer === "Madame")
      port.postMessage({question: "Madame who?"});
    else if (msg.answer === "Madame... Bovary")
      port.postMessage({question: "I don't get it."});
  });
});

Levensduur van de haven

Poorten zijn ontworpen als tweerichtingscommunicatiemethode tussen verschillende delen van de extensie. Een frame op het hoogste niveau is het kleinste onderdeel van een uitbreiding dat een poort kan gebruiken. Wanneer een deel van een extensie tabs.connect() , runtime.connect() of runtime.connectNative() aanroept, wordt er een poort gemaakt die onmiddellijk berichten kan verzenden met behulp van postMessage() .

Als er meerdere frames op een tabblad staan, wordt door het aanroepen van tabs.connect() de gebeurtenis runtime.onConnect één keer aangeroepen voor elk frame op het tabblad. Op dezelfde manier kan, als runtime.connect() wordt aangeroepen, de onConnect -gebeurtenis één keer worden geactiveerd voor elk frame in het uitbreidingsproces.

Mogelijk wilt u weten wanneer een verbinding is gesloten, bijvoorbeeld of u voor elke open poort een afzonderlijke status handhaaft. Luister hiervoor naar de gebeurtenis runtime.Port.onDisconnect . Deze gebeurtenis wordt geactiveerd wanneer er geen geldige poorten zijn aan de andere kant van het kanaal. Dit kan een van de volgende oorzaken hebben:

  • Er zijn geen luisteraars voor runtime.onConnect aan de andere kant.
  • Het tabblad met de poort wordt verwijderd (bijvoorbeeld als er op het tabblad wordt genavigeerd).
  • Het frame waarin connect() werd aangeroepen, is verwijderd.
  • Alle frames die de poort hebben ontvangen (via runtime.onConnect ) zijn gelost.
  • runtime.Port.disconnect() wordt aangeroepen door het andere uiteinde . Als een connect() -aanroep resulteert in meerdere poorten aan de kant van de ontvanger, en disconnect() wordt aangeroepen op een van deze poorten, dan wordt de onDisconnect -gebeurtenis alleen geactiveerd op de verzendende poort, niet op de andere poorten.

Berichten over meerdere extensies

Naast het verzenden van berichten tussen verschillende componenten in uw extensie, kunt u de berichten-API gebruiken om met andere extensies te communiceren. Hiermee kunt u een openbare API beschikbaar stellen die andere extensies kunnen gebruiken.

Als u wilt luisteren naar inkomende verzoeken en verbindingen van andere extensies, gebruikt u de methoden runtime.onMessageExternal of runtime.onConnectExternal . Hier is een voorbeeld van elk:

service-werker.js

// For a single request:
chrome.runtime.onMessageExternal.addListener(
  function(request, sender, sendResponse) {
    if (sender.id === blocklistedExtension)
      return;  // don't allow this extension access
    else if (request.getTargetData)
      sendResponse({targetData: targetData});
    else if (request.activateLasers) {
      var success = activateLasers();
      sendResponse({activateLasers: success});
    }
  });

// For long-lived connections:
chrome.runtime.onConnectExternal.addListener(function(port) {
  port.onMessage.addListener(function(msg) {
    // See other examples for sample onMessage handlers.
  });
});

Om een ​​bericht naar een ander toestel te sturen, geeft u als volgt de ID door van het toestel waarmee u wilt communiceren:

service-werker.js

// The ID of the extension we want to talk to.
var laserExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";

// For a simple request:
chrome.runtime.sendMessage(laserExtensionId, {getTargetData: true},
  function(response) {
    if (targetInRange(response.targetData))
      chrome.runtime.sendMessage(laserExtensionId, {activateLasers: true});
  }
);

// For a long-lived connection:
var port = chrome.runtime.connect(laserExtensionId);
port.postMessage(...);

Berichten verzenden vanaf webpagina's

Extensies kunnen ook berichten van andere webpagina's ontvangen en erop reageren, maar kunnen geen berichten naar webpagina's verzenden. Als u berichten van een webpagina naar een extensie wilt verzenden, geeft u in uw manifest.json op met welke websites u wilt communiceren met behulp van de manifestsleutel "externally_connectable" . Bijvoorbeeld:

manifest.json

"externally_connectable": {
  "matches": ["https://*.example.com/*"]
}

Hierdoor wordt de berichten-API zichtbaar voor elke pagina die overeenkomt met de URL-patronen die u opgeeft. Het URL-patroon moet minimaal een domein op het tweede niveau bevatten; dat wil zeggen dat hostnaampatronen zoals "*", "*.com", "*.co.uk" en "*.appspot.com" niet worden ondersteund. Vanaf Chrome 107 kunt u <all_urls> gebruiken om toegang te krijgen tot alle domeinen. Houd er rekening mee dat, omdat het van invloed is op alle hosts, beoordelingen in de Chrome-webwinkel voor extensies die er gebruik van maken langer kunnen duren .

Gebruik de API's runtime.sendMessage() of runtime.connect() om een ​​bericht naar een specifieke app of extensie te sturen. Bijvoorbeeld:

webpagina.js

// The ID of the extension we want to talk to.
var editorExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";

// Make a simple request:
chrome.runtime.sendMessage(editorExtensionId, {openUrlInEditor: url},
  function(response) {
    if (!response.success)
      handleError(url);
  });

Luister vanaf uw extensie naar berichten van webpagina's met behulp van de runtime.onMessageExternal of runtime.onConnectExternal API's, zoals bij berichten over meerdere extensies . Hier is een voorbeeld:

service-werker.js

chrome.runtime.onMessageExternal.addListener(
  function(request, sender, sendResponse) {
    if (sender.url === blocklistedWebsite)
      return;  // don't allow this web page access
    if (request.openUrlInEditor)
      openUrl(request.openUrlInEditor);
  });

Native berichten

Extensies kunnen berichten uitwisselen met native applicaties die zijn geregistreerd als native messaging host . Zie Native messaging voor meer informatie over deze functie.

Beveiligingsoverwegingen

Hier volgen enkele beveiligingsoverwegingen met betrekking tot berichtenuitwisseling.

Inhoudsscripts zijn minder betrouwbaar

Inhoudsscripts zijn minder betrouwbaar dan de medewerker van de extensieservice. Een kwaadwillende webpagina kan bijvoorbeeld het weergaveproces dat de inhoudsscripts uitvoert, in gevaar brengen. Ga ervan uit dat berichten uit een inhoudsscript mogelijk door een aanvaller zijn gemaakt en zorg ervoor dat u alle invoer valideert en opschoont . Stel dat gegevens die naar het inhoudsscript worden verzonden, naar de webpagina kunnen lekken. Beperk de reikwijdte van bevoorrechte acties die kunnen worden geactiveerd door berichten die worden ontvangen van inhoudsscripts.

Cross-site scripting

Zorg ervoor dat u uw scripts beschermt tegen cross-site scripting . Wanneer u gegevens ontvangt van een niet-vertrouwde bron, zoals gebruikersinvoer, andere websites via een inhoudsscript of een API, zorg er dan voor dat u dit niet als HTML interpreteert of op een manier gebruikt waardoor onverwachte code kan worden uitgevoerd.

Veiligere methoden

Gebruik waar mogelijk API's die geen scripts uitvoeren:

service-werker.js

chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
  // JSON.parse doesn't evaluate the attacker's scripts.
  var resp = JSON.parse(response.farewell);
});

service-werker.js

chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
  // innerText does not let the attacker inject HTML elements.
  document.getElementById("resp").innerText = response.farewell;
});
Onveilige methoden

Vermijd het gebruik van de volgende methoden die uw extensie kwetsbaar maken:

service-werker.js

chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
  // WARNING! Might be evaluating a malicious script!
  var resp = eval(`(${response.farewell})`);
});

service-werker.js

chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
  // WARNING! Might be injecting a malicious script!
  document.getElementById("resp").innerHTML = response.farewell;
});