Skip to content

Instantly share code, notes, and snippets.

@mixedpuppy
Forked from rpl/README.md
Created January 16, 2018 19:35
Show Gist options
  • Save mixedpuppy/0a256a6a17238f0bb3b7ffa6fbb564d1 to your computer and use it in GitHub Desktop.
Save mixedpuppy/0a256a6a17238f0bb3b7ffa6fbb564d1 to your computer and use it in GitHub Desktop.
A fiction example webextension (used to design a new userScripts WE API)
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;
}
for (const grant of userScript.metadata.grants) {
userScript.registerAPI(GM_something);
}
});
// 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 (!apiContentScript) {
// Lazily register the content script which will provide the
// custom APIs to the userScript sandboxes.
apiContentScript = await browser.contentScripts.register({
file: "apiContentScript.js",
matches: ["<all_urls>"],
runAt: "document_start",
});
}
// 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).
parentAPI: parentAPI // An optional parameter which can be used to specify a set of userScripts API method
// that have to be executed in a regular extension page (vs. being executed in the
// contentScript context as the apiContentScript)
});
}
{
"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