Tampermonkey进阶指南

一. 前言

img

img

Tampermonkey is one of the most popular browser extension with over 10 million users. It's available for Chrome, Microsoft Edge, Safari, Opera Next, and Firefox.

It allows its users to customize and enhance the functionality of your favorite web pages. Userscripts are small JavaScript programs that can be used to add new features or modify existing ones on web pages. With Tampermonkey, you can easily create, manage, and run these userscripts on any website you visit.

本文以Documentation | Tampermonkey内容展开, 将对其中内容进行翻译和注释, 以及实际的使用例子.

1.1 操作页面

测试环境:

  • 浏览器: edge_115
  • TamperMonkey: 4.19.0

img

#, 表示脚本注入的顺序, 可以修改, 按照创建的先后来确定.

img

setting页面设置为高级, 一些高阶的菜单才会显示出来, 如编辑器设置, 安全设置等等.

例如下载文件会用到的:

img

假如文件格式不在这个列表中, 将无法下载.

由于设置页面内容极多, 这里不一一赘述.

1.2 基本原理

img

执行一个tampermonkey脚本, 自定义的脚本 => tampermonkey(管理) => (注入) => 干预网页.

img

这个过程涉及到一些列的安全(限制)问题, 例如同源安全策略, 内容安全策略, 沙盒机制等.

Tampermonkey可以突破其中的一些限制, 为用户高度自定义网页提供可能, 这种能力是浏览器赋予所有扩展的, 并不是Tampermonkey做了什么才实现的, 但是制作浏览器扩展却是颇为麻烦的事情, 假如需要发布给他人使用, 还得上传到chrome store, 这又是一堆繁琐的事情.Tampermonkey站出来了, 将其中的一些扩展的特性封装成为若干的api接口, 使用者只需要在脚本中简单调用即可实现扩展才能实现的功能, 这大大简化了为特定的站点开发脚本和发布成本.

Q200: Is it possible to install scripts as native Chrome extension even though Tampermonkey is installed?

A200 Yes it is. If you want to install a script just click at the install link. Tampermonkey now asks you whether to install the script in Tampermonkey or native in Chrome.

但chrome v3协议的发布(Chrome Manifest V3 - 披着羊皮的狼? - 知乎 (zhihu.com)), 相关的权限的收紧, 对于Tampermonkey影响颇大(甚至危及Tampermonkey), 该协议的争议颇多 以及其他的一些原因, Google目前暂缓该协议的执行.

1.3 沙箱

// ==UserScript==
// @name         New Userscript
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        https://www.qq.com/
// ==/UserScript==

console.log(this);

从安全角度来看, 理论上tampermonkey应当实现脚本相对独立运行的隔离机制(sandbox), 即简单理解就是从用户脚本可以访问到(甚至可以修改)页面的元素或者变量, 但是反过来不能( 或者是有限访问, 或者是只能间接 )让页面的脚本访问到用户脚本的资源, 避免页面脚本(经过特意构造相关用户脚本函数陷阱)来劫持用户脚本, 或者是页面脚本读取了用户脚本相关的信息.

tampermonkey中以下headerapi和以上机制相关:

img

但是关于上述内容的文档, 并没有很细致, 甚至没有具体提及其中的细节, 理解起来颇为不易, 几乎无法检索到相关内容.

1.4 访问本地脚本

Q204: How can I allow Tampermonkey access to local file URIs?

A204 File access for Tampermonkey is only available at Chrome and derivates. In order to enable it open Google Chrome and click on the "More" icon (the three dots) in the top-right corner of the window. From the menu, select "More tools" and then click on "Extensions".

