Set up initial manifest v3 support (#605)
This commit is contained in:
parent
fd91a5b383
commit
51223abfa6
@ -156,6 +156,46 @@
|
|||||||
"node": true,
|
"node": true,
|
||||||
"webextensions": false
|
"webextensions": false
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"ext/mixed/js/core.js",
|
||||||
|
"ext/mixed/js/yomichan.js",
|
||||||
|
"ext/mixed/js/environment.js",
|
||||||
|
"ext/mixed/js/japanese.js",
|
||||||
|
"ext/mixed/js/cache-map.js",
|
||||||
|
"ext/mixed/js/dictionary-data-util.js",
|
||||||
|
"ext/mixed/js/object-property-accessor.js",
|
||||||
|
"ext/bg/js/anki.js",
|
||||||
|
"ext/bg/js/audio-downloader.js",
|
||||||
|
"ext/bg/js/clipboard-monitor.js",
|
||||||
|
"ext/bg/js/clipboard-reader.js",
|
||||||
|
"ext/bg/js/database.js",
|
||||||
|
"ext/bg/js/deinflector.js",
|
||||||
|
"ext/bg/js/dictionary-database.js",
|
||||||
|
"ext/bg/js/json-schema.js",
|
||||||
|
"ext/bg/js/mecab.js",
|
||||||
|
"ext/bg/js/media-utility.js",
|
||||||
|
"ext/bg/js/options.js",
|
||||||
|
"ext/bg/js/profile-conditions.js",
|
||||||
|
"ext/bg/js/request-builder.js",
|
||||||
|
"ext/bg/js/native-simple-dom-parser.js",
|
||||||
|
"ext/bg/js/text-source-map.js",
|
||||||
|
"ext/bg/js/translator.js",
|
||||||
|
"ext/bg/js/backend.js",
|
||||||
|
"ext/bg/js/background-main.js"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"browser": false,
|
||||||
|
"serviceworker": true,
|
||||||
|
"es2017": true,
|
||||||
|
"webextensions": true
|
||||||
|
},
|
||||||
|
"globals": {
|
||||||
|
"FileReader": "readonly",
|
||||||
|
"Intl": "readonly",
|
||||||
|
"crypto": "readonly"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -130,6 +130,27 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "chrome-mv3",
|
||||||
|
"fileName": "yomichan-chrome-mv3.zip",
|
||||||
|
"modifications": [
|
||||||
|
{"action": "set", "path": ["manifest_version"], "value": 3},
|
||||||
|
{"action": "move", "path": ["browser_action"], "newPath": ["action"]},
|
||||||
|
{"action": "delete", "path": ["background", "page"]},
|
||||||
|
{"action": "delete", "path": ["background", "persistent"]},
|
||||||
|
{"action": "set", "path": ["background", "service_worker"], "value": "sw.js"},
|
||||||
|
{"action": "move", "path": ["content_security_policy"], "newPath": ["content_security_policy_old"]},
|
||||||
|
{"action": "set", "path": ["content_security_policy"], "value": {}},
|
||||||
|
{"action": "move", "path": ["content_security_policy_old"], "newPath": ["content_security_policy", "extension_pages"]},
|
||||||
|
{"action": "move", "path": ["sandbox", "content_security_policy"], "newPath": ["content_security_policy", "sandbox"]},
|
||||||
|
{"action": "remove", "path": ["permissions"], "item": "<all_urls>"},
|
||||||
|
{"action": "set", "path": ["host_permissions"], "value": ["<all_urls>"], "after": "optional_permissions"},
|
||||||
|
{"action": "add", "path": ["permissions"], "items": ["scripting"]},
|
||||||
|
{"action": "move", "path": ["web_accessible_resources"], "newPath": ["web_accessible_resources_old"]},
|
||||||
|
{"action": "set", "path": ["web_accessible_resources"], "value": [{"resources": [], "matches": ["<all_urls>"]}], "after": "web_accessible_resources_old"},
|
||||||
|
{"action": "move", "path": ["web_accessible_resources_old"], "newPath": ["web_accessible_resources", 0, "resources"]}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "firefox",
|
"name": "firefox",
|
||||||
"fileName": "yomichan-firefox.xpi",
|
"fileName": "yomichan-firefox.xpi",
|
||||||
|
@ -47,6 +47,7 @@ class Backend {
|
|||||||
this._mecab = new Mecab();
|
this._mecab = new Mecab();
|
||||||
this._mediaUtility = new MediaUtility();
|
this._mediaUtility = new MediaUtility();
|
||||||
this._clipboardReader = new ClipboardReader({
|
this._clipboardReader = new ClipboardReader({
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
document: (typeof document === 'object' && document !== null ? document : null),
|
document: (typeof document === 'object' && document !== null ? document : null),
|
||||||
pasteTargetSelector: '#clipboard-paste-target',
|
pasteTargetSelector: '#clipboard-paste-target',
|
||||||
imagePasteTargetSelector: '#clipboard-image-paste-target',
|
imagePasteTargetSelector: '#clipboard-image-paste-target',
|
||||||
@ -551,43 +552,7 @@ class Backend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_onApiInjectStylesheet({type, value}, sender) {
|
_onApiInjectStylesheet({type, value}, sender) {
|
||||||
if (!sender.tab) {
|
return this._injectStylesheet(type, value, sender);
|
||||||
return Promise.reject(new Error('Invalid tab'));
|
|
||||||
}
|
|
||||||
|
|
||||||
const tabId = sender.tab.id;
|
|
||||||
const frameId = sender.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
chrome.tabs.insertCSS(tabId, details, () => {
|
|
||||||
const e = chrome.runtime.lastError;
|
|
||||||
if (e) {
|
|
||||||
reject(new Error(e.message));
|
|
||||||
} else {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onApiGetStylesheetContent({url}) {
|
async _onApiGetStylesheetContent({url}) {
|
||||||
@ -1772,4 +1737,88 @@ 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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
|
|
||||||
class NativeSimpleDOMParser {
|
class NativeSimpleDOMParser {
|
||||||
constructor(content) {
|
constructor(content) {
|
||||||
|
// TODO : Remove
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
this._document = new DOMParser().parseFromString(content, 'text/html');
|
this._document = new DOMParser().parseFromString(content, 'text/html');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,7 +304,12 @@ function promiseTimeout(delay, resolveValue) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function promiseAnimationFrame(timeout=null) {
|
function promiseAnimationFrame(timeout=null) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
if (typeof cancelAnimationFrame !== 'function' || typeof requestAnimationFrame !== 'function') {
|
||||||
|
reject(new Error('Animation not supported in this context'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let timer = null;
|
let timer = null;
|
||||||
let frameRequest = null;
|
let frameRequest = null;
|
||||||
const onFrame = (time) => {
|
const onFrame = (time) => {
|
||||||
@ -318,12 +323,14 @@ function promiseAnimationFrame(timeout=null) {
|
|||||||
const onTimeout = () => {
|
const onTimeout = () => {
|
||||||
timer = null;
|
timer = null;
|
||||||
if (frameRequest !== null) {
|
if (frameRequest !== null) {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
cancelAnimationFrame(frameRequest);
|
cancelAnimationFrame(frameRequest);
|
||||||
frameRequest = null;
|
frameRequest = null;
|
||||||
}
|
}
|
||||||
resolve({time: timeout, timeout: true});
|
resolve({time: timeout, timeout: true});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
frameRequest = requestAnimationFrame(onFrame);
|
frameRequest = requestAnimationFrame(onFrame);
|
||||||
if (typeof timeout === 'number') {
|
if (typeof timeout === 'number') {
|
||||||
timer = setTimeout(onTimeout, timeout);
|
timer = setTimeout(onTimeout, timeout);
|
||||||
|
45
ext/sw.js
Normal file
45
ext/sw.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
self.importScripts(
|
||||||
|
'/mixed/lib/wanakana.min.js',
|
||||||
|
'/mixed/js/core.js',
|
||||||
|
'/mixed/js/yomichan.js',
|
||||||
|
'/mixed/js/environment.js',
|
||||||
|
'/mixed/js/japanese.js',
|
||||||
|
'/mixed/js/cache-map.js',
|
||||||
|
'/mixed/js/dictionary-data-util.js',
|
||||||
|
'/mixed/js/object-property-accessor.js',
|
||||||
|
'/bg/js/anki.js',
|
||||||
|
'/bg/js/audio-downloader.js',
|
||||||
|
'/bg/js/clipboard-monitor.js',
|
||||||
|
'/bg/js/clipboard-reader.js',
|
||||||
|
'/bg/js/database.js',
|
||||||
|
'/bg/js/deinflector.js',
|
||||||
|
'/bg/js/dictionary-database.js',
|
||||||
|
'/bg/js/json-schema.js',
|
||||||
|
'/bg/js/mecab.js',
|
||||||
|
'/bg/js/media-utility.js',
|
||||||
|
'/bg/js/options.js',
|
||||||
|
'/bg/js/profile-conditions.js',
|
||||||
|
'/bg/js/request-builder.js',
|
||||||
|
'/bg/js/native-simple-dom-parser.js',
|
||||||
|
'/bg/js/text-source-map.js',
|
||||||
|
'/bg/js/translator.js',
|
||||||
|
'/bg/js/backend.js',
|
||||||
|
'/bg/js/background-main.js'
|
||||||
|
);
|
80
test/test-sw.js
Normal file
80
test/test-sw.js
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Yomichan Authors
|
||||||
|
* Author: 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const {JSDOM} = require('jsdom');
|
||||||
|
const {VM} = require('../dev/vm');
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
|
||||||
|
function getAllHtmlScriptPaths(fileName) {
|
||||||
|
const domSource = fs.readFileSync(fileName, {encoding: 'utf8'});
|
||||||
|
const dom = new JSDOM(domSource);
|
||||||
|
const {window} = dom;
|
||||||
|
const {document} = window;
|
||||||
|
try {
|
||||||
|
const scripts = document.querySelectorAll('script');
|
||||||
|
return [...scripts].map(({src}) => src);
|
||||||
|
} finally {
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
try {
|
||||||
|
// Verify that sw.js scripts match background.html scripts
|
||||||
|
const rootDir = path.join(__dirname, '..');
|
||||||
|
const extDirName = 'ext';
|
||||||
|
const extDir = path.join(rootDir, extDirName);
|
||||||
|
|
||||||
|
const scripts = getAllHtmlScriptPaths(path.join(extDir, 'bg', 'background.html'));
|
||||||
|
const importedScripts = [];
|
||||||
|
|
||||||
|
const importScripts = (...scripts2) => {
|
||||||
|
importedScripts.push(...scripts2);
|
||||||
|
};
|
||||||
|
|
||||||
|
const vm = new VM({importScripts});
|
||||||
|
vm.context.self = vm.context;
|
||||||
|
vm.execute(['sw.js']);
|
||||||
|
|
||||||
|
vm.assert.deepStrictEqual(scripts, importedScripts);
|
||||||
|
|
||||||
|
// Verify that eslint config lists files correctly
|
||||||
|
const expectedSwRulesFiles = scripts.filter((src) => !src.startsWith('/mixed/lib/')).map((src) => `${extDirName}${src}`);
|
||||||
|
const eslintConfig = JSON.parse(fs.readFileSync(path.join(rootDir, '.eslintrc.json'), {encoding: 'utf8'}));
|
||||||
|
const swRules = eslintConfig.overrides.find((item) => (
|
||||||
|
typeof item.env === 'object' &&
|
||||||
|
item.env !== null &&
|
||||||
|
item.env.serviceworker === true
|
||||||
|
));
|
||||||
|
assert.ok(typeof swRules !== 'undefined');
|
||||||
|
assert.ok(Array.isArray(swRules.files));
|
||||||
|
assert.deepStrictEqual(swRules.files, expectedSwRulesFiles);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
process.exit(-1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (require.main === module) { main(); }
|
Loading…
Reference in New Issue
Block a user