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;