This will open the Chrome extensions page (chrome://settings/), which shows a list of all the extensions that are installed in your browser. Find Tampermonkey and click on the "Details" link to the right of its name.

On the extension's details page, scroll down to the "Permissions" section. Here, you will see a list of all the permissions that the extension has been granted. To enable file access, you will need to check the box next to the "Allow access to file URLs" permission. [( video tutorial)](javascript:void(0))

扩展管理, 找到tampermonkey, 详情页面 => 允许访问文件urls.

img

// 在脚本中添加
@require //local/file.js

还有个DEV版本, 可以进一步和本地编辑器(vscode)整合在一起.

二. Userscript Header

header 作用 重要程度 备注
@name 脚本名称
@namespace 脚本命名空间
@copyright 版权声明
@version 版本号 影响脚本的更新发布
@description 脚本描述
@icon, @iconURL, @defaulticon 脚本ico
@icon64, @icon64URL 脚本ico, base64格式
@grant 批准api 1
@author 脚本作者
@homepage, @homepageURL, @website, @source 作者主页
@antifeature 商业行为
@require 外部脚本请求 1
@resource 外部资源请求 1
@include 脚本适配的url, 宽泛 1 未来受到v3协议影响最大的
@match 脚本适配url, 窄 1
@exclude 脚本排除url 1
@run-at 脚本注入的时间点 1
@sandbox 沙箱模式
@connect 跨域请求host 1 不要设置为全部站点
@noframes 脚本运行是否受到子框架的触发 1
@updateURL 脚本的更新地址url, 需要搭配版本号使用
@downloadURL 脚本的更新地址url 1
@supportURL 脚本bug反馈, 赞助支持
@webRequest 监听匹配规则的url web请求拦截 实验性api, 不稳定
@unwrap 注入的脚本是否进行包裹

这是一个调用了大部分TamperMonkeyheaderapi的脚本的头部示例.

// ==UserScript==
// @name         zhihu optimizer
// @namespace    https://github.com/Kyouichirou
// @version      3.5.4.5
// @description  now, I can say this is the best GM script for zhihu!
// @author       HLA
// @run-at       document-start
// @match        https://*.zhihu.com/*
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_listValues
// @grant        unsafeWindow
// @grant        GM_xmlhttpRequest
// @grant        GM_deleteValue
// @grant        GM_unregisterMenuCommand
// @grant        GM_addValueChangeListener
// @grant        GM_removeValueChangeListener
// @grant        GM_registerMenuCommand
// @grant        GM_notification
// @grant        GM_openInTab
// @grant        GM_getTab
// @grant        GM_getTabs
// @grant        GM_download
// @grant        GM_saveTab
// @grant        GM_info
// @grant        window.onurlchange
// @grant        window.close
// @connect      www.zhihu.com
// @connect      lens.zhihu.com
// @connect      api.zhihu.com
// @connect      cn.bing.com
// @connect      img.meituan.net
// @connect      www.cnblogs.com
// @require      https://cdn.staticfile.org/xlsx/0.10.0/xlsx.core.min.js
// @icon         https://static.zhihu.com/heifetz/favicon.ico
// @compatible   chrome 80+
// @license      MIT
// @noframes
// @note         cent browser(https://www.centbrowser.com/) is recommended, and edeg browser is not recommended, too bloated and jumbled
// @note         just test on chrome 80+ // 非标准
// ==/UserScript==

相关头部元数据在greasyfork读取情况, Script meta keys read by Greasy Fork.

2.1 基本信息

2.1.1 @name

脚本名称

The name of the script.

Internationalization is done by adding an appendix naming the locale.

// @name    A test
// @name:de Ein Test

2.1.2 @namespace

脚本命名空间

The namespace of the script.

@namespace@name 这两个属性被作为脚本的唯一标识符, 用户脚本管理器根据它们来判断一个脚本是否已安装. Sleazy Fork 也需要这些属性, 若用户在更新脚本时改变了两者中的任意一项, 将发出警告.

版权声明

A copyright statement shown at the header of the script's editor right below the script name.

2.1.4 @version

版本号(重要), 按照下面的规范来写, 脚本检查更新的关键

The script version. This is used for the update check and needs to be increased at every update.

In this list the next entry is considered to be a higher version number, eg: Alpha-v1 < Alpha-v10 and 16.4 == 16.04

  • Alpha-v1
  • Alpha-v10
  • Alpha-v2
  • Beta
  • 0.5pre3
  • 0.5prelimiary
  • 0.6pre4
  • 0.6pre5
  • 0.7pre4
  • 0.7pre10
  • 1.-1
  • 1 == 1. == 1.0 == 1.0.0
  • 1.1a
  • 1.1aa
  • 1.1ab
  • 1.1b
  • 1.1c
  • 1.1.-1
  • 1.1 == 1.1.0 == 1.1.00
  • 1.1.1.1.1
  • 1.1.1.1.2
  • 1.1.1.1
  • 1.10.0-alpha
  • 1.10 == 1.10.0
  • 1.11.0-alpha
  • 1.11.0-alpha.1
  • 1.11.0-alpha+1
  • 1.11.0-0.3.7
  • 1.12+1 == 1.12+1.0
  • 1.12+1.1 == 1.12+1.1.0
  • 1.12+2
  • 1.12+2.1
  • 1.12+3
  • 1.12+4
  • 1.12
  • 2.0
  • 16.4 == 16.04

2.1.5 @description

脚本描述

A short significant description.

Internationalization is done by adding an appendix naming the locale.

// @description    This userscript does wonderful things
// @description:de Dieses Userscript tut wundervolle Dinge

2.1.6 @icon, @iconURL, @defaulticon

脚本的图表

The script icon in low res.

2.1.7 @icon64, @icon64URL

脚本图表, base64

This scripts icon in 64x64 pixels. If this tag, but @icon is given the @icon image will be scaled at some places at the options page.

2.1.8 @author

脚本作者

The scripts author.

2.1.9 @homepage, @homepageURL, @website, @source

The authors homepage that is used at the options page to link from the scripts name to the given page. Please note that if the @namespace tag starts with http:// its content will be used for this too.

项目页面, 或者时作者的个人页面.

2.1.10 @antifeature

披露会为脚本作者而非用户带来额外收益的功能. 你可以通过在脚本的元数据中添加 @antifeature type description 值的方法披露. 其中type( 类型) 是必须填写的内容, 而 description( 描述) 则可以留空. 点击此处了解 Sleazy Fork 读取的 type( 类型) 的相关信息.

This tag allows script developers to disclose whether they monetize their scripts. It is for example required by GreasyFork.

Syntax:

can have the following values:

  • ads
  • tracking
  • miner
// @antifeature       ads         We show you ads
// @antifeature:fr    ads         Nous vous montrons des publicités
// @antifeature       tracking    We have some sort of analytics included
// @antifeature       miner       We use your computer's resources to mine a crypto currency

Internationalization is done by adding an appendix naming the locale.

2.1.11 @updateURL

脚本更新源: url, 需要设置版本号.

An update URL for the userscript. Note: a @version tag is required to make update checks work.

2.1.12 @downloadURL

检测到更新时将从其中下载脚本的URL. 如果使用值none, 则不会进行更新检查.

Defines the URL where the script will be downloaded from when an update was detected. If the value none is used, then no update check will be done.

2.1.13 @supportURL

支持页面, 例如bug上报, 个人赞助.

Defines the URL where the user can report issues and get personal support.

2.2 @grant

批准脚本的使用哪些api接口.

@grant is used to whitelist GM_* and GM.* functions, the unsafeWindow object and some powerful window functions.

// @grant GM_setValue
// @grant GM_getValue
// @grant GM.setValue
// @grant GM.getValue
// @grant GM_setClipboard
// @grant unsafeWindow
// @grant window.close
// @grant window.focus
// @grant window.onurlchange

Since closing and focusing tabs is a powerful feature this needs to be added to the @grant statements as well. In case @grant is followed by none the sandbox is disabled. In this mode no GM_* function but the GM_info property will be available.

默认状态下, @grant none, 意味着沙箱是被禁用的, 除了GM_info之外, 任意的GM_*()函数都将被禁用.

window.close(), window.focus()需要专门声明相关的权限.

// @grant none

If no @grant tag is given an empty list is assumed. However this different from using none.

// ==UserScript==
// @name         csdn_optimizer_x
// @namespace    https://github.com/Kyouichirou
// @version      1.4.2
// @description  make csdn better
// @author       HLA
// @include      /https:\/\/(\w+\.)?\w+\.csdn\.net.*/
// @license      MIT
// @icon        https://blog.csdn.net/favicon.ico
// ==/UserScript==

(function() {
    document.onclick = () => unsafeWindow.close(); // 这种情况下unsafewindow是可以访问到的, 只是无法关闭标签
})();

注意@grant none和没有@grant是不同的, 后者不存在的时候, 是可以访问到unsafeWindow.

// ==UserScript==
// @name         csdn_optimizer_x
// @namespace    https://github.com/Kyouichirou
// @version      1.4.2
// @description  make csdn better
// @author       HLA
// @include      /https:\/\/(\w+\.)?\w+\.csdn\.net.*/
// @grant        none
// @license      MIT
// @icon        https://blog.csdn.net/favicon.ico
// ==/UserScript==

(function() {
    document.onclick = () => unsafeWindow.close(); // 这种情况下unsafewindow是访问不到的
})();

将无法访问到对象
// ==UserScript==
// @name         csdn_optimizer_x
// @namespace    https://github.com/Kyouichirou
// @version      1.4.2
// @description  make csdn better
// @author       HLA
// @include      /https:\/\/(\w+\.)?\w+\.csdn\.net.*/
// @grant        window.close
// @license      MIT
// @icon        https://blog.csdn.net/favicon.ico
// ==/UserScript==

(function() {
    'use strict';
    document.onclick = () => window.close(); // 可以正常执行, 也是唯一可以关闭标签的方式
})();
// ==UserScript==
// @name         csdn_optimizer_x
// @namespace    https://github.com/Kyouichirou
// @version      1.4.2
// @description  make csdn better
// @author       HLA
// @include      /https:\/\/(\w+\.)?\w+\.csdn\.net.*/
// @grant        window.close
// @license      MIT
// @icon        https://blog.csdn.net/favicon.ico
// ==/UserScript==

(function() {
    'use strict';
    document.onclick = () => unsafeWindow.close(); // 可以访问unsafewindow, 但是无法关闭窗体
})();
// ==UserScript==
// @name         csdn_optimizer_x
// @namespace    https://github.com/Kyouichirou
// @version      1.4.2
// @description  make csdn better
// @author       HLA
// @include      /https:\/\/(\w+\.)?\w+\.csdn\.net.*/
// @grant        GM_setValue
// @license      MIT
// @icon        https://blog.csdn.net/favicon.ico
// ==/UserScript==

(function() {
    'use strict';
    document.onclick = () => unsafeWindow.close(); // 可以访问unsafewindow, 但是无法关闭窗体
})();
// ==UserScript==
// @name         csdn_optimizer_x
// @namespace    https://github.com/Kyouichirou
// @version      1.4.2
// @description  make csdn better
// @author       HLA
// @include      /https:\/\/(\w+\.)?\w+\.csdn\.net.*/
// @license      MIT
// @grant       GM_log
// @grant       unsafeWindow
// @icon        https://blog.csdn.net/favicon.ico
// ==/UserScript==

(function() {
    document.onclick = () => unsafeWindow.close(); // 无法关闭
})();

2.3 @require

引入外部的脚本, 如一些第三方库.

Points to a JavaScript file that is loaded and executed before the script itself starts running. Note: the scripts loaded via @require and their "use strict" statements might influence the userscript's strict mode!

// @require https://code.jquery.com/jquery-2.1.4.min.js
// @require https://code.jquery.com/jquery-2.1.3.min.js#sha256=23456...
// @require https://code.jquery.com/jquery-2.1.2.min.js#md5=34567...,sha256=6789...
// @require tampermonkey://vendor/jquery.js
// @require tampermonkey://vendor/jszip/jszip.js

Please check the sub-resource integrity section for more information how to ensure integrity.

Multiple tag instances are allowed.

2.4 @resource

重要性: 重要.

引入外部资源( 如 JavaScript, CSS, 图像等) 到脚本. 查看关于引入外部脚本到代码的规则. 如果你使用 SRI( Sub-Resource Integrity) 来记录外部脚本哈希( 更多信息请参阅 TamerMonkey 文档MDN 文档), 当 SRI 与文件的实际哈希不一致时将会在 Sleazy Fork 进行警告.

Preloads resources that can by accessed via GM_getResourceURL and GM_getResourceText by the script.

// @resource icon1       http://www.tampermonkey.net/favicon.ico
// @resource icon2       /images/icon.png
// @resource html        http://www.tampermonkey.net/index.html
// @resource xml         http://www.tampermonkey.net/crx/tampermonkey.xml
// @resource SRIsecured1 http://www.tampermonkey.net/favicon.ico#md5=123434...
// @resource SRIsecured2 http://www.tampermonkey.net/favicon.ico#md5=123434...;sha256=234234...

Please check the sub-resource integrity section for more information how to ensure integrity.

Multiple tag instances are allowed.

2.5 @include

指定在哪些站点上运行脚本, 脚本的运行适配url/

但是由于Google认为这种模式过于宽泛, 导致运行的效率底下, 在其V3扩展协议中这种方式将被禁用.

The pages on that a script should run. Multiple tag instances are allowed. @include doesn't support the URL hash parameter. You have to match the path without the hash parameter and make use of window.onurlchange

// @include http://www.tampermonkey.net/*
// @include http://*
// @include https://*
// @include /^https:\/\/www\.tampermonkey\.net\/.*$/
// @include *

Note: When writing something like *://tmnk.net/* many script developers expect the script to run at tmnk.net only, but this is not the case. It also runs at https://example.com/?http://tmnk.net/ as well.

Therefore Tampermonkey interprets @includes that contain a :// a little bit like @match. Every * before :// only matches everything except : characters to makes sure only the URL scheme is matched. Also, if such an @include contains a / after ://, then everything between those strings is treat as host, matching everything except / characters. The same applies to * directly following ://.

// ==UserScript==
// @name         test_include_match
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @include      /https:\/\/blog\.csdn\.net/\w+\/article\/details\/\d+/
// @icon         https://www.google.com/s2/favicons?sz=64&domain=csdn.net
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    console.log('ok');
})();

