Preamble
FireMonkey is a totally new combined user-script and user-style manager. While it has similar functions to other user-Script managers like Greasemonkey/Tampermonkey/Violentmonkey, and user-style managers like Stylish/Stylus/xStyle, there are also differences. Similar to other userscript managers, scripts (or CSS) are injected in tabs when tab is reloading. The behaviour differs from Stylus which can inject CSS into a tab and remove it without having to reload the tab. ⓘ To make permanent changes without reloading the tab is not possible with the current API but changes are planned as part of Manifest v3 update.
Injection into page
context are prevented by Firefox on sites with restrictive Content Security Policy (CSP) e.g. github.com, gitee.com, twitter.com, mail.google.com (Bug 1411641), as well as certain domains.
FireMonkey v2.44 is expected to be fully compatible with all Greasemonkey GM4 compatible userscripts, and 90% of other userscripts. (You can post the issues to the Support.)
Many userscripts declare @grant
for GM APIs that they don't use.
Area | Details | Exposure |
---|---|---|
UserScript | ||
uncommon @grant | GM_cookie , GM_getTab , GM_getTabs , GM_saveTab ⓘ | 55 / 63,435 (0.08%) scripts use these APIs |
uncommon @grant | window.close , window.focus , window.onurlchange ⓘ | 483 / 63,435 (0.76%) scripts use these APIs Probably a lot less as there would be duplicates |
Content Security Policy (CSP) | unsafeWindow or page context injection on pages with strict CSP that only work with Tampermonkey since Tampermonkey alters CSP ⓘ | |
UserStyle | ||
@var range @var number @var select invalid JSON |
|
|
@var checkbox | Requires JavaScript to process logical true/false which is not part of CSS | |
@preprocessor less | Requires Less JavaScript Library | |
@preprocessor stylus | If uses JavaScript or features which are not part of CSS | |
@-moz-document regexp | not supported by Firefox API |
Quick Fixes
- SyntaxError: return not in function
- 💡 Use Wrap code in IIFE
- Greasemonkey, Tampermonkey & Violentmonkey (as well as Node.js), wrap the userscripts in an anonymous IIFE (Immediately Invoked Function Expression) in order to limit its exposure to the page JavaScript. As a result, such JavaScript parse errors do not cause a problem, however that created a situation where developers write JavaScript with syntax errors.
- ESLint displays the parse error for above
- JSHint currently doesn't display the error
- See also: JavaScript: Mark return out of function
@grant none
& works in TM|VM- 💡 Try setting
@inject-into page
in User Metadata - Userscript needs to run in sub-frames
- 💡 Try setting
@allFrames true
in User Metadata - See also: defaults
- No
@run-at
and script runs late - 💡 Try setting
@run-at document-end
in User Metadata - See also: defaults
window.addEventListener('load', ...)
(or'DOMContentLoaded'
) does not fire- 💡 Try setting
@run-at document-end
in User Metadata - See also: @run-at
Manifest V3
Changes introduced by Chrome in manifest v3 (MV3) will have considerable effect on the extensions.
With the info available so far, ManifestV3 will kill Violentmonkey, Tampermonkey, Stylus, Stylish, and similar extensions. Google's engineers are either lying or evading such questions when asked directly. There's nothing we can do about that.
Violentmonkey and manifest V3
- 1. Executing arbitrary strings
Chrome MV3 initially removed the option to execute arbitrary strings which is needed to inject userscripts.
In Manifest V2, it was possible to execute an arbitrary string of code using
Executing arbitrary stringstabs.executeScript()
and the code property on the options object. Manifest V3 does not allow arbitrary code execution. In order to adapt to this requirement, extension developers can use thescripting.executeScript()
method to either inject a static file or a function.Chrome has since changed its position on supporting userscript managers.
Summary: Userscript managers cannot inject scripts that are not included in the extension's package due to platform and policy changes that prevent arbitrary code execution. To address this, the Manifest V3 platform will be expanded to specifically support user-authored scripts and styles.
Estimated timeline:
Known issues when migrating to Manifest V3Canary support around October, 2022.Targeting Canary support in the first quarter of 2023.See also
- 2. includeGlobs/excludeGlobs
- Chrome MV3 has removed
includeGlobs/excludeGlobs
parameters which affects the use ofinclude/exclude
. ⓘ - 3. Setting Cookie, Host, Origin, Referer
- UserScript managers have been using
webRequest.onBeforeSendHeaders
to implement setting, Cookie, Host, Origin, Referer. While Firefox continues to support'blocking'
in`webRequest`
, Chrome does not. It odes not appears to be achievable with DNR (declarativeNetRequest). - 4. GM xmlHttpRequest
GM xmlHttpRequest
was created to bypass CORS restrictions. The API is performed from the background script where CORS does not apply. Additionally, it can set a few Forbidden header name which might not be possible in Chrome MV3 due to the removal of blocking version of the Web Request API. ⓘ.- Greasemonkey: This method performs a similar function to the standard XMLHttpRequest object, but allows these requests to cross the same origin policy boundaries.
- Violentmonkey: Makes a request like XMLHttpRequest, with some special capabilities, not restricted by same-origin policy.
The future of
GM xmlHttpRequest
in Chrome for Tampermonkey & Violentmonkey will be subject to change.An example of a cross-origin request: the front-end JavaScript code served from
https://domain-a.com
uses XMLHttpRequest to make a request forhttps://domain-b.com/data.json
.For security reasons, browsers restrict cross-origin HTTP requests initiated from scripts. For example,
Cross-Origin Resource Sharing (CORS)XMLHttpRequest
and the Fetch API follow the same-origin policy. This means that a web application using those APIs can only request resources from the same origin the application was loaded from unless the response from other origins includes the right CORS headers.To improve security, cross-origin fetches will soon be disallowed from content scripts in Chrome Extensions. Such requests can be made from extension background pages instead, and relayed to content scripts when needed. [The document has been edited on 2020-09-17 to reflect that CORS-for-content-scripts has successfully launched in Chrome 85*.]
Changes to Cross-Origin Requests in Chrome Extension Content Scripts⚠️ Warning
In Manifest V3,
Cross-origin XMLHttpRequestXMLHttpRequest
is not supported in background pages (provided by Service Workers). Please consider using its modern replacement,fetch()
.
See also
- Manifest v3 in Firefox: Recap & Next Steps
- [Chrome] Manifest V3: examine the effects
- Requiring Service Workers for extensions fundamentally user hostile
- Issue 1054624: Dynamic content script support
- Chrome's Manifest V3 proposal would break Tampermonkey
- Background user scripts in Manifest V3? - Google Groups
- Manifest V3: The Ghostery perspective
- Mozilla opens testing for Manifest v3 extensions in Firefox
- Objects in Manifest V3 are closer than they appear — and it's not good news
- Chrome Users Beware: Manifest V3 is Deceitful and Threatening
- Google's Manifest V3 Still Hurts Privacy, Security, and Innovation
- Firefox Just Gave 3 Billion Google Chrome Users A Reason To Switch
- Opinion: it is time to switch from Chrome to another browser
- The EFF will fight Google Chrome Manifest v3 which kills extensions that reliably block ads
- Google makes the perfect case for why you shouldn’t use Chrome
How to Use
Toolbar Icon
Toolbar Icon Context Menu (not available on Android)
- Options
- Open the Options page
- New UserScript
- Open the Options page and display a new Script Template that you can edit and Save
- New UserCSS
- Open the Options page and display a new CSS Template that you can edit and Save
- Help
- Display the Help document
- Log
- Display the latest (default 100) error and script-update messages
- Log data is not available in Container/Private mode (Bug 1659998)
- Locale Maker
- Open the Locale Maker stand-alone module to create & share locale translations
Toolbar Icon Badge 5
Badge indicates the number of Script/CSS that are active in the tab (and all its iframes)
Toolbar Icon Title
Displays the active Script/CSS on mouse-over
Toolbar Icon Pop-up
The pop-up displays the installed Scripts/CSS. "Tab Scripts & CSS" shows the ones running on the active Tab. The number in the "Tab Scripts & CSS" shows the number of frames of the page. The minimum is 1 which is the main/top frame.
- ✔
- Shows Script/CSS is enabled. Disabled Script/CSS are greyed out and clicking that area toggles the enable/disable
- ✘
- Shows that there was an error when registering the script & the Information page displays the error
- Click to Hide/Show Script & CSS
- {name}
- Shows the Information for the Script/CSS
- Displays homepage link if script has
@homepage
,@homepageURL
,updateURL
,@website
, or@source
- Displays support link if script has
@support
, or@supportURL
-
- 🖌 Edit
- Button on Information page opens the Options Page and selects the current Script/CSS
- ▷ Run
- Inject the displayed userScript or userCSS (not userStyle) as long as it was not already injected in the tab
- The userScript is injected as plain JavaScript. Its metadata block would not be processed and it has no access to GM API.
- UserScript is injected into
content
context which has more privilege thanuserScript
context and untrusted code should not be used. - ◁ Undo
- Undo/Remove the displayed userCSS if it was injected temporarily by ▷ Run
- Script Commands
- Shows available Script Commands, click on each to run
- Scratchpad
- Insert CSS & JavaScript temporarily into the active tab
- Scratchpad recalls the last entries for repeated use
- JavaScript is injected into
content
context which has more privilege thanuserScript
context and untrusted code should not be used. -
- ▷ Run
- Insert the Scratchpad code (JavaScript or CSS)
- ◁ Undo
- Undo/Remove the CSS Inserted by Scratchpad
- Click to clear the box and the saved code
- Find scripts for this site
- Search for scripts for this site
- Options
- Open the Options Page
- ➕
- Display a new Script Template that you can edit and Save
- ➕
- Display a new CSS Template that you can edit and Save
- Help
- Display the Help document
Page Context Menu (not available on Android)
Install Stylish Style
Install styles on userstyles.org
Script/CSS Install
Web Install
Web Install is available from greasyfork.org & openuserjs.org. Script install URL will be set for script update.
Direct Install
Scripts can be directly installed from any script ending in .user.js/.user.css
(remote or local file system) if loaded into a tab (open or drag & drop). In case of HTTP/s links, the same link will be set for script update. (v2.57)
Due to sandbox CSP header on (Bug 1411641).https://raw.githubusercontent.com/........uers.js
tab is forwarded to cdn.jsdelivr.net
mirror.
📱 Firefox on Android
Support is experimental and based on user feedback.
Options
Auto-Update Interval
You can set the number of days between updates. Setting 0 (default) means there will be no auto-update. In order to minimise the impact on your browsing, auto-update is set to run when Firefox is idle and then 10 updates at a time (until the next idle time).
Updating each script can take around 4-5 seconds. If there are many scripts, that can add up to consider amount of time. I would suggest manually updating Scripts/CSS or enabling auto-update only on the ones that require regular update.
Enable Storage Sync
Enable to sync storage if it is under 100KB. If the storage is over 100KB, there will be a notification and the Sync will be turned off automatically to avoid repeated errors.
For Firefox a user must have Add-ons checked under the "Sync Settings" options in "about:preferences".
The main use case of this API is to store preferences about your extension and allow the user to sync them to different profiles. You can store up to 100KB of data using this API. If you try to store more than this, the call will fail with an error message. The API is provided without any guarantees about uptime or performance.
storage.sync
Storage quotas for sync data
- Maximum total size 102,400 bytes (100KB)
- Maximum item size 8,192 bytes (8KB)
- Maximum number of items 512
Each script will have a single entry in FireMonkey which is the script itself plus all its associated data (around 1-2KB more). The maximum allowed by Firefox Sync is 8KB for each item. Therefore, each script should be smaller than 6-7KB in order to Sync. Firefox Sync is impractical for user script/style managers unless user has a few small scripts/styles.
There is a discussion to increase the storage sync quota to 1MB (Discuss limits applied to storage.local and storage.sync API).
Counter
Enable/Disable Script/CSS counter (Toolbar Icon Badge & Toolbar Icon Title)
Global Script Exclude Matches
Exclude Match pattens (one per line) to prevent all scripts (not CSS) from running on them. Changes to Global Script Exclude Matches will unregister all running script. Once tab is reloaded or on new tab, new settings will take effect.
Invalid match pattern will cause all scripts to fail.
Custom Options CSS (advanced users)
Customize Options page style and/or CodeMirror style/theme with valid CSS.
Changes can break the layout.
Creating a Custom Theme
You can create your own custom theme by overriding a theme like the default theme.
/* DEFAULT THEME */ .cm-s-default .cm-header {color: blue;} .cm-s-default .cm-quote {color: #090;} .cm-negative {color: #d44;} .cm-positive {color: #292;} .cm-header, .cm-strong {font-weight: bold:} .cm-em {font-style: italic:} .cm-link {text-decoration: underline:} .cm-strikethrough {text-decoration: line-through:} .cm-s-default .cm-keyword {color: #708;} .cm-s-default .cm-atom {color: #219;} .cm-s-default .cm-number {color: #164;} .cm-s-default .cm-def {color: #00f;} .cm-s-default .cm-variable, .cm-s-default .cm-punctuation, .cm-s-default .cm-property, .cm-s-default .cm-operator {} .cm-s-default .cm-variable-2 {color: #05a;} .cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;} .cm-s-default .cm-comment {color: #a50;} .cm-s-default .cm-string {color: #a11;} .cm-s-default .cm-string-2 {color: #f50;} .cm-s-default .cm-meta {color: #555;} .cm-s-default .cm-qualifier {color: #555;} .cm-s-default .cm-builtin {color: #30a;} .cm-s-default .cm-bracket {color: #997;} .cm-s-default .cm-tag {color: #170;} .cm-s-default .cm-attribute {color: #00c;} .cm-s-default .cm-hr {color: #999;} .cm-s-default .cm-link {color: #00c;} .cm-s-default .cm-error {color: #f00;} .cm-invalidchar {color: #f00;} .CodeMirror-composing { border-bottom: 2px solid: } /* Default styles for common addons */ div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;} div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;} .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } .CodeMirror-activeline-background {background: #e8f2ff;} /* STOP */
Custom Popup CSS (advanced users)
Customize Toolbar Popup style/theme with valid CSS.
Changes can break the layout.
CodeMirror (advanced users)
Customize CodeMirror Configuration & JSHint Options with a valid JSON. JSHint is set to ECMAScript 2020 ES11 & fairly strict guidelines for coding excellence.
- CodeMirror Options
- Any option except
lint
|mode
- JSHint Options
- Any option except
globals
- Tab to indent with spaces
- Also relates to
tabSize
,indentWithTabs
&indentUnit
- The setting causes the whole line to move, no matter where the cursor is
- Key Maps
- Set Key Maps to Emacs, Vim, and Sublime Text-style keymaps
- Rulers
- Set Rulers (
array
of objects)
{ // CodeMirror Options "indentWithTabs": true, "indentUnit": 4, "tabSize": 4, // JSHint Options "jshint": { "curly": false, "varstmt": false }, // match-highlighter examples "highlightSelectionMatches": true, "highlightSelectionMatches": { "showToken": true, "annotateScrollbar": true }, // Tab to indent with spaces "extraKeys": { "Tab": "indentMore", "Shift-Tab": "indentLess" }, // Key Maps "keyMap": "emacs", "keyMap": "sublime", "keyMap": "vim", // rulers "rulers": [ { "color": "#f50", "column": 30, "lineStyle": "solid" } ], }
Preferences: Import/Export
You can import/export Preferences (for backup or share) from/to a local file on your computer.
Import from URL will fetch
preference JSON from a remote server. The URL is then saved temporarily in localStorage
for reuse.
Import is non-destructive. Click Save to apply the changes.
Since v2.25, import will over-write options and scripts with the same name but keeps other scripts. Import is additive which means entries from an even incomplete preferences will be added.
It is better to avoid importing pre-2.25 back-ups.
Save
Please note that changes will not take effect until they are saved.
Script & CSS
Sidebar
- Script List
- Lists both UserScript and UserCSS
- Scripts and CSS have different icons, and disabled ones are grey
- Click on the Script/CSS to view/edit/enable/disable/delete
- shows that there was an error when registering the script (
Scripts with errors get disabled automatically) - Click to hide/show all enabled Script & CSS
- Click to hide/show all disabled Script & CSS
- Click to hide/show all Scripts
- Click to hide/show all CSS
- Counter
- Shows the number of Script & CSS at the bottom
- 📥 Import
- Import one or more Script/CSS from file
- Script/CSS with the same name will be overwritten without warning
- 📤 Export
- Export all Script/CSS to FireMonkey folder in the download location set in Firefox Options ➜ Downloads
- 📱 Disabled on Android
- 📥 Import from Stylus
- Export styles from Stylus and Import the JSON (on pop-up change the file type selection to All Files)
- Script/CSS with the same name will be overwritten without warning (note: UserStyle)
Editing
CodeMirror editor and a number of its addons is used for easier editing (v2.0).
CodeMirror & addon Guide
Extra Keyboard Commands (when editor is active)
- Ctrl-S Save
- Ctrl-/ Toggle comment
- Ctrl-Space Show hint
- F11 Toggle fullscreen
- Esc Exit fullscreen
Editor Area Buttons
- Multiple Enable/Disable, Delete & Export
- Ctrl or Shift click to select multiple of items listed under Script & CSS
- 💾 Save
- Save the currently displaying Script/CSS
- Trailing Spaces are removed automatically when saving
- 🔄 Update
- Update the currently displaying Script/CSS
- 📤 Export
- Export the currently displaying Script/CSS to file
- ☑️ Enable
- Enable/Disable the currently displaying Script/CSS
- Changes apply immediately without the need to Save.
- ☑️ Auto-update
- Enable/Disable the Auto-Update of the currently displaying Script/CSS
- Changes apply immediately without the need to Save.
- Delete
- Delete the currently displaying Script/CSS after confirmation
- ➕
- Display a new Script Template that you can edit and Save
- ➕
- Display a new CSS Template that you can edit and Save
- Beautify
- Beautify the code
- Beautify is non-destructive. Click Save to apply the changes.
- User Variables
- View & edit User Variables, if any
- Reset will revert to default values after Save
- Editing is non-destructive. Click Save to apply the changes.
- 🗒 Report
- Display Lint Report with click-able lines to go to the corresponding line in the Script/CSS
- Menu
-
- Theme
- Select Light/Dark Theme for the editor
- Default scheme is based on prefers-color-scheme but also possible to set to dark on light schemes.
- 💾 Save Template
- Save the currently displaying Script/CSS as a Template
- ➜ Tabs to Spaces
- Convert Tabs to spaces (2 per tab)
- 🔠 Selection to Uppercase
- Convert selection to uppercase characters
- 🔡 Selection to Lowercase
- Convert selection to lowercase characters
- 🛠 Wrap code in IIFE
- Wrap code in IIFE (Immediately Invoked Function Expression) to fix
SyntaxError: return not in function
. - 👤 User Metadata
- Persistent user metadata rules to add/amend/disable original Script & CSS metadata rules
- You can use the select to quickly enter values.
// Disable Rule // without a value disables ALL original metadata rules of each entry // with a value disables SINGLE original metadata rule of each entry @disable-match @disable-exclude-match @disable-include @disable-exclude @disable-container (Firefox 97-98) // Add/Amend Rule changes existing metadata rules @match @exclude-match @matchAboutBlank @allFrames @inject-into @run-at @container (Firefox 97-98) @updateURL @metaURL
- 💿 Storage
- View/Edit userscript storage
- Storage value must be a valid JSON or nothing (v2.66)
Editor Area Statistic Info
Statistical information is displayed under the the editor of the Script/CSS.
Self-Update
For users that edit scripts regularly, a proper editor is more convenient. You can then update the installed version by:
- Copy & Paste directly into the script Editor
- Import from Navigation Buttons
- Drag & Drop to a tab (or open in tab) and confirm to install
In case of repeated edit, you can keep the tab open and refresh and confirm to update the old one.
Update & Auto-Update
Any target can be set for @updateURL
as long it directly leads to the final file and it is in plain text. The .js
or .css
file extensions are not necessary although it makes sense to have them. The .user.
is not necessary.
FireMonkey uses @version
for the update process and both the current file and the update file must have @version
and the version of the target file must be higher.
- In case of GreasyFork/SleazyFork/OpenUserJS, the
@version
viahttps://...../*.meta.js
is checked first - Update will be canceled if the new version has a different name which already exists among user's installed Scripts/CSS
@downloadURL/@installURL
, if present, is set for script update@metaURL
, if present, is set for script update check (v2.68)- If
@downloadURL
, ends in.meta.js
, it will be set as@metaURL
(v2.68) - Install URL will be set as update URL if there is no
@updateURL/@downloadURL
in the metadata block (v2.57) Auto-update will only update enabled Scripts/CSS but manual update can update both enabled and disabled ones
meta.js/meta.css
FireMonkey only checks @version
, therefore a minimal meta.js/meta.css
would suffice.
@version 1.0
FireMonkey | Greasemonkey | Tampermonkey | Violentmonkey | |
---|---|---|---|---|
@updateURL | update target | JSON | ||
@downloadURL | = @updateURL | update target | update target ⓘ | |
install source | update target ⓘ | |||
@metaURL (v2.68) | ||||
meta.js | update target ⓘ | |||
meta.css | ||||
last-modified header | ||||
GreasyFork/SleazyFork | GitHub/Gist | OpenUserJS | ||
@updateURL | ⓘ | |||
@downloadURL | ||||
meta.js | ||||
meta.css | ||||
last-modified header |
Metadata Block
For easier compatibility, format is similar to Greasemonkey Metadata Block for UserScripts. The commenting is different for CSS and as a result a uniform commenting for Metadata Block can be used. The Metadata Block identifier (case-insensitive e.g. ==userscript== etc) for UserScript is ==UserScript== ... ==/UserScript==
and for UserCSS is ==UserCSS== ... ==/UserCSS==
. For clarity, it is best to have the Metadata Block on top (or after 'use strict';
if wanted, but FireMonkey can pick it up from anywhere within the code.
Read UserStyle for ==UserStyle== ... ==/UserStyle==
// ==UserScript== // @name My Script // @description Scripting is fun // @match http://www.example.com/* // @match http://www.example.org/* // @version 1.0 // ==/UserScript==
/* ==UserScript== @name My Script @description Scripting is fun @match http://www.example.com/* @match http://www.example.org/* @version 1.0 ==/UserScript== */
/* ==UserCSS== @name My CSS @description CSS is fun @match http://www.example.com/* @match http://www.example.org/* @version 1.0 ==/UserCSS== */
FireMonkey supports the following entries based on contentScripts.register() & userScripts.register(). Each entry must be in the format of @entry ...some space ... value
. In case of matches & globs, repeat entry for each detail. Match details must conform to the Match Patterns.
Script/CSS developers are free to add any other @entry (e.g. @homepage, @copyright, @support etc) for reference or information.
@name @author @description @version needed for updates @updateURL needed for updates @metaURL meta.js/meta.css URL (v2.8) @match @exclude-match @include merged into includeGlobs @exclude merged into excludeMatches @require script-name/other-URL/specific-library@resource resourceName resourceURL @run-at document-start/document-end/document-idle (default) @noframes if present will set @allFrames to false (default) @allFrames true/false (default) @inject-into page @container default|private|container-1|container-2|... (Firefox 97-98) @matchAboutBlank true/false (default) @includeGlob merged into includeGlobs @excludeGlob merged into excludeMatches @matches browser API style same as @match @excludeMatches browser API style same as @exclude-match @includeGlobs browser API style same as @includeGlob @excludeGlobs browser API style same as @excludeGlobs @runAt browser API style same as @run-at
Metadata | FireMonkey | Greasemonkey | Tampermonkey | Violentmonkey |
---|---|---|---|---|
@name | ||||
@name:xx-YY | v4.11 ⓘ | |||
@author | undocumented ⓘ | |||
@description | ||||
@description:xx-YY | v4.11 ⓘ | |||
@version | ||||
@updateURL | undocumented ⓘ | |||
@metaURL | v2.68 | |||
@match | v0.9.8 (2011) ⓘ | |||
@include | ||||
@exclude | ||||
@exclude-match | ||||
@require | ||||
@resource | ||||
@run-at | ||||
@downloadURL | ||||
@noframes | ||||
@allFrames | ||||
@matchAboutBlank | ||||
@antifeature | v2.0 | |||
@inject-into | v2.13 | |||
@includeGlob | ||||
@excludeGlob | ||||
@matches | ||||
@excludeMatches | ||||
@includeGlobs | ||||
@excludeGlobs | ||||
@runAt | ||||
@homepage | ||||
@homepageURL | ||||
@website | ||||
@source | ||||
@support | ||||
@supportURL | ||||
@container | Firefox 97-98 | |||
@grant | different | |||
@var | ||||
@namespace | ||||
@connect | ||||
@unwrap | v2.13.1 | |||
@nocompat | ||||
@icon ⓘ | ||||
@icon64 ⓘ | ||||
@icon64URL ⓘ | ||||
@iconURL ⓘ | ||||
@defaulticon ⓘ |
defaults
FireMonkey conforms to the Firefox (& Chrome) content_scripts API defaults. In most cases, it would be sufficient and prevents unnecessary overheads of injecting into sub-frames needlessly. However, Captcha, Disqus, etc are usually included as iframes
, therefore, userscript dealing with them should be set to inject into all frames.
FireMonkey | Greasemonkey | Tampermonkey | Violentmonkey | |
---|---|---|---|---|
run-at | document-idle (JS) document-start (CSS) | document-end | document-idle ⓘ ⓘ | document-end |
allFrames | false | true | true | true |
matchAboutBlank | false | @include about:blank |
@name
FireMonkey uses the name
(case-sensitive) as ID for Scripts & CSS, therefore names must be unique. A shorter & concise name is recommended.
Localization
Some entries can be localized for display propose e.g. @name:zh-CN
, @description:kr
. The language code is case-sensitive and must match Language Tags and Locale Identifiers e.g. "en", "en-US", "de", "fr", etc.
Entries without local identifier can also match navigator.language
with local identifier. For example, if navigator.language
is "en-US" it will match name:en-US
or name:en
.
@run-at
FireMonkey default run-at
is document_idle
for Scripts, therefore there is no need for event listeners such as 'load'
or 'DOMContentLoaded'
as they may not apply. If script has to run earlier, then the run-at
has to be set accordingly.
Greasemonkey uses hyphen separated words (document-start|document-end|document-idle
), while the API uses underscore separated words (document_start|document_end|document_idle
). FireMonkey converts the hyphen so both can be used.
FireMonkey & Firefox API run-at
states directly correspond to Document.readyState.
- document_start
- Corresponds to
loading
. The DOM is still loading. - GM addElement/addScript/addStyle &
@inject-into page
, fail in case of@run-at document-start
since the DOM is not available yet to inject into. - document_end
- Corresponds to
interactive
. The DOM has finished loading, but resources such as scripts, images, stylesheets and frames may still be loading. - The state indicates that the DOMContentLoaded event is about to fire.
- document_idle
- Corresponds to
complete
. The document and all its resources have finished loading. - The state indicates that the load event is about to fire.
Tampermonkey supports additional non-standard run-at
.
Tampermonkey Documentation
- document-body
- The script will be injected if the body element exists.
- context-menu
- The script will be injected if it is clicked at the browser context menu (desktop Chrome-based browsers only).
- Note: all
@include
and@exclude
statements will be ignored if this value is used, but this may change in the future.
@grant
Userscripts will have access to all available APIs and therefore @grant
is not needed, however the following has been implemented.
- v2.42: Experimental
@grant
background processing was added to auto-disable GM_* API if userScript supports async type GM.* API - v2.45: To prevent creating unnecessary message listeners,
@grant
is required for GM registerMenuCommand - v2.68:
GM_getResourceText/GM.getResourceText
affects the processing of@resource
. ⓘ
@grant
for GM APIs in FireMonkey in userScripts
context, are inconsequential security-wise. Even in the disputed case of GM.xmlHttpRequest
, the following scripts work the same way in FireMonkey and there is no difference in their security i.e. script A is not more secure than script B, and vice versa.
// ==UserScript== // @name Script A // @match https://*.example.com/* // @grant GM.xmlHttpRequest // ==/UserScript== GM.xmlHttpRequest({ url: 'https://abcd.com/', onload: response => { console.log(response.responseText); } });
// ==UserScript== // @name Script B // @match https://*.example.com/* // ==/UserScript== GM.xmlHttpRequest({ url: 'https://abcd.com/', onload: response => { console.log(response.responseText); } });
// @resource remoteCode http://www.someSite.com/badJS.js // @resource remoteDom http://www.someSite.com/htmlWithBadJS.html const js = GM_getResourceText('remoteCode'); eval(js); const dom = GM_getResourceText('remoteDom'); element.innerHTML = dom;
@container
Setting one or more entries will further limit the script/CSS to certain contextual identity containers e.g. default|private|container-1|container-2|...
after URL matching.
UserCSS Firefox 97: Bug 1470651,
UserScript Firefox 98: Bug 1738567
@container default @container private @container container-1
- no container
- inject into all tabs
- default
- inject only into non-private, non-container tabs
- private
- inject only into private tabs
- 'private' only works if user has allowed the extension to run in private mode.
- container-N
- inject only into Firefox container-N tabs
@inject-into
Setting the value page
will inject the entire userscript into the page
context (more: Extension JavaScript Context).
- Only applies to userscripts
- Only
page
is supported - In
page
mode, userscript injection might get blocked by page Content Security Policy (CSP) - In
page
mode, userscripts have no access to GM functions however ...unsafeWindow
is available to userscript (not @require scripts) for Violentmonkey compatibilityGM.info|GM_info
is available to userscript (not @require scripts) for Tampermonkey/Violentmonkey compatibility
FireMonkey | Greasemonkey | Tampermonkey | Violentmonkey | |
---|---|---|---|---|
default | userScript | content ⓘ | content | page ⓘ ⓘ |
options | page | content | auto | |||
@inject-into | page | page | content | auto | ||
@unwrap | No GM API | No GM API | ||
@grant *** @grant none |
no change | no change | content page ⓘ |
no change |
unsafeWindow |
userScript page |
content ⓘ |
content page |
content ⓘ page ⓘ |
GM info |
userScript page |
content |
content page |
content page |
GM functions |
userScript page |
content |
content page |
content page |
isolated context 1 | ||||
alter page CSP 2 | ⓘ | ⓘ ⓘ | ||
🛡️ Context Security | ||||
context | userScript | content | content | content |
typeof browser typeof chrome | undefined | object | undefined | undefined |
browser chrome | ReferenceError: browser(chrome) is not defined | Object { menus: Getter & Setter, manifest: Getter & Setter, normandyAddonStudy: Getter & Setter, extension: Getter & Setter, i18n: Getter & Setter, storage: Getter & Setter, test: Getter & Setter, userScripts: Getter & Setter, runtime: {…} } | ReferenceError: browser(chrome) is not defined | ReferenceError: browser(chrome) is not defined |
browser.storage chrome.storage | ReferenceError: browser(chrome) is not defined | Object { onChanged: Getter & Setter, local: Getter & Setter, managed: Getter & Setter, sync: Getter & Setter, StorageChange: Getter & Setter, StorageArea: Getter & Setter, StorageAreaSync: Getter & Setter } | ReferenceError: browser(chrome) is not defined | ReferenceError: browser(chrome) is not defined |
window.browser window.chrome | undefined | Object { menus: Getter & Setter, manifest: Getter & Setter, normandyAddonStudy: Getter & Setter, extension: Getter & Setter, i18n: Getter & Setter, storage: Getter & Setter, test: Getter & Setter, userScripts: Getter & Setter, runtime: {…} } | undefined | undefined |
FireMonkey | Greasemonkey | Tampermonkey | Violentmonkey | |
---|---|---|---|---|
default | USER_SCRIPT | USER_SCRIPT | USER_SCRIPT | USER_SCRIPT |
1 Userscripts in FireMonkey are by default injected into a separate isolated userScript
contexts, and therefore there is no interaction between the userscripts. In content
or page
context, the userscripts will share the same context which can result in unexpected behaviour e.g.
- Scripts running on the same page share the same window wrapper
- [Firefox] Scripts share the same window wrapper if `@inject-into content` is used
2 Content Security Policy
Add-ons must not relax web page security headers, such as the Content Security Policy.
(Add-on Policies)It is also worth noting that the hack with removing the CSP can cause a conflict with other addons that use CSP to block content (like uBlock Origin)
. (ⓘ)- Tampermonkey should not modify CSP on websites where it is disabled
@var
Configurable User variables for userScript & userCSS (one per line)
Values are saved according to JavaScript types e.g. boolean
(not suitable for useCSS), number
, & string
. Number strings are converted to number
.
@var
can be used as userScript & userCSS configuration page.
- name
- No white space
- label
- Must be quoted (
'
or"
) if contains white spaces - type & value
- Set configuration type (case-sensitive) interface and corresponding value
- text
- Display a text input
- number
- Display a number input, valid JSON array of
[default, min, max, step, unit]
- Array must be a valid JSON e.g.
.5
, or single quotes'em'
are invalid - Array must contain 4 or 5 items
- Use
null
when not setting a value - If
unit
is not included, value will be a plainnumber
- range
- Display a range input, same rules as
number
- checkbox
- Display a checkbox input, value of
0/1
(not suitable for useCSS) - color
- Display a color input, value of 3/6-digit hex code
- Alpha values are maintained but not customizable in HTML5 Color Picker
- select
- Display a select element
- Default option can be indicated by adding an asterisk * to the end of the key name, otherwise it will be the first option
- Valid JSON array of values e.g.
["value1", "value2", "value3"]
- Valid JSON object of
{"label1": "value1", "label2": "value2"}
- Valid JSON array of values e.g.
- If the string requires quotes, it must be included in the value e.g. font names with spaces
"\"Times New Roman\""
.
/* Do not include the keyword !important in the variable value */ /* format (all on a single line) */ @var type name label value /* implementation in userScript */ const name = value; /* implementation in userCSS */ :root { --name: value; } /* use in userCSS */ body { color: var(--name); } /* Example */ @var text fontSize "Font Size" 2em @var text bgImg "Bg Image" url('http://example.com/bkgd.jpg') @var text border Border 1px solid gray @var text logoImg "Logo Image" none @var checkbox enable Enabled 1 @var color fontColor "Font Color" #ff7700 @var number imgOpacity "Image Opacity" [0.5, 0, 1, 0.1] @var range imgHeight "Max Image Height" [50, 10, 200, 10, "px"] @var select fontName "Font Name" ["Arial", "Helvetica*", "\"Times New Roman\""] @var select headerBg "Header Background Color" {"Orange": "#ffa500", "Skyblue*": "#87ceeb"}
See also
@antifeature
Under-development feature on GreasyFork (#604) to mark possibly undesirable behaviour e.g. miners, ads etc.
Anti-Features are flags packagers apply to apps, warning of possibly undesirable behaviour from the user's perspective, often serving the interest of the developer or a third party.
AntiFeatures
@matchAboutBlank
The code cannot be inserted in top-level about:
frames.
Insert the content scripts into pages whose URL is
"about:blank"
or"about:srcdoc"
, if the URL of the page that opened or created this page matches the patterns specified in the rest of thecontent_scripts
key.This is especially useful to run scripts in empty iframes , whose URL is
"about:blank"
. To do this you should also set theall_frames
key.For example, suppose you have a
content_scripts
key like this:"content_scripts": [ { "js": ["my-script.js"], "matches": ["https://example.org/"], "match_about_blank": true, "all_frames": true } ]If the user loads
content_scriptshttps://example.org/
, and this page embeds an empty iframe, then"my-script.js"
will be loaded into the iframe.
matchAboutBlank Optional
boolean
. Iftrue
, the code will be injected into embeddedabout:blank
andabout:srcdoc
frames if your extension has access to their parent document. The code cannot be inserted in top-levelabout:
frames.Defaults to
tabs.executeScript()false
.
@icon
Icons (@icon, @icon64, @icon64URL, @iconURL, @defaulticon) are not processed. Remote icons can be used to track users e.g. ⓘ
// @icon https://tracking-site.com/.../x-userscript.png
@require
You can use @require
for both scripts and CSS.
- UserCSS
- Require another installed UserCSS by name
- Require a remote CSS
- UserScript
- Require another installed UserScript by name
- Require a remote JS
- Require a remote CSS (URL ending with
.css
)
@require script-name
@require
can be used to pre-include other existing scripts into a script (or other existing CSS into a CSS). For example, user/developer can save some code as a script and name it HelperSet
. It does not need to have match/matches/include/includeGlobs
etc. It is best to have the script disabled.
// ==UserScript== // @name HelperSet // @description A set of helper functions // @version 1.0 // ==/UserScript== function someFunc(id) { // some code } function otherFunc(text) { // some code }
// ==UserScript== // @name My Script // @description Scripting is fun // @match http://www.example.com/* // @match http://www.example.org/* // @version 1.0 // @require HelperSet // ==/UserScript== // some code
// @require jquery-3// @require HelperSet // @require https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.0/css/fontawesome.min.css
/* ==UserCSS== @name My CSS @description CSS is fun @match http://www.example.com/* @match http://www.example.org/* @version 1.0 @require DefaultCSS @require https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css ==/UserCSS== */
@require other-URL ⚠️
The API works slightly differently in comparison to other script managers as the target is not not stored. It uses fetch()
to get the target the first time but on subsequently Request.cache will be used by the browser. Consequently, the latest version of the file will always be fetched. Please note that processing many scripts with many @require
can take time when registering scripts (i.e. browser start-up, disabling & re-enabling Script/CSS and/or FireMonkey).
You can also use @import in userCSS for remote CSS.
/* ==UserCSS== @name My CSS @description CSS is fun @match http://www.example.com/* @match http://www.example.org/* @version 1.0 ==/UserCSS== */ @import 'https://fonts.googleapis.com/css?family=Tangerine'; /* --- or --- */ @import url('https://fonts.googleapis.com/css?family=Tangerine'); body { font-family: 'Tangerine', serif; font-size: 48px; }
📌 Bundled Libraries are removed in v2.68
The removal should only affect userscripts that used library shorthand.
@require specific-library
Few libraries have been packed with FireMonkey which will be injected for user-scripts using @require
. A shorthand (case-insensitive) can also be used.
Based on data provided by GreasyFork (2020-02-09), 8,011/31,170 (26%) use @require
and from those 7,082/11,236 (63%) entries relate to these libraries (& gm4-polyfill).
Following CDN sources are processed:
- ajax.aspnetcdn.com
- ajax.googleapis.com
- apps.bdimg.com
- cdn.bootcdn.net
- cdn.bootcss.com
- cdn.jsdelivr.net
- cdn.staticfile.org
- cdnjs.cloudflare.com
- code.jquery.com
- lf*-cdn-tos.bytecdntp.com
- lib.baomitu.com
- libs.baidu.com
- pagecdn.io
- unpkg.com
CDN Links to | Shorthand | Injected Library Version |
---|---|---|
bootstrap 4 | bootstrap-4 | 4.6.1 |
bootstrap 5 | bootstrap-5 | 5.1.3 ⓘ |
jquery 1 | jquery-1 | 1.12.4 |
jquery 2 | jquery-2 | 2.2.4 |
jquery 3 | jquery-3 | 3.6.0 ⓘ |
jquery-ui 1 | jquery-ui-1 | 1.13.1 |
moment | moment-2 | 2.29.1 ⓘ |
underscore | underscore-1 | 1.13.1 ⓘ |
// @require jquery-3 // @require https://code.jquery.com/jquery-3.5.1.js // @require https://code.jquery.com/jquery-3.5.1.min.js // @require https://code.jquery.com/jquery-3.5.0.slim.js // @require https://code.jquery.com/jquery-3.5.0.slim.min.js // @require https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.js ... etc // @require https://unpkg.com/jquery@3.5.0/dist/jquery.js ... etc // @require https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.0/css/fontawesome.min.css
On popular demand, more libraries may be added in future.
ℹ️ Storing require & resource Targets
In legacy Firefox, extensions were able to save files to their folder in Firefox profile. Script managers would download @require
& @resource
targets, save it to users' HD and inject it when called.
Firefox Quantum no longer allows extension to save files to Firefox profile folder. Script managers will have to use the storage assigned to the extension to save @require
& @resource
files in form of data.
Saving large volume of data to storage impact RAM and CPU resource usage of the extension, slows down read/write to the extension storage and generally impact the browser performance. Known Libraries often are 100+KB to 1+MB e.g. jquery-3.4.1.js 273MB,jquery-3.4.1.min.js 86KB, jquery-ui-1.12.1.js 508KB, jquery-ui-1.12.1.min.js 247KB, angular-1.7.6.js 1.3MB ... etc. In case of @resource
, images for example could be many megabytes of data.
Considering that a user may have many user-Scripts and they may have many @require
& @resource
, the size of storage can get extremely large and its effect on browser performance quite considerable.
Match Patterns
Match Patterns is used in match/exclude-match
and globs in includeGlobs/excludeGlobs
.
A glob is just a string that may contain wildcards. There are two types of wildcard, and you can combine them in the same glob:
- Wildcard * matches zero or more characters
- Character ? matches exactly one character
For example: "*na?i"
would match "illuminati"
and "annunaki"
, but not "sagnarelli"
.
Paths are case-sensitive.
Pattern | match | no-match |
---|---|---|
<all_urls>
Match all URLs (in this case http, https, file) |
http://example.org/ https://files.example.org/
|
resource://a/b/c/ |
*://*/*
Match all http, https |
http://example.org/ https://www.example.org/aaa/ |
|
*://*.example.org/*
|
http://example.org/ https://www.example.org/ http://www.sub.example.org/aaa/ |
|
*://example.org/
|
http://example.org/ https://example.org/ |
https://www.example.org/ http://example.org/aaa/ |
https://*/path
|
https://www.example.org/path |
http://example.org/ http://example.org/path |
file:///blah/*
|
file:///blah/ file:///blah/etc |
file:///etc/ |
Invalid Pattern | Reason |
---|---|
resource://path/ | Unsupported scheme |
https://mozilla.org | No path |
https://mozilla.*.org/ | "*" in host must be at the start |
https://*zilla.org/ | "*" in host must be the only character or be followed by "." |
http*://mozilla.org/ | "*" in scheme must be the only character |
https://mozilla.org:80/ | Host must not include a port number |
*://* | Empty path: this should be "*://*/*" |
file://* | Empty path: this should be "file:///*" |
- accounts-static.cdn.mozilla.net
- accounts.firefox.com
- addons.cdn.mozilla.net
- addons.mozilla.org
- api.accounts.firefox.com
- content.cdn.mozilla.net
- content.cdn.mozilla.net
- discovery.addons.mozilla.org
- input.mozilla.org
- install.mozilla.org
- oauth.accounts.firefox.com
- profile.accounts.firefox.com
- support.mozilla.org
- sync.services.mozilla.com
- testpilot.firefox.com
@include - @exclude
They are the old and error-prone method of matching which has been superseded by @match
/@exclude-match
.
@match/@exclude-match
performance is far more efficient.- FireMonkey attempts to convert
@include/@exclude
to@match/@exclude-match
(v2.35).
If all are not converted, then all will be merged into@includeGlob/@excludeGlob
(duplicates removed). - Due to matching order in Firefox/Chrome APIs, having mixed
@match/@exclude-match
with@include/@exclude/includeGlob/excludeGlob
might have unexpected matched/unmatched results.
include_globs
& exclude_globs
are currently missing from manifest v3 chrome.scripting and scripting.RegisteredContentScript API which could result in deprecation of @include
& @exclude
in userscript managers.
Tampermonkey
eslint: userscripts/better-use-match - Using @include is potentially unsafe and may be obsolete in Manifest v3 in early 2023. Please switch to @match.
ref: Tampermonkey lint.js & /_locales/en/messages.json
Convert deprecated @include with regular expression to @match in userscript
Exclude matches and globs
- include_globs
- Optional. Applied after matches to include only those URLs that also match this glob. Intended to emulate the
@include
Greasemonkey keyword.- exclude_globs
- Optional. Applied after matches to exclude URLs that match this glob. Intended to emulate the
@exclude
Greasemonkey keyword.
The
Greasemonkey @match@match
metadata imperative is very similar to@include
, however it is safer. It sets more strict rules on what the*
character means.
It is recommended to use
Violentmonkey Matching@match
/@exclude-match
rather than@include
/@exclude
because the match rules are safer and more strict.
📊 URL Matching Performance
From best (top) to worst:
- match & exclude-match
- includeGlob & excludeGlob
- include & exclude
- include & exclude with regular expression
Regular Expression in @include & @exclude
Regular Expression support has been implemented for @include/@exclude
(v2.5).
- Regular Expressions start & end with forward-slash e.g.
/http://test\.com/.+/
- Regular Expressions are processed with new RegExp()
- Regular Expressions are set as case-insensitive
- No need to escape forward-slashes
- Regular Expression Character classes should be escaped e.g.
\.
\\d
\\s
\\w
See also
@includeGlob & @excludeGlob
Due to the following logic process in Firefox & Chrome, when using includeGlob
, there must also be the mandatory match
.
Since
matches
is the only mandatory key, the other three keys are used to limit further the URLs that match. To match the key as a whole, a URL must:content_scripts
- match the
matches
property- AND match the
include_globs
property, if present- AND NOT match the
exclude_matches
property, if present- AND NOT match the
exclude_globs
property, if present
/*
==UserCSS==
@name My CSS
@description CSS is fun
@match *://*/*
@includeGlobs http://www.google.*/*
@version 1.0
==/UserCSS==
*/
Converting include/exclude to match/exclude-match
Some example of how you can convert to more robust Match Patterns.
@include | @match |
---|---|
* | *://*/* |
http://* | http://*/* |
https://* | https://*/* |
http*://* | *://*/* |
http*://a.b.c/* | *://a.b.c/* |
*.example.com/* | *://*.example.com/* |
Script API
FireMonkey supports both GM3 & GM4 style (i.e. GM.* & GM_*) APIs.
API | FireMonkey | Greasemonkey | Tampermonkey | Violentmonkey | Stats1 |
---|---|---|---|---|---|
Storage | |||||
GM.getValue | v2.12.0 ⓘ | 739 | |||
GM.setValue | 727 | ||||
GM.deleteValue | 208 | ||||
GM.listValues | 77 | ||||
GM.addValueChangeListener | 10 | ||||
GM.removeValueChangeListener | 1 | ||||
GM.getValues (v2.68) | |||||
GM.setValues (v2.68) | |||||
GM.deleteValues (v2.68) | |||||
DOM | |||||
GM.addElement | v4.12 | v2.13.1 | 4 | ||
GM.addScript | 0 | ||||
GM.addStyle | ⓘ | 102 | |||
GM.popup | 0 | ||||
Other | |||||
GM.createObjectURL (v2.68) ⓘ | 0 | ||||
GM.download | 18 | ||||
GM.fetch | 0 | ||||
GM.getResourceText | ⓘ | 11 | |||
GM.getResourceUrl | different ⓘ | ⓘ | v2.13.1 | 30 | |
GM.import (v2.68) ⓘ | 0 | ||||
GM.info | 69 | ||||
GM.log | 7 | ||||
GM.notification | 42 | ||||
GM.openInTab | 178 | ||||
GM.registerMenuCommand | v4.11 ⓘ | v2.12.10 | 131 | ||
GM.setClipboard | ⓘ | 77 | |||
GM.unregisterMenuCommand | 6 | ||||
GM.xmlHttpRequest | 699 | ||||
GM.cookie | 9 | ||||
GM.getTab | ⓘ | 3 | |||
GM.getTabs | ⓘ | 0 | |||
Storage | |||||
GM_getValue | ⓘ | 5,837 | |||
GM_setValue | 3,782 | ||||
GM_deleteValue | 1,577 | ||||
GM_listValues | ⓘ | 711 | |||
GM_addValueChangeListener | 209 | ||||
GM_removeValueChangeListener | 77 | ||||
GM_getValues (v2.68) | |||||
GM_setValues (v2.68) | |||||
GM_deleteValues (v2.68) | |||||
DOM | |||||
GM_addElement | v4.11 ⓘ | v2.13.1 | 41 | ||
GM_addScript | 0 | ||||
GM_addStyle | 5,452 | ||||
GM_popup | 0 | ||||
Other | |||||
GM_createObjectURL (v2.68) ⓘ | 0 | ||||
GM_download | 478 | ||||
GM_fetch | 3 | ||||
GM_getResourceText | 954 | ||||
GM_getResourceURL | different ⓘ | 355 | |||
GM_info | 731 | ||||
GM_log | 865 | ||||
GM_notification | 617 | ||||
GM_openInTab | (sync returns object) | 1,326 | |||
GM_registerMenuCommand | 1,923 | ||||
GM_setClipboard | 1,556 | ||||
GM_unregisterMenuCommand | 220 | ||||
GM_xmlhttpRequest | 5,436 | ||||
GM_cookie | ⓘ | 49 | |||
GM_getTab | ⓘ | 43 | |||
GM_getTabs | ⓘ | 39 | |||
GM_saveTab | ⓘ | 37 | |||
unsafeWindow |
userScript page |
content ⓘ |
content page |
content ⓘ page ⓘ |
3,057 |
window.close 2 | limited support | ⓘ | v2.6.2 ⓘ | 279 | |
window.focus 3 | v2.12.10 ⓘ | 135 | |||
window.onurlchange 4 | v4.11 ⓘ | 69 | |||
📥 Injected Scripts
✔ every page ✔ every frame ✔ when no active userscript ✔ when turned off |
content.js page.js rea/common.js |
browser.js injected-web.js injected.js sandbox/injected-web.js |
|||
log match userscript line | |||||
Open Source ⓘ | Proprietary License EULA | minified | |||
👁 No Data Collection | Privacy policy | Privacy policy | |||
👁 No Tracking | Google favicon | ||||
👥 Firefox Users | 1400 | 218k | 593k | 72k | |
🔄 Last Update | 2023 | 2021 | 2022 | 2023 |
1 GreasyFork 2022-02-22 from 63,465 userscripts ⓘ
2 window.close
grant results is overwriting the global window.close() and bypassing the safeguard to close a window that it has not opened. Such grant would allow malware userscripts to run code and hide the result by closing the window before user has a change to notice. Furthermore, the choice to close a window/tab opened by the user, should remain with the user. Tabs opened with GM openInTab can be closed with JavaScript window.close()
(v2.45).
3 window.focus
grant results is overwriting the global window.focus(). Such grant could be disruptive in cases where user is playing a game or in the middle of something in another window/tab and the userscript in the background causes its window/tab to become active and come to the foreground. Furthermore, the choice to focus a window/tab, should remain with the user.
4 window.onurlchange
is an experimental implementation in Tampermonkey 4.11.6120 (2020-09-17).
Alternative: Detecting JavaScript Navigation
API | FireMonkey | Stylus | Stylish | xStyle |
---|---|---|---|---|
@var | ||||
@advanced | ||||
@preprocessor | default uso less (@var only) stylus (@var only) |
default uso less stylus |
uso | uso |
@var type @advanced type |
text color (3/4/6/8n hex, rgb/rgba) checkbox (n/a in CSS) range number select dropdown image |
ⓘ text color (3/4/6/8n hex, rgb/rgba) checkbox range number select dropdown image |
ⓘ text color (6n hex, rgb) dropdown image |
ⓘ text color (6n hex, rgb) dropdown image |
@-moz-document url | ||||
@-moz-document domain | ||||
@-moz-document url-prefix | ||||
@-moz-document regexp | ||||
📥 Injected Scripts
✔ every page ✔ every frame ✔ when no active userstyle ✔ when turned off |
content/apply.js content/style-injector.js js/msg.js js/polyfill.js js/prefs.js js/promisify.js |
src/inject/apply.js | src/inject/apply.js | |
Open Source | Proprietary License | minified | ||
👁 No Data Collection | Privacy policy | |||
👥 Firefox Users | 1400 | 76k | 50k | 280 |
🔄 Last Update | 2023 | 2023 | 2018 | 2018 |
Script Storage
ℹ️ Asynchronous & Synchronous Storage
There are 2 types storage APIs available to extensions:
- Extension Storage
- Asynchronous private permanent storage accessible to the extension only
- Synchronous private Web API storage accessible only to the extension domain's privileged pages (background, options, popup etc, but not contentScript or userscript) e.g. sessionStorage | localStorage | IndexedDB
- Web Storage
- Synchronous public storage which is also accessible to web pages to read/write/delete e.g. sessionStorage | localStorage | IndexedDB
Experimental sync storage feature was added in v2.43 which can improve compatibility for most userscripts. It delays userscript execution until sync storage is available (~5-10ms), for userscripts that use sync GM_getValue/GM_setValue/GM_listValues/GM_deleteValue
AND NOT their async GM.*
counterparts.
The recommended async GM.getValue
& GM.listValues
will get the most up-to-date values at any moment.
FireMonkey | Greasemonkey | Tampermonkey | Violentmonkey | |
---|---|---|---|---|
UserScript Storage | extension storage | IndexedDB | extension storage | extension storage |
FireMonkey | Stylus | Stylish | xStyle | |
UserStyle Storage | extension storage | IndexedDB | IndexedDB | IndexedDB |
New API Options: getValue, setValue, deleteValue (v2.69)
Userscripts often need to get/set multiple values. Each storage operation result in costly asynchronous read/write which means resources, time, and hard disk wear (especially for SSDs). These new API options aim to alleviate the aforementioned. The new options are based on StorageArea.get(), StorageArea.set() & StorageArea.remove().
Return value: GM.* Promise | GM_* sync return
- getValue
- Standard: A key with optional default
- New: A key (
string
) or keys (an array of strings, or an object specifying default values) to identify the item(s) to be retrieved from storage. If you pass an empty object or array here, an empty object will be retrieved. If you passnull
, or anundefined
value, the entire storage contents will be retrieved. - Return value: value (or default), storage
object
, key/valueobject
- setValue
- Standard: A key/value pair
- New: An
object
containing one or more key/value pairs to be stored in storage. If an item already exists, its value will be updated. - Return value:
undefined
- deleteValue
- Standard: A key
- New: A
string
, orarray
of strings, representing the key(s) of the item(s) to be removed. - Return value:
undefined
- listValues
- Standard: no argument
- Return value:
array
// stored value for key OR default const value = await GM.getValue(key, default); const value = GM_getValue(key, default); // an array of keys set by script OR [] const keys = await GM.listValues(); const keys = GM_listValues(); // value can be string, number, boolean, or object await GM.setValue(key, value); GM_setValue(key, value); await GM.deleteValue(key); GM_deleteValue(key)
// get the entire storage const storage = await GM.getValue(); // return {key: value, key2: value2, ...} // get values const values = await GM.getValue([key, key2, ...]); // return {key: value, key2: value2, ...} // get values with default const values = await GM.getValue({key: default, key2: default2, ...}); // return {key: value || default, key2: value2 || default2, ...} await GM.setValue({key: value, key2: value2, ...}); await GM.deleteValue([key, key2, ...]);
ℹ️ Dynamic import()
JavaScript ECMAScript 2020 (ES11) dynamic import() can be used in a userscript but there are caveats.
- MIME type
import()
expects JavaScript MIME type e.g. 'text/javascript'- CDN providers usually set the proper headers, however, some sites may not, which results in an error
- See also: JavaScript legacy MIME types
-
https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.slim.min.js https://code.jquery.com/jquery-3.7.0.slim.min.js https://www.unpkg.com/react@16.7.0/umd/react.production.min.js --------------------------------------------------- content-type: application/javascript; charset=utf-8 https://ajax.googleapis.com/ajax/libs/jquery/3.7.0/jquery.min.js -------------------------------------------- content-type: text/javascript; charset=UTF-8
// https://raw.githubusercontent.com/erosman/psl/main/psl.js // content-type: text/plain; charset=utf-8 const {PSL} = await import('https://raw.githubusercontent.com/erosman/psl/main/psl.js'); // TypeError: error loading dynamically imported module // Loading module from “https://raw.githubusercontent.com/erosman/psl/main/psl.js” was blocked because of a disallowed MIME type (“text/plain”).
// https://cdn.jsdelivr.net/gh/erosman/psl@main/psl.js // content-type: application/javascript; charset=utf-8 const {PSL} = await import('https://cdn.jsdelivr.net/gh/erosman/psl@main/psl.js'); const result = PSL.parse('mail.yahoo.co.uk'); // Object { subdomain: "mail", domain: "yahoo.co.uk", sld: "yahoo", tld: "co.uk" }
- CORS
import()
may also fail due to Cross-Origin Resource Sharing (CORS)-
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource.
- CSP
import()
may also fail due to Content Security Policy (CSP)-
Content-Security-Policy: The page's settings blocked the loading of a resource at https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.slim.min.js (“script-src”).
Alternative import()
- 1. Get the module as text
-
// @resource psl https://raw.githubusercontent.com/erosman/psl/main/psl.js // @grant GM_getResourceText const psl = GM_getResourceText('psl'); GM getResourceText is updated in v2.68.
- or ...
-
const response = await GM.fetch('https://raw.githubusercontent.com/erosman/psl/main/psl.js'); const psl = response.text;
- 2. Create object URL from the text
-
const url = GM.createObjectURL(psl);
- or ...
-
const blob = new Blob([psl], {type: 'text/javascript'}); const url = URL.createObjectURL(blob); // optional release later for optimal performance and memory usage
- 3. import()
-
// variables must match module exports const {PSL} = await import(url);
- 4. Release the object URL
-
// optional release the object URL for optimal performance and memory usage URL.revokeObjectURL(url);
import (v2.68)
Asynchronous GM.import
for importing internal or remote dependency-free modules
It returns a promise which fulfills to a module namespace object: an object containing all exports from moduleName
.
The current format is due to the limitations in userScripts
context, as it is not possible to directly pass the class
to the userScripts
context. The API may change once MV3 scripting is finalised.
Return value: object
|| undefined
const obj = await GM.import(module [, option]);
Internal Module
PSL (Public Suffix List) was created for the multi-site "Find scripts for this site", and has been made available to userscripts.
import Option
Experimental option based on Import Attributes, can be used to import various values.
Return value: based on option type (bold values are part of Import Attributes proposal)
- module exports
- javascript, no type (default)
- cjs (CommonJS Module)
- (UMD Modules can be imported without type as global, or as 'cjs', e.g. jQuery)
- JSON object
- json
- string
- css, text
- object URL
- gif, jpeg, jpg, png, webp
- (Type will be taken from the target so any of the above will be the same)
- DocumentFragment
- html, xhtml, xml, svg
- (Any of the above will be the same)
- WebAssembly module exports (not supported ATM)
- webassembly, wasm
// import internal PSL as module const {PSL} = await GM.import('PSL'); const result = PSL.parse('mail.yahoo.co.uk'); // Object { subdomain: "mail", domain: "yahoo.co.uk", sld: "yahoo", tld: "co.uk" }
// variables must match module exports const {PSL} = await GM.import('https://raw.githubusercontent.com/erosman/psl/main/psl.js'); const result = PSL.parse('mail.yahoo.co.uk'); // Object { subdomain: "mail", domain: "yahoo.co.uk", sld: "yahoo", tld: "co.uk" }
// source: http://country.io/ const obj = await GM.import('http://country.io/currency.json', {type: 'json'}); console.log(obj); // {"BD": "BDT", "BE": "EUR", ... }
const css = await GM.import('https://raw.githubusercontent.com/erosman/support/FireMonkey/content/default.css', {type: 'css'}); console.log(css); // '/* ----- Dark Theme ----- */\r\n:root,\r\nbody.dark {\r\n --color: #fff; ...
// source: https://github.com/angus-c/just // ES Module const {default: unique} = await GM.import('https://raw.githubusercontent.com/angus-c/just/master/packages/array-unique/index.mjs'); console.log(unique([1, 2, 3, 2, 3, 4, 3, 2, 1, 3])); // Array(4) [ 1, 2, 3, 4 ] // CommonJS Module const unique = await GM.import('https://raw.githubusercontent.com/angus-c/just/master/packages/array-unique/index.cjs', {type: 'cjs'}); console.log(unique([1, 2, 3, 2, 3, 4, 3, 2, 1, 3])); // Array(4) [ 1, 2, 3, 4 ]
const docFrag = await GM.import('https://raw.githubusercontent.com/erosman/support/FireMonkey/content/help.html', {type: 'html'}); const div = document.createElement('div'); div.appendChild(docFrag); console.log(div.firstElementChild); // <meta charset="utf-8">
const objectURL = await GM.import('https://github.com/erosman/support/raw/master/image/firemonkey.png', {type: 'png'}); const img = document.createElement('img'); img.src = objectURL; document.body.appendChild(img); // in most cases, above should not be necessary, unless image hotlinking was blocked, or to avoid tracking // alternatively ... const img = document.createElement('img'); img.src = 'https://github.com/erosman/support/raw/master/image/firemonkey.png'; document.body.appendChild(img);
// importing jQuery as global variable import('https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.slim.min.js') .then(() => { // jQuery is only available in this block statement jQuery('<div>Hello, World!</div>').appendTo('body'); });
// importing jQuery as global variable (async () => { await import('https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.slim.min.js'); // jQuery is only available in this block statement jQuery('<div>Hello, World!</div>').appendTo('body'); })();
(async () => { // importing jQuery as CommonJS module const jq = await GM.import('https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.slim.min.js', {type: 'cjs'}); jq('<div>Hello, World!</div>').appendTo('body'); })();
createObjectURL (v2.68)
Utility function based on URL: createObjectURL() to first convert the string/object
to a blob
and then to a string containing a URL representing the object given in the parameter. The API was created to facilitate GM import.
The default for type
(optional) is set as 'text/javascript'
(to use with GM import) but any MIME type can be set.
Return value: object URL || undefined
const url = GM.createObjectURL(str [, type]); const url = GM_createObjectURL(str [, type]);
addElement
Utility function compatibility API with Tampermonkey ⓘ & Violentmonkey ⓘ (experimental and subject to change).
GM addElement
may fail in case of @run-at document-start
since the DOM is not available yet to inject into.
Return value: created element || undefined
(on error or for scripts)
const elem = GM.addElement(tagName, attributes); const elem = GM.addElement(parentNode, tagName, attributes);
- parentNode (optional)
- Any node or ShadowRoot
- If omitted, it will be set as
document.head
(<head>
) ||document.body
(<body>
) for 'script', 'link', 'style', 'meta' tagsdocument.body
(<body>
) ||document.documentElement
(<html>
) for others
- tagName (string)
- Any valid HTML tag
- attributes (object)
- Object with any valid attribute and/or
textContent
// loading an external script const elem = GM.addElement('script', {src: 'https://....'}); elem.onload = () => console.log(elem, 'loaded'); // appending to shadowRoot const elem = GM_addElement(parentElement.shadowRoot, 'iframe', {src: 'https://....'}); // appending to DOM const elem = GM_addElement(parentElement, 'a', {href: 'https://....', title: 'Some title', target: '_blank', textContent: 'Some text'});
addScript
Utility function to inject script element (code runs in page context).
GM addScript
may fail in case of @run-at document-start
since the DOM is not available yet to inject into.
Return value: undefined
// standard DOM method const script = document.createElement('script'); script.textContent = `... code ...`; document.body.appendChild(script); script.remove(); // Example 1: string const js = `function sum(x, y) { return x + y; }`; GM_addScript(js); // Example 2: function function someFunc() { // some code } GM.addScript('(' + someFunc + ')();');
addStyle
Utility function to inject style element
GM addStyle
may fail in case of @run-at document-start
since the DOM is not available yet to inject into.
Return value: undefined
// standard DOM method const style = document.createElement('style'); style.textContent = `... css ...`; document.head.appendChild(style); // GM addStyle const css = `body { border-top: 2px solid grey; }`; GM.addStyle(css);
fetch
FireMonkey fetch
API is based on the JavaScript Fetch API which provides the new Promise based interface for fetching resources (including across the network). It will seem familiar to anyone who has used XMLHttpRequest
, but it provides a more powerful and flexible feature set.
For simplicity and compatibility, the same GM style naming is kept for fetch
API although it is not available in Greasemonkey.
Check console & FireMonkey log in case of error e.g.: TypeError: "NetworkError when attempting to fetch resource.").
Return value: response object
|| undefined
Since v2.33 successful GM.fetch returns an object in all successful cases.
// Response Object const response = await GM.fetch(url [, init]); const response = await GM_fetch(url [, init]);
- url
- http, https,
ftp, ftpsⓘ, (file not allowed), can be relative to the web page - init (Optional)
- An options object containing any custom settings that you want to apply to the request. The possible options are:
-
method:
The request method, e.g. GET, HEAD, POST, PUT, DELETE, etc. (can be omitted, defaults to 'GET')headers:
Any headers you want to add to your request, contained within a Headers object or an object literal with ByteString values.body:
Any body that you want to add to your request: this can be a Blob, BufferSource, FormData, URLSearchParams, or USVString object. Note that a request using the GET or HEAD method cannot have a body.mode:
The mode you want to use for the request, e.g., cors, no-cors, or same-origin.credentials:
The request credentials you want to use for the request: omit, same-origin (default), or include. To automatically send cookies for the current domain, this option must be provided. Starting with Chrome 50, this property also takes a FederatedCredential instance or a PasswordCredential instance.Since the request is made from the background script, same-origin and include have the same effect.
cache:
The cache mode you want to use for the request.redirect:
The redirect mode to use: follow (automatically follow redirects), error (abort with an error if a redirect occurs), or manual (handle redirects manually). In Chrome the default is follow (before Chrome 47 it defaulted to manual).referrer:
A USVString specifying no-referrer, client, or a URL. The default is client.referrerPolicy:
Specifies the value of the referer HTTP header. May be one of no-referrer, no-referrer-when-downgrade, origin, origin-when-cross-origin, unsafe-url.integrity:
Contains the subresource integrity value of the request (e.g. sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=).keepalive:
The keepalive option can be used to allow the request to outlive the page. Fetch with the keepalive flag is a replacement for the Navigator.sendBeacon() API.signal:
An AbortSignal object instance; allows you to communicate with a fetch request and abort it if desired via an AbortController.anonymous:
(Optional, true/false (default))
If true, no cookie will be sent with the request.responseType:
(Optional, FireMonkey only)
You can set a responseType for the response e.g:'text'
(default),'json'
,'blob'
,'arrayBuffer'
,'formData'
(it is not possible to return thefetch
response directly from background script)
{ headers: { age: "52512", "cache-control": "max-age=86400, public", "content-encoding": "br", "content-type": "text/html; charset=utf-8", date: "Wed, 24 Mar 2021 16:25:31 GMT", "last-modified": "Wed, 24 Mar 2021 02:02:53 GMT", // ... etc }, bodyUsed: false, ok: true, redirected: true, status: 200, statusText: "OK", type: "basic", url: "https://developer.mozilla.org/docs/Web/API/Fetch_API/Using_Fetch", // plus one of the following properties based on responseType, if method is not HEAD text: ..., json: ..., blob: ..., arrayBuffer: ..., formData: ..., }
// HEAD request const response = await GM.fetch('https://example.com/etc', {method: 'HEAD'}); // simplest, returns response text const response = await GM.fetch('https://example.com/etc'); const text = response.text; // if needed to check the response if (response.ok) { ... } // returns response JSON const response = await GM.fetch('https://example.com/etc', {responseType: 'json'}); const obj = response.json; // with init, returns response text const response = await GM.fetch('https://example.com/etc', { method: 'POST', body: JSON.stringify(data), // data can be `string` or {object}! headers:{ 'Content-Type': 'application/json' }); const text = response.text; // if you don't need to wait for the response GM.fetch('https://example.com/etc') .then(response => callback(response)) .catch(error => console.error(error.message));
xmlHttpRequest
The xmlHttpRequest
interface and mostly compatible with Greasemonkey API.
Return value: response object
// Response Object GM.xmlHttpRequest({url, onload}); // note uppercase H GM_xmlhttpRequest({url, onload}); // note lowercase h
GM.xmlHttpRequest({ url, // http/https,ftp/ftpsⓘ, (file not allowed) can be relative to the web page method, // Optional, defaults to 'GET' if omitted headers, // optional, header object to send with the request data, // optional, e.g. POST data overrideMimeType, // optional, MIME type to send with the request user, // optional, username to send with the request password, // optional, password to send with the request timeout, // optional, number of milliseconds before terminating the request (default 0 = no timeout) withCredentials, // Optional, Boolean, for cross-site Access-Control e.g. cookies, authorization headers or TLS client certificates, defaults to false, no effect on same-site requests Since the request is made from the background script, true/false have the same effect. responseType // Optional, '' or 'text' (default), 'arraybuffer', 'blob', 'document', 'json' if used, get the result from response (not responseText) anonymous, // Optional, true/false (default) if true, no cookie will be sent with the request onload, // callback function onerror, // callback function onabort, // callback function ontimeout, // callback function });
onload, onerror, ontimeout, onabort
{
readyState
response
responseHeaders
responseText
responseType
responseURL
responseXML
status
statusText
finalUrl // clone of responseURL for GM|TM|VM compatibility
}
// simplest GM.xmlHttpRequest({ url: 'https://example.com/etc', onload: response => { console.log(response.responseText); } }); // POST request GM.xmlHttpRequest({ url: 'https://example.com/etc', method: 'POST', data: JSON.stringify(data), // data can be `string` or {object}! headers:{ 'Content-Type': 'application/json' } onload: response => { console.log(response.responseText); }, onerror: response => { console.log(`${response.status} ${response.statusText}`); }, ); // HEAD request GM.xmlHttpRequest({ url: 'https://example.com/etc', method: 'HEAD', onload: response => { console.log(response.responseHeaders); } });
xmlHttpRequest withCredentials
GM.xmlHttpRequest/GM_xmlhttpRequest
are sent from background script where credentials are not available. Sometimes it is necessary to send xmlHttpRequest
from the page context.
const xhr = new window.XMLHttpRequest(); xhr.open('GET', 'https://example.com/'); xhr.withCredentials = true; xhr.onload = response => { console.log(response.responseText); }; xhr.onerror = response => { console.log(`${response.status} ${response.statusText}`); }; xhr.send(null); // or fetch window.fetch('https://example.com/', { credentials: 'include' }) .then(response => response.json()) .catch(error => console.error(error.message));
ℹ️ JavaScript xmlHttpRequest & fetch
Mapped fetch
& XMLHttpRequest
to page window
as a workaround in v2.53
Due to a bug in Firefox userScript
context, CORS fails in JavaScript new XMLHttpRequest()
& fetch()
. Therefore the behaviour of JavaScript xmlHttpRequest & fetch in GM|TM|VM will be somehow different.
After liaising with Mozilla engineers, once the bug is fixed, userscripts will get the same origin as the web-page they are on.
• the content script sandbox has an expanded principal that includes the extension principal, and so in manifest_version 2 extension that allows the imported fetch and XMLHttpRequest to do cross site requests based on the extension host permission (but in manifest_version 3 this is not going to be allowed anymore)
• the user script sandbox has an expanded principal but it doesn't include the extension principal and so the imported fetch and XMLHttpRequest can't do cross site requests based on the extension host permission (and this part is actually intended, the single userScript is not supposed to silently inherit expanded permission that the userScript manager extension does have)
Luca Greco scripts running using userScripts API seem to block all cross-origin requests
See also
Userscripts injected in content
context (as in GM|TM|VM), or in FireMonkey Scratchpad & temporary script injection, carry certain security concerns. Changes are planned as part of Manifest v3 update.
ℹ️ Forbidden header name
All Forbidden header names will be removed from the request by FireMonkey before sending to prevent error.
A forbidden header name is the name of any HTTP header that cannot be modified programmatically; specifically, an HTTP request header name (in contrast with a Forbidden response header name).
Modifying such headers is forbidden because the user agent retains full control over them. Names starting with `
Sec-
` are reserved for creating new headers safe from APIs usingfetch
that grant developers control over headers, such asXMLHttpRequest
.Forbidden header names start with
Proxy-
orSec-
, or are one of the following names:Forbidden header name
- Accept-Charset
- Accept-Encoding
- Access-Control-Request-Headers
- Access-Control-Request-Method
- Connection
- Content-Length
- Cookie
- Cookie2
- Date
- DNT
- Expect
- Host
- Keep-Alive
- Origin
- Proxy-
- Sec-
- Referer
- TE
- Trailer
- Transfer-Encoding
- Upgrade
- Via
See also
After confirmation from AMO (ref 2019-09-28 Rob Wu), modification to the following Forbidden headers will be allowed:
Header names are case-insensitive (v2.30).
- Cookie
- Host
- Origin
- Referer
notification
Currently, only plain text is processed for notification. For compatibility, options object {text, title, image, onclick}
is processed and the text is passed for notification.
Return value: notification's ID
GM_notification('some text'); GM_notification({text: 'some text', image: 'https://example.com/icon.jpg', onclick: '...'});
- text
- text string
- title
- Not processed, script name shows as title
- image
- a data URL, blob URL, or http/https URL
- onclick
- Not processed, may be added on popular demand
openInTab
// usually there is no need to wait for the following, but if needed, use await GM.openInTab(url); GM_openInTab(url, open_in_background);
The default value for open_in_background
honours Firefox configuration. Tabs opened with GM openInTab can be closed with JavaScript window.close()
(v2.45).
Return value: Boolean
true/false
(v2.48)
The object support in openInTab
is not unified among userscript managers.
FireMonkey | Greasemonkey | Tampermonkey | Violentmonkey | |
---|---|---|---|---|
open_in_background + default values |
false | false | true | false |
object + default values |
object (v2.48) active true behaviour (v2.45) active !open_in_background insert true (Firefox default) setParent same as opener tab incognito same as opener tab pinned same as opener tab container same as opener tab | ⓘ | ⓘ active false insert true setParent false incognito false | ⓘ active true insert true (v2.11.0) pinned false (v2.12.5) container 0, 1, etc (v2.12.5) |
return value | true/false (v2.48) | onclose callback closed boolean close function | onclose callback closed boolean close function |
registerMenuCommand
Script Commands can be accessed via toolbar pop-up and are listed under the userscript name. Please note that onclick must be a function.
Return value: undefined
GM.registerMenuCommand(name, onclick); GM_registerMenuCommand(name, onclick);
v2.45: To prevent creating unnecessary message listeners, @grant
is required for GM registerMenuCommand.
// direct method ➜ error: alert runs immediately
GM_registerMenuCommand('Hello, world (direct)', alert('Hello, world! (direct)'));
// anonymous function
GM_registerMenuCommand('Hello, world (anon)', function() { alert('Hello, world! (anon)'); });
// named function
function sayHello() { alert('Hello, world! (named)'); }
GM.registerMenuCommand('Hello, world (named)', sayHello);
unregisterMenuCommand
Unregister the previously created Script Command.
Return value: undefined
GM.unregisterMenuCommand(name); GM_unregisterMenuCommand(name);
GM.unregisterMenuCommand('Hello, world (named)');
getResourceText
If Metadata Block contains @resource
and GM_getResourceText/GM.getResourceText
, text @resource
targets (not images) are fetched at the registration time and cached. Consequently, the latest version of the file will always be fetched. (v2.68)
Return value: text string
|| ''
const text = await GM.getResourceText(resourceName); // returns a Promise const text = GM_getResourceText(resourceName);
getResourceUrl
The API works differently in comparison to other script managers as the target is not stored, but it should work out in most cases. It maps directly to the resourceURL.
Return value: URL string
|| undefined
const url = await GM.getResourceUrl(resourceName); // returns a Promise, note Url camel-case const url = GM_getResourceURL(resourceName); // note URL uppercase
FireMonkey | Greasemonkey | Tampermonkey | Violentmonkey | |
---|---|---|---|---|
return | actual URL | object URL | data URL | object URL (data URL If isBlobUrl is false) |
store data | no | blob | data URL | data URL |
GM.getResourceUrl return | Promise | Promise | Promise | Promise |
// ==UserScript== // @name GM.getResourceUrl test // @description GM.getResourceUrl() API method // @resource CSS http://www.example.com/example.css // @resource logo http://www.example.com/logo.jpg // ==/UserScript==// instead of this code (async () => { const link = document.createElement('link'); link.href = await GM.getResourceUrl('CSS'); link.rel = 'stylesheet'; document.body.appendChild(style); })();
// you can use this one const link = document.createElement('link'); link.href = 'http://www.example.com/example.css'; link.rel = 'stylesheet'; document.head.appendChild(link);
// instead of this code (async () => { const img = document.createElement('img'); img.src = await GM.getResourceUrl('logo'); document.body.appendChild(img); })();
// you can use this one const img = document.createElement('img'); img.src = 'http://www.example.com/logo.jpg'; document.head.appendChild(img);
// ==UserScript== // @name SVG test // @namespace http://tampermonkey.net/ // @version 0.1 // @description try to take over the world! // @author You // @match *://*/a.html // @resource EMOJI_SVG https://raw.githubusercontent.com/twitter/twemoji/master/assets/svg/1f36d.svg // @resource EMOJI_PNG https://raw.githubusercontent.com/twitter/twemoji/master/assets/72x72/1f36d.png // @grant GM_getResourceURL // @grant GM_addStyle // ==/UserScript== (function() { 'use strict'; const svg = GM_getResourceUrl("EMOJI_SVG"); GM_addStyle(` .icon.svg { background-image: url("${svg}"); } `); const png = GM_getResourceUrl("EMOJI_PNG"); GM_addStyle(` .icon.png { background-image: url("${png}"); } `); })();
// ==UserScript== // @name SVG test // @namespace http://tampermonkey.net/ // @version 0.1 // @description try to take over the world! // @author You // @match *://*/a.html // @grant GM_addStyle // ==/UserScript== (function() { 'use strict'; // no real need for @resource & GM_getResourceURL const svg = 'https://raw.githubusercontent.com/twitter/twemoji/master/assets/svg/1f36d.svg'; const png = 'https://raw.githubusercontent.com/twitter/twemoji/master/assets/72x72/1f36d.png'; // more efficient to combine GM_addStyle values GM_addStyle(` .icon.svg { background-image: url('${svg}'); } .icon.png { background-image: url('${png}'); } `); })();
addValueChangeListener
Script storage change listener that returns the key as listener ID (although not necessary in FireMonkey).
Return value: listener ID = key
// note: listenerId = key (not necessary in FireMonkey) const listenerId = GM.addValueChangeListener(key, callback); GM.addValueChangeListener(key, callback); GM_addValueChangeListener(key, callback);
(key, oldValue, newValue, remote)
are passed to the callback function.
- key: the storage key
- oldValue: original value or
undefined
if it was created - newValue: new value or or
undefined
if it was deleted - remote:
true
if change came from another tab orfalse
if from the same tab
// anonymous function GM_addValueChangeListener('test-key', function(...arg) { console.log(...arg); }); // anonymous arrow function GM_addValueChangeListener('test-key', (key, oldValue, newValue, remote) => { console.log(key, oldValue, newValue, remote); });
removeValueChangeListener
Remove listener for key
Return value: undefined
GM.removeValueChangeListener(key); GM_removeValueChangeListener(key);
download
Simple file download from the Internet. url
must be valid but filename
is optional.
url
: http, https, ftp, ftps ⓘ, (file not allowed), can be relative to the web page
If the specified url
uses the HTTP or HTTPS protocol, then the request will include all cookies currently set for its hostname.
Return value: download id
number || undefined
|| Promise reject (check log for error)
// usually there is no need to wait for the following, but if needed, use await GM.download(url, filename); GM_download(url, filename);
GM_download('https;//www.example.com/icon.jpg'); GM_download('https;//www.example.com/icon.jpg', 'new-name.jpg');
setClipboard
Copy text (default) or data (must include type) to the clipboard
Return value: undefined
or reject with error message
// usually there is no need to wait for the following, but if needed, use await GM.setClipboard(text); GM_setClipboard(data, type);
The type support in setClipboard
is not unified among userscript managers.
FireMonkey | Greasemonkey | Tampermonkey | Violentmonkey | |
---|---|---|---|---|
text | ||||
type | v2.45 object e.g. {mimetype: 'text/plain'} string MIME type e.g. 'text/plain' |
ⓘ | ⓘ object e.g. {type: 'text', mimetype: 'text/plain'} string e.g. 'text' or 'html' |
ⓘ string MIME type e.g. 'text/plain' |
popup
FireMonkey only utility function to create a shadow DOM popup blank element with animation that can be customized.
Return value: popup element
const popup = GM.popup(options); const popup = GM_popup(options);
Popup Methods
Utility functions to interact with the created popup e.g.
GM_popup()
, NAME.addStyle()
, NAME.append()
, NAME.show()
, NAME.hide()
, and NAME.remove()
// create a new popup (multiple different popups can be created) // the name used (e.g. popup here) can be anything const popup = GM_popup(); // or GM.popup() // add overall style if needed const css = `p { border: 2px solid #000; }`; popup.addStyle(css); // add content as string const str = '<p>Good <span>Morning</span></p>'; popup.append(str); // add content as single node const div = document.createElement('div'); const p = document.createElement('p'); p.setAttribute('style','color: #fff; text-align: center; font-weight: bold;'); // add inline style if needed div.appendChild(p); popup.append(div); // add content as several nodes (a, b, c, ...) const input = document.createElement('input'); popup.append(div, p, input); // add JavaScript if needed p.addEventListener('click', someFunc); input.addEventListener('change', otherFunc); // show & hide popup.show(); popup.hide(); // remove (from document) popup.remove(); // example with registerMenuCommand GM_registerMenuCommand('Configuration', function() { popup.show(); });
Popup Options (Optional)
Ready-made styles can be set when creating a popup e.g. {type: 'panel-top', modal: false}
- {type: 'center'}
- Basic Center type: center (default, same as omitted)
- Slide Center types: slide-left | slide-right | slide-top | slide-bottom
- Panel types: panel-left | panel-right | panel-top | panel-bottom
- {modal: true} true (default)/false
- By default, popup is modal. In modal mode, clicking the background closes the popup.
- All Center Types are also Modal but the Panel Types can be non-Modal.
- If modal is set to false, use CSS to control the width/height of both
:host
&.content
const popup = GM_popup({type: 'panel-top'});
Popup Elements
Initial DOM elements of the popup can be accessed directly e.g. NAME.host
, NAME.style
, NAME.content
& NAME.close
.
popup.content.querySelector('button').addEventListener('click', someFunc); popup.content.setAttribute('style', `color: #fff; text-align: center; font-weight: bold; font-size: 14px; padding: 5px; background-color: #8b0000; position: fixed; left: 0px; top: -100px; width: 100%; z-index: 101; box-shadow: 0px 3px 10px rgba(0, 0, 0, 0.8); transition: all 1s ease-in-out 0s;`); popup.content.style.color = 'blue'; const p = document.createElement('p'); popup.content.appendChild(p); const button = document.createElement('button'); button.addEventListener('click', someFunc); popup.content.appendChild(button);
Popup Styling
CSS selectors :host
& .content
can be used to change the popup overall style. The content that you add can be styled normally.
The default 'full-page' popup background cover the full page.
CSS selector .close
can be used to style the close button ✖
.
// styling background with :host const css = `:host { background: transparent; }`; popup.addStylecss); // styling content with .content const css = `.content { width: 20em; height: 15em; color: #00f; background: #f0f8ff; text-align: center; border: 2px solid #aaa; }`; popup.addStyle(css);
Background with CSS selector :host
Content with CSS selector .content
info
Return value: object
const info = GM.info; const info = GM_info;
The object properties in GM info
are not unified among userscript managers.
ⓘ
ⓘ
ⓘ
{ // application data scriptHandler: 'FireMonkey', version: 'e.g. 2.68', platform: { // FM|VM, VM: includes browserName, browserVersion arch: 'e.g. arm x86-32 x86-64', os: 'e.g. mac win android cros linux openbsd' }, browser: { // FM only name: 'Firefox', vendor: 'Mozilla', version: 'e.g. 114.0a1', buildID: 'e.g. 20230421211246' }, // script data scriptMetaStr: 'script metadata', // FM|GM|VM without start/end strings, TM with // since it is script data, it is better under info.script.metadata script: { name: 'script name', version: 'script version', // FM|TM|VM: string, GM: string|null description: 'script description', matches: [array of match], excludeMatches: [array of exclude-match], // FM|VM includes: [array of regex include], excludes: [array of regex exclude], includeGlobs: [array of include/includeGlob], // FM only excludeGlobs: [array of exclude/excludeGlob], // FM only grant: [array], // FM|TM|VM require: [array], // FM|VM resources: {object of name: url}, // GM: { {...} }, TM: {...}, VM: [ {...} ] 'run-at': 'e.g. document-idle', // FM|TM runAt: 'e.g. document-idle', // VM|GM, GM: runAt: "end" injectInto: 'e.g. page', // FM|VM, VM: info.injectInto namespace: '', // FM|VM: string, GM|TM: string|null metadata: 'script metadata', // FM only, TM under info.script.header } }
log
Utility function no longer supported by Greasemonkey or Violentmonkey but Tampermonkey still has it. Multiple parameters can be passed. You can also use console.log()
instead.
Return value: undefined
GM.log(text [, text2, ...]) GM_log(text [, text2, ...])
GM_log('one'); GM.log('one', 'two', 'three'); GM_log(GM_info);
⚠️ unsafeWindow
unsafeWindow
in FireMonkey is an alias for window.wrappedJSObject
. You can also use window.wrappedJSObject
or window.eval()
to access page JavaScript globals or to create function & objects in `page` context.
Return value: window.wrappedJSObject
window.eval()
makes the object available to the page script as well while unsafeWindow
, window.wrappedJSObject
does not.
Context | FireMonkey | Greasemonkey | Tampermonkey | Violentmonkey |
---|---|---|---|---|
UserScript | window.wrappedJSObject access page DOM access page JS |
FWIW: Tampermonkey lately uses the userScripts API as content script replacement to allow userscripts to overcome (all remaining) CSP issues without executing them in the content script world and to allow userscripts to run in a clean execution environment if wanted. TM also uses |
||
content | window.wrappedJSObject ⓘ access page DOM access page JS |
ret.window access page DOM access page JS |
global VM object access page DOM access page JS In this mode, |
|
page | window access page DOM access page JS |
window access page DOM access page JS In this mode, |
// page-script.js var foo = "I'm defined in a page script!"; function runTest() { console.log(foo); } // user-script.js console.log(window.foo); // undefined console.log(unsafeWindow.foo); // "I'm defined in a page script!" console.log(window.wrappedJSObject.foo); // "I'm defined in a page script!" unsafeWindow.runTest(); // "I'm defined in a page script!" window.wrappedJSObject.runTest(); // "I'm defined in a page script!" // overriding window functions let hasFocus = new window.Function('return true'); unsafeWindow.document.hasFocus = hasFocus; // another example Object.defineProperty(unsafeWindow.document, 'hidden', {value: false});
This API object allows a User script to access "custom" properties--variable and functions defined in the page--set by the web page. The
unsafeWindow
object is shorthand forwindow.wrappedJSObject
. It is the raw window object inside theXPCNativeWrapper
provided by the Greasemonkey Sandbox.USE OF UNSAFEWINDOW IS INSECURE, AND IT SHOULD BE AVOIDED WHENEVER POSSIBLE.
unsafeWindow
Safer alternatives to unsafeWindow
are also listed in above page.
This command can open certain security holes in your user script, and it is recommended to use this command sparingly.
Please be sure to read the entire article and understand it before using it in a script.
unsafeWindow
exportFunction() & cloneInto()
Support for exportFunction & cloneinto was added in v2.19.
unsafeWindow.setTimeout = exportFunction(setTimeout, unsafeWindow); exportFunction(notify, window, {defineAs: 'notify'}); // object without methods unsafeWindow.messenger = cloneInto(obj, unsafeWindow); // object with methods unsafeWindow.messenger = cloneInto(obj, unsafeWindow, {cloneFunctions: true});
Cookies Isolation
Browsing modes in Firefox can be divided into 3 distinct modes: normal, container, & private/incognito.
JavaScript xmlHttpRequest/fetch
send cookies and credentials with their HTTP requests. In web pages, xmlHttpRequest/fetch
send cookies according to the mode of the tab i.e. cookies belonging to normal browsing mode are not sent when in container or private mode, and vice versa. Cookies in each mode are isolated to preserve users' security and privacy.
Private Browsing
Private Browsing does not save your browsing information, such as history and cookies, and leaves no trace after you end the session. Firefox also has Enhanced Tracking Protection, which prevents hidden trackers from collecting your data across multiple sites and slowing down your browsing.
Private Browsing - Use Firefox without saving history
Cookies
Cookies were first used to customize websites, keep track of shopping carts, and maintain online account security, but today most are used to help companies serve targeted ads.
Here’s how it works: You visit a site, an advertiser leaves a cookie on your browser. The cookie is your unique ID. Your information is stored in the cloud along with that ID. That can include which sites you visited, how long you visited them, what you clicked on, your language preferences and more.
Cookies also help advertisers deliver ads in your social media feeds. Social sites have their own tracking schemes and they’re far more robust. They can track every click, post, and comment. In addition, cookies can report what you’ve been doing online to a social site, which is how some ads follow you into social media.
Firefox Multi-Account Containers
The Firefox Multi-Account Containers add-on isn’t technically a form of private browsing or tracking protection, but it can help keep companies from knowing everything you do online. It lets you open fresh, cookie-free tabs that can be used for different accounts—personal, work, shopping, etc. That means you can use Multi-Account Containers to open several Google accounts at once without any overlap. Most trackers won’t associate the different accounts, keeping your work life separate from your personal life online. Some more advanced trackers, however, can and will track you across different accounts, so beware.
Incognito browser: What it really means
GM xmlHttpRequest/fetch
are sent from FireMonkey background script where modes do not apply. In order to honour users' browsing mode and privacy choice, FireMonkey (v2.35+) isolates cookies that are sent by the userscript according the mode of the tab userscript is running in.
- Normal Browsing Mode
- Cookies are handled by Firefox according to
withCredentials
orcredentials
- Cookies set via GM API headers will merge with Firefox cookies
- Cookies sent back in the response will be handled by Firefox
- Container or Private (Incognito) Browsing Mode
- FireMonkey gets and sends contextual cookies according to the mode
- Cookies set via GM API headers will merge with above cookies
- anonymous: true
- Tells browsers to exclude credentials from the request, and ignore any credentials sent back in the response (e.g., any
Set-Cookie
header) - Cookies set via GM API headers will be sent
fetch
: Same as userscript setting{credentials: 'omit'}
ⓘxmlHttpRequest
:mozAnon
will be set ⓘ
FireMonkey | Greasemonkey | Tampermonkey | Violentmonkey | |
---|---|---|---|---|
Sending Cookies | ⓘ | ⓘ | ||
xmlHttpRequest Browser Cookie Isolation |
v4.12.6132 ⓘ ⓘ | |||
xmlHttpRequest withCredentials (not effective) |
||||
fetch Browser Cookie Isolation |
— | — | — | |
fetch credentials (only 'omit' effective) |
— | — | — | |
anonymous flag | v2.10.1 ⓘ | |||
anonymous block Set-Cookie |
v2.12.5 ⓘ ⓘ | |||
Container/Incognito block Set-Cookie |
||||
download Browser Cookie Isolation |
— | |||
getResourceText Browser Cookie Isolation |
— | |||
First-party isolation ⓘ | ⓘ | ⓘ | ⓘ |
See also
- Enable extensions to send network requests (fetch) with a specific cookieStoreId (container tab context)
- Support cookieStoreId option in userScripts.register
- Contextual Identities: Allow matching on cookieStoreId for browser.contentScripts.register
ℹ️ Detecting JavaScript Navigation
When sites use JavaScript to navigate, Firefox API does not detect the navigation and does not re-inject the userScript/userCSS. In case of userCSS it does not matter since the rules will continue to apply nonetheless, but in case of userScript, it needs to re-run. One way to detect JavaScript navigation is to use MutationObserver with appropriate MutationObserverInit.
// select a simple node that changes in navigation to attach a MutationObserver e.g. <title> // For better performance avoid using a node with a lot of children like <body> when childList: true new MutationObserver(mutationsList => { console.log(mutationsList[0].target.textContent); // re-run the necessary function }) .observe( document.querySelector('title'), {subtree: true, childList: true} );
Navigation: navigate() method
The navigate()
method of the Navigation
interface navigates to a specific URL, updating any provided state in the history entries list.
Not supported by Firefox yet
See also: Implement Navigation API
ℹ️ Xray Vision & Sharing objects with page scripts
Extension JavaScript Context (Scope)
Contexts are sandboxed layers of JavaScript in an extension to ensure security.
Context | Browser API Access | Details |
---|---|---|
browser | all | Trusted privileged code to interact with the browser |
content/contentScript | some | Trusted extension's own JavaScript injected into web page with some browser API privileges (there is only one content context per extension per frame) |
userScript | none (only GM API) | Untrusted unverified 3rd party JavaScript injected into web page without direct browser API privileges (there can be many isolated userScript contexts) |
page | none | JavaScript injected in a web page by the website (there is only one page context per frame) |
As an extension developer you should consider that scripts running in arbitrary web pages are hostile code whose aim is to steal the user's personal information, damage their computer, or attack them in some other way.
The isolation between content scripts and scripts loaded by web pages is intended to make it more difficult for hostile web pages to do this.
Since the techniques described in this section break down that isolation, they are inherently dangerous and should be used with great care.
Sharing objects with page scripts
...
Note that once you do this, you can no longer rely on any of this object's properties or functions being, or doing, what you expect. Any of them, even setters and getters, could have been redefined by untrusted code.
By default, content scripts don't get access to objects created by page scripts. However, they can communicate with page scripts using the DOM
Communicating with the web pagewindow.postMessage
andwindow.addEventListener
APIs.
In Chrome,
eval()
always runs code in the context of the content script, not in the context of the page.In Firefox:
Using eval() in content scripts
- If you call
eval()
, it runs code in the context of the content script.- If you call
window.eval()
, it runs code in the context of the page.
Type | Browser API Access | Details |
---|---|---|
ISOLATED | some | The default execution environment of content scripts. This environment is isolated from the page's context: while they share the same document, the global scopes and available APIs differ. |
USER_SCRIPT | only runtime.sendMessage() |
Details to be finalised See also: User Scripts API |
MAIN | none | The execution environment of the web page. This environment is shared with the web page, without isolation. Scripts in this environment do not have any access to APIs that are only available to content scripts. Due to the lack of isolation, the web page can detect the executed code and interfere with it.
Do not use the |
Injecting code into page context
Sometimes it is necessary to have a script that is available to page script and/or run in page context, as an extension to the page script functions.
const script = document.createElement('script'); script.textContent = `... code ...`; document.body.appendChild(script); script.remove();
See also
- Insert code into the page context using a content script by Rob W
- How can I prevent a webpage from detecting I am running a script? by Brock Adams
- Can a website know if I am running a userscript? by Brock Adams
- Can a webpage detect a tampermonkey userscript? by Brock Adams
Receiving data from page context
CustomEvent can be used send data from a page script.
// inject a function that generates & dispatches a CustomEvent const code = `function sendMessage(message) => { window.dispatchEvent(new CustomEvent('sendMessage', {detail: message})); }; // sendMessage when needed if(... condition ...) { sendMessage(data); }`; const script = document.createElement('script'); script.textContent = code; document.head.appendChild(script); script.remove(); // in user-script window.addEventListener('sendMessage', onMessage); function onMessage(e) { const message = e.detail; // some code }
In Firefox, window.eval()
can also be used to inject code into a page context.
// inject a function that generates & dispatches a CustomEvent const code = `function sendMessage(message) => { window.dispatchEvent(new CustomEvent('sendMessage', {detail: message})); }; // sendMessage when needed if(... condition ...) { sendMessage(data); }`; window.eval(code); // in user-script window.addEventListener('sendMessage', onMessage); function onMessage(e) { const message = e.detail; // some code }
UserCSS
Standard CSS (Cascading Style Sheets) can be injected directly into a page. If the goal is to inject CSS, it is by far more efficient to insert CSS as UserCSS, instead of using UserScript to inject CSS. Furthermore, CSS rules will apply to newly created elements in dynamically updated pages (e.g. on scroll) while JavaScript would need additional listeners to wait for new element to be created and then run then code again.
FireMonkey like other CSS managers (e.g. Stylus) injects CSS at document-start
by default. The benefit of document-start
for CSS is that the changes will display earlier. The drawback is that the page CSS may override these CSS and if so, it is better to inject later at document-end
or document-idle
.
Here is an example of simple UserCSS that I use to mark watched/visited videos on YouTube.
/* ==UserCSS== @name YouTube @match *://*.youtube.com/* ==/UserCSS== */ a[href*="/watch?v="]:visited, a[href*="/watch?v="]:visited h3, a[href*="/watch?v="]:visited yt-formatted-string { color: #f50 !important; }
I have requested for an option to inject UserCSS as "user" which prevents websites from overriding the CSS (Add cssOrigin to contentScripts API) and will implement it once it is available.
Live userCSS Update
Experimental feature was added in v2.43 to update open tabs when userCSS style changes. There are 2 types of CSS that are injected by FireMonkey:
- Permanent CSS injected by Firefox API when tab is loading
- Temporary CSS injected into tabs from Scratchpad, Popup ➜ CSS ➜ Info ➜ ▷ Run, or Live userCSS update
Live userCSS update is applied to the relevant tabs under the following circumstances:
- When a disabled userCSS is enabled, a temporary CSS is injected
- When the CSS of an enabled userCSS is edited, the temporary CSS is removed and a new one is injected
- When an enabled userCSS is disabled, the temporary CSS is removed (if present)
Please note that temporary CSS can not replace permanent CSS but can override it. The current Firefox API does not facilitate the removal of a permanent CSS.
Color Picker
Color indicator before CSS colors (named color, #rgb, #rrggbb, rgb(n,n,n) & rgba(n,n,n,a)) shows the colors. Clicking the indicator will open the HTML5 Color Picker.
New values from Color picker replace the old entries in the same format as the original e.g. named color to named color (if available), #rgb
to #rgb
etc.
Customise 3rd party userCSS
You can override a 3rd party userCSS with custom CSS.
- Disable the 3rd party script (it will auto-update in FM 2.19)
- Create a new userCSS
- Copy the relevant data from the target userCSS e.g.
@match
etc - Give it a new name
- Use
@require
to inject the 3rd party userCSS
/* ==UserCSS== @name ABC Style @match *://*.example.com/* @version 1.0 ==/UserCSS== */ body { border-top: 2px solid grey; }
/* ==UserCSS== @name ABC Style Custom @match *://*.example.com/* @require ABC Style ==/UserCSS== */ body { border-top-color: blue; }
If the 3rd Party userCSS uses CSS custom properties (variables) e.g color: var(--main-color)
, you can also override them.
/* ==UserCSS== @name ABC Style Custom @match *://*.example.com/* @require ABC Style ==/UserCSS== */ :root { --main-color: #fff; --border: #ddd; --color: #000; }
Character Escaping in CSS
CSS Escaping in userScript requires double escaping.
// target element <div class="RichText RichText--sans lg:mb-32"> ... </div> // in userScript const css = ` .lg\\:mb-32 { border: 1px solid red; } `; GM_addStyle(css); // in userCSS .lg\:mb-32 { border: 1px solid blue; }
UserStyle
Partial compatibility with standard CSS syntax UserStyle ==UserStyle== ... ==/UserStyle==
is available. All url()
, url-prefix()
& domain()
are processed. Few very basic regexp()
are converted to match pattern but other sections with regexp()
are ignored since browser API does not support Regular Expression.
The default 'run-at'
is set to 'document-start'
for UserStyles.
See also
@-moz-document regexp()
is not supported as Regular Expressions are not supported by Firefox API.
Installing Styles from userstyles.org (v2.0)
Right-click context-menu on the style page and FireMonkey will create a UserStyle based on the details. UserCSS/UserStyle are by far more efficient and use less resources than a UserScript only for injecting CSS/Style.
userstyles.org is sometimes slow and may time out.
ℹ️ Stylish/Stylus/xStyle Type UserStyle
Since Firefox 61, extensions can not use @-moz-document (never supported on Chrome & other browsers) and therefore extensions would have to:
- Break each UserStyle into sections for each
@-moz-document
- Add listeners to monitor changes to tab/iframe URLs
- Run a comparison loop against each tab and its iframes and each
@-moz-document
in every UserStyle - If there is a match, inject the style section into the tab/iframe
In reality, @-moz-document
segments are separate styles that have been written under one name.
Above process is considerably more resource intensive than using the dedicated API to inject Style/CSS.
Initially in Level 3,
@document
was postponed to Level 4, but then subsequently removed....
-x- Implemented with the vendor prefix: `-moz-`
* Notes Disabled by default in web pages, except for an empty
url-prefix()
value, which is supported due to its use in Firefox browser detection. Still supported in user stylesheets.🏴 Disabled From version 61: this feature is behind the
@documentlayout.css.moz-document.content.enabled
preference (needs to be set totrue
). To change preferences in Firefox, visitabout:config
.
Converting UserStyle @-moz-document to UserCSS @match
The first 3 are quite straight forward and easy to convert. The only difficulty is with the regexp()
. The more complex the regexp (Regular Expressions), the more @match
may be needed, but once done, it is easy to read and maintain.
From | To |
---|---|
@-moz-document domain('images.example.com') | @match *://images.example.com/* |
@-moz-document url-prefix('http://www.example.com') | @match http://www.example.com/* |
@-moz-document url('http://www.example.com/test.html') | @match http://www.example.com/test.html |
@-moz-document regexp('http://www\\.example\\.(com|de)/images/.*') | @match http://www.example.com/images/* @match http://www.example.de/images/* |
@-moz-document regexp('https?:\/\/(www\.|old\.)?reddit.com.*') | @match *://*.reddit.com/* |
Debugging Script & CSS
- Injection Errors
- Check the FireMonkey Log page for recent errors
- CodeMirror Lint
- Check lint messages and report for errors and warnings
- JavaScript Compile Errors (Syntax Errors)
- Usually appear in Browser Console (Ctrl + Shift + J) with a line number and a clickable link to the blob (with some string name) e.g.
SyntaxError: missing : after property id [Learn More] 1f0135df-f993-4f26-9127-bc8ffa21951e:18:11
- JavaScript Run Errors
- Some JavaScript run errors in a page can be checked in Developer Tool (Ctrl + Shift + I or F12) for the page that the JS is injected into e.g.
ReferenceError: assignment to undeclared variable abc [Learn More] 1f0135df-f993-4f26-9127-bc8ffa21951e:18:11 ReferenceError: abc is not defined[Learn More] 1f0135df-f993-4f26-9127-bc8ffa21951e:20:11
- View Inserted UserScript
- You can see the actual blob JavaScript in Developer Tools (F12) Debugger tab under
FireMonkey://userscript/
(v2.68) (user-script://FireMonkey/
pre-2.68) (you may have to refresh the page) - See also:
- Display inconsistency of folder & file (Fixed in FireFox 114)
- Display inconsistency of sourceURL containing a URL
- CSS Errors
- The CSS error can be checked in Developer Tools (F12) for the page that the CSS is injected into.
- You can check Style Editor tab and the injected CSS shows as a blob with a string of characters e.g. da084755-6d22-45a5-870d-82c369299d55.
- More Information: Debugging CSS
- Extension Developer Tools
- about:debugging#/runtime/this-firefox ➟ FireMonkey ➟ Inspect
Injection Comparison
Other userScript/userStyle Managers
- Add listeners to all HTTP requests
- Loop through all scripts on every request to check if the there are scripts that match
- If matches are found, process the scripts/CSS and inject the scripts/CSS
- All injections are handled manually by the extension
- Script injection is made into privileged
content
context, orpage
context
FireMonkey
- All scripts are prepared and passed to Firefox native engine at start-up and when script/CSS is updated
- From then, FireMonkey does nothing and all script/CSS injections are handled by Firefox natively
- Script injection is made into secure
userScript
context
Performance Test
- Load the same userScript/userStyle/userCSS in FireMonkey and in other managers
- Open
about:performance
- Go to a page that above runs on
- Compare
JavaScript Best Practices
- Google JavaScript Style Guide
- JavaScript Style Guide
- 30 JavaScript Best Practices for Beginners
- JavaScript best practices to improve code quality
- JavaScript Best Practices
- JavaScript best practices
- Airbnb JavaScript Style Guide
⚠️ FTP
FTP support has been discontinued in Firefox 90+.
Aligning with our intent to deprecate non-secure HTTP and increase the percentage of secure connections, we, as well as other major web browsers, decided to discontinue support of the FTP protocol.
Stopping FTP support in Firefox 90
Firefox Minimum Version
FireMonkey | Firefox | Note |
---|---|---|
2.68 | 93 (2021-10-05) | To benefit from ECMAScript 2022 aka ES13 Statistics: 2 users below Firefox 95 |
2.42 | 74 (2020-03-10) | To benefit from ECMAScript 2020 aka ES11 Statistics: no users below Firefox 77 |
1.25 | 68 (2019-07-09) | Official userScripts API release |
1.0 - 1.24 | 65 (2019-01-29) | Initial userScripts API release API was added in Firefox 65-67 but was disabled and had to be enabled via about:config?filter=extensions.webextensions.userScripts.enabled
|
Support
Please use the GitHub Community Support.