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

Proposal: dom.execute() #678

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all 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
174 changes: 174 additions & 0 deletions proposals/dom_execute.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# Proposal: browser.dom.execute()
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the motivation to use the dom namespace instead of the scripting namespace? Considering it's similarities with scripting.executeScript and the seemingly lack of connection with the DOM. Considering execute(), createPort() and also openOrClosedShadowRoot(), would it not make sense to use browser.document instead?


**Summary**

This API allows an isolated world (such as a content script world or user
script world) to synchronously execute code in the main world of the document
Copy link
Member

Choose a reason for hiding this comment

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

Is this proposal limited to the main world? I was imagining this API to also be used from content scripts to execute in a user script world.

its injected in.

**Document Metadata**

**Author:** rdcronin

**Sponsoring Browser:** Chromee
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
**Sponsoring Browser:** Chromee
**Sponsoring Browser:** Chrome


**Contributors:** Rob--W, ...

**Created:** 2024-06-24

**Related Issues:** <TODO>

## Motivation

### Objective

This allows isolated worlds to synchronously inject in the main world. This is
both beneficial by itself and also a prerequisite for our current plan to
implement inter-world communication (the latter of which will be a separate
proposal).

#### Use Cases

##### Executing script in the main world

Today, extensions can execute script in the main world by:
* Leveraging scripting.executeScript() or registering content scripts or user
scripts, or
* Appending a new script tag to the document

The former is heavily asynchronous or requires knowledge of whether to inject
beforehand (to register a script). The latter is very visible to the site and
Copy link
Member

Choose a reason for hiding this comment

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

The latter is also asynchronous, because the default CSP of content scripts in MV3 forbids inline script elements, leaving the only option to be <script src> with extension URL as src.

has more potential side effects. While there's no way to fully "hide" a script
from a site, it is desirable to avoid adding DOM elements (which could
interfere with sites with certain properties).

### Known Consumers

User script managers have expressed an interest in this API, but this is also
useful to any extension that wishes to execute script in the main world while
leveraging information from the isolated world.

## Specification

### Schema

```
declare namespace dom {
interface ScriptInjection {
func: () => any;
args: any[];
};

export function execute(
injection: ScriptInjection
): any
}
```

### Behavior

#### Function execution

The function is executed _synchronously_ in the main world. The return value
is the serialized return value from the function execution.

#### Cross-world contamination

To prevent any "piercing" of the different worlds, all pieces are serialized in
their original world, then a copy is deserialized in the target world.
Otherwise, there would be a risk of sharing variables between worlds (even when
non-obvious, such as by accessing the prototype of an item).

##### Argument serialization

Arguments are serialized and deserialized using the Structured Cloning
algorithm. This allows for more flexibility than simply JSON conversion. In
the future, some arguments may have custom serialization.
Comment on lines +84 to +86
Copy link
Member

Choose a reason for hiding this comment

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

Have the proposal authors considered whether arguments should support transferable objects?


##### Function serialization

The injected function is serialized to and parsed from a string. Bound
parameters are not supported. Arguments must be curried in through the `args`
key.

##### Return value serialization

Like arguments, the return value of the execution is serialized and
deserialized using the Structured Cloning algorithm.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
deserialized using the Structured Cloning algorithm.
deserialized using the Structured Cloning algorithm.
Thrown errors are also serialized using the Structured Cloning algorithm.
When an input parameter or return value cannot be serialized, an error is thrown synchronously.
Notably, `Promise` instanced such as returned by `async function` cannot be serialized.

I elaborated on the serialization semantics. Although it would be nice to support Promise, I also recognize the complexity associated with supporting it, so I'm explicitly calling out non-support for Promise. I'm willing to consider support in the future, but note that a future expansion (dom.createPort proposal) also allows for async communication.


##### Injected script privileges

The injected script has the same privileges has other script in the main world.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
The injected script has the same privileges has other script in the main world.
The injected script has the same privileges as other scripts in the main world.

This goes for API access, Content Security Policy, origin access, and more.

##### CSP Application

The injected script is not considered inline and does not have a corresponding
Copy link
Contributor

Choose a reason for hiding this comment

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

Would this also apply if JavaScript in general is disabled? With script-src set to "none" for example.

source; as such, CSP restrictions on script-src (and similar) do not apply.
However, since the script executes in the main world, other CSP restrictions
(including connect-src and which sources may be added to the document, among
many others) *may* apply to the injected script.

### New Permissions

There are no new permissions for this capability. This does not allow any new
data access, since it is only accessible once the extension has already
injected a script into the document. Extensions can also already interact with
the main world of the document through either appending script tags or directly
injecting with registered content or user scripts, or the
scripting.executeScript() method.

### Manifest File Changes

There are no necessary manifest changes.

## Security and Privacy

Copy link
Contributor

Choose a reason for hiding this comment

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

Having this API in it's current form would not make sense outside isolated worlds.

Suggested change
### Availability
Initially `dom.execute()` will only be available in isolated worlds (content scripts and user scripts).

### Exposed Sensitive Data

This does not result in any new data being exposed.

### Abuse Mitigations

This doesn't enable any new data access. To prevent any risk of cross-world
interaction, all data that passes between worlds is serialized in the original
world, then deserialized (creating a new copy) in the target world. The
executed script has no additional capabilities or APIs; it has the same
privilege as the document's own main world code.

### Additional Security Considerations

N/A.

## Alternatives

### Existing Workarounds

Extension developers can inject in the main world through registered content or
user scripts, along with the scripting.executeScript() API. They can also
inject code in the main world by adding a script tag to the DOM. However, the
API-powered options are asynchronous, and a script tag is inherently visible to
other scripts running in the main world.

### Open Web API

The open web is unaware of the concept of multiple Javascript worlds, so this
wouldn't belong as an open web API.

## Implementation Notes

N/A.
Copy link
Member

Choose a reason for hiding this comment

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

The whole proposal sketches this as a feature designed for isolated worlds. While the dom namespace is available to content scripts, it is not restricted to content scripts - regular extension documents can also access the API.

Should we restrict the availability of this new functionality to content scripts only, or do you want it to have the same availability as the current dom namespace?


## Future Work

### Inter-world communication

This API is a necessary first step before introducing inter-world communication
channels. We plan to do that alongside the implementation for this API.

### Specifying a target world
Copy link
Contributor

Choose a reason for hiding this comment

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

Which means not specifying the target world in the future would default to the main world. If that is a downside or if explicitly specifying a target world right from the start is preferred specifying the target world right now world be good.


For now, this API is intended to let scripts running in content or user script
worlds inject in the main world. In the future, we will expand this to allow a
script to inject in additional other worlds, such as a content script injecting
in a user script world.