Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 28 additions & 4 deletions src/app/service/content/create_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { protect } from "./gm_api/gm_context";
import { isEarlyStartScript } from "./utils";
import { ListenerManager } from "./listener_manager";
import { createGMBase } from "./gm_api/gm_api";
import { attachNavigateHandler, type UrlChangeEvent } from "./gm_api/navigation_handle";

// 构建沙盒上下文
export const createContext = (
Expand Down Expand Up @@ -102,6 +103,10 @@ export const createContext = (
}
}
context.unsafeWindow = window;
if (scriptGrants.has("window.onurlchange") && context.onurlchange === undefined) {
context.onurlchange = null;
attachNavigateHandler(window as any);
}
return context;
};

Expand Down Expand Up @@ -370,6 +375,23 @@ export const createProxyContext = <const Context extends GMWorldContext>(context
},
};

// @grant window.onurlchange
if (context?.onurlchange === null) {
let currentValue: ((this: GlobalEventHandlers, ev: UrlChangeEvent) => any) | null = null;
ownDescs.onurlchange = {
enumerable: true,
configurable: true,
get() {
return currentValue;
},
set(nv) {
if (typeof nv !== "function") nv = null;
currentValue = nv;
return true;
},
};
}

// 把初始Copy加上特殊变量后,生成一份新Copy
mySandbox = Object.create(Object.getPrototypeOf(sharedInitCopy), ownDescs);

Expand All @@ -389,7 +411,7 @@ export const createProxyContext = <const Context extends GMWorldContext>(context

// 把 GM context物件的 window属性内容移至exposedWindow
// 由于目前只有 window.close, window.open, window.onurlchange, 不需要循环 window
const cWindow = context.window;
const cWindow = context.window as (Window & Record<string, any>) | undefined;

// @grant window.close
if (cWindow?.close) {
Expand All @@ -402,9 +424,11 @@ export const createProxyContext = <const Context extends GMWorldContext>(context
}

// @grant window.onurlchange
if (cWindow?.onurlchange === null) {
// 目前 TM 只支援 null. ScriptCat不需要grant预设启用?
mySandbox.onurlchange = null;
if (context?.onurlchange === null) {
const handle = function (this: Window & Record<string, any>, e: UrlChangeEvent) {
this.onurlchange?.(e);
} as EventListener;
(<EventTarget>window).addEventListener("urlchange", handle.bind(mySandbox), false);
}

// 从网页 console 隔离出来的沙盒 console
Expand Down
58 changes: 58 additions & 0 deletions src/app/service/content/gm_api/navigation_handle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
export class UrlChangeEvent extends Event {
readonly url: string;
constructor(type: string, url: string) {
super(type);
this.url = url;
}
}

let attached = false;

const getPropGetter = (obj: any, key: string) => {
// 避免直接 obj[key] 读取。或会被 hack
let t = obj;
let pd: PropertyDescriptor | undefined;
while (t) {
pd = Object.getOwnPropertyDescriptor(t, key);
if (pd) break;
t = Object.getPrototypeOf(t);
}
if (pd) {
return pd?.get?.bind(obj);
}
};

// 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
export const attachNavigateHandler = (win: Window & { navigation: EventTarget }) => {
if (attached) return;
attached = true;
// 以 location.href 判断避免 replaceState/pushState 重复执行重复触发
const loc = win.location;
const getUrl = getPropGetter(loc, "href");
const dispatch = win.dispatchEvent.bind(win);
let lastUrl = getUrl?.();
let callSeq = 0;
const handler = async (ev: Event): Promise<void> => {
callSeq = callSeq > 512 ? 1 : callSeq + 1;
const seq = callSeq;
let newUrl = getUrl?.(); // 取得当前 location.href
const destUrl = (ev as any).destination?.url;
if (destUrl !== newUrl && newUrl === lastUrl) {
// 某些情况,location.href 未更新就触发了
// 用 postMessage 推迟到下一个 macrotask 阶段
await new Promise((resolve) => {
self.addEventListener("message", resolve, { once: true });
self.postMessage({ [`${Math.random()}`]: {} }, "*"); // 传一个 dummy message
});
if (seq !== callSeq) return; // 等待时,或许已经触发了其他 navigate
newUrl = getUrl?.(); // 再次取得当前 location.href
}
if (newUrl === lastUrl) return;
lastUrl = newUrl;
const urlChangeEv = new UrlChangeEvent("urlchange", (destUrl || newUrl) as string);
dispatch(urlChangeEv);
};
win.navigation?.addEventListener("navigate", handler, false);
};
Loading