popup页与注册菜单
Some checks failed
test / Run tests (push) Failing after 3s
build / Build (push) Failing after 6s
Some checks failed
test / Run tests (push) Failing after 3s
build / Build (push) Failing after 6s
This commit is contained in:
@ -14,7 +14,7 @@ export class OffscreenManager {
|
||||
|
||||
private windowMessage = new WindowMessage(window, sandbox, true);
|
||||
|
||||
private windowApi: Server = new Server("offscreen", this.windowMessage);
|
||||
private windowServer: Server = new Server("offscreen", this.windowMessage);
|
||||
|
||||
private messageQueue: MessageQueue = new MessageQueue();
|
||||
|
||||
@ -36,21 +36,21 @@ export class OffscreenManager {
|
||||
|
||||
async initManager() {
|
||||
// 监听消息
|
||||
this.windowApi.on("logger", this.logger.bind(this));
|
||||
this.windowApi.on("preparationSandbox", this.preparationSandbox.bind(this));
|
||||
this.windowApi.on("sendMessageToServiceWorker", this.sendMessageToServiceWorker.bind(this));
|
||||
this.windowServer.on("logger", this.logger.bind(this));
|
||||
this.windowServer.on("preparationSandbox", this.preparationSandbox.bind(this));
|
||||
this.windowServer.on("sendMessageToServiceWorker", this.sendMessageToServiceWorker.bind(this));
|
||||
const script = new ScriptService(
|
||||
this.windowApi.group("script"),
|
||||
this.windowServer.group("script"),
|
||||
this.extensionMessage,
|
||||
this.windowMessage,
|
||||
this.messageQueue
|
||||
);
|
||||
script.init();
|
||||
// 转发从sandbox来的gm api请求
|
||||
forwardMessage("serviceWorker", "runtime/gmApi", this.windowApi, this.extensionMessage);
|
||||
forwardMessage("serviceWorker", "runtime/gmApi", this.windowServer, this.extensionMessage);
|
||||
// 转发message queue请求
|
||||
|
||||
const gmApi = new GMApi(this.windowApi.group("gmApi"));
|
||||
const gmApi = new GMApi(this.windowServer.group("gmApi"));
|
||||
gmApi.init();
|
||||
}
|
||||
}
|
||||
|
@ -27,3 +27,19 @@ export function subscribeScriptRunStatus(
|
||||
) {
|
||||
return messageQueue.subscribe("scriptRunStatus", callback);
|
||||
}
|
||||
|
||||
export type ScriptMenuRegisterCallbackValue = {
|
||||
uuid: string;
|
||||
id: number;
|
||||
name: string;
|
||||
accessKey: string;
|
||||
tabId: number;
|
||||
frameId: number;
|
||||
};
|
||||
|
||||
export function subscribeScriptMenuRegister(
|
||||
messageQueue: MessageQueue,
|
||||
callback: (message: ScriptMenuRegisterCallbackValue) => void
|
||||
) {
|
||||
return messageQueue.subscribe("registerMenuCommand", callback);
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import { Client } from "@Packages/message/client";
|
||||
import { InstallSource } from ".";
|
||||
import { Resource } from "@App/app/repo/resource";
|
||||
import { MessageSend } from "@Packages/message/server";
|
||||
import { ScriptMenu, ScriptMenuItem } from "./popup";
|
||||
|
||||
export class ServiceWorkerClient extends Client {
|
||||
constructor(msg: MessageSend) {
|
||||
@ -90,3 +91,27 @@ export class RuntimeClient extends Client {
|
||||
return this.do("scriptLoad", { flag, uuid });
|
||||
}
|
||||
}
|
||||
|
||||
export type GetPopupDataReq = {
|
||||
tabId: number;
|
||||
url: string;
|
||||
};
|
||||
|
||||
export type GetPopupDataRes = {
|
||||
scriptList: ScriptMenu[];
|
||||
backScriptList: ScriptMenu[];
|
||||
};
|
||||
|
||||
export class PopupClient extends Client {
|
||||
constructor(msg: MessageSend) {
|
||||
super(msg, "serviceWorker/popup");
|
||||
}
|
||||
|
||||
getPopupData(data: GetPopupDataReq): Promise<GetPopupDataRes> {
|
||||
return this.do("getPopupData", data);
|
||||
}
|
||||
|
||||
menuClick(uuid: string, data: ScriptMenuItem) {
|
||||
return this.do("menuClick", { uuid, id: data.id, tabId: data.tabId, frameId: data.frameId });
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import LoggerCore from "@App/app/logger/core";
|
||||
import Logger from "@App/app/logger/logger";
|
||||
import { Script, ScriptDAO } from "@App/app/repo/scripts";
|
||||
import { GetSender, Group, MessageSend, MessageSender } from "@Packages/message/server";
|
||||
import { GetSender, Group, MessageSend } from "@Packages/message/server";
|
||||
import { ValueService } from "@App/app/service/service_worker/value";
|
||||
import PermissionVerify from "./permission_verify";
|
||||
import { connect } from "@Packages/message/client";
|
||||
@ -21,7 +21,6 @@ export type MessageRequest = {
|
||||
|
||||
export type Request = MessageRequest & {
|
||||
script: Script;
|
||||
sender: MessageSender;
|
||||
};
|
||||
|
||||
export type Api = (request: Request, con: GetSender) => Promise<any>;
|
||||
@ -48,7 +47,7 @@ export default class GMApi {
|
||||
if (!api) {
|
||||
return Promise.reject(new Error("gm api is not found"));
|
||||
}
|
||||
const req = await this.parseRequest(data, { tabId: 0 });
|
||||
const req = await this.parseRequest(data);
|
||||
try {
|
||||
await this.permissionVerify.verify(req, api);
|
||||
} catch (e) {
|
||||
@ -59,14 +58,13 @@ export default class GMApi {
|
||||
}
|
||||
|
||||
// 解析请求
|
||||
async parseRequest(data: MessageRequest, sender: MessageSender): Promise<Request> {
|
||||
async parseRequest(data: MessageRequest): Promise<Request> {
|
||||
const script = await this.scriptDAO.get(data.uuid);
|
||||
if (!script) {
|
||||
return Promise.reject(new Error("script is not found"));
|
||||
}
|
||||
const req: Request = <Request>data;
|
||||
req.script = script;
|
||||
req.sender = sender;
|
||||
return Promise.resolve(req);
|
||||
}
|
||||
|
||||
@ -76,8 +74,6 @@ export default class GMApi {
|
||||
return Promise.reject(new Error("param is failed"));
|
||||
}
|
||||
const [key, value] = request.params;
|
||||
const sender = <MessageSender & { runFlag: string }>request.sender;
|
||||
sender.runFlag = request.runFlag;
|
||||
return this.value.setValue(request.script.uuid, key, value);
|
||||
}
|
||||
|
||||
@ -183,8 +179,8 @@ export default class GMApi {
|
||||
}
|
||||
|
||||
@PermissionVerify.API()
|
||||
GM_registerMenuCommand(request: Request, con: GetSender) {
|
||||
console.log("registerMenuCommand", request.params);
|
||||
GM_registerMenuCommand(request: Request, sender: GetSender) {
|
||||
console.log("registerMenuCommand", request.params, sender.getSender(), sender.getSender().tab!.id!);
|
||||
const [id, name, accessKey] = request.params;
|
||||
// 触发菜单注册, 在popup中处理
|
||||
this.mq.emit("registerMenuCommand", {
|
||||
@ -192,24 +188,20 @@ export default class GMApi {
|
||||
id: id,
|
||||
name: name,
|
||||
accessKey: accessKey,
|
||||
con: con.getConnect(),
|
||||
});
|
||||
con.getConnect().onDisconnect(() => {
|
||||
// 取消注册
|
||||
this.mq.emit("unregisterMenuCommand", {
|
||||
uuid: request.script.uuid,
|
||||
name: name,
|
||||
});
|
||||
tabId: sender.getSender().tab!.id!,
|
||||
frameId: sender.getSender().frameId,
|
||||
});
|
||||
}
|
||||
|
||||
@PermissionVerify.API()
|
||||
GM_unregisterMenuCommand(request: Request) {
|
||||
GM_unregisterMenuCommand(request: Request, sender: GetSender) {
|
||||
const [id] = request.params;
|
||||
// 触发菜单取消注册, 在popup中处理
|
||||
this.mq.emit("unregisterMenuCommand", {
|
||||
uuid: request.script.uuid,
|
||||
id: id,
|
||||
tabId: sender.getSender().tab!.id!,
|
||||
frameId: sender.getSender().frameId,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,26 +1,279 @@
|
||||
import { MessageQueue } from "@Packages/message/message_queue";
|
||||
import { GetSender, Group } from "@Packages/message/server";
|
||||
import { RuntimeService } from "./runtime";
|
||||
import { Group } from "@Packages/message/server";
|
||||
import { RuntimeService, ScriptMatchInfo } from "./runtime";
|
||||
import Cache from "@App/app/cache";
|
||||
import { GetPopupDataReq, GetPopupDataRes } from "./client";
|
||||
import {
|
||||
SCRIPT_RUN_STATUS,
|
||||
Metadata,
|
||||
SCRIPT_STATUS_ENABLE,
|
||||
Script,
|
||||
ScriptDAO,
|
||||
SCRIPT_TYPE_NORMAL,
|
||||
} from "@App/app/repo/scripts";
|
||||
import {
|
||||
ScriptMenuRegisterCallbackValue,
|
||||
subscribeScriptDelete,
|
||||
subscribeScriptEnable,
|
||||
subscribeScriptInstall,
|
||||
subscribeScriptMenuRegister,
|
||||
subscribeScriptRunStatus,
|
||||
} from "../queue";
|
||||
|
||||
export type ScriptMenuItem = {
|
||||
id: number;
|
||||
name: string;
|
||||
accessKey?: string;
|
||||
tabId: number | "background";
|
||||
frameId: number;
|
||||
};
|
||||
|
||||
export type ScriptMenu = {
|
||||
uuid: string; // 脚本uuid
|
||||
name: string; // 脚本名称
|
||||
enable: boolean; // 脚本是否启用
|
||||
updatetime: number; // 脚本更新时间
|
||||
hasUserConfig: boolean; // 是否有用户配置
|
||||
metadata: Metadata; // 脚本元数据
|
||||
runStatus?: SCRIPT_RUN_STATUS; // 脚本运行状态
|
||||
runNum: number; // 脚本运行次数
|
||||
runNumByIframe: number; // iframe运行次数
|
||||
menus: ScriptMenuItem[]; // 脚本菜单
|
||||
customExclude: string[]; // 自定义排除
|
||||
};
|
||||
|
||||
// 处理popup页面的数据
|
||||
export class PopupService {
|
||||
scriptDAO = new ScriptDAO();
|
||||
|
||||
constructor(
|
||||
private group: Group,
|
||||
private mq: MessageQueue,
|
||||
private runtime: RuntimeService
|
||||
) {}
|
||||
|
||||
registerMenuCommand(message: { uuid: string; id: string; name: string; accessKey: string; con: GetSender }) {
|
||||
console.log("registerMenuCommand", message);
|
||||
async registerMenuCommand(message: ScriptMenuRegisterCallbackValue) {
|
||||
// 给脚本添加菜单
|
||||
const data = await this.getScriptMenu(message.tabId);
|
||||
const script = data.find((item) => item.uuid === message.uuid);
|
||||
if (script) {
|
||||
const menu = script.menus.find((item) => item.id === message.id);
|
||||
if (!menu) {
|
||||
script.menus.push({
|
||||
id: message.id,
|
||||
name: message.name,
|
||||
accessKey: message.accessKey,
|
||||
tabId: message.tabId,
|
||||
frameId: message.frameId,
|
||||
});
|
||||
} else {
|
||||
menu.name = message.name;
|
||||
menu.accessKey = message.accessKey;
|
||||
menu.tabId = message.tabId;
|
||||
}
|
||||
}
|
||||
console.log(data);
|
||||
Cache.getInstance().set("tabScript:" + message.tabId, data);
|
||||
}
|
||||
|
||||
unregisterMenuCommand(message: { id: string }) {
|
||||
console.log("unregisterMenuCommand", message);
|
||||
async unregisterMenuCommand({ id, uuid, tabId }: { id: number; uuid: string; tabId: number }) {
|
||||
const data = await this.getScriptMenu(tabId);
|
||||
// 删除脚本菜单
|
||||
const script = data.find((item) => item.uuid === uuid);
|
||||
if (script) {
|
||||
script.menus = script.menus.filter((item) => item.id !== id);
|
||||
}
|
||||
Cache.getInstance().set("tabScript:" + tabId, data);
|
||||
}
|
||||
|
||||
scriptToMenu(script: Script): ScriptMenu {
|
||||
return {
|
||||
uuid: script.uuid,
|
||||
name: script.name,
|
||||
enable: script.status === SCRIPT_STATUS_ENABLE,
|
||||
updatetime: script.updatetime || 0,
|
||||
hasUserConfig: !!script.config,
|
||||
metadata: script.metadata,
|
||||
runStatus: script.runStatus,
|
||||
runNum: 0,
|
||||
runNumByIframe: 0,
|
||||
menus: [],
|
||||
customExclude: (script as ScriptMatchInfo).customizeExcludeMatches || [],
|
||||
};
|
||||
}
|
||||
|
||||
// 获取popup页面数据
|
||||
async getPopupData(req: GetPopupDataReq): Promise<GetPopupDataRes> {
|
||||
// 获取当前tabId
|
||||
const scriptUuid = await this.runtime.getPageScriptByUrl(req.url);
|
||||
// 与运行时脚本进行合并
|
||||
const runScript = await this.getScriptMenu(req.tabId);
|
||||
// 筛选出未运行的脚本
|
||||
const notRunScript = scriptUuid.filter((script) => {
|
||||
return !runScript.find((item) => item.uuid === script.uuid);
|
||||
});
|
||||
// 将未运行的脚本转换为菜单
|
||||
const scriptList = notRunScript.map((script): ScriptMenu => {
|
||||
return this.scriptToMenu(script);
|
||||
});
|
||||
runScript.push(...scriptList);
|
||||
// 后台脚本只显示开启或者运行中的脚本
|
||||
|
||||
return { scriptList: runScript, backScriptList: await this.getScriptMenu(-1) };
|
||||
}
|
||||
|
||||
async getScriptMenu(tabId: number) {
|
||||
return ((await Cache.getInstance().get("tabScript:" + tabId)) || []) as ScriptMenu[];
|
||||
}
|
||||
|
||||
async addScriptRunNumber({
|
||||
tabId,
|
||||
frameId,
|
||||
scripts,
|
||||
}: {
|
||||
tabId: number;
|
||||
frameId: number;
|
||||
scripts: ScriptMatchInfo[];
|
||||
}) {
|
||||
if (frameId === undefined) {
|
||||
// 清理数据
|
||||
await Cache.getInstance().del("tabScript:" + tabId);
|
||||
}
|
||||
// 设置数据
|
||||
const data = await this.getScriptMenu(tabId);
|
||||
// 设置脚本运行次数
|
||||
scripts.forEach((script) => {
|
||||
const scriptMenu = data.find((item) => item.uuid === script.uuid);
|
||||
if (scriptMenu) {
|
||||
scriptMenu.runNum = (scriptMenu.runNum || 0) + 1;
|
||||
if (frameId) {
|
||||
scriptMenu.runNumByIframe = (scriptMenu.runNumByIframe || 0) + 1;
|
||||
}
|
||||
} else {
|
||||
const item = this.scriptToMenu(script);
|
||||
item.runNum = 1;
|
||||
if (frameId) {
|
||||
item.runNumByIframe = 1;
|
||||
}
|
||||
data.push(item);
|
||||
}
|
||||
});
|
||||
Cache.getInstance().set("tabScript:" + tabId, data);
|
||||
}
|
||||
|
||||
dealBackgroundScriptInstall() {
|
||||
// 处理后台脚本
|
||||
subscribeScriptInstall(this.mq, async ({ script }) => {
|
||||
if (script.type === SCRIPT_TYPE_NORMAL) {
|
||||
return;
|
||||
}
|
||||
const menu = await this.getScriptMenu(-1);
|
||||
const scriptMenu = menu.find((item) => item.uuid === script.uuid);
|
||||
if (script.status === SCRIPT_STATUS_ENABLE) {
|
||||
// 加入菜单
|
||||
if (!scriptMenu) {
|
||||
const item = this.scriptToMenu(script);
|
||||
menu.push(item);
|
||||
}
|
||||
} else {
|
||||
// 移出菜单
|
||||
if (scriptMenu) {
|
||||
menu.splice(menu.indexOf(scriptMenu), 1);
|
||||
}
|
||||
}
|
||||
Cache.getInstance().set("tabScript:" + -1, menu);
|
||||
});
|
||||
subscribeScriptEnable(this.mq, async ({ uuid }) => {
|
||||
const script = await this.scriptDAO.get(uuid);
|
||||
if (!script) {
|
||||
return;
|
||||
}
|
||||
if (script.type === SCRIPT_TYPE_NORMAL) {
|
||||
return;
|
||||
}
|
||||
const menu = await this.getScriptMenu(-1);
|
||||
const scriptMenu = menu.find((item) => item.uuid === uuid);
|
||||
if (script.status === SCRIPT_STATUS_ENABLE) {
|
||||
// 加入菜单
|
||||
if (!scriptMenu) {
|
||||
const item = this.scriptToMenu(script);
|
||||
menu.push(item);
|
||||
}
|
||||
} else {
|
||||
// 移出菜单
|
||||
if (scriptMenu) {
|
||||
menu.splice(menu.indexOf(scriptMenu), 1);
|
||||
}
|
||||
}
|
||||
Cache.getInstance().set("tabScript:" + -1, menu);
|
||||
});
|
||||
subscribeScriptDelete(this.mq, async ({ uuid }) => {
|
||||
const menu = await this.getScriptMenu(-1);
|
||||
const scriptMenu = menu.find((item) => item.uuid === uuid);
|
||||
if (scriptMenu) {
|
||||
menu.splice(menu.indexOf(scriptMenu), 1);
|
||||
Cache.getInstance().set("tabScript:" + -1, menu);
|
||||
}
|
||||
});
|
||||
subscribeScriptRunStatus(this.mq, async ({ uuid, runStatus }) => {
|
||||
const menu = await this.getScriptMenu(-1);
|
||||
const scriptMenu = menu.find((item) => item.uuid === uuid);
|
||||
if (scriptMenu) {
|
||||
scriptMenu.runStatus = runStatus;
|
||||
Cache.getInstance().set("tabScript:" + -1, menu);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
menuClick({ uuid, id, tabId, frameId }: { uuid: string; id: number; tabId: number; frameId: number }) {
|
||||
// 菜单点击事件
|
||||
console.log("click menu", uuid, id, tabId);
|
||||
this.runtime.sendMessageToTab(tabId, "menuClick", {
|
||||
uuid,
|
||||
id,
|
||||
tabId,
|
||||
frameId,
|
||||
});
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
init() {
|
||||
// 处理脚本菜单数据
|
||||
this.mq.subscribe("registerMenuCommand", this.registerMenuCommand.bind(this));
|
||||
subscribeScriptMenuRegister(this.mq, this.registerMenuCommand.bind(this));
|
||||
this.mq.subscribe("unregisterMenuCommand", this.unregisterMenuCommand.bind(this));
|
||||
this.group.on("getPopupData", this.getPopupData.bind(this));
|
||||
this.group.on("menuClick", this.menuClick.bind(this));
|
||||
this.dealBackgroundScriptInstall();
|
||||
|
||||
// 监听tab开关
|
||||
chrome.tabs.onRemoved.addListener((tabId) => {
|
||||
// 清理数据
|
||||
Cache.getInstance().del("tabScript:" + tabId);
|
||||
});
|
||||
// 监听运行次数
|
||||
this.mq.subscribe(
|
||||
"pageLoad",
|
||||
async ({ tabId, frameId, scripts }: { tabId: number; frameId: number; scripts: ScriptMatchInfo[] }) => {
|
||||
this.addScriptRunNumber({ tabId, frameId, scripts });
|
||||
// 设置角标和脚本
|
||||
chrome.action.getBadgeText(
|
||||
{
|
||||
tabId: tabId,
|
||||
},
|
||||
(res: string) => {
|
||||
if (res || scripts.length) {
|
||||
chrome.action.setBadgeText({
|
||||
text: (scripts.length + (parseInt(res, 10) || 0)).toString(),
|
||||
tabId: tabId,
|
||||
});
|
||||
chrome.action.setBadgeBackgroundColor({
|
||||
color: "#4e5969",
|
||||
tabId: tabId,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,14 @@
|
||||
import { MessageQueue } from "@Packages/message/message_queue";
|
||||
import { GetSender, Group, MessageSend } from "@Packages/message/server";
|
||||
import { Script, SCRIPT_STATUS_ENABLE, SCRIPT_TYPE_NORMAL, ScriptDAO, ScriptRunResouce } from "@App/app/repo/scripts";
|
||||
import {
|
||||
Script,
|
||||
SCRIPT_STATUS,
|
||||
SCRIPT_STATUS_DISABLE,
|
||||
SCRIPT_STATUS_ENABLE,
|
||||
SCRIPT_TYPE_NORMAL,
|
||||
ScriptDAO,
|
||||
ScriptRunResouce,
|
||||
} from "@App/app/repo/scripts";
|
||||
import { ValueService } from "./value";
|
||||
import GMApi from "./gm_api";
|
||||
import { subscribeScriptDelete, subscribeScriptEnable, subscribeScriptInstall } from "../queue";
|
||||
@ -11,17 +19,21 @@ import { randomString } from "@App/pkg/utils/utils";
|
||||
import { compileInjectScript } from "@App/runtime/content/utils";
|
||||
import Cache from "@App/app/cache";
|
||||
import { dealPatternMatches, UrlMatch } from "@App/pkg/utils/match";
|
||||
import { ExtensionContentMessageSend } from "@Packages/message/extension_message";
|
||||
import { sendMessage } from "@Packages/message/client";
|
||||
|
||||
// 为了优化性能,存储到缓存时删除了code与value
|
||||
export interface ScriptMatchInfo extends ScriptRunResouce {
|
||||
matches: string[];
|
||||
excludeMatches: string[];
|
||||
customizeExcludeMatches: string[];
|
||||
}
|
||||
|
||||
export class RuntimeService {
|
||||
scriptDAO: ScriptDAO = new ScriptDAO();
|
||||
|
||||
scriptMatch: UrlMatch<string> = new UrlMatch<string>();
|
||||
scriptCustomizeMatch: UrlMatch<string> = new UrlMatch<string>();
|
||||
scriptMatchCache: Map<string, ScriptMatchInfo> | null | undefined;
|
||||
|
||||
constructor(
|
||||
@ -52,11 +64,10 @@ export class RuntimeService {
|
||||
// 如果是普通脚本, 在service worker中进行注册
|
||||
// 如果是后台脚本, 在offscreen中进行处理
|
||||
if (script.type === SCRIPT_TYPE_NORMAL) {
|
||||
// 注册入页面脚本
|
||||
if (data.enable) {
|
||||
this.registryPageScript(script);
|
||||
} else {
|
||||
this.unregistryPageScript(script);
|
||||
// 加载页面脚本
|
||||
await this.loadPageScript(script);
|
||||
if (!data.enable) {
|
||||
await this.unregistryPageScript(script);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -67,7 +78,7 @@ export class RuntimeService {
|
||||
return;
|
||||
}
|
||||
if (script.type === SCRIPT_TYPE_NORMAL) {
|
||||
this.registryPageScript(script);
|
||||
await this.loadPageScript(script);
|
||||
}
|
||||
});
|
||||
// 监听脚本删除
|
||||
@ -77,7 +88,8 @@ export class RuntimeService {
|
||||
return;
|
||||
}
|
||||
if (script.type === SCRIPT_TYPE_NORMAL) {
|
||||
this.unregistryPageScript(script);
|
||||
await this.unregistryPageScript(script);
|
||||
this.deleteScriptMatch(script.uuid);
|
||||
}
|
||||
});
|
||||
|
||||
@ -85,20 +97,22 @@ export class RuntimeService {
|
||||
const scriptDao = new ScriptDAO();
|
||||
const list = await scriptDao.all();
|
||||
list.forEach((script) => {
|
||||
if (script.status !== SCRIPT_STATUS_ENABLE || script.type !== SCRIPT_TYPE_NORMAL) {
|
||||
if (script.type !== SCRIPT_TYPE_NORMAL) {
|
||||
return;
|
||||
}
|
||||
this.mq.publish("enableScript", { uuid: script.uuid, enable: true });
|
||||
this.mq.publish("enableScript", { uuid: script.uuid, enable: script.status === SCRIPT_STATUS_ENABLE });
|
||||
});
|
||||
// 监听offscreen环境初始化, 初始化完成后, 再将后台脚本运行起来
|
||||
this.mq.subscribe("preparationOffscreen", () => {
|
||||
list.forEach((script) => {
|
||||
if (script.status !== SCRIPT_STATUS_ENABLE || script.type === SCRIPT_TYPE_NORMAL) {
|
||||
if (script.type === SCRIPT_TYPE_NORMAL) {
|
||||
return;
|
||||
}
|
||||
this.mq.publish("enableScript", { uuid: script.uuid, enable: true });
|
||||
this.mq.publish("enableScript", { uuid: script.uuid, enable: script.status === SCRIPT_STATUS_ENABLE });
|
||||
});
|
||||
});
|
||||
|
||||
this.loadScriptMatchInfo();
|
||||
}
|
||||
|
||||
messageFlag() {
|
||||
@ -107,23 +121,72 @@ export class RuntimeService {
|
||||
});
|
||||
}
|
||||
|
||||
async pageLoad(_, sender: GetSender) {
|
||||
const [scriptFlag, match] = await Promise.all([this.messageFlag(), this.loadScriptMatchInfo()]);
|
||||
// 给指定tab发送消息
|
||||
sendMessageToTab(tabId: number, action: string, data: any) {
|
||||
if (tabId === -1) {
|
||||
// 如果是-1, 代表给offscreen发送消息
|
||||
return sendMessage(this.sender, "offscreen/runtime/" + action, data);
|
||||
}
|
||||
return sendMessage(new ExtensionContentMessageSend(tabId), "content/runtime/" + action, data);
|
||||
}
|
||||
|
||||
async getPageScriptUuidByUrl(url: string) {
|
||||
const match = await this.loadScriptMatchInfo();
|
||||
// 匹配当前页面的脚本
|
||||
const matchScriptUuid = match.match(url!);
|
||||
// 排除自定义匹配
|
||||
const excludeScriptUuid = this.scriptCustomizeMatch.match(url!);
|
||||
const excludeMatch = new Set<string>();
|
||||
excludeScriptUuid.forEach((uuid) => {
|
||||
excludeMatch.add(uuid);
|
||||
});
|
||||
return matchScriptUuid.filter((value) => {
|
||||
// 过滤掉自定义排除的脚本
|
||||
return !excludeMatch.has(value);
|
||||
});
|
||||
}
|
||||
|
||||
async getPageScriptByUrl(url: string) {
|
||||
const matchScriptUuid = await this.getPageScriptUuidByUrl(url);
|
||||
return matchScriptUuid.map((uuid) => {
|
||||
return Object.assign({}, this.scriptMatchCache?.get(uuid));
|
||||
});
|
||||
}
|
||||
|
||||
async pageLoad(_: any, sender: GetSender) {
|
||||
const [scriptFlag] = await Promise.all([this.messageFlag(), this.loadScriptMatchInfo()]);
|
||||
const chromeSender = sender.getSender() as chrome.runtime.MessageSender;
|
||||
|
||||
// 匹配当前页面的脚本
|
||||
const matchScriptUuid = match.match(chromeSender.url!);
|
||||
const matchScriptUuid = await this.getPageScriptUuidByUrl(chromeSender.url!);
|
||||
|
||||
const scripts = await Promise.all(
|
||||
matchScriptUuid.map(
|
||||
(uuid) =>
|
||||
new Promise((resolve) => {
|
||||
// 获取value
|
||||
const scriptRes = Object.assign({}, this.scriptMatchCache?.get(uuid));
|
||||
resolve(scriptRes);
|
||||
})
|
||||
)
|
||||
matchScriptUuid.map(async (uuid): Promise<undefined | ScriptRunResouce> => {
|
||||
const scriptRes = Object.assign({}, this.scriptMatchCache?.get(uuid));
|
||||
// 判断脚本是否开启
|
||||
if (scriptRes.status === SCRIPT_STATUS_DISABLE) {
|
||||
return undefined;
|
||||
}
|
||||
// 如果是iframe,判断是否允许在iframe里运行
|
||||
if (chromeSender.frameId !== undefined) {
|
||||
if (scriptRes.metadata.noframes) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
// 获取value
|
||||
return scriptRes;
|
||||
})
|
||||
);
|
||||
return Promise.resolve({ flag: scriptFlag, scripts: scripts });
|
||||
|
||||
const enableScript = scripts.filter((item) => item);
|
||||
|
||||
this.mq.emit("pageLoad", {
|
||||
tabId: chromeSender.tab?.id,
|
||||
frameId: chromeSender.frameId,
|
||||
scripts: enableScript,
|
||||
});
|
||||
|
||||
return Promise.resolve({ flag: scriptFlag, scripts: enableScript });
|
||||
}
|
||||
|
||||
// 停止脚本
|
||||
@ -174,7 +237,7 @@ export class RuntimeService {
|
||||
});
|
||||
}
|
||||
|
||||
loadScripting: Promise<void> | null | undefined;
|
||||
loadingScript: Promise<void> | null | undefined;
|
||||
|
||||
// 加载脚本匹配信息,由于service_worker的机制,如果由不活动状态恢复过来时,会优先触发事件
|
||||
// 可能当时会没有脚本匹配信息,所以使用脚本信息时,尽量使用此方法获取
|
||||
@ -182,29 +245,33 @@ export class RuntimeService {
|
||||
if (this.scriptMatchCache) {
|
||||
return this.scriptMatch;
|
||||
}
|
||||
if (this.loadScripting) {
|
||||
await this.loadScripting;
|
||||
if (this.loadingScript) {
|
||||
await this.loadingScript;
|
||||
} else {
|
||||
// 如果没有缓存, 则创建一个新的缓存
|
||||
this.loadScripting = Cache.getInstance()
|
||||
const cache = new Map<string, ScriptMatchInfo>();
|
||||
this.loadingScript = Cache.getInstance()
|
||||
.get("scriptMatch")
|
||||
.then((data: { [key: string]: ScriptMatchInfo }) => {
|
||||
this.scriptMatchCache = new Map<string, ScriptMatchInfo>();
|
||||
if (data) {
|
||||
Object.keys(data).forEach((key) => {
|
||||
const item = data[key];
|
||||
this.scriptMatchCache!.set(item.uuid, item);
|
||||
cache.set(item.uuid, item);
|
||||
item.matches.forEach((match) => {
|
||||
this.scriptMatch.add(match, item.uuid);
|
||||
});
|
||||
item.excludeMatches.forEach((match) => {
|
||||
this.scriptMatch.exclude(match, item.uuid);
|
||||
});
|
||||
item.customizeExcludeMatches.forEach((match) => {
|
||||
this.scriptCustomizeMatch.exclude(match, item.uuid);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
await this.loadScripting;
|
||||
this.loadScripting = null;
|
||||
await this.loadingScript;
|
||||
this.loadingScript = null;
|
||||
this.scriptMatchCache = cache;
|
||||
}
|
||||
return this.scriptMatch;
|
||||
}
|
||||
@ -229,28 +296,43 @@ export class RuntimeService {
|
||||
await this.loadScriptMatchInfo();
|
||||
}
|
||||
this.scriptMatchCache!.set(item.uuid, item);
|
||||
// 清理一下老数据
|
||||
this.scriptMatch.del(item.uuid);
|
||||
this.scriptCustomizeMatch.del(item.uuid);
|
||||
// 添加新的数据
|
||||
item.matches.forEach((match) => {
|
||||
this.scriptMatch.add(match, item.uuid);
|
||||
});
|
||||
item.excludeMatches.forEach((match) => {
|
||||
this.scriptMatch.exclude(match, item.uuid);
|
||||
});
|
||||
item.customizeExcludeMatches.forEach((match) => {
|
||||
this.scriptCustomizeMatch.exclude(match, item.uuid);
|
||||
});
|
||||
this.saveScriptMatchInfo();
|
||||
}
|
||||
|
||||
async deleteScriptMatch(uuid: string) {
|
||||
async updateScriptStatus(uuid: string, status: SCRIPT_STATUS) {
|
||||
if (!this.scriptMatchCache) {
|
||||
await this.loadScriptMatchInfo();
|
||||
}
|
||||
this.scriptMatchCache!.delete(uuid);
|
||||
this.scriptMatch.del(uuid);
|
||||
this.scriptMatchCache!.get(uuid)!.status = status;
|
||||
this.saveScriptMatchInfo();
|
||||
}
|
||||
|
||||
async registryPageScript(script: Script) {
|
||||
if (await Cache.getInstance().has("registryScript:" + script.uuid)) {
|
||||
deleteScriptMatch(uuid: string) {
|
||||
if (!this.scriptMatchCache) {
|
||||
return;
|
||||
}
|
||||
this.scriptMatchCache.delete(uuid);
|
||||
this.scriptMatch.del(uuid);
|
||||
this.scriptCustomizeMatch.del(uuid);
|
||||
this.saveScriptMatchInfo();
|
||||
}
|
||||
|
||||
// 加载页面脚本, 会把脚本信息放入缓存中
|
||||
// 如果脚本开启, 则注册脚本
|
||||
async loadPageScript(script: Script) {
|
||||
const matches = script.metadata["match"];
|
||||
if (!matches) {
|
||||
return;
|
||||
@ -262,7 +344,7 @@ export class RuntimeService {
|
||||
matches.push(...(script.metadata["include"] || []));
|
||||
const patternMatches = dealPatternMatches(matches);
|
||||
const scriptMatchInfo: ScriptMatchInfo = Object.assign(
|
||||
{ matches: patternMatches.result, excludeMatches: [] },
|
||||
{ matches: patternMatches.result, excludeMatches: [], customizeExcludeMatches: [] },
|
||||
scriptRes
|
||||
);
|
||||
|
||||
@ -272,30 +354,46 @@ export class RuntimeService {
|
||||
matches: patternMatches.patternResult,
|
||||
world: "MAIN",
|
||||
};
|
||||
if (!script.metadata["noframes"]) {
|
||||
registerScript.allFrames = true;
|
||||
}
|
||||
|
||||
if (script.metadata["exclude-match"]) {
|
||||
const excludeMatches = script.metadata["exclude-match"];
|
||||
excludeMatches.push(...(script.metadata["exclude"] || []));
|
||||
if (script.metadata["exclude"]) {
|
||||
const excludeMatches = script.metadata["exclude"];
|
||||
const result = dealPatternMatches(excludeMatches);
|
||||
|
||||
registerScript.excludeMatches = result.patternResult;
|
||||
scriptMatchInfo.excludeMatches = result.result;
|
||||
}
|
||||
if (script.metadata["run-at"]) {
|
||||
registerScript.runAt = getRunAt(script.metadata["run-at"]);
|
||||
// 自定义排除
|
||||
if (script.selfMetadata && script.selfMetadata.exclude) {
|
||||
const excludeMatches = script.selfMetadata.exclude;
|
||||
const result = dealPatternMatches(excludeMatches);
|
||||
|
||||
if (!registerScript.excludeMatches) {
|
||||
registerScript.excludeMatches = [];
|
||||
}
|
||||
registerScript.excludeMatches.push(...result.patternResult);
|
||||
scriptMatchInfo.customizeExcludeMatches = result.result;
|
||||
}
|
||||
|
||||
// 将脚本match信息放入缓存中
|
||||
this.addScriptMatch(scriptMatchInfo);
|
||||
|
||||
// 如果脚本开启, 则注册脚本
|
||||
if (script.status === SCRIPT_STATUS_ENABLE) {
|
||||
if (!script.metadata["noframes"]) {
|
||||
registerScript.allFrames = true;
|
||||
}
|
||||
if (script.metadata["run-at"]) {
|
||||
registerScript.runAt = getRunAt(script.metadata["run-at"]);
|
||||
}
|
||||
await chrome.userScripts.register([registerScript]);
|
||||
await Cache.getInstance().set("registryScript:" + script.uuid, true);
|
||||
}
|
||||
chrome.userScripts.register([registerScript], async () => {
|
||||
// 标记为已注册
|
||||
Cache.getInstance().set("registryScript:" + script.uuid, true);
|
||||
// 将脚本match信息放入缓存中
|
||||
this.addScriptMatch(scriptMatchInfo);
|
||||
});
|
||||
}
|
||||
|
||||
unregistryPageScript(script: Script) {
|
||||
async unregistryPageScript(script: Script) {
|
||||
if (!(await Cache.getInstance().get("registryScript:" + script.uuid))) {
|
||||
return;
|
||||
}
|
||||
chrome.userScripts.unregister(
|
||||
{
|
||||
ids: [script.uuid],
|
||||
@ -303,6 +401,8 @@ export class RuntimeService {
|
||||
() => {
|
||||
// 删除缓存
|
||||
Cache.getInstance().del("registryScript:" + script.uuid);
|
||||
// 修改脚本状态为disable
|
||||
this.updateScriptStatus(script.uuid, SCRIPT_STATUS_DISABLE);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -17,3 +17,45 @@ export function getRunAt(runAts: string[]): chrome.userScripts.RunAt {
|
||||
}
|
||||
return "document_idle";
|
||||
}
|
||||
|
||||
export function mapToObject(map: Map<string, any>): { [key: string]: any } {
|
||||
const obj: { [key: string]: any } = {};
|
||||
map.forEach((value, key) => {
|
||||
if (value instanceof Map) {
|
||||
obj[key] = mapToObject(value);
|
||||
} else if (obj[key] instanceof Array) {
|
||||
obj[key].push(value);
|
||||
} else {
|
||||
obj[key] = value;
|
||||
}
|
||||
});
|
||||
return obj;
|
||||
}
|
||||
|
||||
export function objectToMap(obj: { [key: string]: any }): Map<string, any> {
|
||||
const map = new Map<string, any>();
|
||||
Object.keys(obj).forEach((key) => {
|
||||
if (obj[key] instanceof Map) {
|
||||
map.set(key, objectToMap(obj[key]));
|
||||
} else if (obj[key] instanceof Array) {
|
||||
map.set(key, obj[key]);
|
||||
} else {
|
||||
map.set(key, obj[key]);
|
||||
}
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
export function arrayToObject(arr: Array<any>): any[] {
|
||||
const obj: any[] = [];
|
||||
arr.forEach((item) => {
|
||||
if (item instanceof Map) {
|
||||
obj.push(mapToObject(item));
|
||||
} else if (item instanceof Array) {
|
||||
obj.push(arrayToObject(item));
|
||||
} else {
|
||||
obj.push(item);
|
||||
}
|
||||
});
|
||||
return obj;
|
||||
}
|
||||
|
Reference in New Issue
Block a user