summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.eslintrc.json40
-rw-r--r--dev/data/manifest-variants.json21
-rw-r--r--ext/bg/js/backend.js123
-rw-r--r--ext/bg/js/native-simple-dom-parser.js2
-rw-r--r--ext/mixed/js/core.js9
-rw-r--r--ext/sw.js45
-rw-r--r--test/test-sw.js80
7 files changed, 282 insertions, 38 deletions
diff --git a/.eslintrc.json b/.eslintrc.json
index 3723d97d..486d7bd1 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -156,6 +156,46 @@
"node": true,
"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"
+ }
}
]
}
diff --git a/dev/data/manifest-variants.json b/dev/data/manifest-variants.json
index 3f65d86f..0532ab9b 100644
--- a/dev/data/manifest-variants.json
+++ b/dev/data/manifest-variants.json
@@ -131,6 +131,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",
"fileName": "yomichan-firefox.xpi",
"modifications": [
diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js
index fb11ba2e..322d9400 100644
--- a/ext/bg/js/backend.js
+++ b/ext/bg/js/backend.js
@@ -47,6 +47,7 @@ class Backend {
this._mecab = new Mecab();
this._mediaUtility = new MediaUtility();
this._clipboardReader = new ClipboardReader({
+ // eslint-disable-next-line no-undef
document: (typeof document === 'object' && document !== null ? document : null),
pasteTargetSelector: '#clipboard-paste-target',
imagePasteTargetSelector: '#clipboard-image-paste-target',
@@ -551,43 +552,7 @@ class Backend {
}
_onApiInjectStylesheet({type, value}, sender) {
- if (!sender.tab) {
- 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();
- }
- });
- });
+ return this._injectStylesheet(type, value, sender);
}
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();
+ }
+ });
+ });
+ }
}
diff --git a/ext/bg/js/native-simple-dom-parser.js b/ext/bg/js/native-simple-dom-parser.js
index 4e0d89ea..c1752bc4 100644
--- a/ext/bg/js/native-simple-dom-parser.js
+++ b/ext/bg/js/native-simple-dom-parser.js
@@ -17,6 +17,8 @@
class NativeSimpleDOMParser {
constructor(content) {
+ // TODO : Remove
+ // eslint-disable-next-line no-undef
this._document = new DOMParser().parseFromString(content, 'text/html');
}
diff --git a/ext/mixed/js/core.js b/ext/mixed/js/core.js
index 72ab7474..aa894e01 100644
--- a/ext/mixed/js/core.js
+++ b/ext/mixed/js/core.js
@@ -304,7 +304,12 @@ function promiseTimeout(delay, resolveValue) {
}
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 frameRequest = null;
const onFrame = (time) => {
@@ -318,12 +323,14 @@ function promiseAnimationFrame(timeout=null) {
const onTimeout = () => {
timer = null;
if (frameRequest !== null) {
+ // eslint-disable-next-line no-undef
cancelAnimationFrame(frameRequest);
frameRequest = null;
}
resolve({time: timeout, timeout: true});
};
+ // eslint-disable-next-line no-undef
frameRequest = requestAnimationFrame(onFrame);
if (typeof timeout === 'number') {
timer = setTimeout(onTimeout, timeout);
diff --git a/ext/sw.js b/ext/sw.js
new file mode 100644
index 00000000..1b377ae0
--- /dev/null
+++ b/ext/sw.js
@@ -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'
+);
diff --git a/test/test-sw.js b/test/test-sw.js
new file mode 100644
index 00000000..a2cb9df4
--- /dev/null
+++ b/test/test-sw.js
@@ -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(); }