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 and for a new script/CSS to have an effect, tab must be reloaded. The behaviour differs from Stylus which can inject CSS into a tab and remove it without having to reload the tab.1

Some functions like script injection are prevented by Firefox on sites with restrictive Content Security Policy (CSP), as well as certain domains e.g. accounts-static.cdn.mozilla.net, accounts.firefox.com, addons.cdn.mozilla.net, 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 (ref Content scripts).

1 It is not possible with the current API (Changes are planned as part of Manifest v3 update).

Firefox Minimum Version

Originally, userScripts API was added in Firefox 65-67 but was disabled and had to be enabled via about:config?filter=extensions.webextensions.userScripts.enabled.

The official release of userScripts API was postponed to Firefox 68.

Minimum Version
FireMonkey Firefox Note
1.0-1.2465Initial userScripts API release
1.2568Offitial userScripts API release
2.4274To benefit from ECMAScript 2020 aka ES11
Statitics: no FireMonkey users below Firefox 77

How to Use

Toolbar Icon

Toolbar Icon Context Menu

Options
Open the Options page
New UserScript
Open the Options page and display a new Script Template that you can edit and Save 1
New UserCSS
Open the Options page and display a new CSS Template that you can edit and Save 1
Help
Display Help document 1
Log
Display the latest (default 100) error and script-update messages when not in Private Window 1
Log data is not available in Container/Private mode
Locale Maker
Open the Locale Maker stand-alone module to create & share locale translations

1 In Private Window opens the Options page (ref Bug 1659998).

Toolbar Icon Badge 5

Badge indicates the number of Script/CSS that are active in the tab (and all its iframes)

Toolbar Icon Title

Mouse-over Title displays active Script/CSS

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
UserScript is injected into content context which has more privilege than userScript context and untrusted code should not be used.
The userScript is injected as plain JavaScript. Its metadata block would not be processed and it has no access to GM API.
◁ 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 than userScript 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 greasyfork.org 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
Open the Help document

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 (remote or local file system) if loaded into a tab (open or drag & drop)

In case of greasyfork.org & openuserjs.org links, the same link will be set for script update.

Due to sandbox CSP header on https://raw.githubusercontent.com/........uers.js tab is forwarded to cdn.jsdelivr.net mirror. (ref Bug 1411641).

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).

Please note that 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

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.

This is the impracticality of using Firefox Sync in script/style managers which only works when user has a few small scripts/styles.

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.

