/* * 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}); } }); }); } }