mirror of https://github.com/gorhill/uBlock.git
fix #1772: ability to preview procedural cosmetic filters
This commit is contained in:
parent
72d55f4ace
commit
c084853d9a
|
@ -142,25 +142,24 @@ vAPI.setTimeout = vAPI.setTimeout || self.setTimeout.bind(self);
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
vAPI.shutdown = (function() {
|
vAPI.shutdown = {
|
||||||
var jobs = [];
|
jobs: [],
|
||||||
|
add: function(job) {
|
||||||
var add = function(job) {
|
this.jobs.push(job);
|
||||||
jobs.push(job);
|
},
|
||||||
};
|
exec: function() {
|
||||||
|
|
||||||
var exec = function() {
|
|
||||||
var job;
|
var job;
|
||||||
while ( (job = jobs.pop()) ) {
|
while ( (job = this.jobs.pop()) ) {
|
||||||
job();
|
job();
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
remove: function(job) {
|
||||||
return {
|
var pos;
|
||||||
add: add,
|
while ( (pos = this.jobs.indexOf(job)) !== -1 ) {
|
||||||
exec: exec
|
this.jobs.splice(pos, 1);
|
||||||
};
|
}
|
||||||
})();
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
|
@ -123,26 +123,24 @@ vAPI.setTimeout = vAPI.setTimeout || function(callback, delay, extra) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
vAPI.shutdown = (function() {
|
vAPI.shutdown = {
|
||||||
var jobs = [];
|
jobs: [],
|
||||||
|
add: function(job) {
|
||||||
var add = function(job) {
|
this.jobs.push(job);
|
||||||
jobs.push(job);
|
},
|
||||||
};
|
exec: function() {
|
||||||
|
|
||||||
var exec = function() {
|
|
||||||
//console.debug('Shutting down...');
|
|
||||||
var job;
|
var job;
|
||||||
while ( (job = jobs.pop()) ) {
|
while ( (job = this.jobs.pop()) ) {
|
||||||
job();
|
job();
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
remove: function(job) {
|
||||||
return {
|
var pos;
|
||||||
add: add,
|
while ( (pos = this.jobs.indexOf(job)) !== -1 ) {
|
||||||
exec: exec
|
this.jobs.splice(pos, 1);
|
||||||
};
|
}
|
||||||
})();
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
|
|
@ -56,12 +56,14 @@ section {
|
||||||
border: 0;
|
border: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
section > textarea {
|
section > div {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
section > div > textarea {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #aaa;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font: 11px monospace;
|
font: 11px monospace;
|
||||||
height: 6em;
|
height: 6em;
|
||||||
|
@ -70,15 +72,22 @@ section > textarea {
|
||||||
resize: none;
|
resize: none;
|
||||||
width: 100%;
|
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;
|
direction: ltr;
|
||||||
margin: 2px 0;
|
margin: 2px 0;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
section > div > span:last-of-type {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
ul {
|
ul {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
|
@ -137,8 +146,12 @@ svg > path + path {
|
||||||
body.preview svg > path {
|
body.preview svg > path {
|
||||||
fill: rgba(0,0,0,0.10);
|
fill: rgba(0,0,0,0.10);
|
||||||
}
|
}
|
||||||
|
body.preview svg > path + path {
|
||||||
|
stroke: none;
|
||||||
|
}
|
||||||
aside {
|
aside {
|
||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
|
border: 1px solid #aaa;
|
||||||
bottom: 4px;
|
bottom: 4px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
|
@ -162,7 +175,10 @@ body.paused > aside:hover {
|
||||||
<svg><path></path><path></path></svg>
|
<svg><path></path><path></path></svg>
|
||||||
<aside>
|
<aside>
|
||||||
<section>
|
<section>
|
||||||
|
<div>
|
||||||
<textarea lang="en" dir="ltr" spellcheck="false"></textarea>
|
<textarea lang="en" dir="ltr" spellcheck="false"></textarea>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
<div><!--
|
<div><!--
|
||||||
--><button id="preview" type="button">{{preview}}</button><!--
|
--><button id="preview" type="button">{{preview}}</button><!--
|
||||||
--><button id="create" type="button" disabled>{{create}}</button><!--
|
--><button id="create" type="button" disabled>{{create}}</button><!--
|
||||||
|
|
|
@ -21,14 +21,14 @@
|
||||||
|
|
||||||
/* global CSS */
|
/* global CSS */
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
/*! http://mths.be/cssescape v0.2.1 by @mathias | MIT license */
|
/*! http://mths.be/cssescape v0.2.1 by @mathias | MIT license */
|
||||||
;(function(root) {
|
;(function(root) {
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
if (!root.CSS) {
|
if (!root.CSS) {
|
||||||
root.CSS = {};
|
root.CSS = {};
|
||||||
}
|
}
|
||||||
|
@ -116,8 +116,6 @@
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
if ( typeof vAPI !== 'object' ) {
|
if ( typeof vAPI !== 'object' ) {
|
||||||
|
@ -147,7 +145,6 @@ var cosmeticFilterCandidates = [];
|
||||||
var targetElements = [];
|
var targetElements = [];
|
||||||
var candidateElements = [];
|
var candidateElements = [];
|
||||||
var bestCandidateFilter = null;
|
var bestCandidateFilter = null;
|
||||||
var previewedElements = [];
|
|
||||||
|
|
||||||
var lastNetFilterSession = window.location.host + window.location.pathname;
|
var lastNetFilterSession = window.location.host + window.location.pathname;
|
||||||
var lastNetFilterHostname = '';
|
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
|
// https://github.com/gorhill/uBlock/issues/1897
|
||||||
// Ignore `data:` URI, they can't be handled by an HTTP observer.
|
// Ignore `data:` URI, they can't be handled by an HTTP observer.
|
||||||
|
|
||||||
|
@ -692,46 +630,31 @@ var filtersFrom = function(x, y) {
|
||||||
return netFilterCandidates.length + cosmeticFilterCandidates.length;
|
return netFilterCandidates.length + cosmeticFilterCandidates.length;
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/*******************************************************************************
|
||||||
|
|
||||||
var elementsFromFilter = function(filter) {
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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 = [];
|
var out = [];
|
||||||
|
|
||||||
filter = filter.trim();
|
|
||||||
if ( filter === '' ) {
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 ) {
|
|
||||||
try {
|
|
||||||
elems = document.querySelectorAll(filter.slice(2));
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
elems = [];
|
|
||||||
}
|
|
||||||
iElem = elems.length;
|
|
||||||
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
|
// https://github.com/chrisaljoudi/uBlock/issues/945
|
||||||
// Transform into a regular expression, this allows the user to edit and
|
// Transform into a regular expression, this allows the user to edit and
|
||||||
// insert wildcard(s) into the proposed filter
|
// insert wildcard(s) into the proposed filter.
|
||||||
var reStr = '';
|
var reStr = '';
|
||||||
if ( filter.length > 1 && filter.charAt(0) === '/' && filter.slice(-1) === '/' ) {
|
if ( filter.length > 1 && filter.charAt(0) === '/' && filter.slice(-1) === '/' ) {
|
||||||
reStr = filter.slice(1, -1);
|
reStr = filter.slice(1, -1);
|
||||||
|
@ -766,8 +689,9 @@ var elementsFromFilter = function(filter) {
|
||||||
var src1stProps = netFilter1stSources;
|
var src1stProps = netFilter1stSources;
|
||||||
var src2ndProps = netFilter2ndSources;
|
var src2ndProps = netFilter2ndSources;
|
||||||
var srcProp, src;
|
var srcProp, src;
|
||||||
elems = document.querySelectorAll(Object.keys(src1stProps).join());
|
var elems = document.querySelectorAll(Object.keys(src1stProps).join()),
|
||||||
iElem = elems.length;
|
iElem = elems.length,
|
||||||
|
elem;
|
||||||
while ( iElem-- ) {
|
while ( iElem-- ) {
|
||||||
elem = elems[iElem];
|
elem = elems[iElem];
|
||||||
srcProp = src1stProps[elem.localName];
|
srcProp = src1stProps[elem.localName];
|
||||||
|
@ -802,7 +726,277 @@ var elementsFromFilter = function(filter) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
return fromProceduralCosmeticFilter(filter);
|
||||||
|
}
|
||||||
|
var out = [],
|
||||||
|
iElem = elems.length;
|
||||||
|
while ( iElem-- ) {
|
||||||
|
out.push({ type: 'cosmetic', elem: elems[iElem]});
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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 = [];
|
||||||
|
for ( var i = 0, n = elems.length; i < n; i++ ) {
|
||||||
|
if ( document.querySelector(arg) ) {
|
||||||
|
out.push({ type: 'cosmetic', elem: elems[i] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
// https://www.youtube.com/watch?v=nuUXJ6RfIik
|
||||||
|
|
||||||
|
@ -810,8 +1004,8 @@ var elementsFromFilter = function(filter) {
|
||||||
|
|
||||||
var userFilterFromCandidate = function() {
|
var userFilterFromCandidate = function() {
|
||||||
var v = taCandidate.value;
|
var v = taCandidate.value;
|
||||||
var items = elementsFromFilter(v);
|
var items = filterToDOMInterface.set(v);
|
||||||
if ( items.length === 0 ) {
|
if ( !items || items.length === 0 ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -850,13 +1044,18 @@ var userFilterFromCandidate = function() {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var onCandidateChanged = function() {
|
var onCandidateChanged = function() {
|
||||||
unpreview();
|
var elems = [],
|
||||||
|
items = filterToDOMInterface.set(taCandidate.value),
|
||||||
var elems = [];
|
valid = items !== undefined;
|
||||||
var items = elementsFromFilter(taCandidate.value);
|
if ( valid ) {
|
||||||
for ( var i = 0; i < items.length; i++ ) {
|
for ( var i = 0; i < items.length; i++ ) {
|
||||||
elems.push(items[i].elem);
|
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;
|
dialog.querySelector('#create').disabled = elems.length === 0;
|
||||||
highlightElements(elems, true);
|
highlightElements(elems, true);
|
||||||
};
|
};
|
||||||
|
@ -887,16 +1086,21 @@ var candidateFromFilterChoice = function(filterChoice) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return path: the target element, then all siblings prepended
|
// Return path: the target element, then all siblings prepended
|
||||||
var selector = [];
|
var selector = '', joiner = '';
|
||||||
for ( ; slot < filters.length; slot++ ) {
|
for ( ; slot < filters.length; slot++ ) {
|
||||||
filter = filters[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
|
// 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;
|
break;
|
||||||
}
|
}
|
||||||
|
// Stop if current selector matches only one element on the page
|
||||||
|
if ( document.querySelectorAll(selector).length === 1 ) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return '##' + selector.join(' > ');
|
joiner = ' > ';
|
||||||
|
}
|
||||||
|
return '##' + selector;
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -926,8 +1130,7 @@ var onDialogClicked = function(ev) {
|
||||||
else if ( ev.target.id === 'create' ) {
|
else if ( ev.target.id === 'create' ) {
|
||||||
// We have to exit from preview mode: this guarantees matching elements
|
// We have to exit from preview mode: this guarantees matching elements
|
||||||
// will be found for the candidate filter.
|
// will be found for the candidate filter.
|
||||||
unpreview();
|
filterToDOMInterface.preview(false);
|
||||||
|
|
||||||
var filter = userFilterFromCandidate();
|
var filter = userFilterFromCandidate();
|
||||||
if ( filter ) {
|
if ( filter ) {
|
||||||
var d = new Date();
|
var d = new Date();
|
||||||
|
@ -939,7 +1142,7 @@ var onDialogClicked = function(ev) {
|
||||||
pageDomain: window.location.hostname
|
pageDomain: window.location.hostname
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
filterElements(taCandidate.value);
|
filterToDOMInterface.preview(taCandidate.value);
|
||||||
stopPicker();
|
stopPicker();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -949,15 +1152,15 @@ var onDialogClicked = function(ev) {
|
||||||
}
|
}
|
||||||
|
|
||||||
else if ( ev.target.id === 'quit' ) {
|
else if ( ev.target.id === 'quit' ) {
|
||||||
unpreview();
|
filterToDOMInterface.preview(false);
|
||||||
stopPicker();
|
stopPicker();
|
||||||
}
|
}
|
||||||
|
|
||||||
else if ( ev.target.id === 'preview' ) {
|
else if ( ev.target.id === 'preview' ) {
|
||||||
if ( pickerBody.classList.contains('preview') ) {
|
if ( filterToDOMInterface.previewing() ) {
|
||||||
unpreview();
|
filterToDOMInterface.preview(false);
|
||||||
} else {
|
} else {
|
||||||
preview(taCandidate.value);
|
filterToDOMInterface.preview(taCandidate.value);
|
||||||
}
|
}
|
||||||
highlightElements(targetElements, true);
|
highlightElements(targetElements, true);
|
||||||
}
|
}
|
||||||
|
@ -1068,9 +1271,13 @@ var onSvgHovered = (function() {
|
||||||
|
|
||||||
var onSvgClicked = function(ev) {
|
var onSvgClicked = function(ev) {
|
||||||
// https://github.com/chrisaljoudi/uBlock/issues/810#issuecomment-74600694
|
// 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') ) {
|
if ( pickerBody.classList.contains('paused') ) {
|
||||||
|
if ( filterToDOMInterface.previewing() === false ) {
|
||||||
unpausePicker();
|
unpausePicker();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ( filtersFrom(ev.clientX, ev.clientY) === 0 ) {
|
if ( filtersFrom(ev.clientX, ev.clientY) === 0 ) {
|
||||||
|
@ -1116,7 +1323,7 @@ var pausePicker = function() {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var unpausePicker = function() {
|
var unpausePicker = function() {
|
||||||
unpreview();
|
filterToDOMInterface.preview(false);
|
||||||
pickerBody.classList.remove('paused');
|
pickerBody.classList.remove('paused');
|
||||||
svgListening(true);
|
svgListening(true);
|
||||||
};
|
};
|
||||||
|
@ -1127,10 +1334,11 @@ var unpausePicker = function() {
|
||||||
// in use: to ensure this, release all local references.
|
// in use: to ensure this, release all local references.
|
||||||
|
|
||||||
var stopPicker = function() {
|
var stopPicker = function() {
|
||||||
|
vAPI.shutdown.remove(stopPicker);
|
||||||
|
|
||||||
targetElements = [];
|
targetElements = [];
|
||||||
candidateElements = [];
|
candidateElements = [];
|
||||||
bestCandidateFilter = null;
|
bestCandidateFilter = null;
|
||||||
previewedElements = [];
|
|
||||||
|
|
||||||
if ( pickerRoot === null ) {
|
if ( pickerRoot === null ) {
|
||||||
return;
|
return;
|
||||||
|
@ -1262,24 +1470,25 @@ var startPicker = function(details) {
|
||||||
pickerRoot = document.createElement('iframe');
|
pickerRoot = document.createElement('iframe');
|
||||||
pickerRoot.id = vAPI.sessionId;
|
pickerRoot.id = vAPI.sessionId;
|
||||||
pickerRoot.style.cssText = [
|
pickerRoot.style.cssText = [
|
||||||
'display: block',
|
|
||||||
'visibility: visible',
|
|
||||||
'opacity: 1',
|
|
||||||
'position: fixed',
|
|
||||||
'top: 0',
|
|
||||||
'left: 0',
|
|
||||||
'width: 100%',
|
|
||||||
'height: 100%',
|
|
||||||
'background: transparent',
|
'background: transparent',
|
||||||
'margin: 0',
|
|
||||||
'padding: 0',
|
|
||||||
'border: 0',
|
'border: 0',
|
||||||
'border-radius: 0',
|
'border-radius: 0',
|
||||||
'box-shadow: none',
|
'box-shadow: none',
|
||||||
|
'display: block',
|
||||||
|
'height: 100%',
|
||||||
|
'left: 0',
|
||||||
|
'margin: 0',
|
||||||
|
'max-height: none',
|
||||||
|
'opacity: 1',
|
||||||
'outline: 0',
|
'outline: 0',
|
||||||
|
'padding: 0',
|
||||||
|
'position: fixed',
|
||||||
|
'top: 0',
|
||||||
|
'visibility: visible',
|
||||||
|
'width: 100%',
|
||||||
'z-index: 2147483647',
|
'z-index: 2147483647',
|
||||||
''
|
''
|
||||||
].join('!important;');
|
].join(' !important;');
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/1529
|
// https://github.com/gorhill/uBlock/issues/1529
|
||||||
// In addition to inline styles, harden the element picker styles by using
|
// In addition to inline styles, harden the element picker styles by using
|
||||||
|
@ -1307,8 +1516,6 @@ pickerRoot.onload = function() {
|
||||||
|
|
||||||
document.documentElement.appendChild(pickerRoot);
|
document.documentElement.appendChild(pickerRoot);
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
// https://www.youtube.com/watch?v=sociXdKnyr8
|
// https://www.youtube.com/watch?v=sociXdKnyr8
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
Loading…
Reference in New Issue