Omdat contentscripts worden uitgevoerd in de context van een webpagina en niet van de extensie, hebben ze vaak een manier nodig om met de rest van de extensie te communiceren. Een RSS-reader-extensie kan bijvoorbeeld contentscripts gebruiken om de aanwezigheid van een RSS-feed op een pagina te detecteren en vervolgens de achtergrondpagina op de hoogte te stellen om een actie-icoon voor die pagina weer te geven.
Communicatie tussen extensies en hun contentscripts werkt via berichtuitwisseling. Beide kanten kunnen luisteren naar berichten die van de andere kant worden verzonden en reageren via hetzelfde kanaal. Een bericht kan elk geldig JSON-object bevatten (null, boolean, getal, tekenreeks, array of object). Er is een eenvoudige API voor eenmalige verzoeken en een complexere API waarmee je langdurige verbindingen kunt opzetten voor het uitwisselen van meerdere berichten met een gedeelde context. Het is ook mogelijk om een bericht naar een andere extensie te sturen als je de ID ervan kent, zoals beschreven in het gedeelte over berichtenuitwisseling tussen extensies .
Eenvoudige eenmalige verzoeken
Als je slechts één bericht naar een ander onderdeel van je extensie hoeft te sturen (en eventueel een reactie terug wilt ontvangen), kun je het beste de vereenvoudigde `runtime.sendMessage` of `tabs.sendMessage` gebruiken. Hiermee kun je een eenmalig JSON-serialiseerbaar bericht vanuit een contentscript naar de extensie sturen, of andersom. Een optionele callback-parameter stelt je in staat om de reactie van de andere kant af te handelen, indien aanwezig.
Het versturen van een verzoek vanuit een content-script ziet er als volgt uit:
chrome.runtime.sendMessage({greeting: "hello"}, function(response) {
console.log(response.farewell);
});
Het versturen van een verzoek vanuit de extensie naar een contentscript werkt vrijwel hetzelfde, met als enige verschil dat je moet specificeren naar welk tabblad het verzoek moet worden verzonden. Dit voorbeeld laat zien hoe je een bericht naar het contentscript in het geselecteerde tabblad stuurt.
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {greeting: "hello"}, function(response) {
console.log(response.farewell);
});
});
Aan de ontvangende kant moet je een `runtime.onMessage` gebeurtenislistener instellen om het bericht af te handelen. Dit ziet er hetzelfde uit vanuit een contentscript of een extensiepagina.
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 bovenstaande voorbeeld werd sendResponse synchroon aangeroepen. Als u sendResponse asynchroon wilt gebruiken, voeg dan return true; toe aan de onMessage gebeurtenishandler.
sendResponse callback is alleen geldig als deze synchroon wordt gebruikt, of als de gebeurtenishandler true retourneert om aan te geven dat deze asynchroon zal reageren. De callback van de sendMessage functie wordt automatisch aangeroepen als geen enkele handler `true` retourneert of als de sendResponse callback door de garbage collector wordt verwijderd.Langdurige verbindingen
Soms is het handig om een gesprek te voeren dat langer duurt dan een enkele vraag en reactie. In dat geval kunt u een langdurig kanaal openen vanuit uw contentscript naar een extensiepagina, of andersom, met respectievelijk `runtime.connect` of `tabs.connect` . Het kanaal kan optioneel een naam hebben, zodat u onderscheid kunt maken tussen verschillende soorten verbindingen.
Een mogelijk gebruiksscenario is een extensie voor het automatisch invullen van formulieren. Het contentscript zou een verbinding kunnen openen met de extensiepagina voor een specifieke login en voor elk invoerelement op de pagina een bericht naar de extensie sturen om de formuliergegevens op te vragen. De gedeelde verbinding zorgt ervoor dat de extensie een gedeelde status behoudt die de verschillende berichten van het contentscript met elkaar verbindt.
Bij het tot stand brengen van een verbinding krijgt elk uiteinde een runtime.Port -object toegewezen, dat wordt gebruikt voor het verzenden en ontvangen van berichten via die verbinding.
Zo open je een kanaal vanuit een contentscript en verstuur en ontvang je berichten:
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"});
});
Het versturen van een verzoek vanuit de extensie naar een contentscript werkt vrijwel hetzelfde, met als enige verschil dat je moet specificeren met welk tabblad je verbinding wilt maken. Vervang in het bovenstaande voorbeeld de aanroep naar `connect` door `tabs.connect` .
Om inkomende verbindingen af te handelen, moet u een `runtime.onConnect` gebeurtenislistener instellen. Dit ziet er hetzelfde uit vanuit een contentscript of een extensiepagina. Wanneer een ander onderdeel van uw extensie `connect()` aanroept, wordt deze gebeurtenis geactiveerd, samen met het `runtime.Port` object dat u kunt gebruiken om berichten via de verbinding te verzenden en te ontvangen. Zo ziet het eruit om te reageren op inkomende verbindingen:
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 een tweewegcommunicatiemethode tussen verschillende onderdelen van de extensie, waarbij een (top-level) frame wordt beschouwd als het kleinste onderdeel. Bij het aanroepen van tabs.connect , runtime.connect of runtime.connectNative wordt een poort aangemaakt. Deze poort kan direct worden gebruikt om berichten naar de andere kant te verzenden via postMessage .
Als een tabblad meerdere frames bevat, resulteert het aanroepen van `tabs.connect` in meerdere aanroepen van de `runtime.onConnect` -gebeurtenis (één keer voor elk frame in het tabblad). Op dezelfde manier kan de `onConnect`-gebeurtenis meerdere keren worden geactiveerd als `runtime.connect` wordt gebruikt (één keer voor elk frame in het extensieproces).
U wilt wellicht weten wanneer een verbinding wordt verbroken, bijvoorbeeld als u voor elke open poort een aparte status bijhoudt. Hiervoor kunt u luisteren naar de runtime.Port.onDisconnect -gebeurtenis. Deze gebeurtenis wordt geactiveerd wanneer er geen geldige poorten meer beschikbaar zijn aan de andere kant van het kanaal. Dit gebeurt in de volgende situaties:
- Er zijn geen listeners voor runtime.onConnect aan de andere kant.
- Het tabblad met de poort wordt gesloten (bijvoorbeeld als er naar een ander tabblad wordt genavigeerd).
- Het frame van waaruit
connectwerd gedaan, is ontladen. - Alle frames die de poort hebben ontvangen (via runtime.onConnect ) zijn ontladen.
- runtime.Port.disconnect wordt aangeroepen door de andere partij . Merk op dat als een
connectaanroep resulteert in meerdere poorten aan de ontvangende kant, endisconnect()wordt aangeroepen op een van deze poorten, deonDisconnect-gebeurtenis alleen wordt geactiveerd op de poort van de zender en niet op de andere poorten.
Berichtenverkeer tussen verschillende extensies
Naast het versturen van berichten tussen verschillende componenten in je extensie, kun je de berichten-API ook gebruiken om met andere extensies te communiceren. Hierdoor kun je een openbare API beschikbaar stellen waar andere extensies gebruik van kunnen maken.
Het luisteren naar inkomende verzoeken en verbindingen is vergelijkbaar met het interne geval, met als enige verschil dat je de methoden `runtime.onMessageExternal` of `runtime.onConnectExternal` gebruikt. Hier is een voorbeeld van beide:
// For simple requests:
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.
});
});
Het versturen van een bericht naar een andere extensie werkt op dezelfde manier als het versturen van een bericht binnen je eigen extensie. Het enige verschil is dat je de ID van de extensie waarmee je wilt communiceren moet doorgeven. Bijvoorbeeld:
// The ID of the extension we want to talk to.
var laserExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";
// Make a simple request:
chrome.runtime.sendMessage(laserExtensionId, {getTargetData: true},
function(response) {
if (targetInRange(response.targetData))
chrome.runtime.sendMessage(laserExtensionId, {activateLasers: true});
}
);
// Start a long-running conversation:
var port = chrome.runtime.connect(laserExtensionId);
port.postMessage(...);
Berichten verzenden vanaf webpagina's
Net als bij berichtenuitwisseling tussen extensies , kan uw app of extensie berichten ontvangen en beantwoorden van gewone webpagina's. Om deze functie te gebruiken, moet u eerst in uw manifest.json specificeren met welke websites u wilt communiceren. Bijvoorbeeld:
"externally_connectable": {
"matches": ["*://*.example.com/*"]
}
Hiermee wordt de berichten-API beschikbaar gesteld aan elke pagina die overeenkomt met de URL-patronen die u opgeeft. Het URL-patroon moet ten minste een domein op het tweede niveau bevatten; hostnaampatronen zoals "*", "*.com", "*.co.uk" en "*.appspot.com" zijn dus niet toegestaan. Gebruik vanaf de webpagina de API's `runtime.sendMessage` of `runtime.connect` om een bericht naar een specifieke app of extensie te verzenden. Bijvoorbeeld:
// 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);
});
Vanuit uw app of extensie kunt u berichten van webpagina's ontvangen via de API's `runtime.onMessageExternal` of `runtime.onConnectExternal` , vergelijkbaar met berichtenuitwisseling tussen extensies . Alleen de webpagina kan een verbinding initiëren. Hier is een voorbeeld:
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 en apps kunnen berichten uitwisselen met native applicaties die geregistreerd staan als native messaging host . Zie Native messaging voor meer informatie over deze functie.
Veiligheidsaspecten
Scripts voor content zijn minder betrouwbaar.
Inhoudsscripts zijn minder betrouwbaar dan de achtergrondpagina van de extensie (een kwaadwillende webpagina kan bijvoorbeeld het renderproces waarin de inhoudsscripts worden uitgevoerd, compromitteren). Ga ervan uit dat berichten van een inhoudsscript mogelijk door een aanvaller zijn opgesteld en zorg ervoor dat alle invoer wordt gevalideerd en gezuiverd . Ga ervan uit dat alle gegevens die naar het inhoudsscript worden verzonden, naar de webpagina kunnen lekken. Beperk de reikwijdte van geprivilegieerde acties die kunnen worden geactiveerd door berichten die van inhoudsscripts worden ontvangen.
Cross-site scripting
Wanneer uw scripts een bericht ontvangen van een contentscript of een andere extensie, moeten ze ervoor zorgen dat ze geen slachtoffer worden van cross-site scripting . Dit advies geldt zowel voor scripts die binnen de achtergrondpagina van de extensie draaien als voor contentscripts die binnen andere weborigins draaien. Vermijd met name het gebruik van gevaarlijke API's zoals de onderstaande:
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
// WARNING! Might be evaluating an evil script!
var resp = eval("(" + response.farewell + ")");
});
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
// WARNING! Might be injecting a malicious script!
document.getElementById("resp").innerHTML = response.farewell;
});
Geef in plaats daarvan de voorkeur aan veiligere API's die geen scripts uitvoeren:
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
// JSON.parse does not evaluate the attacker's scripts.
var resp = JSON.parse(response.farewell);
});
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
// innerText does not let the attacker inject HTML elements.
document.getElementById("resp").innerText = response.farewell;
});
Voorbeelden
Eenvoudige voorbeelden van communicatie via berichten vindt u in de map examples/api/messaging . Het native messaging-voorbeeld laat zien hoe een Chrome-app kan communiceren met een native app. Zie Voorbeelden voor meer voorbeelden en hulp bij het bekijken van de broncode.