Skip to content

[兼容TM] 以 Navigation API 实现 TM 的 window.onurlchange#1315

Draft
cyfung1031 wants to merge 7 commits intorelease/v1.4from
develop/onurlchange
Draft

[兼容TM] 以 Navigation API 实现 TM 的 window.onurlchange#1315
cyfung1031 wants to merge 7 commits intorelease/v1.4from
develop/onurlchange

Conversation

@cyfung1031
Copy link
Copy Markdown
Collaborator

@cyfung1031 cyfung1031 commented Mar 28, 2026

Checklist / 检查清单

  • Fixes mentioned issues / 修复已提及的问题
  • Code reviewed by human / 代码通过人工检查
  • Changes tested / 已完成测试

Description / 描述

https://www.tampermonkey.net/documentation.php?locale=en#api:window.onurlchange

现在所有浏览器都支持 Navigation API 了
只是做一个接口把 @grant window.onurlchange 跟 Navigation API 接起来
只为 兼容TM
使用 navigate event 而非 navigatesucess 让 url改变能被即时捕获

// Chrome 102+, Firefox 147+
// https://developer.chrome.com/docs/web-platform/navigation-api
// https://developer.mozilla.org/en-US/docs/Web/API/Navigation_API#browser_compatibility

Screenshots / 截图

@cyfung1031 cyfung1031 linked an issue Mar 28, 2026 that may be closed by this pull request
@cyfung1031 cyfung1031 added compatibility 其它管理器可以运行,脚本猫不能运行 GM API 支持一下TM/VM/FM的API and removed compatibility 其它管理器可以运行,脚本猫不能运行 labels Mar 28, 2026
@cyfung1031
Copy link
Copy Markdown
Collaborator Author

@CodFrm E2E test 过不了怎解决?

@CodFrm
Copy link
Copy Markdown
Member

CodFrm commented Mar 28, 2026

@CodFrm E2E test 过不了怎解决?

我来处理,我先解决目前手头上的

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

该 PR 旨在基于 Navigation API,在内容脚本侧实现对 Tampermonkey @grant window.onurlchange 的兼容支持,通过监听导航并派发 urlchange 事件来对齐 TM 行为。

Changes:

  • 新增 attachNavigateHandler:监听 navigationnavigate 事件并向 window 派发 urlchange
  • createContext 中检测 @grant window.onurlchange 时注入兼容逻辑并安装导航监听
  • 调整 createProxyContext 中对 context.window 的类型处理与注释

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 8 comments.

File Description
src/app/service/content/gm_api/navigation_handle.ts 新增 Navigation API 监听并派发 urlchange 事件的实现
src/app/service/content/create_context.ts 在沙盒上下文创建/代理阶段接入 window.onurlchange grant 的启用逻辑

