TBD
Last active
January 23, 2018 16:50
-
-
Save rpl/34cb471b752055d99ed445446613ae75 to your computer and use it in GitHub Desktop.
A fiction example webextension (used to design a new userScripts WE API)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const userScriptAPIs = { | |
// GM_something -> name of the API method injected in the sandbox | |
// args -> arguments of the API call | |
// userScriptSandboxAPI -> an API object which provides the metadata of the userScript caller | |
// and expose other helper methods. | |
async GM_something([param1, cb], userScript) { | |
if (!validateGMSomethingArgs([param1, cb])) { | |
// Throws an error (converted by a wrapper implemented internally | |
// into an valid rejection Error instance for the caller sandbox). | |
throw new Error("..."); | |
} | |
const result = await userScript.parent.GM_something(param1); | |
cb(result); | |
}, | |
async GM_something_else(args, userScriptName) { | |
const data = await browser.storage.local.get(userScriptName); | |
return doSomethingElseWith(data, args) | |
}, | |
... | |
}; | |
// This method (only available into the content scripts if the userScripts permission | |
// has been asked by the extension) is called when a userScript is going to | |
// be executed in its newly created sandbox and allows the extension to register a set of | |
// custom API methods into it. | |
browser.userScripts.onUserScript((userScript) => { | |
if (!userScript.metadata.grants) { | |
// no userScript API to grant to this userScript. | |
return; | |
} | |
// collected the APIs that should be granted into | |
// an map "API function name" -> "API function implementation" | |
let grantedAPIs = {}; | |
for (const grant of userScript.metadata.grants) { | |
grantedAPIs[grant] = userScriptAPIs[grant]; | |
} | |
// Register all the allowed API on the sandbox. | |
userScript.registerAPI(grantedAPIs); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Map<name: string -> {source: string, apiOptions: object, metadata: object, script: RegisteredUserScript}> | |
const userScrips: new Map(); | |
// RegisteredContentScript | |
let apiContentScript; | |
// This would be a custom function implemented by the extension, | |
// which would parse the user script source and extract its | |
// metadata (e.g. name, grants etc.) | |
// and the apiOptions (url pattern to match, when it should run ext.) | |
function parseUserScript(source) { | |
... // parse source header for userScript name and options | |
return {metadata, apiOptions}; | |
} | |
const parentAPI = { | |
async GM_something([param1], userScript) { | |
// May check userScript.metadata to affect the result. | |
const result = // ... | |
const result = await somethingAsync(args); | |
return result; | |
} | |
}; | |
async fuction registerUserScript(userScriptSource) { | |
if (!apiScript) { | |
// Lazily register the custom userScripts API methods: | |
// - contentAPI is going to run as a regular content scripts injected automatically where on webpages | |
// that matches one of the registered userScripts | |
// - parentAPI is an optional parameter which can be used to specify a set of userScripts API methods | |
// that have to be executed in a regular extension page (vs. being executed in the | |
// contentScript context as the apiContentScript) | |
apiScript = await browser.userScripts.registerAPI({ | |
contentAPI: {file: "apiContentScript.js"} | |
parentAPI: parentAPI | |
}); | |
} | |
// parse the script source and return the userScript | |
// name (used as the key in the map) and its options | |
// (e.g. matches url pattern, include/exclude pattern, runAt | |
// etc.) | |
const {metadata, apiOptions} = parseUserScript(userScriptSource); | |
userScripts.set(metadata.name, {source: userScriptSource, apiOptions, metadata}); | |
const userScript = await browser.userScripts.register({ | |
...apiOptions, // Used by the API to know which urls to match etc. | |
code: source, // Used by the API to know which source to execute in the userScript sandbox | |
metadata: metadata, // A serializable metadata object which is received by the userScripts API method | |
// implementation (provided by the extension from the registered content script). | |
}); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"manifest_version": 2, | |
"name": "example-userscript-manager", | |
"version": "2.0", | |
"background": { | |
"scripts": ["background.js"] | |
}, | |
"permissions": ["userScripts", "<all_urls>", "..."] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@mixedpuppy 👍 allowing to register a set of function all at once from userScript.registerAPI sound good to me too
yeah, exactly, the
apiOptions
from the example are supposed to include most of the options supported by contentScripts.register (at leastmatches / excludeMatches
,includeGlobs / excludeGlobs
,allFrames
,matchAboutBlank
andrunAt
)