从Background.js在页面级执行代码并返回值
我有一个自己的脚本和variables,我需要执行和检索我的扩展的Background.js返回值的网页。
我明白(我想!)为了与网页交互,它必须通过chrome.tabs.executeScript或ContentScript完成,但是因为代码必须在原始页面的上下文中执行(为了有范围到脚本和variables),它需要首先注入页面。
在Rob W的这篇伟大的文章之后,我可以调用页面级脚本/variables,但是我正在努力理解如何以这种方式返回值。
这是我到目前为止…
网页代码 (我想与之交互):
<html> <head> <script> var favColor = "Blue"; function getURL() { return window.location.href; } </script> </head> <body> <p>Example web page with script content I want interact with...</p> </body> </html>
manifest.json :
{ // Extension ID: behakphdmjpjhhbilolgcfgpnpcoamaa "name": "MyExtension", "version": "1.0", "manifest_version": 2, "description": "My Desc Here", "background": { "scripts": ["background.js"] }, "icons": { "128": "icon-128px.png" }, "permissions": [ "background", "tabs", "http://*/", "https://*/", "file://*/", //### (DEBUG ONLY) "nativeMessaging" ] }
background.js
codeToExec = ['var actualCode = "alert(favColor)";', 'var script = document.createElement("script");', ' script.textContent = actualCode;', '(document.head||document.documentElement).appendChild(script);', 'script.parentNode.removeChild(script);'].join('\n'); chrome.tabs.executeScript( tab.id, {code:codeToExec}, function(result) { console.log('Result = ' + result); } );
我意识到代码目前只是“提醒”favColorvariables(这只是一个testing,以确保我能看到它的工作)。 但是,如果我尝试返回该variables(通过将其作为最后一个语句或通过说“返回favColor”),executeScriptcallback从不具有该值。
所以,这里似乎至less有三个层次:
- background.js
- 内容脚本
- 实际的网页 (包含脚本/variables)
…我想知道从1级到3级(上面)交谈的build议方式和返回值是什么?
在此先感谢:o)
在理解三层上下文分离方面,你是完全正确的。
- 背景页面是一个单独的页面,因此不共享JS或DOM与可见页面。
- 内容脚本是从网页的JS上下文中分离出来的,但是共享DOM。
- 您可以使用共享DOM将代码注入到页面的上下文中。 它可以访问JS上下文,但不能访问Chrome API。
为了沟通,这些图层使用不同的方法:
背景< – >通过Chrome API进行内容交谈。
最原始的是executeScript
的callback,但对于其他任何东西都是不切实际的。
常用的方法是使用消息 。
不常见,但可以使用chrome.storage
及其onChanged
事件进行通信。
页面< – >扩展名不能使用相同的技术。
由于注入的页面上下文脚本在技术上与页面自己的脚本没有区别,因此您正在寻找网页与扩展对话的方法。 有两种方法可用:
-
尽pipe页面访问
chrome.*
非常有限,但它们仍然可以使用Messaging来联系扩展。 这是通过"externally_connectable"
方法实现的 。我最近详细描述了这个答案 。 简而言之,如果您的扩展程序声明允许域与其通信,并且域知道分机的ID,则它可以向该分机发送外部消息。
好处是直接与扩展相关联,但不利的一面是需要将您使用的域名专门列入白名单 ,并且需要跟踪您的扩展ID(但由于您正在注入代码,因此您可以提供带有ID的代码)。 如果你需要在任何领域使用它,这是不合适的。
-
另一个解决scheme是使用DOM事件。 由于DOM是在内容脚本和页面脚本之间共享的,所以一个事件产生的事件对另一个事件是可见的。
该文档演示了如何使用
window.postMessage
来达到这个效果。 使用自定义事件在概念上更清楚。我再一次回答了这个问题 。
这种方法的缺点是要求内容脚本充当代理 。 内容脚本中必须包含以下内容:
window.addEventListener("PassToBackground", function(evt) { chrome.runtime.sendMessage(evt.detail); }, false);
而后台脚本使用
chrome.runtime.onMessage
监听器来处理这个chrome.runtime.onMessage
。
我鼓励你编写一个单独的内容脚本,并用file
属性而不是code
调用executeScript
,而不要依赖它的callback。 消息更清晰,并允许不止一次地将数据返回到后台脚本。
Xan的答案中的方法(使用事件进行交stream)是推荐的方法。 然而,实施这个概念(并且以一种安全的方式!)是比较困难的。
所以我会指出,可以同步地从页面返回一个值到内容脚本。 当在页面中插入带有内联脚本的<script>
标签时,脚本会立即同步执行(在.appendChild(script)
方法返回之前)。
您可以通过使用注入的脚本将结果分配给可由内容脚本访问的DOM对象来利用此行为。 例如,通过覆盖当前活动的<script>
标签的文本内容。 <script>
标记中的代码只执行一次,因此您可以将任何垃圾分配给<script>
标记的内容,因为它不会再被parsing为代码。 例如:
// background script // The next code will run as a content script (via chrome.tabs.executeScript) var codeToExec = [ // actualCode will run in the page's context 'var actualCode = "document.currentScript.textContent = favColor;";', 'var script = document.createElement("script");', 'script.textContent = actualCode;', '(document.head||document.documentElement).appendChild(script);', 'script.remove();', 'script.textContent;' ].join('\n'); chrome.tabs.executeScript(tab.id, { code: codeToExec }, function(result) { // NOTE: result is an array of results. It is usually an array with size 1, // unless an error occurs (eg no permission to access page), or // when you're executing in multiple frames (via allFrames:true). console.log('Result = ' + result[0]); });
这个例子是可用的,但并不完美。 在你的代码中使用这个之前,确保你实现了正确的error handling。 目前,当favColor
未定义时,脚本会引发错误。 因此,脚本文本不会更新,并且返回的值不正确。 在实现适当的error handling之后,这个例子将会非常稳固。
而且这个例子几乎不可读,因为脚本是由一个string构成的。 如果逻辑相当大,但内容脚本在单独的文件中并使用chrome.tabs.executeScript(tab.id, {file: ...}, ...);
。
当actualCode
变得比几行更长时,我build议把代码包装在一个函数文字中,并用'('
和')();
连接起来')();
让你更容易编写代码,而不必在actualCode
代码中添加引号和反斜杠( 在问题中引用的答案基本上是“方法2b” )。