diff --git a/platform/chromium/vapi-background.js b/platform/chromium/vapi-background.js index 5e95622d6..86de1b7df 100644 --- a/platform/chromium/vapi-background.js +++ b/platform/chromium/vapi-background.js @@ -76,159 +76,166 @@ vAPI.storage = chrome.storage.local; // Do not mess up with existing settings if not assigning them stricter // values. -vAPI.browserSettings = { - webRTCSupported: undefined, - - // https://github.com/gorhill/uBlock/issues/875 - // Must not leave `lastError` unchecked. - noopCallback: function() { - void chrome.runtime.lastError; - }, - - // Calling with `true` means IP address leak is not prevented. - // https://github.com/gorhill/uBlock/issues/533 - // We must first check wether this Chromium-based browser was compiled - // with WebRTC support. To do this, we use an iframe, this way the - // empty RTCPeerConnection object we create to test for support will - // be properly garbage collected. This prevents issues such as - // a computer unable to enter into sleep mode, as reported in the - // Chrome store: - // https://github.com/gorhill/uBlock/issues/533#issuecomment-167931681 - setWebrtcIPAddress: function(setting) { - // We don't know yet whether this browser supports WebRTC: find out. - if ( this.webRTCSupported === undefined ) { - this.webRTCSupported = { setting: setting }; - var iframe = document.createElement('iframe'); - var me = this; - var messageHandler = function(ev) { - if ( ev.origin !== self.location.origin ) { - return; - } - window.removeEventListener('message', messageHandler); - var setting = me.webRTCSupported.setting; - me.webRTCSupported = ev.data === 'webRTCSupported'; - me.setWebrtcIPAddress(setting); - iframe.parentNode.removeChild(iframe); - iframe = null; - }; - window.addEventListener('message', messageHandler); - iframe.src = 'is-webrtc-supported.html'; - document.body.appendChild(iframe); - return; - } - - // We are waiting for a response from our iframe. This makes the code - // safe to re-entrancy. - if ( typeof this.webRTCSupported === 'object' ) { - this.webRTCSupported.setting = setting; - return; - } - - // https://github.com/gorhill/uBlock/issues/533 - // WebRTC not supported: `webRTCMultipleRoutesEnabled` can NOT be - // safely accessed. Accessing the property will cause full browser - // crash. - if ( this.webRTCSupported !== true ) { - return; - } - - var cp = chrome.privacy, - cpn = cp.network; - - // Older version of Chromium do not support this setting, and is - // marked as "deprecated" since Chromium 48. - if ( typeof cpn.webRTCMultipleRoutesEnabled === 'object' ) { - try { - if ( setting ) { - cpn.webRTCMultipleRoutesEnabled.clear({ - scope: 'regular' - }, this.noopCallback); - } else { - cpn.webRTCMultipleRoutesEnabled.set({ - value: false, - scope: 'regular' - }, this.noopCallback); - } - } catch(ex) { - console.error(ex); - } - } - - // This setting became available in Chromium 48. - if ( typeof cpn.webRTCIPHandlingPolicy === 'object' ) { - try { - if ( setting ) { - cpn.webRTCIPHandlingPolicy.clear({ - scope: 'regular' - }, this.noopCallback); - } else { - // Respect current stricter setting if any. - cpn.webRTCIPHandlingPolicy.get({}, function(details) { - var value = details.value === 'disable_non_proxied_udp' ? - 'disable_non_proxied_udp' : - 'default_public_interface_only'; - cpn.webRTCIPHandlingPolicy.set({ - value: value, - scope: 'regular' - }, this.noopCallback); - }.bind(this)); - } - } catch(ex) { - console.error(ex); - } - } - }, - - set: function(details) { - for ( var setting in details ) { - if ( details.hasOwnProperty(setting) === false ) { - continue; - } - switch ( setting ) { - case 'prefetching': - try { - if ( !!details[setting] ) { - chrome.privacy.network.networkPredictionEnabled.clear({ - scope: 'regular' - }, this.noopCallback); - } else { - chrome.privacy.network.networkPredictionEnabled.set({ - value: false, - scope: 'regular' - }, this.noopCallback); - } - } catch(ex) { - console.error(ex); - } - break; - - case 'hyperlinkAuditing': - try { - if ( !!details[setting] ) { - chrome.privacy.websites.hyperlinkAuditingEnabled.clear({ - scope: 'regular' - }, this.noopCallback); - } else { - chrome.privacy.websites.hyperlinkAuditingEnabled.set({ - value: false, - scope: 'regular' - }, this.noopCallback); - } - } catch(ex) { - console.error(ex); - } - break; - - case 'webrtcIPAddress': - this.setWebrtcIPAddress(!!details[setting]); - break; - - default: - break; - } - } +vAPI.browserSettings = (function() { + // Not all platforms support `chrome.privacy`. + if ( chrome.privacy instanceof Object === false ) { + return; } -}; + + return { + webRTCSupported: undefined, + + // https://github.com/gorhill/uBlock/issues/875 + // Must not leave `lastError` unchecked. + noopCallback: function() { + void chrome.runtime.lastError; + }, + + // Calling with `true` means IP address leak is not prevented. + // https://github.com/gorhill/uBlock/issues/533 + // We must first check wether this Chromium-based browser was compiled + // with WebRTC support. To do this, we use an iframe, this way the + // empty RTCPeerConnection object we create to test for support will + // be properly garbage collected. This prevents issues such as + // a computer unable to enter into sleep mode, as reported in the + // Chrome store: + // https://github.com/gorhill/uBlock/issues/533#issuecomment-167931681 + setWebrtcIPAddress: function(setting) { + // We don't know yet whether this browser supports WebRTC: find out. + if ( this.webRTCSupported === undefined ) { + this.webRTCSupported = { setting: setting }; + var iframe = document.createElement('iframe'); + var me = this; + var messageHandler = function(ev) { + if ( ev.origin !== self.location.origin ) { + return; + } + window.removeEventListener('message', messageHandler); + var setting = me.webRTCSupported.setting; + me.webRTCSupported = ev.data === 'webRTCSupported'; + me.setWebrtcIPAddress(setting); + iframe.parentNode.removeChild(iframe); + iframe = null; + }; + window.addEventListener('message', messageHandler); + iframe.src = 'is-webrtc-supported.html'; + document.body.appendChild(iframe); + return; + } + + // We are waiting for a response from our iframe. This makes the code + // safe to re-entrancy. + if ( typeof this.webRTCSupported === 'object' ) { + this.webRTCSupported.setting = setting; + return; + } + + // https://github.com/gorhill/uBlock/issues/533 + // WebRTC not supported: `webRTCMultipleRoutesEnabled` can NOT be + // safely accessed. Accessing the property will cause full browser + // crash. + if ( this.webRTCSupported !== true ) { + return; + } + + var cp = chrome.privacy, + cpn = cp.network; + + // Older version of Chromium do not support this setting, and is + // marked as "deprecated" since Chromium 48. + if ( typeof cpn.webRTCMultipleRoutesEnabled === 'object' ) { + try { + if ( setting ) { + cpn.webRTCMultipleRoutesEnabled.clear({ + scope: 'regular' + }, this.noopCallback); + } else { + cpn.webRTCMultipleRoutesEnabled.set({ + value: false, + scope: 'regular' + }, this.noopCallback); + } + } catch(ex) { + console.error(ex); + } + } + + // This setting became available in Chromium 48. + if ( typeof cpn.webRTCIPHandlingPolicy === 'object' ) { + try { + if ( setting ) { + cpn.webRTCIPHandlingPolicy.clear({ + scope: 'regular' + }, this.noopCallback); + } else { + // Respect current stricter setting if any. + cpn.webRTCIPHandlingPolicy.get({}, function(details) { + var value = details.value === 'disable_non_proxied_udp' ? + 'disable_non_proxied_udp' : + 'default_public_interface_only'; + cpn.webRTCIPHandlingPolicy.set({ + value: value, + scope: 'regular' + }, this.noopCallback); + }.bind(this)); + } + } catch(ex) { + console.error(ex); + } + } + }, + + set: function(details) { + for ( var setting in details ) { + if ( details.hasOwnProperty(setting) === false ) { + continue; + } + switch ( setting ) { + case 'prefetching': + try { + if ( !!details[setting] ) { + chrome.privacy.network.networkPredictionEnabled.clear({ + scope: 'regular' + }, this.noopCallback); + } else { + chrome.privacy.network.networkPredictionEnabled.set({ + value: false, + scope: 'regular' + }, this.noopCallback); + } + } catch(ex) { + console.error(ex); + } + break; + + case 'hyperlinkAuditing': + try { + if ( !!details[setting] ) { + chrome.privacy.websites.hyperlinkAuditingEnabled.clear({ + scope: 'regular' + }, this.noopCallback); + } else { + chrome.privacy.websites.hyperlinkAuditingEnabled.set({ + value: false, + scope: 'regular' + }, this.noopCallback); + } + } catch(ex) { + console.error(ex); + } + break; + + case 'webrtcIPAddress': + this.setWebrtcIPAddress(!!details[setting]); + break; + + default: + break; + } + } + } + }; +})(); /******************************************************************************/ /******************************************************************************/ @@ -329,7 +336,10 @@ vAPI.tabs.registerListeners = function() { chrome.webNavigation.onBeforeNavigate.addListener(onBeforeNavigate); chrome.webNavigation.onCommitted.addListener(onCommitted); - chrome.webNavigation.onCreatedNavigationTarget.addListener(onCreatedNavigationTarget); + // Not supported on Firefox WebExtensions yet. + if ( chrome.webNavigation.onCreatedNavigationTarget instanceof Object ) { + chrome.webNavigation.onCreatedNavigationTarget.addListener(onCreatedNavigationTarget); + } chrome.tabs.onActivated.addListener(onActivated); chrome.tabs.onUpdated.addListener(onUpdated); @@ -1243,6 +1253,11 @@ vAPI.adminStorage = { /******************************************************************************/ vAPI.cloud = (function() { + // Not all platforms support `chrome.storage.sync`. + if ( chrome.storage.sync instanceof Object === false ) { + return; + } + var chunkCountPerFetch = 16; // Must be a power of 2 // Mind chrome.storage.sync.MAX_ITEMS (512 at time of writing) diff --git a/platform/webext/polyfill.js b/platform/webext/polyfill.js index ca8d82cd2..077ae784c 100644 --- a/platform/webext/polyfill.js +++ b/platform/webext/polyfill.js @@ -1,7 +1,7 @@ /******************************************************************************* uBlock Origin - a browser extension to block requests. - Copyright (C) 2014-2016 The uBlock Origin authors + Copyright (C) 2016 The uBlock Origin authors 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 @@ -23,192 +23,8 @@ 'use strict'; -/******************************************************************************/ /******************************************************************************/ -// https://github.com/gorhill/uBlock/issues/1067 -// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith -// Firefox 17/Chromium 41 supports `startsWith`. - -if ( String.prototype.startsWith instanceof Function === false ) { - String.prototype.startsWith = function(needle, pos) { - if ( typeof pos !== 'number' ) { - pos = 0; - } - return this.lastIndexOf(needle, pos) === pos; - }; -} - -/******************************************************************************/ - -// https://github.com/gorhill/uBlock/issues/1067 -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith -// Firefox 17/Chromium 41 supports `endsWith`. - -if ( String.prototype.endsWith instanceof Function === false ) { - String.prototype.endsWith = function(needle, pos) { - if ( typeof pos !== 'number' ) { - pos = this.length; - } - pos -= needle.length; - return this.indexOf(needle, pos) === pos; - }; -} - -/******************************************************************************/ - -// 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 -// is not an accurate API of the real Set() type. - -if ( self.Set instanceof Function === false ) { - self.Set = function(iter) { - this.clear(); - if ( Array.isArray(iter) ) { - for ( var i = 0, n = iter.length; i < n; i++ ) { - this.add(iter[i]); - } - return; - } - }; - - self.Set.polyfill = true; - - self.Set.prototype.clear = function() { - this._set = Object.create(null); - this.size = 0; - // Iterator stuff - this._values = undefined; - this._i = undefined; - this.value = undefined; - this.done = true; - }; - - self.Set.prototype.add = function(k) { - if ( this._set[k] === undefined ) { - this._set[k] = true; - this.size += 1; - } - return this; - }; - - self.Set.prototype.delete = function(k) { - if ( this._set[k] !== undefined ) { - delete this._set[k]; - this.size -= 1; - return true; - } - return false; - }; - - self.Set.prototype.has = function(k) { - return this._set[k] !== undefined; - }; - - self.Set.prototype.next = function() { - if ( this._i < this.size ) { - this.value = this._values[this._i++]; - } else { - this._values = undefined; - this.value = undefined; - this.done = true; - } - return this; - }; - - self.Set.prototype.values = function() { - this._values = Object.keys(this._set); - this._i = 0; - this.value = undefined; - this.done = false; - return this; - }; -} - -/******************************************************************************/ - -// 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 -// is not an accurate API of the real Map() type. - -if ( self.Map instanceof Function === false ) { - self.Map = function(iter) { - this.clear(); - if ( Array.isArray(iter) ) { - for ( var i = 0, n = iter.length, entry; i < n; i++ ) { - entry = iter[i]; - this.set(entry[0], entry[1]); - } - return; - } - }; - - self.Map.polyfill = true; - - self.Map.prototype.clear = function() { - this._map = Object.create(null); - this.size = 0; - // Iterator stuff - this._keys = undefined; - this._i = undefined; - this.value = undefined; - this.done = true; - }; - - self.Map.prototype.delete = function(k) { - if ( this._map[k] !== undefined ) { - delete this._map[k]; - this.size -= 1; - return true; - } - return false; - }; - - self.Map.prototype.entries = function() { - this._keys = Object.keys(this._map); - this._i = 0; - this.value = [ undefined, undefined ]; - this.done = false; - return this; - }; - - self.Map.prototype.get = function(k) { - return this._map[k]; - }; - - self.Map.prototype.has = function(k) { - return this._map[k] !== undefined; - }; - - self.Map.prototype.next = function() { - if ( this._i < this.size ) { - var key = this._keys[this._i++]; - this.value[0] = key; - this.value[1] = this._map[key]; - } else { - this._keys = undefined; - this.value = undefined; - this.done = true; - } - return this; - }; - - self.Map.prototype.set = function(k, v) { - if ( v !== undefined ) { - if ( this._map[k] === undefined ) { - this.size += 1; - } - this._map[k] = v; - } else { - if ( this._map[k] !== undefined ) { - this.size -= 1; - } - delete this._map[k]; - } - return this; - }; -} +// Nothing to polyfill so far for Firefox's WebExtensions. /******************************************************************************/ diff --git a/src/js/background.js b/src/js/background.js index 21e29050a..49776b977 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -70,6 +70,10 @@ return { webrtcIPAddressHidden: false }, + // Features detection. + privacySettingsSupported: vAPI.browserSettings instanceof Object, + cloudStorageSupported: vAPI.cloud instanceof Object, + // https://github.com/chrisaljoudi/uBlock/issues/180 // Whitelist directives need to be loaded once the PSL is available netWhitelist: {}, diff --git a/src/js/messaging.js b/src/js/messaging.js index e22fd65ff..b5b441bf2 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -650,7 +650,7 @@ vAPI.messaging.listen('elementPicker', onMessage); var onMessage = function(request, sender, callback) { // Cloud storage support is optional. - if ( vAPI.cloud instanceof Object === false ) { + if ( µBlock.cloudStorageSupported !== true ) { callback(); return; } @@ -725,7 +725,8 @@ var getLocalData = function(callback) { lastRestoreTime: o.lastRestoreTime, lastBackupFile: o.lastBackupFile, lastBackupTime: o.lastBackupTime, - cloudStorageSupported: vAPI.cloud instanceof Object + cloudStorageSupported: µb.cloudStorageSupported, + privacySettingsSupported: µb.privacySettingsSupported }); }; diff --git a/src/js/settings.js b/src/js/settings.js index 57dfd111a..26aa954e3 100644 --- a/src/js/settings.js +++ b/src/js/settings.js @@ -142,6 +142,11 @@ var onLocalDataReceived = function(details) { if ( details.cloudStorageSupported === false ) { uDom('#cloud-storage-enabled').attr('disabled', ''); } + if ( details.privacySettingsSupported === false ) { + uDom('#prefetching-disabled').attr('disabled', ''); + uDom('#hyperlink-auditing-disabled').attr('disabled', ''); + uDom('#webrtc-ipaddress-hidden').attr('disabled', ''); + } }; /******************************************************************************/ diff --git a/src/js/start.js b/src/js/start.js index eae6fdbf9..2c81d10df 100644 --- a/src/js/start.js +++ b/src/js/start.js @@ -68,7 +68,7 @@ var onAllReady = function() { µb.assets.remoteFetchBarrier -= 1; // vAPI.cloud is optional. - if ( vAPI.cloud instanceof Object ) { + if ( µb.cloudStorageSupported ) { vAPI.cloud.start([ 'tpFiltersPane', 'myFiltersPane', @@ -169,11 +169,13 @@ var onUserSettingsReady = function(fetched) { µb.assets.autoUpdate = userSettings.autoUpdate; µb.assets.autoUpdateDelay = µb.updateAssetsEvery; - vAPI.browserSettings.set({ - 'hyperlinkAuditing': !userSettings.hyperlinkAuditingDisabled, - 'prefetching': !userSettings.prefetchingDisabled, - 'webrtcIPAddress': !userSettings.webrtcIPAddressHidden - }); + if ( µb.privacySettingsSupported ) { + vAPI.browserSettings.set({ + 'hyperlinkAuditing': !userSettings.hyperlinkAuditingDisabled, + 'prefetching': !userSettings.prefetchingDisabled, + 'webrtcIPAddress': !userSettings.webrtcIPAddressHidden + }); + } µb.permanentFirewall.fromString(fetched.dynamicFilteringString); µb.sessionFirewall.assign(µb.permanentFirewall); diff --git a/src/js/ublock.js b/src/js/ublock.js index 7472577ea..f35e9fa62 100644 --- a/src/js/ublock.js +++ b/src/js/ublock.js @@ -283,7 +283,9 @@ var matchWhitelistDirective = function(url, hostname, directive) { this.contextMenu.update(null); break; case 'hyperlinkAuditingDisabled': - vAPI.browserSettings.set({ 'hyperlinkAuditing': !value }); + if ( this.privacySettingsSupported ) { + vAPI.browserSettings.set({ 'hyperlinkAuditing': !value }); + } break; case 'noCosmeticFiltering': if ( this.hnSwitches.toggle('no-cosmetic-filtering', '*', value ? 1 : 0) ) { @@ -301,10 +303,14 @@ var matchWhitelistDirective = function(url, hostname, directive) { } break; case 'prefetchingDisabled': - vAPI.browserSettings.set({ 'prefetching': !value }); + if ( this.privacySettingsSupported ) { + vAPI.browserSettings.set({ 'prefetching': !value }); + } break; case 'webrtcIPAddressHidden': - vAPI.browserSettings.set({ 'webrtcIPAddress': !value }); + if ( this.privacySettingsSupported ) { + vAPI.browserSettings.set({ 'webrtcIPAddress': !value }); + } break; default: break;