diff --git a/platform/firefox/vapi-background.js b/platform/firefox/vapi-background.js
index 324651d97..52485c6fd 100644
--- a/platform/firefox/vapi-background.js
+++ b/platform/firefox/vapi-background.js
@@ -42,6 +42,10 @@ vAPI.firefox = true;
vAPI.fennec = Services.appinfo.ID === '{aa3c5121-dab2-40e2-81ca-7ea25febc110}';
vAPI.thunderbird = Services.appinfo.ID === '{3550f703-e582-4d05-9a08-453d09bdfdc6}';
+if ( vAPI.fennec ) {
+ vAPI.battery = true;
+}
+
/******************************************************************************/
var deferUntil = function(testFn, mainFn, details) {
diff --git a/src/3p-filters.html b/src/3p-filters.html
index 5d378d4e5..8bce9d01a 100644
--- a/src/3p-filters.html
+++ b/src/3p-filters.html
@@ -19,9 +19,12 @@
-
-
-
+
+
+
diff --git a/src/js/3p-filters.js b/src/js/3p-filters.js
index 03dc77548..54bfda204 100644
--- a/src/js/3p-filters.js
+++ b/src/js/3p-filters.js
@@ -31,7 +31,8 @@
var userListName = vAPI.i18n('1pPageName');
var listDetails = {};
-var cosmeticSwitch = true;
+var parseCosmeticFilters = true;
+var ignoreGenericCosmeticFilters = false;
var externalLists = '';
var cacheWasPurged = false;
var needUpdate = false;
@@ -216,7 +217,8 @@ var renderFilterLists = function() {
var onListsReceived = function(details) {
// Before all, set context vars
listDetails = details;
- cosmeticSwitch = details.cosmetic;
+ parseCosmeticFilters = details.parseCosmeticFilters;
+ ignoreGenericCosmeticFilters = details.ignoreGenericCosmeticFilters;
needUpdate = false;
hasCachedContent = false;
@@ -257,7 +259,8 @@ var renderFilterLists = function() {
.replace('{{cosmeticFilterCount}}', renderNumber(details.cosmeticFilterCount))
);
uDom('#autoUpdate').prop('checked', listDetails.autoUpdate === true);
- uDom('#parseCosmeticFilters').prop('checked', listDetails.cosmetic === true);
+ uDom('#parseCosmeticFilters').prop('checked', listDetails.parseCosmeticFilters === true);
+ uDom('#ignoreGenericCosmeticFilters').prop('checked', listDetails.ignoreGenericCosmeticFilters === true);
renderWidgets();
renderBusyOverlay(details.manualUpdate, details.manualUpdateProgress);
@@ -302,7 +305,10 @@ var renderWidgets = function() {
// Return whether selection of lists changed.
var listsSelectionChanged = function() {
- if ( listDetails.cosmetic !== cosmeticSwitch ) {
+ if (
+ listDetails.parseCosmeticFilters !== parseCosmeticFilters ||
+ listDetails.parseCosmeticFilters && listDetails.ignoreGenericCosmeticFilters !== ignoreGenericCosmeticFilters
+ ) {
return true;
}
@@ -401,14 +407,16 @@ var onPurgeClicked = function() {
var selectFilterLists = function(callback) {
// Cosmetic filtering switch
- messaging.send(
- 'dashboard',
- {
- what: 'userSettings',
- name: 'parseAllABPHideFilters',
- value: listDetails.cosmetic
- }
- );
+ messaging.send('dashboard', {
+ what: 'userSettings',
+ name: 'parseAllABPHideFilters',
+ value: listDetails.parseCosmeticFilters
+ });
+ messaging.send('dashboard', {
+ what: 'userSettings',
+ name: 'ignoreGenericCosmeticFilters',
+ value: listDetails.ignoreGenericCosmeticFilters
+ });
// Filter lists
var switches = [];
@@ -497,7 +505,8 @@ var autoUpdateCheckboxChanged = function() {
/******************************************************************************/
var cosmeticSwitchChanged = function() {
- listDetails.cosmetic = this.checked;
+ listDetails.parseCosmeticFilters = uDom.nodeFromId('parseCosmeticFilters').checked;
+ listDetails.ignoreGenericCosmeticFilters = uDom.nodeFromId('ignoreGenericCosmeticFilters').checked;
renderWidgets();
};
@@ -556,6 +565,7 @@ var groupEntryClickHandler = function() {
var getCloudData = function() {
var bin = {
parseCosmeticFilters: uDom.nodeFromId('parseCosmeticFilters').checked,
+ ignoreGenericCosmeticFilters: uDom.nodeFromId('ignoreGenericCosmeticFilters').checked,
selectedLists: [],
externalLists: externalLists
};
@@ -580,9 +590,12 @@ var setCloudData = function(data, append) {
var elem, checked;
elem = uDom.nodeFromId('parseCosmeticFilters');
- checked = data.parseCosmeticFilters === true ||
- append && elem.checked;
- elem.checked = listDetails.cosmetic = checked;
+ checked = data.parseCosmeticFilters === true || append && elem.checked;
+ elem.checked = listDetails.parseCosmeticFilters = checked;
+
+ elem = uDom.nodeFromId('ignoreGenericCosmeticFilters');
+ checked = data.ignoreGenericCosmeticFilters === true || append && elem.checked;
+ elem.checked = listDetails.ignoreGenericCosmeticFilters = checked;
var lis = uDom('#lists .listEntry'), li, listKey;
var i = lis.length;
@@ -613,6 +626,7 @@ self.cloud.onPull = setCloudData;
uDom('#autoUpdate').on('change', autoUpdateCheckboxChanged);
uDom('#parseCosmeticFilters').on('change', cosmeticSwitchChanged);
+uDom('#ignoreGenericCosmeticFilters').on('change', cosmeticSwitchChanged);
uDom('#buttonApply').on('click', buttonApplyHandler);
uDom('#buttonUpdate').on('click', buttonUpdateHandler);
uDom('#buttonPurgeAll').on('click', buttonPurgeAllHandler);
diff --git a/src/js/background.js b/src/js/background.js
index d0d92f754..bd5d07003 100644
--- a/src/js/background.js
+++ b/src/js/background.js
@@ -59,6 +59,7 @@ return {
externalLists: defaultExternalLists,
firewallPaneMinimized: true,
hyperlinkAuditingDisabled: true,
+ ignoreGenericCosmeticFilters: false,
largeMediaSize: 50,
parseAllABPHideFilters: true,
prefetchingDisabled: true,
@@ -92,7 +93,7 @@ return {
// read-only
systemSettings: {
compiledMagic: 'splsmclwnvoj',
- selfieMagic: 'splsmclwnvoj'
+ selfieMagic: 'rkzqonintytj'
},
restoreBackupSettings: {
diff --git a/src/js/contentscript.js b/src/js/contentscript.js
index 4fa8a34a4..ae813bc7f 100644
--- a/src/js/contentscript.js
+++ b/src/js/contentscript.js
@@ -280,7 +280,8 @@ var runXpathJob = function(job, fn) {
/******************************************************************************/
var domFilterer = {
- commitMissCount: 0,
+ addedNodesHandlerMissCount: 0,
+ removedNodesHandlerMissCount: 0,
disabledId: vAPI.randomToken(),
enabled: true,
hiddenId: vAPI.randomToken(),
@@ -387,7 +388,7 @@ var domFilterer = {
mustCommit = true;
}
if ( mustCommit && commitIfNeeded ) {
- this.commit();
+ this.commit('all');
}
return mustCommit;
},
@@ -397,6 +398,8 @@ var domFilterer = {
return;
}
+ vAPI.executionCost.start();
+
var beforeHiddenNodeCount = this.hiddenNodeCount,
styleText = '', i, n;
@@ -452,9 +455,9 @@ var domFilterer = {
var commitHit = this.hiddenNodeCount !== beforeHiddenNodeCount;
if ( commitHit ) {
- this.commitMissCount = 0;
+ this.addedNodesHandlerMissCount = 0;
} else {
- this.commitMissCount += 1;
+ this.addedNodesHandlerMissCount += 1;
}
// Un-hide nodes previously hidden.
@@ -478,11 +481,13 @@ var domFilterer = {
503
);
}
+
+ vAPI.executionCost.stop('domFilterer.commit_');
},
commit: function(nodes, commitNow) {
var firstCommit = stagedNodes.length === 0;
- if ( nodes === undefined ) {
+ if ( nodes === 'all' ) {
stagedNodes = [ document.documentElement ];
} else if ( stagedNodes[0] !== document.documentElement ) {
stagedNodes = stagedNodes.concat(nodes);
@@ -601,6 +606,29 @@ var domFilterer = {
if ( shadow && shadow[shadowId] && shadow.firstElementChild !== null ) {
shadow.removeChild(shadow.firstElementChild);
}
+ },
+
+ domChangedHandler: function(addedNodes, removedNodes) {
+ if ( addedNodes.length !== 0 ) {
+ this.commit(addedNodes);
+ }
+ // https://github.com/gorhill/uBlock/issues/873
+ // This will ensure our style elements will stay in the DOM.
+ if ( removedNodes && this.removedNodesHandlerMissCount < 16 ) {
+ if ( domFilterer.checkStyleTags(true) ) {
+ this.removedNodesHandlerMissCount = 0;
+ } else {
+ this.removedNodesHandlerMissCount += 1;
+ }
+ }
+ },
+
+ start: function() {
+ var domChangedHandler = this.domChangedHandler.bind(this);
+ vAPI.domWatcher.addListener(domChangedHandler);
+ vAPI.shutdown.add(function() {
+ vAPI.domWatcher.removeListener(domChangedHandler);
+ });
}
};
@@ -660,7 +688,7 @@ return domFilterer;
if ( cfeDetails.cosmeticHide.length !== 0 || cfeDetails.cosmeticDonthide.length !== 0 ) {
domFilterer.addExceptions(cfeDetails.cosmeticDonthide);
domFilterer.addSelectors(cfeDetails.cosmeticHide);
- domFilterer.commit(undefined, true);
+ domFilterer.commit('all', true);
}
}
@@ -759,7 +787,7 @@ vAPI.domWatcher = (function() {
}
}
addedNodeLists.length = 0;
- if ( addedNodes.length !== 0 ) {
+ if ( addedNodes.length !== 0 || removedNodes ) {
listeners[0](addedNodes, removedNodes);
if ( listeners[1] ) {
listeners[1](addedNodes, removedNodes);
@@ -825,26 +853,16 @@ vAPI.domWatcher = (function() {
domLayoutObserver = null;
};
- var stop = function() {
- if ( domLayoutObserver !== null ) {
- domLayoutObserver.disconnect();
- domLayoutObserver = null;
- }
- if ( safeObserverHandlerTimer !== null ) {
- window.cancelAnimationFrame(safeObserverHandlerTimer);
- }
- };
-
var start = function() {
- // Observe changes in the DOM only if...
- // - there is a document.body
- // - there is at least one `script` tag
- if ( document.body === null || document.querySelector('script') === null ) {
- vAPI.domWatcher = null;
- return;
- }
- // https://github.com/gorhill/uMatrix/issues/144
- vAPI.shutdown.add(stop);
+ vAPI.shutdown.add(function() {
+ if ( domLayoutObserver !== null ) {
+ domLayoutObserver.disconnect();
+ domLayoutObserver = null;
+ }
+ if ( safeObserverHandlerTimer !== null ) {
+ window.cancelAnimationFrame(safeObserverHandlerTimer);
+ }
+ });
};
return {
@@ -887,16 +905,11 @@ vAPI.domCollapser = (function() {
if ( !response ) {
return;
}
- // https://github.com/gorhill/uMatrix/issues/144
- if ( response.shutdown ) {
- vAPI.shutdown.exec();
- return;
- }
-
var requests = response.result;
if ( requests === null || Array.isArray(requests) === false ) {
return;
}
+ vAPI.executionCost.start();
var selectors = [],
netSelectorCacheCountMax = response.netSelectorCacheCountMax,
aa = [ null ],
@@ -947,9 +960,11 @@ vAPI.domCollapser = (function() {
}
);
}
+ vAPI.executionCost.stop('domCollapser/onProcessed');
};
var send = function() {
+ vAPI.executionCost.start();
timer = null;
messaging.send(
'contentscript',
@@ -961,6 +976,7 @@ vAPI.domCollapser = (function() {
}, onProcessed
);
roundtripRequests = [];
+ vAPI.executionCost.stop('domCollapser/send');
};
var process = function(delay) {
@@ -1084,6 +1100,13 @@ vAPI.domCollapser = (function() {
}
};
+ var onResourceFailed = function(ev) {
+ vAPI.executionCost.start();
+ vAPI.domCollapser.add(ev.target);
+ vAPI.domCollapser.process();
+ vAPI.executionCost.stop('domCollapser/onResourceFailed');
+ };
+
var domChangedHandler = function(nodes) {
var node;
for ( var i = 0, ni = nodes.length; i < ni; i++ ) {
@@ -1101,13 +1124,6 @@ vAPI.domCollapser = (function() {
process();
};
- var onResourceFailed = function(ev) {
- vAPI.executionCost.start();
- vAPI.domCollapser.add(ev.target);
- vAPI.domCollapser.process();
- vAPI.executionCost.stop('domIsLoaded/onResourceFailed');
- };
-
var start = function() {
// Listener to collapse blocked resources.
// - Future requests not blocked yet
@@ -1131,15 +1147,13 @@ vAPI.domCollapser = (function() {
process(0);
document.addEventListener('error', onResourceFailed, true);
- if ( vAPI.domWatcher ) {
- vAPI.domWatcher.addListener(domChangedHandler);
- }
+ vAPI.domWatcher.addListener(domChangedHandler);
- // https://github.com/gorhill/uMatrix/issues/144
vAPI.shutdown.add(function() {
document.removeEventListener('error', onResourceFailed, true);
- if ( vAPI.domWatcher ) {
- vAPI.domWatcher.removeListener(domChangedHandler);
+ vAPI.domWatcher.removeListener(domChangedHandler);
+ if ( timer !== null ) {
+ clearTimeout(timer);
}
});
};
@@ -1159,29 +1173,17 @@ vAPI.domCollapser = (function() {
/******************************************************************************/
vAPI.domSurveyor = (function() {
- // https://github.com/chrisaljoudi/uBlock/issues/789
- // https://github.com/gorhill/uBlock/issues/873
- // Be sure that our style tags used for cosmetic filtering are still
- // applied.
-
var domFilterer = null,
messaging = vAPI.messaging,
surveyPhase3Nodes = [],
cosmeticSurveyingMissCount = 0,
highGenerics = null,
lowGenericSelectors = [],
- queriedSelectors = Object.create(null),
- removedNodesHandlerMissCount = 0;
+ queriedSelectors = Object.create(null);
// Handle main process' response.
var surveyPhase3 = function(response) {
- // https://github.com/gorhill/uMatrix/issues/144
- if ( response && response.shutdown ) {
- vAPI.shutdown.exec();
- return;
- }
-
vAPI.executionCost.start();
var result = response && response.result,
@@ -1232,7 +1234,7 @@ vAPI.domSurveyor = (function() {
domFilterer.commit(surveyPhase3Nodes);
surveyPhase3Nodes = [];
- vAPI.executionCost.stop('domIsLoaded/surveyPhase2');
+ vAPI.executionCost.stop('domSurveyor/surveyPhase3');
};
// Query main process.
@@ -1465,31 +1467,27 @@ vAPI.domSurveyor = (function() {
};
var domChangedHandler = function(addedNodes, removedNodes) {
- if ( cosmeticSurveyingMissCount < 256 ) {
- surveyPhase1(addedNodes);
- } else {
- domFilterer.commit(addedNodes);
+ if ( cosmeticSurveyingMissCount > 255 ) {
+ vAPI.domWatcher.removeListener(domChangedHandler);
+ vAPI.domSurveyor = null;
+ domFilterer.domChangedHandler(addedNodes, removedNodes);
+ domFilterer.start();
+ return;
}
- // https://github.com/gorhill/uBlock/issues/873
- // This will ensure our style elements will stay in the DOM.
- if ( removedNodes && removedNodesHandlerMissCount < 16 ) {
- if ( domFilterer.checkStyleTags(true) === false ) {
- removedNodesHandlerMissCount += 1;
- }
+
+ surveyPhase1(addedNodes);
+ if ( removedNodes ) {
+ domFilterer.domChangedHandler([], true);
}
};
var start = function() {
domFilterer = vAPI.domFilterer;
- if ( domFilterer === null ) {
- return;
- }
- domFilterer.checkStyleTags(false);
- domFilterer.commit();
domChangedHandler([ document.documentElement ]);
- if ( vAPI.domWatcher ) {
- vAPI.domWatcher.addListener(domChangedHandler);
- }
+ vAPI.domWatcher.addListener(domChangedHandler);
+ vAPI.shutdown.add(function() {
+ vAPI.domWatcher.removeListener(domChangedHandler);
+ });
};
return {
@@ -1514,16 +1512,20 @@ vAPI.domIsLoaded = function(ev) {
vAPI.executionCost.start();
- if ( vAPI.domWatcher ) {
- vAPI.domWatcher.start();
- }
+ vAPI.domWatcher.start();
+ vAPI.domCollapser.start();
- if ( vAPI.domCollapser ) {
- vAPI.domCollapser.start();
- }
-
- if ( vAPI.domFilterer && vAPI.domSurveyor ) {
- vAPI.domSurveyor.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(false);
+ vAPI.domFilterer.commit('all');
+ if ( vAPI.domSurveyor ) {
+ vAPI.domSurveyor.start();
+ } else {
+ vAPI.domFilterer.start();
+ }
}
// To send mouse coordinates to main process, as the chrome API fails
@@ -1533,30 +1535,26 @@ vAPI.domIsLoaded = function(ev) {
// as nuisance popups.
// Ref.: https://developer.mozilla.org/en-US/docs/Web/Events/contextmenu
+ var onMouseClick = function(ev) {
+ var elem = ev.target;
+ while ( elem !== null && elem.localName !== 'a' ) {
+ elem = elem.parentElement;
+ }
+ vAPI.messaging.send(
+ 'contentscript',
+ {
+ what: 'mouseClick',
+ x: ev.clientX,
+ y: ev.clientY,
+ url: elem !== null ? elem.href : ''
+ }
+ );
+ };
+
(function() {
if ( window !== window.top || !vAPI.domFilterer ) {
return;
}
-
- var messaging = vAPI.messaging;
-
- var onMouseClick = function(ev) {
- vAPI.executionCost.start();
- var elem = ev.target;
- while ( elem !== null && elem.localName !== 'a' ) {
- elem = elem.parentElement;
- }
- messaging.send(
- 'contentscript',
- {
- what: 'mouseClick',
- x: ev.clientX,
- y: ev.clientY,
- url: elem !== null ? elem.href : ''
- });
- vAPI.executionCost.stop('domIsLoaded/onMouseClick');
- };
-
document.addEventListener('mousedown', onMouseClick, true);
// https://github.com/gorhill/uMatrix/issues/144
diff --git a/src/js/cosmetic-filtering.js b/src/js/cosmetic-filtering.js
index 598bfa65f..6b85a12b8 100644
--- a/src/js/cosmetic-filtering.js
+++ b/src/js/cosmetic-filtering.js
@@ -690,9 +690,12 @@ FilterContainer.prototype.reset = function() {
this.selectorCacheTimer = null;
}
- // permanent
+ // generic filters
+ this.hasGenericHide = false;
+
// [class], [id]
this.lowGenericHide = {};
+ this.lowGenericHideCount = 0;
// [alt="..."], [title="..."]
this.highLowGenericHide = {};
@@ -726,6 +729,33 @@ FilterContainer.prototype.reset = function() {
/******************************************************************************/
+FilterContainer.prototype.freeze = function() {
+ this.duplicateBuster = {};
+
+ if ( this.highHighSimpleGenericHide !== '' ) {
+ this.highHighSimpleGenericHideArray.unshift(this.highHighSimpleGenericHide);
+ }
+ this.highHighSimpleGenericHide = this.highHighSimpleGenericHideArray.join(',\n');
+ this.highHighSimpleGenericHideArray = [];
+
+ if ( this.highHighComplexGenericHide !== '' ) {
+ this.highHighComplexGenericHideArray.unshift(this.highHighComplexGenericHide);
+ }
+ this.highHighComplexGenericHide = this.highHighComplexGenericHideArray.join(',\n');
+ this.highHighComplexGenericHideArray = [];
+
+ this.hasGenericHide = this.lowGenericHideCount !== 0 ||
+ this.highLowGenericHideCount !== 0 ||
+ this.highMediumGenericHideCount !== 0 ||
+ this.highHighSimpleGenericHideCount !== 0 ||
+ this.highHighComplexGenericHideCount !== 0;
+
+ this.parser.reset();
+ this.frozen = true;
+};
+
+/******************************************************************************/
+
// https://github.com/chrisaljoudi/uBlock/issues/1004
// Detect and report invalid CSS selectors.
@@ -1006,26 +1036,23 @@ FilterContainer.prototype.compileEntitySelector = function(hostname, parsed, out
/******************************************************************************/
-FilterContainer.prototype.fromCompiledContent = function(text, lineBeg, skip) {
- if ( skip ) {
- return this.skipCompiledContent(text, lineBeg);
+FilterContainer.prototype.fromCompiledContent = function(lineIter, skipGenericCosmetic, skipCosmetic) {
+ if ( skipCosmetic ) {
+ this.skipCompiledContent(lineIter);
+ return;
+ }
+ if ( skipGenericCosmetic ) {
+ this.skipGenericCompiledContent(lineIter);
+ return;
}
- var lineEnd;
- var textEnd = text.length;
var line, fields, filter, key, bucket;
- while ( lineBeg < textEnd ) {
- if ( text.charCodeAt(lineBeg) !== 0x63 /* 'c' */ ) {
- return lineBeg;
+ while ( lineIter.eot() === false ) {
+ if ( lineIter.text.charCodeAt(lineIter.offset) !== 0x63 /* 'c' */ ) {
+ return;
}
- lineEnd = text.indexOf('\n', lineBeg);
- if ( lineEnd === -1 ) {
- lineEnd = textEnd;
- }
- line = text.slice(lineBeg + 2, lineEnd);
- lineBeg = lineEnd + 1;
-
+ line = lineIter.next().slice(2);
this.acceptedCount += 1;
if ( this.duplicateBuster.hasOwnProperty(line) ) {
@@ -1069,6 +1096,7 @@ FilterContainer.prototype.fromCompiledContent = function(text, lineBeg, skip) {
} else {
this.lowGenericHide[fields[1]] = new FilterBucket(bucket, filter);
}
+ this.lowGenericHideCount += 1;
continue;
}
@@ -1124,30 +1152,114 @@ FilterContainer.prototype.fromCompiledContent = function(text, lineBeg, skip) {
// Generic exception filters: expected to be a rare occurrence.
if ( fields[0] === 'g1' ) {
this.genericDonthide.push(fields[1]);
+ continue;
}
+
+ this.discardedCount += 1;
}
- return textEnd;
};
/******************************************************************************/
-FilterContainer.prototype.skipCompiledContent = function(text, lineBeg) {
- var lineEnd;
- var textEnd = text.length;
+FilterContainer.prototype.skipGenericCompiledContent = function(lineIter) {
+ var line, fields, filter, bucket;
- while ( lineBeg < textEnd ) {
- if ( text.charCodeAt(lineBeg) !== 0x63 /* 'c' */ ) {
- return lineBeg;
- }
- lineEnd = text.indexOf('\n', lineBeg);
- if ( lineEnd === -1 ) {
- lineEnd = textEnd;
+ while ( lineIter.eot() === false ) {
+ if ( lineIter.text.charCodeAt(lineIter.offset) !== 0x63 /* 'c' */ ) {
+ return;
}
+ line = lineIter.next().slice(2);
+
this.acceptedCount += 1;
+ if ( this.duplicateBuster.hasOwnProperty(line) ) {
+ this.discardedCount += 1;
+ continue;
+ }
+
+ fields = line.split('\v');
+
+ // h [\t] ir [\t] twitter.com [\t] .promoted-tweet
+ if ( fields[0] === 'h' ) {
+ this.duplicateBuster[line] = true;
+ // Special filter: script tags. Not a real CSS selector.
+ if ( fields[3].startsWith('script') ) {
+ this.createScriptFilter(fields[2], fields[3].slice(6));
+ continue;
+ }
+ filter = new FilterHostname(fields[3], fields[2]);
+ bucket = this.hostnameFilters[fields[1]];
+ if ( bucket === undefined ) {
+ this.hostnameFilters[fields[1]] = filter;
+ } else if ( bucket instanceof FilterBucket ) {
+ bucket.add(filter);
+ } else {
+ this.hostnameFilters[fields[1]] = new FilterBucket(bucket, filter);
+ }
+ continue;
+ }
+
+ // entity [\t] selector
+ if ( fields[0] === 'e' ) {
+ this.duplicateBuster[line] = true;
+ // Special filter: script tags. Not a real CSS selector.
+ if ( fields[2].startsWith('script') ) {
+ this.createScriptFilter(fields[1], fields[2].slice(6));
+ continue;
+ }
+ bucket = this.entityFilters[fields[1]];
+ if ( bucket === undefined ) {
+ this.entityFilters[fields[1]] = [fields[2]];
+ } else {
+ bucket.push(fields[2]);
+ }
+ continue;
+ }
+
+ // https://github.com/chrisaljoudi/uBlock/issues/497
+ // Generic exception filters: expected to be a rare occurrence.
+ if ( fields[0] === 'g1' ) {
+ this.duplicateBuster[line] = true;
+ this.genericDonthide.push(fields[1]);
+ continue;
+ }
+
+ this.discardedCount += 1;
+ }
+};
+
+/******************************************************************************/
+
+FilterContainer.prototype.skipCompiledContent = function(lineIter) {
+ var line, fields;
+
+ while ( lineIter.eot() === false ) {
+ if ( lineIter.text.charCodeAt(lineIter.offset) !== 0x63 /* 'c' */ ) {
+ return;
+ }
+ line = lineIter.next().slice(2);
+
+ this.acceptedCount += 1;
+ if ( this.duplicateBuster.hasOwnProperty(line) ) {
+ this.discardedCount += 1;
+ continue;
+ }
+
+ fields = line.split('\v');
+
+ if ( fields[0] === 'h' && fields[3].startsWith('script') ) {
+ this.duplicateBuster[line] = true;
+ this.createScriptFilter(fields[2], fields[3].slice(6));
+ continue;
+ }
+
+ if ( fields[0] === 'e' && fields[2].startsWith('script') ) {
+ this.duplicateBuster[line] = true;
+ this.createScriptFilter(fields[1], fields[2].slice(6));
+ continue;
+ }
+
this.discardedCount += 1;
- lineBeg = lineEnd + 1;
}
- return textEnd;
};
/******************************************************************************/
@@ -1260,27 +1372,6 @@ FilterContainer.prototype.retrieveScriptTags = function(domain, hostname) {
/******************************************************************************/
-FilterContainer.prototype.freeze = function() {
- this.duplicateBuster = {};
-
- if ( this.highHighSimpleGenericHide !== '' ) {
- this.highHighSimpleGenericHideArray.unshift(this.highHighSimpleGenericHide);
- }
- this.highHighSimpleGenericHide = this.highHighSimpleGenericHideArray.join(',\n');
- this.highHighSimpleGenericHideArray = [];
-
- if ( this.highHighComplexGenericHide !== '' ) {
- this.highHighComplexGenericHideArray.unshift(this.highHighComplexGenericHide);
- }
- this.highHighComplexGenericHide = this.highHighComplexGenericHideArray.join(',\n');
- this.highHighComplexGenericHideArray = [];
-
- this.parser.reset();
- this.frozen = true;
-};
-
-/******************************************************************************/
-
FilterContainer.prototype.toSelfie = function() {
var selfieFromDict = function(dict) {
var selfie = [];
@@ -1312,7 +1403,9 @@ FilterContainer.prototype.toSelfie = function() {
discardedCount: this.discardedCount,
hostnameSpecificFilters: selfieFromDict(this.hostnameFilters),
entitySpecificFilters: this.entityFilters,
+ hasGenericHide: this.hasGenericHide,
lowGenericHide: selfieFromDict(this.lowGenericHide),
+ lowGenericHideCount: this.lowGenericHideCount,
highLowGenericHide: this.highLowGenericHide,
highLowGenericHideCount: this.highLowGenericHideCount,
highMediumGenericHide: this.highMediumGenericHide,
@@ -1378,7 +1471,9 @@ FilterContainer.prototype.fromSelfie = function(selfie) {
this.discardedCount = selfie.discardedCount;
this.hostnameFilters = dictFromSelfie(selfie.hostnameSpecificFilters);
this.entityFilters = selfie.entitySpecificFilters;
+ this.hasGenericHide = selfie.hasGenericHide;
this.lowGenericHide = dictFromSelfie(selfie.lowGenericHide);
+ this.lowGenericHideCount = selfie.lowGenericHideCount;
this.highLowGenericHide = selfie.highLowGenericHide;
this.highLowGenericHideCount = selfie.highLowGenericHideCount;
this.highMediumGenericHide = selfie.highMediumGenericHide;
@@ -1573,7 +1668,7 @@ FilterContainer.prototype.retrieveDomainSelectors = function(request, noCosmetic
ready: this.frozen,
domain: domain,
entity: pos === -1 ? domain : domain.slice(0, pos - domain.length),
- noDOMSurveying: false,
+ noDOMSurveying: this.hasGenericHide === false,
cosmeticHide: [],
cosmeticDonthide: [],
netHide: [],
@@ -1600,7 +1695,9 @@ FilterContainer.prototype.retrieveDomainSelectors = function(request, noCosmetic
// cached cosmetic filters.
if ( cacheEntry ) {
cacheEntry.retrieve('cosmetic', r.cosmeticHide);
- r.noDOMSurveying = cacheEntry.cosmeticSurveyingMissCount > cosmeticSurveyingMissCountMax;
+ if ( r.noDOMSurveying === false ) {
+ r.noDOMSurveying = cacheEntry.cosmeticSurveyingMissCount > cosmeticSurveyingMissCountMax;
+ }
}
// Exception cosmetic filters.
diff --git a/src/js/dashboard-common.js b/src/js/dashboard-common.js
index ba7660edf..7cef4f68a 100644
--- a/src/js/dashboard-common.js
+++ b/src/js/dashboard-common.js
@@ -110,5 +110,6 @@ uDom('.whatisthis').on('click', function() {
uDom(this)
.parent()
.descendants('.whatisthis-expandable')
+ .first()
.toggleClass('whatisthis-expanded');
});
diff --git a/src/js/messaging.js b/src/js/messaging.js
index dc5c9c011..317f1356c 100644
--- a/src/js/messaging.js
+++ b/src/js/messaging.js
@@ -534,23 +534,19 @@ var onMessage = function(request, sender, callback) {
break;
case 'retrieveGenericCosmeticSelectors':
- response = {
- shutdown: !pageStore || !pageStore.getNetFilteringSwitch(),
- result: null
- };
- if ( !response.shutdown && pageStore.getGenericCosmeticFilteringSwitch() ) {
- response.result = µb.cosmeticFilteringEngine.retrieveGenericSelectors(request);
+ if ( pageStore && pageStore.getGenericCosmeticFilteringSwitch() ) {
+ response = {
+ result: µb.cosmeticFilteringEngine.retrieveGenericSelectors(request)
+ };
}
break;
case 'filterRequests':
- response = {
- shutdown: !pageStore || !pageStore.getNetFilteringSwitch(),
- result: null,
- netSelectorCacheCountMax: µb.cosmeticFilteringEngine.netSelectorCacheCountMax
- };
- if ( !response.shutdown ) {
- response.result = filterRequests(pageStore, request);
+ if ( pageStore && pageStore.getNetFilteringSwitch() ) {
+ response = {
+ result: filterRequests(pageStore, request),
+ netSelectorCacheCountMax: µb.cosmeticFilteringEngine.netSelectorCacheCountMax
+ };
}
break;
@@ -855,9 +851,10 @@ var getLists = function(callback) {
autoUpdate: µb.userSettings.autoUpdate,
available: null,
cache: null,
- cosmetic: µb.userSettings.parseAllABPHideFilters,
+ parseCosmeticFilters: µb.userSettings.parseAllABPHideFilters,
cosmeticFilterCount: µb.cosmeticFilteringEngine.getFilterCount(),
current: µb.remoteBlacklists,
+ ignoreGenericCosmeticFilters: µb.userSettings.ignoreGenericCosmeticFilters,
manualUpdate: false,
netFilterCount: µb.staticNetFilteringEngine.getFilterCount(),
userFiltersPath: µb.userFiltersPath
diff --git a/src/js/start.js b/src/js/start.js
index b8f364532..b5550fb4e 100644
--- a/src/js/start.js
+++ b/src/js/start.js
@@ -1,7 +1,7 @@
/*******************************************************************************
- uBlock - a browser extension to block requests.
- Copyright (C) 2014-2015 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,7 +19,9 @@
Home: https://github.com/gorhill/uBlock
*/
-/* global publicSuffixList, vAPI, µBlock */
+/* global publicSuffixList */
+
+'use strict';
/******************************************************************************/
@@ -27,8 +29,6 @@
µBlock.restart = (function() {
-'use strict';
-
//quickProfiler.start('start.js');
/******************************************************************************/
@@ -178,6 +178,13 @@ var onUserSettingsReady = function(fetched) {
µb.sessionURLFiltering.assign(µb.permanentURLFiltering);
µb.hnSwitches.fromString(fetched.hostnameSwitchesString);
+ // https://github.com/gorhill/uBlock/issues/1892
+ // For first installation on a battery-powered device, disable generic
+ // cosmetic filtering.
+ if ( µb.firstInstall && vAPI.battery ) {
+ userSettings.ignoreGenericCosmeticFilters = true;
+ }
+
// Remove obsolete setting
delete userSettings.logRequests;
vAPI.storage.remove('logRequests');
diff --git a/src/js/static-net-filtering.js b/src/js/static-net-filtering.js
index bb4b1dba9..bdca79f3a 100644
--- a/src/js/static-net-filtering.js
+++ b/src/js/static-net-filtering.js
@@ -2055,22 +2055,15 @@ FilterContainer.prototype.compileToAtomicFilter = function(filterClass, parsed,
/******************************************************************************/
-FilterContainer.prototype.fromCompiledContent = function(text, lineBeg) {
- var lineEnd;
- var textEnd = text.length;
+FilterContainer.prototype.fromCompiledContent = function(lineIter) {
var line, fields, bucket, entry, factory, filter;
- while ( lineBeg < textEnd ) {
- if ( text.charCodeAt(lineBeg) !== 0x6E /* 'n' */ ) {
- return lineBeg;
+ while ( lineIter.eot() === false ) {
+ if ( lineIter.text.charCodeAt(lineIter.offset) !== 0x6E /* 'n' */ ) {
+ return;
}
- lineEnd = text.indexOf('\n', lineBeg);
- if ( lineEnd === -1 ) {
- lineEnd = textEnd;
- }
- line = text.slice(lineBeg + 2, lineEnd);
+ line = lineIter.next().slice(2);
fields = line.split('\v');
- lineBeg = lineEnd + 1;
// Special cases: delegate to more specialized engines.
// Redirect engine.
@@ -2124,7 +2117,6 @@ FilterContainer.prototype.fromCompiledContent = function(text, lineBeg) {
}
bucket[fields[1]] = new FilterBucket(entry, filter);
}
- return textEnd;
};
/******************************************************************************/
diff --git a/src/js/storage.js b/src/js/storage.js
index 96b0e5d03..163409439 100644
--- a/src/js/storage.js
+++ b/src/js/storage.js
@@ -632,14 +632,14 @@
// applying 1st-party filters.
µBlock.applyCompiledFilters = function(rawText, firstparty) {
- var skipCosmetic = !firstparty && !this.userSettings.parseAllABPHideFilters;
- var staticNetFilteringEngine = this.staticNetFilteringEngine;
- var cosmeticFilteringEngine = this.cosmeticFilteringEngine;
- var lineBeg = 0;
- var rawEnd = rawText.length;
- while ( lineBeg < rawEnd ) {
- lineBeg = cosmeticFilteringEngine.fromCompiledContent(rawText, lineBeg, skipCosmetic);
- lineBeg = staticNetFilteringEngine.fromCompiledContent(rawText, lineBeg);
+ var skipCosmetic = !firstparty && !this.userSettings.parseAllABPHideFilters,
+ skipGenericCosmetic = this.userSettings.ignoreGenericCosmeticFilters,
+ staticNetFilteringEngine = this.staticNetFilteringEngine,
+ cosmeticFilteringEngine = this.cosmeticFilteringEngine,
+ lineIter = new this.LineIterator(rawText);
+ while ( lineIter.eot() === false ) {
+ cosmeticFilteringEngine.fromCompiledContent(lineIter, skipGenericCosmetic, skipCosmetic);
+ staticNetFilteringEngine.fromCompiledContent(lineIter);
}
};
diff --git a/src/js/utils.js b/src/js/utils.js
index 14fcb2893..de1f5380e 100644
--- a/src/js/utils.js
+++ b/src/js/utils.js
@@ -1,7 +1,7 @@
/*******************************************************************************
uBlock Origin - a browser extension to block requests.
- Copyright (C) 2014-2015 Raymond Hill
+ 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
@@ -123,3 +123,30 @@
// https://www.youtube.com/watch?v=DyvzfyqYm_s
/******************************************************************************/
+
+µBlock.LineIterator = function(text, offset) {
+ this.text = text;
+ this.offset = offset || 0;
+};
+
+µBlock.LineIterator.prototype.next = function() {
+ if ( this.offset >= this.text.length ) {
+ return undefined;
+ }
+ var lineEnd = this.text.indexOf('\n', this.offset);
+ if ( lineEnd === -1 ) {
+ lineEnd = this.text.indexOf('\r', this.offset);
+ if ( lineEnd === -1 ) {
+ lineEnd = this.text.length;
+ }
+ }
+ var line = this.text.slice(this.offset, lineEnd);
+ this.offset = lineEnd + 1;
+ return line;
+};
+
+µBlock.LineIterator.prototype.eot = function() {
+ return this.offset >= this.text.length;
+};
+
+/******************************************************************************/