mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-11 17:41:03 +01:00
320 lines
10 KiB
JavaScript
320 lines
10 KiB
JavaScript
/*******************************************************************************
|
|
|
|
µBlock - a browser extension to block requests.
|
|
Copyright (C) 2014 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
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
|
|
|
Home: https://github.com/gorhill/uBlock
|
|
*/
|
|
|
|
/* global vAPI, µBlock */
|
|
|
|
/******************************************************************************/
|
|
/******************************************************************************/
|
|
|
|
(function() {
|
|
|
|
'use strict';
|
|
|
|
/******************************************************************************/
|
|
|
|
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;
|
|
}
|
|
var pageStore = µb.bindTabToPageStats(details.tabId, details.url, 'afterNavigate');
|
|
|
|
// https://github.com/gorhill/uBlock/issues/630
|
|
// The hostname of the bound document must always be present in the
|
|
// mini-matrix. That's the best place I could find for the fix, all other
|
|
// options had bad side-effects or complications.
|
|
// TODO: Eventually, we will have to use an API to check whether a scheme
|
|
// is supported as I suspect we are going to start to see `ws`, `wss`
|
|
// as well soon.
|
|
if ( pageStore && details.url.lastIndexOf('http', 0) === 0 ) {
|
|
pageStore.hostnameToCountMap[pageStore.pageHostname] = 0;
|
|
}
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
// 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;
|
|
}
|
|
if ( !changeInfo.url ) {
|
|
return;
|
|
}
|
|
µb.bindTabToPageStats(tabId, changeInfo.url, 'tabUpdated');
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
vAPI.tabs.onClosed = function(tabId) {
|
|
if ( tabId < 0 ) {
|
|
return;
|
|
}
|
|
µb.unbindTabFromPageStats(tabId);
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
// https://github.com/gorhill/uBlock/issues/297
|
|
|
|
vAPI.tabs.onPopup = function(details) {
|
|
//console.debug('vAPI.tabs.onPopup: details = %o', details);
|
|
|
|
var pageStore = µb.pageStoreFromTabId(details.openerTabId);
|
|
var openerURL = details.openerURL || '';
|
|
|
|
if ( openerURL === '' && pageStore ) {
|
|
openerURL = pageStore.pageURL;
|
|
}
|
|
|
|
if ( openerURL === '' ) {
|
|
return;
|
|
}
|
|
|
|
var µburi = µb.URI;
|
|
var openerHostname = µburi.hostnameFromURI(openerURL);
|
|
var openerDomain = µburi.domainFromHostname(openerHostname);
|
|
|
|
var targetURL = details.targetURL;
|
|
|
|
// If the page URL is that of our "blocked page" URL, extract the URL of
|
|
// the page which was blocked.
|
|
if ( targetURL.lastIndexOf(vAPI.getURL('document-blocked.html'), 0) === 0 ) {
|
|
var matches = /details=([^&]+)/.exec(targetURL);
|
|
if ( matches !== null ) {
|
|
targetURL = JSON.parse(atob(matches[1])).url;
|
|
}
|
|
}
|
|
|
|
var context = {
|
|
pageHostname: openerHostname,
|
|
pageDomain: openerDomain,
|
|
rootHostname: openerHostname,
|
|
rootDomain: openerDomain,
|
|
requestURL: targetURL,
|
|
requestHostname: µb.URI.hostnameFromURI(targetURL),
|
|
requestType: 'popup'
|
|
};
|
|
|
|
var result = '';
|
|
|
|
// Check user switch first
|
|
if ( µb.hnSwitches.evaluateZ('doBlockAllPopups', openerHostname) ) {
|
|
result = 'ub:doBlockAllPopups true';
|
|
}
|
|
|
|
// https://github.com/gorhill/uBlock/issues/323
|
|
// https://github.com/gorhill/uBlock/issues/1142
|
|
// If popup OR opener URL is whitelisted, do not block the popup
|
|
if (
|
|
result === '' &&
|
|
µb.getNetFilteringSwitch(openerURL) &&
|
|
µb.getNetFilteringSwitch(targetURL)
|
|
) {
|
|
result = µb.staticNetFilteringEngine.matchStringExactType(context, targetURL, 'popup');
|
|
}
|
|
|
|
// https://github.com/gorhill/uBlock/issues/91
|
|
if ( pageStore ) {
|
|
pageStore.logRequest(context, result);
|
|
}
|
|
|
|
// Not blocked
|
|
if ( µb.isAllowResult(result) ) {
|
|
return;
|
|
}
|
|
|
|
// Blocked
|
|
|
|
// It is a popup, block and remove the tab.
|
|
µb.unbindTabFromPageStats(details.targetTabId);
|
|
vAPI.tabs.remove(details.targetTabId);
|
|
|
|
return true;
|
|
};
|
|
|
|
vAPI.tabs.registerListeners();
|
|
|
|
/******************************************************************************/
|
|
/******************************************************************************/
|
|
|
|
// https://github.com/gorhill/httpswitchboard/issues/303
|
|
// Some kind of trick going on here:
|
|
// Any scheme other than 'http' and 'https' is remapped into a fake
|
|
// URL which trick the rest of µBlock into being able to process an
|
|
// otherwise unmanageable scheme. µBlock needs web page to have a proper
|
|
// hostname to work properly, so just like the 'chromium-behind-the-scene'
|
|
// fake domain name, we map unknown schemes into a fake '{scheme}-scheme'
|
|
// hostname. This way, for a specific scheme you can create scope with
|
|
// rules which will apply only to that scheme.
|
|
|
|
µb.normalizePageURL = function(tabId, pageURL) {
|
|
if ( vAPI.isNoTabId(tabId) ) {
|
|
return 'http://behind-the-scene/';
|
|
}
|
|
var uri = this.URI.set(pageURL);
|
|
var scheme = uri.scheme;
|
|
if ( scheme === 'https' || scheme === 'http' ) {
|
|
return uri.normalizedURI();
|
|
}
|
|
|
|
var url = 'http://' + scheme + '-scheme/';
|
|
|
|
if ( uri.hostname !== '' ) {
|
|
url += uri.hostname + '/';
|
|
}
|
|
|
|
return url;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
// Create an entry for the tab if it doesn't exist.
|
|
|
|
µb.bindTabToPageStats = function(tabId, pageURL, context) {
|
|
this.updateBadgeAsync(tabId);
|
|
|
|
// https://github.com/gorhill/httpswitchboard/issues/303
|
|
// Normalize page URL
|
|
var normalURL = this.normalizePageURL(tabId, pageURL);
|
|
|
|
// Do not create a page store for URLs which are of no interests
|
|
if ( normalURL === '' ) {
|
|
this.unbindTabFromPageStats(tabId);
|
|
return null;
|
|
}
|
|
|
|
// Reuse page store if one exists: this allows to guess if a tab is a popup
|
|
var pageStore = this.pageStores[tabId];
|
|
|
|
// Tab is not bound
|
|
if ( !pageStore ) {
|
|
return this.pageStores[tabId] = this.PageStore.factory(tabId, pageURL, normalURL);
|
|
}
|
|
|
|
// https://github.com/gorhill/uBlock/issues/516
|
|
// If context if 'beforeRequest', do not rebind
|
|
if ( context === 'beforeRequest' ) {
|
|
return pageStore;
|
|
}
|
|
|
|
// Rebind according to context. We rebind even if the URL did not change,
|
|
// as maybe the tab was force-reloaded, in which case the page stats must
|
|
// be all reset.
|
|
pageStore.reuse(pageURL, normalURL, context);
|
|
|
|
return pageStore;
|
|
};
|
|
|
|
µb.unbindTabFromPageStats = function(tabId) {
|
|
//console.debug('µBlock> unbindTabFromPageStats(%d)', tabId);
|
|
var pageStore = this.pageStores[tabId];
|
|
if ( pageStore !== undefined ) {
|
|
pageStore.dispose();
|
|
delete this.pageStores[tabId];
|
|
}
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
µb.pageUrlFromTabId = function(tabId) {
|
|
var pageStore = this.pageStores[tabId];
|
|
return pageStore ? pageStore.pageURL : '';
|
|
};
|
|
|
|
µb.pageUrlFromPageStats = function(pageStats) {
|
|
if ( pageStats ) {
|
|
return pageStats.pageURL;
|
|
}
|
|
return '';
|
|
};
|
|
|
|
µb.pageStoreFromTabId = function(tabId) {
|
|
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)
|
|
);
|
|
|
|
/******************************************************************************/
|
|
/******************************************************************************/
|
|
|
|
// Stale page store entries janitor
|
|
// https://github.com/gorhill/uBlock/issues/455
|
|
|
|
var pageStoreJanitorPeriod = 15 * 60 * 1000;
|
|
var pageStoreJanitorSampleAt = 0;
|
|
var pageStoreJanitorSampleSize = 10;
|
|
|
|
var pageStoreJanitor = function() {
|
|
var vapiTabs = vAPI.tabs;
|
|
var tabIds = Object.keys(µb.pageStores).sort();
|
|
var checkTab = function(tabId) {
|
|
vapiTabs.get(tabId, function(tab) {
|
|
if ( !tab ) {
|
|
//console.error('tab.js> pageStoreJanitor(): stale page store found:', µb.pageUrlFromTabId(tabId));
|
|
µb.unbindTabFromPageStats(tabId);
|
|
}
|
|
});
|
|
};
|
|
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.isNoTabId(tabId) ) {
|
|
continue;
|
|
}
|
|
checkTab(tabId);
|
|
}
|
|
pageStoreJanitorSampleAt = n;
|
|
|
|
setTimeout(pageStoreJanitor, pageStoreJanitorPeriod);
|
|
};
|
|
|
|
setTimeout(pageStoreJanitor, pageStoreJanitorPeriod);
|
|
|
|
/******************************************************************************/
|
|
/******************************************************************************/
|
|
|
|
})();
|
|
|
|
/******************************************************************************/
|