img

这是match做不到的

// @match     /https:\/\/blog\.csdn\.net/\w+\/article\/details\/\d+/

// 需要改成这种模式, 但是这种匹配模式相当僵化
// @match        https://blog.csdn.net/qq_*/article/details/*

2.6 @match

指定在哪些站点上运行脚本, 脚本的运行适配url, 相对固定的匹配.

In Tampermonkey, the @match directive is used to specify the web pages that your script should run on. The value of @match should be a URL pattern that matches the pages you want your script to run on. Here are the parts of the URL pattern that you'll need to set:

// @match <protocol>://<domain><path>
  • protocol - This is the first part of the URL, before the colon. It specifies the protocol that the page uses, such as http or https. * matches both.
  • domain - This is the second part of the URL, after the protocol and two slashes. It specifies the domain name of the website, such as tmnk.com. You can use the wildcard character this way *.tmnk.net to match tmnk.net and any sub-domain of it like www.tmnk.net.
  • path - This is the part of the URL that comes after the domain name, and may include additional subdirectories or filenames. You can use the wildcard character * to match any part of the path.

Please check this documentation to get more information about match pattern. Note: the <all_urls> statement is not yet supported and the scheme part also accepts http*://.

Multiple tag instances are allowed.

More examples:

// @match *://*/*
// @match https://*/*
// @match http://*/foo*
// @match https://*.tampermonkey.net/foo*bar

2.7 @exclude

排除掉哪些适配url, 优先级别最高, 相比于include match

Exclude URLs even it they are included by @include or @match.

Multiple tag instances are allowed.

2.8 @run-at

脚本注入的时间点.

Defines the moment the script is injected. In opposition to other script handlers, @run-at defines the first possible moment a script wants to run. This means it may happen, that a script that uses the @require tag may be executed after the document is already loaded, cause fetching the required script took that long. Anyhow, all DOMNodeInserted and DOMContentLoaded events that happended after the given injection moment are cached and delivered to the script when it is injected.

注意@require在请求外部资源时, 可能缓慢, 脚本的注入可能是在页面文档加载完成后执行.

// @run-at document-start
// 尽可能快注入脚本, 通常希望目标页面尚未加载渲染前, 例如需要尽快注入操作的CSS.

The script will be injected as fast as possible.

// @run-at document-body
// 等待页面主体加载完成

The script will be injected if the body element exists.

// @run-at document-end
// 等待DOMContentLoaded事件触发执行, 早一点或者晚一点?

The script will be injected when or after the DOMContentLoaded event was dispatched.

// @run-at document-idle
// 默认状态执行(缺省), DOMContentLoaded触发

The script will be injected after the DOMContentLoaded event was dispatched. This is the default value if no @run-at tag is given.

// @run-at context-menu
// 右键菜单触发.

需要注意假如使用该header, 将导致所有的include, exclude的规则将被忽视.

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.

2.9 @sandbox

沙箱机制允许tampermonkey决定注入脚本到哪个页面中.

@sandbox allows Tampermonkey to decide where the userscript is injected:

  • MAIN_WORLD - the page

    目标页面

  • ISOLATED_WORLD - the extension's content script/

    tampermonkey自身的content脚本

  • USERSCRIPT_WORLD - a special context created for userscripts.

    一个特殊的上下文, 由脚本所创建

But instead of specifying an environment, the userscript can express what exactly it needs access to. @sandbox supports three possible arguments:

  • raw "Raw" access means that a script for compatibility reasons always needs to run in page context, the MAIN_WORLD. At the moment this mode is the default if @sandbox is omitted.

    这是默认的模式, 假如缺失值. 出于兼容的原因, 脚本总是需要在页面上下文中运行.

  • JavaScript "JavaScript" access mode means that this script needs access to unsafeWindow. At Firefox a special context, the USERSCRIPT_WORLD, is created which should also bypass all remaining CSP issues. It however, might create new issues since now cloneInto and exportFunction are necessary to share objects with the page. raw mode is used as fallback at other browsers.

    这个模式是专门针对FireFox的?

    Firefox中创建了一个特殊的上下文USERSCRIPT_WORLD, 它也应该绕过所有剩余的CSP问题. 然后这又创造了另一问题 cloneInto and exportFunction, 这需要和页面分享对象. 在页面上下文中执行在其他浏览器中用作回退.

  • DOM Use this access mode if the script only needs DOM and no direct unsafeWindow access. If enabled these scripts are executed inside the extension context, the ISOLATED_WORLD, or at any other enabled context otherwise, because they all grant DOM access.

    这个模式下, 意味着脚本只需要DOM, 而不需要直接的unsafeWindow访问. 如果启用此设置, 脚本将可以在任意 已启用上下文的环境下执行(?), 因为都批准了DOM访问权限.

    There is also an option to configure the available sandbox modes which can be used by userscripts. Attention: any option that enables "DOM" mode is potentially unsecure. Userscripts that run in extension context have almost full extension permissions and can even modify and install new userscripts.

// @sandbox JavaScript

2.10 @connect

当批准@grant GM_xmlhttpRequest, 选择需要连接的urlhost

// @connect *
// *, 则意味着可以连接任意的host

这个header是为了解决普通的xhr, fetch受制于同源安全策略的限制, 无法发出跨域的请求.

例如脚本在baidu.com页面运行, 需要连接qq.com上的url.

This tag defines the domains (no top-level domains) including subdomains which are allowed to be retrieved by GM_xmlhttpRequest

// @connect <value>

<value> can be:

  • a domain name like example.com (this will also allow all subdomains).
  • a subdomain name like subdomain.example.com.
  • self to whitelist the domain the script is currently running at.
  • localhost to access the localhost.
  • an IP address like 1.2.3.4.
  • *.

If it's not possible to declare all domains a userscript might connect to then it's a good practice to do the following:

  1. Declare all known or at least all common domains that might be connected by the script to avoid the confirmation dialog for most users.
  2. Additionally add @connect * to the script to allow Tampermonkey to offer an "Always allow all domains" button.

Users can also whitelist all requests by adding * to the user domain whitelist at the script settings tab.

Notes:

  • Both, the initial and the final URL will be checked!
  • For backward compatibility to Scriptish @domain tags are interpreted as well.
  • Multiple tag instances are allowed.

More examples:

// @connect tmnk.net
// @connect www.tampermonkey.net
// @connect self
// @connect localhost
// @connect 8.8.8.8
// @connect *

支持多种类型的连接host.

2.11 @noframes

设置脚本只在主页面运行, 不会被iframe子框架触发.

This tag makes the script running on the main pages, but not at iframes.

2.12 @webRequest

// ==UserScript==
// @name         GM_webRequest testing
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        *://*/*
// @include      *gstatic.com/*
// @include      https://google.com/*
// @include      *//google.com/*
// @grant        GM_webRequest
// @webRequest   [{"selector":"*cancel.me/*","action":"cancel"},{"selector":{"include":"*","exclude":"http://exclude.me/*"},"action":{"redirect":"http://new_static.url"}},{"selector":{"match":"*://match.me/*"},"action":{"redirect":{"from":"([^:]+)://match.me/(.*)","to":"$1://redirected.to/$2"}}}]
// ==/UserScript==

console.log("GM_webRequest start");

var currently_active_webrequest_rule = JSON.stringify(GM_info.script.webRequest); // == @webRequst header from above

GM_webRequest([
    //{ selector: '*gstatic.com/*', action: 'cancel' },
    { selector: { include: '*gstatic.com/*', exclude: 'http://exclude.me/*' }, action: { redirect: 'https://google.com/' } },
    //{ selector: { match: '*://*.gstatic.com*' }, action: { redirect: { from: '([^:]+)://(.+)gstatic.com/(.*)',  to: '$1://google.com/$2' } } }
], function(info, message, details) {
    console.log(info, message, details); // This also does not work
});

@webRequest takes a JSON document that matches GM_webRequest's rule parameter. It allows the rules to apply even before the userscript is loaded.

2.13 @unwrap

Injects the userscript without any wrapper and sandbox into the page, which might be useful for Scriptlets.

将没有任何包装器和沙箱的用户脚本注入到页面中, 这对于scriptlet可能很有用.

scriptlet

A scriptlet is a piece of software code that is used by a native Web page scripting language to perform a specific function or process. Scriptlets are primarily implemented in JavaServer Pages (JSP) and include variables, expressions or statements that are used only when requested by a certain client or process.

