Skip to content

Commit

Permalink
wait for NavLock to latch + use offscreen db cache
Browse files Browse the repository at this point in the history
  • Loading branch information
tophf committed Jan 9, 2025
1 parent cfa3014 commit 31b2623
Show file tree
Hide file tree
Showing 23 changed files with 292 additions and 251 deletions.
1 change: 0 additions & 1 deletion src/.types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ declare var __: {
KEEP_ALIVE: <T>(job: T) => T,
MV3: boolean,
PAGE_BG: 'background' | 'sw',
PAGE_OFFSCREEN: 'offscreen',
THEMES: Record<string, string>,
ZIP: boolean,
}
Expand Down
2 changes: 1 addition & 1 deletion src/background/broadcast.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import '@/js/browser';
import {getWindowClients} from '@/background/common';
import {rxIgnorableError} from '@/js/msg-api';
import {ownRoot, supported} from '@/js/urls';
import {getActiveTab} from '@/js/util-webext';
import tabCache, * as tabMan from './tab-manager';
import {getWindowClients} from './util';

let /**@type{?[]}*/toBroadcast;
let /**@type{boolean[]}*/toBroadcastStyled;
Expand Down
32 changes: 15 additions & 17 deletions src/background/color-scheme.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import {kStyleViaXhr} from '@/js/consts';
import {CONNECTED} from '@/js/port';
import {kDark, kStyleViaXhr, STATE_DB} from '@/js/consts';
import {CLIENT} from '@/js/port';
import * as prefs from '@/js/prefs';
import {debounce, isCssDarkScheme} from '@/js/util';
import {broadcastExtension} from './broadcast';
import {bgBusy, bgInit, bgPreInit, stateDB} from './common';
import offscreen from './offscreen';
import {bgBusy, bgInit, bgPreInit} from './common';
import {stateDB} from './db';
import offscreen, {offscreenCache} from './offscreen';

const changeListeners = new Set();
const kSTATE = 'schemeSwitcher.enabled';
const kSTART = 'schemeSwitcher.nightStart';
const kEND = 'schemeSwitcher.nightEnd';
const kDark = 'dark';
const kLight = 'light';
const kNever = 'never';
const kSystem = 'system';
Expand All @@ -35,20 +35,16 @@ let prefState;
chrome.alarms.onAlarm.addListener(onAlarm);

