diff --git a/platform/chromium/vapi-background.js b/platform/chromium/vapi-background.js index 86c6c4191..876895970 100644 --- a/platform/chromium/vapi-background.js +++ b/platform/chromium/vapi-background.js @@ -620,7 +620,7 @@ vAPI.tabs.injectScript = function(tabId, details, callback) { // https://code.google.com/p/chromium/issues/detail?id=410868#c8 void chrome.runtime.lastError; if ( typeof callback === 'function' ) { - callback(); + callback.apply(null, arguments); } }; if ( tabId ) { @@ -1038,6 +1038,61 @@ vAPI.messaging.broadcast = function(message) { ); })(); +vAPI.net = { + listenerMap: new WeakMap(), + // legacy Chromium understands only these network request types. + validTypes: (function() { + let types = new Set([ + 'main_frame', + 'sub_frame', + 'stylesheet', + 'script', + 'image', + 'object', + 'xmlhttprequest', + 'other' + ]); + let wrrt = browser.webRequest.ResourceType; + if ( wrrt instanceof Object ) { + for ( let typeKey in wrrt ) { + if ( wrrt.hasOwnProperty(typeKey) ) { + types.add(wrrt[typeKey]); + } + } + } + return types; + })(), + denormalizeFilters: null, + normalizeDetails: null, + addListener: function(which, clientListener, filters, options) { + if ( typeof this.denormalizeFilters === 'function' ) { + filters = this.denormalizeFilters(filters); + } + let actualListener; + if ( typeof this.normalizeDetails === 'function' ) { + actualListener = function(details) { + vAPI.net.normalizeDetails(details); + return clientListener(details); + }; + this.listenerMap.set(clientListener, actualListener); + } + browser.webRequest[which].addListener( + actualListener || clientListener, + filters, + options + ); + }, + removeListener: function(which, clientListener) { + let actualListener = this.listenerMap.get(clientListener); + if ( actualListener !== undefined ) { + this.listenerMap.delete(clientListener); + } + browser.webRequest[which].removeListener( + actualListener || clientListener + ); + }, +}; + /******************************************************************************/ /******************************************************************************/ @@ -1097,62 +1152,6 @@ vAPI.commands = chrome.commands; /******************************************************************************/ /******************************************************************************/ -// This is called only once, when everything has been loaded in memory after -// the extension was launched. It can be used to inject content scripts -// in already opened web pages, to remove whatever nuisance could make it to -// the web pages before uBlock was ready. - -vAPI.onLoadAllCompleted = function() { - // http://code.google.com/p/chromium/issues/detail?id=410868#c11 - // Need to be sure to access `vAPI.lastError()` to prevent - // spurious warnings in the console. - var onScriptInjected = function() { - vAPI.lastError(); - }; - var scriptStart = function(tabId) { - var manifest = chrome.runtime.getManifest(); - if ( manifest instanceof Object === false ) { return; } - for ( var contentScript of manifest.content_scripts ) { - for ( var file of contentScript.js ) { - vAPI.tabs.injectScript(tabId, { - file: file, - allFrames: contentScript.all_frames, - runAt: contentScript.run_at - }, onScriptInjected); - } - } - }; - var bindToTabs = function(tabs) { - var µb = µBlock; - var i = tabs.length, tab; - while ( i-- ) { - tab = tabs[i]; - µb.tabContextManager.commit(tab.id, tab.url); - µb.bindTabToPageStats(tab.id); - // https://github.com/chrisaljoudi/uBlock/issues/129 - if ( /^https?:\/\//.test(tab.url) ) { - scriptStart(tab.id); - } - } - }; - - chrome.tabs.query({ url: '' }, bindToTabs); -}; - -/******************************************************************************/ -/******************************************************************************/ - -vAPI.punycodeHostname = function(hostname) { - return hostname; -}; - -vAPI.punycodeURL = function(url) { - return url; -}; - -/******************************************************************************/ -/******************************************************************************/ - // https://github.com/gorhill/uBlock/issues/531 // Storage area dedicated to admin settings. Read-only. diff --git a/platform/chromium/vapi-webrequest.js b/platform/chromium/vapi-webrequest.js index 8fa29324e..40553819f 100644 --- a/platform/chromium/vapi-webrequest.js +++ b/platform/chromium/vapi-webrequest.js @@ -1,7 +1,7 @@ /******************************************************************************* uBlock Origin - a browser extension to block requests. - Copyright (C) 2017-2018 Raymond Hill + 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 @@ -25,63 +25,24 @@ /******************************************************************************/ -vAPI.net = { - onBeforeRequest: {}, - onBeforeMaybeSpuriousCSPReport: {}, - onHeadersReceived: {}, - nativeCSPReportFiltering: false -}; - -vAPI.net.registerListeners = function() { - - var µb = µBlock, - µburi = µb.URI, - wrApi = chrome.webRequest; - - // https://bugs.chromium.org/p/chromium/issues/detail?id=410382 - // Between Chromium 38-48, plug-ins' network requests were reported as - // type "other" instead of "object". - var is_v38_48 = /\bChrom[a-z]+\/(?:3[89]|4[0-8])\.[\d.]+\b/.test(navigator.userAgent); - - // legacy Chromium understands only these network request types. - var validTypes = { - main_frame: true, - sub_frame: true, - stylesheet: true, - script: true, - image: true, - object: true, - xmlhttprequest: true, - other: true - }; - // modern Chromium/WebExtensions: more types available. - if ( wrApi.ResourceType ) { - (function() { - for ( var typeKey in wrApi.ResourceType ) { - if ( wrApi.ResourceType.hasOwnProperty(typeKey) ) { - validTypes[wrApi.ResourceType[typeKey]] = true; - } - } - })(); - } - - var extToTypeMap = new Map([ +(function() { + let extToTypeMap = new Map([ ['eot','font'],['otf','font'],['svg','font'],['ttf','font'],['woff','font'],['woff2','font'], ['mp3','media'],['mp4','media'],['webm','media'], ['gif','image'],['ico','image'],['jpeg','image'],['jpg','image'],['png','image'],['webp','image'] ]); - var denormalizeTypes = function(aa) { + let denormalizeTypes = function(aa) { if ( aa.length === 0 ) { - return Object.keys(validTypes); + return Array.from(vAPI.net.validTypes); } - var out = []; - var i = aa.length, + let out = []; + let i = aa.length, type, needOther = true; while ( i-- ) { type = aa[i]; - if ( validTypes[type] ) { + if ( vAPI.net.validTypes.has(type) ) { out.push(type); } if ( type === 'other' ) { @@ -94,7 +55,7 @@ vAPI.net.registerListeners = function() { return out; }; - var headerValue = function(headers, name) { + let headerValue = function(headers, name) { var i = headers.length; while ( i-- ) { if ( headers[i].name.toLowerCase() === name ) { @@ -104,7 +65,9 @@ vAPI.net.registerListeners = function() { return ''; }; - var normalizeRequestDetails = function(details) { + let parsedURL = new URL('https://www.example.org/'); + + vAPI.net.normalizeDetails = function(details) { // Chromium 63+ supports the `initiator` property, which contains // the URL of the origin from which the network request was made. if ( @@ -116,7 +79,7 @@ vAPI.net.registerListeners = function() { details.documentUrl = details.initiator; } - var type = details.type; + let type = details.type; // https://github.com/gorhill/uBlock/issues/1493 // Chromium 49+/WebExtensions support a new request type: `ping`, @@ -137,7 +100,8 @@ vAPI.net.registerListeners = function() { } // Try to map known "extension" part of URL to request type. - var path = µburi.pathFromURI(details.url), + parsedURL.href = details.url; + let path = parsedURL.pathname, pos = path.indexOf('.', path.length - 6); if ( pos !== -1 && (type = extToTypeMap.get(path.slice(pos + 1))) ) { details.type = type; @@ -160,58 +124,16 @@ vAPI.net.registerListeners = function() { return; } } + }; - // https://github.com/chrisaljoudi/uBlock/issues/862 - // If no transposition possible, transpose to `object` as per - // Chromium bug 410382 - // https://code.google.com/p/chromium/issues/detail?id=410382 - if ( is_v38_48 ) { - details.type = 'object'; + vAPI.net.denormalizeFilters = function(filters) { + let urls = filters.urls || [ '' ]; + let types = filters.types; + if ( Array.isArray(types) ) { + types = denormalizeTypes(types); } - }; - - var onBeforeRequestClient = this.onBeforeRequest.callback; - var onBeforeRequest = function(details) { - normalizeRequestDetails(details); - return onBeforeRequestClient(details); - }; - - // This is needed for Chromium 49-55. - var onBeforeSendHeaders = validTypes.csp_report - // modern Chromium/WebExtensions: type 'csp_report' is supported - ? null - // legacy Chromium - : function(details) { - if ( details.type !== 'ping' || details.method !== 'POST' ) { return; } - var type = headerValue(details.requestHeaders, 'content-type'); - if ( type === '' ) { return; } - if ( type.endsWith('/csp-report') ) { - details.type = 'csp_report'; - return onBeforeRequestClient(details); - } - }; - - var onHeadersReceivedClient = this.onHeadersReceived.callback, - onHeadersReceivedClientTypes = this.onHeadersReceived.types.slice(0), - onHeadersReceivedTypes = denormalizeTypes(onHeadersReceivedClientTypes); - var onHeadersReceived = function(details) { - normalizeRequestDetails(details); if ( - onHeadersReceivedClientTypes.length !== 0 && - onHeadersReceivedClientTypes.indexOf(details.type) === -1 - ) { - return; - } - return onHeadersReceivedClient(details); - }; - - var urls, types; - - if ( onBeforeRequest ) { - urls = this.onBeforeRequest.urls || ['']; - types = this.onBeforeRequest.types || undefined; - if ( - (validTypes.websocket) && + (vAPI.net.validTypes.has('websocket')) && (types === undefined || types.indexOf('websocket') !== -1) && (urls.indexOf('') === -1) ) { @@ -222,51 +144,8 @@ vAPI.net.registerListeners = function() { urls.push('wss://*/*'); } } - wrApi.onBeforeRequest.addListener( - onBeforeRequest, - { urls: urls, types: types }, - this.onBeforeRequest.extra - ); - } - - // https://github.com/gorhill/uBlock/issues/3140 - this.nativeCSPReportFiltering = validTypes.csp_report; - if ( - this.nativeCSPReportFiltering && - typeof this.onBeforeMaybeSpuriousCSPReport.callback === 'function' - ) { - wrApi.onBeforeRequest.addListener( - this.onBeforeMaybeSpuriousCSPReport.callback, - { - urls: [ 'http://*/*', 'https://*/*' ], - types: [ 'csp_report' ] - }, - [ 'blocking', 'requestBody' ] - ); - } - - // Chromium 48 and lower does not support `ping` type. - // Chromium 56 and higher does support `csp_report` stype. - if ( onBeforeSendHeaders ) { - wrApi.onBeforeSendHeaders.addListener( - onBeforeSendHeaders, - { - 'urls': [ '' ], - 'types': [ 'ping' ] - }, - [ 'blocking', 'requestHeaders' ] - ); - } - - if ( onHeadersReceived ) { - urls = this.onHeadersReceived.urls || ['']; - types = onHeadersReceivedTypes; - wrApi.onHeadersReceived.addListener( - onHeadersReceived, - { urls: urls, types: types }, - this.onHeadersReceived.extra - ); - } -}; + return { types, urls }; + }; +})(); /******************************************************************************/ diff --git a/platform/chromium/vapi.js b/platform/chromium/vapi.js index 39a242cd1..92635a42e 100644 --- a/platform/chromium/vapi.js +++ b/platform/chromium/vapi.js @@ -1,7 +1,7 @@ /******************************************************************************* uBlock Origin - a browser extension to block requests. - Copyright (C) 2017-2018 The uBlock Origin authors + Copyright (C) 2017-present 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 @@ -27,6 +27,14 @@ /******************************************************************************/ +if ( self.browser instanceof Object ) { + self.chrome = self.browser; +} else { + self.browser = self.chrome; +} + +/******************************************************************************/ + // https://bugzilla.mozilla.org/show_bug.cgi?id=1408996#c9 var vAPI = window.vAPI; // jshint ignore:line diff --git a/platform/firefox/vapi-webrequest.js b/platform/firefox/vapi-webrequest.js index 682fb5461..398d853bb 100644 --- a/platform/firefox/vapi-webrequest.js +++ b/platform/firefox/vapi-webrequest.js @@ -25,20 +25,7 @@ /******************************************************************************/ -vAPI.net = { - onBeforeRequest: {}, - onBeforeMaybeSpuriousCSPReport: {}, - onHeadersReceived: {}, - nativeCSPReportFiltering: true, - webRequest: browser.webRequest, - canFilterResponseBody: - typeof browser.webRequest === 'object' && - typeof browser.webRequest.filterResponseData === 'function' -}; - -/******************************************************************************/ - -vAPI.net.registerListeners = function() { +(function() { // https://github.com/gorhill/uBlock/issues/2950 // Firefox 56 does not normalize URLs to ASCII, uBO must do this itself. @@ -56,40 +43,18 @@ vAPI.net.registerListeners = function() { mustPunycode = evalMustPunycode(); }, { once: true }); - let wrApi = browser.webRequest; - - // legacy Chromium understands only these network request types. - let validTypes = new Set([ - 'image', - 'main_frame', - 'object', - 'other', - 'script', - 'stylesheet', - 'sub_frame', - 'xmlhttprequest', - ]); - // modern Chromium/WebExtensions: more types available. - if ( wrApi.ResourceType ) { - for ( let typeKey in wrApi.ResourceType ) { - if ( wrApi.ResourceType.hasOwnProperty(typeKey) ) { - validTypes.add(wrApi.ResourceType[typeKey]); - } - } - } - let denormalizeTypes = function(aa) { if ( aa.length === 0 ) { - return Array.from(validTypes); + return Array.from(vAPI.net.validTypes); } let out = new Set(), i = aa.length; while ( i-- ) { let type = aa[i]; - if ( validTypes.has(type) ) { + if ( vAPI.net.validTypes.has(type) ) { out.add(type); } - if ( type === 'image' && validTypes.has('imageset') ) { + if ( type === 'image' && vAPI.net.validTypes.has('imageset') ) { out.add('imageset'); } } @@ -100,7 +65,7 @@ vAPI.net.registerListeners = function() { let reAsciiHostname = /^https?:\/\/[0-9a-z_.:@-]+[/?#]/; let parsedURL = new URL('about:blank'); - let normalizeRequestDetails = function(details) { + vAPI.net.normalizeDetails = function(details) { if ( details.tabId === vAPI.noTabId && typeof details.documentUrl === 'string' @@ -132,79 +97,14 @@ vAPI.net.registerListeners = function() { } }; - // This is to work around Firefox's inability to redirect xmlhttprequest - // requests to data: URIs. - let pseudoRedirector = { - filters: new Map(), - reDataURI: /^data:\w+\/\w+;base64,/, - dec: null, - init: function() { - this.dec = new Uint8Array(128); - let s = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; - for ( let i = 0, n = s.length; i < n; i++ ) { - this.dec[s.charCodeAt(i)] = i; - } - return this.dec; - }, - start: function(requestId, redirectUrl) { - let match = this.reDataURI.exec(redirectUrl); - if ( match === null ) { return redirectUrl; } - let s = redirectUrl.slice(match[0].length).replace(/=*$/, ''); - let f = browser.webRequest.filterResponseData(requestId); - f.onstop = this.done; - f.onerror = this.disconnect; - this.filters.set(f, s); - }, - done: function() { - let pr = pseudoRedirector; - let bufIn = pr.filters.get(this); - if ( bufIn === undefined ) { return pr.disconnect(this); } - let dec = pr.dec || pr.init(); - let sizeIn = bufIn.length; - let iIn = 0; - let sizeOut = sizeIn * 6 >>> 3; - let bufOut = new Uint8Array(sizeOut); - let iOut = 0; - let n = sizeIn & ~3; - while ( iIn < n ) { - let b0 = dec[bufIn.charCodeAt(iIn++)]; - let b1 = dec[bufIn.charCodeAt(iIn++)]; - let b2 = dec[bufIn.charCodeAt(iIn++)]; - let b3 = dec[bufIn.charCodeAt(iIn++)]; - bufOut[iOut++] = (b0 << 2) & 0xFC | (b1 >>> 4); - bufOut[iOut++] = (b1 << 4) & 0xF0 | (b2 >>> 2); - bufOut[iOut++] = (b2 << 6) & 0xC0 | b3; - } - if ( iIn !== sizeIn ) { - let b0 = dec[bufIn.charCodeAt(iIn++)]; - let b1 = dec[bufIn.charCodeAt(iIn++)]; - bufOut[iOut++] = (b0 << 2) & 0xFC | (b1 >>> 4); - if ( iIn !== sizeIn ) { - let b2 = dec[bufIn.charCodeAt(iIn++)]; - bufOut[iOut++] = (b1 << 4) & 0xF0 | (b2 >>> 2); - } - } - this.write(bufOut); - pr.disconnect(this); - }, - disconnect: function(f) { - let pr = pseudoRedirector; - pr.filters.delete(f); - f.disconnect(); + vAPI.net.denormalizeFilters = function(filters) { + let urls = filters.urls || [ '' ]; + let types = filters.types; + if ( Array.isArray(types) ) { + types = denormalizeTypes(types); } - }; - - let onBeforeRequestClient = this.onBeforeRequest.callback; - let onBeforeRequest = function(details) { - normalizeRequestDetails(details); - return onBeforeRequestClient(details); - }; - - if ( onBeforeRequest ) { - let urls = this.onBeforeRequest.urls || ['']; - let types = this.onBeforeRequest.types || undefined; if ( - (validTypes.has('websocket')) && + (vAPI.net.validTypes.has('websocket')) && (types === undefined || types.indexOf('websocket') !== -1) && (urls.indexOf('') === -1) ) { @@ -215,48 +115,8 @@ vAPI.net.registerListeners = function() { urls.push('wss://*/*'); } } - wrApi.onBeforeRequest.addListener( - onBeforeRequest, - { urls: urls, types: types }, - this.onBeforeRequest.extra - ); - } - - // https://github.com/gorhill/uBlock/issues/3140 - if ( typeof this.onBeforeMaybeSpuriousCSPReport.callback === 'function' ) { - wrApi.onBeforeRequest.addListener( - this.onBeforeMaybeSpuriousCSPReport.callback, - { - urls: [ 'http://*/*', 'https://*/*' ], - types: [ 'csp_report' ] - }, - [ 'blocking', 'requestBody' ] - ); - } - - let onHeadersReceivedClient = this.onHeadersReceived.callback, - onHeadersReceivedClientTypes = this.onHeadersReceived.types.slice(0), - onHeadersReceivedTypes = denormalizeTypes(onHeadersReceivedClientTypes); - let onHeadersReceived = function(details) { - normalizeRequestDetails(details); - if ( - onHeadersReceivedClientTypes.length !== 0 && - onHeadersReceivedClientTypes.indexOf(details.type) === -1 - ) { - return; - } - return onHeadersReceivedClient(details); + return { types, urls }; }; - - if ( onHeadersReceived ) { - let urls = this.onHeadersReceived.urls || ['']; - let types = onHeadersReceivedTypes; - wrApi.onHeadersReceived.addListener( - onHeadersReceived, - { urls: urls, types: types }, - this.onHeadersReceived.extra - ); - } -}; +})(); /******************************************************************************/ diff --git a/src/background.html b/src/background.html index 98b6bafe5..fa1327b31 100644 --- a/src/background.html +++ b/src/background.html @@ -13,6 +13,7 @@ + @@ -34,7 +35,6 @@ - diff --git a/src/js/background.js b/src/js/background.js index dac1228cf..67110162b 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -63,17 +63,10 @@ var µBlock = (function() { // jshint ignore:line 'vivaldi-scheme', 'wyciwyg-scheme', // Firefox's "What-You-Cache-Is-What-You-Get" ]; - // https://github.com/gorhill/uBlock/issues/3693#issuecomment-379782428 - if ( vAPI.webextFlavor.soup.has('webext') === false ) { - whitelistDefault.push('behind-the-scene'); - } return { firstInstall: false, - onBeforeStartQueue: [], - onStartCompletedQueue: [], - userSettings: { advancedUserEnabled: false, alwaysDetachLogger: true, @@ -98,13 +91,13 @@ var µBlock = (function() { // jshint ignore:line hiddenSettingsDefault: hiddenSettingsDefault, hiddenSettings: (function() { - var out = Object.assign({}, hiddenSettingsDefault), + let out = Object.assign({}, hiddenSettingsDefault), json = vAPI.localStorage.getItem('immediateHiddenSettings'); if ( typeof json === 'string' ) { try { - var o = JSON.parse(json); + let o = JSON.parse(json); if ( o instanceof Object ) { - for ( var k in o ) { + for ( let k in o ) { if ( out.hasOwnProperty(k) ) { out[k] = o[k]; } @@ -122,7 +115,7 @@ var µBlock = (function() { // jshint ignore:line // Features detection. privacySettingsSupported: vAPI.browserSettings instanceof Object, cloudStorageSupported: vAPI.cloud instanceof Object, - canFilterResponseBody: vAPI.net.canFilterResponseBody === true, + canFilterResponseData: typeof browser.webRequest.filterResponseData === 'function', canInjectScriptletsNow: vAPI.webextFlavor.soup.has('chromium'), // https://github.com/chrisaljoudi/uBlock/issues/180 diff --git a/src/js/html-filtering.js b/src/js/html-filtering.js index 6b005bd8c..99c1d033a 100644 --- a/src/js/html-filtering.js +++ b/src/js/html-filtering.js @@ -254,7 +254,7 @@ api.fromCompiledContent = function(reader) { // Don't bother loading filters if stream filtering is not supported. - if ( µb.canFilterResponseBody === false ) { return; } + if ( µb.canFilterResponseData === false ) { return; } // 1002 = html filtering reader.select(1002); diff --git a/src/js/pagestore.js b/src/js/pagestore.js index b12075466..a957fcecf 100644 --- a/src/js/pagestore.js +++ b/src/js/pagestore.js @@ -683,22 +683,6 @@ PageStore.prototype.filterCSPReport = function(context) { } return 1; } - // https://github.com/gorhill/uBlock/issues/3140 - // Special handling of CSP reports if and only if these can't be filtered - // natively. - if ( - vAPI.net.nativeCSPReportFiltering !== true && - this.internalRedirectionCount !== 0 - ) { - if ( µb.logger.isEnabled() ) { - this.logData = { - result: 1, - source: 'global', - raw: 'no-spurious-csp-report' - }; - } - return 1; - } return 0; }; diff --git a/src/js/scriptlets/should-inject-contentscript.js b/src/js/scriptlets/should-inject-contentscript.js new file mode 100644 index 000000000..92c8552c8 --- /dev/null +++ b/src/js/scriptlets/should-inject-contentscript.js @@ -0,0 +1,33 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2018-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 +*/ + +'use strict'; + +// If content scripts are already injected, we need to respond with `false`, +// to "should inject content scripts?" + +(function() { + try { + return vAPI.uBO !== true; + } catch(ex) { + } + return true; +})(); \ No newline at end of file diff --git a/src/js/start.js b/src/js/start.js index 078e1bd0e..bad7435c2 100644 --- a/src/js/start.js +++ b/src/js/start.js @@ -48,25 +48,14 @@ vAPI.app.onShutdown = function() { /******************************************************************************/ -var processCallbackQueue = function(queue, callback) { - var processOne = function() { - var fn = queue.pop(); - if ( fn ) { - fn(processOne); - } else if ( typeof callback === 'function' ) { - callback(); - } - }; - processOne(); -}; - -/******************************************************************************/ - // Final initialization steps after all needed assets are in memory. // - Initialize internal state with maybe already existing tabs. // - Schedule next update operation. var onAllReady = function() { + µb.webRequest.start(); + initializeTabs(); + // https://github.com/chrisaljoudi/uBlock/issues/184 // Check for updates not too far in the future. µb.assets.addObserver(µb.assetObserver.bind(µb)); @@ -84,8 +73,55 @@ var onAllReady = function() { µb.contextMenu.update(null); µb.firstInstall = false; +}; - processCallbackQueue(µb.onStartCompletedQueue); +/******************************************************************************/ + +// This is called only once, when everything has been loaded in memory after +// the extension was launched. It can be used to inject content scripts +// in already opened web pages, to remove whatever nuisance could make it to +// the web pages before uBlock was ready. + +let initializeTabs = function() { + let handleScriptResponse = function(tabId, results) { + if ( + Array.isArray(results) === false || + results.length === 0 || + results[0] !== true + ) { + return; + } + // Inject dclarative content scripts programmatically. + let manifest = chrome.runtime.getManifest(); + if ( manifest instanceof Object === false ) { return; } + for ( let contentScript of manifest.content_scripts ) { + for ( let file of contentScript.js ) { + vAPI.tabs.injectScript(tabId, { + file: file, + allFrames: contentScript.all_frames, + runAt: contentScript.run_at + }); + } + } + }; + let bindToTabs = function(tabs) { + for ( let tab of tabs ) { + µb.tabContextManager.commit(tab.id, tab.url); + µb.bindTabToPageStats(tab.id); + // https://github.com/chrisaljoudi/uBlock/issues/129 + // Find out whether content scripts need to be injected + // programmatically. This may be necessary for web pages which + // were loaded before uBO launched. + if ( /^https?:\/\//.test(tab.url) === false ) { continue; } + vAPI.tabs.injectScript( + tab.id, + { file: 'js/scriptlets/should-inject-contentscript.js' }, + handleScriptResponse.bind(null, tab.id) + ); + } + }; + + browser.tabs.query({ url: '' }, bindToTabs); }; /******************************************************************************/ @@ -329,10 +365,8 @@ var onAdminSettingsRestored = function() { /******************************************************************************/ return function() { - processCallbackQueue(µb.onBeforeStartQueue, function() { - // https://github.com/gorhill/uBlock/issues/531 - µb.restoreAdminSettings(onAdminSettingsRestored); - }); + // https://github.com/gorhill/uBlock/issues/531 + µb.restoreAdminSettings(onAdminSettingsRestored); }; /******************************************************************************/ diff --git a/src/js/text-encode.js b/src/js/text-encode.js index 93ac0ff62..614db6481 100644 --- a/src/js/text-encode.js +++ b/src/js/text-encode.js @@ -25,7 +25,7 @@ µBlock.textEncode = (function() { - if ( µBlock.canFilterResponseBody !== true ) { return; } + if ( µBlock.canFilterResponseData !== true ) { return; } // charset aliases extracted from: // https://github.com/inexorabletash/text-encoding/blob/b4e5bc26e26e51f56e3daa9f13138c79f49d3c34/lib/encoding.js#L342 diff --git a/src/js/traffic.js b/src/js/traffic.js index e1f8cf794..cc3157ba3 100644 --- a/src/js/traffic.js +++ b/src/js/traffic.js @@ -29,10 +29,6 @@ /******************************************************************************/ -var exports = {}; - -/******************************************************************************/ - // Platform-specific behavior. // https://github.com/uBlockOrigin/uBlock-issues/issues/42 @@ -58,55 +54,9 @@ window.addEventListener('webextFlavor', function() { /******************************************************************************/ -// https://github.com/gorhill/uBlock/issues/2067 -// Experimental: Block everything until uBO is fully ready. -// TODO: re-work vAPI code to match more closely how listeners are -// registered with the webRequest API. This will simplify implementing -// the feature here: we could have a temporary onBeforeRequest listener -// which blocks everything until all is ready. -// This would allow to avoid the permanent special test at the top of -// the main onBeforeRequest just to implement this. -// https://github.com/gorhill/uBlock/issues/3130 -// Don't block root frame. - -var onBeforeReady = null; - -µBlock.onStartCompletedQueue.push(function(callback) { - vAPI.onLoadAllCompleted(); - callback(); -}); - -if ( µBlock.hiddenSettings.suspendTabsUntilReady ) { - onBeforeReady = (function() { - var suspendedTabs = new Set(); - µBlock.onStartCompletedQueue.push(function(callback) { - onBeforeReady = null; - for ( var tabId of suspendedTabs ) { - vAPI.tabs.reload(tabId); - } - callback(); - }); - return function(details) { - if ( - details.type !== 'main_frame' && - vAPI.isBehindTheSceneTabId(details.tabId) === false - ) { - suspendedTabs.add(details.tabId); - return true; - } - }; - })(); -} - -/******************************************************************************/ - // Intercept and filter web requests. var onBeforeRequest = function(details) { - if ( onBeforeReady !== null && onBeforeReady(details) ) { - return { cancel: true }; - } - // Special handling for root document. // https://github.com/chrisaljoudi/uBlock/issues/1001 // This must be executed regardless of whether the request is @@ -444,7 +394,7 @@ var onBeforeBehindTheSceneRequest = function(details) { // Blocked? if ( result === 1 ) { - return { 'cancel': true }; + return { cancel: true }; } }; @@ -452,91 +402,93 @@ var onBeforeBehindTheSceneRequest = function(details) { // https://github.com/gorhill/uBlock/issues/3140 -var onBeforeMaybeSpuriousCSPReport = function(details) { - var tabId = details.tabId; +let onBeforeMaybeSpuriousCSPReport = (function() { + let textDecoder; - // Ignore behind-the-scene requests. - if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; } + return function(details) { + let tabId = details.tabId; - // Lookup the page store associated with this tab id. - var µb = µBlock, - pageStore = µb.pageStoreFromTabId(tabId); - if ( pageStore === null ) { return; } + // Ignore behind-the-scene requests. + if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; } - // If uBO is disabled for the page, it can't possibly causes CSP reports - // to be triggered. - if ( pageStore.getNetFilteringSwitch() === false ) { return; } + // Lookup the page store associated with this tab id. + let µb = µBlock, + pageStore = µb.pageStoreFromTabId(tabId); + if ( pageStore === null ) { return; } - // A resource was redirected to a neutered one? - // TODO: mind injected scripts/styles as well. - if ( pageStore.internalRedirectionCount === 0 ) { return; } + // If uBO is disabled for the page, it can't possibly causes CSP + // reports to be triggered. + if ( pageStore.getNetFilteringSwitch() === false ) { return; } - var textDecoder = onBeforeMaybeSpuriousCSPReport.textDecoder; - if ( - textDecoder === undefined && - typeof self.TextDecoder === 'function' - ) { - textDecoder = - onBeforeMaybeSpuriousCSPReport.textDecoder = new TextDecoder(); - } + // A resource was redirected to a neutered one? + // TODO: mind injected scripts/styles as well. + if ( pageStore.internalRedirectionCount === 0 ) { return; } - // Find out whether the CSP report is a potentially spurious CSP report. - // If from this point on we are unable to parse the CSP report data, the - // safest assumption to protect users is to assume the CSP report is - // spurious. - if ( - textDecoder !== undefined && - details.method === 'POST' - ) { - var raw = details.requestBody && details.requestBody.raw; if ( - Array.isArray(raw) && - raw.length !== 0 && - raw[0] instanceof Object && - raw[0].bytes instanceof ArrayBuffer + textDecoder === undefined && + typeof self.TextDecoder === 'function' ) { - var data; - try { - data = JSON.parse(textDecoder.decode(raw[0].bytes)); - } catch (ex) { - } - if ( data instanceof Object ) { - var report = data['csp-report']; - if ( report instanceof Object ) { - var blocked = report['blocked-uri'] || report['blockedURI'], - validBlocked = typeof blocked === 'string', - source = report['source-file'] || report['sourceFile'], - validSource = typeof source === 'string'; - if ( - (validBlocked || validSource) && - (!validBlocked || !blocked.startsWith('data')) && - (!validSource || !source.startsWith('data')) - ) { - return; + textDecoder = new TextDecoder(); + } + + // Find out whether the CSP report is a potentially spurious CSP report. + // If from this point on we are unable to parse the CSP report data, + // the safest assumption to protect users is to assume the CSP report + // is spurious. + if ( + textDecoder !== undefined && + details.method === 'POST' + ) { + let raw = details.requestBody && details.requestBody.raw; + if ( + Array.isArray(raw) && + raw.length !== 0 && + raw[0] instanceof Object && + raw[0].bytes instanceof ArrayBuffer + ) { + let data; + try { + data = JSON.parse(textDecoder.decode(raw[0].bytes)); + } catch (ex) { + } + if ( data instanceof Object ) { + let report = data['csp-report']; + if ( report instanceof Object ) { + let blocked = + report['blocked-uri'] || report['blockedURI'], + validBlocked = typeof blocked === 'string', + source = + report['source-file'] || report['sourceFile'], + validSource = typeof source === 'string'; + if ( + (validBlocked || validSource) && + (!validBlocked || !blocked.startsWith('data')) && + (!validSource || !source.startsWith('data')) + ) { + return; + } } } } } - } - // Potentially spurious CSP report. - if ( µb.logger.isEnabled() ) { - var hostname = µb.URI.hostnameFromURI(details.url); - µb.logger.writeOne( - tabId, - 'net', - { result: 1, source: 'global', raw: 'no-spurious-csp-report' }, - 'csp_report', - details.url, - hostname, - hostname - ); - } + // Potentially spurious CSP report. + if ( µb.logger.isEnabled() ) { + let hostname = µb.URI.hostnameFromURI(details.url); + µb.logger.writeOne( + tabId, + 'net', + { result: 1, source: 'global', raw: 'no-spurious-csp-report' }, + 'csp_report', + details.url, + hostname, + hostname + ); + } - return { cancel: true }; -}; - -onBeforeMaybeSpuriousCSPReport.textDecoder = undefined; + return { cancel: true }; + }; +})(); /******************************************************************************/ @@ -592,7 +544,7 @@ var onHeadersReceived = function(details) { // At this point we have a HTML document. - let filteredHTML = µb.canFilterResponseBody && + let filteredHTML = µb.canFilterResponseData && filterDocument(pageStore, details) === true; let modifiedHeaders = injectCSP(pageStore, details) === true; @@ -880,7 +832,7 @@ var filterDocument = (function() { if ( headerValueFromName('content-disposition', headers) ) { return; } var stream = request.stream = - vAPI.net.webRequest.filterResponseData(details.requestId); + browser.webRequest.filterResponseData(details.requestId); stream.ondata = onStreamData; stream.onstop = onStreamStop; stream.onerror = onStreamError; @@ -1122,77 +1074,96 @@ var headerValueFromName = function(headerName, headers) { /******************************************************************************/ -vAPI.net.onBeforeRequest = { - urls: [ - 'http://*/*', - 'https://*/*' - ], - extra: [ 'blocking' ], - callback: onBeforeRequest -}; - -vAPI.net.onBeforeMaybeSpuriousCSPReport = { - callback: onBeforeMaybeSpuriousCSPReport -}; - -vAPI.net.onHeadersReceived = { - urls: [ - 'http://*/*', - 'https://*/*' - ], - types: [ - 'main_frame', - 'sub_frame', - 'image', - 'media' - ], - extra: [ 'blocking', 'responseHeaders' ], - callback: onHeadersReceived -}; - -vAPI.net.registerListeners(); - -/******************************************************************************/ - var isTemporarilyWhitelisted = function(result, hostname) { - var obsolete, pos; - for (;;) { - obsolete = documentWhitelists[hostname]; + let obsolete = documentWhitelists.get(hostname); if ( obsolete !== undefined ) { if ( obsolete > Date.now() ) { - if ( result === 0 ) { - return 2; - } + if ( result === 0 ) { return 2; } } else { - delete documentWhitelists[hostname]; + documentWhitelists.delete(hostname); } } - pos = hostname.indexOf('.'); + let pos = hostname.indexOf('.'); if ( pos === -1 ) { break; } hostname = hostname.slice(pos + 1); } return result; }; -var documentWhitelists = Object.create(null); +let documentWhitelists = new Map(); /******************************************************************************/ -exports.temporarilyWhitelistDocument = function(hostname) { - if ( typeof hostname !== 'string' || hostname === '' ) { - return; +return { + start: (function() { + let suspendedTabs = new Set(); + let onBeforeReady = function(details) { + if ( details.type !== 'main_frame' && details.tabId > 0 ) { + suspendedTabs.add(details.tabId); + console.info('uBO suspend tab %d, block %s', details.tabId, details.url); + return { cancel: true }; + } + }; + // https://github.com/gorhill/uBlock/issues/2067 + // Experimental: Block everything until uBO is fully ready. + // https://github.com/gorhill/uBlock/issues/3130 + // Don't block root frame. + if ( µBlock.hiddenSettings.suspendTabsUntilReady ) { + vAPI.net.addListener( + 'onBeforeRequest', + onBeforeReady, + { urls: [ 'http://*/*', 'https://*/*' ] }, + [ 'blocking' ] + ); + } + return function() { + vAPI.net.removeListener('onBeforeRequest', onBeforeReady); + vAPI.net.addListener( + 'onBeforeRequest', + onBeforeRequest, + { urls: [ 'http://*/*', 'https://*/*' ] }, + [ 'blocking' ] + ); + vAPI.net.addListener( + 'onHeadersReceived', + onHeadersReceived, + { + types: [ 'main_frame', 'sub_frame', 'image', 'media' ], + urls: [ 'http://*/*', 'https://*/*' ], + }, + [ 'blocking', 'responseHeaders' ] + ); + if ( vAPI.net.validTypes.has('csp_report') ) { + vAPI.net.addListener( + 'onBeforeRequest', + onBeforeMaybeSpuriousCSPReport, + { + types: [ 'csp_report' ], + urls: [ 'http://*/*', 'https://*/*' ] + }, + [ 'blocking', 'requestBody' ] + ); + } + // https://github.com/gorhill/uBlock/issues/2067 + // Force-reload tabs for which network requests were blocked + // during launch. This can happen only if tabs were "suspended". + for ( let tabId of suspendedTabs ) { + console.info('uBO suspend tab %d, force reload', tabId); + vAPI.tabs.reload(tabId); + } + suspendedTabs.clear(); + }; + })(), + + temporarilyWhitelistDocument: function(hostname) { + if ( typeof hostname !== 'string' || hostname === '' ) { return; } + documentWhitelists.set(hostname, Date.now() + 60 * 1000); } - - documentWhitelists[hostname] = Date.now() + 60 * 1000; }; /******************************************************************************/ -return exports; - -/******************************************************************************/ - })(); /******************************************************************************/