From 6050ab395988fe585f699ff66eb1666a075bf63f Mon Sep 17 00:00:00 2001 From: gorhill Date: Mon, 14 Jul 2014 11:24:59 -0400 Subject: [PATCH] this fixes #58 --- js/abp-filters.js | 19 +++++++++++-- js/background.js | 1 - js/pagestore.js | 16 +++++++++++ js/tab.js | 41 +++++++++++---------------- js/traffic.js | 72 +++++++++++++++++++++++++++++++++++++++-------- 5 files changed, 108 insertions(+), 41 deletions(-) diff --git a/js/abp-filters.js b/js/abp-filters.js index 3425d20dc..1274aa6d3 100644 --- a/js/abp-filters.js +++ b/js/abp-filters.js @@ -28,6 +28,17 @@ /******************************************************************************/ +// fedcba9876543210 +// | | | +// | | | +// | | | +// | | | +// | | | +// | | | +// | | +---- party [0 - 7] +// | +---- type [0 - 15] +// +---- [BlockAction | AllowAction] + const BlockAction = 0 << 15; const AllowAction = 1 << 15; @@ -74,7 +85,8 @@ var typeNameToTypeValue = { 'script': 5 << 11, 'xmlhttprequest': 6 << 11, 'sub_frame': 7 << 11, - 'other': 8 << 11 + 'other': 8 << 11, + 'popup': 9 << 11 }; // ABP filters: https://adblockplus.org/en/filters @@ -805,7 +817,8 @@ FilterParser.prototype.toNormalizedType = { 'script': 'script', 'xmlhttprequest': 'xmlhttprequest', 'subdocument': 'sub_frame', - 'other': 'other' + 'other': 'other', + 'popup': 'popup' }; /******************************************************************************/ @@ -955,7 +968,7 @@ FilterParser.prototype.parse = function(s) { continue; } if ( opt === 'popup' ) { - this.elemHiding = true; + this.parseOptType('popup', not); break; } this.unsupported = true; diff --git a/js/background.js b/js/background.js index b1d883cb9..1115954fa 100644 --- a/js/background.js +++ b/js/background.js @@ -61,7 +61,6 @@ return { }, pageStores: {}, - pageStoreDump: {}, storageQuota: chrome.storage.local.QUOTA_BYTES, storageUsed: 0, diff --git a/js/pagestore.js b/js/pagestore.js index d28459f08..90822c4ef 100644 --- a/js/pagestore.js +++ b/js/pagestore.js @@ -60,6 +60,22 @@ function PageStore(tabId, pageURL) { PageStore.prototype.init = function(tabId, pageURL) { this.tabId = tabId; + this.previousPageURL = ''; + this.pageURL = pageURL; + this.pageHostname = µb.URI.hostnameFromURI(pageURL); + this.pageDomain = µb.URI.domainFromHostname(this.pageHostname); + this.perLoadBlockedRequestCount = 0; + this.perLoadAllowedRequestCount = 0; + this.blockedRequests = {}; + this.allowedRequests = {}; + this.disposeTime = 0; + return this; +}; + +/******************************************************************************/ + +PageStore.prototype.reuse = function(pageURL) { + this.previousPageURL = this.pageURL; this.pageURL = pageURL; this.pageHostname = µb.URI.hostnameFromURI(pageURL); this.pageDomain = µb.URI.domainFromHostname(this.pageHostname); diff --git a/js/tab.js b/js/tab.js index aedde2bbd..a0e189397 100644 --- a/js/tab.js +++ b/js/tab.js @@ -41,9 +41,15 @@ if ( !tab.url || tab.url === '' ) { return; } - if ( changeInfo.url && µBlock.pageStores[tabId] ) { - µBlock.updateBadgeAsync(tabId); + var µb = µBlock; + if ( !changeInfo.url || !µb.pageStores[tabId] ) { + return; } + // If URL is unsupported scheme, unbind tab + if ( changeInfo.url && changeInfo.url.slice(0, 4) !== 'http' ) { + µb.unbindTabFromPageStats(tabId); + } + µb.updateBadgeAsync(tabId); } chrome.tabs.onUpdated.addListener(onTabUpdated); @@ -92,32 +98,23 @@ µBlock.bindTabToPageStats = function(tabId, pageURL) { this.updateBadgeAsync(tabId); - // First unbind whatever page store is bound to the tab id. - this.unbindTabFromPageStats(tabId); - // https://github.com/gorhill/httpswitchboard/issues/303 - // Normalize to a page-URL. + // Normalize page URL pageURL = this.normalizePageURL(pageURL); - // do not create stats store for urls which are of no interests + // Do not create a page store for URLs which are of no interests if ( pageURL === '' ) { + this.unbindTabFromPageStats(tabId); return null; } - // Bring back existing page store or create new one. + // Reuse page store if one exists: this allows to guess if a tab is + // a popup. var pageStore = this.pageStores[tabId]; - if ( !pageStore ) { - var k = pageURL + ' ' + tabId; - pageStore = this.pageStoreDump[k]; - if ( pageStore ) { - delete this.pageStoreDump[k]; - } - } - - if ( !pageStore ) { + if ( pageStore ) { + pageStore.reuse(pageURL); + } else { pageStore = this.PageStore.factory(tabId, pageURL); - pageStore.perLoadAllowedRequestCount = - pageStore.perLoadBlockedRequestCount = 0; } //console.log('µBlock> bindTabToPageStats(%d, "%s")', tabId, pageURL); @@ -127,12 +124,6 @@ }; µBlock.unbindTabFromPageStats = function(tabId) { - var pageStore = this.pageStores[tabId]; - if ( pageStore ) { - //pageStore.disposeTime = Date.now(); - //this.pageStoreDump[pageStore.pageURL + ' ' + tabId] = pageStore; - //console.log('µBlock> unbindTabFromPageStats(%d)', tabId); - } delete this.pageStores[tabId]; }; diff --git a/js/traffic.js b/js/traffic.js index 5d32b38d1..c449ad47f 100644 --- a/js/traffic.js +++ b/js/traffic.js @@ -29,6 +29,58 @@ /******************************************************************************/ +// Intercept root frame requests. This is where we identify and block popups. + +var onBeforeRootDocumentRequestHandler = function(tabId, details) { + var µb = µBlock; + + // Ignore non-http schemes: I don't think this could ever happened + // because of filters at addListener() time... Will see. + var requestURL = details.url; + if ( requestURL.slice(0, 4) !== 'http' ) { + console.error('onBeforeRootDocumentRequestHandler(): Unexpected scheme!'); + µb.unbindTabFromPageStats(tabId); + return; + } + + // Lookup the page store associated with this tab id. + var pageStore = µb.bindTabToPageStats(tabId, requestURL); + if ( !pageStore ) { + return; + } + + // Heuristic to determine whether we are dealing with a popup: + // - the page store is new (it's not a reused one) + + // Can't be a popup, the tab was in use previously. + if ( pageStore.previousPageURL !== '' ) { + return; + } + + var reason = false; + if ( µb.getNetFilteringSwitch(pageStore.pageHostname) ) { + reason = µb.abpFilters.matchString( + pageStore, + requestURL, + 'popup', + µb.URI.hostnameFromURI(requestURL) + ); + } + + // Not blocked? + if ( reason === false || reason.slice(0, 2) === '@@' ) { + return; + } + + // It is a popup, block and remove the tab. + µb.unbindTabFromPageStats(tabId); + chrome.tabs.remove(tabId); + + return { 'cancel': true }; +}; + +/******************************************************************************/ + // Intercept and filter web requests according to white and black lists. var onBeforeRequestHandler = function(details) { @@ -40,24 +92,22 @@ var onBeforeRequestHandler = function(details) { return; } - var µb = µBlock; + // Special handling for root document. var requestType = details.type; - - // Never block root main doc. if ( requestType === 'main_frame' && details.parentFrameId < 0 ) { - µb.bindTabToPageStats(tabId, details.url); - return; + return onBeforeRootDocumentRequestHandler(tabId, details); } + // Ignore non-http schemes: I don't think this could ever happened + // because of filters at addListener() time... Will see. var requestURL = details.url; - var µburi = µb.URI.set(details.url); - - // Ignore non-http schemes - var requestScheme = µburi.scheme; - if ( requestScheme.indexOf('http') !== 0 ) { + if ( requestURL.slice(0, 4) !== 'http' ) { + console.error('onBeforeRequestHandler(): Unexpected scheme!'); return; } + var µb = µBlock; + var µburi = µb.URI.set(requestURL); var requestHostname = µburi.hostname; var requestPath = µburi.path; @@ -76,9 +126,7 @@ var onBeforeRequestHandler = function(details) { var reason = false; if ( µb.getNetFilteringSwitch(pageStore.pageHostname) ) { - //quickProfiler.start('abpFilters.matchString'); reason = µb.abpFilters.matchString(pageStore, requestURL, requestType, requestHostname); - //quickProfiler.stop(); } // Record what happened. pageStore.recordRequest(requestType, requestURL, reason);