diff --git a/platform/chromium/vapi-background.js b/platform/chromium/vapi-background.js index fc5281f0d..791afb7d9 100644 --- a/platform/chromium/vapi-background.js +++ b/platform/chromium/vapi-background.js @@ -71,6 +71,17 @@ vAPI.noTabId = '-1'; /******************************************************************************/ +var onCreatedNavigationTarget = function(details) { + vAPI.tabs.onPopup({ + openerTabId: details.sourceTabId, + openerURL: '', + targetURL: details.url, + targetTabId: details.tabId + }); +}; + +/******************************************************************************/ + vAPI.tabs.registerListeners = function() { if ( typeof this.onNavigation === 'function' ) { chrome.webNavigation.onCommitted.addListener(this.onNavigation); @@ -85,7 +96,7 @@ vAPI.tabs.registerListeners = function() { } if ( typeof this.onPopup === 'function' ) { - chrome.webNavigation.onCreatedNavigationTarget.addListener(this.onPopup); + chrome.webNavigation.onCreatedNavigationTarget.addListener(onCreatedNavigationTarget); } }; @@ -493,15 +504,6 @@ vAPI.net.registerListeners = function() { this.onBeforeRequest.extra ); - chrome.webRequest.onBeforeSendHeaders.addListener( - this.onBeforeSendHeaders.callback, - { - 'urls': this.onBeforeSendHeaders.urls || [''], - 'types': this.onBeforeSendHeaders.types || [] - }, - this.onBeforeSendHeaders.extra - ); - var onHeadersReceivedClient = this.onHeadersReceived.callback; var onHeadersReceived = function(details) { normalizeRequestDetails(details); @@ -515,6 +517,56 @@ vAPI.net.registerListeners = function() { }, this.onHeadersReceived.extra ); + + // Intercept root frame requests. + // This is where we identify and block popups early, whenever possible. + + var onBeforeSendHeaders = function(details) { + // Do not block behind the scene requests. + if ( vAPI.isNoTabId(details.tabId) ) { + return; + } + + // Only root document. + if ( details.parentFrameId !== -1 ) { + return; + } + + var referrer = headerValue(details.requestHeaders, 'referer'); + if ( referrer === '' ) { + return; + } + + var result = vAPI.tabs.onPopup({ + openerTabId: undefined, + openerURL: referrer, + targetTabId: details.tabId, + targetURL: details.url + }); + + if ( result ) { + return { 'cancel': true }; + } + }; + + var headerValue = function(headers, name) { + var i = headers.length; + while ( i-- ) { + if ( headers[i].name.toLowerCase() === name ) { + return headers[i].value; + } + } + return ''; + }; + + chrome.webRequest.onBeforeSendHeaders.addListener( + onBeforeSendHeaders, + { + 'urls': [ 'http://*/*', 'https://*/*' ], + 'types': [ 'main_frame' ] + }, + [ 'blocking', 'requestHeaders' ] + ); }; /******************************************************************************/ diff --git a/platform/firefox/vapi-background.js b/platform/firefox/vapi-background.js index 8940be2b2..5be481baa 100644 --- a/platform/firefox/vapi-background.js +++ b/platform/firefox/vapi-background.js @@ -1118,9 +1118,9 @@ var httpObserver = { } var result = vAPI.tabs.onPopup({ - tabId: tabId, - sourceTabId: sourceTabId, - url: URI.asciiSpec + targetTabId: tabId, + openerTabId: sourceTabId, + targetURL: URI.asciiSpec }); return result === true; diff --git a/platform/safari/vapi-background.js b/platform/safari/vapi-background.js index 6b34892af..036566a56 100644 --- a/platform/safari/vapi-background.js +++ b/platform/safari/vapi-background.js @@ -209,15 +209,15 @@ var url = e.url, tabId = vAPI.tabs.getTabId(e.target); var details = { - url: url, - tabId: tabId, - sourceTabId: vAPI.tabs.popupCandidate + targetURL: url, + targetTabId: tabId, + openerTabId: vAPI.tabs.popupCandidate }; vAPI.tabs.popupCandidate = false; if(vAPI.tabs.onPopup(details)) { e.preventDefault(); - if(vAPI.tabs.stack[details.sourceTabId]) { - vAPI.tabs.stack[details.sourceTabId].activate(); + if(vAPI.tabs.stack[details.openerTabId]) { + vAPI.tabs.stack[details.openerTabId].activate(); } } }, true); @@ -663,9 +663,9 @@ } else { e.message = !vAPI.tabs.onPopup({ - url: e.message.url, - tabId: 0, - sourceTabId: vAPI.tabs.getTabId(e.target) + targetURL: e.message.url, + targetTabId: 0, + openerTabId: vAPI.tabs.getTabId(e.target) }); } break; diff --git a/src/js/tab.js b/src/js/tab.js index b32d4c5d0..13a9c0d7b 100644 --- a/src/js/tab.js +++ b/src/js/tab.js @@ -37,6 +37,7 @@ var µb = µBlock; // When the DOM content of root frame is loaded, this means the tab // content has changed. + vAPI.tabs.onNavigation = function(details) { if ( details.frameId !== 0 ) { return; @@ -55,9 +56,12 @@ vAPI.tabs.onNavigation = function(details) { } }; +/******************************************************************************/ + // It may happen the URL in the tab changes, while the page's document // stays the same (for instance, Google Maps). Without this listener, // the extension icon won't be properly refreshed. + vAPI.tabs.onUpdated = function(tabId, changeInfo, tab) { if ( !tab.url || tab.url === '' ) { return; @@ -68,6 +72,8 @@ vAPI.tabs.onUpdated = function(tabId, changeInfo, tab) { µb.bindTabToPageStats(tabId, changeInfo.url, 'tabUpdated'); }; +/******************************************************************************/ + vAPI.tabs.onClosed = function(tabId) { if ( tabId < 0 ) { return; @@ -75,28 +81,49 @@ vAPI.tabs.onClosed = function(tabId) { µb.unbindTabFromPageStats(tabId); }; +/******************************************************************************/ + // https://github.com/gorhill/uBlock/issues/297 + vAPI.tabs.onPopup = function(details) { //console.debug('vAPI.tabs.onPopup: url="%s"', details.url); - var pageStore = µb.pageStoreFromTabId(details.sourceTabId); - if ( !pageStore ) { + var pageStore = µb.pageStoreFromTabId(details.openerTabId); + var openerURL = details.openerURL || ''; + + if ( openerURL === '' && pageStore ) { + openerURL = pageStore.pageURL; + } + + if ( openerURL === '' ) { return; } - var requestURL = details.url; + + var µburi = µb.URI; + var openerHostname = µburi.hostnameFromURI(openerURL); + var openerDomain = µburi.domainFromHostname(openerHostname); + + var context = { + pageHostname: openerHostname, + pageDomain: openerDomain, + rootHostname: openerHostname, + rootDomain: openerDomain + }; + + var targetURL = details.targetURL; var result = ''; // https://github.com/gorhill/uBlock/issues/323 // If popup URL is whitelisted, do not block it - if ( µb.getNetFilteringSwitch(requestURL) ) { - result = µb.staticNetFilteringEngine.matchStringExactType(pageStore, requestURL, 'popup'); + if ( µb.getNetFilteringSwitch(targetURL) ) { + result = µb.staticNetFilteringEngine.matchStringExactType(context, targetURL, 'popup'); } // https://github.com/gorhill/uBlock/issues/91 - if ( result !== '' ) { + if ( pageStore ) { var context = { - requestURL: requestURL, - requestHostname: µb.URI.hostnameFromURI(requestURL), + requestURL: targetURL, + requestHostname: µb.URI.hostnameFromURI(targetURL), requestType: 'popup' }; pageStore.logRequest(context, result); @@ -110,10 +137,9 @@ vAPI.tabs.onPopup = function(details) { // Blocked // It is a popup, block and remove the tab. - µb.unbindTabFromPageStats(details.tabId); - vAPI.tabs.remove(details.tabId); + µb.unbindTabFromPageStats(details.targetTabId); + vAPI.tabs.remove(details.targetTabId); - // for Safari and Firefox return true; }; diff --git a/src/js/traffic.js b/src/js/traffic.js index da6176280..f6ebd1659 100644 --- a/src/js/traffic.js +++ b/src/js/traffic.js @@ -185,89 +185,6 @@ var onBeforeBehindTheSceneRequest = function(details) { /******************************************************************************/ -// Intercept root frame requests. This is where we identify and block popups. - -var onBeforeSendHeaders = function(details) { - // TODO: I vaguely remember reading that when pre-fetch is enabled, - // the tab id could be -1, despite the request not really being a - // behind-the-scene request. If true, the test below would prevent - // the popup blocker from working. Need to check this. - //console.debug('traffic.js > onBeforeSendHeaders(): "%s" (%o) because "%s"', details.url, details, result); - - // Do not block behind the scene requests. - var tabId = details.tabId; - if ( vAPI.isNoTabId(tabId) ) { - return; - } - - // Only root document. - if ( details.parentFrameId !== -1 ) { - return; - } - - var µb = µBlock; - var requestURL = details.url; - - // Lookup the page store associated with this tab id. - var pageStore = µb.pageStoreFromTabId(tabId); - if ( !pageStore ) { - // This happens under normal circumstances in Opera. - return; - } - - // Heuristic to determine whether we are dealing with a popup: - // - the page store is new (it's not a reused one) - // - the referrer is not nil - - // Can't be a popup, the tab was in use previously. - if ( pageStore.previousPageURL !== '' ) { - return; - } - - var referrer = headerValue(details.requestHeaders, 'referer'); - if ( referrer === '' ) { - return; - } - - // https://github.com/gorhill/uBlock/issues/323 - if ( pageStore.getNetFilteringSwitch() === false ) { - return; - } - - // TODO: I think I should test the switch of the referrer instead, not the - // switch of the popup. If so, that would require being able to lookup - // a page store from a URL. Have to keep in mind the same URL can appear - // in multiple tabs. - - // https://github.com/gorhill/uBlock/issues/67 - // We need to pass the details of the page which opened this popup, - // so that the `third-party` option works. - // Create a synthetic context based on the referrer. - var µburi = µb.URI; - var referrerHostname = µburi.hostnameFromURI(referrer); - var pageDetails = { - pageHostname: referrerHostname, - pageDomain: µburi.domainFromHostname(referrerHostname) - }; - pageDetails.rootHostname = pageDetails.pageHostname; - pageDetails.rootDomain = pageDetails.pageDomain; - //console.debug('traffic.js > Referrer="%s"', referrer); - var result = µb.staticNetFilteringEngine.matchStringExactType(pageDetails, requestURL, 'popup'); - - // Not blocked? - if ( µb.isAllowResult(result) ) { - return; - } - - // It is a popup, block and remove the tab. - µb.unbindTabFromPageStats(tabId); - vAPI.tabs.remove(tabId); - - return { 'cancel': true }; -}; - -/******************************************************************************/ - // To handle `inline-script`. var onHeadersReceived = function(details) { @@ -343,18 +260,6 @@ var onHeadersReceived = function(details) { /******************************************************************************/ -var headerValue = function(headers, name) { - var i = headers.length; - while ( i-- ) { - if ( headers[i].name.toLowerCase() === name ) { - return headers[i].value; - } - } - return ''; -}; - -/******************************************************************************/ - var headerStartsWith = function(headers, prefix) { var i = headers.length; while ( i-- ) { @@ -386,18 +291,6 @@ vAPI.net.onBeforeRequest = { callback: onBeforeRequest }; -vAPI.net.onBeforeSendHeaders = { - urls: [ - 'http://*/*', - 'https://*/*' - ], - types: [ - "main_frame" - ], - extra: [ 'blocking', 'requestHeaders' ], - callback: onBeforeSendHeaders -}; - vAPI.net.onHeadersReceived = { urls: [ 'http://*/*',