Components.utils.exportFunction

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.

Syntax

Components.utils.exportFunction(func, targetScope[, options]);

Parameters

func : function
The function to export.
targetScope : object
The object to attach the function to. This does not have to be the global window object: it could be any other object in the target window, or an object created by the caller.
options : object
Optional parameter that supplies additional options. The following options are currently defined:
  • 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.

Returns

The placeholder function which has been created in the target context.

Exporting functions that take arguments

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.

Modifying the argument

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.

Xray filtering and waiving

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);

Passing functions as arguments

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);

Cross-origin checking

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.

Example

Export to global scope

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"

Export to an existing local object

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"