Note: 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
/* 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 2019 ES10 & 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
Example
{
  // CodeMirror Options
  "indentWithTabs": true,
  "indentUnit": 4,
  "tabSize": 4,

  // JSHint Options
  "jshint": {
    "curly": false,
    "varstmt": false
  }

  // match-highlighter examples
  highlightSelectionMatches: true,
  highlightSelectionMatches: {showToken: /\w/, annotateScrollbar: true},

  // Tab to indent with spaces
  "extraKeys": {
    "Tab": "indentMore",
    "Shift-Tab": "indentLess"
  }
}

Preferences: Import/Export

You can import/export Preferences (for backup or share) from/to a local file on your computer.

Import is non-destructive. Click save to apply the new settings.

Since v2.25, import will over-write options and scripts with the same name but keeps other scripts.

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

List of both UserScript and UserCSS

Scripts and CSS have different icons and the icon for disabled ones are grey.

Click on the Script/CSS to view/edit/enable/disable/delete or start a new Script/CSS.

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
The counter shows the number of Script & CSS at the bottom
πŸ“₯ Import
Import one or more Script/CSS from file, same name Script/CSS will be overwritten without warning
πŸ“€ Export
Export all Script/CSS to FireMonkey folder in the download location set in Firefox Options -> Downloads
Note: 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), same name Script/CSS 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)

Editor Area Buttons

πŸ’Ύ 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
πŸ—’ Menu
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
 
πŸ”‘ Selection to Lowercase
 
πŸ‘€ User Metadata
Persistent user metadata rules to add/amend/disable original Script & CSS metadata rules
// 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)
Multiple Enable/Disable, Delete & Export
Ctrl or Shift click to select multiple of items listed under Script & CSS

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:

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 & OpenUserJS, the @version via Metadata Block in https://...../*.meta.js is verified first.

Update will fail if the new version has a different name which already exists among user's installed Scripts/CSS.

Auto-update will only update enabled Scripts/CSS but manual update can update both enabled and disabled ones.

@downloadURL/@installURL, if present, is set for script update.

Update Comparison
FireMonkey Greasemonkey Tampermonkey Violentmonkey
@updateURLupdate targetJSON
@downloadURL= @updateURLupdate targetupdate target β“˜
install sourceupdate target β“˜
meta.jsupdate target β“˜
meta.css
last-modified header
GreasyFork 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==

JS Single-line Comment (better compatibility)
// ==UserScript==
// @name            My Script
// @description     Scripting is fun
// @match           http://www.example.com/*
// @match           http://www.example.org/*
// @version         1.0
// ==/UserScript==
JS Multi-line Comment
/*
==UserScript==
@name               My Script
@description        Scripting is fun
@match              http://www.example.com/*
@match              http://www.example.org/*
@version            1.0
==/UserScript==
*/
CSS Multi-line Comment
/*
==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 & globes, 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.

Metadata Block
@name
@author
@description
@version            needed for updates
@updateURL          needed for updates

@match
@exclude-match
@include            merged into includeGlobs
@exclude            merged into excludeMatches

@require            (script-name/specific-library/other-URL)
@resource           (resourceName resourceURL)
@noframes           if present will set @allFrames to false (default)
@run-at             document-start/document-end/document-idle (default)
@inject-into        page
@container          default|private|container-1|container-2|... (Firefox 97-98)

@includeGlob
@excludeGlob
@matchAboutBlank    true/false (default)
@allFrames          true/false (default)



@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 Comparison
Metadata FireMonkey Greasemonkey Tampermonkey Violentmonkey
@name
@name:xx-YY v4.11 β“˜
@author undocumented β“˜
@description
@description:xx-YY v4.11 β“˜
@version
@updateURL undocumented β“˜
@match v0.9.8 (2011) β“˜
@include
@exclude
@exclude-match
@includeGlob
@excludeGlob
@matchAboutBlank
@allFrames
@noframes
@require
@resource
@run-at
@downloadURL
@antifeature
@inject-into
@matches
@excludeMatches
@includeGlobs
@excludeGlobs
@runAt
@homepage
@homepageURL
@website
@source
@support
@supportURL
@container Firefox 97-98
@grant
@namespace
@icon
@connect
@unwrap
@nocompat
@icon64
@icon64URL
@iconURL
@defaulticon

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.

grant not processed

Experimental @grant background processing was added in v2.42 to auto-disable GM_* API if userScript supports async type GM.* API.

Userscripts will have access to all available APIs and therefore @grant is not needed or processed.

@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.

grant Comparison
Script A Script B
// ==UserScript==
// @name            Script A
// @match           http://www.example.com/*
// @grant           GM.xmlHttpRequest
// ==/UserScript==

GM.xmlHttpRequest({
  url: 'https://abcd.com/',
  onload: response => {
    console.log(response.responseText);
  }
});
// ==UserScript==
// @name            Script B
// @match           http://www.example.com/*
// ==/UserScript==


GM.xmlHttpRequest({
  url: 'https://abcd.com/',
  onload: response => {
    console.log(response.responseText);
  }
});

container (UserCSS Firefox 97, UserScript Firefox 98)

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. (Bug 1470651 & Bug 1738567)

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
@container          container-1
@container          private

inject-into

Setting the value page will inject the entire userscript into the page context (more: Extension JavaScript Context).

Context Comparison
FireMonkey Greasemonkey Tampermonkey Violentmonkey
default userScript content β“˜ contentpage β“˜ β“˜
options page | content | auto
@inject-into page page | content | auto
@grant xyz
@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 Comparison
contextuserScriptcontentcontentcontent
typeof browser
typeof chrome
undefinedobjectundefinedundefined
browser
chrome
ReferenceError: browser(chrome) is not definedObject { 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 definedReferenceError: browser(chrome) is not defined
browser.storage
chrome.storage
ReferenceError: browser(chrome) is not definedObject { 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 definedReferenceError: browser(chrome) is not defined
window.browser
window.chrome
undefinedObject { 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: {…} }undefinedundefined

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.

2 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). β“˜

antifeature

Under-development feature on GreasyFork (ref #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 the content_scripts key.

This is especially useful to run scripts in empty iframes , whose URL is "about:blank". To do this you should also set the all_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 https://example.org/, and this page embeds an empty iframe, then "my-script.js" will be loaded into the iframe.

content_scripts

matchAboutBlank Optional

boolean. If true, the code will be injected into embedded about:blank and about:srcdoc frames if your extension has access to their parent document. The code cannot be inserted in top-level about: frames.

Defaults to false.

tabs.executeScript()

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.
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.

run-at (non-standard)

Tampermonkey supports additional non-standard run-at.

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.
Tampermonkey Documentation

defaults

FireMonkey conforms to the Firefox (& Chrome) content_scripts API defaults.

Default Comparison
FireMonkey Greasemonkey Tampermonkey Violentmonkey
run-atdocument-idle (JS)
document-start (CSS)
document-enddocument-idle [❓]document-end
allFramesfalsetruetruetrue
matchAboutBlankfalse@include   about:blank

πŸ“Š URL Matching Performance

The performance from top (best) to bottom (worst):

  1. match & exclude-match
  2. includeGlob & excludeGlob
  3. include & exclude
  4. include & exclude with regular expression

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 & exclude

They are the old and error-prone method of matching which has been superseded by @match/@exclude-match.

Entries under @include/@exclude will be merged into @includeGlob/@excludeGlob (v2.5) (duplicates removed).

Please note that @match/@exclude-match performance is far more efficient.

include_globs: Applied after matches to include only those URLs that also match this glob. Intended to emulate the @include Greasemonkey keyword.

exclude_globs: Applied after matches to exclude URLs that match this glob. Intended to emulate the @exclude Greasemonkey keyword.

Exclude matches and globs

The @match metadata imperative is very similar to @include, however it is safer. It sets more strict rules on what the * character means.

Greasemonkey @match

It is recommended to use @match / @exclude-match rather than @include / @exclude because the match rules are safer and more strict.

Violentmonkey Matching

Regular Expression in include & exclude

Regular Expression support has been implemented for @include/@exclude (v2.5).

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
Example with includeGlob
/*
==UserCSS==
@name               My CSS
@description        CSS is fun
@match              *://*/*
@includeGlobs       http://www.example.*/*
@version            1.0
==/UserCSS==
*/

