diff --git a/platform/chromium/polyfill.js b/platform/chromium/polyfill.js index ca8d82cd28b80..d39d2d39c0ce3 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 b540cb3c70cba..573fc0b1d53fb 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 0dd976fff3639..78396637b2adf 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 3a8c2b6d82d53..ca2b440569fae 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 a3e944674cf87..c0cb3493dcbd3 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 0000000000000..208f9d882cf29 --- /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 0000000000000..9fb4a985bb641 --- /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 308334f8bbca8..ba77d4510d329 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 0000000000000..f445eb2ec72cb --- /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 0000000000000..97421873ad055 --- /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 454630cc2e969..a79b6f5ba5d04 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 5e6f8842c1511..0660e0ecc76c4 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 3ec295cf12a0b..b3cdee4689b19 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 0000000000000..29a979391a469 --- /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 be1a23ff9e646..625dc183d0eae 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 6315eaf1859b7..6e75aa1584e30 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 ad7a967842fc3..9032f7bd02d6b 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 7a42cd708230c..6a4e4a56893e7 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 e738aeeaecf01..23038dcc6c54d 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 e87cf360f49cf..c4d293a65940a 100644 --- a/src/settings.html +++ b/src/settings.html @@ -17,7 +17,7 @@

  • -
  • +