diff --git a/.eslintrc.json b/.eslintrc.json
index 1608bc9f..bfc9c3d2 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -186,6 +186,7 @@
"ext/js/background/environment.js",
"ext/js/background/profile-conditions-util.js",
"ext/js/background/request-builder.js",
+ "ext/js/background/script-manager.js",
"ext/js/comm/anki.js",
"ext/js/comm/clipboard-monitor.js",
"ext/js/comm/clipboard-reader.js",
diff --git a/ext/background.html b/ext/background.html
index 6259e519..44027a48 100644
--- a/ext/background.html
+++ b/ext/background.html
@@ -27,6 +27,7 @@
+
diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js
index ca2f5f16..f48a87f8 100644
--- a/ext/js/background/backend.js
+++ b/ext/js/background/backend.js
@@ -31,6 +31,7 @@
* PermissionsUtil
* ProfileConditionsUtil
* RequestBuilder
+ * ScriptManager
* StringUtil
* Translator
* wanakana
@@ -67,6 +68,7 @@ class Backend {
requestBuilder: this._requestBuilder
});
this._optionsUtil = new OptionsUtil();
+ this._scriptManager = new ScriptManager();
this._searchPopupTabId = null;
this._searchPopupTabCreatePromise = null;
@@ -559,8 +561,10 @@ class Backend {
return Promise.resolve({tabId, frameId});
}
- _onApiInjectStylesheet({type, value}, sender) {
- return this._injectStylesheet(type, value, sender);
+ async _onApiInjectStylesheet({type, value}, sender) {
+ const {frameId, tab} = sender;
+ if (!isObject(tab)) { throw new Error('Invalid tab'); }
+ return await this._scriptManager.injectStylesheet(type, value, tab.id, frameId);
}
async _onApiGetStylesheetContent({url}) {
@@ -746,7 +750,7 @@ class Backend {
_onApiDocumentStart(params, sender) {
const {tab, frameId, url} = sender;
if (typeof url !== 'string' || typeof tab !== 'object' || tab === null) { return; }
- this._updateTabAccessibility(url, tab, frameId);
+ this._updateTabAccessibility(url, tab.id, frameId);
}
async _onApiGetTermFrequencies({termReadingList, dictionaries}) {
@@ -2085,145 +2089,6 @@ class Backend {
});
}
- _injectStylesheet(type, value, target) {
- if (isObject(chrome.tabs) && typeof chrome.tabs.insertCSS === 'function') {
- return this._injectStylesheetMV2(type, value, target);
- } else if (isObject(chrome.scripting) && typeof chrome.scripting.insertCSS === 'function') {
- return this._injectStylesheetMV3(type, value, target);
- } else {
- return Promise.reject(new Error('insertCSS function not available'));
- }
- }
-
- _injectStylesheetMV2(type, value, target) {
- return new Promise((resolve, reject) => {
- if (!target.tab) {
- reject(new Error('Invalid tab'));
- return;
- }
-
- const tabId = target.tab.id;
- const frameId = target.frameId;
- const details = (
- type === 'file' ?
- {
- file: value,
- runAt: 'document_start',
- cssOrigin: 'author',
- allFrames: false,
- matchAboutBlank: true
- } :
- {
- code: value,
- runAt: 'document_start',
- cssOrigin: 'user',
- allFrames: false,
- matchAboutBlank: true
- }
- );
- if (typeof frameId === 'number') {
- details.frameId = frameId;
- }
-
- chrome.tabs.insertCSS(tabId, details, () => {
- const e = chrome.runtime.lastError;
- if (e) {
- reject(new Error(e.message));
- } else {
- resolve();
- }
- });
- });
- }
-
- _injectStylesheetMV3(type, value, target) {
- return new Promise((resolve, reject) => {
- if (!target.tab) {
- reject(new Error('Invalid tab'));
- return;
- }
-
- const tabId = target.tab.id;
- const frameId = target.frameId;
- const details = (
- type === 'file' ?
- {origin: chrome.scripting.StyleOrigin.AUTHOR, files: [value]} :
- {origin: chrome.scripting.StyleOrigin.USER, css: value}
- );
- details.target = {
- tabId,
- allFrames: false
- };
- if (typeof frameId === 'number') {
- details.target.frameIds = [frameId];
- }
-
- chrome.scripting.insertCSS(details, () => {
- const e = chrome.runtime.lastError;
- if (e) {
- reject(new Error(e.message));
- } else {
- resolve();
- }
- });
- });
- }
-
- _injectScript(file, tabId, frameId) {
- if (isObject(chrome.tabs) && typeof chrome.tabs.executeScript === 'function') {
- return this._injectScriptMV2(file, tabId, frameId);
- } else if (isObject(chrome.scripting) && typeof chrome.scripting.executeScript === 'function') {
- return this._injectScriptMV3(file, tabId, frameId);
- } else {
- return Promise.reject(new Error('executeScript function not available'));
- }
- }
-
- _injectScriptMV2(file, tabId, frameId) {
- return new Promise((resolve, reject) => {
- const details = {
- allFrames: false,
- frameId,
- file,
- matchAboutBlank: true,
- runAt: 'document_start'
- };
- const callback = (results) => {
- const e = chrome.runtime.lastError;
- if (e) {
- reject(new Error(e.message));
- } else {
- const result = results[0];
- resolve({frameId, result});
- }
- };
- chrome.tabs.executeScript(tabId, details, callback);
- });
- }
-
- _injectScriptMV3(file, tabId, frameId) {
- return new Promise((resolve, reject) => {
- const details = {
- files: [file],
- target: {
- allFrames: false,
- frameIds: [frameId],
- tabId
- }
- };
- const callback = (results) => {
- const e = chrome.runtime.lastError;
- if (e) {
- reject(new Error(e.message));
- } else {
- const {frameId: frameId2, result} = results[0];
- resolve({frameId: frameId2, result});
- }
- };
- chrome.scripting.executeScript(details, callback);
- });
- }
-
_getTabById(tabId) {
return new Promise((resolve, reject) => {
chrome.tabs.get(
@@ -2275,7 +2140,7 @@ class Backend {
}
}
- async _updateTabAccessibility(url, tab, frameId) {
+ async _updateTabAccessibility(url, tabId, frameId) {
let file = null;
switch (new URL(url).hostname) {
@@ -2291,7 +2156,7 @@ class Backend {
if (file === null) { return; }
- return await this._injectScript(file, tab.id, frameId);
+ await this._scriptManager.injectScript(file, tabId, frameId);
}
async _getNormalizedDictionaryDatabaseMedia(targets) {
diff --git a/ext/js/background/script-manager.js b/ext/js/background/script-manager.js
new file mode 100644
index 00000000..a5dbe0d2
--- /dev/null
+++ b/ext/js/background/script-manager.js
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2021 Yomichan Authors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+/**
+ * This class is used to manage script injection into content tabs.
+ */
+class ScriptManager {
+ /**
+ * Injects a stylesheet into a specific tab and frame.
+ * @param {string} type The type of content to inject; either 'file' or 'code'.
+ * @param {string} content The content to inject.
+ * If type is 'file', this argument should be a path to a file.
+ * If type is 'code', this argument should be the CSS content.
+ * @param {number} tabId The id of the tab to inject into.
+ * @param {number} frameId The id of the frame to inject into.
+ * @returns {Promise}
+ */
+ injectStylesheet(type, content, tabId, frameId) {
+ if (isObject(chrome.tabs) && typeof chrome.tabs.insertCSS === 'function') {
+ return this._injectStylesheetMV2(type, content, tabId, frameId);
+ } else if (isObject(chrome.scripting) && typeof chrome.scripting.insertCSS === 'function') {
+ return this._injectStylesheetMV3(type, content, tabId, frameId);
+ } else {
+ return Promise.reject(new Error('Stylesheet injection not supported'));
+ }
+ }
+ /**
+ * Injects a script into a specific tab and frame.
+ * @param {string} file The path to a file to inject.
+ * @param {number} tabId The id of the tab to inject into.
+ * @param {number} frameId The id of the frame to inject into.
+ * @returns {Promise<{frameId: number, result: object}>} The id of the frame and the result of the script injection.
+ */
+ injectScript(file, tabId, frameId) {
+ if (isObject(chrome.tabs) && typeof chrome.tabs.executeScript === 'function') {
+ return this._injectScriptMV2(file, tabId, frameId);
+ } else if (isObject(chrome.scripting) && typeof chrome.scripting.executeScript === 'function') {
+ return this._injectScriptMV3(file, tabId, frameId);
+ } else {
+ return Promise.reject(new Error('Script injection not supported'));
+ }
+ }
+
+ // Private
+
+ _injectStylesheetMV2(type, content, tabId, frameId) {
+ return new Promise((resolve, reject) => {
+ const details = (
+ type === 'file' ?
+ {
+ file: content,
+ runAt: 'document_start',
+ cssOrigin: 'author',
+ allFrames: false,
+ matchAboutBlank: true
+ } :
+ {
+ code: content,
+ runAt: 'document_start',
+ cssOrigin: 'user',
+ allFrames: false,
+ matchAboutBlank: true
+ }
+ );
+ if (typeof frameId === 'number') {
+ details.frameId = frameId;
+ }
+ chrome.tabs.insertCSS(tabId, details, () => {
+ const e = chrome.runtime.lastError;
+ if (e) {
+ reject(new Error(e.message));
+ } else {
+ resolve();
+ }
+ });
+ });
+ }
+
+ _injectStylesheetMV3(type, content, tabId, frameId) {
+ return new Promise((resolve, reject) => {
+ const details = (
+ type === 'file' ?
+ {origin: chrome.scripting.StyleOrigin.AUTHOR, files: [content]} :
+ {origin: chrome.scripting.StyleOrigin.USER, css: content}
+ );
+ details.target = {
+ tabId,
+ allFrames: false
+ };
+ if (typeof frameId === 'number') {
+ details.target.frameIds = [frameId];
+ }
+ chrome.scripting.insertCSS(details, () => {
+ const e = chrome.runtime.lastError;
+ if (e) {
+ reject(new Error(e.message));
+ } else {
+ resolve();
+ }
+ });
+ });
+ }
+
+ _injectScriptMV2(file, tabId, frameId) {
+ return new Promise((resolve, reject) => {
+ const details = {
+ allFrames: false,
+ frameId,
+ file,
+ matchAboutBlank: true,
+ runAt: 'document_start'
+ };
+ chrome.tabs.executeScript(tabId, details, (results) => {
+ const e = chrome.runtime.lastError;
+ if (e) {
+ reject(new Error(e.message));
+ } else {
+ const result = results[0];
+ resolve({frameId, result});
+ }
+ });
+ });
+ }
+
+ _injectScriptMV3(file, tabId, frameId) {
+ return new Promise((resolve, reject) => {
+ const details = {
+ files: [file],
+ target: {
+ allFrames: false,
+ frameIds: [frameId],
+ tabId
+ }
+ };
+ chrome.scripting.executeScript(details, (results) => {
+ const e = chrome.runtime.lastError;
+ if (e) {
+ reject(new Error(e.message));
+ } else {
+ const {frameId: frameId2, result} = results[0];
+ resolve({frameId: frameId2, result});
+ }
+ });
+ });
+ }
+}
diff --git a/ext/sw.js b/ext/sw.js
index 6443c57f..2624a04d 100644
--- a/ext/sw.js
+++ b/ext/sw.js
@@ -24,6 +24,7 @@ self.importScripts(
'/js/background/environment.js',
'/js/background/profile-conditions-util.js',
'/js/background/request-builder.js',
+ '/js/background/script-manager.js',
'/js/comm/anki.js',
'/js/comm/clipboard-monitor.js',
'/js/comm/clipboard-reader.js',