window["__f__lkptg9op.3c8"] = function () {
    with (this) {
        (async (u, { p, r, s }) => { try { r(u, s, [undefined, undefined, undefined, p.GM_info, p.unsafeWindow, p.GM]); } catch (e) { if (e.message && e.stack) { console.error("ERROR: Execution of script 'csdn_optimizer_x' failed! " + e.message); console.log(e.stack); } else { console.error(e); } } })(async function (define, module, exports, GM_info, unsafeWindow, GM) {
            // ==UserScript==
            // @name         csdn_optimizer_x
            // @namespace    https://github.com/Kyouichirou
            // @version      1.4.2
            // @description  make csdn better
            // @author       HLA
            // @include      /https:\/\/(\w+\.)?\w+\.csdn\.net.*/
            // @license     MIT
            // @grant       GM_info
            // @icon        https://blog.csdn.net/favicon.ico
            // ==/UserScript==

            console.log(this.unsafeWindow === unsafeWindow);
            console.log(this);
            //console.log(globalThis.unsafeWindow === unsafeWindow);
            //console.log(this === globalThis);
        }, seed)
    }
    //# sourceURL=chrome-extension://iikmkjmpaadaobahmlepeloendndfphd/userscript.html?name=csdn_optimizer_x.user.js&id=7ee31434-0352-4ac0-82d3-d3bb842ffa70
}
// ==UserScript==
// @name         New Userscript
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        https://www.qq.com/
// @grant        none
// @unwrap
// ==/UserScript==

// unsafeWindow, 在这种环境下为undefined
console.log(this);
//# sourceURL=chrome-extension://iikmkjmpaadaobahmlepeloendndfphd/userscript.html?name=New-Userscript.user.js&id=cda33c15-55fb-4e4b-a485-1a44534ef1bd

三. Application Programming Interface

api 作用 重要性 备注
unsafeWindow 运行页面的window 1
Subresource Integrity 外部资源完整性检查(hash)
GM_addElement(tag_name, attributes), GM_addElement(parent_node, tag_name, attributes) 添加html元素 1
GM_addStyle(css) 添加css 1
GM_download(details), GM_download(url, name) 文件下载 1
GM_getResourceText(name) 获取由‎ @resource 预加载的(文本)资源 如脚本,css
GM_getResourceURL(name) 读取header @resource中特定资源的链接 例如图片的url
GM_info 打印脚本的详情
GM_log(message) 打印log
GM_notification(details, ondone), GM_notification(text, title, image, onclick) 右下角弹窗提示
GM_openInTab(url, options), GM_openInTab(url, loadInBackground) 打开url链接
GM_registerMenuCommand(name, callback, accessKey) 注册tampermonkey菜单功能 1
GM_unregisterMenuCommand(menuCmdId) 移除tampermonkey菜单功能 1
GM_setClipboard(data, info) 复制内容到剪切板 1
GM_getTab(callback) ‎获取选项卡对象, 生命周期为选项卡的打开->关闭
GM_saveTab(tab) 保存选项卡对象, 生命周期为选项卡的打开->关闭
GM_getTabs(callback) 获取所有选项卡对象, 生命周期为选项卡的打开->关闭
GM_setValue(key, value) 设置存储值 1
GM_getValue(key, defaultValue) 读取存储值 1
GM_deleteValue(key) 删除存储值 1
GM_listValues() 罗列出所有的脚本存储的数据
GM_addValueChangeListener(key, (key, old_value, new_value, remote) => void) 监听存储值的改变 1
GM_removeValueChangeListener(listenerId) 移除存储值监听 1
GM_xmlhttpRequest(details) (跨域)xhr请求 1
GM_webRequest(rules, listener) 监听匹配规则的url web请求拦截 实验性api
GM_cookie.list(details[, callback]) (全部)cookie读取 实验性api
GM_cookie.set(details[, callback]) 设置cookie
GM_cookie.delete(details, callback) 删除cookie
window.onurlchange 监听url的改变 1
window.close 关闭标签
window.focus 窗体激活

3.1 unsafeWindow

This object allows a User script to access "custom" properties set by the web page. The user script is otherwise isolated from these properties.

USE OF UNSAFEWINDOW IS INSECURE, AND IT SHOULD BE AVOIDED WHENEVER POSSIBLE.
unsafeWindow bypasses Greasemonkey's security model, which exists to make sure that malicious web pages cannot alter objects in such a way as to make user scripts (which execute with more privileges than ordinary JavaScript running in a web page) do things that their authors or users did not intend. User scripts should therefore avoid calling or in any other way depending on any properties on unsafeWindow - especially if they are executed for arbitrary web pages, such as those with @include *.

unsafeWindow - GreaseSpot Wiki

The unsafeWindow object provides access to the window object of the page that Tampermonkey is running on, rather than the window object of the Tampermonkey extension. This can be useful in some cases, such as when a userscript needs to access a JavaScript library or variable that is defined on the page.

unsafeWindow 对象提供对Tampermonkey正在运行的页面的window对象的访问, 而不是Tampermonkey扩展的window对象. 在某些情况下很有用, 例如当用户脚本需要访问页面上定义的JavaScript库或变量时.

这个对象unsafeWindow, 并不需要grant就可以在tampermonkey脚本中访问(在浏览器的console无法访问到), 只需要启用任意的@grant ...或者不设置@grant均可访问到这个对象.

// ==UserScript==
// @name         New Userscript
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        https://www.qq.com/
// @grant        unsafeWindow
// @unwrap
// ==/UserScript==

// @unwrap, 将脚本完全暴露了?

this.window.abc_test = 'test';

// this.unsafeWindow.abc_xxx = 'xxx'; // error, undefined

// unsafeWindow.abc_bbc = 'bbc'; // error, undefined

window.abc_cbs = 'cbs';

console.log(this === window); true

console.log(this.window === window); // true

console.log(this.unsafeWindow === this.window); //false

console.log(this);

console.log(window);

console.log(unsafeWindow);  // error, undefined

img

但是@unwrap这意味着, 用户脚本完全暴露在目标页面上(?).

// ==UserScript==
// @name         New Userscript
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        https://www.qq.com/
// ==/UserScript==

console.log(this);

console.log(unsafeWindow);

img

// tampermonkey的源码截取

{
    unsafeWindow: typeof globalThis === "undefined" ? window : globalThis,
    unsafeThis: window,
    vault: window["lkqd4h65.tdr"],
    contextId: "lkqd4h65.tdr",
    __proto__: null
}

可以看到, this指向一个Proxy对象.

new Proxy(Window, {});

img

(测试这个api时, 启用和不启用, 要**重启(务必)**一下浏览器)

由于相关的资料很少(包括Google也没有相对清晰的解释该问题), 此文的作者这种疑惑是最为常见的, 貌似不需要unsafeWindow也可以操作window对象的内容. 在前面已经提过了, 脚本操作的逻辑是用户脚本可以访问(操作)目标页面脚本的内容, 但是页面脚本无法反向访问(操作)用户脚本.

(也许该文的作者也一定程度受到没用重启浏览器的影响.)

img

// ==UserScript==
// @name         New Userscript
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        https://www.qq.com/
// @grant        unsafeWindow
// ==/UserScript==

// 加上 // @sandbox      DOM, 以下结果还是一样?

this.window.abc_test = 'test';

this.unsafeWindow.abc_xxx = 'xxx'; // 还是只能访问到这个

unsafeWindow.abc_bbc = 'bbc'; // 还是只能访问到这个

window.abc_cbs = 'cbs';

console.log(this === window); // true

console.log(this.window === window); // true
console.log(this.unsafeWindow === this.window);

console.log(this); // Proxy

console.log(window); // Proxy

console.log(unsafeWindow); //window

img

综上, 可以看到在不使用// @unwrap的情况下, 脚本this指向是一个Window Proxy, window实际上是tampermonkeycontent.js运行环境的window. 对这个window上的修改, 是无法直接作用于目标页面的window上.

3.2 外部资源引入

3.2.1 Subresource Integrity

子资源完整性( Subresource Integrity, SRI) 是允许浏览器检查其获得的资源( 例如从 CDN 获得的) 是否被篡改的一项安全特性. 它通过验证获取文件的哈希值是否和你提供的哈希值一样来判断资源是否被篡改

Subresource Integrity (SRI) is a security feature that allows userscript developers to ensure that the external resources (such as JavaScript libraries and CSS files) that they include in their userscript have not been tampered with or modified. This is accomplished by generating a cryptographic hash of the resource and including it in @require and @resource tags. When the userscript is installed, Tampermonkey will calculate the hash of the resource and compare it to the included hash. If the two hashes do not match, Tampermonkey will refuse to load the resource, preventing attackers from injecting malicious code into your userscript.

The hash component of the URL of @resource and @require tags is used for this purpose.

// @resource SRIsecured1 http://example.com/favicon1.ico#md5=ad34bb...
// @resource SRIsecured2 http://example.com/favicon2.ico#md5=ac3434...,sha256=23fd34...
// @require              https://code.jquery.com/jquery-2.1.1.min.js#md5=45eef...
// @require              https://code.jquery.com/jquery-2.1.2.min.js#md5-ac56d...,sha256-6e789...
// @require              https://code.jquery.com/jquery-3.6.0.min.js#sha256-/xUj+3OJU...ogEvDej/m4=

