简体   繁体   English

CustomEvent.detail是否“污染”?

[英]CustomEvent.detail “tainted”?

I'm developing a Chrome extension to add convenience to a website. 我正在开发Chrome扩展程序,以增加网站的便利性。
I have access to the page's DOM, but I also need to interact with the "first-party" JS on that page, which I cannot do from my extension. 我可以访问页面的DOM,但是我还需要与该页面上的“第一方” JS进行交互,而我无法通过其扩展程序进行此操作。

I can inject arbitrary tags into the page (most notably also <script> tags), but since escaping strings like 我可以在页面中注入任意标签(最著名的是<script>标签),但是由于转义了像

{
    html: '<div onclick="doSomething(this, \'someName\')"></div>'
}

is a real pain, I'd like to keep the injected code at an absolute minimum. 是一个真正的痛苦,我想将注入的代码保持在绝对最低限度。

I tried injecting event listeners into the page in order to fetch JS variables from the page, but ran into a problem. 我尝试将事件侦听器注入页面中,以便从页面中获取JS变量,但是遇到了问题。
It seems that if a CustomEvent is passed from an extension to a website or back, and if CustomEvent.detail contains certain types of objects (at least functions and errors) somewhere, the entire CustomEvent.detail will be purged, ie set to null. 看来,如果CustomEvent从扩展名传递到网站或返回,又或者CustomEvent.detail在某处包含某些类型的对象(至少是函数和错误),则将清除整个CustomEvent.detail ,即设置为null。

Example

Script (extension.js): 脚本(extension.js):

(function()
{
    var script = document.createElement('script');
    script.innerHTML = [
"window.addEventListener('xyz', function(ev)",
"    {                                      ",
"        console.log('after dispatch:');    ",
"        console.log(ev.detail);            ",
"    });                                    ",
    ].join('\n');
    document.head.appendChild(script);
    // JSON-serializable data
    var e = new CustomEvent('xyz', { detail: { x: 42, name: 'Schroedinger' } });
    console.log('before dispatch:')
    console.log(e.detail);
    window.dispatchEvent(e);
    // non-JSON-serializable data
    var detail = { x: 42, name: 'Schroedinger' };
    detail.detail = detail; // Create circular reference
    e = new CustomEvent('xyz', { detail: detail });
    console.log('before dispatch:')
    console.log(e.detail);
    window.dispatchEvent(e);
    // data with function
    e = new CustomEvent('xyz', { detail: { x: 42, name: 'Schroedinger', func: function(){} } });
    console.log('before dispatch:');
    console.log(e.detail);
    window.dispatchEvent(e);
    // data with error object
    e = new CustomEvent('xyz', { detail: { x: 42, name: 'Schroedinger', err: new Error() } });
    console.log('before dispatch:');
    console.log(e.detail);
    window.dispatchEvent(e);
})();

Output (paragraphed for readability): 输出(为便于阅读而分类):

before dispatch:
Object {x: 42, name: "Schroedinger"}
after dispatch:
Object {x: 42, name: "Schroedinger"}

before dispatch:
Object {x: 42, name: "Schroedinger", detail: Object}
after dispatch:
Object {x: 42, name: "Schroedinger", detail: Object}

before dispatch:
Object {x: 42, name: "Schroedinger", func: function (){}}
after dispatch:
null

