mirror of https://github.com/gorhill/uBlock.git
better abstraction of user styles
This commit is contained in:
parent
34b359aa92
commit
c39adacc50
|
@ -66,6 +66,17 @@
|
||||||
Additionally, the domSurveyor can turn itself off once it decides that
|
Additionally, the domSurveyor can turn itself off once it decides that
|
||||||
it has become pointless (repeatedly not finding new cosmetic filters).
|
it has become pointless (repeatedly not finding new cosmetic filters).
|
||||||
|
|
||||||
|
The domFilterer makes use of platform-dependent user styles[1] code, or
|
||||||
|
provide a default generic implementation if none is present.
|
||||||
|
At time of writing, only modern Firefox provides a custom implementation,
|
||||||
|
which makes for solid, reliable and low overhead cosmetic filtering on
|
||||||
|
Firefox.
|
||||||
|
The generic implementation[2] performs as best as can be, but won't ever be
|
||||||
|
as reliable as real user styles.
|
||||||
|
[1] "user styles" refer to local CSS rules which have priority over, and
|
||||||
|
can't be overriden by a web page's own CSS rules.
|
||||||
|
[2] below, see platformUserCSS / platformHideNode / platformUnhideNode
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -138,8 +149,7 @@ var reParserEx = /:(?:has|matches-css|matches-css-before|matches-css-after|style
|
||||||
var allExceptions = createSet(),
|
var allExceptions = createSet(),
|
||||||
allSelectors = createSet(),
|
allSelectors = createSet(),
|
||||||
stagedNodes = [],
|
stagedNodes = [],
|
||||||
matchesProp = vAPI.matchesProp,
|
matchesProp = vAPI.matchesProp;
|
||||||
userCSS = vAPI.userCSS;
|
|
||||||
|
|
||||||
// Complex selectors, due to their nature may need to be "de-committed". A
|
// Complex selectors, due to their nature may need to be "de-committed". A
|
||||||
// Set() is used to implement this functionality.
|
// Set() is used to implement this functionality.
|
||||||
|
@ -161,6 +171,62 @@ var cosmeticFiltersActivated = function() {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
// If a platform does not support its own vAPI.userCSS (user styles), we
|
||||||
|
// provide a default (imperfect) implementation.
|
||||||
|
|
||||||
|
// Probably no longer need to watch for style tags removal/tampering with fix
|
||||||
|
// to https://github.com/gorhill/uBlock/issues/963
|
||||||
|
|
||||||
|
var platformUserCSS = (function() {
|
||||||
|
if ( vAPI.userCSS instanceof Object ) {
|
||||||
|
return vAPI.userCSS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
enabled: true,
|
||||||
|
styles: [],
|
||||||
|
add: function(css) {
|
||||||
|
var style = document.createElement('style');
|
||||||
|
style.setAttribute('type', 'text/css');
|
||||||
|
style.textContent = css;
|
||||||
|
if ( document.head ) {
|
||||||
|
document.head.appendChild(style);
|
||||||
|
}
|
||||||
|
this.styles.push(style);
|
||||||
|
if ( style.sheet ) {
|
||||||
|
style.sheet.disabled = !this.enabled;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
remove: function(css) {
|
||||||
|
var i = this.styles.length,
|
||||||
|
style, parent;
|
||||||
|
while ( i-- ) {
|
||||||
|
style = this.styles[i];
|
||||||
|
if ( style.textContent !== css ) { continue; }
|
||||||
|
parent = style.parentNode;
|
||||||
|
if ( parent !== null ) {
|
||||||
|
parent.removeChild(style);
|
||||||
|
}
|
||||||
|
this.styles.splice(i, 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
toggle: function(state) {
|
||||||
|
if ( this.styles.length === '' ) { return; }
|
||||||
|
if ( state === undefined ) {
|
||||||
|
state = !this.enabled;
|
||||||
|
}
|
||||||
|
var i = this.styles.length, style;
|
||||||
|
while ( i-- ) {
|
||||||
|
style = this.styles[i];
|
||||||
|
if ( style.sheet !== null ) {
|
||||||
|
style.sheet.disabled = !state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.enabled = state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
// If a platform does not provide its own (improved) vAPI.hideNode, we assign
|
// If a platform does not provide its own (improved) vAPI.hideNode, we assign
|
||||||
// a default one to try to override author styles as best as can be.
|
// a default one to try to override author styles as best as can be.
|
||||||
|
|
||||||
|
@ -344,7 +410,6 @@ var runXpathJob = function(job, fn) {
|
||||||
|
|
||||||
var domFilterer = {
|
var domFilterer = {
|
||||||
addedNodesHandlerMissCount: 0,
|
addedNodesHandlerMissCount: 0,
|
||||||
removedNodesHandlerMissCount: 0,
|
|
||||||
commitTimer: null,
|
commitTimer: null,
|
||||||
disabledId: vAPI.randomToken(),
|
disabledId: vAPI.randomToken(),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
@ -428,54 +493,6 @@ var domFilterer = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
addStyleTag: function(text) {
|
|
||||||
var styleTag = document.createElement('style');
|
|
||||||
styleTag.setAttribute('type', 'text/css');
|
|
||||||
styleTag.textContent = text;
|
|
||||||
if ( document.head ) {
|
|
||||||
document.head.appendChild(styleTag);
|
|
||||||
}
|
|
||||||
this.styleTags.push(styleTag);
|
|
||||||
if ( userCSS ) {
|
|
||||||
userCSS.add(text);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
checkStyleTags_: function() {
|
|
||||||
var doc = document,
|
|
||||||
html = doc.documentElement,
|
|
||||||
head = doc.head,
|
|
||||||
newParent = head || html;
|
|
||||||
if ( newParent === null ) { return; }
|
|
||||||
this.removedNodesHandlerMissCount += 1;
|
|
||||||
var styles = this.styleTags,
|
|
||||||
style, oldParent;
|
|
||||||
for ( var i = 0; i < styles.length; i++ ) {
|
|
||||||
style = styles[i];
|
|
||||||
oldParent = style.parentNode;
|
|
||||||
// https://github.com/gorhill/uBlock/issues/1031
|
|
||||||
// If our style tag was disabled, re-insert into the page.
|
|
||||||
if (
|
|
||||||
style.disabled &&
|
|
||||||
oldParent !== null &&
|
|
||||||
style.hasAttribute(this.disabledId) === false
|
|
||||||
) {
|
|
||||||
oldParent.removeChild(style);
|
|
||||||
oldParent = null;
|
|
||||||
}
|
|
||||||
if ( oldParent === head || oldParent === html ) { continue; }
|
|
||||||
style.disabled = false;
|
|
||||||
newParent.appendChild(style);
|
|
||||||
this.removedNodesHandlerMissCount = 0;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
checkStyleTags: function() {
|
|
||||||
if ( this.removedNodesHandlerMissCount < 16 ) {
|
|
||||||
this.checkStyleTags_();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
commit_: function() {
|
commit_: function() {
|
||||||
this.commitTimer.clear();
|
this.commitTimer.clear();
|
||||||
|
|
||||||
|
@ -538,7 +555,7 @@ var domFilterer = {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( styleText !== '' ) {
|
if ( styleText !== '' ) {
|
||||||
this.addStyleTag(styleText);
|
platformUserCSS.add(styleText);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Un-hide nodes previously hidden.
|
// Un-hide nodes previously hidden.
|
||||||
|
@ -623,19 +640,17 @@ var domFilterer = {
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleOff: function() {
|
toggleOff: function() {
|
||||||
if ( userCSS ) {
|
platformUserCSS.toggle(false);
|
||||||
userCSS.toggle(false);
|
|
||||||
}
|
|
||||||
this.enabled = false;
|
this.enabled = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleOn: function() {
|
toggleOn: function() {
|
||||||
if ( userCSS ) {
|
platformUserCSS.toggle(true);
|
||||||
userCSS.toggle(true);
|
|
||||||
}
|
|
||||||
this.enabled = true;
|
this.enabled = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
userCSS: platformUserCSS,
|
||||||
|
|
||||||
unhideNode: function(node) {
|
unhideNode: function(node) {
|
||||||
if ( node[this.hiddenId] !== undefined ) {
|
if ( node[this.hiddenId] !== undefined ) {
|
||||||
this.hiddenNodeCount--;
|
this.hiddenNodeCount--;
|
||||||
|
@ -651,13 +666,8 @@ var domFilterer = {
|
||||||
platformHideNode(node);
|
platformHideNode(node);
|
||||||
},
|
},
|
||||||
|
|
||||||
domChangedHandler: function(addedNodes, removedNodes) {
|
domChangedHandler: function(addedNodes) {
|
||||||
this.commit(addedNodes);
|
this.commit(addedNodes);
|
||||||
// https://github.com/gorhill/uBlock/issues/873
|
|
||||||
// This will ensure our style elements will stay in the DOM.
|
|
||||||
if ( removedNodes ) {
|
|
||||||
domFilterer.checkStyleTags();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
start: function() {
|
start: function() {
|
||||||
|
@ -818,9 +828,9 @@ vAPI.domWatcher = (function() {
|
||||||
}
|
}
|
||||||
addedNodeLists.length = 0;
|
addedNodeLists.length = 0;
|
||||||
if ( addedNodes.length !== 0 || removedNodes ) {
|
if ( addedNodes.length !== 0 || removedNodes ) {
|
||||||
listeners[0](addedNodes, removedNodes);
|
listeners[0](addedNodes);
|
||||||
if ( listeners[1] ) {
|
if ( listeners[1] ) {
|
||||||
listeners[1](addedNodes, removedNodes);
|
listeners[1](addedNodes);
|
||||||
}
|
}
|
||||||
addedNodes.length = 0;
|
addedNodes.length = 0;
|
||||||
removedNodes = false;
|
removedNodes = false;
|
||||||
|
@ -1485,19 +1495,16 @@ vAPI.domSurveyor = (function() {
|
||||||
surveyPhase2(addedNodes);
|
surveyPhase2(addedNodes);
|
||||||
};
|
};
|
||||||
|
|
||||||
var domChangedHandler = function(addedNodes, removedNodes) {
|
var domChangedHandler = function(addedNodes) {
|
||||||
if ( cosmeticSurveyingMissCount > 255 ) {
|
if ( cosmeticSurveyingMissCount > 255 ) {
|
||||||
vAPI.domWatcher.removeListener(domChangedHandler);
|
vAPI.domWatcher.removeListener(domChangedHandler);
|
||||||
vAPI.domSurveyor = null;
|
vAPI.domSurveyor = null;
|
||||||
domFilterer.domChangedHandler(addedNodes, removedNodes);
|
domFilterer.domChangedHandler(addedNodes);
|
||||||
domFilterer.start();
|
domFilterer.start();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
surveyPhase1(addedNodes);
|
surveyPhase1(addedNodes);
|
||||||
if ( removedNodes ) {
|
|
||||||
domFilterer.checkStyleTags();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var start = function() {
|
var start = function() {
|
||||||
|
@ -1535,11 +1542,6 @@ vAPI.domIsLoaded = function(ev) {
|
||||||
vAPI.domCollapser.start();
|
vAPI.domCollapser.start();
|
||||||
|
|
||||||
if ( vAPI.domFilterer ) {
|
if ( vAPI.domFilterer ) {
|
||||||
// https://github.com/chrisaljoudi/uBlock/issues/789
|
|
||||||
// https://github.com/gorhill/uBlock/issues/873
|
|
||||||
// Be sure our style tags used for cosmetic filtering are still
|
|
||||||
// applied.
|
|
||||||
vAPI.domFilterer.checkStyleTags();
|
|
||||||
// To avoid neddless CPU overhead, we commit existing cosmetic filters
|
// To avoid neddless CPU overhead, we commit existing cosmetic filters
|
||||||
// only if the page loaded "slowly", i.e. if the code here had to wait
|
// only if the page loaded "slowly", i.e. if the code here had to wait
|
||||||
// for a DOMContentLoaded event -- in which case the DOM may have
|
// for a DOMContentLoaded event -- in which case the DOM may have
|
||||||
|
|
|
@ -28,24 +28,12 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var styles = vAPI.domFilterer.styleTags;
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var elems = [];
|
var elems = [];
|
||||||
try {
|
try {
|
||||||
elems = document.querySelectorAll('[' + vAPI.domFilterer.hiddenId + ']');
|
elems = document.querySelectorAll('[' + vAPI.domFilterer.hiddenId + ']');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
}
|
}
|
||||||
i = elems.length;
|
var i = elems.length;
|
||||||
while ( i-- ) {
|
while ( i-- ) {
|
||||||
vAPI.domFilterer.showNode(elems[i]);
|
vAPI.domFilterer.showNode(elems[i]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,24 +28,12 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert all cosmetic filtering-related style tags in the DOM
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var elems = [];
|
var elems = [];
|
||||||
try {
|
try {
|
||||||
elems = document.querySelectorAll('[' + vAPI.domFilterer.hiddenId + ']');
|
elems = document.querySelectorAll('[' + vAPI.domFilterer.hiddenId + ']');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
}
|
}
|
||||||
i = elems.length;
|
var i = elems.length;
|
||||||
while ( i-- ) {
|
while ( i-- ) {
|
||||||
vAPI.domFilterer.unshowNode(elems[i]);
|
vAPI.domFilterer.unshowNode(elems[i]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -686,10 +686,6 @@ var cosmeticFilterMapper = (function() {
|
||||||
matchesFnName = 'webkitMatchesSelector';
|
matchesFnName = 'webkitMatchesSelector';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Why the call to hideNode()?
|
|
||||||
// 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(rootNode) {
|
var nodesFromStyleTag = function(rootNode) {
|
||||||
var filterMap = nodeToCosmeticFilterMap,
|
var filterMap = nodeToCosmeticFilterMap,
|
||||||
selectors, selector,
|
selectors, selector,
|
||||||
|
@ -741,16 +737,7 @@ var cosmeticFilterMapper = (function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
var incremental = function(rootNode) {
|
var incremental = function(rootNode) {
|
||||||
var styleTags = vAPI.domFilterer.styleTags || [];
|
vAPI.domFilterer.userCSS.toggle(false);
|
||||||
var styleTag;
|
|
||||||
var i = styleTags.length;
|
|
||||||
while ( i-- ) {
|
|
||||||
styleTag = styleTags[i];
|
|
||||||
if ( styleTag.sheet !== null ) {
|
|
||||||
styleTag.sheet.disabled = true;
|
|
||||||
styleTag[vAPI.sessionId] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nodesFromStyleTag(rootNode);
|
nodesFromStyleTag(rootNode);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -760,16 +747,7 @@ var cosmeticFilterMapper = (function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
var shutdown = function() {
|
var shutdown = function() {
|
||||||
var styleTags = vAPI.domFilterer.styleTags || [];
|
vAPI.domFilterer.userCSS.toggle(true);
|
||||||
var styleTag;
|
|
||||||
var i = styleTags.length;
|
|
||||||
while ( i-- ) {
|
|
||||||
styleTag = styleTags[i];
|
|
||||||
if ( styleTag.sheet !== null ) {
|
|
||||||
styleTag.sheet.disabled = false;
|
|
||||||
styleTag[vAPI.sessionId] = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
Loading…
Reference in New Issue