From ec710f40074d6bb1fa43ef49cac9f9263c5267de Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Mon, 11 May 2026 12:12:57 +0200 Subject: [PATCH] refactor(bundle-size): drop dead code paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mechanical hygiene with no behavioural change: - Audit `Utils.ts` to remove dead helpers and inline tiny one-shot utilities at call sites. - Switch `MutationEvent` removed/added node loops from index-based `for` to `for...of` (slightly shorter emit, easier to read). - Drop dead `moz`/`ms` branches from the Outline fullscreen-event detection cascade. Firefox unprefixed `fullscreenchange` since v64 (Dec 2018) and the `ms*` variant was IE 11 / legacy EdgeHTML — both excluded by the project's browserslist. The Safari `webkit` branch stays: Safari 15-16.3 still needs it (unprefixed since 16.4). --- src/MutationEvent.ts | 7 ++- src/Outline.ts | 8 +-- src/Utils.ts | 120 ++++++++++++++----------------------------- 3 files changed, 44 insertions(+), 91 deletions(-) diff --git a/src/MutationEvent.ts b/src/MutationEvent.ts index 75202dd9..e5db6b17 100644 --- a/src/MutationEvent.ts +++ b/src/MutationEvent.ts @@ -60,15 +60,14 @@ export function observeMutations( } } } else { - for (let i = 0; i < removed.length; i++) { - const removedNode = removed[i]; + for (const removedNode of removed) { removedNodes.add(removedNode); updateTabsterElements(removedNode, true); tabster._dummyObserver.domChanged?.(target as HTMLElement); } - for (let i = 0; i < added.length; i++) { - updateTabsterElements(added[i]); + for (const addedNode of added) { + updateTabsterElements(addedNode); tabster._dummyObserver.domChanged?.(target as HTMLElement); } } diff --git a/src/Outline.ts b/src/Outline.ts index 4dba0087..77bd671c 100644 --- a/src/Outline.ts +++ b/src/Outline.ts @@ -80,14 +80,10 @@ export class OutlineAPI implements Types.OutlineAPI { this._fullScreenEventName = "fullscreenchange"; this._fullScreenElementName = "fullscreenElement"; } else if ("onwebkitfullscreenchange" in document) { + // Safari 15-16.3 only ships the webkit-prefixed API; + // unprefixed landed in Safari 16.4. this._fullScreenEventName = "webkitfullscreenchange"; this._fullScreenElementName = "webkitFullscreenElement"; - } else if ("onmozfullscreenchange" in document) { - this._fullScreenEventName = "mozfullscreenchange"; - this._fullScreenElementName = "mozFullScreenElement"; - } else if ("onmsfullscreenchange" in document) { - this._fullScreenEventName = "msfullscreenchange"; - this._fullScreenElementName = "msFullscreenElement"; } } } diff --git a/src/Utils.ts b/src/Utils.ts index 4744724d..d4dc5845 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -62,35 +62,23 @@ interface WindowWithUtilsConext extends Window { export function getInstanceContext(getWindow: GetWindow): InstanceContext { const win = getWindow() as WindowWithUtilsConext; - - let ctx = win.__tabsterInstanceContext; - - if (!ctx) { - ctx = { - elementByUId: {}, - containerBoundingRectCache: {}, - lastContainerBoundingRectCacheId: 0, - }; - - win.__tabsterInstanceContext = ctx; - } - - return ctx; + return (win.__tabsterInstanceContext ??= { + elementByUId: {}, + containerBoundingRectCache: {}, + lastContainerBoundingRectCacheId: 0, + }); } export function disposeInstanceContext(win: Window): void { - const ctx = (win as WindowWithUtilsConext).__tabsterInstanceContext; - + const w = win as WindowWithUtilsConext; + const ctx = w.__tabsterInstanceContext; if (ctx) { ctx.elementByUId = {}; - ctx.containerBoundingRectCache = {}; - if (ctx.containerBoundingRectCacheTimer) { win.clearTimeout(ctx.containerBoundingRectCacheTimer); } - - delete (win as WindowWithUtilsConext).__tabsterInstanceContext; + delete w.__tabsterInstanceContext; } } @@ -333,21 +321,14 @@ export function shouldIgnoreFocus(element: HTMLElement): boolean { export function getUId(wnd: Window): string { const rnd = new Uint32Array(4); - wnd.crypto.getRandomValues(rnd); - - const srnd: string[] = []; - - for (let i = 0; i < rnd.length; i++) { - srnd.push(rnd[i].toString(36)); - } - - srnd.push("|"); - srnd.push((++_uidCounter).toString(36)); - srnd.push("|"); - srnd.push(Date.now().toString(36)); - - return srnd.join(""); + return ( + Array.from(rnd, (n) => n.toString(36)).join("") + + "|" + + (++_uidCounter).toString(36) + + "|" + + Date.now().toString(36) + ); } export function getElementUId( @@ -355,19 +336,13 @@ export function getElementUId( element: HTMLElementWithUID ): string { const context = getInstanceContext(getWindow); - let uid = element.__tabsterElementUID; - - if (!uid) { - uid = element.__tabsterElementUID = getUId(getWindow()); - } - + const uid = (element.__tabsterElementUID ??= getUId(getWindow())); if ( !context.elementByUId[uid] && documentContains(element.ownerDocument, element) ) { context.elementByUId[uid] = new WeakHTMLElement(element); } - return uid; } @@ -379,32 +354,22 @@ export function getElementByUId( } export function getWindowUId(win: WindowWithUID): string { - let uid = win.__tabsterCrossOriginWindowUID; - - if (!uid) { - uid = win.__tabsterCrossOriginWindowUID = getUId(win); - } - - return uid; + return (win.__tabsterCrossOriginWindowUID ??= getUId(win)); } export function clearElementCache( getWindow: GetWindow, parent?: HTMLElement ): void { - const context = getInstanceContext(getWindow); - - for (const key of Object.keys(context.elementByUId)) { - const wel = context.elementByUId[key]; - const el = wel && wel.get(); - - if (el && parent) { - if (!dom.nodeContains(parent, el)) { - continue; - } + const cache = getInstanceContext(getWindow).elementByUId; + for (const key of Object.keys(cache)) { + const el = cache[key]?.get(); + // When `parent` is given, only entries inside it are pruned; otherwise + // the entire cache is cleared. + if (parent && el && !dom.nodeContains(parent, el)) { + continue; } - - delete context.elementByUId[key]; + delete cache[key]; } } @@ -551,22 +516,18 @@ export function augmentAttribute( export function getTabsterAttributeOnElement( element: HTMLElement ): TabsterAttributeProps | null { - if (!element.hasAttribute(TABSTER_ATTRIBUTE_NAME)) { + // `getAttribute` already returns null when the attribute is absent — + // no need for a separate `hasAttribute` probe. + const rawAttribute = element.getAttribute(TABSTER_ATTRIBUTE_NAME); + if (rawAttribute === null) { return null; } - - // We already checked the presence with `hasAttribute` - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const rawAttribute = element.getAttribute(TABSTER_ATTRIBUTE_NAME)!; - let tabsterAttribute: TabsterAttributeProps; try { - tabsterAttribute = JSON.parse(rawAttribute); + return JSON.parse(rawAttribute); } catch { console.error("Tabster: failed to parse attribute", rawAttribute); - tabsterAttribute = {}; + return {}; } - - return tabsterAttribute; } export function isDisplayNone(element: HTMLElement): boolean { @@ -621,24 +582,21 @@ export function getRadioButtonGroup( if (!isRadio(element)) { return; } - const name = (element as HTMLInputElement).name; - let radioButtons = Array.from(dom.getElementsByName(element, name)); + const radioButtons: HTMLInputElement[] = []; let checked: HTMLInputElement | undefined; - - radioButtons = radioButtons.filter((el) => { + for (const el of dom.getElementsByName(element, name)) { if (isRadio(el)) { - if ((el as HTMLInputElement).checked) { - checked = el as HTMLInputElement; + const input = el as HTMLInputElement; + radioButtons.push(input); + if (input.checked) { + checked = input; } - return true; } - return false; - }); - + } return { name, - buttons: new Set(radioButtons as HTMLInputElement[]), + buttons: new Set(radioButtons), checked, }; }