diff --git a/src/archiver.mjs b/src/archiver.mjs index f440e47..7c25f04 100644 --- a/src/archiver.mjs +++ b/src/archiver.mjs @@ -22,6 +22,68 @@ const VIEWPORT = { height: 768 }; +const COMMON_ANNOYANCE_SELECTORS = [ + "[id^=\"sp_message_container_\"]", + "iframe[id^=\"sp_message_iframe_\"]", + "iframe[title*=\"consent\" i]", + "iframe[title*=\"privacy manager\" i]", + "#onetrust-consent-sdk", + "#onetrust-banner-sdk", + "#didomi-host", + "#qc-cmp2-container", + ".qc-cmp2-container", + "#CybotCookiebotDialog", + ".iubenda-cs-container", + "#cmpwrapper", + "[id^=\"cmpbox\"]", + ".fc-consent-root", + ".fc-dialog-container", + "[aria-modal=\"true\"][id*=\"consent\" i]", + "[aria-modal=\"true\"][id*=\"cookie\" i]", + "[role=\"dialog\"][aria-label*=\"cookie\" i]", + "[role=\"dialog\"][aria-label*=\"consent\" i]", + "[id*=\"cookie-banner\" i]", + "[class*=\"cookie-banner\" i]", + "[id*=\"cookie-consent\" i]", + "[class*=\"cookie-consent\" i]", + "[id*=\"cookie-notice\" i]", + "[class*=\"cookie-notice\" i]", + "[id*=\"cookie-popup\" i]", + "[class*=\"cookie-popup\" i]", + "[id*=\"adblock\" i]", + "[class*=\"adblock\" i]", + "[id*=\"ad-block\" i]", + "[class*=\"ad-block\" i]" +]; + +const COMMON_ANNOYANCE_ROOT_CLASSES = [ + "sp-message-open", + "didomi-popup-open", + "qc-cmp-ui-showing", + "ot-sdk-show-settings", + "iubenda-cs-visible" +]; + +const COMMON_ANNOYANCE_CSS = ` +${COMMON_ANNOYANCE_SELECTORS.join(",\n")} { + display: none !important; + visibility: hidden !important; + pointer-events: none !important; +} + +html.sp-message-open, +body.sp-message-open, +html.didomi-popup-open, +body.didomi-popup-open, +html.qc-cmp-ui-showing, +body.qc-cmp-ui-showing, +html.iubenda-cs-visible, +body.iubenda-cs-visible { + overflow: auto !important; + position: static !important; +} +`; + export { DEFAULT_USER_AGENT, defaultArchivePath }; // --------------------------------------------------------------------------- @@ -1176,9 +1238,10 @@ async function setupRequestBlocking(page, sourceHostname) { } async function injectCosmeticFilters(page, hostname) { - if (!privacyFiltersAvailable || filterRules.cosmeticRules.length === 0) return; - - const lines = getCosmeticCssForHostname(filterRules, hostname); + const lines = [COMMON_ANNOYANCE_CSS]; + if (privacyFiltersAvailable && filterRules.cosmeticRules.length > 0) { + lines.push(...getCosmeticCssForHostname(filterRules, hostname)); + } if (lines.length > 0) { try { await page.addStyleTag({ content: lines.join("\n") }); @@ -1188,6 +1251,42 @@ async function injectCosmeticFilters(page, hostname) { } } +async function removeCommonAnnoyances(page) { + try { + await page.evaluate(({ selectors, rootClasses }) => { + for (const selector of selectors) { + try { + document.querySelectorAll(selector).forEach((element) => element.remove()); + } catch { + // Ignore selectors unsupported by the current browser. + } + } + + for (const root of [document.documentElement, document.body].filter(Boolean)) { + root.classList.remove(...rootClasses); + root.removeAttribute("data-previous-scroll-y"); + + const overflow = root.style.overflow || ""; + const position = root.style.position || ""; + if (/hidden|clip/i.test(overflow)) { + root.style.removeProperty("overflow"); + } + if (/fixed/i.test(position)) { + root.style.removeProperty("position"); + root.style.removeProperty("top"); + root.style.removeProperty("left"); + root.style.removeProperty("right"); + } + } + }, { + selectors: COMMON_ANNOYANCE_SELECTORS, + rootClasses: COMMON_ANNOYANCE_ROOT_CLASSES + }); + } catch { + // Ignore cleanup failures; the archive is still useful. + } +} + const GM_MOCK = ` if (typeof GM === "undefined") { window.GM = { @@ -1320,6 +1419,7 @@ export async function renderPage(sourceUrl, options = {}) { await page.waitForTimeout(userscriptDelay); await waitForNetworkIdle(page); + await removeCommonAnnoyances(page); await snapshotLoadedResourceUrls(page); await snapshotRuntimeStyles(page); diff --git a/test/archiver.test.mjs b/test/archiver.test.mjs index 90ddd74..7c3d059 100644 --- a/test/archiver.test.mjs +++ b/test/archiver.test.mjs @@ -78,6 +78,28 @@ test("applies cosmetic filters with domain exceptions and skips unsupported proc assert.match(getCosmeticCssForHostname(rules, "foo.com").join("\n"), /\.adguard/); }); +test("renderPage removes common cookie consent overlays before snapshot", async () => { + const html = ` + + +
Article text
+ + + `; + + const rendered = await renderPage(`data:text/html,${encodeURIComponent(html)}`, { + userscriptDelay: 0 + }); + + assert.match(rendered, /Article text/); + assert.doesNotMatch(rendered, /sp_message_container_123/); + assert.doesNotMatch(rendered, /sp_message_iframe_123/); + assert.doesNotMatch(rendered, /]*class="[^"]*sp-message-open/i); + assert.doesNotMatch(rendered, /]*class="[^"]*didomi-popup-open/i); +}); + test("renderPage serializes CSSOM-inserted style rules", async () => { const html = `