w3c / webextensions

Charter and administrivia for the WebExtensions Community Group (WECG)

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Proposal: userScripts.execute() method

newRoland opened this issue · comments

The user-scripts proposal mentions this as a potential future enhancement, nevertheless, I'm creating a formal proposal so that it can be discussed and tracked.

Context

The userScripts API addresses a significant gap that MV3 created for extensions that need to execute user-supplied JavaScript. Nonetheless, a gap persists for userscripts not driven by URLs. Many extensions activate userscripts through varied methods including gestures, keyboard shortcuts, context menus, voice, and more.

Issue was first mentioned by @Robbendebiene.

Proposal

A method to execute user-supplied javascript on the fly.

browser.userScripts.execute({code: "", world, allFrames, etc})

Security Considerations

The current User Scripts proposal already allows this capability. The extension can register the user script to run on all domains, and call a wrapped userscript on demand. For example, when the user triggers a gesture. This is inefficient and it would be harmful to users if developers had to resort to that.

A few extensions that would need this.

Cr×Mouse Chrome™ Gestures
Vimium [1]
AutoHotkey
modern scroll
Script Runner Pro
Foxy Gestures
Gesturefy
smartUp Gestures
Tampermonkey, Violentmonkey (edge cases like RegExp)
Surfingkeys [1]
Automa [1]

A method to execute user scripts on the fly.
browser.userScripts.execute({code: ""})

Firefox only userScripts in MV2 is replaced by scripting API in MV3 which has scripting.executeScript() method.

It is my understanding that the ability to pass a string to the scripting API is already agreed upon. Therefore, all its applicable methods should have the same ability.

@erosman

Is this confirmed? I was under the impression that they deliberately stripped away that parameter to prevent the execution of remote code.

Although, I don't see executeScript() mentioned in the proposal, I cant imagine that it would be allowed in register() method and not in executeScript().
Userscript managers also require executeScript() for their functions.

It would be good to mention it in the user scripts proposal.


Footnote

While the User Scripts API mentions userScripts namespace, I am under the impression that implementation would be under the original scripting namespace.

userScripts

Note: When using Manifest V3 or higher, use scripting.registerContentScripts() to register scripts.

There will be an additional 'USER_SCRIPT' option in the scripting.ExecutionWorld.

scripting.ExecutionWorld

Specifies the execution environment of a script injected with scripting.executeScript() or registered with scripting.registerContentScripts().

While the User Scripts API mentions userScripts namespace, I am under the impression that implementation would be under the original scripting namespace.

For Chromium, the getScripts, register, unregister, and update methods have been implemented under the userScripts namespace.

Preface: I am focusing the discussion to the userScripts API, not the scripting API, given their separate use cases.

What is the request here? The ability to run arbitrary code through the userScripts API? Or the ability for user script managers to run a new user script in already-loaded documents?

For the latter, rather than a userScripts.execute method that mimics tabs.executeScript, I think that it would make more sense to allow a user script to be executed again in a specific context. That ensures that a user script will only run in contexts where a registered user script would usually run. Otherwise issue #8 would be encountered.

Use case is for extensions where users can execute user-supplied code through gestures, shortcut keys, etc. You can never predict when, or where the user will trigger the gesture, or shortcut, so it's not possible to bind it to a specific URL. I think userScripts.execute() is the right approach here.

To avoid issue #8, the userScripts.execute() function could take a documentId, and that way the developer can execute on a specific document.

Since MessageSender includes documentId, the flow is straightforward.

// content script where gesture, shortcut, voice command, etc is triggered. 
browser.runtime.sendMessage({action: "ExecuteUserScript", id: "foo"})

// background 
function onMessage(msg, sender, sendResponse) {
    if (msg.action === "ExecuteUserScript") {
        const code = await getUserScriptFromStorage(msg.id)
        userScripts.execute({code, documentId: sender.documentId})
    }
}

What is the request here? The ability to run arbitrary code through the userScripts API? Or the ability for user script managers to run a new user script in already-loaded documents?

What is the difference?

Both are almost same to me. An extension user wants to create his/her own script(arbitrary code, also as a new user script) to be executed in the context of the extension(could be a user script manager) on document loaded.

I'd like to highlight a use case where something like userScripts.execute() would be useful.

Powerlet is an interface to search and execute scripts from bookmarklets. The user will click the extension button or use a keyboard shortcut to activate the extension popup, search from there and execute a particular script. This is equivalent to clicking on a bookmark button on the browser's bookmarks toolbar.

The project has an issue mentioning migration to MV3.

I've been experimenting with MV3, it might be my lack of experience but I don't see a way to support similar extensions with the current userScripts API as execution is limited to page load. (Which is what I believe this issue refers to.)

I believe that the scripting API for MV3 doesn't work either as it requires either a script file to be packaged with the extension or a function to be provided which will then be serialized / deserialized. Any form of converting a bookmarklet URL to a function will be blocked (think eval(code) or new Function(code)).

It'd be nice to continue being able to extend the browser like that, I see this no less secure than running scripts on page load, even less intrusive IMO.

@oliverdunk Any chance we get this before Chrome's MV2 deadline in June? Many MV2 Chrome extensions have features that require this API.

Current options for extensions that require this API.

  1. Wait it out and hope this is implemented before June's deadline.
  2. Hope the deadline is extended again.
  3. If it's not a core feature, get rid of it.
  4. Update to MV3 and use the wrapped userScript approach.

@newRoland, as you mention there is a wrapped userScript approach which should be sufficient as a workaround here. With that in mind, we're not considering this a blocker, and would suggest using the workaround for migration in the short term. There's definitely still a chance a way of doing one-time injection lands in time, since we're planning to do further work on the userScripts API over the next few months, I just can't make any promises :)

FYI: @EmiliaPaz has filed a PR with a proposal at #540

PR #540 has been approved.