Użyj skryptu zawartości, aby uzyskać dostęp do zmiennych kontekstu strony i funkcji

Uczę się jak tworzyć rozszerzenia Chrome. Właśnie zacząłem opracowywać jeden, aby złapać wydarzenia na YouTube. Chcę go używać z YouTube flash player (później postaram się, aby był kompatybilny z HTML5).

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!");

Problem polega na tym, że konsola daje mi "rozpoczęty!", ale nie ma " Stan się zmienił!" kiedy odtwarzam / wstrzymuję filmy na YouTube.

Gdy ten kod zostanie umieszczony w konsoli zadziałało. Co robię źle?

Author: wOxxOm, 2012-03-01

6 answers

Przyczyna:
Skrypty zawartości są wykonywane w środowisku "isolated world".

Rozwiązanie::
Aby uzyskać dostęp do funkcji / zmiennych kontekstu strony ("main world"), musisz wprowadzić kod do samej strony za pomocą DOM. To samo, jeśli chcesz wyeksponować swoje funkcje / zmienne w kontekście strony(w Twoim przypadku jest to metoda state()).

  • Uwaga w przypadku komunikacji z skrypt strony jest potrzebny:
    Użyj dom CustomEvent handler. Przykłady: one, dwa i trzy .

  • Uwaga w przypadku, gdy chrome API jest potrzebne w skrypcie strony:
    Ponieważ interfejsy API chrome.* nie mogą być używane w skrypcie strony, musisz użyć ich w skrypcie zawartości i wysłać wyniki do skryptu strony za pomocą wiadomości DOM (patrz uwaga powyżej).

Ostrzeżenie o bezpieczeństwie :
Strona może redefiniować lub powiększać / hook a wbudowany prototyp, więc Twój kod może się nie udać, jeśli strona zrobiła to w niezgodny sposób. Jeśli chcesz mieć pewność, że ujawniony kod działa w bezpiecznym środowisku, powinieneś albo zadeklarować swój skrypt zawartości za pomocą "run_at": "document_start" i użyć metod 2-3, a nie 1, albo B) wyodrębnić oryginalną natywną wbudowaną zawartość za pomocą pustego iframe, przykład. Zauważ, że w przypadku document_start może być konieczne użycie zdarzenia DOMContentLoaded wewnątrz odsłoniętego kodu, aby czekać na DOM.

Tabela spis treści

  • Metoda 1: Inject another file
  • Metoda 2: wstrzyknięcie wbudowanego kodu
  • metoda 2b: Używanie funkcji
  • Metoda 3: użycie zdarzenia inline
  • wartości dynamiczne w kodzie

Metoda 1: Inject another file

jest to najprostsza/najlepsza metoda, gdy masz dużo kodu. Dołącz swój rzeczywisty kod JS do pliku w swoim rozszerzeniu, powiedzmy script.js. Następnie niech twój skrypt treści będzie następujący (wyjaśnione tutaj: Google Chome "Application Shortcut" Custom 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);

Uwaga: Ze względów bezpieczeństwa Chrome zapobiega wczytywaniu plików js. Twój plik musi być dodany jako "web_accessible_resources" pozycja (przykład) :

// manifest.json must include: 
"web_accessible_resources": ["script.js"],
Jeśli nie, w konsoli pojawi się następujący błąd:]}

Odmowa załadowania chrome-extension://[EXTENSIONID]/script.js. Zasoby muszą być wymienione w kluczu manifestu web_accessible_resources, aby mogły być ładowane przez strony poza przedłużeniem.

Metoda 2: wstrzyknięcie wbudowanego kodu

Ta metoda jest przydatna, gdy chcesz szybko uruchomić mały fragment kodu. (Zobacz także: jak wyłączyć skróty klawiszowe facebook z rozszerzeniem 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();

Uwaga: literały szablonów {[33] } są obsługiwane tylko w Chrome 41 i nowszych wersjach. Jeśli chcesz, aby rozszerzenie działało w Chrome 40-, użyj:

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');

Metoda 2b: Używanie funkcji

Dla dużej części kodu, cytowanie ciągu jest niewykonalne. Zamiast używać tablicy, można użyć funkcji i stringified:

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

Ta metoda działa, ponieważ operator + na łańcuchach znaków i funkcji konwertuje wszystkie obiekty na łańcuch znaków. Jeśli zamierzasz używać kodu więcej niż raz, dobrze jest utworzyć funkcję, aby uniknąć powtarzania kodu. Implementacja może wyglądać następująco:

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

Uwaga: Ponieważ funkcja jest serializowana, oryginalny zakres i wszystkie powiązane właściwości zostają utracone!

var scriptToInject = function() {
    console.log(typeof scriptToInject);
};
injectScript(scriptToInject);
// Console output:  "undefined"

Metoda 3: Użycie Zdarzenie inline

Czasami chcesz uruchomić jakiś kod natychmiast, np. uruchomić jakiś kod przed utworzeniem elementu <head>. Można to zrobić wstawiając znacznik <script> z textContent (patrz Metoda 2/2B).

Alternatywą, ale niezalecaną jest użycie zdarzeń inline. Nie jest to zalecane, ponieważ jeśli strona definiuje politykę bezpieczeństwa treści, która zabrania skryptów wbudowanych, to słuchacze zdarzeń wbudowanych są blokowane. Skrypty inline wtryskiwane przez rozszerzenie, z drugiej ręka, dalej biegnij. Jeśli nadal chcesz używać zdarzeń w wierszu, oto jak:]}

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');

