Preamble
FireMonkey is a totally new combined user-script and user-style manager. While it has similar functions to other user-Script managers like Greasemonkey/Tampermonkey/Violentmonkey, and user-style managers like Stylish/Stylus/xStyle, there are also differences. Similar to other userscript managers, scripts (or CSS) are injected in tabs and for a new script/CSS to have an effect, tab must be reloaded. The behaviour differs from Stylus which can inject CSS into a tab and remove it without having to reload the tab.1
Some functions like script injection are prevented by Firefox on sites with restrictive Content Security Policy (CSP), as well as certain domains e.g. accounts-static.cdn.mozilla.net, accounts.firefox.com, addons.cdn.mozilla.net, api.accounts.firefox.com, content.cdn.mozilla.net, content.cdn.mozilla.net, discovery.addons.mozilla.org, input.mozilla.org, install.mozilla.org, oauth.accounts.firefox.com, profile.accounts.firefox.com, support.mozilla.org, sync.services.mozilla.com, testpilot.firefox.com (ref Content scripts).
1 It is not possible with the current API (Changes are planned as part of Manifest v3 update).
Firefox Minimum Version
Originally, userScripts API was added in Firefox 65-67 but was disabled and had to be enabled via about:config?filter=extensions.webextensions.userScripts.enabled
.
The official release of userScripts API was postponed to Firefox 68.
FireMonkey | Firefox | Note |
---|---|---|
1.0-1.24 | 65 | Initial userScripts API release |
1.25 | 68 | Offitial userScripts API release |
2.42 | 74 | To benefit from ECMAScript 2020 aka ES11 Statitics: no FireMonkey users below Firefox 77 |
How to Use
Toolbar Icon
Toolbar Icon Context Menu
Options
- Open the Options page
New UserScript
- Open the Options page and display a new Script Template that you can edit and Save 1
New UserCSS
- Open the Options page and display a new CSS Template that you can edit and Save 1
Help
- Display Help document 1
Log
- Display the latest (default 100) error and script-update messages when not in Private Window 1
- Log data is not available in Container/Private mode
Locale Maker
- Open the Locale Maker stand-alone module to create & share locale translations
1 In Private Window opens the Options page (ref Bug 1659998).
Toolbar Icon Badge 5
Badge indicates the number of Script/CSS that are active in the tab (and all its iframes)
Toolbar Icon Title
Mouse-over Title displays active Script/CSS
Toolbar Icon Pop-up
The pop-up displays the installed Scripts/CSS. "Tab Scripts & CSS" shows the ones running on the active Tab.
The number in the "Tab Scripts & CSS" shows the number of frames of the page. The minimum is 1 which is the main/top frame.
- β
- Shows Script/CSS is enabled. Disabled Script/CSS are greyed out and clicking that area toggles the enable/disable
- β
- Shows that there was an error when registering the script & the Information page displays the error
- Click to Hide/Show Script & CSS
- {name}
- Shows the Information for the Script/CSS
- Displays homepage link if script has
@homepage
,@homepageURL
,updateURL
,@website
, or@source
- Displays support link if script has
@support
, or@supportURL
-
- π Edit
- Button on Information page opens the Options Page and selects the current Script/CSS
- β· Run
- Inject the displayed userScript or userCSS (not userStyle) as long as it was not already injected in the tab
- UserScript is injected into
content
context which has more privilege thanuserScript
context and untrusted code should not be used. - The userScript is injected as plain JavaScript. Its metadata block would not be processed and it has no access to GM API.
- β Undo
- Undo/Remove the displayed userCSS if it was injected temporarily by β· Run
- Script Commands
- Shows available Script Commands, click on each to run
- Scratchpad
- Insert CSS & JavaScript temporarily into the active tab
- Scratchpad recalls the last entries for repeated use
- JavaScript is injected into
content
context which has more privilege thanuserScript
context and untrusted code should not be used. -
- β· Run
- Insert the Scratchpad code (JavaScript or CSS)
- β Undo
- Undo/Remove the CSS Inserted by Scratchpad
- Click to clear the box and the saved code
- Find scripts for this site
- Search greasyfork.org for scripts for this site
Options
- Open the Options Page
- β
- Display a new Script Template that you can edit and Save
- β
- Display a new CSS Template that you can edit and Save
- Help
- Open the Help document
Page Context Menu
Install Stylish Style
Install styles on userstyles.org
Script/CSS Install
Web Install
Web Install is available from greasyfork.org & openuserjs.org. Script install URL will be set for script update.
Direct Install
Scripts can be directly installed from any script ending in .user.js
(remote or local file system) if loaded into a tab (open or drag & drop)
In case of greasyfork.org & openuserjs.org links, the same link will be set for script update.
Due to sandbox CSP header on https://raw.githubusercontent.com/........uers.js
tab is forwarded to cdn.jsdelivr.net
mirror. (ref Bug 1411641).
Firefox on Android
Support is experimental and based on user feedback.
Options
Auto-Update Interval
You can set the number of days between updates. Setting 0 (default) means there will be no auto-update.
In order to minimise the impact on your browsing, auto-update is set to run when Firefox is idle and then 10 updates at a time (until the next idle time).
Please note that updating each script can take around 4-5 seconds. If there are many scripts, that can add up to consider amount of time. I would suggest manually updating Scripts/CSS or enabling auto-update only on the ones that require regular update.
Enable Storage Sync
Enable to sync storage if it is under 100KB. If the storage is over 100KB, there will be a notification and the Sync will be turned off automatically to avoid repeated errors.
For Firefox a user must have Add-ons checked under the "Sync Settings" options in "about:preferences".
The main use case of this API is to store preferences about your extension and allow the user to sync them to different profiles. You can store up to 100KB of data using this API. If you try to store more than this, the call will fail with an error message. The API is provided without any guarantees about uptime or performance.
storage.sync
Storage quotas for sync data
- Maximum total size 102400 (100kb)
- Maximum item size 8192 (8kb)
- Maximum number of items 512
Each script will have a single entry in FireMonkey which is the script itself plus all its associated data (around 1-2kb more).
The maximum allowed by Firefox Sync is 8kb for each item. Therefore, each script should be smaller than 6-7kb in order to Sync.
This is the impracticality of using Firefox Sync in script/style managers which only works when user has a few small scripts/styles.
Counter
Enable/Disable Script/CSS counter (Toolbar Icon Badge & Toolbar Icon Title)
Global Script Exclude Matches
Exclude Match pattens (one per line) to prevent all scripts (not CSS) from running on them.
Changes to Global Script Exclude Matches will unregister all running script. Once tab is reloaded or on new tab, new settings will take effect.
Note: Invalid match pattern will cause all scripts to fail.
Custom Options CSS (advanced users)
Customize Options page style and/or CodeMirror style/theme with valid CSS.
Changes can break the layout.
Creating a Custom Theme
You can create your own custom theme by overriding a theme like the default theme.
/* DEFAULT THEME */ .cm-s-default .cm-header {color: blue;} .cm-s-default .cm-quote {color: #090;} .cm-negative {color: #d44;} .cm-positive {color: #292;} .cm-header, .cm-strong {font-weight: bold;} .cm-em {font-style: italic;} .cm-link {text-decoration: underline;} .cm-strikethrough {text-decoration: line-through;} .cm-s-default .cm-keyword {color: #708;} .cm-s-default .cm-atom {color: #219;} .cm-s-default .cm-number {color: #164;} .cm-s-default .cm-def {color: #00f;} .cm-s-default .cm-variable, .cm-s-default .cm-punctuation, .cm-s-default .cm-property, .cm-s-default .cm-operator {} .cm-s-default .cm-variable-2 {color: #05a;} .cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;} .cm-s-default .cm-comment {color: #a50;} .cm-s-default .cm-string {color: #a11;} .cm-s-default .cm-string-2 {color: #f50;} .cm-s-default .cm-meta {color: #555;} .cm-s-default .cm-qualifier {color: #555;} .cm-s-default .cm-builtin {color: #30a;} .cm-s-default .cm-bracket {color: #997;} .cm-s-default .cm-tag {color: #170;} .cm-s-default .cm-attribute {color: #00c;} .cm-s-default .cm-hr {color: #999;} .cm-s-default .cm-link {color: #00c;} .cm-s-default .cm-error {color: #f00;} .cm-invalidchar {color: #f00;} .CodeMirror-composing { border-bottom: 2px solid; } /* Default styles for common addons */ div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;} div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;} .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } .CodeMirror-activeline-background {background: #e8f2ff;} /* STOP */
Custom Popup CSS (advanced users)
Customize Toolbar Popup style/theme with valid CSS.
Changes can break the layout.
CodeMirror (advanced users)
Customize CodeMirror Configuration & JSHint Options with a valid JSON. JSHint is set to ECMAScript 2019 ES10 & fairly strict guidelines for coding excellence.
- CodeMirror Options
- Any option except
lint
|mode
- JSHint Options
- Any option except
globals
- Tab to indent with spaces
- Also relates to
tabSize
,indentWithTabs
&indentUnit
- The setting causes the whole line to move, no matter where the cursor is
{ // CodeMirror Options "indentWithTabs": true, "indentUnit": 4, "tabSize": 4, // JSHint Options "jshint": { "curly": false, "varstmt": false } // match-highlighter examples highlightSelectionMatches: true, highlightSelectionMatches: {showToken: /\w/, annotateScrollbar: true}, // Tab to indent with spaces "extraKeys": { "Tab": "indentMore", "Shift-Tab": "indentLess" } }
Preferences: Import/Export
You can import/export Preferences (for backup or share) from/to a local file on your computer.
Import is non-destructive. Click save to apply the new settings.
Since v2.25, import will over-write options and scripts with the same name but keeps other scripts.
It is better to avoid importing pre-2.25 back-ups.
Save
Please note that changes will not take effect until they are saved.
Script & CSS
Sidebar
List of both UserScript and UserCSS
Scripts and CSS have different icons and the icon for disabled ones are grey.
Click on the Script/CSS to view/edit/enable/disable/delete or start a new Script/CSS.
shows that there was an error when registering the script. Scripts with errors get disabled automatically.
- Click to hide/show all enabled Script & CSS
- Click to hide/show all disabled Script & CSS
- Click to hide/show all Scripts
- Click to hide/show all CSS
- Counter
- The counter shows the number of Script & CSS at the bottom
- π₯ Import
- Import one or more Script/CSS from file, same name Script/CSS will be overwritten without warning
- π€ Export
- Export all Script/CSS to FireMonkey folder in the download location set in Firefox Options -> Downloads
- Note: Disabled on Android
- π₯ Import from Stylus
- Export styles from Stylus and Import the JSON (on pop-up change the file type selection to All Files), same name Script/CSS will be overwritten without warning (note: UserStyle)
Editing
CodeMirror editor and a number of its addons is used for easier editing (v2.0).
CodeMirror & addon Guide
Extra Keyboard Commands (when editor is active)
- Ctrl-S Save
- Ctrl-Q Toggle comment
- Ctrl-Space Show hint
- F11 Toggle fullscreen
- Esc Exit fullscreen
Editor Area Buttons
- πΎ Save
- Save the currently displaying Script/CSS
- Trailing Spaces are removed automatically when saving
- π Update
- Update the currently displaying Script/CSS
- π€ Export
- Export the currently displaying Script/CSS to file
- βοΈ Enable
- Enable/Disable the currently displaying Script/CSS
- Changes apply immediately without the need to SAVE
- βοΈ Auto-update
- Enable/Disable the Auto-Update of the currently displaying Script/CSS
- Changes apply immediately without the need to SAVE
Delete
- Delete the currently displaying Script/CSS after confirmation
- β
- Display a new Script Template that you can edit and Save
- β
- Display a new CSS Template that you can edit and Save
- π Menu
- Display Lint Report with click-able lines to go to the corresponding line in the Script/CSS
Menu
-
- Theme
- Select Light/Dark Theme for the editor
- Default scheme is based on prefers-color-scheme but also possible to set to dark on light schemes.
- πΎ Save Template
- Save the currently displaying Script/CSS as a Template
- β Tabs to Spaces
- Convert Tabs to spaces (2 per tab)
- π Selection to Uppercase
- π‘ Selection to Lowercase
- π€ User Metadata
- Persistent user metadata rules to add/amend/disable original Script & CSS metadata rules
// Disable Rule // without a value disables ALL original metadata rules of each entry // with a value disables SINGLE original metadata rule of each entry @disable-match @disable-exclude-match @disable-include @disable-exclude @disable-container (Firefox 97-98) // Add/Amend Rule changes existing metadata rules @match @exclude-match @matchAboutBlank @allFrames @inject-into @run-at @container (Firefox 97-98)
- Multiple Enable/Disable, Delete & Export
- Ctrl or Shift click to select multiple of items listed under Script & CSS
Editor Area Statistic Info
Statistical information is displayed under the the editor of the Script/CSS.
Self-Update
For users that edit scripts regularly, a proper editor is more convenient. You can then update the installed version by:
- Copy & Paste directly into the script Editor
- Import from Navigation Buttons
- Drag & Drop to a tab (or open in tab) and confirm to install
In case of repeated edit, you can keep the tab open and refresh and confirm to update the old one.
Update & Auto-Update
Any target can be set for @updateURL
as long it directly leads to the final file and it is in plain text. The .js
or .css
file extensions are not necessary although it makes sense to have them. The .user.
is not necessary.
FireMonkey uses @version
for the update process and both the current file and the update file must have @version
and the version of the target file must be higher.
In case of GreasyFork & OpenUserJS, the @version
via Metadata Block in https://...../*.meta.js
is verified first.
Update will fail if the new version has a different name which already exists among user's installed Scripts/CSS.
Auto-update will only update enabled Scripts/CSS but manual update can update both enabled and disabled ones.
@downloadURL/@installURL
, if present, is set for script update.
FireMonkey | Greasemonkey | Tampermonkey | Violentmonkey | |
---|---|---|---|---|
@updateURL | update target | JSON | ||
@downloadURL | = @updateURL | update target | update target β | |
install source | update target β | |||
meta.js | update target β | |||
meta.css | ||||
last-modified header | ||||
GreasyFork | GitHub/Gist | OpenUserJS | ||
@updateURL | β | |||
@downloadURL | ||||
meta.js | ||||
meta.css | ||||
last-modified header |
Metadata Block
For easier compatibility, format is similar to Greasemonkey
Metadata Block for UserScripts. The commenting is different for CSS and
as a result a uniform commenting for Metadata Block can be used. The
Metadata Block identifier (case-insensitive e.g. ==userscript== etc) for
UserScript is ==UserScript== ... ==/UserScript==
and for UserCSS is ==UserCSS== ... ==/UserCSS==
. For clarity, it is best to have the Metadata Block on top (or after 'use strict';
if wanted, but FireMonkey can pick it up from anywhere within the code.
Read UserStyle for ==UserStyle== ... ==/UserStyle==
// ==UserScript== // @name My Script // @description Scripting is fun // @match http://www.example.com/* // @match http://www.example.org/* // @version 1.0 // ==/UserScript==
/* ==UserScript== @name My Script @description Scripting is fun @match http://www.example.com/* @match http://www.example.org/* @version 1.0 ==/UserScript== */
/* ==UserCSS== @name My CSS @description CSS is fun @match http://www.example.com/* @match http://www.example.org/* @version 1.0 ==/UserCSS== */
FireMonkey supports the following entries based on contentScripts.register() & userScripts.register(). Each entry must be in the format of @entry ...some space ... value
. In case of matches & globes, repeat entry for each detail. Match details must conform to the Match Patterns.
Script/CSS developers are free to add any other @entry (e.g. @homepage, @copyright, @support etc) for reference or information.
@name
@author
@description
@version needed for updates
@updateURL needed for updates
@match
@exclude-match
@include merged into includeGlobs
@exclude merged into excludeMatches
@require (script-name/specific-library/other-URL)
@resource (resourceName resourceURL)
@noframes if present will set @allFrames to false (default)
@run-at document-start/document-end/document-idle (default)
@inject-into page
@container default|private|container-1|container-2|... (Firefox 97-98)
@includeGlob
@excludeGlob
@matchAboutBlank true/false (default)
@allFrames true/false (default)
@matches browser API style same as @match
@excludeMatches browser API style same as @exclude-match
@includeGlobs browser API style same as @includeGlob
@excludeGlobs browser API style same as @excludeGlobs
@runAt browser API style same as @run-at
Metadata | FireMonkey | Greasemonkey | Tampermonkey | Violentmonkey |
---|---|---|---|---|
@name | ||||
@name:xx-YY | v4.11 β | |||
@author | undocumented β | |||
@description | ||||
@description:xx-YY | v4.11 β | |||
@version | ||||
@updateURL | undocumented β | |||
@match | v0.9.8 (2011) β | |||
@include | ||||
@exclude | ||||
@exclude-match | ||||
@includeGlob | ||||
@excludeGlob | ||||
@matchAboutBlank | ||||
@allFrames | ||||
@noframes | ||||
@require | ||||
@resource | ||||
@run-at | ||||
@downloadURL | ||||
@antifeature | ||||
@inject-into | ||||
@matches | ||||
@excludeMatches | ||||
@includeGlobs | ||||
@excludeGlobs | ||||
@runAt | ||||
@homepage | ||||
@homepageURL | ||||
@website | ||||
@source | ||||
@support | ||||
@supportURL | ||||
@container | Firefox 97-98 | |||
@grant | ||||
@namespace | ||||
@icon | ||||
@connect | ||||
@unwrap | ||||
@nocompat | ||||
@icon64 | ||||
@icon64URL | ||||
@iconURL | ||||
@defaulticon |
name
FireMonkey uses the name
(case-sensitive) as ID for Scripts & CSS, therefore names must be unique. A shorter & concise name is recommended.
Localization
Some entries can be localized for display propose e.g. @name:zh-CN
, @description:kr
. The language code is case-sensitive and must match Language Tags and Locale Identifiers e.g. "en", "en-US", "de", "fr", etc.
Entries without local identifier can also match navigator.language
with local identifier. For example, if navigator.language
is "en-US" it will match name:en-US
or name:en
.
grant not processed
Experimental @grant background processing was added in v2.42 to auto-disable GM_* API if userScript supports async type GM.* API.
Userscripts will have access to all available APIs and therefore @grant
is not needed or processed.
@grant
for GM APIs in FireMonkey in userScripts
context, are inconsequential security-wise. Even in the disputed case of GM.xmlHttpRequest
, the following scripts work the same way in FireMonkey and there is no difference in their security i.e. script A is not more secure than script B, and vice versa.
Script A | Script B |
---|---|
// ==UserScript== // @name Script A // @match http://www.example.com/* // @grant GM.xmlHttpRequest // ==/UserScript== GM.xmlHttpRequest({ url: 'https://abcd.com/', onload: response => { console.log(response.responseText); } }); |
// ==UserScript== // @name Script B // @match http://www.example.com/* // ==/UserScript== GM.xmlHttpRequest({ url: 'https://abcd.com/', onload: response => { console.log(response.responseText); } }); |
container (UserCSS Firefox 97, UserScript Firefox 98)
Setting one or more entries will further limit the script/CSS to certain contextual identity containers e.g. default|private|container-1|container-2|...
after URL matching. (Bug 1470651 & Bug 1738567)
- no container
- inject into all tabs
- default
- inject only into non-private, non-container tabs
- private
- inject only into private tabs
- `private` only works if user has allowed the extension to run in private mode.
- container-N
- inject only into Firefox container-N tabs
@container container-1 @container private
inject-into
Setting the value page
will inject the entire userscript into the page
context (more: Extension JavaScript Context).
- Only applies to userscripts
- Only
page
is supported - In
page
mode, userscript injection might get blocked by page Content Security Policy (CSP) - In
page
mode, userscripts have no access to GM functions however ...unsafeWindow
is available for Violentmonkey compatibilityGM.info|GM_info
is available for Tampermonkey & Violentmonkey compatibility
FireMonkey | Greasemonkey | Tampermonkey | Violentmonkey | |
---|---|---|---|---|
default | userScript | content β | content | page β β |
options | page | content | auto | |||
@inject-into | page | page | content | auto | ||
@grant xyz @grant none |
no change | no change | content page β |
no change |
unsafeWindow |
userScript page |
content β |
content page |
content β page β |
GM info |
userScript page |
content |
content page |
content page |
GM functions |
userScript page |
content |
content page |
content page |
isolated context 1 | ||||
alter page CSP 2 | β | β β | ||
π‘οΈ Context Security Comparison | ||||
context | userScript | content | content | content |
typeof browser typeof chrome | undefined | object | undefined | undefined |
browser chrome | ReferenceError: browser(chrome) is not defined | Object { menus: Getter & Setter, manifest: Getter & Setter, normandyAddonStudy: Getter & Setter, extension: Getter & Setter, i18n: Getter & Setter, storage: Getter & Setter, test: Getter & Setter, userScripts: Getter & Setter, runtime: {β¦} } | ReferenceError: browser(chrome) is not defined | ReferenceError: browser(chrome) is not defined |
browser.storage chrome.storage | ReferenceError: browser(chrome) is not defined | Object { onChanged: Getter & Setter, local: Getter & Setter, managed: Getter & Setter, sync: Getter & Setter, StorageChange: Getter & Setter, StorageArea: Getter & Setter, StorageAreaSync: Getter & Setter } | ReferenceError: browser(chrome) is not defined | ReferenceError: browser(chrome) is not defined |
window.browser window.chrome | undefined | Object { menus: Getter & Setter, manifest: Getter & Setter, normandyAddonStudy: Getter & Setter, extension: Getter & Setter, i18n: Getter & Setter, storage: Getter & Setter, test: Getter & Setter, userScripts: Getter & Setter, runtime: {β¦} } | undefined | undefined |
1 Userscripts in FireMonkey are by default injected into a separate isolated userScript
contexts, and therefore there is no interaction between the userscripts. In content
or page
context, the userscripts will share the same context which can result in unexpected behaviour e.g.
- Scripts running on the same page share the same window wrapper
- [Firefox] Scripts share the same window wrapper if `@inject-into content` is used
2 It is also worth noting that the hack with removing
the CSP can cause a conflict with other addons that use CSP to block
content (like uBlock Origin)
. β
antifeature
Under-development feature on GreasyFork (ref #604) to mark possibly undesirable behaviour e.g. miners, ads etc.
Anti-Features are flags packagers apply to apps, warning of possibly undesirable behaviour from the user's perspective, often serving the interest of the developer or a third party.
AntiFeatures
matchAboutBlank
The code cannot be inserted in top-level about:
frames.
Insert the content scripts into pages whose URL is
"about:blank"
or"about:srcdoc"
, if the URL of the page that opened or created this page matches the patterns specified in the rest of thecontent_scripts
key.This is especially useful to run scripts in empty iframes , whose URL is
"about:blank"
. To do this you should also set theall_frames
key.For example, suppose you have a
content_scripts
key like this:"content_scripts": [ { "js": ["my-script.js"], "matches": ["https://example.org/"], "match_about_blank": true, "all_frames": true } ]If the user loads
content_scriptshttps://example.org/
, and this page embeds an empty iframe, then"my-script.js"
will be loaded into the iframe.
matchAboutBlank Optional
boolean
. Iftrue
, the code will be injected into embeddedabout:blank
andabout:srcdoc
frames if your extension has access to their parent document. The code cannot be inserted in top-levelabout:
frames.Defaults to
tabs.executeScript()false
.
run-at
FireMonkey default run-at
is document_idle
for Scripts, therefore there is no need for event listeners such as 'load'
or 'DOMContentLoaded'
as they may not apply. If script has to run earlier, then the run-at
has to be set accordingly.
Greasemonkey uses hyphen separated words (document-start|document-end|document-idle
), while the API uses underscore separated words (document_start|document_end|document_idle
). FireMonkey converts the hyphen so both can be used.
FireMonkey & Firefox API run-at
states directly correspond to Document.readyState.
- document_start
- Corresponds to
loading
. The DOM is still loading. - document_end
- Corresponds to
interactive
. The DOM has finished loading, but resources such as scripts, images, stylesheets and frames may still be loading. - The state indicates that the DOMContentLoaded event is about to fire.
- document_idle
- Corresponds to
complete
. The document and all its resources have finished loading. - The state indicates that the load event is about to fire.
run-at (non-standard)
Tampermonkey supports additional non-standard run-at
.
Tampermonkey Documentation
- document-body
- The script will be injected if the body element exists.
- context-menu
- The script will be injected if it is clicked at the browser context menu (desktop Chrome-based browsers only).
- Note: all
@include
and@exclude
statements will be ignored if this value is used, but this may change in the future.
defaults
FireMonkey conforms to the Firefox (& Chrome) content_scripts API defaults.
FireMonkey | Greasemonkey | Tampermonkey | Violentmonkey | |
---|---|---|---|---|
run-at | document-idle (JS) document-start (CSS) | document-end | document-idle [β] | document-end |
allFrames | false | true | true | true |
matchAboutBlank | false | @include about:blank |
π URL Matching Performance
The performance from top (best) to bottom (worst):
- match & exclude-match
- includeGlob & excludeGlob
- include & exclude
- include & exclude with regular expression
Due to matching order in Firefox/Chrome APIs, having mixed @match/@exclude-match
with @include/@exclude/includeGlob/excludeGlob
might have unexpected matched/unmatched results.
include & exclude
They are the old and error-prone method of matching which has been superseded by @match
/@exclude-match
.
Entries under @include/@exclude
will be merged into @includeGlob/@excludeGlob
(v2.5) (duplicates removed).
Please note that @match
/@exclude-match
performance is far more efficient.
include_globs: Applied after matches to include only those URLs that also match this glob. Intended to emulate the @include Greasemonkey keyword.
exclude_globs: Applied after matches to exclude URLs that match this glob. Intended to emulate the @exclude Greasemonkey keyword.
Exclude matches and globs
The
Greasemonkey @match@match
metadata imperative is very similar to@include
, however it is safer. It sets more strict rules on what the*
character means.
It is recommended to use
Violentmonkey Matching@match
/@exclude-match
rather than@include
/@exclude
because the match rules are safer and more strict.
Regular Expression in include & exclude
Regular Expression support has been implemented for @include/@exclude
(v2.5).
- Regular Expressions start & end with forward-slash
/
- Regular Expressions are processed with new RegExp()
- Regular Expressions are set as case-insensitive
- No need to escape forward-slashes
- Regular Expression Character classes should be escaped e.g.
\.
\\d
\\s
\\w
includeGlob & excludeGlob
Due to the following logic process in Firefox & Chrome, when using includeGlob
, there must also be the mandatory match
.
Since
matches
is the only mandatory key, the other three keys are used to limit further the URLs that match. To match the key as a whole, a URL must:content_scripts
- match the
matches
property- AND match the
include_globs
property, if present- AND NOT match the
exclude_matches
property, if present- AND NOT match the
exclude_globs
property, if present
/*
==UserCSS==
@name My CSS
@description CSS is fun
@match *://*/*
@includeGlobs http://www.example.*/*
@version 1.0
==/UserCSS==
*/
Converting include/exclude to match/exclude-match
Some example of how you can convert to more robust Match Patterns.
From include | To match |
---|---|
* | *://*/* |
http://* | http://*/* |
https://* | https://*/* |
http*://* | *://*/* |
http*://a.b.c/* | *://a.b.c/* |
*://google.* http*://www.google.* | *://*.google.TLD/* |
*.example.com/* | *://*.example.com/* |
Match Patterns
FireMonkey uses Match Patterns for @matches/@match
& @excludeMatches
and globs for @includeGlobs
& @excludeGlobs
Note: Paths are case-sensitive.
A glob is just a string that may contain wildcards. There are two types of wildcard, and you can combine them in the same glob:
- Wildcard * matches zero or more characters
- Character ? matches exactly one character
For example: "*na?i"
would match "illuminati"
and "annunaki"
, but not "sagnarelli"
.
Pattern | match | no-match |
---|---|---|
<all_urls>
Match all URLs (in this case http, https, file) |
http://example.org/ https://files.example.org/
|
|
*://*/*
Match all http, https |
http://example.org/ https://www.example.org/aaa/ |
|
*://*.example.org/*
|
http://example.org/ https://www.example.org/ http://www.sub.example.org/aaa/ |
|
*://example.org/
|
http://example.org/ |
https://www.example.org/ http://example.org/aaa/ |
https://*/path
|
https://www.example.org/path |
http://example.org/ http://example.org/path |
file:///blah/*
|
file:///blah/ file:///blah/bleh |
file:///bleh/ |
Invalid Pattern | Reason |
---|---|
resource://path/ | Unsupported scheme |
https://mozilla.org | No path |
https://mozilla.*.org/ | "*" in host must be at the start |
https://*zilla.org/ | "*" in host must be the only character or be followed by "." |
http*://mozilla.org/ | "*" in scheme must be the only character |
https://mozilla.org:80/ | Host must not include a port number |
*://* | Empty path: this should be "*://*/*" |
file://* | Empty path: this should be "file:///*" |
Note: Content scripts are blocked by Firefox on the following domains:
- accounts-static.cdn.mozilla.net
- accounts.firefox.com
- addons.cdn.mozilla.net
- addons.mozilla.org
- api.accounts.firefox.com
- content.cdn.mozilla.net
- content.cdn.mozilla.net
- discovery.addons.mozilla.org
- input.mozilla.org
- install.mozilla.org
- oauth.accounts.firefox.com
- profile.accounts.firefox.com
- support.mozilla.org
- sync.services.mozilla.com
- testpilot.firefox.com
FTP β οΈ
Aligning with our intent to deprecate non-secure HTTP and increase the percentage of secure connections, we, as well as other major web browsers, decided to discontinue support of the FTP protocol.
Stopping FTP support in Firefox 90
The implementation is currently disabled in the Firefox Nightly and Beta pre-release channels and will be disabled when Firefox 88 is released on April 19, 2021. The implementation will be removed in Firefox 90. After FTP is disabled in Firefox, the browser will delegate
Built-in FTP implementation to be removed in Firefox 90ftp://
links to external applications in the same manner as other protocol handlers.
The Firefox platform development team recently announced plans to first disable, and then remove the implementation for built-in FTP from the browser.
What to expect for the upcoming deprecation of FTP in Firefox
TLD
A TLD (case-insensitive) wildcard can be used and FireMonkey will automatically generate a list of most common country TLD @matches
. Due to the extra Firefox API processing for many @matches
, it is best to use it only when necessary.
.com .au .br .ca .ch .cn .co.uk .de .es .fr .in .it .jp .mx .nl .no .pl .ru .se .uk .us
.ca .cn .co.jp .co.uk .com .com.au .com.br .com.mx .com.sg .com.tr .de .es .fr .in .it .nl
.at .be .ca .ch .cn .co.th .co.uk .com.au .com.cn .com.hk .com.my .com.sg .com.tw .com .de .dk .es .fi .fr .gr .hu .ie .in .it .nl .no .ph .ph .pl .ru .vn
.ae .al .am .as .at .az .ba .be .bf .bg .bi .bj .bs .bt .by .ca .cat .cd .cf .cg .ch .ci .cl .cm .cn .co.ao .co.bw .co.ck .co.cr .co.id .co.il .co.in .co.jp .co.ke .co.kr .co.ls .co.ma .co.mz .co.nz .co.th .co.tz .co.ug .co.uk .co.uz .co.ve .co.vi .co.za .co.zm .co.zw .com .com.af .com.ag .com.ai .com.ar .com.au .com.bd .com.bh .com.bn .com.bo .com.br .com.bz .com.co .com.cu .com.cy .com.do .com.ec .com.eg .com.et .com.fj .com.gh .com.gi .com.gt .com.hk .com.jm .com.kh .com.kw .com.lb .com.ly .com.mm .com.mt .com.mx .com.my .com.na .com.nf .com.ng .com.ni .com.np .com.om .com.pa .com.pe .com.pg .com.ph .com.pk .com.pr .com.py .com.qa .com.sa .com.sb .com.sg .com.sl .com.sv .com.tj .com.tr .com.tw .com.ua .com.uy .com.vc .com.vn .cv .cz .de .dj .dk .dm .dz .ee .es .fi .fm .fr .ga .ge .gg .gl .gm .gp .gr .gy .hn .hr .ht .hu .ie .im .iq .is .it .je .jo .kg .ki .kz .la .li .lk .lt .lu .lv .md .me .mg .mk .ml .mn .ms .mu .mv .mw .ne .ng .nl .no .nr .nu .pl .pn .ps .pt .ro .rs .ru .rw .sc .se .sh .si .sk .sm .sn .so .sr .st .td .tg .tk .tl .tm .tn .to .tt .vg .vu .ws
require
The API is added for convenience and compatibility but it works differently in comparison to other script managers.
You can use @require
for both scripts and CSS.
- UserCSS
- Require another installed UserCSS by name
- Require a remote CSS
- UserScript
- Require another installed UserScript by name
- Require a remote JS
- Require a remote CSS (URL ending with
.css
)
require script-name
@require
can be used to pre-include other existing scripts into a script (or other existing CSS into a CSS). For example:
@require My Script
For example, user/developer can save some code as a script and name it HelperSet
. It does not need to have match/matches/include/includeGlobs
etc. It is best to keep the script DISABLED (not enabled) e.g.:
// ==UserScript== // @name HelperSet // @description A set of helper functions // @version 1.0 // ==/UserScript== function someFunc(id) { // some code } function otherFunc(text) { // some code }
// ==UserScript== // @name My Script // @description Scripting is fun // @match http://www.example.com/* // @match http://www.example.org/* // @version 1.0 // @require HelperSet // ==/UserScript== ..... code .....
// @require jquery-3 // @require HelperSet // @require https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta2/css/fontawesome.min.css
/* ==UserCSS== @name My CSS @description CSS is fun @match http://www.example.com/* @match http://www.example.org/* @version 1.0 @require DefaultCSS @require https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta2/css/fontawesome.min.css ==/UserCSS== */
require specific-library
Few libraries have been packed with FireMonkey which will be injected for user-scripts using @require
. A shorthand (case-insensitive) can also be used.
Based on data provided by GreasyFork, 8,011/31,170 (26%) use @require
and from those 7,082/11,236 (63%) entries relate to these libraries (& gm4-polyfill).
Following CDN sources are processed:
- ajax.aspnetcdn.com
- ajax.googleapis.com
- apps.bdimg.com
- cdn.bootcss.com
- cdn.jsdelivr.net
- cdn.staticfile.org
- cdnjs.cloudflare.com
- code.jquery.com
- lib.baomitu.com
- libs.baidu.com
- pagecdn.io
- unpkg.com
CDN Links to | Shorthand | Injected Library Version |
---|---|---|
bootstrap 4 | bootstrap-4 | 4.6.1 |
bootstrap 5 | bootstrap-5 | 5.1.3 |
jquery 1 | jquery-1 | 1.12.4 |
jquery 2 | jquery-2 | 2.2.4 |
jquery 3 | jquery-3 | 3.6.0 |
jquery-ui 1 | jquery-ui-1 | 1.12.1 |
moment | moment-2 | 2.29.1 |
underscore | underscore-1 | 1.13.1 |
// @require jquery-3 // @require https://code.jquery.com/jquery-3.5.1.js // @require https://code.jquery.com/jquery-3.5.1.min.js // @require https://code.jquery.com/jquery-3.5.0.slim.js // @require https://code.jquery.com/jquery-3.5.0.slim.min.js // @require https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.js ... etc // @require https://unpkg.com/jquery@3.5.0/dist/jquery.js ... etc // @require https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta2/css/fontawesome.min.css
On popular demand, more libraries may be added in future.
require other-URL β οΈ
The API is added for convenience and compatibility but it works
differently in comparison to other script managers as the target is not
not stored. It uses fetch()
to get the target the first time but on subsequently Request.cache will be used by the browser. Please note:
- Processing many scripts with many
@require
can take time when registering scripts (i.e. browser start-up, disabling & re-enabling FireMonkey) - gm4-polyfill is not applicable (and may cause conflict) and therefore is not processed
βΉοΈ 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.
// ==UserScript== // @name GM.getResourceUrl test // @description GM.getResourceUrl() API method // @resource CSS http://www.example.com/example.css // ==/UserScript==// instead of this code (async () => { const link = document.createElement('link'); link.href = await GM.getResourceUrl('CSS'); link.rel = 'stylesheet'; document.body.appendChild(style); })();
// you can use this one const link = document.createElement('link'); link.href = 'http://www.example.com/example.css'; link.rel = 'stylesheet'; document.head.appendChild(link);
// ==UserScript== // @name GM.getResourceUrl test // @description GM.getResourceUrl() API method // @resource logo http://www.example.com/logo.jpg // ==/UserScript==// instead of this code (async () => { const img = document.createElement('img'); img.src = await GM.getResourceUrl('logo'); document.body.appendChild(img); })();
// you can use this one const img = document.createElement('img'); img.src = 'http://www.example.com/logo.jpg'; document.head.appendChild(img);
Script API
FireMonkey supports both GM4 & GM3 style APIs (e.g. GM.*** & GM_***) via mostly asynchronous Promise
. Therefore if scripts requires a return
, it must wait for the Promise
to resolve.
Following APIs (similar to Greasemonkey) are provided to the user scripts (more may be added later)
(async () => { 'use strict'; // ----- Synchronous Partial β // stored value for key OR default const value = GM_getValue(key, default); // an array of keys set by script OR [] const keys = GM_listValues(); // ----- Promise // stored value for key OR default const value = await GM.getValue(key, default); // an array of keys set by script OR [] const keys = await GM.listValues(); // value can be string, number, boolean, or object GM.setValue(key, value) GM_setValue(key, value) GM.deleteValue(key) GM_deleteValue(key) // Response Object const response = await GM.fetch(url [, init]); (FireMonkey only) const response = await GM_fetch(url [, init]); (FireMonkey only) // Response Text const text = await GM.getResourceText(resourceName); const text = await GM_getResourceText(resourceName); // usually there is no need to wait for the following, but if needed, use await GM.openInTab(url, open_in_background) GM_openInTab(url, open_in_background) GM.setClipboard(text) GM_setClipboard(text) GM.notification(text) GM_notification({text: '...', image: '...', ....}) GM.download(url, fielname) GM_download(url, fielname) // ----- Callback Function GM.xmlHttpRequest({ url: 'https://example.com/etc', onload: response => { console.log(response.responseText); } }); // note: listenerId = key (not necessary in FireMonkey) const listenerId = GM.addValueChangeListener(key, callback); GM.addValueChangeListener(key, callback) GM_addValueChangeListener(key, callback) // ----- Synchronous Functions GM.addStyle(css) GM_addStyle(css) GM.addScript(code) (FireMonkey only) GM_addScript(code) (FireMonkey only) GM.popup(options) (FireMonkey only) GM_popup(options) (FireMonkey only) GM.getResourceUrl(resourceName) GM_getResourceURL(resourceName) GM.registerMenuCommand(name, onclick) GM_registerMenuCommand(name, onclick) GM.unregisterMenuCommand(name) GM_unregisterMenuCommand(name) GM.removeValueChangeListener(key) GM_removeValueChangeListener(key) GM.log(text [, text2, ..., textN]) GM_log(text [, text2, ..., textN]) GM.info GM_info unsafeWindow })();
API | FireMonkey | Greasemonkey | Tampermonkey | Violentmonkey | ||
---|---|---|---|---|---|---|
GM.addScript π³ | ||||||
GM.addStyle | β | |||||
GM.addValueChangeListener | ||||||
GM.cookie | ||||||
GM.deleteValue | ||||||
GM.download | ||||||
GM.fetch π³ | ||||||
GM.getResourceText | ||||||
GM.getResourceUrl | β | β β | ||||
GM.getValue | β | |||||
GM.info | ||||||
GM.listValues | ||||||
GM.log | ||||||
GM.notification | ||||||
GM.openInTab | ||||||
GM.popup π³ | ||||||
GM.registerMenuCommand | v4.11 β | |||||
GM.removeValueChangeListener | ||||||
GM.setClipboard | β | |||||
GM.setValue | ||||||
GM.unregisterMenuCommand | ||||||
GM.xmlHttpRequest | ||||||
GM_addElement | v4.11 β | |||||
GM_addScript π³ | ||||||
GM_addStyle | ||||||
GM_addValueChangeListener | ||||||
GM_cookie | β | |||||
GM_deleteValue | async | |||||
GM_download | ||||||
GM_fetch π³ | ||||||
GM_getResourceText | async | |||||
GM_getResourceURL | ||||||
GM_getTab | β | |||||
GM_getTabs | β | |||||
GM_getValue | partial β | |||||
GM_info | ||||||
GM_listValues | partial β | |||||
GM_log | ||||||
GM_notification | ||||||
GM_openInTab | ||||||
GM_popup π³ | ||||||
GM_registerMenuCommand | ||||||
GM_removeValueChangeListener | ||||||
GM_saveTab | β | |||||
GM_setClipboard | ||||||
GM_setValue | async | |||||
GM_unregisterMenuCommand | ||||||
GM_xmlhttpRequest | ||||||
unsafeWindow |
userScript page |
content β |
content page |
content β page β |
||
window.close 1 | β | v2.6.2 β | ||||
window.focus 2 | v2.12.10 β | |||||
window.onurlchange 3 | v4.11 β | |||||
π₯ Injected Scripts
β every page β every frame β when no active userscript β when turned off |
content.js page.js rea/common.js |
browser.js injected-web.js injected.js sandbox/injected-web.js |
||||
![]() |
Proprietary License | minified | ||||
π No Data Collection | Privacy policy | Privacy policy | ||||
π No Tracking | Google favicon | |||||
π₯ Firefox Users | ~670 | 260k | 457k | 46k |
1 window.close
grant results is overwriting the global window.close() and bypassing the specific JavaScript window.opener
safeguard to close a window that it has not opened. Such grant would
allow malware userscripts to run code and hide the result by closing the
window before user has a change to notice. Furthermore, the choice to
close a window/tab opened by the user, should remain with the user.
2 window.focus
grant results is overwriting the global window.focus().
Such grant could be disruptive in cases where user is playing a game or
in the middle of something in another window/tab and the userscript in
the background causes its window/tab to become active and come to the
foreground. Furthermore, the choice to focus a window/tab, should remain
with the user.
3 window.onurlchange
is an experimental implementation in Tampermonkey 4.11.6120 (2020-09-17).
Alternative: Detecting JavaScript Navigation
API | FireMonkey | Stylus | Stylish | xStyle | ||
---|---|---|---|---|---|---|
@-moz-document url | ||||||
@-moz-document domain | ||||||
@-moz-document url-prefix | ||||||
@-moz-document regexp | ||||||
π₯ Injected Scripts
β every page β every frame β when no active userstyle β when turned off |
content/apply.js content/style-injector.js js/msg.js js/polyfill.js js/prefs.js js/promisify.js/td> | src/inject/apply.js | src/inject/apply.js | |||
![]() |
Proprietary License | minified | ||||
π No Data Collection | Privacy policy | |||||
π₯ Firefox Users | ~670 | 66k | 66k | 380 |
API | FireMonkey | Tampermonkey|Violentmonkey | Same |
---|---|---|---|
GM_addScript π³ | sync | β | β |
GM_addStyle | sync | sync | |
GM_addValueChangeListener | callback | callback | |
GM_deleteValue | async | sync | shouldn't matter |
GM_download | async | async | |
GM_fetch π³ | async | β | β |
GM_getResourceText | async | sync | |
GM_getResourceUrl | sync | sync | different implementation |
GM_getValue | semi-sync | sync | |
GM_info | sync | sync | |
GM_listValues | semi-sync | sync | |
GM_log | sync | sync | |
GM_notification | async | async | |
GM_openInTab | async | async | |
GM_popup π³ | sync | β | β |
GM_registerMenuCommand | callback | callback | |
GM_removeValueChangeListener | sync | sync | |
GM_setClipboard | async | async | |
GM_setValue | async | sync | |
GM_unregisterMenuCommand | sync | sync | |
GM_xmlHttpRequest | callback | callback |
βΉοΈ Asynchronous & Synchronous Storage
There are 2 types storage systems available to extensions:
- Extension Storage
- Asynchronous private permanent storage accessible to the extension only
- Synchronous private Web API storage accessible only to the extension domain's privileged pages (background, options, popup etc, but not contentScript or userscript) e.g. sessionStorage | localStorage | IndexedDB
- Web Storage
- Synchronous public storage which is also accessible to web pages to read/write/delete e.g. sessionStorage | localStorage | IndexedDB
- π ° The only permanent private storage is the extension storage
- π ° User-script storage is saved to the extension storage for security, privacy and permanency
- π ° The only way to get extension storage is via an async operation
- πΈ The only way to pass above data to a user-script BEFORE it starts to run, is to delay running/injection of the script until above async operation is completed
- π ° On average, an async storage retrieval takes about 2-4 milliseconds
- πΈ On average, the operation of delaying the script until its storage is prepared takes many times longer than async storage retrieval
- πΈ Delaying the script injection affects script operation especially on
document-start
- π ° Async storage always gets the updated data
- πΈ Quasi-sync storage only gets the data available at the time of injection synchronously and values updated in other tabs are not available synchronously
- πΈ There is no real sync user-script storage operation (all permanent storage sync API are fake sync)
- π ° The only way to update storage is via an async operation
βΉοΈ getValue & listValues
Partial compatibility has been implemented. Please note:
- Scripts that run
GM_getValue
&GM_listValues
immediately will get the values at the start of the session which is the browser start-up time or script enabled time, whichever later - Scripts that run
GM_getValue
&GM_listValues
on anevent
will get the values at page load time
The async GM.getValue
& GM.listValues
will get the most up-to-date values at that moment.
openInTab(url, open_in_background)
The option support for openInTab
is not unified between userscript managers.
The default value for open_in_background
honors Firefox configuration.
FireMonkey | Greasemonkey | Tampermonkey | Violentmonkey | |
---|---|---|---|---|
open_in_background true/false | ||||
object | β | β
{
active,
insert,
setParent,
incognito
} |
β
{
active,
insert,
container,
pinned
} |
fetch(url [, init]) (β οΈ breaking changes in v2.33)
FireMonkey fetch
API is based on the JavaScript Fetch API
which provides the new Promise based interface for fetching resources
(including across the network). It will seem familiar to anyone who has
used XMLHttpRequest
, but it provides a more powerful and flexible feature set.
For simplicity and compatibility, the same GM style naming is kept for fetch
API although it is not available in Greasemonkey.
FireMonkey fetch
returns response object/string or null
(check console & the log for errors e.g.: TypeError: "NetworkError when attempting to fetch resource.").
FireMonkey GM fetch
fixes Bug 1405971.
GM.fetch(url [, init]); GM_fetch(url [, init]);
url
http, https, ftp, ftps β, (file not allowed), can be relative to the web page
init (Optional)
An options object containing any custom settings that you want to apply to the request. The possible options are:
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 FireMonkey's 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'
Since v2.33. GM.fetch returns an object in all cases.
// example of response object { headers: { age: "52512", "cache-control": "max-age=86400, public", "content-encoding": "br", "content-type": "text/html; charset=utf-8", date: "Wed, 24 Mar 2021 16:25:31 GMT", "last-modified": "Wed, 24 Mar 2021 02:02:53 GMT", ... ... etc }, bodyUsed: false, ok: true, redirected: true, status: 200, statusText: "OK", type: "basic", url: "https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch", // plus one of the following properties based on responseType, if method is not HEAD text: ..., json: ..., blob: ..., arrayBuffer: ..., formData: ..., } // HEAD request const response = await GM.fetch('https://example.com/etc', {method: 'HEAD'}); // simplest, returns response text const response = await GM.fetch('https://example.com/etc'); const text = response.text; // with init, returns response text const response = await GM.fetch('https://example.com/etc', { method: 'POST', body: JSON.stringify(data), // data can be `string` or {object}! headers:{ 'Content-Type': 'application/json' }); const text = response.text; // returns response JSON const response = await GM.fetch('https://example.com/etc', {responseType: 'json'}); const obj = response.json; // if you don't need to wait for the response GM.fetch('https://example.com/etc') .then(response => callback(response)) .catch(error => console.error(error.message));
xmlHttpRequest({object})
GM.xmlHttpRequest/GM_xmlhttpRequest
is the xmlHttpRequest interface and mostly compatible with Greasemonkey API.
All requests are asynchronous
, therefore Greasemonkey synchronous
is not supported.
GM.xmlHttpRequest({ url, // http, https,ftp, ftpsβ, (file not allowed), can be relative to the web page method, // Optional, defaults to 'GET' if omitted headers, // optional, header object to send with the request data, // optional, e.g. POST data overrideMimeType, // optional, MIME type to send with the request user, // optional, username to send with the request password, // optional, password to send with the request timeout, // optional, number of milliseconds before terminating the request (default 0 = no timeout) withCredentials, // Optional, Boolean, for cross-site Access-Control e.g. cookies, authorization headers or TLS client certificates, defaults to false, no effect on same-site requests Since the request is made from FireMonkey's background script, true/false have the same effect. responseType // Optional, '' or 'text' (default), 'arraybuffer', 'blob', 'document', 'json' if used, get the result from response (not responseText) anonymous, // Optional, true/false (default) if true, no cookie will be sent with the request onload, // callback function onerror, // callback function onabort, // callback function ontimeout, // callback function });
onload, onerror, ontimeout, onabort { readyState response responseHeaders responseText responseType responseURL responseXML status statusText finalUrl // clone of responseURL for GM/TM/VM compatibility }
// simplest GM.xmlHttpRequest({ url: 'https://example.com/etc', onload: response => { console.log(response.responseText); } }); // POST request GM.xmlHttpRequest({ url: 'https://example.com/etc', method: 'POST', data: JSON.stringify(data), // data can be `string` or {object}! headers:{ 'Content-Type': 'application/json' } onload: response => { console.log(response.responseText); }, onerror: response => { console.log(`${response.status} ${response.statusText}`); }, ); // HEAD request GM.xmlHttpRequest({ url: 'https://example.com/etc', method: 'HEAD', onload: response => { console.log(response.responseHeaders); } });
xmlHttpRequest withCredentials
GM.xmlHttpRequest/GM_xmlhttpRequest
are sent from background script where credentials are not available. Sometimes it is necessary to send xmlHttpRequest
from the page context.
XPCNativeWrapper is due to be removed (ref Bug 1481337).
const xhr = XPCNativeWrapper(new window.wrappedJSObject.XMLHttpRequest()); xhr.open('GET', 'https://example.com/etc'); xhr.withCredentials = true; xhr.onload = response => { console.log(response.responseText); }, xhr.onerror = response => { console.log(`${response.status} ${response.statusText}`); } xhr.send(null);
βΉοΈ JavaScript xmlHttpRequest & fetch
Due to a bug in Firefox userScript
context, CORS fails in JavaScript new XMLHttpRequest()
& fetch()
(ref Bug 1715249).
After liaising with Mozilla engineers, once the bug is fixed, userscripts will get the same origin as the web-page they are on.
Please note that userScript
context used in FireMonkey is more restrictive than the content
context used in GM|TM|VM. Therefore the behaviour of JavaScript xmlHttpRequest & fetch in GM|TM|VM will be somwhow different.
Userscripts injected in content
context (as in GM|TM|VM), or in FireMonkey Scratchpad & temporary script injection, carry certain security concerns. Changes are planned as part of Manifest v3 update.
β’ the content script sandbox has an expanded principal that includes the extension principal, and so in manifest_version 2 extension that allows the imported fetch and XMLHttpRequest to do cross site requests based on the extension host permission (but in manifest_version 3 this is not going to be allowed anymore)
β’ the user script sandbox has an expanded principal but it doesn't include the extension principal and so the imported fetch and XMLHttpRequest can't do cross site requests based on the extension host permission (and this part is actually intended, the single userScript is not supposed to silently inherit expanded permission that the userScript manager extension does have)
Luca Greco Bug 1715249
βΉοΈ Forbidden header name
A forbidden header name (also forbidden header name) is the name of any HTTP header that cannot be modified programmatically; specifically, an HTTP request header name (in contrast with a Forbidden response header name).
Modifying such headers is forbidden because the user agent retains full control over them. Names starting with `Sec-
` are reserved for creating new headers safe from APIs using fetch
that grant developers control over headers, such as XMLHttpRequest
.
All Forbidden header names will be removed from the request by FireMonkey before sending.
Forbidden header names start with Proxy-
or Sec-
, or are one of the following names:
- Accept-Charset
- Accept-Encoding
- Access-Control-Request-Headers
- Access-Control-Request-Method
- Connection
- Content-Length
- Cookie
- Cookie2
- Date
- DNT
- Expect
- Host
- Keep-Alive
- Origin
- Proxy-
- Sec-
- Referer
- TE
- Trailer
- Transfer-Encoding
- Upgrade
- Via
After confirmation from AMO (ref 2019-09-28 Rob W), modification to the following Forbidden headers will be allowed:
Header names are case-insensitive (v2.30).
- Cookie
- Host
- Origin
- Referer
notification(text)
Currently, only plain text is processed for notification. For compatibility, options object {text, title, image, onclick}
is processed and the text is passed for notification.
Notes on notification options:
- text
- text string
- title
- Not processed, script name shows as title
- image
- a data URL, a blob URL, a http or https URL
- onclick
- Not processed, may be added on popular demand
GM_notification('some text'); GM_notification({text: 'some text', image: 'https://example.com/icon.jpg', ...});
registerMenuCommand(name, onclick)
If there are Script Commands for a tab, they will be listed at the bottom of toolbar pop-up under their user-script name. Please note that onclick must be a function.
// direct method -> error: alert runs immediately
GM_registerMenuCommand('Hello, world (direct)', alert('Hello, world! (direct)'));
// anonymous function
GM_registerMenuCommand('Hello, world (anon)', function() { alert('Hello, world! (anon)'); });
// named function
function sayHello() { alert('Hello, world! (named)'); }
GM.registerMenuCommand('Hello, world (named)', sayHello);
unregisterMenuCommand(name)
Unregistered the previously created script menu.
GM.unregisterMenuCommand('Hello, world (named)');
getResourceText(resourceName)
The API is added for convenience and compatibility but it works
differently in comparison to other script managers as the target is not
not stored. It uses fetch()
to get the target the first time but on subsequently Request.cache will be used by the browser.
const text = await GM_getResourceText('resourceName');
getResourceUrl(resourceName)
The API is added for convenience and compatibility but it works
differently in comparison to other script managers as the target is not
not stored. It maps directly to the resourceURL and returns the URL, or undefined
.
const url = GM_getResourceUrl('resourceName');
addValueChangeListener(key, callback)
Script storage change listener that returns the key as listener ID (although not necessary in FireMonkey).
(key, oldValue, newValue, remote)
are passed to the callback function.
- key: the storage key
- oldValue: original value or
undefined
if it was created - newValue: new value or or
undefined
if it was deleted - remote:
true
if change came from another tab orfalse
if from the same tab
// anonymous function GM_addValueChangeListener('test-key', function(...arg) { console.log(...arg); }); // anonymous arrow function GM_addValueChangeListener('test-key', (key, oldValue, newValue, remote) => { console.log(key, oldValue, newValue, remote); });
removeValueChangeListener(key)
Remove listener for key
GM_removeValueChangeListener('test-key');
download(url [, filename])
Simple file download from the Internet. url
must be valid but filename
is optional.
url
: http, https, ftp, ftps β, (file not allowed), can be relative to the web page
If the specified url
uses the HTTP or HTTPS protocol, then the request will include all cookies currently set for its hostname.
GM_download('https;//www.example.com/icon.jpg'); GM_download('https;//www.example.com/icon.jpg', 'new-name.jpg');
addStyle(css)
Utility function to inject style element e.g.
const script = document.createElement('style'); script.textContent = `... css ...`; document.head.appendChild(script);
const css = `body { border-top: 2px solid grey; }`; GM_addStyle(css); GM.addStyle(css);
addScript(code)
FireMonkey only utility function to inject script element (code runs in page context) e.g.
const script = document.createElement('script'); script.textContent = `... code ...`; document.body.appendChild(script); script.remove();
// Example 1: string const js = `function sum(x, y) { return x + y; }`; GM_addScript(js); GM.addScript(js); // Example 2: function function someFunc() { // some code } GM_addScript('(' + someFunc + ')();'); GM.addScript('(' + someFunc + ')();');
popup(options)
FireMonkey only utility function to create a shadow DOM popup blank element with animation that can be customized.
Popup Methods
Utility functions to interact with the created popup e.g.
GM_popup()
, NAME.addStyle()
, NAME.append()
, NAME.show()
, NAME.hide()
, and NAME.remove()
// create a new popup (multiple different popups can be created) // the name used (e.g. popup here) can be anything const popup = GM_popup(); // or GM.popup() // add overall style if needed const css = `p { border: 2px solid #000; }`; popup.addStyle(css); // add content as string const str = '<p>Good <span>Morning</span></p>'; popup.append(str); // add content as single node const div = document.createElement('div'); const p = document.createElement('p'); p.setAttribute('style','color: #fff; text-align: center; font-weight: bold;'); // add inline style if needed div.appendChild(p); popup.append(div); // add content as several nodes (a, b, c, ...) const input = document.createElement('input'); popup.append(div, p, input); // add JavaScript if needed p.addEventListener('click', someFunc); input.addEventListener('change', otherFunc); // show & hide popup.show(); popup.hide(); // remove (from document) popup.remove(); // example with registerMenuCommand GM_registerMenuCommand('Configuration', function() { popup.show(); });
Popup Options (Optional)
Ready-made styles can be set when creating a popup e.g. {type: 'panel-top', modal: false}
- {type: 'center'}
- Basic Center type: center (default, same as omitted)
- Slide Center types: slide-left | slide-right | slide-top | slide-bottom
- Panel types: panel-left | panel-right | panel-top | panel-bottom
- {modal: true} true (default)/false
- By default, popup is modal. In modal mode, clicking the background closes the popup.
- All Center Types are also Modal but the Panel Types can be non-Modal.
- If modal is set to false, use CSS to control the width/height of both
:host
&.content
const popup = GM_popup({type: 'panel-top'});
Popup Elements
Initial DOM elements of the popup can be accessed directly e.g. NAME.host
, NAME.style
, NAME.content
& NAME.close
.
popup.content.querySelector('button').addEventListener('click', someFunc); popup.content.setAttribute('style', `color: #fff; text-align: center; font-weight: bold; font-size: 14px; padding: 5px; background-color: #8b0000; position: fixed; left: 0px; top: -100px; width: 100%; z-index: 101; box-shadow: 0px 3px 10px rgba(0, 0, 0, 0.8); transition: all 1s ease-in-out 0s;`); popup.contennt.style.color = 'blue'; const p = document.createElement('p'); popup.content.appendChild(p); const button = document.createElement('button'); button.addEventListener('click', someFunc); popup.content.appendChild(button);
Popup Styling
CSS selectors :host
& .content
can be used to change the popup overall style. The content that you add can be styled normally.
The default 'full-page' popup background cover the full page.
CSS selector .close
can be used to style the close button β
.
// styling background with :host const css = `:host { background: transparent; }`; popup.addStyle(css); // styling content with .content const css = `.content { width: 20em; height: 15em; color: #00f; background: #f0f8ff; text-align: center; border: 2px solid #aaa; }`; popup.addStyle(css);
This is the background with CSS selector :host
.content
{type: 'panel-left'}
{type: 'panel-right'}
{type: 'panel-top'}
{type: 'panel-bottom'}
info
{ scriptHandler: 'FireMonkey', version: 'e.g. 2.14', scriptMetaStr: null, platform: { os: 'e.g. mac win android cros linux openbsd', arch 'e.g. arm x86-32 x86-64' }, browser: { name: 'Firefox', vendor: 'Mozilla', version: 'e.g. 75.0a1'. buildID: 'e.g. 20200220224950' }, script: { name: 'script name', version: 'script version', description: 'script description', matches: [array of match], excludeMatches: [array of exclude-match], includes: [array of regex include], excludes: [array of regex exclude], includeGlobs: [array of include/includeGlob], excludeGlobs: [array of exclude/excludeGlob], 'run-at': 'e.g. document-idle', namespace: null, resources: {object of name: url} } }
log(text [, text2, ..., textN])
The API is added for convenience and compatibility and is no longer
supported by Greasemonkey or Violentmonkey but Tampermonkey still has
it. Multiple parameters can be passed e.g. GM_log('one', 'two', 'three')
. You can also use console.log()
instead.
GM_log('one'); GM.log('one', 'two', 'three'); GM_log(GM_info);
unsafeWindow β οΈ
unsafeWindow
in FireMonkey is an alias for window.wrappedJSObject
. You can also use window.wrappedJSObject
or window.eval()
to access page JavaScript globals.
unsafeWindow
, window.wrappedJSObject
or window.eval()
can be used to create function & objects.
Please note that window.eval()
makes the object available to the page script as well while unsafeWindow
, window.wrappedJSObject
does not.
// page-script.js var foo = "I'm defined in a page script!"; function runTest() { console.log(foo); } // content-script.js console.log(window.foo); // undefined console.log(unsafeWindow.foo); // "I'm defined in a page script!" console.log(window.wrappedJSObject.foo); // "I'm defined in a page script!" unsafeWindow.runTest(); // "I'm defined in a page script!" window.wrappedJSObject.runTest(); // "I'm defined in a page script!" // overriding window functions let hasFocus = new window.Function('return true'); unsafeWindow.document.hasFocus = hasFocus; // another example Object.defineProperty(unsafeWindow.document, 'hidden', {value: false});
This API object allows a User script to access "custom" properties--variable and functions defined in the page--set by the web page. The
unsafeWindow
object is shorthand forwindow.wrappedJSObject
. It is the raw window object inside theXPCNativeWrapper
provided by the Greasemonkey Sandbox.USE OF UNSAFEWINDOW IS INSECURE, AND IT SHOULD BE AVOIDED WHENEVER POSSIBLE.
unsafeWindow
Safer alternatives to unsafeWindow
are also listed in above page.
This command can open certain security holes in your user script, and it is recommended to use this command sparingly.
Please be sure to read the entire article and understand it before using it in a script.
unsafeWindow
exportFunction() & cloneInto()
Support for exportFunction & cloneinto was added in v2.19.
unsafeWindow.setTimeout = exportFunction(setTimeout, unsafeWindow); exportFunction(notify, window, {defineAs: 'notify'}); // object without methods unsafeWindow.messenger = cloneInto(obj, unsafeWindow); // object with methods unsafeWindow.messenger = cloneInto(obj, unsafeWindow, {cloneFunctions: true});
Cookies Isolation
Browsing modes in Firefox can be divided into 3 distinct modes: normal, container, & private/incognito.
JavaScript xmlHttpRequest/fetch
send cookies and credentials with their HTTP requests. In web pages, xmlHttpRequest/fetch
send cookies according to the mode of the tab i.e. cookies belonging to
normal browsing mode are not sent when in container or private mode,
and vice versa. Cookies in each mode are isolated to preserve users'
security and privacy.
Private Browsing
Private Browsing does not save your browsing information, such as history and cookies, and leaves no trace after you end the session. Firefox also has Enhanced Tracking Protection, which prevents hidden trackers from collecting your data across multiple sites and slowing down your browsing.
Private Browsing - Use Firefox without saving history
Firefox Multi-Account Containers
The Firefox Multi-Account Containers add-on isnβt technically a form of private browsing or tracking protection, but it can help keep companies from knowing everything you do online. It lets you open fresh, cookie-free tabs that can be used for different accountsβpersonal, work, shopping, etc. That means you can use Multi-Account Containers to open several Google accounts at once without any overlap. Most trackers wonβt associate the different accounts, keeping your work life separate from your personal life online. Some more advanced trackers, however, can and will track you across different accounts, so beware.
Incognito browser: What it really means
Cookies
Cookies were first used to customize websites, keep track of shopping carts, and maintain online account security, but today most are used to help companies serve targeted ads.
Hereβs how it works: You visit a site, an advertiser leaves a cookie on your browser. The cookie is your unique ID. Your information is stored in the cloud along with that ID. That can include which sites you visited, how long you visited them, what you clicked on, your language preferences and more.
Cookies also help advertisers deliver ads in your social media feeds. Social sites have their own tracking schemes and theyβre far more robust. They can track every click, post, and comment. In addition, cookies can report what youβve been doing online to a social site, which is how some ads follow you into social media.
Incognito browser: What it really means
GM xmlHttpRequest/fetch
are sent from FireMonkey background script where modes do not apply. In order to honour users' browsing mode and privacy choice, FireMonkey (v2.35+) isolates cookies that are sent by the userscript according the mode of the tab userscript is running in.
- Normal Browsing Mode
- Cookies are handled by Firefox according to
withCredentials
orcredentials
- Cookies set via GM API headers will merge with Firefox cookies
- Cookies sent back in the response will be handled by Firefox
- Container or Private (Incognito) Browsing Mode
- FireMonkey gets and sends contextual cookies according to the mode
- Cookies set via GM API headers will merge with above cookies
- anonymous: true
- Tells browsers to exclude credentials from the request, and ignore any credentials sent back in the response (e.g., any
Set-Cookie
header) - Cookies set via GM API headers will be sent
fetch
: Same as userscript setting{credentials: 'omit'}
βxmlHttpRequest
:mozAnon
will be set β
FireMonkey | Greasemonkey | Tampermonkey | Violentmonkey | |
---|---|---|---|---|
Sending Cookies | β | β | ||
xmlHttpRequest Browser Cookie Isolation |
v4.12.6132 β | |||
xmlHttpRequest withCredentials (not effective) |
||||
fetch Browser Cookie Isolation |
β | β | β | |
fetch credentials (only 'omit' effective) |
β | β | β | |
anonymous flag | v2.10.1 β | |||
anonymous block Set-Cookie |
v2.12.5 β β | |||
Container/Incognito block Set-Cookie |
||||
download Browser Cookie Isolation |
β | |||
getResourceText Browser Cookie Isolation |
β |
Further information:
- Enable extensions to send network requests (fetch) with a specific cookieStoreId (container tab context)
- Support cookieStoreId option in userScripts.register
- Contextual Identities: Allow matching on cookieStoreId for browser.contentScripts.register
βΉοΈ Detecting JavaScript Navigation
When sites use JavaScript to navigate, Firefox API does not detect the navigation and does not re-inject the userScript/userCSS. In case of userCSS it does not matter since the rules will continue to apply nonetheless, but in case of userScript, it needs to re-run. One way to detect JavaScript navigation is to use MutationObserver with appropriate MutationObserverInit.
// select a simple node that changes in navigation to attach a MutationObserver e.g. <title> // For better performance avoid using a node with a lot of children like <body> when childList: true new MutationObserver((mutationsList) => { console.log(mutationsList[0].target.textContent); // re-run the necessary function }).observe( document.querySelector('title'), {subtree: true, childList: true} );
βΉοΈ Xray Vision & Sharing objects with page scripts
Extension JavaScript Context (Scope)
Contexts are sandboxed layers of JavaScript in an extension to ensure security.
Type | Browser API Access | Details |
---|---|---|
broswer | all | Trusted privileged code to interact with the browser |
content/contentScript | some | Trusted extension's own JavaScript injected into web page with some browser API privileges (there is only one content context per extension per frame) |
userScript | none (only GM API) | Untrusted unverified 3rd party JavaScript injected into web page without direct browser API privileges (there can be many isolated userScript contexts) |
page | none | JavaScript injected in a web page by the website (there is only one page context per frame) |
As an extension developer you should consider that scripts running in arbitrary web pages are hostile code whose aim is to steal the user's personal information, damage their computer, or attack them in some other way.
The isolation between content scripts and scripts loaded by web pages is intended to make it more difficult for hostile web pages to do this.
Since the techniques described in this section break down that isolation, they are inherently dangerous and should be used with great care.
Sharing objects with page scripts
...
Note that once you do this, you can no longer rely on any of this object's properties or functions being, or doing, what you expect. Any of them, even setters and getters, could have been redefined by untrusted code.
By default, content scripts don't get access to objects created by page scripts. However, they can communicate with page scripts using the DOM
Communicating with the web pagewindow.postMessage
andwindow.addEventListener
APIs.
In Chrome,
eval()
always runs code in the context of the content script, not in the context of the page.In Firefox:
Using eval() in content scripts
- If you call
eval()
, it runs code in the context of the content script.- If you call
window.eval()
, it runs code in the context of the page.
Injecting code into page context
Sometimes it is necessary to have a script that is available to page script and/or run in page context, as an extension to the page script functions.
const script = document.createElement('script'); script.textContent = `... code ...`; document.body.appendChild(script); script.remove();
Further information:
- Insert code into the page context using a content script by Rob W
- How can I prevent a webpage from detecting I am running a script? by Brock Adams
- Can a website know if I am running a userscript? by Brock Adams
- Can a webpage detect a tampermonkey userscript? by Brock Adams
Receiving data from page context
CustomEvent can be used send data from a page script.
// inject a function that generates & dispatches a CustomEvent const script = document.createElement('script'); script.textContent = `function sendMessage(message) => { window.dispatchEvent(new CustomEvent('sendMessage', {detail: message})); }; // run sendMessage when needed if(... condition ...) { sendMessage(data); } `; document.head.appendChild(script); script.remove(); // in user-script window.addEventListener('sendMessage', onMessage); function onMessage(e) { const message = e.detail; // run some code }
In Firefox, window.eval()
can also be used to inject code into a page context.
// inject a function that generates & dispatches a CustomEvent window.eval(`function sendMessage(message) => { window.dispatchEvent(new CustomEvent('sendMessage', {detail: message})); }; // run sendMessage when needed if(... condition ...) { sendMessage(data); } `); // in user-script window.addEventListener('sendMessage', onMessage); function onMessage(e) { const message = e.detail; // run some code }
UserCSS
CSS can be injected directly into a page. If the goal is to inject CSS, it is by far more efficient to inject CSS as UserCSS, instead of using UserScript to inject CSS. Here is an example of simple UserCSS that I use to mark watched/visited videos on YouTube.
Furthermore, CSS rules will apply to newly created elements in dynamically updated pages (e.g. on scroll) while JavaScript would need additional listeners to wait for new element to be created and then run then code again.
CSS mangers like Stylus tend to inject CSS at document-start
. The benefit of document-start
for CSS is that the changes will display earlier. The drawback is that
the page CSS may override these CSS and if so, it is better to inject
later at document-end
or document-idle
.
I have requested for an option to inject UserCSS as "user" which prevents websites from overriding the CSS (Add cssOrigin to contentScripts API) and will implement it once it is available.
/* ==UserCSS== @name YouTube @match *://*.youtube.com/* @author erosman @version 1.0 ==/UserCSS== */ a[href*="/watch?v="]:visited, a[href*="/watch?v="]:visited yt-formatted-string, a[href*="/watch?v="]:visited h3 { color: #f50 !important; }
Color Picker
Color indicator before CSS colors (named color, #rgb, #rrggbb, rgb(n,n,n) & rgba(n,n,n,a)) shows the colors. Clicking the indicator will open the HTML5 Color Picker.
New values from Color picker replace the old entries in the same format as the original e.g. named color to named color (if available), #rgb
to #rgb
etc.
Customise 3rd party userCSS
You can override a 3rd party userCSS with custom CSS.
- Disable the 3rd party script (it will auto-update in FM 2.19)
- Create a new userCSS
- Copy the relevant data from the target userCSS e.g.
@match
etc - Give it a new name
- Use
@require
to inject the 3rd party userCSS
/* ==UserCSS== @name ABC Style @match *://*.example.com/* @author erosman @version 1.0 ==/UserCSS== */ body { border-top: 2px solid grey; }
/* ==UserCSS== @name ABC Style Custom @match *://*.example.com/* @require ABC Style ==/UserCSS== */ body { border-top-color: blue; }
If the 3rd Party userCSS uses CSS custom properties (variables) e.g color: var(--main-color)
, you can also override them.
/*
==UserCSS==
@name ABC Style Custom
@match *://*.example.com/*
@require ABC Style
==/UserCSS==
*/
:root {
--main-color: #fff;
--border: #ddd;
--color: #000;
}
UserStyle
Partial compatibility with standard CSS syntax UserStyle ==UserStyle== ... ==/UserStyle==
is available. All url()
, url-prefix()
& domain()
are processed. Few very basic regexp()
are converted to match pattern but other sections with regexp()
are ignored since browser API does not support Regular Expression.
The default 'run-at'
is set to 'document-start'
for UserStyles.
Ref: Implement Stylus style Compatibility | Support FireMonkey's UserCSS spec
Installing Styles from userstyles.org (v2.0)
Right-click context-menu on the style page and FireMonkey will create a UserStyle based on the details. UserCSS/UserStyle are by far more efficient and use less resources than a UserScript only for injecting CSS/Style.
Please note that @-moz-document regexp()
in not supported.
Please note that userstyles.org is sometimes slow and may time out.
βΉοΈ Stylish/Stylus/xStyle Type UserStyle
In Firefox Quantum, extensions can not use @-moz-document and therefore extensions would have to:
- Break each UserStyle into sections for each
@-moz-document
- Add listeners to monitor changes to tab/iframe URLs
- Run a comparison loop against each tab and its iframes and each
@-moz-document
in every UserStyle - If there is a match, inject the style section into the tab/iframe
Above process is considerably more resource intensive than using the dedicated API to inject Style/CSS.
-x- Implemented with the vendor prefix: `-moz-`
* Notes Disabled by default in web pages, except for an empty
url-prefix()
value, which is supported due to its use in Firefox browser detection. Still supported in user stylesheets.π΄ Disabled From version 61: this feature is behind the
@documentlayout.css.moz-document.content.enabled
preference (needs to be set totrue
). To change preferences in Firefox, visitabout:config
.
Converting UserStyle @-moz-document to UserCSS @match
The first 3 are quite straight forward and easy to convert. The only difficulty is with the regexp()
. The more complex the regexp (Regular Expressions), the more @match
may be needed, but once done, it is easy to read and maintain.
From | To |
---|---|
@-moz-document domain('images.example.com') |
@match *://images.example.com/* |
@-moz-document url-prefix('http://www.example.com') |
@match http://www.example.com/* |
@-moz-document url('http://www.example.com/test.html') |
@match http://www.example.com/test.html |
@-moz-document regexp('http://www\\.example\\.(com|de)/images/.*') |
@match http://www.example.com/images/*
@match http://www.example.de/images/* |
@-moz-document regexp('https?:\/\/(www\.|old\.)?reddit.com.*') |
@match *://*.reddit.com/* |
Character Escaping in CSS
CSS Escaping in userScript requires double scaping.
// target element <div class="RichText RichText--sans lg:mb-32"> ... </div> // in userScript const css = ` .lg\\:mb-32 { border: 1px solid red; } `; GM_addStyle(css); // in userCSS .lg\:mb-32 { border: 1px solid blue; }
Debugging Script & CSS
- CodeMirror Lint
- Check lint messages and report for errors and warnings
- JavaScript Compile Errors (Syntax Errors)
- Usually appear in Browser Console (Ctrl + Shift + J) with a line number and a clickable link to the blob (with some string name) e.g.
SyntaxError: missing : after property id [Learn More] 1f0135df-f993-4f26-9127-bc8ffa21951e:18:11
- JavaScript Run Errors
- Some JavaScript run errors in a page can be checked in Developer Tool (F12) for the page that the JS is injected into e.g.
ReferenceError: assignment to undeclared variable abc [Learn More] 1f0135df-f993-4f26-9127-bc8ffa21951e:18:11 ReferenceError: abc is not defined[Learn More] 1f0135df-f993-4f26-9127-bc8ffa21951e:20:11
- View Inserted UserScript
- You can see the actual blob JavaScript in Developer Tools (F12) Debugger tab under user-script:// (you may have to refresh the page)
- CSS Errors
- The CSS error can be checked in Developer Tools (F12) for the page that the CSS is injected into.
- You can check Style Editor tab and the injected CSS shows as a blob with a string of characters e.g. da084755-6d22-45a5-870d-82c369299d55.
- Injection Errors
- Check the Log page for recent errors
Injection Comparison
Other userscript & userStyle Managers
- Add listeners to all HTTP requests
- Loop through all scripts on every request to check if the there are scripts that match
- If matches are found, process the scripts/CSS and inject the scripts/CSS
- All injections are handled manually by the extension
- Script injection is made into privileged & unsecured
content
context, orpage
context
FireMonkey
- All scripts are prepared and passed to Firefox native engine at start-up
- From then, FireMonkey does nothing and all script/CSS injections are handled by Firefox natively
- Script injection is made into secure
userScript
context
Performance Test
UserScript
- Load the same userscript/s in FM|GM|TM|VM
- Open
about:performance
- Go to a page that above runs on
- Compare
UserStyle/UserCSS
- Load the same userStyle/userCSS in FM|Stylish|Stylus|xStyle
- Open
about:performance
- Go to a page that above runs on
- Compare
Combined UserScript + UserStyle/UserCSS
- Load the same userscript/s in FM|GM|TM|VM plus the same userStyle/userCSS in FM|Stylish|Stylus|xStyle
- Open
about:performance
- Go to a page that above runs on
- Compare FM with combined userscript manger + userstyle manager
Support
Please use the GitHub Community Support.