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 = / : + (?: a f t e r | b e f o r e ) $ / ,
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