merge "next" branch

This commit is contained in:
gorhill 2016-10-02 13:31:01 -04:00
commit 89c2185e20
11 changed files with 496 additions and 309 deletions

View file

@ -2,7 +2,7 @@
"manifest_version": 2,
"name": "uBlock Origin",
"version": "1.9.12",
"version": "1.9.13.0",
"default_locale": "en",
"description": "__MSG_extShortDesc__",

View file

@ -142,25 +142,24 @@ vAPI.setTimeout = vAPI.setTimeout || self.setTimeout.bind(self);
/******************************************************************************/
vAPI.shutdown = (function() {
var jobs = [];
var add = function(job) {
jobs.push(job);
};
var exec = function() {
vAPI.shutdown = {
jobs: [],
add: function(job) {
this.jobs.push(job);
},
exec: function() {
var job;
while ( (job = jobs.pop()) ) {
while ( (job = this.jobs.pop()) ) {
job();
}
};
return {
add: add,
exec: exec
};
})();
},
remove: function(job) {
var pos;
while ( (pos = this.jobs.indexOf(job)) !== -1 ) {
this.jobs.splice(pos, 1);
}
}
};
/******************************************************************************/
/******************************************************************************/

View file

@ -123,26 +123,24 @@ vAPI.setTimeout = vAPI.setTimeout || function(callback, delay, extra) {
/******************************************************************************/
vAPI.shutdown = (function() {
var jobs = [];
var add = function(job) {
jobs.push(job);
};
var exec = function() {
//console.debug('Shutting down...');
vAPI.shutdown = {
jobs: [],
add: function(job) {
this.jobs.push(job);
},
exec: function() {
var job;
while ( (job = jobs.pop()) ) {
while ( (job = this.jobs.pop()) ) {
job();
}
};
return {
add: add,
exec: exec
};
})();
},
remove: function(job) {
var pos;
while ( (pos = this.jobs.indexOf(job)) !== -1 ) {
this.jobs.splice(pos, 1);
}
}
};
/******************************************************************************/

View file

@ -2,7 +2,7 @@
"manifest_version": 2,
"name": "uBlock Origin",
"version": "1.9.12",
"version": "1.9.13.0",
"default_locale": "en",
"description": "__MSG_extShortDesc__",

View file

@ -473,11 +473,6 @@ body.portrait #firewallContainer > div > span:nth-of-type(4) {
#actionSelector.colorBlind > span:nth-of-type(3) {
background-color: rgb(0, 19, 110);
}
#firewallContainer span.aRule #actionSelector > span:nth-of-type(1),
#firewallContainer span.nRule #actionSelector > span:nth-of-type(2),
#firewallContainer span.bRule #actionSelector > span:nth-of-type(3) {
visibility: hidden;
}
#rulesetTools {
background-color: transparent;

View file

@ -56,12 +56,14 @@ section {
border: 0;
box-sizing: border-box;
display: inline-block;
position: relative;
width: 100%;
}
section > textarea {
section > div {
position: relative;
}
section > div > textarea {
background-color: #fff;
border: 1px solid #ccc;
border: 1px solid #aaa;
box-sizing: border-box;
font: 11px monospace;
height: 6em;
@ -70,15 +72,22 @@ section > textarea {
resize: none;
width: 100%;
}
section > div {
section > div > textarea.invalidFilter {
background-color: #fee;
}
section > div > textarea + div {
background-color: #aaa;
bottom: 0;
color: white;
padding: 2px 4px;
position: absolute;
right: 0;
}
section > div + div {
direction: ltr;
margin: 2px 0;
text-align: right;
}
section > div > span:last-of-type {
position: absolute;
right: 0;
}
ul {
padding: 0;
list-style-type: none;
@ -137,8 +146,12 @@ svg > path + path {
body.preview svg > path {
fill: rgba(0,0,0,0.10);
}
body.preview svg > path + path {
stroke: none;
}
aside {
background-color: #eee;
border: 1px solid #aaa;
bottom: 4px;
box-sizing: border-box;
visibility: hidden;
@ -162,7 +175,10 @@ body.paused > aside:hover {
<svg><path></path><path></path></svg>
<aside>
<section>
<textarea lang="en" dir="ltr" spellcheck="false"></textarea>
<div>
<textarea lang="en" dir="ltr" spellcheck="false"></textarea>
<div></div>
</div>
<div><!--
--><button id="preview" type="button">{{preview}}</button><!--
--><button id="create" type="button" disabled>{{create}}</button><!--

View file

@ -1304,41 +1304,31 @@ FilterContainer.prototype.createUserScriptRule = function(hash, hostname, select
// 14 -1
FilterContainer.prototype.retrieveUserScripts = function(domain, hostname) {
if ( this.userScriptCount === 0 ) {
return;
}
if ( this.userScriptCount === 0 ) { return; }
var reng = µb.redirectEngine;
if ( !reng ) {
return;
}
if ( !reng ) { return; }
var out = [],
scripts = Object.create(null),
scripts = new Map(),
pos = domain.indexOf('.'),
entity = pos !== -1 ? domain.slice(0, pos) + '.*' : '',
token, content;
entity = pos !== -1 ? domain.slice(0, pos) + '.*' : '';
// Implicit
var hn = hostname;
for (;;) {
token = hn + '.js';
if (
(scripts[token] === undefined) &&
(content = reng.resourceContentFromName(token, 'application/javascript'))
) {
scripts[token] = out.length;
out.push(content);
}
this._lookupUserScript(scripts, hn + '.js', reng, out);
if ( hn === domain ) { break; }
pos = hn.indexOf('.');
if ( pos === -1 ) { break; }
hn = hn.slice(pos + 1);
}
if ( entity !== '' ) {
this._lookupUserScript(scripts, entity + '.js', reng, out);
}
// Explicit (hash is domain).
var selectors = [],
selector, bucket;
var selectors = [], bucket;
if ( (bucket = this.userScripts.get(domain)) ) {
bucket.retrieve(hostname, selectors);
}
@ -1347,15 +1337,7 @@ FilterContainer.prototype.retrieveUserScripts = function(domain, hostname) {
}
var i = selectors.length;
while ( i-- ) {
selector = selectors[i];
token = selector.slice(14, -1);
if (
(scripts[token] === undefined) &&
(content = reng.resourceContentFromName(token, 'application/javascript'))
) {
scripts[token] = out.length;
out.push(content);
}
this._lookupUserScript(scripts, selectors[i].slice(14, -1), reng, out);
}
if ( out.length === 0 ) {
@ -1364,7 +1346,7 @@ FilterContainer.prototype.retrieveUserScripts = function(domain, hostname) {
// Exceptions should be rare, so we check for exception only if there are
// scriptlets returned.
var exceptions = [], j;
var exceptions = [], j, token;
if ( (bucket = this.userScripts.get('!' + domain)) ) {
bucket.retrieve(hostname, exceptions);
}
@ -1374,7 +1356,7 @@ FilterContainer.prototype.retrieveUserScripts = function(domain, hostname) {
i = exceptions.length;
while ( i-- ) {
token = exceptions[i].slice(14, -1);
if ( (j = scripts[token]) !== undefined ) {
if ( (j = scripts.get(token)) !== undefined ) {
out[j] = '// User script "' + token + '" excepted.\n';
}
}
@ -1382,6 +1364,15 @@ FilterContainer.prototype.retrieveUserScripts = function(domain, hostname) {
return out.join('\n');
};
FilterContainer.prototype._lookupUserScript = function(dict, token, reng, out) {
if ( dict.has(token) ) { return; }
var content = reng.resourceContentFromName(token, 'application/javascript');
if ( content ) {
dict.set(token, out.length);
out.push(content);
}
};
/******************************************************************************/
FilterContainer.prototype.toSelfie = function() {

View file

@ -1,7 +1,7 @@
/*******************************************************************************
uBlock - 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
@ -19,15 +19,15 @@
Home: https://github.com/gorhill/uBlock
*/
/* global punycode, µBlock */
/* global punycode */
/* jshint bitwise: false */
'use strict';
/******************************************************************************/
µBlock.Firewall = (function() {
'use strict';
/******************************************************************************/
var magicId = 'chmdgxwtetgu';
@ -94,6 +94,10 @@ var isIPAddress = function(hostname) {
/******************************************************************************/
// TODO: Rearrange the code so as to avoid calling isIPAddress() from within
// toBroaderHostname(). A hostname will never magically become an IP address
// when broadened -- so no need to test for this condition each call.
var toBroaderHostname = function(hostname) {
if ( isIPAddress(hostname) ) {
return '*';
@ -261,38 +265,10 @@ Matrix.prototype.unsetCell = function(srcHostname, desHostname, type) {
return true;
};
/******************************************************************************/
Matrix.prototype.setCellZ = function(srcHostname, desHostname, type, action) {
this.evaluateCellZY(srcHostname, desHostname, type);
if ( this.r === action ) {
return false;
}
this.setCell(srcHostname, desHostname, type, 0);
this.evaluateCellZY(srcHostname, desHostname, type);
if ( this.r === action ) {
return true;
}
this.setCell(srcHostname, desHostname, type, action);
return true;
};
/******************************************************************************/
Matrix.prototype.blockCell = function(srcHostname, desHostname, type) {
return this.setCellZ(srcHostname, desHostname, type, 1);
};
// https://www.youtube.com/watch?v=Csewb_eIStY
/******************************************************************************/
Matrix.prototype.allowCell = function(srcHostname, desHostname, type) {
return this.setCellZ(srcHostname, desHostname, type, 2);
};
/******************************************************************************/
Matrix.prototype.evaluateCell = function(srcHostname, desHostname, type) {
var key = srcHostname + ' ' + desHostname;
var bitmap = this.rules[key];

View file

@ -239,7 +239,7 @@ var updateFirewallCell = function(scope, des, type, rule) {
cell.toggleClass(action + 'Rule', true);
}
// Use dark shade visual cue if the filter is specific to the cell.
// Use dark shade visual cue if the rule is specific to the cell.
var ownRule = false;
var matches = reSrcHostnameFromRule.exec(rule);
if ( matches !== null ) {

View file

@ -21,14 +21,14 @@
/* global CSS */
'use strict';
/******************************************************************************/
/******************************************************************************/
/*! http://mths.be/cssescape v0.2.1 by @mathias | MIT license */
;(function(root) {
'use strict';
if (!root.CSS) {
root.CSS = {};
}
@ -116,8 +116,6 @@
(function() {
'use strict';
/******************************************************************************/
if ( typeof vAPI !== 'object' ) {
@ -147,7 +145,6 @@ var cosmeticFilterCandidates = [];
var targetElements = [];
var candidateElements = [];
var bestCandidateFilter = null;
var previewedElements = [];
var lastNetFilterSession = window.location.host + window.location.pathname;
var lastNetFilterHostname = '';
@ -268,65 +265,6 @@ var highlightElements = function(elems, force) {
/******************************************************************************/
var filterElements = function(filter) {
var htmlElem = document.documentElement;
var items = elementsFromFilter(filter);
var i = items.length, item, elem, style;
while ( i-- ) {
item = items[i];
elem = item.elem;
// https://github.com/gorhill/uBlock/issues/1629
if ( elem === pickerRoot ) {
continue;
}
style = elem.style;
if (
(elem !== htmlElem) &&
(item.type === 'cosmetic' ||
item.type === 'network' && item.src !== undefined)
) {
previewedElements.push({
elem: elem,
prop: 'display',
value: style.getPropertyValue('display'),
priority: style.getPropertyPriority('display')
});
style.setProperty('display', 'none', 'important');
}
if ( item.type === 'network' && item.style === 'background-image' ) {
previewedElements.push({
elem: elem,
prop: 'background-image',
value: style.getPropertyValue('background-image'),
priority: style.getPropertyPriority('background-image')
});
style.setProperty('background-image', 'none', 'important');
}
}
};
/******************************************************************************/
var preview = function(filter) {
filterElements(filter);
pickerBody.classList.add('preview');
};
/******************************************************************************/
var unpreview = function() {
var items = previewedElements;
var i = items.length, item;
while ( i-- ) {
item = items[i];
item.elem.style.setProperty(item.prop, item.value, item.priority);
}
previewedElements.length = 0;
pickerBody.classList.remove('preview');
};
/******************************************************************************/
// https://github.com/gorhill/uBlock/issues/1897
// Ignore `data:` URI, they can't be handled by an HTTP observer.
@ -692,117 +630,374 @@ var filtersFrom = function(x, y) {
return netFilterCandidates.length + cosmeticFilterCandidates.length;
};
/******************************************************************************/
/*******************************************************************************
var elementsFromFilter = function(filter) {
var out = [];
filterToDOMInterface.set
@desc Look-up all the HTML elements matching the filter passed in
argument.
@param string, a cosmetic of network filter.
@return array, or undefined if the filter is invalid.
filter = filter.trim();
if ( filter === '' ) {
return out;
}
filterToDOMInterface.preview
@desc Apply/unapply filter to the DOM.
@param string, a cosmetic of network filter, or literal false to remove
the effects of the filter on the DOM.
@return undefined.
// Cosmetic filters: these are straight CSS selectors
// TODO: This is still not working well for a[href], because there are
// many ways to compose a valid href to the same effective URL.
// One idea is to normalize all a[href] on the page, but for now I will
// wait and see, as I prefer to refrain from tampering with the page
// content if I can avoid it.
var elems, iElem, elem;
if ( filter.lastIndexOf('##', 0) === 0 ) {
TODO: need to be revised once I implement chained cosmetic operators.
*/
var filterToDOMInterface = (function() {
// Net filters: we need to lookup manually -- translating into a foolproof
// CSS selector is just not possible.
var fromNetworkFilter = function(filter) {
var out = [];
// https://github.com/chrisaljoudi/uBlock/issues/945
// Transform into a regular expression, this allows the user to edit and
// insert wildcard(s) into the proposed filter.
var reStr = '';
if ( filter.length > 1 && filter.charAt(0) === '/' && filter.slice(-1) === '/' ) {
reStr = filter.slice(1, -1);
}
else {
var rePrefix = '', reSuffix = '';
if ( filter.slice(0, 2) === '||' ) {
filter = filter.replace('||', '');
} else {
if ( filter.charAt(0) === '|' ) {
rePrefix = '^';
filter = filter.slice(1);
}
}
if ( filter.slice(-1) === '|' ) {
reSuffix = '$';
filter = filter.slice(0, -1);
}
reStr = rePrefix +
filter.replace(/[.+?${}()|[\]\\]/g, '\\$&').replace(/[\*^]+/g, '.*') +
reSuffix;
}
var reFilter = null;
try {
elems = document.querySelectorAll(filter.slice(2));
reFilter = new RegExp(reStr);
}
catch (e) {
elems = [];
return out;
}
iElem = elems.length;
// Lookup by tag names.
var src1stProps = netFilter1stSources;
var src2ndProps = netFilter2ndSources;
var srcProp, src;
var elems = document.querySelectorAll(Object.keys(src1stProps).join()),
iElem = elems.length,
elem;
while ( iElem-- ) {
out.push({
type: 'cosmetic',
elem: elems[iElem],
});
}
return out;
}
// Net filters: we need to lookup manually -- translating into a
// foolproof CSS selector is just not possible
// https://github.com/chrisaljoudi/uBlock/issues/945
// Transform into a regular expression, this allows the user to edit and
// insert wildcard(s) into the proposed filter
var reStr = '';
if ( filter.length > 1 && filter.charAt(0) === '/' && filter.slice(-1) === '/' ) {
reStr = filter.slice(1, -1);
}
else {
var rePrefix = '', reSuffix = '';
if ( filter.slice(0, 2) === '||' ) {
filter = filter.replace('||', '');
} else {
if ( filter.charAt(0) === '|' ) {
rePrefix = '^';
filter = filter.slice(1);
elem = elems[iElem];
srcProp = src1stProps[elem.localName];
src = elem[srcProp];
if ( typeof src !== 'string' || src.length === 0 ) {
srcProp = src2ndProps[elem.localName];
src = elem[srcProp];
}
if ( src && reFilter.test(src) ) {
out.push({
type: 'network',
elem: elem,
src: srcProp,
opts: filterTypes[elem.localName],
});
}
}
if ( filter.slice(-1) === '|' ) {
reSuffix = '$';
filter = filter.slice(0, -1);
// Find matching background image in current set of candidate elements.
elems = candidateElements;
iElem = elems.length;
while ( iElem-- ) {
elem = elems[iElem];
if ( reFilter.test(backgroundImageURLFromElement(elem)) ) {
out.push({
type: 'network',
elem: elem,
style: 'background-image',
opts: 'image',
});
}
}
reStr = rePrefix +
filter.replace(/[.+?${}()|[\]\\]/g, '\\$&').replace(/[\*^]+/g, '.*') +
reSuffix;
}
var reFilter = null;
try {
reFilter = new RegExp(reStr);
}
catch (e) {
return out;
}
};
// Lookup by tag names.
var src1stProps = netFilter1stSources;
var src2ndProps = netFilter2ndSources;
var srcProp, src;
elems = document.querySelectorAll(Object.keys(src1stProps).join());
iElem = elems.length;
while ( iElem-- ) {
elem = elems[iElem];
srcProp = src1stProps[elem.localName];
src = elem[srcProp];
if ( typeof src !== 'string' || src.length === 0 ) {
srcProp = src2ndProps[elem.localName];
src = elem[srcProp];
// Cosmetic filters: these are straight CSS selectors.
// TODO: This is still not working well for a[href], because there are many
// ways to compose a valid href to the same effective URL. One idea is to
// normalize all a[href] on the page, but for now I will wait and see, as I
// prefer to refrain from tampering with the page content if I can avoid it.
var fromCosmeticFilter = function(filter) {
var elems;
try {
elems = document.querySelectorAll(filter);
}
if ( src && reFilter.test(src) ) {
out.push({
type: 'network',
elem: elem,
src: srcProp,
opts: filterTypes[elem.localName],
});
catch (e) {
return fromProceduralCosmeticFilter(filter);
}
}
var out = [],
iElem = elems.length;
while ( iElem-- ) {
out.push({ type: 'cosmetic', elem: elems[iElem]});
}
return out;
};
// Find matching background image in current set of candidate elements.
elems = candidateElements;
iElem = elems.length;
while ( iElem-- ) {
elem = elems[iElem];
if ( reFilter.test(backgroundImageURLFromElement(elem)) ) {
out.push({
type: 'network',
elem: elem,
style: 'background-image',
opts: 'image',
});
// https://github.com/gorhill/uBlock/issues/1772
// Handle procedural cosmetic filters.
var fromProceduralCosmeticFilter = function(filter) {
if ( filter.charCodeAt(filter.length - 1) === 0x29 /* ')' */ ) {
var parts = reProceduralCosmeticFilter.exec(filter);
if (
parts !== null &&
proceduralCosmeticFilterFunctions.hasOwnProperty(parts[2])
) {
return proceduralCosmeticFilterFunctions[parts[2]](
parts[1].trim(),
parts[3].trim()
);
}
}
}
};
return out;
};
var reProceduralCosmeticFilter = /^(.*?):(matches-css|has|style|xpath)\((.+?)\)$/;
// Collection of handlers for procedural cosmetic filters.
var proceduralCosmeticFilterFunctions = {
'has': function(selector, arg) {
if ( selector === '' ) { return; }
var elems;
try {
elems = document.querySelectorAll(selector);
document.querySelector(arg);
} catch(ex) {
return;
}
var out = [], elem;
for ( var i = 0, n = elems.length; i < n; i++ ) {
elem = elems[i];
if ( elem.querySelector(arg) ) {
out.push({ type: 'cosmetic', elem: elem });
}
}
return out;
},
'matches-css': function(selector, arg) {
if ( selector === '' ) { return; }
var elems;
try {
elems = document.querySelectorAll(selector);
} catch(ex) {
return;
}
var out = [], elem, style,
pos = arg.indexOf(':');
if ( pos === -1 ) { return; }
var prop = arg.slice(0, pos).trim(),
reText = arg.slice(pos + 1).trim();
if ( reText === '' ) { return; }
var re = reText !== '*' ?
new RegExp('^' + reText.replace(/[.+?${}()|[\]\\^]/g, '\\$&').replace(/\*+/g, '.*?') + '$') :
/./;
for ( var i = 0, n = elems.length; i < n; i++ ) {
elem = elems[i];
style = window.getComputedStyle(elem, null);
if ( re.test(style[prop]) ) {
out.push({ type: 'cosmetic', elem: elem });
}
}
return out;
},
'style': function(selector, arg) {
if ( selector === '' || arg === '' ) { return; }
var elems;
try {
elems = document.querySelectorAll(selector);
} catch(ex) {
return;
}
var out = [];
for ( var i = 0, n = elems.length; i < n; i++ ) {
out.push({ type: 'cosmetic', elem: elems[i] });
}
lastAction = selector + ' { ' + arg + ' }';
return out;
},
'xpath': function(selector, arg) {
if ( selector !== '' ) { return []; }
var result;
try {
result = document.evaluate(
arg,
document,
null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
null
);
} catch(ex) {
return;
}
if ( result === undefined ) { return []; }
var out = [], elem, i = result.snapshotLength;
while ( i-- ) {
elem = result.snapshotItem(i);
if ( elem.nodeType === 1 ) {
out.push({ type: 'cosmetic', elem: elem });
}
}
return out;
}
};
var lastFilter,
lastResultset,
lastAction,
appliedStyleTag,
applied = false,
previewing = false;
var queryAll = function(filter) {
filter = filter.trim();
if ( filter === lastFilter ) {
return lastResultset;
}
unapply();
if ( filter === '' ) {
lastFilter = '';
lastResultset = [];
} else {
lastFilter = filter;
lastAction = undefined;
lastResultset = filter.lastIndexOf('##', 0) === 0 ?
fromCosmeticFilter(filter.slice(2)) :
fromNetworkFilter(filter);
if ( previewing ) {
apply(filter);
}
}
return lastResultset;
};
var applyHide = function() {
var htmlElem = document.documentElement,
items = lastResultset,
item, elem, style;
for ( var i = 0, n = items.length; i < n; i++ ) {
item = items[i];
elem = item.elem;
// https://github.com/gorhill/uBlock/issues/1629
if ( elem === pickerRoot ) {
continue;
}
style = elem.style;
if (
(elem !== htmlElem) &&
(item.type === 'cosmetic' || item.type === 'network' && item.src !== undefined)
) {
item.display = style.getPropertyValue('display');
item.displayPriority = style.getPropertyPriority('display');
style.setProperty('display', 'none', 'important');
}
if ( item.type === 'network' && item.style === 'background-image' ) {
item.backgroundImage = style.getPropertyValue('background-image');
item.backgroundImagePriority = style.getPropertyPriority('background-image');
style.setProperty('background-image', 'none', 'important');
}
}
};
var unapplyHide = function() {
var items = lastResultset, item;
for ( var i = 0, n = items.length; i < n; i++ ) {
item = items[i];
if ( item.hasOwnProperty('display') ) {
item.elem.style.setProperty(
'display',
item.display,
item.displayPriority
);
delete item.display;
}
if ( item.hasOwnProperty('backgroundImage') ) {
item.elem.style.setProperty(
'background-image',
item.backgroundImage,
item.backgroundImagePriority
);
delete item.backgroundImage;
}
}
};
var unapplyStyle = function() {
if ( !appliedStyleTag || appliedStyleTag.parentNode === null ) {
return;
}
appliedStyleTag.parentNode.removeChild(appliedStyleTag);
};
var applyStyle = function() {
if ( !appliedStyleTag ) {
appliedStyleTag = document.createElement('style');
appliedStyleTag.setAttribute('type', 'text/css');
}
appliedStyleTag.textContent = lastAction;
if ( appliedStyleTag.parentNode === null ) {
document.head.appendChild(appliedStyleTag);
}
};
var apply = function() {
if ( applied ) {
unapply();
}
if ( lastResultset === undefined ) {
return;
}
if ( typeof lastAction === 'string' ) {
applyStyle();
} else {
applyHide();
}
applied = true;
};
var unapply = function() {
if ( !applied ) {
return;
}
if ( typeof lastAction === 'string' ) {
unapplyStyle();
} else {
unapplyHide();
}
applied = false;
};
var preview = function(filter) {
previewing = filter !== false;
if ( previewing ) {
if ( queryAll(filter) !== undefined ) {
apply();
}
} else {
unapply();
}
pickerBody.classList.toggle('preview', previewing);
};
return {
previewing: function() { return previewing; },
preview: preview,
set: queryAll
};
})();
// https://www.youtube.com/watch?v=nuUXJ6RfIik
@ -810,8 +1005,8 @@ var elementsFromFilter = function(filter) {
var userFilterFromCandidate = function() {
var v = taCandidate.value;
var items = elementsFromFilter(v);
if ( items.length === 0 ) {
var items = filterToDOMInterface.set(v);
if ( !items || items.length === 0 ) {
return false;
}
@ -850,13 +1045,18 @@ var userFilterFromCandidate = function() {
/******************************************************************************/
var onCandidateChanged = function() {
unpreview();
var elems = [];
var items = elementsFromFilter(taCandidate.value);
for ( var i = 0; i < items.length; i++ ) {
elems.push(items[i].elem);
var elems = [],
items = filterToDOMInterface.set(taCandidate.value),
valid = items !== undefined;
if ( valid ) {
for ( var i = 0; i < items.length; i++ ) {
elems.push(items[i].elem);
}
}
pickerBody.querySelector('body section textarea + div').textContent = valid ?
items.length.toLocaleString() :
'0';
taCandidate.classList.toggle('invalidFilter', !valid);
dialog.querySelector('#create').disabled = elems.length === 0;
highlightElements(elems, true);
};
@ -885,18 +1085,23 @@ var candidateFromFilterChoice = function(filterChoice) {
if ( filterChoice.modifier ) {
return filter.replace(/:nth-of-type\(\d+\)/, '');
}
// Return path: the target element, then all siblings prepended
var selector = [];
var selector = '', joiner = '';
for ( ; slot < filters.length; slot++ ) {
filter = filters[slot];
selector.unshift(filter.replace(/^##/, ''));
selector = filter.slice(2) + joiner + selector;
// Stop at any element with an id: these are unique in a web page
if ( filter.slice(0, 3) === '###' ) {
if ( filter.lastIndexOf('###', 0) === 0 ) {
break;
}
// Stop if current selector matches only one element on the page
if ( document.querySelectorAll(selector).length === 1 ) {
break;
}
joiner = ' > ';
}
return '##' + selector.join(' > ');
return '##' + selector;
};
/******************************************************************************/
@ -926,8 +1131,7 @@ var onDialogClicked = function(ev) {
else if ( ev.target.id === 'create' ) {
// We have to exit from preview mode: this guarantees matching elements
// will be found for the candidate filter.
unpreview();
filterToDOMInterface.preview(false);
var filter = userFilterFromCandidate();
if ( filter ) {
var d = new Date();
@ -939,7 +1143,7 @@ var onDialogClicked = function(ev) {
pageDomain: window.location.hostname
}
);
filterElements(taCandidate.value);
filterToDOMInterface.preview(taCandidate.value);
stopPicker();
}
}
@ -949,15 +1153,15 @@ var onDialogClicked = function(ev) {
}
else if ( ev.target.id === 'quit' ) {
unpreview();
filterToDOMInterface.preview(false);
stopPicker();
}
else if ( ev.target.id === 'preview' ) {
if ( pickerBody.classList.contains('preview') ) {
unpreview();
if ( filterToDOMInterface.previewing() ) {
filterToDOMInterface.preview(false);
} else {
preview(taCandidate.value);
filterToDOMInterface.preview(taCandidate.value);
}
highlightElements(targetElements, true);
}
@ -1068,9 +1272,13 @@ var onSvgHovered = (function() {
var onSvgClicked = function(ev) {
// https://github.com/chrisaljoudi/uBlock/issues/810#issuecomment-74600694
// Unpause picker if user click outside dialog
// Unpause picker if:
// - click outside dialog AND
// - not in preview mode
if ( pickerBody.classList.contains('paused') ) {
unpausePicker();
if ( filterToDOMInterface.previewing() === false ) {
unpausePicker();
}
return;
}
if ( filtersFrom(ev.clientX, ev.clientY) === 0 ) {
@ -1116,7 +1324,7 @@ var pausePicker = function() {
/******************************************************************************/
var unpausePicker = function() {
unpreview();
filterToDOMInterface.preview(false);
pickerBody.classList.remove('paused');
svgListening(true);
};
@ -1127,10 +1335,11 @@ var unpausePicker = function() {
// in use: to ensure this, release all local references.
var stopPicker = function() {
vAPI.shutdown.remove(stopPicker);
targetElements = [];
candidateElements = [];
bestCandidateFilter = null;
previewedElements = [];
if ( pickerRoot === null ) {
return;
@ -1262,24 +1471,25 @@ var startPicker = function(details) {
pickerRoot = document.createElement('iframe');
pickerRoot.id = vAPI.sessionId;
pickerRoot.style.cssText = [
'display: block',
'visibility: visible',
'opacity: 1',
'position: fixed',
'top: 0',
'left: 0',
'width: 100%',
'height: 100%',
'background: transparent',
'margin: 0',
'padding: 0',
'border: 0',
'border-radius: 0',
'box-shadow: none',
'display: block',
'height: 100%',
'left: 0',
'margin: 0',
'max-height: none',
'opacity: 1',
'outline: 0',
'padding: 0',
'position: fixed',
'top: 0',
'visibility: visible',
'width: 100%',
'z-index: 2147483647',
''
].join('!important;');
].join(' !important;');
// https://github.com/gorhill/uBlock/issues/1529
// In addition to inline styles, harden the element picker styles by using
@ -1307,8 +1517,6 @@ pickerRoot.onload = function() {
document.documentElement.appendChild(pickerRoot);
/******************************************************************************/
// https://www.youtube.com/watch?v=sociXdKnyr8
/******************************************************************************/

View file

@ -330,11 +330,15 @@ var matchWhitelistDirective = function(url, hostname, directive) {
/******************************************************************************/
// https://github.com/gorhill/uBlock/issues/2033
// Always set own rules, trying to be fancy to avoid setting seemingly
// (but not really) redundant rules led to this issue.
µBlock.toggleFirewallRule = function(details) {
var requestType = details.requestType;
if ( details.action !== 0 ) {
this.sessionFirewall.setCellZ(details.srcHostname, details.desHostname, requestType, details.action);
this.sessionFirewall.setCell(details.srcHostname, details.desHostname, requestType, details.action);
} else {
this.sessionFirewall.unsetCell(details.srcHostname, details.desHostname, requestType);
}
@ -342,7 +346,7 @@ var matchWhitelistDirective = function(url, hostname, directive) {
// https://github.com/chrisaljoudi/uBlock/issues/731#issuecomment-73937469
if ( details.persist ) {
if ( details.action !== 0 ) {
this.permanentFirewall.setCellZ(details.srcHostname, details.desHostname, requestType, details.action);
this.permanentFirewall.setCell(details.srcHostname, details.desHostname, requestType, details.action);
} else {
this.permanentFirewall.unsetCell(details.srcHostname, details.desHostname, requestType, details.action);
}