I'm currently developing a Google Chrome extension to add some convenience to a website I'm required to use.
I need to interact with the on-page JS, and in order to do so, I append a script
tag to the document head, containing a custom event listener from where I can access whatever I need.
Since I have to pack the listener code inside strings, embedding things like
{
html: '<div onclick="doSomething(this, \'someName\')"></div>'
}
is a real pain.
Now, since there's a cool thing called CustomEvent
, I thought I could use that instead to fire data forth and back between the page and my extension.
But it seems that as soon as CustomEvent.detail
contains a reference to something non-JSON-serializable, the entire detail
field is set to null
as soon as the Event crosses the boundary between my extension and the page.
Example
Script (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
e = new CustomEvent('xyz', { detail: { x: 42, name: 'Schroedinger', func: function(){} } });
console.log('before dispatch:');
console.log(e.detail);
window.dispatchEvent(e);
})();
Output:
before dispatch:
Object {x: 42, name: "Schroedinger"}
after dispatch:
Object {x: 42, name: "Schroedinger"}
before dispatch:
Object {x: 42, name: "Schroedinger", func: function (){}}
after dispatch:
null
This looks like a silently failing serialization to me, but I was unable to find any documentation about CustomEvent.detail
being serialized at any point.
It could also be a security measure, but again, I was unable to find any documentation mentioning it, especially not the Chrome permissions list.
Tested in Chrome 40.0.2214.115m and 43.0.2357.124m.
Best How To :
What I found out
Alright, so after a lot of trial and error, I can only assume that this is indeed a security feature.
In ran an equivalent test in Firefox by putting the event listener in a separate file that could be loaded via mozIJSSubScriptLoader
:
test.js:
(function()
{
window.addEventListener('xyz', function(ev)
{
console.log('after dispatch:');
console.log(ev.detail);
});
})();
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 message 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.
In light of that, I would be very surprised if Chrome's behaviour was not a security measure.
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).
Together with chrome.extension.getURL
, that can be used to run a JS file packaged with the extension in the context of the page:
(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.
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. 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 JSON-serializable.