Conversation
|
呀。測試沒通過 因為我主要用 UserScript. 不會有回傳值 it("global", async () => {
scriptRes2.code = "window.testObj = 'ok';return window.testObj";
sandboxExec.scriptFunc = compileScript(compileScriptCode(scriptRes2));
let ret = await sandboxExec.exec();
expect(ret).toEqual("ok");
scriptRes2.code = "window.testObj = 'ok2';return testObj";
sandboxExec.scriptFunc = compileScript(compileScriptCode(scriptRes2));
ret = await sandboxExec.exec();
expect(ret).toEqual("ok2");
});
it("this", async () => {
scriptRes2.code = "this.testObj='ok2';return testObj;";
sandboxExec.scriptFunc = compileScript(compileScriptCode(scriptRes2));
const ret = await sandboxExec.exec();
expect(ret).toEqual("ok2");
});這些測試要有回傳值... 如果要這樣保留傳回值測試,可以這樣改 在測試時指定為測試模式 測試模式的話不使用 return `with(arguments[0]){
${preCode}
[((async function(){
${code}
})(),0)];
}`;而使用 return `with(arguments[0]){
${preCode}
return (function(){
${code}
})();
}`; |
bec07c0 to
660ccc3
Compare
| try { | ||
| return factory.apply(context, []); | ||
| } catch (e) { | ||
| if (e.message && e.stack) { |
There was a problem hiding this comment.
这里的错误处理是有意义的,避免一个脚本出错,影响到上层的内容,例如脚本执行顺序是A-B-C,如果A的错误不catch,会导致B、C无法运行
There was a problem hiding this comment.
喔原來是這樣。這個可以改回來。(最初想法只是不想加一堆代碼做錯誤處理)
(改回來就是上面 // 注釋的部份)
src/app/service/content/utils.ts
Outdated
| return false; | ||
| const exposedProxy = new Proxy(exposedObject, { | ||
| defineProperty(target, name, desc) { | ||
| return Reflect.defineProperty(target, name, desc); |
There was a problem hiding this comment.
一般在Proxy裡面都是用Reflect
你看回傳就懂了吧
Reflect.defineProperty跟Proxy裡面的defineProperty一樣,傳boolean
基本上 Proxy裡面的設定,全部都跟Reflect對應
There was a problem hiding this comment.
There was a problem hiding this comment.
之前exposedProxy (proxy) 的target 不是exposedObject (thisContext),才需要寫defineProperty吧
defineProperty(target, name, desc) {
return Reflect.defineProperty(target, name, desc);
},
現在參數都是一樣,直接把defineProperty拿掉也可以
There was a problem hiding this comment.
好的,听起来没问题,这一块内容从开始到现在没有怎么改变过,担心会影响到脚本的功能,一直都是往上堆🤣
|
ExecScript.apply,这个我也记不清了,似乎是和none的this有关系 TM 建议不要改动单元测试,使用单元测试能通过的模式 |
這個範例沒有 TM或者其他都是沒GM_info吧
ScriptCat現在, 剛推了這個commit了,單元測試能過。不用擔心 |
|
但TM事实上就是有的(可以看到this是一个{}),不好意思,我的示例有问题 // ==UserScript==
// @name Grant None
// @namespace https://bbs.tampermonkey.net.cn/
// @version 0.1.0
// @description try to take over the world!
// @author You
// @match https://bbs.tampermonkey.net.cn/
// @grant none
// ==/UserScript==
console.log("Grant None", this, GM_info);(非@grant none)的情况,是一个Proxy |
| */ | ||
| exec() { | ||
| this.logger.debug("script start"); | ||
| return this.scriptFunc.apply(this.proxyContent, [this.proxyContent, this.GM_info]); |
There was a problem hiding this comment.
在之前的实现中,不注入任何GM Api,但是GM_info是通过参数传递的
There was a problem hiding this comment.
这里的this,似乎也要传递,参考我上述的脚本
另外有一些脚本会直接使用下面的形式,没有this似乎也会出现问题
onload=()=>{
console.log('load');
}There was a problem hiding this comment.
我不明白。首先這個肯定跟單元測試無關。已經通過了
現在代碼:
scriptcat/src/app/service/content/utils.ts
Lines 24 to 40 in b1d64f0
現在的是
async function(){
${code}
}還有
return factory.apply(context, []); 這不是已經指定了this 嗎? (雖然我不明白使用了 with(context) 還要再指定 this = context 是有什麼用)
There was a problem hiding this comment.
我打算(修改commit) 保留 code 裡面的 this = context ( 油猴是這樣)
但 this.scriptFunc.apply 不用 指定this 吧
|
@CodFrm 確認了最新版油猴。先不理他那三個undefined define exports module 我會加回來(類似現時ScriptCat這樣)
|
|
似乎是单元测试失效了,我修复了一下,另外不用特意添加testMode吧 this也有问题,我补充单元测试了 |
實際使用不用回傳值嘛 不是只為了測試才return 嗎 |
|
后台脚本中,需要用到返回值 后台脚本return了promise,需要拿这个promise做处理,可以参考example中的
https://github.com/scriptscat/scriptcat/blob/main/example/gm_bg_menu.js |
| expect(ret.script.version).toEqual("1.0.0"); | ||
| expect(ret.GM_info.version).toEqual(ExtVersion); | ||
| expect(ret.GM_info.script.version).toEqual("1.0.0"); | ||
| expect(ret._this).toEqual({}); |
There was a problem hiding this comment.
你這個要跟 TM 一樣把 this 指定成 {}?
而不是window (原生) ?
|
@CodFrm 过了单元测试。 |
There was a problem hiding this comment.
Pull Request Overview
This PR overhauls the GM API injection mechanism to centralize and streamline how APIs are exposed in sandboxed scripts, reduce proxy overhead, and improve maintainability.
- Consolidates API registration and method injection in
GMContext/createContextand introduces a@protecteddecorator for hidden members. - Refactors
compileScriptCode,proxyContext, andexecScriptto use argument-based injection and strict equality checks. - Removes the custom
lodash.hasutility and replaces it with an inlinehasimplementation in the content layer.
Reviewed Changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/pkg/utils/lodash.ts | Removed legacy has export in favor of inline implementation. |
| src/app/service/service_worker/runtime.ts | Switched to strict !== undefined filters and updated injector. |
| src/app/service/service_worker/client.ts | Replaced != with !== in response code check. |
| src/app/service/content/utils.ts | Added inline has, protect import, and revised code wrapper. |
| src/app/service/content/types.ts | Updated ScriptFunc signature and added follow/fnKey fields. |
| src/app/service/content/gm_context.ts | Changed API map to Map<string, ApiValue[]> and added decorator. |
| src/app/service/content/gm_api.ts | Major refactor: extracted helper functions and unified API calls. |
| src/app/service/content/exec_script.ts | Adjusted constructor and exec to use argument-based injection. |
| src/app/service/content/exec_script.test.ts | Updated tests to align with new exec and compileScriptCode. |
| src/app/service/content/create_context.ts | Redesigned method injection (__methodInject__) and removed legacy patterns. |
Comments suppressed due to low confidence (2)
src/app/service/content/gm_context.ts:21
- [nitpick] Defining a static method named
protectedcan be confusing sinceprotectedis a TypeScript access modifier keyword. Consider renaming this decorator todefineProtectedormarkProtectedfor clarity.
public static protected(value: any = undefined) {
src/app/service/content/gm_context.ts:17
- [nitpick] You’ve introduced a
protectlist that hides properties from the sandbox. Add unit tests inutils.test.tsor a new test suite to verify that @Protected members are indeed not accessible viaproxyContext.
export const protect: { [key: string]: any } = {};
| window: { | ||
| onurlchange: null, | ||
| }, | ||
| grantSet: new Set() |
There was a problem hiding this comment.
The passed-in grantSet argument is not used here; initializing a new empty set will prevent any @grant-based methods from being injected. Replace new Set() with the function parameter grantSet.
| grantSet: new Set() | |
| grantSet: grantSet |
There was a problem hiding this comment.
預計 scripts 通常都有 @grant 而且多於一個。所以 __methodInject__ 裡不放初始化(或判定)。直接預設一個空的Set
There was a problem hiding this comment.
兩個 grantSet 是不同的。雖然名字一樣
傳入的是 需要的 grant (不重覆)
context的 grantSet 是用來記錄有沒有注入到 context
| } | ||
| }) | ||
| .filter((it) => it != null) | ||
| .filter((it) => it !== undefined) |
There was a problem hiding this comment.
Changing from != null to !== undefined will no longer filter out null values. If both null and undefined should be removed, consider using it != null or explicitly checking for both.
| .filter((it) => it !== undefined) | |
| .filter((it) => it != null) |
There was a problem hiding this comment.
上述没有返回值,(it) => it != null似乎是合理的
!== undefined 没有问题
There was a problem hiding this comment.
因為代碼裡只有 有物件 和 無物件(沒回傳=undefined)
不會出現null, 反而是undefined
!== 避免類型轉換
| // if (scriptRes.metadata.grant) { | ||
| // // 处理GM.与GM_,将GM_与GM.都复制一份 | ||
| // const grant: string[] = []; | ||
| // scriptRes.metadata.grant.forEach((val) => { | ||
| // // if (val.startsWith("GM_")) { | ||
| // // const t = val.slice(3); | ||
| // // grant.push(`GM.${t}`); | ||
| // // } else if (val.startsWith("GM.")) { | ||
| // // grant.push(val); | ||
| // // } | ||
| // // grant.push(val); | ||
| // grant.push(val); | ||
| // }); | ||
| // // 去重 | ||
| // const uniqueGrant = new Set(grant); | ||
| // for(const grant of uniqueGrant){ | ||
| // context.__methodInject__(grant); | ||
| // } | ||
| // } |
There was a problem hiding this comment.
[nitpick] There is a large block of commented-out legacy code remaining here. Consider removing it to improve readability and reduce confusion.
| // if (scriptRes.metadata.grant) { | |
| // // 处理GM.与GM_,将GM_与GM.都复制一份 | |
| // const grant: string[] = []; | |
| // scriptRes.metadata.grant.forEach((val) => { | |
| // // if (val.startsWith("GM_")) { | |
| // // const t = val.slice(3); | |
| // // grant.push(`GM.${t}`); | |
| // // } else if (val.startsWith("GM.")) { | |
| // // grant.push(val); | |
| // // } | |
| // // grant.push(val); | |
| // grant.push(val); | |
| // }); | |
| // // 去重 | |
| // const uniqueGrant = new Set(grant); | |
| // for(const grant of uniqueGrant){ | |
| // context.__methodInject__(grant); | |
| // } | |
| // } | |
| // Removed commented-out legacy code for improved readability. |
There was a problem hiding this comment.
現在的寫法已經不用把GM. GM_ 重覆grant
depend 那個已經很足夠
只是保留一下舊代碼
There was a problem hiding this comment.
旧代码删掉没有关系,我一般也习惯通过git history去查看
|
🙏非常感谢,现在这块的可读性很好了 |
| @GMContext.API({ | ||
| depend: ["CAT_fetchBlob", "CAT_createBlobUrl", "CAT_fetchDocument"], | ||
| }) | ||
| public ['GM.xmlHttpRequest'](details: GMTypes.XHRDetails) { |
There was a problem hiding this comment.
应该只存在 GM_xmlhttpRequest 和 GM.xmlHttpRequest 吧,不需要定义另外的两个(没想到可以这样定义)
There was a problem hiding this comment.
原來是這樣! 我以為你方便用家打錯大小寫
There was a problem hiding this comment.
这个问题我觉得也很尴尬,命名不规范,GM_getResourceURL与GM.getResourceUrl也是一样
| } | ||
| let { follow } = param; | ||
| if (!follow) follow = key; // follow 是实际 @grant 的权限 | ||
| GMContextApiSet(follow, key, descriptor.value, param); |
There was a problem hiding this comment.
GM.saveTab 之类的 API 似乎会出现问题
// ==UserScript==
// @name New Userscript
// @namespace https://bbs.tampermonkey.net.cn/
// @version 0.1.0
// @description try to take over the world!
// @author You
// @match https://bbs.tampermonkey.net.cn/
// @grant GM.getTab
// @grant GM_getTab
// ==/UserScript==
console.log(GM_getTab, "---", GM.getTab);There was a problem hiding this comment.
?
為什麼呢
它們都沒有depend
裡面的都是直接用sendMessage
There was a problem hiding this comment.
没有实现GM.getTab,在此之前是直接替换_=>.去处理的,所以就没这个问题
There was a problem hiding this comment.
我重現到了。我看一下是哪裡的問題。
There was a problem hiding this comment.
呀。對。因為現在要寫清楚每一個
最初ScriptCat以為 . 跟 _ 一樣嘛
然後 GM_cookie 出來後就死掉
反正就是每個用法都要寫出來。
現在要加回 GM.getTab
There was a problem hiding this comment.
@CodFrm 剛想到了
可以像你後台腳本那邊做法一樣
加 alias
@GMContext.API()
GM_saveTab(obj: object) {
@GMContext.API({alias: 'GM.saveTab'})
GM_saveTab(obj: object) {
我等一下提交這個 alias 吧
這樣代碼也比較少
There was a problem hiding this comment.
也可以,另外将静态方法作为实际实现,和定义放在一起,不放在顶层,我觉得也是可以的
There was a problem hiding this comment.






(首先不好意思,没有及时提交,
跟最新的commit会有衝突)之前我在改动时发现了以下问题
在脚本裡可以直接存取变量
EE(修改后也知道了是手动打这些。手动加漏了吧)研究 GM API 的行为时,发现depend 会导致脚本裡出现其他API
(例如我们只需要 GM.getValues, 但会连同 GM_getValue 都给了出来)
於是重构了几个相关部份
为了容易看,我把他们分开了5个commit
重构 GM Api 内部代码处理
+
methodInject 处理内部化
这个是弃用以往的做法,会在GMContext那裡直接写好API,而不是各处加一堆特别处理(像cookie)
现在定明,GM.cookie.set 裡面做什麼,当@grant 了什麼权限才会有这个之类
这样除了清楚,方便日后修改外,还减少了一堆 nested function 的效能下降隐藏问题
现在我们用TypeScript,用ESM,通用的我们可以直接抽出来,不用grant来Grant去
我不是全改了。只是常用修改了一下。
当然还保留了 depend 的做法。使用方法不变。见 methodInject
针对
EE那些漏掉的问题现在会在context出现的都要写在 GM_Base, 然后加一个
@protected这样的话,就会自动生成一个 不要外漏的清单
优化注入代码
这个改动了Context那个部份
在產生sandbox context 时,直接按protect清单,把context裡的GM或CAT API东西抄到 exposedObject (旧称thisContext)
(终极目标是减少proxy的处理。Proxy用在with上面有效能问题。)
另外,本来是用有名字的变量
context来做Injection. 实际上我们用arguments就好了arguments只在顶层scope有效。跑code 那裡就不能存取
error 那个我拿走了
因为现在async 设计,async裡的出错也不会被抓到
如果真的发生了问题,瀏览器本身会报错 (有sourceURL能追踪)
所以不用特别加报错处理吧
要注入的东西在生成sandbox就注入
之后的proxy 不要搞这麼多判断
我看不懂之前 ExecScript
.apply时要把 this 都绑定的理由。现在是改成绑定null. 实际使用也没差别之前绑定了GM_info. 但这个会在context 给出,应该不用绑定吧
跟TM那些一样。用了
@grant none,GM_info跟GM也不会有这些就是提交的改动
看不懂再提出。
或者需要修改的地方 (我只使用一般TM腳本,沒使用什麼後台腳本。TM腳本有測試過沒問題)