web-dev-qa-db-ger.com

Fügen Sie Code mithilfe eines Inhaltsskripts in den Seitenkontext ein

Ich lerne, wie ich Chrome Erweiterungen erstelle. Ich habe gerade damit begonnen, eine zu entwickeln, um YouTube-Ereignisse zu erfassen. 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 "Gestartet!" gibt, aber es gibt kein "Status geändert!" wenn ich YouTube-Videos abspiele/pausiere.

Wenn dieser Code in die Konsole gestellt wird, hat es funktioniert. Was mache ich falsch?

443
André Alves

Inhaltsskripte werden in einer "isolierte Welt" -Umgebung ausgeführt. Sie müssen Ihre Methode state() in die Seite selbst einfügen.

Wenn Sie eine der _chrome.*_ APIs im Skript verwenden möchten, müssen Sie einen speziellen Ereignishandler implementieren, wie in dieser Antwort beschrieben: Chrome-Erweiterung - Abrufen der ursprünglichen Nachricht von Google Mail .

Wenn Sie ansonsten keine _chrome.*_-APIs verwenden müssen, empfehle ich dringend, den gesamten JS-Code in die Seite einzufügen, indem Sie ein _<script>_ -Tag hinzufügen:

Inhaltsverzeichnis

  • Methode 1: Injizieren Sie eine andere Datei
  • Methode 2: Injizieren Sie eingebetteten Code
  • Methode 2b: Verwenden einer Funktion
  • Methode 3: Verwenden eines Inline-Ereignisses
  • Dynamische Werte im injizierten Code

Methode 1: Injizieren Sie eine andere Datei

Dies ist die einfachste/beste Methode, wenn Sie viel Code haben. Fügen Sie Ihren tatsächlichen JS-Code in eine Datei in Ihrer Erweiterung ein, z. B. _script.js_. Dann lassen Sie Ihr Inhaltsskript wie folgt aussehen (hier erklärt: Google Chome "Anwendungsverknüpfung", benutzerdefiniertes Javascript ):

_var s = document.createElement('script');
// TODO: add "script.js" to web_accessible_resources in manifest.json
s.src = chrome.runtime.getURL('script.js');
s.onload = function() {
    this.remove();
};
(document.head || document.documentElement).appendChild(s);
_

Hinweis: Wenn Sie diese Methode verwenden, muss die injizierte _script.js_ -Datei zum Abschnitt "web_accessible_resources" hinzugefügt werden ( Beispiel =). Wenn Sie dies nicht tun, wird Chrome ablehnen, um Ihr Skript zu laden und den folgenden Fehler in der Konsole anzuzeigen:

Laden der Chrome-Erweiterung verweigert: // [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: Injizieren Sie eingebetteten Code

Diese Methode ist nützlich, wenn Sie schnell einen kleinen Code ausführen möchten. (Siehe auch: Wie deaktiviere ich Facebook-Hotkeys mit der Erweiterung Chrome? ).

_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();
_

Hinweis: Vorlagenliterale werden nur in Chrome 41 und höher unterstützt. Wenn die Erweiterung in Chrome 40- funktionieren soll, 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

Für einen großen Teil des Codes ist es nicht möglich, die Zeichenfolge in Anführungszeichen zu setzen. Anstelle eines Arrays kann eine Funktion verwendet und mit einem String versehen 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, da der Operator _+_ für Zeichenfolgen und eine Funktion alle Objekte in eine Zeichenfolge konvertiert. Wenn Sie den Code mehrmals verwenden möchten, empfiehlt es sich, eine Funktion zu erstellen, um Codewiederholungen zu vermeiden. Eine Implementierung könnte folgendermaßen aussehen:

_function injectScript(func) {
    var actualCode = '(' + func + ')();'
    ...
}
injectScript(function() {
   alert("Injected script");
});
_

Hinweis: Da die Funktion serialisiert ist, gehen der ursprüngliche Bereich 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 sofort Code ausführen, z. um Code auszuführen, bevor das Element _<head>_ erstellt wird. Dies kann durch Einfügen eines _<script>_ -Tags mit textContent erreicht werden (siehe Methode 2/2b).