Tampermonkey supports SHA-256 and MD5 hashes natively, all other (SHA-1, SHA-384 and SHA-512) depend on window.crypto.

In case multiple hashes (separated by comma or semicolon) are given the last currently supported one is used by Tampermonkey. All hashes need to be encoded in either hex or Base64 format.

3.2.2 GM_getResourceText(name)

读取 @resource预加载资源

Allows userscripts to access the text of a resource (such as a JavaScript or CSS file) that has been included in a userscript via @resource.

The function takes a single parameter, which is the "name" of the resource to retrieve. It returns the text of the resource as a string.

Here is an example of how the function might be used:

const scriptText = GM_getResourceText("myscript.js");
const script = document.createElement("script");
script.textContent = scriptText;
document.body.appendChild(script);

3.2.3 GM_getResourceURL(name)

读取@resource预加载资源特定资源的url

GM_getResourceURL allows userscripts to access the URL of a resource (such as a CSS or image file) that has been included in the userscript via a @resource tag at the script header.

The function takes a single parameter, which is the "name" of the resource to retrieve. It returns the URL of the resource as a string.

const imageUrl = GM_getResourceURL("myimage.png");
const image = document.createElement("img");
image.src = imageUrl;
document.body.appendChild(image);

3.3 元素添加

3.3.1 GM_addElement(tag_name, attributes), GM_addElement(parent_node, tag_name, attributes)

GM_addElement allows Tampermonkey scripts to add new elements to the page that Tampermonkey is running on. This can be useful for a variety of purposes, such as adding script and img tags if the page limits these elements with a content security policy (CSP).

It creates an HTML element specified by "tag_name" and applies all given "attributes" and returns the injected HTML element. If a "parent_node" is given, then it is attached to it or to document head or body otherwise.

'GM_addElement允许Tampermonkey向正在运行的页面添加新元素. 这对于特定目的都很有用, 例如, 如果页面使用内容安全策略(CSP)限制这些元素.

它创建一个由*" tag_name" 指定的HTML元素, 并应用所有给定的" 属性" , 并返回注入的HTML元素. 如果给出了"parent_node"*, 则将其附加到它或附加到文档头部或正文.

For suitable "attributes", please consult the appropriate documentation. For example:

GM_addElement('script', {
  textContent: 'window.foo = "bar";'
});

GM_addElement('script', {
  src: 'https://example.com/script.js',
  type: 'text/javascript'
});

GM_addElement(document.getElementsByTagName('div')[0], 'img', {
  src: 'https://example.com/image.png'
});

GM_addElement(shadowDOM, 'style', {
  textContent: 'div { color: black; };'
});

Note: this feature is experimental and the API may change.

3.3.2 GM_addStyle(css)

向目标html注入css元素.

Adds the given style to the document and returns the injected style element.

返回创建的css节点.

const css = GM_addStyle(css);

3.4 菜单命令

// ==UserScript==
// @name         bili_speed_control
// @namespace    https://github.com/Kyouichirou
// @version      0.1
// @description  control bilibili video play speed!
// @author       You
// @include      https://www.bilibili.com/video/BV*
// @grant        GM_registerMenuCommand
// @noframes
// ==/UserScript==

// 一个简单的bilibili播放速度控制

(() => {
    'use strict';
    let is_first = true;
    let video_speed = 2;
    let video = document.getElementsByTagName('video');
    if (video.length === 0) {
        video = document.getElementsByClassName('bwp-video');
        if (video.length > 0) video = video[0];
        else {
            console.warn('no video element of bwp-video');
            video = null;
        }
    } else video = video[0];
    if (video) {
        video.oncanplay = (e) => {
            if (is_first) return;
            const target = e.target;
            if (target.playbackRate !== video_speed) setTimeout(() => { target.playbackRate = video_speed }, 1000);
        }
        const speed_control = (mode) => {
            is_first = false;
            video_speed += (mode ? 0.5 : -0.5);
            if (video_speed < 5) {
                video.playbackRate = video_speed;
                is_first = video_speed < 2;
            }
        }
        GM_registerMenuCommand(
            'speedup',
            speed_control.bind(null, true),
        );
        GM_registerMenuCommand(
            'slow',
            speed_control.bind(null, false),
        );
        console.log('speed control ready');
    }
})();

3.4.1 GM_registerMenuCommand(name, callback, accessKey)

注册菜单命令

GM_registerMenuCommand allows userscripts to add a new entry to the userscript's menu in the browser, and specify a function to be called when the menu item is selected.

The function takes three parameters:

  • name: A string containing the text to display for the menu item.

    函数名称.

  • callback: A function to be called when the menu item is selected. The function will be passed a single parameter, which is the currently active tab. As of Tampermonkey 4.14 a MouseEvent or KeyboardEvent is passed as function argument.

    调用的函数

  • accessKey: An optional access key for the menu item. This can be used to create a shortcut for the menu item. For example, if the access key is "s", the user can select the menu item by pressing "s" when Tampermonkey's popup-menu is open.

    快捷键, 默认s

The function return a menu entry ID that can be used to unregister the command.

函数返回一个id, 用于解除注册的函数.

Here is an example of how the function might be used:

const menu_command_id = GM_registerMenuCommand("Show Alert", function(event: MouseEvent | KeyboardEvent) {
  alert("Menu item selected");
}, "a");

3.4.2 GM_unregisterMenuCommand(menuCmdId)

解除菜单的注册命令.

GM_unregisterMenuCommand removes an existing entry from the userscript's menu in the browser.

The function takes a single parameter, which is the ID of the menu item to remove. It does not return a value.

Here is an example of how the function might be used:

const menu_command_id = GM_registerMenuCommand(...);
GM_unregisterMenuCommand(menu_command_id);

3.5 标签信息

3.5.1 GM_getTab(callback)

The GM_getTab function takes a single parameter, a callback function that will be called with an object that is persistent as long as this tab is open.

GM_getTab((tab) => console.log(tab));

3.5.2 GM_saveTab(tab)

这个api主要用于同一脚本, 不同标签之间的通信.

例如A标签设置了tab标签信息, B标签打开, 读取标签信息, 根据tab信息操作.

同时A标签关闭之后, B在执行某个动作可以先判断是否tab信息还存在, 然后再继续执行.

这个api相当于GM_addValueChangeListener()的弱化版本, 以及自动化版本(关闭标签则自动销毁对象),

The GM_saveTab function allows a userscript to save information about a tab for later use.

The function takes a "tab" parameter, which is an object containing the information to be saved about the tab.

The GM_saveTab function saves the provided tab information, so that it can be retrieved later using the GM_getValue function.

Here is an example of how the GM_saveTab function might be used in a userscript:

GM_getTab(function(tab) {
    tab.newInfo = "new!";
    GM_saveTab(tab); // 两个api一起搭配使用
});
// 当tab关闭之后, tab对象将自动销毁
// ==UserScript==
// @name         New Userscript
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        https://www.qq.com/
// @grant        unsafeWindow
// @grant        GM_getTab
// @grant        GM_getTabs
// @grant        GM_saveTab
// ==/UserScript==

(() => {
    GM_getTabs((tabs) => {
        if (Object.keys(tabs).length > 0) {
            console.log(tabs);
        } else {
            GM_getTab((tab) => {
                console.log(tab);
                tab.id = 'test';
                GM_saveTab(tab);
            });
        }
    });
})();

In this example, the GM_saveTab function is called with the tab object returned by the GM_getTab function, and a new key called "newInfo".

3.5.3 GM_getTabs(callback)

The GM_getTabs function takes a single parameter: a callback function that will be called with the information about the tabs.

The "tabs" object that is passed to the callback function contains objects, with each object representing the saved tab information stored by GM_saveTab.

GM_getTabs((tabs) => {
    for (const [tabId, tab] of Object.entries(tabs)) {
        console.log(`tab ${tabId}`, tab);
    }
});

3.6 脚本存储

3.6.1 GM_setValue(key, value)

设置存储的值.

The GM_setValue allows a userscript to set the value of a specific key in the userscript's storage.

The GM_setValue function takes two parameters:

  • A string specifying the key for which the value should be set.
  • The value to be set for the key. This value can be of any type (string, number, object, etc.).

The GM_setValue function does not return any value. Instead, it sets the provided value for the specified key in the userscript's storage.

Here is an example of how GM_setValue and its async pendant GM.setValue might be used in a userscript:

GM_setValue("someKey", "someData");
await GM.setValue("otherKey", "otherData");

3.6.2 GM_getValue(key, defaultValue)

读取存储值

The GM_getValue function allows a userscript to retrieve the value of a specific key in the extension's storage. It takes two parameters:

  • A string specifying the key for which the value should be retrieved.
  • A default value to be returned if the key does not exist in the extension's storage. This default value can be of any type (string, number, object, etc.).

The GM_getValue function returns the value of the specified key from the extension's storage, or the default value if the key does not exist.

