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 userScripts.execute() API proposal #540

Merged
merged 3 commits into from
Mar 21, 2024

Conversation

EmiliaPaz
Copy link
Contributor

Proposal for adding <browser>.userScripts.execute() API to allow extensions to inject user scripts programmatically into web contents.

@Getfree
Copy link

Getfree commented Feb 7, 2024

What would be the function's arguments?
Does it accept a "code" parameter?
Does it accept a "runAt" parameter?
Is it going to be affected by the CSP of the target page?
Will the script run in the global scope or a local scope? (i.e. can the script create global variables?)

@tophf
Copy link

tophf commented Feb 8, 2024

Hopefully you don't mind me posting the answers to these questions which I found while researching the PR as it's something I'll use in my extensions as well:

affected by the CSP of the target page?

chrome.userScripts.register runs code in the MAIN world despite github.com's script-src CSP, so injection itself is not affected and the same should work for chrome.userScripts.execute, but a clarification for the MAIN world would be helpful anyway as the documentation mentions CSP exemption only for the USER_SCRIPT world, so it could clarify that the code will be injected in the MAIN world regardless of page's CSP, while things created by this code will be subject to the CSP of the page e.g. a style element will be blocked by a restrictive style-src CSP.

arguments

It's UserScriptInjection object in the code of the PR, which expectedly looks like a hybrid of chrome.userScripts.register and chrome.scripting.executeScript.

code

Yes, it's js: [{code: 'console.log(1)'}] same as chrome.userScripts.register, see ScriptSource definition.

runAt

It's a boolean injectImmediately same as chrome.scripting.executeScript, false (by default) means document_idle.

global scope or a local scope

chrome.userScripts.register uses global scope, so it should be the same, but either way we can create global variables explicitly like window.foo = 123, which would be necessary when func is implemented in the future as it'll run in an IIFE.

@EmiliaPaz
Copy link
Contributor Author

Thanks @Getfree for taking a look at the proposal. And special thanks to @tophf for providing answers. These are mostly correct, but I am expanding them more:

What would be the function's arguments?

userScripts.execute(injection: UserScriptInjection). You can see it in more detail on the API Schema section in the proposal.

Does it accept a "code" parameter?

Yes, injection has a User Script API ScriptSource parameter which includes code.

Does it accept a "runAt" parameter?

No. By default in run at document_idle but can be set to injectImmediately

Is it going to be affected by the CSP of the target page?

Depends on the world it's injected to. User script can be registered/executed in the USER_SCRIPT or MAIN world. The USER_SCRIPT world is an execution environment specific to user scripts and is exempt from the page's CSP. The MAIN world is the execution environment shared with the host page's JavaScript, and thus follows the page's CSP.

@tophf , not sure I follow your answer here. Extension can decide the world in which a script is registered. Script registered in MAIN world will follow the page's CSP, and when registered in USER_SCRIPT world will follow the extension CSP or a customized one For example:

  • script1 is registered in MAIN world and it does not have access to eval() since page csp dissallows eval().
  • script2 is registered in USER_SCRIPT world. Extension can configure the USER_SCRIPT world to allow eval(), does script gets access to eval()

Will the script run in the global scope or a local scope? (i.e. can the script create global variables?)

It runs on the global scope.

@tophf
Copy link

tophf commented Feb 13, 2024

@tophf , not sure I follow your answer here.

I was referring to the existing problem in the currently used workaround for MV3 to run arbitrary code by creating a script element inside code that already runs in the MAIN world - this workaround didn't work with a strict CSP of the page. With the new userScripts API the code will run regardless of the CSP of the page (it still affects the artifacts created by this code). I think this is an important info that could be used for an explicit clarification in the documentation.

proposals/user-scripts-execute-api.md Outdated Show resolved Hide resolved
proposals/user-scripts-execute-api.md Outdated Show resolved Hide resolved
proposals/user-scripts-execute-api.md Outdated Show resolved Hide resolved
proposals/user-scripts-execute-api.md Outdated Show resolved Hide resolved
Addressed @Rob--W comments:
  - Add errors property to InjectionResult
  - Nits

Co-authored-by: Rob Wu <[email protected]>
proposals/user-scripts-execute-api.md Outdated Show resolved Hide resolved
proposals/user-scripts-execute-api.md Outdated Show resolved Hide resolved
Copy link
Contributor

@rdcronin rdcronin left a comment

Choose a reason for hiding this comment

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

LG; thanks, Emilia!


### Existing Workarounds

Developers can utilize `userScripts.register()` to inject JavaScript into a known set of hosts, but this doesn't cover the case of programmatic injections to a specific target or one-time injections.
Copy link
Contributor

Choose a reason for hiding this comment

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

They can also use scripting.executeScript() today, but that doesn't allow for remotely-hosted code.


### Add func to `ScriptSource`

Add `func` and `args` property to `ScriptSource` to specify a JavaScript function to inject. This could be used both by `userScripts.register()` and `userScripts.execute()`.
Copy link
Contributor

Choose a reason for hiding this comment

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

It's worth highlighting that this isn't as useful in this context, since the extension can trivially construct a string that enables this same functionality. Additionally, if the extension is using func + args, it isn't using remotely-hosted code, and could also use scripting.executeScript(). The only case it would need to use this in that scenario would be to inject in the user script world.

Copy link

Choose a reason for hiding this comment

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

...also when Chrome makes args use the structured clone algorithm.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yep, that's a reasonable point, too

@EmiliaPaz
Copy link
Contributor Author

This PR has been approved and is ready to merge (I don't have the power to do so :) )

@xeenon xeenon merged commit 1d3d2ad into w3c:main Mar 21, 2024
3 checks passed
github-actions bot added a commit that referenced this pull request Mar 21, 2024
SHA: 1d3d2ad
Reason: push, by xeenon

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
github-actions bot added a commit that referenced this pull request Mar 21, 2024
SHA: 1d3d2ad
Reason: push, by xeenon

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@Rob--W
Copy link
Member

Rob--W commented Nov 12, 2024

I filed https://bugzilla.mozilla.org/show_bug.cgi?id=1930776 to track the implementation of this in Firefox. Is there any Chromium bug for this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants