refactor content script code + add support for new `:has` & `:xpath` filters

Aside extending cosmetic filtering abilities, I expect this will
also take care of some long standing issues (I will have to find them
and mark them as "resolved" by this commit, as time allow).
This commit is contained in:
gorhill 2016-06-27 19:09:04 -04:00
parent 72fdce64f0
commit 6c513629bf
14 changed files with 1003 additions and 896 deletions

View File

@ -27,16 +27,10 @@
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["js/vapi-client.js", "js/contentscript-start.js"],
"js": ["js/vapi-client.js", "js/contentscript.js"],
"run_at": "document_start",
"all_frames": true
},
{
"matches": ["http://*/*", "https://*/*"],
"js": ["js/contentscript-end.js"],
"run_at": "document_end",
"all_frames": true
},
{
"matches": ["http://*/*", "https://*/*"],
"js": ["js/scriptlets/subscriber.js"],

View File

@ -1076,16 +1076,6 @@ vAPI.onLoadAllCompleted = function() {
var scriptDone = function() {
vAPI.lastError();
};
var scriptEnd = function(tabId) {
if ( vAPI.lastError() ) {
return;
}
vAPI.tabs.injectScript(tabId, {
file: 'js/contentscript-end.js',
allFrames: true,
runAt: 'document_idle'
}, scriptDone);
};
var scriptStart = function(tabId) {
vAPI.tabs.injectScript(tabId, {
file: 'js/vapi-client.js',
@ -1093,10 +1083,10 @@ vAPI.onLoadAllCompleted = function() {
runAt: 'document_idle'
}, function(){ });
vAPI.tabs.injectScript(tabId, {
file: 'js/contentscript-start.js',
file: 'js/contentscript.js',
allFrames: true,
runAt: 'document_idle'
}, function(){ scriptEnd(tabId); });
}, scriptDone);
};
var bindToTabs = function(tabs) {
var µb = µBlock;

View File

@ -21,14 +21,14 @@
/* global HTMLDocument, XMLDocument */
'use strict';
// For non background pages
/******************************************************************************/
(function(self) {
'use strict';
/******************************************************************************/
/******************************************************************************/

View File

@ -1,7 +1,7 @@
/*******************************************************************************
µBlock - a browser extension to block requests.
Copyright (C) 2014 The µBlock authors
uBlock Origin - a browser extension to block requests.
Copyright (C) 2014-2016 The uBlock Origin authors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -442,7 +442,7 @@ var contentObserver = {
try {
lss(this.contentBaseURI + 'vapi-client.js', sandbox);
lss(this.contentBaseURI + 'contentscript-start.js', sandbox);
lss(this.contentBaseURI + 'contentscript.js', sandbox);
} catch (ex) {
//console.exception(ex.msg, ex.stack);
return;
@ -451,7 +451,6 @@ var contentObserver = {
let docReady = (e) => {
let doc = e.target;
doc.removeEventListener(e.type, docReady, true);
lss(this.contentBaseURI + 'contentscript-end.js', sandbox);
if (
doc.querySelector('a[href^="abp:"],a[href^="https://subscribe.adblockplus.org/?"]') ||

View File

@ -27,16 +27,10 @@
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["js/vapi-client.js", "js/contentscript-start.js"],
"js": ["js/vapi-client.js", "js/contentscript.js"],
"run_at": "document_start",
"all_frames": true
},
{
"matches": ["http://*/*", "https://*/*"],
"js": ["js/contentscript-end.js"],
"run_at": "document_end",
"all_frames": true
},
{
"matches": ["http://*/*", "https://*/*"],
"js": ["js/scriptlets/subscriber.js"],

View File

@ -1,242 +0,0 @@
/*******************************************************************************
uBlock Origin - a browser extension to block requests.
Copyright (C) 2014-2016 Raymond Hill
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Home: https://github.com/gorhill/uBlock
*/
/* jshint multistr: true */
/******************************************************************************/
// Injected into content pages
/******************************************************************************/
(function() {
'use strict';
/******************************************************************************/
// This can happen
if ( typeof vAPI !== 'object' ) {
return;
}
// https://github.com/chrisaljoudi/uBlock/issues/456
// Already injected?
if ( vAPI.contentscriptStartInjected ) {
return;
}
vAPI.contentscriptStartInjected = true;
vAPI.styles = vAPI.styles || [];
/******************************************************************************/
/******************************************************************************/
// Domain-based ABP cosmetic filters.
// These can be inserted before the DOM is loaded.
var cosmeticFilters = function(details) {
var donthideCosmeticFilters = {};
var hideCosmeticFilters = {};
var donthide = details.cosmeticDonthide;
var hide = details.cosmeticHide;
var i;
if ( donthide.length !== 0 ) {
i = donthide.length;
while ( i-- ) {
donthideCosmeticFilters[donthide[i]] = true;
}
}
// https://github.com/chrisaljoudi/uBlock/issues/143
if ( hide.length !== 0 ) {
i = hide.length;
var selector;
while ( i-- ) {
selector = hide[i];
if ( donthideCosmeticFilters[selector] ) {
hide.splice(i, 1);
} else {
hideCosmeticFilters[selector] = true;
}
}
}
if ( hide.length !== 0 ) {
// https://github.com/gorhill/uBlock/issues/1015
// Boost specificity of our CSS rules.
var styleText = ':root ' + hide.join(',\n:root ');
var style = document.createElement('style');
style.setAttribute('type', 'text/css');
// The linefeed before the style block is very important: do not remove!
style.appendChild(document.createTextNode(styleText + '\n{display:none !important;}'));
//console.debug('µBlock> "%s" cosmetic filters: injecting %d CSS rules:', details.domain, details.hide.length, hideStyleText);
var parent = document.head || document.documentElement;
if ( parent ) {
parent.appendChild(style);
vAPI.styles.push(style);
}
hideElements(styleText);
}
vAPI.donthideCosmeticFilters = donthideCosmeticFilters;
vAPI.hideCosmeticFilters = hideCosmeticFilters;
};
/******************************************************************************/
var netFilters = function(details) {
var parent = document.head || document.documentElement;
if ( !parent ) {
return;
}
var style = document.createElement('style');
var text = details.netHide.join(',\n');
var css = details.netCollapse ?
'\n{display:none !important;}' :
'\n{visibility:hidden !important;}';
style.appendChild(document.createTextNode(text + css));
parent.appendChild(style);
//console.debug('document.querySelectorAll("%s") = %o', text, document.querySelectorAll(text));
};
/******************************************************************************/
// Create script tags and assign data URIs looked up from our library of
// redirection resources: Sometimes it is useful to use these resources as
// standalone scriptlets. These scriptlets are injected from within the
// content scripts because what must be injected, if anything, depends on the
// currently active filters, as selected by the user.
// Library of redirection resources is located at:
// https://github.com/gorhill/uBlock/blob/master/assets/ublock/resources.txt
var injectScripts = function(scripts) {
var parent = document.head || document.documentElement;
if ( !parent ) {
return;
}
var scriptTag = document.createElement('script');
scriptTag.appendChild(document.createTextNode(scripts));
parent.appendChild(scriptTag);
vAPI.injectedScripts = scripts;
};
/******************************************************************************/
var filteringHandler = function(details) {
var styleTagCount = vAPI.styles.length;
if ( details ) {
if (
(vAPI.skipCosmeticFiltering = details.skipCosmeticFiltering) !== true &&
(details.cosmeticHide.length !== 0 || details.cosmeticDonthide.length !== 0)
) {
cosmeticFilters(details);
}
if ( details.netHide.length !== 0 ) {
netFilters(details);
}
if ( details.scripts ) {
injectScripts(details.scripts);
}
// The port will never be used again at this point, disconnecting allows
// the browser to flush this script from memory.
}
// This is just to inform the background process that cosmetic filters were
// actually injected.
if ( vAPI.styles.length !== styleTagCount ) {
vAPI.messaging.send('contentscript', { what: 'cosmeticFiltersActivated' });
}
// https://github.com/chrisaljoudi/uBlock/issues/587
// If no filters were found, maybe the script was injected before uBlock's
// process was fully initialized. When this happens, pages won't be
// cleaned right after browser launch.
vAPI.contentscriptStartInjected = details && details.ready;
};
/******************************************************************************/
var hideElements = function(selectors) {
if ( document.body === null ) {
return;
}
var elems = document.querySelectorAll(selectors);
var i = elems.length;
if ( i === 0 ) {
return;
}
// https://github.com/chrisaljoudi/uBlock/issues/158
// Using CSSStyleDeclaration.setProperty is more reliable
if ( document.body.shadowRoot === undefined ) {
while ( i-- ) {
elems[i].style.setProperty('display', 'none', 'important');
}
return;
}
// https://github.com/gorhill/uBlock/issues/435
// Using shadow content so that we do not have to modify style
// attribute.
var sessionId = vAPI.sessionId;
var elem, shadow;
while ( i-- ) {
elem = elems[i];
shadow = elem.shadowRoot;
// https://www.chromestatus.com/features/4668884095336448
// "Multiple shadow roots is being deprecated."
if ( shadow !== null ) {
if ( shadow.className !== sessionId ) {
elem.style.setProperty('display', 'none', 'important');
}
continue;
}
// https://github.com/gorhill/uBlock/pull/555
// Not all nodes can be shadowed:
// https://github.com/w3c/webcomponents/issues/102
// https://github.com/gorhill/uBlock/issues/762
// Remove display style that might get in the way of the shadow
// node doing its magic.
try {
shadow = elem.createShadowRoot();
shadow.className = sessionId;
elem.style.removeProperty('display');
} catch (ex) {
elem.style.setProperty('display', 'none', 'important');
}
}
};
/******************************************************************************/
var url = window.location.href;
vAPI.messaging.send(
'contentscript',
{
what: 'retrieveDomainCosmeticSelectors',
pageURL: url,
locationURL: url
},
filteringHandler
);
/******************************************************************************/
/******************************************************************************/
})();
/******************************************************************************/

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
/*******************************************************************************
µBlock - a browser extension to block requests.
Copyright (C) 2014 Raymond Hill
uBlock Origin - a browser extension to block requests.
Copyright (C) 2014-2016 Raymond Hill
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -20,14 +20,14 @@
*/
/* jshint bitwise: false */
/* global punycode, µBlock */
/* global punycode */
'use strict';
/******************************************************************************/
µBlock.cosmeticFilteringEngine = (function(){
'use strict';
/******************************************************************************/
var µb = µBlock;
@ -141,40 +141,6 @@ FilterPlainMore.fromSelfie = function(s) {
/******************************************************************************/
var FilterBucket = function(a, b) {
this.f = null;
this.filters = [];
if ( a !== undefined ) {
this.filters[0] = a;
if ( b !== undefined ) {
this.filters[1] = b;
}
}
};
FilterBucket.prototype.add = function(a) {
this.filters.push(a);
};
FilterBucket.prototype.retrieve = function(s, out) {
var i = this.filters.length;
while ( i-- ) {
this.filters[i].retrieve(s, out);
}
};
FilterBucket.prototype.fid = '[]';
FilterBucket.prototype.toSelfie = function() {
return this.filters.length.toString();
};
FilterBucket.fromSelfie = function() {
return new FilterBucket();
};
/******************************************************************************/
// Any selector specific to a hostname
// Examples:
// search.snapdo.com###ABottomD
@ -235,6 +201,40 @@ FilterEntity.fromSelfie = function(s) {
return new FilterEntity(decode(s.slice(0, pos)), s.slice(pos + 1));
};
/******************************************************************************/
var FilterBucket = function(a, b) {
this.f = null;
this.filters = [];
if ( a !== undefined ) {
this.filters[0] = a;
if ( b !== undefined ) {
this.filters[1] = b;
}
}
};
FilterBucket.prototype.add = function(a) {
this.filters.push(a);
};
FilterBucket.prototype.retrieve = function(s, out) {
var i = this.filters.length;
while ( i-- ) {
this.filters[i].retrieve(s, out);
}
};
FilterBucket.prototype.fid = '[]';
FilterBucket.prototype.toSelfie = function() {
return this.filters.length.toString();
};
FilterBucket.fromSelfie = function() {
return new FilterBucket();
};
/******************************************************************************/
/******************************************************************************/
@ -245,11 +245,13 @@ var FilterParser = function() {
this.invalid = false;
this.cosmetic = true;
this.reScriptTagFilter = /^script:(contains|inject)\((.+?)\)$/;
this.reNeedHostname = /^(?:.+?:has|:xpath)\(.+?\)$/;
};
/******************************************************************************/
FilterParser.prototype.reset = function() {
this.raw = '';
this.prefix = this.suffix = this.style = '';
this.unhide = 0;
this.hostnames.length = 0;
@ -264,6 +266,8 @@ FilterParser.prototype.parse = function(raw) {
// important!
this.reset();
this.raw = raw;
// Find the bounds of the anchor.
var lpos = raw.indexOf('#');
if ( lpos === -1 ) {
@ -349,6 +353,16 @@ FilterParser.prototype.parse = function(raw) {
this.hostnames = this.prefix.split(/\s*,\s*/);
}
// For some selectors, it is mandatory to have a hostname or entity.
if (
this.hostnames.length === 0 &&
this.unhide === 0 &&
this.reNeedHostname.test(this.suffix)
) {
this.invalid = true;
return this;
}
// Script tag filters: pre-process them so that can be used with minimal
// overhead in the content script.
// Examples:
@ -357,10 +371,16 @@ FilterParser.prototype.parse = function(raw) {
// focus.de##script:inject(uabinject-defuser.js)
var matches = this.reScriptTagFilter.exec(this.suffix);
if ( matches === null ) {
return this;
if ( matches !== null ) {
return this.parseScriptTagFilter(matches);
}
return this;
};
/******************************************************************************/
FilterParser.prototype.parseScriptTagFilter = function(matches) {
// Currently supported only as non-generic selector. Also, exception
// script tag filter makes no sense, ignore.
if ( this.hostnames.length === 0 || this.unhide === 1 ) {
@ -378,7 +398,7 @@ FilterParser.prototype.parse = function(raw) {
} else {
token = token.slice(1, -1);
if ( isBadRegex(token) ) {
µb.logger.writeOne('', 'error', 'Cosmetic filtering bad regular expression: ' + raw + ' (' + isBadRegex.message + ')');
µb.logger.writeOne('', 'error', 'Cosmetic filtering bad regular expression: ' + this.raw + ' (' + isBadRegex.message + ')');
this.invalid = true;
}
}
@ -676,19 +696,33 @@ FilterContainer.prototype.reset = function() {
FilterContainer.prototype.isValidSelector = (function() {
var div = document.createElement('div');
var matchesProp = (function() {
if ( typeof div.matches === 'function' ) {
return 'matches';
}
if ( typeof div.mozMatchesSelector === 'function' ) {
return 'mozMatchesSelector';
}
if ( typeof div.webkitMatchesSelector === 'function' ) {
return 'webkitMatchesSelector';
}
return '';
})();
// Not all browsers support `Element.matches`:
// http://caniuse.com/#feat=matchesselector
if ( typeof div.matches !== 'function' ) {
if ( matchesProp === '' ) {
return function() {
return true;
};
}
var reHasSelector = /^(.+?):has\((.+?)\)$/;
var reXpathSelector = /^:xpath\((.+?)\)$/;
return function(s) {
try {
// https://github.com/gorhill/uBlock/issues/693
div.matches(s + ',\n#foo');
div[matchesProp](s + ',\n#foo');
// Discard new ABP's `-abp-properties` directive until it is
// implemented (if ever).
if ( s.indexOf('[-abp-properties=') === -1 ) {
@ -697,6 +731,24 @@ FilterContainer.prototype.isValidSelector = (function() {
} catch (e) {
}
// We reach this point very rarely.
var matches;
// Future `:has`-based filter? If so, validate both parts of the whole
// selector.
matches = reHasSelector.exec(s);
if ( matches !== null ) {
return this.isValidSelector(matches[1]) && this.isValidSelector(matches[2]);
}
// Custom `:xpath`-based filter?
matches = reXpathSelector.exec(s);
if ( matches !== null ) {
try {
return document.createExpression(matches[1], null) instanceof XPathExpression;
} catch (e) {
}
return false;
}
// Special `script:` filter?
if ( s.startsWith('script') ) {
if ( s.startsWith('?', 6) || s.startsWith('+', 6) ) {
return true;
@ -924,7 +976,7 @@ FilterContainer.prototype.fromCompiledContent = function(text, lineBeg, skip) {
fields = line.split('\v');
// h ir twitter.com .promoted-tweet
// h [\t] ir [\t] twitter.com [\t] .promoted-tweet
if ( fields[0] === 'h' ) {
// Special filter: script tags. Not a real CSS selector.
if ( fields[3].startsWith('script') ) {
@ -943,8 +995,8 @@ FilterContainer.prototype.fromCompiledContent = function(text, lineBeg, skip) {
continue;
}
// lg 105 .largeAd
// lg+ 2jx .Mpopup + #Mad > #MadZone
// lg [\t] 105 [\t] .largeAd
// lg+ [\t] 2jx [\t] .Mpopup + #Mad > #MadZone
if ( fields[0] === 'lg' || fields[0] === 'lg+' ) {
filter = fields[0] === 'lg' ?
filterPlain :
@ -960,7 +1012,7 @@ FilterContainer.prototype.fromCompiledContent = function(text, lineBeg, skip) {
continue;
}
// entity selector
// entity [\t] selector
if ( fields[0] === 'e' ) {
// Special filter: script tags. Not a real CSS selector.
if ( fields[2].startsWith('script') ) {
@ -1392,8 +1444,6 @@ FilterContainer.prototype.retrieveGenericSelectors = function(request) {
hideHigh: this.highHighGenericHide,
hideHighCount: this.highHighGenericHideCount
};
// https://github.com/chrisaljoudi/uBlock/issues/497
r.donthide = this.genericDonthide;
}
var hideSelectors = r.hide;
@ -1437,13 +1487,16 @@ FilterContainer.prototype.retrieveDomainSelectors = function(request) {
// r.ready will tell the content script the cosmetic filtering engine is
// up and ready.
// https://github.com/chrisaljoudi/uBlock/issues/497
// Generic exception filters are to be applied on all pages.
var r = {
ready: this.frozen,
domain: domain,
entity: pos === -1 ? domain : domain.slice(0, pos - domain.length),
skipCosmeticFiltering: this.acceptedCount === 0,
cosmeticHide: [],
cosmeticDonthide: [],
cosmeticDonthide: this.genericDonthide,
netHide: [],
netCollapse: µb.userSettings.collapseBlocked,
scripts: this.retrieveScriptTags(domain, hostname)

View File

@ -1,7 +1,7 @@
/*******************************************************************************
uBlock Origin - a browser extension to block requests.
Copyright (C) 2015 Raymond Hill
Copyright (C) 2015-2016 Raymond Hill
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -19,14 +19,14 @@
Home: https://github.com/gorhill/uBlock
*/
/* global vAPI, uDom */
/* global uDom */
'use strict';
/******************************************************************************/
(function() {
'use strict';
/******************************************************************************/
var showdomButton = uDom.nodeFromId('showdom');
@ -257,21 +257,17 @@ var countFromNode = function(li) {
/******************************************************************************/
var selectorFromNode = function(node, nth) {
var selectorFromNode = function(node) {
var selector = '';
var code;
if ( nth === undefined ) {
nth = 1;
}
while ( node !== null ) {
if ( node.localName === 'li' ) {
code = node.querySelector('code:nth-of-type(' + nth + ')');
code = node.querySelector('code');
if ( code !== null ) {
selector = code.textContent + ' > ' + selector;
if ( selector.indexOf('#') !== -1 ) {
break;
}
nth = 1;
}
}
node = node.parentElement;
@ -281,6 +277,21 @@ var selectorFromNode = function(node, nth) {
/******************************************************************************/
var selectorFromFilter = function(node) {
while ( node !== null ) {
if ( node.localName === 'li' ) {
var code = node.querySelector('code:nth-of-type(2)');
if ( code !== null ) {
return code.textContent;
}
}
node = node.parentElement;
}
return '';
};
/******************************************************************************/
var nidFromNode = function(node) {
var li = node;
while ( li !== null ) {
@ -482,10 +493,11 @@ var onClick = function(ev) {
messaging.sendTo(
'loggerUI',
{
what: 'toggleNodes',
what: 'toggleFilter',
original: false,
target: target.classList.toggle('off'),
selector: selectorFromNode(target, 2),
selector: selectorFromNode(target),
filter: selectorFromFilter(target),
nid: ''
},
inspectedTabId,
@ -504,7 +516,7 @@ var onClick = function(ev) {
what: 'toggleNodes',
original: true,
target: target.classList.toggle('off') === false,
selector: selectorFromNode(target, 1),
selector: selectorFromNode(target),
nid: nidFromNode(target)
},
inspectedTabId,

View File

@ -19,42 +19,27 @@
Home: https://github.com/gorhill/uBlock
*/
/******************************************************************************/
(function() {
'use strict';
/******************************************************************************/
if ( typeof vAPI !== 'object' ) {
return;
}
(function() {
/******************************************************************************/
var loggedSelectors = vAPI.loggedSelectors || {};
var injectedSelectors = [];
var reProperties = /\s*\{[^}]+\}\s*/;
var i;
var styles = vAPI.styles || [];
i = styles.length;
while ( i-- ) {
injectedSelectors = injectedSelectors.concat(styles[i].textContent.replace(reProperties, '').split(/\s*,\n\s*/));
}
if ( injectedSelectors.length === 0 ) {
if ( typeof vAPI !== 'object' || typeof vAPI.domFilterer !== 'object' ) {
return;
}
var matchedSelectors = [];
var selector;
var loggedSelectors = vAPI.loggedSelectors || {},
matchedSelectors = [],
selectors, i, selector, entry, nodes, j;
i = injectedSelectors.length;
// CSS-based selectors.
selectors = vAPI.domFilterer.simpleSelectors.concat(vAPI.domFilterer.complexSelectors);
i = selectors.length;
while ( i-- ) {
selector = injectedSelectors[i];
selector = selectors[i];
if ( loggedSelectors.hasOwnProperty(selector) ) {
continue;
}
@ -62,27 +47,67 @@ while ( i-- ) {
continue;
}
loggedSelectors[selector] = true;
// https://github.com/gorhill/uBlock/issues/1015
// Discard `:root ` prefix.
matchedSelectors.push(selector.slice(6));
matchedSelectors.push(selector);
}
// `:has`-based selectors.
selectors = vAPI.domFilterer.simpleHasSelectors.concat(vAPI.domFilterer.complexHasSelectors);
i = selectors.length;
while ( i-- ) {
entry = selectors[i];
selector = entry.a + ':has(' + entry.b + ')';
if ( loggedSelectors.hasOwnProperty(selector) ) {
continue;
}
nodes = document.querySelectorAll(entry.a);
j = nodes.length;
while ( j-- ) {
if ( nodes[j].querySelector(entry.b) !== null ) {
loggedSelectors[selector] = true;
matchedSelectors.push(selector);
break;
}
}
}
// `:xpath`-based selectors.
var xpr = null,
xpathexpr;
selectors = vAPI.domFilterer.xpathSelectors;
i = selectors.length;
while ( i-- ) {
xpathexpr = selectors[i];
selector = ':xpath(' + xpathexpr + ')';
if ( loggedSelectors.hasOwnProperty(selector) ) {
continue;
}
xpr = document.evaluate(
'boolean(' + xpathexpr + ')',
document,
null,
XPathResult.BOOLEAN_TYPE,
xpr
);
if ( xpr.booleanValue ) {
loggedSelectors[selector] = true;
matchedSelectors.push(selector);
}
}
vAPI.loggedSelectors = loggedSelectors;
/******************************************************************************/
vAPI.messaging.send(
'scriptlets',
{
what: 'logCosmeticFilteringData',
frameURL: window.location.href,
frameHostname: window.location.hostname,
matchedSelectors: matchedSelectors
}
);
if ( matchedSelectors.length ) {
vAPI.messaging.send(
'scriptlets',
{
what: 'logCosmeticFilteringData',
frameURL: window.location.href,
frameHostname: window.location.hostname,
matchedSelectors: matchedSelectors
}
);
}
/******************************************************************************/
})();
/******************************************************************************/

View File

@ -1,7 +1,7 @@
/*******************************************************************************
uBlock - a browser extension to block requests.
Copyright (C) 2015 Raymond Hill
uBlock Origin - a browser extension to block requests.
Copyright (C) 2015-2016 Raymond Hill
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -19,75 +19,51 @@
Home: https://github.com/gorhill/uBlock
*/
/******************************************************************************/
(function() {
'use strict';
/******************************************************************************/
if ( typeof vAPI !== 'object' ) {
return;
}
/******************************************************************************/
var styles = vAPI.styles;
if ( Array.isArray(styles) === false ) {
return;
}
var sessionId = vAPI.sessionId;
/******************************************************************************/
// Remove all cosmetic filtering-related styles from the DOM
var selectors = [];
var reProperties = /\s*\{[^}]+\}\s*/;
var style, i;
i = styles.length;
while ( i-- ) {
style = styles[i];
selectors.push(style.textContent.replace(reProperties, ''));
if ( style.sheet !== null ) {
style.sheet.disabled = true;
style[vAPI.sessionId] = true;
(function() {
if ( typeof vAPI !== 'object' || typeof vAPI.domFilterer !== 'object' ) {
return;
}
}
if ( selectors.length === 0 ) {
return;
}
var styles = vAPI.domFilterer.styleTags;
var elems = [];
try {
elems = document.querySelectorAll(selectors.join(','));
} catch (e) {
}
i = elems.length;
var elem, shadow;
while ( i-- ) {
elem = elems[i];
shadow = elem.shadowRoot;
if ( shadow === undefined ) {
style = elem.style;
if ( typeof style === 'object' || typeof style.removeProperty === 'function' ) {
style.removeProperty('display');
// Disable all cosmetic filtering-related styles from the DOM
var i = styles.length, style;
while ( i-- ) {
style = styles[i];
if ( style.sheet !== null ) {
style.sheet.disabled = true;
style[vAPI.sessionId] = true;
}
continue;
}
if ( shadow !== null && shadow.className === sessionId && shadow.firstElementChild === null ) {
shadow.appendChild(document.createElement('content'));
var elems = [];
try {
elems = document.querySelectorAll('[' + vAPI.domFilterer.hiddenId + ']');
} catch (e) {
}
i = elems.length;
while ( i-- ) {
var elem = elems[i];
var shadow = elem.shadowRoot;
if ( shadow === undefined ) {
style = elem.style;
if ( typeof style === 'object' || typeof style.removeProperty === 'function' ) {
style.removeProperty('display');
}
continue;
}
if (
shadow !== null &&
shadow.className === vAPI.domFilterer.shadowId &&
shadow.firstElementChild === null
) {
shadow.appendChild(document.createElement('content'));
}
}
}
/******************************************************************************/
vAPI.domFilterer.toggleOff();
})();
/******************************************************************************/

View File

@ -1,7 +1,7 @@
/*******************************************************************************
uBlock - a browser extension to block requests.
Copyright (C) 2015 Raymond Hill
uBlock Origin - a browser extension to block requests.
Copyright (C) 2015-2016 Raymond Hill
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -19,75 +19,51 @@
Home: https://github.com/gorhill/uBlock
*/
/******************************************************************************/
(function() {
'use strict';
/******************************************************************************/
if ( typeof vAPI !== 'object' ) {
return;
}
/******************************************************************************/
var styles = vAPI.styles;
if ( Array.isArray(styles) === false ) {
return;
}
var sessionId = vAPI.sessionId;
/******************************************************************************/
// Insert all cosmetic filtering-related style tags in the DOM
var selectors = [];
var reProperties = /\s*\{[^}]+\}\s*/;
var style, i;
i = styles.length;
while ( i-- ) {
style = styles[i];
selectors.push(style.textContent.replace(reProperties, ''));
if ( style.sheet !== null ) {
style.sheet.disabled = false;
style[vAPI.sessionId] = undefined;
(function() {
if ( typeof vAPI !== 'object' || typeof vAPI.domFilterer !== 'object' ) {
return;
}
}
if ( selectors.length === 0 ) {
return;
}
// Insert all cosmetic filtering-related style tags in the DOM
var elems = [];
try {
elems = document.querySelectorAll(selectors.join(','));
} catch (e) {
}
var elem, shadow;
i = elems.length;
while ( i-- ) {
elem = elems[i];
shadow = elem.shadowRoot;
if ( shadow === undefined ) {
style = elems[i].style;
if ( typeof style === 'object' || typeof style.removeProperty === 'function' ) {
style.setProperty('display', 'none', 'important');
var styles = vAPI.domFilterer.styleTags;
var i = styles.length, style;
while ( i-- ) {
style = styles[i];
if ( style.sheet !== null ) {
style.sheet.disabled = false;
style[vAPI.sessionId] = undefined;
}
continue;
}
if ( shadow !== null && shadow.className === sessionId && shadow.firstElementChild !== null ) {
shadow.removeChild(shadow.firstElementChild);
var elems = [];
try {
elems = document.querySelectorAll('[' + vAPI.domFilterer.hiddenId + ']');
} catch (e) {
}
i = elems.length;
while ( i-- ) {
var elem = elems[i];
var shadow = elem.shadowRoot;
if ( shadow === undefined ) {
style = elems[i].style;
if ( typeof style === 'object' || typeof style.removeProperty === 'function' ) {
style.setProperty('display', 'none', 'important');
}
continue;
}
if (
shadow !== null &&
shadow.className === vAPI.domFilterer.shadowId &&
shadow.firstElementChild !== null
) {
shadow.removeChild(shadow.firstElementChild);
}
}
}
/******************************************************************************/
vAPI.domFilterer.toggleOn();
})();
/******************************************************************************/

View File

@ -19,51 +19,29 @@
Home: https://github.com/gorhill/uBlock
*/
/******************************************************************************/
(function() {
'use strict';
/******************************************************************************/
if ( typeof vAPI !== 'object' ) {
return;
}
/******************************************************************************/
// Insert all cosmetic filtering-related style tags in the DOM
var injectedSelectors = [];
var filteredElementCount = 0;
var reProperties = /\s*\{[^}]+\}\s*/;
var i;
var styles = vAPI.styles || [];
i = styles.length;
while ( i-- ) {
injectedSelectors = injectedSelectors.concat(styles[i].textContent.replace(reProperties, '').split(/\s*,\n\s*/));
}
if ( injectedSelectors.length !== 0 ) {
filteredElementCount = document.querySelectorAll(injectedSelectors.join(',')).length;
}
/******************************************************************************/
vAPI.messaging.send(
'scriptlets',
{
what: 'liveCosmeticFilteringData',
pageURL: window.location.href,
filteredElementCount: filteredElementCount
(function() {
if ( typeof vAPI !== 'object' || typeof vAPI.domFilterer !== 'object' ) {
return;
}
);
/******************************************************************************/
var xpr = document.evaluate(
'count(//*[@' + vAPI.domFilterer.hiddenId + '])',
document,
null,
XPathResult.NUMBER_TYPE,
null
);
vAPI.messaging.send(
'scriptlets',
{
what: 'liveCosmeticFilteringData',
pageURL: window.location.href,
filteredElementCount: xpr && xpr.numberValue || 0
}
);
})();
/******************************************************************************/

View File

@ -28,13 +28,14 @@
/******************************************************************************/
if ( typeof vAPI !== 'object' ) {
if ( typeof vAPI !== 'object' || typeof vAPI.domFilterer !== 'object' ) {
return;
}
/******************************************************************************/
var sessionId = vAPI.sessionId;
var shadowId = vAPI.domFilterer.shadowId;
if ( document.querySelector('iframe.dom-inspector.' + sessionId) !== null ) {
return;
@ -690,19 +691,20 @@ var cosmeticFilterMapper = (function() {
// Not all target nodes have necessarily been force-hidden,
// do it now so that the inspector does not unhide these
// nodes when disabling style tags.
var nodesFromStyleTag = function(styleTag, rootNode) {
var filterMap = nodeToCosmeticFilterMap;
var styleText = styleTag.textContent;
var selectors = styleText.slice(0, styleText.lastIndexOf('\n')).split(/,\n/);
var i = selectors.length;
var selector, nodes, j, node;
var nodesFromStyleTag = function(rootNode) {
var filterMap = nodeToCosmeticFilterMap,
selectors, selector,
nodes, node,
entries, entry,
i, j;
// CSS-based selectors: simple one.
selectors = vAPI.domFilterer.simpleSelectors;
i = selectors.length;
while ( i-- ) {
// https://github.com/gorhill/uBlock/issues/1015
// Discard `:root ` prefix.
selector = selectors[i].slice(6);
selector = selectors[i];
if ( filterMap.has(rootNode) === false && rootNode[matchesFnName](selector) ) {
filterMap.set(rootNode, selector);
hideNode(node);
}
nodes = rootNode.querySelectorAll(selector);
j = nodes.length;
@ -710,24 +712,106 @@ var cosmeticFilterMapper = (function() {
node = nodes[j];
if ( filterMap.has(node) === false ) {
filterMap.set(node, selector);
hideNode(node);
}
}
}
// CSS-based selectors: complex one (must query from doc root).
selectors = vAPI.domFilterer.complexSelectors;
i = selectors.length;
while ( i-- ) {
selector = selectors[i];
nodes = document.querySelectorAll(selector);
j = nodes.length;
while ( j-- ) {
node = nodes[j];
if ( filterMap.has(node) === false ) {
filterMap.set(node, selector);
}
}
}
// `:has()`-based selectors: simple ones.
entries = vAPI.domFilterer.simpleHasSelectors;
i = entries.length;
while ( i-- ) {
entry = entries[i];
selector = entries.a + ':has(' + entries.b + ')';
if (
filterMap.has(rootNode) === false &&
rootNode[matchesFnName](entry.a) &&
rootNode.querySelector(entry.b) !== null
) {
filterMap.set(rootNode, selector);
}
nodes = rootNode.querySelectorAll(entries.a);
j = nodes.length;
while ( j-- ) {
node = nodes[j];
if (
filterMap.has(node) === false &&
node.querySelector(entry.b) !== null
) {
filterMap.set(node, selector);
}
}
}
// `:has()`-based selectors: complex ones (must query from doc root).
entries = vAPI.domFilterer.complexHasSelectors;
i = entries.length;
while ( i-- ) {
entry = entries[i];
selector = entries.a + ':has(' + entries.b + ')';
nodes = document.querySelectorAll(entries.a);
j = nodes.length;
while ( j-- ) {
node = nodes[j];
if (
filterMap.has(node) === false &&
node.querySelector(entry.b) !== null
) {
filterMap.set(node, selector);
}
}
}
// `:xpath()`-based selectors.
var xpr;
entries = vAPI.domFilterer.xpathSelectors;
i = entries.length;
while ( i-- ) {
entry = entries[i];
selector = ':xpath(' + entry + ')';
xpr = document.evaluate(
entry,
document,
null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
xpr
);
j = xpr.snapshotLength;
while ( j-- ) {
node = xpr.snapshotItem(j);
if ( filterMap.has(node) === false ) {
filterMap.set(node, selector);
}
}
}
};
var incremental = function(rootNode) {
var styleTags = vAPI.styles || [];
var styleTags = vAPI.domFilterer.styleTags || [];
var styleTag;
var i = styleTags.length;
while ( i-- ) {
styleTag = styleTags[i];
nodesFromStyleTag(styleTag, rootNode);
if ( styleTag.sheet !== null ) {
styleTag.sheet.disabled = true;
styleTag[vAPI.sessionId] = true;
}
}
nodesFromStyleTag(rootNode);
};
var reset = function() {
@ -736,7 +820,7 @@ var cosmeticFilterMapper = (function() {
};
var shutdown = function() {
var styleTags = vAPI.styles || [];
var styleTags = vAPI.domFilterer.styleTags || [];
var styleTag;
var i = styleTags.length;
while ( i-- ) {
@ -761,12 +845,51 @@ var elementsFromSelector = function(selector, context) {
if ( !context ) {
context = document;
}
var out = [];
var out;
if ( selector.indexOf(':') !== -1 ) {
out = elementsFromSpecialSelector(selector);
if ( out !== undefined ) {
return out;
}
}
// plain CSS selector
try {
out = context.querySelectorAll(selector);
} catch (ex) {
}
return out;
return out || [];
};
var elementsFromSpecialSelector = function(selector) {
var out = [], i;
var matches = /^(.+?):has\((.+?)\)$/.exec(selector);
if ( matches !== null ) {
var nodes = document.querySelector(matches[1]);
i = nodes.length;
while ( i-- ) {
var node = nodes[i];
if ( node.querySelector(matches[2]) !== null ) {
out.push(node);
}
}
return out;
}
matches = /^:xpath\((.+?)\)$/.exec(selector);
if ( matches !== null ) {
var xpr = document.evaluate(
matches[1],
document,
null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
null
);
i = xpr.snapshotLength;
while ( i-- ) {
out.push(xpr.snapshotItem(i));
}
return out;
}
};
/******************************************************************************/
@ -970,7 +1093,7 @@ var showNode = function(node, v1, v2) {
} else {
node.style.setProperty('display', v1, v2);
}
} else if ( shadow !== null && shadow.className === sessionId && shadow.firstElementChild === null ) {
} else if ( shadow !== null && shadow.className === shadowId && shadow.firstElementChild === null ) {
shadow.appendChild(document.createElement('content'));
}
};
@ -983,7 +1106,7 @@ var hideNode = function(node) {
node.style.setProperty('display', 'none', 'important');
return;
}
if ( shadow !== null && shadow.className === sessionId ) {
if ( shadow !== null && shadow.className === shadowId ) {
if ( shadow.firstElementChild !== null ) {
shadow.removeChild(shadow.firstElementChild);
}
@ -995,7 +1118,7 @@ var hideNode = function(node) {
} catch (ex) {
return;
}
shadow.className = sessionId;
shadow.className = shadowId;
};
/******************************************************************************/
@ -1052,6 +1175,12 @@ var onMessage = function(request) {
highlightElements();
break;
case 'toggleFilter':
highlightedElementLists[0] = selectNodes(request.filter, request.nid);
toggleNodes(highlightedElementLists[0], request.original, request.target);
highlightElements(true);
break;
case 'toggleNodes':
highlightedElementLists[0] = selectNodes(request.selector, request.nid);
toggleNodes(highlightedElementLists[0], request.original, request.target);