diff --git a/platform/chromium/vapi-background.js b/platform/chromium/vapi-background.js index 42a9fead5..aca17ca4f 100644 --- a/platform/chromium/vapi-background.js +++ b/platform/chromium/vapi-background.js @@ -824,7 +824,7 @@ vAPI.net.registerListeners = function() { // something else. Test case for "unfriendly" font URLs: // https://www.google.com/fonts if ( details.type === 'object' ) { - if ( headerValue(details.responseHeaders, 'content-type').lastIndexOf('font/', 0) === 0 ) { + if ( headerValue(details.responseHeaders, 'content-type').startsWith('font/') ) { details.type = 'font'; var r = onBeforeRequestClient(details); if ( typeof r === 'object' && r.cancel === true ) { diff --git a/platform/firefox/vapi-background.js b/platform/firefox/vapi-background.js index ca4a32fb4..9b1e2397b 100644 --- a/platform/firefox/vapi-background.js +++ b/platform/firefox/vapi-background.js @@ -2361,7 +2361,7 @@ vAPI.net.registerListeners = function() { var tabId = tabWatcher.tabIdFromTarget(browser); // Ignore notifications related to our popup - if ( details.url.lastIndexOf(vAPI.getURL('popup.html'), 0) === 0 ) { + if ( details.url.startsWith(vAPI.getURL('popup.html')) ) { return; } diff --git a/src/background.html b/src/background.html index cb37ad841..109f9eb6d 100644 --- a/src/background.html +++ b/src/background.html @@ -8,6 +8,7 @@ + diff --git a/src/js/assets.js b/src/js/assets.js index 08616c54f..c000b328d 100644 --- a/src/js/assets.js +++ b/src/js/assets.js @@ -312,7 +312,7 @@ var getTextFileFromURL = function(url, onLoad, onError) { // appears to be a HTML document: could happen when server serves // some kind of error page I suppose var text = this.responseText.trim(); - if ( text.charAt(0) === '<' && text.slice(-1) === '>' ) { + if ( text.startsWith('<') && text.endsWith('>') ) { return onError.call(this); } return onLoad.call(this); diff --git a/src/js/cosmetic-filtering.js b/src/js/cosmetic-filtering.js index e2ea564d4..58f829bda 100644 --- a/src/js/cosmetic-filtering.js +++ b/src/js/cosmetic-filtering.js @@ -191,7 +191,7 @@ var FilterHostname = function(s, hostname) { }; FilterHostname.prototype.retrieve = function(hostname, out) { - if ( hostname.slice(-this.hostname.length) === this.hostname ) { + if ( hostname.endsWith(this.hostname) ) { out.push(this.s); } }; @@ -219,7 +219,7 @@ var FilterEntity = function(s, entity) { }; FilterEntity.prototype.retrieve = function(entity, out) { - if ( entity.slice(-this.entity.length) === this.entity ) { + if ( entity.endsWith(this.entity) ) { out.push(this.s); } }; @@ -244,7 +244,6 @@ var FilterParser = function() { this.hostnames = []; this.invalid = false; this.cosmetic = true; - this.reScriptContains = /^script:contains\(.+?\)$/; }; /******************************************************************************/ @@ -321,7 +320,7 @@ FilterParser.prototype.parse = function(raw) { // 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) === '}' ) { + if ( this.suffix.endsWith('}') ) { // Not supported for now: this code will ensure some backward // compatibility for when cosmetic filters with explicit style // properties start to be in use. @@ -341,7 +340,7 @@ FilterParser.prototype.parse = function(raw) { // Normalize high-medium selectors: `href` is assumed to imply `a` tag. We // need to do this here in order to correctly avoid duplicates. The test // is designed to minimize overhead -- this is a low occurrence filter. - if ( this.suffix.charAt(1) === '[' && this.suffix.slice(2, 9) === 'href^="' ) { + if ( this.suffix.startsWith('[href^="', 1) ) { this.suffix = this.suffix.slice(1); } @@ -357,9 +356,9 @@ FilterParser.prototype.parse = function(raw) { // Inline script tag filter? if ( - this.suffix.charAt(0) !== 's' || - this.reScriptContains.test(this.suffix) === false ) - { + this.suffix.startsWith('script:contains(') === false || + this.suffix.endsWith(')') === false + ) { return this; } @@ -370,17 +369,17 @@ FilterParser.prototype.parse = function(raw) { return this; } - var suffix = this.suffix; + var suffix = this.suffix.slice(16, -1); this.suffix = 'script//:'; // Plain string-based? - if ( suffix.charAt(16) !== '/' || suffix.slice(-2) !== '/)' ) { - this.suffix += suffix.slice(16, -1).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + if ( suffix.startsWith('/') === false || suffix.endsWith('/') === false ) { + this.suffix += suffix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); return this; } // Regex-based - this.suffix += suffix.slice(17, -2).replace(/\\/g, '\\'); + this.suffix += suffix.slice(1, -1); // Valid regex? if ( isBadRegex(this.suffix) ) { @@ -687,7 +686,7 @@ FilterContainer.prototype.isValidSelector = (function() { return true; } catch (e) { } - if ( s.lastIndexOf('script//:', 0) === 0 ) { + if ( s.startsWith('script//:') ) { return true; } console.error('uBlock> invalid cosmetic filter:', s); @@ -730,10 +729,10 @@ FilterContainer.prototype.compile = function(s, out) { var hostname; while ( i-- ) { hostname = hostnames[i]; - if ( hostname.charAt(0) !== '~' ) { + if ( hostname.startsWith('~') === false ) { applyGlobally = false; } - if ( hostname.slice(-2) === '.*' ) { + if ( hostname.endsWith('.*') ) { this.compileEntitySelector(hostname, parsed, out); } else { this.compileHostnameSelector(hostname, parsed, out); @@ -839,7 +838,7 @@ FilterContainer.prototype.reHighMedium = /^\[href\^="https?:\/\/([^"]{8})[^"]*"\ FilterContainer.prototype.compileHostnameSelector = function(hostname, parsed, out) { // https://github.com/chrisaljoudi/uBlock/issues/145 var unhide = parsed.unhide; - if ( hostname.charAt(0) === '~' ) { + if ( hostname.startsWith('~') ) { hostname = hostname.slice(1); unhide ^= 1; } @@ -915,7 +914,7 @@ 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 ) { + if ( fields[3].startsWith('script//:') ) { this.createScriptTagFilter(fields[2], fields[3].slice(9)); continue; } @@ -951,7 +950,7 @@ 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 ) { + if ( fields[2].startsWith('script//:') ) { this.createScriptTagFilter(fields[1], fields[2].slice(9)); continue; } @@ -1224,16 +1223,17 @@ FilterContainer.prototype.addToSelectorCache = function(details) { /******************************************************************************/ FilterContainer.prototype.removeFromSelectorCache = function(targetHostname, type) { + var targetHostnameLength = targetHostname.length; for ( var hostname in this.selectorCache ) { if ( this.selectorCache.hasOwnProperty(hostname) === false ) { continue; } if ( targetHostname !== '*' ) { - if ( hostname.slice(0 - targetHostname.length) !== targetHostname ) { + if ( hostname.endsWith(targetHostname) === false ) { continue; } - if ( hostname.length !== targetHostname.length && - hostname.charAt(0 - targetHostname.length - 1) !== '.' ) { + if ( hostname.length !== targetHostnameLength && + hostname.charAt(hostname.length - targetHostnameLength - 1) !== '.' ) { continue; } } diff --git a/src/js/dynamic-net-filtering.js b/src/js/dynamic-net-filtering.js index bbe75e152..fff159efc 100644 --- a/src/js/dynamic-net-filtering.js +++ b/src/js/dynamic-net-filtering.js @@ -89,7 +89,7 @@ var isIPAddress = function(hostname) { if ( reIPv4VeryCoarse.test(hostname) ) { return true; } - return hostname.charAt(0) === '['; + return hostname.startsWith('['); }; /******************************************************************************/ @@ -325,7 +325,7 @@ var is3rdParty = function(srcHostname, desHostname) { // etc. var srcDomain = domainFromHostname(srcHostname) || srcHostname; - if ( desHostname.slice(0 - srcDomain.length) !== srcDomain ) { + if ( desHostname.endsWith(srcDomain) === false ) { return true; } // Do not confuse 'example.com' with 'anotherexample.com' @@ -548,7 +548,7 @@ Matrix.prototype.fromString = function(text, append) { // Ignore special rules: // hostname-based switch rules - if ( fields[0].slice(-1) === ':' ) { + if ( fields[0].endsWith(':') ) { continue; } diff --git a/src/js/hnswitches.js b/src/js/hnswitches.js index 08d59e9b6..0655d41d2 100644 --- a/src/js/hnswitches.js +++ b/src/js/hnswitches.js @@ -80,7 +80,7 @@ var isIPAddress = function(hostname) { if ( reIPv4VeryCoarse.test(hostname) ) { return true; } - return hostname.charAt(0) === '['; + return hostname.startsWith('['); }; /******************************************************************************/ @@ -177,7 +177,7 @@ HnSwitches.prototype.toggleBranchZ = function(switchName, targetHostname, newSta if ( hostname.length <= targetLen ) { continue; } - if ( hostname.slice(-targetLen) !== targetHostname ) { + if ( hostname.endsWith(targetHostname) === false ) { continue; } if ( hostname.charAt(hostname.length - targetLen - 1) !== '.' ) { diff --git a/src/js/logger-ui-inspector.js b/src/js/logger-ui-inspector.js index 10d2bf29e..7a1341663 100644 --- a/src/js/logger-ui-inspector.js +++ b/src/js/logger-ui-inspector.js @@ -32,7 +32,7 @@ var showdomButton = uDom.nodeFromId('showdom'); // Don't bother if the browser is not modern enough. -if ( typeof Map === undefined || typeof WeakMap === undefined ) { +if ( typeof Map === undefined || Map.polyfill || typeof WeakMap === undefined ) { showdomButton.classList.add('disabled'); return; } diff --git a/src/js/messaging.js b/src/js/messaging.js index 802107905..847e12f9e 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -1287,7 +1287,7 @@ var onMessage = function(request, sender, callback) { if ( pageStore === null ) { continue; } - if ( pageStore.rawURL.lastIndexOf(loggerURL, 0) === 0 ) { + if ( pageStore.rawURL.startsWith(loggerURL) ) { continue; } tabIds[tabId] = pageStore.title; diff --git a/src/js/polyfill.js b/src/js/polyfill.js new file mode 100644 index 000000000..f6e280e8a --- /dev/null +++ b/src/js/polyfill.js @@ -0,0 +1,146 @@ +/******************************************************************************* + + 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 +*/ + +/******************************************************************************/ + +(function() { + +'use strict'; + +/******************************************************************************/ + +// https://github.com/gorhill/uBlock/issues/1067 +// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith +// Firefox 17/Chromium 41 supports `startsWith`. + +if ( String.prototype.startsWith instanceof Function === false ) { + String.prototype.startsWith = function(needle, pos) { + if ( typeof pos !== 'number' ) { + pos = 0; + } + return this.lastIndexOf(needle, pos) === pos; + }; +} + +// https://github.com/gorhill/uBlock/issues/1067 +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith +// Firefox 17/Chromium 41 supports `endsWith`. + +if ( String.prototype.endsWith instanceof Function === false ) { + String.prototype.endsWith = function(needle, pos) { + if ( typeof pos !== 'number' ) { + pos = this.length; + } + pos -= needle.length; + return this.indexOf(needle, pos) === pos; + }; +} + +/******************************************************************************/ + +// https://github.com/gorhill/uBlock/issues/1070 +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set#Browser_compatibility +// This polyfill is designed to fulfill *only* what uBlock Origin needs -- this +// is not an accurate API of the real Set() type. + +if ( typeof self.Set !== 'function' ) { + self.Set = function() { + this.clear(); + }; + + self.Set.polyfill = true; + + self.Set.prototype.clear = function() { + this._set = Object.create(null); + this.size = 0; + }; + + self.Set.prototype.add = function(k) { + if ( this._set[k] === undefined ) { + this._set[k] = true; + this.size += 1; + } + }; + + self.Set.prototype.delete = function(k) { + if ( this._set[k] !== undefined ) { + delete this._set[k]; + this.size -= 1; + } + }; + + self.Set.prototype.has = function(k) { + return this._set[k] !== undefined; + }; +} + +/******************************************************************************/ + +// https://github.com/gorhill/uBlock/issues/1070 +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set#Browser_compatibility +// This polyfill is designed to fulfill *only* what uBlock Origin needs -- this +// is not an accurate API of the real Map() type. + +if ( typeof self.Map !== 'function' ) { + self.Map = function() { + this.clear(); + }; + + self.Map.polyfill = true; + + self.Map.prototype.clear = function() { + this._map = Object.create(null); + this.size = 0; + }; + + self.Map.prototype.delete = function(k) { + if ( this._map[k] !== undefined ) { + delete this._map[k]; + this.size -= 1; + } + }; + + self.Map.prototype.get = function(k) { + return this._map[k]; + }; + + self.Set.prototype.has = function(k) { + return this._map[k] !== undefined; + }; + + self.Map.prototype.set = function(k, v) { + if ( v !== undefined ) { + if ( this._map[k] === undefined ) { + this.size += 1; + } + this._map[k] = v; + } else { + if ( this._map[k] !== undefined ) { + this.size -= 1; + } + delete this._map[k]; + } + }; +} + +/******************************************************************************/ + +})(); diff --git a/src/js/redirect-engine.js b/src/js/redirect-engine.js index a5357f079..cb7cb25da 100644 --- a/src/js/redirect-engine.js +++ b/src/js/redirect-engine.js @@ -227,11 +227,11 @@ RedirectEngine.prototype.compileRuleFromStaticFilter = function(line) { var srcs = []; var options = matches[3].split(','), option; while ( (option = options.pop()) ) { - if ( option.lastIndexOf('redirect=', 0) === 0 ) { + if ( option.startsWith('redirect=') ) { redirect = option.slice(9); continue; } - if ( option.lastIndexOf('domain=', 0) === 0 ) { + if ( option.startsWith('domain=') ) { srcs = option.slice(7).split('|'); continue; } @@ -251,7 +251,7 @@ RedirectEngine.prototype.compileRuleFromStaticFilter = function(line) { } // Need one single type -- not negated. - if ( type === undefined || type.charAt(0) === '~' ) { + if ( type === undefined || type.startsWith('~') ) { return; } @@ -270,7 +270,7 @@ RedirectEngine.prototype.compileRuleFromStaticFilter = function(line) { if ( src === '' ) { continue; } - if ( src.charAt(0) === '~' ) { + if ( src.startsWith('~') ) { continue; } // Need at least one specific src or des. @@ -376,7 +376,7 @@ RedirectEngine.prototype.resourcesFromString = function(text) { line = text.slice(lineBeg, lineEnd); lineBeg = lineEnd + 1; - if ( line.charAt(0) === '#' ) { + if ( line.startsWith('#') ) { continue; } diff --git a/src/js/reverselookup-worker.js b/src/js/reverselookup-worker.js index 946001cf1..fb6582b9b 100644 --- a/src/js/reverselookup-worker.js +++ b/src/js/reverselookup-worker.js @@ -99,7 +99,7 @@ var fromNetFilter = function(details) { var fromCosmeticFilter = function(details) { var filter = details.rawFilter; - var exception = filter.lastIndexOf('#@#', 0) === 0; + var exception = filter.startsWith('#@#'); filter = exception ? filter.slice(3) : filter.slice(2); diff --git a/src/js/static-net-filtering.js b/src/js/static-net-filtering.js index 3c38b593c..5d9f6b28d 100644 --- a/src/js/static-net-filtering.js +++ b/src/js/static-net-filtering.js @@ -168,13 +168,11 @@ var atoi = function(s) { return cachedParseInt(s, 10); }; -var isFirstParty = function(firstPartyDomain, hostname) { - if ( hostname.slice(0 - firstPartyDomain.length) !== firstPartyDomain ) { - return false; - } +var isFirstParty = function(domain, hostname) { // Be sure to not confuse 'example.com' with 'anotherexample.com' - var c = hostname.charAt(hostname.length - firstPartyDomain.length - 1); - return c === '.' || c === ''; + return hostname.endsWith(domain) && + (hostname.length === domain.length || + hostname.charAt(hostname.length - domain.length - 1) === '.'); }; var isBadRegex = function(s) { @@ -235,26 +233,25 @@ var hostnameTestPicker = function(owner) { // Only one hostname if ( domainOpt.indexOf('|') === -1 ) { - return domainOpt.charAt(0) !== '~' ? hostnameHitTest : hostnameMissTest; + return domainOpt.startsWith('~') ? hostnameMissTest : hostnameHitTest; } // Multiple hostnames: use a dictionary. - var dict = owner._hostnameDict = Object.create(null); var hostnames = domainOpt.split('|'); - var i, hostname; + var i, hostname, dict; // First find out whether we have a homogeneous dictionary var hit = false, miss = false; i = hostnames.length; while ( i-- ) { - if ( hostnames[i].charAt(0) !== '~' ) { - hit = true; - if ( miss ) { + if ( hostnames[i].startsWith('~') ) { + miss = true; + if ( hit ) { break; } } else { - miss = true; - if ( hit ) { + hit = true; + if ( miss ) { break; } } @@ -264,40 +261,44 @@ var hostnameTestPicker = function(owner) { // Spotted one occurrence in EasyList Lite (cjxlist.txt): // domain=photobucket.com|~secure.photobucket.com if ( hit && miss ) { + dict = owner._hostnameDict = new Map(); i = hostnames.length; while ( i-- ) { hostname = hostnames[i]; - if ( hostname.charAt(0) !== '~' ) { - dict[hostname] = true; + if ( hostname.startsWith('~') ) { + dict.set(hostname.slice(1), false); } else { - dict[hostname.slice(1)] = false; + dict.set(hostname, true); } } return hostnameMixedSetTest; } // Homogeneous dictionary. + dict = owner._hostnameDict = new Set(); i = hostnames.length; while ( i-- ) { hostname = hostnames[i]; - if ( hostname.charAt(0) !== '~' ) { - dict[hostname] = true; - } else { - dict[hostname.slice(1)] = true; - } + dict.add(hostname.startsWith('~') ? hostname.slice(1) : hostname); } return hit ? hostnameHitSetTest : hostnameMissSetTest; }; var hostnameHitTest = function(owner) { - var hostname = owner.domainOpt; - return pageHostnameRegister.slice(0 - hostname.length) === hostname; + var current = pageHostnameRegister; + var target = owner.domainOpt; + return current.endsWith(target) && + (current.length === target.length || + current.charAt(current.length - target.length - 1) === '.'); }; var hostnameMissTest = function(owner) { - var hostname = owner.domainOpt; - return pageHostnameRegister.slice(1 - hostname.length) !== hostname.slice(1); + var current = pageHostnameRegister; + var target = owner.domainOpt; + return current.endsWith(target) === false || + (current.length !== target.length && + current.charAt(current.length - target.length - 1) !== '.'); }; var hostnameHitSetTest = function(owner) { @@ -305,7 +306,7 @@ var hostnameHitSetTest = function(owner) { var needle = pageHostnameRegister; var pos; for (;;) { - if ( dict[needle] ) { + if ( dict.has(needle) ) { return true; } pos = needle.indexOf('.'); @@ -322,7 +323,7 @@ var hostnameMissSetTest = function(owner) { var needle = pageHostnameRegister; var pos; for (;;) { - if ( dict[needle] ) { + if ( dict.has(needle) ) { return false; } pos = needle.indexOf('.'); @@ -341,11 +342,11 @@ var hostnameMixedSetTest = function(owner) { var hit = false; var v, pos; for (;;) { - v = dict[needle] || undefined; + v = dict.get(needle); if ( v === false ) { return false; } - if ( v /* === true */ ) { + if ( v === true ) { hit = true; } pos = needle.indexOf('.'); @@ -393,7 +394,7 @@ var FilterPlain = function(s, tokenBeg) { }; FilterPlain.prototype.match = function(url, tokenBeg) { - return url.substr(tokenBeg - this.tokenBeg, this.s.length) === this.s; + return url.startsWith(this.s, tokenBeg - this.tokenBeg); }; FilterPlain.fid = @@ -424,8 +425,8 @@ var FilterPlainHostname = function(s, tokenBeg, domainOpt) { }; FilterPlainHostname.prototype.match = function(url, tokenBeg) { - return this.hostnameTest(this) && - url.substr(tokenBeg - this.tokenBeg, this.s.length) === this.s; + return url.startsWith(this.s, tokenBeg - this.tokenBeg) && + this.hostnameTest(this); }; FilterPlainHostname.fid = @@ -453,7 +454,7 @@ var FilterPlainPrefix0 = function(s) { }; FilterPlainPrefix0.prototype.match = function(url, tokenBeg) { - return url.substr(tokenBeg, this.s.length) === this.s; + return url.startsWith(this.s, tokenBeg); }; FilterPlainPrefix0.fid = @@ -482,8 +483,8 @@ var FilterPlainPrefix0Hostname = function(s, domainOpt) { }; FilterPlainPrefix0Hostname.prototype.match = function(url, tokenBeg) { - return this.hostnameTest(this) && - url.substr(tokenBeg, this.s.length) === this.s; + return url.startsWith(this.s, tokenBeg) && + this.hostnameTest(this); }; FilterPlainPrefix0Hostname.fid = @@ -511,7 +512,7 @@ var FilterPlainPrefix1 = function(s) { }; FilterPlainPrefix1.prototype.match = function(url, tokenBeg) { - return url.substr(tokenBeg - 1, this.s.length) === this.s; + return url.startsWith(this.s, tokenBeg - 1); }; FilterPlainPrefix1.fid = @@ -540,8 +541,8 @@ var FilterPlainPrefix1Hostname = function(s, domainOpt) { }; FilterPlainPrefix1Hostname.prototype.match = function(url, tokenBeg) { - return this.hostnameTest(this) && - url.substr(tokenBeg - 1, this.s.length) === this.s; + return url.startsWith(this.s, tokenBeg - 1) && + this.hostnameTest(this); }; FilterPlainPrefix1Hostname.fid = @@ -569,7 +570,7 @@ var FilterPlainLeftAnchored = function(s) { }; FilterPlainLeftAnchored.prototype.match = function(url) { - return url.slice(0, this.s.length) === this.s; + return url.startsWith(this.s); }; FilterPlainLeftAnchored.fid = @@ -598,8 +599,8 @@ var FilterPlainLeftAnchoredHostname = function(s, domainOpt) { }; FilterPlainLeftAnchoredHostname.prototype.match = function(url) { - return this.hostnameTest(this) && - url.slice(0, this.s.length) === this.s; + return url.startsWith(this.s) && + this.hostnameTest(this); }; FilterPlainLeftAnchoredHostname.fid = @@ -627,7 +628,7 @@ var FilterPlainRightAnchored = function(s) { }; FilterPlainRightAnchored.prototype.match = function(url) { - return url.slice(-this.s.length) === this.s; + return url.endsWith(this.s); }; FilterPlainRightAnchored.fid = @@ -656,8 +657,8 @@ var FilterPlainRightAnchoredHostname = function(s, domainOpt) { }; FilterPlainRightAnchoredHostname.prototype.match = function(url) { - return this.hostnameTest(this) && - url.slice(-this.s.length) === this.s; + return url.endsWith(this.s) && + this.hostnameTest(this); }; FilterPlainRightAnchoredHostname.fid = @@ -688,7 +689,7 @@ var FilterPlainHnAnchored = function(s) { }; FilterPlainHnAnchored.prototype.match = function(url, tokenBeg) { - if ( url.substr(tokenBeg, this.s.length) !== this.s ) { + if ( url.startsWith(this.s, tokenBeg) === false ) { return false; } // Valid only if hostname-valid characters to the left of token @@ -715,6 +716,7 @@ FilterPlainHnAnchored.fromSelfie = function(s) { }; // https://www.youtube.com/watch?v=71YS6xDB-E4 +// https://www.youtube.com/watch?v=qBPML7ton0E /******************************************************************************/ @@ -727,10 +729,10 @@ var FilterPlainHnAnchoredHostname = function(s, domainOpt) { }; FilterPlainHnAnchoredHostname.prototype.match = function(url, tokenBeg) { - if ( this.hostnameTest(this) === false ) { - return false; - } - if ( url.substr(tokenBeg, this.s.length) !== this.s ) { + if ( + url.startsWith(this.s, tokenBeg) === false || + this.hostnameTest(this) === false + ) { return false; } // Valid only if hostname-valid characters to the left of token @@ -805,10 +807,8 @@ FilterGenericHostname.prototype = Object.create(FilterGeneric.prototype); FilterGenericHostname.prototype.constructor = FilterGenericHostname; FilterGenericHostname.prototype.match = function(url) { - if ( this.hostnameTest(this) === false ) { - return false; - } - return FilterGeneric.prototype.match.call(this, url); + return this.hostnameTest(this) && + FilterGeneric.prototype.match.call(this, url); }; FilterGenericHostname.fid = @@ -884,10 +884,8 @@ FilterGenericHnAnchoredHostname.prototype = Object.create(FilterGenericHnAnchore FilterGenericHnAnchoredHostname.prototype.constructor = FilterGenericHnAnchoredHostname; FilterGenericHnAnchoredHostname.prototype.match = function(url) { - if ( this.hostnameTest(this) === false ) { - return false; - } - return FilterGenericHnAnchored.prototype.match.call(this, url); + return this.hostnameTest(this) && + FilterGenericHnAnchored.prototype.match.call(this, url); }; FilterGenericHnAnchoredHostname.fid = @@ -1004,7 +1002,7 @@ FilterHostnameDict.prototype.cutoff = 250; FilterHostnameDict.prototype.meltBucket = function(len, bucket) { var map = {}; - if ( bucket.charAt(0) === ' ' ) { + if ( bucket.startsWith(' ') ) { bucket.trim().split(' ').map(function(k) { map[k] = true; }); @@ -1105,7 +1103,7 @@ FilterHostnameDict.prototype.matchesExactly = function(hn) { if ( typeof bucket === 'object' ) { bucket = this.dict[key] = this.freezeBucket(bucket); } - if ( bucket.charAt(0) === ' ' ) { + if ( bucket.startsWith(' ') ) { return bucket.indexOf(' ' + hn + ' ') !== -1; } // binary search @@ -1379,7 +1377,7 @@ FilterParser.prototype.parseOptions = function(s) { var opt, not; for ( var i = 0; i < opts.length; i++ ) { opt = opts[i]; - not = opt.charAt(0) === '~'; + not = opt.startsWith('~'); if ( not ) { opt = opt.slice(1); } @@ -1411,7 +1409,7 @@ FilterParser.prototype.parseOptions = function(s) { this.parseOptType(opt, not); continue; } - if ( opt.lastIndexOf('domain=', 0) === 0 ) { + if ( opt.startsWith('domain=') ) { this.domainOpt = opt.slice(7); continue; } @@ -1423,7 +1421,7 @@ FilterParser.prototype.parseOptions = function(s) { this.parseOptParty(true, not); continue; } - if ( opt.lastIndexOf('redirect=', 0) === 0 ) { + if ( opt.startsWith('redirect=') ) { if ( this.action === BlockAction ) { this.redirect = true; continue; @@ -1466,7 +1464,7 @@ FilterParser.prototype.parse = function(raw) { // block or allow filter? // Important: this must be executed before parsing options - if ( s.lastIndexOf('@@', 0) === 0 ) { + if ( s.startsWith('@@') ) { this.action = AllowAction; s = s.slice(2); } @@ -1475,7 +1473,7 @@ FilterParser.prototype.parse = function(raw) { // https://github.com/gorhill/uBlock/issues/842 // - ensure sure we are not dealing with a regex-based filter. // - lookup the last occurrence of `$`. - if ( s.charAt(0) !== '/' || s.slice(-1) !== '/' ) { + if ( s.startsWith('/') === false || s.endsWith('/') === false ) { pos = s.lastIndexOf('$'); if ( pos !== -1 ) { // https://github.com/gorhill/uBlock/issues/952 @@ -1490,7 +1488,7 @@ FilterParser.prototype.parse = function(raw) { } // regex? - if ( s.charAt(0) === '/' && s.slice(-1) === '/' && s.length > 2 ) { + if ( s.startsWith('/') && s.endsWith('/') && s.length > 2 ) { this.isRegex = true; this.f = s.slice(1, -1); if ( isBadRegex(this.f) ) { @@ -1505,7 +1503,7 @@ FilterParser.prototype.parse = function(raw) { } // hostname-anchored - if ( s.lastIndexOf('||', 0) === 0 ) { + if ( s.startsWith('||') ) { this.hostnameAnchored = true; s = s.slice(2); @@ -1519,7 +1517,7 @@ FilterParser.prototype.parse = function(raw) { } // https://github.com/chrisaljoudi/uBlock/issues/1096 - if ( s.charAt(0) === '^' ) { + if ( s.startsWith('^') ) { this.unsupported = true; return this; } @@ -1533,13 +1531,13 @@ FilterParser.prototype.parse = function(raw) { } // left-anchored - if ( s.charAt(0) === '|' ) { + if ( s.startsWith('|') ) { this.anchor = -1; s = s.slice(1); } // right-anchored - if ( s.slice(-1) === '|' ) { + if ( s.endsWith('|') ) { this.anchor = 1; s = s.slice(0, -1); } @@ -1547,11 +1545,11 @@ FilterParser.prototype.parse = function(raw) { // normalize placeholders if ( this.reHasWildcard.test(s) ) { // remove pointless leading * - if ( s.charAt(0) === '*' ) { + if ( s.startsWith('*') ) { s = s.replace(/^\*+([^%0-9a-z])/, '$1'); } // remove pointless trailing * - if ( s.slice(-1) === '*' ) { + if ( s.endsWith('*') ) { s = s.replace(/([^%0-9a-z])\*+$/, '$1'); } } diff --git a/src/js/storage.js b/src/js/storage.js index 100ded88c..518abfa8f 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -125,7 +125,7 @@ // https://github.com/gorhill/uBlock/issues/277 // uBlock's filter lists are always enabled by default, so we // have to include in backup only those which are turned off. - if ( path.lastIndexOf('assets/ublock/', 0) === 0 ) { + if ( path.startsWith('assets/ublock/') ) { if ( entry.off !== true ) { delete result[path]; } diff --git a/src/js/tab.js b/src/js/tab.js index 855e3770e..4b9ce8546 100644 --- a/src/js/tab.js +++ b/src/js/tab.js @@ -453,7 +453,7 @@ vAPI.tabs.onNavigation = function(details) { // TODO: Eventually, we will have to use an API to check whether a scheme // is supported as I suspect we are going to start to see `ws`, `wss` // as well soon. - if ( pageStore && tabContext.rawURL.lastIndexOf('http', 0) === 0 ) { + if ( pageStore && tabContext.rawURL.startsWith('http') ) { pageStore.hostnameToCountMap[tabContext.rootHostname] = 0; } }; @@ -618,7 +618,7 @@ vAPI.tabs.onPopupUpdated = (function() { // If the page URL is that of our "blocked page" URL, extract the URL of // the page which was blocked. - if ( targetURL.lastIndexOf(vAPI.getURL('document-blocked.html'), 0) === 0 ) { + if ( targetURL.startsWith(vAPI.getURL('document-blocked.html')) ) { var matches = /details=([^&]+)/.exec(targetURL); if ( matches !== null ) { targetURL = JSON.parse(atob(matches[1])).url; diff --git a/src/js/ublock.js b/src/js/ublock.js index 5128f05b8..e9bc3dc86 100644 --- a/src/js/ublock.js +++ b/src/js/ublock.js @@ -47,7 +47,9 @@ var isHandcraftedWhitelistDirective = function(directive) { var matchWhitelistDirective = function(url, hostname, directive) { // Directive is a plain hostname if ( directive.indexOf('/') === -1 ) { - return hostname.slice(-directive.length) === directive; + return hostname.endsWith(directive) && + (hostname.length === directive.length || + hostname.charAt(hostname.length - directive.length - 1) === '.'); } // Match URL exactly if ( directive.indexOf('*') === -1 ) { @@ -187,7 +189,7 @@ var matchWhitelistDirective = function(url, hostname, directive) { } // Don't throw out commented out lines: user might want to fix them - if ( line.charAt(0) === '#' ) { + if ( line.startsWith('#') ) { key = '#'; directive = line; } diff --git a/src/js/uritools.js b/src/js/uritools.js index 7c11a1191..bdc23e14f 100644 --- a/src/js/uritools.js +++ b/src/js/uritools.js @@ -179,7 +179,7 @@ URI.set = function(uri) { } this.hostname = matches[1] !== undefined ? matches[1] : ''; // http://en.wikipedia.org/wiki/FQDN - if ( this.hostname.slice(-1) === '.' ) { + if ( this.hostname.endsWith('.') ) { this.hostname = this.hostname.slice(0, -1); } this.port = matches[2] !== undefined ? matches[2].slice(1) : ''; @@ -271,7 +271,7 @@ URI.hostnameFromURI = function(uri) { } // http://en.wikipedia.org/wiki/FQDN var hostname = matches[1]; - if ( hostname.slice(-1) === '.' ) { + if ( hostname.endsWith('.') ) { hostname = hostname.slice(0, -1); } return hostname.toLowerCase(); diff --git a/src/js/url-net-filtering.js b/src/js/url-net-filtering.js index 4525786d3..a027cec69 100644 --- a/src/js/url-net-filtering.js +++ b/src/js/url-net-filtering.js @@ -92,7 +92,7 @@ var indexOfMatch = function(urls, url) { if ( entry.url.length > urlLen ) { continue; } - if ( url.lastIndexOf(entry.url, 0) === 0 ) { + if ( url.startsWith(entry.url) ) { return i; } }