Here is an example of how the GM_getValue function might be used in a userscript:

const someKey = GM_getValue("someKey", null);

// 这里需要注意, 这个api是为了兼容Greasemonkey而推出的, 优先级GM_getValue > GM.getValue
// Greasemonkey的这个api是异步执行的.
const otherKey = await GM.getValue("otherKey", null);

In this example, the GM_getValue function is called with the key "someKey" and a default value of null. If the "someKey" key exists in the extension's storage, its value will be returned and stored in the someKey variable. If the key does not exist, the default value of null will be returned and stored in the savedTab variable.

3.6.3 GM_deleteValue(key)

删除存储值.

Deletes "key" from the userscript's storage.

GM_deleteValue("someKey");
await GM.deleteValue("otherKey");

3.6.4 GM_listValues()

列出所有的存储值.

The GM_listValues function returns a list of keys of all stored data.

const keys = GM_listValues();
const asyncKeys = await GM.listValues();

3.6.5 GM_addValueChangeListener(key, (key, old_value, new_value, remote) => void)

监听存储值的变化, 可以用于多标签之间的脚本通信.

The GM_addValueChangeListener function allows a userscript to add a listener for changes to the value of a specific key in the userscript's storage.

The function takes two parameters:

  • A string specifying the key for which changes should be monitored.

  • A callback function that will be called when the value of the key changes. The callback function should have the following signature:

      function(key, oldValue, newValue, remote) {
          // key is the key whose value has changed, 键值
          // oldValue is the previous value of the key, 旧值
          // newValue is the new value of the key, 新值
          // remote is a boolean indicating whether the change originated from a different userscript instance
          // 改变的来源, 是否为同一脚本的实例
      }
    

The GM_addValueChangeListener function returns a "listenerId" value that can be used to remove the listener later using the GM_removeValueChangeListener function. The very same applies to GM.addValueChangeListener and GM.removeValueChangeListener with the only difference that both return a promise;

Here is an example of how the GM_addValueChangeListener function might be used in a userscript:

// Add a listener for changes to the "savedTab" key
var listenerId = GM_addValueChangeListener("savedTab", function(key, oldValue, newValue, remote) {
  // Print a message to the console when the value of the "savedTab" key changes
  console.log("The value of the '" + key + "' key has changed from '" + oldValue + "' to '" + newValue + "'");
});

GM_addValueChangeListener can be used by userscripts to communicate with other userscript instances at other tabs.

需要注意这个监听并不是写入值就会触发回调函数, 而是值的内容必须发生变化才会触发(不管是对象还是字符串, 数字), 首次无值就一定会触发.

// ==UserScript==
// @name         test
// @namespace    http://tampermonkey.net/
// @description  try to take over the world!
// @author       You
// @match        https://www.ithome.com/*
// @grant        GM_setValue
// @grant        GM_addValueChangeListener
// @noframes
// ==/UserScript==

(function() {
    'use strict';

    // Your code here...
    GM_addValueChangeListener('test', () => console.log('a')); // 只能触发一次
    document.onclick = () => GM_setValue('test', 1); 
})();

3.6.6 GM_removeValueChangeListener(listenerId)

移除脚本监听

GM_removeValueChangeListener and GM.removeValueChangeListener both get one argument called "listenerId" and remove the change listener with this ID.

3.7 网络请求

3.7.1 GM_xmlhttpRequest(details)

The GM_xmlhttpRequest allows a userscripts to send an HTTP request and handle the response. The function takes a single parameter: an object containing the details of the request to be sent and the callback functions to be called when the response is received.

The object can have the following properties:

  • method one of GET, HEAD, POST

    支持三种模式

  • url the destination URL

    访问的url

  • headers e.g. user-agent, referer, ... (some special headers are not supported by Safari and Android browsers)

    http表头

  • data some string to send via a POST request

    使用post时, 需要提交的数据

  • redirect one of follow, error or manual; controls what to happen when a redirect is detected (build 6180+, enforces fetch mode)

  • cookie a cookie to be patched into the sent cookie set

    需要发送的cookie

  • binary send the data string in binary mode

    以二进制模式发送数据

  • nocache don't cache the resource

    不要缓存内容

  • revalidate revalidate maybe cached content

    重新验证可能缓存的内容

  • timeout a timeout in ms

    超时的事件

  • context a property which will be added to the response object

    向响应内容添加一个属性

  • responseType one of arraybuffer, blob, json or stream

    返回的响应内容的格式

  • overrideMimeType a MIME type for the request

    请求的MIME类型

  • anonymous don't send cookies with the request (enforces fetch mode)

    匿名模式, 不发生cookie

  • fetch use a fetch instead of a XMLHttpRequest request (at Chrome this causes details.timeout and xhr.onprogress to not work and makes xhr.onreadystatechange receive only readyState DONE (==4) events)

    使用fetch替代XMLHttpRequest, 注意差异

  • user a user name for authentication

    授权用户

  • password a password

    授权密码

  • onabort callback to be executed if the request was aborted

    放弃请求回调

  • onerror callback to be executed if the request ended up with an error

    错误回调

  • onloadstart callback to be executed on load start, provides access to the stream object if responseType is set to stream

    请求开始后回调

  • onprogress callback to be executed if the request made some progress

    在请求中回调

  • onreadystatechange callback to be executed if the request's readyState changed

    请求状态发生改变回调

  • ontimeout callback to be executed if the request failed due to a timeout

    超时回调

  • onload

    请求完成回调

    callback to be executed if the request was loaded.

      function(response) {
        // response is an object containing the details of the response
      }
    

    response

    has the following attributes:

    • finalUrl - the final URL after all redirects from where the data was loaded
    • readyState - the request's readyState
    • status - the request's status
    • statusText - the request's status text
    • responseHeaders - the request's response headers
    • response - the response data as object if details.responseType was set
    • responseXML - the response data as XML document
    • responseText - the response data as plain string

GM_xmlhttpRequest returns an object with the following property:

该函数返回一个对象, 具有放弃方法.

  • abort - function to be called to cancel this request

Here is an example of how the GM_xmlhttpRequest function might be used in a userscript:

GM_xmlhttpRequest({
  method: "GET",
  url: "https://example.com/",
  headers: {
    "Content-Type": "application/json"
  },
  onload: function(response) {
    console.log(response.responseText);
  }
});

Note: the synchronous flag at details is not supported

注意: 同步模式在详情中不被支持

Important: if you want to use this method then please also check the documentation about @connect.

需要设置 @connect example.com.

3.7.2 GM_download(details), GM_download(url, name)

下载文件.

GM_download allows userscripts to download a file from a specified URL and save it to the user's local machine.

The GM_download function takes the following parameters:

details can have the following attributes:

  • url: The URL of the file to download. This must be a valid URL and must point to a file that is accessible to the user.

    下载链接

  • name: The name to use for the downloaded file. This should include the file's extension, such as .txt or .pdf. For security reasons the file extension needs to be whitelisted at Tampermonkey's options page.

    文件名

  • headers: An object containing HTTP headers to include in the download request. See GM_xmlhttpRequest for more details.

    http请求头

  • saveAs: A boolean value indicating whether to use the user's default download location, or to prompt the user to choose a different location. This option works in browser API mode only.

    是否出现savaAs弹窗提示

  • conflictAction: A string that control what happens when a file with this name already exists. This option works in browser API mode only. Possible values are uniquify, overwrite and prompt. Please check this link for more details.

    冲突之后的处理

  • onload: A function to call when the download has completed successfully.

    下载完成回调函数

  • onerror: A function to call if the download fails or is cancelled.

    错误回调函数

  • onprogress A callback to be executed if this download made some progress.

    在下载中回调函数

  • ontimeout A callback to be executed if this download failed due to a timeout.

    超时回调函数

The download argument of the onerror callback can have the following attributes:

  • error

    错误对象属性

    : error reason

    • not_enabled - the download feature isn't enabled by the user
    • not_whitelisted - the requested file extension is not whitelisted
    • not_permitted - the user enabled the download feature, but did not give the downloads permission
    • not_supported - the download feature isn't supported by the browser/version
    • not_succeeded - the download wasn't started or failed, the details attribute may provide more information
  • details: detail about that error

Returns an object with the following property:

  • abort: A function which can be called to cancel this download.

    放弃下载

Depending on the download mode GM_info provides a property called downloadMode which is set to one of the following values: native, disabled or browser.

GM_download("http://example.com/file.txt", "file.txt");

const download = GM_download({
    url: "http://example.com/file.txt",
    name: "file.txt",
    saveAs: true
});

// cancel download after 5 seconds
window.setTimeout(() => download.abort(), 5000);

Note: The browser might modify the desired filename. Especially a file extension might be added if the browser finds this to be safe to download at the current OS.

注意下载的文件格式, 出于浏览器安全原因.

3.7.3 GM_webRequest(rules, listener)

Note: this API is experimental and might change at any time. It might also disappear or change during manifest v3 migration.

