-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontent.js
More file actions
89 lines (76 loc) · 2.78 KB
/
Copy pathcontent.js
File metadata and controls
89 lines (76 loc) · 2.78 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
(() => {
const ROOT = document.documentElement;
const AD_CELL_SELECTOR = 'div[data-testid="cellInnerDiv"]';
const AD_MARKER_SELECTOR = 'div[data-testid="placementTracking"]';
const SIDEBAR_AD_SELECTOR = '[data-testid="whoToFollowSspAd"]';
const AD_FLAG_ATTR = 'data-xmate-ad';
// Apply the toggle attribute as early as possible so content.css can hide
// ads with zero flash. Defaults to "on" until storage says otherwise.
ROOT.setAttribute('data-xmate', 'on');
chrome.storage.local.get({ enabled: true, blockedCount: 0 }, (data) => {
ROOT.setAttribute('data-xmate', data.enabled ? 'on' : 'off');
});
chrome.storage.onChanged.addListener((changes, area) => {
if (area === 'local' && changes.enabled) {
ROOT.setAttribute('data-xmate', changes.enabled.newValue ? 'on' : 'off');
}
});
// content.css hides plain promoted tweets on its own, but it can't tell a
// promoted video from an ordinary one — X uses data-testid="placementTracking"
// both for an ad's impression block and for the wrapper its video player
// mounts into. The two are distinguishable by position: an ad's block sits
// outside the tweet's <article>, a video player's sits inside it. Cells that
// really are ads get flagged here for the CSS to hide.
function isAdCell(cell) {
return Array.prototype.some.call(
cell.querySelectorAll(AD_MARKER_SELECTOR),
(marker) => !marker.closest('article')
);
}
// Counting is purely for the popup's "ads blocked" stat.
const seen = new WeakSet();
function countNewlyBlocked() {
let newly = 0;
document.querySelectorAll(AD_CELL_SELECTOR).forEach((cell) => {
// Cells are recycled by X's virtualized list, so the flag is refreshed
// every pass rather than only set once.
if (!isAdCell(cell)) {
cell.removeAttribute(AD_FLAG_ATTR);
return;
}
cell.setAttribute(AD_FLAG_ATTR, '');
if (seen.has(cell)) return;
seen.add(cell);
newly++;
});
document.querySelectorAll(SIDEBAR_AD_SELECTOR).forEach((el) => {
if (seen.has(el)) return;
seen.add(el);
newly++;
});
if (newly > 0) {
chrome.storage.local.get({ blockedCount: 0 }, (data) => {
chrome.storage.local.set({ blockedCount: data.blockedCount + newly });
});
}
}
let scheduled = false;
function scheduleCount() {
if (scheduled) return;
scheduled = true;
setTimeout(() => {
scheduled = false;
countNewlyBlocked();
}, 500);
}
const observer = new MutationObserver(scheduleCount);
function start() {
countNewlyBlocked();
observer.observe(document.body, { childList: true, subtree: true });
}
if (document.body) {
start();
} else {
document.addEventListener('DOMContentLoaded', start, { once: true });
}
})();