Open Bug 1445909 Opened 7 years ago Updated 10 months ago

Evaluate extended matching capabilities for registered userScripts (and/or WebExtensions Content Scripts in general)

Categories

(WebExtensions :: General, enhancement, P3)

60 Branch
enhancement

Tracking

(Not tracked)

People

(Reporter: rpl, Unassigned)

References

Details

This is a follow up issue related to the userScripts API, which arises from the bugzilla comments related to the differences and incompatibilities between the matching capabilities historically supported and often used by the existent userScripts vs. the ones natively provided by the WebExtensions APIs (in particular the following ones used to configure the content scripts matching: "matches", "excludeMatches", "includeGlobs" and "excludeGlobs). More details about these differences and incompatibilities (and how they are currently workarounded by "userScripts management extensions" like Greasemonkey) are mentioned in the following bugzilla comments: - Bug 1437098 Comment 1 (which explains the differences between how an set of "matches"/"excludeMatches"/"includeGlobs"/"excludeGlobs" are composed by the WebExtensions APIs and by most userScripts managers) - Bug 1437098 Comment 3, Bug 1437098 Comment 4 and Bug 1437098 Comment 9 (which explain that the userScripts also support matching using a RegExp and how Greasemonkey may workaround them, which is basically by wrapping the userScript code with some "preamble" js code which checks if the userScript match by checking its matching RegExp against the document url and only execute the actual userScript if it matches) The goal of this issue is evaluating if (and eventually which part) of these extended capabilities could be interesting and reasonable to support as part of the natively supported matching capabilities), and/or how to provide the userScripts API a way to extend the matching capabilities (when needed by the userScripts registered by the user) more efficiently than by just leaving the "userScript manager extension" to manually wrap the actual userScript to achieve it (which basically means that we are going to do some extra work, e.g. creating the isolated userScript sandbox, for a user script that is not going to match).
Blocks: 1437098
Priority: -- → P2
See Also: → 1315558
Summary: Evaluate extended matching capabilities for registered userScripts (and/ore WebExtensions Content Scripts in general) → Evaluate extended matching capabilities for registered userScripts (and/or WebExtensions Content Scripts in general)
Perhaps there a matching callback? Pass the URL (and maybe other useful context) to a (background?) JS function that decides whether this (content|user) script should run. Which lets the extension choose on any criteria it likes, including state other than the URL (e.g. has the user just clicked a button?).
A callback [1] is an interesting idea but I think it should be an "in addition to [comment 0]" rather than "instead of." The rational is that most scripts are only going to have a need for standard matching or under certain situations standard matching plus "some other obscure detail." If matching is only done using a callback then it's likely that script managers would have to re-implement Firefox's existing (internal) match interfaces. > Which lets the extension choose on any criteria it likes, including state other than the URL (e.g. has the user just clicked a button?). Could you elaborate on this, or give a more complete use case? At least for the button example, in which I'm assuming it's a button on a web page, having the script, or injecting a content script, that implements similar functionality would be a better fit, no? Further since the background doesn't have DOM access I feel this particular request might be unlikely. [1] The callback would of course receive, at minimum, the URL and the "scriptMetadata" (passed into `.register`)
Oh, actually, in thinking enough to type out an example, I have the perfect one. Another thing the "just register scripts" API doesn't handle. Users can turn Greasemonkey on and off at runtime. If I turn it off then scripts don't run until I turn it back on. I do this by interacting with the extension through the browser action's popup. This is most useful when (e.g.) a single rarely-used site interacts poorly with an installed script; just turn GM off and load the site, then turn GM back on. Or if you just want to check if a mysterious behavior is caused by a GM script, temporarily turn it off. So yes, it should be combinable with and/or other glob/match rules. But I'm thinking GM would probably do *only* callback. That way we can preserve legacy behavior with perfect fidelity, and also control things like the global enable flag.
> Users can turn Greasemonkey on and off at runtime. If I turn it off then scripts don't run until I turn it back on. I do this by interacting with the extension through the browser action's popup. This is all doable already. When you `.register` a script it returns an object with a single method `.unregister`. When that method is called the script is unregistered and does not run. It's fairly trivial to: > // Assume the result of `.register` is on script.registration > let unregister = userscripts.map(script => { > return script.registration.unregister; > }); > await Promise.all(unregister); As for performance, I don't know. It'd require profiling. It's not incredibly clear-cut on what kind over overhead the callback would impose vs native Firefox matching.
Slight error in my code s/script.registration.unregister/script.registration.unregister()/
The userScripts API should be performant by design. That precludes the use of callbacks to the background page, because then scripts in pages need to be suspended until the extension's callback has responded (at least for document_start scripts). (In reply to Anthony Lieuallen from comment #3) > Users can turn Greasemonkey on and off at runtime. As sdaniele3 says, the only primitives that you need for this is a way to register scripts, and a way to unregister them. Firefox only needs to know about the scripts if it is going to run them, and since the API uses strings as input, Greasemonkey still needs to store/maintain the scripts in a separate database. > But I'm thinking GM would probably do *only* callback. If GM cannot provide the desired functionality without callbacks, then the API is not complete. If you provide specific examples of missing pieces, they can be added. For backwards-compatibility, you can feature-detect the absence of APIs, and wrap the script string in a compatibility layer if needed.
As it has already been described in the previous comments, the "extended matching" still has to happen in the child content process for performance reasons (otherwise the loading of the matched page has to be suspended until we get a response from another process, which is something that we definitely want to avoid). Follows some approaches that I've been thinking of to cover the scenarios / requirements currently described. For the extended matching capabilities (e.g. to implement regexp matching and/or other customization to the matching capabilities) that can happen in the child process: an option could be to introduce an additional matchScript parameter, which the extension may use to provide a small code fragment to run to verify if the userScript is actually matching the window/document (in addition to the matches property, which would be probably be "<all_urls>" in many cases) based on its (synchronously) returned value, if the matchScript returns true the internals can proceed to create the userScript sandbox, inject the custom APIs and execute the userScript (otherwise we just early exit because the userScript didn't fully matched the target) To be able to manually trigger the execution of a registered script (without waiting for the web pages to be reloaded): an option could be to introduce an additional script.execute() API method which could trigger the script execution on all the existent window that it matches (and we could probably also allow tabIds as an additional optional parameter, to only trigger it on the given array of tab ids), this way a listener subscribed to an API event in one of the other extension pages (e.g. the background page or a popup or a sidebar) can trigger the execution of the script programmatically. To be able to temporarily disable a registered script we can evaluate to define a script API method (e.g. `script.setEnabled(true/false)`) which could prevent the script from being executed without unregister it completely.
Product: Toolkit → WebExtensions
Depends on: 1509339
Blocks: 1514809
No longer blocks: 1514809
Priority: P2 → P3
Blocks: 1595853
No longer blocks: 1437098

Adding a condition for continuing after onBeforeScript or an option to break it, would be an idea. e.g.

browser.userScripts.onBeforeScript.addListener(script => {
  
  if (condition) {
    return false;
  }
  // ....
}
Severity: normal → S3

Removing this from Bug 1595853 dependencis, given that the userScripts API namespace Bug 1595853 is the one that was available only in Firefox manifest_version 2 extensions and deprecated in manifest_version 3 extensions (where the same namespace is meant to expose an API with the same use case in mind but different "interface").

Introducing support for the new MV3 API is tracked by Bug 1875475, and so instead of closing this bug as wontfix I'm adding it as a seealso for Bug 1875475, in case this enhancement may still be relevant for the new API (and if it is not we will then closing it as wontfix).

No longer blocks: 1595853
See Also: → 1875475
You need to log in before you can comment on or make changes to this bug.