Ich lerne, wie man Chrome-Erweiterungen erstellt. Ich habe gerade angefangen, eine zu entwickeln, um YouTube-Events zu verfolgen. Ich möchte es mit YouTube Flash Player verwenden (später werde ich versuchen, es mit HTML5 kompatibel zu machen).
manifest.json:
{
"name": "MyExtension",
"version": "1.0",
"description": "Gotta catch Youtube events!",
"permissions": ["tabs", "http://*/*"],
"content_scripts" : [{
"matches" : [ "www.youtube.com/*"],
"js" : ["myScript.js"]
}]
}
myScript.js:
function state() { console.log("State Changed!"); }
var player = document.getElementById("movie_player");
player.addEventListener("onStateChange", "state");
console.log("Started!");
Das Problem ist, dass die Konsole mir das gibt “Gestartet!”aber es gibt keinen “Zustand geändert!” wenn ich YouTube-Videos abspiele/pausiere.
Wenn dieser Code in die Konsole eingegeben wird, hat es funktioniert. Was mache ich falsch?
Ursache:
Inhaltsskripte werden in einem ausgeführt “isolierte Welt” Umgebung.
Lösung::
Zu betreten Funktionen/Variablen des Seitenkontexts (“Hauptwelt”) müssen Sie den Code mit DOM in die Seite selbst einfügen. Dasselbe, wenn Sie möchten exponieren Ihre Funktionen/Variablen zum Seitenkontext (in Ihrem Fall ist es die state()
Methode).
-
Hinweis für den Fall, dass eine Kommunikation mit dem Seitenskript erforderlich ist:
Verwenden Sie DOM CustomEvent
Handler. Beispiele: eins, zwei und drei.
-
Hinweis für den Fall chrome
API wird im Seitenskript benötigt:
Seit chrome.*
APIs können nicht im Seitenskript verwendet werden, Sie müssen sie im Inhaltsskript verwenden und die Ergebnisse per DOM-Messaging an das Seitenskript senden (siehe Hinweis oben).
Sicherheitswarnung:
Eine Seite kann einen integrierten Prototyp neu definieren oder erweitern/einhängen, sodass Ihr offengelegter Code möglicherweise fehlschlägt, wenn die Seite dies auf inkompatible Weise getan hat. Wenn Sie sicherstellen möchten, dass Ihr exponierter Code in einer sicheren Umgebung ausgeführt wird, sollten Sie entweder a) Ihr Inhaltsskript mit deklarieren “run_at”: “document_start” und verwenden Sie die Methoden 2-3 und nicht 1, oder b) extrahieren Sie die ursprünglichen nativen integrierten Funktionen über ein leeres Iframe, Beispiel. Beachte das mit document_start
müssen Sie möglicherweise verwenden DOMContentLoaded
Ereignis innerhalb des exponierten Codes, um auf DOM zu warten.
Inhaltsverzeichnis
- Methode 1: Fügen Sie eine andere Datei ein – kompatibel mit ManifestV3
- Methode 2: Eingebetteten Code einfügen
- Methode 2b: Verwenden einer Funktion
- Methode 3: Verwenden eines Inline-Ereignisses
- Dynamische Werte im eingefügten Code
Methode 1: Injizieren Sie eine andere Datei
Die derzeit einzige ManifestV3-kompatible Methode. Besonders gut, wenn Sie viel Code haben. Legen Sie den Code beispielsweise in einer Datei innerhalb Ihrer Erweiterung ab script.js
. Laden Sie es dann wie folgt in Ihr Inhaltsskript:
var s = document.createElement('script');
s.src = chrome.runtime.getURL('script.js');
s.onload = function() {
this.remove();
};
(document.head || document.documentElement).appendChild(s);
Die js-Datei muss in verfügbar gemacht werden web_accessible_resources
:
-
manifest.json-Beispiel für ManifestV2
"web_accessible_resources": ["script.js"],
-
manifest.json-Beispiel für ManifestV3
"web_accessible_resources": [{
"resources": ["script.js"],
"matches": ["<all_urls>"]
}]
Wenn nicht, erscheint der folgende Fehler in der Konsole:
Laden von chrome-extension:// verweigern[EXTENSIONID]/script.js. Ressourcen müssen im Manifestschlüssel web_accessible_resources aufgeführt sein, damit sie von Seiten außerhalb der Erweiterung geladen werden können.
Methode 2: Eingebetteten Code einfügen
Diese Methode ist nützlich, wenn Sie schnell einen kleinen Codeabschnitt ausführen möchten. (Siehe auch: Wie deaktiviere ich Facebook-Hotkeys mit der Chrome-Erweiterung?).
var actualCode = `// Code here.
// If you want to use a variable, use $ and curly braces.
// For example, to use a fixed random number:
var someFixedRandomValue = ${ Math.random() };
// NOTE: Do not insert unsafe variables in this way, see below
// at "Dynamic values in the injected code"
`;
var script = document.createElement('script');
script.textContent = actualCode;
(document.head||document.documentElement).appendChild(script);
script.remove();
Notiz: Template-Literale werden nur in Chrome 41 und höher unterstützt. Wenn Sie möchten, dass die Erweiterung in Chrome 40- funktioniert, verwenden Sie:
var actualCode = ['/* Code here. Example: */' + 'alert(0);',
'// Beware! This array have to be joined',
'// using a newline. Otherwise, missing semicolons',
'// or single-line comments (//) will mess up your',
'// code ----->'].join('\n');
Methode 2b: Verwenden einer Funktion
Bei einem großen Codeblock ist es nicht möglich, die Zeichenfolge in Anführungszeichen zu setzen. Anstatt ein Array zu verwenden, kann eine Funktion verwendet und gestringt werden:
var actualCode="(" + function() {
// All code is executed in a local scope.
// For example, the following does NOT overwrite the global `alert` method
var alert = null;
// To overwrite a global variable, prefix `window`:
window.alert = null;
} + ')();';
var script = document.createElement('script');
script.textContent = actualCode;
(document.head||document.documentElement).appendChild(script);
script.remove();
Diese Methode funktioniert, weil die +
Operator auf Strings und eine Funktion wandelt alle Objekte in einen String um. Wenn Sie beabsichtigen, den Code mehr als einmal zu verwenden, ist es ratsam, eine Funktion zu erstellen, um Codewiederholungen zu vermeiden. Eine Implementierung könnte wie folgt aussehen:
function injectScript(func) {
var actualCode="(" + func + ')();'
...
}
injectScript(function() {
alert("Injected script");
});
Hinweis: Da die Funktion serialisiert wird, gehen der ursprüngliche Gültigkeitsbereich und alle gebundenen Eigenschaften verloren!
var scriptToInject = function() {
console.log(typeof scriptToInject);
};
injectScript(scriptToInject);
// Console output: "undefined"
Methode 3: Verwenden eines Inline-Ereignisses
Manchmal möchten Sie Code sofort ausführen, z. B. um Code vor dem auszuführen <head>
Element erstellt wird. Dies kann durch Einfügen von a erfolgen <script>
tag mit textContent
(siehe Methode 2/2b).
Eine Alternative, aber nicht empfehlenswert ist die Verwendung von Inline-Ereignissen. Es wird nicht empfohlen, denn wenn die Seite eine Inhaltssicherheitsrichtlinie definiert, die Inline-Skripts verbietet, werden Inline-Ereignis-Listener blockiert. Von der Erweiterung eingefügte Inline-Skripte werden hingegen weiterhin ausgeführt. Wenn Sie trotzdem Inline-Ereignisse verwenden möchten, gehen Sie wie folgt vor:
var actualCode="// Some code example \n" +
'console.log(document.documentElement.outerHTML);';
document.documentElement.setAttribute('onreset', actualCode);
document.documentElement.dispatchEvent(new CustomEvent('reset'));
document.documentElement.removeAttribute('onreset');
Hinweis: Diese Methode geht davon aus, dass es keine anderen globalen Ereignis-Listener gibt, die die behandeln reset
Veranstaltung. Wenn ja, können Sie auch eines der anderen globalen Ereignisse auswählen. Öffnen Sie einfach die JavaScript-Konsole (F12), geben Sie ein document.documentElement.on
und wählen Sie eines der verfügbaren Ereignisse aus.
Dynamische Werte im eingefügten Code
Gelegentlich müssen Sie eine beliebige Variable an die eingefügte Funktion übergeben. Zum Beispiel:
var GREETING = "Hi, I'm ";
var NAME = "Rob";
var scriptToInject = function() {
alert(GREETING + NAME);
};
Um diesen Code einzufügen, müssen Sie die Variablen als Argumente an die anonyme Funktion übergeben. Achten Sie auf eine korrekte Umsetzung! Folgendes wird nicht Arbeit:
var scriptToInject = function (GREETING, NAME) { ... };
var actualCode="(" + scriptToInject + ')(' + GREETING + ',' + NAME + ')';
// The previous will work for numbers and booleans, but not strings.
// To see why, have a look at the resulting string:
var actualCode = "(function(GREETING, NAME) {...})(Hi, I'm ,Rob)";
// ^^^^^^^^ ^^^ No string literals!
Die Lösung ist zu verwenden JSON.stringify
bevor Sie das Argument übergeben. Beispiel:
var actualCode="(" + function(greeting, name) { ...
} + ')(' + JSON.stringify(GREETING) + ',' + JSON.stringify(NAME) + ')';
Wenn Sie viele Variablen haben, lohnt es sich, sie zu verwenden JSON.stringify
einmal, um die Lesbarkeit zu verbessern, wie folgt:
...
} + ')(' + JSON.stringify([arg1, arg2, arg3, arg4]) + ')';
Das einzige fehlen Aus Rob Ws ausgezeichneter Antwort ist verborgen, wie zwischen dem eingefügten Seitenskript und dem Inhaltsskript kommuniziert wird.
Fügen Sie auf der Empfängerseite (entweder Ihr Inhaltsskript oder das eingefügte Seitenskript) einen Ereignis-Listener hinzu:
document.addEventListener('yourCustomEvent', function (e) {
var data = e.detail;
console.log('received', data);
});
Senden Sie auf der Initiatorseite (Inhaltsskript oder eingefügtes Seitenskript) das Ereignis:
var data = {
allowedTypes: 'those supported by structured cloning, see the list below',
inShort: 'no DOM elements or classes/functions',
};
document.dispatchEvent(new CustomEvent('yourCustomEvent', { detail: data }));
Anmerkungen:
- DOM-Messaging verwendet einen strukturierten Klonalgorithmus, der nur übertragen kann einige Arten von Daten zusätzlich zu primitiven Werten. Es kann keine Klasseninstanzen oder Funktionen oder DOM-Elemente senden.
-
Um in Firefox ein Objekt (dh keinen primitiven Wert) vom Inhaltsskript an den Seitenkontext zu senden, müssen Sie es explizit mit in das Ziel klonen cloneInto
(eine integrierte Funktion), andernfalls schlägt sie mit einem Sicherheitsverletzungsfehler fehl.
document.dispatchEvent(new CustomEvent('yourCustomEvent', {
detail: cloneInto(data, document.defaultView),
}));
Ich bin auch auf das Problem der Reihenfolge geladener Skripte gestoßen, das durch sequentielles Laden von Skripten gelöst wurde. Das Laden basiert auf der Antwort von Rob W.
function scriptFromFile(file) {
var script = document.createElement("script");
script.src = chrome.extension.getURL(file);
return script;
}
function scriptFromSource(source) {
var script = document.createElement("script");
script.textContent = source;
return script;
}
function inject(scripts) {
if (scripts.length === 0)
return;
var otherScripts = scripts.slice(1);
var script = scripts[0];
var onload = function() {
script.parentNode.removeChild(script);
inject(otherScripts);
};
if (script.src != "") {
script.onload = onload;
document.head.appendChild(script);
} else {
document.head.appendChild(script);
onload();
}
}
Das Verwendungsbeispiel wäre:
var formulaImageUrl = chrome.extension.getURL("formula.png");
var codeImageUrl = chrome.extension.getURL("code.png");
inject([
scriptFromSource("var formulaImageUrl="" + formulaImageUrl + "";"),
scriptFromSource("var codeImageUrl="" + codeImageUrl + "";"),
scriptFromFile("EqEditor/eq_editor-lite-17.js"),
scriptFromFile("EqEditor/eq_config.js"),
scriptFromFile("highlight/highlight.pack.js"),
scriptFromFile("injected.js")
]);
Eigentlich bin ich ziemlich neu bei JS, also fühlen Sie sich frei, mich auf die besseren Wege zu bringen.
im Inhaltsskript füge ich dem Kopf ein Skript-Tag hinzu, das einen ‘onmessage’-Handler bindet, innerhalb des Handlers verwende ich eval, um Code auszuführen. Im Booth-Inhaltsskript verwende ich auch den Onmessage-Handler, sodass ich eine Zwei-Wege-Kommunikation erhalte.
Chrome-Dokumente
//Content Script
var pmsgUrl = chrome.extension.getURL('pmListener.js');
$("head").first().append("<script src=""+pmsgUrl+"" type="text/javascript"></script>");
//Listening to messages from DOM
window.addEventListener("message", function(event) {
console.log('CS :: message in from DOM', event);
if(event.data.hasOwnProperty('cmdClient')) {
var obj = JSON.parse(event.data.cmdClient);
DoSomthingInContentScript(obj);
}
});
pmListener.js ist ein URL-Listener für Post-Nachrichten
//pmListener.js
//Listen to messages from Content Script and Execute Them
window.addEventListener("message", function (msg) {
console.log("im in REAL DOM");
if (msg.data.cmnd) {
eval(msg.data.cmnd);
}
});
console.log("injected To Real Dom");
Auf diese Weise kann ich eine 2-Wege-Kommunikation zwischen CS und Real Dom haben. Es ist zum Beispiel sehr nützlich, wenn Sie Webscoket-Ereignisse oder alle im Speicher befindlichen Variablen oder Ereignisse abhören müssen.
Sie können eine von mir erstellte Hilfsfunktion verwenden, um Code im Seitenkontext auszuführen und den zurückgegebenen Wert zurückzuerhalten.
Dazu wird eine Funktion in einen String serialisiert und in die Webseite eingefügt.
Der Nutzen ist hier auf GitHub verfügbar.
Anwendungsbeispiele –
// Some code that exists only in the page context -
window.someProperty = 'property';
function someFunction(name="test") {
return new Promise(res => setTimeout(()=>res('resolved ' + name), 1200));
}
/////////////////
// Content script examples -
await runInPageContext(() => someProperty); // returns 'property'
await runInPageContext(() => someFunction()); // returns 'resolved test'
await runInPageContext(async (name) => someFunction(name), 'with name' ); // 'resolved with name'
await runInPageContext(async (...args) => someFunction(...args), 'with spread operator and rest parameters' ); // returns 'resolved with spread operator and rest parameters'
await runInPageContext({
func: (name) => someFunction(name),
args: ['with params object'],
doc: document,
timeout: 10000
} ); // returns 'resolved with params object'
Versuchen Sie, die Anführungszeichen um Ihren Funktionsnamen zu entfernen:
player.addEventListener("onStateChange", state);
– Eduardo
1. März 2012 um 17:13 Uhr
Es ist auch bemerkenswert, dass Sie beim Schreiben von Übereinstimmungen nicht vergessen, diese einzuschließen
https://
oderhttp://
Daswww.youtube.com/*
würde dich nicht erweiterung packen lassen und werfen Fehler wegen fehlendem Schema-Trennzeichen– Nilay Vishwakarma
31. Januar 2017 um 19:34 Uhr
Siehe auch bugs.chromium.org/p/chromium/issues/detail?id=478183
– Schrittmacher
15. Juli 2017 um 19:21 Uhr