From c3e6dae72c686b3fe79702400ce01d5a1249d971 Mon Sep 17 00:00:00 2001 From: Anton Bershanskiy <8knots@protonmail.com> Date: Sat, 1 Oct 2022 20:55:30 +0300 Subject: [PATCH] more --- src/background/index.ts | 9 ++- src/background/tab-manager.ts | 58 +++++++++++++------- src/ui/popup/components/header/index.tsx | 4 +- tasks/bundle-manifest.js | 16 ++++-- tests/browser/environment.js | 3 +- tests/browser/globals.d.ts | 1 + tests/inject/dynamic/image-analysis.tests.ts | 6 +- 7 files changed, 66 insertions(+), 31 deletions(-) diff --git a/src/background/index.ts b/src/background/index.ts index b9daf7017e7f..174c4a9f68bd 100644 --- a/src/background/index.ts +++ b/src/background/index.ts @@ -40,6 +40,9 @@ type TestMessage = { type: 'setDataIsMigratedForTesting'; data: boolean; id: number; +} | { + type: 'getManifest'; + id: number; }; // Start extension @@ -129,7 +132,7 @@ if (__WATCH__) { }; listen(); -} else if (!__DEBUG__){ +} else if (!__DEBUG__ && !__TEST__) { chrome.runtime.onInstalled.addListener(({reason}) => { if (reason === 'install') { chrome.tabs.create({url: getHelpURL()}); @@ -181,6 +184,10 @@ if (__TEST__) { DevTools.setDataIsMigratedForTesting(message.data); respond({type: 'setDataIsMigratedForTesting-response', id: message.id}); break; + case 'getManifest': { + const data = chrome.runtime.getManifest(); + respond({type: 'getManifest-response', data, id: message.id}); + } } } catch (err) { respond({type: 'error', data: String(err)}); diff --git a/src/background/tab-manager.ts b/src/background/tab-manager.ts index 3f415e6a7c9b..d94ba00540f8 100644 --- a/src/background/tab-manager.ts +++ b/src/background/tab-manager.ts @@ -4,7 +4,7 @@ import type {FetchRequestParameters} from './utils/network'; import type {Message} from '../definitions'; import {isFirefox} from '../utils/platform'; import {MessageType} from '../utils/message'; -import {logWarn} from './utils/log'; +import {logInfo, logWarn} from './utils/log'; import {StateManager} from '../utils/state-manager'; import {getURLHostOrProtocol} from '../utils/url'; import {isPanel} from './utils/tab'; @@ -12,12 +12,6 @@ import {isPanel} from './utils/tab'; declare const __CHROMIUM_MV3__: boolean; declare const __THUNDERBIRD__: boolean; -async function queryTabs(query: chrome.tabs.QueryInfo = {}) { - return new Promise((resolve) => - chrome.tabs.query(query, resolve) - ); -} - interface ConnectionMessageOptions { url: string; frameURL: string; @@ -52,7 +46,7 @@ enum DocumentState { HIDDEN = 2, FROZEN = 3, TERMINATED = 4, - DISCARDED = 5 + DISCARDED = 5, } export default class TabManager { @@ -134,7 +128,7 @@ export default class TabManager { const frameId = sender.frameId; const frameURL = sender.url; if (this.tabs[tabId][frameId].timestamp < this.timestamp) { - const message = this.getTabMessage(this.getTabURL(sender.tab), frameURL); + const message = this.getTabMessage(await this.getTabURL(sender.tab), frameURL); chrome.tabs.sendMessage(tabId, message, {frameId}); } this.tabs[sender.tab.id][sender.frameId] = { @@ -207,6 +201,12 @@ export default class TabManager { chrome.tabs.onRemoved.addListener(async (tabId) => this.removeFrame(tabId, 0)); } + private static async queryTabs(query: chrome.tabs.QueryInfo = {}) { + return new Promise((resolve) => + chrome.tabs.query(query, resolve) + ); + } + private static addFrame(tabId: number, frameId: number, senderURL: string, timestamp: number) { let frames: {[frameId: number]: FrameInfo}; if (this.tabs[tabId]) { @@ -238,7 +238,23 @@ export default class TabManager { this.stateManager.saveState(); } - static getTabURL(tab: chrome.tabs.Tab): string { + private static async getTabURL(tab: chrome.tabs.Tab): Promise { + if (__CHROMIUM_MV3__) { + try { + if (this.tabs[tab.id] && this.tabs[tab.id][0]) { + return this.tabs[tab.id][0].url || 'about:blank'; + } + return (await chrome.scripting.executeScript({ + target: { + tabId: tab.id, + frameIds: [0], + }, + func: () => window.location.href, + }))[0].result; + } catch (e) { + return 'about:blank'; + } + } // It can happen in cases whereby the tab.url is empty. // Luckily this only and will only happen on `about:blank`-like pages. // Due to this we can safely use `about:blank` as fallback value. @@ -246,8 +262,8 @@ export default class TabManager { } static async updateContentScript(options: {runOnProtectedPages: boolean}) { - (await queryTabs()) - .filter((tab) => options.runOnProtectedPages || canInjectScript(tab.url)) + (await this.queryTabs()) + .filter((tab) => __CHROMIUM_MV3__ || options.runOnProtectedPages || canInjectScript(tab.url)) .filter((tab) => !Boolean(this.tabs[tab.id])) .forEach((tab) => { if (!tab.discarded) { @@ -258,7 +274,7 @@ export default class TabManager { allFrames: true, }, files: ['/inject/index.js'], - }); + }, () => logInfo('Could not update content script in tab', tab, chrome.runtime.lastError)); } else { chrome.tabs.executeScript(tab.id, { runAt: 'document_start', @@ -283,23 +299,23 @@ export default class TabManager { // sendMessage will send a tab messages to all active tabs and their frames. // If onlyUpdateActiveTab is specified, it will only send a new message to any // tab that matches the active tab's hostname. This is to ensure that when a user - // has multiple tabs of the same website, it will ensure that every tab will receive - // the new message and not just that tab as Dark Reader currently doesn't have per-tab - // operations, this should be the expected behavior. + // has multiple tabs of the same website, every tab will receive the new message + // and not just that tab as Dark Reader currently doesn't have per-tab operations, + // this should be the expected behavior. static async sendMessage(onlyUpdateActiveTab = false) { this.timestamp++; const activeTabHostname = onlyUpdateActiveTab ? getURLHostOrProtocol(await this.getActiveTabURL()) : null; - (await queryTabs()) + (await this.queryTabs()) .filter((tab) => Boolean(this.tabs[tab.id])) .forEach((tab) => { const frames = this.tabs[tab.id]; Object.entries(frames) .filter(([, {state}]) => state === DocumentState.ACTIVE || state === DocumentState.PASSIVE) - .forEach(([id, {url}]) => { + .forEach(async ([id, {url}]) => { const frameId = Number(id); - const tabURL = this.getTabURL(tab); + const tabURL = await this.getTabURL(tab); // Check if hostname are equal when we only want to update active tab. if (onlyUpdateActiveTab && getURLHostOrProtocol(tabURL) !== activeTabHostname) { return; @@ -335,7 +351,7 @@ export default class TabManager { } static async getActiveTab() { - let tab = (await queryTabs({ + let tab = (await this.queryTabs({ active: true, lastFocusedWindow: true, // Explicitly exclude Dark Reader's Dev Tools and other special windows from the query @@ -344,7 +360,7 @@ export default class TabManager { if (!tab) { // When Dark Reader's DevTools are open, last focused window might be the DevTools window // so we lift this restriction and try again (with the best guess) - tab = (await queryTabs({ + tab = (await this.queryTabs({ active: true, windowType: 'normal', }))[0]; diff --git a/src/ui/popup/components/header/index.tsx b/src/ui/popup/components/header/index.tsx index ae989d4ca7d4..99aa4691c657 100644 --- a/src/ui/popup/components/header/index.tsx +++ b/src/ui/popup/components/header/index.tsx @@ -11,6 +11,8 @@ import {AutomationMode} from '../../../../utils/automation'; import {isLocalFile} from '../../../../utils/url'; import {isChromium} from '../../../../utils/platform'; +declare const __CHROMIUM_MV3__: boolean; + function multiline(...lines: string[]) { return lines.join('\n'); } @@ -44,7 +46,7 @@ function Header({data, actions, onMoreToggleSettingsClick}: HeaderProps) { data={data} actions={actions} /> - {!isFile && (tab.isProtected || !tab.isInjected) ? ( + {!isFile && (tab.isProtected || (!__CHROMIUM_MV3__ && !tab.isInjected)) ? ( {getLocalMessage('page_protected')} diff --git a/tasks/bundle-manifest.js b/tasks/bundle-manifest.js index fba178b3e3d8..a9a55edba007 100644 --- a/tasks/bundle-manifest.js +++ b/tasks/bundle-manifest.js @@ -17,7 +17,7 @@ async function writeJSON(path, json) { return await writeFile(path, content); } -async function patchManifest(platform, debug, watch) { +async function patchManifest(platform, debug, watch, test) { const manifest = await readJSON(`${srcDir}/manifest.json`); const manifestPatch = platform === PLATFORM.CHROME ? {} : await readJSON(`${srcDir}/manifest-${platform}.json`); const patched = {...manifest, ...manifestPatch}; @@ -25,16 +25,22 @@ async function patchManifest(platform, debug, watch) { patched.browser_action = undefined; } if (debug) { - patched.version = '0.0.0.0'; + patched.version = '1'; patched.description = `Debug build, platform: ${platform}, watch: ${watch ? 'yes' : 'no'}.`; } + if (debug && !test && platform === PLATFORM.CHROME_MV3) { + patched.permissions.push('tabs'); + } + if (debug && (platform === PLATFORM.CHROME || platform === PLATFORM.CHROME_MV3)) { + patched.version_name = 'Debug'; + } return patched; } -async function manifests({platforms, debug, watch}) { +async function manifests({platforms, debug, watch, test}) { const enabledPlatforms = Object.values(PLATFORM).filter((platform) => platform !== PLATFORM.API && platforms[platform]); for (const platform of enabledPlatforms) { - const manifest = await patchManifest(platform, debug, watch); + const manifest = await patchManifest(platform, debug, watch, test); const destDir = getDestDir({debug, platform}); await writeJSON(`${destDir}/manifest.json`, manifest); } @@ -52,7 +58,7 @@ const bundleManifestTask = createTask( const changed = chrome || changedFiles.some((file) => file.endsWith(`manifest-${platform}.json`)); platforms[platform] = changed && buildPlatforms[platform]; } - await manifests({platforms, debug: true, watch: true}); + await manifests({platforms, debug: true, watch: true, test: false}); reload.reload({type: reload.FULL}); }, ); diff --git a/tests/browser/environment.js b/tests/browser/environment.js index f347e0afb1bf..ad2ec1b0d041 100644 --- a/tests/browser/environment.js +++ b/tests/browser/environment.js @@ -115,7 +115,7 @@ class PuppeteerEnvironment extends JestNodeEnvironment.TestEnvironment { /** * @param {string} path - * @returns {puppeteer.Page} + * @returns {Promise} */ async openExtensionPage(path) { if (this.global.product === 'chrome') { @@ -236,6 +236,7 @@ class PuppeteerEnvironment extends JestNodeEnvironment.TestEnvironment { const bg = await this.getBackgroundPage(); await bg.emulateMediaFeatures([{name, value}]); }, + getManifest: async () => await sendToUIPage({type: 'getManifest'}), }; }); } diff --git a/tests/browser/globals.d.ts b/tests/browser/globals.d.ts index 1153f09f6bcd..743f3413f377 100644 --- a/tests/browser/globals.d.ts +++ b/tests/browser/globals.d.ts @@ -25,5 +25,6 @@ declare global { getChromeStorage: (region: 'local' | 'sync', keys: string[]) => Promise<{[key: string]: any}>; setDataIsMigratedForTesting: (value: boolean) => Promise; emulateMedia: (name: string, value: string) => Promise; + getManifest: () => Promise; }; } diff --git a/tests/inject/dynamic/image-analysis.tests.ts b/tests/inject/dynamic/image-analysis.tests.ts index 29d89e247dae..36d22d77a9c5 100644 --- a/tests/inject/dynamic/image-analysis.tests.ts +++ b/tests/inject/dynamic/image-analysis.tests.ts @@ -274,7 +274,9 @@ describe('IMAGE ANALYSIS', () => { ); createOrUpdateDynamicTheme(theme, null, false); await waitForEvent('__darkreader__test__asyncQueueComplete'); - expect(getComputedStyle(container.querySelector('h1')).backgroundImage.startsWith(isFirefox ? `url("about:invalid"), url("about:invalid")` : `url("http://localhost`)).toBeTrue(); - expect(getComputedStyle(container.querySelector('h1')).backgroundImage.endsWith(`url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iOCIgaGVpZ2h0PSI4Ij48ZGVmcz48ZmlsdGVyIGlkPSJkYXJrcmVhZGVyLWltYWdlLWZpbHRlciI+PGZlQ29sb3JNYXRyaXggdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAuMzMzIC0wLjY2NyAtMC42NjcgMC4wMDAgMS4wMDAgLTAuNjY3IDAuMzMzIC0wLjY2NyAwLjAwMCAxLjAwMCAtMC42NjcgLTAuNjY3IDAuMzMzIDAuMDAwIDEuMDAwIDAuMDAwIDAuMDAwIDAuMDAwIDEuMDAwIDAuMDAwIiAvPjwvZmlsdGVyPjwvZGVmcz48aW1hZ2Ugd2lkdGg9IjgiIGhlaWdodD0iOCIgZmlsdGVyPSJ1cmwoI2RhcmtyZWFkZXItaW1hZ2UtZmlsdGVyKSIgeGxpbms6aHJlZj0iZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlCNGJXeHVjejBpYUhSMGNEb3ZMM2QzZHk1M015NXZjbWN2TWpBd01DOXpkbWNpSUhacFpYZENiM2c5SWpBZ01DQTRJRGdpSUhkcFpIUm9QU0k0SWlCb1pXbG5hSFE5SWpnaVBnb2dJQ0FnUEhKbFkzUWdabWxzYkQwaWQyaHBkR1VpSUhkcFpIUm9QU0l4TURBbElpQm9aV2xuYUhROUlqRXdNQ1VpSUM4K0Nqd3ZjM1puUGc9PSIgLz48L3N2Zz4=")`)).toBeTrue(); + expect(getComputedStyle(container.querySelector('h1')).backgroundImage === (isFirefox ? + 'url("about:invalid"), url("about:invalid"), url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iOCIgaGVpZ2h0PSI4Ij48ZGVmcz48ZmlsdGVyIGlkPSJkYXJrcmVhZGVyLWltYWdlLWZpbHRlciI+PGZlQ29sb3JNYXRyaXggdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAuMzMzIC0wLjY2NyAtMC42NjcgMC4wMDAgMS4wMDAgLTAuNjY3IDAuMzMzIC0wLjY2NyAwLjAwMCAxLjAwMCAtMC42NjcgLTAuNjY3IDAuMzMzIDAuMDAwIDEuMDAwIDAuMDAwIDAuMDAwIDAuMDAwIDEuMDAwIDAuMDAwIiAvPjwvZmlsdGVyPjwvZGVmcz48aW1hZ2Ugd2lkdGg9IjgiIGhlaWdodD0iOCIgZmlsdGVyPSJ1cmwoI2RhcmtyZWFkZXItaW1hZ2UtZmlsdGVyKSIgeGxpbms6aHJlZj0iZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlCNGJXeHVjejBpYUhSMGNEb3ZMM2QzZHk1M015NXZjbWN2TWpBd01DOXpkbWNpSUhacFpYZENiM2c5SWpBZ01DQTRJRGdpSUhkcFpIUm9QU0k0SWlCb1pXbG5hSFE5SWpnaVBnb2dJQ0FnUEhKbFkzUWdabWxzYkQwaWQyaHBkR1VpSUhkcFpIUm9QU0l4TURBbElpQm9aV2xuYUhROUlqRXdNQ1VpSUM4K0Nqd3ZjM1puUGc9PSIgLz48L3N2Zz4=")' + : + `url("about:invalid"), url("about:invalid")`)); }); });