有没有办法从谷歌Chrome内容脚本中检索页面的javascript变量?
我创建了一个小帮手方法,玩得开心:)
检索窗口的变量“lannister”,“always”,“pays”,“his”,“debts”,执行以下命令:
var windowVariables = retrieveWindowVariables(["lannister", "always", "pays", "his", "debts"]);
console.log(windowVariables.lannister);
console.log(windowVariables.always);
我的代码:
function retrieveWindowVariables(variables) {
var ret = {};
var scriptContent = "";
for (var i = 0; i < variables.length; i++) {
var currVariable = variables[i];
scriptContent += "if (typeof " + currVariable + " !== 'undefined') $('body').attr('tmp_" + currVariable + "', " + currVariable + ");\n"
}
var script = document.createElement('script');
script.id = 'tmpScript';
script.appendChild(document.createTextNode(scriptContent));
(document.body || document.head || document.documentElement).appendChild(script);
for (var i = 0; i < variables.length; i++) {
var currVariable = variables[i];
ret[currVariable] = $("body").attr("tmp_" + currVariable);
$("body").removeAttr("tmp_" + currVariable);
}
$("#tmpScript").remove();
return ret;
}
请注意我使用jQuery ..您可以轻松地使用本机js“removeAttribute”和“removeChild”。
使用Liran的解决方案,我正在为Objects
添加一些修复,这是正确的解决方案:
function retrieveWindowVariables(variables) {
var ret = {};
var scriptContent = "";
for (var i = 0; i < variables.length; i++) {
var currVariable = variables[i];
scriptContent += "if (typeof " + currVariable + " !== 'undefined') $('body').attr('tmp_" + currVariable + "', JSON.stringify(" + currVariable + "));\n"
}
var script = document.createElement('script');
script.id = 'tmpScript';
script.appendChild(document.createTextNode(scriptContent));
(document.body || document.head || document.documentElement).appendChild(script);
for (var i = 0; i < variables.length; i++) {
var currVariable = variables[i];
ret[currVariable] = $.parseJSON($("body").attr("tmp_" + currVariable));
$("body").removeAttr("tmp_" + currVariable);
}
$("#tmpScript").remove();
return ret;
}
Chrome的文档为您提供了一个很好的起点:https://developer.chrome.com/extensions/content_scripts#host-page-communication
此方法允许您将全局页面变量提取到内容脚本。它还使用一个想法只接受您在握手时识别的传入消息。您也可以使用Math.random()
进行握手,但我有一些乐趣。
propagateVariable
字符串化,并将当前的handShake和目标变量名称传递给字符串以进行保存,因为该函数无法访问我们的内容脚本范围。const globalToExtract = 'someVariableName';
const array = new Uint32Array(5);
const handShake = window.crypto.getRandomValues(array).toString();
function propagateVariable(handShake, variableName) {
const message = { handShake };
message[variableName] = window[variableName];
window.postMessage(message, "*");
}
(function injectPropagator() {
const script = `( ${propagateVariable.toString()} )('${handShake}', '${globalToExtract}');`
const scriptTag = document.createElement('script');
const scriptBody = document.createTextNode(script);
scriptTag.id = 'chromeExtensionDataPropagator';
scriptTag.appendChild(scriptBody);
document.body.append(scriptTag);
})();
window.addEventListener("message", function({data}) {
console.log("INCOMINGGGG!", data);
// We only accept messages from ourselves
if (data.handShake != handShake) return;
console.log("Content script received: ", data);
}, false);
function extractGlobal(variableName) {
const array = new Uint32Array(5);
const handShake = window.crypto.getRandomValues(array).toString();
function propagateVariable(handShake, variableName) {
const message = { handShake };
message[variableName] = window[variableName];
window.postMessage(message, "*");
}
(function injectPropagator() {
const script = `( ${propagateVariable.toString()} )('${handShake}', '${variableName}');`
const scriptTag = document.createElement('script');
const scriptBody = document.createTextNode(script);
scriptTag.id = 'chromeExtensionDataPropagator';
scriptTag.appendChild(scriptBody);
document.body.append(scriptTag);
})();
return new Promise(resolve => {
window.addEventListener("message", function({data}) {
// We only accept messages from ourselves
if (data.handShake != handShake) return;
resolve(data);
}, false);
});
}
extractGlobal('someVariableName').then(data => {
// Do Work Here
});
如果使用es模块,我建议将类放入自己的文件并将其导出为默认值。然后它变成:
ExtractPageVariable('someGlobalPageVariable').data.then(pageVar => {
// Do work here 💪
});
class ExtractPageVariable {
constructor(variableName) {
this._variableName = variableName;
this._handShake = this._generateHandshake();
this._inject();
this._data = this._listen();
}
get data() {
return this._data;
}
// Private
_generateHandshake() {
const array = new Uint32Array(5);
return window.crypto.getRandomValues(array).toString();
}
_inject() {
function propagateVariable(handShake, variableName) {
const message = { handShake };
message[variableName] = window[variableName];
window.postMessage(message, "*");
}
const script = `( ${propagateVariable.toString()} )('${this._handShake}', '${this._variableName}');`
const scriptTag = document.createElement('script');
const scriptBody = document.createTextNode(script);
scriptTag.id = 'chromeExtensionDataPropagator';
scriptTag.appendChild(scriptBody);
document.body.append(scriptTag);
}
_listen() {
return new Promise(resolve => {
window.addEventListener("message", ({data}) => {
// We only accept messages from ourselves
if (data.handShake != this._handShake) return;
resolve(data);
}, false);
})
}
}
const windowData = new ExtractPageVariable('somePageVariable').data;
windowData.then(console.log);
windowData.then(data => {
// Do work here
});
我实际上使用本地Storage API解决了这个问题。注意:要使用它,我们的内容脚本应该能够读取localStorage。在manifest.json文件中,只需添加“storage”字符串:
"permissions": [...,"storage"]
劫持功能存在于内容脚本中:
function hijack(callback) {
"use strict";
var code = function() {
//We have access to topframe - no longer a contentscript
var ourLocalStorageObject = {
globalVar: window.globalVar,
globalVar2: window.globalVar2
};
var dataString = JSON.stringify(ourLocalStorageObject);
localStorage.setItem("ourLocalStorageObject", dataString);
};
var script = document.createElement('script');
script.textContent = '(' + code + ')()';
(document.head||document.documentElement).appendChild(script);
script.parentNode.removeChild(script);
callback();
}
现在我们可以从内容脚本调用
document.addEventListener("DOMContentLoaded", function(event) {
hijack(callback);
});
或者如果你在你的内容中使用jQuery,就像我一样:
$(document).ready(function() {
hijack(callback);
});
提取内容:
function callback() {
var localStorageString = localStorage.getItem("ourLocalStorageObject");
var ourLocalStorageObject= JSON.parse(localStorageString);
console.log("I can see now on content script", ourLocalStorageObject);
//(optional cleanup):
localStorage.removeItem("ourLocalStorageObject");
}
这可以多次调用,因此如果您的页面更改元素或内部代码,您可以添加事件侦听器以使用新数据更新扩展。
编辑:我添加了回调,因此您可以确保您的数据不会无效(我自己有这个问题)
如果您知道要访问哪些变量,则可以创建一个快速自定义内容脚本来检索其值。
在popup.js
:
chrome.tabs.executeScript(null, {code: 'var name = "property"'}, function() {
chrome.tabs.executeScript(null, {file: "retrieveValue.js"}, function(ret) {
for (var i = 0; i < ret.length; i++) {
console.log(ret[i]); //prints out each returned element in the array
}
});
});
在retrieveValue.js
:
function returnValues() {
return document.getElementById("element")[name];
//return any variables you need to retrieve
}
returnValues();
您可以修改代码以返回数组或其他对象。
没有。
内容脚本在称为孤立世界的特殊环境中执行。他们可以访问注入页面的DOM,但不能访问页面创建的任何JavaScript变量或函数。它将每个内容脚本视为在其运行的页面上没有执行其他JavaScript。反过来也是如此:页面上运行的JavaScript无法调用任何函数或访问内容脚本定义的任何变量。
隔离世界允许每个内容脚本对其JavaScript环境进行更改,而不必担心与页面或其他内容脚本冲突。例如,内容脚本可以包含JQuery v1,页面可以包含JQuery v2,它们不会相互冲突。
孤立世界的另一个重要好处是它们将页面上的JavaScript与扩展中的JavaScript完全分开。这使我们能够为不能从网页访问的内容脚本提供额外的功能,而无需担心网页访问它。
正如在其他答案中部分解释的那样,页面中的JS变量与Chrome扩展内容脚本隔离。通常,无法访问它们。
但是,如果在页面中注入JavaScript标记,则可以访问其中定义的任何变量。
我使用实用程序函数在页面中注入我的脚本:
/**
* inject - Inject some javascript in order to expose JS variables to our content JavaScript
* @param {string} source - the JS source code to execute
* Example: inject('(' + myFunction.toString() + ')()');
*/
function inject(source) {
const j = document.createElement('script'),
f = document.getElementsByTagName('script')[0];
j.textContent = source;
f.parentNode.insertBefore(j, f);
f.parentNode.removeChild(j);
}
然后你可以这样做:
function getJSvar(whichVar) {
document.body.setAttribute('data-'+whichVar,whichVar);
}
inject('(' + getJSvar.toString() + ')("somePageVariable")');
var pageVar = document.body.getAttribute('data-somePageVariable');
请注意,如果变量是复杂数据类型(对象,数组...),则需要将值存储为getJSvar()中的JSON字符串,并将JSON.parse存储在内容脚本中。