From 85a5209e9d8a2a2d3a274aa1cabcffb4c2019d38 Mon Sep 17 00:00:00 2001 From: tophf Date: Sat, 25 Jan 2025 05:12:54 +0300 Subject: [PATCH] wake up SW earlier to avoid FOUC --- package.json | 3 ++ pnpm-lock.yaml | 44 ++++++++++++++++ src/.types.d.ts | 1 + src/background/broadcast-injector-config.js | 4 ++ src/background/style-manager/index.js | 3 +- src/background/sw/keep-alive.js | 3 +- src/background/tab-manager.js | 1 + src/content/apply.js | 57 ++++++++++++++------- src/content/style-injector.js | 11 ++-- src/js/consts.js | 4 ++ src/js/prefs.js | 1 - src/options/index.js | 3 +- 12 files changed, 109 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index 47a442cadb..0458c4f4a5 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 98cfd1d4e0..b70a949cae 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,9 +51,15 @@ importers: '@babel/preset-env': specifier: ^7.26.0 version: 7.26.0(@babel/core@7.26.0) + '@types/chrome': + specifier: ^0.0.299 + version: 0.0.299 '@types/codemirror': specifier: ^5.60.15 version: 5.60.15 + '@types/firefox-webext-browser': + specifier: ^120.0.4 + version: 120.0.4 '@types/webpack': specifier: ^5.28.5 version: 5.28.5(webpack-cli@5.1.4) @@ -66,6 +72,9 @@ importers: chalk: specifier: ^4.1.2 version: 4.1.2 + chrome-types: + specifier: ^0.1.334 + version: 0.1.334 copy-webpack-plugin: specifier: ^12.0.2 version: 12.0.2(webpack@5.97.1) @@ -1041,6 +1050,9 @@ packages: resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} engines: {node: '>=10.13.0'} + '@types/chrome@0.0.299': + resolution: {integrity: sha512-hPoHmFWUaKw2s3wDjUtMsvZqeh9lKL7nFQx6yaAYt7RxBKv/V3rDUaA4BgUR145nnpF6hpNhHaxhtcCSFJ5b7w==} + '@types/codemirror@5.60.15': resolution: {integrity: sha512-dTOvwEQ+ouKJ/rE9LT1Ue2hmP6H1mZv5+CCnNWu2qtiOe2LQa9lCprEY20HxiDmV/Bxh+dXjywmy5aKvoGjULA==} @@ -1053,6 +1065,18 @@ packages: '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/filesystem@0.0.36': + resolution: {integrity: sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==} + + '@types/filewriter@0.0.33': + resolution: {integrity: sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==} + + '@types/firefox-webext-browser@120.0.4': + resolution: {integrity: sha512-lBrpf08xhiZBigrtdQfUaqX1UauwZ+skbFiL8u2Tdra/rklkKadYmIzTwkNZSWtuZ7OKpFqbE2HHfDoFqvZf6w==} + + '@types/har-format@1.2.16': + resolution: {integrity: sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==} + '@types/html-minifier-terser@6.1.0': resolution: {integrity: sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==} @@ -1309,6 +1333,9 @@ packages: resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} engines: {node: '>=6.0'} + chrome-types@0.1.334: + resolution: {integrity: sha512-d889kLTmpi1vYgQ/CsqCWUhZOMGhX574N1623TrwY51iYPS2lyO8jRSFUROxqYeChMrgExOsvOCfFb1fANhm5A==} + ci-info@3.9.0: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} @@ -4248,6 +4275,11 @@ snapshots: '@trysound/sax@0.2.0': {} + '@types/chrome@0.0.299': + dependencies: + '@types/filesystem': 0.0.36 + '@types/har-format': 1.2.16 + '@types/codemirror@5.60.15': dependencies: '@types/tern': 0.23.9 @@ -4264,6 +4296,16 @@ snapshots: '@types/estree@1.0.6': {} + '@types/filesystem@0.0.36': + dependencies: + '@types/filewriter': 0.0.33 + + '@types/filewriter@0.0.33': {} + + '@types/firefox-webext-browser@120.0.4': {} + + '@types/har-format@1.2.16': {} + '@types/html-minifier-terser@6.1.0': {} '@types/istanbul-lib-coverage@2.0.6': {} @@ -4549,6 +4591,8 @@ snapshots: chrome-trace-event@1.0.4: {} + chrome-types@0.1.334: {} + ci-info@3.9.0: {} clean-css@5.3.3: diff --git a/src/.types.d.ts b/src/.types.d.ts index 37118fb82f..2bdeb3d634 100644 --- a/src/.types.d.ts +++ b/src/.types.d.ts @@ -91,6 +91,7 @@ declare namespace Injection { top?: string | false; off?: boolean; order?: Order; + wake?: boolean; } interface Order { main: Record; diff --git a/src/background/broadcast-injector-config.js b/src/background/broadcast-injector-config.js index 191da0087c..e82b63c197 100644 --- a/src/background/broadcast-injector-config.js +++ b/src/background/broadcast-injector-config.js @@ -1,3 +1,4 @@ +import {pKeepAlive} from '@/js/consts'; import * as prefs from '@/js/prefs'; import {isEmptyObj} from '@/js/util'; import {broadcast} from './broadcast'; @@ -9,6 +10,7 @@ let sentCfg = {}; const INJECTOR_CONFIG_MAP = { exposeIframes: 'top', disableAll: 'off', + [pKeepAlive]: 'wake', styleViaASS: 'ass', }; @@ -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; diff --git a/src/background/style-manager/index.js b/src/background/style-manager/index.js index edfd44c009..7180e6d24a 100644 --- a/src/background/style-manager/index.js +++ b/src/background/style-manager/index.js @@ -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'; @@ -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') { diff --git a/src/background/sw/keep-alive.js b/src/background/sw/keep-alive.js index 39247acb28..ae610dda98 100644 --- a/src/background/sw/keep-alive.js +++ b/src/background/sw/keep-alive.js @@ -1,3 +1,4 @@ +import {pKeepAlive} from '@/js/consts'; import * as prefs from '@/js/prefs'; import {bgBusy} from '../common'; @@ -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(); diff --git a/src/background/tab-manager.js b/src/background/tab-manager.js index bb0850d4c3..70b5a0e5e8 100644 --- a/src/background/tab-manager.js +++ b/src/background/tab-manager.js @@ -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); } diff --git a/src/content/apply.js b/src/content/apply.js index 1aefb0d8e7..e5c13a57ca 100644 --- a/src/content/apply.js +++ b/src/content/apply.js @@ -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'; @@ -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 */ @@ -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 @@ -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); } @@ -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 @@ -189,7 +182,7 @@ 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; @@ -197,6 +190,7 @@ function updateConfig({cfg}) { 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); } } @@ -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; @@ -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); @@ -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; @@ -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(); diff --git a/src/content/style-injector.js b/src/content/style-injector.js index 0485a36641..66860e77d2 100644 --- a/src/content/style-injector.js +++ b/src/content/style-injector.js @@ -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; @@ -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 } */`; } @@ -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) { @@ -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; diff --git a/src/js/consts.js b/src/js/consts.js index 9f1708c827..257e99ba9a 100644 --- a/src/js/consts.js +++ b/src/js/consts.js @@ -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 diff --git a/src/js/prefs.js b/src/js/prefs.js index fb0b0fb219..b38c6b6c7d 100644 --- a/src/js/prefs.js +++ b/src/js/prefs.js @@ -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 diff --git a/src/options/index.js b/src/options/index.js index 84e4c3c4d6..72a95e1f2c 100644 --- a/src/options/index.js +++ b/src/options/index.js @@ -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'; @@ -18,7 +19,7 @@ $$('input[min], input[max]').forEach(enforceInputRange); $('#FOUC .items').textContent = t(__.MV3 ? 'optionFOUCMV3' : 'optionFOUCMV2') .replace('', t('optionsAdvancedStyleViaXhr')) .replace('', 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]')) {