From 6c21818eba54b9c5191845086bec28e4a548ee3b Mon Sep 17 00:00:00 2001 From: praschke Date: Wed, 16 Aug 2023 11:01:52 +0100 Subject: fix libraries in README and Firefox CSP --- dev/data/manifest-variants.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'dev/data') diff --git a/dev/data/manifest-variants.json b/dev/data/manifest-variants.json index 3179734f..7d762951 100644 --- a/dev/data/manifest-variants.json +++ b/dev/data/manifest-variants.json @@ -205,13 +205,20 @@ "sandbox" ] }, + { + "action": "delete", + "path": [ + "content_security_policy", + "sandbox" + ] + }, { "action": "set", "path": [ "content_security_policy", "extension_pages" ], - "value": "default-src 'self'; script-src 'self' 'unsafe-eval'; img-src blob: 'self'; style-src 'self' 'unsafe-inline'; media-src *; connect-src *" + "value": "default-src 'self'; script-src 'self'; img-src blob: 'self'; style-src 'self' 'unsafe-inline'; media-src *; connect-src *" }, { "action": "set", -- cgit v1.2.3 From 4b7f91fa5f43ba6023f1c9991348b56b3e26a11b Mon Sep 17 00:00:00 2001 From: praschke Date: Wed, 16 Aug 2023 11:37:13 +0100 Subject: fix script and style injection in Firefox Firefox added the scripting API in 102. This should fix the majority of warnings listed in #96: - insertCSS - executeScript - getRegisteredContentScripts - contentScripts.register - registerContentScripts - unregisterContentScripts --- dev/data/manifest-variants.json | 2 +- ext/js/background/backend.js | 4 +- ext/js/background/script-manager.js | 111 ++++-------------------------------- 3 files changed, 14 insertions(+), 103 deletions(-) (limited to 'dev/data') diff --git a/dev/data/manifest-variants.json b/dev/data/manifest-variants.json index 7d762951..e167c755 100644 --- a/dev/data/manifest-variants.json +++ b/dev/data/manifest-variants.json @@ -228,7 +228,7 @@ "value": { "gecko": { "id": "{cb7c0bec-7085-4f84-8422-7b55a7c4467c}", - "strict_min_version": "101.0" + "strict_min_version": "102.0" } } }, diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index db6cfada..dd233abb 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -593,7 +593,7 @@ class Backend { async _onApiInjectStylesheet({type, value}, sender) { const {frameId, tab} = sender; if (!isObject(tab)) { throw new Error('Invalid tab'); } - return await this._scriptManager.injectStylesheet(type, value, tab.id, frameId, false, true, 'document_start'); + return await this._scriptManager.injectStylesheet(type, value, tab.id, frameId, false); } async _onApiGetStylesheetContent({url}) { @@ -790,7 +790,7 @@ class Backend { if (typeof tabId !== 'number') { throw new Error('Sender has invalid tab ID'); } const {frameId} = sender; for (const file of files) { - await this._scriptManager.injectScript(file, tabId, frameId, false, true, 'document_start'); + await this._scriptManager.injectScript(file, tabId, frameId, false); } } diff --git a/ext/js/background/script-manager.js b/ext/js/background/script-manager.js index 722a46f0..a0aed2a3 100644 --- a/ext/js/background/script-manager.js +++ b/ext/js/background/script-manager.js @@ -36,14 +36,10 @@ class ScriptManager { * @param {number} tabId The id of the tab to inject into. * @param {number} [frameId] The id of the frame to inject into. * @param {boolean} [allFrames] Whether or not the stylesheet should be injected into all frames. - * @param {boolean} [matchAboutBlank] Whether or not the stylesheet should be injected into about:blank frames. - * @param {string} [runAt] The time to inject the stylesheet at. * @returns {Promise} */ - injectStylesheet(type, content, tabId, frameId, allFrames, matchAboutBlank, runAt) { - if (isObject(chrome.tabs) && typeof chrome.tabs.insertCSS === 'function') { - return this._injectStylesheetMV2(type, content, tabId, frameId, allFrames, matchAboutBlank, runAt); - } else if (isObject(chrome.scripting) && typeof chrome.scripting.insertCSS === 'function') { + injectStylesheet(type, content, tabId, frameId, allFrames) { + if (isObject(chrome.scripting) && typeof chrome.scripting.insertCSS === 'function') { return this._injectStylesheetMV3(type, content, tabId, frameId, allFrames); } else { return Promise.reject(new Error('Stylesheet injection not supported')); @@ -56,14 +52,10 @@ class ScriptManager { * @param {number} tabId The id of the tab to inject into. * @param {number} [frameId] The id of the frame to inject into. * @param {boolean} [allFrames] Whether or not the script should be injected into all frames. - * @param {boolean} [matchAboutBlank] Whether or not the script should be injected into about:blank frames. - * @param {string} [runAt] The time to inject the script at. * @returns {Promise<{frameId: number, result: object}>} The id of the frame and the result of the script injection. */ - injectScript(file, tabId, frameId, allFrames, matchAboutBlank, runAt) { - if (isObject(chrome.tabs) && typeof chrome.tabs.executeScript === 'function') { - return this._injectScriptMV2(file, tabId, frameId, allFrames, matchAboutBlank, runAt); - } else if (isObject(chrome.scripting) && typeof chrome.scripting.executeScript === 'function') { + injectScript(file, tabId, frameId, allFrames) { + if (isObject(chrome.scripting) && typeof chrome.scripting.executeScript === 'function') { return this._injectScriptMV3(file, tabId, frameId, allFrames); } else { return Promise.reject(new Error('Script injection not supported')); @@ -122,19 +114,6 @@ class ScriptManager { throw new Error('Registration already exists'); } - // Firefox - if ( - typeof browser === 'object' && browser !== null && - isObject(browser.contentScripts) && - typeof browser.contentScripts.register === 'function' - ) { - const details2 = this._convertContentScriptRegistrationDetails(details, id, true); - const registration = await browser.contentScripts.register(details2); - this._contentScriptRegistrations.set(id, registration); - return; - } - - // Chrome if (isObject(chrome.scripting) && typeof chrome.scripting.registerContentScripts === 'function') { const details2 = this._convertContentScriptRegistrationDetails(details, id, false); await new Promise((resolve, reject) => { @@ -161,18 +140,17 @@ class ScriptManager { * @returns {Promise} `true` if the content script was unregistered, `false` otherwise. */ async unregisterContentScript(id) { - // Chrome if (isObject(chrome.scripting) && typeof chrome.scripting.unregisterContentScripts === 'function') { this._contentScriptRegistrations.delete(id); try { - await this._unregisterContentScriptChrome(id); + await this._unregisterContentScriptMV3(id); return true; } catch (e) { return false; } } - // Firefox or fallback + // Fallback const registration = this._contentScriptRegistrations.get(id); if (typeof registration === 'undefined') { return false; } this._contentScriptRegistrations.delete(id); @@ -187,19 +165,7 @@ class ScriptManager { * @returns {string[]} An array of the required permissions, which may be empty. */ getRequiredContentScriptRegistrationPermissions() { - if ( - // Firefox - ( - typeof browser === 'object' && browser !== null && - isObject(browser.contentScripts) && - typeof browser.contentScripts.register === 'function' - ) || - // Chrome - ( - isObject(chrome.scripting) && - typeof chrome.scripting.registerContentScripts === 'function' - ) - ) { + if (isObject(chrome.scripting) && typeof chrome.scripting.registerContentScripts === 'function') { return []; } @@ -209,39 +175,6 @@ class ScriptManager { // Private - _injectStylesheetMV2(type, content, tabId, frameId, allFrames, matchAboutBlank, runAt) { - return new Promise((resolve, reject) => { - const details = ( - type === 'file' ? - { - file: content, - runAt, - cssOrigin: 'author', - allFrames, - matchAboutBlank - } : - { - code: content, - runAt, - cssOrigin: 'user', - allFrames, - matchAboutBlank - } - ); - 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, allFrames) { return new Promise((resolve, reject) => { const details = ( @@ -267,27 +200,6 @@ class ScriptManager { }); } - _injectScriptMV2(file, tabId, frameId, allFrames, matchAboutBlank, runAt) { - return new Promise((resolve, reject) => { - const details = { - allFrames, - frameId, - file, - matchAboutBlank, - runAt - }; - 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, allFrames) { return new Promise((resolve, reject) => { const details = { @@ -310,7 +222,7 @@ class ScriptManager { }); } - _unregisterContentScriptChrome(id) { + _unregisterContentScriptMV3(id) { return new Promise((resolve, reject) => { chrome.scripting.unregisterContentScripts({ids: [id]}, () => { const e = chrome.runtime.lastError; @@ -407,7 +319,7 @@ class ScriptManager { const {urlRegex} = details; if (urlRegex !== null && !urlRegex.test(url)) { return; } - let {allFrames, css, js, matchAboutBlank, runAt} = details; + let {allFrames, css, js, runAt} = details; if (isWebNavigation) { if (allFrames) { @@ -425,14 +337,13 @@ class ScriptManager { const promises = []; if (Array.isArray(css)) { - const runAtCss = (typeof runAt === 'string' ? runAt : 'document_start'); for (const file of css) { - promises.push(this.injectStylesheet('file', file, tabId, frameId, allFrames, matchAboutBlank, runAtCss)); + promises.push(this.injectStylesheet('file', file, tabId, frameId, allFrames)); } } if (Array.isArray(js)) { for (const file of js) { - promises.push(this.injectScript(file, tabId, frameId, allFrames, matchAboutBlank, runAt)); + promises.push(this.injectScript(file, tabId, frameId, allFrames)); } } await Promise.all(promises); -- cgit v1.2.3 From 2ac3115c526a766b19bded840417dc1303016e66 Mon Sep 17 00:00:00 2001 From: praschke Date: Wed, 16 Aug 2023 11:52:29 +0100 Subject: fix #173 `declarativeNetRequest.updateDynamicRules()` returns with an unexpected error in Firefox, but only after the browser has been restarted. On a fresh install of Yomitan it works, causing bug flakiness. `declarativeNetRequest` can be disabled in the manifest as a workaround. --- dev/data/manifest-variants.json | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'dev/data') diff --git a/dev/data/manifest-variants.json b/dev/data/manifest-variants.json index e167c755..304a3a0e 100644 --- a/dev/data/manifest-variants.json +++ b/dev/data/manifest-variants.json @@ -247,6 +247,13 @@ "items": [ "nativeMessaging" ] + }, + { + "action": "remove", + "path": [ + "permissions" + ], + "item": "declarativeNetRequest" } ], "excludeFiles": [ -- cgit v1.2.3 From 6f24ac4e2a1ee8928e0be69bea774bcade28f2a9 Mon Sep 17 00:00:00 2001 From: Darius Jahandarie Date: Sat, 23 Sep 2023 18:05:33 +0900 Subject: Remove hardcoded version from manifest * Dynamically set version as argument to build script * Set version using tag ref_name in CI * [Cleanup] gitignore ext/manifest.json as it's dynamically generated --- .github/workflows/ci.yml | 9 +- .github/workflows/create-prerelease-on-tag.yml | 2 +- .github/workflows/playwright.yml | 9 +- .gitignore | 1 + CONTRIBUTING.md | 10 +- dev/build.js | 12 ++- dev/data/manifest-variants.json | 2 +- ext/manifest.json | 131 ------------------------- 8 files changed, 23 insertions(+), 153 deletions(-) delete mode 100644 ext/manifest.json (limited to 'dev/data') diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c62c9893..1b81df2f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,16 +41,11 @@ jobs: env: CI: true - - name: Manifest - run: npm run test-manifest - env: - CI: true + - name: Build + run: npm run build - name: Validate manifest.json of the extension uses: cardinalby/schema-validator-action@c2da05377e89dd0c9b7be9420da0b3534b1efcce # pin@v1 with: file: ext/manifest.json schema: "https://json.schemastore.org/chrome-manifest.json" - - - name: Build - run: npm run test-build diff --git a/.github/workflows/create-prerelease-on-tag.yml b/.github/workflows/create-prerelease-on-tag.yml index 06cb8c7e..0999c5ec 100644 --- a/.github/workflows/create-prerelease-on-tag.yml +++ b/.github/workflows/create-prerelease-on-tag.yml @@ -21,7 +21,7 @@ jobs: node-version-file: ".node-version" - name: Lint - run: npm run-script build + run: npm run-script build -- --yomitan-version ${{ github.ref_name }} shell: bash - name: Release diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index a1135157..8efc184a 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -14,13 +14,13 @@ jobs: run: rm -rf /usr/share/fonts - uses: actions/checkout@v3 - + - name: Install CJK fonts uses: awalsh128/cache-apt-pkgs-action@1850ee53f6e706525805321a3f2f863dcf73c962 # v1.3.0 with: packages: fonts-ipafont-mincho execute_install_scripts: true - + - uses: actions/setup-node@v3 with: cache: "npm" @@ -29,6 +29,9 @@ jobs: - name: Install dependencies run: npm ci + - name: Build + run: npm run build + - name: Cache playwright browsers id: cache-playwright uses: actions/cache@v3 @@ -40,7 +43,7 @@ jobs: - if: ${{ steps.cache-playwright.outputs.cache-hit != 'true' }} name: Install Playwright Browsers run: npx playwright install chromium - + - name: Grab latest dictionaries from dictionaries branch uses: actions/checkout@v3 with: diff --git a/.gitignore b/.gitignore index d4e5da07..405fead0 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ dictionaries/ /playwright-report/ /playwright/.cache/ /test/playwright/__screenshots__/ +ext/manifest.json diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ffc76b74..b67f6092 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,9 +50,10 @@ Several command line arguments are available for these scripts: * `[target]` - Builds a specific target. * `--all` - Builds all targets specified in [manifest-variants.json](dev/data/manifest-variants.json). * `--default` - Restores the default manifest file. -* `--manifest ` - Overwrites [ext/manifest.json](ext/manifest.json) with the manifest variant for the specified build target. +* `--manifest ` - Overwrites `ext/manifest.json` with the manifest variant for the specified build target. * `--dry-run` - Runs the full build process (excluding zip building), checking that the configuration is valid. * `--dry-run-build-zip` - If `--dry-run` is also specified, zip building will also be performed in memory; no files are created. +* `--yomitan-version ` - Sets the version number in the extension manifest. Defaults to 0.0.0.0 if not set. If no arguments are specified, the command is equivalent to `build.bat --all`. @@ -66,11 +67,8 @@ Otherwise, the [JSZip](https://stuk.github.io/jszip/) API is used to generate th ## Manifest Manifest variants for different build targets are specified in [manifest-variants.json](dev/data/manifest-variants.json). -This file is used to overwrite the [manfiest.json](ext/manifest.json) file included in the extension. -By default, this manifest should be the default `chrome` manifest, and changes to [manfiest.json](ext/manifest.json) should not be committed -unless there is a corresponding change in [manifest-variants.json](dev/data/manifest-variants.json). -There is a continuous integration test which validates this, and the default manifest can be restored by running -`build.bat --default`. +This file is used to generate the `ext/manifest.json` file included in the extension. +The generated `ext/manfiest.json` should not be committed. ## Style diff --git a/dev/build.js b/dev/build.js index 5222c4c8..24b1e2d0 100644 --- a/dev/build.js +++ b/dev/build.js @@ -108,7 +108,7 @@ function getIndexOfFilePath(array, item) { return -1; } -async function build(buildDir, extDir, manifestUtil, variantNames, manifestPath, dryRun, dryRunBuildZip) { +async function build(buildDir, extDir, manifestUtil, variantNames, manifestPath, dryRun, dryRunBuildZip, yomitanVersion) { const sevenZipExes = ['7za', '7z']; // Create build directory @@ -130,6 +130,8 @@ async function build(buildDir, extDir, manifestUtil, variantNames, manifestPath, process.stdout.write(message); }; + process.stdout.write(`Version: ${yomitanVersion}...\n`); + for (const variantName of variantNames) { const variant = manifestUtil.getVariant(variantName); if (typeof variant === 'undefined' || variant.buildable === false) { continue; } @@ -148,7 +150,7 @@ async function build(buildDir, extDir, manifestUtil, variantNames, manifestPath, const fileNameSafe = path.basename(fileName); const fullFileName = path.join(buildDir, fileNameSafe); if (!dryRun) { - fs.writeFileSync(manifestPath, ManifestUtil.createManifestString(modifiedManifest)); + fs.writeFileSync(manifestPath, ManifestUtil.createManifestString(modifiedManifest).replace('$YOMITAN_VERSION', yomitanVersion)); } if (!dryRun || dryRunBuildZip) { @@ -183,11 +185,13 @@ async function main(argv) { ['manifest', null], ['dry-run', false], ['dry-run-build-zip', false], + ['yomitan-version', '0.0.0.0'], [null, []] ])); const dryRun = args.get('dry-run'); const dryRunBuildZip = args.get('dry-run-build-zip'); + const yomitanVersion = args.get('yomitan-version'); const manifestUtil = new ManifestUtil(); @@ -202,14 +206,14 @@ async function main(argv) { manifestUtil.getVariants().filter(({buildable}) => buildable !== false).map(({name}) => name) : args.get(null) ); - await build(buildDir, extDir, manifestUtil, variantNames, manifestPath, dryRun, dryRunBuildZip); + await build(buildDir, extDir, manifestUtil, variantNames, manifestPath, dryRun, dryRunBuildZip, yomitanVersion); } finally { // Restore manifest const manifestName = (!args.get('default') && args.get('manifest') !== null) ? args.get('manifest') : null; const restoreManifest = manifestUtil.getManifest(manifestName); process.stdout.write('Restoring manifest...\n'); if (!dryRun) { - fs.writeFileSync(manifestPath, ManifestUtil.createManifestString(restoreManifest)); + fs.writeFileSync(manifestPath, ManifestUtil.createManifestString(restoreManifest).replace('$YOMITAN_VERSION', yomitanVersion)); } } } diff --git a/dev/data/manifest-variants.json b/dev/data/manifest-variants.json index 304a3a0e..26d91d26 100644 --- a/dev/data/manifest-variants.json +++ b/dev/data/manifest-variants.json @@ -2,7 +2,7 @@ "manifest": { "manifest_version": 3, "name": "Yomitan", - "version": "23.4.7.0", + "version": "$YOMITAN_VERSION", "description": "Japanese dictionary with Anki integration", "author": "TheMoeWay", "icons": { diff --git a/ext/manifest.json b/ext/manifest.json deleted file mode 100644 index 018a4a0b..00000000 --- a/ext/manifest.json +++ /dev/null @@ -1,131 +0,0 @@ -{ - "manifest_version": 3, - "name": "Yomitan", - "version": "23.4.7.0", - "description": "Japanese dictionary with Anki integration", - "author": "TheMoeWay", - "icons": { - "16": "images/icon16.png", - "19": "images/icon19.png", - "32": "images/icon32.png", - "38": "images/icon38.png", - "48": "images/icon48.png", - "64": "images/icon64.png", - "128": "images/icon128.png" - }, - "action": { - "default_icon": { - "16": "images/icon16.png", - "19": "images/icon19.png", - "32": "images/icon32.png", - "38": "images/icon38.png", - "48": "images/icon48.png", - "64": "images/icon64.png", - "128": "images/icon128.png" - }, - "default_title": "Yomitan", - "default_popup": "action-popup.html" - }, - "background": { - "service_worker": "sw.js" - }, - "content_scripts": [ - { - "run_at": "document_idle", - "matches": [ - "http://*/*", - "https://*/*", - "file://*/*" - ], - "match_about_blank": true, - "all_frames": true, - "js": [ - "js/core.js", - "js/yomichan.js", - "js/app/frontend.js", - "js/app/popup.js", - "js/app/popup-factory.js", - "js/app/popup-proxy.js", - "js/app/popup-window.js", - "js/app/theme-controller.js", - "js/comm/api.js", - "js/comm/cross-frame-api.js", - "js/comm/frame-ancestry-handler.js", - "js/comm/frame-client.js", - "js/comm/frame-offset-forwarder.js", - "js/data/sandbox/string-util.js", - "js/dom/dom-text-scanner.js", - "js/dom/document-util.js", - "js/dom/text-source-element.js", - "js/dom/text-source-range.js", - "js/input/hotkey-handler.js", - "js/language/text-scanner.js", - "js/script/dynamic-loader.js", - "js/app/content-script-main.js" - ] - } - ], - "minimum_chrome_version": "96.0.0.0", - "options_ui": { - "page": "settings.html", - "open_in_tab": true - }, - "sandbox": { - "pages": [ - "template-renderer.html" - ] - }, - "permissions": [ - "storage", - "clipboardWrite", - "unlimitedStorage", - "webRequest", - "declarativeNetRequest", - "scripting" - ], - "optional_permissions": [ - "clipboardRead", - "nativeMessaging" - ], - "host_permissions": [ - "" - ], - "commands": { - "toggleTextScanning": { - "suggested_key": { - "default": "Alt+Delete" - }, - "description": "Toggle text scanning on/off" - }, - "openInfoPage": { - "description": "Open the info page" - }, - "openSettingsPage": { - "description": "Open the settings page" - }, - "openSearchPage": { - "suggested_key": { - "default": "Alt+Insert" - }, - "description": "Open the search page" - }, - "openPopupWindow": { - "description": "Open the popup window" - } - }, - "web_accessible_resources": [ - { - "resources": [ - "popup.html", - "template-renderer.html" - ], - "matches": [ - "" - ] - } - ], - "content_security_policy": { - "extension_pages": "default-src 'self'; img-src blob: 'self'; style-src 'self' 'unsafe-inline'; media-src *; connect-src *", - "sandbox": "sandbox allow-scripts; default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'unsafe-inline'" - } -} -- cgit v1.2.3