Converting include/exclude to match/exclude-match

Some example of how you can convert to more robust Match Patterns.

Conversation Examples
From include To match
**://*/*
http://*http://*/*
https://*https://*/*
http*://**://*/*
http*://a.b.c/**://a.b.c/*
*://google.*
http*://www.google.*
*://*.google.TLD/*
*.example.com/**://*.example.com/*

Match Patterns

FireMonkey uses Match Patterns for @matches/@match & @excludeMatches and globs for @includeGlobs & @excludeGlobs

Note: Paths are case-sensitive.

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:

For example: "*na?i" would match "illuminati" and "annunaki", but not "sagnarelli".

Examples
Pattern match no-match
<all_urls>

Match all URLs

(in this case http, https, file)

http://example.org/

https://files.example.org/

ftp://example.org/some/path/ β“˜

*://*/*

Match all http, https

http://example.org/

https://www.example.org/aaa/

ftp://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://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/bleh

file:///bleh/

Invalid Match Patterns
Invalid Pattern Reason
resource://path/Unsupported scheme
https://mozilla.orgNo 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:///*"

Note: Content scripts are blocked by Firefox on the following domains:

FTP ⚠️

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

The implementation is currently disabled in the Firefox Nightly and Beta pre-release channels and will be disabled when Firefox 88 is released on April 19, 2021. The implementation will be removed in Firefox 90. After FTP is disabled in Firefox, the browser will delegate ftp:// links to external applications in the same manner as other protocol handlers.

Built-in FTP implementation to be removed in Firefox 90

The Firefox platform development team recently announced plans to first disable, and then remove the implementation for built-in FTP from the browser.

What to expect for the upcoming deprecation of FTP in Firefox

TLD

A TLD (case-insensitive) wildcard can be used and FireMonkey will automatically generate a list of most common country TLD @matches. Due to the extra Firefox API processing for many @matches, it is best to use it only when necessary.

