From cedf6b25c4327d33411877dbb412877dfa7753e9 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 22 Nov 2021 19:29:20 -0500 Subject: [PATCH] ScriptManager (#2021) * Create ScriptManager class * Use ScriptManager in Backend --- .eslintrc.json | 1 + ext/background.html | 1 + ext/js/background/backend.js | 153 ++------------------------ ext/js/background/script-manager.js | 160 ++++++++++++++++++++++++++++ ext/sw.js | 1 + 5 files changed, 172 insertions(+), 144 deletions(-) create mode 100644 ext/js/background/script-manager.js 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',