Firefox's Same-origin policy is an implementation of some standards from WHATWG that is stricter about cross-origin windows and iframes than Chrome and Safari.
When the two documents do not have the same origin, these references provide very limited access to Window and Location objects, as described in the next two sections.
I ran into this functionality via a "permission denied"
error when calling window.open()
in a
Firefox extension.
In mediathread-firefox, the content page's origin
could be, for example, images.google.com
or
en.wikipedia.org
. I wasn't clear about the
pop-up window's origin. According to the Same-origin
policy's documentation, my about:blank
pop-up window should inherit the parent window's origin.
However, according to someone on irc, this inheritance rule
only applies to iframes.
Inherited origins
Content from about:blank, javascript: and data: URLs inherits the origin from the document that loaded the URL, since the URL itself does not give any information about the origin.
"Limited access to the Window object" described above
isn't sufficient to do what the extension was trying to do:
Add DOM elements to window.document
.
I couldn't think of a way to get around this security
policy. Windows, origins, and CORS rules get muddled up
when running in a browser extension's content script.
Because I knew it was possible and permitted to do
this kind of thing from the main add-on code
(in mediathread-firefox, this is everything in the
src/
directory, as opposed to
data/
), I decided to see what was required
to make it work that way instead.
It's not straightforward, since we don't have the DOM
available on the add-on script side. But we can
communicate between the two threads with
port.emit()
and port.on()
.
See the diagram here:
Content_Scripts#Communicating_with_the_add-on.
The Add-on SDK has a few different modules to choose from
to open a pop-up style dialog box:
windows,
panel. I chose to use panel, since calling
windows.open()
with this API opened a
full browser window with toolbars and scrollbars, and
I couldn't figure out how to hide them.
I'm transferring the HTML data that needs to be displayed
in the pop-up with the collect
event
from the content script to the add-on script. The add-on
script then opens a Panel that contains a content script.
I use the form-payload
event to transfer
the form's HTML from the add-on script to the Panel's
content script. It's confusing! On the add-on script
side:
var worker = tabs.activeTab.attach({ contentScriptFile: [ self.data.url('./lib/jquery-2.1.4.min.js'), self.data.url('./lib/URI.js'), self.data.url('./src/collect-panel.js'), self.data.url('./src/common/settings.js'), self.data.url('./src/common/host-handler.js'), self.data.url('./src/common/asset-handler.js'), self.data.url('./src/common/collect.js'), self.data.url('./src/init.js') ], contentScriptWhen: 'ready' }); worker.port.on('collect', function(payload) { var panel = Panel({ width: 400, height: 400, contentURL: self.data.url('./collect-popup/index.html'), contentStyleFile: self.data.url('./collect-popup/style.css'), contentScriptFile: [ self.data.url('./lib/jquery-2.1.4.min.js'), self.data.url('./collect-popup/popup.js') ] }); panel.port.on('collect-cancel', function() { panel.hide(); }); panel.port.on('collect-submit', function() { // Tell the main content script about the submission so // it can display a notice. worker.port.emit('collect-submit'); panel.hide(); }); panel.show(); panel.port.emit('form-payload', payload.form); });
The pop-up Panel's content script:
$(document).ready(function() { self.port.on('form-payload', function(form) { var $form = $(form); $form.find('input.cont').remove(); $form.find('input.analyze').remove(); $form.append( '<input type="hidden" value="cont" name="button">'); $form.append(''); $form.append(''); $form.append( '<div class="help-text">' + 'Clicking "Save" will add this item to your ' + 'Mediathread collection and return you to ' + 'collecting.' + '</div>'); $('#bucket-wrap').append($form); $('#submit-cancel').click(function() { self.port.emit('collect-cancel'); }); $('form').on('submit', function() { self.port.emit('collect-submit'); }); }); });