GM_webRequest (re-)registers rules for web request manipulations and the listener of triggered rules. If you need to just register rules it's better to use @webRequest header. Note, webRequest proceeds only requests with types sub_frame, script, xhr and websocket.

对于子框架, 脚本, xhr, websocket的网络请求进行拦截, 如重定向.

Parameters:

  • rules, object[], array of rules with following properties:
    • selector - string|object, for which URLs the rule should be triggered, string value is shortening for{ include: [selector] }, object properties:

    • include - string|string[], URLs, patterns, and regexpes for rule triggering;

      • match - string|string[], URLs and patterns for rule trigering;
    • exclude - string|string[], URLs, patterns, and regexpes for not triggering the rule;

    • action, string | object, what to do with the request, string value"cancel"is shortening for{ cancel: true }, object properties:

      • cancel - boolean, whether to cancel the request;
      • redirect - string|object, redirect to some URL which must be included in any @match or @include header. When a string, redirects to the static URL. If object:
        • from - string, a regexp to extract some parts of the URL, e.g. "([^:]+)://match.me/(.*)";
        • to - string, pattern for substitution, e.g. "$1://redirected.to/$2";
  • listener - function, is called when the rule is triggered, cannot impact on the rule action, arguments:
    • info - string, type of action: "cancel", "redirect";

    • message - string, "ok" or "error";

    • details - object, info about the request and rule:

      • rule - object, the triggered rule;
    • url - string, URL of the request;

      • redirect_url - string, where the request was redirected;
    • description - string, error description.

Example

GM_webRequest([
    { selector: '*cancel.me/*', action: 'cancel' },
    { selector: { include: '*', exclude: 'http://exclude.me/*' }, action: { redirect: 'http://new_static.url' } },
    { selector: { match: '*://match.me/*' }, action: { redirect: { from: '([^:]+)://match.me/(.*)',  to: '$1://redirected.to/$2' } } }
], function(info, message, details) {
    console.log(info, message, details);
});

img

cookie的构成:

  • name

  • value

  • domain

    DomainPath 标识定义了 Cookie 的作用域: 即允许 Cookie 应该发送给哪些 URL.

    Domain 指定了哪些主机可以接受 Cookie. 如果不指定, 该属性默认为同一 host 设置 cookie, 不包含子域名. 如果指定了 Domain, 则一般包含子域名. 因此, 指定 Domain 比省略它的限制要少. 但是, 当子域需要共享有关用户的信息时, 这可能会有所帮助.

    例如, 如果设置 Domain=mozilla.org, 则 Cookie 也包含在子域名中( 如 developer.mozilla.org) .

  • path

    Path 属性指定了一个 URL 路径, 该 URL 路径必须存在于请求的 URL中, 以便发送 Cookie 标头. 以字符 %x2F (" /" ) 作为路径分隔符, 并且子路径也会被匹配.

    例如, 设置 Path=/docs, 则以下地址都会匹配:

    • /docs
    • /docs/
    • /docs/Web/
    • /docs/Web/HTTP

    但是这些请求路径不会匹配以下地址:

    • /
    • /docsets
    • /fr/docs
  • expires, 和localstorage不同, cookie是有使用寿命的.

  • httponly

    有两种方法可以确保 Cookie 被安全发送, 并且不会被意外的参与者或脚本访问: Secure 属性和 HttpOnly 属性.

    标记为 Secure 的 Cookie 只应通过被 HTTPS 协议加密过的请求发送给服务端. 它永远不会使用不安全的 HTTP 发送( 本地主机除外) , 这意味着中间人攻击者无法轻松访问它. 不安全的站点( 在 URL 中带有 http:) 无法使用 Secure 属性设置 cookie. 但是, Secure 不会阻止对 cookie 中敏感信息的访问. 例如, 有权访问客户端硬盘( 或, 如果未设置 HttpOnly 属性, 则为 JavaScript) 的人可以读取和修改它.

    JavaScript Document.cookie API 无法访问带有 HttpOnly 属性的 cookie; 此类 Cookie 仅作用于服务器. 例如, 持久化服务器端会话的 Cookie 不需要对 JavaScript 可用, 而应具有 HttpOnly 属性. 此预防措施有助于缓解跨站点脚本( XSS) (en-US)攻击.

  • secure

  • samesite

    SameSite 属性允许服务器指定是否/何时通过跨站点请求发送( 其中站点由注册的域和方案定义: http 或 https) . 这提供了一些针对跨站点请求伪造攻击( CSRF) 的保护. 它采用三个可能的值: Strict, LaxNone.

    使用 Strict, cookie 仅发送到它来源的站点. Lax 与 Strict 相似, 只是在用户导航到 cookie 的源站点时发送 cookie. 例如, 通过跟踪来自外部站点的链接. None 指定浏览器会在同站请求和跨站请求下继续发送 cookie, 但仅在安全的上下文中( 即, 如果 SameSite=None, 且还必须设置 Secure 属性) . 如果没有设置 SameSite 属性, 则将 cookie 视为 Lax.

    Set-Cookie: mykey=myvalue; SameSite=Strict

3.8.1 GM_cookie.list(details[, callback])

Note: the GM_cookie API is experimental and might return a not supported error at some Tampermonkey versions.

Tampermonkey checks if the script has @include or @match access to given details.url arguments!

Parameters:

  • details

    object, containing properties of the cookies to retrieve

    • url string?, representing the URL to retrieve cookies from (defaults to current document URL)
    • domain string?, representing the domain of the cookies to retrieve
    • name string?, representing the name of the cookies to retrieve
    • path string?, representing the path of the cookies to retrieve
  • callback

    function, to be called when the cookies have been retrieved. The function will be passed two arguments:

    • cookies object[], representing the retrieved cookies
  • error string, representing an error message if an error occurred, null otherwise.

The cookie objects have the following properties:

  • domain string, representing the domain of the cookie
  • firstPartyDomain string?: the first party domain of the cookie.
  • hostOnly boolean, indicating whether the cookie is a host-only cookie
  • httpOnly boolean, indicating whether the cookie is an HTTP-only cookie
  • name string, representing the name of the cookie
  • path string, representing the path of the cookie
  • sameSite string, indicating the SameSite attribute of the cookie
  • secure boolean, indicating whether the cookie requires a secure connection
  • session boolean, indicating whether the cookie is a session cookie
  • value string, representing the value of the cookie

Example usage:

// Retrieve all cookies with name "mycookie"
GM_cookie.list({ name: "mycookie" }, function(cookies, error) {
  if (!error) {
    console.log(cookies);
  } else {
    console.error(error);
  }
});

// Retrieve all cookies for the current domain
const cookies = await GM.cookies.list()
console.log(cookies);

3.8.2 GM_cookie.set(details[, callback])

设置cookie.

Sets a cookie with the given details. Supported properties are defined here.

Parameters:

  • details

    : An object containing the details of the cookie to be set. The object can have the following properties:

    • url string?, the URL to associate the cookie with. If not specified, the cookie is associated with the current document's URL.
    • name string, the name of the cookie.
    • value string, the value of the cookie.
    • domain string?, the domain of the cookie.
    • firstPartyDomain string?: the first party domain of the cookie.
    • path string?, the path of the cookie.
    • secure boolean?, whether the cookie should only be sent over HTTPS.
    • httpOnly boolean?, whether the cookie should be marked as HttpOnly.
    • expirationDate number?, the expiration date of the cookie in seconds since the Unix epoch. If not specified, the cookie never expires.
  • callback

    function

    , a function to be called when the operation is complete. The function is passed one argument:

    • error string?, if there was an error setting the cookie, this contains an error message. Otherwise, it is undefined.

Example:

GM_cookie.set({
  url: 'https://example.com',
  name: 'name',
  value: 'value',
  domain: '.example.com',
  path: '/',
  secure: true,
  httpOnly: true,
  expirationDate: Math.floor(Date.now() / 1000) + (60 * 60 * 24 * 30) // Expires in 30 days
}, function(error) {
  if (error) {
    console.error(error);
  } else {
    console.log('Cookie set successfully.');
  }
});

GM.cookie.set({
  name: 'name',
  value: 'value'
})
.then(() => {
  console.log('Cookie set successfully.');
})
.catch((error) => {
  console.error(error);
});

3.8.3 GM_cookie.delete(details, callback)

删除一个cookie.

Deletes a cookie.

Parameters:

The details object must contain at least one of the following properties:

  • url string?, the URL associated with the cookie. If url is not specified, the current document's URL will be used.
  • name string?, the name of the cookie to delete.
  • firstPartyDomain string?: the first party domain of the cookie to delete.

The callback function is optional and will be called when the cookie has been deleted or an error has occurred. It takes one argument:

  • error string?, an error message, or undefined if the cookie was deleted successfully.

Example:

GM_cookie.delete({ name: 'cookie_name' }, function(error) {
    if (error) {
        console.error(error);
    } else {
        console.log('Cookie deleted successfully');
    }
});

