diff options
Diffstat (limited to 'ext/js/background/script-manager.js')
-rw-r--r-- | ext/js/background/script-manager.js | 160 |
1 files changed, 160 insertions, 0 deletions
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 <https://www.gnu.org/licenses/>. + */ + +/** + * 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<void>} + */ + 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}); + } + }); + }); + } +} |