-
Notifications
You must be signed in to change notification settings - Fork 2.6k
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 example using MV3 userScripts API #576
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Rob--W, if you want to publish this example before the 135, I think we should add some installation instructions to the README that mention that you need to enable |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,116 @@ | ||||||||||||||||||||||||||||||
# userScripts-mv3 | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
A user script manager, demonstrating the userScripts API, the permissions API, | ||||||||||||||||||||||||||||||
`optional_permissions`, and Manifest Version 3 (MV3). | ||||||||||||||||||||||||||||||
The extension is an example of a | ||||||||||||||||||||||||||||||
[user script manager](https://en.wikipedia.org/wiki/Userscript_manager). | ||||||||||||||||||||||||||||||
Comment on lines
+3
to
+6
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
This covers the following aspects to extension development: | ||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Comment on lines
+3
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reword to fix a grammar issue and for clarity. Currently the intro sentence is incomplete.
Suggested change
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
- Showing onboarding UI after installation. | ||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
- Designing background scripts that can restart repeatedly with minimal | ||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Rob--W as we seem to make the assumption that this is going to be rendered as a markdown document (e.g. use of hyperlink in the introductory paragraph) is there any need to impose line breaks? |
||||||||||||||||||||||||||||||
overhead. This is especially relevant to Manifest Version 3. | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
- Minimizing the overhead of background script startup, which is especially | ||||||||||||||||||||||||||||||
relevant because event pages . | ||||||||||||||||||||||||||||||
Comment on lines
+15
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The original statement seems incomplete, not entirely sure how to complete it, e.g. why is it irrelevant?
Suggested change
Comment on lines
+15
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reword to make this comment more applicable to other browsers. It also addresses the fact that other browsers may also support service workers in the future.
Suggested change
NOTE: If you accept this suggestion, you should also update this line with a similar change. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correcting grammar in suggestion
Suggested change
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
- Monitoring an optional (userScripts) permission, and dynamically registering | ||||||||||||||||||||||||||||||
events and scripts based on its availability. | ||||||||||||||||||||||||||||||
Comment on lines
+18
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Comment on lines
+18
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reword for clarity. The parenthetical should appear after the entire phrase that it applies to. I tend to prefer to include the quotation marks when referring to a permission. That said, I don't know if we have an established pattern one way or the other.
Suggested change
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
- Using the `userScripts` API to register, update and unregister code. | ||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Optional: clarify that the extension is specifically operating on user scripts rather than arbitrary code.
Suggested change
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
- Isolating user scripts in their own execution context (`USER_SCRIPT` world), | ||||||||||||||||||||||||||||||
and conditionally exposing custom functions to user scripts. | ||||||||||||||||||||||||||||||
Comment on lines
+23
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
## What it does | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
This extension is an example of a [user script manager](https://en.wikipedia.org/wiki/Userscript_manager) | ||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: end the sentence with a period.
Suggested change
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
Comment on lines
+29
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Already said this in the introduction, not sure it needs repeating
Suggested change
|
||||||||||||||||||||||||||||||
After loading the extension, the extension detects the new installation and | ||||||||||||||||||||||||||||||
opens the options page embedded in `about:addons`. On the options page: | ||||||||||||||||||||||||||||||
Comment on lines
+31
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
1. You can click on the "Grant access to userScripts API" button to trigger a | ||||||||||||||||||||||||||||||
permission prompt for the "userScripts" permission. | ||||||||||||||||||||||||||||||
Comment on lines
+34
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||
2. Click on the "Add new user script" button to open a form where a new script | ||||||||||||||||||||||||||||||
can be registered. | ||||||||||||||||||||||||||||||
Comment on lines
+36
to
+37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||
3. Input a user script. E.g. by clicking one of the two "Example" buttons to | ||||||||||||||||||||||||||||||
input examples from the [userscript_examples](userscript_examples) directory. | ||||||||||||||||||||||||||||||
Comment on lines
+38
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||
4. Click on the "Save" button to trigger validation and save the script. | ||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
If the "userScripts" permission was granted, this will schedule the execution | ||||||||||||||||||||||||||||||
of the registered user scripts for the websites as specified by the user script. | ||||||||||||||||||||||||||||||
Comment on lines
+42
to
+43
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
See [userscript_examples](userscript_examples) for examples of user scripts and | ||||||||||||||||||||||||||||||
what they do. | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
If you repeat steps 2-4 for both examples, then a visit to https://example.com/ | ||||||||||||||||||||||||||||||
should show the following behavior: | ||||||||||||||||||||||||||||||
Comment on lines
+48
to
+49
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
- Show a dialog containing "This is a demo of a user script". | ||||||||||||||||||||||||||||||
- Insert a button with the label "Show user script info", which opens a new tab | ||||||||||||||||||||||||||||||
displaying the extension information. | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
# What it shows | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
Showing onboarding UI after installation: | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
- `background.js` registers the `runtime.onInstalled` listener that calls | ||||||||||||||||||||||||||||||
`runtime.openOptionsPage` after installation. | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
Designing background scripts that can restart repeatedly with minimal overhead: | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
- This is especially relevant to Manifest Version 3, because background scripts | ||||||||||||||||||||||||||||||
are always non-persistent event pages, which can suspend on inactivity. | ||||||||||||||||||||||||||||||
Comment on lines
+64
to
+65
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||
- Using `storage.session` to store initialization status, to run expensive | ||||||||||||||||||||||||||||||
initialization only once per browser session. | ||||||||||||||||||||||||||||||
- Registering events at the top level to handle events that were triggered | ||||||||||||||||||||||||||||||
while the background script was asleep. | ||||||||||||||||||||||||||||||
Comment on lines
+68
to
+69
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||
- Using [dynamic import](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) | ||||||||||||||||||||||||||||||
to initialize optional JavaScript modules on demand. | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
Monitoring an optional (userScripts) permission, and dynamically registering | ||||||||||||||||||||||||||||||
events and scripts based on its availability: | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
- The `userScripts` permission is optional and can be granted by the user via | ||||||||||||||||||||||||||||||
the options page (`options.html` + `options.mjs`). The permission can also | ||||||||||||||||||||||||||||||
be granted/revoked via browser UI, by the user, as documented at | ||||||||||||||||||||||||||||||
https://support.mozilla.org/en-US/kb/manage-optional-permissions-extensions | ||||||||||||||||||||||||||||||
Comment on lines
+76
to
+79
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
- The `permissions.onAdded` and `permissions.onRemoved` events are used to | ||||||||||||||||||||||||||||||
monitor permission changes and the (un)availability of the `userScripts` API. | ||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
- When the `userScripts` API is available at the startup of `background.js`, | ||||||||||||||||||||||||||||||
and when the permission detected via `permissions.onAdded`, the initialization | ||||||||||||||||||||||||||||||
starts (via the `ensureUserScriptsRegistered` function in `background.js`). | ||||||||||||||||||||||||||||||
Comment on lines
+84
to
+86
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
- When the `userScripts` API is unavailable at the startup of `background.js`, | ||||||||||||||||||||||||||||||
the extension cannot use the `userScripts` API until `permissions.onAdded` is | ||||||||||||||||||||||||||||||
triggered. The options page stores user scripts in `storage.local` to enable | ||||||||||||||||||||||||||||||
the user to edit scripts even without the `userScripts` permission. | ||||||||||||||||||||||||||||||
Comment on lines
+88
to
+91
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
Using the `userScripts` API to register, update and unregister code: | ||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
- The `applyUserScripts()` function in `background.js` demonstrates how one use | ||||||||||||||||||||||||||||||
the various `userScripts` APIs to register, update and unregister scripts. | ||||||||||||||||||||||||||||||
Comment on lines
+95
to
+96
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||
- `userscript_manager_logic.mjs` contains logic specific to user script | ||||||||||||||||||||||||||||||
managers. See [userscript_manager_logic.js](userscript_manager_logic.js) for | ||||||||||||||||||||||||||||||
comments and the conversion logic from a user script string to the format as | ||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||
expected by the userScripts API (RegisteredUserScript). | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
Isolating user scripts in their own execution context (`USER_SCRIPT` world), | ||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||
and conditionally exposing custom functions to user scripts: | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
- Shows the use of multiple `USER_SCRIPT` worlds (with distinct `worldId`) to | ||||||||||||||||||||||||||||||
define separate sandboxes for scripts to run in (see `registeredUserScript` | ||||||||||||||||||||||||||||||
in `userscript_manager_logic.mjs`). | ||||||||||||||||||||||||||||||
Comment on lines
+105
to
+107
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
- Shows the use of `userScripts.configureWorld()` with the `messaging` flag to | ||||||||||||||||||||||||||||||
enable the `runtime.sendMessage()` method in `USER_SCRIPT` worlds. | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
- Shows the use of `runtime.onUserScriptMessage` and `sender.userScriptWorldId` | ||||||||||||||||||||||||||||||
to detect messages and the script that sent messages. | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
- Shows how an initial script can use `runtime.sendMessage` to expose custom | ||||||||||||||||||||||||||||||
APIs to user scripts (see `userscript_api.js`). |
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,176 @@ | ||||||||||||||
"use strict"; | ||||||||||||||
|
||||||||||||||
// This background.js file is responsible for observing the availability of the | ||||||||||||||
// userScripts API, and registering user scripts when needed. | ||||||||||||||
// | ||||||||||||||
// - The runtime.onInstalled event is used to detect new installations, to open | ||||||||||||||
// extension UI where the user is asked to grant the "userScripts" permission. | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
// | ||||||||||||||
// - The permissions.onAdded and permissions.onRemoved events detect changes to | ||||||||||||||
// the "userScripts" permission, whether triggered from the extension UI, or | ||||||||||||||
// externally (e.g. through browser UI). | ||||||||||||||
Comment on lines
+10
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
// | ||||||||||||||
// - The storage.local API is used to store user scripts across extension | ||||||||||||||
// updates. This is necessary, because the userScripts API clears any | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
// previously registered scripts when an extension is updated. | ||||||||||||||
// | ||||||||||||||
// - The userScripts API manages script registrations with the browser. The | ||||||||||||||
// applyUserScripts() function in this file demonstrates the relevant aspects | ||||||||||||||
// to registering/updating user scripts that apply to most extensions that | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
// manage user scripts. To keep this file reasonably small, most of the | ||||||||||||||
// application-specific logic is in userscript_manager_logic.js | ||||||||||||||
|
||||||||||||||
function isUserScriptsAPIAvailable() { | ||||||||||||||
return !!browser.userScripts; | ||||||||||||||
} | ||||||||||||||
var userScriptsAvailableAtStartup = isUserScriptsAPIAvailable(); | ||||||||||||||
|
||||||||||||||
var managerLogic; // Lazily initialized by ensureManagerLogicLoaded(). | ||||||||||||||
async function ensureManagerLogicLoaded() { | ||||||||||||||
if (!managerLogic) { | ||||||||||||||
managerLogic = await import("./userscript_manager_logic.mjs"); | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
browser.runtime.onInstalled.addListener(details => { | ||||||||||||||
if (details.reason !== "install") { | ||||||||||||||
// Only show extension's onboarding logic on extension installation, and | ||||||||||||||
// not e.g. on browser update or extension updates. | ||||||||||||||
Comment on lines
+37
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
return; | ||||||||||||||
} | ||||||||||||||
if (!isUserScriptsAPIAvailable()) { | ||||||||||||||
// The extension needs the "userScripts" permission, but this has not been | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
// granted. Open the extension's options_ui page where we have implemented | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
// onboarding logic, in options.html + options.mjs | ||||||||||||||
browser.runtime.openOptionsPage(); | ||||||||||||||
} | ||||||||||||||
}); | ||||||||||||||
|
||||||||||||||
browser.permissions.onRemoved.addListener(permissions => { | ||||||||||||||
if (permissions.permissions.includes("userScripts")) { | ||||||||||||||
// Pretend that userScripts was not available, so that if the permission is | ||||||||||||||
// restored, that permissions.onAdded will re-initialize. | ||||||||||||||
Comment on lines
+51
to
+52
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
userScriptsAvailableAtStartup = false; | ||||||||||||||
|
||||||||||||||
// Clear cached state, so that ensureUserScriptsRegistered() will refresh | ||||||||||||||
// the registered user scripts if the permissions is granted again. | ||||||||||||||
Comment on lines
+55
to
+56
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
browser.storage.session.remove("didInitScripts"); | ||||||||||||||
|
||||||||||||||
// Note: the "userScripts" namespace is unavailable, so we cannot and | ||||||||||||||
// should not try to unregister scripts. | ||||||||||||||
} | ||||||||||||||
}); | ||||||||||||||
|
||||||||||||||
browser.permissions.onAdded.addListener(permissions => { | ||||||||||||||
if (permissions.permissions.includes("userScripts")) { | ||||||||||||||
if (userScriptsAvailableAtStartup) { | ||||||||||||||
// If background.js woke up to dispatch permissions.onAdded, then we | ||||||||||||||
// would already have detected the availability of the userScripts API | ||||||||||||||
// and started initialization. Return now to avoid double-initialization. | ||||||||||||||
Comment on lines
+67
to
+69
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
return; | ||||||||||||||
} | ||||||||||||||
browser.runtime.onUserScriptMessage.addListener(onUserScriptMessage); | ||||||||||||||
ensureUserScriptsRegistered(); | ||||||||||||||
} | ||||||||||||||
}); | ||||||||||||||
|
||||||||||||||
// When the user modifies a user script in options.html / options.mjs, the | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
// changes are stored in storage.local and this listener is triggered. | ||||||||||||||
browser.storage.local.onChanged.addListener(changes => { | ||||||||||||||
if (changes.savedScripts?.newValue && isUserScriptsAPIAvailable()) { | ||||||||||||||
// userScripts API is available and there are changes that we can apply! | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
applyUserScripts(changes.savedScripts.newValue); | ||||||||||||||
} | ||||||||||||||
}); | ||||||||||||||
|
||||||||||||||
if (userScriptsAvailableAtStartup) { | ||||||||||||||
// Register listener immediately if the API is available, in case the | ||||||||||||||
// background.js was awakened to dispatch the onUserScriptMessage event. | ||||||||||||||
Comment on lines
+87
to
+88
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
browser.runtime.onUserScriptMessage.addListener(onUserScriptMessage); | ||||||||||||||
ensureUserScriptsRegistered(); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
async function onUserScriptMessage(message, sender) { | ||||||||||||||
await ensureManagerLogicLoaded(); | ||||||||||||||
return managerLogic.handleUserScriptMessage(message, sender); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
async function ensureUserScriptsRegistered() { | ||||||||||||||
let { didInitScripts } = await browser.storage.session.get("didInitScripts"); | ||||||||||||||
if (didInitScripts) { | ||||||||||||||
// The scripts has already been initialized, e.g. at a (previous) startup | ||||||||||||||
// of this background script. Skip expensive initialization. | ||||||||||||||
Comment on lines
+101
to
+102
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
return; | ||||||||||||||
} | ||||||||||||||
let { savedScripts } = await browser.storage.local.get("savedScripts"); | ||||||||||||||
savedScripts ||= []; | ||||||||||||||
try { | ||||||||||||||
await applyUserScripts(savedScripts); | ||||||||||||||
} finally { | ||||||||||||||
// Set a flag to mark completion of initialization, to avoid running all of | ||||||||||||||
// this logic again at the next startup of this background.js script. | ||||||||||||||
Comment on lines
+110
to
+111
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
await browser.storage.session.set({ didInitScripts: true }); | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
async function applyUserScripts(userScriptTexts) { | ||||||||||||||
await ensureManagerLogicLoaded(); | ||||||||||||||
// Note: assuming userScriptTexts to be valid, validated by options.mjs. | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
let scripts = userScriptTexts.map(str => managerLogic.parseUserScript(str)); | ||||||||||||||
|
||||||||||||||
// Registering scripts is expensive. Compare the scripts with the old scripts | ||||||||||||||
// to make sure that we only update scripts that have changed. | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
let oldScripts = await browser.userScripts.getScripts(); | ||||||||||||||
|
||||||||||||||
let { | ||||||||||||||
scriptsToRemove, | ||||||||||||||
scriptsToUpdate, | ||||||||||||||
scriptsToRegister, | ||||||||||||||
} = managerLogic.computeScriptDifferences(oldScripts, scripts); | ||||||||||||||
|
||||||||||||||
// Now we have computed the changed scripts, apply the changes in this order: | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
// 1. Unregister obsolete scripts. | ||||||||||||||
// 2. Reset / configure worlds. | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
// 3. Update / register new scripts. | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
// This order is significant: scripts rely on world configurations, and while | ||||||||||||||
// running this asynchronous script updating logic, the browser may try to | ||||||||||||||
// execute any of the registered scripts when a website loaded in a tab or | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
// iframe, unrelated to the extension execution. | ||||||||||||||
// To prevent scripts from executing with the wrong world configuration, | ||||||||||||||
// worlds are configured before new scripts are registered. | ||||||||||||||
|
||||||||||||||
// 1. Unregister obsolete scripts. | ||||||||||||||
if (scriptsToRemove.length) { | ||||||||||||||
let worldIds = scriptsToRemove.map(s => s.id); | ||||||||||||||
await browser.userScripts.unregister({ worldIds }); | ||||||||||||||
Comment on lines
+144
to
+145
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Line 145 currently throws the following error:
Channel: Nightly I looked through the current version of It looks like this may be the result of some refactoring that occured during development. The script appears to work as expected if we change the body of the if clause as follows:
Suggested change
|
||||||||||||||
} | ||||||||||||||
|
||||||||||||||
// 2. Reset / configure worlds. | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
if (scripts.some(s => s.worldId)) { | ||||||||||||||
// When a userscripts need privileged functionality, we run them in a | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn’t that be "When a userscript needs" or "When userscripts need"? |
||||||||||||||
// sandbox (USER_SCRIPT world). To offer privileged functionality, we need | ||||||||||||||
// a communication channel between the userscript and this privileged side. | ||||||||||||||
// Specifying "messaging:true" exposes runtime.sendMessage() these worlds, | ||||||||||||||
// which upon invocation triggers the runtime.onUserScriptMessage event. | ||||||||||||||
// | ||||||||||||||
// Calling configureWorld without a specific worldId sets the default world | ||||||||||||||
// configuration, which is inherit by every other USER_SCRIPT world that | ||||||||||||||
// does not have a more specific configuration. | ||||||||||||||
// | ||||||||||||||
// Since every USER_SCRIPT world in this demo extension has the same world | ||||||||||||||
// configuration, we can set the default once, without needing to define | ||||||||||||||
// world-specific configurations. | ||||||||||||||
await browser.userScripts.configureWorld({ messaging: true }); | ||||||||||||||
} else { | ||||||||||||||
// Reset the default world's configuration. | ||||||||||||||
await browser.userScripts.resetWorldConfiguration(); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
// 3. Update / register new scripts. | ||||||||||||||
if (scriptsToUpdate.length) { | ||||||||||||||
await browser.userScripts.update(scriptsToUpdate); | ||||||||||||||
} | ||||||||||||||
if (scriptsToRegister.length) { | ||||||||||||||
await browser.userScripts.register(scriptsToRegister); | ||||||||||||||
} | ||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{ | ||
"manifest_version": 3, | ||
"name": "User Scripts Manager extension", | ||
"description": "Demonstrates the userScripts API and optional permission, in MV3.", | ||
"version": "0.1", | ||
"host_permissions": ["*://*/"], | ||
"permissions": ["storage", "unlimitedStorage"], | ||
"optional_permissions": ["userScripts"], | ||
dotproto marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"background": { | ||
"scripts": ["background.js"] | ||
}, | ||
"options_ui": { | ||
"page": "options.html" | ||
}, | ||
"browser_specific_settings": { | ||
"gecko": { | ||
"id": "user-script-manager-example@mozilla.org", | ||
"strict_min_version": "134.0a1" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note: I put |
||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
#edit_script_dialog .source_text { | ||
display: block; | ||
width: 80vw; | ||
min-height: 10em; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<meta charset="utf-8"> | ||
<meta name="viewport" content="width=device-width"> <!-- mobile-friendly --> | ||
<meta name="color-scheme" content="dark light"><!-- Dark theme support --> | ||
<link rel="stylesheet" type="text/css" href="options.css"> | ||
</head> | ||
<body> | ||
This page enables you to create, edit and remove user scripts. | ||
To run them, please allow the extension to run user scripts by clicking this button: | ||
<button id="grant_userScripts_permission"></button> | ||
|
||
<dialog id="edit_script_dialog"> | ||
<div> | ||
Please input a user script and save it.<br> | ||
<button id="sample_unprivileged">Example: Unprivileged user script</button> | ||
<button id="sample_privileged">Example: Privileged user script</button> | ||
</div> | ||
<textarea class="source_text"></textarea> | ||
<button class="save_button">Save</button> | ||
<button class="remove_button">Remove</button> | ||
<output class="validation_status"></output> | ||
</dialog> | ||
|
||
<ul id="list_of_scripts"> | ||
<li><button id="add_new">Add new user script</button></li> | ||
</ul> | ||
|
||
<script src="options.mjs" type="module"></script> | ||
</body> | ||
</html> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.