steven026 发表于 2023-2-23 16:36:28

[油猴脚本开发指南]在控制台中调试油猴API函数

本帖最后由 steven026 于 2023-2-23 16:37 编辑

## 前言

本文为新手向教程,只阐述如何在浏览器控制台(Devtools Console)中使用简便的方法调试油猴、脚本猫中以`// @grant`声明的API函数,不会深入探讨API函数如何使用,具体使用方法建议见各API函数指南(如果有😉)。
众所周知,油猴~~以及脚本猫~~的API函数文档写的非常简单,很多函数需要传入的参数写的都模棱两可让人摸不着头脑,而且油猴没有直接能够调试的方法,静态代码每次都要刷新页面重新加载脚本,这就导致了油猴API函数调试非常麻烦。因此我们希望寻找一个简便的方法进行调试,合理且连贯的调试方法能在学习脚本开发时节约大量试错时间。
另外本篇指南结论非常简单,但希望能阅读完全文理解其中原理以加深对油猴脚本的理解。

## 理论知识
推荐阅读以下文章/文档以更好理解本篇指南具体原理
1.[油猴脚本开发指南]油猴的本质
https://bbs.tampermonkey.net.cn/thread-2494-1-1.html
2.[油猴脚本开发指南]grant介绍,none与unsafeWindow
https://bbs.tampermonkey.net.cn/thread-160-1-1.html
3.Scope(作用域)
https://developer.mozilla.org/zh-CN/docs/Glossary/Scope

***
首先油猴是一个浏览器插件,依靠插件API先将油猴框架及用户脚本(userscript)代码插入到脚本匹配的网页中,然后再执行脚本代码。
从【油猴的本质】指南中可知,之所以油猴脚本能获取window全局上下文,是因为油猴脚本实际作用域是window全局作用域的子域。
而常用的调试工具浏览器控制台(Devtools Console)访问的是top作用域,即window全局作用域,全局作用域无法直接访问子域。因此即使我们在油猴脚本中声明了` @grant GM_xmlhttpRequest`后正常运行脚本,我们直接在控制台中输入`GM_xmlhttpRequest`也无法对其进行调试,会提示`GM_xmlhttpRequest is not defined`,是因为全局作用域无法访问子作用的局部函数。
虽然我们无法直接在全局作用域中访问子域,但是【JavaScript万物皆是对象】我们实际上需要访问的函数方法也是对象,只要知道其地址就可以访问到。因此我们可以将子域中的函数从子域中暴露到全局作用域中,使全局作用域也能直接访问到暴露的函数方法。
这样,我们就可以对其进行调试。

## 代码
## 初级代码
```js
// ==UserScript==
// @name         全局函数
// @match      *://*/*
// @grant      unsafeWindow
// @grant      GM_info
// @grant      GM_xmlhttpRequest
// @grant      GM_setValue
// @grant      GM_getValue
// @grant      GM_setClipboard
// @grant      GM_download
// @grant      GM_openInTab
// @grant      GM_listValues
// @grant      GM_cookie
// ==/UserScript==

unsafeWindow.GM_info=GM_info;
unsafeWindow.GM_xmlhttpRequest=GM_xmlhttpRequest;
unsafeWindow.GM_setValue=GM_setValue;
unsafeWindow.GM_getValue=GM_getValue;
unsafeWindow.GM_openInTab=GM_openInTab;
unsafeWindow.GM_download=GM_download;
unsafeWindow.GM_openInTab=GM_openInTab;
unsafeWindow.GM_listValues=GM_listValues;
unsafeWindow.GM_cookie_=GM_cookie;
```
我们先以这10个API函数为例
如果我们的油猴脚本需要用这10个API函数,就要现在脚本头部对其进行一一声明
可以声明后不使用,而不可以不声明但使用(暂时不清楚声明过多会不会降低油猴运行效率)
声明完函数后,我们才能够在脚本中正常使用(部分API函数不需要声明也可使用,这里不介绍)
然后通过将该函数方法挂载到window对象`unsafeWindow`上以实现暴露到全局的目的。
初级函数中重复`unsafeWindow.XX=XX;`即可达到目的

