From ecab4ab6344c940c82c7d0829232154d7ca0adaf Mon Sep 17 00:00:00 2001 From: Gabe Date: Wed, 15 Oct 2025 21:41:09 +0800 Subject: [PATCH] feat: support subtitle translate for userscript --- config-overrides.js | 2 -- src/common.js | 8 +++-- src/injector.js | 22 ++------------ src/libs/injector.js | 41 +++++++++++++------------- src/libs/translator.js | 19 +++--------- src/libs/trustedTypes.js | 33 +++++++++++++++++++++ src/subtitle/XMLHttpRequestInjector.js | 19 ++++++++++++ src/subtitle/YouTubeCaptionProvider.js | 1 - src/subtitle/subtitle.js | 14 +++------ 9 files changed, 89 insertions(+), 70 deletions(-) create mode 100644 src/libs/trustedTypes.js create mode 100644 src/subtitle/XMLHttpRequestInjector.js diff --git a/config-overrides.js b/config-overrides.js index b74b5b4..b05308c 100644 --- a/config-overrides.js +++ b/config-overrides.js @@ -91,7 +91,6 @@ const userscriptWebpack = (config, env) => { // @grant GM.getValue // @grant GM.deleteValue // @grant GM.info -// @grant GM.addElement // @grant unsafeWindow // @connect translate.googleapis.com // @connect translate-pa.googleapis.com @@ -132,7 +131,6 @@ const userscriptWebpack = (config, env) => { config.entry = { main: paths.appIndexJs, options: paths.appSrc + "/options.js", - injector: paths.appSrc + "/injector.js", "kiss-translator.user": paths.appSrc + "/userscript.js", }; diff --git a/src/common.js b/src/common.js index a49bc84..2945ec8 100644 --- a/src/common.js +++ b/src/common.js @@ -20,6 +20,7 @@ import { trySyncAllSubRules } from "./libs/subRules"; import { isInBlacklist } from "./libs/blacklist"; import { runSubtitle } from "./subtitle/subtitle"; import { logger } from "./libs/log"; +import { injectInlineJs } from "./libs/injector"; /** * 油猴脚本设置页面 @@ -35,9 +36,10 @@ function runSettingPage() { const ping = genEventName(); window.addEventListener(ping, handlePing); // window.eval(`(${injectScript})("${ping}")`); // eslint-disable-line - const script = document.createElement("script"); - script.textContent = `(${injectScript})("${ping}")`; - document.head.append(script); + injectInlineJs( + `(${injectScript})("${ping}")`, + "kiss-translator-options-injector" + ); } } diff --git a/src/injector.js b/src/injector.js index b4a69b7..b237a93 100644 --- a/src/injector.js +++ b/src/injector.js @@ -1,19 +1,3 @@ -(function () { - const originalOpen = XMLHttpRequest.prototype.open; - XMLHttpRequest.prototype.open = function (...args) { - const url = args[1]; - if (typeof url === "string" && url.includes("timedtext")) { - this.addEventListener("load", function () { - window.postMessage( - { - type: "KISS_XHR_DATA_YOUTUBE", - url: this.responseURL, - response: this.responseText, - }, - window.location.origin - ); - }); - } - return originalOpen.apply(this, args); - }; -})(); +import { XMLHttpRequestInjector } from "./subtitle/XMLHttpRequestInjector"; + +XMLHttpRequestInjector(); diff --git a/src/libs/injector.js b/src/libs/injector.js index 8adaa4f..7a88efa 100644 --- a/src/libs/injector.js +++ b/src/libs/injector.js @@ -1,28 +1,29 @@ -// Function to inject inline JavaScript code -export const injectInlineJs = (code) => { - const el = document.createElement("script"); - el.setAttribute("data-source", "kiss-inject injectInlineJs"); - el.setAttribute("type", "text/javascript"); - el.textContent = code; - document.body?.appendChild(el); -}; +import { trustedTypesHelper } from "./trustedTypes"; -// Function to inject external JavaScript file -export const injectExternalJs = (src, id = "kiss-translator-injector") => { +// Function to inject inline JavaScript code +export const injectInlineJs = (code, id = "kiss-translator-inline-js") => { if (document.getElementById(id)) { return; } - // const el = document.createElement("script"); - // el.setAttribute("data-source", "kiss-inject injectExternalJs"); - // el.setAttribute("type", "text/javascript"); - // el.setAttribute("src", src); - // el.setAttribute("id", id); - // document.body?.appendChild(el); - const script = document.createElement("script"); - script.id = id; - script.src = src; - (document.head || document.documentElement).appendChild(script); + const el = document.createElement("script"); + el.type = "text/javascript"; + el.id = id; + el.textContent = trustedTypesHelper.createScript(code); + (document.head || document.documentElement).appendChild(el); +}; + +// Function to inject external JavaScript file +export const injectExternalJs = (src, id = "kiss-translator-external-js") => { + if (document.getElementById(id)) { + return; + } + + const el = document.createElement("script"); + el.type = "text/javascript"; + el.id = id; + el.src = trustedTypesHelper.createScriptURL(src); + (document.head || document.documentElement).appendChild(el); }; // Function to inject internal CSS code diff --git a/src/libs/translator.js b/src/libs/translator.js index 0520889..291858f 100644 --- a/src/libs/translator.js +++ b/src/libs/translator.js @@ -37,6 +37,7 @@ import { browser } from "./browser"; import { isIframe, sendIframeMsg } from "./iframe"; import { TransboxManager } from "./tranbox"; import { InputTranslator } from "./inputTranslate"; +import { trustedTypesHelper } from "./trustedTypes"; /** * @class Translator @@ -1025,7 +1026,7 @@ export class Translator { translatedText, placeholderMap ); - const trustedHTML = this.#createTrustedHTML(htmlString); + const trustedHTML = trustedTypesHelper.createHTML(htmlString); // const parser = new DOMParser(); // const doc = parser.parseFromString(trustedHTML, "text/html"); @@ -1076,19 +1077,6 @@ export class Translator { } } - #createTrustedHTML(html) { - if (window.trustedTypes && window.trustedTypes.createPolicy) { - const policy = window.trustedTypes.createPolicy( - "kiss-translator-policy#html", - { - createHTML: (input) => input, - } - ); - return policy.createHTML(html); - } - return html; - } - // 处理节点转为翻译字符串 #serializeForTranslation(nodes) { let replaceCounter = 0; // {{n}} @@ -1404,7 +1392,8 @@ export class Translator { injectJs && sendBgMsg(MSG_INJECT_JS, injectJs); injectCss && sendBgMsg(MSG_INJECT_CSS, injectCss); } else { - injectJs && injectInlineJs(injectJs); + injectJs && + injectInlineJs(injectJs, "kiss-translator-userinit-injector"); injectCss && injectInternalCss(injectCss); } } catch (err) { diff --git a/src/libs/trustedTypes.js b/src/libs/trustedTypes.js new file mode 100644 index 0000000..7e97b7e --- /dev/null +++ b/src/libs/trustedTypes.js @@ -0,0 +1,33 @@ +export const trustedTypesHelper = (() => { + const POLICY_NAME = "kiss-translator-policy"; + let policy = null; + + if (globalThis.trustedTypes && globalThis.trustedTypes.createPolicy) { + try { + policy = globalThis.trustedTypes.createPolicy(POLICY_NAME, { + createHTML: (string) => string, + createScript: (string) => string, + createScriptURL: (string) => string, + }); + } catch (err) { + if (err.message.includes("already exists")) { + policy = globalThis.trustedTypes.policies.get(POLICY_NAME); + } else { + console.error("cont create Trusted Types", err); + } + } + } + + return { + createHTML: (htmlString) => { + return policy ? policy.createHTML(htmlString) : htmlString; + }, + createScript: (scriptString) => { + return policy ? policy.createScript(scriptString) : scriptString; + }, + createScriptURL: (urlString) => { + return policy ? policy.createScriptURL(urlString) : urlString; + }, + isEnabled: () => policy !== null, + }; +})(); diff --git a/src/subtitle/XMLHttpRequestInjector.js b/src/subtitle/XMLHttpRequestInjector.js new file mode 100644 index 0000000..460c317 --- /dev/null +++ b/src/subtitle/XMLHttpRequestInjector.js @@ -0,0 +1,19 @@ +export const XMLHttpRequestInjector = () => { + const originalOpen = XMLHttpRequest.prototype.open; + XMLHttpRequest.prototype.open = function (...args) { + const url = args[1]; + if (typeof url === "string" && url.includes("timedtext")) { + this.addEventListener("load", function () { + window.postMessage( + { + type: "KISS_XHR_DATA_YOUTUBE", + url: this.responseURL, + response: this.responseText, + }, + window.location.origin + ); + }); + } + return originalOpen.apply(this, args); + }; +}; diff --git a/src/subtitle/YouTubeCaptionProvider.js b/src/subtitle/YouTubeCaptionProvider.js index 56e2441..ba64618 100644 --- a/src/subtitle/YouTubeCaptionProvider.js +++ b/src/subtitle/YouTubeCaptionProvider.js @@ -38,7 +38,6 @@ class YouTubeCaptionProvider { initialize() { window.addEventListener("message", (event) => { - if (event.source !== window) return; if (event.data?.type === MSG_XHR_DATA_YOUTUBE) { const { url, response } = event.data; if (url && response) { diff --git a/src/subtitle/subtitle.js b/src/subtitle/subtitle.js index 78d0589..937726e 100644 --- a/src/subtitle/subtitle.js +++ b/src/subtitle/subtitle.js @@ -5,6 +5,8 @@ import { DEFAULT_API_SETTING } from "../config/api.js"; import { DEFAULT_SUBTITLE_SETTING } from "../config/setting.js"; import { injectExternalJs } from "../libs/injector.js"; import { logger } from "../libs/log.js"; +import { XMLHttpRequestInjector } from "./XMLHttpRequestInjector.js"; +import { injectInlineJs } from "../libs/injector.js"; const providers = [ { pattern: "https://www.youtube.com", start: YouTubeInitializer }, @@ -19,18 +21,10 @@ export function runSubtitle({ href, setting, isUserscript }) { const provider = providers.find((item) => isMatch(href, item.pattern)); if (provider) { + const id = "kiss-translator-xmlHttp-injector"; if (isUserscript) { - GM.addElement("script", { - src: "https://github.com/fishjar/kiss-translator/blob/gh-pages/injector.js", - // src: "http://127.0.0.1:8000/injector.js", - type: "text/javascript", - }).onload = function () { - console.log( - "Script successfully injected and loaded via GM_addElement." - ); - }; + injectInlineJs(`(${XMLHttpRequestInjector})()`, id); } else { - const id = "kiss-translator-injector"; const src = browser.runtime.getURL("injector.js"); injectExternalJs(src, id); }