if (__.MV3) {
bgPreInit.push(stateDB.get(kDark).then(v => {
if (!v) {
isDark = false;
} else {
isDark ??= v[0];
Object.assign(map, v[1]);
}
bgPreInit.push(offscreenCache.then(v => {
if (v && (v = v[STATE_DB])) setSystemDark(v.get(kDark));
else bgInit.push(refreshSystemDark);
}));
bgInit.push(refreshSystemDark);
prefs.subscribe([kSTATE, kStyleViaXhr], () => {
const val = prefState === kSystem || prefs.__values[kStyleViaXhr];
if (val || offscreen[CONNECTED]) {
prefs.subscribe([kSTATE, kStyleViaXhr], (key, val, init) => {
if (init && key !== kStyleViaXhr) // only process the last one on init
return;
val = prefState === kSystem || prefs.__values[kStyleViaXhr];
if (val || offscreen[CLIENT])
offscreen.keepAlive(val);
}
}, true);
} else {
refreshSystemDark();
Expand Down Expand Up @@ -127,6 +123,8 @@ function updateTimePreferDark() {
function update(type, val) {
if (type) {
if (map[type] === val) return;
if (__.MV3 && type === kSystem)
stateDB.put(val, kDark);
map[type] = val;
}
val = map[prefState];
Expand Down
59 changes: 7 additions & 52 deletions src/background/common.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
import {k_busy, kStateDB} from '@/js/consts';
import {CONNECTED, createPortProxy} from '@/js/port';
import {k_busy, kResolve} from '@/js/consts';
import {CHROME} from '@/js/ua';
import {workerPath} from '@/js/urls';
import {promiseWithResolve, sleep} from '@/js/util';
import {browserWindows} from '@/js/util-webext';
import {getDbProxy} from './db';
import offscreen from './offscreen';

export let bgBusy = promiseWithResolve();
/** Minimal init for a wake-up event */
export const bgPreInit = [];
export const bgInit = [];

const CLIENT_TIMEOUT = 100;
export const clientDataJobs = {};

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

/** @return {WindowClient} the offscreen document if it runs, otherwise any available client */
export const getClient = async () => {
for (let busy, client, job, tEnd;
!tEnd || performance.now() < tEnd;
tEnd ??= performance.now() + CLIENT_TIMEOUT) {
for (const c of await getWindowClients()) {
if ((job = clientDataJobs[c.url])) {
(busy ??= []).push(job);
} else if (c.url.endsWith(__.PAGE_OFFSCREEN)) {
return c;
} else {
client = c;
}
}
if (client)
return client;
if (!busy || !await Promise.race([
Promise.any(busy).catch(() => 0),
sleep(CLIENT_TIMEOUT),
])) break;
}
};

/** @return {WindowClient[]} */
export const getWindowClients = () => self.clients.matchAll({
includeUncontrolled: true,
type: 'window',
});

export const stateDB = __.MV3 && getDbProxy(kStateDB, {store: 'kv'});
export const onUnload = new Set();
export const onUrl = new Set();

export const uuidIndex = Object.assign(new Map(), {
custom: {},
Expand All @@ -70,18 +34,6 @@ export const uuidIndex = Object.assign(new Map(), {
},
});

/** @type {WorkerAPI} */
export const worker = !__.MV3
? createPortProxy(workerPath)
: createPortProxy(async () => {
const client = await getClient();
const proxy = !client || client.url.endsWith(__.PAGE_OFFSCREEN) ? (
offscreen[CONNECTED] ??= client,
offscreen
) : createPortProxy(client, {once: true});
return proxy.getWorkerPort(workerPath);
}, {lock: workerPath});

export let isVivaldi = !!(browserWindows && CHROME) && (async () => {
const wnd = (await browserWindows.getAll())[0] ||
await new Promise(resolve => browserWindows.onCreated.addListener(function onCreated(w) {
Expand All @@ -92,7 +44,10 @@ export let isVivaldi = !!(browserWindows && CHROME) && (async () => {
return isVivaldi;
})();

global[k_busy] = bgBusy;
export let bgBusy = global[k_busy] = (_ =>
Object.assign(new Promise(cb => (_ = cb)), {[kResolve]: _})
)();

bgBusy.then(() => {
bgBusy = null;
delete global[k_busy];
Expand Down
82 changes: 66 additions & 16 deletions src/background/db.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import {CACHE_DB, DB, STATE_DB} from '@/js/consts';
import {API} from '@/js/msg-api';
import {CLIENT} from '@/js/port';
import {STORAGE_KEY} from '@/js/prefs';
import {chromeLocal} from '@/js/storage-util';
import {CHROME} from '@/js/ua';
import {deepCopy} from '@/js/util';
import {deepMerge} from '@/js/util';
import {bgBusy} from './common';
import ChromeStorageDB from './db-chrome-storage';
import offscreen, {offscreenCache} from './offscreen';
import {offloadCache} from './style-manager/util';

/*
Initialize a database. There are some problems using IndexedDB in Firefox:
Expand All @@ -15,7 +20,7 @@ import ChromeStorageDB from './db-chrome-storage';
let exec = __.BUILD === 'chrome' || CHROME
? dbExecIndexedDB
: tryUsingIndexedDB;
const DB = 'stylish';
const cachedClient = new WeakSet();
const FALLBACK = 'dbInChromeStorage';
const REASON = FALLBACK + 'Reason';
const CACHING = {};
Expand All @@ -26,7 +31,7 @@ const dataCache = {};
const proxies = {};
const databases = {};
const proxyHandler = {
get: ({dbName}, cmd) => (CACHING[dbName] ? cachedExec : exec).bind(null, dbName, cmd),
get: ({dbName}, cmd) => (CACHING[dbName] || exec).bind(null, dbName, cmd),
};
/**
* @param {string} dbName
Expand All @@ -36,40 +41,85 @@ const proxyHandler = {
* @param {string} [cfg.store]
* @return {IDBObjectStoreMany}
*/
export const getDbProxy = (dbName, {
const getDbProxy = (dbName, {
cache,
id,
store = 'data',
ver = 2,
} = {}) => (proxies[dbName] ??= (
(CACHING[dbName] = cache),
(CACHING[dbName] = typeof cache === 'function' ? cache : cache && cachedExec),
(DATA_KEY[dbName] = !id || typeof id === 'string' ? id : 'id'),
(STORES[dbName] = store),
(VERSIONS[dbName] = ver),
new Proxy({dbName}, proxyHandler)
));

export const db = getDbProxy(DB, {id: true, store: 'styles'});
export const cacheDB = getDbProxy(CACHE_DB, {
id: 'url',
cache: __.MV3 && cachedExecOffscreen,
});
export const db = getDbProxy(DB, {
id: true,
store: 'styles',
cache: __.MV3 && cachedExecOffscreen,
});
export const draftsDb = getDbProxy('drafts', {cache: true});
/** Storage for big items that may exceed 8kB limit of chrome.storage.sync.
* To make an item syncable register it with uuidIndex.addCustom. */
export const prefsDb = getDbProxy(STORAGE_KEY, {cache: true});
export const prefsDb = getDbProxy(STORAGE_KEY, {
cache: !__.MV3 || cachedExecOffscreen,
});
export const stateDB = __.MV3 && getDbProxy(STATE_DB, {
store: 'kv',
cache: cachedExecOffscreen,
});

Object.assign(API, /** @namespace API */ {
draftsDb,
prefsDb,
});

async function cachedExec(dbName, cmd, a, b) {
const hub = dataCache[dbName] || (dataCache[dbName] = {});
const res = cmd === 'get' && a in hub ? hub[a] : await exec(...arguments);
if (cmd === 'get') {
hub[a] = deepCopy(res);
} else if (cmd === 'put') {
const key = DATA_KEY[dbName];
hub[key ? a[key] : b] = deepCopy(a);
} else if (cmd === 'delete') {
delete hub[a];
const old = dataCache[dbName];
const hub = old || (dataCache[dbName] = {__proto__: null});
const res = cmd === 'get' && a in hub
? hub[a]
: old && cmd === 'getAll'
? Object.values(old)
: await exec(...arguments);
switch (cmd) {
case 'put':
cmd = DATA_KEY[dbName];
hub[cmd ? a[cmd] : b] = deepMerge(a);
break;
case 'delete':
delete hub[a];
break;
case 'clear':
delete dataCache[dbName];
break;
}
return res && typeof res === 'object' ? deepMerge(res) : res;
}

async function cachedExecOffscreen(dbName, cmd, a) {
let res;
const isRead = cmd === 'get' || cmd === 'getAll';
if (isRead
&& offscreenCache
&& await offscreenCache
&& (res = offscreenCache[dbName])) {
res = cmd === 'get' ? res.get(a) : [...res.values()];
} else {
if ((a = offscreen[CLIENT])) {
if (!cachedClient.has(a)) {
cachedClient.add(a);
if (!bgBusy) offloadCache(dataCache[STATE_DB] || {});
} else if (!isRead) {
offscreen.dbCache(...arguments);
}
}
res = (dbName === STATE_DB || dbName === STORAGE_KEY ? cachedExec : exec)(...arguments);
}
return res;
}
Expand Down
6 changes: 3 additions & 3 deletions src/background/icon-manager.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {kDisableAll} from '@/js/consts';
import {subscribe, __values as __prefs} from '@/js/prefs';
import {__values as __prefs, subscribe} from '@/js/prefs';
import {CHROME, FIREFOX, MOBILE, VIVALDI} from '@/js/ua';
import {debounce, t} from '@/js/util';
import {ignoreChromeError, MF_ICON_EXT, MF_ICON_PATH} from '@/js/util-webext';
import * as colorScheme from './color-scheme';
import {bgBusy, bgInit} from './common';
import {bgBusy, bgInit, onUnload} from './common';
import {removePreloadedStyles} from './style-via-webrequest';
import tabCache, * as tabMan from './tab-manager';

Expand Down Expand Up @@ -57,7 +57,7 @@ function initIcons(runNow = !__.MV3) {
], () => debounce(refreshAllIcons), runNow);
}

tabMan.onUnload.add((tabId, frameId, port) => {
onUnload.add((tabId, frameId, port) => {
if (frameId && tabMan.getStyleIds(tabId)) {
updateIconBadge.call(port, [], {lazyBadge: true});
}
Expand Down
4 changes: 2 additions & 2 deletions src/background/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import {broadcast, pingTab} from './broadcast';
import './broadcast-injector-config';
import initBrowserCommandsApi from './browser-cmd-hotkeys';
import {setSystemDark} from './color-scheme';
import {bgBusy, bgInit, bgPreInit, dataHub, stateDB} from './common';
import {bgBusy, bgInit, bgPreInit, dataHub} from './common';
import reinjectContentScripts from './content-scripts';
import initContextMenus from './context-menus';
import {draftsDb, prefsDb} from './db';
import {draftsDb, prefsDb, stateDB} from './db';
import download from './download';
import {refreshIconsWhenReady, updateIconBadge} from './icon-manager';
import prefsApi from './prefs-api';
Expand Down
38 changes: 27 additions & 11 deletions src/background/offscreen.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
import {createPortProxy} from '@/js/port';
import {CLIENT, createPortProxy} from '@/js/port';
import {ownRoot} from '@/js/urls';
import {getWindowClients} from './common';
import {bgBusy} from './common';
import {getWindowClients} from './util';

let creating;

export const getOffscreenClient = () => (creating ??= create());
const FILENAME = __.PAGE_OFFSCREEN + '.html';
const FILENAME = 'offscreen.html';
const DOC_URL = ownRoot + FILENAME;

/** @type {OffscreenAPI | CommandsAPI} */
const offscreen = createPortProxy(getOffscreenClient, {
const offscreen = createPortProxy(() => (
creating ??= create().finally(done)
), {
lock: '/' + FILENAME,
});
export default offscreen;

export let offscreenCache = __.MV3 && (async () => {
bgBusy.then(() => (offscreenCache = null));
offscreenCache = (offscreen[CLIENT] = (await findOffscreenClient())) &&
await offscreen.getData();
return offscreenCache;
})();
let creating;

async function findOffscreenClient() {
for (const c of await getWindowClients())
if (c.url === DOC_URL)
return c;
}

async function create() {
__.DEBUGTRACE('getDoc creating...');
try {
Expand All @@ -25,9 +40,10 @@ async function create() {
if (!err.message.startsWith('Only a single offscreen')) throw err;
}
__.DEBUGLOG('getDoc created');
const clients = await getWindowClients();
const client = clients.find(c => c.url === DOC_URL);
return findOffscreenClient();
}

function done() {
creating = null;
__.DEBUGLOG('getDoc', client);
return client;
__.DEBUGLOG('getDoc done');
}
Loading

0 comments on commit 31b2623

Please sign in to comment.