From 2c901588c71827e470dbdab325e10ce56c963e29 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 26 Feb 2018 13:59:16 -0500 Subject: [PATCH] fix #3546, #3428 --- platform/chromium/vapi-background.js | 45 ++++---- platform/chromium/vapi-webrequest.js | 4 +- platform/firefox/vapi-background.js | 26 ++--- platform/webext/vapi-webrequest.js | 10 +- src/js/background.js | 2 +- src/js/logger-ui-inspector.js | 14 +-- src/js/logger-ui.js | 37 +++---- src/js/messaging.js | 24 ++-- src/js/pagestore.js | 23 ++-- src/js/tab.js | 159 +++++++++++++-------------- src/js/traffic.js | 19 +++- src/js/ublock.js | 16 ++- 12 files changed, 191 insertions(+), 188 deletions(-) diff --git a/platform/chromium/vapi-background.js b/platform/chromium/vapi-background.js index 1b9e48778..c0f07427f 100644 --- a/platform/chromium/vapi-background.js +++ b/platform/chromium/vapi-background.js @@ -265,18 +265,29 @@ 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.toString() === '-1'; + if ( typeof tabId === 'string' ) { debugger; } + return tabId < 0; }; -vAPI.noTabId = '-1'; +vAPI.unsetTabId = 0; +vAPI.noTabId = -1; // definitely not any existing tab +vAPI.anyTabId = -2; // one of the existing tab /******************************************************************************/ +// To remove when tabId-as-integer has been tested enough. + var toChromiumTabId = function(tabId) { - if ( typeof tabId === 'string' ) { - tabId = parseInt(tabId, 10); - } + if ( typeof tabId === 'string' ) { debugger; } if ( typeof tabId !== 'number' || isNaN(tabId) || tabId === -1 ) { return 0; } @@ -329,8 +340,8 @@ vAPI.tabs.registerListeners = function() { } if ( typeof vAPI.tabs.onPopupCreated === 'function' ) { vAPI.tabs.onPopupCreated( - details.tabId.toString(), - details.sourceTabId.toString() + details.tabId, + details.sourceTabId ); } }; @@ -364,7 +375,7 @@ vAPI.tabs.registerListeners = function() { if ( changeInfo.url ) { changeInfo.url = sanitizeURL(changeInfo.url); } - onUpdatedClient(tabId.toString(), changeInfo, tab); + onUpdatedClient(tabId, changeInfo, tab); }; chrome.webNavigation.onBeforeNavigate.addListener(onBeforeNavigate); @@ -542,9 +553,7 @@ vAPI.tabs.open = function(details) { vAPI.tabs.replace = function(tabId, url) { tabId = toChromiumTabId(tabId); - if ( tabId === 0 ) { - return; - } + if ( tabId === 0 ) { return; } var targetURL = url; @@ -565,9 +574,7 @@ vAPI.tabs.replace = function(tabId, url) { vAPI.tabs.remove = function(tabId) { tabId = toChromiumTabId(tabId); - if ( tabId === 0 ) { - return; - } + if ( tabId === 0 ) { return; } var onTabRemoved = function() { // https://code.google.com/p/chromium/issues/detail?id=410868#c8 @@ -605,9 +612,7 @@ vAPI.tabs.reload = function(tabId, bypassCache) { vAPI.tabs.select = function(tabId) { tabId = toChromiumTabId(tabId); - if ( tabId === 0 ) { - return; - } + if ( tabId === 0 ) { return; } chrome.tabs.update(tabId, { active: true }, function(tab) { if ( chrome.runtime.lastError ) { @@ -789,7 +794,7 @@ vAPI.messaging.onPortMessage = (function() { case 'connectionRefused': toPort = messaging.ports.get(msg.fromToken); if ( toPort !== undefined ) { - msg.tabId = tabId && tabId.toString(); + msg.tabId = tabId; toPort.postMessage(request); } else { msg.what = 'connectionBroken'; @@ -797,7 +802,7 @@ vAPI.messaging.onPortMessage = (function() { } break; case 'connectionRequested': - msg.tabId = tabId && tabId.toString(); + msg.tabId = tabId; for ( toPort of messaging.ports.values() ) { toPort.postMessage(request); } @@ -809,7 +814,7 @@ vAPI.messaging.onPortMessage = (function() { port.name === msg.fromToken ? msg.toToken : msg.fromToken ); if ( toPort !== undefined ) { - msg.tabId = tabId && tabId.toString(); + msg.tabId = tabId; toPort.postMessage(request); } else { msg.what = 'connectionBroken'; diff --git a/platform/chromium/vapi-webrequest.js b/platform/chromium/vapi-webrequest.js index 49a87e3e2..875404f27 100644 --- a/platform/chromium/vapi-webrequest.js +++ b/platform/chromium/vapi-webrequest.js @@ -1,7 +1,7 @@ /******************************************************************************* uBlock Origin - a browser extension to block requests. - Copyright (C) 2017 Raymond Hill + Copyright (C) 2017-2018 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 @@ -105,8 +105,6 @@ vAPI.net.registerListeners = function() { }; var normalizeRequestDetails = function(details) { - details.tabId = details.tabId.toString(); - var type = details.type; // https://github.com/gorhill/uBlock/issues/1493 diff --git a/platform/firefox/vapi-background.js b/platform/firefox/vapi-background.js index c3579826e..39b46065d 100644 --- a/platform/firefox/vapi-background.js +++ b/platform/firefox/vapi-background.js @@ -1,7 +1,7 @@ /******************************************************************************* uBlock Origin - a browser extension to block requests. - Copyright (C) 2014-2107 The uBlock Origin authors + Copyright (C) 2014-2018 The uBlock Origin authors 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 @@ -833,10 +833,11 @@ var getOwnerWindow = function(target) { /******************************************************************************/ vAPI.isBehindTheSceneTabId = function(tabId) { - return tabId.toString() === '-1'; + return tabId < 0; }; -vAPI.noTabId = '-1'; +vAPI.noTabId = -1; +vAPI.anyTabId = -2; /******************************************************************************/ @@ -1283,7 +1284,7 @@ var tabWatcher = (function() { } var tabId = browserToTabIdMap.get(browser); if ( tabId === undefined ) { - tabId = '' + tabIdGenerator++; + tabId = tabIdGenerator++; browserToTabIdMap.set(browser, tabId); tabIdToBrowserMap.set(tabId, Cu.getWeakReference(browser)); } @@ -1316,7 +1317,7 @@ var tabWatcher = (function() { var removeBrowserEntry = function(tabId, browser) { if ( tabId && tabId !== vAPI.noTabId ) { vAPI.tabs.onClosed(tabId); - delete vAPI.toolbarButton.tabs[tabId]; + vAPI.toolbarButton.tabs.delete(tabId); tabIdToBrowserMap.delete(tabId); } if ( browser ) { @@ -1539,7 +1540,7 @@ vAPI.setIcon = function(tabId, iconStatus, badge) { if ( tabId === undefined ) { tabId = curTabId; } else if ( badge !== undefined ) { - tb.tabs[tabId] = { badge: badge, img: iconStatus === 'on' }; + tb.tabs.set(tabId, { badge: badge, img: iconStatus === 'on' }); } if ( curTabId && tabId === curTabId ) { @@ -2028,7 +2029,7 @@ var httpObserver = { }, // https://github.com/gorhill/uBlock/issues/959 - syntheticPendingRequest: { frameId: 0, parentFrameId: -1, tabId: '', rawtype: 1 }, + syntheticPendingRequest: { frameId: 0, parentFrameId: -1, tabId: 0, rawtype: 1 }, handleRequest: function(channel, URI, details) { var type = this.typeMap[details.rawtype] || 'other'; @@ -2437,7 +2438,7 @@ vAPI.toolbarButton = { viewId: location.host + '-panel', label: vAPI.app.name, tooltiptext: vAPI.app.name, - tabs: {/*tabId: {badge: 0, img: boolean}*/}, + tabs: new Map(/* tabId: { badge: 0, img: boolean } */), init: null, codePath: '' }; @@ -2473,8 +2474,8 @@ vAPI.toolbarButton = { if ( tabId === undefined ) { return label; } - var tabDetails = this.tabs[tabId]; - if ( !tabDetails ) { + var tabDetails = this.tabs.get(tabId); + if ( tabDetails === undefined ) { return label; } if ( !tabDetails.img ) { @@ -2563,7 +2564,7 @@ vAPI.toolbarButton = { return; } - var icon = this.tabs[tabId]; + var icon = this.tabs.get(tabId); button.setAttribute('badge', icon && icon.badge || ''); button.classList.toggle('off', !icon || !icon.img); @@ -3502,9 +3503,8 @@ vAPI.onLoadAllCompleted = function() { // TODO: vAPI shouldn't know about uBlock. Just like in uMatrix, uBlock // should collect on its side all the opened tabs whenever it is ready. var µb = µBlock; - var tabId; for ( var browser of tabWatcher.browsers() ) { - tabId = tabWatcher.tabIdFromTarget(browser); + var tabId = tabWatcher.tabIdFromTarget(browser); µb.tabContextManager.commit(tabId, browser.currentURI.asciiSpec); µb.bindTabToPageStats(tabId); } diff --git a/platform/webext/vapi-webrequest.js b/platform/webext/vapi-webrequest.js index e25b8d164..1b33eed74 100644 --- a/platform/webext/vapi-webrequest.js +++ b/platform/webext/vapi-webrequest.js @@ -1,7 +1,7 @@ /******************************************************************************* uBlock Origin - a browser extension to block requests. - Copyright (C) 2017 Raymond Hill + Copyright (C) 2017-2018 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 @@ -100,10 +100,16 @@ vAPI.net.registerListeners = function() { var punycode = self.punycode; var reAsciiHostname = /^https?:\/\/[0-9a-z_.:@-]+[/?#]/; + var reNetworkURI = /^(?:ftps?|https?|wss?)/; var parsedURL = new URL('about:blank'); var normalizeRequestDetails = function(details) { - details.tabId = details.tabId.toString(); + if ( + details.tabId === vAPI.noTabId && + reNetworkURI.test(details.documentUrl) + ) { + details.tabId = vAPI.anyTabId; + } if ( mustPunycode && !reAsciiHostname.test(details.url) ) { parsedURL.href = details.url; diff --git a/src/js/background.js b/src/js/background.js index e0e444988..485567f3a 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -148,7 +148,7 @@ var µBlock = (function() { // jshint ignore:line selfieAfter: 17 * oneMinute, - pageStores: {}, + pageStores: new Map(), pageStoresToken: 0, storageQuota: vAPI.storage.QUOTA_BYTES, diff --git a/src/js/logger-ui-inspector.js b/src/js/logger-ui-inspector.js index 8b5e3f381..c911a748e 100644 --- a/src/js/logger-ui-inspector.js +++ b/src/js/logger-ui-inspector.js @@ -45,7 +45,7 @@ if ( var logger = self.logger; var inspectorConnectionId; -var inspectedTabId = ''; +var inspectedTabId = 0; var inspectedURL = ''; var inspectedHostname = ''; var inspector = uDom.nodeFromId('domInspector'); @@ -468,7 +468,7 @@ var startDialog = (function() { var onClicked = function(ev) { ev.stopPropagation(); - if ( inspectedTabId === '' ) { return; } + if ( inspectedTabId === 0 ) { return; } var target = ev.target; var parent = target.parentElement; @@ -540,7 +540,7 @@ var onMouseOver = (function() { }; return function(ev) { - if ( inspectedTabId === '' ) { return; } + if ( inspectedTabId === 0 ) { return; } // Convenience: skip real-time highlighting if shift key is pressed. if ( ev.shiftKey ) { return; } // Find closest `li` @@ -560,7 +560,7 @@ var onMouseOver = (function() { /******************************************************************************/ var currentTabId = function() { - if ( showdomButton.classList.contains('active') === false ) { return ''; } + if ( showdomButton.classList.contains('active') === false ) { return 0; } return logger.tabIdFromPageSelector(); }; @@ -568,7 +568,7 @@ var currentTabId = function() { var injectInspector = function() { var tabId = currentTabId(); - if ( tabId === '' ) { return; } + if ( tabId === 0 ) { return; } inspectedTabId = tabId; messaging.send('loggerUI', { what: 'scriptlet', @@ -586,7 +586,7 @@ var shutdownInspector = function() { } logger.removeAllChildren(domTree); inspector.classList.add('vCompact'); - inspectedTabId = ''; + inspectedTabId = 0; }; /******************************************************************************/ @@ -658,7 +658,7 @@ var toggleOff = function() { uDom.nodeFromSelector('#domInspector .permatoolbar .highlightMode').removeEventListener('click', toggleHighlightMode); uDom.nodeFromSelector('#domInspector .permatoolbar .revert').removeEventListener('click', revert); uDom.nodeFromSelector('#domInspector .permatoolbar .commit').removeEventListener('click', startDialog); - inspectedTabId = ''; + inspectedTabId = 0; }; /******************************************************************************/ diff --git a/src/js/logger-ui.js b/src/js/logger-ui.js index 9690cafec..a25eef3b3 100644 --- a/src/js/logger-ui.js +++ b/src/js/logger-ui.js @@ -56,7 +56,7 @@ var tabIdFromPageSelector = logger.tabIdFromPageSelector = function() { if ( tabClass === 'tab_active' && activeTabId !== undefined ) { return activeTabId; } - return /^tab_\d+$/.test(tabClass) ? tabClass.slice(4) : ''; + return /^tab_\d+$/.test(tabClass) ? parseInt(tabClass.slice(4), 10) : 0; }; /******************************************************************************/ @@ -79,8 +79,7 @@ var tdJunkyard = []; var firstVarDataCol = 2; // currently, column 2 (0-based index) var lastVarDataIndex = 4; // currently, d0-d3 var maxEntries = 5000; -var noTabId = ''; -var allTabIds = {}; +var allTabIds = new Map(); var allTabIdsToken; var hiddenTemplate = document.querySelector('#hiddenTemplate > span'); var reRFC3986 = /^([^:\/?#]+:)?(\/\/[^\/?#]*)?([^?#]*)(\?[^#]*)?(#.*)?/; @@ -113,7 +112,7 @@ var staticFilterTypes = { /******************************************************************************/ var classNameFromTabId = function(tabId) { - if ( tabId === noTabId ) { + if ( tabId < 0 ) { return 'tab_bts'; } if ( tabId !== '' ) { @@ -353,7 +352,7 @@ var renderLogEntry = function(entry) { if ( entry.tab ) { tr.classList.add('tab'); tr.classList.add(classNameFromTabId(entry.tab)); - if ( entry.tab === noTabId ) { + if ( entry.tab < 0 ) { tr.cells[1].appendChild(createHiddenTextNode('bts')); } } @@ -392,7 +391,7 @@ var renderLogEntries = function(response) { // 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.hasOwnProperty(entry.tab) === false ) { + if ( entry.tab && tabIds.has(entry.tab) === false ) { tr.classList.remove('canMtx'); } } @@ -422,9 +421,8 @@ var synchronizeTabIds = function(newTabIds) { var oldTabIds = allTabIds; var autoDeleteVoidRows = selectValue === 'tab_active'; var rowVoided = false; - for ( var tabId in oldTabIds ) { - if ( oldTabIds.hasOwnProperty(tabId) === false ) { continue; } - if ( newTabIds.hasOwnProperty(tabId) ) { continue; } + for ( var tabId of oldTabIds.keys() ) { + if ( newTabIds.has(tabId) ) { continue; } // Mark or remove voided rows var trs = uDom('.tab_' + tabId); if ( autoDeleteVoidRows ) { @@ -439,20 +437,20 @@ var synchronizeTabIds = function(newTabIds) { } } - var tabIds = Object.keys(newTabIds).sort(function(a, b) { - return newTabIds[a].localeCompare(newTabIds[b]); + var 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]; - if ( tabId === noTabId ) { continue; } + if ( tabId < 0 ) { continue; } option = select.options[j]; if ( !option ) { option = document.createElement('option'); select.appendChild(option); } // Truncate too long labels. - option.textContent = newTabIds[tabId].slice(0, 80); + option.textContent = newTabIds.get(tabId).slice(0, 80); option.value = classNameFromTabId(tabId); if ( option.value === selectValue ) { select.selectedIndex = j; @@ -500,14 +498,15 @@ var onLogBufferRead = function(response) { return; } - // This tells us the behind-the-scene tab id - noTabId = response.noTabId; - // Tab id of currently active tab if ( response.activeTabId ) { activeTabId = response.activeTabId; } + if ( Array.isArray(response.tabIds) ) { + response.tabIds = new Map(response.tabIds); + } + // This may have changed meanwhile if ( response.maxEntries !== maxEntries ) { maxEntries = response.maxEntries; @@ -625,7 +624,7 @@ var pageSelectorFromURLHash = (function() { var reloadTab = function(ev) { var tabId = tabIdFromPageSelector(); - if ( tabId === '' ) { return; } + if ( tabId === 0 ) { return; } messaging.send('loggerUI', { what: 'reloadTab', tabId: tabId, @@ -965,7 +964,7 @@ var netFilteringManager = (function() { // First, whether picker can be used dialog.querySelector('.picker').classList.toggle( 'hide', - targetTabId === noTabId || + targetTabId < 0 || targetType !== 'image' || /(?:^| )[dlsu]b(?: |$)/.test(targetRow.className) ); @@ -1614,7 +1613,7 @@ var popupManager = (function() { return; } if ( localTabId === 'bts' ) { - realTabId = noTabId; + realTabId = -1; } container = uDom.nodeFromId('popupContainer'); diff --git a/src/js/messaging.js b/src/js/messaging.js index 7e9ee7f10..8cefbfc40 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -1022,28 +1022,26 @@ vAPI.messaging.listen('dashboard', onMessage); /******************************************************************************/ var µb = µBlock, - extensionPageURL = vAPI.getURL(''); + extensionOriginURL = vAPI.getURL(''); /******************************************************************************/ var getLoggerData = function(ownerId, activeTabId, callback) { - var tabIds = {}; - for ( var tabId in µb.pageStores ) { - var pageStore = µb.pageStoreFromTabId(tabId); - if ( pageStore === null ) { continue; } - if ( pageStore.rawURL.startsWith(extensionPageURL) ) { continue; } - tabIds[tabId] = pageStore.title; + var tabIds = new Map(); + for ( var entry of µb.pageStores ) { + var pageStore = entry[1]; + if ( pageStore.rawURL.startsWith(extensionOriginURL) ) { continue; } + tabIds.set(entry[0], pageStore.title); } - if ( activeTabId && tabIds.hasOwnProperty(activeTabId) === false ) { + if ( activeTabId && tabIds.has(activeTabId) === false ) { activeTabId = undefined; } callback({ colorBlind: µb.userSettings.colorBlindFriendly, entries: µb.logger.readAll(ownerId), maxEntries: µb.userSettings.requestLogMaxEntries, - noTabId: vAPI.noTabId, activeTabId: activeTabId, - tabIds: tabIds, + tabIds: Array.from(tabIds), tabIdsToken: µb.pageStoresToken }); }; @@ -1093,11 +1091,7 @@ var onMessage = function(request, sender, callback) { return; } vAPI.tabs.get(null, function(tab) { - getLoggerData( - request.ownerId, - tab && tab.id.toString(), - callback - ); + getLoggerData(request.ownerId, tab && tab.id, callback); }); return; diff --git a/src/js/pagestore.js b/src/js/pagestore.js index b0b3c5abb..c1437d409 100644 --- a/src/js/pagestore.js +++ b/src/js/pagestore.js @@ -1,7 +1,7 @@ /******************************************************************************* uBlock Origin - a browser extension to block requests. - Copyright (C) 2014-2017 Raymond Hill + Copyright (C) 2014-2018 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 @@ -284,7 +284,7 @@ PageStore.prototype.init = function(tabId, context) { this.rawURL = tabContext.rawURL; this.hostnameToCountMap = new Map(); this.contentLastModified = 0; - this.frames = Object.create(null); + this.frames = new Map(); this.logData = undefined; this.perLoadBlockedRequestCount = 0; this.perLoadAllowedRequestCount = 0; @@ -410,27 +410,26 @@ PageStore.prototype.dispose = function() { /******************************************************************************/ PageStore.prototype.disposeFrameStores = function() { - var frames = this.frames; - for ( var k in frames ) { - frames[k].dispose(); + for ( var frameStore of this.frames.values() ) { + frameStore.dispose(); } - this.frames = Object.create(null); + this.frames.clear(); }; /******************************************************************************/ PageStore.prototype.getFrame = function(frameId) { - return this.frames[frameId] || null; + return this.frames.get(frameId) || null; }; /******************************************************************************/ PageStore.prototype.setFrame = function(frameId, frameURL) { - var frameStore = this.frames[frameId]; - if ( frameStore ) { + var frameStore = this.frames.get(frameId); + if ( frameStore !== undefined ) { frameStore.init(frameURL); } else { - this.frames[frameId] = FrameStore.factory(frameURL); + this.frames.set(frameId, FrameStore.factory(frameURL)); } }; @@ -445,8 +444,8 @@ PageStore.prototype.createContextFromPage = function() { PageStore.prototype.createContextFromFrameId = function(frameId) { var context = µb.tabContextManager.createContext(this.tabId); - var frameStore = this.frames[frameId]; - if ( frameStore ) { + var frameStore = this.frames.get(frameId); + if ( frameStore !== undefined ) { context.pageHostname = frameStore.pageHostname; context.pageDomain = frameStore.pageDomain; } else { diff --git a/src/js/tab.js b/src/js/tab.js index 30a671613..23f0758e2 100644 --- a/src/js/tab.js +++ b/src/js/tab.js @@ -1,7 +1,7 @@ /******************************************************************************* uBlock Origin - a browser extension to block requests. - Copyright (C) 2014-2017 Raymond Hill + Copyright (C) 2014-2018 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 @@ -128,7 +128,7 @@ housekeep itself. */ µb.tabContextManager = (function() { - var tabContexts = Object.create(null); + var tabContexts = new Map(); // https://github.com/chrisaljoudi/uBlock/issues/1001 // This is to be used as last-resort fallback in case a tab is found to not @@ -206,7 +206,7 @@ housekeep itself. }; var TabContext = function(tabId) { - this.tabId = tabId.toString(); + this.tabId = tabId; this.stack = []; this.rawURL = this.normalURL = @@ -218,18 +218,16 @@ housekeep itself. this.netFiltering = true; this.netFilteringReadTime = 0; - tabContexts[tabId] = this; + tabContexts.set(tabId, this); }; TabContext.prototype.destroy = function() { - if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { - return; - } + if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { return; } if ( this.gcTimer !== null ) { clearTimeout(this.gcTimer); this.gcTimer = null; } - delete tabContexts[this.tabId]; + tabContexts.delete(this.tabId); }; TabContext.prototype.onTab = function(tab) { @@ -363,7 +361,7 @@ housekeep itself. // These are to be used for the API of the tab context manager. var push = function(tabId, url) { - var entry = tabContexts[tabId]; + var entry = tabContexts.get(tabId); if ( entry === undefined ) { entry = new TabContext(tabId); entry.autodestroy(); @@ -376,13 +374,13 @@ housekeep itself. // Find a tab context for a specific tab. var lookup = function(tabId) { - return tabContexts[tabId] || null; + return tabContexts.get(tabId) || null; }; // Find a tab context for a specific tab. If none is found, attempt to // fix this. When all fail, the behind-the-scene context is returned. var mustLookup = function(tabId) { - var entry = tabContexts[tabId]; + var entry = tabContexts.get(tabId); if ( entry !== undefined ) { return entry; } @@ -407,13 +405,13 @@ housekeep itself. // about to fall through the cracks. // Example: Chromium + case #12 at // http://raymondhill.net/ublock/popup.html - return tabContexts[vAPI.noTabId]; + return tabContexts.get(vAPI.noTabId); }; // https://github.com/gorhill/uBlock/issues/1735 // Filter for popups if actually committing. var commit = function(tabId, url) { - var entry = tabContexts[tabId]; + var entry = tabContexts.get(tabId); if ( entry === undefined ) { entry = push(tabId, url); } else if ( entry.commit(url) ) { @@ -423,7 +421,7 @@ housekeep itself. }; var exists = function(tabId) { - return tabContexts[tabId] !== undefined; + return tabContexts.get(tabId) !== undefined; }; // Behind-the-scene tab context @@ -434,6 +432,9 @@ housekeep itself. entry.normalURL = µb.normalizePageURL(entry.tabId); entry.rootHostname = µb.URI.hostnameFromURI(entry.normalURL); 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. @@ -445,7 +446,7 @@ housekeep itself. var tabContext = lookup(tabId); this.rootHostname = tabContext.rootHostname; this.rootDomain = tabContext.rootDomain; - this.pageHostname = + this.pageHostname = this.pageDomain = this.requestURL = this.requestHostname = @@ -842,13 +843,15 @@ vAPI.tabs.registerListeners(); } // Reuse page store if one exists: this allows to guess if a tab is a popup - var pageStore = this.pageStores[tabId]; + var pageStore = this.pageStores.get(tabId); // Tab is not bound - if ( !pageStore ) { + if ( pageStore === undefined ) { this.updateTitle(tabId); this.pageStoresToken = Date.now(); - return (this.pageStores[tabId] = this.PageStore.factory(tabId, context)); + pageStore = this.PageStore.factory(tabId, context); + this.pageStores.set(tabId, pageStore); + return pageStore; } // https://github.com/chrisaljoudi/uBlock/issues/516 @@ -878,10 +881,10 @@ vAPI.tabs.registerListeners(); µb.unbindTabFromPageStats = function(tabId) { //console.debug('µBlock> unbindTabFromPageStats(%d)', tabId); - var pageStore = this.pageStores[tabId]; + var pageStore = this.pageStores.get(tabId); if ( pageStore !== undefined ) { pageStore.dispose(); - delete this.pageStores[tabId]; + this.pageStores.delete(tabId); this.pageStoresToken = Date.now(); } }; @@ -889,29 +892,36 @@ vAPI.tabs.registerListeners(); /******************************************************************************/ µb.pageStoreFromTabId = function(tabId) { - return this.pageStores[tabId] || null; + return this.pageStores.get(tabId) || null; }; µb.mustPageStoreFromTabId = function(tabId) { - return this.pageStores[tabId] || this.pageStores[vAPI.noTabId]; + return this.pageStores.get(tabId) || this.pageStores.get(vAPI.noTabId); }; /******************************************************************************/ // Permanent page store for behind-the-scene requests. Must never be removed. -µb.pageStores[vAPI.noTabId] = µb.PageStore.factory(vAPI.noTabId); -µb.pageStores[vAPI.noTabId].title = vAPI.i18n('logBehindTheScene'); +(function() { + var 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]'; +})(); /******************************************************************************/ // Update visual of extension icon. µb.updateBadgeAsync = (function() { - var tabIdToTimer = Object.create(null); + var tabIdToTimer = new Map(); var updateBadge = function(tabId) { - delete tabIdToTimer[tabId]; + tabIdToTimer.delete(tabId); var state = false; var badge = ''; @@ -928,82 +938,70 @@ vAPI.tabs.registerListeners(); }; return function(tabId) { - if ( tabIdToTimer[tabId] ) { - return; - } - if ( vAPI.isBehindTheSceneTabId(tabId) ) { - return; - } - tabIdToTimer[tabId] = vAPI.setTimeout(updateBadge.bind(this, tabId), 701); + if ( tabIdToTimer.has(tabId) ) { return; } + if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; } + tabIdToTimer.set( + tabId, + vAPI.setTimeout(updateBadge.bind(this, tabId), 701) + ); }; })(); /******************************************************************************/ µb.updateTitle = (function() { - var tabIdToTimer = Object.create(null); - var tabIdToTryCount = Object.create(null); + var tabIdToTimer = new Map(); var delay = 499; - var tryNoMore = function(tabId) { - delete tabIdToTryCount[tabId]; - }; - - var tryAgain = function(tabId) { - var count = tabIdToTryCount[tabId]; - if ( count === undefined ) { - return false; - } - if ( count === 1 ) { - delete tabIdToTryCount[tabId]; - return false; - } - tabIdToTryCount[tabId] = count - 1; - tabIdToTimer[tabId] = vAPI.setTimeout(updateTitle.bind(µb, tabId), delay); + var tryAgain = function(entry) { + if ( entry.count === 1 ) { return false; } + entry.count -= 1; + tabIdToTimer.set( + entry.tabId, + vAPI.setTimeout(updateTitle.bind(null, entry), delay) + ); return true; }; - var onTabReady = function(tabId, tab) { - if ( !tab ) { - return tryNoMore(tabId); - } - var pageStore = this.pageStoreFromTabId(tabId); - if ( pageStore === null ) { - return tryNoMore(tabId); - } + var onTabReady = function(entry, tab) { + if ( !tab ) { return; } + var µb = µBlock; + var pageStore = µb.pageStoreFromTabId(entry.tabId); + if ( pageStore === null ) { return; } // Firefox needs this: if you detach a tab, the new tab won't have // its rawURL set. Concretely, this causes the logger to report an // entry to itself in the logger's tab selector. // TODO: Investigate for a fix vAPI-side. pageStore.rawURL = tab.url; - this.pageStoresToken = Date.now(); - if ( !tab.title && tryAgain(tabId) ) { - return; - } + µb.pageStoresToken = Date.now(); + if ( !tab.title && tryAgain(entry) ) { return; } // https://github.com/gorhill/uMatrix/issues/225 // Sometimes title changes while page is loading. var settled = tab.title && tab.title === pageStore.title; pageStore.title = tab.title || tab.url || ''; - this.pageStoresToken = Date.now(); - if ( settled || !tryAgain(tabId) ) { - tryNoMore(tabId); + if ( !settled ) { + tryAgain(entry); } }; - var updateTitle = function(tabId) { - delete tabIdToTimer[tabId]; - vAPI.tabs.get(tabId, onTabReady.bind(this, tabId)); + var updateTitle = function(entry) { + tabIdToTimer.delete(entry.tabId); + vAPI.tabs.get(entry.tabId, onTabReady.bind(null, entry)); }; return function(tabId) { - if ( vAPI.isBehindTheSceneTabId(tabId) ) { - return; + if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; } + var timer = tabIdToTimer.get(tabId); + if ( timer !== undefined ) { + clearTimeout(timer); } - if ( tabIdToTimer[tabId] ) { - clearTimeout(tabIdToTimer[tabId]); - } - tabIdToTimer[tabId] = vAPI.setTimeout(updateTitle.bind(this, tabId), delay); - tabIdToTryCount[tabId] = 5; + tabIdToTimer.set( + tabId, + vAPI.setTimeout( + updateTitle.bind(null, { tabId: tabId, count: 5 }), + delay + ) + ); }; })(); @@ -1018,11 +1016,10 @@ var pageStoreJanitorSampleSize = 10; var pageStoreJanitor = function() { var vapiTabs = vAPI.tabs; - var tabIds = Object.keys(µb.pageStores).sort(); + var tabIds = Array.from(µb.pageStores.keys()).sort(); var checkTab = function(tabId) { vapiTabs.get(tabId, function(tab) { if ( !tab ) { - //console.error('tab.js> pageStoreJanitor(): stale page store found:', µtabId); µb.unbindTabFromPageStats(tabId); } }); @@ -1030,14 +1027,10 @@ var pageStoreJanitor = function() { if ( pageStoreJanitorSampleAt >= tabIds.length ) { pageStoreJanitorSampleAt = 0; } - var tabId; var n = Math.min(pageStoreJanitorSampleAt + pageStoreJanitorSampleSize, tabIds.length); for ( var i = pageStoreJanitorSampleAt; i < n; i++ ) { - tabId = tabIds[i]; - // Do not remove behind-the-scene page store - if ( vAPI.isBehindTheSceneTabId(tabId) ) { - continue; - } + var tabId = tabIds[i]; + if ( vAPI.isBehindTheSceneTabId(tabId) ) { continue; } checkTab(tabId); } pageStoreJanitorSampleAt = n; diff --git a/src/js/traffic.js b/src/js/traffic.js index 2e277c5d6..3d4bf0ba3 100644 --- a/src/js/traffic.js +++ b/src/js/traffic.js @@ -336,8 +336,8 @@ var toBlockDocResult = function(url, hostname, logData) { var onBeforeBehindTheSceneRequest = function(details) { var µb = µBlock, - pageStore = µb.pageStoreFromTabId(vAPI.noTabId); - if ( !pageStore ) { return; } + pageStore = µb.pageStoreFromTabId(details.tabId); + if ( pageStore === null ) { return; } var result = 0, context = pageStore.createContextFromPage(), @@ -348,6 +348,13 @@ var onBeforeBehindTheSceneRequest = function(details) { context.requestHostname = µb.URI.hostnameFromURI(requestURL); context.requestType = requestType; + if ( details.tabId === vAPI.anyTabId && context.pageHostname === '' ) { + context.pageHostname = µb.URI.hostnameFromURI(details.documentUrl); + context.pageDomain = µb.URI.domainFromHostname(context.pageHostname); + context.rootHostname = context.pageHostname; + context.rootDomain = context.pageDomain; + } + // 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 @@ -361,7 +368,11 @@ var onBeforeBehindTheSceneRequest = function(details) { // https://github.com/gorhill/uBlock/issues/3150 // Ability to globally block CSP reports MUST also apply to // behind-the-scene network requests. - if ( µb.userSettings.advancedUserEnabled || requestType === 'csp_report' ) { + if ( + details.tabId !== vAPI.noTabId || + µb.userSettings.advancedUserEnabled || + requestType === 'csp_report' + ) { result = pageStore.filterRequest(context); } @@ -369,7 +380,7 @@ var onBeforeBehindTheSceneRequest = function(details) { if ( µb.logger.isEnabled() ) { µb.logger.writeOne( - vAPI.noTabId, + details.tabId, 'net', pageStore.logData, requestType, diff --git a/src/js/ublock.js b/src/js/ublock.js index 9896a43c6..55a7ebbd3 100644 --- a/src/js/ublock.js +++ b/src/js/ublock.js @@ -1,7 +1,7 @@ /******************************************************************************* uBlock Origin - a browser extension to block requests. - Copyright (C) 2014-2017 Raymond Hill + Copyright (C) 2014-2018 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 @@ -510,20 +510,18 @@ var reInvalidHostname = /[^a-z0-9.\-\[\]:]/, /******************************************************************************/ µBlock.logCosmeticFilters = (function() { - var tabIdToTimerMap = {}; + var tabIdToTimerMap = new Map(); var injectNow = function(tabId) { - delete tabIdToTimerMap[tabId]; + tabIdToTimerMap.delete(tabId); µBlock.scriptlets.injectDeep(tabId, 'cosmetic-logger'); }; var injectAsync = function(tabId) { - if ( tabIdToTimerMap.hasOwnProperty(tabId) ) { - return; - } - tabIdToTimerMap[tabId] = vAPI.setTimeout( - injectNow.bind(null, tabId), - 100 + if ( tabIdToTimerMap.has(tabId) ) { return; } + tabIdToTimerMap.set( + tabId, + vAPI.setTimeout(injectNow.bind(null, tabId), 100) ); };