Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add documentation of component.utils exportFunction and cloneInto #30877

Merged
merged 17 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
---
title: cloneInto()
slug: Mozilla/Add-ons/WebExtensions/Content_scripts/cloneInto
page-type: webextension-api-function
browser-compat: webextensions.api.contentScriptGlobalScope.cloneInto
---

{{AddonSidebar()}}

This function provides a safe way to take an object defined in a privileged scope and create a [structured clone](/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm) of it in a less-privileged scope. It returns a reference to the clone:

```js
var clonedObject = cloneInto(myObject, targetWindow);
```

You can then assign the clone to an object in the target scope as an expando property, and scripts running in that scope can access it:

```js
targetWindow.foo = clonedObject;
```

This enables privileged code, such as an extension, to share an object with less-privileged code, such as a web page script.

## Syntax

```js-nolint
let clonedObject = cloneInto(
obj, // object
targetScope, // object
options // object
rebloor marked this conversation as resolved.
Show resolved Hide resolved
);
```

### Parameters

- `obj`
- : `object`. The object to clone.
- `targetScope`
- : `object`. The object to attach the object to.
- `options` {{optional_inline}}
- : `object`. Options for the function.
- `cloneFunctions` {{optional_inline}}
- : `boolean`. Whether the object's functions should be cloned. Default to `false`. Cloned functions have the same semantics as functions exported using [`exportFunction`](/en-US/docs/Mozilla/Add-ons/WebExtensions/API/Content_scripts/exportFunction). See [Cloning objects that have functions](#Cloning_objects_that_have_functions). {{optional_inline}}
- `wrapReflectors` {{optional_inline}}
- : `boolean`. Whether DOM objects should be passed by reference instead of cloned. DOM objects are usually not clonable. Defaults to `false`. See [Cloning objects that contain DOM elements](#Cloning_objects_that_contain_DOM_elements).

### Return Value

A reference to the cloned object.

## Examples

This content script creates an object, clones it into the content window and makes it a property of the content window global:

```js
// content script
var addonScriptObject = { greeting: "hello from your extension" };
window.addonScriptObject = cloneInto(addonScriptObject, window);
```

Scripts running in the page can access the object:

```js
// page script
button.addEventListener(
"click",
function () {
console.log(window.addonScriptObject.greeting); // "hello from your extension"
},
false,
);
```

Of course, you don't have to assign the clone to the window itself; you can assign it to some other object in the target scope:

```js
// Content script
window.foo.addonScriptObject = cloneInto(addonScriptObject, window);
```

You can also pass it into a function defined in the page script. Suppose the page script defines a function like this:

```js
// page script
function foo(greeting) {
console.log("they said: " + greeting.message);
}
```

The content script can define an object, clone it, and pass it into this function:

```js
// content script
var addonScriptObject = { message: "hello from your extension" };
window.foo(cloneInto(addonScriptObject, window)); // "they said: hello from your extension"
```

### Cloning objects that have functions

If the object to clone contains functions, you must pass the `{cloneFunctions:true}` flag, or you get an error. If you do pass this flag, then functions in the object are cloned using the same mechanism used in [`Components.utils.exportFunction`](/en-US/docs/Mozilla/Add-ons/WebExtensions/API/components/utils/exportFunction):

```js
// content script
var addonScriptObject = {
greetme: function () {
alert("hello from your extension");
},
};
window.addonScriptObject = cloneInto(addonScriptObject, window, {
cloneFunctions: true,
});
```

```js
// page script
var test = document.getElementById("test");
test.addEventListener(
"click",
function () {
window.addonScriptObject.greetme();
},
false,
);
```

### Cloning objects that contain DOM elements

By default, if the object you clone contains objects reflected from C++, such as DOM elements, the cloning operation fails with an error. If you pass the `{wrapReflectors:true}` flag, then the object you clone contains these objects:

```js
// content script
var addonScriptObject = {
body: window.document.body,
};
window.addonScriptObject = cloneInto(addonScriptObject, window, {
wrapReflectors: true,
});
```

```js
// page script
var test = document.getElementById("test");
test.addEventListener(
"click",
function () {
console.log(window.addonScriptObject.body.innerHTML);
},
false,
);
```

Access to these objects in the target scope is subject to the normal [script security checks](https://firefox-source-docs.mozilla.org/dom/scriptSecurity/index.html).

{{WebExtExamples}}

## Browser compatibility

{{Compat}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
---
title: exportFunction()
slug: Mozilla/Add-ons/WebExtensions/Content_scripts/exportFunction
page-type: webextension-api-function
browser-compat: webextensions.api.contentScriptGlobalScope.exportFunction
---

{{AddonSidebar()}}

This function provides a safe way to expose a function from a privileged scope to a less-privileged scope. This enables privileged code, such as an extension, to share code with less-privileged code, such as a standard 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 as if called in the privileged context.

The exported function doesn't need to be added to the less privileged code's global window object; it can be exported to any object in the target scope.

See [Exporting functions that take arguments](#Exporting_functions_that_take_arguments) to understand what happens if the functions you export accept arguments.

## Syntax

```js-nolint
let exportFunctionuating = exportFunction(
func, // function
targetScope, // object
options // object
rebloor marked this conversation as resolved.
Show resolved Hide resolved
);
```

### Parameters

- `func`
- : `function`. The function to export.
- `targetScope`
- : `object`. The object to attach the function to. This doesn't have to be the global window object; it could be an object in the target window or created by the caller.
- `options` {{optional_inline}}

- : `object`. Options for the function.

- `defineAs` {{optional_inline}}
- : `string`. The name of the function in `targetScope`. If omitted, you need to assign the return value of `exportFunction()` to an object in the target scope.
- `allowCrossOriginArguments` {{optional_inline}}
- : `boolean`. Whether to check that arguments to the exported function are [subsumed](https://firefox-source-docs.mozilla.org/dom/scriptSecurity/index.html#subsumes) 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 the object. Defaults to `false`.

### Return value

The placeholder function created in the target context.

## Exporting functions that take arguments

Any arguments passed into the function are not cloned. Instead, they are passed through to the privileged scope as [Xrays](https://firefox-source-docs.mozilla.org/dom/scriptSecurity/xray_vision.html).

### Modifying the argument

An Xray for an object refers to the original. Any changes to the argument made in the exported function affect the original object passed in. For example:

```js
// ad// privileged scope: for example, a content script
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is ad// doing here and in other places below?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No idea, some search/replace error, but a bit of head-scratcher.

function changeMyName(user) {
user.name = "Bill";
}
exportFunction(changeMyName, window, {
defineAs: "changeMyName",
});
```

```js
// ad// 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,
);
```

This behavior is subject to the normal rules of Xrays. For example, an expando property added to a DOM node isn't visible in the original object.

### Xray filtering and waiving

Xrays provide a filtered view of the original object. For example, functions aren't visible in the Xrays of JavaScript [`Object`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) types. If you need unfiltered access to the original, you can [waive Xrays](https://firefox-source-docs.mozilla.org/dom/scriptSecurity/xray_vision.html#waiving-xray-vision):

```js
// ad// privileged scope: for example, a content script
function logUser(user) {
// console.log(user.getUser()); // error
console.log(user.wrappedJSObject.getUser()); // "Bill"
}
exportFunction(logUser, window, {
defineAs: "logUser",
});
```

```js
// ad// 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,
);
```

See [Xray vision](https://firefox-source-docs.mozilla.org/dom/scriptSecurity/xray_vision.html) in the Firefox Source Tree documentation for more information.

### Passing functions as arguments

If functions are given as arguments, these are also passed as Xrays. As you can call `Function` Xrays like normal functions, this means that passing callbacks into the exported function works:

```js
// ad// privileged scope: for example, a content script
function logUser(getUser) {
console.log(getUser()); // "Bill"
}
exportFunction(logUser, unsafeWindow, {
defineAs: "logUser",
});
```

```js
// ad// 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 ensure the caller [subsumes](https://firefox-source-docs.mozilla.org/dom/scriptSecurity/index.html#subsumes) that argument. This prevents passing cross-origin objects (such as `Window` or `Location`) to privileged functions, as the privileged code has full access to those objects and could unintentionally do something dangerous. This provision can be overridden by passing `{ allowCrossOriginArguments: true }` to `exportFunction`.

## Examples

### Export to global scope

This script defines a function and then exports it to a content window:

```js
// extension-script.js
var salutation = "hello ";
function greetme(user) {
return salutation + user;
}
exportFunction(greetme, window, { defineAs: "foo" });
```

Instead of using `defineAs`, the script can assign the result of `exportFunction` to an object in the target scope:

```js
// extension-script.js
var salutation = "hello ";
function greetme(user) {
return salutation + user;
}
window.foo = exportFunction(greetme, window);
```

Either way, code running in the content window's scope can call the function:

```js
// 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`:

```js
// page-script.js
var bar = {};
```

Now the extension script can attach the function to `bar`:

```js
// extension-script.js
exportFunction(greetme, window.bar, {
defineAs: "greetme",
});
```

```js
// page-script.js
var value = bar.greetme("bob");
console.log(value);
// "hello bob"
```

{{WebExtExamples}}

## Browser compatibility

{{Compat}}
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ If a content script needs to use a JavaScript library, then the library itself s
```

> [!NOTE]
> Firefox _does_ provide some APIs that enable content scripts to access JavaScript objects created by page scripts, and to expose their own JavaScript objects to page scripts.
> Firefox provides [cloneInto()](/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts/cloneinto) and [exportFunction()](/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts/exportFunction) to enable content scripts to access JavaScript objects created by page scripts and expose their JavaScript objects to page scripts.
>
> See [Sharing objects with page scripts](/en-US/docs/Mozilla/Add-ons/WebExtensions/Sharing_objects_with_page_scripts) for more details.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ Firefox also provides APIs enabling content scripts to make objects available to

### exportFunction

Given a function defined in the content script, `exportFunction()` exports it to the page script's scope, so the page script can call it.
Given a function defined in the content script, [`exportFunction()`](/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts/exportFunction) exports it to the page script's scope, so the page script can call it.

For example, let's consider an extension which has a background script like this:

Expand Down Expand Up @@ -153,7 +153,7 @@ window.notify("Message from the page script!");

### cloneInto

Given an object defined in the content script, this creates a clone of the object in the page script's scope, thereby making the clone accessible to page scripts. By default, this uses the [structured clone algorithm](/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm) to clone the object, meaning that functions in the object are not included in the clone. To include functions, pass the `cloneFunctions` option.
Given an object defined in the content script, [cloneInto()](/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts/cloneinto) creates a clone of the object in the page script's scope, thereby making the clone accessible to page scripts. By default, this uses the [structured clone algorithm](/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm) to clone the object, meaning that functions in the object are not included in the clone. To include functions, pass the `cloneFunctions` option.

For example, here's a content script that defines an object that contains a function, then clones it into the page script's scope:

Expand Down