mirror of https://github.com/gorhill/uBlock.git
Firefox: new method for request handling
Now both nsIContentPolicy and on-http-* observers are used for net request monitoring. Reasons: - In many cases, nsIContentPolicy.shouldLoad is invoked twice for the same resource, because of the speculative parsing. - nsIContentPolicy.shouldLoad don't have information about the channel, so it can't redirect the request, nor change its headers, however on-http-opening-request can. Also, local mirroring and inline-script blocking has been implemented.
This commit is contained in:
parent
720794357c
commit
dbfacad8a6
|
@ -19,7 +19,7 @@
|
||||||
Home: https://github.com/gorhill/uBlock
|
Home: https://github.com/gorhill/uBlock
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* global Services, Components, XPCOMUtils */
|
/* global Services, Components, XPCOMUtils, __URI__ */
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
@ -36,11 +36,15 @@ Cu['import']('resource://gre/modules/XPCOMUtils.jsm');
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const getMessager = win =>
|
const getMessageManager = function(context) {
|
||||||
win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDocShell)
|
return context
|
||||||
.sameTypeRootTreeItem.QueryInterface(Ci.nsIDocShell)
|
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||||
|
.getInterface(Ci.nsIDocShell)
|
||||||
|
.sameTypeRootTreeItem
|
||||||
|
.QueryInterface(Ci.nsIDocShell)
|
||||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||||
.getInterface(Ci.nsIContentFrameMessageManager);
|
.getInterface(Ci.nsIContentFrameMessageManager);
|
||||||
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
@ -49,8 +53,7 @@ const contentPolicy = {
|
||||||
classID: Components.ID('{e6d173c8-8dbf-4189-a6fd-189e8acffd27}'),
|
classID: Components.ID('{e6d173c8-8dbf-4189-a6fd-189e8acffd27}'),
|
||||||
contractID: '@' + appName + '/content-policy;1',
|
contractID: '@' + appName + '/content-policy;1',
|
||||||
ACCEPT: Ci.nsIContentPolicy.ACCEPT,
|
ACCEPT: Ci.nsIContentPolicy.ACCEPT,
|
||||||
REJECT: Ci.nsIContentPolicy.REJECT_REQUEST,
|
messageName: appName + ':shouldLoad',
|
||||||
requestMessageName: appName + ':onBeforeRequest',
|
|
||||||
get componentRegistrar() {
|
get componentRegistrar() {
|
||||||
return Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
|
return Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
|
||||||
},
|
},
|
||||||
|
@ -93,6 +96,7 @@ const contentPolicy = {
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
// https://bugzil.la/612921
|
||||||
shouldLoad: function(type, location, origin, context) {
|
shouldLoad: function(type, location, origin, context) {
|
||||||
if (!context || !/^https?$/.test(location.scheme)) {
|
if (!context || !/^https?$/.test(location.scheme)) {
|
||||||
return this.ACCEPT;
|
return this.ACCEPT;
|
||||||
|
@ -102,23 +106,17 @@ const contentPolicy = {
|
||||||
? context.contentWindow || context
|
? context.contentWindow || context
|
||||||
: (context.ownerDocument || context).defaultView;
|
: (context.ownerDocument || context).defaultView;
|
||||||
|
|
||||||
if (!win) {
|
if (win) {
|
||||||
return this.ACCEPT;
|
getMessageManager(win).sendSyncMessage(this.messageName, {
|
||||||
}
|
|
||||||
|
|
||||||
let result = getMessager(win).sendSyncMessage(this.requestMessageName, {
|
|
||||||
url: location.spec,
|
url: location.spec,
|
||||||
type: type,
|
type: type,
|
||||||
tabId: -1, // determined in background script
|
|
||||||
frameId: type === 6 ? -1 : (win === win.top ? 0 : 1),
|
frameId: type === 6 ? -1 : (win === win.top ? 0 : 1),
|
||||||
parentFrameId: win === win.top ? -1 : 0
|
parentFrameId: win === win.top ? -1 : 0
|
||||||
})[0];
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return result === true ? this.REJECT : this.ACCEPT;
|
|
||||||
}/*,
|
|
||||||
shouldProcess: function() {
|
|
||||||
return this.ACCEPT;
|
return this.ACCEPT;
|
||||||
}*/
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -126,7 +124,7 @@ const contentPolicy = {
|
||||||
const docObserver = {
|
const docObserver = {
|
||||||
contentBaseURI: 'chrome://' + appName + '/content/',
|
contentBaseURI: 'chrome://' + appName + '/content/',
|
||||||
initContext: function(win, sandbox) {
|
initContext: function(win, sandbox) {
|
||||||
let messager = getMessager(win);
|
let messager = getMessageManager(win);
|
||||||
|
|
||||||
if (sandbox) {
|
if (sandbox) {
|
||||||
win = Cu.Sandbox([win], {
|
win = Cu.Sandbox([win], {
|
||||||
|
|
|
@ -23,20 +23,18 @@
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
// https://bugzil.la/673569
|
||||||
|
|
||||||
(function(frameScriptContext) {
|
(function(frameScriptContext) {
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
let appName;
|
let appName = Components.stack.filename.match(/:\/\/([^\/]+)/)[1];
|
||||||
let listeners = {};
|
let listeners = {};
|
||||||
|
|
||||||
try { throw new Error; } catch (ex) {
|
Components.utils['import'](Components.stack.filename.replace('Script', 'Module'), {});
|
||||||
appName = ex.fileName.match(/:\/\/([^\/]+)/)[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
Components.utils['import']('chrome://' + appName + '/content/frameModule.js', {});
|
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
|
|
@ -321,6 +321,7 @@ vAPI.tabs.registerListeners = function() {
|
||||||
// onClosed - handled in windowWatcher.onTabClose
|
// onClosed - handled in windowWatcher.onTabClose
|
||||||
// onPopup ?
|
// onPopup ?
|
||||||
|
|
||||||
|
|
||||||
for (var win of this.getWindows()) {
|
for (var win of this.getWindows()) {
|
||||||
windowWatcher.onReady.call(win);
|
windowWatcher.onReady.call(win);
|
||||||
}
|
}
|
||||||
|
@ -600,10 +601,21 @@ vAPI.setIcon = function(tabId, img, badge) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var button = curWin.document.getElementById(vAPI.toolbarButton.widgetId);
|
var button = curWin.document.getElementById(vAPI.toolbarButton.widgetId);
|
||||||
|
|
||||||
|
if (!button) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*if (!button.classList.contains('badged-button')) {
|
||||||
|
button.classList.add('badged-button');
|
||||||
|
}*/
|
||||||
|
|
||||||
var icon = vAPI.tabIcons[tabId];
|
var icon = vAPI.tabIcons[tabId];
|
||||||
|
|
||||||
button.setAttribute('badge', icon && icon.badge || '');
|
button.setAttribute('badge', icon && icon.badge || '');
|
||||||
icon = vAPI.getURL(icon && icon.img || 'img/browsericons/icon16-off.svg');
|
button.image = vAPI.getURL(
|
||||||
button.style.listStyleImage = 'url(' + icon + ')';
|
button.image && icon && icon.img || 'img/browsericons/icon16-off.svg'
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -623,11 +635,11 @@ vAPI.toolbarButton.init = function() {
|
||||||
defaultArea: CustomizableUI.AREA_NAVBAR,
|
defaultArea: CustomizableUI.AREA_NAVBAR,
|
||||||
label: vAPI.app.name,
|
label: vAPI.app.name,
|
||||||
tooltiptext: vAPI.app.name,
|
tooltiptext: vAPI.app.name,
|
||||||
onViewShowing: function(e) {
|
onViewShowing: function({target}) {
|
||||||
e.target.firstChild.setAttribute('src', vAPI.getURL('popup.html'));
|
target.firstChild.setAttribute('src', vAPI.getURL('popup.html'));
|
||||||
},
|
},
|
||||||
onViewHiding: function(e) {
|
onViewHiding: function({target}) {
|
||||||
e.target.firstChild.setAttribute('src', 'about:blank');
|
target.firstChild.setAttribute('src', 'about:blank');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -645,11 +657,9 @@ vAPI.toolbarButton.register = function(doc) {
|
||||||
var panel = doc.createElement('panelview');
|
var panel = doc.createElement('panelview');
|
||||||
panel.setAttribute('id', this.panelId);
|
panel.setAttribute('id', this.panelId);
|
||||||
|
|
||||||
var iframe = panel.appendChild(doc.createElement('iframe'));
|
var iframe = doc.createElement('iframe');
|
||||||
iframe.setAttribute('type', 'content');
|
iframe.setAttribute('type', 'content');
|
||||||
|
|
||||||
panel.style.overflow = iframe.style.overflow = 'hidden';
|
|
||||||
|
|
||||||
doc.getElementById('PanelUI-multiView')
|
doc.getElementById('PanelUI-multiView')
|
||||||
.appendChild(panel)
|
.appendChild(panel)
|
||||||
.appendChild(iframe);
|
.appendChild(iframe);
|
||||||
|
@ -672,14 +682,13 @@ vAPI.toolbarButton.register = function(doc) {
|
||||||
};
|
};
|
||||||
|
|
||||||
var onPopupReady = function() {
|
var onPopupReady = function() {
|
||||||
if (!this.contentWindow
|
var win = this.contentWindow;
|
||||||
|| this.contentWindow.location.host !== location.host) {
|
|
||||||
|
if (!win || win.location.host !== location.host) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var mutObs = this.contentWindow.MutationObserver;
|
new win.MutationObserver(delayedResize).observe(win.document, {
|
||||||
|
|
||||||
(new mutObs(delayedResize)).observe(this.contentDocument, {
|
|
||||||
childList: true,
|
childList: true,
|
||||||
attributes: true,
|
attributes: true,
|
||||||
characterData: true,
|
characterData: true,
|
||||||
|
@ -712,7 +721,7 @@ vAPI.toolbarButton.register = function(doc) {
|
||||||
'#' + this.panelId + ', #' + this.panelId + ' > iframe {',
|
'#' + this.panelId + ', #' + this.panelId + ' > iframe {',
|
||||||
'width: 180px;',
|
'width: 180px;',
|
||||||
'height: 310px;',
|
'height: 310px;',
|
||||||
'transition: width .1s, height .1s;',
|
'overflow: hidden !important;',
|
||||||
'}'
|
'}'
|
||||||
].join(''));
|
].join(''));
|
||||||
|
|
||||||
|
@ -860,14 +869,127 @@ vAPI.messaging.broadcast = function(message) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
vAPI.net = {
|
vAPI.net = {};
|
||||||
beforeRequestMessageName: location.host + ':onBeforeRequest'
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
var httpObserver = {
|
||||||
|
ABORT: Components.results.NS_BINDING_ABORTED,
|
||||||
|
lastRequest: {
|
||||||
|
url: null,
|
||||||
|
type: null,
|
||||||
|
tabId: null,
|
||||||
|
frameId: null,
|
||||||
|
parentFrameId: null
|
||||||
|
},
|
||||||
|
observe: function(httpChannel, topic) {
|
||||||
|
if (!(httpChannel instanceof Ci.nsIHttpChannel)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
httpChannel = httpChannel.QueryInterface(Ci.nsIHttpChannel);
|
||||||
|
var URI = httpChannel.URI, tabId, result;
|
||||||
|
|
||||||
|
// the first distinct character
|
||||||
|
topic = topic.charAt(8);
|
||||||
|
|
||||||
|
// http-on-modify-request
|
||||||
|
if (topic === 'm') {
|
||||||
|
// var onHeadersReceived = vAPI.net.onHeadersReceived;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// http-on-examine-request
|
||||||
|
if (topic === 'e') {
|
||||||
|
try {
|
||||||
|
tabId = httpChannel.getProperty('tabId');
|
||||||
|
} catch (ex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tabId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var header = 'Content-Security-Policy';
|
||||||
|
|
||||||
|
try {
|
||||||
|
result = httpChannel.getResponseHeader(header);
|
||||||
|
} catch (ex) {
|
||||||
|
result = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = vAPI.net.onHeadersReceived.callback({
|
||||||
|
url: URI.spec,
|
||||||
|
tabId: tabId,
|
||||||
|
parentFrameId: -1,
|
||||||
|
responseHeaders: result ? [{name: header, value: result}] : []
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
httpChannel.setResponseHeader(
|
||||||
|
header,
|
||||||
|
result.responseHeaders[0].value,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// http-on-opening-request
|
||||||
|
|
||||||
|
var lastRequest = this.lastRequest;
|
||||||
|
|
||||||
|
if (!lastRequest.url || lastRequest.url !== URI.spec) {
|
||||||
|
lastRequest.url = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Important! When loading file via XHR for mirroring,
|
||||||
|
// the URL will be the same, so it could fall into an infinite loop
|
||||||
|
lastRequest.url = null;
|
||||||
|
|
||||||
|
if (lastRequest.type === 'main_frame'
|
||||||
|
&& httpChannel instanceof Ci.nsIWritablePropertyBag) {
|
||||||
|
httpChannel.setProperty('tabId', lastRequest.tabId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var onBeforeRequest = vAPI.net.onBeforeRequest;
|
||||||
|
|
||||||
|
if (!onBeforeRequest.types.has(lastRequest.type)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = onBeforeRequest.callback({
|
||||||
|
url: URI.spec,
|
||||||
|
type: lastRequest.type,
|
||||||
|
tabId: lastRequest.tabId,
|
||||||
|
frameId: lastRequest.frameId,
|
||||||
|
parentFrameId: lastRequest.parentFrameId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result || typeof result !== 'object') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.cancel === true) {
|
||||||
|
httpChannel.cancel(this.ABORT);
|
||||||
|
}
|
||||||
|
else if (result.redirectUrl) {
|
||||||
|
httpChannel.redirectionLimit = 1;
|
||||||
|
httpChannel.redirectTo(
|
||||||
|
Services.io.newURI(result.redirectUrl, null, null)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
vAPI.net.registerListeners = function() {
|
vAPI.net.registerListeners = function() {
|
||||||
var types = {
|
var typeMap = {
|
||||||
2: 'script',
|
2: 'script',
|
||||||
3: 'image',
|
3: 'image',
|
||||||
4: 'stylesheet',
|
4: 'stylesheet',
|
||||||
|
@ -877,42 +999,36 @@ vAPI.net.registerListeners = function() {
|
||||||
11: 'xmlhttprequest'
|
11: 'xmlhttprequest'
|
||||||
};
|
};
|
||||||
|
|
||||||
var onBeforeRequest = this.onBeforeRequest;
|
this.onBeforeRequest.types = new Set(this.onBeforeRequest.types);
|
||||||
|
|
||||||
this.onBeforeRequest = function(e) {
|
var shouldLoadListenerMessageName = location.host + ':shouldLoad';
|
||||||
var details = e.data;
|
var shouldLoadListener = function(e) {
|
||||||
|
var lastRequest = httpObserver.lastRequest;
|
||||||
details.type = types[details.type] || 'other';
|
lastRequest.url = e.data.url;
|
||||||
details.tabId = vAPI.tabs.getTabId(e.target);
|
lastRequest.type = typeMap[e.data.type] || 'other';
|
||||||
|
lastRequest.tabId = vAPI.tabs.getTabId(e.target);
|
||||||
if (onBeforeRequest.types.indexOf(details.type) === -1) {
|
lastRequest.frameId = e.data.frameId;
|
||||||
return false;
|
lastRequest.parentFrameId = e.data.parentFrameId;
|
||||||
}
|
|
||||||
|
|
||||||
var block = onBeforeRequest.callback(details);
|
|
||||||
|
|
||||||
if (block && typeof block === 'object') {
|
|
||||||
if (block.cancel === true) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (block.redirectURL) {
|
|
||||||
return block.redirectURL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
vAPI.messaging.globalMessageManager.addMessageListener(
|
vAPI.messaging.globalMessageManager.addMessageListener(
|
||||||
this.beforeRequestMessageName,
|
shouldLoadListenerMessageName,
|
||||||
this.onBeforeRequest
|
shouldLoadListener
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Services.obs.addObserver(httpObserver, 'http-on-opening-request', false);
|
||||||
|
// Services.obs.addObserver(httpObserver, 'http-on-modify-request', false);
|
||||||
|
Services.obs.addObserver(httpObserver, 'http-on-examine-response', false);
|
||||||
|
|
||||||
vAPI.unload.push(function() {
|
vAPI.unload.push(function() {
|
||||||
vAPI.messaging.globalMessageManager.removeMessageListener(
|
vAPI.messaging.globalMessageManager.removeMessageListener(
|
||||||
vAPI.net.beforeRequestMessageName,
|
shouldLoadListenerMessageName,
|
||||||
vAPI.net.onBeforeRequest
|
shouldLoadListener
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Services.obs.removeObserver(httpObserver, 'http-on-opening-request');
|
||||||
|
// Services.obs.removeObserver(httpObserver, 'http-on-modify-request');
|
||||||
|
Services.obs.removeObserver(httpObserver, 'http-on-examine-response');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -95,10 +95,10 @@ vAPI.messaging = {
|
||||||
|
|
||||||
this.channels['vAPI'] = {};
|
this.channels['vAPI'] = {};
|
||||||
this.channels['vAPI'].listener = function(msg) {
|
this.channels['vAPI'].listener = function(msg) {
|
||||||
if (msg.cmd === 'injectScript') {
|
if ( msg.cmd === 'injectScript' ) {
|
||||||
var details = msg.details;
|
var details = msg.details;
|
||||||
|
|
||||||
if (!details.allFrames && window !== window.top) {
|
if ( !details.allFrames && window !== window.top ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,12 +108,14 @@ vAPI.messaging = {
|
||||||
},
|
},
|
||||||
|
|
||||||
close: function() {
|
close: function() {
|
||||||
if (this.connector) {
|
if ( !this.connector ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
removeMessageListener(this.connectorId, this.connector);
|
removeMessageListener(this.connectorId, this.connector);
|
||||||
this.connector = null;
|
this.connector = null;
|
||||||
this.channels = {};
|
this.channels = {};
|
||||||
this.listeners = {};
|
this.listeners = {};
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
channel: function(channelName, callback) {
|
channel: function(channelName, callback) {
|
||||||
|
|
Loading…
Reference in New Issue