Skip to content

Commit

Permalink
wake up SW earlier to avoid FOUC
Browse files Browse the repository at this point in the history
  • Loading branch information
tophf committed Jan 25, 2025
1 parent 5d89669 commit 85a5209
Show file tree
Hide file tree
Showing 12 changed files with 109 additions and 26 deletions.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@
"@babel/core": "^7.26.0",
"@babel/plugin-transform-runtime": "^7.25.9",
"@babel/preset-env": "^7.26.0",
"@types/chrome": "^0.0.299",
"@types/codemirror": "^5.60.15",
"@types/firefox-webext-browser": "^120.0.4",
"@types/webpack": "^5.28.5",
"autoprefixer": "^10.4.20",
"babel-loader": "^9.2.1",
"chalk": "^4.1.2",
"chrome-types": "^0.1.334",
"copy-webpack-plugin": "^12.0.2",
"css-loader": "^7.1.2",
"css-minimizer-webpack-plugin": "^7.0.0",
Expand Down
44 changes: 44 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/.types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ declare namespace Injection {
top?: string | false;
off?: boolean;
order?: Order;
wake?: boolean;
}
interface Order {
main: Record<string,number>;
Expand Down
4 changes: 4 additions & 0 deletions src/background/broadcast-injector-config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {pKeepAlive} from '@/js/consts';
import * as prefs from '@/js/prefs';
import {isEmptyObj} from '@/js/util';
import {broadcast} from './broadcast';
Expand All @@ -9,6 +10,7 @@ let sentCfg = {};
const INJECTOR_CONFIG_MAP = {
exposeIframes: 'top',
disableAll: 'off',
[pKeepAlive]: 'wake',
styleViaASS: 'ass',
};

Expand All @@ -19,6 +21,8 @@ onSchemeChange.add(broadcastInjectorConfig.bind(null, 'dark'));

export default function broadcastInjectorConfig(key, val) {
key = INJECTOR_CONFIG_MAP[key] || key;
if (key === pKeepAlive)
val = val >= 0;
if (!cfg) {
cfg = {};
cfg[key] = val;
Expand Down
3 changes: 2 additions & 1 deletion src/background/style-manager/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {IMPORT_THROTTLE, k_size, kStyleViaXhr, kUrl, UCD} from '@/js/consts';
import {IMPORT_THROTTLE, k_size, kStyleViaXhr, kUrl, pKeepAlive, UCD} from '@/js/consts';
import * as prefs from '@/js/prefs';
import {calcStyleDigest, styleCodeEmpty} from '@/js/sections-util';
import {calcObjSize, mapObj} from '@/js/util';
Expand Down Expand Up @@ -188,6 +188,7 @@ export function getSectionsByUrl(url, id, isInitialApply) {
isTop ? '' // apply.js will use location.origin
: getUrlOrigin(tab.url || td[kUrl]?.[0])
),
wake: p[pKeepAlive] >= 0,
order,
};
if (isInitialApply === 'cfg') {
Expand Down
3 changes: 2 additions & 1 deletion src/background/sw/keep-alive.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {pKeepAlive} from '@/js/consts';
import * as prefs from '@/js/prefs';
import {bgBusy} from '../common';

Expand All @@ -12,7 +13,7 @@ let idleDuration;

keepAlive(bgBusy);
__.KEEP_ALIVE = keepAlive;
prefs.subscribe('keepAlive', (_, val) => {
prefs.subscribe(pKeepAlive, (_, val) => {
idleDuration = Math.max(30, val * 60 | 0/*to integer*/ || 0/*if val is not a number*/);
TTL = val * 60e3;
if (!pulse || !TTL && !busy) reschedule();
Expand Down
1 change: 1 addition & 0 deletions src/background/tab-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,5 +131,6 @@ function onPortDisconnected(port) {
const {sender} = port;
const tabId = sender.tab?.id;
const frameId = sender.frameId;
if (!frameId) return; // ignoring unload of previous page while navigating to a new URL
for (const fn of onUnload) fn(tabId, frameId, port);
}
57 changes: 39 additions & 18 deletions src/content/apply.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {k_deepCopy, kApplyPort} from '@/js/consts';
import {onMessage} from '@/js/msg';
import {API, isFrame, TDM, updateTDM} from '@/js/msg-api';
import * as styleInjector from './style-injector';
import {FF, isXml, own, ownId} from './style-injector';
import {FF, isXml, own, ownId, runtime} from './style-injector';

const SYM_ID = 'styles';
const kPageShow = 'pageshow';
Expand All @@ -27,7 +27,7 @@ const instanceId = (FF || !__.MV3 && !CSS.supports('top', '1ic')) && Math.random
* so we'll add the styles only if the iframe becomes visible */
const xoEventId = `${instanceId || Math.random()}`;

const NAV_ID = 'url:' + chrome.runtime.id;
const NAV_ID = 'url:' + runtime.id;
/** Firefox disallows direct access to global variables in the parent's "isolated world".
* Chrome 63 and older can't construct EventTarget, so we detect them via ResizeObserver,
* using a typeof check to skip an implicit global for <html id="ResizeObserver"> */
Expand Down Expand Up @@ -70,16 +70,8 @@ if (TDM < 0) {
}
styleInjector.onInjectorUpdate = () => {
updateCount();
if (isFrame) {
updateExposeIframes();
if (!styleInjector.list.length) {
port?.disconnect();
port = null;
} else if (!port) {
port = chrome.runtime.connect({name: kApplyPort});
port.onDisconnect.addListener(() => (port = null));
}
}
if (isFrame) updateExposeIframes();
if (isFrame || own.cfg.wake) updatePort();
};
styleInjector.selfDestruct = selfDestruct;
// Declare all vars before init() or it'll throw due to "temporal dead zone" of const/let
Expand All @@ -98,7 +90,8 @@ async function init() {
else data = !__.ENTRY && !isFrameSameOrigin && !isXml && getStylesViaXhr();
// XML in Chrome will be auto-converted to html later, so we can't style it via XHR now
}
if (!chrome.runtime.id) return selfDestruct();
if (!runtime.id)
return selfDestruct();
await applyStyles(data, true);
}

Expand All @@ -118,7 +111,7 @@ function getStylesViaXhr() {
try {
const blobId = (document.cookie.split(ownId + '=')[1] || '').split(';')[0];
if (!blobId) return; // avoiding an exception so we don't spoil debugging in devtools
const url = 'blob:' + chrome.runtime.getURL(blobId);
const url = 'blob:' + runtime.getURL(blobId);
document.cookie = `${ownId}=1; max-age=0; SameSite=Lax`; // remove our cookie
const xhr = new XMLHttpRequest();
xhr.open('GET', url, false); // synchronous
Expand Down Expand Up @@ -189,14 +182,15 @@ function processThrottled() {
}

function updateConfig({cfg}) {
for (const k in cfg) {
for (const /** @type {keyof Injection.Config}*/ k in cfg) {
const v = cfg[k];
if (v === own.cfg[k]) continue;
if (k === 'top' && !isFrame) continue;
own.cfg[k] = v;
if (k === 'off') updateDisableAll();
else if (k === 'order') styleInjector.sort();
else if (k === 'top') updateExposeIframes();
else if (k === 'wake' && __.MV3) updatePort();
else styleInjector.updateConfig(own.cfg);
}
}
Expand Down Expand Up @@ -234,6 +228,16 @@ function updateCount() {
}
}

function updatePort() {
if (!(__.MV3 && own.cfg.wake) && !styleInjector.list.length) {
port?.disconnect();
port = null;
} else if (!port && (isFrame || __.MV3 && own.cfg.wake)) {
port = runtime.connect({name: kApplyPort});
port.onDisconnect.addListener(onPortDisconnected);
}
}

function updateUrl(url) {
if (url !== matchUrl) {
matchUrl = url;
Expand All @@ -249,7 +253,8 @@ function onFrameElementInView(cb) {

/** @param {IntersectionObserverEntry[]} entries */
function onIntersect(entries) {
if (!chrome.runtime.id) return selfDestruct();
if (!runtime.id)
return selfDestruct();
for (const e of entries) {
if (e.intersectionRatio) {
xo.unobserve(e.target);
Expand All @@ -259,15 +264,30 @@ function onIntersect(entries) {
}

function onBFCache(e) {
if (!chrome.runtime.id) return selfDestruct();
if (!runtime.id)
return selfDestruct();
if (e.isTrusted && e.persisted) {
throttledCount = '';
init(); // styles may have been toggled while we were in bfcache
}
}

function onPortDisconnected() {
if (__.MV3 && own.cfg.wake)
addEventListener('mousedown', wakeUpSW, true);
port = null;
}

function wakeUpSW(e) {
if (!runtime.id)
return selfDestruct();
if (!port && e.target.closest('a')?.href)
updatePort();
}

function onReified(e) {
if (!chrome.runtime.id) return selfDestruct();
if (!runtime.id)
return selfDestruct();
if (e.isTrusted) {
updateTDM(2);
document.onprerenderingchange = null;
Expand All @@ -287,6 +307,7 @@ function selfDestruct() {
if (mqDark) mqDark.onchange = null;
if (offscreen) for (const fn of offscreen) fn();
if (TDM < 0) document.onprerenderingchange = null;
if (__.MV3) removeEventListener('mousedown', wakeUpSW, true);
navHubParent?.removeEventListener(NAV_ID, onUrlChanged, true);
offscreen = null;
styleInjector.shutdown();
Expand Down
11 changes: 7 additions & 4 deletions src/content/style-injector.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ const kAss = 'adoptedStyleSheets';
export const own = /** @type {Injection.Response} */{
cfg: {off: false, top: ''},
};
export const ownId = chrome.runtime.id;
export const runtime = chrome.runtime;
export const ownId = runtime.id;
export const isXml = !__.ENTRY && document instanceof XMLDocument;
const wrappedDoc = __.BUILD !== 'chrome' && FF && document.wrappedJSObject
|| document;
Expand Down Expand Up @@ -221,7 +222,7 @@ function setTextAndName(el, {id, code, name}) {
if (el.dataset.name !== name) el.dataset.name = name;
if (!FF) { // Firefox doesn't support sourceURL comment in CSS
name = encodeURIComponent(name.replace(/[?#/']/g, toSafeChar));
code += `\n/*# sourceURL=${chrome.runtime.getURL(name)}.user.css#${id}${
code += `\n/*# sourceURL=${runtime.getURL(name)}.user.css#${id}${
window !== top ? '#' + Math.random().toString(36).slice(2) : '' // https://crbug.com/1298600
} */`;
}
Expand Down Expand Up @@ -297,7 +298,8 @@ function removeStyle(style) {
}

function restoreOrder(mutations) {
if (!chrome.runtime.id) return selfDestruct();
if (!runtime.id)
return selfDestruct();
let bad;
let el = list.length && list[0].el;
if (!el) {
Expand Down Expand Up @@ -360,7 +362,8 @@ export function updateConfig(cfg) {
}

function updateRoot() {
if (!chrome.runtime.id) return selfDestruct();
if (!runtime.id)
return selfDestruct();
// Known to change mysteriously in iframes without triggering RewriteObserver
if (root !== document.documentElement) {
root = document.documentElement;
Expand Down
4 changes: 4 additions & 0 deletions src/js/consts.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,7 @@ export const IMPORT_THROTTLE = 100; //ms

export const BIT_DARK = 1;
export const BIT_SYS_DARK = 2;

//#region prefs
export const pKeepAlive = 'keepAlive';
//#endregion
1 change: 0 additions & 1 deletion src/js/prefs.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ const defaults = {
'exposeStyleName': false, // Add style name to the style for better devtools experience
'keepAlive': 0, // in minutes
'keepAliveIdle': false, // keep alive an idle browser
'keepAliveCache': false,
'newStyleAsUsercss': false, // create new style in usercss format
'openEditInWindow': false, // new editor opens in a own browser window
'openEditInWindow.popup': false, // new editor opens in a simplified browser window without omnibox
Expand Down
3 changes: 2 additions & 1 deletion src/options/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import '@/js/dom-init';
import '@/js/browser';
import {pKeepAlive} from '@/js/consts';
import {$create} from '@/js/dom';
import {getEventKeyName, messageBox, setInputValue, setupLivePrefs} from '@/js/dom-util';
import {htmlToTemplate, tBody, template, templateCache} from '@/js/localization';
Expand All @@ -18,7 +19,7 @@ $$('input[min], input[max]').forEach(enforceInputRange);
$('#FOUC .items').textContent = t(__.MV3 ? 'optionFOUCMV3' : 'optionFOUCMV2')
.replace('<a>', t('optionsAdvancedStyleViaXhr'))
.replace('<b>', t('optionKeepAlive'));
$id('keepAlive').previousElementSibling.firstChild.textContent +=
$id(pKeepAlive).previousElementSibling.firstChild.textContent +=
(/^(zh|ja|ko)/.test($root.lang) ? '' : ' ') +
t('optionKeepAlive2').trim();
for (const el of $$('[show-if]')) {
Expand Down

0 comments on commit 85a5209

Please sign in to comment.