*://*.example.TLD/* (general use)
.com
.au
.br
.ca
.ch
.cn
.co.uk
.de
.es
.fr
.in
.it
.jp
.mx
.nl
.no
.pl
.ru
.se
.uk
.us
.amazon.TLD (domain specific)
.ca
.cn
.co.jp
.co.uk
.com
.com.au
.com.br
.com.mx
.com.sg
.com.tr
.de
.es
.fr
.in
.it
.nl
.ebay.TLD (domain specific)
.at
.be
.ca
.ch
.cn
.co.th
.co.uk
.com.au
.com.cn
.com.hk
.com.my
.com.sg
.com.tw
.com
.de
.dk
.es
.fi
.fr
.gr
.hu
.ie
.in
.it
.nl
.no
.ph
.ph
.pl
.ru
.vn
.google.TLD (domain specific)
.ae
.al
.am
.as
.at
.az
.ba
.be
.bf
.bg
.bi
.bj
.bs
.bt
.by
.ca
.cat
.cd
.cf
.cg
.ch
.ci
.cl
.cm
.cn
.co.ao
.co.bw
.co.ck
.co.cr
.co.id
.co.il
.co.in
.co.jp
.co.ke
.co.kr
.co.ls
.co.ma
.co.mz
.co.nz
.co.th
.co.tz
.co.ug
.co.uk
.co.uz
.co.ve
.co.vi
.co.za
.co.zm
.co.zw
.com
.com.af
.com.ag
.com.ai
.com.ar
.com.au
.com.bd
.com.bh
.com.bn
.com.bo
.com.br
.com.bz
.com.co
.com.cu
.com.cy
.com.do
.com.ec
.com.eg
.com.et
.com.fj
.com.gh
.com.gi
.com.gt
.com.hk
.com.jm
.com.kh
.com.kw
.com.lb
.com.ly
.com.mm
.com.mt
.com.mx
.com.my
.com.na
.com.nf
.com.ng
.com.ni
.com.np
.com.om
.com.pa
.com.pe
.com.pg
.com.ph
.com.pk
.com.pr
.com.py
.com.qa
.com.sa
.com.sb
.com.sg
.com.sl
.com.sv
.com.tj
.com.tr
.com.tw
.com.ua
.com.uy
.com.vc
.com.vn
.cv
.cz
.de
.dj
.dk
.dm
.dz
.ee
.es
.fi
.fm
.fr
.ga
.ge
.gg
.gl
.gm
.gp
.gr
.gy
.hn
.hr
.ht
.hu
.ie
.im
.iq
.is
.it
.je
.jo
.kg
.ki
.kz
.la
.li
.lk
.lt
.lu
.lv
.md
.me
.mg
.mk
.ml
.mn
.ms
.mu
.mv
.mw
.ne
.ng
.nl
.no
.nr
.nu
.pl
.pn
.ps
.pt
.ro
.rs
.ru
.rw
.sc
.se
.sh
.si
.sk
.sm
.sn
.so
.sr
.st
.td
.tg
.tk
.tl
.tm
.tn
.to
.tt
.vg
.vu
.ws

require

The API is added for convenience and compatibility but it works differently in comparison to other script managers.

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:

@require            My Script

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 keep the script DISABLED (not enabled) e.g.:

Script Example
// ==UserScript==
// @name            HelperSet
// @description     A set of helper functions
// @version         1.0
// ==/UserScript==

function someFunc(id) {

  // some code 
}

function otherFunc(text) {

  // some code 
}
Include in Other Scripts
// ==UserScript==
// @name            My Script
// @description     Scripting is fun
// @match           http://www.example.com/*
// @match           http://www.example.org/*
// @version         1.0
// @require         HelperSet
// ==/UserScript==

..... code .....
Multiple require (injected in order)
// @require         jquery-3
// @require         HelperSet
// @require         https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta2/css/fontawesome.min.css
CSS require
/*
==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-beta2/css/fontawesome.min.css
==/UserCSS==
*/

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, 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:

Bundled Libraries
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.12.1
moment moment-2 2.29.1
underscore underscore-1 1.13.1
Example
// @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.0.0-beta2/css/fontawesome.min.css

On popular demand, more libraries may be added in future.

require other-URL ⚠️

The API is added for convenience and compatibility but it works 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. Please note:

ℹ️ 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.

getResourceUrl Alternative for CSS
// ==UserScript==
// @name            GM.getResourceUrl test
// @description     GM.getResourceUrl() API method
// @resource        CSS http://www.example.com/example.css
// ==/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);
getResourceUrl Alternative for image
// ==UserScript==
// @name            GM.getResourceUrl test
// @description     GM.getResourceUrl() API method
// @resource        logo http://www.example.com/logo.jpg
// ==/UserScript==


// 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);

Script API

FireMonkey supports both GM4 & GM3 style APIs (e.g. GM.*** & GM_***) via mostly asynchronous Promise. Therefore if scripts requires a return, it must wait for the Promise to resolve.

