Verwenden Sie ein Inhaltsskript, um auf die Seitenkontextvariablen und -funktionen zuzugreifen

Lesezeit: 14 Minuten

Verwenden Sie ein Inhaltsskript um auf die Seitenkontextvariablen und funktionen
Andre Alves

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?

  • 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:// oder http://Das www.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


Verwenden Sie ein Inhaltsskript um auf die Seitenkontextvariablen und funktionen
Rob W

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.onund 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]) + ')';

  • Diese Antwort sollte Teil der offiziellen Dokumente sein. Offizielle Dokumente sollten mit der empfohlenen Methode versendet werden –> 3 Möglichkeiten, dasselbe zu tun … Falsch?

    – Mars Robertson

    9. Juni 2013 um 11:17 Uhr

  • @ Qantas94Heavy Das CSP der Erweiterung tut es nicht Inhaltsskripte beeinflussen. Nur der CSP der Seite ist relevant. Methode 1 kann mit a blockiert werden script-src Direktive, die den Ursprung der Erweiterung ausschließt, kann Methode 2 blockiert werden, indem ein CSP verwendet wird, der „unsafe-inline“ ausschließt.

    – Rob W

    2. August 2013 um 7:24 Uhr


  • Jemand hat gefragt, warum ich das script-Tag mit entferne script.parentNode.removeChild(script);. Mein Grund dafür ist, dass ich gerne mein Chaos aufräume. Wenn ein Inline-Skript in das Dokument eingefügt wird, wird es sofort ausgeführt und die <script> Tag kann sicher entfernt werden.

    – Rob W

    29. August 2013 um 13:35 Uhr

  • Andere Methode: verwenden location.href = "javascript: alert('yeah')"; irgendwo in Ihrem Inhaltsskript. Es ist einfacher für kurze Codeschnipsel und kann auch auf die JS-Objekte der Seite zugreifen.

    – Metoule

    26. September 2013 um 21:08 Uhr


  • @ChrisP Seien Sie vorsichtig mit der Verwendung javascript:. Code, der sich über mehrere Zeilen erstreckt, funktioniert möglicherweise nicht wie erwartet. Ein Zeilenkommentar (//) wird den Rest abschneiden, sodass dies fehlschlägt: location.href = 'javascript:// Do something <newline> alert(0);';. Dies kann umgangen werden, indem Sie sicherstellen, dass Sie mehrzeilige Kommentare verwenden. Eine weitere Sache, auf die Sie achten sollten, ist, dass das Ergebnis des Ausdrucks void sein sollte. javascript:window.x = 'some variable'; bewirkt, dass das Dokument entladen wird, und wird durch den Ausdruck „irgendeine Variable“ ersetzt. Bei richtiger Anwendung ist es in der Tat eine attraktive Alternative zu <script>.

    – Rob W

    27. September 2013 um 7:42 Uhr

1646886914 595 Verwenden Sie ein Inhaltsskript um auf die Seitenkontextvariablen und funktionen
Laktak

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 habe den Code und die Erklärung in der zweiten Zeile meiner Antwort auf stackoverflow.com/questions/9602022/… verlinkt.

    – Rob W

    11. Oktober 2013 um 9:00 Uhr

  • Haben Sie eine Referenz für Ihre aktualisierte Methode (z. B. einen Fehlerbericht oder einen Testfall?). CustomEvent Der Konstruktor ersetzt den veralteten document.createEvent API.

    – Rob W

    6. November 2013 um 16:23 Uhr

  • Für mich hat ‘dispatchEvent (new CustomEvent …’) funktioniert. Ich habe Chrome 33. Auch hat es vorher nicht funktioniert, weil ich den addEventListener nach dem Einfügen des js-Codes geschrieben habe.

    – jscripter

    10. März 2014 um 9:15 Uhr


  • Ich denke, der offizielle Weg ist die Verwendung von window.postMessage: developer.chrome.com/extensions/…

    – Enrique

    15. Dezember 2018 um 13:08 Uhr

  • wie man eine Antwort vom Inhaltsskript an das Initiatorskript zurücksendet

    – Wein

    16. September 2019 um 7:41 Uhr

Verwenden Sie ein Inhaltsskript um auf die Seitenkontextvariablen und funktionen
Dmitri Ginzburg

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.

  • Diese Art des Einfügens von Skripten ist nicht schön, da Sie den Namensraum der Webseite verunreinigen. Wenn die Webseite eine Variable namens verwendet formulaImageUrl oder codeImageUrl, dann zerstören Sie effektiv die Funktionalität der Seite. Wenn Sie eine Variable an die Webseite übergeben möchten, schlage ich vor, die Daten an das Skriptelement anzuhängen (e.g. script.dataset.formulaImageUrl = formulaImageUrl;) und verwenden Sie zB (function() { var dataset = document.currentScript.dataset; alert(dataset.formulaImageUrl;) })(); im Skript, um auf die Daten zuzugreifen.

    – Rob W

    17. Mai 2015 um 17:27 Uhr

  • @RobW danke für deinen Hinweis, obwohl es mehr um das Beispiel geht. Können Sie bitte klarstellen, warum ich IIFE verwenden sollte, anstatt nur zu bekommen dataset?

    – Dmitri Ginzburg

    17. Mai 2015 um 17:36 Uhr

  • document.currentScript zeigt nur während der Ausführung auf das script-Tag. Wenn Sie jemals auf das Skript-Tag und/oder seine Attribute/Eigenschaften (z dataset), dann müssen Sie es in einer Variablen speichern. Wir brauchen ein IIFE, um eine Schließung zu erhalten, um diese Variable zu speichern, ohne den globalen Namensraum zu verschmutzen.

    – Rob W

    17. Mai 2015 um 17:38 Uhr


  • @RobW ausgezeichnet! Aber können wir nicht einfach einen Variablennamen verwenden, der sich kaum mit dem Vorhandenen überschneiden würde. Ist es nur nicht idiomatisch oder können wir andere Probleme damit haben?

    – Dmitri Ginzburg

    17. Mai 2015 um 17:51 Uhr

  • Sie könnten, aber die Kosten für die Verwendung eines IIFE sind vernachlässigbar, daher sehe ich keinen Grund, die Namespace-Verschmutzung einem IIFE vorzuziehen. Ich schätze die Gewissheit, dass ich die Webseite nicht beschädigen werde von Anderen in gewisser Weise und die Möglichkeit, kurze Variablennamen zu verwenden. Ein weiterer Vorteil der Verwendung eines IIFE besteht darin, dass Sie das Skript bei Bedarf früher beenden können (return;).

    – Rob W

    17. Mai 2015 um 17:58 Uhr

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.

1646886915 215 Verwenden Sie ein Inhaltsskript um auf die Seitenkontextvariablen und funktionen
Arik

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'


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 (leider können keine Objekte und Arrays stringifiziert werden) an die Funktionen übergeben. Fügen Sie es wie folgt in die Barthesen 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+")"; 

986190cookie-checkVerwenden Sie ein Inhaltsskript, um auf die Seitenkontextvariablen und -funktionen zuzugreifen

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy