The content (untrusted) document (including any IFRAMEs) can't directly run functions defined in extensions or in other privileged code loaded by XulRunner. Even if your privileged code assigns a function to the untrusted content window, as soon as the function tries to use/call something privileged you'll get a security error.
You can, however, have the privileged code add an Event Listener to the untrusted content window! With a little wrangling, you can build up a scheme for easily installing "privileged" functions into untrusted content -- i.e: breaking out of the security sandbox.
Here's an example of allowing the untrusted domWindow to perform a (privileged) cross-domain HTTP GET operation:
/*
Give the end-user this API:
xdomainGET(sURL,
function(
iHTTPStatusCode,
sHTTPStatusText,
sHTTPResponseText
) { ... })
*/
function install_xdomainGET_on_my_special_page(chromeWindow, domWindow) {
install_privileged_method(
/* this is the current browser chromeWindow (your XUL overlay is here!) */
chromeWindow,
/* this is the unstrusted domWindow containing your IFRAME */
domWindow,
/* the name of the method we're adding to the untrusted domWindow */
'xdomainGET',
/* This is the methodFactory */
function(priv) {
/* This is the method that will be installed onto the untrusted
domWindow. It takes two arguments, 'sURL' and 'cb'. 'sURL' is a
cross-domain HTTP URL which we want to be able to GET from the
untrusted domWindow. 'cb' is a callback function which returns
3 values to the caller... */
return function(sURL, cb) {
priv.call([sURL], function(rstatus, rdata, rerror){
cb(rstatus ? rdata.statusCode : 0, // iHTTPStatusCode
rstatus ? rdata.statusText : rerror, // sHTTPStatusText
rstatus ? rdata.responseText : null); // sHTTPResponseText
});
};
},
/* This is the privileged 'handler code. It has access to the
chromeWindow, and all of the privileged APIs that are available
there. (e.g: nsI*, XPCom, js-ctypes, etc) */
function (chromeWindow, args, cb) {
var [url] = args;
var xhr = new chromeWindow.XMLHttpRequest();
xhr.onload = function(){
cb(1, {
statusCode: this.status,
statusText: this.statusText,
responseText: this.responseText
}, null);
};
xhr.addEventListener('error', function(error_evt) {
cb(0, null, error_evt.error);
}, false);
xhr.open('get', url, true);
xhr.send();
}
);
}
And here's my library function which makes that possible. Basically, it does two things (from privileged code):
It installs a function into the unstrusted domWindow which providing the expected API to the end-user, which when called packages up the arguments (including a callback function) and raises a synthetic event. It organises this as a Method Factory so that the end-user can have whatever shape of API they desire (as long as it takes a callback!).
It installs an event handler on the untrusted window. When the untrusted window dispatches its synthetic event, the event handler catches it, unpacks the arguments performs the provided privileged code. It then calls the untrusted callback function provided within the event detail.
const Cc = Components.classes;
const Ci = Components.interfaces;
let consoleService = Cc["@mozilla.org/consoleservice;1"]
.getService(Ci.nsIConsoleService);
function LOG(msg) {
consoleService.logStringMessage("EXTENSION: "+msg);
}
// install a privileged method on the given (untrusted) 'target' window
var install_privileged_method = (function(){
var gensym = (function (){
var __sym = 0;
return function () { return '__sym_'+(__sym++); }
})();
return function (chromeWindow, target, slot, handler, methodFactory) {
try {
// initialise target window with ticket/callback-dict/namespace
target.__pmcache__ = target.hasOwnProperty('__pmcache__')
? target.__pmcache__
: { ticket_no: 0, callbacks: {}, namespace: gensym() };
// install the user factory-generated dispatcher method on
// the 'target' untrusted content window.
target[slot] = methodFactory({ call: function(fargs, fcb) {
try {
var ticket_no = target.__pmcache__.ticket_no++;
target.__pmcache__.callbacks[ticket_no] = fcb;
var cevent = target.document.createEvent("CustomEvent");
cevent.initCustomEvent(
target.__pmcache__.namespace+'.'+slot,
true, true, { fargs: fargs, ticket_no: ticket_no }
);
target.dispatchEvent(cevent);
} catch (ue) {
fcb(0, null, 'untrusted dispatcher error: '+ue);
}
}});
LOG("installed untrusted dispatcher for method '"+slot+"'.");
// Add an event listener to (untrusted) target window which
// listens for custom event generated by above dispatcher method.
target.addEventListener(
target.__pmcache__.namespace+'.'+slot,
function(cevent){
var ticket_no = cevent.detail.ticket_no;
var fargs = cevent.detail.fargs;
var fcb = target.__pmcache__.callbacks[ticket_no];
try {
handler(chromeWindow, fargs, fcb);
} catch (pe) {
fcb(0, null, 'privileged handler error: '+pe);
}
},
false,
true
);
LOG("installed privileged handler for method '"+slot+"'.");
} catch (ie) {
LOG("ERROR installing handler/factory for privileged "+
"method '"+slot+"': "+ie);
}
};
})();
Now that we've found a way to break out of the sandbox, we need to make sure that we only add that possibility to our 'trusted' web pages. (i.e: the URL where you've hosted your PHP).
I've been doing this in Firefox, so I'm dealing with one or more TabBrowser
XUL elements in the user interface. As you're using XulRunner, that might not be the case.
In order to find our 'trusted' page, we need to look at all current (and future) chromeWindows and install a 'load' handler on them.
let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
.getService(Ci.nsIWindowMediator);
let windows = wm.getEnumerator("navigator:browser");
while (windows.hasMoreElements()) {
let domWindow = windows.getNext().QueryInterface(Ci.nsIDOMWindow);
WindowListener.setupBrowserUI(domWindow);
}
wm.addListener(WindowListener);
where WindowListener
is defined as:
var WindowListener = {
setupBrowserUI: function(window, xulWindow, othWindow) {
window.gBrowser.addEventListener('load', my_load_handler, true);
},
tearDownBrowserUI: function(window) {
window.gBrowser.removeEventListener('load', my_load_handler, true);
},
onOpenWindow: function(xulWindow) {
let domWindow = xulWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
domWindow.addEventListener("load", function listener() {
domWindow.removeEventListener("load", listener, false);
var domDocument = domWindow.document.documentElement;
var windowType = domDocument.getAttribute("windowtype");
if (windowType == "navigator:browser")
WindowListener.setupBrowserUI(domWindow);
}, false);
},
onCloseWindow: function(xulWindow) { },
onWindowTitleChange: function(xulWindow, newTitle) { }
};
where my_load_handler
is defined as:
var my_load_handler = function (evt) {
try {
var browserEnumerator = wm.getEnumerator("navigator:browser");
while (browserEnumerator.hasMoreElements()) {
var browserWin = browserEnumerator.getNext();
var tabbrowser = browserWin.gBrowser;
var numTabs = tabbrowser.browsers.length;
for (var index = 0; index < numTabs; index++) {
var currentBrowser = tabbrowser.getBrowserAtIndex(index);
var domWindow = currentBrowser.contentWindow.wrappedJSObject;
// identify your target page...
if (domWindow.location.href == 'http://yourserver/yourpage') {
// install the privileged method (if it's not already there!)
if (!domWindow.hasOwnProperty('xdomainGET') {
install_xdomainGET_on_my_special_page(browserWin, domWindow);
}
}
}
}
} catch (e) {
LOG(e);
}
}