Following APIs (similar to Greasemonkey) are provided to the user scripts (more may be added later)

API Examples
(async () => {
'use strict';

  // ----- Synchronous Partial β“˜

  // stored value for key OR default
  const value = GM_getValue(key, default);

  // an array of keys set by script OR []
  const keys = GM_listValues();


  // ----- Promise

  // stored value for key OR default
  const value = await GM.getValue(key, default);

  // an array of keys set by script OR []
  const keys = await GM.listValues();

  // value can be string, number, boolean, or object
  GM.setValue(key, value)
  GM_setValue(key, value)

  GM.deleteValue(key)
  GM_deleteValue(key)


  // Response Object
  const response = await GM.fetch(url [, init]);     (FireMonkey only)
  const response = await GM_fetch(url [, init]);     (FireMonkey only)

  // Response Text
  const text = await GM.getResourceText(resourceName);
  const text = await GM_getResourceText(resourceName);

  // usually there is no need to wait for the following, but if needed, use await
  GM.openInTab(url, open_in_background)
  GM_openInTab(url, open_in_background)

  GM.setClipboard(text)
  GM_setClipboard(text)

  GM.notification(text)
  GM_notification({text: '...', image: '...', ....})

  GM.download(url, fielname)
  GM_download(url, fielname)



  // ----- Callback Function

  GM.xmlHttpRequest({
    url: 'https://example.com/etc',
    onload: response => {
      console.log(response.responseText);
    }
  });


  // note: listenerId = key (not necessary in FireMonkey)
  const listenerId = GM.addValueChangeListener(key, callback);

  GM.addValueChangeListener(key, callback)
  GM_addValueChangeListener(key, callback)


  // ----- Synchronous Functions

  GM.addStyle(css)
  GM_addStyle(css)

  GM.addScript(code)      (FireMonkey only)
  GM_addScript(code)      (FireMonkey only)

  GM.popup(options)       (FireMonkey only)
  GM_popup(options)       (FireMonkey only)

  GM.getResourceUrl(resourceName)
  GM_getResourceURL(resourceName)

  GM.registerMenuCommand(name, onclick)
  GM_registerMenuCommand(name, onclick)

  GM.unregisterMenuCommand(name)
  GM_unregisterMenuCommand(name)

  GM.removeValueChangeListener(key)
  GM_removeValueChangeListener(key)

  GM.log(text [, text2, ..., textN])
  GM_log(text [, text2, ..., textN])

  GM.info
  GM_info

  unsafeWindow
})();
API Comparison
API FireMonkey Greasemonkey Tampermonkey Violentmonkey
GM.addScript πŸ‡³
GM.addStyleβ“˜
GM.addValueChangeListener
GM.cookie
GM.deleteValue
GM.download
GM.fetch πŸ‡³
GM.getResourceText
GM.getResourceUrlβ“˜ β“˜ β“˜
GM.getValue β“˜
GM.info
GM.listValues
GM.log
GM.notification
GM.openInTab
GM.popup πŸ‡³
GM.registerMenuCommandv4.11 β“˜
GM.removeValueChangeListener
GM.setClipboard β“˜
GM.setValue
GM.unregisterMenuCommand
GM.xmlHttpRequest
 
GM_addElementv4.11 β“˜
GM_addScript πŸ‡³
GM_addStyle
GM_addValueChangeListener
GM_cookieβ“˜
GM_deleteValueasync
GM_download
GM_fetch πŸ‡³
GM_getResourceTextasync
GM_getResourceURL
GM_getTabβ“˜
GM_getTabsβ“˜
GM_getValuepartial β“˜
GM_info
GM_listValuespartial β“˜
GM_log
GM_notification
GM_openInTab
GM_popup πŸ‡³
GM_registerMenuCommand
GM_removeValueChangeListener
GM_saveTabβ“˜
GM_setClipboard
GM_setValueasync
GM_unregisterMenuCommand
GM_xmlhttpRequest
 
unsafeWindow userScript
page
content β“˜ content
page
content β“˜
page β“˜
window.close 1β“˜v2.6.2 β“˜
window.focus 2v2.12.10 β“˜
window.onurlchange 3v4.11 β“˜
 
πŸ“₯ 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
 
Open Source β“˜ Proprietary License minified
πŸ‘ No Data Collection Privacy policy Privacy policy
πŸ‘ No Tracking Google favicon
πŸ‘₯ Firefox Users ~670 260k 457k 46k

1 window.close grant results is overwriting the global window.close() and bypassing the specific JavaScript window.opener 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.

2 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.

3 window.onurlchange is an experimental implementation in Tampermonkey 4.11.6120 (2020-09-17).

Alternative: Detecting JavaScript Navigation

API Comparison
API FireMonkey Stylus Stylish xStyle
@-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/td>
src/inject/apply.js src/inject/apply.js
 
Open Source Proprietary License minified
πŸ‘ No Data Collection Privacy policy
πŸ‘₯ Firefox Users ~670 66k 66k 380
API Type Comparison
API FireMonkey Tampermonkey|Violentmonkey Same
GM_addScript πŸ‡³syncβ€”β€”
GM_addStylesyncsync
GM_addValueChangeListenercallbackcallback
GM_deleteValueasyncsyncshouldn't matter
GM_downloadasyncasync
GM_fetch πŸ‡³asyncβ€”β€”
GM_getResourceTextasyncsync
GM_getResourceUrlsyncsyncdifferent implementation
GM_getValuesemi-syncsync
GM_infosyncsync
GM_listValuessemi-syncsync
GM_logsyncsync
GM_notificationasyncasync
GM_openInTabasyncasync
GM_popup πŸ‡³syncβ€”β€”
GM_registerMenuCommandcallbackcallback
GM_removeValueChangeListenersyncsync
GM_setClipboardasyncasync
GM_setValueasyncsync
GM_unregisterMenuCommandsyncsync
GM_xmlHttpRequestcallbackcallback

ℹ️ Asynchronous & Synchronous Storage

There are 2 types storage systems 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

ℹ️ getValue & listValues

Partial compatibility has been implemented. Please note:

The async GM.getValue & GM.listValues will get the most up-to-date values at that moment.

openInTab(url, open_in_background)

The option support for openInTab is not unified between userscript managers.

The default value for open_in_background honors Firefox configuration.

openInTab Option Comparison
FireMonkey Greasemonkey Tampermonkey Violentmonkey
open_in_background
true/false
object β“˜ β“˜ { active, insert, setParent, incognito } β“˜ { active, insert, container, pinned }

fetch(url [, init]) (⚠️ breaking changes in v2.33)

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.

FireMonkey fetch returns response object/string or null (check console & the log for errors e.g.: TypeError: "NetworkError when attempting to fetch resource.").

FireMonkey GM fetch fixes Bug 1405971.

GM.fetch(url [, init]);
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:

Since v2.33. GM.fetch returns an object in all cases.

Example
// example of response object
{
  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/en-US/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;


// 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;


// returns response JSON
const response = await GM.fetch('https://example.com/etc', {responseType: 'json'});
const obj = response.json;


// 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({object})

GM.xmlHttpRequest/GM_xmlhttpRequest is the xmlHttpRequest interface and mostly compatible with Greasemonkey API.

All requests are asynchronous, therefore Greasemonkey synchronous is not supported.

Request
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 FireMonkey's 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
});
Callback Response returns response object
onload, onerror, ontimeout, onabort
{
  readyState
  response
  responseHeaders
  responseText
  responseType
  responseURL
  responseXML
  status
  statusText
  finalUrl                  // clone of responseURL for GM/TM/VM compatibility
}
Example
// 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.

XPCNativeWrapper is due to be removed (ref Bug 1481337).

xmlHttpRequest from Page Context
const xhr = XPCNativeWrapper(new window.wrappedJSObject.XMLHttpRequest());
xhr.open('GET', 'https://example.com/etc');
xhr.withCredentials = true;
xhr.onload = response => {
 console.log(response.responseText);
},
xhr.onerror = response => {
 console.log(`${response.status}  ${response.statusText}`);
}
xhr.send(null);

ℹ️ JavaScript xmlHttpRequest & fetch

Due to a bug in Firefox userScript context, CORS fails in JavaScript new XMLHttpRequest() & fetch() (ref Bug 1715249).

After liaising with Mozilla engineers, once the bug is fixed, userscripts will get the same origin as the web-page they are on.

Please note that userScript context used in FireMonkey is more restrictive than the content context used in GM|TM|VM. Therefore the behaviour of JavaScript xmlHttpRequest & fetch in GM|TM|VM will be somwhow different.

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.

β€’ 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 Bug 1715249

ℹ️ Forbidden header name

A forbidden header name (also 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 using fetch that grant developers control over headers, such as XMLHttpRequest.

All Forbidden header names will be removed from the request by FireMonkey before sending.

Forbidden header names start with Proxy- or Sec-, or are one of the following names:

After confirmation from AMO (ref 2019-09-28 Rob W), modification to the following Forbidden headers will be allowed:

Header names are case-insensitive (v2.30).

notification(text)

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.

Notes on notification options:

text
text string
title
Not processed, script name shows as title
image
a data URL, a blob URL, a http or https URL
onclick
Not processed, may be added on popular demand
GM_notification('some text');
GM_notification({text: 'some text', image: 'https://example.com/icon.jpg', ...});

registerMenuCommand(name, onclick)

If there are Script Commands for a tab, they will be listed at the bottom of toolbar pop-up under their user-script name. Please note that onclick must be a function.

Example
// 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(name)

Unregistered the previously created script menu.

GM.unregisterMenuCommand('Hello, world (named)');

getResourceText(resourceName)

The API is added for convenience and compatibility but it works 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.

const text = await GM_getResourceText('resourceName');

getResourceUrl(resourceName)

The API is added for convenience and compatibility but it works differently in comparison to other script managers as the target is not not stored. It maps directly to the resourceURL and returns the URL, or undefined.

const url = GM_getResourceUrl('resourceName');

addValueChangeListener(key, callback)

Script storage change listener that returns the key as listener ID (although not necessary in FireMonkey).

(key, oldValue, newValue, remote) are passed to the callback function.

// 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(key)

Remove listener for key

GM_removeValueChangeListener('test-key');

download(url [, filename])

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.

GM_download('https;//www.example.com/icon.jpg');
GM_download('https;//www.example.com/icon.jpg', 'new-name.jpg');

addStyle(css)

Utility function to inject style element e.g.

const script = document.createElement('style');
script.textContent = `... css ...`;
document.head.appendChild(script);
Example
const css = `body {
  border-top: 2px solid grey;
}`;

GM_addStyle(css);
GM.addStyle(css);

addScript(code)

FireMonkey only utility function to inject script element (code runs in page context) e.g.

const script = document.createElement('script');
script.textContent = `... code ...`;
document.body.appendChild(script);
script.remove();
Example
// Example 1: string
const js = `function sum(x, y) {
  return x + y;
}`;

GM_addScript(js);
GM.addScript(js);



// Example 2: function
function someFunc() {

  // some code 
}

GM_addScript('(' + someFunc + ')();');
GM.addScript('(' + someFunc + ')();');

FireMonkey only utility function to create a shadow DOM popup blank element with animation that can be customized.

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()

Example
// 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.

Example
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.contennt.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 βœ–.

Example
// styling background with :host
const css = `:host {
  background: transparent;
}`;
popup.addStyle(css);

// 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);

This is the background with CSS selector :host

βœ–This is content with CSS selector .content
{type: 'panel-left'}
{type: 'panel-right'}
{type: 'panel-top'}
{type: 'panel-bottom'}

info

{
  scriptHandler:    'FireMonkey',
  version:          'e.g. 2.14',
  scriptMetaStr:    null,
  platform: {
    os:             'e.g. mac win android cros linux openbsd',
    arch            'e.g. arm x86-32 x86-64'
  },
  browser: {
    name:           'Firefox',
    vendor:         'Mozilla',
    version:        'e.g. 75.0a1'.
    buildID:        'e.g. 20200220224950'
  },
  script: {
    name:           'script name',
    version:        'script version',
    description:    'script description',
    matches:        [array of match],
    excludeMatches: [array of exclude-match],
    includes:       [array of regex include],
    excludes:       [array of regex exclude],
    includeGlobs:   [array of include/includeGlob],
    excludeGlobs:   [array of exclude/excludeGlob],
    'run-at':       'e.g. document-idle',
    namespace:      null,
    resources:      {object of name: url}
  }
}

log(text [, text2, ..., textN])

The API is added for convenience and compatibility and is no longer supported by Greasemonkey or Violentmonkey but Tampermonkey still has it. Multiple parameters can be passed e.g. GM_log('one', 'two', 'three'). You can also use console.log() instead.

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.

unsafeWindow, window.wrappedJSObject or window.eval() can be used to create function & objects.

Please note that window.eval() makes the object available to the page script as well while unsafeWindow, window.wrappedJSObject does not.

Example
// page-script.js
var foo = "I'm defined in a page script!";
function runTest() {
  console.log(foo);
}

// content-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 for window.wrappedJSObject. It is the raw window object inside the XPCNativeWrapper 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});

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

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

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.

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 or credentials
  • xmlHttpRequest: Firefox sends cookies, withCredentials has no effect β“˜
  • fetch : Firefox sends cookies, unless {credentials: 'omit'} β“˜
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 β“˜
Cookies Isolation Comparison
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
β€”

Further information:

ℹ️ 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.

Example
// 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}
);

ℹ️ Xray Vision & Sharing objects with page scripts

Extension JavaScript Context (Scope)

Contexts are sandboxed layers of JavaScript in an extension to ensure security.

Context Layers
Type Browser API Access Details
broswer 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.
...
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.

Sharing objects with page scripts

By default, content scripts don't get access to objects created by page scripts. However, they can communicate with page scripts using the DOM window.postMessage and window.addEventListener APIs.

Communicating with the web page

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

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();

Further information:

Receiving data from page context

CustomEvent can be used send data from a page script.

Example with <script>
// inject a function that generates & dispatches a CustomEvent
const script = document.createElement('script');
script.textContent = `function sendMessage(message) => {
  window.dispatchEvent(new CustomEvent('sendMessage', {detail: message}));
};

// run sendMessage when needed
if(... condition ...) { sendMessage(data); }
`;
document.head.appendChild(script);
script.remove();

// in user-script
window.addEventListener('sendMessage', onMessage);
function onMessage(e) {

  const message = e.detail;
  // run some code
}

In Firefox, window.eval() can also be used to inject code into a page context.

Example with window.eval()
// inject a function that generates & dispatches a CustomEvent
window.eval(`function sendMessage(message) => {
  window.dispatchEvent(new CustomEvent('sendMessage', {detail: message}));
};

// run sendMessage when needed
if(... condition ...) { sendMessage(data); }
`);

// in user-script
window.addEventListener('sendMessage', onMessage);
function onMessage(e) {

  const message = e.detail;
  // run some code
}

UserCSS

CSS can be injected directly into a page. If the goal is to inject CSS, it is by far more efficient to inject CSS as UserCSS, instead of using UserScript to inject CSS. Here is an example of simple UserCSS that I use to mark watched/visited videos on YouTube.

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.

CSS mangers like Stylus tend to inject CSS at document-start. 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.

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.

Example
/*
==UserCSS==
@name           YouTube
@match          *://*.youtube.com/*
@author         erosman
@version        1.0
==/UserCSS==
*/


a[href*="/watch?v="]:visited, a[href*="/watch?v="]:visited yt-formatted-string,
a[href*="/watch?v="]:visited h3 {
  color: #f50 !important;
}

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.

3rd Party userCSS Example
/*
==UserCSS==
@name           ABC Style
@match          *://*.example.com/*
@author         erosman
@version        1.0
==/UserCSS==
*/

body {
  border-top: 2px solid grey;
}
3rd Party userCSS Custom Example
/*
==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.

3rd Party userCSS Custom Example
/*
==UserCSS==
@name           ABC Style Custom
@match          *://*.example.com/*
@require        ABC Style
==/UserCSS==
*/

:root {
  --main-color: #fff;
  --border: #ddd;
  --color: #000;
}

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.

Ref: Implement Stylus style Compatibility | Support FireMonkey's UserCSS spec

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.

Please note that @-moz-document regexp() in not supported.

Please note that userstyles.org is sometimes slow and may time out.

ℹ️ Stylish/Stylus/xStyle Type UserStyle

In Firefox Quantum, extensions can not use @-moz-document and therefore extensions would have to:

Above process is considerably more resource intensive than using the dedicated API to inject Style/CSS.

-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 layout.css.moz-document.content.enabled preference (needs to be set to true). To change preferences in Firefox, visit about:config.

@document

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.

Conversion Guide
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/*

Character Escaping in CSS

CSS Escaping in userScript requires double scaping.

Example
// 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;
}

Debugging Script & CSS

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 (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 user-script:// (you may have to refresh the page)
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.
Injection Errors
Check the Log page for recent errors

Injection Comparison

Other userscript & userStyle Managers

FireMonkey

Performance Test

UserScript

UserStyle/UserCSS

Combined UserScript + UserStyle/UserCSS

Support

Please use the GitHub Community Support.