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