-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontent.js
More file actions
80 lines (70 loc) · 2.79 KB
/
Copy pathcontent.js
File metadata and controls
80 lines (70 loc) · 2.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/*
* Structural allowlist: hide sections that don't contain an organic result.
*
* Google obfuscates and rotates class names every few weeks, but h3.LC20lb
* (organic-result title class) is load-bearing for screen readers and has
* survived a decade unchanged. Use it as the keystone: anything that's a
* "section" (has a heading) but contains zero h3.LC20lb is noise.
*
* No heading-text list to maintain. New Google widgets get hidden automatically
* as long as they don't smuggle in h3.LC20lb elements.
*/
const ORGANIC = 'h3.LC20lb';
const KEEP_NAV = 'a[id^="pnnext"], a[id^="pnprev"]';
const STOP_IDS = new Set(['center_col', 'rcnt', 'main', 'search', 'rso', 'botstuff', 'cnt']);
const SKIP_HEADING_IN = 'h3.LC20lb, table, [role="navigation"], [role="dialog"]';
// Walk up from a heading to the smallest ancestor that doesn't drag in
// organic results or pagination from outside the current subtree.
function findSection(el) {
let cur = el;
while (cur && cur.parentElement) {
const p = cur.parentElement;
if (STOP_IDS.has(p.id) || p.tagName === 'BODY') return cur;
for (const o of p.querySelectorAll(ORGANIC)) if (!cur.contains(o)) return cur;
for (const n of p.querySelectorAll(KEEP_NAV)) if (!cur.contains(n)) return cur;
cur = p;
}
return null;
}
function hide(el) {
if (!el || el.dataset.tbl === '1') return;
el.style.setProperty('display', 'none', 'important');
el.dataset.tbl = '1';
}
// Section wrappers Google uses across queries — fall-back when a section
// renders without a [role="heading"] element (some SERP A/B variants).
const SECTION_WRAPPERS = 'div.ULSxyf, div.MjjYud, div.eJH8qe';
function purge() {
// Stable selectors with known semantics — fast-path before the walk-up.
document.querySelectorAll(
'#tads, #bottomads, [data-text-ad], div.M8OgIe, #m-x-content, .related-question-pair'
).forEach(hide);
// Pass 1: structural allowlist via headings.
document.querySelectorAll('[role="heading"], h2.bNg8Rb').forEach(h => {
if (h.matches(ORGANIC)) return;
if (h.closest(SKIP_HEADING_IN)) return;
const section = findSection(h);
if (!section || section.dataset.tbl === '1') return;
if (section.querySelector(ORGANIC)) return;
if (section.querySelector(KEEP_NAV)) return;
hide(section);
});
// Pass 2: known section-wrapper classes that may lack [role="heading"].
document.querySelectorAll(SECTION_WRAPPERS).forEach(el => {
if (el.dataset.tbl === '1') return;
if (el.querySelector(ORGANIC)) return;
if (el.querySelector(KEEP_NAV)) return;
hide(el);
});
}
let queued = false;
function schedule() {
if (queued) return;
queued = true;
requestAnimationFrame(() => { queued = false; purge(); });
}
purge();
new MutationObserver(schedule).observe(
document.documentElement,
{ childList: true, subtree: true }
);