@@ -127,6 +127,23 @@ FilterHostname.prototype.retrieve = function(hostname, out) {
127127 }
128128} ;
129129
130+ /******************************************************************************/
131+
132+ // Any selector specific to an entity
133+ // Examples:
134+ // google.*###cnt #center_col > #res > #topstuff > .ts
135+
136+ var FilterEntity = function ( s , entity ) {
137+ this . s = s ;
138+ this . entity = entity ;
139+ } ;
140+
141+ FilterEntity . prototype . retrieve = function ( entity , out ) {
142+ if ( entity . slice ( - this . entity . length ) === this . entity ) {
143+ out . push ( this . s ) ;
144+ }
145+ } ;
146+
130147/******************************************************************************/
131148/******************************************************************************/
132149
@@ -224,20 +241,64 @@ FilterParser.prototype.parse = function(s) {
224241/******************************************************************************/
225242
226243var SelectorCacheEntry = function ( ) {
227- this . selectors = [ ] ;
244+ this . cosmetic = { } ;
245+ this . net = { } ;
246+ this . netCount = 0 ;
228247 this . lastAccessTime = Date . now ( ) ;
229248} ;
230249
231- SelectorCacheEntry . prototype . add = function ( selectors ) {
250+ SelectorCacheEntry . prototype . netLowWaterMark = 20 ;
251+ SelectorCacheEntry . prototype . netHighWaterMark = 30 ;
252+
253+ SelectorCacheEntry . prototype . addCosmetic = function ( selectors ) {
254+ var dict = this . cosmetic ;
255+ var i = selectors . length || 0 ;
256+ while ( i -- ) {
257+ dict [ selectors [ i ] ] = true ;
258+ }
259+ } ;
260+
261+ SelectorCacheEntry . prototype . addNet = function ( selector ) {
262+ if ( typeof selector !== 'string' || selector === '' ) {
263+ return ;
264+ }
265+ // Net request-derived selectors: I limit the number of cached selectors,
266+ // as I expect cases where the blocked net-requests are never the
267+ // exact same URL.
268+ var dict = this . net ;
269+ if ( dict [ selector ] !== undefined ) {
270+ dict [ selector ] = Date . now ( ) ;
271+ return ;
272+ }
273+ if ( this . netCount >= this . netHighWaterMark ) {
274+ var keys = Object . keys ( dict ) . sort ( function ( a , b ) {
275+ return dict [ b ] - dict [ a ] ;
276+ } ) . slice ( this . netLowWaterMark ) ;
277+ var i = keys . length ;
278+ while ( i -- ) {
279+ delete dict [ keys [ i ] ] ;
280+ }
281+ }
282+ dict [ selector ] = Date . now ( ) ;
283+ this . netCount += 1 ;
284+ } ;
285+
286+ SelectorCacheEntry . prototype . add = function ( selectors , type ) {
232287 this . lastAccessTime = Date . now ( ) ;
233- this . selectors . push ( selectors ) ;
288+ if ( type === 'cosmetic' ) {
289+ this . addCosmetic ( selectors ) ;
290+ } else {
291+ this . addNet ( selectors ) ;
292+ }
234293} ;
235294
236- SelectorCacheEntry . prototype . retrieve = function ( out ) {
295+ SelectorCacheEntry . prototype . retrieve = function ( type , out ) {
237296 this . lastAccessTime = Date . now ( ) ;
238- var i = this . selectors . length ;
239- while ( i -- ) {
240- out . push ( this . selectors [ i ] ) ;
297+ var dict = type === 'cosmetic' ? this . cosmetic : this . net ;
298+ for ( var selector in dict ) {
299+ if ( dict . hasOwnProperty ( selector ) ) {
300+ out . push ( selector ) ;
301+ }
241302 }
242303} ;
243304
@@ -310,7 +371,7 @@ var makeHash = function(unhide, token, mask) {
310371// High-high generic: everything else
311372// Specific
312373// Specfic hostname
313- //
374+ // Specific entity
314375// Generic filters can only be enforced once the main document is loaded.
315376// Specific filers can be enforced before the main document is loaded.
316377
@@ -343,7 +404,8 @@ FilterContainer.prototype.reset = function() {
343404 this . highGenericDonthide = { } ;
344405 this . hostnameHide = { } ;
345406 this . hostnameDonthide = { } ;
346-
407+ this . entityHide = { } ;
408+ this . entityDonthide = { } ;
347409 // permanent
348410 // [class], [id]
349411 this . lowGenericFilters = { } ;
@@ -367,6 +429,7 @@ FilterContainer.prototype.reset = function() {
367429 this . highHighGenericDonthideCount = 0 ;
368430
369431 this . hostnameFilters = { } ;
432+ this . entityFilters = { } ;
370433} ;
371434
372435/******************************************************************************/
@@ -394,7 +457,7 @@ FilterContainer.prototype.add = function(s) {
394457 if ( hostname . charAt ( 0 ) !== '~' ) {
395458 applyGlobally = false ;
396459 }
397- this . addHostnameSelector ( hostname , parsed ) ;
460+ this . addSpecificSelector ( hostname , parsed ) ;
398461 }
399462 if ( applyGlobally ) {
400463 this . addGenericSelector ( parsed ) ;
@@ -427,6 +490,17 @@ FilterContainer.prototype.addGenericSelector = function(parsed) {
427490
428491/******************************************************************************/
429492
493+ FilterContainer . prototype . addSpecificSelector = function ( hostname , parsed ) {
494+ // rhill 2014-07-13: new filter class: entity.
495+ if ( hostname . slice ( - 2 ) === '.*' ) {
496+ this . addEntitySelector ( hostname , parsed ) ;
497+ } else {
498+ this . addHostnameSelector ( hostname , parsed ) ;
499+ }
500+ } ;
501+
502+ /******************************************************************************/
503+
430504FilterContainer . prototype . addHostnameSelector = function ( hostname , parsed ) {
431505 // https://github.com/gorhill/uBlock/issues/145
432506 var unhide = parsed . unhide ;
@@ -452,6 +526,26 @@ FilterContainer.prototype.addHostnameSelector = function(hostname, parsed) {
452526
453527/******************************************************************************/
454528
529+ FilterContainer . prototype . addEntitySelector = function ( hostname , parsed ) {
530+ var entries = parsed . unhide === 0 ?
531+ this . entityHide :
532+ this . entityDonthide ;
533+ var entity = hostname . slice ( 0 , - 2 ) ;
534+ var entry = entries [ entity ] ;
535+ if ( entry === undefined ) {
536+ entry = entries [ entity ] = { } ;
537+ entry [ parsed . suffix ] = true ;
538+ this . acceptedCount += 1 ;
539+ } else if ( entry [ parsed . suffix ] === undefined ) {
540+ entry [ parsed . suffix ] = true ;
541+ this . acceptedCount += 1 ;
542+ } else {
543+ this . duplicateCount += 1 ;
544+ }
545+ } ;
546+
547+ /******************************************************************************/
548+
455549FilterContainer . prototype . freezeLowGenerics = function ( what , type ) {
456550 var selectors = this [ what ] ;
457551 var matches , selectorPrefix , f , hash , bucket ;
@@ -509,6 +603,30 @@ FilterContainer.prototype.freezeHostnameSpecifics = function(what, type) {
509603
510604/******************************************************************************/
511605
606+ FilterContainer . prototype . freezeEntitySpecifics = function ( what , type ) {
607+ var entries = this [ what ] ;
608+ var filters = this . entityFilters ;
609+ var f , hash , bucket ;
610+ for ( var entity in entries ) {
611+ if ( entries . hasOwnProperty ( entity ) === false ) {
612+ continue ;
613+ }
614+ f = new FilterEntity ( Object . keys ( entries [ entity ] ) . join ( ',\n' ) , entity ) ;
615+ hash = makeHash ( type , entity , this . domainHashMask ) ;
616+ bucket = filters [ hash ] ;
617+ if ( bucket === undefined ) {
618+ filters [ hash ] = f ;
619+ } else if ( bucket instanceof FilterBucket ) {
620+ bucket . add ( f ) ;
621+ } else {
622+ filters [ hash ] = new FilterBucket ( bucket , f ) ;
623+ }
624+ }
625+ this [ what ] = { } ;
626+ } ;
627+
628+ /******************************************************************************/
629+
512630FilterContainer . prototype . freezeHighGenerics = function ( what ) {
513631 var selectors = this [ 'highGeneric' + what ] ;
514632
@@ -564,6 +682,8 @@ FilterContainer.prototype.freeze = function() {
564682 this . freezeHighGenerics ( 'Donthide' ) ;
565683 this . freezeHostnameSpecifics ( 'hostnameHide' , 0 ) ;
566684 this . freezeHostnameSpecifics ( 'hostnameDonthide' , 1 ) ;
685+ this . freezeEntitySpecifics ( 'entityHide' , 0 ) ;
686+ this . freezeEntitySpecifics ( 'entityDonthide' , 1 ) ;
567687 this . filterParser . reset ( ) ;
568688 this . frozen = true ;
569689
@@ -573,11 +693,13 @@ FilterContainer.prototype.freeze = function() {
573693
574694/******************************************************************************/
575695
576- FilterContainer . prototype . addToSelectorCache = function ( hostname , selectors ) {
696+ FilterContainer . prototype . addToSelectorCache = function ( details ) {
697+ var hostname = details . hostname ;
577698 if ( typeof hostname !== 'string' || hostname === '' ) {
578699 return ;
579700 }
580- if ( typeof selectors !== 'string' || selectors === '' ) {
701+ var selectors = details . selectors ;
702+ if ( ! selectors ) {
581703 return ;
582704 }
583705 var entry = this . selectorCache [ hostname ] ;
@@ -588,17 +710,17 @@ FilterContainer.prototype.addToSelectorCache = function(hostname, selectors) {
588710 this . pruneSelectorCache ( ) ;
589711 }
590712 }
591- entry . add ( selectors ) ;
713+ entry . add ( selectors , details . type ) ;
592714} ;
593715
594716/******************************************************************************/
595717
596- FilterContainer . prototype . retrieveFromSelectorCache = function ( hostname , out ) {
718+ FilterContainer . prototype . retrieveFromSelectorCache = function ( hostname , type , out ) {
597719 var entry = this . selectorCache [ hostname ] ;
598720 if ( entry === undefined ) {
599721 return ;
600722 }
601- entry . retrieve ( out ) ;
723+ entry . retrieve ( type , out ) ;
602724} ;
603725
604726/******************************************************************************/
@@ -691,30 +813,41 @@ FilterContainer.prototype.retrieveDomainSelectors = function(request) {
691813 //quickProfiler.start('FilterContainer.retrieve()');
692814
693815 var hostname = µb . URI . hostnameFromURI ( request . locationURL ) ;
816+ var domain = µb . URI . domainFromHostname ( hostname ) ;
817+ var pos = domain . indexOf ( '.' ) ;
818+
694819 var r = {
695- domain : µb . URI . domainFromHostname ( hostname ) ,
696- hide : [ ] ,
697- donthide : [ ]
820+ domain : domain ,
821+ entity : pos === - 1 ? domain : domain . slice ( 0 , pos - domain . length ) ,
822+ cosmeticHide : [ ] ,
823+ cosmeticDonthide : [ ] ,
824+ netHide : [ ] ,
825+ netCollapse : µb . userSettings . collapseBlocked
698826 } ;
699827
700828 var hash , bucket ;
701829 hash = makeHash ( 0 , r . domain , this . domainHashMask ) ;
702830 if ( bucket = this . hostnameFilters [ hash ] ) {
703- bucket . retrieve ( hostname , r . hide ) ;
831+ bucket . retrieve ( hostname , r . cosmeticHide ) ;
832+ }
833+ hash = makeHash ( 0 , r . entity , this . domainHashMask ) ;
834+ if ( bucket = this . entityFilters [ hash ] ) {
835+ bucket . retrieve ( pos === - 1 ? domain : hostname . slice ( 0 , pos - domain . length ) , r . cosmeticHide ) ;
704836 }
705837 hash = makeHash ( 1 , r . domain , this . domainHashMask ) ;
706838 if ( bucket = this . hostnameFilters [ hash ] ) {
707- bucket . retrieve ( hostname , r . donthide ) ;
839+ bucket . retrieve ( hostname , r . cosmeticDonthide ) ;
708840 }
709841
710- this . retrieveFromSelectorCache ( hostname , r . hide ) ;
842+ this . retrieveFromSelectorCache ( hostname , 'cosmetic' , r . cosmeticHide ) ;
843+ this . retrieveFromSelectorCache ( hostname , 'net' , r . netHide ) ;
711844
712845 //quickProfiler.stop();
713846
714847 //console.log(
715848 // 'µBlock> abp-hide-filters.js: "%s" => %d selectors out',
716849 // request.locationURL,
717- // r.hide .length + r.donthide .length
850+ // r.cosmeticHide .length + r.cosmeticDonthide .length
718851 //);
719852
720853 return r ;
0 commit comments