1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-07-08 12:57:57 +02:00
uBlock/platform/firefox/vapi-background-ext.js
Raymond Hill 925c8d5d0c
Add setting to control suspension on network activity at launch
Related discussion:
- a0a9497b4a (commitcomment-62560291)

The new setting, when disabled (enabled by default), allows a user
to prevent uBO from waiting for all filter lists to be loaded
before allowing network activity at launch. The setting is enabled
by default, meaning uBO waits for all filter lists to be loaded in
memory before unsuspending network activity. Some users may find
this behavior undesirable, hence the new setting.

This gives the option to potentially speed up page load at launch,
at the cost of potentially not properly filtering network requests
as per filter lists/rules.

For platforms not supporting the suspension of network activity,
the setting will merely prevent whatever mechanism exists on the
platform to mitigate improper filtering of network requests at
launch. For example, in Chromium-based browsers, unchecking the
new setting will prevent the browser from re-loading tabs for
which there was network activity while in "suspended" state at
launch.
2021-12-30 09:24:38 -05:00

313 lines
11 KiB
JavaScript

/*******************************************************************************
uBlock Origin - a browser extension to block requests.
Copyright (C) 2017-present 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
*/
// For background page
'use strict';
/******************************************************************************/
import {
domainFromHostname,
hostnameFromNetworkURL,
} from './uri-utils.js';
/******************************************************************************/
(( ) => {
// https://github.com/uBlockOrigin/uBlock-issues/issues/407
if ( vAPI.webextFlavor.soup.has('firefox') === false ) { return; }
// Canonical name-uncloaking feature.
let cnameUncloakEnabled = browser.dns instanceof Object;
let cnameUncloakProxied = false;
// https://github.com/uBlockOrigin/uBlock-issues/issues/911
// We detect here whether network requests are proxied, and if so,
// de-aliasing of hostnames will be disabled to avoid possible
// DNS leaks.
const proxyDetector = function(details) {
if ( details.proxyInfo instanceof Object ) {
cnameUncloakEnabled = false;
proxyDetectorTryCount = 0;
}
if ( proxyDetectorTryCount === 0 ) {
browser.webRequest.onHeadersReceived.removeListener(proxyDetector);
return;
}
proxyDetectorTryCount -= 1;
};
let proxyDetectorTryCount = 0;
// Related issues:
// - https://github.com/gorhill/uBlock/issues/1327
// - https://github.com/uBlockOrigin/uBlock-issues/issues/128
// - https://bugzilla.mozilla.org/show_bug.cgi?id=1503721
// Extend base class to normalize as per platform.
vAPI.Net = class extends vAPI.Net {
constructor() {
super();
this.pendingRequests = [];
this.canUncloakCnames = browser.dns instanceof Object;
this.cnames = new Map([ [ '', '' ] ]);
this.cnameIgnoreList = null;
this.cnameIgnore1stParty = true;
this.cnameIgnoreExceptions = true;
this.cnameIgnoreRootDocument = true;
this.cnameMaxTTL = 120;
this.cnameReplayFullURL = false;
this.cnameFlushTime = Date.now() + this.cnameMaxTTL * 60000;
}
setOptions(options) {
super.setOptions(options);
if ( 'cnameUncloakEnabled' in options ) {
cnameUncloakEnabled =
this.canUncloakCnames &&
options.cnameUncloakEnabled !== false;
}
if ( 'cnameUncloakProxied' in options ) {
cnameUncloakProxied = options.cnameUncloakProxied === true;
}
if ( 'cnameIgnoreList' in options ) {
this.cnameIgnoreList =
this.regexFromStrList(options.cnameIgnoreList);
}
if ( 'cnameIgnore1stParty' in options ) {
this.cnameIgnore1stParty =
options.cnameIgnore1stParty !== false;
}
if ( 'cnameIgnoreExceptions' in options ) {
this.cnameIgnoreExceptions =
options.cnameIgnoreExceptions !== false;
}
if ( 'cnameIgnoreRootDocument' in options ) {
this.cnameIgnoreRootDocument =
options.cnameIgnoreRootDocument !== false;
}
if ( 'cnameMaxTTL' in options ) {
this.cnameMaxTTL = options.cnameMaxTTL || 120;
}
if ( 'cnameReplayFullURL' in options ) {
this.cnameReplayFullURL = options.cnameReplayFullURL === true;
}
this.cnames.clear(); this.cnames.set('', '');
this.cnameFlushTime = Date.now() + this.cnameMaxTTL * 60000;
// https://github.com/uBlockOrigin/uBlock-issues/issues/911
// Install/remove proxy detector.
if ( vAPI.webextFlavor.major < 80 ) {
const wrohr = browser.webRequest.onHeadersReceived;
if ( cnameUncloakEnabled === false || cnameUncloakProxied ) {
if ( wrohr.hasListener(proxyDetector) ) {
wrohr.removeListener(proxyDetector);
}
} else if ( wrohr.hasListener(proxyDetector) === false ) {
wrohr.addListener(
proxyDetector,
{ urls: [ '*://*/*' ] },
[ 'blocking' ]
);
}
proxyDetectorTryCount = 32;
}
}
normalizeDetails(details) {
const type = details.type;
if ( type === 'imageset' ) {
details.type = 'image';
return;
}
// https://github.com/uBlockOrigin/uBlock-issues/issues/345
// Re-categorize an embedded object as a `sub_frame` if its
// content type is that of a HTML document.
if ( type === 'object' && Array.isArray(details.responseHeaders) ) {
for ( const header of details.responseHeaders ) {
if ( header.name.toLowerCase() === 'content-type' ) {
if ( header.value.startsWith('text/html') ) {
details.type = 'sub_frame';
}
break;
}
}
}
}
denormalizeTypes(types) {
if ( types.length === 0 ) {
return Array.from(this.validTypes);
}
const out = new Set();
for ( const type of types ) {
if ( this.validTypes.has(type) ) {
out.add(type);
}
if ( type === 'image' && this.validTypes.has('imageset') ) {
out.add('imageset');
}
if ( type === 'sub_frame' ) {
out.add('object');
}
}
return Array.from(out);
}
canonicalNameFromHostname(hn) {
const cn = this.cnames.get(hn);
if ( cn !== undefined && cn !== '' ) {
return cn;
}
}
processCanonicalName(hn, cn, details) {
const hnBeg = details.url.indexOf(hn);
if ( hnBeg === -1 ) { return; }
const oldURL = details.url;
let newURL = oldURL.slice(0, hnBeg) + cn;
const hnEnd = hnBeg + hn.length;
if ( this.cnameReplayFullURL ) {
newURL += oldURL.slice(hnEnd);
} else {
const pathBeg = oldURL.indexOf('/', hnEnd);
if ( pathBeg !== -1 ) {
newURL += oldURL.slice(hnEnd, pathBeg + 1);
}
}
details.url = newURL;
details.aliasURL = oldURL;
return super.onBeforeSuspendableRequest(details);
}
recordCanonicalName(hn, record) {
if ( (this.cnames.size & 0b111111) === 0 ) {
const now = Date.now();
if ( now >= this.cnameFlushTime ) {
this.cnames.clear(); this.cnames.set('', '');
this.cnameFlushTime = now + this.cnameMaxTTL * 60000;
}
}
let cname =
typeof record.canonicalName === 'string' &&
record.canonicalName !== hn
? record.canonicalName
: '';
if (
cname !== '' &&
this.cnameIgnore1stParty &&
domainFromHostname(cname) === domainFromHostname(hn)
) {
cname = '';
}
if (
cname !== '' &&
this.cnameIgnoreList !== null &&
this.cnameIgnoreList.test(cname)
) {
cname = '';
}
this.cnames.set(hn, cname);
return cname;
}
regexFromStrList(list) {
if (
typeof list !== 'string' ||
list.length === 0 ||
list === 'unset' ||
browser.dns instanceof Object === false
) {
return null;
}
if ( list === '*' ) {
return /^./;
}
return new RegExp(
'(?:^|\.)(?:' +
list.trim()
.split(/\s+/)
.map(a => a.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
.join('|') +
')$'
);
}
onBeforeSuspendableRequest(details) {
const r = super.onBeforeSuspendableRequest(details);
if ( cnameUncloakEnabled === false ) { return r; }
if ( r !== undefined ) {
if (
r.cancel === true ||
r.redirectUrl !== undefined ||
this.cnameIgnoreExceptions
) {
return r;
}
}
if (
details.type === 'main_frame' &&
this.cnameIgnoreRootDocument
) {
return;
}
const hn = hostnameFromNetworkURL(details.url);
const cname = this.cnames.get(hn);
if ( cname === '' ) { return; }
if ( cname !== undefined ) {
return this.processCanonicalName(hn, cname, details);
}
return browser.dns.resolve(hn, [ 'canonical_name' ]).then(
rec => {
const cname = this.recordCanonicalName(hn, rec);
if ( cname === '' ) { return; }
return this.processCanonicalName(hn, cname, details);
},
( ) => {
this.cnames.set(hn, '');
}
);
}
suspendOneRequest(details) {
const pending = {
details: Object.assign({}, details),
resolve: undefined,
promise: undefined
};
pending.promise = new Promise(resolve => {
pending.resolve = resolve;
});
this.pendingRequests.push(pending);
return pending.promise;
}
unsuspendAllRequests(discard = false) {
const pendingRequests = this.pendingRequests;
this.pendingRequests = [];
for ( const entry of pendingRequests ) {
entry.resolve(
discard !== true
? this.onBeforeSuspendableRequest(entry.details)
: undefined
);
}
}
canSuspend() {
return true;
}
};
})();
/******************************************************************************/