diff --git a/platform/chromium/polyfill.js b/platform/chromium/polyfill.js
index ca8d82cd2..d39d2d39c 100644
--- a/platform/chromium/polyfill.js
+++ b/platform/chromium/polyfill.js
@@ -21,6 +21,8 @@
// For background page or non-background pages
+/* exported objectAssign */
+
'use strict';
/******************************************************************************/
@@ -57,6 +59,19 @@ if ( String.prototype.endsWith instanceof Function === false ) {
/******************************************************************************/
+// As per MDN, Object.assign appeared first in Chromium 45.
+// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Browser_compatibility
+
+var objectAssign = Object.assign || function(target, source) {
+ var keys = Object.keys(source);
+ for ( var i = 0, n = keys.length, key; i < n; i++ ) {
+ key = keys[i];
+ target[key] = source[key];
+ }
+};
+
+/******************************************************************************/
+
// https://github.com/gorhill/uBlock/issues/1070
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set#Browser_compatibility
// This polyfill is designed to fulfill *only* what uBlock Origin needs -- this
diff --git a/platform/chromium/vapi-background.js b/platform/chromium/vapi-background.js
index b540cb3c7..573fc0b1d 100644
--- a/platform/chromium/vapi-background.js
+++ b/platform/chromium/vapi-background.js
@@ -1196,7 +1196,9 @@ vAPI.onLoadAllCompleted = function() {
µb.tabContextManager.commit(tab.id, tab.url);
µb.bindTabToPageStats(tab.id);
// https://github.com/chrisaljoudi/uBlock/issues/129
- scriptStart(tab.id);
+ if ( /^https?:\/\//.test(tab.url) ) {
+ scriptStart(tab.id);
+ }
}
};
diff --git a/platform/chromium/vapi-client.js b/platform/chromium/vapi-client.js
index 0dd976fff..78396637b 100644
--- a/platform/chromium/vapi-client.js
+++ b/platform/chromium/vapi-client.js
@@ -468,7 +468,7 @@ vAPI.messaging = {
if ( listeners === undefined ) {
return;
}
- var pos = this.listeners.indexOf(callback);
+ var pos = listeners.indexOf(callback);
if ( pos === -1 ) {
console.error('Listener not found on channel "%s"', channelName);
return;
diff --git a/platform/firefox/polyfill.js b/platform/firefox/polyfill.js
index 3a8c2b6d8..ca2b44056 100644
--- a/platform/firefox/polyfill.js
+++ b/platform/firefox/polyfill.js
@@ -21,8 +21,24 @@
// For background page or non-background pages
+/* exported objectAssign */
+
'use strict';
+/******************************************************************************/
+/******************************************************************************/
+
+// As per MDN, Object.assign appeared first in Firefox 34.
+// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Browser_compatibility
+
+var objectAssign = Object.assign || function(target, source) {
+ var keys = Object.keys(source);
+ for ( var i = 0, n = keys.length, key; i < n; i++ ) {
+ key = keys[i];
+ target[key] = source[key];
+ }
+};
+
/******************************************************************************/
// Patching for Pale Moon which does not implement ES6 Set/Map.
diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json
index a3e944674..c0cb3493d 100644
--- a/src/_locales/en/messages.json
+++ b/src/_locales/en/messages.json
@@ -39,6 +39,10 @@
"message":"About",
"description":"appears as tab name in dashboard"
},
+ "advancedSettingsPageName":{
+ "message":"Advanced settings",
+ "description":"Title for the advanced settings page"
+ },
"popupPowerSwitchInfo":{
"message":"Click: disable\/enable uBlock₀ for this site.\n\nCtrl+click: disable uBlock₀ only on this page.",
"description":"English: Click: disable\/enable uBlock₀ for this site.\n\nCtrl+click: disable uBlock₀ only on this page."
@@ -204,8 +208,12 @@
"description": ""
},
"settingsAdvancedUserPrompt":{
- "message":"I am an advanced user (Required reading<\/a>)",
- "description":"English: "
+ "message":"I am an advanced user (required reading<\/a>)",
+ "description":""
+ },
+ "settingsAdvancedUserSettings":{
+ "message":"advanced settings",
+ "description":"For the tooltip of a link which gives access to advanced settings"
},
"settingsPrefetchingDisabledPrompt":{
"message":"Disable pre-fetching (to prevent any connection for blocked network requests)",
@@ -671,13 +679,21 @@
"message": "This device name:",
"description": "used as a prompt for the user to provide a custom device name"
},
+ "advancedSettingsWarning": {
+ "message": "Warning! Change these advanced settings at your own risk.",
+ "description": "A warning to users at the top of 'Advanced settings' page"
+ },
"genericSubmit": {
"message": "Submit",
- "description": "for generic 'submit' buttons"
+ "description": "for generic 'Submit' buttons"
+ },
+ "genericApplyChanges": {
+ "message": "Apply changes",
+ "description": "for generic 'Apply changes' buttons"
},
"genericRevert": {
"message": "Revert",
- "description": "for generic 'revert' buttons"
+ "description": "for generic 'Revert' buttons"
},
"genericBytes": {
"message": "bytes",
diff --git a/src/advanced-settings.html b/src/advanced-settings.html
new file mode 100644
index 000000000..208f9d882
--- /dev/null
+++ b/src/advanced-settings.html
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/css/advanced-settings.css b/src/css/advanced-settings.css
new file mode 100644
index 000000000..9fb4a985b
--- /dev/null
+++ b/src/css/advanced-settings.css
@@ -0,0 +1,15 @@
+div > p:first-child {
+ margin-top: 0;
+ }
+div > p:last-child {
+ margin-bottom: 0;
+ }
+textarea {
+ box-sizing: border-box;
+ font-size: smaller;
+ height: 60vh;
+ text-align: left;
+ white-space: pre;
+ width: 100%;
+ word-wrap: normal;
+ }
diff --git a/src/css/settings.css b/src/css/settings.css
index 308334f8b..ba77d4510 100644
--- a/src/css/settings.css
+++ b/src/css/settings.css
@@ -18,12 +18,15 @@ ul#userSettings .subgroup > span {
font-size: larger;
font-weight: bold;
}
+#advanced-user-enabled ~ a.fa {
+ display: none;
+ }
+body.advancedUser #advanced-user-enabled ~ a.fa {
+ display: inline;
+ }
#localData > ul > li {
margin-top: 1em;
}
#localData > ul > li > ul > li:nth-of-type(2) {
font-family: monospace;
}
-#experimental-enabled {
- margin-top: 1em;
- }
diff --git a/src/document-suspended.html b/src/document-suspended.html
new file mode 100644
index 000000000..f445eb2ec
--- /dev/null
+++ b/src/document-suspended.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/js/advanced-settings.js b/src/js/advanced-settings.js
new file mode 100644
index 000000000..97421873a
--- /dev/null
+++ b/src/js/advanced-settings.js
@@ -0,0 +1,114 @@
+/*******************************************************************************
+
+ uBlock Origin - a browser extension to block requests.
+ Copyright (C) 2016 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 uDom */
+
+'use strict';
+
+/******************************************************************************/
+
+(function() {
+
+/******************************************************************************/
+
+var messaging = vAPI.messaging;
+var cachedData = '';
+var rawAdvancedSettings = uDom.nodeFromId('advancedSettings');
+
+/******************************************************************************/
+
+var hashFromAdvancedSettings = function(raw) {
+ return raw.trim().replace(/\s+/g, '|');
+};
+
+/******************************************************************************/
+
+// This is to give a visual hint that the content of user blacklist has changed.
+
+var advancedSettingsChanged = (function () {
+ var timer = null;
+
+ var handler = function() {
+ timer = null;
+ var changed = hashFromAdvancedSettings(rawAdvancedSettings.value) !== cachedData;
+ uDom.nodeFromId('advancedSettingsApply').disabled = !changed;
+ };
+
+ return function() {
+ if ( timer !== null ) {
+ clearTimeout(timer);
+ }
+ timer = vAPI.setTimeout(handler, 100);
+ };
+})();
+
+/******************************************************************************/
+
+function renderAdvancedSettings() {
+ var onRead = function(raw) {
+ cachedData = hashFromAdvancedSettings(raw);
+ var pretty = [],
+ whitespaces = ' ',
+ lines = raw.split('\n'),
+ max = 0,
+ pos,
+ i, n = lines.length;
+ for ( i = 0; i < n; i++ ) {
+ pos = lines[i].indexOf(' ');
+ if ( pos > max ) {
+ max = pos;
+ }
+ }
+ for ( i = 0; i < n; i++ ) {
+ pos = lines[i].indexOf(' ');
+ pretty.push(whitespaces.slice(0, max - pos) + lines[i]);
+ }
+ rawAdvancedSettings.value = pretty.join('\n') + '\n';
+ advancedSettingsChanged();
+ rawAdvancedSettings.focus();
+ };
+ messaging.send('dashboard', { what: 'readHiddenSettings' }, onRead);
+}
+
+/******************************************************************************/
+
+var applyChanges = function() {
+ messaging.send(
+ 'dashboard',
+ {
+ what: 'writeHiddenSettings',
+ content: rawAdvancedSettings.value
+ },
+ renderAdvancedSettings
+ );
+};
+
+/******************************************************************************/
+
+// Handle user interaction
+uDom('#advancedSettings').on('input', advancedSettingsChanged);
+uDom('#advancedSettingsApply').on('click', applyChanges);
+
+renderAdvancedSettings();
+
+/******************************************************************************/
+
+})();
diff --git a/src/js/background.js b/src/js/background.js
index 454630cc2..a79b6f5ba 100644
--- a/src/js/background.js
+++ b/src/js/background.js
@@ -70,6 +70,14 @@ return {
webrtcIPAddressHidden: false
},
+ hiddenSettingsDefault: {
+ ignoreRedirectFilters: false,
+ ignoreScriptInjectFilters: false,
+ suspendTabsUntilReady: false
+ },
+ // This will be filled ASAP:
+ hiddenSettings: {},
+
// Features detection.
privacySettingsSupported: vAPI.browserSettings instanceof Object,
cloudStorageSupported: vAPI.cloud instanceof Object,
diff --git a/src/js/cosmetic-filtering.js b/src/js/cosmetic-filtering.js
index 5e6f8842c..0660e0ecc 100644
--- a/src/js/cosmetic-filtering.js
+++ b/src/js/cosmetic-filtering.js
@@ -1344,6 +1344,7 @@ FilterContainer.prototype.createUserScriptRule = function(hash, hostname, select
FilterContainer.prototype.retrieveUserScripts = function(domain, hostname) {
if ( this.userScriptCount === 0 ) { return; }
+ if ( µb.hiddenSettings.ignoreScriptInjectFilters === true ) { return; }
var reng = µb.redirectEngine;
if ( !reng ) { return; }
diff --git a/src/js/dashboard.js b/src/js/dashboard.js
index 3ec295cf1..b3cdee468 100644
--- a/src/js/dashboard.js
+++ b/src/js/dashboard.js
@@ -1,7 +1,7 @@
/*******************************************************************************
- µBlock - a browser extension to block requests.
- Copyright (C) 2014 Raymond Hill
+ uBlock Origin - a browser extension to block requests.
+ Copyright (C) 2014-2016 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
@@ -21,12 +21,12 @@
/* global uDom */
+'use strict';
+
/******************************************************************************/
(function() {
-'use strict';
-
/******************************************************************************/
var resizeFrame = function() {
diff --git a/src/js/document-suspended.js b/src/js/document-suspended.js
new file mode 100644
index 000000000..29a979391
--- /dev/null
+++ b/src/js/document-suspended.js
@@ -0,0 +1,46 @@
+/*******************************************************************************
+
+ uBlock Origin - a browser extension to block requests.
+ Copyright (C) 2016 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
+*/
+
+'use strict';
+
+/******************************************************************************/
+
+(function() {
+ var matches = /url=([^&]+)/.exec(window.location.search);
+ if ( matches === null ) { return; }
+
+ var onMessage = function(msg) {
+ if ( msg.what !== 'ublockOrigin-readyState-complete' ) {
+ return;
+ }
+ vAPI.messaging.removeChannelListener('document-suspended.js', onMessage);
+ window.location.replace(document.querySelector('body > a').href);
+ };
+
+ var link = document.querySelector('body > a'),
+ url = decodeURIComponent(matches[1]);
+ link.setAttribute('href', url);
+ link.appendChild(document.createTextNode(url));
+
+ vAPI.messaging.addChannelListener('document-suspended.js', onMessage);
+})();
+
+/******************************************************************************/
diff --git a/src/js/messaging.js b/src/js/messaging.js
index be1a23ff9..625dc183d 100644
--- a/src/js/messaging.js
+++ b/src/js/messaging.js
@@ -750,6 +750,7 @@ var backupUserData = function(callback) {
version: vAPI.app.version,
userSettings: µb.userSettings,
filterLists: {},
+ hiddenSettingsString: µb.stringFromHiddenSettings(),
netWhitelist: µb.stringFromWhitelist(µb.netWhitelist),
dynamicFilteringString: µb.permanentFirewall.toString(),
urlFilteringString: µb.permanentURLFiltering.toString(),
@@ -800,13 +801,9 @@ var restoreUserData = function(request) {
µBlock.saveLocalSettings();
vAPI.storage.set(userData.userSettings, onCountdown);
µb.keyvalSetOne('remoteBlacklists', userData.filterLists, onCountdown);
+ µb.hiddenSettingsFromString(userData.hiddenSettingsString || '');
µb.keyvalSetOne('netWhitelist', userData.netWhitelist || '', onCountdown);
-
- // With versions 0.9.2.4-, dynamic rules were saved within the
- // `userSettings` object. No longer the case.
- var s = userData.dynamicFilteringString || userData.userSettings.dynamicFilteringString || '';
- µb.keyvalSetOne('dynamicFilteringString', s, onCountdown);
-
+ µb.keyvalSetOne('dynamicFilteringString', userData.dynamicFilteringString || '', onCountdown);
µb.keyvalSetOne('urlFilteringString', userData.urlFilteringString || '', onCountdown);
µb.keyvalSetOne('hostnameSwitchesString', userData.hostnameSwitchesString || '', onCountdown);
µb.assets.put(µb.userFiltersPath, userData.userFilters, onCountdown);
@@ -831,6 +828,7 @@ var restoreUserData = function(request) {
var resetUserData = function() {
vAPI.cacheStorage.clear();
vAPI.storage.clear();
+ vAPI.localStorage.removeItem('hiddenSettings');
// Keep global counts, people can become quite attached to numbers
µb.saveLocalSettings();
@@ -975,6 +973,10 @@ var onMessage = function(request, sender, callback) {
µb.assets.purgeCacheableAsset(request.path);
break;
+ case 'readHiddenSettings':
+ response = µb.stringFromHiddenSettings();
+ break;
+
case 'restoreUserData':
restoreUserData(request);
break;
@@ -1005,6 +1007,10 @@ var onMessage = function(request, sender, callback) {
response = getRules();
break;
+ case 'writeHiddenSettings':
+ µb.hiddenSettingsFromString(request.content);
+ break;
+
default:
return vAPI.messaging.UNHANDLED;
}
diff --git a/src/js/settings.js b/src/js/settings.js
index 6315eaf18..6e75aa158 100644
--- a/src/js/settings.js
+++ b/src/js/settings.js
@@ -165,6 +165,15 @@ var resetUserData = function() {
/******************************************************************************/
+var synchronizeDOM = function() {
+ document.body.classList.toggle(
+ 'advancedUser',
+ uDom.nodeFromId('advanced-user-enabled').checked === true
+ );
+};
+
+/******************************************************************************/
+
var changeUserSettings = function(name, value) {
messaging.send(
'dashboard',
@@ -213,6 +222,7 @@ var onUserSettingsReceived = function(details) {
this.getAttribute('data-setting-name'),
this.checked
);
+ synchronizeDOM();
});
});
@@ -230,6 +240,8 @@ var onUserSettingsReceived = function(details) {
uDom('#import').on('click', startImportFilePicker);
uDom('#reset').on('click', resetUserData);
uDom('#restoreFilePicker').on('change', handleImportFilePicker);
+
+ synchronizeDOM();
};
/******************************************************************************/
diff --git a/src/js/start.js b/src/js/start.js
index ad7a96784..9032f7bd0 100644
--- a/src/js/start.js
+++ b/src/js/start.js
@@ -19,7 +19,7 @@
Home: https://github.com/gorhill/uBlock
*/
-/* global publicSuffixList */
+/* global objectAssign, publicSuffixList */
'use strict';
@@ -82,6 +82,9 @@ var onAllReady = function() {
vAPI.onLoadAllCompleted();
µb.contextMenu.update(null);
µb.firstInstall = false;
+
+ vAPI.net.onBeforeReady = null;
+ vAPI.messaging.broadcast({ what: 'ublockOrigin-readyState-complete' });
};
/******************************************************************************/
@@ -182,10 +185,6 @@ var onUserSettingsReady = function(fetched) {
if ( µb.firstInstall && vAPI.battery ) {
userSettings.ignoreGenericCosmeticFilters = true;
}
-
- // Remove obsolete setting
- delete userSettings.logRequests;
- vAPI.storage.remove('logRequests');
};
/******************************************************************************/
@@ -284,6 +283,23 @@ var onAdminSettingsRestored = function() {
/******************************************************************************/
+µb.hiddenSettings = (function() {
+ var json = vAPI.localStorage.getItem('hiddenSettings');
+ if ( typeof json === 'string' ) {
+ try {
+ var out = JSON.parse(json);
+ if ( out instanceof Object ) {
+ return out;
+ }
+ }
+ catch(ex) {
+ }
+ }
+ return objectAssign({}, µb.hiddenSettingsDefault);
+})();
+
+/******************************************************************************/
+
return function() {
// https://github.com/gorhill/uBlock/issues/531
µb.restoreAdminSettings(onAdminSettingsRestored);
diff --git a/src/js/storage.js b/src/js/storage.js
index 7a42cd708..6a4e4a568 100644
--- a/src/js/storage.js
+++ b/src/js/storage.js
@@ -19,7 +19,7 @@
Home: https://github.com/gorhill/uBlock
*/
-/* global YaMD5, punycode, publicSuffixList */
+/* global YaMD5, objectAssign, punycode, publicSuffixList */
'use strict';
@@ -80,6 +80,51 @@
/******************************************************************************/
+// For now, only boolean type is supported.
+
+µBlock.hiddenSettingsFromString = function(raw) {
+ var out = objectAssign({}, this.hiddenSettingsDefault),
+ lineIter = new this.LineIterator(raw),
+ line, matches, name, value;
+ while ( lineIter.eot() === false ) {
+ line = lineIter.next();
+ matches = /^\s*(\S+)\s+(.+)$/.exec(line);
+ if ( matches === null || matches.length !== 3 ) { continue; }
+ name = matches[1];
+ if ( out.hasOwnProperty(name) === false ) { continue; }
+ value = matches[2];
+ switch ( typeof out[name] ) {
+ case 'boolean':
+ if ( value === 'true' ) {
+ out[name] = true;
+ } else if ( value === 'false' ) {
+ out[name] = false;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ this.hiddenSettings = out;
+ vAPI.localStorage.setItem('hiddenSettings', JSON.stringify(out));
+ vAPI.storage.set({ hiddenSettingsString: this.stringFromHiddenSettings() });
+};
+
+/******************************************************************************/
+
+µBlock.stringFromHiddenSettings = function() {
+ var out = [],
+ keys = Object.keys(this.hiddenSettings).sort(),
+ key;
+ for ( var i = 0; i < keys.length; i++ ) {
+ key = keys[i];
+ out.push(key + ' ' + this.hiddenSettings[key]);
+ }
+ return out.join('\n');
+};
+
+/******************************************************************************/
+
µBlock.savePermanentFirewallRules = function() {
this.keyvalSetOne('dynamicFilteringString', this.permanentFirewall.toString());
};
diff --git a/src/js/traffic.js b/src/js/traffic.js
index e738aeeae..23038dcc6 100644
--- a/src/js/traffic.js
+++ b/src/js/traffic.js
@@ -111,22 +111,24 @@ var onBeforeRequest = function(details) {
// https://github.com/gorhill/uBlock/issues/949
// Redirect blocked request?
- var url = µb.redirectEngine.toURL(requestContext);
- if ( url !== undefined ) {
- pageStore.internalRedirectionCount += 1;
- if ( µb.logger.isEnabled() ) {
- µb.logger.writeOne(
- tabId,
- 'redirect',
- 'rr:' + µb.redirectEngine.resourceNameRegister,
- requestType,
- requestURL,
- requestContext.rootHostname,
- requestContext.pageHostname
- );
+ if ( µb.hiddenSettings.ignoreRedirectFilters !== true ) {
+ var url = µb.redirectEngine.toURL(requestContext);
+ if ( url !== undefined ) {
+ pageStore.internalRedirectionCount += 1;
+ if ( µb.logger.isEnabled() ) {
+ µb.logger.writeOne(
+ tabId,
+ 'redirect',
+ 'rr:' + µb.redirectEngine.resourceNameRegister,
+ requestType,
+ requestURL,
+ requestContext.rootHostname,
+ requestContext.pageHostname
+ );
+ }
+ requestContext.dispose();
+ return { redirectUrl: url };
}
- requestContext.dispose();
- return { redirectUrl: url };
}
requestContext.dispose();
@@ -136,9 +138,16 @@ var onBeforeRequest = function(details) {
/******************************************************************************/
var onBeforeRootFrameRequest = function(details) {
- var tabId = details.tabId;
- var requestURL = details.url;
- var µb = µBlock;
+ if (
+ vAPI.net.onBeforeReady instanceof Function &&
+ vAPI.net.onBeforeReady(details) === true
+ ) {
+ return { cancel: true };
+ }
+
+ var tabId = details.tabId,
+ requestURL = details.url,
+ µb = µBlock;
µb.tabContextManager.push(tabId, requestURL);
@@ -146,9 +155,10 @@ var onBeforeRootFrameRequest = function(details) {
// https://github.com/chrisaljoudi/uBlock/issues/1001
// This must be executed regardless of whether the request is
// behind-the-scene
- var µburi = µb.URI;
- var requestHostname = µburi.hostnameFromURI(requestURL);
- var requestDomain = µburi.domainFromHostname(requestHostname) || requestHostname;
+ var µburi = µb.URI,
+ requestHostname = µburi.hostnameFromURI(requestURL),
+ requestDomain = µburi.domainFromHostname(requestHostname) || requestHostname,
+ result = '';
var context = {
rootHostname: requestHostname,
rootDomain: requestDomain,
@@ -159,8 +169,6 @@ var onBeforeRootFrameRequest = function(details) {
requestType: 'main_frame'
};
- var result = '';
-
// If the site is whitelisted, disregard strict blocking
if ( µb.getNetFilteringSwitch(requestURL) === false ) {
result = 'ua:whitelisted';
@@ -634,6 +642,33 @@ var headerIndexFromName = function(headerName, headers) {
/******************************************************************************/
+// https://github.com/gorhill/uBlock/issues/2067
+// Experimental: Suspend tabs until uBO is fully ready.
+
+vAPI.net.onBeforeReady = function(details) {
+ if ( µBlock.hiddenSettings.suspendTabsUntilReady !== true ) {
+ return;
+ }
+ var pageURL = details.url;
+ if ( /^https?:\/\//.test(pageURL) === false ) {
+ return;
+ }
+ if (
+ details.tabId === -1 ||
+ details.type !== 'main_frame' ||
+ details.frameId !== 0
+ ) {
+ return;
+ }
+ vAPI.tabs.replace(
+ details.tabId,
+ vAPI.getURL('document-suspended.html?url=') + encodeURIComponent(pageURL)
+ );
+ return true;
+};
+
+/******************************************************************************/
+
vAPI.net.onBeforeRequest = {
urls: [
'http://*/*',
diff --git a/src/settings.html b/src/settings.html
index e87cf360f..c4d293a65 100644
--- a/src/settings.html
+++ b/src/settings.html
@@ -17,7 +17,7 @@
-
+