Skip to content

Commit 31b2623

Browse files
committed
wait for NavLock to latch + use offscreen db cache
1 parent cfa3014 commit 31b2623

23 files changed

+292
-251
lines changed

src/.types.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ declare var __: {
1616
KEEP_ALIVE: <T>(job: T) => T,
1717
MV3: boolean,
1818
PAGE_BG: 'background' | 'sw',
19-
PAGE_OFFSCREEN: 'offscreen',
2019
THEMES: Record<string, string>,
2120
ZIP: boolean,
2221
}

src/background/broadcast.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import '@/js/browser';
2-
import {getWindowClients} from '@/background/common';
32
import {rxIgnorableError} from '@/js/msg-api';
43
import {ownRoot, supported} from '@/js/urls';
54
import {getActiveTab} from '@/js/util-webext';
65
import tabCache, * as tabMan from './tab-manager';
6+
import {getWindowClients} from './util';
77

88
let /**@type{?[]}*/toBroadcast;
99
let /**@type{boolean[]}*/toBroadcastStyled;

src/background/color-scheme.js

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
import {kStyleViaXhr} from '@/js/consts';
2-
import {CONNECTED} from '@/js/port';
1+
import {kDark, kStyleViaXhr, STATE_DB} from '@/js/consts';
2+
import {CLIENT} from '@/js/port';
33
import * as prefs from '@/js/prefs';
44
import {debounce, isCssDarkScheme} from '@/js/util';
55
import {broadcastExtension} from './broadcast';
6-
import {bgBusy, bgInit, bgPreInit, stateDB} from './common';
7-
import offscreen from './offscreen';
6+
import {bgBusy, bgInit, bgPreInit} from './common';
7+
import {stateDB} from './db';
8+
import offscreen, {offscreenCache} from './offscreen';
89

910
const changeListeners = new Set();
1011
const kSTATE = 'schemeSwitcher.enabled';
1112
const kSTART = 'schemeSwitcher.nightStart';
1213
const kEND = 'schemeSwitcher.nightEnd';
13-
const kDark = 'dark';
1414
const kLight = 'light';
1515
const kNever = 'never';
1616
const kSystem = 'system';
@@ -35,20 +35,16 @@ let prefState;
3535
chrome.alarms.onAlarm.addListener(onAlarm);
3636

3737
if (__.MV3) {
38-
bgPreInit.push(stateDB.get(kDark).then(v => {
39-
if (!v) {
40-
isDark = false;
41-
} else {
42-
isDark ??= v[0];
43-
Object.assign(map, v[1]);
44-
}
38+
bgPreInit.push(offscreenCache.then(v => {
39+
if (v && (v = v[STATE_DB])) setSystemDark(v.get(kDark));
40+
else bgInit.push(refreshSystemDark);
4541
}));
46-
bgInit.push(refreshSystemDark);
47-
prefs.subscribe([kSTATE, kStyleViaXhr], () => {
48-
const val = prefState === kSystem || prefs.__values[kStyleViaXhr];
49-
if (val || offscreen[CONNECTED]) {
42+
prefs.subscribe([kSTATE, kStyleViaXhr], (key, val, init) => {
43+
if (init && key !== kStyleViaXhr) // only process the last one on init
44+
return;
45+
val = prefState === kSystem || prefs.__values[kStyleViaXhr];
46+
if (val || offscreen[CLIENT])
5047
offscreen.keepAlive(val);
51-
}
5248
}, true);
5349
} else {
5450
refreshSystemDark();
@@ -127,6 +123,8 @@ function updateTimePreferDark() {
127123
function update(type, val) {
128124
if (type) {
129125
if (map[type] === val) return;
126+
if (__.MV3 && type === kSystem)
127+
stateDB.put(val, kDark);
130128
map[type] = val;
131129
}
132130
val = map[prefState];

src/background/common.js

Lines changed: 7 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,10 @@
1-
import {k_busy, kStateDB} from '@/js/consts';
2-
import {CONNECTED, createPortProxy} from '@/js/port';
1+
import {k_busy, kResolve} from '@/js/consts';
32
import {CHROME} from '@/js/ua';
4-
import {workerPath} from '@/js/urls';
5-
import {promiseWithResolve, sleep} from '@/js/util';
63
import {browserWindows} from '@/js/util-webext';
7-
import {getDbProxy} from './db';
8-
import offscreen from './offscreen';
94

10-
export let bgBusy = promiseWithResolve();
115
/** Minimal init for a wake-up event */
126
export const bgPreInit = [];
137
export const bgInit = [];
14-
15-
const CLIENT_TIMEOUT = 100;
168
export const clientDataJobs = {};
179

1810
/** Temporary storage for data needed elsewhere e.g. in a content script */
@@ -31,36 +23,8 @@ export const dataHub = {
3123
};
3224
const data = {__proto__: null};
3325

34-
/** @return {WindowClient} the offscreen document if it runs, otherwise any available client */
35-
export const getClient = async () => {
36-
for (let busy, client, job, tEnd;
37-
!tEnd || performance.now() < tEnd;
38-
tEnd ??= performance.now() + CLIENT_TIMEOUT) {
39-
for (const c of await getWindowClients()) {
40-
if ((job = clientDataJobs[c.url])) {
41-
(busy ??= []).push(job);
42-
} else if (c.url.endsWith(__.PAGE_OFFSCREEN)) {
43-
return c;
44-
} else {
45-
client = c;
46-
}
47-
}
48-
if (client)
49-
return client;
50-
if (!busy || !await Promise.race([
51-
Promise.any(busy).catch(() => 0),
52-
sleep(CLIENT_TIMEOUT),
53-
])) break;
54-
}
55-
};
56-
57-
/** @return {WindowClient[]} */
58-
export const getWindowClients = () => self.clients.matchAll({
59-
includeUncontrolled: true,
60-
type: 'window',
61-
});
62-
63-
export const stateDB = __.MV3 && getDbProxy(kStateDB, {store: 'kv'});
26+
export const onUnload = new Set();
27+
export const onUrl = new Set();
6428

6529
export const uuidIndex = Object.assign(new Map(), {
6630
custom: {},
@@ -70,18 +34,6 @@ export const uuidIndex = Object.assign(new Map(), {
7034
},
7135
});
7236

73-
/** @type {WorkerAPI} */
74-
export const worker = !__.MV3
75-
? createPortProxy(workerPath)
76-
: createPortProxy(async () => {
77-
const client = await getClient();
78-
const proxy = !client || client.url.endsWith(__.PAGE_OFFSCREEN) ? (
79-
offscreen[CONNECTED] ??= client,
80-
offscreen
81-
) : createPortProxy(client, {once: true});
82-
return proxy.getWorkerPort(workerPath);
83-
}, {lock: workerPath});
84-
8537
export let isVivaldi = !!(browserWindows && CHROME) && (async () => {
8638
const wnd = (await browserWindows.getAll())[0] ||
8739
await new Promise(resolve => browserWindows.onCreated.addListener(function onCreated(w) {
@@ -92,7 +44,10 @@ export let isVivaldi = !!(browserWindows && CHROME) && (async () => {
9244
return isVivaldi;
9345
})();
9446

95-
global[k_busy] = bgBusy;
47+
export let bgBusy = global[k_busy] = (_ =>
48+
Object.assign(new Promise(cb => (_ = cb)), {[kResolve]: _})
49+
)();
50+
9651
bgBusy.then(() => {
9752
bgBusy = null;
9853
delete global[k_busy];

src/background/db.js

Lines changed: 66 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1+
import {CACHE_DB, DB, STATE_DB} from '@/js/consts';
12
import {API} from '@/js/msg-api';
3+
import {CLIENT} from '@/js/port';
24
import {STORAGE_KEY} from '@/js/prefs';
35
import {chromeLocal} from '@/js/storage-util';
46
import {CHROME} from '@/js/ua';
5-
import {deepCopy} from '@/js/util';
7+
import {deepMerge} from '@/js/util';
8+
import {bgBusy} from './common';
69
import ChromeStorageDB from './db-chrome-storage';
10+
import offscreen, {offscreenCache} from './offscreen';
11+
import {offloadCache} from './style-manager/util';
712

813
/*
914
Initialize a database. There are some problems using IndexedDB in Firefox:
@@ -15,7 +20,7 @@ import ChromeStorageDB from './db-chrome-storage';
1520
let exec = __.BUILD === 'chrome' || CHROME
1621
? dbExecIndexedDB
1722
: tryUsingIndexedDB;
18-
const DB = 'stylish';
23+
const cachedClient = new WeakSet();
1924
const FALLBACK = 'dbInChromeStorage';
2025
const REASON = FALLBACK + 'Reason';
2126
const CACHING = {};
@@ -26,7 +31,7 @@ const dataCache = {};
2631
const proxies = {};
2732
const databases = {};
2833
const proxyHandler = {
29-
get: ({dbName}, cmd) => (CACHING[dbName] ? cachedExec : exec).bind(null, dbName, cmd),
34+
get: ({dbName}, cmd) => (CACHING[dbName] || exec).bind(null, dbName, cmd),
3035
};
3136
/**
3237
* @param {string} dbName
@@ -36,40 +41,85 @@ const proxyHandler = {
3641
* @param {string} [cfg.store]
3742
* @return {IDBObjectStoreMany}
3843
*/
39-
export const getDbProxy = (dbName, {
44+
const getDbProxy = (dbName, {
4045
cache,
4146
id,
4247
store = 'data',
4348
ver = 2,
4449
} = {}) => (proxies[dbName] ??= (
45-
(CACHING[dbName] = cache),
50+
(CACHING[dbName] = typeof cache === 'function' ? cache : cache && cachedExec),
4651
(DATA_KEY[dbName] = !id || typeof id === 'string' ? id : 'id'),
4752
(STORES[dbName] = store),
4853
(VERSIONS[dbName] = ver),
4954
new Proxy({dbName}, proxyHandler)
5055
));
5156

52-
export const db = getDbProxy(DB, {id: true, store: 'styles'});
57+
export const cacheDB = getDbProxy(CACHE_DB, {
58+
id: 'url',
59+
cache: __.MV3 && cachedExecOffscreen,
60+
});
61+
export const db = getDbProxy(DB, {
62+
id: true,
63+
store: 'styles',
64+
cache: __.MV3 && cachedExecOffscreen,
65+
});
5366
export const draftsDb = getDbProxy('drafts', {cache: true});
5467
/** Storage for big items that may exceed 8kB limit of chrome.storage.sync.
5568
* To make an item syncable register it with uuidIndex.addCustom. */
56-
export const prefsDb = getDbProxy(STORAGE_KEY, {cache: true});
69+
export const prefsDb = getDbProxy(STORAGE_KEY, {
70+
cache: !__.MV3 || cachedExecOffscreen,
71+
});
72+
export const stateDB = __.MV3 && getDbProxy(STATE_DB, {
73+
store: 'kv',
74+
cache: cachedExecOffscreen,
75+
});
5776

5877
Object.assign(API, /** @namespace API */ {
5978
draftsDb,
6079
prefsDb,
6180
});
6281

6382
async function cachedExec(dbName, cmd, a, b) {
64-
const hub = dataCache[dbName] || (dataCache[dbName] = {});
65-
const res = cmd === 'get' && a in hub ? hub[a] : await exec(...arguments);
66-
if (cmd === 'get') {
67-
hub[a] = deepCopy(res);
68-
} else if (cmd === 'put') {
69-
const key = DATA_KEY[dbName];
70-
hub[key ? a[key] : b] = deepCopy(a);
71-
} else if (cmd === 'delete') {
72-
delete hub[a];
83+
const old = dataCache[dbName];
84+
const hub = old || (dataCache[dbName] = {__proto__: null});
85+
const res = cmd === 'get' && a in hub
86+
? hub[a]
87+
: old && cmd === 'getAll'
88+
? Object.values(old)
89+
: await exec(...arguments);
90+
switch (cmd) {
91+
case 'put':
92+
cmd = DATA_KEY[dbName];
93+
hub[cmd ? a[cmd] : b] = deepMerge(a);
94+
break;
95+
case 'delete':
96+
delete hub[a];
97+
break;
98+
case 'clear':
99+
delete dataCache[dbName];
100+
break;
101+
}
102+
return res && typeof res === 'object' ? deepMerge(res) : res;
103+
}
104+
105+
async function cachedExecOffscreen(dbName, cmd, a) {
106+
let res;
107+
const isRead = cmd === 'get' || cmd === 'getAll';
108+
if (isRead
109+
&& offscreenCache
110+
&& await offscreenCache
111+
&& (res = offscreenCache[dbName])) {
112+
res = cmd === 'get' ? res.get(a) : [...res.values()];
113+
} else {
114+
if ((a = offscreen[CLIENT])) {
115+
if (!cachedClient.has(a)) {
116+
cachedClient.add(a);
117+
if (!bgBusy) offloadCache(dataCache[STATE_DB] || {});
118+
} else if (!isRead) {
119+
offscreen.dbCache(...arguments);
120+
}
121+
}
122+
res = (dbName === STATE_DB || dbName === STORAGE_KEY ? cachedExec : exec)(...arguments);
73123
}
74124
return res;
75125
}

src/background/icon-manager.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import {kDisableAll} from '@/js/consts';
2-
import {subscribe, __values as __prefs} from '@/js/prefs';
2+
import {__values as __prefs, subscribe} from '@/js/prefs';
33
import {CHROME, FIREFOX, MOBILE, VIVALDI} from '@/js/ua';
44
import {debounce, t} from '@/js/util';
55
import {ignoreChromeError, MF_ICON_EXT, MF_ICON_PATH} from '@/js/util-webext';
66
import * as colorScheme from './color-scheme';
7-
import {bgBusy, bgInit} from './common';
7+
import {bgBusy, bgInit, onUnload} from './common';
88
import {removePreloadedStyles} from './style-via-webrequest';
99
import tabCache, * as tabMan from './tab-manager';
1010

@@ -57,7 +57,7 @@ function initIcons(runNow = !__.MV3) {
5757
], () => debounce(refreshAllIcons), runNow);
5858
}
5959

60-
tabMan.onUnload.add((tabId, frameId, port) => {
60+
onUnload.add((tabId, frameId, port) => {
6161
if (frameId && tabMan.getStyleIds(tabId)) {
6262
updateIconBadge.call(port, [], {lazyBadge: true});
6363
}

src/background/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ import {broadcast, pingTab} from './broadcast';
1111
import './broadcast-injector-config';
1212
import initBrowserCommandsApi from './browser-cmd-hotkeys';
1313
import {setSystemDark} from './color-scheme';
14-
import {bgBusy, bgInit, bgPreInit, dataHub, stateDB} from './common';
14+
import {bgBusy, bgInit, bgPreInit, dataHub} from './common';
1515
import reinjectContentScripts from './content-scripts';
1616
import initContextMenus from './context-menus';
17-
import {draftsDb, prefsDb} from './db';
17+
import {draftsDb, prefsDb, stateDB} from './db';
1818
import download from './download';
1919
import {refreshIconsWhenReady, updateIconBadge} from './icon-manager';
2020
import prefsApi from './prefs-api';

src/background/offscreen.js

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,33 @@
1-
import {createPortProxy} from '@/js/port';
1+
import {CLIENT, createPortProxy} from '@/js/port';
22
import {ownRoot} from '@/js/urls';
3-
import {getWindowClients} from './common';
3+
import {bgBusy} from './common';
4+
import {getWindowClients} from './util';
45

5-
let creating;
6-
7-
export const getOffscreenClient = () => (creating ??= create());
8-
const FILENAME = __.PAGE_OFFSCREEN + '.html';
6+
const FILENAME = 'offscreen.html';
97
const DOC_URL = ownRoot + FILENAME;
8+
109
/** @type {OffscreenAPI | CommandsAPI} */
11-
const offscreen = createPortProxy(getOffscreenClient, {
10+
const offscreen = createPortProxy(() => (
11+
creating ??= create().finally(done)
12+
), {
1213
lock: '/' + FILENAME,
1314
});
1415
export default offscreen;
1516

17+
export let offscreenCache = __.MV3 && (async () => {
18+
bgBusy.then(() => (offscreenCache = null));
19+
offscreenCache = (offscreen[CLIENT] = (await findOffscreenClient())) &&
20+
await offscreen.getData();
21+
return offscreenCache;
22+
})();
23+
let creating;
24+
25+
async function findOffscreenClient() {
26+
for (const c of await getWindowClients())
27+
if (c.url === DOC_URL)
28+
return c;
29+
}
30+
1631
async function create() {
1732
__.DEBUGTRACE('getDoc creating...');
1833
try {
@@ -25,9 +40,10 @@ async function create() {
2540
if (!err.message.startsWith('Only a single offscreen')) throw err;
2641
}
2742
__.DEBUGLOG('getDoc created');
28-
const clients = await getWindowClients();
29-
const client = clients.find(c => c.url === DOC_URL);
43+
return findOffscreenClient();
44+
}
45+
46+
function done() {
3047
creating = null;
31-
__.DEBUGLOG('getDoc', client);
32-
return client;
48+
__.DEBUGLOG('getDoc done');
3349
}

0 commit comments

Comments
 (0)