refactor some webRequest-related code (now that firefox legacy is out of the way)

This commit is contained in:
Raymond Hill 2018-10-28 10:58:25 -03:00
parent 5e08d083e0
commit 9039874fc9
No known key found for this signature in database
GPG key ID: 25E1490B761470C2
12 changed files with 341 additions and 580 deletions

View file

@ -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: '<all_urls>' }, 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.

View file

@ -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 || [ '<all_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 || ['<all_urls>'];
types = this.onBeforeRequest.types || undefined;
if (
(validTypes.websocket) &&
(vAPI.net.validTypes.has('websocket')) &&
(types === undefined || types.indexOf('websocket') !== -1) &&
(urls.indexOf('<all_urls>') === -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': [ '<all_urls>' ],
'types': [ 'ping' ]
},
[ 'blocking', 'requestHeaders' ]
);
}
if ( onHeadersReceived ) {
urls = this.onHeadersReceived.urls || ['<all_urls>'];
types = onHeadersReceivedTypes;
wrApi.onHeadersReceived.addListener(
onHeadersReceived,
{ urls: urls, types: types },
this.onHeadersReceived.extra
);
}
};
return { types, urls };
};
})();
/******************************************************************************/

View file

@ -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

View file

@ -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 || [ '<all_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 || ['<all_urls>'];
let types = this.onBeforeRequest.types || undefined;
if (
(validTypes.has('websocket')) &&
(vAPI.net.validTypes.has('websocket')) &&
(types === undefined || types.indexOf('websocket') !== -1) &&
(urls.indexOf('<all_urls>') === -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 || ['<all_urls>'];
let types = onHeadersReceivedTypes;
wrApi.onHeadersReceived.addListener(
onHeadersReceived,
{ urls: urls, types: types },
this.onHeadersReceived.extra
);
}
};
})();
/******************************************************************************/

View file

@ -13,6 +13,7 @@
<script src="js/vapi-background.js"></script>
<script src="js/vapi-webrequest.js"></script><!-- Forks can pick the webext, chromium, or their own implementation -->
<script src="js/background.js"></script>
<script src="js/traffic.js"></script>
<script src="js/hntrie.js"></script>
<script src="js/utils.js"></script>
<script src="js/uritools.js"></script>
@ -34,7 +35,6 @@
<script src="js/logger.js"></script>
<script src="js/pagestore.js"></script>
<script src="js/tab.js"></script>
<script src="js/traffic.js"></script>
<script src="js/text-encode.js"></script>
<script src="js/contextmenu.js"></script>
<script src="js/reverselookup.js"></script>

View file

@ -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

View file

@ -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);

View file

@ -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;
};

View file

@ -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;
})();

View file

@ -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: '<all_urls>' }, 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);
};
/******************************************************************************/

View file

@ -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

View file

@ -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;
/******************************************************************************/
})();
/******************************************************************************/