This function provides a safe way to expose a function from a privileged scope to a less-privileged scope. In this way privileged code, such as an extension, can share code with less-privileged code like a normal web page script. A function exported from privileged to less-privileged code can be called from the less privileged code's context.
The function has access to its surrounding closure just as if it were being called in the privileged context.
The exported function does not have to be added to the less privileged code's global window object: it can be exported to any object in the target scope.
exportFunction()
is made available as a global in sandboxes which have the wantExportHelpers
option set in the Sandbox()
constructor. This includes Add-on SDK content scripts.
To understand what happens if the functions you export accept arguments, see Exporting functions that take arguments below.
Components.utils.exportFunction(func, targetScope[, options]);
func : function
targetScope : object
options : object
defineAs
: determines the name of the function in targetScope
. If this is omitted, you need to assign the return value of exportFunction()
to an object in the target scope.allowCallbacks
: deprecated/redundant from Firefox 34. This option allows the exported function to accept callbacks as arguments. Boolean, defaulting to false
. This option is new in Firefox 33. From Firefox 34 onwards this option has no effect: the exported function is always able to accept callbacks as arguments.allowCrossOriginArguments
: do not check that arguments to the exported function are subsumed by the caller: this allows the caller to pass objects with a different origin into the exported function, which can then use its privileged status to make cross-origin requests with them. Boolean, defaulting to false
. This option is new in Firefox 34.The placeholder function which has been created in the target context.
Until Firefox 34, any arguments that the function takes are structured-cloned across the security boundary unless they are native objects such as DOM nodes. Because structured cloning does not clone functions, this meant that the function may not return a function, and by default, may not take any functions as arguments. However, in Firefox 33, you could use the allowCallbacks
option to enable the function to accept callbacks.
From Firefox 34 onwards, any arguments passed into the function are not cloned. Instead, they are passed through to the privileged scope as Xrays.
While cloning creates a copy of an object, an Xray for an object refers to the original, so any changes to the argument that are made in the exported function will affect the original object that was passed in:
// privileged scope: for example, a content script function changeMyName(user) { user.name = "Bill"; } exportFunction(changeMyName, contentWindow, { defineAs: "changeMyName" });
// less-privileged scope: for example, a page script var user = {name: "Jim"}; var test = document.getElementById("test"); test.addEventListener("click", function() { console.log(user.name); // "Jim" window.changeMyName(user); console.log(user.name); // "Bill" }, false);
Note that this is subject to the normal rules of Xrays: for example, an expando property added to a DOM node will not be visible in the original object.
Xrays provide a filtered view of the original object. For the full details refer to the documentation for Xray vision, but for example: functions are not visible in the Xrays of JavaScript Object
types. If you need unfiltered access to the original, you can waive Xrays:
// privileged scope: for example, a content script function logUser(user) { // console.log(user.getUser()); // error console.log(user.wrappedJSObject.getUser()); // "Bill" } exportFunction(logUser, contentWindow, { defineAs: "logUser" });
// less-privileged scope: for example, a page script var user = {getUser: function() {return "Bill";}} var test = document.getElementById("test"); test.addEventListener("click", function() { window.logUser(user); }, false);
If functions are given as arguments, these are also passed as Xrays. Because you can call Function
Xrays just like normal functions, this means that passing callbacks into the exported function just works, making the allowCallbacks
option redundant:
// privileged scope: for example, a content script function logUser(getUser) { console.log(getUser()); // "Bill" } exportFunction(logUser, unsafeWindow, { defineAs: "logUser" });
// less-privileged scope: for example, a page script function getUser() { return "Bill"; } var test = document.getElementById("test"); test.addEventListener("click", function() { window.logUser(getUser); }, false);
When the exported function is called each argument, including this
, is checked to make sure that the caller subsumes that argument. This prevents passing cross-origin objects (like Window
or Location
) to privileged functions, since the privileged code will have full access to those objects and might unintentionally do something dangerous. This provision can be overridden by passing { allowCrossOriginArguments: true }
to exportFunction
.
This add-on script defines a function, then exports it to a content window:
// addon-script.js var salutation = "hello "; function greetme(user) { return salutation + user; } Components.utils.exportFunction(greetme, contentWindow, {defineAs: "foo"});
Instead of using defineAs
, the script can assign the result of exportFunction
to an object in the target scope:
// addon-script.js var salutation = "hello "; function greetme(user) { return salutation + user; } contentWindow.foo = Components.utils.exportFunction(greetme, contentWindow);
Either way, code running in the content window's scope can now call the function:
// page-script.js var greeting = foo("alice"); console.log(greeting); // "hello alice"
Instead of attaching the function to the target's global window
object, the caller can attach it to any other object in the target context. Suppose the content window defines a local variable bar
:
// page-script.js var bar = {};
Now the add-on script can attach the function to bar
:
// addon-script.js Components.utils.exportFunction(greetme, contentWindow.bar, {defineAs: "greetme"});
// page-script.js var value = bar.greetme("bob"); console.log(value); // "hello bob"