Uwaga: ta metoda zakłada, że nie ma innych globalnych detektorów zdarzeń, które obsługują Zdarzenie reset. Jeśli tak, możesz również wybrać jedno z innych wydarzeń globalnych. Po prostu otwórz konsolę JavaScript (F12), wpisz document.documentElement.on i wybierz dostępne zdarzenia.

Dynamiczne wartości w kodzie

Od czasu do czasu trzeba przekazać dowolną zmienną do wstrzykiwanej funkcji. Na przykład:

var GREETING = "Hi, I'm ";
var NAME = "Rob";
var scriptToInject = function() {
    alert(GREETING + NAME);
};

Aby wprowadzić ten kod, musisz przekazać zmienne jako argumenty do funkcji anonimowej. Pamiętaj, aby wdrożyć go poprawnie! Następujące będą Nie działać:

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!

Rozwiązaniem jest użycie JSON.stringify przed przekazaniem argumentu. Przykład:

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

Jeśli masz wiele zmiennych, warto użyć JSON.stringify raz, aby poprawić czytelność, w następujący sposób:

...
} + ')(' + JSON.stringify([arg1, arg2, arg3, arg4]) + ')';
 934
Author: Rob W,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2021-01-29 09:24:04

Jedyną rzeczą Brak ukrytą przed doskonałą odpowiedzią Roba W jest sposób komunikowania się między skryptem strony a skryptem treści.

Po stronie odbiorczej (Twój skrypt zawartości lub skrypt strony) Dodaj detektor zdarzeń:

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

Po stronie inicjatora (content script lub injected page script) wyślij Zdarzenie:

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 }));

Uwagi:

  • DOM messaging wykorzystuje algorytm strukturalnego klonowania, który może przenosić tylko niektóre typy danych oprócz prymitywnych wartości. Nie może wysyłać instancji klas, funkcji ani elementów DOM.
  • W Firefoksie, aby wysłać obiekt (tzn. nie prymitywną wartość) ze skryptu zawartości do kontekstu strony, musisz jawnie sklonować go do celu za pomocą cloneInto (wbudowana funkcja), w przeciwnym razie zawiedzie z błędem naruszenia bezpieczeństwa.

    document.dispatchEvent(new CustomEvent('yourCustomEvent', {
      detail: cloneInto(data, document.defaultView),
    }));
    
 75
Author: laktak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-04-18 04:36:55

Miałem również do czynienia z problemem porządkowania załadowanych skryptów, który został rozwiązany poprzez sekwencyjne Ładowanie skryptów. Ładowanie jest oparte na odpowiedzi Roba 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();
    }
}

Przykład użycia to:

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")
]);
Właściwie, jestem trochę nowy w JS, więc nie krępuj się, aby ping mnie do lepszych sposobów.
 8
Author: Dmitry Ginzburg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-18 01:11:13

W Content script, dodaję tag script do głowy, który wiąże handler 'onmessage' , wewnątrz Handlera którego używam , eval do wykonania kodu. W Booth content script używam również onmessage handler, więc dostaję dwukierunkową komunikację. 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 is a post message URL listener

//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");

W ten sposób mogę mieć dwukierunkową komunikację pomiędzy CS a Real Dom. To bardzo przydatne, na przykład, jeśli chcesz słuchać wydarzeń webscoket , lub do każdego w pamięci zmienne lub zdarzenia.

 7
Author: doron aviguy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-11 08:29:11

Możesz użyć funkcji użytkowej, którą stworzyłem, aby uruchomić kod w kontekście strony i odzyskać zwróconą wartość.

Odbywa się to poprzez serializowanie funkcji do ciągu znaków i wstrzyknięcie jej do strony internetowej.

Narzędzie jest dostępne tutaj na GitHub .

Przykłady użycia -



// 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'


 3
Author: Arik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-04-04 22:18:59

Jeśli chcesz wprowadzić czystą funkcję, zamiast tekstu, możesz użyć tej metody:

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');

I można przekazać parametry (niestety nie można przeciągać żadnych obiektów i tablic) do funkcji. Dodajcie to do gołych, TAK:

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
Author: Tarmo Saluste,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-05 11:31:55