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
|
||||
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(),
|
||||
allSelectors = createSet(),
|
||||
stagedNodes = [],
|
||||
matchesProp = vAPI.matchesProp,
|
||||
userCSS = vAPI.userCSS;
|
||||
matchesProp = vAPI.matchesProp;
|
||||
|
||||
// Complex selectors, due to their nature may need to be "de-committed". A
|
||||
// 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
|
||||
// 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 = {
|
||||
addedNodesHandlerMissCount: 0,
|
||||
removedNodesHandlerMissCount: 0,
|
||||
commitTimer: null,
|
||||
disabledId: vAPI.randomToken(),
|
||||
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() {
|
||||
this.commitTimer.clear();
|
||||
|
||||
|
@ -538,7 +555,7 @@ var domFilterer = {
|
|||
}
|
||||
|
||||
if ( styleText !== '' ) {
|
||||
this.addStyleTag(styleText);
|
||||
platformUserCSS.add(styleText);
|
||||
}
|
||||
|
||||
// Un-hide nodes previously hidden.
|
||||
|
@ -623,19 +640,17 @@ var domFilterer = {
|
|||
},
|
||||
|
||||
toggleOff: function() {
|
||||
if ( userCSS ) {
|
||||
userCSS.toggle(false);
|
||||
}
|
||||
platformUserCSS.toggle(false);
|
||||
this.enabled = false;
|
||||
},
|
||||
|
||||
toggleOn: function() {
|
||||
if ( userCSS ) {
|
||||
userCSS.toggle(true);
|
||||
}
|
||||
platformUserCSS.toggle(true);
|
||||
this.enabled = true;
|
||||
},
|
||||
|
||||
userCSS: platformUserCSS,
|
||||
|
||||
unhideNode: function(node) {
|
||||
if ( node[this.hiddenId] !== undefined ) {
|
||||
this.hiddenNodeCount--;
|
||||
|
@ -651,13 +666,8 @@ var domFilterer = {
|
|||
platformHideNode(node);
|
||||
},
|
||||
|
||||
domChangedHandler: function(addedNodes, removedNodes) {
|
||||
domChangedHandler: function(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() {
|
||||
|
@ -818,9 +828,9 @@ vAPI.domWatcher = (function() {
|
|||
}
|
||||
addedNodeLists.length = 0;
|
||||
if ( addedNodes.length !== 0 || removedNodes ) {
|
||||
listeners[0](addedNodes, removedNodes);
|
||||
listeners[0](addedNodes);
|
||||
if ( listeners[1] ) {
|
||||
listeners[1](addedNodes, removedNodes);
|
||||
listeners[1](addedNodes);
|
||||
}
|
||||
addedNodes.length = 0;
|
||||
removedNodes = false;
|
||||
|
@ -1485,19 +1495,16 @@ vAPI.domSurveyor = (function() {
|
|||
surveyPhase2(addedNodes);
|
||||
};
|
||||
|
||||
var domChangedHandler = function(addedNodes, removedNodes) {
|
||||
var domChangedHandler = function(addedNodes) {
|
||||
if ( cosmeticSurveyingMissCount > 255 ) {
|
||||
vAPI.domWatcher.removeListener(domChangedHandler);
|
||||
vAPI.domSurveyor = null;
|
||||
domFilterer.domChangedHandler(addedNodes, removedNodes);
|
||||
domFilterer.domChangedHandler(addedNodes);
|
||||
domFilterer.start();
|
||||
return;
|
||||
}
|
||||
|
||||
surveyPhase1(addedNodes);
|
||||
if ( removedNodes ) {
|
||||
domFilterer.checkStyleTags();
|
||||
}
|
||||
};
|
||||
|
||||
var start = function() {
|
||||
|
@ -1535,11 +1542,6 @@ vAPI.domIsLoaded = function(ev) {
|
|||
vAPI.domCollapser.start();
|
||||
|
||||
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
|
||||
// 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
|
||||
|
|
|
@ -28,24 +28,12 @@
|
|||
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 = [];
|
||||
try {
|
||||
elems = document.querySelectorAll('[' + vAPI.domFilterer.hiddenId + ']');
|
||||
} catch (e) {
|
||||
}
|
||||
i = elems.length;
|
||||
var i = elems.length;
|
||||
while ( i-- ) {
|
||||
vAPI.domFilterer.showNode(elems[i]);
|
||||
}
|
||||
|
|
|
@ -28,24 +28,12 @@
|
|||
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 = [];
|
||||
try {
|
||||
elems = document.querySelectorAll('[' + vAPI.domFilterer.hiddenId + ']');
|
||||
} catch (e) {
|
||||
}
|
||||
i = elems.length;
|
||||
var i = elems.length;
|
||||
while ( i-- ) {
|
||||
vAPI.domFilterer.unshowNode(elems[i]);
|
||||
}
|
||||
|
|
|
@ -686,10 +686,6 @@ var cosmeticFilterMapper = (function() {
|
|||
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 filterMap = nodeToCosmeticFilterMap,
|
||||
selectors, selector,
|
||||
|
@ -741,16 +737,7 @@ var cosmeticFilterMapper = (function() {
|
|||
};
|
||||
|
||||
var incremental = function(rootNode) {
|
||||
var styleTags = vAPI.domFilterer.styleTags || [];
|
||||
var styleTag;
|
||||
var i = styleTags.length;
|
||||
while ( i-- ) {
|
||||
styleTag = styleTags[i];
|
||||
if ( styleTag.sheet !== null ) {
|
||||
styleTag.sheet.disabled = true;
|
||||
styleTag[vAPI.sessionId] = true;
|
||||
}
|
||||
}
|
||||
vAPI.domFilterer.userCSS.toggle(false);
|
||||
nodesFromStyleTag(rootNode);
|
||||
};
|
||||
|
||||
|
@ -760,16 +747,7 @@ var cosmeticFilterMapper = (function() {
|
|||
};
|
||||
|
||||
var shutdown = function() {
|
||||
var styleTags = vAPI.domFilterer.styleTags || [];
|
||||
var styleTag;
|
||||
var i = styleTags.length;
|
||||
while ( i-- ) {
|
||||
styleTag = styleTags[i];
|
||||
if ( styleTag.sheet !== null ) {
|
||||
styleTag.sheet.disabled = false;
|
||||
styleTag[vAPI.sessionId] = undefined;
|
||||
}
|
||||
}
|
||||
vAPI.domFilterer.userCSS.toggle(true);
|
||||
};
|
||||
|
||||
return {
|
||||
|
|
Loading…
Reference in New Issue