-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontent.js
More file actions
173 lines (153 loc) · 5.4 KB
/
Copy pathcontent.js
File metadata and controls
173 lines (153 loc) · 5.4 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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
// Selectors and text patterns that identify Clear verification prompts
const CLEAR_SELECTORS = [
// iframes embedding Clear's verification flow
'iframe[src*="clearme.com"]',
'iframe[src*="clearidentity.com"]',
'iframe[src*="clear.com"]',
'iframe[title*="CLEAR"]',
'iframe[title*="Clear"]',
// LinkedIn "Verify now" / "Verify with CLEAR" links — href is stable, class names are not
'a[href*="linkedin.com/verify"]',
'a[href*="/verify/?entryPoint"]',
// LinkedIn "Add verification badge" link on profile selfview topcard
'a[href*="linkedin.com/trust/verification"]',
'a[href*="/trust/verification"]',
// LinkedIn-specific Clear banner/modal containers
'[data-test-id*="clear"]',
'[data-tracking-control-name*="clear"]',
'[aria-label*="CLEAR"]',
'[aria-label*="Verify with CLEAR"]',
// Generic modal/banner wrappers that mention Clear
'.clear-verification',
'.clear-prompt',
'#clear-modal',
'#clear-banner',
];
// Text phrases that indicate a Clear prompt (case-insensitive)
const CLEAR_TEXT_PATTERNS = [
/verify\s+(with\s+)?clear/i,
/verif(y|ied)\s+by\s+clear/i,
/clear\s+(identity|verification|verified)/i,
/powered\s+by\s+clear/i,
/trusted\s+by\s+clear/i,
/use\s+clear\s+to\s+verify/i,
/add\s+verification\s+badge/i,
];
function isLikelyClearNode(el) {
const text = el.innerText || el.textContent || "";
return CLEAR_TEXT_PATTERNS.some((re) => re.test(text));
}
// Walk up the DOM to find the closest safe container to remove.
function findDismissibleAncestor(el) {
let node = el;
while (node && node !== document.body) {
const role = node.getAttribute && node.getAttribute("role");
const tag = node.tagName && node.tagName.toLowerCase();
// LinkedIn wraps each nav menu item's secondary action in a div with
// data-display-contents="true" — it holds only the verify link, safe to remove whole.
if (node !== el && node.getAttribute && node.getAttribute("data-display-contents") === "true") {
return node;
}
// Hard structural boundaries — walking past <nav> or <main> would remove
// site-level chrome (e.g. the entire LinkedIn nav bar). Return the original
// matched element so only it is removed.
if (tag === "nav" || tag === "main") {
return el;
}
// Stop at modal/dialog/banner-like containers
if (
role === "dialog" ||
role === "alertdialog" ||
role === "banner" ||
tag === "dialog" ||
node.classList.contains("modal") ||
node.classList.contains("artdeco-modal") ||
node.classList.contains("msg-overlay-bubble-header") ||
// LinkedIn uses these for promoted banners
node.classList.contains("scaffold-layout__aside") ||
node.classList.contains("ad-banner-container")
) {
return node;
}
// If the node is small enough to be a self-contained promo card, stop here
const style = window.getComputedStyle(node);
if (
(style.position === "fixed" || style.position === "sticky") &&
node !== document.documentElement
) {
return node;
}
node = node.parentElement;
}
return el;
}
function removeNode(node) {
if (node && node.parentNode) {
node.parentNode.removeChild(node);
}
}
function scanAndRemove() {
// 1. Remove by CSS selector
CLEAR_SELECTORS.forEach((sel) => {
document.querySelectorAll(sel).forEach((el) => removeNode(findDismissibleAncestor(el)));
});
// 2. Remove by text content — scan leaf-ish nodes to avoid false positives
const walker = document.createTreeWalker(
document.body || document.documentElement,
NodeFilter.SHOW_ELEMENT,
{
acceptNode(node) {
// Skip script/style/noscript
const tag = node.tagName.toLowerCase();
if (tag === "script" || tag === "style" || tag === "noscript") {
return NodeFilter.FILTER_REJECT;
}
return NodeFilter.FILTER_ACCEPT;
},
}
);
const candidates = [];
while (walker.nextNode()) {
const node = walker.currentNode;
// Only check nodes whose own text (not children) contains a pattern,
// or small subtrees (< 200 chars) to avoid matching the whole page body.
const ownText = Array.from(node.childNodes)
.filter((n) => n.nodeType === Node.TEXT_NODE)
.map((n) => n.textContent)
.join("");
if (CLEAR_TEXT_PATTERNS.some((re) => re.test(ownText))) {
candidates.push(node);
}
}
candidates.forEach((el) => {
const target = findDismissibleAncestor(el);
// Safety: don't remove the whole body or html
if (target !== document.body && target !== document.documentElement) {
removeNode(target);
}
});
}
// Run once as soon as the DOM is available
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", scanAndRemove);
} else {
scanAndRemove();
}
// Watch for dynamically injected prompts (LinkedIn is a SPA)
const observer = new MutationObserver((mutations) => {
let shouldScan = false;
for (const mutation of mutations) {
if (mutation.addedNodes.length > 0) {
shouldScan = true;
break;
}
}
if (shouldScan) {
scanAndRemove();
}
});
observer.observe(document.documentElement, { childList: true, subtree: true });
// Exported for testing only — `module` is not defined in browser content scripts
if (typeof module !== "undefined") {
module.exports = { CLEAR_SELECTORS, CLEAR_TEXT_PATTERNS, isLikelyClearNode, findDismissibleAncestor, removeNode, scanAndRemove };
}