diff --git a/platform/chromium/vapi-background.js b/platform/chromium/vapi-background.js index c556ee59d..a4e341bd7 100644 --- a/platform/chromium/vapi-background.js +++ b/platform/chromium/vapi-background.js @@ -282,21 +282,12 @@ vAPI.tabs = {}; /******************************************************************************/ -// https://github.com/gorhill/uBlock/issues/3546 -// Added a new flavor of behind-the-scene tab id: vAPI.anyTabId. -// vAPI.anyTabId will be used for network requests which can be filtered, -// because they comes with enough contextual information. It's just not -// possible to pinpoint exactly from which tab it comes from. For example, -// with Firefox/webext, the `documentUrl` property is available for every -// network requests. - vAPI.isBehindTheSceneTabId = function(tabId) { return tabId < 0; }; vAPI.unsetTabId = 0; vAPI.noTabId = -1; // definitely not any existing tab -vAPI.anyTabId = -2; // one of the existing tab /******************************************************************************/ diff --git a/platform/chromium/vapi-webrequest.js b/platform/chromium/vapi-webrequest.js index f9a8d97f5..e949b01dd 100644 --- a/platform/chromium/vapi-webrequest.js +++ b/platform/chromium/vapi-webrequest.js @@ -74,11 +74,9 @@ // Chromium 63+ supports the `initiator` property, which contains // the URL of the origin from which the network request was made. if ( - details.tabId === vAPI.noTabId && typeof details.initiator === 'string' && details.initiator !== 'null' ) { - details.tabId = vAPI.anyTabId; details.documentUrl = details.initiator; } diff --git a/platform/firefox/vapi-webrequest.js b/platform/firefox/vapi-webrequest.js index 398d853bb..c2c1c9043 100644 --- a/platform/firefox/vapi-webrequest.js +++ b/platform/firefox/vapi-webrequest.js @@ -1,7 +1,7 @@ /******************************************************************************* uBlock Origin - a browser extension to block requests. - Copyright (C) 2017-2018 Raymond Hill + Copyright (C) 2017-present 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 @@ -66,13 +66,6 @@ let parsedURL = new URL('about:blank'); vAPI.net.normalizeDetails = function(details) { - if ( - details.tabId === vAPI.noTabId && - typeof details.documentUrl === 'string' - ) { - details.tabId = vAPI.anyTabId; - } - if ( mustPunycode && !reAsciiHostname.test(details.url) ) { parsedURL.href = details.url; details.url = details.url.replace( diff --git a/platform/webext/vapi-webrequest.js b/platform/webext/vapi-webrequest.js index a388adcc0..92f902d02 100644 --- a/platform/webext/vapi-webrequest.js +++ b/platform/webext/vapi-webrequest.js @@ -85,13 +85,11 @@ vAPI.net.registerListeners = function() { // Chromium uses `initiator` property. if ( details.documentUrl === undefined && - typeof details.initiator === 'string' + typeof details.initiator === 'string' && + details.initiator !== 'null' ) { details.documentUrl = details.initiator; } - if ( typeof details.documentUrl === 'string' ) { - details.tabId = vAPI.anyTabId; - } } // https://github.com/gorhill/uBlock/issues/1493 diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index 0ce842287..07c262708 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -536,7 +536,7 @@ "description":"Appears in the logger's tab selector" }, "logBehindTheScene":{ - "message":"Behind the scene", + "message":"Tabless", "description":"Pretty name for behind-the-scene network requests" }, "loggerCurrentTab":{ diff --git a/src/background.html b/src/background.html index fa1327b31..29b9a2970 100644 --- a/src/background.html +++ b/src/background.html @@ -20,6 +20,7 @@ + diff --git a/src/css/logger-ui.css b/src/css/logger-ui.css index 925f52f98..a2170377b 100644 --- a/src/css/logger-ui.css +++ b/src/css/logger-ui.css @@ -16,9 +16,11 @@ textarea { width: 100%; } .permatoolbar { + align-items: center; background-color: white; border: 0; box-sizing: border-box; + display: flex; font-size: 120%; left: 0; margin: 0; @@ -28,14 +30,15 @@ textarea { z-index: 10; } .permatoolbar .button { + align-items: center; background-color: white; border: none; box-sizing: border-box; cursor: pointer; - display: inline-block; + display: inline-flex; font-size: 150%; margin: 0; - padding: 8px; + padding: 0.25em; } .permatoolbar .button.disabled { opacity: 0.2; @@ -54,8 +57,14 @@ textarea { vertical-align: middle; } #pageSelector { + margin-right: 1em; + padding: 0.25em 0; width: 28em; - padding: 0.2em 0; + } +#showpopup img { + filter: grayscale(100%); + height: auto; + width: 1em; } /* @@ -128,19 +137,22 @@ textarea { width: 4.6em; } #netInspector table > colgroup > col:nth-of-type(2) { - width: 2.1em; + width: 16%; } #netInspector table > colgroup > col:nth-of-type(3) { - width: 20%; - } -#netInspector table > colgroup > col:nth-of-type(4) { width: 2.1em; } +#netInspector table > colgroup > col:nth-of-type(4) { + width: 20%; + } #netInspector table > colgroup > col:nth-of-type(5) { - width: 5.8em; + width: 2.4em; } #netInspector table > colgroup > col:nth-of-type(6) { - width: calc(100% - 14.6em - 20%); + width: 6em; + } +#netInspector table > colgroup > col:nth-of-type(7) { + width: calc(100% - 4.6em - 16% - 2.1em - 20% - 2.4em - 6em); } #netInspector.f table tr.f { display: none; @@ -205,6 +217,12 @@ body #netInspector td { text-overflow: ellipsis; white-space: nowrap; } +#netInspector tr[data-tabid].void { + opacity: 0.3; + } +#netInspector tr[data-tabid].void:hover { + opacity: 0.7; + } #netInspector tr td:nth-of-type(1) { cursor: default; @@ -212,68 +230,71 @@ body #netInspector td { white-space: nowrap; } #netInspector tr td:nth-of-type(2) { - text-align: center; - white-space: nowrap; } -#netInspector tr.tab_bts > td:nth-of-type(2):before { - content: '\f070'; - font: 1em FontAwesome; - } -#netInspector tr.tab:not(.canMtx) { - opacity: 0.3; - } -#netInspector tr.tab:not(.canMtx):hover { - opacity: 0.7; - } -#netInspector tr.tab:not(.canMtx) > td:nth-of-type(2):before { - content: '\f00d'; - font: 1em FontAwesome; - } -body:not(.popupOn) #netInspector tr.canMtx td:nth-of-type(2) { +#netInspector tr.canLookup td:nth-of-type(2) { cursor: zoom-in; } -body:not(.popupOn) #netInspector tr.canMtx td:nth-of-type(2):hover { - background: #ccc; - } -#netInspector tr.canLookup td:nth-of-type(3) { - cursor: zoom-in; - } -#netInspector tr.cat_net td:nth-of-type(4), -#netInspector tr.cat_cosmetic td:nth-of-type(4), -#netInspector tr.cat_redirect td:nth-of-type(4) { +#netInspector tr.cat_net td:nth-of-type(3), +#netInspector tr.cat_cosmetic td:nth-of-type(3), +#netInspector tr.cat_redirect td:nth-of-type(3) { font: 12px monospace; text-align: center; white-space: nowrap; } -#netInspector tr.cat_net td:nth-of-type(4) { +#netInspector tr.cat_net td:nth-of-type(3) { cursor: pointer; - position: relative; } -#netInspector tr.cat_net td:nth-of-type(4):hover { +#netInspector tr.cat_net td:nth-of-type(3):hover { background: #ccc; } -#netInspector tr.cat_net td:nth-of-type(6) > span > b { +#netInspector tr td:nth-of-type(4) { + } +#netInspector tr[data-dochn] td:nth-of-type(4) { + direction: rtl; + } +#netInspector tr td:nth-of-type(5) { + cursor: default; + position: relative; + text-align: center; + } +#netInspector tr.tab_bts td:nth-of-type(5)::after { + border: 5px solid #bbb; + border-bottom: 0; + border-top: 0; + bottom: 0; + content: '\00a0'; + left: 0; + position: absolute; + right: 0; + top: 0; + width: calc(100% - 10px); + z-index: -1; + } +#netInspector tr td:nth-of-type(6) { + text-align: right; + } +#netInspector tr.cat_net td:nth-of-type(7) > span > b { font-weight: bold; } -#netInspector tr td:nth-of-type(6) b { +#netInspector tr td:nth-of-type(7) b { font-weight: normal; } -#netInspector tr.blocked td:nth-of-type(6) b { +#netInspector tr.blocked td:nth-of-type(7) b { background-color: rgba(192, 0, 0, 0.2); } -body.colorBlind #netInspector tr.blocked td:nth-of-type(6) b { +body.colorBlind #netInspector tr.blocked td:nth-of-type(7) b { background-color: rgba(0, 19, 110, 0.2); } -#netInspector tr.nooped td:nth-of-type(6) b { +#netInspector tr.nooped td:nth-of-type(7) b { background-color: rgba(108, 108, 108, 0.2); } -body.colorBlind #netInspector tr.nooped td:nth-of-type(6) b { +body.colorBlind #netInspector tr.nooped td:nth-of-type(7) b { background-color: rgba(96, 96, 96, 0.2); } -#netInspector tr.allowed td:nth-of-type(6) b { +#netInspector tr.allowed td:nth-of-type(7) b { background-color: rgba(0, 160, 0, 0.2); } -body.colorBlind #netInspector tr.allowed td:nth-of-type(6) b { +body.colorBlind #netInspector tr.allowed td:nth-of-type(7) b { background-color: rgba(255, 194, 57, 0.2); } @@ -281,49 +302,17 @@ body.colorBlind #netInspector tr.allowed td:nth-of-type(6) b { background: white; border: 1px solid gray; border-radius: 3px; + bottom: 0; box-sizing: border-box; display: none; overflow: hidden; position: fixed; right: 0; - top: 0; z-index: 200; } -#netInspector.popupOn #popupContainer { +#inspectors.popupOn #popupContainer { display: block; } -#popupContainer > div { - background: #aaa; - border: 0; - } -body[dir="ltr"] #popupContainer > div { - text-align: right; - } -body[dir="rtl"] #popupContainer > div { - text-align: left; - } -#popupContainer > div > span { - color: #eee; - cursor: pointer; - display: inline-block; - font: 14px FontAwesome; - padding: 0 3px; - } -#popupContainer > div > span:hover { - color: white; - } -#popupContainer > iframe { - border: 0; - padding: 0; - margin: 0; - width: 100%; - } -#popupContainer.hide { - width: 6em !important; - } -#popupContainer.hide > iframe { - display: none; - } .modalDialog { align-items: center; diff --git a/src/js/assets.js b/src/js/assets.js index cd1869249..b80c6ba21 100644 --- a/src/js/assets.js +++ b/src/js/assets.js @@ -27,13 +27,12 @@ /******************************************************************************/ -var reIsExternalPath = /^(?:[a-z-]+):\/\//, +const reIsExternalPath = /^(?:[a-z-]+):\/\//, reIsUserAsset = /^user-/, errorCantConnectTo = vAPI.i18n('errorCantConnectTo'), noopfunc = function(){}; -var api = { -}; +const api = {}; /******************************************************************************/ @@ -64,14 +63,14 @@ var fireNotification = function(topic, details) { /******************************************************************************/ api.fetchText = function(url, onLoad, onError) { - var isExternal = reIsExternalPath.test(url), - actualUrl = isExternal ? url : vAPI.getURL(url); + const isExternal = reIsExternalPath.test(url); + let actualUrl = isExternal ? url : vAPI.getURL(url); // https://github.com/gorhill/uBlock/issues/2592 // Force browser cache to be bypassed, but only for resources which have // been fetched more than one hour ago. if ( isExternal ) { - var queryValue = '_=' + Math.floor(Date.now() / 7200000); + const queryValue = '_=' + Math.floor(Date.now() / 7200000); if ( actualUrl.indexOf('?') === -1 ) { actualUrl += '?'; } else { @@ -84,12 +83,12 @@ api.fetchText = function(url, onLoad, onError) { onError = onLoad; } - var contentLoaded = 0, - timeoutAfter = µBlock.hiddenSettings.assetFetchTimeout * 1000 || 30000, - timeoutTimer, - xhr = new XMLHttpRequest(); + const timeoutAfter = µBlock.hiddenSettings.assetFetchTimeout * 1000 || 30000; + const xhr = new XMLHttpRequest(); + let contentLoaded = 0; + let timeoutTimer; - var cleanup = function() { + const cleanup = function() { xhr.removeEventListener('load', onLoadEvent); xhr.removeEventListener('error', onErrorEvent); xhr.removeEventListener('abort', onErrorEvent); @@ -101,11 +100,11 @@ api.fetchText = function(url, onLoad, onError) { }; // https://github.com/gorhill/uMatrix/issues/15 - var onLoadEvent = function() { + const onLoadEvent = function() { cleanup(); // xhr for local files gives status 0, but actually succeeds - var details = { - url: url, + const details = { + url, content: '', statusCode: this.status || 200, statusText: this.statusText || '' @@ -120,7 +119,7 @@ api.fetchText = function(url, onLoad, onError) { // we never download anything else than plain text: discard if response // appears to be a HTML document: could happen when server serves // some kind of error page I suppose - var text = this.responseText.trim(); + const text = this.responseText.trim(); if ( text.startsWith('<') && text.endsWith('>') ) { return onError.call(null, details); } @@ -128,19 +127,21 @@ api.fetchText = function(url, onLoad, onError) { onLoad(details); }; - var onErrorEvent = function() { + const onErrorEvent = function() { cleanup(); - µBlock.logger.writeOne('', 'error', errorCantConnectTo.replace('{{msg}}', actualUrl)); - onError({ url: url, content: '' }); + µBlock.logger.writeOne({ + error: errorCantConnectTo.replace('{{msg}}', actualUrl) + }); + onError({ url, content: '' }); }; - var onTimeout = function() { + const onTimeout = function() { xhr.abort(); }; // https://github.com/gorhill/uBlock/issues/2526 // - Timeout only when there is no progress. - var onProgressEvent = function(ev) { + const onProgressEvent = function(ev) { if ( ev.loaded === contentLoaded ) { return; } contentLoaded = ev.loaded; if ( timeoutTimer !== undefined ) { diff --git a/src/js/cosmetic-filtering.js b/src/js/cosmetic-filtering.js index eb59ecea3..3b9d0f184 100644 --- a/src/js/cosmetic-filtering.js +++ b/src/js/cosmetic-filtering.js @@ -1,7 +1,7 @@ /******************************************************************************* uBlock Origin - a browser extension to block requests. - Copyright (C) 2014-2018 Raymond Hill + Copyright (C) 2014-present 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 @@ -579,8 +579,11 @@ FilterContainer.prototype.compileGenericSelector = function(parsed, writer) { /******************************************************************************/ -FilterContainer.prototype.compileGenericHideSelector = function(parsed, writer) { - let selector = parsed.suffix; +FilterContainer.prototype.compileGenericHideSelector = function( + parsed, + writer +) { + const selector = parsed.suffix; // For some selectors, it is mandatory to have a hostname or entity: // ##.foo:-abp-contains(...) @@ -596,18 +599,16 @@ FilterContainer.prototype.compileGenericHideSelector = function(parsed, writer) // ##:xpath(...) // ##.foo:style(...) if ( this.reNeedHostname.test(selector) ) { - µb.logger.writeOne( - '', - 'error', - 'Cosmetic filtering – invalid generic filter: ##' + selector - ); + µb.logger.writeOne({ + error: 'Cosmetic filtering – invalid generic filter: ##' + selector + }); return; } let type = selector.charCodeAt(0); if ( type === 0x23 /* '#' */ ) { - let key = this.keyFromSelector(selector); + const key = this.keyFromSelector(selector); if ( key === undefined ) { return; } // Simple selector-based CSS rule: no need to test for whether the // selector is valid, the regex took care of this. Most generic @@ -624,7 +625,7 @@ FilterContainer.prototype.compileGenericHideSelector = function(parsed, writer) } if ( type === 0x2E /* '.' */ ) { - let key = this.keyFromSelector(selector); + const key = this.keyFromSelector(selector); if ( key === undefined ) { return; } // Simple selector-based CSS rule: no need to test for whether the // selector is valid, the regex took care of this. Most generic @@ -640,16 +641,16 @@ FilterContainer.prototype.compileGenericHideSelector = function(parsed, writer) return; } - let compiled = µb.staticExtFilteringEngine.compileSelector(selector); + const compiled = µb.staticExtFilteringEngine.compileSelector(selector); if ( compiled === undefined ) { return; } // TODO: Detect and error on procedural cosmetic filters. // https://github.com/gorhill/uBlock/issues/909 // Anything which contains a plain id/class selector can be classified // as a low generic cosmetic filter. - let matches = this.rePlainSelectorEx.exec(selector); + const matches = this.rePlainSelectorEx.exec(selector); if ( matches !== null ) { - let key = matches[1] || matches[2]; + const key = matches[1] || matches[2]; type = key.charCodeAt(0); writer.push([ type === 0x23 ? 1 : 3 /* lg+ */, diff --git a/src/js/filtering-context.js b/src/js/filtering-context.js new file mode 100644 index 000000000..8c0855c82 --- /dev/null +++ b/src/js/filtering-context.js @@ -0,0 +1,282 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2018-present 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 +*/ + +'use strict'; + +/******************************************************************************/ + +µBlock.FilteringContext = function(other) { + if ( other instanceof µBlock.FilteringContext ) { + return this.fromFilteringContext(other); + } + this.tstamp = 0; + this.realm = ''; + this.type = undefined; + this.url = undefined; + this.hostname = undefined; + this.domain = undefined; + this.docId = undefined; + this.docOrigin = undefined; + this.docHostname = undefined; + this.docDomain = undefined; + this.tabId = undefined; + this.tabOrigin = undefined; + this.tabHostname = undefined; + this.tabDomain = undefined; + this.filter = undefined; +}; + +µBlock.FilteringContext.prototype = { + fromTabId: function(tabId) { + if ( tabId !== -1 || tabId !== this.tabId ) { + const tabContext = µBlock.tabContextManager.mustLookup(tabId); + this.tabOrigin = tabContext.origin; + this.tabHostname = tabContext.rootHostname; + this.tabDomain = tabContext.rootDomain; + this.tabId = tabId; + } + return this; + }, + fromWebrequestDetails: function(details) { + const tabId = details.tabId; + if ( tabId > 0 && details.type === 'main_frame' ) { + µBlock.tabContextManager.push(tabId, details.url); + } + this.fromTabId(tabId); + this.realm = ''; + this.type = details.type; + this.setURL(details.url); + this.docId = details.type !== 'sub_frame' + ? details.frameId + : details.parentFrameId; + if ( this.tabId > 0 ) { + if ( this.docId === 0 ) { + this.docOrigin = this.tabOrigin; + this.docHostname = this.tabHostname; + this.docDomain = this.tabDomain; + } else if ( details.documentUrl !== undefined ) { + this.setDocOriginFromURL(details.documentUrl); + } else { + const pageStore = µBlock.pageStoreFromTabId(this.docId); + const docStore = pageStore && pageStore.frames.get(this.docId); + if ( docStore ) { + this.docOrigin = undefined; + this.docHostname = docStore.pageHostname; + this.docDomain = docStore.pageDomain; + } + } + } else if ( details.documentUrl !== undefined ) { + this.setTabOriginFromURL(details.documentUrl); + this.setDocOriginFromURL(details.documentUrl); + } else { + this.setDocOrigin(this.tabOrigin); + } + this.filter = undefined; + return this; + }, + fromFilteringContext: function(other) { + this.realm = other.realm; + this.type = other.type; + this.url = other.url; + this.hostname = other.hostname; + this.domain = other.domain; + this.docId = other.docId; + this.docOrigin = other.docOrigin; + this.docHostname = other.docHostname; + this.docDomain = other.docDomain; + this.tabId = other.tabId; + this.tabOrigin = other.tabOrigin; + this.tabHostname = other.tabHostname; + this.tabDomain = other.tabDomain; + this.filter = undefined; + return this; + }, + duplicate: function() { + return (new µBlock.FilteringContext(this)); + }, + setRealm: function(a) { + this.realm = a; + return this; + }, + setType: function(a) { + this.type = a; + return this; + }, + setURL: function(a) { + if ( a !== this.url ) { + this.hostname = this.domain = undefined; + this.url = a; + } + return this; + }, + getHostname: function() { + if ( this.hostname === undefined ) { + this.hostname = this.hostnameFromURI(this.url); + } + return this.hostname; + }, + setHostname: function(a) { + if ( a !== this.hostname ) { + this.domain = undefined; + this.hostname = a; + } + return this; + }, + getDomain: function() { + if ( this.domain === undefined ) { + this.domain = this.domainFromHostname(this.getHostname()); + } + return this.domain; + }, + setDomain: function(a) { + this.domain = a; + return this; + }, + getDocOrigin: function() { + if ( this.docOrigin === undefined ) { + this.docOrigin = this.tabOrigin; + } + return this.docOrigin; + }, + setDocOrigin: function(a) { + if ( a !== this.docOrigin ) { + this.docHostname = this.docDomain = undefined; + this.docOrigin = a; + } + return this; + }, + setDocOriginFromURL: function(a) { + return this.setDocOrigin(this.originFromURI(a)); + }, + getDocHostname: function() { + if ( this.docHostname === undefined ) { + this.docHostname = this.hostnameFromURI(this.getDocOrigin()); + } + return this.docHostname; + }, + setDocHostname: function(a) { + if ( a !== this.docHostname ) { + this.docDomain = undefined; + this.docHostname = a; + } + return this; + }, + getDocDomain: function() { + if ( this.docDomain === undefined ) { + this.docDomain = this.domainFromHostname(this.getDocHostname()); + } + return this.docDomain; + }, + setDocDomain: function(a) { + this.docDomain = a; + return this; + }, + // The idea is to minimize the amout of work done to figure out whether + // the resource is 3rd-party to the document. + is3rdPartyToDoc: function() { + const docDomain = this.getDocDomain(); + if ( this.domain !== undefined ) { return this.domain !== docDomain; } + const hostname = this.getHostname(); + if ( hostname.endsWith(docDomain) === false ) { return true; } + const i = hostname.length - docDomain.length; + if ( i === 0 ) { return false; } + return hostname.charCodeAt(i - 1) !== 0x2E /* '.' */; + }, + setTabId: function(a) { + this.tabId = a; + return this; + }, + getTabOrigin: function() { + if ( this.tabOrigin === undefined ) { + const tabContext = µBlock.tabContextManager.mustLookup(this.tabId); + this.tabOrigin = tabContext.origin; + this.tabHostname = tabContext.rootHostname; + this.tabDomain = tabContext.rootDomain; + } + return this.tabOrigin; + }, + setTabOrigin: function(a) { + if ( a !== this.tabOrigin ) { + this.tabHostname = this.tabDomain = undefined; + this.tabOrigin = a; + } + return this; + }, + setTabOriginFromURL: function(a) { + return this.setTabOrigin(this.originFromURI(a)); + }, + getTabHostname: function() { + if ( this.tabHostname === undefined ) { + this.tabHostname = this.hostnameFromURI(this.getTabOrigin()); + } + return this.tabHostname; + }, + setTabHostname: function(a) { + if ( a !== this.tabHostname ) { + this.tabDomain = undefined; + this.tabHostname = a; + } + return this; + }, + getTabDomain: function() { + if ( this.tabDomain === undefined ) { + this.tabDomain = this.domainFromHostname(this.getTabHostname()); + } + return this.tabDomain; + }, + setTabDomain: function(a) { + this.docDomain = a; + return this; + }, + // The idea is to minimize the amout of work done to figure out whether + // the resource is 3rd-party to the top document. + is3rdPartyToTab: function() { + const tabDomain = this.getTabDomain(); + if ( this.domain !== undefined ) { return this.domain !== tabDomain; } + const hostname = this.getHostname(); + if ( hostname.endsWith(tabDomain) === false ) { return true; } + const i = hostname.length - tabDomain.length; + if ( i === 0 ) { return false; } + return hostname.charCodeAt(i - 1) !== 0x2E /* '.' */; + }, + setFilter: function(a) { + this.filter = a; + return this; + }, + toLogger: function() { + this.tstamp = Date.now(); + if ( this.domain === undefined ) { + void this.getDomain(); + } + if ( this.docDomain === undefined ) { + void this.getDocDomain(); + } + if ( this.tabDomain === undefined ) { + void this.getTabDomain(); + } + µBlock.logger.writeOne(this); + }, + originFromURI: µBlock.URI.originFromURI, + hostnameFromURI: µBlock.URI.hostnameFromURI, + domainFromHostname: µBlock.URI.domainFromHostname, +}; + +µBlock.filteringContext = new µBlock.FilteringContext(); diff --git a/src/js/html-filtering.js b/src/js/html-filtering.js index cac9e0752..b27e09cd2 100644 --- a/src/js/html-filtering.js +++ b/src/js/html-filtering.js @@ -162,18 +162,18 @@ }; const logOne = function(details, exception, selector) { - µb.logger.writeOne( - details.tabId, - 'cosmetic', - { + µBlock.filteringContext + .duplicate() + .fromTabId(details.tabId) + .setRealm('cosmetic') + .setType('dom') + .setURL(details.url) + .setDocOriginFromURL(details.url) + .setFilter({ source: 'cosmetic', raw: (exception === 0 ? '##' : '#@#') + '^' + selector - }, - 'dom', - details.url, - null, - details.hostname - ); + }) + .toLogger(); }; const applyProceduralSelector = function(details, selector) { @@ -192,7 +192,7 @@ modified = true; } } - if ( modified && µb.logger.isEnabled() ) { + if ( modified && µb.logger.enabled ) { logOne(details, 0, pselector.raw); } return modified; @@ -209,7 +209,7 @@ modified = true; } } - if ( modified && µb.logger.isEnabled() ) { + if ( modified && µb.logger.enabled ) { logOne(details, 0, selector); } return modified; @@ -277,7 +277,7 @@ }; api.retrieve = function(details) { - let hostname = details.hostname; + const hostname = details.hostname; // https://github.com/gorhill/uBlock/issues/2835 // Do not filter if the site is under an `allow` rule. @@ -288,13 +288,13 @@ return; } - let toRemoveArray = []; - let domainHash = µb.staticExtFilteringEngine.makeHash(details.domain); + const toRemoveArray = []; + const domainHash = µb.staticExtFilteringEngine.makeHash(details.domain); if ( domainHash !== 0 ) { filterDB.retrieve(domainHash, hostname, toRemoveArray); } - let entity = details.entity; - let entityHash = µb.staticExtFilteringEngine.makeHash(entity); + const entity = details.entity; + const entityHash = µb.staticExtFilteringEngine.makeHash(entity); if ( entityHash !== 0 ) { filterDB.retrieve(entityHash, entity, toRemoveArray); } @@ -313,14 +313,14 @@ return toRemoveArray; } - let toRemoveMap = new Map(); - for ( let entry of toRemoveArray ) { + const toRemoveMap = new Map(); + for ( const entry of toRemoveArray ) { toRemoveMap.set(entry.selector, entry); } - for ( let entry of notToRemoveArray ) { + for ( const entry of notToRemoveArray ) { if ( toRemoveMap.has(entry.selector) === false ) { continue; } toRemoveMap.delete(entry.selector); - if ( µb.logger.isEnabled() === false ) { continue; } + if ( µb.logger.enabled === false ) { continue; } let selector = entry.selector; if ( entry.type === 65 ) { selector = JSON.parse(selector).raw; diff --git a/src/js/logger-ui-inspector.js b/src/js/logger-ui-inspector.js index c911a748e..4103ba2cb 100644 --- a/src/js/logger-ui-inspector.js +++ b/src/js/logger-ui-inspector.js @@ -559,16 +559,16 @@ var onMouseOver = (function() { /******************************************************************************/ -var currentTabId = function() { +const currentTabId = function() { if ( showdomButton.classList.contains('active') === false ) { return 0; } return logger.tabIdFromPageSelector(); }; /******************************************************************************/ -var injectInspector = function() { - var tabId = currentTabId(); - if ( tabId === 0 ) { return; } +const injectInspector = function() { + const tabId = currentTabId(); + if ( tabId <= 0 ) { return; } inspectedTabId = tabId; messaging.send('loggerUI', { what: 'scriptlet', @@ -592,7 +592,11 @@ var shutdownInspector = function() { /******************************************************************************/ var onTabIdChanged = function() { - if ( inspectedTabId !== currentTabId() ) { + const tabId = currentTabId(); + if ( tabId <= 0 ) { + return toggleOff(); + } + if ( inspectedTabId !== tabId ) { shutdownInspector(); injectInspector(); } @@ -632,7 +636,8 @@ var revert = function() { /******************************************************************************/ -var toggleOn = function() { +const toggleOn = function() { + uDom.nodeFromId('inspectors').classList.add('dom'); window.addEventListener('beforeunload', toggleOff); document.addEventListener('tabIdChanged', onTabIdChanged); domTree.addEventListener('click', onClicked, true); @@ -647,7 +652,9 @@ var toggleOn = function() { /******************************************************************************/ -var toggleOff = function() { +const toggleOff = function() { + showdomButton.classList.remove('active'); + uDom.nodeFromId('inspectors').classList.remove('dom'); shutdownInspector(); window.removeEventListener('beforeunload', toggleOff); document.removeEventListener('tabIdChanged', onTabIdChanged); @@ -663,7 +670,7 @@ var toggleOff = function() { /******************************************************************************/ -var toggle = function() { +const toggle = function() { if ( showdomButton.classList.toggle('active') ) { toggleOn(); } else { diff --git a/src/js/logger-ui.js b/src/js/logger-ui.js index 3dbb17abc..0c1e2bb07 100644 --- a/src/js/logger-ui.js +++ b/src/js/logger-ui.js @@ -29,16 +29,17 @@ /******************************************************************************/ -var logger = self.logger = { +const messaging = vAPI.messaging; + +const logger = self.logger = { ownerId: Date.now() }; -var messaging = vAPI.messaging; -var activeTabId; +let activeTabId; /******************************************************************************/ -var removeAllChildren = logger.removeAllChildren = function(node) { +const removeAllChildren = logger.removeAllChildren = function(node) { while ( node.firstChild ) { node.removeChild(node.firstChild); } @@ -46,18 +47,19 @@ var removeAllChildren = logger.removeAllChildren = function(node) { /******************************************************************************/ -var tabIdFromClassName = function(className) { - var matches = className.match(/\btab_([^ ]+)\b/); +const tabIdFromClassName = function(className) { + const matches = className.match(/\btab_([^ ]+)\b/); if ( matches === null ) { return 0; } if ( matches[1] === 'bts' ) { return -1; } return parseInt(matches[1], 10); }; -var tabIdFromPageSelector = logger.tabIdFromPageSelector = function() { - var tabClass = uDom.nodeFromId('pageSelector').value; +const tabIdFromPageSelector = logger.tabIdFromPageSelector = function() { + const tabClass = uDom.nodeFromId('pageSelector').value; if ( tabClass === 'tab_active' && activeTabId !== undefined ) { return activeTabId; } + if ( tabClass === 'tab_bts' ) { return -1; } return /^tab_\d+$/.test(tabClass) ? parseInt(tabClass.slice(4), 10) : 0; }; @@ -78,12 +80,11 @@ var tabIdFromPageSelector = logger.tabIdFromPageSelector = function() { var tbody = document.querySelector('#netInspector tbody'); var trJunkyard = []; var tdJunkyard = []; -var firstVarDataCol = 2; // currently, column 2 (0-based index) -var lastVarDataIndex = 4; // currently, d0-d3 +var firstVarDataCol = 1; +var lastVarDataIndex = 6; var maxEntries = 5000; var allTabIds = new Map(); var allTabIdsToken; -var hiddenTemplate = document.querySelector('#hiddenTemplate > span'); var reRFC3986 = /^([^:\/?#]+:)?(\/\/[^\/?#]*)?([^?#]*)(\?[^#]*)?(#.*)?/; var netFilteringDialog = uDom.nodeFromId('netFilteringDialog'); @@ -159,14 +160,15 @@ var renderedURLTemplate = document.querySelector('#renderedURLTemplate > span'); /******************************************************************************/ -var createCellAt = function(tr, index) { - var td = tr.cells[index]; - var mustAppend = !td; +const createCellAt = function(tr, index) { + let td = tr.cells[index]; + const mustAppend = !td; if ( mustAppend ) { td = tdJunkyard.pop(); } if ( td ) { td.removeAttribute('colspan'); + td.removeAttribute('title'); td.textContent = ''; } else { td = document.createElement('td'); @@ -180,24 +182,25 @@ var createCellAt = function(tr, index) { /******************************************************************************/ var createRow = function(layout) { - var tr = trJunkyard.pop(); + let tr = trJunkyard.pop(); if ( tr ) { tr.className = ''; - tr.removeAttribute('data-hn-page'); - tr.removeAttribute('data-hn-frame'); + tr.removeAttribute('data-tabhn'); + tr.removeAttribute('data-dochn'); tr.removeAttribute('data-filter'); + tr.removeAttribute('data-tabid'); + tr.removeAttribute('title'); } else { tr = document.createElement('tr'); } - for ( var index = 0; index < firstVarDataCol; index++ ) { + let index = 0; + for ( ; index < firstVarDataCol; index++ ) { createCellAt(tr, index); } - var i = 1, span = 1, td; + let i = 1, span = 1, td; for (;;) { td = createCellAt(tr, index); - if ( i === lastVarDataIndex ) { - break; - } + if ( i === lastVarDataIndex ) { break; } if ( layout.charAt(i) !== '1' ) { span += 1; } else { @@ -221,24 +224,15 @@ var createRow = function(layout) { /******************************************************************************/ -var createHiddenTextNode = function(text) { - var node = hiddenTemplate.cloneNode(true); - node.textContent = text; - return node; -}; - -/******************************************************************************/ - var padTo2 = function(v) { return v < 10 ? '0' + v : v; }; /******************************************************************************/ -var createGap = function(tabId, url) { - var tr = createRow('1'); - tr.classList.add('tab'); - tr.classList.add('canMtx'); +const createGap = function(tabId, url) { + const tr = createRow('1'); + tr.setAttribute('data-tabid', tabId); tr.classList.add('tab_' + tabId); tr.classList.add('maindoc'); tr.cells[firstVarDataCol].textContent = url; @@ -247,36 +241,30 @@ var createGap = function(tabId, url) { /******************************************************************************/ -var renderNetLogEntry = function(tr, entry) { - var trcl = tr.classList; - var filter = entry.d0 || undefined; - var type = entry.d1; - var url = entry.d2; - var td; - - trcl.add('canMtx'); +var renderNetLogEntry = function(tr, details) { + const trcl = tr.classList; + const type = details.type; + const url = details.url; + let td; // If the request is that of a root frame, insert a gap in the table // in order to visually separate entries for different documents. if ( type === 'main_frame' ) { - createGap(entry.tab, url); + createGap(details.tabId, url); } - // Contexts - if ( entry.d3 ) { - tr.setAttribute('data-hn-page', entry.d3); - } - if ( entry.d4 ) { - tr.setAttribute('data-hn-frame', entry.d4); + tr.classList.add('cat_' + details.realm); + + let filter = details.filter || undefined; + let filteringType; + if ( filter !== undefined ) { + if ( typeof filter.source === 'string' ) { + filteringType = filter.source; + trcl.add(filteringType); + } } - var filteringType; - if ( filter !== undefined && typeof filter.source === 'string' ) { - filteringType = filter.source; - trcl.add(filteringType); - } - - td = tr.cells[2]; + td = tr.cells[1]; if ( filter !== undefined ) { if ( filteringType === 'static' ) { td.textContent = filter.raw; @@ -290,7 +278,7 @@ var renderNetLogEntry = function(tr, entry) { } } - td = tr.cells[3]; + td = tr.cells[2]; if ( filter !== undefined ) { if ( filter.result === 1 ) { trcl.add('blocked'); @@ -301,65 +289,86 @@ var renderNetLogEntry = function(tr, entry) { } else if ( filter.result === 3 ) { trcl.add('nooped'); td.textContent = '**'; - } else if ( filter.source === 'redirect' ) { + } else if ( filteringType === 'redirect' ) { trcl.add('redirect'); td.textContent = '<<'; } } - tr.cells[4].textContent = (prettyRequestTypes[type] || type); + if ( details.tabHostname ) { + tr.setAttribute('data-tabhn', details.tabHostname); + } + if ( details.docHostname ) { + tr.setAttribute('data-dochn', details.docHostname); + tr.cells[3].textContent = details.docHostname; + } - var re = null; + // Partyness + if ( details.realm === 'net' && details.domain !== undefined ) { + td = tr.cells[4]; + let text = ''; + if ( details.tabDomain !== undefined ) { + text += details.domain === details.tabDomain ? '1' : '3'; + } else { + text += '?'; + } + if ( details.docDomain !== details.tabDomain ) { + text += ','; + if ( details.docDomain !== undefined ) { + text += details.domain === details.docDomain ? '1' : '3'; + } else { + text += '?'; + } + } + td.textContent = text; + text = details.domain; + if ( details.docDomain ) { + text = details.docDomain + ' \u21d2 ' + text; + } + if ( details.tabDomain && details.tabDomain !== details.docDomain ) { + text = details.tabDomain + ' \u21d2 ' + text; + } + td.setAttribute('title', text); + } + + tr.cells[5].textContent = (prettyRequestTypes[type] || type); + + let re = null; if ( filteringType === 'static' ) { re = new RegExp(filter.regex, 'gi'); } else if ( filteringType === 'dynamicUrl' ) { re = regexFromURLFilteringResult(filter.rule.join(' ')); } - tr.cells[5].appendChild(nodeFromURL(url, re)); + tr.cells[6].appendChild(nodeFromURL(url, re)); }; /******************************************************************************/ -var renderLogEntry = function(entry) { - var tr; - var fvdc = firstVarDataCol; +var renderLogEntry = function(details) { + const fvdc = firstVarDataCol; + let tr; - switch ( entry.cat ) { - case 'error': - case 'info': + if ( details.error !== undefined ) { tr = createRow('1'); - tr.cells[fvdc].textContent = entry.d0; - break; - - case 'cosmetic': - case 'net': - case 'redirect': - tr = createRow('1111'); - renderNetLogEntry(tr, entry); - break; - - default: + tr.cells[fvdc].textContent = details.error; + } else if ( details.url !== undefined ) { + tr = createRow('111111'); + renderNetLogEntry(tr, details); + } else { tr = createRow('1'); - tr.cells[fvdc].textContent = entry.d0; - break; + tr.cells[fvdc].textContent = '???'; } // Fields common to all rows. - var time = logDate; - time.setTime(entry.tstamp - logDateTimezoneOffset); + const time = logDate; + time.setTime(details.tstamp - logDateTimezoneOffset); tr.cells[0].textContent = padTo2(time.getUTCHours()) + ':' + padTo2(time.getUTCMinutes()) + ':' + padTo2(time.getSeconds()); - if ( entry.tab ) { - tr.classList.add('tab'); - tr.classList.add(classNameFromTabId(entry.tab)); - if ( entry.tab < 0 ) { - tr.cells[1].appendChild(createHiddenTextNode('bts')); - } - } - if ( entry.cat !== '' ) { - tr.classList.add('cat_' + entry.cat); + if ( details.tabId ) { + tr.setAttribute('data-tabid', details.tabId); + tr.classList.add(classNameFromTabId(details.tabId)); } rowFilterer.filterOne(tr, true); @@ -368,29 +377,29 @@ var renderLogEntry = function(entry) { }; // Reuse date objects. -var logDate = new Date(), - logDateTimezoneOffset = logDate.getTimezoneOffset() * 60000; +const logDate = new Date(); +const logDateTimezoneOffset = logDate.getTimezoneOffset() * 60000; /******************************************************************************/ -var renderLogEntries = function(response) { +const renderLogEntries = function(response) { document.body.classList.toggle('colorBlind', response.colorBlind); - let entries = response.entries; + const entries = response.entries; if ( entries.length === 0 ) { return; } // Preserve scroll position - let height = tbody.offsetHeight; + const height = tbody.offsetHeight; - let tabIds = allTabIds; - for ( let i = 0, n = entries.length; i < n; i++ ) { - let entry = entries[i]; - let tr = renderLogEntry(entries[i]); + const tabIds = allTabIds; + for ( const entry of entries ) { + const details = JSON.parse(entry.details); + const tr = renderLogEntry(details); // https://github.com/gorhill/uBlock/issues/1613#issuecomment-217637122 // Unlikely, but it may happen: mark as void if associated tab no // longer exist. - if ( entry.tab && tabIds.has(entry.tab) === false ) { - tr.classList.remove('canMtx'); + if ( details.tabId && tabIds.has(details.tabId) === false ) { + tr.classList.add('void'); } } @@ -400,9 +409,9 @@ var renderLogEntries = function(response) { truncateLog(maxEntries); // Follow waterfall if not observing top of waterfall. - let yDelta = tbody.offsetHeight - height; + const yDelta = tbody.offsetHeight - height; if ( yDelta === 0 ) { return; } - let container = uDom.nodeFromSelector('#netInspector .vscrollable'); + const container = uDom.nodeFromSelector('#netInspector .vscrollable'); if ( container.scrollTop !== 0 ) { container.scrollTop += yDelta; } @@ -428,21 +437,20 @@ let updateCurrentTabTitle = (function() { /******************************************************************************/ -var synchronizeTabIds = function(newTabIds) { - var select = uDom.nodeFromId('pageSelector'); - var selectValue = select.value; - - var oldTabIds = allTabIds; - var autoDeleteVoidRows = selectValue === 'tab_active'; - var rowVoided = false; - for ( var tabId of oldTabIds.keys() ) { +const synchronizeTabIds = function(newTabIds) { + const select = uDom.nodeFromId('pageSelector'); + const selectValue = select.value; + const oldTabIds = allTabIds; + const autoDeleteVoidRows = selectValue === 'tab_active'; + let rowVoided = false; + for ( const tabId of oldTabIds.keys() ) { if ( newTabIds.has(tabId) ) { continue; } // Mark or remove voided rows - var trs = uDom('.tab_' + tabId); + const trs = uDom('.tab_' + tabId); if ( autoDeleteVoidRows ) { toJunkyard(trs); } else { - trs.removeClass('canMtx'); + trs.addClass('void'); rowVoided = true; } // Remove popup if it is currently bound to a removed tab. @@ -451,14 +459,14 @@ var synchronizeTabIds = function(newTabIds) { } } - var tabIds = Array.from(newTabIds.keys()).sort(function(a, b) { + const tabIds = Array.from(newTabIds.keys()).sort(function(a, b) { return newTabIds.get(a).localeCompare(newTabIds.get(b)); }); - var option; - for ( var i = 0, j = 3; i < tabIds.length; i++ ) { - tabId = tabIds[i]; + let j = 3; + for ( let i = 0; i < tabIds.length; i++ ) { + const tabId = tabIds[i]; if ( tabId < 0 ) { continue; } - option = select.options[j]; + let option = select.options[j]; if ( !option ) { option = document.createElement('option'); select.appendChild(option); @@ -508,7 +516,7 @@ var truncateLog = function(size) { /******************************************************************************/ -var onLogBufferRead = function(response) { +const onLogBufferRead = function(response) { if ( !response || response.unavailable ) { readLogBufferAsync(); return; @@ -547,7 +555,7 @@ var onLogBufferRead = function(response) { if ( rowVoided ) { uDom('#clean').toggleClass( 'disabled', - tbody.querySelector('#netInspector tr.tab:not(.canMtx)') === null + tbody.querySelector('#netInspector tr[data-tabid].void') === null ); } @@ -566,20 +574,20 @@ var onLogBufferRead = function(response) { // automatically. If called after init time, this will be messy, and this would // require a bit more code to ensure no multi time out events. -var readLogBuffer = function() { +const readLogBuffer = function() { if ( logger.ownerId === undefined ) { return; } vAPI.messaging.send( 'loggerUI', { what: 'readAll', ownerId: logger.ownerId, - tabIdsToken: allTabIdsToken + tabIdsToken: allTabIdsToken, }, onLogBufferRead ); }; -var readLogBufferAsync = function() { +const readLogBufferAsync = function() { if ( logger.ownerId === undefined ) { return; } vAPI.setTimeout(readLogBuffer, 1200); }; @@ -646,10 +654,14 @@ let pageSelectorFromURLHash = (function() { select.selectedIndex = option.index; select.value = option.value; - uDom('.needtab').toggleClass( + uDom('.needdom').toggleClass( 'disabled', tabClass === '' || tabClass === 'tab_bts' ); + uDom('.needscope').toggleClass( + 'disabled', + tabClass === '' + ); }; })(); @@ -657,7 +669,7 @@ let pageSelectorFromURLHash = (function() { var reloadTab = function(ev) { var tabId = tabIdFromPageSelector(); - if ( tabId === 0 ) { return; } + if ( tabId <= 0 ) { return; } messaging.send('loggerUI', { what: 'reloadTab', tabId: tabId, @@ -1227,10 +1239,10 @@ var netFilteringManager = (function() { dialog = netFilteringDialog.querySelector('.dialog'); targetRow = ev.target.parentElement; targetTabId = tabIdFromClassName(targetRow.className); - targetType = targetRow.cells[4].textContent.trim() || ''; - targetURLs = createTargetURLs(targetRow.cells[5].textContent); - targetPageHostname = targetRow.getAttribute('data-hn-page') || ''; - targetFrameHostname = targetRow.getAttribute('data-hn-frame') || ''; + targetType = targetRow.cells[5].textContent.trim() || ''; + targetURLs = createTargetURLs(targetRow.cells[6].textContent); + targetPageHostname = targetRow.getAttribute('data-tabhn') || ''; + targetFrameHostname = targetRow.getAttribute('data-dochn') || ''; // We need the root domain names for best user experience. messaging.send( @@ -1345,7 +1357,7 @@ var reverseLookupManager = (function() { let toggleOn = function(ev) { let row = ev.target.parentElement; - rawFilter = row.cells[2].textContent; + rawFilter = row.cells[1].textContent; if ( rawFilter === '' ) { return; } if ( row.classList.contains('cat_net') ) { @@ -1363,7 +1375,7 @@ var reverseLookupManager = (function() { 'loggerUI', { what: 'listsFromCosmeticFilter', - url: row.cells[5].textContent, + url: row.cells[6].textContent, rawFilter: rawFilter, }, reverseLookupDone @@ -1385,10 +1397,10 @@ var reverseLookupManager = (function() { /******************************************************************************/ /******************************************************************************/ -var rowFilterer = (function() { - var filters = []; +const rowFilterer = (function() { + let filters = []; - var parseInput = function() { + const parseInput = function() { filters = []; var rawPart, hardBeg, hardEnd; @@ -1438,33 +1450,30 @@ var rowFilterer = (function() { } }; - var filterOne = function(tr, clean) { - var ff = filters; - var fcount = ff.length; + const filterOne = function(tr, clean) { + const ff = filters; + const fcount = ff.length; if ( fcount === 0 && clean === true ) { return; } // do not filter out doc boundaries, they help separate important // section of log. - var cl = tr.classList; - if ( cl.contains('maindoc') ) { - return; - } + const cl = tr.classList; + if ( cl.contains('maindoc') ) { return; } if ( fcount === 0 ) { cl.remove('f'); return; } - var cc = tr.cells; - var ccount = cc.length; - var hit, j, f; + const cc = tr.cells; + const ccount = cc.length; // each filter expression must hit (implicit and-op) // if... // positive filter expression = there must one hit on any field // negative filter expression = there must be no hit on all fields - for ( var i = 0; i < fcount; i++ ) { - f = ff[i]; - hit = !f.r; - for ( j = 0; j < ccount; j++ ) { + for ( let i = 0; i < fcount; i++ ) { + let f = ff[i]; + let hit = !f.r; + for ( let j = 1; j < ccount; j++ ) { if ( f.re.test(cc[j].textContent) ) { hit = f.r; break; @@ -1478,7 +1487,7 @@ var rowFilterer = (function() { cl.remove('f'); }; - var filterAll = function() { + const filterAll = function() { // Special case: no filter if ( filters.length === 0 ) { uDom('#netInspector tr').removeClass('f'); @@ -1492,7 +1501,7 @@ var rowFilterer = (function() { } }; - var onFilterChangedAsync = (function() { + const onFilterChangedAsync = (function() { var timer = null; var commit = function() { timer = null; @@ -1507,7 +1516,7 @@ var rowFilterer = (function() { }; })(); - var onFilterButton = function() { + const onFilterButton = function() { uDom.nodeFromId('netInspector').classList.toggle('f'); }; @@ -1559,14 +1568,14 @@ var clearBuffer = function() { ); uDom.nodeFromId('clean').classList.toggle( 'disabled', - tbody.querySelector('#netInspector tr.tab:not(.canMtx)') === null + tbody.querySelector('#netInspector tr[data-tabid].void') === null ); }; /******************************************************************************/ var cleanBuffer = function() { - var rows = uDom('#netInspector tr.tab:not(.canMtx)').remove(); + var rows = uDom('#netInspector tr[data-tabid].void').remove(); var i = rows.length; while ( i-- ) { trJunkyard.push(rows.nodeAt(i)); @@ -1587,44 +1596,23 @@ var toggleVCompactRow = function(ev) { /******************************************************************************/ -var toggleInspectors = function() { - uDom.nodeFromId('inspectors').classList.toggle('dom'); -}; - -/******************************************************************************/ - var popupManager = (function() { - var realTabId = null; - var localTabId = null; - var container = null; - var popup = null; - var popupObserver = null; - var style = null; - var styleTemplate = [ - '#netInspector tr:not(.tab_{{tabId}}) {', - 'cursor: not-allowed;', - 'opacity: 0.2;', - '}' - ].join('\n'); + let realTabId = 0; + let popup = null; + let popupObserver = null; - var resizePopup = function() { - if ( popup === null ) { - return; - } - var popupBody = popup.contentWindow.document.body; - if ( popupBody.clientWidth !== 0 && container.clientWidth !== popupBody.clientWidth ) { - container.style.setProperty('width', popupBody.clientWidth + 'px'); + const resizePopup = function() { + if ( popup === null ) { return; } + const popupBody = popup.contentWindow.document.body; + if ( popupBody.clientWidth !== 0 && popup.clientWidth !== popupBody.clientWidth ) { + popup.style.setProperty('width', popupBody.clientWidth + 'px'); } if ( popupBody.clientHeight !== 0 && popup.clientHeight !== popupBody.clientHeight ) { popup.style.setProperty('height', popupBody.clientHeight + 'px'); } }; - var toggleSize = function() { - container.classList.toggle('hide'); - }; - - var onLoad = function() { + const onLoad = function() { resizePopup(); popupObserver.observe(popup.contentDocument.body, { subtree: true, @@ -1632,61 +1620,55 @@ var popupManager = (function() { }); }; - var toggleOn = function(td) { - var tr = td.parentNode; - realTabId = localTabId = tabIdFromClassName(tr.className); - if ( realTabId === 0 ) { return; } - - container = uDom.nodeFromId('popupContainer'); - - container.querySelector('div > span:nth-of-type(1)').addEventListener('click', toggleSize); - container.querySelector('div > span:nth-of-type(2)').addEventListener('click', toggleOff); - - popup = document.createElement('iframe'); - popup.addEventListener('load', onLoad); - popup.setAttribute('src', 'popup.html?tabId=' + realTabId); - popupObserver = new MutationObserver(resizePopup); - container.appendChild(popup); - - style = uDom.nodeFromId('popupFilterer'); - style.textContent = styleTemplate.replace('{{tabId}}', localTabId); - - var parent = uDom.nodeFromId('netInspector'); - var rect = parent.getBoundingClientRect(); - container.style.setProperty('top', rect.top + 'px'); - container.style.setProperty('right', (rect.right - parent.clientWidth) + 'px'); - parent.classList.add('popupOn'); + const setTabId = function(tabId) { + if ( popup === null ) { return; } + popup.setAttribute('src', 'popup.html?tabId=' + tabId); }; - var toggleOff = function() { - uDom.nodeFromId('netInspector').classList.remove('popupOn'); + const onTabIdChanged = function() { + const tabId = tabIdFromPageSelector(); + if ( tabId === 0 ) { return toggleOff(); } + realTabId = tabId; + setTabId(realTabId); + }; - container.querySelector('div > span:nth-of-type(1)').removeEventListener('click', toggleSize); - container.querySelector('div > span:nth-of-type(2)').removeEventListener('click', toggleOff); - container.classList.remove('hide'); + const toggleOn = function() { + const tabId = tabIdFromPageSelector(); + if ( tabId === 0 ) { return; } + realTabId = tabId; + popup = uDom.nodeFromId('popupContainer'); + + popup.addEventListener('load', onLoad); + popupObserver = new MutationObserver(resizePopup); + + const parent = uDom.nodeFromId('inspectors'); + const rect = parent.getBoundingClientRect(); + popup.style.setProperty('right', (rect.right - parent.clientWidth) + 'px'); + parent.classList.add('popupOn'); + + document.addEventListener('tabIdChanged', onTabIdChanged); + + setTabId(realTabId); + }; + + const toggleOff = function() { + document.removeEventListener('tabIdChanged', onTabIdChanged); + uDom.nodeFromId('inspectors').classList.remove('popupOn'); popup.removeEventListener('load', onLoad); popupObserver.disconnect(); popupObserver = null; popup.setAttribute('src', ''); - container.removeChild(popup); - popup = null; - - style.textContent = ''; - style = null; - - container = null; - realTabId = null; + + realTabId = 0; }; - var exports = { - toggleOn: function(ev) { - if ( realTabId === null ) { - toggleOn(ev.target); - } + const exports = { + toggleOn: function() { + void (realTabId === 0 ? toggleOn() : toggleOff()); }, toggleOff: function() { - if ( realTabId !== null ) { + if ( realTabId !== 0 ) { toggleOff(); } } @@ -1701,14 +1683,14 @@ var popupManager = (function() { /******************************************************************************/ -var grabView = function() { +const grabView = function() { if ( logger.ownerId === undefined ) { logger.ownerId = Date.now(); } readLogBufferAsync(); }; -var releaseView = function() { +const releaseView = function() { if ( logger.ownerId === undefined ) { return; } vAPI.messaging.send( 'loggerUI', @@ -1728,16 +1710,15 @@ readLogBuffer(); uDom('#pageSelector').on('change', pageSelectorChanged); uDom('#refresh').on('click', reloadTab); -uDom('#showdom').on('click', toggleInspectors); +uDom('#showpopup').on('click', popupManager.toggleOn); uDom('#netInspector .vCompactToggler').on('click', toggleVCompactView); uDom('#clean').on('click', cleanBuffer); uDom('#clear').on('click', clearBuffer); uDom('#maxEntries').on('change', onMaxEntriesChanged); uDom('#netInspector table').on('click', 'tr > td:nth-of-type(1)', toggleVCompactRow); -uDom('#netInspector table').on('click', 'tr.canMtx > td:nth-of-type(2)', popupManager.toggleOn); -uDom('#netInspector').on('click', 'tr.canLookup > td:nth-of-type(3)', reverseLookupManager.toggleOn); -uDom('#netInspector').on('click', 'tr.cat_net > td:nth-of-type(4)', netFilteringManager.toggleOn); +uDom('#netInspector').on('click', 'tr.canLookup > td:nth-of-type(2)', reverseLookupManager.toggleOn); +uDom('#netInspector').on('click', 'tr.cat_net > td:nth-of-type(3)', netFilteringManager.toggleOn); // https://github.com/gorhill/uBlock/issues/507 // Ensure tab selector is in sync with URL hash diff --git a/src/js/logger.js b/src/js/logger.js index f4e33f5c8..96df0c844 100644 --- a/src/js/logger.js +++ b/src/js/logger.js @@ -1,7 +1,7 @@ /******************************************************************************* - uBlock - a browser extension to block requests. - Copyright (C) 2015-2017 Raymond Hill + uBlock Origin - a browser extension to block requests. + Copyright (C) 2015-present 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 @@ -26,34 +26,28 @@ µBlock.logger = (function() { - var LogEntry = function(args) { - this.init(args); + const LogEntry = function(details) { + this.init(details); }; - LogEntry.prototype.init = function(args) { - this.tstamp = Date.now(); - this.tab = args[0] || ''; - this.cat = args[1] || ''; - this.d0 = args[2]; - this.d1 = args[3]; - this.d2 = args[4]; - this.d3 = args[5]; - this.d4 = args[6]; + LogEntry.prototype.init = function(details) { + this.details = JSON.stringify(details); }; - var buffer = null; - var lastReadTime = 0; - var writePtr = 0; + let buffer = null; + let lastReadTime = 0; + let writePtr = 0; // After 60 seconds without being read, a buffer will be considered // unused, and thus removed from memory. - var logBufferObsoleteAfter = 30 * 1000; + const logBufferObsoleteAfter = 30 * 1000; - var janitor = function() { + const janitor = ( ) => { if ( buffer !== null && lastReadTime < (Date.now() - logBufferObsoleteAfter) ) { + api.enabled = false; buffer = null; writePtr = 0; api.ownerId = undefined; @@ -64,31 +58,30 @@ } }; - var api = { + const api = { + enabled: false, ownerId: undefined, - writeOne: function() { + writeOne: function(details) { if ( buffer === null ) { return; } if ( writePtr === buffer.length ) { - buffer.push(new LogEntry(arguments)); + buffer.push(new LogEntry(details)); } else { - buffer[writePtr].init(arguments); + buffer[writePtr].init(details); } writePtr += 1; }, readAll: function(ownerId) { this.ownerId = ownerId; if ( buffer === null ) { + this.enabled = true; buffer = []; vAPI.setTimeout(janitor, logBufferObsoleteAfter); } - var out = buffer.slice(0, writePtr); + const out = buffer.slice(0, writePtr); writePtr = 0; lastReadTime = Date.now(); return out; }, - isEnabled: function() { - return buffer !== null; - } }; return api; diff --git a/src/js/messaging.js b/src/js/messaging.js index 4d64f42f3..f242809e4 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -527,9 +527,8 @@ var onMessage = function(request, sender, callback) { case 'shouldRenderNoscriptTags': if ( pageStore === null ) { break; } - let tabContext = µb.tabContextManager.lookup(tabId); - if ( tabContext === null ) { break; } - if ( pageStore.filterScripting(tabContext.rootHostname, undefined) ) { + const fctxt = µBlock.filteringContext.fromTabId(tabId); + if ( pageStore.filterScripting(fctxt, undefined) ) { vAPI.tabs.injectScript( tabId, { @@ -561,7 +560,10 @@ var onMessage = function(request, sender, callback) { request.domain = µb.URI.domainFromHostname(request.hostname); request.entity = µb.URI.entityFromDomain(request.domain); response.specificCosmeticFilters = - µb.cosmeticFilteringEngine.retrieveSpecificSelectors(request, response); + µb.cosmeticFilteringEngine.retrieveSpecificSelectors( + request, + response + ); if ( µb.canInjectScriptletsNow === false ) { response.scriptlets = µb.scriptletFilteringEngine.retrieve(request); } @@ -1319,25 +1321,20 @@ var domSurveyFinalReport = function(tabId) { /******************************************************************************/ -var logCosmeticFilters = function(tabId, details) { - if ( µb.logger.isEnabled() === false ) { - return; - } +const logCosmeticFilters = function(tabId, details) { + if ( µb.logger.enabled === false ) { return; } - var selectors = details.matchedSelectors; - - selectors.sort(); - - for ( var i = 0; i < selectors.length; i++ ) { - µb.logger.writeOne( - tabId, - 'cosmetic', - { source: 'cosmetic', raw: '##' + selectors[i] }, - 'dom', - details.frameURL, - null, - details.frameHostname - ); + const filter = { source: 'cosmetic', raw: '' }; + const fctxt = µb.filteringContext.duplicate(); + fctxt.fromTabId(tabId) + .setRealm('cosmetic') + .setType('dom') + .setURL(details.frameURL) + .setDocOriginFromURL(details.frameURL) + .setFilter(filter); + for ( const selector of details.matchedSelectors.sort() ) { + filter.raw = '##' + selector; + fctxt.toLogger(); } }; diff --git a/src/js/pagestore.js b/src/js/pagestore.js index 67572ea55..a9dff032b 100644 --- a/src/js/pagestore.js +++ b/src/js/pagestore.js @@ -37,18 +37,18 @@ To create a log of net requests /******************************************************************************/ -var µb = µBlock; +const µb = µBlock; /******************************************************************************/ /******************************************************************************/ // To mitigate memory churning -var netFilteringCacheJunkyard = [], - netFilteringCacheJunkyardMax = 10; +const netFilteringCacheJunkyard = []; +const netFilteringCacheJunkyardMax = 10; /******************************************************************************/ -var NetFilteringResultCache = function() { +const NetFilteringResultCache = function() { this.boundPruneAsyncCallback = this.pruneAsyncCallback.bind(this); this.init(); }; @@ -60,7 +60,7 @@ NetFilteringResultCache.prototype.shelfLife = 15 * 1000; /******************************************************************************/ NetFilteringResultCache.factory = function() { - var entry = netFilteringCacheJunkyard.pop(); + let entry = netFilteringCacheJunkyard.pop(); if ( entry === undefined ) { entry = new NetFilteringResultCache(); } else { @@ -90,31 +90,35 @@ NetFilteringResultCache.prototype.dispose = function() { /******************************************************************************/ -NetFilteringResultCache.prototype.rememberResult = function(context, result, logData) { +NetFilteringResultCache.prototype.rememberResult = function( + fctxt, + result, + logData +) { if ( this.results.size === 0 ) { this.pruneAsync(); } - var key = context.pageHostname + ' ' + context.requestType + ' ' + context.requestURL; + const key = fctxt.getDocHostname() + ' ' + fctxt.type + ' ' + fctxt.url; this.results.set(key, { result: result, logData: logData, tstamp: Date.now() }); if ( result !== 1 ) { return; } - var now = Date.now(); + const now = Date.now(); this.blocked.set(key, now); this.hash = now; }; /******************************************************************************/ -NetFilteringResultCache.prototype.rememberBlock = function(details) { +NetFilteringResultCache.prototype.rememberBlock = function(fctxt) { if ( this.blocked.size === 0 ) { this.pruneAsync(); } - var now = Date.now(); + const now = Date.now(); this.blocked.set( - details.pageHostname + ' ' + details.requestType + ' ' + details.requestURL, + fctxt.getDocHostname() + ' ' + fctxt.type + ' ' + fctxt.url, now ); this.hash = now; @@ -162,21 +166,20 @@ NetFilteringResultCache.prototype.pruneAsyncCallback = function() { /******************************************************************************/ -NetFilteringResultCache.prototype.lookupResult = function(context) { +NetFilteringResultCache.prototype.lookupResult = function(fctxt) { return this.results.get( - context.pageHostname + ' ' + - context.requestType + ' ' + - context.requestURL + fctxt.getDocHostname() + ' ' + + fctxt.type + ' ' + + fctxt.url ); }; /******************************************************************************/ NetFilteringResultCache.prototype.lookupAllBlocked = function(hostname) { - var result = [], - pos; - for ( var entry of this.blocked ) { - pos = entry[0].indexOf(' '); + const result = []; + for ( const entry of this.blocked ) { + const pos = entry[0].indexOf(' '); if ( entry[0].slice(0, pos) === hostname ) { result[result.length] = entry[0].slice(pos + 1); } @@ -193,19 +196,19 @@ NetFilteringResultCache.prototype.lookupAllBlocked = function(hostname) { // refactoring. // To mitigate memory churning -var frameStoreJunkyard = []; -var frameStoreJunkyardMax = 50; +const frameStoreJunkyard = []; +const frameStoreJunkyardMax = 50; /******************************************************************************/ -var FrameStore = function(frameURL) { +const FrameStore = function(frameURL) { this.init(frameURL); }; /******************************************************************************/ FrameStore.factory = function(frameURL) { - var entry = frameStoreJunkyard.pop(); + const entry = frameStoreJunkyard.pop(); if ( entry === undefined ) { return new FrameStore(frameURL); } @@ -215,7 +218,7 @@ FrameStore.factory = function(frameURL) { /******************************************************************************/ FrameStore.prototype.init = function(frameURL) { - var µburi = µb.URI; + const µburi = µb.URI; this.pageHostname = µburi.hostnameFromURI(frameURL); this.pageDomain = µburi.domainFromHostname(this.pageHostname) || this.pageHostname; return this; @@ -235,12 +238,12 @@ FrameStore.prototype.dispose = function() { /******************************************************************************/ // To mitigate memory churning -var pageStoreJunkyard = []; -var pageStoreJunkyardMax = 10; +const pageStoreJunkyard = []; +const pageStoreJunkyardMax = 10; /******************************************************************************/ -var PageStore = function(tabId, context) { +const PageStore = function(tabId, context) { this.init(tabId, context); this.journal = []; this.journalTimer = null; @@ -251,7 +254,7 @@ var PageStore = function(tabId, context) { /******************************************************************************/ PageStore.factory = function(tabId, context) { - var entry = pageStoreJunkyard.pop(); + let entry = pageStoreJunkyard.pop(); if ( entry === undefined ) { entry = new PageStore(tabId, context); } else { @@ -297,6 +300,18 @@ PageStore.prototype.init = function(tabId, context) { this.netFilteringCache = NetFilteringResultCache.factory(); this.internalRedirectionCount = 0; + // The current filtering context is cloned because: + // - We may be called with or without the current context having been + // initialized. + // - If it has been initialized, we do not want to change the state + // of the current context. + const fctxt = µb.logger.enabled + ? µBlock.filteringContext + .duplicate() + .fromTabId(tabId) + .setURL(tabContext.rawURL) + : undefined; + // https://github.com/uBlockOrigin/uBlock-issues/issues/314 const masterSwitch = tabContext.getNetFilteringSwitch(); @@ -307,18 +322,13 @@ PageStore.prototype.init = function(tabId, context) { if ( masterSwitch && this.noCosmeticFiltering && - µb.logger.isEnabled() && + µb.logger.enabled && context === 'tabCommitted' ) { - µb.logger.writeOne( - tabId, - 'cosmetic', - µb.sessionSwitches.toLogData(), - 'dom', - tabContext.rawURL, - this.tabHostname, - this.tabHostname - ); + fctxt.setRealm('cosmetic') + .setType('dom') + .setFilter(µb.sessionSwitches.toLogData()) + .toLogger(); } // Support `generichide` filter option. @@ -332,18 +342,13 @@ PageStore.prototype.init = function(tabId, context) { this.noGenericCosmeticFiltering = result === 2; if ( result !== 0 && - µb.logger.isEnabled() && + µb.logger.enabled && context === 'tabCommitted' ) { - µb.logger.writeOne( - tabId, - 'net', - µb.staticNetFilteringEngine.toLogData(), - 'generichide', - tabContext.rawURL, - this.tabHostname, - this.tabHostname - ); + fctxt.setRealm('net') + .setType('generichide') + .setFilter(µb.staticNetFilteringEngine.toLogData()) + .toLogger(); } } } @@ -357,7 +362,7 @@ PageStore.prototype.reuse = function(context) { // When force refreshing a page, the page store data needs to be reset. // If the hostname changes, we can't merely just update the context. - var tabContext = µb.tabContextManager.mustLookup(this.tabId); + const tabContext = µb.tabContextManager.mustLookup(this.tabId); if ( tabContext.rootHostname !== this.tabHostname ) { context = ''; } @@ -416,22 +421,18 @@ PageStore.prototype.dispose = function() { /******************************************************************************/ PageStore.prototype.disposeFrameStores = function() { - for ( var frameStore of this.frames.values() ) { + for ( const frameStore of this.frames.values() ) { frameStore.dispose(); } this.frames.clear(); }; -/******************************************************************************/ - PageStore.prototype.getFrame = function(frameId) { return this.frames.get(frameId) || null; }; -/******************************************************************************/ - PageStore.prototype.setFrame = function(frameId, frameURL) { - var frameStore = this.frames.get(frameId); + const frameStore = this.frames.get(frameId); if ( frameStore !== undefined ) { frameStore.init(frameURL); } else { @@ -441,54 +442,19 @@ PageStore.prototype.setFrame = function(frameId, frameURL) { /******************************************************************************/ -PageStore.prototype.createContextFromPage = function() { - var context = µb.tabContextManager.createContext(this.tabId); - context.pageHostname = context.rootHostname; - context.pageDomain = context.rootDomain; - return context; -}; - -PageStore.prototype.createContextFromFrameId = function(frameId) { - var context = µb.tabContextManager.createContext(this.tabId); - var frameStore = this.frames.get(frameId); - if ( frameStore !== undefined ) { - context.pageHostname = frameStore.pageHostname; - context.pageDomain = frameStore.pageDomain; - } else { - context.pageHostname = context.rootHostname; - context.pageDomain = context.rootDomain; - } - return context; -}; - -PageStore.prototype.createContextFromFrameHostname = function(frameHostname) { - var context = µb.tabContextManager.createContext(this.tabId); - context.pageHostname = frameHostname; - context.pageDomain = µb.URI.domainFromHostname(frameHostname) || frameHostname; - return context; -}; - -/******************************************************************************/ - PageStore.prototype.getNetFilteringSwitch = function() { return µb.tabContextManager.mustLookup(this.tabId).getNetFilteringSwitch(); }; -/******************************************************************************/ - PageStore.prototype.getSpecificCosmeticFilteringSwitch = function() { return this.noCosmeticFiltering !== true; }; -/******************************************************************************/ - PageStore.prototype.getGenericCosmeticFilteringSwitch = function() { return this.noGenericCosmeticFiltering !== true && this.noCosmeticFiltering !== true; }; -/******************************************************************************/ - PageStore.prototype.toggleNetFilteringSwitch = function(url, scope, state) { µb.toggleNetFilteringSwitch(url, scope, state); this.netFilteringCache.empty(); @@ -562,15 +528,14 @@ PageStore.prototype.journalProcess = function(fromTimer) { } this.journalTimer = null; - var journal = this.journal, - n = journal.length, - aggregateCounts = 0, - now = Date.now(), - pivot = this.journalLastCommitted || 0; + const journal = this.journal; + const now = Date.now(); + let aggregateCounts = 0; + let pivot = this.journalLastCommitted || 0; // Everything after pivot originates from current page. - for ( let i = pivot; i < n; i += 2 ) { - let hostname = journal[i]; + for ( let i = pivot; i < journal.length; i += 2 ) { + const hostname = journal[i]; let hostnameCounts = this.hostnameToCountMap.get(hostname); if ( hostnameCounts === undefined ) { hostnameCounts = 0; @@ -605,88 +570,101 @@ PageStore.prototype.journalProcess = function(fromTimer) { /******************************************************************************/ -PageStore.prototype.filterRequest = function(context) { - this.logData = undefined; +PageStore.prototype.filterRequest = function(fctxt) { + fctxt.filter = undefined; if ( this.getNetFilteringSwitch() === false ) { return 0; } - var requestType = context.requestType; + const requestType = fctxt.type; - if ( requestType === 'csp_report' && this.filterCSPReport(context) === 1 ) { + if ( requestType === 'csp_report' && this.filterCSPReport(fctxt) === 1 ) { return 1; } - if ( requestType.endsWith('font') && this.filterFont(context) === 1 ) { + if ( requestType.endsWith('font') && this.filterFont(fctxt) === 1 ) { return 1; } if ( requestType === 'script' && - this.filterScripting(context.rootHostname, true) === 1 + this.filterScripting(fctxt, true) === 1 ) { return 1; } - var cacheableResult = this.cacheableResults[requestType] === true; + const cacheableResult = this.cacheableResults.has(requestType); if ( cacheableResult ) { - var entry = this.netFilteringCache.lookupResult(context); + const entry = this.netFilteringCache.lookupResult(fctxt); if ( entry !== undefined ) { - this.logData = entry.logData; + fctxt.filter = entry.logData; return entry.result; } } // Dynamic URL filtering. - var result = µb.sessionURLFiltering.evaluateZ(context.rootHostname, context.requestURL, requestType); - if ( result !== 0 && µb.logger.isEnabled() ) { - this.logData = µb.sessionURLFiltering.toLogData(); + let result = µb.sessionURLFiltering.evaluateZ( + fctxt.getTabHostname(), + fctxt.url, + requestType + ); + if ( result !== 0 && µb.logger.enabled ) { + fctxt.filter = µb.sessionURLFiltering.toLogData(); } // Dynamic hostname/type filtering. if ( result === 0 && µb.userSettings.advancedUserEnabled ) { - result = µb.sessionFirewall.evaluateCellZY(context.rootHostname, context.requestHostname, requestType); - if ( result !== 0 && result !== 3 && µb.logger.isEnabled() ) { - this.logData = µb.sessionFirewall.toLogData(); + result = µb.sessionFirewall.evaluateCellZY( + fctxt.getTabHostname(), + fctxt.getHostname(), + requestType + ); + if ( result !== 0 && result !== 3 && µb.logger.enabled ) { + fctxt.filter = µb.sessionFirewall.toLogData(); } } // Static filtering has lowest precedence. if ( result === 0 || result === 3 ) { - result = µb.staticNetFilteringEngine.matchString(context); - if ( result !== 0 && µb.logger.isEnabled() ) { - this.logData = µb.staticNetFilteringEngine.toLogData(); + result = µb.staticNetFilteringEngine.matchString(fctxt); + if ( result !== 0 && µb.logger.enabled ) { + fctxt.filter = µb.staticNetFilteringEngine.toLogData(); } } if ( cacheableResult ) { - this.netFilteringCache.rememberResult(context, result, this.logData); - } else if ( result === 1 && this.collapsibleResources[requestType] === true ) { - this.netFilteringCache.rememberBlock(context, true); + this.netFilteringCache.rememberResult(fctxt, result, this.logData); + } else if ( result === 1 && this.collapsibleResources.has(requestType) ) { + this.netFilteringCache.rememberBlock(fctxt, true); } return result; }; -PageStore.prototype.cacheableResults = { - sub_frame: true -}; +PageStore.prototype.cacheableResults = new Set([ + 'sub_frame', +]); -PageStore.prototype.collapsibleResources = { - image: true, - media: true, - object: true, - sub_frame: true -}; +PageStore.prototype.collapsibleResources = new Set([ + 'image', + 'media', + 'object', + 'sub_frame', +]); /******************************************************************************/ -PageStore.prototype.filterCSPReport = function(context) { - if ( µb.sessionSwitches.evaluateZ('no-csp-reports', context.requestHostname) ) { - if ( µb.logger.isEnabled() ) { - this.logData = µb.sessionSwitches.toLogData(); +PageStore.prototype.filterCSPReport = function(fctxt) { + if ( + µb.sessionSwitches.evaluateZ( + 'no-csp-reports', + fctxt.getHostname() + ) + ) { + if ( µb.logger.enabled ) { + fctxt.filter = µb.sessionSwitches.toLogData(); } return 1; } @@ -695,13 +673,18 @@ PageStore.prototype.filterCSPReport = function(context) { /******************************************************************************/ -PageStore.prototype.filterFont = function(context) { - if ( context.requestType === 'font' ) { +PageStore.prototype.filterFont = function(fctxt) { + if ( fctxt.type === 'font' ) { this.remoteFontCount += 1; } - if ( µb.sessionSwitches.evaluateZ('no-remote-fonts', context.rootHostname) !== false ) { - if ( µb.logger.isEnabled() ) { - this.logData = µb.sessionSwitches.toLogData(); + if ( + µb.sessionSwitches.evaluateZ( + 'no-remote-fonts', + fctxt.getTabHostname() + ) !== false + ) { + if ( µb.logger.enabled ) { + fctxt.filter = µb.sessionSwitches.toLogData(); } return 1; } @@ -710,18 +693,22 @@ PageStore.prototype.filterFont = function(context) { /******************************************************************************/ -PageStore.prototype.filterScripting = function(rootHostname, netFiltering) { +PageStore.prototype.filterScripting = function(fctxt, netFiltering) { + fctxt.filter = undefined; if ( netFiltering === undefined ) { netFiltering = this.getNetFilteringSwitch(); } if ( netFiltering === false || - µb.sessionSwitches.evaluateZ('no-scripting', rootHostname) === false + µb.sessionSwitches.evaluateZ( + 'no-scripting', + fctxt.getTabHostname() + ) === false ) { return 0; } - if ( µb.logger.isEnabled() ) { - this.logData = µb.sessionSwitches.toLogData(); + if ( µb.logger.enabled ) { + fctxt.filter = µb.sessionSwitches.toLogData(); } return 1; }; @@ -730,13 +717,18 @@ PageStore.prototype.filterScripting = function(rootHostname, netFiltering) { // The caller is responsible to check whether filtering is enabled or not. -PageStore.prototype.filterLargeMediaElement = function(size) { - this.logData = undefined; +PageStore.prototype.filterLargeMediaElement = function(fctxt, size) { + fctxt.filter = undefined; if ( Date.now() < this.allowLargeMediaElementsUntil ) { return 0; } - if ( µb.sessionSwitches.evaluateZ('no-large-media', this.tabHostname) !== true ) { + if ( + µb.sessionSwitches.evaluateZ( + 'no-large-media', + fctxt.getTabHostname() + ) !== true + ) { return 0; } if ( (size >>> 10) < µb.userSettings.largeMediaSize ) { @@ -751,8 +743,8 @@ PageStore.prototype.filterLargeMediaElement = function(size) { ); } - if ( µb.logger.isEnabled() ) { - this.logData = µb.sessionSwitches.toLogData(); + if ( µb.logger.enabled ) { + fctxt.filter = µb.sessionSwitches.toLogData(); } return 1; @@ -763,26 +755,27 @@ PageStore.prototype.filterLargeMediaElement = function(size) { /******************************************************************************/ PageStore.prototype.getBlockedResources = function(request, response) { - var µburi = µb.URI, - normalURL = µb.normalizePageURL(this.tabId, request.frameURL), - frameHostname = µburi.hostnameFromURI(normalURL), - resources = request.resources; + const normalURL = µb.normalizePageURL(this.tabId, request.frameURL); + const resources = request.resources; + const fctxt = µBlock.filteringContext; + fctxt.fromTabId(this.tabId) + .setDocOriginFromURL(normalURL); // Force some resources to go through the filtering engine in order to // populate the blocked-resources cache. This is required because for // some resources it's not possible to detect whether they were blocked // content script-side (i.e. `iframes` -- unlike `img`). if ( Array.isArray(resources) && resources.length !== 0 ) { - var context = this.createContextFromFrameHostname(frameHostname); - for ( var resource of resources ) { - context.requestType = resource.type; - context.requestHostname = µburi.hostnameFromURI(resource.url); - context.requestURL = resource.url; - this.filterRequest(context); + for ( const resource of resources ) { + this.filterRequest( + fctxt.setType(resource.type) + .setURL(resource.url) + ); } } if ( this.netFilteringCache.hash === response.hash ) { return; } response.hash = this.netFilteringCache.hash; - response.blockedResources = this.netFilteringCache.lookupAllBlocked(frameHostname); + response.blockedResources = + this.netFilteringCache.lookupAllBlocked(fctxt.getDocHostname()); }; /******************************************************************************/ diff --git a/src/js/redirect-engine.js b/src/js/redirect-engine.js index 8b106449f..bfcdfb5c0 100644 --- a/src/js/redirect-engine.js +++ b/src/js/redirect-engine.js @@ -95,14 +95,14 @@ const RedirectEntry = function() { // - https://stackoverflow.com/a/8056313 // - https://bugzilla.mozilla.org/show_bug.cgi?id=998076 -RedirectEntry.prototype.toURL = function(details) { +RedirectEntry.prototype.toURL = function(fctxt) { if ( this.warURL !== undefined && - details instanceof Object && - details.requestType !== 'xmlhttprequest' && + fctxt instanceof Object && + fctxt.type !== 'xmlhttprequest' && ( suffersSpuriousRedirectConflicts === false || - details.requestURL.startsWith('https:') + fctxt.url.startsWith('https:') ) ) { return this.warURL + '?secret=' + vAPI.warSecret; @@ -187,14 +187,14 @@ RedirectEngine.prototype.toBroaderHostname = function(hostname) { /******************************************************************************/ -RedirectEngine.prototype.lookup = function(context) { - var type = context.requestType; +RedirectEngine.prototype.lookup = function(fctxt) { + const type = fctxt.type; if ( this.ruleTypes.has(type) === false ) { return; } - var src = context.pageHostname, - des = context.requestHostname, - desAll = this._desAll, - reqURL = context.requestURL; - var n = 0; + const desAll = this._desAll; + const reqURL = fctxt.url; + let src = fctxt.getDocHostname(); + let des = fctxt.getHostname(); + let n = 0; for (;;) { if ( this.ruleDestinations.has(des) ) { desAll[n] = des; n += 1; @@ -203,11 +203,10 @@ RedirectEngine.prototype.lookup = function(context) { if ( des === '' ) { break; } } if ( n === 0 ) { return; } - var entries; for (;;) { if ( this.ruleSources.has(src) ) { - for ( var i = 0; i < n; i++ ) { - entries = this.rules.get(src + ' ' + desAll[i] + ' ' + type); + for ( let i = 0; i < n; i++ ) { + const entries = this.rules.get(src + ' ' + desAll[i] + ' ' + type); if ( entries && this.lookupToken(entries, reqURL) ) { return this.resourceNameRegister; } @@ -234,12 +233,12 @@ RedirectEngine.prototype.lookupToken = function(entries, reqURL) { /******************************************************************************/ -RedirectEngine.prototype.toURL = function(context) { - let token = this.lookup(context); +RedirectEngine.prototype.toURL = function(fctxt) { + let token = this.lookup(fctxt); if ( token === undefined ) { return; } let entry = this.resources.get(token); if ( entry !== undefined ) { - return entry.toURL(context); + return entry.toURL(fctxt); } }; diff --git a/src/js/scriptlet-filtering.js b/src/js/scriptlet-filtering.js index 258d580ee..57441bf8e 100644 --- a/src/js/scriptlet-filtering.js +++ b/src/js/scriptlet-filtering.js @@ -24,18 +24,26 @@ /******************************************************************************/ µBlock.scriptletFilteringEngine = (function() { - let api = {}; - - let µb = µBlock, - scriptletDB = new µb.staticExtFilteringEngine.HostnameBasedDB(), + const µb = µBlock, duplicates = new Set(), - acceptedCount = 0, - discardedCount = 0, scriptletCache = new µb.MRUCache(32), - exceptionsRegister = new Set(), scriptletsRegister = new Map(), + exceptionsRegister = new Set(), reEscapeScriptArg = /[\\'"]/g; + let acceptedCount = 0, + discardedCount = 0, + scriptletDB = new µb.staticExtFilteringEngine.HostnameBasedDB(); + + const api = { + get acceptedCount() { + return acceptedCount; + }, + get discardedCount() { + return discardedCount; + } + }; + // Purpose of `contentscriptCode` below is too programmatically inject // content script code which only purpose is to inject scriptlets. This // essentially does the same as what uBO's declarative content script does, @@ -58,8 +66,8 @@ // Consequently, the programmatic-injection code path is taken only with // Chromium-based browsers. - let contentscriptCode = (function() { - let parts = [ + const contentscriptCode = (function() { + const parts = [ '(', function(hostname, scriptlets) { if ( @@ -163,15 +171,15 @@ }; })(); - let lookupScriptlet = function(raw, reng, toInject) { + const lookupScriptlet = function(raw, reng, toInject) { if ( toInject.has(raw) ) { return; } if ( scriptletCache.resetTime < reng.modifyTime ) { scriptletCache.reset(); } - var content = scriptletCache.lookup(raw); + let content = scriptletCache.lookup(raw); if ( content === undefined ) { - var token, args, - pos = raw.indexOf(','); + const pos = raw.indexOf(','); + let token, args; if ( pos === -1 ) { token = raw; } else { @@ -196,13 +204,12 @@ // Fill template placeholders. Return falsy if: // - At least one argument contains anything else than /\w/ and `.` - let patchScriptlet = function(content, args) { - var i = 1, - pos, arg; + const patchScriptlet = function(content, args) { + let i = 1; while ( args !== '' ) { - pos = args.indexOf(','); + let pos = args.indexOf(','); if ( pos === -1 ) { pos = args.length; } - arg = args.slice(0, pos).trim().replace(reEscapeScriptArg, '\\$&'); + const arg = args.slice(0, pos).trim().replace(reEscapeScriptArg, '\\$&'); content = content.replace('{{' + i + '}}', arg); args = args.slice(pos + 1).trim(); i++; @@ -210,19 +217,19 @@ return content; }; - let logOne = function(isException, token, details) { - µb.logger.writeOne( - details.tabId, - 'cosmetic', - { + const logOne = function(isException, token, details) { + µBlock.filteringContext + .duplicate() + .fromTabId(details.tabId) + .setRealm('cosmetic') + .setType('dom') + .setURL(details.url) + .setDocOriginFromURL(details.url) + .setFilter({ source: 'cosmetic', raw: (isException ? '#@#' : '##') + '+js(' + token + ')' - }, - 'dom', - details.url, - null, - details.hostname - ); + }) + .toLogger(); }; api.reset = function() { @@ -254,7 +261,7 @@ // because there is no way to create an exception to an exception. for ( let hn of parsed.hostnames ) { - let negated = hn.charCodeAt(0) === 0x7E /* '~' */; + const negated = hn.charCodeAt(0) === 0x7E /* '~' */; if ( negated ) { hn = hn.slice(1); } @@ -280,13 +287,13 @@ while ( reader.next() ) { acceptedCount += 1; - let fingerprint = reader.fingerprint(); + const fingerprint = reader.fingerprint(); if ( duplicates.has(fingerprint) ) { discardedCount += 1; continue; } duplicates.add(fingerprint); - let args = reader.args(); + const args = reader.args(); if ( args.length < 4 ) { continue; } scriptletDB.add( args[1], @@ -299,10 +306,10 @@ if ( scriptletDB.size === 0 ) { return; } if ( µb.hiddenSettings.ignoreScriptInjectFilters ) { return; } - let reng = µb.redirectEngine; + const reng = µb.redirectEngine; if ( !reng ) { return; } - let hostname = request.hostname; + const hostname = request.hostname; // https://github.com/gorhill/uBlock/issues/2835 // Do not inject scriptlets if the site is under an `allow` rule. @@ -313,8 +320,8 @@ return; } - let domain = request.domain, - entity = request.entity; + const domain = request.domain; + const entity = request.entity; // https://github.com/gorhill/uBlock/issues/1954 // Implicit @@ -322,7 +329,7 @@ for (;;) { lookupScriptlet(hn + '.js', reng, scriptletsRegister); if ( hn === domain ) { break; } - var pos = hn.indexOf('.'); + const pos = hn.indexOf('.'); if ( pos === -1 ) { break; } hn = hn.slice(pos + 1); } @@ -332,16 +339,16 @@ // Explicit let entries = []; - let domainHash = µb.staticExtFilteringEngine.makeHash(domain); + const domainHash = µb.staticExtFilteringEngine.makeHash(domain); if ( domainHash !== 0 ) { scriptletDB.retrieve(domainHash, hostname, entries); } - let entityHash = µb.staticExtFilteringEngine.makeHash(entity); + const entityHash = µb.staticExtFilteringEngine.makeHash(entity); if ( entityHash !== 0 ) { scriptletDB.retrieve(entityHash, entity, entries); } scriptletDB.retrieve(0, hostname, entries); - for ( let entry of entries ) { + for ( const entry of entries ) { lookupScriptlet(entry.token, reng, scriptletsRegister); } @@ -356,19 +363,19 @@ scriptletDB.retrieve(entityHash | 0b0001, entity, entries); } scriptletDB.retrieve(0 | 0b0001, hostname, entries); - for ( let entry of entries ) { + for ( const entry of entries ) { exceptionsRegister.add(entry.token); } // Return an array of scriptlets, and log results if needed. - let out = [], - logger = µb.logger.isEnabled() ? µb.logger : null, - isException; - for ( let entry of scriptletsRegister ) { - if ( (isException = exceptionsRegister.has(entry[0])) === false ) { + const out = []; + const loggerEnabled = µb.logger.enabled; + for ( const entry of scriptletsRegister ) { + const isException = exceptionsRegister.has(entry[0]); + if ( isException === false ) { out.push(entry[1]); } - if ( logger !== null ) { + if ( loggerEnabled ) { logOne(isException, entry[0], request); } } @@ -384,7 +391,7 @@ api.injectNow = function(details) { if ( typeof details.frameId !== 'number' ) { return; } if ( µb.URI.isNetworkURI(details.url) === false ) { return; } - let request = { + const request = { tabId: details.tabId, frameId: details.frameId, url: details.url, @@ -394,7 +401,7 @@ }; request.domain = µb.URI.domainFromHostname(request.hostname); request.entity = µb.URI.entityFromDomain(request.domain); - let scriptlets = µb.scriptletFilteringEngine.retrieve(request); + const scriptlets = µb.scriptletFilteringEngine.retrieve(request); if ( scriptlets === undefined ) { return; } let code = contentscriptCode.assemble(request.hostname, scriptlets); if ( µb.hiddenSettings.debugScriptlets ) { @@ -419,19 +426,6 @@ scriptletDB = new µb.staticExtFilteringEngine.HostnameBasedDB(selfie); }; - Object.defineProperties(api, { - acceptedCount: { - get: function() { - return acceptedCount; - } - }, - discardedCount: { - get: function() { - return discardedCount; - } - } - }); - return api; })(); diff --git a/src/js/static-ext-filtering.js b/src/js/static-ext-filtering.js index e53e7739a..b54058547 100644 --- a/src/js/static-ext-filtering.js +++ b/src/js/static-ext-filtering.js @@ -663,11 +663,9 @@ return compiled; } - µb.logger.writeOne( - '', - 'error', - 'Cosmetic filtering – invalid filter: ' + raw - ); + µb.logger.writeOne({ + error: 'Cosmetic filtering – invalid filter: ' + raw + }); }; return entryPoint; diff --git a/src/js/static-net-filtering.js b/src/js/static-net-filtering.js index f02b3fe50..30b788639 100644 --- a/src/js/static-net-filtering.js +++ b/src/js/static-net-filtering.js @@ -126,13 +126,6 @@ let pageHostnameRegister = '', // Local helpers -// Be sure to not confuse 'example.com' with 'anotherexample.com' -const isFirstParty = function(domain, hostname) { - return hostname.endsWith(domain) && - (hostname.length === domain.length || - hostname.charCodeAt(hostname.length - domain.length - 1) === 0x2E /* '.' */); -}; - const normalizeRegexSource = function(s) { try { const re = new RegExp(s); @@ -2184,12 +2177,10 @@ FilterContainer.prototype.compile = function(raw, writer) { // ORDER OF TESTS IS IMPORTANT! // Ignore empty lines - var s = raw.trim(); - if ( s.length === 0 ) { - return false; - } + const s = raw.trim(); + if ( s.length === 0 ) { return false; } - var parsed = this.filterParser.parse(s); + const parsed = this.filterParser.parse(s); // Ignore element-hiding filters if ( parsed.elemHiding ) { @@ -2198,7 +2189,9 @@ FilterContainer.prototype.compile = function(raw, writer) { // Ignore filters with unsupported options if ( parsed.unsupported ) { - µb.logger.writeOne('', 'error', 'Network filtering – invalid filter: ' + raw); + µb.logger.writeOne({ + error: 'Network filtering – invalid filter: ' + raw + }); return false; } @@ -2217,7 +2210,7 @@ FilterContainer.prototype.compile = function(raw, writer) { parsed.makeToken(); - var fdata; + let fdata; if ( parsed.isRegex ) { fdata = FilterRegex.compile(parsed); } else if ( parsed.hostnamePure ) { @@ -2256,7 +2249,7 @@ FilterContainer.prototype.compile = function(raw, writer) { fdata = FilterPlain.compile(parsed); } - var fwrapped; + let fwrapped; if ( parsed.domainOpt !== '' ) { fwrapped = fdata; fdata = FilterOrigin.compile(parsed); @@ -2520,24 +2513,22 @@ FilterContainer.prototype.matchStringGenericHide = function(requestURL) { // Some type of requests are exceptional, they need custom handling, // not the generic handling. -FilterContainer.prototype.matchStringExactType = function(context, requestURL, requestType) { +FilterContainer.prototype.matchStringExactType = function(fctxt, requestType) { // Special cases. if ( requestType === 'generichide' ) { - return this.matchStringGenericHide(requestURL); + return this.matchStringGenericHide(fctxt.url); } let type = typeNameToTypeValue[requestType]; if ( type === undefined ) { return 0; } // Prime tokenizer: we get a normalized URL in return. - let url = this.urlTokenizer.setURL(requestURL); + let url = this.urlTokenizer.setURL(fctxt.url); // These registers will be used by various filters - pageHostnameRegister = context.pageHostname || ''; - requestHostnameRegister = µb.URI.hostnameFromURI(url); + pageHostnameRegister = fctxt.getDocHostname(); + requestHostnameRegister = fctxt.getHostname(); - let party = isFirstParty(context.pageDomain, requestHostnameRegister) - ? FirstParty - : ThirdParty; + let party = fctxt.is3rdPartyToDoc() ? ThirdParty : FirstParty; let categories = this.categories, catBits, bucket; @@ -2602,15 +2593,15 @@ FilterContainer.prototype.matchStringExactType = function(context, requestURL, r /******************************************************************************/ -FilterContainer.prototype.matchString = function(context) { +FilterContainer.prototype.matchString = function(fctxt) { // https://github.com/chrisaljoudi/uBlock/issues/519 // Use exact type match for anything beyond `other` // Also, be prepared to support unknown types - let type = typeNameToTypeValue[context.requestType]; + let type = typeNameToTypeValue[fctxt.type]; if ( type === undefined ) { type = otherTypeBitValue; } else if ( type === 0 || type > otherTypeBitValue ) { - return this.matchStringExactType(context, context.requestURL, context.requestType); + return this.matchStringExactType(fctxt, fctxt.type); } // The logic here is simple: @@ -2635,17 +2626,17 @@ FilterContainer.prototype.matchString = function(context) { // filter. // Prime tokenizer: we get a normalized URL in return. - let url = this.urlTokenizer.setURL(context.requestURL); + let url = this.urlTokenizer.setURL(fctxt.url); // These registers will be used by various filters - pageHostnameRegister = context.pageHostname || ''; - requestHostnameRegister = context.requestHostname; + pageHostnameRegister = fctxt.getDocHostname(); + requestHostnameRegister = fctxt.getHostname(); this.fRegister = null; - let party = isFirstParty(context.pageDomain, context.requestHostname) - ? FirstParty - : ThirdParty; + let party = fctxt.is3rdPartyToDoc() + ? ThirdParty + : FirstParty; let categories = this.categories, catBits, bucket; diff --git a/src/js/tab.js b/src/js/tab.js index 35f90d9f5..8146f84b2 100644 --- a/src/js/tab.js +++ b/src/js/tab.js @@ -215,6 +215,7 @@ housekeep itself. this.stack = []; this.rawURL = this.normalURL = + this.origin = this.rootHostname = this.rootDomain = ''; this.commitTimer = null; @@ -305,14 +306,21 @@ housekeep itself. TabContext.prototype.update = function() { this.netFilteringReadTime = 0; if ( this.stack.length === 0 ) { - this.rawURL = this.normalURL = this.rootHostname = this.rootDomain = ''; + this.rawURL = + this.normalURL = + this.origin = + this.rootHostname = + this.rootDomain = ''; return; } var stackEntry = this.stack[this.stack.length - 1]; this.rawURL = stackEntry.url; this.normalURL = µb.normalizePageURL(this.tabId, this.rawURL); - this.rootHostname = µb.URI.hostnameFromURI(this.normalURL); - this.rootDomain = µb.URI.domainFromHostname(this.rootHostname) || this.rootHostname; + this.origin = µb.URI.originFromURI(this.normalURL); + this.rootHostname = µb.URI.hostnameFromURI(this.origin); + this.rootDomain = + µb.URI.domainFromHostname(this.rootHostname) || + this.rootHostname; }; // Called whenever a candidate root URL is spotted for the tab. @@ -431,15 +439,13 @@ housekeep itself. // Behind-the-scene tab context (function() { - var entry = new TabContext(vAPI.noTabId); + const entry = new TabContext(vAPI.noTabId); entry.stack.push(new StackEntry('', true)); entry.rawURL = ''; entry.normalURL = µb.normalizePageURL(entry.tabId); - entry.rootHostname = µb.URI.hostnameFromURI(entry.normalURL); + entry.origin = µb.URI.originFromURI(entry.normalURL); + entry.rootHostname = µb.URI.hostnameFromURI(entry.origin); entry.rootDomain = µb.URI.domainFromHostname(entry.rootHostname); - - entry = new TabContext(vAPI.anyTabId); - entry.stack.push(new StackEntry('', true)); })(); // Context object, typically to be used to feed filtering engines. @@ -454,6 +460,7 @@ housekeep itself. this.pageHostname = this.pageDomain = this.requestURL = + this.origin = this.requestHostname = this.requestDomain = ''; return this; @@ -550,8 +557,7 @@ vAPI.tabs.onClosed = function(tabId) { vAPI.tabs.onPopupUpdated = (function() { // The same context object will be reused everytime. This also allows to // remember whether a popup or popunder was matched. - var context = {}, - logData; + const fctxt = µBlock.filteringContext.setFilter(undefined); // https://github.com/gorhill/uBlock/commit/1d448b85b2931412508aa01bf899e0b6f0033626#commitcomment-14944764 // See if two URLs are different, disregarding scheme -- because the @@ -560,10 +566,10 @@ vAPI.tabs.onPopupUpdated = (function() { // Maybe no link element was clicked. // https://github.com/gorhill/uBlock/issues/3287 // Do not bail out if the target URL has no hostname. - var areDifferentURLs = function(a, b) { + const areDifferentURLs = function(a, b) { if ( b === '' ) { return true; } if ( b.startsWith('about:') ) { return false; } - var pos = a.indexOf('://'); + let pos = a.indexOf('://'); if ( pos === -1 ) { return false; } a = a.slice(pos); pos = b.indexOf('://'); @@ -573,19 +579,12 @@ vAPI.tabs.onPopupUpdated = (function() { return b !== a; }; - var popupMatch = function(openerURL, targetURL, popupType) { - var openerHostname = µb.URI.hostnameFromURI(openerURL), - openerDomain = µb.URI.domainFromHostname(openerHostname), - result; - - context.pageHostname = openerHostname; - context.pageDomain = openerDomain; - context.rootURL = openerURL; - context.rootHostname = openerHostname; - context.rootDomain = openerDomain; - context.requestURL = targetURL; - context.requestHostname = µb.URI.hostnameFromURI(targetURL); - context.requestType = 'popup'; + const popupMatch = function(openerURL, targetURL, popupType) { + fctxt.setTabOriginFromURL(openerURL) + .setDocOriginFromURL(openerURL) + .setURL(targetURL) + .setType('popup'); + let result; // https://github.com/gorhill/uBlock/issues/1735 // Do not bail out on `data:` URI, they are commonly used for popups. @@ -603,16 +602,19 @@ vAPI.tabs.onPopupUpdated = (function() { // Ignore bad target URL. On Firefox, an `about:blank` tab may be // opened for a new tab before it is filled in with the real target // URL. - if ( openerHostname !== '' && targetURL !== 'about:blank' ) { + if ( fctxt.getTabHostname() !== '' && targetURL !== 'about:blank' ) { // Check per-site switch first // https://github.com/gorhill/uBlock/issues/3060 // - The no-popups switch must apply only to popups, not to // popunders. if ( popupType === 'popup' && - µb.sessionSwitches.evaluateZ('no-popups', openerHostname) + µb.sessionSwitches.evaluateZ( + 'no-popups', + fctxt.getTabHostname() + ) ) { - logData = { + fctxt.filter = { raw: 'no-popups: ' + µb.sessionSwitches.z + ' true', result: 1, source: 'switch' @@ -624,7 +626,7 @@ vAPI.tabs.onPopupUpdated = (function() { // Take into account popup-specific rules in dynamic URL // filtering, OR generic allow rules. result = µb.sessionURLFiltering.evaluateZ( - openerHostname, + fctxt.getTabHostname(), targetURL, popupType ); @@ -632,7 +634,7 @@ vAPI.tabs.onPopupUpdated = (function() { result === 1 && µb.sessionURLFiltering.type === popupType || result === 2 ) { - logData = µb.sessionURLFiltering.toLogData(); + fctxt.filter = µb.sessionURLFiltering.toLogData(); return result; } @@ -641,12 +643,12 @@ vAPI.tabs.onPopupUpdated = (function() { // rules are ignored, as block rules are not meant to block // specific types like `popup` (just like with static filters). result = µb.sessionFirewall.evaluateCellZY( - openerHostname, - context.requestHostname, + fctxt.getTabHostname(), + fctxt.getHostname(), popupType ); if ( result === 2 ) { - logData = µb.sessionFirewall.toLogData(); + fctxt.filter = µb.sessionFirewall.toLogData(); return 2; } } @@ -656,12 +658,11 @@ vAPI.tabs.onPopupUpdated = (function() { // Don't block if uBlock is turned off in popup's context if ( µb.getNetFilteringSwitch(targetURL) ) { result = µb.staticNetFilteringEngine.matchStringExactType( - context, - targetURL, + fctxt, popupType ); if ( result !== 0 ) { - logData = µb.staticNetFilteringEngine.toLogData(); + fctxt.filter = µb.staticNetFilteringEngine.toLogData(); return result; } } @@ -669,23 +670,23 @@ vAPI.tabs.onPopupUpdated = (function() { return 0; }; - var mapPopunderResult = function(popunderURL, popunderHostname, result) { + const mapPopunderResult = function(popunderURL, popunderHostname, result) { if ( - logData === undefined || - logData.source !== 'static' || - logData.token === µb.staticNetFilteringEngine.noTokenHash + fctxt.filter === undefined || + fctxt.filter !== 'static' || + fctxt.filter.token === µb.staticNetFilteringEngine.noTokenHash ) { return 0; } - if ( logData.token === µb.staticNetFilteringEngine.dotTokenHash ) { + if ( fctxt.filter.token === µb.staticNetFilteringEngine.dotTokenHash ) { return result; } - var re = new RegExp(logData.regex, 'i'), - matches = re.exec(popunderURL); + const re = new RegExp(fctxt.filter.regex, 'i'); + const matches = re.exec(popunderURL); if ( matches === null ) { return 0; } - var beg = matches.index, - end = beg + matches[0].length, - pos = popunderURL.indexOf(popunderHostname); + const beg = matches.index; + const end = beg + matches[0].length; + const pos = popunderURL.indexOf(popunderHostname); if ( pos === -1 ) { return 0; } // https://github.com/gorhill/uBlock/issues/1471 // We test whether the opener hostname as at least one character @@ -698,36 +699,32 @@ vAPI.tabs.onPopupUpdated = (function() { : 0; }; - var popunderMatch = function(openerURL, targetURL) { - var result = popupMatch(targetURL, openerURL, 'popunder'); - if ( result === 1 ) { - return result; - } + const popunderMatch = function(openerURL, targetURL) { + let result = popupMatch(targetURL, openerURL, 'popunder'); + if ( result === 1 ) { return result; } + // https://github.com/gorhill/uBlock/issues/1010#issuecomment-186824878 // Check the opener tab as if it were the newly opened tab: if there // is a hit against a popup filter, and if the matching filter is not // a broad one, we will consider the opener tab to be a popunder tab. // For now, a "broad" filter is one which does not touch any part of // the hostname part of the opener URL. - var popunderURL = openerURL, + let popunderURL = openerURL, popunderHostname = µb.URI.hostnameFromURI(popunderURL); - if ( popunderHostname === '' ) { - return 0; - } + if ( popunderHostname === '' ) { return 0; } + result = mapPopunderResult( popunderURL, popunderHostname, popupMatch(targetURL, popunderURL, 'popup') ); - if ( result !== 0 ) { - return result; - } + if ( result !== 0 ) { return result; } + // https://github.com/gorhill/uBlock/issues/1598 - // Try to find a match against origin part of the opener URL. + // Try to find a match against origin part of the opener URL. popunderURL = µb.URI.originFromURI(popunderURL); - if ( popunderURL === '' ) { - return 0; - } + if ( popunderURL === '' ) { return 0; } + return mapPopunderResult( popunderURL, popunderHostname, @@ -737,40 +734,43 @@ vAPI.tabs.onPopupUpdated = (function() { return function(targetTabId, openerDetails) { // Opener details. - var openerTabId = openerDetails.tabId; - var tabContext = µb.tabContextManager.lookup(openerTabId); + const openerTabId = openerDetails.tabId; + let tabContext = µb.tabContextManager.lookup(openerTabId); if ( tabContext === null ) { return; } - var openerURL = tabContext.rawURL; + const openerURL = tabContext.rawURL; if ( openerURL === '' ) { return; } // Popup details. tabContext = µb.tabContextManager.lookup(targetTabId); if ( tabContext === null ) { return; } - var targetURL = tabContext.rawURL; + let targetURL = tabContext.rawURL; if ( targetURL === '' ) { return; } // https://github.com/gorhill/uBlock/issues/341 // Allow popups if uBlock is turned off in opener's context. - if ( µb.getNetFilteringSwitch(openerURL) === false ) { - return; - } + if ( µb.getNetFilteringSwitch(openerURL) === false ) { return; } // https://github.com/gorhill/uBlock/issues/1538 - if ( µb.getNetFilteringSwitch(µb.normalizePageURL(openerTabId, openerURL)) === false ) { + if ( + µb.getNetFilteringSwitch(µb.normalizePageURL( + openerTabId, + openerURL) + ) === false + ) { return; } // If the page URL is that of our "blocked page" URL, extract the URL of // the page which was blocked. if ( targetURL.startsWith(vAPI.getURL('document-blocked.html')) ) { - var matches = /details=([^&]+)/.exec(targetURL); + const matches = /details=([^&]+)/.exec(targetURL); if ( matches !== null ) { targetURL = JSON.parse(atob(matches[1])).url; } } // Popup test. - var popupType = 'popup', + let popupType = 'popup', result = 0; // https://github.com/gorhill/uBlock/issues/2919 // - If the target tab matches a clicked link, assume it's legit. @@ -788,18 +788,21 @@ vAPI.tabs.onPopupUpdated = (function() { // Log only for when there was a hit against an actual filter (allow or block). // https://github.com/gorhill/uBlock/issues/2776 - if ( µb.logger.isEnabled() ) { - µb.logger.writeOne( - popupType === 'popup' ? openerTabId : targetTabId, - 'net', - result !== 0 ? logData : undefined, - popupType, - popupType === 'popup' ? targetURL : openerURL, - µb.URI.hostnameFromURI(context.rootURL), - µb.URI.hostnameFromURI(context.rootURL) - ); + if ( µb.logger.enabled ) { + fctxt.setRealm('net').setType(popupType); + if ( popupType === 'popup' ) { + fctxt.setURL(targetURL) + .setTabId(openerTabId) + .setTabOriginFromURL(openerURL) + .setDocOriginFromURL(openerURL); + } else { + fctxt.setURL(openerURL) + .setTabId(targetTabId) + .setTabOriginFromURL(targetURL) + .setDocOriginFromURL(targetURL); + } + fctxt.toLogger(); } - logData = undefined; // Not blocked if ( result !== 1 ) { @@ -808,9 +811,9 @@ vAPI.tabs.onPopupUpdated = (function() { // Only if a popup was blocked do we report it in the dynamic // filtering pane. - var pageStore = µb.pageStoreFromTabId(openerTabId); + const pageStore = µb.pageStoreFromTabId(openerTabId); if ( pageStore ) { - pageStore.journalAddRequest(context.requestHostname, result); + pageStore.journalAddRequest(fctxt.getHostname(), result); pageStore.popupBlockedCount += 1; } @@ -910,13 +913,9 @@ vAPI.tabs.registerListeners(); // Permanent page store for behind-the-scene requests. Must never be removed. (function() { - var pageStore = µb.PageStore.factory(vAPI.noTabId); + const pageStore = µb.PageStore.factory(vAPI.noTabId); µb.pageStores.set(pageStore.tabId, pageStore); pageStore.title = vAPI.i18n('logBehindTheScene'); - - pageStore = µb.PageStore.factory(vAPI.anyTabId); - µb.pageStores.set(pageStore.tabId, pageStore); - pageStore.title = '[Any one of the known tabs]'; })(); /******************************************************************************/ diff --git a/src/js/traffic.js b/src/js/traffic.js index a46aa5112..1d5d0ba57 100644 --- a/src/js/traffic.js +++ b/src/js/traffic.js @@ -49,148 +49,98 @@ window.addEventListener('webextFlavor', function() { dontCacheResponseHeaders = vAPI.webextFlavor.soup.has('firefox'); cantMergeCSPHeaders = - vAPI.webextFlavor.soup.has('firefox') && vAPI.webextFlavor.major < 59; + vAPI.webextFlavor.soup.has('firefox') && + vAPI.webextFlavor.major < 59; }, { once: true }); /******************************************************************************/ // Intercept and filter web requests. -var onBeforeRequest = function(details) { +const onBeforeRequest = function(details) { + const fctxt = µBlock.filteringContext.fromWebrequestDetails(details); + // Special handling for root document. // https://github.com/chrisaljoudi/uBlock/issues/1001 // This must be executed regardless of whether the request is // behind-the-scene - var requestType = details.type; - if ( requestType === 'main_frame' ) { - return onBeforeRootFrameRequest(details); + if ( details.type === 'main_frame' ) { + return onBeforeRootFrameRequest(fctxt); } // Special treatment: behind-the-scene requests - var tabId = details.tabId; - if ( vAPI.isBehindTheSceneTabId(tabId) ) { - return onBeforeBehindTheSceneRequest(details); + const tabId = details.tabId; + if ( tabId < 0 ) { + return onBeforeBehindTheSceneRequest(fctxt); } // Lookup the page store associated with this tab id. - var µb = µBlock, - pageStore = µb.pageStoreFromTabId(tabId); - if ( !pageStore ) { - var tabContext = µb.tabContextManager.mustLookup(tabId); - if ( vAPI.isBehindTheSceneTabId(tabContext.tabId) ) { - return onBeforeBehindTheSceneRequest(details); + const µb = µBlock; + let pageStore = µb.pageStoreFromTabId(tabId); + if ( pageStore === null ) { + const tabContext = µb.tabContextManager.mustLookup(tabId); + if ( tabContext.tabId < 0 ) { + return onBeforeBehindTheSceneRequest(fctxt); } - vAPI.tabs.onNavigation({ tabId: tabId, frameId: 0, url: tabContext.rawURL }); + vAPI.tabs.onNavigation({ tabId, frameId: 0, url: tabContext.rawURL }); pageStore = µb.pageStoreFromTabId(tabId); } - // https://github.com/chrisaljoudi/uBlock/issues/886 - // For requests of type `sub_frame`, the parent frame id must be used - // to lookup the proper context: - // > If the document of a (sub-)frame is loaded (type is main_frame or - // > sub_frame), frameId indicates the ID of this frame, not the ID of - // > the outer frame. - // > (ref: https://developer.chrome.com/extensions/webRequest) - var isFrame = requestType === 'sub_frame'; + const result = pageStore.filterRequest(fctxt); - // https://github.com/chrisaljoudi/uBlock/issues/114 - var requestContext = pageStore.createContextFromFrameId( - isFrame ? details.parentFrameId : details.frameId - ); + pageStore.journalAddRequest(fctxt.getHostname(), result); - // Setup context and evaluate - var requestURL = details.url; - requestContext.requestURL = requestURL; - requestContext.requestHostname = µb.URI.hostnameFromURI(requestURL); - requestContext.requestType = requestType; - - var result = pageStore.filterRequest(requestContext); - - pageStore.journalAddRequest(requestContext.requestHostname, result); - - if ( µb.logger.isEnabled() ) { - µb.logger.writeOne( - tabId, - 'net', - pageStore.logData, - requestType, - requestURL, - requestContext.rootHostname, - requestContext.pageHostname - ); + if ( µb.logger.enabled ) { + fctxt.setRealm('net').toLogger(); } // Not blocked if ( result !== 1 ) { - // https://github.com/chrisaljoudi/uBlock/issues/114 - if ( details.parentFrameId !== -1 && isFrame ) { - pageStore.setFrame(details.frameId, requestURL); + if ( details.parentFrameId !== -1 && details.type === 'sub_frame' ) { + pageStore.setFrame(details.frameId, details.url); } - requestContext.dispose(); return; } // Blocked // https://github.com/gorhill/uBlock/issues/949 - // Redirect blocked request? + // Redirect blocked request? if ( µb.hiddenSettings.ignoreRedirectFilters !== true ) { - var url = µb.redirectEngine.toURL(requestContext); + const url = µb.redirectEngine.toURL(fctxt); if ( url !== undefined ) { pageStore.internalRedirectionCount += 1; - if ( µb.logger.isEnabled() ) { - µb.logger.writeOne( - tabId, - 'redirect', - { source: 'redirect', raw: µb.redirectEngine.resourceNameRegister }, - requestType, - requestURL, - requestContext.rootHostname, - requestContext.pageHostname - ); + if ( µb.logger.enabled ) { + fctxt.setRealm('redirect') + .setFilter({ source: 'redirect', raw: µb.redirectEngine.resourceNameRegister }) + .toLogger(); } - requestContext.dispose(); return { redirectUrl: url }; } } - requestContext.dispose(); return { cancel: true }; }; /******************************************************************************/ -var onBeforeRootFrameRequest = function(details) { - var tabId = details.tabId, - requestURL = details.url, - µb = µBlock; - - µb.tabContextManager.push(tabId, requestURL); +const onBeforeRootFrameRequest = function(fctxt) { + const µb = µBlock; + const requestURL = fctxt.url; // Special handling for root document. // https://github.com/chrisaljoudi/uBlock/issues/1001 - // This must be executed regardless of whether the request is - // behind-the-scene - var µburi = µb.URI, - requestHostname = µburi.hostnameFromURI(requestURL), - requestDomain = µburi.domainFromHostname(requestHostname) || requestHostname; - var context = { - rootHostname: requestHostname, - rootDomain: requestDomain, - pageHostname: requestHostname, - pageDomain: requestDomain, - requestURL: requestURL, - requestHostname: requestHostname, - requestType: 'main_frame' - }; - var result = 0, - logData, - logEnabled = µb.logger.isEnabled(); + // This must be executed regardless of whether the request is + // behind-the-scene + const requestHostname = fctxt.getHostname(); + const logEnabled = µb.logger.enabled; + let result = 0, + logData; // If the site is whitelisted, disregard strict blocking if ( µb.getNetFilteringSwitch(requestURL) === false ) { result = 2; - if ( logEnabled === true ) { + if ( logEnabled ) { logData = { engine: 'u', result: 2, raw: 'whitelisted' }; } } @@ -207,10 +157,7 @@ var onBeforeRootFrameRequest = function(details) { } // Temporarily whitelisted? - if ( - result === 0 && - strictBlockBypasser.isBypassed(requestHostname) - ) { + if ( result === 0 && strictBlockBypasser.isBypassed(requestHostname) ) { result = 2; if ( logEnabled ) { logData = { engine: 'u', result: 2, raw: 'no-strict-blocking: true (temporary)' }; @@ -218,20 +165,20 @@ var onBeforeRootFrameRequest = function(details) { } // Static filtering: We always need the long-form result here. - var snfe = µb.staticNetFilteringEngine; + const snfe = µb.staticNetFilteringEngine; // Check for specific block if ( result === 0 ) { - result = snfe.matchStringExactType(context, requestURL, 'main_frame'); - if ( result !== 0 || logEnabled === true ) { + result = snfe.matchStringExactType(fctxt, 'main_frame'); + if ( result !== 0 || logEnabled ) { logData = snfe.toLogData(); } } // Check for generic block if ( result === 0 ) { - result = snfe.matchStringExactType(context, requestURL, 'no_type'); - if ( result !== 0 || logEnabled === true ) { + result = snfe.matchStringExactType(fctxt, 'no_type'); + if ( result !== 0 || logEnabled ) { logData = snfe.toLogData(); } // https://github.com/chrisaljoudi/uBlock/issues/1128 @@ -249,22 +196,14 @@ var onBeforeRootFrameRequest = function(details) { } // Log - var pageStore = µb.bindTabToPageStats(tabId, 'beforeRequest'); + const pageStore = µb.bindTabToPageStats(fctxt.tabId, 'beforeRequest'); if ( pageStore ) { pageStore.journalAddRootFrame('uncommitted', requestURL); pageStore.journalAddRequest(requestHostname, result); } if ( logEnabled ) { - µb.logger.writeOne( - tabId, - 'net', - logData, - 'main_frame', - requestURL, - requestHostname, - requestHostname - ); + fctxt.setRealm('net').setFilter(logData).toLogger(); } // Not blocked @@ -275,15 +214,18 @@ var onBeforeRootFrameRequest = function(details) { if ( logData === undefined ) { return; } // Blocked - var query = btoa(JSON.stringify({ + const query = btoa(JSON.stringify({ url: requestURL, hn: requestHostname, - dn: requestDomain, + dn: fctxt.getDomain() || requestHostname, fc: logData.compiled, fs: logData.raw })); - vAPI.tabs.replace(tabId, vAPI.getURL('document-blocked.html?details=') + query); + vAPI.tabs.replace( + fctxt.tabId, + vAPI.getURL('document-blocked.html?details=') + query + ); return { cancel: true }; }; @@ -293,10 +235,10 @@ var onBeforeRootFrameRequest = function(details) { // https://github.com/gorhill/uBlock/issues/3208 // Mind case insensitivity. -var toBlockDocResult = function(url, hostname, logData) { +const toBlockDocResult = function(url, hostname, logData) { if ( typeof logData.regex !== 'string' ) { return false; } - var re = new RegExp(logData.regex, 'i'), - match = re.exec(url.toLowerCase()); + const re = new RegExp(logData.regex, 'i'); + const match = re.exec(url.toLowerCase()); if ( match === null ) { return false; } // https://github.com/chrisaljoudi/uBlock/issues/1128 @@ -315,31 +257,11 @@ var toBlockDocResult = function(url, hostname, logData) { // `beacon`, so now we can block them according to the state of the // "Disable hyperlink auditing/beacon" setting. -var onBeforeBehindTheSceneRequest = function(details) { - var µb = µBlock, - pageStore = µb.pageStoreFromTabId(details.tabId); +const onBeforeBehindTheSceneRequest = function(fctxt) { + const µb = µBlock; + const pageStore = µb.pageStoreFromTabId(fctxt.tabId); if ( pageStore === null ) { return; } - var µburi = µb.URI, - context = pageStore.createContextFromPage(), - requestType = details.type, - requestURL = details.url; - - context.requestURL = requestURL; - context.requestHostname = µburi.hostnameFromURI(requestURL); - context.requestType = requestType; - - var normalURL; - if ( details.tabId === vAPI.anyTabId && context.pageHostname === '' ) { - normalURL = µb.normalizePageURL(0, details.documentUrl); - context.pageHostname = µburi.hostnameFromURI(normalURL); - context.pageDomain = µburi.domainFromHostname(context.pageHostname); - context.rootHostname = context.pageHostname; - context.rootDomain = context.pageDomain; - } - - pageStore.logData = undefined; - // https://bugs.chromium.org/p/chromium/issues/detail?id=637577#c15 // Do not filter behind-the-scene network request of type `beacon`: there // is no point. In any case, this will become a non-issue once @@ -359,14 +281,14 @@ var onBeforeBehindTheSceneRequest = function(details) { // requests. Hopefully this will not break stuff as it used to be the // case. - var result = 0; + let result = 0; if ( - µburi.isNetworkURI(details.documentUrl) || + µb.URI.isNetworkURI(fctxt.docOrigin) || µb.userSettings.advancedUserEnabled || - requestType === 'csp_report' + fctxt.type === 'csp_report' ) { - result = pageStore.filterRequest(context); + result = pageStore.filterRequest(fctxt); // The "any-tab" scope is not whitelist-able, and in such case we must // use the origin URL as the scope. Most such requests aren't going to @@ -374,30 +296,19 @@ var onBeforeBehindTheSceneRequest = function(details) { // result only when the request is being blocked. if ( result === 1 && - normalURL !== undefined && - µb.getNetFilteringSwitch(normalURL) === false + µb.getNetFilteringSwitch(fctxt.tabOrigin) === false ) { result = 2; - pageStore.logData = { engine: 'u', result: 2, raw: 'whitelisted' }; + fctxt.filter = { engine: 'u', result: 2, raw: 'whitelisted' }; } } - pageStore.journalAddRequest(context.requestHostname, result); + pageStore.journalAddRequest(fctxt.getHostname(), result); - if ( µb.logger.isEnabled() ) { - µb.logger.writeOne( - details.tabId, - 'net', - pageStore.logData, - requestType, - requestURL, - context.rootHostname, - context.rootHostname - ); + if ( µb.logger.enabled ) { + fctxt.setRealm('net').toLogger(); } - context.dispose(); - // Blocked? if ( result === 1 ) { return { cancel: true }; @@ -408,18 +319,17 @@ var onBeforeBehindTheSceneRequest = function(details) { // https://github.com/gorhill/uBlock/issues/3140 -let onBeforeMaybeSpuriousCSPReport = (function() { +const onBeforeMaybeSpuriousCSPReport = (function() { let textDecoder; return function(details) { - let tabId = details.tabId; + const fctxt = µBlock.filteringContext.fromWebrequestDetails(details); // Ignore behind-the-scene requests. - if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; } + if ( fctxt.tabId < 0 ) { return; } // Lookup the page store associated with this tab id. - let µb = µBlock, - pageStore = µb.pageStoreFromTabId(tabId); + const pageStore = µBlock.pageStoreFromTabId(fctxt.tabId); if ( pageStore === null ) { return; } // If uBO is disabled for the page, it can't possibly causes CSP @@ -445,7 +355,7 @@ let onBeforeMaybeSpuriousCSPReport = (function() { textDecoder !== undefined && details.method === 'POST' ) { - let raw = details.requestBody && details.requestBody.raw; + const raw = details.requestBody && details.requestBody.raw; if ( Array.isArray(raw) && raw.length !== 0 && @@ -458,14 +368,14 @@ let onBeforeMaybeSpuriousCSPReport = (function() { } catch (ex) { } if ( data instanceof Object ) { - let report = data['csp-report']; + const report = data['csp-report']; if ( report instanceof Object ) { - let blocked = - report['blocked-uri'] || report['blockedURI'], - validBlocked = typeof blocked === 'string', - source = - report['source-file'] || report['sourceFile'], - validSource = typeof source === 'string'; + const blocked = + report['blocked-uri'] || report['blockedURI']; + const validBlocked = typeof blocked === 'string'; + const source = + report['source-file'] || report['sourceFile']; + const validSource = typeof source === 'string'; if ( (validBlocked || validSource) && (!validBlocked || !blocked.startsWith('data')) && @@ -478,18 +388,13 @@ let onBeforeMaybeSpuriousCSPReport = (function() { } } - // Potentially spurious CSP report. - if ( µb.logger.isEnabled() ) { - let hostname = µb.URI.hostnameFromURI(details.url); - µb.logger.writeOne( - tabId, - 'net', - { result: 1, source: 'global', raw: 'no-spurious-csp-report' }, - 'csp_report', - details.url, - hostname, - hostname - ); + // At this point, we have a potentially spurious CSP report. + + if ( µBlock.logger.enabled ) { + fctxt.setRealm('net') + .setType('csp_report') + .setFilter({ result: 1, source: 'global', raw: 'no-spurious-csp-report' }) + .toLogger(); } return { cancel: true }; @@ -504,44 +409,49 @@ let onBeforeMaybeSpuriousCSPReport = (function() { // - HTML filtering (requires ability to modify response body) // - CSP injection -var onHeadersReceived = function(details) { - // Do not interfere with behind-the-scene requests. - let tabId = details.tabId; - if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; } +const onHeadersReceived = function(details) { + const fctxt = µBlock.filteringContext.fromWebrequestDetails(details); - let µb = µBlock, - requestType = details.type, - isRootDoc = requestType === 'main_frame', - isDoc = isRootDoc || requestType === 'sub_frame'; + // Do not interfere with behind-the-scene requests. + if ( fctxt.tabId < 0 ) { return; } + + const µb = µBlock; + const requestType = details.type; + const isRootDoc = requestType === 'main_frame'; + const isDoc = isRootDoc || requestType === 'sub_frame'; if ( isRootDoc ) { - µb.tabContextManager.push(tabId, details.url); + µb.tabContextManager.push(details.tabId, details.url); } - let pageStore = µb.pageStoreFromTabId(tabId); + let pageStore = µb.pageStoreFromTabId(fctxt.tabId); if ( pageStore === null ) { if ( isRootDoc === false ) { return; } - pageStore = µb.bindTabToPageStats(tabId, 'beforeRequest'); + pageStore = µb.bindTabToPageStats(fctxt.tabId, 'beforeRequest'); } if ( pageStore.getNetFilteringSwitch() === false ) { return; } - if ( requestType === 'image' || requestType === 'media' ) { - return foilLargeMediaElement(pageStore, details); - } - - if ( isDoc === false ) { return; } - // Keep in mind response headers will be modified in-place if needed, so // `details.responseHeaders` will always point to the modified response // headers. - let responseHeaders = details.responseHeaders; + const responseHeaders = details.responseHeaders; + + if ( requestType === 'image' || requestType === 'media' ) { + return foilLargeMediaElement( + fctxt, + pageStore, + responseHeaders + ); + } + + if ( isDoc === false ) { return; } // https://github.com/gorhill/uBlock/issues/2813 // Disable the blocking of large media elements if the document is itself // a media element: the resource was not prevented from loading so no // point to further block large media elements for the current document. if ( isRootDoc ) { - let contentType = headerValueFromName('content-type', responseHeaders); + const contentType = headerValueFromName('content-type', responseHeaders); if ( reMediaContentTypes.test(contentType) ) { pageStore.allowLargeMediaElementsUntil = Date.now() + 86400000; return; @@ -550,10 +460,10 @@ var onHeadersReceived = function(details) { // At this point we have a HTML document. - let filteredHTML = µb.canFilterResponseData && - filterDocument(pageStore, details) === true; + const filteredHTML = µb.canFilterResponseData && + filterDocument(pageStore, fctxt, details) === true; - let modifiedHeaders = injectCSP(pageStore, details) === true; + let modifiedHeaders = injectCSP(fctxt, pageStore, responseHeaders) === true; // https://bugzilla.mozilla.org/show_bug.cgi?id=1376932 // Prevent document from being cached by the browser if we modified it, @@ -577,7 +487,7 @@ var onHeadersReceived = function(details) { } }; -var reMediaContentTypes = /^(?:audio|image|video)\//; +const reMediaContentTypes = /^(?:audio|image|video)\//; /******************************************************************************* @@ -603,13 +513,13 @@ var reMediaContentTypes = /^(?:audio|image|video)\//; **/ -var filterDocument = (function() { - var µb = µBlock, - filterers = new Map(), - domParser, xmlSerializer, +const filterDocument = (function() { + const µb = µBlock; + const filterers = new Map(); + let domParser, xmlSerializer, utf8TextDecoder, textDecoder, textEncoder; - var textDecode = function(encoding, buffer) { + const textDecode = function(encoding, buffer) { if ( textDecoder !== undefined && textDecoder.encoding !== encoding @@ -622,25 +532,25 @@ var filterDocument = (function() { return textDecoder.decode(buffer); }; - var reContentTypeDocument = /^(?:text\/html|application\/xhtml\+xml)/i, - reContentTypeCharset = /charset=['"]?([^'" ]+)/i; + const reContentTypeDocument = /^(?:text\/html|application\/xhtml\+xml)/i; + const reContentTypeCharset = /charset=['"]?([^'" ]+)/i; - var mimeFromContentType = function(contentType) { - var match = reContentTypeDocument.exec(contentType); + const mimeFromContentType = function(contentType) { + const match = reContentTypeDocument.exec(contentType); if ( match !== null ) { return match[0].toLowerCase(); } }; - var charsetFromContentType = function(contentType) { - var match = reContentTypeCharset.exec(contentType); + const charsetFromContentType = function(contentType) { + const match = reContentTypeCharset.exec(contentType); if ( match !== null ) { return match[1].toLowerCase(); } }; - var charsetFromDoc = function(doc) { - var meta = doc.querySelector('meta[charset]'); + const charsetFromDoc = function(doc) { + let meta = doc.querySelector('meta[charset]'); if ( meta !== null ) { return meta.getAttribute('charset').toLowerCase(); } @@ -652,7 +562,7 @@ var filterDocument = (function() { } }; - var streamClose = function(filterer, buffer) { + const streamClose = function(filterer, buffer) { if ( buffer !== undefined ) { filterer.stream.write(buffer); } else if ( filterer.buffer !== undefined ) { @@ -661,8 +571,8 @@ var filterDocument = (function() { filterer.stream.close(); }; - var onStreamData = function(ev) { - var filterer = filterers.get(this); + const onStreamData = function(ev) { + const filterer = filterers.get(this); if ( filterer === undefined ) { this.write(ev.data); this.disconnect(); @@ -691,7 +601,7 @@ var filterDocument = (function() { filterer.buffer = new Uint8Array(ev.data); return; } - var buffer = new Uint8Array( + const buffer = new Uint8Array( filterer.buffer.byteLength + ev.data.byteLength ); @@ -700,8 +610,8 @@ var filterDocument = (function() { filterer.buffer = buffer; }; - var onStreamStop = function() { - var filterer = filterers.get(this); + const onStreamStop = function() { + const filterer = filterers.get(this); filterers.delete(this); if ( filterer === undefined || filterer.buffer === null ) { this.close(); @@ -717,10 +627,10 @@ var filterDocument = (function() { textEncoder = new TextEncoder(); } - var doc; + let doc; // If stream encoding is still unknnown, try to extract from document. - var charsetFound = filterer.charset, + let charsetFound = filterer.charset, charsetUsed = charsetFound; if ( charsetFound === undefined ) { if ( utf8TextDecoder === undefined ) { @@ -759,7 +669,7 @@ var filterDocument = (function() { } } - var modified = false; + let modified = false; if ( filterer.selectors !== undefined ) { if ( µb.htmlFilteringEngine.apply(doc, filterer) ) { modified = true; @@ -771,12 +681,12 @@ var filterDocument = (function() { } // https://stackoverflow.com/questions/6088972/get-doctype-of-an-html-as-string-with-javascript/10162353#10162353 - var doctypeStr = doc.doctype instanceof Object ? + const doctypeStr = doc.doctype instanceof Object ? xmlSerializer.serializeToString(doc.doctype) + '\n' : ''; // https://github.com/gorhill/uBlock/issues/3391 - var encodedStream = textEncoder.encode( + let encodedStream = textEncoder.encode( doctypeStr + doc.documentElement.outerHTML ); @@ -790,26 +700,26 @@ var filterDocument = (function() { streamClose(filterer, encodedStream); }; - var onStreamError = function() { + const onStreamError = function() { filterers.delete(this); }; - return function(pageStore, details) { + return function(pageStore, fctxt, extras) { // https://github.com/gorhill/uBlock/issues/3478 - var statusCode = details.statusCode || 0; + const statusCode = extras.statusCode || 0; if ( statusCode !== 0 && (statusCode < 200 || statusCode >= 300) ) { return; } - var hostname = µb.URI.hostnameFromURI(details.url); + const hostname = fctxt.getHostname(); if ( hostname === '' ) { return; } - var domain = µb.URI.domainFromHostname(hostname); + const domain = fctxt.getDomain(); - var request = { + const request = { stream: undefined, - tabId: details.tabId, - url: details.url, + tabId: fctxt.tabId, + url: fctxt.url, hostname: hostname, domain: domain, entity: µb.URI.entityFromDomain(domain), @@ -822,12 +732,12 @@ var filterDocument = (function() { request.selectors = µb.htmlFilteringEngine.retrieve(request); if ( request.selectors === undefined ) { return; } - var headers = details.responseHeaders, - contentType = headerValueFromName('content-type', headers); + const headers = extras.responseHeaders; + const contentType = headerValueFromName('content-type', headers); if ( contentType !== '' ) { request.mime = mimeFromContentType(contentType); if ( request.mime === undefined ) { return; } - var charset = charsetFromContentType(contentType); + let charset = charsetFromContentType(contentType); if ( charset !== undefined ) { charset = µb.textEncode.normalizeCharset(charset); if ( charset === undefined ) { return; } @@ -837,8 +747,8 @@ var filterDocument = (function() { // https://bugzilla.mozilla.org/show_bug.cgi?id=1426789 if ( headerValueFromName('content-disposition', headers) ) { return; } - var stream = request.stream = - browser.webRequest.filterResponseData(details.requestId); + const stream = request.stream = + browser.webRequest.filterResponseData(extras.requestId); stream.ondata = onStreamData; stream.onstop = onStreamStop; stream.onerror = onStreamError; @@ -850,74 +760,39 @@ var filterDocument = (function() { /******************************************************************************/ -var injectCSP = function(pageStore, details) { - let µb = µBlock, - tabId = details.tabId, - requestURL = details.url, - loggerEnabled = µb.logger.isEnabled(), - logger = µb.logger, - cspSubsets = []; - - let context = pageStore.createContextFromPage(); - context.requestHostname = µb.URI.hostnameFromURI(requestURL); - if ( details.type !== 'main_frame' ) { - context.pageHostname = context.pageDomain = context.requestHostname; - } - context.requestURL = requestURL; +const injectCSP = function(fctxt, pageStore, responseHeaders) { + const µb = µBlock; + const loggerEnabled = µb.logger.enabled; + const cspSubsets = []; // Start collecting policies >>>>>>>> // ======== built-in policies - let builtinDirectives = []; + const builtinDirectives = []; - context.requestType = 'script'; - if ( pageStore.filterScripting(context.rootHostname, true) === 1 ) { + if ( pageStore.filterScripting(fctxt, true) === 1 ) { builtinDirectives.push("script-src http: https:"); - if ( loggerEnabled === true ) { - logger.writeOne( - tabId, - 'net', - pageStore.logData, - 'no-scripting', - requestURL, - context.rootHostname, - context.pageHostname - ); + if ( loggerEnabled ) { + fctxt.setRealm('net').setType('scripting').toLogger(); } } else { - context.requestType = 'inline-script'; - if ( pageStore.filterRequest(context) === 1 ) { + fctxt.type = 'inline-script'; + if ( pageStore.filterRequest(fctxt) === 1 ) { builtinDirectives.push("script-src 'unsafe-eval' * blob: data:"); - } - if ( loggerEnabled === true ) { - logger.writeOne( - tabId, - 'net', - pageStore.logData, - 'inline-script', - requestURL, - context.rootHostname, - context.pageHostname - ); + if ( loggerEnabled ) { + fctxt.setRealm('net').toLogger(); + } } } // https://github.com/gorhill/uBlock/issues/1539 // - Use a CSP to also forbid inline fonts if remote fonts are blocked. - context.requestType = 'inline-font'; - if ( pageStore.filterRequest(context) === 1 ) { + fctxt.type = 'inline-font'; + if ( pageStore.filterRequest(fctxt) === 1 ) { builtinDirectives.push('font-src *'); - if ( loggerEnabled === true ) { - logger.writeOne( - tabId, - 'net', - pageStore.logData, - 'inline-font', - requestURL, - context.rootHostname, - context.pageHostname - ); + if ( loggerEnabled ) { + fctxt.setRealm('net').toLogger(); } } @@ -929,32 +804,30 @@ var injectCSP = function(pageStore, details) { // Static filtering. - let logDataEntries = []; + const logDataEntries = loggerEnabled ? [] : undefined; µb.staticNetFilteringEngine.matchAndFetchData( 'csp', - requestURL, + fctxt.url, cspSubsets, - loggerEnabled === true ? logDataEntries : undefined + logDataEntries ); // URL filtering `allow` rules override static filtering. if ( cspSubsets.length !== 0 && - µb.sessionURLFiltering.evaluateZ(context.rootHostname, requestURL, 'csp') === 2 + µb.sessionURLFiltering.evaluateZ( + fctxt.getTabHostname(), + fctxt.url, + 'csp' + ) === 2 ) { - if ( loggerEnabled === true ) { - logger.writeOne( - tabId, - 'net', - µb.sessionURLFiltering.toLogData(), - 'csp', - requestURL, - context.rootHostname, - context.pageHostname - ); + if ( loggerEnabled ) { + fctxt.setRealm('net') + .setType('csp') + .setFilter(µb.sessionURLFiltering.toLogData()) + .toLogger(); } - context.dispose(); return; } @@ -962,45 +835,34 @@ var injectCSP = function(pageStore, details) { if ( cspSubsets.length !== 0 && µb.userSettings.advancedUserEnabled && - µb.sessionFirewall.evaluateCellZY(context.rootHostname, context.rootHostname, '*') === 2 + µb.sessionFirewall.evaluateCellZY( + fctxt.getTabHostname(), + fctxt.getTabHostname(), + '*' + ) === 2 ) { - if ( loggerEnabled === true ) { - logger.writeOne( - tabId, - 'net', - µb.sessionFirewall.toLogData(), - 'csp', - requestURL, - context.rootHostname, - context.pageHostname - ); + if ( loggerEnabled ) { + fctxt.setRealm('net') + .setType('csp') + .setFilter(µb.sessionFirewall.toLogData()) + .toLogger(); } - context.dispose(); return; } // <<<<<<<< All policies have been collected // Static CSP policies will be applied. - for ( let entry of logDataEntries ) { - logger.writeOne( - tabId, - 'net', - entry, - 'csp', - requestURL, - context.rootHostname, - context.pageHostname - ); + if ( logDataEntries !== undefined ) { + fctxt.setRealm('net').setType('csp'); + for ( const entry of logDataEntries ) { + fctxt.setFilter(entry).toLogger(); + } } - context.dispose(); + if ( cspSubsets.length === 0 ) { return; } - if ( cspSubsets.length === 0 ) { - return; - } - - µb.updateToolbarIcon(tabId, 0x02); + µb.updateToolbarIcon(fctxt.tabId, 0x02); // Use comma to merge CSP directives. // Ref.: https://www.w3.org/TR/CSP2/#implementation-considerations @@ -1010,17 +872,19 @@ var injectCSP = function(pageStore, details) { // if the current environment does not support merging headers: // Firefox 58/webext and less can't merge CSP headers, so we will merge // them here. - let headers = details.responseHeaders; if ( cantMergeCSPHeaders ) { - let i = headerIndexFromName('content-security-policy', headers); + const i = headerIndexFromName( + 'content-security-policy', + responseHeaders + ); if ( i !== -1 ) { - cspSubsets.unshift(headers[i].value.trim()); - headers.splice(i, 1); + cspSubsets.unshift(responseHeaders[i].value.trim()); + responseHeaders.splice(i, 1); } } - headers.push({ + responseHeaders.push({ name: 'Content-Security-Policy', value: cspSubsets.join(', ') }); @@ -1033,27 +897,16 @@ var injectCSP = function(pageStore, details) { // https://github.com/gorhill/uBlock/issues/1163 // "Block elements by size" -var foilLargeMediaElement = function(pageStore, details) { - var µb = µBlock; - - var i = headerIndexFromName('content-length', details.responseHeaders); +const foilLargeMediaElement = function(fctxt, pageStore, responseHeaders) { + const i = headerIndexFromName('content-length', responseHeaders); if ( i === -1 ) { return; } - var tabId = details.tabId, - size = parseInt(details.responseHeaders[i].value, 10) || 0, - result = pageStore.filterLargeMediaElement(size); + const size = parseInt(responseHeaders[i].value, 10) || 0; + const result = pageStore.filterLargeMediaElement(fctxt, size); if ( result === 0 ) { return; } - if ( µb.logger.isEnabled() ) { - µb.logger.writeOne( - tabId, - 'net', - pageStore.logData, - details.type, - details.url, - pageStore.tabHostname, - pageStore.tabHostname - ); + if ( µBlock.logger.enabled ) { + fctxt.setRealm('net').toLogger(); } return { cancel: true }; @@ -1063,8 +916,8 @@ var foilLargeMediaElement = function(pageStore, details) { // Caller must ensure headerName is normalized to lower case. -var headerIndexFromName = function(headerName, headers) { - var i = headers.length; +const headerIndexFromName = function(headerName, headers) { + let i = headers.length; while ( i-- ) { if ( headers[i].name.toLowerCase() === headerName ) { return i; @@ -1073,19 +926,19 @@ var headerIndexFromName = function(headerName, headers) { return -1; }; -var headerValueFromName = function(headerName, headers) { - var i = headerIndexFromName(headerName, headers); +const headerValueFromName = function(headerName, headers) { + const i = headerIndexFromName(headerName, headers); return i !== -1 ? headers[i].value : ''; }; /******************************************************************************/ -let strictBlockBypasser = { +const strictBlockBypasser = { hostnameToDeadlineMap: new Map(), cleanupTimer: undefined, cleanup: function() { - for ( let [ hostname, deadline ] of this.hostnameToDeadlineMap ) { + for ( const [ hostname, deadline ] of this.hostnameToDeadlineMap ) { if ( deadline <= Date.now() ) { this.hostnameToDeadlineMap.delete(hostname); } @@ -1114,7 +967,7 @@ let strictBlockBypasser = { ); } for (;;) { - let deadline = this.hostnameToDeadlineMap.get(hostname); + const deadline = this.hostnameToDeadlineMap.get(hostname); if ( deadline !== undefined ) { if ( deadline > Date.now() ) { this.hostnameToDeadlineMap.set( @@ -1125,7 +978,7 @@ let strictBlockBypasser = { } this.hostnameToDeadlineMap.delete(hostname); } - let pos = hostname.indexOf('.'); + const pos = hostname.indexOf('.'); if ( pos === -1 ) { break; } hostname = hostname.slice(pos + 1); } @@ -1137,8 +990,8 @@ let strictBlockBypasser = { return { start: (function() { - let suspendedTabs = new Set(); - let onBeforeReady = function(details) { + const suspendedTabs = new Set(); + const onBeforeReady = function(details) { if ( details.type !== 'main_frame' && details.tabId > 0 ) { suspendedTabs.add(details.tabId); console.info('uBO suspend tab %d, block %s', details.tabId, details.url); @@ -1188,7 +1041,7 @@ return { // https://github.com/gorhill/uBlock/issues/2067 // Force-reload tabs for which network requests were blocked // during launch. This can happen only if tabs were "suspended". - for ( let tabId of suspendedTabs ) { + for ( const tabId of suspendedTabs ) { console.info('uBO suspend tab %d, force reload', tabId); vAPI.tabs.reload(tabId); } diff --git a/src/js/ublock.js b/src/js/ublock.js index 1df50ed83..d6ae70a0e 100644 --- a/src/js/ublock.js +++ b/src/js/ublock.js @@ -538,7 +538,7 @@ var matchBucket = function(url, hostname, bucket, start) { // cosmetic filters. µBlock.logCosmeticFilters = function(tabId, frameId) { - if ( this.logger.isEnabled() ) { + if ( this.logger.enabled ) { vAPI.tabs.injectScript(tabId, { file: '/js/scriptlets/cosmetic-logger.js', frameId: frameId, diff --git a/src/js/uritools.js b/src/js/uritools.js index addc03229..7225aee8f 100644 --- a/src/js/uritools.js +++ b/src/js/uritools.js @@ -1,7 +1,7 @@ /******************************************************************************* uBlock Origin - a browser extension to block requests. - Copyright (C) 2014-2018 Raymond Hill + Copyright (C) 2014-present 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 @@ -51,7 +51,7 @@ var reRFC3986 = /^([^:\/?#]+:)?(\/\/[^\/?#]*)?([^?#]*)(\?[^#]*)?(#.*)?/; // Derived var reSchemeFromURI = /^[^:\/?#]+:/; var reAuthorityFromURI = /^(?:[^:\/?#]+:)?(\/\/[^\/?#]+)/; -var reOriginFromURI = /^(?:[^:\/?#]+:)\/\/(?:[^\/?#]+)?/; +var reOriginFromURI = /^(?:[^:\/?#]+:)\/\/[^\/?#]+/; var reCommonHostnameFromURL = /^https?:\/\/([0-9a-z_][0-9a-z._-]*[0-9a-z])\//; var rePathFromURI = /^(?:[^:\/?#]+:)?(?:\/\/[^\/?#]*)?([^?#]*)/; var reMustNormalizeHostname = /[^0-9a-z._-]/; @@ -225,7 +225,7 @@ URI.assemble = function(bits) { /******************************************************************************/ URI.originFromURI = function(uri) { - var matches = reOriginFromURI.exec(uri); + const matches = reOriginFromURI.exec(uri); return matches !== null ? matches[0].toLowerCase() : ''; }; diff --git a/src/lib/publicsuffixlist.js b/src/lib/publicsuffixlist.js index 1a04d78b9..cc7ae2dfc 100644 --- a/src/lib/publicsuffixlist.js +++ b/src/lib/publicsuffixlist.js @@ -37,14 +37,14 @@ /******************************************************************************/ -var exceptions = new Map(); -var rules = new Map(); +let exceptions = new Map(); +let rules = new Map(); // This value dictate how the search will be performed: // < this.cutoffLength = indexOf() // >= this.cutoffLength = binary search -var cutoffLength = 256; -var mustPunycode = /[^a-z0-9.-]/; +const cutoffLength = 256; +const mustPunycode = /[^a-z0-9.-]/; /******************************************************************************/ @@ -284,7 +284,7 @@ function crystallize(store) { /******************************************************************************/ -var selfieMagic = 1; +const selfieMagic = 1; function toSelfie() { return { @@ -311,12 +311,15 @@ function fromSelfie(selfie) { root = root || window; root.publicSuffixList = { - 'version': '1.0', - 'parse': parse, - 'getDomain': getDomain, - 'getPublicSuffix': getPublicSuffix, - 'toSelfie': toSelfie, - 'fromSelfie': fromSelfie, + version: '1.0', + parse: parse, + getDomain: getDomain, + getPublicSuffix: getPublicSuffix, + toSelfie: toSelfie, + fromSelfie: fromSelfie, + get empty() { + return rules.size === 0; + } }; /******************************************************************************/ diff --git a/src/logger-ui.html b/src/logger-ui.html index 0453f148a..5a01029f5 100644 --- a/src/logger-ui.html +++ b/src/logger-ui.html @@ -17,8 +17,9 @@