-
Notifications
You must be signed in to change notification settings - Fork 56
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
Main world User Script shared params #284
Comments
Could this also be used to facilitate secure cc @hackademix |
Previously: #103 |
Yes 👍🏻 kzar is actually on my team so was documenting our workaround. |
In the latest meeting I think there was some confusion with this being for executeScript this would be additionally required for chrome.scripting.registerContentScripts executed prior to the page load. One suggestion was to use func; perhaps this could be resolved with allowing func to registerContentScripts such that the following is possible: let someSharedGlobal = 0
browser.alarms.onAlarm.addListener(() => {
someSharedGlobal++
});
chrome.scripting.registerContentScripts([{
js: ['script.js'],
matches: ['<all_urls>'],
runAt: 'document_start',
world: 'MAIN',
func: () => {
let myVar = someSharedGlobal
}
}]) // script.js
console.log(`alarm hit ${myVar} times`) |
Thank you for clarifying we need this ability for declaratively declared ( Rather than |
This would solve some of it; the other problem being the state is dynamic and so currently the ISOLATED script still has to message pass to the background/worker context which creates a race condition. Essentially what we currently have is: // background.js
let someSharedGlobal = 0
browser.alarms.onAlarm.addListener(() => {
someSharedGlobal++
})
chrome.runtime.onMessage.addListener((message) => {
return someSharedGlobal
})
chrome.scripting.registerContentScripts([
{
id: '1-isolated',
js: ['isolated.js'],
allFrames: true,
matches: ['<all_urls>'],
runAt: 'document_start',
world: 'ISOLATED'
},
{
id: '2-main',
js: ['script.js'],
allFrames: true,
matches: ['<all_urls>'],
runAt: 'document_start',
world: 'MAIN'
}
]) // isolated.js
function getSecret () {
return new Promise(resolve => {
window.addEventListener('secret', event => {
event.stopImmediatePropagation()
resolve(event.detail)
}, { once: true })
})
}
const secret = await getSecret()
chrome.runtime.sendMessage({
messageType: 'registeredContentScript',
}, someSharedGlobal => {
// Init script.js with the someSharedGlobal.
window.dispatchEvent(new CustomEvent(secret, {
detail: {
type: 'register',
someSharedGlobal
}
}))
}) // script.js
const secret = window.crypto.randomUUID()
window.addEventListener(secret, ({ detail: message }) => {
if (!message) return
if (message.type === 'register') {
console.log(`alarm hit ${message.someSharedGlobal} times`)
}
})
window.dispatchEvent(new CustomEvent('secret', {
detail: secret
})) In the example code we're using DOM events; they perhaps could be static functions and the proposed changes of serializing globals would simplify; however there would still be a delay in getting the data from the message handler in the background.js It would be fine if your suggestion worked like: let someSharedGlobal = 0
browser.alarms.onAlarm.addListener(() => {
someSharedGlobal++
});
chrome.scripting.registerContentScripts([{
js: ['script.js'],
matches: ['<all_urls>'],
runAt: 'document_start',
world: 'MAIN',
args: [
'someSharedGlobal'
]
}]) const myVar = globalThis.someSharedGlobal
delete globalThis.someSharedGlobal
// script.js
console.log(`alarm hit ${myVar} times`) However I'd prefer these vars not to be global at all to reduce the need for any clear up mistakes:
|
Any API design that depends on an ephemeral MV3 service worker's top-level lexical scope is unlikely to be implemented. browser.alarms.onAlarm.addListener(async () => {
let someSharedGlobal = await browser.storage.local.get('someSharedGlobal') | 0;
someSharedGlobal++;
await browser.scripting.registerContentScriptArgument({
id: 1,
args: { someSharedGlobal }
});
await browser.storage.local.set({ someSharedGlobal })
});
chrome.scripting.registerContentScripts([{
id: 1,
js: ['script.js'],
/* ... */
}]) https://bugs.chromium.org/p/chromium/issues/detail?id=1054624#c56 is a proposal that is in line with requiring static arguments. |
Right I was omitting state storage for brevity (the code sample was already big enough 😆 ); we're not relying on that instead we're using session and local browser.storage.
|
It's not just about toy example in your comment. I've already removed that part of the comment, because it seemed tangential to the main issue. |
Ah I understand now, on worker startup there's likely a race between storage fetch and the func argument execution; I can see why this was omitted from the API now.
I agree the arguments API proposal resolves this, optionally just populating 'arguments' with chrome.scripting.globalParams or similar would work similar to comment 58 |
https://bugs.chromium.org/p/chromium/issues/detail?id=1261964 in Chrome (same for Firefox and maybe Safari as well) shows that a page can spoof the environment in a same-origin iframe/page prior to document_start injection point e.g. it can install setters on I wonder if there's an internal JS engine trick that can limit the visibility of a global variable to just the injected code? Or somehow set the global without triggering setters , just overwrite the descriptor regardless of its configurable:false mode. A simpler solution might be for the browser to auto-wrap the injected code in an IIFE like |
Currently, without a separate isolated world, all globals are visible to any main world scripts. That's one of the big things isolated world gives you. |
To be clear we explicitly don't want it as a global at all. Alternatively the ability to reach into the window like you can in Firefox xrays would equally work for our use cases. |
Has anyone else from the browser teams looked at this some more?
The registerContentScriptArgument proposal seems the simplest and cleanest. Service worker: const pies = true
await browser.scripting.registerContentScriptArgument({
id: 1,
args: { pies }
});
chrome.scripting.registerContentScripts([{
id: 1,
js: ['script.js'],
}]) Script: if (args.pies) {
console.log('hey I like pies')
} Executes as: ((args) => {
if (args.pies) {
console.log('hey I like pies')
}
})({pies: true}) After looking at @EmiliaPaz's #279 (comment) proposal, it's clear that isn't the direction of this request. There's no desire for arbitrary script execution here, we're just wanting to pass variables into the main world script faster than the page can execute. |
Passing state/parameters to content scripts that are injected into the main world currently requires message passing that is visible to the page and also delays the functionality that the script can provide.
A use case DuckDuckGo need this for is:
chrome.scripting.globalParams was added to solve similar use-cases however it's not ever exposed to the main world scripts. I'd instead propose a way to allow these variables to be passed into the script (perhaps cloned so that modification isn't possible) some avenues to inject these would be:
These shouldn't be exposed on globalThis/window as the page should have no visibility of these parameters; most importantly the page should have no ability to read the contents.
Moved out from: #279 (comment)
cc @dotproto see also: https://bugs.chromium.org/p/chromium/issues/detail?id=1054624#c47
The text was updated successfully, but these errors were encountered: