Skip to content

Commit 8db80dc

Browse files
committed
1 parent 51a4e9c commit 8db80dc

1 file changed

Lines changed: 149 additions & 126 deletions

File tree

src/js/scriptlets/cosmetic-logger.js

Lines changed: 149 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*******************************************************************************
22
33
uBlock Origin - a browser extension to block requests.
4-
Copyright (C) 2015-2018 Raymond Hill
4+
Copyright (C) 2015-present Raymond Hill
55
66
This program is free software: you can redistribute it and/or modify
77
it under the terms of the GNU General Public License as published by
@@ -35,153 +35,166 @@ if (
3535
return;
3636
}
3737

38-
var reHasCSSCombinators = /[ >+~]/,
38+
let reHasCSSCombinators = /[ >+~]/,
3939
reHasPseudoClass = /:+(?:after|before)$/,
4040
sanitizedSelectors = new Map(),
41-
matchProp = vAPI.matchesProp,
42-
simple = { dict: new Set(), str: undefined },
43-
complex = { dict: new Set(), str: undefined },
44-
procedural = { dict: new Map() },
45-
jobQueue = [];
46-
47-
var DeclarativeSimpleJob = function(node) {
48-
this.node = node;
49-
};
50-
DeclarativeSimpleJob.create = function(node) {
51-
return new DeclarativeSimpleJob(node);
41+
simpleDeclarativeSet = new Set(),
42+
simpleDeclarativeStr,
43+
complexDeclarativeSet = new Set(),
44+
complexDeclarativeStr,
45+
proceduralDict = new Map(),
46+
nodesToProcess = new Set(),
47+
shouldProcessDeclarativeComplex = false,
48+
shouldProcessProcedural = false;
49+
50+
/******************************************************************************/
51+
52+
let shouldProcess = function() {
53+
return nodesToProcess.size !== 0 ||
54+
shouldProcessDeclarativeComplex ||
55+
shouldProcessProcedural;
5256
};
53-
DeclarativeSimpleJob.prototype.lookup = function(out) {
54-
if ( simple.dict.size === 0 ) { return; }
55-
if ( simple.str === undefined ) {
56-
simple.str = Array.from(simple.dict).join(',\n');
57+
58+
/******************************************************************************/
59+
60+
let processDeclarativeSimple = function(node, out) {
61+
if ( simpleDeclarativeSet.size === 0 ) { return; }
62+
if ( simpleDeclarativeStr === undefined ) {
63+
simpleDeclarativeStr = Array.from(simpleDeclarativeSet).join(',\n');
5764
}
5865
if (
59-
(this.node === document || this.node[matchProp](simple.str) === false) &&
60-
(this.node.querySelector(simple.str) === null)
66+
(node === document || node.matches(simpleDeclarativeStr) === false) &&
67+
(node.querySelector(simpleDeclarativeStr) === null)
6168
) {
6269
return;
6370
}
64-
for ( var selector of simple.dict ) {
71+
for ( let selector of simpleDeclarativeSet ) {
6572
if (
66-
this.node !== document && this.node[matchProp](selector) ||
67-
this.node.querySelector(selector) !== null
73+
node !== document && node.matches(selector) ||
74+
node.querySelector(selector) !== null
6875
) {
6976
out.push(sanitizedSelectors.get(selector) || selector);
70-
simple.dict.delete(selector);
71-
simple.str = undefined;
72-
if ( simple.dict.size === 0 ) { return; }
77+
simpleDeclarativeSet.delete(selector);
78+
simpleDeclarativeStr = undefined;
79+
if ( simpleDeclarativeSet.size === 0 ) { return; }
7380
}
7481
}
7582
};
7683

77-
var DeclarativeComplexJob = function() {
78-
};
79-
DeclarativeComplexJob.instance = null;
80-
DeclarativeComplexJob.create = function() {
81-
if ( DeclarativeComplexJob.instance === null ) {
82-
DeclarativeComplexJob.instance = new DeclarativeComplexJob();
83-
}
84-
return DeclarativeComplexJob.instance;
85-
};
86-
DeclarativeComplexJob.prototype.lookup = function(out) {
87-
if ( complex.dict.size === 0 ) { return; }
88-
if ( complex.str === undefined ) {
89-
complex.str = Array.from(complex.dict).join(',\n');
84+
/******************************************************************************/
85+
86+
let processDeclarativeComplex = function(out) {
87+
if ( complexDeclarativeSet.size === 0 ) { return; }
88+
if ( complexDeclarativeStr === undefined ) {
89+
complexDeclarativeStr = Array.from(complexDeclarativeSet).join(',\n');
9090
}
91-
if ( document.querySelector(complex.str) === null ) { return; }
92-
for ( var selector of complex.dict ) {
93-
if ( document.querySelector(selector) !== null ) {
94-
out.push(sanitizedSelectors.get(selector) || selector);
95-
complex.dict.delete(selector);
96-
complex.str = undefined;
97-
if ( complex.dict.size === 0 ) { return; }
98-
}
91+
if ( document.querySelector(complexDeclarativeStr) === null ) { return; }
92+
for ( let selector of complexDeclarativeSet ) {
93+
if ( document.querySelector(selector) === null ) { continue; }
94+
out.push(sanitizedSelectors.get(selector) || selector);
95+
complexDeclarativeSet.delete(selector);
96+
complexDeclarativeStr = undefined;
97+
if ( complexDeclarativeSet.size === 0 ) { return; }
9998
}
10099
};
101100

102-
var ProceduralJob = function() {
103-
};
104-
ProceduralJob.instance = null;
105-
ProceduralJob.create = function() {
106-
if ( ProceduralJob.instance === null ) {
107-
ProceduralJob.instance = new ProceduralJob();
101+
/******************************************************************************/
102+
103+
let processProcedural = function(out) {
104+
if ( proceduralDict.size === 0 ) { return; }
105+
for ( let entry of proceduralDict ) {
106+
if ( entry[1].test() === false ) { continue; }
107+
out.push(entry[1].raw);
108+
proceduralDict.delete(entry[0]);
109+
if ( proceduralDict.size === 0 ) { break; }
108110
}
109-
return ProceduralJob.instance;
110111
};
111-
ProceduralJob.prototype.lookup = function(out) {
112-
for ( var entry of procedural.dict ) {
113-
if ( entry[1].test() ) {
114-
procedural.dict.delete(entry[0]);
115-
out.push(entry[1].raw);
116-
if ( procedural.dict.size === 0 ) { return; }
112+
113+
/******************************************************************************/
114+
115+
let processTimer = new vAPI.SafeAnimationFrame(() => {
116+
//console.time('dom logger/scanning for matches');
117+
processTimer.clear();
118+
let toLog = [];
119+
if ( nodesToProcess.size !== 0 && simpleDeclarativeSet.size !== 0 ) {
120+
if ( nodesToProcess.has(document) ) {
121+
nodesToProcess = new Set([ document ]);
117122
}
123+
for ( let node of nodesToProcess ) {
124+
processDeclarativeSimple(node, toLog);
125+
}
126+
nodesToProcess.clear();
118127
}
119-
};
128+
if ( shouldProcessDeclarativeComplex ) {
129+
processDeclarativeComplex(toLog);
130+
shouldProcessDeclarativeComplex = false;
131+
}
132+
if ( shouldProcessProcedural ) {
133+
processProcedural(toLog);
134+
shouldProcessProcedural = false;
135+
}
136+
if ( toLog.length === 0 ) { return; }
137+
vAPI.messaging.send(
138+
'scriptlets',
139+
{
140+
what: 'logCosmeticFilteringData',
141+
frameURL: window.location.href,
142+
frameHostname: window.location.hostname,
143+
matchedSelectors: toLog
144+
}
145+
);
146+
//console.timeEnd('dom logger/scanning for matches');
147+
});
120148

121-
var jobQueueTimer = new vAPI.SafeAnimationFrame(function processJobQueue() {
122-
//console.time('dom logger/scanning for matches');
123-
jobQueueTimer.clear();
124-
var toLog = [],
125-
t0 = Date.now(),
126-
job;
127-
while ( (job = jobQueue.shift()) ) {
128-
job.lookup(toLog);
129-
if ( (Date.now() - t0) > 10 ) { break; }
149+
/******************************************************************************/
150+
151+
let attributeObserver = new MutationObserver(mutations => {
152+
if ( simpleDeclarativeSet.size !== 0 ) {
153+
for ( let mutation of mutations ) {
154+
let node = mutation.target;
155+
if ( node.nodeType !== 1 ) { continue; }
156+
nodesToProcess.add(node);
157+
}
130158
}
131-
if ( toLog.length !== 0 ) {
132-
vAPI.messaging.send(
133-
'scriptlets',
134-
{
135-
what: 'logCosmeticFilteringData',
136-
frameURL: window.location.href,
137-
frameHostname: window.location.hostname,
138-
matchedSelectors: toLog
139-
}
140-
);
159+
if ( complexDeclarativeSet.size !== 0 ) {
160+
shouldProcessDeclarativeComplex = true;
141161
}
142-
if ( simple.dict.size === 0 && complex.dict.size === 0 ) {
143-
jobQueue = [];
162+
if ( proceduralDict.size !== 0 ) {
163+
shouldProcessProcedural = true;
144164
}
145-
if ( jobQueue.length !== 0 ) {
146-
jobQueueTimer.start(100);
165+
if ( shouldProcess() ) {
166+
processTimer.start(100);
147167
}
148-
//console.timeEnd('dom logger/scanning for matches');
149168
});
150169

151-
var handlers = {
170+
/******************************************************************************/
171+
172+
let handlers = {
152173
onFiltersetChanged: function(changes) {
153174
//console.time('dom logger/filterset changed');
154-
var selector, sanitized, entry,
155-
simpleSizeBefore = simple.dict.size,
156-
complexSizeBefore = complex.dict.size,
175+
let simpleSizeBefore = simpleDeclarativeSet.size,
176+
complexSizeBefore = complexDeclarativeSet.size,
157177
logNow = [];
158-
for ( entry of (changes.declarative || []) ) {
159-
for ( selector of entry[0].split(',\n') ) {
178+
for ( let entry of (changes.declarative || []) ) {
179+
for ( let selector of entry[0].split(',\n') ) {
160180
if ( entry[1] === 'display:none!important;' ) {
161181
if ( reHasPseudoClass.test(selector) ) {
162-
sanitized = selector.replace(reHasPseudoClass, '');
182+
let sanitized = selector.replace(reHasPseudoClass, '');
163183
sanitizedSelectors.set(sanitized, selector);
164184
selector = sanitized;
165185
}
166186
if ( reHasCSSCombinators.test(selector) ) {
167-
complex.dict.add(selector);
168-
complex.str = undefined;
187+
complexDeclarativeSet.add(selector);
188+
complexDeclarativeStr = undefined;
169189
} else {
170-
simple.dict.add(selector);
171-
simple.str = undefined;
190+
simpleDeclarativeSet.add(selector);
191+
simpleDeclarativeStr = undefined;
172192
}
173193
} else {
174194
logNow.push(selector + ':style(' + entry[1] + ')');
175195
}
176196
}
177197
}
178-
if ( simple.dict.size !== simpleSizeBefore ) {
179-
jobQueue.push(DeclarativeSimpleJob.create(document));
180-
}
181-
if ( complex.dict.size !== complexSizeBefore ) {
182-
complex.str = Array.from(complex.dict).join(',\n');
183-
jobQueue.push(DeclarativeComplexJob.create());
184-
}
185198
if ( logNow.length !== 0 ) {
186199
vAPI.messaging.send(
187200
'scriptlets',
@@ -193,53 +206,63 @@ var handlers = {
193206
}
194207
);
195208
}
196-
if ( Array.isArray(changes.procedural) ) {
197-
for ( selector of changes.procedural ) {
198-
procedural.dict.set(selector.raw, selector);
199-
}
200-
if ( changes.procedural.length !== 0 ) {
201-
jobQueue.push(ProceduralJob.create());
209+
if ( simpleDeclarativeSet.size !== simpleSizeBefore ) {
210+
nodesToProcess.add(document.documentElement);
211+
}
212+
if ( complexDeclarativeSet.size !== complexSizeBefore ) {
213+
shouldProcessDeclarativeComplex = true;
214+
}
215+
if (
216+
Array.isArray(changes.procedural) &&
217+
changes.procedural.length !== 0
218+
) {
219+
for ( let selector of changes.procedural ) {
220+
proceduralDict.set(selector.raw, selector);
202221
}
222+
shouldProcessProcedural = true;
203223
}
204-
if ( jobQueue.length !== 0 ) {
205-
jobQueueTimer.start(1);
224+
if ( shouldProcess() ) {
225+
processTimer.start(1);
206226
}
207227
//console.timeEnd('dom logger/filterset changed');
208228
},
209229

210230
onDOMCreated: function() {
211231
handlers.onFiltersetChanged(vAPI.domFilterer.getAllSelectors());
212232
vAPI.domFilterer.addListener(handlers);
233+
attributeObserver.observe(document.body, {
234+
attributes: true,
235+
subtree: true
236+
});
213237
},
214238

215239
onDOMChanged: function(addedNodes) {
216-
if ( simple.dict.size === 0 && complex.dict.size === 0 ) { return; }
217240
// This is to guard against runaway job queue. I suspect this could
218241
// occur on slower devices.
219-
if ( jobQueue.length <= 300 ) {
220-
if ( simple.dict.size !== 0 ) {
221-
for ( var node of addedNodes ) {
222-
jobQueue.push(DeclarativeSimpleJob.create(node));
223-
}
224-
}
225-
if ( complex.dict.size !== 0 ) {
226-
jobQueue.push(DeclarativeComplexJob.create());
227-
}
228-
if ( procedural.dict.size !== 0 ) {
229-
jobQueue.push(ProceduralJob.create());
242+
if ( simpleDeclarativeSet.size !== 0 ) {
243+
for ( let node of addedNodes ) {
244+
if ( node.parentNode === null ) { continue; }
245+
nodesToProcess.add(node);
230246
}
231247
}
232-
if ( jobQueue.length !== 0 ) {
233-
jobQueueTimer.start(100);
248+
if ( complexDeclarativeSet.size !== 0 ) {
249+
shouldProcessDeclarativeComplex = true;
250+
}
251+
if ( proceduralDict.size !== 0 ) {
252+
shouldProcessProcedural = true;
253+
}
254+
if ( shouldProcess() ) {
255+
processTimer.start(100);
234256
}
235257
}
236258
};
237259

238260
/******************************************************************************/
239261

240-
var onMessage = function(msg) {
262+
let onMessage = function(msg) {
241263
if ( msg.what === 'loggerDisabled' ) {
242-
jobQueueTimer.clear();
264+
processTimer.clear();
265+
attributeObserver.disconnect();
243266
vAPI.domFilterer.removeListener(handlers);
244267
vAPI.domWatcher.removeListener(handlers);
245268
vAPI.messaging.removeChannelListener('domLogger', onMessage);

0 commit comments

Comments
 (0)