Skip to content

Commit 00bfc3c

Browse files
authored
Refactor: send export CSS command directly from UI to CS (darkreader#11273)
1 parent 2792091 commit 00bfc3c

7 files changed

Lines changed: 105 additions & 71 deletions

File tree

src/background/tab-manager.ts

Lines changed: 17 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,21 @@
11
import {canInjectScript} from '../background/utils/extension-api';
22
import {createFileLoader} from './utils/network';
33
import type {FetchRequestParameters} from './utils/network';
4-
import type {MessageBGtoCS, MessageCStoBG, MessageUItoBG} from '../definitions';
4+
import type {MessageBGtoCS, MessageCStoBG, MessageUItoBG, documentId, frameId, tabId} from '../definitions';
55
import {isFirefox} from '../utils/platform';
66
import {MessageTypeCStoBG, MessageTypeBGtoCS, MessageTypeUItoBG} from '../utils/message';
77
import {ASSERT, logInfo, logWarn} from './utils/log';
88
import {StateManager} from '../utils/state-manager';
99
import {getURLHostOrProtocol} from '../utils/url';
1010
import {isPanel} from './utils/tab';
1111
import {makeFirefoxHappy} from './make-firefox-happy';
12+
import {getActiveTab, queryTabs} from '../utils/tabs';
1213

1314
declare const __CHROMIUM_MV2__: boolean;
1415
declare const __CHROMIUM_MV3__: boolean;
1516
declare const __FIREFOX_MV2__: boolean;
1617
declare const __THUNDERBIRD__: boolean;
1718

18-
// ContextId is a number on Firefox and documentId is a string in Chromium,
19-
// let's use string for simplicity
20-
type documentId = string;
21-
type tabId = number;
22-
type frameId = number;
23-
2419
interface TabManagerOptions {
2520
getConnectionMessage: (tabURl: string, url: string, isTopFrame: boolean) => Promise<MessageBGtoCS>;
2621
getTabMessage: (tabURL: string, url: string, isTopFrame: boolean) => MessageBGtoCS;
@@ -55,12 +50,12 @@ enum DocumentState {
5550
}
5651

5752
export default class TabManager {
58-
private static tabs: {[tabId: tabId]: {[frameId: frameId]: DocumentInfo}};
53+
private static tabs: TabManagerState['tabs'];
5954
private static stateManager: StateManager<TabManagerState>;
6055
private static fileLoader: {get: (params: FetchRequestParameters) => Promise<string | null>} | null = null;
6156
private static onColorSchemeChange: TabManagerOptions['onColorSchemeChange'];
6257
private static getTabMessage: TabManagerOptions['getTabMessage'];
63-
private static timestamp = 0;
58+
private static timestamp: TabManagerState['timestamp'];
6459
private static readonly LOCAL_STORAGE_KEY = 'TabManager-state';
6560

6661
public static init({getConnectionMessage, onColorSchemeChange, getTabMessage}: TabManagerOptions): void {
@@ -208,12 +203,6 @@ export default class TabManager {
208203
break;
209204
}
210205

211-
case MessageTypeUItoBG.REQUEST_EXPORT_CSS: {
212-
const activeTab = await TabManager.getActiveTab();
213-
chrome.tabs.sendMessage<MessageBGtoCS>(activeTab.id!, {type: MessageTypeBGtoCS.EXPORT_CSS}, {frameId: 0});
214-
break;
215-
}
216-
217206
default:
218207
break;
219208
}
@@ -234,12 +223,6 @@ export default class TabManager {
234223
}
235224
}
236225

237-
private static async queryTabs(query: chrome.tabs.QueryInfo = {}) {
238-
return new Promise<chrome.tabs.Tab[]>((resolve) =>
239-
chrome.tabs.query(query, resolve)
240-
);
241-
}
242-
243226
private static addFrame(tabId: tabId, frameId: frameId, documentId: documentId, url: string, timestamp: number) {
244227
let frames: {[frameId: frameId]: DocumentInfo};
245228
if (TabManager.tabs[tabId]) {
@@ -273,8 +256,11 @@ export default class TabManager {
273256
TabManager.stateManager.saveState();
274257
}
275258

276-
private static async getTabURL(tab: chrome.tabs.Tab): Promise<string> {
259+
private static async getTabURL(tab: chrome.tabs.Tab | null): Promise<string> {
277260
if (__CHROMIUM_MV3__) {
261+
if (!tab) {
262+
return 'abou:blank';
263+
}
278264
try {
279265
if (TabManager.tabs[tab.id!] && TabManager.tabs[tab.id!][0]) {
280266
return TabManager.tabs[tab.id!][0].url || 'about:blank';
@@ -298,7 +284,7 @@ export default class TabManager {
298284
}
299285

300286
public static async updateContentScript(options: {runOnProtectedPages: boolean}): Promise<void> {
301-
(await TabManager.queryTabs())
287+
(await queryTabs())
302288
.filter((tab) => __CHROMIUM_MV3__ || options.runOnProtectedPages || canInjectScript(tab.url))
303289
.filter((tab) => !Boolean(TabManager.tabs[tab.id!]))
304290
.forEach((tab) => {
@@ -343,7 +329,7 @@ export default class TabManager {
343329

344330
const activeTabHostname = onlyUpdateActiveTab ? getURLHostOrProtocol(await TabManager.getActiveTabURL()) : null;
345331

346-
(await TabManager.queryTabs())
332+
(await queryTabs())
347333
.filter((tab) => Boolean(TabManager.tabs[tab.id!]))
348334
.forEach((tab) => {
349335
const frames = TabManager.tabs[tab.id!];
@@ -373,48 +359,19 @@ export default class TabManager {
373359
}
374360

375361
public static async canAccessActiveTab(): Promise<boolean> {
376-
const tab = await TabManager.getActiveTab();
377-
return Boolean(TabManager.tabs[tab.id!]);
362+
const tab = await getActiveTab();
363+
return tab && Boolean(TabManager.tabs[tab.id!]) || false;
378364
}
379365

380366
public static async isActiveTabDarkThemeDetected(): Promise<boolean | null> {
381-
const tab = await TabManager.getActiveTab();
367+
const tab = await getActiveTab();
368+
if (!tab) {
369+
return null;
370+
}
382371
return TabManager.tabs[tab.id!] && TabManager.tabs[tab.id!][0] && TabManager.tabs[tab.id!][0].darkThemeDetected || null;
383372
}
384373

385374
public static async getActiveTabURL(): Promise<string> {
386-
return TabManager.getTabURL(await TabManager.getActiveTab());
387-
}
388-
389-
public static async getActiveTab(): Promise<chrome.tabs.Tab> {
390-
let tab = (await TabManager.queryTabs({
391-
active: true,
392-
lastFocusedWindow: true,
393-
// Explicitly exclude Dark Reader's Dev Tools and other special windows from the query
394-
windowType: 'normal',
395-
}))[0];
396-
if (!tab) {
397-
tab = (await TabManager.queryTabs({
398-
active: true,
399-
lastFocusedWindow: true,
400-
windowType: 'app',
401-
}))[0];
402-
}
403-
if (!tab) {
404-
// When Dark Reader's DevTools are open, last focused window might be the DevTools window
405-
// so we lift this restriction and try again (with the best guess)
406-
tab = (await TabManager.queryTabs({
407-
active: true,
408-
windowType: 'normal',
409-
}))[0];
410-
if (!tab) {
411-
tab = (await TabManager.queryTabs({
412-
active: true,
413-
windowType: 'app',
414-
}))[0];
415-
}
416-
logWarn('TabManager.getActiveTab() could not reliably find the active tab, picking the best guess', tab);
417-
}
418-
return tab;
375+
return TabManager.getTabURL(await getActiveTab());
419376
}
420377
}

src/definitions.d.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
import type {ParsedColorSchemeConfig} from './utils/colorscheme-parser';
22
import type {FilterMode} from './generators/css-filter';
3-
import type {MessageTypeBGtoCS, MessageTypeBGtoUI, MessageTypeCStoBG, MessageTypeCStoUI, MessageTypeUItoBG} from './utils/message';
3+
import type {MessageTypeBGtoCS, MessageTypeBGtoUI, MessageTypeCStoBG, MessageTypeCStoUI, MessageTypeUItoBG, MessageTypeUItoCS} from './utils/message';
44
import type {AutomationMode} from './utils/automation';
55
import type {ThemeEngine} from './generators/theme-engines';
66

77
export type ColorScheme = 'dark' | 'light';
88

9+
// ContextId is a number on Firefox and documentId is a string in Chromium,
10+
// let's use string for simplicity
11+
export type documentId = string;
12+
export type tabId = number;
13+
export type frameId = number;
14+
915
export interface ExtensionData {
1016
isEnabled: boolean;
1117
isReady: boolean;
@@ -141,6 +147,10 @@ export interface MessageCStoBG {
141147
data?: any;
142148
}
143149

150+
export interface MessageUItoCS {
151+
type: MessageTypeUItoCS;
152+
}
153+
144154
export interface MessageCStoUI {
145155
id?: string;
146156
type: MessageTypeCStoUI;

src/inject/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import {createOrUpdateDynamicTheme, removeDynamicTheme, cleanDynamicThemeCache}
55
import {logWarn, logInfoCollapsed} from './utils/log';
66
import {isSystemDarkModeEnabled, runColorSchemeChangeDetector, stopColorSchemeChangeDetector, emulateColorScheme} from '../utils/media-query';
77
import {collectCSS} from './dynamic-theme/css-collection';
8-
import type {DynamicThemeFix, MessageBGtoCS, MessageCStoBG, MessageCStoUI, Theme} from '../definitions';
9-
import {MessageTypeBGtoCS, MessageTypeCStoBG, MessageTypeCStoUI} from '../utils/message';
8+
import type {DynamicThemeFix, MessageBGtoCS, MessageCStoBG, MessageCStoUI, MessageUItoCS, Theme} from '../definitions';
9+
import {MessageTypeBGtoCS, MessageTypeCStoBG, MessageTypeCStoUI, MessageTypeUItoCS} from '../utils/message';
1010

1111
declare const __TEST__: boolean;
1212

@@ -73,7 +73,7 @@ function sendMessage(message: MessageCStoBG | MessageCStoUI) {
7373
}
7474
}
7575

76-
function onMessage({type, data}: MessageBGtoCS) {
76+
function onMessage({type, data}: MessageBGtoCS | MessageUItoCS & {data: any}) {
7777
logInfoCollapsed(`onMessage[${type}]`, data);
7878
switch (type) {
7979
case MessageTypeBGtoCS.ADD_CSS_FILTER:
@@ -126,7 +126,7 @@ function onMessage({type, data}: MessageBGtoCS) {
126126
}
127127
break;
128128
}
129-
case MessageTypeBGtoCS.EXPORT_CSS:
129+
case MessageTypeUItoCS.EXPORT_CSS:
130130
collectCSS().then((collectedCSS) => sendMessage({type: MessageTypeCStoUI.EXPORT_CSS_RESPONSE, data: collectedCSS}));
131131
break;
132132
case MessageTypeBGtoCS.UNSUPPORTED_SENDER:

src/ui/popup/manage-settings-page/export-theme.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import {Button} from '../../controls';
33
import {saveFile} from '../../utils';
44
import ControlGroup from '../control-group';
55
import {getURLHostOrProtocol} from '../../../utils/url';
6-
import type {MessageCStoUI, MessageUItoBG} from '../../../definitions';
7-
import {MessageTypeCStoUI, MessageTypeUItoBG} from '../../../utils/message';
6+
import type {MessageCStoUI, MessageUItoCS} from '../../../definitions';
7+
import {MessageTypeCStoUI, MessageTypeUItoCS} from '../../../utils/message';
8+
import {getActiveTab} from '../../../utils/tabs';
89

910
export default function ExportTheme() {
1011
const listener = ({type, data}: MessageCStoUI, sender: chrome.runtime.MessageSender) => {
@@ -15,9 +16,13 @@ export default function ExportTheme() {
1516
}
1617
};
1718

18-
function exportCSS() {
19+
async function exportCSS() {
20+
const activeTab = await getActiveTab();
21+
if (!activeTab || !activeTab.id) {
22+
return;
23+
}
1924
chrome.runtime.onMessage.addListener(listener);
20-
chrome.runtime.sendMessage<MessageUItoBG>({type: MessageTypeUItoBG.REQUEST_EXPORT_CSS});
25+
chrome.tabs.sendMessage<MessageUItoCS>(activeTab.id, {type: MessageTypeUItoCS.EXPORT_CSS}, {frameId: 0});
2126
}
2227

2328
return (

src/utils/message.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ export enum MessageTypeUItoBG {
1616
APPLY_DEV_STATIC_THEMES = 'ui-bg-apply-dev-static-themes',
1717
RESET_DEV_STATIC_THEMES = 'ui-bg-reset-dev-static-themes',
1818
SAVE_FILE = 'ui-bg-save-file',
19-
REQUEST_EXPORT_CSS = 'ui-bg-request-export-css',
2019
COLOR_SCHEME_CHANGE = 'ui-bg-color-scheme-change',
2120
HIDE_HIGHLIGHTS = 'ui-bg-hide-highlights'
2221
}
@@ -31,7 +30,6 @@ export enum MessageTypeBGtoCS {
3130
ADD_STATIC_THEME = 'bg-cs-add-static-theme',
3231
ADD_SVG_FILTER = 'bg-cs-add-svg-filter',
3332
ADD_DYNAMIC_THEME = 'bg-cs-add-dynamic-theme',
34-
EXPORT_CSS = 'bg-cs-export-css',
3533
UNSUPPORTED_SENDER = 'bg-cs-unsupported-sender',
3634
CLEAN_UP = 'bg-cs-clean-up',
3735
RELOAD = 'bg-cs-reload',
@@ -54,3 +52,7 @@ export enum MessageTypeCStoBG {
5452
export enum MessageTypeCStoUI {
5553
EXPORT_CSS_RESPONSE = 'cs-ui-export-css-response'
5654
}
55+
56+
export enum MessageTypeUItoCS {
57+
EXPORT_CSS = 'ui-cs-export-css'
58+
}

src/utils/tabs.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
declare const __TEST__: boolean;
2+
declare const __DEBUG__: boolean;
3+
4+
// Promissified version of chrome.tabs.query
5+
export async function queryTabs(query: chrome.tabs.QueryInfo = {}): Promise<chrome.tabs.Tab[]> {
6+
return new Promise<chrome.tabs.Tab[]>((resolve) => chrome.tabs.query(query, resolve));
7+
}
8+
9+
/**
10+
* Attempts to find the current active tab
11+
* Despite all efforts, sometimes active tab may not be determined so we explicitly return nullable value,
12+
* and handle this case in callers explicitly
13+
*/
14+
export async function getActiveTab(): Promise<chrome.tabs.Tab | null> {
15+
let log: string | null = null;
16+
let tab = (await queryTabs({
17+
active: true,
18+
lastFocusedWindow: true,
19+
// Explicitly exclude Dark Reader's Dev Tools and other special windows from the query
20+
windowType: 'normal',
21+
}))[0];
22+
if (!tab) {
23+
tab = (await queryTabs({
24+
active: true,
25+
lastFocusedWindow: true,
26+
windowType: 'app',
27+
}))[0];
28+
}
29+
if (!tab) {
30+
if (__DEBUG__ || __TEST__) {
31+
log = 'method 1';
32+
}
33+
// When Dark Reader's DevTools are open, last focused window might be the DevTools window
34+
// so we lift this restriction and try again (with the best guess)
35+
tab = (await queryTabs({
36+
active: true,
37+
windowType: 'normal',
38+
}))[0];
39+
}
40+
if (!tab) {
41+
if (__DEBUG__ || __TEST__) {
42+
log = 'method 2';
43+
}
44+
tab = (await queryTabs({
45+
active: true,
46+
windowType: 'app',
47+
}))[0];
48+
}
49+
if (log) {
50+
console.warn(`TabManager.getActiveTab() could not reliably find the active tab, picking the best guess ${log}`, tab);
51+
}
52+
// In rare cases tab can be null, despite what TypeScript says
53+
return tab || null;
54+
}
55+
56+
export async function getActiveTabURL(): Promise<string | null> {
57+
const tab = await getActiveTab();
58+
return tab && tab.url || null;
59+
}

tests/project/__snapshots__/tsconf.tests.ts.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ exports[`TypeScript project config file should parse and resolve correctly: src
222222
"./utils/promise-barrier.ts",
223223
"./utils/state-manager-impl.ts",
224224
"./utils/state-manager.ts",
225+
"./utils/tabs.ts",
225226
"./utils/text.ts",
226227
"./utils/throttle.ts",
227228
"./utils/time.ts",

0 commit comments

Comments
 (0)