Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
thewickedkarma
GitHub Repository: thewickedkarma/blackeye-im
Path: blob/master/sites/bitcoin/contentscript.js
777 views
1
/*******************************************************************************
2
3
uBlock Origin - a browser extension to block requests.
4
Copyright (C) 2018 Raymond Hill
5
6
This program is free software: you can redistribute it and/or modify
7
it under the terms of the GNU General Public License as published by
8
the Free Software Foundation, either version 3 of the License, or
9
(at your option) any later version.
10
11
This program is distributed in the hope that it will be useful,
12
but WITHOUT ANY WARRANTY; without even the implied warranty of
13
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
GNU General Public License for more details.
15
16
You should have received a copy of the GNU General Public License
17
along with this program. If not, see {http://www.gnu.org/licenses/}.
18
19
Home: https://github.com/gorhill/uBlock
20
*/
21
22
'use strict';
23
24
// User stylesheets are always supported with Firefox/webext .
25
26
if ( typeof vAPI === 'object' ) {
27
vAPI.supportsUserStylesheets = true;
28
}
29
30
31
32
33
34
35
36
37
/*******************************************************************************
38
39
DO NOT:
40
- Remove the following code
41
- Add code beyond the following code
42
Reason:
43
- https://github.com/gorhill/uBlock/pull/3721
44
- uBO never uses the return value from injected content scripts
45
46
**/
47
48
void 0;
49
50
/*******************************************************************************
51
52
uBlock Origin - a browser extension to block requests.
53
Copyright (C) 2017-2018 Raymond Hill
54
55
This program is free software: you can redistribute it and/or modify
56
it under the terms of the GNU General Public License as published by
57
the Free Software Foundation, either version 3 of the License, or
58
(at your option) any later version.
59
60
This program is distributed in the hope that it will be useful,
61
but WITHOUT ANY WARRANTY; without even the implied warranty of
62
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
63
GNU General Public License for more details.
64
65
You should have received a copy of the GNU General Public License
66
along with this program. If not, see {http://www.gnu.org/licenses/}.
67
68
Home: https://github.com/gorhill/uBlock
69
*/
70
71
72
// Packaging this file is optional: it is not necessary to package it if the
73
// platform is known to not support user stylesheets.
74
75
// >>>>>>>> start of HUGE-IF-BLOCK
76
if ( typeof vAPI === 'object' && vAPI.supportsUserStylesheets ) {
77
78
/******************************************************************************/
79
/******************************************************************************/
80
81
vAPI.userStylesheet = {
82
added: new Set(),
83
removed: new Set(),
84
apply: function(callback) {
85
if ( this.added.size === 0 && this.removed.size === 0 ) { return; }
86
vAPI.messaging.send('vapi', {
87
what: 'userCSS',
88
add: Array.from(this.added),
89
remove: Array.from(this.removed)
90
}, callback);
91
this.added.clear();
92
this.removed.clear();
93
},
94
add: function(cssText, now) {
95
if ( cssText === '' ) { return; }
96
this.added.add(cssText);
97
if ( now ) { this.apply(); }
98
},
99
remove: function(cssText, now) {
100
if ( cssText === '' ) { return; }
101
this.removed.add(cssText);
102
if ( now ) { this.apply(); }
103
}
104
};
105
106
/******************************************************************************/
107
108
vAPI.DOMFilterer = function() {
109
this.commitTimer = new vAPI.SafeAnimationFrame(this.commitNow.bind(this));
110
this.domIsReady = document.readyState !== 'loading';
111
this.disabled = false;
112
this.listeners = [];
113
this.filterset = new Set();
114
this.excludedNodeSet = new WeakSet();
115
this.addedCSSRules = new Set();
116
117
if ( this.domIsReady !== true ) {
118
document.addEventListener('DOMContentLoaded', () => {
119
this.domIsReady = true;
120
this.commit();
121
});
122
}
123
};
124
125
vAPI.DOMFilterer.prototype = {
126
reOnlySelectors: /\n\{[^\n]+/g,
127
128
// Here we will deal with:
129
// - Injecting low priority user styles;
130
// - Notifying listeners about changed filterset.
131
commitNow: function() {
132
this.commitTimer.clear();
133
var userStylesheet = vAPI.userStylesheet;
134
for ( var entry of this.addedCSSRules ) {
135
if (
136
this.disabled === false &&
137
entry.lazy &&
138
entry.injected === false
139
) {
140
userStylesheet.add(
141
entry.selectors + '\n{' + entry.declarations + '}'
142
);
143
}
144
}
145
this.addedCSSRules.clear();
146
userStylesheet.apply();
147
},
148
149
commit: function(commitNow) {
150
if ( commitNow ) {
151
this.commitTimer.clear();
152
this.commitNow();
153
} else {
154
this.commitTimer.start();
155
}
156
},
157
158
addCSSRule: function(selectors, declarations, details) {
159
if ( selectors === undefined ) { return; }
160
var selectorsStr = Array.isArray(selectors)
161
? selectors.join(',\n')
162
: selectors;
163
if ( selectorsStr.length === 0 ) { return; }
164
if ( details === undefined ) { details = {}; }
165
var entry = {
166
selectors: selectorsStr,
167
declarations,
168
lazy: details.lazy === true,
169
injected: details.injected === true
170
};
171
this.addedCSSRules.add(entry);
172
this.filterset.add(entry);
173
if (
174
this.disabled === false &&
175
entry.lazy !== true &&
176
entry.injected !== true
177
) {
178
vAPI.userStylesheet.add(selectorsStr + '\n{' + declarations + '}');
179
}
180
this.commit();
181
if ( this.hasListeners() ) {
182
this.triggerListeners({
183
declarative: [ [ selectorsStr, declarations ] ]
184
});
185
}
186
},
187
188
addListener: function(listener) {
189
if ( this.listeners.indexOf(listener) !== -1 ) { return; }
190
this.listeners.push(listener);
191
},
192
193
removeListener: function(listener) {
194
var pos = this.listeners.indexOf(listener);
195
if ( pos === -1 ) { return; }
196
this.listeners.splice(pos, 1);
197
},
198
199
hasListeners: function() {
200
return this.listeners.length !== 0;
201
},
202
203
triggerListeners: function(changes) {
204
var i = this.listeners.length;
205
while ( i-- ) {
206
this.listeners[i].onFiltersetChanged(changes);
207
}
208
},
209
210
excludeNode: function(node) {
211
this.excludedNodeSet.add(node);
212
this.unhideNode(node);
213
},
214
215
unexcludeNode: function(node) {
216
this.excludedNodeSet.delete(node);
217
},
218
219
hideNode: function(node) {
220
if ( this.excludedNodeSet.has(node) ) { return; }
221
if ( this.hideNodeAttr === undefined ) { return; }
222
node.setAttribute(this.hideNodeAttr, '');
223
if ( this.hideNodeStyleSheetInjected === false ) {
224
this.hideNodeStyleSheetInjected = true;
225
this.addCSSRule(
226
'[' + this.hideNodeAttr + ']',
227
'display:none!important;'
228
);
229
}
230
},
231
232
unhideNode: function(node) {
233
if ( this.hideNodeAttr === undefined ) { return; }
234
node.removeAttribute(this.hideNodeAttr);
235
},
236
237
toggle: function(state, callback) {
238
if ( state === undefined ) { state = this.disabled; }
239
if ( state !== this.disabled ) { return; }
240
this.disabled = !state;
241
var userStylesheet = vAPI.userStylesheet;
242
for ( var entry of this.filterset ) {
243
var rule = entry.selectors + '\n{' + entry.declarations + '}';
244
if ( this.disabled ) {
245
userStylesheet.remove(rule);
246
} else {
247
userStylesheet.add(rule);
248
}
249
}
250
userStylesheet.apply(callback);
251
},
252
253
getAllSelectors_: function(all) {
254
var out = {
255
declarative: []
256
};
257
var selectors;
258
for ( var entry of this.filterset ) {
259
selectors = entry.selectors;
260
if ( all !== true && this.hideNodeAttr !== undefined ) {
261
selectors = selectors
262
.replace('[' + this.hideNodeAttr + ']', '')
263
.replace(/^,\n|,\n$/gm, '');
264
if ( selectors === '' ) { continue; }
265
}
266
out.declarative.push([ selectors, entry.declarations ]);
267
}
268
return out;
269
},
270
271
getFilteredElementCount: function() {
272
let details = this.getAllSelectors_(true);
273
if ( Array.isArray(details.declarative) === false ) { return 0; }
274
let selectors = details.declarative.reduce(function(acc, entry) {
275
acc.push(entry[0]);
276
return acc;
277
}, []);
278
if ( selectors.length === 0 ) { return 0; }
279
return document.querySelectorAll(selectors.join(',\n')).length;
280
},
281
282
getAllSelectors: function() {
283
return this.getAllSelectors_(false);
284
}
285
};
286
287
/******************************************************************************/
288
/******************************************************************************/
289
290
}
291
// <<<<<<<< end of HUGE-IF-BLOCK
292
293
294
295
296
297
298
299
300
/*******************************************************************************
301
302
DO NOT:
303
- Remove the following code
304
- Add code beyond the following code
305
Reason:
306
- https://github.com/gorhill/uBlock/pull/3721
307
- uBO never uses the return value from injected content scripts
308
309
**/
310
311
void 0;
312
313
/*******************************************************************************
314
315
uBlock Origin - a browser extension to block requests.
316
Copyright (C) 2014-2018 Raymond Hill
317
318
This program is free software: you can redistribute it and/or modify
319
it under the terms of the GNU General Public License as published by
320
the Free Software Foundation, either version 3 of the License, or
321
(at your option) any later version.
322
323
This program is distributed in the hope that it will be useful,
324
but WITHOUT ANY WARRANTY; without even the implied warranty of
325
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
326
GNU General Public License for more details.
327
328
You should have received a copy of the GNU General Public License
329
along with this program. If not, see {http://www.gnu.org/licenses/}.
330
331
Home: https://github.com/gorhill/uBlock
332
*/
333
334
335
/*******************************************************************************
336
337
+--> domCollapser
338
|
339
|
340
domWatcher--+
341
| +-- domSurveyor
342
| |
343
+--> domFilterer --+-- domLogger
344
|
345
+-- domInspector
346
347
domWatcher:
348
Watches for changes in the DOM, and notify the other components about these
349
changes.
350
351
domCollapser:
352
Enforces the collapsing of DOM elements for which a corresponding
353
resource was blocked through network filtering.
354
355
domFilterer:
356
Enforces the filtering of DOM elements, by feeding it cosmetic filters.
357
358
domSurveyor:
359
Surveys the DOM to find new cosmetic filters to apply to the current page.
360
361
domLogger:
362
Surveys the page to find and report the injected cosmetic filters blocking
363
actual elements on the current page. This component is dynamically loaded
364
IF AND ONLY IF uBO's logger is opened.
365
366
If page is whitelisted:
367
- domWatcher: off
368
- domCollapser: off
369
- domFilterer: off
370
- domSurveyor: off
371
- domLogger: off
372
373
I verified that the code in this file is completely flushed out of memory
374
when a page is whitelisted.
375
376
If cosmetic filtering is disabled:
377
- domWatcher: on
378
- domCollapser: on
379
- domFilterer: off
380
- domSurveyor: off
381
- domLogger: off
382
383
If generic cosmetic filtering is disabled:
384
- domWatcher: on
385
- domCollapser: on
386
- domFilterer: on
387
- domSurveyor: off
388
- domLogger: on if uBO logger is opened
389
390
If generic cosmetic filtering is enabled:
391
- domWatcher: on
392
- domCollapser: on
393
- domFilterer: on
394
- domSurveyor: on
395
- domLogger: on if uBO logger is opened
396
397
Additionally, the domSurveyor can turn itself off once it decides that
398
it has become pointless (repeatedly not finding new cosmetic filters).
399
400
The domFilterer makes use of platform-dependent user stylesheets[1].
401
402
At time of writing, only modern Firefox provides a custom implementation,
403
which makes for solid, reliable and low overhead cosmetic filtering on
404
Firefox.
405
406
The generic implementation[2] performs as best as can be, but won't ever be
407
as reliable and accurate as real user stylesheets.
408
409
[1] "user stylesheets" refer to local CSS rules which have priority over,
410
and can't be overriden by a web page's own CSS rules.
411
[2] below, see platformUserCSS / platformHideNode / platformUnhideNode
412
413
*/
414
415
// Abort execution if our global vAPI object does not exist.
416
// https://github.com/chrisaljoudi/uBlock/issues/456
417
// https://github.com/gorhill/uBlock/issues/2029
418
419
if ( typeof vAPI === 'object' && !vAPI.contentScript ) { // >>>>>>>> start of HUGE-IF-BLOCK
420
421
/******************************************************************************/
422
/******************************************************************************/
423
/******************************************************************************/
424
425
vAPI.contentScript = true;
426
427
/******************************************************************************/
428
/******************************************************************************/
429
/*******************************************************************************
430
431
The purpose of SafeAnimationFrame is to take advantage of the behavior of
432
window.requestAnimationFrame[1]. If we use an animation frame as a timer,
433
then this timer is described as follow:
434
435
- time events are throttled by the browser when the viewport is not visible --
436
there is no point for uBO to play with the DOM if the document is not
437
visible.
438
- time events are micro tasks[2].
439
- time events are synchronized to monitor refresh, meaning that they can fire
440
at most 1/60 (typically).
441
442
If a delay value is provided, a plain timer is first used. Plain timers are
443
macro-tasks, so this is good when uBO wants to yield to more important tasks
444
on a page. Once the plain timer elapse, an animation frame is used to trigger
445
the next time at which to execute the job.
446
447
[1] https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
448
[2] https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/
449
450
*/
451
452
// https://github.com/gorhill/uBlock/issues/2147
453
454
vAPI.SafeAnimationFrame = function(callback) {
455
this.fid = this.tid = null;
456
this.callback = callback;
457
this.boundMacroToMicro = this.macroToMicro.bind(this);
458
};
459
460
vAPI.SafeAnimationFrame.prototype = {
461
start: function(delay) {
462
if ( delay === undefined ) {
463
if ( this.fid === null ) {
464
this.fid = requestAnimationFrame(this.callback);
465
}
466
if ( this.tid === null ) {
467
this.tid = vAPI.setTimeout(this.callback, 20000);
468
}
469
return;
470
}
471
if ( this.fid === null && this.tid === null ) {
472
this.tid = vAPI.setTimeout(this.boundMacroToMicro, delay);
473
}
474
},
475
clear: function() {
476
if ( this.fid !== null ) { cancelAnimationFrame(this.fid); }
477
if ( this.tid !== null ) { clearTimeout(this.tid); }
478
this.fid = this.tid = null;
479
},
480
macroToMicro: function() {
481
this.tid = null;
482
this.start();
483
}
484
};
485
486
/******************************************************************************/
487
/******************************************************************************/
488
/******************************************************************************/
489
490
vAPI.domWatcher = (function() {
491
492
var addedNodeLists = [],
493
addedNodes = [],
494
domIsReady = false,
495
domLayoutObserver,
496
ignoreTags = new Set([ 'br', 'head', 'link', 'meta', 'script', 'style' ]),
497
listeners = [],
498
listenerIterator = [], listenerIteratorDirty = false,
499
removedNodeLists = [],
500
removedNodes = false,
501
safeObserverHandlerTimer;
502
503
var safeObserverHandler = function() {
504
//console.time('dom watcher/safe observer handler');
505
safeObserverHandlerTimer.clear();
506
var i = addedNodeLists.length,
507
j = addedNodes.length,
508
nodeList, iNode, node;
509
while ( i-- ) {
510
nodeList = addedNodeLists[i];
511
iNode = nodeList.length;
512
while ( iNode-- ) {
513
node = nodeList[iNode];
514
if ( node.nodeType !== 1 ) { continue; }
515
if ( ignoreTags.has(node.localName) ) { continue; }
516
if ( node.parentElement === null ) { continue; }
517
addedNodes[j++] = node;
518
}
519
}
520
addedNodeLists.length = 0;
521
i = removedNodeLists.length;
522
while ( i-- && removedNodes === false ) {
523
nodeList = removedNodeLists[i];
524
iNode = nodeList.length;
525
while ( iNode-- ) {
526
if ( nodeList[iNode].nodeType !== 1 ) { continue; }
527
removedNodes = true;
528
break;
529
}
530
}
531
removedNodeLists.length = 0;
532
//console.timeEnd('dom watcher/safe observer handler');
533
if ( addedNodes.length === 0 && removedNodes === false ) { return; }
534
for ( var listener of getListenerIterator() ) {
535
listener.onDOMChanged(addedNodes, removedNodes);
536
}
537
addedNodes.length = 0;
538
removedNodes = false;
539
};
540
541
// https://github.com/chrisaljoudi/uBlock/issues/205
542
// Do not handle added node directly from within mutation observer.
543
var observerHandler = function(mutations) {
544
//console.time('dom watcher/observer handler');
545
var nodeList, mutation,
546
i = mutations.length;
547
while ( i-- ) {
548
mutation = mutations[i];
549
nodeList = mutation.addedNodes;
550
if ( nodeList.length !== 0 ) {
551
addedNodeLists.push(nodeList);
552
}
553
if ( removedNodes ) { continue; }
554
nodeList = mutation.removedNodes;
555
if ( nodeList.length !== 0 ) {
556
removedNodeLists.push(nodeList);
557
}
558
}
559
if ( addedNodeLists.length !== 0 || removedNodes ) {
560
safeObserverHandlerTimer.start(
561
addedNodeLists.length < 100 ? 1 : undefined
562
);
563
}
564
//console.timeEnd('dom watcher/observer handler');
565
};
566
567
var startMutationObserver = function() {
568
if ( domLayoutObserver !== undefined || !domIsReady ) { return; }
569
domLayoutObserver = new MutationObserver(observerHandler);
570
domLayoutObserver.observe(document.documentElement, {
571
//attributeFilter: [ 'class', 'id' ],
572
//attributes: true,
573
childList: true,
574
subtree: true
575
});
576
safeObserverHandlerTimer = new vAPI.SafeAnimationFrame(safeObserverHandler);
577
vAPI.shutdown.add(cleanup);
578
};
579
580
var stopMutationObserver = function() {
581
if ( domLayoutObserver === undefined ) { return; }
582
cleanup();
583
vAPI.shutdown.remove(cleanup);
584
};
585
586
var getListenerIterator = function() {
587
if ( listenerIteratorDirty ) {
588
listenerIterator = listeners.slice();
589
listenerIteratorDirty = false;
590
}
591
return listenerIterator;
592
};
593
594
var addListener = function(listener) {
595
if ( listeners.indexOf(listener) !== -1 ) { return; }
596
listeners.push(listener);
597
listenerIteratorDirty = true;
598
if ( domIsReady !== true ) { return; }
599
listener.onDOMCreated();
600
startMutationObserver();
601
};
602
603
var removeListener = function(listener) {
604
var pos = listeners.indexOf(listener);
605
if ( pos === -1 ) { return; }
606
listeners.splice(pos, 1);
607
listenerIteratorDirty = true;
608
if ( listeners.length === 0 ) {
609
stopMutationObserver();
610
}
611
};
612
613
var cleanup = function() {
614
if ( domLayoutObserver !== undefined ) {
615
domLayoutObserver.disconnect();
616
domLayoutObserver = null;
617
}
618
if ( safeObserverHandlerTimer !== undefined ) {
619
safeObserverHandlerTimer.clear();
620
safeObserverHandlerTimer = undefined;
621
}
622
};
623
624
var start = function() {
625
domIsReady = true;
626
for ( var listener of getListenerIterator() ) {
627
listener.onDOMCreated();
628
}
629
startMutationObserver();
630
};
631
632
return {
633
start: start,
634
addListener: addListener,
635
removeListener: removeListener
636
};
637
})();
638
639
/******************************************************************************/
640
/******************************************************************************/
641
/******************************************************************************/
642
643
vAPI.matchesProp = (function() {
644
var docElem = document.documentElement;
645
if ( typeof docElem.matches !== 'function' ) {
646
if ( typeof docElem.mozMatchesSelector === 'function' ) {
647
return 'mozMatchesSelector';
648
} else if ( typeof docElem.webkitMatchesSelector === 'function' ) {
649
return 'webkitMatchesSelector';
650
} else if ( typeof docElem.msMatchesSelector === 'function' ) {
651
return 'msMatchesSelector';
652
}
653
}
654
return 'matches';
655
})();
656
657
/******************************************************************************/
658
/******************************************************************************/
659
/******************************************************************************/
660
661
vAPI.injectScriptlet = function(doc, text) {
662
if ( !doc ) { return; }
663
let script;
664
try {
665
script = doc.createElement('script');
666
script.appendChild(doc.createTextNode(text));
667
(doc.head || doc.documentElement).appendChild(script);
668
} catch (ex) {
669
}
670
if ( script ) {
671
if ( script.parentNode ) {
672
script.parentNode.removeChild(script);
673
}
674
script.textContent = '';
675
}
676
};
677
678
/******************************************************************************/
679
/******************************************************************************/
680
/*******************************************************************************
681
682
The DOM filterer is the heart of uBO's cosmetic filtering.
683
684
DOMBaseFilterer: platform-specific
685
|
686
|
687
+---- DOMFilterer: adds procedural cosmetic filtering
688
689
*/
690
691
vAPI.DOMFilterer = (function() {
692
693
// 'P' stands for 'Procedural'
694
695
var PSelectorHasTextTask = function(task) {
696
var arg0 = task[1], arg1;
697
if ( Array.isArray(task[1]) ) {
698
arg1 = arg0[1]; arg0 = arg0[0];
699
}
700
this.needle = new RegExp(arg0, arg1);
701
};
702
PSelectorHasTextTask.prototype.exec = function(input) {
703
var output = [];
704
for ( var node of input ) {
705
if ( this.needle.test(node.textContent) ) {
706
output.push(node);
707
}
708
}
709
return output;
710
};
711
712
var PSelectorIfTask = function(task) {
713
this.pselector = new PSelector(task[1]);
714
};
715
PSelectorIfTask.prototype.target = true;
716
PSelectorIfTask.prototype.exec = function(input) {
717
var output = [];
718
for ( var node of input ) {
719
if ( this.pselector.test(node) === this.target ) {
720
output.push(node);
721
}
722
}
723
return output;
724
};
725
726
var PSelectorIfNotTask = function(task) {
727
PSelectorIfTask.call(this, task);
728
this.target = false;
729
};
730
PSelectorIfNotTask.prototype = Object.create(PSelectorIfTask.prototype);
731
PSelectorIfNotTask.prototype.constructor = PSelectorIfNotTask;
732
733
var PSelectorMatchesCSSTask = function(task) {
734
this.name = task[1].name;
735
var arg0 = task[1].value, arg1;
736
if ( Array.isArray(arg0) ) {
737
arg1 = arg0[1]; arg0 = arg0[0];
738
}
739
this.value = new RegExp(arg0, arg1);
740
};
741
PSelectorMatchesCSSTask.prototype.pseudo = null;
742
PSelectorMatchesCSSTask.prototype.exec = function(input) {
743
var output = [], style;
744
for ( var node of input ) {
745
style = window.getComputedStyle(node, this.pseudo);
746
if ( style === null ) { return null; } /* FF */
747
if ( this.value.test(style[this.name]) ) {
748
output.push(node);
749
}
750
}
751
return output;
752
};
753
754
var PSelectorMatchesCSSAfterTask = function(task) {
755
PSelectorMatchesCSSTask.call(this, task);
756
this.pseudo = ':after';
757
};
758
PSelectorMatchesCSSAfterTask.prototype = Object.create(PSelectorMatchesCSSTask.prototype);
759
PSelectorMatchesCSSAfterTask.prototype.constructor = PSelectorMatchesCSSAfterTask;
760
761
var PSelectorMatchesCSSBeforeTask = function(task) {
762
PSelectorMatchesCSSTask.call(this, task);
763
this.pseudo = ':before';
764
};
765
PSelectorMatchesCSSBeforeTask.prototype = Object.create(PSelectorMatchesCSSTask.prototype);
766
PSelectorMatchesCSSBeforeTask.prototype.constructor = PSelectorMatchesCSSBeforeTask;
767
768
var PSelectorXpathTask = function(task) {
769
this.xpe = document.createExpression(task[1], null);
770
this.xpr = null;
771
};
772
PSelectorXpathTask.prototype.exec = function(input) {
773
var output = [], j;
774
for ( var node of input ) {
775
this.xpr = this.xpe.evaluate(
776
node,
777
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
778
this.xpr
779
);
780
j = this.xpr.snapshotLength;
781
while ( j-- ) {
782
node = this.xpr.snapshotItem(j);
783
if ( node.nodeType === 1 ) {
784
output.push(node);
785
}
786
}
787
}
788
return output;
789
};
790
791
var PSelector = function(o) {
792
if ( PSelector.prototype.operatorToTaskMap === undefined ) {
793
PSelector.prototype.operatorToTaskMap = new Map([
794
[ ':has', PSelectorIfTask ],
795
[ ':has-text', PSelectorHasTextTask ],
796
[ ':if', PSelectorIfTask ],
797
[ ':if-not', PSelectorIfNotTask ],
798
[ ':matches-css', PSelectorMatchesCSSTask ],
799
[ ':matches-css-after', PSelectorMatchesCSSAfterTask ],
800
[ ':matches-css-before', PSelectorMatchesCSSBeforeTask ],
801
[ ':xpath', PSelectorXpathTask ]
802
]);
803
}
804
this.budget = 200; // I arbitrary picked a 1/5 second
805
this.raw = o.raw;
806
this.cost = 0;
807
this.lastAllowanceTime = 0;
808
this.selector = o.selector;
809
this.tasks = [];
810
var tasks = o.tasks;
811
if ( !tasks ) { return; }
812
for ( var task of tasks ) {
813
this.tasks.push(new (this.operatorToTaskMap.get(task[0]))(task));
814
}
815
};
816
PSelector.prototype.operatorToTaskMap = undefined;
817
PSelector.prototype.prime = function(input) {
818
var root = input || document;
819
if ( this.selector !== '' ) {
820
return root.querySelectorAll(this.selector);
821
}
822
return [ root ];
823
};
824
PSelector.prototype.exec = function(input) {
825
var nodes = this.prime(input);
826
for ( var task of this.tasks ) {
827
if ( nodes.length === 0 ) { break; }
828
nodes = task.exec(nodes);
829
}
830
return nodes;
831
};
832
PSelector.prototype.test = function(input) {
833
var nodes = this.prime(input), AA = [ null ], aa;
834
for ( var node of nodes ) {
835
AA[0] = node; aa = AA;
836
for ( var task of this.tasks ) {
837
aa = task.exec(aa);
838
if ( aa.length === 0 ) { break; }
839
}
840
if ( aa.length !== 0 ) { return true; }
841
}
842
return false;
843
};
844
845
var DOMProceduralFilterer = function(domFilterer) {
846
this.domFilterer = domFilterer;
847
this.domIsReady = false;
848
this.domIsWatched = false;
849
this.addedSelectors = new Map();
850
this.addedNodes = false;
851
this.removedNodes = false;
852
this.selectors = new Map();
853
};
854
855
DOMProceduralFilterer.prototype = {
856
857
addProceduralSelectors: function(aa) {
858
var raw, o, pselector,
859
mustCommit = this.domIsWatched;
860
for ( var i = 0, n = aa.length; i < n; i++ ) {
861
raw = aa[i];
862
o = JSON.parse(raw);
863
if ( o.style ) {
864
this.domFilterer.addCSSRule(o.style[0], o.style[1]);
865
mustCommit = true;
866
continue;
867
}
868
if ( o.pseudoclass ) {
869
this.domFilterer.addCSSRule(
870
o.raw,
871
'display:none!important;'
872
);
873
mustCommit = true;
874
continue;
875
}
876
if ( o.tasks ) {
877
if ( this.selectors.has(raw) === false ) {
878
pselector = new PSelector(o);
879
this.selectors.set(raw, pselector);
880
this.addedSelectors.set(raw, pselector);
881
mustCommit = true;
882
}
883
continue;
884
}
885
}
886
if ( mustCommit === false ) { return; }
887
this.domFilterer.commit();
888
if ( this.domFilterer.hasListeners() ) {
889
this.domFilterer.triggerListeners({
890
procedural: Array.from(this.addedSelectors.values())
891
});
892
}
893
},
894
895
commitNow: function() {
896
if ( this.selectors.size === 0 || this.domIsReady === false ) {
897
return;
898
}
899
900
if ( this.addedNodes || this.removedNodes ) {
901
this.addedSelectors.clear();
902
}
903
904
var entry, nodes, i;
905
906
if ( this.addedSelectors.size !== 0 ) {
907
//console.time('procedural selectors/filterset changed');
908
for ( entry of this.addedSelectors ) {
909
nodes = entry[1].exec();
910
i = nodes.length;
911
while ( i-- ) {
912
this.domFilterer.hideNode(nodes[i]);
913
}
914
}
915
this.addedSelectors.clear();
916
//console.timeEnd('procedural selectors/filterset changed');
917
return;
918
}
919
920
//console.time('procedural selectors/dom layout changed');
921
922
this.addedNodes = this.removedNodes = false;
923
924
var t0 = Date.now(),
925
t1, pselector, allowance;
926
927
for ( entry of this.selectors ) {
928
pselector = entry[1];
929
allowance = Math.floor((t0 - pselector.lastAllowanceTime) / 2000);
930
if ( allowance >= 1 ) {
931
pselector.budget += allowance * 50;
932
if ( pselector.budget > 200 ) { pselector.budget = 200; }
933
pselector.lastAllowanceTime = t0;
934
}
935
if ( pselector.budget <= 0 ) { continue; }
936
nodes = pselector.exec();
937
t1 = Date.now();
938
pselector.budget += t0 - t1;
939
if ( pselector.budget < -500 ) {
940
console.info('uBO: disabling %s', pselector.raw);
941
pselector.budget = -0x7FFFFFFF;
942
}
943
t0 = t1;
944
i = nodes.length;
945
while ( i-- ) {
946
this.domFilterer.hideNode(nodes[i]);
947
}
948
}
949
950
//console.timeEnd('procedural selectors/dom layout changed');
951
},
952
953
createProceduralFilter: function(o) {
954
return new PSelector(o);
955
},
956
957
onDOMCreated: function() {
958
this.domIsReady = true;
959
this.domFilterer.commitNow();
960
},
961
962
onDOMChanged: function(addedNodes, removedNodes) {
963
if ( this.selectors.size === 0 ) { return; }
964
this.addedNodes = this.addedNodes || addedNodes.length !== 0;
965
this.removedNodes = this.removedNodes || removedNodes;
966
this.domFilterer.commit();
967
}
968
};
969
970
var DOMFiltererBase = vAPI.DOMFilterer;
971
972
var domFilterer = function() {
973
DOMFiltererBase.call(this);
974
this.exceptions = [];
975
this.proceduralFilterer = new DOMProceduralFilterer(this);
976
this.hideNodeAttr = undefined;
977
this.hideNodeStyleSheetInjected = false;
978
979
// May or may not exist: cache locally since this may be called often.
980
this.baseOnDOMChanged = DOMFiltererBase.prototype.onDOMChanged;
981
982
if ( vAPI.domWatcher instanceof Object ) {
983
vAPI.domWatcher.addListener(this);
984
}
985
};
986
domFilterer.prototype = Object.create(DOMFiltererBase.prototype);
987
domFilterer.prototype.constructor = domFilterer;
988
989
domFilterer.prototype.commitNow = function() {
990
DOMFiltererBase.prototype.commitNow.call(this);
991
this.proceduralFilterer.commitNow();
992
};
993
994
domFilterer.prototype.addProceduralSelectors = function(aa) {
995
this.proceduralFilterer.addProceduralSelectors(aa);
996
};
997
998
domFilterer.prototype.createProceduralFilter = function(o) {
999
return this.proceduralFilterer.createProceduralFilter(o);
1000
};
1001
1002
domFilterer.prototype.getAllSelectors = function() {
1003
var out = DOMFiltererBase.prototype.getAllSelectors.call(this);
1004
out.procedural = Array.from(this.proceduralFilterer.selectors.values());
1005
return out;
1006
};
1007
1008
domFilterer.prototype.getAllExceptionSelectors = function() {
1009
return this.exceptions.join(',\n');
1010
};
1011
1012
domFilterer.prototype.onDOMCreated = function() {
1013
if ( DOMFiltererBase.prototype.onDOMCreated !== undefined ) {
1014
DOMFiltererBase.prototype.onDOMCreated.call(this);
1015
}
1016
this.proceduralFilterer.onDOMCreated();
1017
};
1018
1019
domFilterer.prototype.onDOMChanged = function() {
1020
if ( this.baseOnDOMChanged !== undefined ) {
1021
this.baseOnDOMChanged.apply(this, arguments);
1022
}
1023
this.proceduralFilterer.onDOMChanged.apply(
1024
this.proceduralFilterer,
1025
arguments
1026
);
1027
};
1028
1029
return domFilterer;
1030
})();
1031
1032
vAPI.domFilterer = new vAPI.DOMFilterer();
1033
1034
/******************************************************************************/
1035
/******************************************************************************/
1036
/******************************************************************************/
1037
1038
vAPI.domCollapser = (function() {
1039
var resquestIdGenerator = 1,
1040
processTimer,
1041
toProcess = [],
1042
toFilter = [],
1043
toCollapse = new Map(),
1044
cachedBlockedSet,
1045
cachedBlockedSetHash,
1046
cachedBlockedSetTimer;
1047
var src1stProps = {
1048
'embed': 'src',
1049
'iframe': 'src',
1050
'img': 'src',
1051
'object': 'data'
1052
};
1053
var src2ndProps = {
1054
'img': 'srcset'
1055
};
1056
var tagToTypeMap = {
1057
embed: 'object',
1058
iframe: 'sub_frame',
1059
img: 'image',
1060
object: 'object'
1061
};
1062
var netSelectorCacheCount = 0,
1063
messaging = vAPI.messaging;
1064
1065
var cachedBlockedSetClear = function() {
1066
cachedBlockedSet =
1067
cachedBlockedSetHash =
1068
cachedBlockedSetTimer = undefined;
1069
};
1070
1071
// https://github.com/chrisaljoudi/uBlock/issues/174
1072
// Do not remove fragment from src URL
1073
var onProcessed = function(response) {
1074
if ( !response ) { // This happens if uBO is disabled or restarted.
1075
toCollapse.clear();
1076
return;
1077
}
1078
1079
var targets = toCollapse.get(response.id);
1080
if ( targets === undefined ) { return; }
1081
toCollapse.delete(response.id);
1082
if ( cachedBlockedSetHash !== response.hash ) {
1083
cachedBlockedSet = new Set(response.blockedResources);
1084
cachedBlockedSetHash = response.hash;
1085
if ( cachedBlockedSetTimer !== undefined ) {
1086
clearTimeout(cachedBlockedSetTimer);
1087
}
1088
cachedBlockedSetTimer = vAPI.setTimeout(cachedBlockedSetClear, 30000);
1089
}
1090
if ( cachedBlockedSet === undefined || cachedBlockedSet.size === 0 ) {
1091
return;
1092
}
1093
var selectors = [],
1094
iframeLoadEventPatch = vAPI.iframeLoadEventPatch,
1095
netSelectorCacheCountMax = response.netSelectorCacheCountMax,
1096
tag, prop, src, value;
1097
1098
for ( var target of targets ) {
1099
tag = target.localName;
1100
prop = src1stProps[tag];
1101
if ( prop === undefined ) { continue; }
1102
src = target[prop];
1103
if ( typeof src !== 'string' || src.length === 0 ) {
1104
prop = src2ndProps[tag];
1105
if ( prop === undefined ) { continue; }
1106
src = target[prop];
1107
if ( typeof src !== 'string' || src.length === 0 ) { continue; }
1108
}
1109
if ( cachedBlockedSet.has(tagToTypeMap[tag] + ' ' + src) === false ) {
1110
continue;
1111
}
1112
// https://github.com/chrisaljoudi/uBlock/issues/399
1113
// Never remove elements from the DOM, just hide them
1114
target.style.setProperty('display', 'none', 'important');
1115
target.hidden = true;
1116
// https://github.com/chrisaljoudi/uBlock/issues/1048
1117
// Use attribute to construct CSS rule
1118
if (
1119
netSelectorCacheCount <= netSelectorCacheCountMax &&
1120
(value = target.getAttribute(prop))
1121
) {
1122
selectors.push(tag + '[' + prop + '="' + value + '"]');
1123
netSelectorCacheCount += 1;
1124
}
1125
if ( iframeLoadEventPatch !== undefined ) {
1126
iframeLoadEventPatch(target);
1127
}
1128
}
1129
1130
if ( selectors.length !== 0 ) {
1131
messaging.send(
1132
'contentscript',
1133
{
1134
what: 'cosmeticFiltersInjected',
1135
type: 'net',
1136
hostname: window.location.hostname,
1137
selectors: selectors
1138
}
1139
);
1140
}
1141
};
1142
1143
var send = function() {
1144
processTimer = undefined;
1145
toCollapse.set(resquestIdGenerator, toProcess);
1146
var msg = {
1147
what: 'getCollapsibleBlockedRequests',
1148
id: resquestIdGenerator,
1149
frameURL: window.location.href,
1150
resources: toFilter,
1151
hash: cachedBlockedSetHash
1152
};
1153
messaging.send('contentscript', msg, onProcessed);
1154
toProcess = [];
1155
toFilter = [];
1156
resquestIdGenerator += 1;
1157
};
1158
1159
var process = function(delay) {
1160
if ( toProcess.length === 0 ) { return; }
1161
if ( delay === 0 ) {
1162
if ( processTimer !== undefined ) {
1163
clearTimeout(processTimer);
1164
}
1165
send();
1166
} else if ( processTimer === undefined ) {
1167
processTimer = vAPI.setTimeout(send, delay || 20);
1168
}
1169
};
1170
1171
var add = function(target) {
1172
toProcess[toProcess.length] = target;
1173
};
1174
1175
var addMany = function(targets) {
1176
var i = targets.length;
1177
while ( i-- ) {
1178
add(targets[i]);
1179
}
1180
};
1181
1182
var iframeSourceModified = function(mutations) {
1183
var i = mutations.length;
1184
while ( i-- ) {
1185
addIFrame(mutations[i].target, true);
1186
}
1187
process();
1188
};
1189
var iframeSourceObserver = new MutationObserver(iframeSourceModified);
1190
var iframeSourceObserverOptions = {
1191
attributes: true,
1192
attributeFilter: [ 'src' ]
1193
};
1194
1195
// The injected scriptlets are those which were injected in the current
1196
// document, from within `bootstrapPhase1`, and which scriptlets are
1197
// selectively looked-up from:
1198
// https://github.com/uBlockOrigin/uAssets/blob/master/filters/resources.txt
1199
var primeLocalIFrame = function(iframe) {
1200
if ( vAPI.injectedScripts ) {
1201
vAPI.injectScriptlet(iframe.contentDocument, vAPI.injectedScripts);
1202
}
1203
};
1204
1205
// https://github.com/gorhill/uBlock/issues/162
1206
// Be prepared to deal with possible change of src attribute.
1207
var addIFrame = function(iframe, dontObserve) {
1208
if ( dontObserve !== true ) {
1209
iframeSourceObserver.observe(iframe, iframeSourceObserverOptions);
1210
}
1211
var src = iframe.src;
1212
if ( src === '' || typeof src !== 'string' ) {
1213
primeLocalIFrame(iframe);
1214
return;
1215
}
1216
if ( src.startsWith('http') === false ) { return; }
1217
toFilter[toFilter.length] = {
1218
type: 'sub_frame',
1219
url: iframe.src
1220
};
1221
add(iframe);
1222
};
1223
1224
var addIFrames = function(iframes) {
1225
var i = iframes.length;
1226
while ( i-- ) {
1227
addIFrame(iframes[i]);
1228
}
1229
};
1230
1231
var onResourceFailed = function(ev) {
1232
if ( tagToTypeMap[ev.target.localName] !== undefined ) {
1233
add(ev.target);
1234
process();
1235
}
1236
};
1237
1238
var domWatcherInterface = {
1239
onDOMCreated: function() {
1240
if ( vAPI instanceof Object === false ) { return; }
1241
if ( vAPI.domCollapser instanceof Object === false ) {
1242
if ( vAPI.domWatcher instanceof Object ) {
1243
vAPI.domWatcher.removeListener(domWatcherInterface);
1244
}
1245
return;
1246
}
1247
// Listener to collapse blocked resources.
1248
// - Future requests not blocked yet
1249
// - Elements dynamically added to the page
1250
// - Elements which resource URL changes
1251
// https://github.com/chrisaljoudi/uBlock/issues/7
1252
// Preferring getElementsByTagName over querySelectorAll:
1253
// http://jsperf.com/queryselectorall-vs-getelementsbytagname/145
1254
var elems = document.images || document.getElementsByTagName('img'),
1255
i = elems.length, elem;
1256
while ( i-- ) {
1257
elem = elems[i];
1258
if ( elem.complete ) {
1259
add(elem);
1260
}
1261
}
1262
addMany(document.embeds || document.getElementsByTagName('embed'));
1263
addMany(document.getElementsByTagName('object'));
1264
addIFrames(document.getElementsByTagName('iframe'));
1265
process(0);
1266
1267
document.addEventListener('error', onResourceFailed, true);
1268
1269
vAPI.shutdown.add(function() {
1270
document.removeEventListener('error', onResourceFailed, true);
1271
if ( processTimer !== undefined ) {
1272
clearTimeout(processTimer);
1273
}
1274
});
1275
},
1276
onDOMChanged: function(addedNodes) {
1277
var ni = addedNodes.length;
1278
if ( ni === 0 ) { return; }
1279
for ( var i = 0, node; i < ni; i++ ) {
1280
node = addedNodes[i];
1281
if ( node.localName === 'iframe' ) {
1282
addIFrame(node);
1283
}
1284
if ( node.childElementCount === 0 ) { continue; }
1285
var iframes = node.getElementsByTagName('iframe');
1286
if ( iframes.length !== 0 ) {
1287
addIFrames(iframes);
1288
}
1289
}
1290
process();
1291
}
1292
};
1293
1294
if ( vAPI.domWatcher instanceof Object ) {
1295
vAPI.domWatcher.addListener(domWatcherInterface);
1296
}
1297
1298
return {
1299
add: add,
1300
addMany: addMany,
1301
addIFrame: addIFrame,
1302
addIFrames: addIFrames,
1303
process: process
1304
};
1305
})();
1306
1307
/******************************************************************************/
1308
/******************************************************************************/
1309
/******************************************************************************/
1310
1311
vAPI.domSurveyor = (function() {
1312
var messaging = vAPI.messaging,
1313
domFilterer,
1314
hostname = '',
1315
queriedIds = new Set(),
1316
queriedClasses = new Set(),
1317
pendingIdNodes = { nodes: [], added: [] },
1318
pendingClassNodes = { nodes: [], added: [] },
1319
surveyCost = 0;
1320
1321
// This is to shutdown the surveyor if result of surveying keeps being
1322
// fruitless. This is useful on long-lived web page. I arbitrarily
1323
// picked 5 minutes before the surveyor is allowed to shutdown. I also
1324
// arbitrarily picked 256 misses before the surveyor is allowed to
1325
// shutdown.
1326
var canShutdownAfter = Date.now() + 300000,
1327
surveyingMissCount = 0;
1328
1329
// Handle main process' response.
1330
1331
var surveyPhase3 = function(response) {
1332
var result = response && response.result,
1333
mustCommit = false;
1334
1335
if ( result ) {
1336
var selectors = result.simple;
1337
if ( Array.isArray(selectors) && selectors.length !== 0 ) {
1338
domFilterer.addCSSRule(
1339
selectors,
1340
'display:none!important;',
1341
{ type: 'simple' }
1342
);
1343
mustCommit = true;
1344
}
1345
selectors = result.complex;
1346
if ( Array.isArray(selectors) && selectors.length !== 0 ) {
1347
domFilterer.addCSSRule(
1348
selectors,
1349
'display:none!important;',
1350
{ type: 'complex' }
1351
);
1352
mustCommit = true;
1353
}
1354
selectors = result.injected;
1355
if ( typeof selectors === 'string' && selectors.length !== 0 ) {
1356
domFilterer.addCSSRule(
1357
selectors,
1358
'display:none!important;',
1359
{ injected: true }
1360
);
1361
mustCommit = true;
1362
}
1363
}
1364
1365
if ( hasChunk(pendingIdNodes) || hasChunk(pendingClassNodes) ) {
1366
surveyTimer.start(1);
1367
}
1368
1369
if ( mustCommit ) {
1370
surveyingMissCount = 0;
1371
canShutdownAfter = Date.now() + 300000;
1372
return;
1373
}
1374
1375
surveyingMissCount += 1;
1376
if ( surveyingMissCount < 256 || Date.now() < canShutdownAfter ) {
1377
return;
1378
}
1379
1380
//console.info('dom surveyor shutting down: too many misses');
1381
1382
surveyTimer.clear();
1383
vAPI.domWatcher.removeListener(domWatcherInterface);
1384
vAPI.domSurveyor = null;
1385
};
1386
1387
var surveyTimer = new vAPI.SafeAnimationFrame(function() {
1388
surveyPhase1();
1389
});
1390
1391
// The purpose of "chunkification" is to ensure the surveyor won't unduly
1392
// block the main event loop.
1393
1394
var hasChunk = function(pending) {
1395
return pending.nodes.length !== 0 ||
1396
pending.added.length !== 0;
1397
};
1398
1399
var addChunk = function(pending, added) {
1400
if ( added.length === 0 ) { return; }
1401
if (
1402
Array.isArray(added) === false ||
1403
pending.added.length === 0 ||
1404
Array.isArray(pending.added[0]) === false ||
1405
pending.added[0].length >= 1000
1406
) {
1407
pending.added.push(added);
1408
} else {
1409
pending.added = pending.added.concat(added);
1410
}
1411
};
1412
1413
var nextChunk = function(pending) {
1414
var added = pending.added.length !== 0 ? pending.added.shift() : [],
1415
nodes;
1416
if ( pending.nodes.length === 0 ) {
1417
if ( added.length <= 1000 ) { return added; }
1418
nodes = Array.isArray(added)
1419
? added
1420
: Array.prototype.slice.call(added);
1421
pending.nodes = nodes.splice(1000);
1422
return nodes;
1423
}
1424
if ( Array.isArray(added) === false ) {
1425
added = Array.prototype.slice.call(added);
1426
}
1427
if ( pending.nodes.length < 1000 ) {
1428
nodes = pending.nodes.concat(added.splice(0, 1000 - pending.nodes.length));
1429
pending.nodes = added;
1430
} else {
1431
nodes = pending.nodes.splice(0, 1000);
1432
pending.nodes = pending.nodes.concat(added);
1433
}
1434
return nodes;
1435
};
1436
1437
// Extract all classes/ids: these will be passed to the cosmetic
1438
// filtering engine, and in return we will obtain only the relevant
1439
// CSS selectors.
1440
1441
// https://github.com/gorhill/uBlock/issues/672
1442
// http://www.w3.org/TR/2014/REC-html5-20141028/infrastructure.html#space-separated-tokens
1443
// http://jsperf.com/enumerate-classes/6
1444
1445
var surveyPhase1 = function() {
1446
//console.time('dom surveyor/surveying');
1447
surveyTimer.clear();
1448
var t0 = window.performance.now();
1449
var rews = reWhitespace,
1450
qq, iout, nodes, i, node, v, vv, j;
1451
var ids = [];
1452
iout = 0;
1453
qq = queriedIds;
1454
nodes = nextChunk(pendingIdNodes);
1455
i = nodes.length;
1456
while ( i-- ) {
1457
node = nodes[i];
1458
v = node.id;
1459
if ( typeof v !== 'string' ) { continue; }
1460
v = v.trim();
1461
if ( qq.has(v) === false && v.length !== 0 ) {
1462
ids[iout++] = v; qq.add(v);
1463
}
1464
}
1465
var classes = [];
1466
iout = 0;
1467
qq = queriedClasses;
1468
nodes = nextChunk(pendingClassNodes);
1469
i = nodes.length;
1470
while ( i-- ) {
1471
node = nodes[i];
1472
vv = node.className;
1473
if ( typeof vv !== 'string' ) { continue; }
1474
if ( rews.test(vv) === false ) {
1475
if ( qq.has(vv) === false && vv.length !== 0 ) {
1476
classes[iout++] = vv; qq.add(vv);
1477
}
1478
} else {
1479
vv = node.classList;
1480
j = vv.length;
1481
while ( j-- ) {
1482
v = vv[j];
1483
if ( qq.has(v) === false ) {
1484
classes[iout++] = v; qq.add(v);
1485
}
1486
}
1487
}
1488
}
1489
surveyCost += window.performance.now() - t0;
1490
// Phase 2: Ask main process to lookup relevant cosmetic filters.
1491
if ( ids.length !== 0 || classes.length !== 0 ) {
1492
messaging.send(
1493
'contentscript',
1494
{
1495
what: 'retrieveGenericCosmeticSelectors',
1496
hostname: hostname,
1497
ids: ids.join('\n'),
1498
classes: classes.join('\n'),
1499
exceptions: domFilterer.exceptions,
1500
cost: surveyCost
1501
},
1502
surveyPhase3
1503
);
1504
} else {
1505
surveyPhase3(null);
1506
}
1507
//console.timeEnd('dom surveyor/surveying');
1508
};
1509
var reWhitespace = /\s/;
1510
1511
var domWatcherInterface = {
1512
onDOMCreated: function() {
1513
if (
1514
vAPI instanceof Object === false ||
1515
vAPI.domSurveyor instanceof Object === false ||
1516
vAPI.domFilterer instanceof Object === false
1517
) {
1518
if ( vAPI instanceof Object ) {
1519
if ( vAPI.domWatcher instanceof Object ) {
1520
vAPI.domWatcher.removeListener(domWatcherInterface);
1521
}
1522
vAPI.domSurveyor = null;
1523
}
1524
return;
1525
}
1526
//console.time('dom surveyor/dom layout created');
1527
domFilterer = vAPI.domFilterer;
1528
addChunk(pendingIdNodes, document.querySelectorAll('[id]'));
1529
addChunk(pendingClassNodes, document.querySelectorAll('[class]'));
1530
surveyTimer.start();
1531
//console.timeEnd('dom surveyor/dom layout created');
1532
},
1533
onDOMChanged: function(addedNodes) {
1534
if ( addedNodes.length === 0 ) { return; }
1535
//console.time('dom surveyor/dom layout changed');
1536
var idNodes = [], iid = 0,
1537
classNodes = [], iclass = 0;
1538
var i = addedNodes.length,
1539
node, nodeList, j;
1540
while ( i-- ) {
1541
node = addedNodes[i];
1542
idNodes[iid++] = node;
1543
classNodes[iclass++] = node;
1544
if ( node.childElementCount === 0 ) { continue; }
1545
nodeList = node.querySelectorAll('[id]');
1546
j = nodeList.length;
1547
while ( j-- ) {
1548
idNodes[iid++] = nodeList[j];
1549
}
1550
nodeList = node.querySelectorAll('[class]');
1551
j = nodeList.length;
1552
while ( j-- ) {
1553
classNodes[iclass++] = nodeList[j];
1554
}
1555
}
1556
if ( idNodes.length !== 0 || classNodes.lengh !== 0 ) {
1557
addChunk(pendingIdNodes, idNodes);
1558
addChunk(pendingClassNodes, classNodes);
1559
surveyTimer.start(1);
1560
}
1561
//console.timeEnd('dom surveyor/dom layout changed');
1562
}
1563
};
1564
1565
var start = function(details) {
1566
if ( vAPI.domWatcher instanceof Object === false ) { return; }
1567
hostname = details.hostname;
1568
vAPI.domWatcher.addListener(domWatcherInterface);
1569
};
1570
1571
return {
1572
start: start
1573
};
1574
})();
1575
1576
/******************************************************************************/
1577
/******************************************************************************/
1578
/******************************************************************************/
1579
1580
// Bootstrapping allows all components of the content script to be launched
1581
// if/when needed.
1582
1583
(function bootstrap() {
1584
1585
var bootstrapPhase2 = function(ev) {
1586
// This can happen on Firefox. For instance:
1587
// https://github.com/gorhill/uBlock/issues/1893
1588
if ( window.location === null ) { return; }
1589
1590
if ( ev ) {
1591
document.removeEventListener('DOMContentLoaded', bootstrapPhase2);
1592
}
1593
1594
if ( vAPI instanceof Object === false ) {
1595
return;
1596
}
1597
1598
if ( vAPI.domWatcher instanceof Object ) {
1599
vAPI.domWatcher.start();
1600
}
1601
1602
// Element picker works only in top window for now.
1603
if (
1604
window !== window.top ||
1605
vAPI.domFilterer instanceof Object === false
1606
) {
1607
return;
1608
}
1609
1610
// To send mouse coordinates to main process, as the chrome API fails
1611
// to provide the mouse position to context menu listeners.
1612
// https://github.com/chrisaljoudi/uBlock/issues/1143
1613
// Also, find a link under the mouse, to try to avoid confusing new tabs
1614
// as nuisance popups.
1615
// Ref.: https://developer.mozilla.org/en-US/docs/Web/Events/contextmenu
1616
1617
var onMouseClick = function(ev) {
1618
var elem = ev.target;
1619
while ( elem !== null && elem.localName !== 'a' ) {
1620
elem = elem.parentElement;
1621
}
1622
vAPI.messaging.send(
1623
'contentscript',
1624
{
1625
what: 'mouseClick',
1626
x: ev.clientX,
1627
y: ev.clientY,
1628
url: elem !== null && ev.isTrusted !== false ? elem.href : ''
1629
}
1630
);
1631
};
1632
1633
document.addEventListener('mousedown', onMouseClick, true);
1634
1635
// https://github.com/gorhill/uMatrix/issues/144
1636
vAPI.shutdown.add(function() {
1637
document.removeEventListener('mousedown', onMouseClick, true);
1638
});
1639
};
1640
1641
var bootstrapPhase1 = function(response) {
1642
// cosmetic filtering engine aka 'cfe'
1643
var cfeDetails = response && response.specificCosmeticFilters;
1644
if ( !cfeDetails || !cfeDetails.ready ) {
1645
vAPI.domWatcher = vAPI.domCollapser = vAPI.domFilterer =
1646
vAPI.domSurveyor = vAPI.domIsLoaded = null;
1647
return;
1648
}
1649
1650
if ( response.noCosmeticFiltering ) {
1651
vAPI.domFilterer = null;
1652
vAPI.domSurveyor = null;
1653
} else {
1654
var domFilterer = vAPI.domFilterer;
1655
if ( response.noGenericCosmeticFiltering || cfeDetails.noDOMSurveying ) {
1656
vAPI.domSurveyor = null;
1657
}
1658
domFilterer.exceptions = cfeDetails.exceptionFilters;
1659
domFilterer.hideNodeAttr = cfeDetails.hideNodeAttr;
1660
domFilterer.hideNodeStyleSheetInjected =
1661
cfeDetails.hideNodeStyleSheetInjected === true;
1662
domFilterer.addCSSRule(
1663
cfeDetails.declarativeFilters,
1664
'display:none!important;'
1665
);
1666
domFilterer.addCSSRule(
1667
cfeDetails.highGenericHideSimple,
1668
'display:none!important;',
1669
{ type: 'simple', lazy: true }
1670
);
1671
domFilterer.addCSSRule(
1672
cfeDetails.highGenericHideComplex,
1673
'display:none!important;',
1674
{ type: 'complex', lazy: true }
1675
);
1676
domFilterer.addCSSRule(
1677
cfeDetails.injectedHideFilters,
1678
'display:none!important;',
1679
{ injected: true }
1680
);
1681
domFilterer.addProceduralSelectors(cfeDetails.proceduralFilters);
1682
}
1683
1684
if ( cfeDetails.networkFilters.length !== 0 ) {
1685
vAPI.userStylesheet.add(
1686
cfeDetails.networkFilters + '\n{display:none!important;}');
1687
}
1688
1689
vAPI.userStylesheet.apply();
1690
1691
// Library of resources is located at:
1692
// https://github.com/gorhill/uBlock/blob/master/assets/ublock/resources.txt
1693
if ( response.scriptlets ) {
1694
vAPI.injectScriptlet(document, response.scriptlets);
1695
vAPI.injectedScripts = response.scriptlets;
1696
}
1697
1698
if ( vAPI.domSurveyor instanceof Object ) {
1699
vAPI.domSurveyor.start(cfeDetails);
1700
}
1701
1702
// https://github.com/chrisaljoudi/uBlock/issues/587
1703
// If no filters were found, maybe the script was injected before
1704
// uBlock's process was fully initialized. When this happens, pages
1705
// won't be cleaned right after browser launch.
1706
if (
1707
typeof document.readyState === 'string' &&
1708
document.readyState !== 'loading'
1709
) {
1710
bootstrapPhase2();
1711
} else {
1712
document.addEventListener('DOMContentLoaded', bootstrapPhase2);
1713
}
1714
};
1715
1716
// This starts bootstrap process.
1717
vAPI.messaging.send(
1718
'contentscript',
1719
{
1720
what: 'retrieveContentScriptParameters',
1721
url: window.location.href,
1722
isRootFrame: window === window.top,
1723
charset: document.characterSet
1724
},
1725
bootstrapPhase1
1726
);
1727
})();
1728
1729
/******************************************************************************/
1730
/******************************************************************************/
1731
/******************************************************************************/
1732
1733
} // <<<<<<<< end of HUGE-IF-BLOCK
1734
1735