aboutsummaryrefslogtreecommitdiffstats
path: root/src/js
diff options
context:
space:
mode:
Diffstat (limited to 'src/js')
-rw-r--r--src/js/3p-filters.js14
-rw-r--r--src/js/assets.js80
-rw-r--r--src/js/background.js2
-rw-r--r--src/js/contentscript-end.js12
-rw-r--r--src/js/contentscript-start.js4
-rw-r--r--src/js/cosmetic-filtering.js120
-rw-r--r--src/js/document-blocked.js11
-rw-r--r--src/js/messaging.js2
-rw-r--r--src/js/reverselookup-worker.js32
-rw-r--r--src/js/rpcreceiver.js53
-rw-r--r--src/js/static-net-filtering.js5
11 files changed, 286 insertions, 49 deletions
diff --git a/src/js/3p-filters.js b/src/js/3p-filters.js
index a3e81b8..b5fca1b 100644
--- a/src/js/3p-filters.js
+++ b/src/js/3p-filters.js
@@ -371,8 +371,22 @@ var onPurgeClicked = function() {
if ( !href ) {
return;
}
+
messager.send({ what: 'purgeCache', path: href });
button.remove();
+
+ // If the cached version is purged, the installed version must be assumed
+ // to be obsolete.
+ var entry = listDetails.current && listDetails.current[href];
+ if ( entry && entry.off !== true ) {
+ if ( typeof entry.homeURL !== 'string' || entry.homeURL === '' ) {
+ li.descendants('span.status.new').css('display', '');
+ } else {
+ li.descendants('span.status.obsolete').css('display', '');
+ }
+ needUpdate = true;
+ }
+
if ( li.descendants('input').first().prop('checked') ) {
cacheWasPurged = true;
renderWidgets();
diff --git a/src/js/assets.js b/src/js/assets.js
index 8549cd8..08616c5 100644
--- a/src/js/assets.js
+++ b/src/js/assets.js
@@ -61,6 +61,7 @@ var lastRepoMetaTimestamp = 0;
var lastRepoMetaIsRemote = false;
var refreshRepoMetaPeriod = 5 * oneHour;
var errorCantConnectTo = vAPI.i18n('errorCantConnectTo');
+var xhrTimeout = vAPI.localStorage.getItem('xhrTimeout') || 30000;
var exports = {
autoUpdate: true,
@@ -275,8 +276,14 @@ var cachedAssetsManager = (function() {
exports.remove(/./);
};
+ exports.exists = function(path) {
+ return entries !== null && entries.hasOwnProperty(path);
+ };
+
exports.onRemovedListener = null;
+ getEntries(function(){});
+
return exports;
})();
@@ -285,6 +292,10 @@ var cachedAssetsManager = (function() {
var getTextFileFromURL = function(url, onLoad, onError) {
// console.log('µBlock.assets/getTextFileFromURL("%s"):', url);
+ if ( typeof onError !== 'function' ) {
+ onError = onLoad;
+ }
+
// https://github.com/gorhill/uMatrix/issues/15
var onResponseReceived = function() {
this.onload = this.onerror = this.ontimeout = null;
@@ -318,7 +329,7 @@ var getTextFileFromURL = function(url, onLoad, onError) {
var xhr = new XMLHttpRequest();
try {
xhr.open('get', url, true);
- xhr.timeout = 30000;
+ xhr.timeout = xhrTimeout;
xhr.onload = onResponseReceived;
xhr.onerror = onErrorReceived;
xhr.ontimeout = onErrorReceived;
@@ -376,11 +387,16 @@ var getRepoMetadata = function(callback) {
lastRepoMetaTimestamp = Date.now();
lastRepoMetaIsRemote = exports.remoteFetchBarrier === 0;
+ var defaultChecksums;
var localChecksums;
var repoChecksums;
var checksumsReceived = function() {
- if ( localChecksums === undefined || repoChecksums === undefined ) {
+ if (
+ defaultChecksums === undefined ||
+ localChecksums === undefined ||
+ repoChecksums === undefined
+ ) {
return;
}
// Remove from cache assets which no longer exist in the repo
@@ -392,6 +408,17 @@ var getRepoMetadata = function(callback) {
continue;
}
entry = entries[path];
+ // https://github.com/gorhill/uBlock/issues/760
+ // If the resource does not have a cached instance, we must reset
+ // the checksum to its value at install time.
+ if (
+ stringIsNotEmpty(defaultChecksums[path]) &&
+ entry.localChecksum !== defaultChecksums[path] &&
+ cachedAssetsManager.exists(path) === false
+ ) {
+ entry.localChecksum = defaultChecksums[path];
+ checksumsChanged = true;
+ }
// If repo checksums could not be fetched, assume no change.
// https://github.com/gorhill/uBlock/issues/602
// Added: if repo checksum is that of the empty string,
@@ -450,41 +477,64 @@ var getRepoMetadata = function(callback) {
return out.join('\n');
};
- var parseChecksums = function(text, which) {
- var entries = repoMetadata.entries;
+ var parseChecksums = function(text, eachFn) {
var lines = text.split(/\n+/);
var i = lines.length;
- var fields, assetPath;
+ var fields;
while ( i-- ) {
fields = lines[i].trim().split(/\s+/);
if ( fields.length !== 2 ) {
continue;
}
- assetPath = fields[1];
- if ( entries[assetPath] === undefined ) {
- entries[assetPath] = new AssetEntry();
- }
- entries[assetPath][which + 'Checksum'] = fields[0];
+ eachFn(fields[1], fields[0]);
}
};
var onLocalChecksumsLoaded = function(details) {
+ var entries = repoMetadata.entries;
+ var processChecksum = function(path, checksum) {
+ if ( entries.hasOwnProperty(path) === false ) {
+ entries[path] = new AssetEntry();
+ }
+ entries[path].localChecksum = checksum;
+ };
if ( (localChecksums = validateChecksums(details)) ) {
- parseChecksums(localChecksums, 'local');
+ parseChecksums(localChecksums, processChecksum);
}
checksumsReceived();
};
var onRepoChecksumsLoaded = function(details) {
+ var entries = repoMetadata.entries;
+ var processChecksum = function(path, checksum) {
+ if ( entries.hasOwnProperty(path) === false ) {
+ entries[path] = new AssetEntry();
+ }
+ entries[path].repoChecksum = checksum;
+ };
if ( (repoChecksums = validateChecksums(details)) ) {
- parseChecksums(repoChecksums, 'repo');
+ parseChecksums(repoChecksums, processChecksum);
}
checksumsReceived();
};
+ // https://github.com/gorhill/uBlock/issues/760
+ // We need the checksum values at install time, because some resources
+ // may have been purged, in which case the checksum must be reset to the
+ // value at install time.
+ var onDefaultChecksumsLoaded = function() {
+ defaultChecksums = Object.create(null);
+ var processChecksum = function(path, checksum) {
+ defaultChecksums[path] = checksum;
+ };
+ parseChecksums(this.responseText || '', processChecksum);
+ checksumsReceived();
+ };
+
repoMetadata = new RepoMetadata();
repoMetadata.waiting.push(callback);
readRepoFile('assets/checksums.txt', onRepoChecksumsLoaded);
+ getTextFileFromURL(vAPI.getURL('assets/checksums.txt'), onDefaultChecksumsLoaded);
readLocalFile('assets/checksums.txt', onLocalChecksumsLoaded);
};
@@ -1166,8 +1216,14 @@ exports.purge = function(pattern, before) {
cachedAssetsManager.remove(pattern, before);
};
+exports.purgeCacheableAsset = function(pattern, before) {
+ cachedAssetsManager.remove(pattern, before);
+ lastRepoMetaTimestamp = 0;
+};
+
exports.purgeAll = function(callback) {
cachedAssetsManager.removeAll(callback);
+ lastRepoMetaTimestamp = 0;
};
/******************************************************************************/
diff --git a/src/js/background.js b/src/js/background.js
index 1aa5d54..8ff3b2b 100644
--- a/src/js/background.js
+++ b/src/js/background.js
@@ -57,7 +57,7 @@ return {
collapseBlocked: true,
colorBlindFriendly: false,
contextMenuEnabled: true,
- dynamicFilteringEnabled: false,
+ dynamicFilteringEnabled: true,
experimentalEnabled: false,
externalLists: defaultExternalLists,
firewallPaneMinimized: true,
diff --git a/src/js/contentscript-end.js b/src/js/contentscript-end.js
index 776862f..f2986b8 100644
--- a/src/js/contentscript-end.js
+++ b/src/js/contentscript-end.js
@@ -478,7 +478,7 @@ var uBlockCollapser = (function() {
// https://www.chromestatus.com/features/4668884095336448
// "Multiple shadow roots is being deprecated."
if ( shadow !== null ) {
- if ( shadow.className !== sessionId ) {
+ if ( shadow.className !== sessionId ) {
elem.style.setProperty('display', 'none', 'important');
}
continue;
@@ -486,9 +486,13 @@ var uBlockCollapser = (function() {
// https://github.com/gorhill/uBlock/pull/555
// Not all nodes can be shadowed:
// https://github.com/w3c/webcomponents/issues/102
+ // https://github.com/gorhill/uBlock/issues/762
+ // Remove display style that might get in the way of the shadow
+ // node doing its magic.
try {
shadow = elem.createShadowRoot();
shadow.className = sessionId;
+ elem.style.removeProperty('display');
} catch (ex) {
elem.style.setProperty('display', 'none', 'important');
}
@@ -921,11 +925,15 @@ var uBlockCollapser = (function() {
return;
}
var onMouseClick = function(ev) {
+ var elem = ev.target;
+ while ( elem !== null && elem.localName !== 'a' ) {
+ elem = elem.parentElement;
+ }
messager.send({
what: 'mouseClick',
x: ev.clientX,
y: ev.clientY,
- url: ev.target && ev.target.localName === 'a' ? ev.target.href : ''
+ url: elem !== null ? elem.href : ''
});
};
diff --git a/src/js/contentscript-start.js b/src/js/contentscript-start.js
index ac7d924..a625085 100644
--- a/src/js/contentscript-start.js
+++ b/src/js/contentscript-start.js
@@ -195,9 +195,13 @@ var hideElements = function(selectors) {
// https://github.com/gorhill/uBlock/pull/555
// Not all nodes can be shadowed:
// https://github.com/w3c/webcomponents/issues/102
+ // https://github.com/gorhill/uBlock/issues/762
+ // Remove display style that might get in the way of the shadow
+ // node doing its magic.
try {
shadow = elem.createShadowRoot();
shadow.className = sessionId;
+ elem.style.removeProperty('display');
} catch (ex) {
elem.style.setProperty('display', 'none', 'important');
}
diff --git a/src/js/cosmetic-filtering.js b/src/js/cosmetic-filtering.js
index 9a5d23f..3ef45d4 100644
--- a/src/js/cosmetic-filtering.js
+++ b/src/js/cosmetic-filtering.js
@@ -230,20 +230,19 @@ FilterEntity.fromSelfie = function(s) {
/******************************************************************************/
var FilterParser = function() {
- this.prefix = '';
- this.suffix = '';
+ this.prefix = this.suffix = this.style = '';
this.unhide = 0;
this.hostnames = [];
this.invalid = false;
this.cosmetic = true;
- this.reParser = /^\s*([^#]*)(##|#@#)(.+)\s*$/;
+ this.reParser = /^([^#]*?)(##|#@#)(.+)$/;
+ this.reScriptContains = /^script:contains\(.+?\)$/;
};
/******************************************************************************/
FilterParser.prototype.reset = function() {
- this.prefix = '';
- this.suffix = '';
+ this.prefix = this.suffix = this.style = '';
this.unhide = 0;
this.hostnames.length = 0;
this.invalid = false;
@@ -262,10 +261,20 @@ FilterParser.prototype.parse = function(s) {
this.cosmetic = false;
return this;
}
-
- // Remember original string
- this.prefix = matches[1];
- this.suffix = matches[3];
+ this.prefix = matches[1].trim();
+ this.unhide = matches[2].charAt(1) === '@' ? 1 : 0;
+ this.suffix = matches[3].trim();
+
+ // Cosmetic filters with explicit style properties can apply only:
+ // - to specific cosmetic filters (those which apply to a specific site)
+ // - to block cosmetic filters (not exception cosmetic filters)
+ if ( this.suffix.slice(-1) === '}' ) {
+ // Not supported for now: this code will ensure some backward
+ // compatibility for when cosmetic filters with explicit style
+ // properties start to be in use.
+ this.invalid = true;
+ return this;
+ }
// 2014-05-23:
// https://github.com/gorhill/httpswitchboard/issues/260
@@ -283,10 +292,31 @@ FilterParser.prototype.parse = function(s) {
this.suffix = this.suffix.slice(1);
}
- this.unhide = matches[2].charAt(1) === '@' ? 1 : 0;
if ( this.prefix !== '' ) {
this.hostnames = this.prefix.split(/\s*,\s*/);
}
+
+ // Script tag filters: pre-process them so that can be used with minimal
+ // overhead in the content script.
+ // Examples:
+ // focus.de##script:contains(/uabInject/)
+ // focus.de##script:contains(uabInject)
+ if ( this.suffix.charAt(0) === 's' && this.reScriptContains.test(this.suffix) ) {
+ // Currently supported only as non-generic selector. Also, exception
+ // script tag filter makes no sense, ignore.
+ if ( this.hostnames.length === 0 || this.unhide === 1 ) {
+ this.invalid = true;
+ return this;
+ }
+ var suffix = this.suffix;
+ this.suffix = 'script//:';
+ if ( suffix.charAt(16) !== '/' || suffix.slice(-2) !== '/)' ) {
+ this.suffix += suffix.slice(16, -1).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+ } else {
+ this.suffix += suffix.slice(17, -2).replace(/\\/g, '\\');
+ }
+ }
+
return this;
};
@@ -554,6 +584,8 @@ FilterContainer.prototype.reset = function() {
// hostname, entity-based filters
this.hostnameFilters = {};
this.entityFilters = {};
+ this.scriptTagFilters = {};
+ this.scriptTagFilterCount = 0;
};
/******************************************************************************/
@@ -576,11 +608,14 @@ FilterContainer.prototype.isValidSelector = (function() {
try {
// https://github.com/gorhill/uBlock/issues/693
div.matches(s + ',\n#foo');
+ return true;
} catch (e) {
- console.error('uBlock> invalid cosmetic filter:', s);
- return false;
}
- return true;
+ if ( s.lastIndexOf('script//:', 0) === 0 ) {
+ return true;
+ }
+ console.error('uBlock> invalid cosmetic filter:', s);
+ return false;
};
})();
@@ -790,6 +825,11 @@ FilterContainer.prototype.fromCompiledContent = function(text, lineBeg, skip) {
// h ir twitter.com .promoted-tweet
if ( fields[0] === 'h' ) {
+ // Special filter: script tags. Not a real CSS selector.
+ if ( fields[3].lastIndexOf('script//:', 0) === 0 ) {
+ this.createScriptTagFilter(fields[2], fields[3].slice(9));
+ continue;
+ }
filter = new FilterHostname(fields[3], fields[2]);
bucket = this.hostnameFilters[fields[1]];
if ( bucket === undefined ) {
@@ -821,6 +861,11 @@ FilterContainer.prototype.fromCompiledContent = function(text, lineBeg, skip) {
// entity selector
if ( fields[0] === 'e' ) {
+ // Special filter: script tags. Not a real CSS selector.
+ if ( fields[2].lastIndexOf('script//:', 0) === 0 ) {
+ this.createScriptTagFilter(fields[1], fields[2].slice(9));
+ continue;
+ }
bucket = this.entityFilters[fields[1]];
if ( bucket === undefined ) {
this.entityFilters[fields[1]] = [fields[2]];
@@ -886,6 +931,49 @@ FilterContainer.prototype.skipCompiledContent = function(text, lineBeg) {
/******************************************************************************/
+FilterContainer.prototype.createScriptTagFilter = function(hostname, s) {
+ if ( this.scriptTagFilters.hasOwnProperty(hostname) ) {
+ this.scriptTagFilters[hostname] += '|' + s;
+ } else {
+ this.scriptTagFilters[hostname] = s;
+ }
+ this.scriptTagFilterCount += 1;
+};
+
+/******************************************************************************/
+
+FilterContainer.prototype.retrieveScriptTagRegex = function(domain, hostname) {
+ if ( this.scriptTagFilterCount === 0 ) {
+ return;
+ }
+ var out = [], hn = hostname, pos;
+ for (;;) {
+ if ( this.scriptTagFilters.hasOwnProperty(hn) ) {
+ out.push(this.scriptTagFilters[hn]);
+ }
+ if ( hn === domain ) {
+ break;
+ }
+ pos = hn.indexOf('.');
+ if ( pos === -1 ) {
+ break;
+ }
+ hn = hn.slice(pos + 1);
+ }
+ pos = domain.indexOf('.');
+ if ( pos !== -1 ) {
+ hn = domain.slice(0, pos);
+ if ( this.scriptTagFilters.hasOwnProperty(hn) ) {
+ out.push(this.scriptTagFilters[hn]);
+ }
+ }
+ if ( out.length !== 0 ) {
+ return out.join('|');
+ }
+};
+
+/******************************************************************************/
+
FilterContainer.prototype.freeze = function() {
this.duplicateBuster = {};
@@ -939,7 +1027,9 @@ FilterContainer.prototype.toSelfie = function() {
highMediumGenericHideCount: this.highMediumGenericHideCount,
highHighGenericHide: this.highHighGenericHide,
highHighGenericHideCount: this.highHighGenericHideCount,
- genericDonthide: this.genericDonthide
+ genericDonthide: this.genericDonthide,
+ scriptTagFilters: this.scriptTagFilters,
+ scriptTagFilterCount: this.scriptTagFilterCount
};
};
@@ -1000,6 +1090,8 @@ FilterContainer.prototype.fromSelfie = function(selfie) {
this.highHighGenericHide = selfie.highHighGenericHide;
this.highHighGenericHideCount = selfie.highHighGenericHideCount;
this.genericDonthide = selfie.genericDonthide;
+ this.scriptTagFilters = selfie.scriptTagFilters;
+ this.scriptTagFilterCount = selfie.scriptTagFilterCount;
this.frozen = true;
};
diff --git a/src/js/document-blocked.js b/src/js/document-blocked.js
index 5b1c469..c64954a 100644
--- a/src/js/document-blocked.js
+++ b/src/js/document-blocked.js
@@ -193,12 +193,9 @@ uDom.nodeFromId('why').textContent = details.fs;
};
var renderParams = function(parentNode, rawURL) {
- var url = null;
- try {
- url = new URL(rawURL);
- } catch(ex) {
- }
- if ( url === null || url.search.length === 0 ) {
+ var a = document.createElement('a');
+ a.href = rawURL;
+ if ( a.search.length === 0 ) {
return false;
}
@@ -209,7 +206,7 @@ uDom.nodeFromId('why').textContent = details.fs;
);
parentNode.appendChild(li);
- var params = url.search.slice(1).split('&');
+ var params = a.search.slice(1).split('&');
var param, name, value, ul;
for ( var i = 0; i < params.length; i++ ) {
param = params[i];
diff --git a/src/js/messaging.js b/src/js/messaging.js
index 2841fef..6076f8d 100644
--- a/src/js/messaging.js
+++ b/src/js/messaging.js
@@ -815,7 +815,7 @@ var onMessage = function(request, sender, callback) {
switch ( request.what ) {
case 'purgeCache':
- µb.assets.purge(request.path);
+ µb.assets.purgeCacheableAsset(request.path);
break;
default:
diff --git a/src/js/reverselookup-worker.js b/src/js/reverselookup-worker.js
index b82f9ea..946001c 100644
--- a/src/js/reverselookup-worker.js
+++ b/src/js/reverselookup-worker.js
@@ -31,25 +31,35 @@ var listEntries = Object.create(null);
// Helpers
-var rescape = function(s) {
+var reEscape = function(s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
};
+var reSpecialChars = /[\*\^\t\v\n]/;
+
/******************************************************************************/
var fromNetFilter = function(details) {
var lists = [];
- var entry, pos;
+ var compiledFilter = details.compiledFilter;
+ var entry, content, pos, c;
for ( var path in listEntries ) {
entry = listEntries[path];
if ( entry === undefined ) {
continue;
}
- pos = entry.content.indexOf(details.compiledFilter);
+ content = entry.content;
+ pos = content.indexOf(compiledFilter);
if ( pos === -1 ) {
continue;
}
+ // https://github.com/gorhill/uBlock/issues/835
+ // We need an exact match.
+ c = content.charAt(pos + compiledFilter.length);
+ if ( c !== '' && reSpecialChars.test(c) === false ) {
+ continue;
+ }
lists.push({
title: entry.title,
supportURL: entry.supportURL
@@ -103,16 +113,16 @@ var fromCosmeticFilter = function(details) {
var matches = rePlainSelector.exec(filter);
if ( matches ) {
if ( matches[0] === filter ) { // simple CSS selector
- reStr = rescape('c\vlg\v' + filter);
+ reStr = reEscape('c\vlg\v' + filter);
} else { // complex CSS selector
- reStr = rescape('c\vlg+\v' + matches[0] + '\v' + filter);
+ reStr = reEscape('c\vlg+\v' + matches[0] + '\v' + filter);
}
} else if ( reHighLow.test(filter) ) { // [alt] or [title]
- reStr = rescape('c\vhlg0\v' + filter);
+ reStr = reEscape('c\vhlg0\v' + filter);
} else if ( reHighMedium.test(filter) ) { // [href^="..."]
- reStr = rescape('c\vhmg0\v') + '[a-z.-]+' + rescape('\v') + '[a-z]*' + rescape(filter);
+ reStr = reEscape('c\vhmg0\v') + '[a-z.-]+' + reEscape('\v') + '[a-z]*' + reEscape(filter);
} else { // all else
- reStr = rescape('c\vhhg0\v' + filter);
+ reStr = reEscape('c\vhhg0\v' + filter);
}
candidates[details.rawFilter] = new RegExp(reStr + '(?:\\n|$)');
@@ -125,9 +135,9 @@ var fromCosmeticFilter = function(details) {
if ( hostname !== '' ) {
for ( ;; ) {
candidates[hostname + '##' + filter] = new RegExp(
- rescape('c\vh\v') +
+ reEscape('c\vh\v') +
'\\w+' +
- rescape('\v' + hostname + '\v' + filter) +
+ reEscape('\v' + hostname + '\v' + filter) +
'(?:\\n|$)'
);
// If there is no valid domain, there won't be any other
@@ -152,7 +162,7 @@ var fromCosmeticFilter = function(details) {
if ( pos !== -1 ) {
var entity = domain.slice(0, pos);
candidates[entity + '.*##' + filter] = new RegExp(
- rescape('c\ve\v' + entity + '\v' + filter) +
+ reEscape('c\ve\v' + entity + '\v' + filter) +
'(?:\\n|$)'
);
}
diff --git a/src/js/rpcreceiver.js b/src/js/rpcreceiver.js
new file mode 100644
index 0000000..995fd76
--- /dev/null
+++ b/src/js/rpcreceiver.js
@@ -0,0 +1,53 @@
+/*******************************************************************************
+
+ uBlock Origin - a browser extension to block requests.
+ Copyright (C) 2015 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
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+/* global vAPI, µBlock */
+
+/******************************************************************************/
+
+(function() {
+
+'use strict';
+
+/******************************************************************************/
+
+if ( typeof vAPI.rpcReceiver !== 'object' ) {
+ return;
+}
+
+/******************************************************************************/
+
+vAPI.rpcReceiver.getScriptTagFilters = function(details) {
+ var µb = µBlock;
+ var cfe = µb.cosmeticFilteringEngine;
+ if ( !cfe ) { return; }
+ var hostname = details.hostname;
+ return cfe.retrieveScriptTagRegex(
+ µb.URI.domainFromHostname(hostname) || hostname,
+ hostname
+ );
+};
+
+/******************************************************************************/
+
+})();
+
+/******************************************************************************/
diff --git a/src/js/static-net-filtering.js b/src/js/static-net-filtering.js
index 92cc7af..6bbf13a 100644
--- a/src/js/static-net-filtering.js
+++ b/src/js/static-net-filtering.js
@@ -1454,7 +1454,10 @@ FilterParser.prototype.parseOptions = function(s) {
this.parseOptParty(false, not);
continue;
}
- if ( opt === 'elemhide' ) {
+ // https://issues.adblockplus.org/ticket/616
+ // `generichide` concept already supported, just a matter of
+ // adding support for the new keyword.
+ if ( opt === 'elemhide' || opt === 'generichide' ) {
if ( this.action === AllowAction ) {
this.parseOptType('elemhide', false);
this.action = BlockAction;