Skip to content

Commit c053361

Browse files
committed
Mind id/class changes in generic cosmetic filtering surveyor
Related issue: uBlockOrigin/uBlock-issues#3904
1 parent 0240190 commit c053361

File tree

1 file changed

+91
-83
lines changed

1 file changed

+91
-83
lines changed

src/js/contentscript.js

Lines changed: 91 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -373,13 +373,11 @@ vAPI.SafeAnimationFrame = class {
373373
let i = mutations.length;
374374
while ( i-- ) {
375375
const mutation = mutations[i];
376-
let nodeList = mutation.addedNodes;
377-
if ( nodeList.length !== 0 ) {
378-
addedNodeLists.push(nodeList);
379-
}
380-
nodeList = mutation.removedNodes;
381-
if ( nodeList.length !== 0 ) {
382-
removedNodeLists.push(nodeList);
376+
if ( mutation.addedNodes.length !== 0 ) {
377+
addedNodeLists.push(mutation.addedNodes);
378+
}
379+
if ( mutation.removedNodes.length !== 0 ) {
380+
removedNodeLists.push(mutation.removedNodes);
383381
}
384382
}
385383
if ( addedNodeLists.length !== 0 || removedNodeLists.length !== 0 ) {
@@ -393,8 +391,6 @@ vAPI.SafeAnimationFrame = class {
393391
if ( domLayoutObserver !== undefined ) { return; }
394392
domLayoutObserver = new MutationObserver(observerHandler);
395393
domLayoutObserver.observe(document, {
396-
//attributeFilter: [ 'class', 'id' ],
397-
//attributes: true,
398394
childList: true,
399395
subtree: true
400396
});
@@ -928,6 +924,22 @@ vAPI.DOMFilterer = class {
928924
// vAPI.domSurveyor
929925

930926
{
927+
const queriedHashes = new Set();
928+
const newHashes = new Set();
929+
const maxSurveyNodes = 65536;
930+
const pendingLists = [];
931+
const pendingNodes = [];
932+
const processedSet = new Set();
933+
const ignoreTags = Object.assign(Object.create(null), {
934+
br: 1, head: 1, link: 1, meta: 1, script: 1, style: 1
935+
});
936+
let domObserver;
937+
let domFilterer;
938+
let hostname = '';
939+
let domChanged = false;
940+
let scannedCount = 0;
941+
let stopped = false;
942+
931943
// http://www.cse.yorku.ca/~oz/hash.html#djb2
932944
// Must mirror cosmetic filtering compiler's version
933945
const hashFromStr = (type, s) => {
@@ -946,20 +958,12 @@ vAPI.DOMFilterer = class {
946958
}
947959
};
948960

949-
const queriedHashes = new Set();
950-
const maxSurveyNodes = 65536;
951-
const pendingLists = [];
952-
const pendingNodes = [];
953-
const processedSet = new Set();
954-
let domFilterer;
955-
let hostname = '';
956-
let domChanged = false;
957-
let scannedCount = 0;
958-
let stopped = false;
961+
const qsa = (context, selector) =>
962+
Array.from(context.querySelectorAll(selector));
959963

960964
const addPendingList = list => {
961965
if ( list.length === 0 ) { return; }
962-
pendingLists.push(Array.from(list));
966+
pendingLists.push(list);
963967
};
964968

965969
const nextPendingNodes = ( ) => {
@@ -986,7 +990,7 @@ vAPI.DOMFilterer = class {
986990
};
987991

988992
const hasPendingNodes = ( ) => {
989-
return pendingLists.length !== 0;
993+
return pendingLists.length !== 0 || newHashes.size !== 0 ;
990994
};
991995

992996
// Extract all classes/ids: these will be passed to the cosmetic
@@ -997,18 +1001,18 @@ vAPI.DOMFilterer = class {
9971001
// http://www.w3.org/TR/2014/REC-html5-20141028/infrastructure.html#space-separated-tokens
9981002
// http://jsperf.com/enumerate-classes/6
9991003

1000-
const idFromNode = (node, out) => {
1004+
const idFromNode = node => {
10011005
const raw = node.id;
10021006
if ( typeof raw !== 'string' || raw.length === 0 ) { return; }
10031007
const hash = hashFromStr(0x23 /* '#' */, raw.trim());
10041008
if ( queriedHashes.has(hash) ) { return; }
10051009
queriedHashes.add(hash);
1006-
out.push(hash);
1010+
newHashes.add(hash);
10071011
};
10081012

10091013
// https://github.com/uBlockOrigin/uBlock-issues/discussions/2076
10101014
// Performance: avoid using Element.classList
1011-
const classesFromNode = (node, out) => {
1015+
const classesFromNode = node => {
10121016
const s = node.getAttribute('class');
10131017
if ( typeof s !== 'string' ) { return; }
10141018
const len = s.length;
@@ -1022,32 +1026,29 @@ vAPI.DOMFilterer = class {
10221026
const hash = hashFromStr(0x2E /* '.' */, token);
10231027
if ( queriedHashes.has(hash) ) { continue; }
10241028
queriedHashes.add(hash);
1025-
out.push(hash);
1029+
newHashes.add(hash);
10261030
}
10271031
};
10281032

1029-
const getSurveyResults = (hashes, safeOnly) => {
1030-
if ( self.vAPI.messaging instanceof Object === false ) {
1031-
stop(); return;
1032-
}
1033-
const promise = hashes.length === 0
1033+
const getSurveyResults = safeOnly => {
1034+
if ( Boolean(self.vAPI?.messaging) === false ) { return stop(); }
1035+
const promise = newHashes.size === 0
10341036
? Promise.resolve(null)
10351037
: self.vAPI.messaging.send('contentscript', {
10361038
what: 'retrieveGenericCosmeticSelectors',
10371039
hostname,
1038-
hashes,
1040+
hashes: Array.from(newHashes),
10391041
exceptions: domFilterer.exceptions,
10401042
safeOnly,
10411043
});
10421044
promise.then(response => {
10431045
processSurveyResults(response);
10441046
});
1047+
newHashes.clear();
10451048
};
10461049

10471050
const doSurvey = ( ) => {
1048-
if ( self.vAPI instanceof Object === false ) { return; }
10491051
const t0 = performance.now();
1050-
const hashes = [];
10511052
const nodes = pendingNodes;
10521053
const deadline = t0 + 4;
10531054
let scanned = 0;
@@ -1060,8 +1061,8 @@ vAPI.DOMFilterer = class {
10601061
if ( processedSet.has(node) ) { continue; }
10611062
processedSet.add(node);
10621063
}
1063-
idFromNode(node, hashes);
1064-
classesFromNode(node, hashes);
1064+
idFromNode(node);
1065+
classesFromNode(node);
10651066
scanned += 1;
10661067
}
10671068
if ( performance.now() >= deadline ) { break; }
@@ -1071,7 +1072,7 @@ vAPI.DOMFilterer = class {
10711072
stop();
10721073
}
10731074
processedSet.clear();
1074-
getSurveyResults(hashes);
1075+
getSurveyResults();
10751076
};
10761077

10771078
const surveyTimer = new vAPI.SafeAnimationFrame(doSurvey);
@@ -1121,66 +1122,73 @@ vAPI.DOMFilterer = class {
11211122
});
11221123
};
11231124

1124-
const domWatcherInterface = {
1125-
onDOMCreated: function() {
1126-
domFilterer = vAPI.domFilterer;
1127-
// https://github.com/uBlockOrigin/uBlock-issues/issues/1692
1128-
// Look-up safe-only selectors to mitigate probability of
1129-
// html/body elements of erroneously being targeted.
1130-
const hashes = [];
1131-
if ( document.documentElement !== null ) {
1132-
idFromNode(document.documentElement, hashes);
1133-
classesFromNode(document.documentElement, hashes);
1134-
}
1135-
if ( document.body !== null ) {
1136-
idFromNode(document.body, hashes);
1137-
classesFromNode(document.body, hashes);
1138-
}
1139-
if ( hashes.length !== 0 ) {
1140-
getSurveyResults(hashes, true);
1141-
}
1142-
addPendingList(document.querySelectorAll(
1143-
'[id]:not(html):not(body),[class]:not(html):not(body)'
1144-
));
1145-
if ( hasPendingNodes() ) {
1146-
surveyTimer.start();
1147-
}
1148-
},
1149-
onDOMChanged: function(addedNodes) {
1150-
if ( addedNodes.length === 0 ) { return; }
1151-
domChanged = true;
1152-
for ( const node of addedNodes ) {
1153-
addPendingList([ node ]);
1154-
if ( node.firstElementChild === null ) { continue; }
1155-
addPendingList(
1156-
node.querySelectorAll(
1157-
'[id]:not(html):not(body),[class]:not(html):not(body)'
1158-
)
1159-
);
1160-
}
1161-
if ( hasPendingNodes() ) {
1162-
surveyTimer.start(1);
1125+
const onDomChanged = mutations => {
1126+
domChanged = true;
1127+
for ( const mutation of mutations ) {
1128+
if ( mutation.type === 'childList' ) {
1129+
const { addedNodes } = mutation;
1130+
if ( addedNodes.length === 0 ) { continue; }
1131+
for ( const node of addedNodes ) {
1132+
if ( node.nodeType !== 1 ) { continue; }
1133+
if ( ignoreTags[node.localName] ) { continue; }
1134+
if ( node.parentElement === null ) { continue; }
1135+
addPendingList([ node ]);
1136+
if ( node.firstElementChild === null ) { continue; }
1137+
addPendingList(qsa(node, '[id],[class]'));
1138+
}
1139+
} else if ( mutation.attributeName === 'class' ) {
1140+
classesFromNode(mutation.target);
1141+
} else {
1142+
idFromNode(mutation.target);
11631143
}
11641144
}
1145+
if ( hasPendingNodes() ) {
1146+
surveyTimer.start();
1147+
}
11651148
};
11661149

11671150
const start = details => {
1168-
if ( self.vAPI instanceof Object === false ) { return; }
1169-
if ( self.vAPI.domFilterer instanceof Object === false ) { return; }
1170-
if ( self.vAPI.domWatcher instanceof Object === false ) { return; }
1151+
if ( Boolean(self.vAPI?.domFilterer) === false ) { return stop(); }
11711152
hostname = details.hostname;
1172-
self.vAPI.domWatcher.addListener(domWatcherInterface);
1153+
domFilterer = vAPI.domFilterer;
1154+
// https://github.com/uBlockOrigin/uBlock-issues/issues/1692
1155+
// Look-up safe-only selectors to mitigate probability of
1156+
// html/body elements of erroneously being targeted.
1157+
if ( document.documentElement !== null ) {
1158+
idFromNode(document.documentElement);
1159+
classesFromNode(document.documentElement);
1160+
}
1161+
if ( document.body !== null ) {
1162+
idFromNode(document.body);
1163+
classesFromNode(document.body);
1164+
}
1165+
if ( newHashes.size !== 0 ) {
1166+
getSurveyResults(newHashes, true);
1167+
}
1168+
addPendingList(qsa(document, '[id],[class]'));
1169+
if ( hasPendingNodes() ) {
1170+
surveyTimer.start();
1171+
}
1172+
domObserver = new MutationObserver(onDomChanged);
1173+
domObserver.observe(document, {
1174+
attributeFilter: [ 'class', 'id' ],
1175+
attributes: true,
1176+
childList: true,
1177+
subtree: true
1178+
});
11731179
};
11741180

11751181
const stop = ( ) => {
11761182
stopped = true;
11771183
pendingLists.length = 0;
11781184
surveyTimer.clear();
1179-
if ( self.vAPI instanceof Object === false ) { return; }
1180-
if ( self.vAPI.domWatcher instanceof Object ) {
1181-
self.vAPI.domWatcher.removeListener(domWatcherInterface);
1185+
if ( domObserver ) {
1186+
domObserver.disconnect();
1187+
domObserver = undefined;
1188+
}
1189+
if ( self.vAPI?.domSurveyor ) {
1190+
self.vAPI.domSurveyor = null;
11821191
}
1183-
self.vAPI.domSurveyor = null;
11841192
};
11851193

11861194
self.vAPI.domSurveyor = { start, addHashes };

0 commit comments

Comments
 (0)