Javascript 使用内容脚本访问页面上下文变量和函数

Javascript 使用内容脚本访问页面上下文变量和函数,javascript,google-chrome,google-chrome-extension,youtube-api,content-script,Javascript,Google Chrome,Google Chrome Extension,Youtube Api,Content Script,我正在学习如何创建Chrome扩展。我刚开始开发一个用来捕捉YouTube事件的。我想稍后将其与YouTube flash player一起使用,我将尝试使其与HTML5兼容 manifest.json: myScript.js: 问题是控制台给我的是启动!,但国家没有改变!当我播放/暂停YouTube视频时 当这个代码放在控制台中时,它工作了。我做错了什么?根本原因: 内容脚本在环境中执行 解决方案: 要访问页面上下文主世界的函数/变量,必须使用DOM将代码注入页面本身。如果您想将函数/变量公

我正在学习如何创建Chrome扩展。我刚开始开发一个用来捕捉YouTube事件的。我想稍后将其与YouTube flash player一起使用,我将尝试使其与HTML5兼容

manifest.json:

myScript.js:

问题是控制台给我的是启动!,但国家没有改变!当我播放/暂停YouTube视频时

当这个代码放在控制台中时,它工作了。我做错了什么?

根本原因: 内容脚本在环境中执行

解决方案: 要访问页面上下文主世界的函数/变量,必须使用DOM将代码注入页面本身。如果您想将函数/变量公开给本例中的页面上下文,那么它就是state方法

注意:如果需要与页面脚本通信: 使用DOM CustomEvent处理程序。示例:、和

注意:如果页面脚本中需要chrome API: 由于chrome.*API不能在页面脚本中使用,因此您必须在内容脚本中使用它们,并通过DOM消息将结果发送到页面脚本。请参见上面的说明

安全警告: 一个页面可能会重新定义或扩充/钩住一个内置原型,因此如果该页面以不兼容的方式进行操作,那么暴露的代码可能会失败。如果您想确保公开的代码在安全的环境中运行,那么您应该使用方法2-3而不是1声明内容脚本,或者通过空iframe提取原始本机内置。注意,对于document_start,您可能需要在公开的代码中使用DOMContentLoaded事件来等待DOM

目录 方法1:注入另一个文件-与ManifestV3兼容 方法2:注入嵌入式代码 方法2b:使用函数 方法3:使用内联事件 注入代码中的动态值 方法1:注入另一个文件 目前唯一兼容的方法。当你有很多代码的时候特别好。将代码放在扩展名内的文件中,比如script.js。然后将其加载到您的中,如下所示:

var s = document.createElement('script');
s.src = chrome.runtime.getURL('script.js');
s.onload = function() {
    this.remove();
};
(document.head || document.documentElement).appendChild(s);
js文件必须在以下位置公开:

ManifestV2的manifest.json示例

web\u可访问的\u资源:[script.js], ManifestV3的manifest.json示例