## 进阶代码
!(data/attachment/forum/202302/23/160659haxdex9nvra53tjf.png)
我们拿`GM_info`试验一下,可以看到原本无法访问的函数对象现在可以正常在控制台中访问并进行调试了。
观察`GM_info`返回的对象,可以看到我们之前声明的10个API函数也出现在了GM_info中,聪明的你看到这里是不是想到了什么?
### 进阶代码1 eval
```js
// ==UserScript==
// @name         全局函数
// @match      *://*/*
// @grant      unsafeWindow
// @grant      GM_info
// @grant      GM_xmlhttpRequest
// @grant      GM_setValue
// @grant      GM_getValue
// @grant      GM_setClipboard
// @grant      GM_download
// @grant      GM_openInTab
// @grant      GM_listValues
// @grant      GM_cookie
// ==/UserScript==
/* eslint no-eval:0 */

for(const grant of GM_info.script.grant){
    unsafeWindow=eval(grant)
}
```
我们可以直接利用for循环+eval对其批量赋值进行暴露
由于GM_info.script.grant中储存的只是API函数名称的字符串,并没有储存API函数本身,因此我们需要借助eval(或setTimeout)将字符串解析成函数,从而进行暴露。
这样的好处是我们不需要再以静态的方法每声明一个API函数就静态暴露一次,而是可以通过for循环这种动态的方法批量暴露,即以后我们删改`// @grant`声明后,不需要再额外修改代码主体,将其全部交给for循环,重复利用代码,提高代码利用率,做到`write less do more`的效果。
坏处也很明显,会触发eslint规则不要使用eval`eslint: no-eval - eval can be harmful`
摆烂的话可以直接无视,或者加一行注释`/* eslint no-eval:0 */`来禁用这个eslint规则。
如果不想摆烂又符合eslint规范,可以继续研究。

### 进阶代码2 argument
```js
// ==UserScript==
// @name         全局函数
// @match      *://*/*
// @grant      unsafeWindow
// @grant      GM_info
// @grant      GM_xmlhttpRequest
// @grant      GM_setValue
// @grant      GM_getValue
// @grant      GM_setClipboard
// @grant      GM_download
// @grant      GM_openInTab
// @grant      GM_listValues
// @grant      GM_cookie
// ==/UserScript==

console.log(arguments)
for(let i=0;i<GM_info.script.grant.length;i++){
    unsafeWindow]=arguments
}
```
!(data/attachment/forum/202302/23/162344e2jp5jjxj6p4j5pp.png)
先在脚本中加一行`console.log(arguments)`,然后点击`console`的源头,可以看到油猴脚本的本质,`argument`中也一起传递了API函数的参数,这部分并不像`GM_info`中是字符串,而是真实的函数对象,直接将这部分对象暴露给全局即可,由于有index偏移,所以需要使用索引for循环
注:油猴版本不同,传参的内容和顺序也可能不同,建议自己调试,顺便可以加深理解

至此我们实现了3种调试在控制台中调试油猴API函数方法

## 完结撒花
(课后作业:通过`//@require`产生的变量是否也能用这类方式在控制台中调试? )
##### 实际应用
(出处:https://bbs.tampermonkey.net.cn/thread-3917-1-1.html)
!(https://bbs.tampermonkey.net.cn/data/attachment/forum/202212/21/162930qrew0oxuu7zus0rk.png)

王一之 发表于 2023-2-23 17:26:02

es规则可以去这里调试:https://eslint.org/play/

另外脚本猫需要有 `namespace`之类的属性,否则保存会报错

```js
// ==UserScript==
// @name         New Userscript
// @namespace    https://bbs.tampermonkey.net.cn/
// @version      0.1.0
// @descriptiontry to take over the world!
// @author       You
// @match      *://*/*
// @grant      unsafeWindow
// @grant      GM_info
// @grant      GM_xmlhttpRequest
// @grant      GM_setValue
// @grant      GM_getValue
// @grant      GM_setClipboard
// @grant      GM_download
// @grant      GM_openInTab
// @grant      GM_listValues
// @grant      GM_cookie
// ==/UserScript==

for (const grant of GM_info.script.grant) {
    unsafeWindow = eval(grant)
}

const a = 1;

a = 2;
```

steven026 发表于 2023-2-24 08:51:45

王一之 发表于 2023-2-23 17:26
es规则可以去这里调试:https://eslint.org/play/

另外脚本猫需要有 `namespace`之类的属性,否则保存 ...

脚本猫可以做个兼容么?不然有些脚本还要写个脚本猫专用和非脚本猫专用=-=
还有比如脚本猫没有// @grant none可以视为非沙盒环境,而油猴没有的话则视为沙盒环境

王一之 发表于 2023-2-24 11:06:50

steven026 发表于 2023-2-24 08:51
脚本猫可以做个兼容么?不然有些脚本还要写个脚本猫专用和非脚本猫专用=-=
还有比如脚本猫没有// @grant...

啊?

// @namespace    https://bbs.tampermonkey.net.cn/
// @version      0.1.0
// @descriptiontry to take over the world!

不本来就有的么
页: [1]
查看完整版本: [油猴脚本开发指南]在控制台中调试油猴API函数