Skip to content
12 changes: 9 additions & 3 deletions packages/message/common.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { extensionEnv, type TExtensionEnv } from "@App/app/service/extension/extension_env";
import { randomMessageFlag } from "@App/pkg/utils/utils";
Comment thread
cyfung1031 marked this conversation as resolved.
Outdated

// 避免页面载入后改动全域物件导致消息传递失败
Expand Down Expand Up @@ -40,7 +41,7 @@ export function negotiateEventFlag(messageFlag: string, readyCount: number, onIn
break;
case "requestEventFlag":
// 广播通信 flag 给 inject/content
pageDispatchCustomEvent(messageFlag, { action: "broadcastEventFlag", eventFlag: eventFlag });
pageDispatchCustomEvent(messageFlag, { action: "broadcastEventFlag", eventFlag: eventFlag, extensionEnv });
break;
}
};
Expand All @@ -51,16 +52,21 @@ export function negotiateEventFlag(messageFlag: string, readyCount: number, onIn
}
Comment thread
cyfung1031 marked this conversation as resolved.

// 获取协商后的 eventFlag
export function getEventFlag(messageFlag: string, onReady: (eventFlag: string) => void) {
export function getEventFlag(
messageFlag: string,
onReady: (eventFlag: string, extensionEnv: TExtensionEnv | undefined) => void
) {
let eventFlag = "";
let extensionEnv = null;
const fnEventFlagListener: EventListener = (ev: Event) => {
if (!(ev instanceof CustomEvent)) return;
if (ev.detail?.action != "broadcastEventFlag") return;
eventFlag = ev.detail.eventFlag;
extensionEnv = ev.detail.extensionEnv;
Comment thread
cyfung1031 marked this conversation as resolved.
Outdated
pageRemoveEventListener(messageFlag, fnEventFlagListener);
// 告知对方已收到 eventFlag
pageDispatchCustomEvent(messageFlag, { action: "receivedEventFlag" });
onReady(eventFlag);
onReady(eventFlag, extensionEnv);
};

// 设置事件,然后对 scripting 请求 flag
Expand Down
6 changes: 5 additions & 1 deletion src/app/service/content/exec_warp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import ExecScript from "./exec_script";
import type { Message } from "@Packages/message/types";
import type { ScriptLoadInfo } from "../service_worker/types";
import type { GMInfoEnv } from "./types";
import { type TExtensionEnv } from "../extension/extension_env";

export class CATRetryError {
msg: string;
Expand All @@ -23,7 +24,7 @@ export class BgExecScriptWarp extends ExecScript {

setInterval: Map<number, boolean>;

constructor(scriptRes: ScriptLoadInfo, message: Message) {
constructor(scriptRes: ScriptLoadInfo, message: Message, extensionEnv: TExtensionEnv | undefined) {
const thisContext: { [key: string]: any } = {};
const setTimeout = new Map<number, any>();
const setInterval = new Map<number, any>();
Expand Down Expand Up @@ -73,6 +74,9 @@ export class BgExecScriptWarp extends ExecScript {
},
isIncognito: false,
};
const { inIncognitoContext, userAgentData } = extensionEnv || {};
if (typeof inIncognitoContext === "boolean") envInfo.isIncognito = inIncognitoContext;
if (userAgentData) envInfo.userAgentData = userAgentData;
super(scriptRes, {
envPrefix: "offscreen",
message: message,
Expand Down
4 changes: 2 additions & 2 deletions src/app/service/content/script_executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ export type ExecScriptEntry = {
scriptFunc: any;
};

export const initEnvInfo = {
export const initEnvInfo: GMInfoEnv = {
/** userAgentData - 从全局变量获取 */
userAgentData: typeof UserAgentData === "object" ? UserAgentData : {},
/** sandboxMode - 预留字段,当前固定为 raw */
sandboxMode: "raw",
/** isIncognito - inject/content 环境下无法判断,固定为 false */
/** isIncognito - inject/content 环境下透過 scripting 环境判断 */
/** 使用者可透过 「 await navigator.storage.persisted() 」来判断,但ScriptCat不会主动执行此代码来判断 */
isIncognito: false,
} satisfies GMInfoEnv;
Expand Down
11 changes: 9 additions & 2 deletions src/app/service/content/script_runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import type { GMInfoEnv, ValueUpdateDataEncoded } from "./types";
import type { ScriptEnvTag } from "@Packages/message/consts";
import { onInjectPageLoaded } from "./external";
import type { CustomEventMessage } from "@Packages/message/custom_event_message";
import { type TExtensionEnv } from "../extension/extension_env";

export class ScriptRuntime {
constructor(
private readonly scripEnvTag: ScriptEnvTag,
private readonly server: Server,
private readonly msg: Message,
private readonly scriptExecutor: ScriptExecutor
private readonly scriptExecutor: ScriptExecutor,
private readonly extensionEnv: TExtensionEnv | undefined
) {}

// content环境的特殊初始化
Expand Down Expand Up @@ -66,8 +68,13 @@ export class ScriptRuntime {
this.startScripts(data.scripts, data.envInfo);
});

// 用于 early-start 的扩充参数
const { inIncognitoContext } = this.extensionEnv || {};
const initialEnvInfo = { ...initEnvInfo };
if (typeof inIncognitoContext === "boolean") initialEnvInfo.isIncognito = inIncognitoContext;

// 检查early-start的脚本
this.scriptExecutor.checkEarlyStartScript(this.scripEnvTag, initEnvInfo);
this.scriptExecutor.checkEarlyStartScript(this.scripEnvTag, initialEnvInfo);
}

startScripts(scripts: TScriptInfo[], envInfo: GMInfoEnv) {
Expand Down
35 changes: 35 additions & 0 deletions src/app/service/extension/extension_env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
export type TExtensionEnv = {
inIncognitoContext: boolean;
userAgentData?: GMUserAgentData | null;
};

type GMUserAgentData = typeof GM_info.userAgentData;

export const extensionEnv: TExtensionEnv = {
inIncognitoContext: chrome.extension.inIncognitoContext,
} satisfies TExtensionEnv;

export const getExtensionUserAgentData = async (): Promise<GMUserAgentData | null> => {
// @ts-ignore
const userAgentData = navigator.userAgentData;
if (userAgentData) {
const resultData: GMUserAgentData = {
brands: userAgentData.brands,
mobile: userAgentData.mobile,
platform: userAgentData.platform,
} satisfies GMUserAgentData;
// 处理architecture和bitness
if (chrome.runtime.getPlatformInfo) {
try {
const platformInfo = await chrome.runtime.getPlatformInfo();
resultData.architecture = platformInfo.nacl_arch;
resultData.bitness = platformInfo.arch.includes("64") ? "64" : "32";
} catch (e) {
// 避免 API 无法执行的问题。不影响整体运作
console.warn(e);
}
}
return resultData;
}
return null;
};
4 changes: 4 additions & 0 deletions src/app/service/offscreen/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ export function preparationSandbox(windowMessage: WindowMessage) {
return sendMessage(windowMessage, "offscreen/preparationSandbox");
}

export function getExtensionEnv(windowMessage: WindowMessage) {
return sendMessage(windowMessage, "offscreen/getExtensionEnv", { requireUAD: true });
}

// 代理发送消息到ServiceWorker
export function sendMessageToServiceWorker(windowMessage: WindowMessage, action: string, data?: any) {
return sendMessage(windowMessage, "offscreen/sendMessageToServiceWorker", { action, data });
Expand Down
10 changes: 9 additions & 1 deletion src/app/service/offscreen/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ export class OffscreenManager {
this.serviceWorker.preparationOffscreen();
}

async getExtensionEnv(data: { requireUAD: boolean }) {
return this.sendMessageToServiceWorker({
action: "getExtensionEnv",
data: data,
});
}

sendMessageToServiceWorker(data: { action: string; data: any }) {
return sendMessage(this.extMsgSender, `serviceWorker/${data.action}`, data.data);
}
Expand All @@ -47,6 +54,7 @@ export class OffscreenManager {
// 监听消息
this.windowServer.on("logger", this.logger.bind(this));
this.windowServer.on("preparationSandbox", this.preparationSandbox.bind(this));
this.windowServer.on("getExtensionEnv", this.getExtensionEnv.bind(this));
this.windowServer.on("sendMessageToServiceWorker", this.sendMessageToServiceWorker.bind(this));
const script = new ScriptService(
this.windowServer.group("script"),
Expand All @@ -55,7 +63,7 @@ export class OffscreenManager {
this.messageQueue
);
script.init();
// 转发从sandbox来的gm api请求
// 转发从sandbox来的请求
forwardMessage("serviceWorker", "runtime/gmApi", this.windowServer, this.extMsgSender);
// 转发valueUpdate与emitEvent
forwardMessage("sandbox", "runtime/valueUpdate", this.windowServer, this.windowMessage);
Expand Down
5 changes: 3 additions & 2 deletions src/app/service/sandbox/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Server } from "@Packages/message/server";
import { type WindowMessage } from "@Packages/message/window_message";
import { preparationSandbox } from "../offscreen/client";
import { getExtensionEnv, preparationSandbox } from "../offscreen/client";
import { Runtime } from "./runtime";

// sandbox环境的管理器
Expand All @@ -12,7 +12,8 @@ export class SandboxManager {
}

initManager() {
const runtime = new Runtime(this.windowMessage, this.api);
const extensionEnvAsync = getExtensionEnv(this.windowMessage);
const runtime = new Runtime(this.windowMessage, this.api, extensionEnvAsync);
runtime.init();
// 通知初始化好环境了
preparationSandbox(this.windowMessage);
Expand Down
19 changes: 17 additions & 2 deletions src/app/service/sandbox/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { parseUserConfig } from "@App/pkg/utils/yaml";
import { decodeRValue } from "@App/pkg/utils/message_value";
import { extractCronExpr } from "@App/pkg/utils/cron";
import { changeLanguage, initLanguage, t } from "@App/locales/locales";
import { type TExtensionEnv } from "../extension/extension_env";

const utime_1min = 60 * 1000;
const utime_1hr = 60 * 60 * 1000;
Expand All @@ -40,7 +41,8 @@ export class Runtime {

constructor(
private windowMessage: WindowMessage,
private api: Server
private api: Server,
private readonly extensionEnvAsync: Promise<TExtensionEnv | undefined>
) {
this.logger = LoggerCore.getInstance().logger({ component: "sandbox" });
// 重试队列,5s检查一次
Expand Down Expand Up @@ -171,7 +173,20 @@ export class Runtime {
// 暂未实现执行完成后立马释放,会在下一次执行时释放
await this.stopScript(script.uuid);
}
const exec = new BgExecScriptWarp(script, this.windowMessage);
const extensionEnv = await this.extensionEnvAsync;

// 判断 run-in
const runIn = script.metadata?.["run-in"]?.[0];
const inIncognitoContext = extensionEnv?.inIncognitoContext;
if (runIn && runIn !== "all" && typeof inIncognitoContext === "boolean") {
// 判断插件运行环境
const contextType = inIncognitoContext ? "incognito-tabs" : "normal-tabs";
if (runIn !== contextType) {
return;
}
}

const exec = new BgExecScriptWarp(script, this.windowMessage, extensionEnv);
this.execScriptMap.set(script.uuid, exec);
proxyUpdateRunStatus(this.windowMessage, { uuid: script.uuid, runStatus: SCRIPT_RUN_STATUS_RUNNING });
// 修改掉脚本掉最后运行时间, 数据库也需要修改
Expand Down
10 changes: 10 additions & 0 deletions src/app/service/service_worker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { FaviconDAO } from "@App/app/repo/favicon";
import { onRegularUpdateCheckAlarm } from "./regular_updatecheck";
import { cacheInstance } from "@App/app/cache";
import { InfoNotification } from "./utils";
import { extensionEnv, getExtensionUserAgentData } from "../extension/extension_env";

// service worker的管理器
export default class ServiceWorkerManager {
Expand All @@ -36,8 +37,17 @@ export default class ServiceWorkerManager {
dao.save(data);
}

async getExtensionEnv(data: { requireUAD: boolean }) {
const result = { ...extensionEnv };
if (data.requireUAD) {
result.userAgentData = await getExtensionUserAgentData();
}
return result;
}

initManager() {
this.api.on("logger", this.logger.bind(this));
this.api.on("getExtensionEnv", this.getExtensionEnv.bind(this));
this.api.on("preparationOffscreen", async () => {
// 准备好环境
await this.sender.init();
Expand Down
22 changes: 2 additions & 20 deletions src/app/service/service_worker/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import type { CompiledResource, ResourceType } from "@App/app/repo/resource";
import { CompiledResourceDAO } from "@App/app/repo/resource";
import { setOnTabURLChanged } from "./url_monitor";
import { scriptToMenu, type TPopupPageLoadInfo } from "./popup_scriptmenu";
import { getExtensionUserAgentData } from "../extension/extension_env";

const ORIGINAL_URLMATCH_SUFFIX = "{ORIGINAL}"; // 用于标记原始URLPatterns的后缀

Expand Down Expand Up @@ -151,26 +152,7 @@ export class RuntimeService {
}

async initUserAgentData() {
// @ts-ignore
const userAgentData = navigator.userAgentData;
if (userAgentData) {
this.userAgentData = {
brands: userAgentData.brands,
mobile: userAgentData.mobile,
platform: userAgentData.platform,
};
// 处理architecture和bitness
if (chrome.runtime.getPlatformInfo) {
try {
const platformInfo = await chrome.runtime.getPlatformInfo();
this.userAgentData.architecture = platformInfo.nacl_arch;
this.userAgentData.bitness = platformInfo.arch.includes("64") ? "64" : "32";
} catch (e) {
// 避免 API 无法执行的问题。不影响整体运作
console.warn(e);
}
}
}
this.userAgentData = (await getExtensionUserAgentData()) || {};
}

showNoDeveloperModeWarning() {
Expand Down
5 changes: 3 additions & 2 deletions src/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import type { Message } from "@Packages/message/types";
import { getEventFlag } from "@Packages/message/common";
import { ScriptRuntime } from "./app/service/content/script_runtime";
import { ScriptEnvTag } from "@Packages/message/consts";
import { type TExtensionEnv } from "./app/service/extension/extension_env";

const messageFlag = process.env.SC_RANDOM_KEY!;

getEventFlag(messageFlag, (eventFlag: string) => {
getEventFlag(messageFlag, (eventFlag: string, extensionEnv: TExtensionEnv | undefined) => {
const scriptEnvTag = ScriptEnvTag.content;

const msg: Message = new CustomEventMessage(eventFlag, false, scriptEnvTag);
Expand All @@ -26,7 +27,7 @@ getEventFlag(messageFlag, (eventFlag: string) => {

const server = new Server("content", msg);
const scriptExecutor = new ScriptExecutor(msg, new CustomEventMessage(eventFlag, true, scriptEnvTag));
const runtime = new ScriptRuntime(scriptEnvTag, server, msg, scriptExecutor);
const runtime = new ScriptRuntime(scriptEnvTag, server, msg, scriptExecutor, extensionEnv);
runtime.contentInit();
runtime.init();
});
5 changes: 3 additions & 2 deletions src/inject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import type { Message } from "@Packages/message/types";
import { getEventFlag } from "@Packages/message/common";
import { ScriptRuntime } from "./app/service/content/script_runtime";
import { ScriptEnvTag } from "@Packages/message/consts";
import { type TExtensionEnv } from "./app/service/extension/extension_env";

const messageFlag = process.env.SC_RANDOM_KEY!;

getEventFlag(messageFlag, (eventFlag: string) => {
getEventFlag(messageFlag, (eventFlag: string, extensionEnv: TExtensionEnv | undefined) => {
const scriptEnvTag = ScriptEnvTag.inject;

const msg: Message = new CustomEventMessage(eventFlag, false, scriptEnvTag);
Expand All @@ -26,7 +27,7 @@ getEventFlag(messageFlag, (eventFlag: string) => {

const server = new Server("inject", msg);
const scriptExecutor = new ScriptExecutor(msg, new CustomEventMessage(eventFlag, true, ScriptEnvTag.content));
const runtime = new ScriptRuntime(scriptEnvTag, server, msg, scriptExecutor);
const runtime = new ScriptRuntime(scriptEnvTag, server, msg, scriptExecutor, extensionEnv);
runtime.init();

// inject环境,直接判断白名单,注入对外接口
Expand Down
Loading