可访问的网络资源:[{ 参考资料:[script.js], 匹配项:[

注意:仅在Chrome 41及以上版本中支持。如果您希望扩展在Chrome 40-中工作,请使用:

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');
方法2b:使用函数 对于大块代码,引用字符串是不可行的。可以使用函数代替数组,并将其字符串化:

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();
此方法有效,因为字符串和函数上的+运算符将所有对象转换为字符串。如果您打算多次使用代码,明智的做法是创建函数以避免代码重复。实现可能如下所示:

function injectScript(func) {
    var actualCode = '(' + func + ')();'
    ...
}
injectScript(function() {
   alert("Injected script");
});
注意:由于函数是序列化的,原始作用域和所有绑定属性都将丢失

var scriptToInject = function() {
    console.log(typeof scriptToInject);
};
injectScript(scriptToInject);
// Console output:  "undefined"
方法3:使用内联事件 有时,您希望立即运行一些代码,例如,在创建元素之前运行一些代码。这可以通过插入带有textContent的标记来完成。请参见方法2/2b

另一种方法是使用内联事件,但不建议使用。不建议使用内联事件,因为如果页面定义了禁止内联脚本的内容安全策略,则会阻止内联事件侦听器。另一方面,由扩展注入的内联脚本仍会运行。 如果仍要使用内联事件,请执行以下操作:

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');
注意:此方法假定没有其他全局事件侦听器处理重置事件。如果有,您也可以选择其他全局事件之一。只需打开JavaScript控制台F12,键入document.documentElement.on,然后选择可用事件

注入代码中的动态值 有时,您需要将任意变量传递给注入函数。例如:

var GREETING = "Hi, I'm ";
var NAME = "Rob";
var scriptToInject = function() {
    alert(GREETING + NAME);
};
若要插入此代码,您需要将变量作为参数传递给匿名函数。请确保正确实现它!以下操作将不起作用:

解决方案是在传递参数之前使用。示例:

var actualCode = '(' + function(greeting, name) { ...
} + ')(' + JSON.stringify(GREETING) + ',' + JSON.stringify(NAME) + ')';
如果您有许多变量,则值得使用JSON.stringify一次,以提高可读性,如下所示:

...
} + ')(' + JSON.stringify([arg1, arg2, arg3, arg4]) + ')';
Rob W的优秀答案中唯一缺少的是如何在注入的页面脚本和内容脚本之间进行通信

在接收端,您的内容脚本或注入的页面脚本添加一个事件侦听器:

document.addEventListener('yourCustomEvent', function (e) {
  var data = e.detail;
  console.log('received', data);
});
在启动器端内容脚本或注入页面脚本上发送事件:

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

DOM消息传递使用结构化克隆算法,该算法只能在原语值之外进行传输。它不能发送类实例、函数或DOM元素。 在Firefox中,要从内容脚本向页面上下文发送一个对象(即非原语值),必须使用cloneInto buil将其显式克隆到目标中 t-in函数,否则它将失败并出现安全冲突错误

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

我还面临着加载脚本的排序问题,这是通过顺序加载脚本解决的。装载量是基于

用法示例如下:

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

实际上,我对JS有点陌生,所以请随意选择更好的方法。

在Content script中,我在头部添加了脚本标记,它绑定了一个“onmessage”处理程序,在我使用的处理程序中,eval来执行代码。 在booth内容脚本中,我也使用onmessage处理程序,所以我得到了双向通信。

这样,我可以在CS和真实Dom之间进行双向通信。 它非常有用,例如,如果您需要收听WebCoket事件,
或任何内存中的变量或事件。

如果希望注入纯函数而不是文本,可以使用以下方法:

函数注入{ document.body.style.backgroundColor='蓝色'; } //这包括作为文本的函数和使其自身运行的barenthese。 var实际代码=+注入+; document.documentElement.setAttribute'onreset',actualCode; document.documentElement.dispatchEventnew CustomEvent“重置”;
document.documentElement.removeAttribute'onreset' 您可以使用我创建的实用程序函数在页面上下文中运行代码并获取返回值

这是通过将函数序列化为字符串并将其注入网页来实现的

实用程序是

用法示例-

//某些代码仅存在于页面上下文中- window.someProperty='property'; 函数someFunctionname='test'{ 返回新Promisers=>setTimeout=>res'resolved'+名称,1200; } ///////////////// //内容脚本示例- 等待runInPageContext=>someProperty;//返回“属性” 等待runInPageContext=>someFunction;//返回“已解决的测试” 等待runInPageContextasync name=>someFunctionname,'with name';//'已解析为“名称” 等待runInPageContextasync…args=>someFunction…args,'带有扩展运算符和rest参数';//返回“使用扩展运算符和rest参数解析” 等待runInPageContext{ func:name=>someFunctionname, args:['with params object'], 文件:文件, 超时:10000 } ; // 返回“已使用params对象解析”
尝试删除函数名周围的引号:player.addEventListeneronStateChange,state;另外值得注意的是,在编写匹配项时,不要忘记包含https://或http://,该www.youtube.com/*将不允许您打包扩展名,并且还会抛出一个错误,因为该答案应该是官方文档的一部分。官方文件应该与建议的方式->3种方式来做同样的事情。。。错误?@Qantas94Heavy扩展的CSP不会影响内容脚本。只有页面的CSP是相关的。方法1可以通过使用排除扩展源的script src指令来阻止,方法2可以通过使用排除不安全内联“”的CSP来阻止。有人问我为什么要使用script.parentNode.removeChildscript;删除脚本标记;。我这样做的原因是因为我喜欢收拾我的烂摊子。当在文档中插入内联脚本时,会立即执行该脚本并安全地删除标记;内容脚本中的任意位置。对于简短的代码片段更容易,并且还可以访问页面的JS对象。@ChrisP请小心使用javascript:。跨越多行的代码可能无法按预期工作。行注释//将截断其余部分,因此此操作将失败:location.href='javascript://Do something alert0;';。这可以通过确保使用多行注释来避免。另一件需要注意的事情是表达式的结果应该是无效的。javascript:window.x='somevariable';将导致文档卸载,并替换为短语“some variable”。如果使用得当,它确实是一个很有吸引力的替代方法。实际上,我已经链接到我答案第二行的代码和解释,to。您是否有更新方法的参考资料,例如bug报告或测试用例?CustomEvent构造函数取代不推荐使用的document.createEvent API。对于我来说,“dispatchEventnew CustomEvent…”有效。我有铬33。此外,它以前也不起作用,因为我在注入js代码后编写了addEventListener。我认为正式的方法是使用window.postMessage:如何将响应从内容脚本发送回启动器脚本这种插入脚本的方法不好,因为这样会污染网页的名称空间。如果网页使用了一个名为formulaImageUrl或codeImageUrl的变量,那么实际上就是在破坏网页的功能。如果要向网页传递变量,我建议将数据附加到脚本元素,例如script.dataset.formulaImageUrl=formulaImageUrl;并使用函数{var dataset=document.currentScript.dataset;alertdataset.formulaImageUrl;};在脚本中访问数据。@Ro
谢谢你的留言,虽然更多的是关于样品的。请你澄清一下,为什么我应该使用iLife而不是仅仅获取数据集?仅在脚本执行时指向脚本标记。如果要访问脚本标记和/或其属性/属性(如数据集),则需要将其存储在变量中。我们需要一个IIFE来获得一个闭包来存储这个变量,而不会污染全局名称空间。@RobW好极了!但我们不能只使用一些变量名,它几乎不会与现有变量名相交。它只是非惯用语言,还是我们可能会有其他问题?你可以,但是使用IIFE的成本可以忽略不计,所以我认为没有理由宁愿使用名称空间污染而不是IIFE。我很重视我不会以某种方式破坏他人的网页,以及使用短变量名的能力。使用IIFE的另一个优点是,如果需要返回,可以提前退出脚本;。这很酷…但是第二个版本,有一个颜色变量,对我来说不起作用…我得到了“未识别”并且代码抛出了一个错误…没有将其视为变量。这太不可思议了,谢谢!
document.addEventListener('yourCustomEvent', function (e) {
  var data = e.detail;
  console.log('received', data);
});
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 }));
document.dispatchEvent(new CustomEvent('yourCustomEvent', {
  detail: cloneInto(data, document.defaultView),
}));
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();
    }
}
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")
]);
//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

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