Eine Alternative, aber nicht empfohlen, ist die Verwendung von Inline-Ereignissen. Dies wird nicht empfohlen, da Inline-Ereignis-Listener blockiert werden, wenn auf der Seite eine Inhaltssicherheitsrichtlinie definiert ist, die Inline-Skripts verbietet. Von der Erweiterung eingefügte Inline-Skripte werden dagegen weiterhin ausgeführt. Wenn Sie weiterhin 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: Bei dieser Methode wird davon ausgegangen, dass es keine anderen globalen Ereignis-Listener gibt, die das Ereignis reset verarbeiten. In diesem Fall können Sie auch eines der anderen globalen Ereignisse auswählen. Öffnen Sie einfach die JavaScript-Konsole (F12), geben Sie _document.documentElement.on_ ein und wählen Sie eines der verfügbaren Ereignisse aus.

Dynamische Werte im injizierten Code

Gelegentlich müssen Sie der injizierten Funktion eine beliebige Variable ü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. Stellen Sie sicher, dass Sie es korrekt implementieren! Folgendes wird nicht ​​funktionieren:

_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 besteht darin, vor dem Übergeben des Arguments JSON.stringify zu verwenden. Beispiel:

_var actualCode = '(' + function(greeting, name) { ...
} + ')(' + JSON.stringify(GREETING) + ',' + JSON.stringify(NAME) + ')';
_

Wenn Sie viele Variablen haben, lohnt es sich, _JSON.stringify_ einmal zu verwenden, um die Lesbarkeit zu verbessern:

_...
} + ')(' + JSON.stringify([arg1, arg2, arg3, arg4]) + ')';
_
813
Rob W

Die einzige Sache fehlt Vor Rob Ws hervorragender Antwort verborgen ist, wie man zwischen dem Skript für injizierte Seiten und dem Inhaltsskript kommuniziert.

Fügen Sie auf der empfangenden Seite (entweder Ihr Inhaltsskript oder das Seitenskript) einen Ereignis-Listener hinzu:

document.addEventListener('yourCustomEvent', function (e) {
  var data = e.detail;
  console.log('received', data);
});

Senden Sie auf der Initiatorseite (Inhalts- oder Seitenskript) das Ereignis:

var data = {
  any: 'JSON-ifiable data',
  meaning: 'no DOM elements or classes/functions',
};

document.dispatchEvent(new CustomEvent('yourCustomEvent', { detail: data }));

Stellen Sie sicher, dass die übertragenen Daten JSON-fähig sind

Sie können das Stringing/Parsen explizit ausführen, um die nicht übertragbaren Daten zu entfernen. Andernfalls wird nur null für das gesamte detail in modernem Chrome übertragen, siehe crbug.com/9177 .

  • empfänger:

    document.addEventListener('yourCustomEvent', function (e) {
      var data = JSON.parse(e.detail);
      console.log('received', data);
    });
    
  • initiator:

    document.dispatchEvent(new CustomEvent('yourCustomEvent', {
      detail: JSON.stringify(data),
    }));
    
52
laktak

Ich war auch mit dem Problem der Bestellung geladener Skripte konfrontiert, das durch sequentielles Laden von Skripten gelöst wurde. Das Laden basiert auf Rob Ws Antwort .

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 Anwendungsbeispiel 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 ein bisschen neu bei JS, also zögern Sie nicht, mir die besseren Wege zu zeigen.

8
Dmitry Ginzburg

im Inhaltsskript füge ich dem Kopf ein Skript-Tag hinzu, das einen 'onmessage'-Handler bindet. Innerhalb des Handlers, den ich verwende, wird eval Code ausgeführt. Im Standinhaltsskript verwende ich auch den Onmessage-Handler, so dass ich bidirektionale Kommunikation bekomme. Chrome Docs

//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 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 bidirektionale Kommunikation zwischen CS und Real Dom herstellen. Dies ist sehr nützlich, wenn Sie beispielsweise Web-Coket-Ereignisse oder Variablen oder Ereignisse im Arbeitsspeicher abhören müssen.

6
doron aviguy

Wenn Sie anstelle von Text eine reine Funktion einfügen möchten, können Sie diese Methode verwenden:

function inject(){
    document.body.style.backgroundColor = 'blue';
}

// this includes the function as text and the barentheses make it run itself.
var actualCode = "("+inject+")()"; 

document.documentElement.setAttribute('onreset', actualCode);
document.documentElement.dispatchEvent(new CustomEvent('reset'));
document.documentElement.removeAttribute('onreset');

Und Sie können Parameter an die Funktionen übergeben (leider können keine Objekte und Arrays verkettet werden). Füge es wie folgt in die Baretheses ein:

function inject(color){
    document.body.style.backgroundColor = color;
}

// this includes the function as text and the barentheses make it run itself.
var color = 'yellow';
var actualCode = "("+inject+")("+color+")"; 
0
Tarmo Saluste