diff --git a/platform/chromium/vapi-background.js b/platform/chromium/vapi-background.js index 7efbc44b0..21d6c7723 100644 --- a/platform/chromium/vapi-background.js +++ b/platform/chromium/vapi-background.js @@ -61,6 +61,14 @@ vAPI.tabs = {}; /******************************************************************************/ +vAPI.isNoTabId = function(tabId) { + return tabId.toString() === '-1'; +}; + +vAPI.noTabId = '-1'; + +/******************************************************************************/ + vAPI.tabs.registerListeners = function() { if ( typeof this.onNavigation === 'function' ) { chrome.webNavigation.onCommitted.addListener(this.onNavigation); diff --git a/platform/firefox/vapi-background.js b/platform/firefox/vapi-background.js index d257f26dc..7a14cd4fd 100644 --- a/platform/firefox/vapi-background.js +++ b/platform/firefox/vapi-background.js @@ -329,6 +329,14 @@ vAPI.tabs = {}; /******************************************************************************/ +vAPI.isNoTabId = function(tabId) { + return tabId.toString() === '_'; +}; + +vAPI.noTabId = '_'; + +/******************************************************************************/ + vAPI.tabs.registerListeners = function() { // onNavigation and onUpdated handled with tabsProgressListener // onClosed - handled in windowWatcher.onTabClose diff --git a/platform/safari/vapi-background.js b/platform/safari/vapi-background.js index 6769855fa..15b26578e 100644 --- a/platform/safari/vapi-background.js +++ b/platform/safari/vapi-background.js @@ -187,6 +187,14 @@ vAPI.tabs = { /******************************************************************************/ +vAPI.isNoTabId = function(tabId) { + return tabId.toString() === '-1'; +}; + +vAPI.noTabId = '-1'; + +/******************************************************************************/ + vAPI.tabs.registerListeners = function() { var onNavigation = this.onNavigation; diff --git a/src/devtools.html b/src/devtools.html index 09721d152..29288991a 100644 --- a/src/devtools.html +++ b/src/devtools.html @@ -2,7 +2,7 @@ -µBlock — Statistics + diff --git a/src/js/async.js b/src/js/async.js index d5fe4c665..219e5afa1 100644 --- a/src/js/async.js +++ b/src/js/async.js @@ -187,7 +187,7 @@ return asyncJobManager; }; var updateBadgeAsync = function(tabId) { - if ( tabId < 0 ) { + if ( vAPI.isNoTabId(tabId) ) { return; } µb.asyncJobs.add('updateBadge-' + tabId, tabId, updateBadge, 250); diff --git a/src/js/devtools.js b/src/js/devtools.js index 5650813d1..b6f59ad62 100644 --- a/src/js/devtools.js +++ b/src/js/devtools.js @@ -30,7 +30,7 @@ /******************************************************************************/ -var messager = vAPI.messaging.channel('stats.js'); +var messager = vAPI.messaging.channel('devtools.js'); /******************************************************************************/ @@ -74,9 +74,21 @@ var selectPage = function() { var inspector = uDom('#content'); var currentSrc = inspector.attr('src'); var targetSrc = 'devtool-log.html?tabId=' + tabId; - if ( targetSrc !== currentSrc ) { - inspector.attr('src', targetSrc); + if ( targetSrc === currentSrc ) { + return; } + inspector.attr('src', targetSrc); + + // This is useful for when the user force-refresh the page: this will + // prevent a reset to the original request log. + // This is also useful for an outside observer to find out which tab is + // being logged, i.e. the popup menu can initialize itself according to + // what tab is currently being logged. + window.history.pushState( + {}, + '', + window.location.href.replace(/^(.+[\?&])tabId=([^&]+)(.*)$/, '$1tabId=' + tabId + '$3') + ); }; /******************************************************************************/ diff --git a/src/js/messaging.js b/src/js/messaging.js index 2b4efc4e4..ddd8320bc 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -72,7 +72,9 @@ var onMessage = function(request, sender, callback) { break; case 'reloadTab': - vAPI.tabs.reload(request.tabId); + if ( vAPI.isNoTabId(request.tabId) === false ) { + vAPI.tabs.reload(request.tabId); + } break; case 'userSettings': @@ -204,12 +206,35 @@ var getStats = function(tabId) { /******************************************************************************/ +var getTargetTabId = function(tab) { + if ( !tab ) { + return ''; + } + + // If the URL is that of the network request logger, fill the popup with + // the data from the tab being observed by the logger. + // This allows a user to actually modify filtering profile for + // behind-the-scene requests. + if ( tab.url.indexOf(vAPI.getURL('devtools.html')) !== 0 ) { + return tab.id; + } + + // Extract the target tab id from the URL + var matches = tab.url.match(/[\?&]tabId=([^&]+)/); + if ( matches && matches.length === 2 ) { + return matches[1]; + } + return tab.id; +}; + +/******************************************************************************/ + var onMessage = function(request, sender, callback) { // Async switch ( request.what ) { case 'getPopupData': vAPI.tabs.get(null, function(tab) { - var tabId = tab && tab.id; + var tabId = getTargetTabId(tab); callback(getStats(tabId)); }); return; @@ -751,7 +776,7 @@ vAPI.messaging.listen('whitelist.js', onMessage); /******************************************************************************/ /******************************************************************************/ -// stats.js +// devtools.js (function() { @@ -767,25 +792,41 @@ var getPageDetails = function(callback) { var out = {}; var tabIds = Object.keys(µb.pageStores); - var countdown = tabIds.length; - if ( countdown === 0 ) { + // Just in case... I expect there will always be a behind-the-scene page + // store, but just to be safe. + if ( tabIds.length === 0 ) { callback(out); return; } - var onTabDetails = function(tab) { - if ( tab ) { - out[tab.id] = tab.title; - } + var countdown = tabIds.length; + var doCountdown = function() { countdown -= 1; if ( countdown === 0 ) { callback(out); } }; + // Let's not populate the page selector with reference itself + var devtoolsURL = vAPI.getURL('devtools.html'); + var devtoolsURLLen = devtoolsURL.length; + + var onTabDetails = function(tab) { + if ( tab && tab.url.slice(0, devtoolsURLLen) !== devtoolsURL ) { + out[tab.id] = tab.title; + } + doCountdown(); + }; + var i = countdown; while ( i-- ) { - vAPI.tabs.get(tabIds[i], onTabDetails); + // Special case: behind-the-scene virtual tab (does not really exist) + if ( vAPI.isNoTabId(tabIds[i]) ) { + out[vAPI.noTabId] = vAPI.i18n('logBehindTheScene'); + doCountdown(); + } else { + vAPI.tabs.get(tabIds[i], onTabDetails); + } } }; @@ -813,7 +854,7 @@ var onMessage = function(request, sender, callback) { callback(response); }; -vAPI.messaging.listen('stats.js', onMessage); +vAPI.messaging.listen('devtools.js', onMessage); /******************************************************************************/ diff --git a/src/js/mirrors.js b/src/js/mirrors.js index 4f109cd82..d21ece306 100644 --- a/src/js/mirrors.js +++ b/src/js/mirrors.js @@ -65,6 +65,7 @@ var metadata = { }; var hashToContentMap = {}; +var urlKeyPendingMap = {}; var loaded = false; @@ -377,8 +378,14 @@ var cacheAsset = function(url) { if ( metadataExists(urlKey) ) { return; } + // Avoid re-entrancy + if ( urlKeyPendingMap.hasOwnProperty(urlKey) ) { + return; + } + urlKeyPendingMap[urlKey] = true; var onRemoteAssetLoaded = function() { + delete urlKeyPendingMap[urlKey]; this.onload = this.onerror = null; if ( this.status !== 200 ) { return; @@ -410,6 +417,7 @@ var cacheAsset = function(url) { }; var onRemoteAssetError = function() { + delete urlKeyPendingMap[urlKey]; this.onload = this.onerror = null; }; @@ -422,7 +430,11 @@ var cacheAsset = function(url) { /******************************************************************************/ -var toURL = function(url, cache) { +var toURL = function(url, type, cache) { + // Unsupported types + if ( type === 'font' ) { + return ''; + } exports.tryCount += 1; var urlKey = toUrlKey(url); if ( urlKey === '' ) { diff --git a/src/js/tab.js b/src/js/tab.js index 8867dc7a6..5cc3db40c 100644 --- a/src/js/tab.js +++ b/src/js/tab.js @@ -121,12 +121,21 @@ vAPI.tabs.registerListeners(); // hostname. This way, for a specific scheme you can create scope with // rules which will apply only to that scheme. -µb.normalizePageURL = function(pageURL) { +µb.normalizePageURL = function(tabId, pageURL) { + if ( vAPI.isNoTabId(tabId) ) { + return 'http://behind-the-scene/'; + } var uri = this.URI.set(pageURL); - if ( uri.scheme === 'https' || uri.scheme === 'http' ) { + var scheme = uri.scheme; + if ( scheme === 'https' || scheme === 'http' ) { return uri.normalizedURI(); } - return ''; + + if ( uri.hostname !== '' ) { + return 'http://' + scheme + '-' + uri.hostname + uri.path; + } + + return 'http://' + scheme + '-scheme/'; }; /******************************************************************************/ @@ -138,7 +147,7 @@ vAPI.tabs.registerListeners(); // https://github.com/gorhill/httpswitchboard/issues/303 // Normalize page URL - pageURL = this.normalizePageURL(pageURL); + pageURL = this.normalizePageURL(tabId, pageURL); // Do not create a page store for URLs which are of no interests if ( pageURL === '' ) { @@ -194,6 +203,15 @@ vAPI.tabs.registerListeners(); return this.pageStores[tabId]; }; +/******************************************************************************/ + +// Permanent page store for behind-the-scene requests. Must never be removed. + +µb.pageStores[vAPI.noTabId] = µb.PageStore.factory( + vAPI.noTabId, + µb.normalizePageURL(vAPI.noTabId) +); + /******************************************************************************/ /******************************************************************************/ @@ -218,9 +236,15 @@ 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++ ) { - checkTab(tabIds[i]); + tabId = tabIds[i]; + // Do not remove behind-the-scene page store + if ( vAPI.isNoTabId(tabId) ) { + continue; + } + checkTab(tabId); } pageStoreJanitorSampleAt = n; diff --git a/src/js/traffic.js b/src/js/traffic.js index cc77076bf..58f304c4d 100644 --- a/src/js/traffic.js +++ b/src/js/traffic.js @@ -38,11 +38,6 @@ var onBeforeRequest = function(details) { // Do not block behind the scene requests. var tabId = details.tabId; - if ( tabId < 0 ) { - // TODO: logging behind-the-scene requests could be nice.. - return; - } - var µb = µBlock; var requestURL = details.url; var requestType = details.type; @@ -120,7 +115,7 @@ var onBeforeRequest = function(details) { // https://code.google.com/p/chromium/issues/detail?id=387198 // Not all redirects will succeed, until bug above is fixed. - var redirectURL = pageStore.toMirrorURL(requestURL); + var redirectURL = pageStore.toMirrorURL(requestURL, requestType); if ( redirectURL !== '' ) { pageStore.logBuffer.writeOne(requestContext, 'ma:'); diff --git a/src/js/ublock.js b/src/js/ublock.js index a0be4c09d..36d05219f 100644 --- a/src/js/ublock.js +++ b/src/js/ublock.js @@ -288,10 +288,10 @@ var matchWhitelistDirective = function(url, hostname, directive) { return type; } var ext = path.slice(pos) + '.'; - if ( '.css.eot.ttf.otf.svg.woff.woff2.'.indexOf(ext) !== -1 ) { - return 'stylesheet'; + if ( '.eot.ttf.otf.svg.woff.woff2.'.indexOf(ext) !== -1 ) { + return 'font'; } - if ( '.ico.png.gif.jpg.jpeg.'.indexOf(ext) !== -1 ) { + if ( '.ico.'.indexOf(ext) !== -1 ) { return 'image'; } return type;