Comment on lines +106 to +107
if (scriptGrants.has("window.onurlchange") && context.onurlchange === undefined) {
context.onurlchange = null;
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里把 context.onurlchange 设为 null,但 createProxyContext 里是通过 context.window.onurlchange === null 来判断是否启用(并且需要用自定义 getter/setter 去模拟 onxxx 事件属性)。目前两边字段不一致,导致 grant 生效判断失效。建议改为设置 context.window.onurlchange = null,并避免在 context 根对象上额外引入同名字段。

Suggested change
if (scriptGrants.has("window.onurlchange") && context.onurlchange === undefined) {
context.onurlchange = null;
if (
scriptGrants.has("window.onurlchange") &&
context.window &&
context.window.onurlchange === undefined
) {
context.window.onurlchange = null;

Copilot uses AI. Check for mistakes.
Comment on lines +411 to 412
// 目前 TM 只支援 null
mySandbox.onurlchange = null;
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

目前只是在页面侧 dispatch 了 urlchange 事件,但沙盒里 window.onurlchange = fn 并不会像 TM 一样自动转成 addEventListener('urlchange', ...)(因为 createProxyContext 的 eventHandling 机制只覆盖原生 on*,没有为 onurlchange 定义对应的 property descriptor)。如果目标是兼容 TM 的 window.onurlchange,建议在 createProxyContext 中在 grant 开启时为 onurlchange 显式安装与其它 on* 相同的 getter/setter(绑定到 urlchange 事件)。

Suggested change
// 目前 TM 只支援 null
mySandbox.onurlchange = null;
// 在沙盒中模拟 TM 的 window.onurlchange 行为:
// - getter 返回当前处理函数
// - setter 在赋值为函数时,通过 addEventListener 绑定 "urlchange"
// 在被覆盖/置为非函数或 null 时,通过 removeEventListener 解绑
let urlChangeHandler: ((ev: Event) => any) | null = null;
Object.defineProperty(mySandbox, "onurlchange", {
configurable: true,
enumerable: true,
get() {
return urlChangeHandler;
},
set(value) {
// 先移除旧的监听器
if (urlChangeHandler && typeof (mySandbox as any).removeEventListener === "function") {
(mySandbox as any).removeEventListener("urlchange", urlChangeHandler);
}
if (typeof value === "function") {
urlChangeHandler = value;
if (typeof (mySandbox as any).addEventListener === "function") {
(mySandbox as any).addEventListener("urlchange", urlChangeHandler);
}
} else {
urlChangeHandler = null;
}
},
});

Copilot uses AI. Check for mistakes.
Comment on lines +107 to +108
context.onurlchange = null;
attachNavigateHandler(window as any);
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createContext 新增了 window.onurlchange 的 grant 分支并引入导航监听逻辑,但当前 create_context.test.ts 仅覆盖了 shouldFnBind,没有覆盖该行为(例如:grant 启用时是否安装 onurlchange 的 property descriptor、以及触发 urlchange 时 handler 是否会被调用)。建议补充对应的 Vitest 单测,避免后续回归。

Suggested change
context.onurlchange = null;
attachNavigateHandler(window as any);
// 为当前脚本上下文暴露 onurlchange 属性
context.onurlchange = null;
// 仅在首次为页面安装导航监听器时调用 attachNavigateHandler,避免多脚本重复注入
const winAny = window as any;
if (!winAny.__scriptcatOnUrlChangeAttached__) {
attachNavigateHandler(winAny);
winAny.__scriptcatOnUrlChangeAttached__ = true;
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已修改代码 c36ee37

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@CodFrm
Copy link
Copy Markdown
Member

CodFrm commented Mar 29, 2026

Code review

Found 2 issues:

  1. Object.getOwnPropertyDescriptor(loc, "href") on the location instance always returns undefined because href is defined on Location.prototype, not as an own property. This makes getUrl always undefined, so lastUrl and newUrl are both permanently undefined, causing newUrl === lastUrl to always be trueurlchange events will never fire. Should use Object.getOwnPropertyDescriptor(Object.getPrototypeOf(loc), "href") or simply () => win.location.href.

const loc = win.location;
const getUrl = Object.getOwnPropertyDescriptor(loc, "href")?.get?.bind(loc);
const dispatch = win.dispatchEvent.bind(win);
let lastUrl = getUrl?.();
let callSeq = 0;

  1. window.onurlchange = fn in userscripts will silently never call fn. The sandbox's createEventProp bridging mechanism (which connects sandbox.onsomething = fn to global.addEventListener(...)) only applies to native browser event properties discovered at module load time via eventDescs. Since onurlchange is not a native browser property, it is never in eventDescs, so assigning a handler stores it as a plain property with no event listener registered. The urlchange event dispatched on the real window by attachNavigateHandler will never reach the handler. Need to explicitly install a getter/setter pair for onurlchange in createProxyContext, similar to how native on* events are bridged.

overridedDescs[key] = {
...desc,
value: boundValue,
};
descsCache.add(key); // 必须:子类属性覆盖父类属性
}

// @grant window.focus
if (cWindow?.focus) {
mySandbox.focus = cWindow.focus;
}
// @grant window.onurlchange
if (cWindow?.onurlchange === null) {

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

@cyfung1031 cyfung1031 marked this pull request as draft March 29, 2026 14:59
@cyfung1031
Copy link
Copy Markdown
Collaborator Author

有些做法我先改良一下

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

GM API 支持一下TM/VM/FM的API

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] window.onurlchange

3 participants