before dispatch:
Object {x: 42, name: "Schroedinger", err: Error at chrome-extension://...}
after dispatch:
null

I initially thought JSON-serializability was the issue, but circular references pass just fine in events, when they would break if JSON-serialized. 我最初以为JSON可序列化是问题所在,但是循环引用在事件中传递得很好,如果JSON序列化会破坏循环引用。
It feels like certain objects "taint" the event detail the same way non-crossorigin images taint canvases , except there's nothing in the console. 感觉某些对象“污染”事件的细节与非跨域图像污染画布的方式相同,只是控制台中什么也没有。

I was unable to find any documentation regarding this behaviour, and (as Paul S. suggested), there does not seem to be a "privilege" for that on the Chrome permissions list . 我找不到有关此行为的任何文档,并且(如Paul S.建议的那样), Chrome权限列表上似乎没有该权限的“特权”。

Tested in Chrome 40.0.2214.115m, 43.0.2357.124m and 48.0.2547.0-dev. 已在Chrome 40.0.2214.115m,43.0.2357.124m和48.0.2547.0-dev中测试。

What I found out 我发现了什么

I initially thought this was a security feature, mostly because Firefox behaves that way. 我最初以为这是一种安全功能,主要是因为Firefox的行为方式如此。

In ran an equivalent test in Firefox by putting the event listener in a separate file that could be loaded via mozIJSSubScriptLoader : 通过将事件侦听器放置在可以通过mozIJSSubScriptLoader加载的单独文件中,在Firefox中进行了等效测试:

test.js: test.js:

 (function() { window.addEventListener('xyz', function(ev) { console.log('after dispatch:'); console.log(ev.detail); }); })(); 

firefox.js: firefox.js:

 (function() { var mozIJSSubScriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"].getService(Components.interfaces.mozIJSSubScriptLoader); window.addEventListener('load', function load(event) { window.removeEventListener('load', load); window.gBrowser.addEventListener('DOMContentLoaded', function(event) { mozIJSSubScriptLoader.loadSubScript('chrome://my-extension/content/test.js', window.content, 'UTF-8'); // JSON-serializable data var e = new CustomEvent('xyz', { detail: { x: 42, name: 'Schroedinger' } }); console.log('before dispatch:') console.log(e.detail); window.content.dispatchEvent(e); // non-JSON-serializable data e = new CustomEvent('xyz', { detail: { x: 42, name: 'Schroedinger', func: function(){} } }); console.log('before dispatch:'); console.log(e.detail); window.content.dispatchEvent(e); }); }); })(); 

Result: 结果:

控制台日志

(Note that the error occurs twice.) (请注意该错误发生两次。)

So in Firefox it doesn't even matter what detail contains - as long as it comes from an extension, the page is not allowed to access it. 因此,在Firefox中,甚至包含什么detail都不重要-只要它来自扩展程序,就不允许该页面访问它。
Looks like a security feature to me. 在我看来,这是一项安全功能。

The reason I put the above in a quote is because this is somewhat different in Chrome! 我将以上内容引用为引号的原因是,这在Chrome中有些不同!

After some deeper investigation it looks like although the extension and the page share the DOM tree, they exist in two different contexts. 经过更深入的研究后,尽管扩展名和页面共享DOM树,但它们存在于两个不同的上下文中。
I don't know whether this is actually a security feature or just a technical consequence, but this, of course, has the consequence that only clonable objects can be passed back and forth. 我不知道这实际上是安全功能还是技术上的后果,但这当然会导致只能来回传递可克隆对象的结果。

What puzzles me though is the fact that the operation silently fails, when, according to the HTML standard, §2.7.5 (structured clone) , the entire operation should fail with an error: 不过,令我感到困惑的是,该操作默默地失败了,根据HTML标准§2.7.5(结构化克隆) ,整个操作应该失败并出现错误:

↪ If input is another native object type (eg Error, Function) input如果输入是另一种本机对象类型(例如,错误,函数)
↪ If input is a host object (eg a DOM node) input如果输入是宿主对象(例如DOM节点)
Throw a DataCloneError exception and abort the overall structured clone algorithm. 引发DataCloneError异常并中止整体结构化克隆算法。

Workaround 解决方法

I ended up using a fairly easy (although not so pretty) workaround: 我最终使用了一个相当简单(尽管不是很漂亮)的解决方法:
In Chrome, there's no equivalent to mozIJSSubScriptLoader , but you're allowed to append <script> tags to a page from within your extension (you're not allowed to do that in FF). 在Chrome浏览器中,没有等效于mozIJSSubScriptLoader ,但是您可以在扩展程序内将<script>标记附加到页面上(不允许在FF中执行此操作)。
Together with chrome.extension.getURL , that can be used to run a JS file packaged with the extension in the context of the page: chrome.extension.getURL一起,可用于在页面上下文中运行与扩展名打包在一起的JS文件:

(function()
{
    var script = document.createElement('script');
    script.src = chrome.extension.getURL('extension.js');
    document.head.appendChild(script);
})();

Of course that requires that 当然需要

"web_accessible_resources": [ "extension.js" ]

is set in manifest.json , which isn't pretty, but shouldn't be an actual problem. 是在manifest.json中设置的,虽然不漂亮,但这不是实际问题。

The drawback of this is, of course, that from within extension.js you no longer have access to any chrome API your extension has access to, but in my case I didn't need that. 当然,这样做的缺点是,在extension.js您不再有权访问您的扩展程序可以访问的任何chrome API,但就我而言,我并不需要它。 It wouldn't be too difficult to set up a proxy via CustomEvent for that though, as the biggest part of the Chrome API only requires and returns data that is clonable. 它不会是太难通过设立一个代理CustomEvent的,虽然,作为Chrome的API中最重要的部分只需要返回的数据可克隆的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM