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.

Compatibility Issues
Area Details Exposure
UserScript
uncommon @grantGM_cookie, GM_getTab, GM_getTabs, GM_saveTab 55 / 63,435 (0.08%) scripts use these APIs
uncommon @grantwindow.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
range
number
The units value - framed in (double) quotes ...
⚠️ because we're parsing the meta data as JSON, all fractional values must include an integer value, e.g. [.5] will cause a parse error, so use [0.5]
select
Use a JSON format for the values set in the select's options.
Writing UserCSS
@var checkboxRequires JavaScript to process logical true/false which is not part of CSS
@preprocessor lessRequires Less JavaScript Library
@preprocessor stylusIf uses JavaScript or features which are not part of CSS
@-moz-document regexpnot 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 tabs.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 the scripting.executeScript() method to either inject a static file or a function.

Executing arbitrary strings

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: Canary support around October, 2022. Targeting Canary support in the first quarter of 2023.

Known issues when migrating to Manifest V3

See also

2. includeGlobs/excludeGlobs
Chrome MV3 has removed includeGlobs/excludeGlobs parameters which affects the use of include/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 for https://domain-b.com/data.json.

For security reasons, browsers restrict cross-origin HTTP requests initiated from scripts. For example, 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.

Cross-Origin Resource Sharing (CORS)

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, XMLHttpRequest is not supported in background pages (provided by Service Workers). Please consider using its modern replacement, fetch().

Cross-origin XMLHttpRequest

See also

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 than userScript 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 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 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

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 https://raw.githubusercontent.com/........uers.js tab is forwarded to cdn.jsdelivr.net mirror. (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).

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

Theme Example

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

Example

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

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)

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:

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.

meta.js/meta.css

FireMonkey only checks @version, therefore a minimal meta.js/meta.css would suffice.

@version            1.0
Update Comparison
FireMonkey Greasemonkey Tampermonkey Violentmonkey
@updateURLupdate targetJSON
@downloadURL= @updateURLupdate targetupdate target
install sourceupdate target
@metaURL (v2.68)
meta.jsupdate 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==

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

Metadata Block

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

Default Comparison
FireMonkey Greasemonkey Tampermonkey Violentmonkey
run-atdocument-idle (JS)
document-start (CSS)
document-enddocument-idle document-end
allFramesfalsetruetruetrue
matchAboutBlankfalse@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.

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

@grant

Userscripts will have access to all available APIs and therefore @grant is not needed, however the following has been implemented.

@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);
    }
  });

🛡️ Examples of Security Concerns

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

Context Comparison: MV2
FireMonkey Greasemonkey Tampermonkey Violentmonkey
default userScript content contentpage
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
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
Context Comparison: MV3
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.

2 Content Security Policy

@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 plain number
range
50 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"}
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 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()

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

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

// some code

Multiple require (injected in order)

// @require         jquery-3
// @require         HelperSet
// @require         https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.0/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/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.

CSS require Alternative

/*
==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:

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

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

Paths are case-sensitive.

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/

resource://a/b/c/

*://*/*

Match all http, https

http://example.org/

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

ftp://www.example.org/

*://*.example.org/*

http://example.org/

https://www.example.org/

http://www.sub.example.org/aaa/

ftp://www.example.org/

*://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 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:///*"
Content scripts are blocked by Firefox on the following domains:

@include - @exclude

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

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

📊 URL Matching Performance

From best (top) to worst:

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

Regular Expression in @include & @exclude

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

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

Example

/*
==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.

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

UserScript API Comparison
API FireMonkey Greasemonkey Tampermonkey Violentmonkey Stats1
Storage
GM.getValue v2.12.0 739
GM.setValue727
GM.deleteValue208
GM.listValues77
GM.addValueChangeListener10
GM.removeValueChangeListener1
GM.getValues (v2.68)
GM.setValues (v2.68)
GM.deleteValues (v2.68)
DOM
GM.addElementv4.12v2.13.14
GM.addScript0
GM.addStyle102
GM.popup0
Other
GM.createObjectURL (v2.68) 0
GM.download18
GM.fetch0
GM.getResourceText11
GM.getResourceUrldifferent v2.13.130
GM.import (v2.68) 0
GM.info69
GM.log7
GM.notification42
GM.openInTab178
GM.registerMenuCommandv4.11 v2.12.10131
GM.setClipboard 77
GM.unregisterMenuCommand6
GM.xmlHttpRequest699
GM.cookie9
GM.getTab3
GM.getTabs0
 
Storage
GM_getValue5,837
GM_setValue3,782
GM_deleteValue1,577
GM_listValues711
GM_addValueChangeListener209
GM_removeValueChangeListener77
GM_getValues (v2.68)
GM_setValues (v2.68)
GM_deleteValues (v2.68)
DOM
GM_addElementv4.11 v2.13.141
GM_addScript0
GM_addStyle5,452
GM_popup0
Other
GM_createObjectURL (v2.68) 0
GM_download478
GM_fetch3
GM_getResourceText954
GM_getResourceURLdifferent 355
GM_info731
GM_log865
GM_notification617
GM_openInTab(sync returns object)1,326
GM_registerMenuCommand1,923
GM_setClipboard1,556
GM_unregisterMenuCommand220
GM_xmlhttpRequest5,436
GM_cookie49
GM_getTab43
GM_getTabs39
GM_saveTab37
 
unsafeWindow userScript
page
content content
page
content
page
3,057
window.close 2limited supportv2.6.2 279
window.focus 3v2.12.10 135
window.onurlchange 4v4.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

UserCSS API Comparison
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.

Storage Comparison
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 pass null, or an undefined value, the entire storage contents will be retrieved.
Return value: value (or default), storage object, key/value object
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, or array of strings, representing the key(s) of the item(s) to be removed.
Return value: undefined
listValues
Standard: no argument
Return value: array

Example

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

New Options

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

Examples

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

Examples of importing jQuery (UMD module)

// 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' tags
  • document.body (<body>) || document.documentElement (<html>) for others
tagName (string)
Any valid HTML tag
attributes (object)
Object with any valid attribute and/or textContent

Example

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

Example

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

Example

// 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 the fetch response directly from background script)

Response

{
  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: ...,
}

Example

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

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

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.

xmlHttpRequest from 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 using fetch that grant developers control over headers, such as XMLHttpRequest.

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

Forbidden header name

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

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.

openInTab Option Comparison
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.

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

Unregister the previously created Script Command.

Return value: undefined

GM.unregisterMenuCommand(name);
GM_unregisterMenuCommand(name);

Example

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
getResourceUrl Comparison
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

getResourceUrl Alternative

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

Example: Cannot load SVG images with GM_getResourceURL and GM_addStyle

// ==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}"); }
  `);
})();

Recommendation for above

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

Example

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

Example

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.

setClipboard Comparison
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'

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

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

Example

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

info

Return value: object

const info = GM.info;
const info = GM_info;

The object properties in GM info are not unified among userscript managers.

Return Object

{
  // 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, ...])

Example

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.

unsafeWindow Comparison: MV2
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 <all_urls> to support @include. User scripts in Manifest V3
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, unsafeWindow refers to the global object in content script. As a result, the script can access and modify the page's DOM, but cannot access JavaScript objects of the web page. Violentmonkey @inject-into
page window
access page DOM
access page JS
window
access page DOM
access page JS
In this mode, unsafeWindow refers to the window object, allowing the script to access JavaScript objects of the web page, just like normal page scripts can. Violentmonkey @inject-into

Example

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

Example

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

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 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
First-party isolation

See also

ℹ️ 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}
);

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 Layers: MV2
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.
...
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
Context Layers: MV3 scripting.ExecutionWorld
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 MAIN world unless it is acceptable for web pages to read, access, or modify the logic or data that flows through the executed code.

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

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

Example with window.eval()

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

Example

/*
==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:

Live userCSS update is applied to the relevant tabs under the following circumstances:

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.

3rd Party userCSS Example

/*
==UserCSS==
@name           ABC Style
@match          *://*.example.com/*
@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;
}

Character Escaping in CSS

CSS Escaping in userScript requires double escaping.

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

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:

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

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

FireMonkey

Performance Test

JavaScript Best Practices

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