3.9 其他

3.9.1 window.onurlchange

监听页面url的变化, 这对于越来越多采用异步框架单一页面的站点尤为重要.

If a script runs on a single-page application, then it can use window.onurlchange to listen for URL changes:

// ==UserScript==
...
// @grant window.onurlchange
// ==/UserScript==

if (window.onurlchange === null) {
    // feature is supported
    window.addEventListener('urlchange', (info) => ...);
}
// ==UserScript==
// @name         music163
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        https://music.163.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=163.com
// @grant        window.onurlchange
// ==/UserScript==

(function() {
    'use strict';

    // Your code here...
    if (window.onurlchange === null) {
        console.log('support');
         window.addEventListener('urlchange', (info) => console.log(info)); // return {url: '....'}
    }
})();

3.9.2 window.close

关闭标签页, 一般情况下, JavaScriptwindow.close()只能关闭由其自身打开的标签.

Usually JavaScript is not allowed to close tabs via window.close. Userscripts, however, can do this if the permission is requested via @grant.

Note: for security reasons it is not allowed to close the last tab of a window.

注意: 无法关闭浏览器剩下的最后一个标签, 出于安全原因.

// ==UserScript==
...
// @grant window.close
// ==/UserScript==

if (condition) {
    window.close();
}

3.9.3 window.focus

标签窗体激活.

window.focus brings the window to the front, while unsafeWindow.focus may fail due to user settings.

// ==UserScript==
...
// @grant window.focus
// ==/UserScript==

if (condition) {
    window.focus();
}

3.9.4 GM_setClipboard(data, info)

将内容复制到剪切板.

GM_setClipboard sets the text of the clipboard to a specified value.

The function takes a parameter "data", which is the string to set as the clipboard text and a parameter "info".

"info" can be just a string expressing the type text or html or an object like

info不限于text文本, object, html一样可以.

{
    type: 'text',
    mimetype: 'text/plain'
}
GM_setClipboard("This is the clipboard text.", "text");

3.9.5 GM_info

脚本详情.

Get some info about the script and TM. The object might look like this:

type ScriptGetInfo = {
    downloadMode: string,
    isFirstPartyIsolation?: boolean,
    isIncognito: boolean,
    sandboxMode: SandboxMode,
    scriptHandler: string,
    scriptMetaStr: string | null,
    scriptUpdateURL: string | null,
    scriptWillUpdate: boolean,
    version?: string,
    script: {
        antifeatures: { [antifeature: string]: { [locale: string]: string } },
        author: string | null,
        blockers: string[],
        connects: string[],
        copyright: string | null,
        deleted?: number | undefined,
        description_i18n: { [locale: string]: string } | null,
        description: string,
        downloadURL: string | null,
        excludes: string[],
        fileURL: string | null,
        grant: string[],
        header: string | null,
        homepage: string | null,
        icon: string | null,
        icon64: string | null,
        includes: string[],
        lastModified: number,
        matches: string[],
        name_i18n: { [locale: string]: string } | null,
        name: string,
        namespace: string | null,
        position: number,
        resources: Resource[],
        supportURL: string | null,
        system?: boolean | undefined,
        'run-at': string | null,
        unwrap: boolean | null,
        updateURL: string | null,
        version: string,
        webRequest: WebRequestRule[] | null,
        options: {
            check_for_updates: boolean,
            comment: string | null,
            compatopts_for_requires: boolean,
            compat_wrappedjsobject: boolean,
            compat_metadata: boolean,
            compat_foreach: boolean,
            compat_powerful_this: boolean | null,
            sandbox: string | null,
            noframes: boolean | null,
            unwrap: boolean | null,
            run_at: string | null,
            tab_types: string | null,
            override: {
                use_includes: string[],
                orig_includes: string[],
                merge_includes: boolean,
                use_matches: string[],
                orig_matches: string[],
                merge_matches: boolean,
                use_excludes: string[],
                orig_excludes: string[],
                merge_excludes: boolean,
                use_connects: string[],
                orig_connects: string[],
                merge_connects: boolean,
                use_blockers: string[],
                orig_run_at: string | null,
                orig_noframes: boolean | null
            }
        }
    }
};

type SandboxMode = 'js' | 'raw' | 'dom';

type Resource = {
    name: string,
    url: string,
    error?: string,
    content?: string,
    meta?: string
};

type WebRequestRule = {
    selector: { include?: string | string[], match?: string | string[], exclude?: string | string[] } | string,
    action: string | {
        cancel?: boolean,
        redirect?: {
            url: string,
            from?: string,
            to?: string
        } | string
    }
};

3.9.6 GM_log(message)

打印.

Log a message to the console.

3.9.7 GM_notification(details, ondone), GM_notification(text, title, image, onclick)

右下角的弹窗提示.

GM_notification allows users to display notifications on the screen, using a provided message and other optional parameters.

The function takes several parameters. Either a details object or multiple parameters.

The details object can have the following attributes, from which some can also be used as direct parameter.

The available options include:

  • text: A string containing the message to display in the notification.

    提示内容

  • title: The title of the notification.

    弹窗标题

  • image: The URL of an image to display in the notification.

    弹窗展示的图片

  • highlight: A boolean flag whether to highlight the tab that sends the notfication (required unless text is set)

    弹窗时是否高亮所在的tab.

  • silent: A boolean flag whether to not play a sound

    弹窗时是否发出声音

  • timeout: The time, in milliseconds, after which the notification should automatically close.

    自动关闭的事件间隔

  • onclick: A callback function that will be called when the user clicks on the notification.

    点击时执行的函数.

  • ondone A callback function that will be called when the notification is closed (no matter if this was triggered by a timeout or a click) or the tab was highlighted

    当执行完毕后的回调函数, 不管是timeout还是手动关闭弹窗均触发.

The function does not return a value.

函数没有任何的返回值.

Here is an example of how the function might be used:

GM_notification({
  text: "This is the notification message.",
  title: "Notification Title",
  onclick: () => alert('I was clicked!')
});

3.9.8 GM_openInTab(url, options), GM_openInTab(url, loadInBackground)

打开新的标签(tab)

GM_openInTab allows userscripts to open a new tab in the browser and navigate to a specified URL.

The function takes two parameters:

A string names "url" containing the URL of the page to open in the new tab.

An optional options object that can be used to customize the behavior of the new tab. The available options include:

  • active: A boolean value indicating whether the new tab should be active (selected) or not. The default is false.

    打开后是否激活新的tab

  • insert: An integer indicating the position at which the new tab should be inserted in the tab strip. The default is false, which means the new tab will be added to the end of the tab strip.

    打开标签的位置.

  • setParent: A boolean value indicating whether the new tab should be considered a child of the current tab. The default is false.

    是否将当前的窗口设置为新的tab的父窗体.

  • incognito A boolean value that makes the tab being opened inside a incognito mode/private mode window.

    在无痕模式下打开

  • loadInBackground A boolean value has the opposite meaning of active and was added to achieve Greasemonkey 3.x compatibility.

    是否后台加载

The function returns an object with the function close, the listener onclose and a flag called closed.

函数返回一个对象, 具有close方法, 方法有回调函数onclose.

Here is an example of how the function might be used:

// Open a new tab and navigate to the specified URL
GM_openInTab("https://www.example.com/");

3.9.9 <><![CDATA[...]]></>

CDATA-based way of storing meta data is supported via compatibility option. Tampermonkey tries to automatically detect whether a script needs this option to be enabled.

var inline_src = (<><![CDATA[
    console.log('Hello World!');
]]></>).toString();

eval(inline_src);

四. 小结

由于TamperMonkey并不是(纯)商业化或者大型开源产品, 在文档, 在产品稳定性上都有一定瑕疵, 例如window.onurlchange这个api在刚上线时, 在不同版本的浏览器上运行差异很大, 文档细节不够等. 瑕不掩瑜, Tampermoneky应当是最佳的脚本管理器, 但是 V3协议始终是朵乌云.

附录, chrome store用户量最大的部分Top扩展

Extension Category Installs
Google Translate Productivity 10M+
Netflix Party Fun 10M+
Tampermonkey Productivity 10M+
AdBlock — best ad blocker Productivity 10M+
Adblock Plus - free ad blocker Productivity 10M+
uBlock Origin Productivity 10M+
Adobe Acrobat Productivity 10M+
Chrome Remote Desktop Productivity 10M+
Pinterest Save button Productivity 10M+
LastPass: Free Password Manager Productivity 10M+
Avast SafePrice | Comparison, deals, coupons Shopping 10M+
Avast Online Security Social & Communication 10M+
Skype Social & Communication 10M+
Cisco Webex Extension Social & Communication 10M+
Grammarly for Chrome Productivity 10M+
Honey Shopping 10M+
AVG SafePrice | Comparison, deals, coupons Shopping 9M+
Screencastify - Screen Video Recorder Productivity 8M+
Hola Free VPN, unblock any site! Productivity 8M+