From 15d84b8a2ae8e541b52a42948ed88a033dbaff2c Mon Sep 17 00:00:00 2001 From: Todd Schiller Date: Fri, 5 Jun 2026 15:37:08 -0400 Subject: [PATCH 1/2] Perf: CSS-first hide for chat-widget-hide (#150 Tier 2 #13) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit chat-widget-hide is a clean CSS-first candidate — every selector is vendor-specific id/class/iframe-attribute and the rule has no candidateFilter. Replace the JS scan path with an injected display:none!important stylesheet (also adopted into open shadow roots via the existing helper). Lazily-injected widgets (HubSpot's conversations-embed, Intercom's loader, etc.) are now hidden at parse time with no observer, throttle, or per-batch QSA on our side. To preserve the toolbar badge count — which previously relied on the JS path stamping HIDDEN_ATTR — placeholder-count now accepts a CSS-first selector union via registerCssFirstSelectors and adds its match count to the tally per recount. EasyList in ads-hide stays uncounted as before (its 13k selectors would dwarf the badge). cookie-banner-hide and newsletter-modal-hide both depend on candidateFilter (overlay position checks, viewport area, email-input gate) so they stay on the JS path for now. Co-Authored-By: Claude Opus 4.7 (1M context) --- extension/src/lib/css-hide-stylesheet.ts | 76 +++++++++++ extension/src/lib/placeholder-count.ts | 45 ++++++- .../rules/__tests__/chat-widget-hide.test.ts | 70 +++++----- extension/src/rules/chat-widget-hide.ts | 121 +++++++++++------- 4 files changed, 229 insertions(+), 83 deletions(-) create mode 100644 extension/src/lib/css-hide-stylesheet.ts diff --git a/extension/src/lib/css-hide-stylesheet.ts b/extension/src/lib/css-hide-stylesheet.ts new file mode 100644 index 0000000..415a059 --- /dev/null +++ b/extension/src/lib/css-hide-stylesheet.ts @@ -0,0 +1,76 @@ +// Copyright (c) 2026 PixieBrix, Inc. +// Licensed under PolyForm Shield 1.0.0 — see LICENSE. + +// Inject a `display:none !important` stylesheet for a known list of static +// selectors, and adopt the same sheet into every open shadow root. Used by +// hide rules whose matches need no JS-side processing (no placeholder UI, +// no per-element side effects, no candidate filtering) — the CSS engine +// matches and hides for us, even for lazily-injected nodes, with no +// MutationObserver on our side. + +import type { AdoptedShadowSheet } from "./shadow-stylesheets"; +import { adoptStylesheetIntoShadowRoots } from "./shadow-stylesheets"; + +export interface HideStylesheet { + // Tear down the document-scope