-
Notifications
You must be signed in to change notification settings - Fork 20
User Scripts
User scripts are subject to all of the usual limitations of web extensions. Specifically, the user script is evaluated in the content script environment. Within the content script you will have limited access to web extension APIs.
To access privileged APIs, the user script must execute in the background script environment. Foxy Gestures provides a executeInBackground()
function to allow user scripts to execute code in the background.
Available web extension APIs are restricted based on the permissions key in manifest.json so please create an issue if you require access to additional APIs.
User scripts are evaluated in the content/commands.js module within the frame where the gesture began. Functions passed to executeInBackground()
are evaluated in background/commands.js. You may invoke any of the built-in commands in each module in your user script.
The following useful variables are in scope in your user script. These may be changed or renamed as the extension evolves.
- data: An object containing the serializable event information for the gesture. For example:
data = {
"context":{
"frameUrl":"https://www.reddit.com/"
},
"element":{
"tag":"IMG",
"mediaSource": "https://b.thumbs.redditmedia.com/NZkUwNgjHbgvcWULQwrU81FeUDQQMKDnK8mDMaparLc.jpg",
"mediaType": "image/jpg"
}
}
-
mouseDown: A reference to the first mousedown event in the gesture.
-
mouseDown.target: A reference to the DOM element under the mouse when the gesture started.
-
data.element.linkHref is the href attribute of any enclosing <a> tag, not just the href of the element under the gesture..
-
data.element.mediaSource is the src attribute of any media element (img,video,audio,etc.) under the mouse.
-
data.element.mediaType is the mime type of any media element under the mouse. Only populated if specified, such as in HTML5 audio and video elements. It may be used to infer a file type from extensionless source URLs.
By default, a wheel or chord gesture can only be performed once and then the gesture is complete. (This ensures that the gesture state is cleaned up if the active tab changes.) If you want to repeat the gesture without releasing all buttons, you can end your user script like so:
// Allow the wheel or chord gesture to repeat.
var result = { repeat: true };
result;
executeInBackground(func, args);
Executes a function in the privileged background context. func
is a function to execute. args
is an array of arguments to pass to func
. This function works by serializing func
using Function.prototype.toString()
and sending it to the background context. You may not use any closures or references external to func
. All values in args
must be serializable as JSON, so you cannot use DOM references. This function returns a promise that is resolved with the return value of func
.
Example:
var src = data.element.mediaInfo && data.element.mediaInfo.source;
var promise = executeInBackground(src => {
return browser.downloads.download({ url: src });
}, [ src ]);
promise.then(downloadId => {
console.log('download ID is', downloadID);
});
getCurrentWindow()
Returns a promise that resolves to the currently active window.
getCurrentWindowTabs()
Returns a promise that resolves to an array of tabs in the currently active window.
getActiveTab(callback)
Invokes the callback passing the currently active tab as the only argument.
var src = data.element.mediaInfo && data.element.mediaInfo.source;
if (src) {
executeInBackground(src => {
getActiveTab(tab => browser.tabs.create({
url: src,
index: tab.index + 1,
active: true
}));
}, [ src ]);
}
Saves an image, video, or audio file to custom location in the downloads folder. Rules are evaluated in order to determine the target path and filename.
(function () {
let src = data.element.mediaInfo && data.element.mediaInfo.source;
if (!src) {
setStatus('Quick Save: No media found');
return;
}
let placeholders = [], rules = [];
// -- Define placeholders --
placeholders.push([ '%IMAGE_DIR%', 'Images/' ]);
placeholders.push([ '%VIDEO_DIR%', 'Video/' ]);
placeholders.push([ '%AUDIO_DIR%', 'Audio/' ]);
// -- Define rules --
// Capturing group 1 must be the filename.
// Defaults
rules.push({ name: 'Images', regex: /([^\/]+\.(gif|png|jpe?g))($|\?|#)/i, target: '%IMAGE_DIR%/%F%', erase: true });
rules.push({ name: 'Video', regex: /([^\/]+\.(webm|mp4|mkv))($|\?|#)/i, target: '%VIDEO_DIR%/%F%' });
rules.push({ name: 'Audio', regex: /([^\/]+\.(mp3|flac|m4a|aac))($|\?|#)/i, target: '%AUDIO_DIR%/%F%' });
// Evaluate rules
let match = null;
for (let i = 0; i < rules.length; i++) {
if ((match = src.match(rules[i].regex)) != null) {
// Evaluate placeholders
let filename = match[1];
placeholders.push([ '%F%', filename ]);
let target = placeholders.reduce((target, placeholder) => target.replace(placeholder[0], placeholder[1]), rules[i].target);
// -- Privileged operation begin --
return executeInBackground((rule, src, target) => {
// Start the download
return browser.downloads.download({ url: src, filename: target }).then(id => {
if (rule.erase) {
// Remove the download from history on completion
let listener = (downloadDelta) => {
if ((downloadDelta.id === id) && (downloadDelta.state.current !== 'in_progress')) {
browser.downloads.onChanged.removeListener(listener);
browser.downloads.erase({ id: downloadDelta.id });
}
};
browser.downloads.onChanged.addListener(listener);
}
});
}, [ rules[i], src, target ])
// -- Privileged operation end --
.then(x => setStatus('Quick Save: Saved "' + filename + '" using ' + rules[i].name))
.catch(err => setStatus('Quick Save: Failed to save "' + filename + ': ' + err));
}
}
setStatus('Quick Save: No matching rule');
return;
}());