Skip to content

Commit a8a99b9

Browse files
committed
兼容 FF: GM_setClipboard
1 parent 7d85856 commit a8a99b9

File tree

5 files changed

+72
-31
lines changed

5 files changed

+72
-31
lines changed

src/app/service/content/gm_api/gm_api.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1278,7 +1278,19 @@ export default class GMApi extends GM_Base {
12781278
@GMContext.API({})
12791279
GM_setClipboard(data: string, info?: GMTypes.GMClipboardInfo, cb?: () => void) {
12801280
if (this.isInvalidContext()) return;
1281-
this.sendMessage("GM_setClipboard", [data, info])
1281+
// 物件参数意义不明。日后再检视特殊处理
1282+
// 未支持 TM4.19+ application/octet-stream
1283+
// 参考: https://github.com/Tampermonkey/tampermonkey/issues/1250
1284+
let mimetype: string | undefined;
1285+
if (typeof info === "object" && info?.mimetype) {
1286+
mimetype = info.mimetype;
1287+
} else {
1288+
mimetype = (typeof info === "string" ? info : info?.type) || "text/plain";
1289+
if (mimetype === "text") mimetype = "text/plain";
1290+
else if (mimetype === "html") mimetype = "text/html";
1291+
}
1292+
data = `${data}`; // 强制 string type
1293+
this.sendMessage("GM_setClipboard", [data, mimetype])
12821294
.then(() => {
12831295
if (typeof cb === "function") {
12841296
cb();
@@ -1294,7 +1306,11 @@ export default class GMApi extends GM_Base {
12941306
@GMContext.API({ depend: ["GM_setClipboard"] })
12951307
["GM.setClipboard"](data: string, info?: string | { type?: string; mimetype?: string }): Promise<void> {
12961308
if (this.isInvalidContext()) return new Promise<void>(() => {});
1297-
return this.sendMessage("GM_setClipboard", [data, info]);
1309+
return new Promise<void>((resolve) => {
1310+
this.GM_setClipboard(data, info, () => {
1311+
resolve();
1312+
});
1313+
});
12981314
}
12991315

13001316
@GMContext.API()

src/app/service/content/utils.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,7 @@ export function compileScriptCode(scriptRes: ScriptRunResource, scriptCode?: str
4242

4343
export function compileScriptCodeByResource(resource: CompileScriptCodeResource): string {
4444
const sourceURL = `//# sourceURL=${chrome.runtime.getURL(`/${encodeURI(resource.name)}.user.js`)}`;
45-
const requireCode = resource.require.map((r) => r.content).join("\n;");
46-
const preCode = requireCode; // 不需要 async 封装
45+
const preCode = resource.require.map((r) => r.content).join("\n;");
4746
const code = [resource.code, sourceURL].join("\n"); // 需要 async 封装, 可top-level await
4847
// context 和 name 以unnamed arguments方式导入。避免代码能直接以变量名存取
4948
// this = context: globalThis

src/app/service/offscreen/gm_api.ts

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { BgGMXhr } from "@App/pkg/utils/xhr/bg_gm_xhr";
22
import type { IGetSender, Group } from "@Packages/message/server";
3+
import { mightPrepareSetClipboard, setClipboard } from "../service_worker/clipboard";
34

45
export default class GMApi {
56
constructor(private group: Group) {}
@@ -11,32 +12,12 @@ export default class GMApi {
1112
bgGmXhr.do();
1213
}
1314

14-
textarea: HTMLTextAreaElement = document.createElement("textarea");
15-
16-
clipboardData: { type?: string; data: string } | undefined;
17-
18-
async setClipboard({ data, type }: { data: string; type: string }) {
19-
this.clipboardData = {
20-
type,
21-
data,
22-
};
23-
this.textarea.focus();
24-
document.execCommand("copy", false, <any>null);
15+
async setClipboard({ data, mimetype }: { data: string; mimetype: string }) {
16+
setClipboard(data, mimetype);
2517
}
2618

2719
init() {
28-
this.textarea.style.display = "none";
29-
document.documentElement.appendChild(this.textarea);
30-
document.addEventListener("copy", (e: ClipboardEvent) => {
31-
if (!this.clipboardData || !e.clipboardData) {
32-
return;
33-
}
34-
e.preventDefault();
35-
const { type, data } = this.clipboardData;
36-
e.clipboardData.setData(type || "text/plain", data);
37-
this.clipboardData = undefined;
38-
});
39-
20+
mightPrepareSetClipboard();
4021
this.group.on("xmlHttpRequest", this.xmlHttpRequest.bind(this));
4122
this.group.on("setClipboard", this.setClipboard.bind(this));
4223
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
let textareaDOM: HTMLTextAreaElement | undefined;
2+
let customClipboardData: { mimetype: string; data: string } | undefined;
3+
4+
// 抽出成独立处理。日后有需要可以改成 chrome API
5+
export const setClipboard = (data: string, mimetype: string) => {
6+
if (!textareaDOM) {
7+
throw new Error("mightPrepareSetClipboard shall be called first.");
8+
}
9+
customClipboardData = {
10+
mimetype,
11+
data,
12+
};
13+
textareaDOM!.focus();
14+
document.execCommand("copy", false, <any>null);
15+
};
16+
17+
// 设置 setClipboard 相关DOM
18+
export const mightPrepareSetClipboard = () => {
19+
if (textareaDOM) {
20+
return;
21+
}
22+
if (typeof document !== "object") {
23+
throw new Error(
24+
"mightPrepareSetClipboard shall be only called in either Chrome offscreen or FF background script."
25+
);
26+
}
27+
textareaDOM = document.createElement("textarea") as HTMLTextAreaElement;
28+
textareaDOM.style.display = "none";
29+
document.documentElement.appendChild(textareaDOM);
30+
document.addEventListener("copy", (e: ClipboardEvent) => {
31+
if (!customClipboardData || !e?.clipboardData?.setData) {
32+
return;
33+
}
34+
e.preventDefault();
35+
const { mimetype, data } = customClipboardData;
36+
customClipboardData = undefined;
37+
e.clipboardData.setData(mimetype || "text/plain", data);
38+
});
39+
};

src/app/service/service_worker/gm_api/gm_api.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import {
4242
} from "./gm_xhr";
4343
import { headerModifierMap, headersReceivedMap } from "./gm_xhr";
4444
import { BgGMXhr } from "@App/pkg/utils/xhr/bg_gm_xhr";
45+
import { mightPrepareSetClipboard, setClipboard } from "../clipboard";
4546

4647
let generatedUniqueMarkerIDs = "";
4748
let generatedUniqueMarkerIDWhen = "";
@@ -1242,10 +1243,15 @@ export default class GMApi {
12421243
}
12431244

12441245
@PermissionVerify.API()
1245-
async GM_setClipboard(request: GMApiRequest<[string, GMTypes.GMClipboardInfo?]>, _sender: IGetSender) {
1246-
const [data, type] = request.params;
1247-
const clipboardType = type || "text/plain";
1248-
await sendMessage(this.msgSender, "offscreen/gmApi/setClipboard", { data, type: clipboardType });
1246+
async GM_setClipboard(request: GMApiRequest<[string, string]>, _sender: IGetSender) {
1247+
const [data, mimetype] = request.params;
1248+
if (typeof document === "object" && document?.documentElement) {
1249+
// FF background script
1250+
mightPrepareSetClipboard();
1251+
setClipboard(data, mimetype);
1252+
} else {
1253+
await sendMessage(this.msgSender, "offscreen/gmApi/setClipboard", { data, mimetype });
1254+
}
12491255
}
12501256

12511257
@PermissionVerify.API()

0 commit comments

Comments
 (0)