mirror of
https://github.com/gorhill/uMatrix.git
synced 2024-06-29 11:30:20 +12:00
Firefox: blocking improvements / other fixes
- Implement pop-up blocking - Support blocking redirected requests - Fix Local mirroring and inline-script blocking - Block content on data: and about:blank pages
This commit is contained in:
parent
f1e9bc363e
commit
bc9c9ca2d9
|
@ -19,7 +19,7 @@
|
||||||
Home: https://github.com/gorhill/uBlock
|
Home: https://github.com/gorhill/uBlock
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* global Services, CustomizableUI */
|
/* global Services, XPCOMUtils, CustomizableUI */
|
||||||
|
|
||||||
// For background page
|
// For background page
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@
|
||||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||||
|
|
||||||
Cu['import']('resource://gre/modules/Services.jsm');
|
Cu['import']('resource://gre/modules/Services.jsm');
|
||||||
|
Cu['import']('resource://gre/modules/XPCOMUtils.jsm');
|
||||||
Cu['import']('resource:///modules/CustomizableUI.jsm');
|
Cu['import']('resource:///modules/CustomizableUI.jsm');
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -310,7 +311,7 @@ var tabsProgressListener = {
|
||||||
tabId: tabId,
|
tabId: tabId,
|
||||||
url: browser.currentURI.spec
|
url: browser.currentURI.spec
|
||||||
});
|
});
|
||||||
} else {
|
} else if ( location.scheme === 'http' || location.scheme === 'https' ) {
|
||||||
vAPI.tabs.onNavigation({
|
vAPI.tabs.onNavigation({
|
||||||
frameId: 0,
|
frameId: 0,
|
||||||
tabId: tabId,
|
tabId: tabId,
|
||||||
|
@ -538,7 +539,7 @@ vAPI.tabs.open = function(details) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
vAPI.tabs.close = function(tabIds) {
|
vAPI.tabs.remove = function(tabIds) {
|
||||||
if ( !Array.isArray(tabIds) ) {
|
if ( !Array.isArray(tabIds) ) {
|
||||||
tabIds = [tabIds];
|
tabIds = [tabIds];
|
||||||
}
|
}
|
||||||
|
@ -902,77 +903,178 @@ vAPI.messaging.broadcast = function(message) {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var httpObserver = {
|
var httpObserver = {
|
||||||
|
classDescription: 'net-channel-event-sinks for ' + location.host,
|
||||||
|
classID: Components.ID('{dc8d6319-5f6e-4438-999e-53722db99e84}'),
|
||||||
|
contractID: '@' + location.host + '/net-channel-event-sinks;1',
|
||||||
ABORT: Components.results.NS_BINDING_ABORTED,
|
ABORT: Components.results.NS_BINDING_ABORTED,
|
||||||
|
ACCEPT: Components.results.NS_SUCCEEDED,
|
||||||
|
MAIN_FRAME: Ci.nsIContentPolicy.TYPE_DOCUMENT,
|
||||||
|
typeMap: {
|
||||||
|
2: 'script',
|
||||||
|
3: 'image',
|
||||||
|
4: 'stylesheet',
|
||||||
|
5: 'object',
|
||||||
|
6: 'main_frame',
|
||||||
|
7: 'sub_frame',
|
||||||
|
11: 'xmlhttprequest'
|
||||||
|
},
|
||||||
lastRequest: {
|
lastRequest: {
|
||||||
url: null,
|
url: null,
|
||||||
type: null,
|
type: null,
|
||||||
tabId: null,
|
tabId: null,
|
||||||
frameId: null,
|
frameId: null,
|
||||||
parentFrameId: null
|
parentFrameId: null,
|
||||||
|
opener: null
|
||||||
},
|
},
|
||||||
|
|
||||||
QueryInterface: (function() {
|
get componentRegistrar() {
|
||||||
var {XPCOMUtils} = Cu['import']('resource://gre/modules/XPCOMUtils.jsm', {});
|
return Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
|
||||||
return XPCOMUtils.generateQI([
|
},
|
||||||
Ci.nsIObserver,
|
|
||||||
Ci.nsISupportsWeakReference
|
get categoryManager() {
|
||||||
]);
|
return Cc['@mozilla.org/categorymanager;1']
|
||||||
})(),
|
.getService(Ci.nsICategoryManager);
|
||||||
|
},
|
||||||
|
|
||||||
|
QueryInterface: XPCOMUtils.generateQI([
|
||||||
|
Ci.nsIFactory,
|
||||||
|
Ci.nsIObserver,
|
||||||
|
Ci.nsIChannelEventSink,
|
||||||
|
Ci.nsISupportsWeakReference
|
||||||
|
]),
|
||||||
|
|
||||||
|
createInstance: function(outer, iid) {
|
||||||
|
if ( outer ) {
|
||||||
|
throw Components.results.NS_ERROR_NO_AGGREGATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.QueryInterface(iid);
|
||||||
|
},
|
||||||
|
|
||||||
register: function() {
|
register: function() {
|
||||||
Services.obs.addObserver(httpObserver, 'http-on-opening-request', true);
|
Services.obs.addObserver(this, 'http-on-opening-request', true);
|
||||||
// Services.obs.addObserver(httpObserver, 'http-on-modify-request', true);
|
Services.obs.addObserver(this, 'http-on-examine-response', true);
|
||||||
Services.obs.addObserver(httpObserver, 'http-on-examine-response', true);
|
|
||||||
|
this.componentRegistrar.registerFactory(
|
||||||
|
this.classID,
|
||||||
|
this.classDescription,
|
||||||
|
this.contractID,
|
||||||
|
this
|
||||||
|
);
|
||||||
|
this.categoryManager.addCategoryEntry(
|
||||||
|
'net-channel-event-sinks',
|
||||||
|
this.contractID,
|
||||||
|
this.contractID,
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
unregister: function() {
|
unregister: function() {
|
||||||
Services.obs.removeObserver(httpObserver, 'http-on-opening-request');
|
Services.obs.removeObserver(this, 'http-on-opening-request');
|
||||||
// Services.obs.removeObserver(httpObserver, 'http-on-modify-request');
|
Services.obs.removeObserver(this, 'http-on-examine-response');
|
||||||
Services.obs.removeObserver(httpObserver, 'http-on-examine-response');
|
|
||||||
|
this.componentRegistrar.unregisterFactory(this.classID, this);
|
||||||
|
this.categoryManager.deleteCategoryEntry(
|
||||||
|
'net-channel-event-sinks',
|
||||||
|
this.contractID,
|
||||||
|
false
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
observe: function(httpChannel, topic) {
|
handlePopup: function(URI, tabId, sourceTabId) {
|
||||||
// No need for QueryInterface if this check is performed?
|
if ( !sourceTabId ) {
|
||||||
if ( !(httpChannel instanceof Ci.nsIHttpChannel) ) {
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( URI.scheme !== 'http' && URI.scheme !== 'https' ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = vAPI.tabs.onPopup({
|
||||||
|
tabId: tabId,
|
||||||
|
sourceTabId: sourceTabId,
|
||||||
|
url: URI.spec
|
||||||
|
});
|
||||||
|
|
||||||
|
return result === true;
|
||||||
|
},
|
||||||
|
|
||||||
|
handleRequest: function(channel, details) {
|
||||||
|
var onBeforeRequest = vAPI.net.onBeforeRequest;
|
||||||
|
var type = this.typeMap[details.type] || 'other';
|
||||||
|
|
||||||
|
if ( onBeforeRequest.types.has(type) === false ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = onBeforeRequest.callback({
|
||||||
|
url: channel.URI.spec,
|
||||||
|
type: type,
|
||||||
|
tabId: details.tabId,
|
||||||
|
frameId: details.frameId,
|
||||||
|
parentFrameId: details.parentFrameId
|
||||||
|
});
|
||||||
|
|
||||||
|
if ( !result || typeof result !== 'object' ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( result.cancel === true ) {
|
||||||
|
channel.cancel(this.ABORT);
|
||||||
|
return true;
|
||||||
|
} else if ( result.redirectUrl ) {
|
||||||
|
channel.redirectionLimit = 1;
|
||||||
|
channel.redirectTo(
|
||||||
|
Services.io.newURI(result.redirectUrl, null, null)
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
observe: function(channel, topic) {
|
||||||
|
if ( !(channel instanceof Ci.nsIHttpChannel) ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var URI = httpChannel.URI, tabId, result;
|
var URI = channel.URI;
|
||||||
|
var channelData, result;
|
||||||
|
|
||||||
if ( topic === 'http-on-modify-request' ) {
|
if ( topic === 'http-on-examine-response' ) {
|
||||||
// var onHeadersReceived = vAPI.net.onHeadersReceived;
|
if ( !(channel instanceof Ci.nsIWritablePropertyBag) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( topic === 'http-on-examine-request' ) {
|
|
||||||
try {
|
try {
|
||||||
tabId = httpChannel.getProperty('tabId');
|
channelData = channel.getProperty(location.host + 'reqdata');
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !tabId ) {
|
// [tabId, type, sourceTabId - given if it was a popup]
|
||||||
|
if ( !channelData || channelData[0] !== this.MAIN_FRAME ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
topic = 'Content-Security-Policy';
|
topic = 'Content-Security-Policy';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
result = httpChannel.getResponseHeader(topic);
|
result = channel.getResponseHeader(topic);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
result = null;
|
result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = vAPI.net.onHeadersReceived.callback({
|
result = vAPI.net.onHeadersReceived.callback({
|
||||||
url: URI.spec,
|
url: URI.spec,
|
||||||
tabId: tabId,
|
tabId: channelData[1],
|
||||||
parentFrameId: -1,
|
parentFrameId: -1,
|
||||||
responseHeaders: result ? [{name: topic, value: result}] : []
|
responseHeaders: result ? [{name: topic, value: result}] : []
|
||||||
});
|
});
|
||||||
|
|
||||||
if ( result ) {
|
if ( result ) {
|
||||||
httpChannel.setResponseHeader(
|
channel.setResponseHeader(
|
||||||
topic,
|
topic,
|
||||||
result.responseHeaders.pop().value,
|
result.responseHeaders.pop().value,
|
||||||
true
|
true
|
||||||
|
@ -995,37 +1097,91 @@ var httpObserver = {
|
||||||
// the URL will be the same, so it could fall into an infinite loop
|
// the URL will be the same, so it could fall into an infinite loop
|
||||||
lastRequest.url = null;
|
lastRequest.url = null;
|
||||||
|
|
||||||
if ( lastRequest.type === 'main_frame'
|
var sourceTabId = null;
|
||||||
&& httpChannel instanceof Ci.nsIWritablePropertyBag ) {
|
|
||||||
httpChannel.setProperty('tabId', lastRequest.tabId);
|
// popup candidate (only for main_frame type)
|
||||||
|
if ( lastRequest.opener ) {
|
||||||
|
for ( var tab of vAPI.tabs.getAll() ) {
|
||||||
|
var tabURI = tab.linkedBrowser.currentURI;
|
||||||
|
|
||||||
|
// not the best approach
|
||||||
|
if ( tabURI.spec === this.lastRequest.opener ) {
|
||||||
|
sourceTabId = vAPI.tabs.getTabId(tab);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( this.handlePopup(channel.URI, lastRequest.tabId, sourceTabId) ) {
|
||||||
|
channel.cancel(this.ABORT);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var onBeforeRequest = vAPI.net.onBeforeRequest;
|
if ( this.handleRequest(channel, lastRequest) ) {
|
||||||
|
|
||||||
if ( !onBeforeRequest.types.has(lastRequest.type) ) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = onBeforeRequest.callback({
|
// if request is not handled we may use the data in on-modify-request
|
||||||
url: URI.spec,
|
if ( channel instanceof Ci.nsIWritablePropertyBag ) {
|
||||||
type: lastRequest.type,
|
channel.setProperty(
|
||||||
tabId: lastRequest.tabId,
|
location.host + 'reqdata',
|
||||||
frameId: lastRequest.frameId,
|
[lastRequest.type, lastRequest.tabId, sourceTabId]
|
||||||
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)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// contentPolicy.shouldLoad doesn't detect redirects, this needs to be used
|
||||||
|
asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback) {
|
||||||
|
var result = this.ACCEPT;
|
||||||
|
|
||||||
|
// If error thrown, the redirect will fail
|
||||||
|
try {
|
||||||
|
// skip internal redirects?
|
||||||
|
/*if ( flags & 4 ) {
|
||||||
|
console.log('internal redirect skipped');
|
||||||
|
return;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
var scheme = newChannel.URI.scheme;
|
||||||
|
|
||||||
|
if ( scheme !== 'http' && scheme !== 'https' ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !(oldChannel instanceof Ci.nsIWritablePropertyBag) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var channelData = oldChannel.getProperty(location.host + 'reqdata');
|
||||||
|
var [type, tabId, sourceTabId] = channelData;
|
||||||
|
|
||||||
|
if ( this.handlePopup(newChannel.URI, tabId, sourceTabId) ) {
|
||||||
|
result = this.ABORT;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var details = {
|
||||||
|
type: type,
|
||||||
|
tabId: tabId,
|
||||||
|
// well...
|
||||||
|
frameId: type === this.MAIN_FRAME ? -1 : 0,
|
||||||
|
parentFrameId: -1
|
||||||
|
};
|
||||||
|
|
||||||
|
if ( this.handleRequest(newChannel, details) ) {
|
||||||
|
result = this.ABORT;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// carry the data on in case of multiple redirects
|
||||||
|
if ( newChannel instanceof Ci.nsIWritablePropertyBag ) {
|
||||||
|
newChannel.setProperty(location.host + 'reqdata', channelData);
|
||||||
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
// console.error(ex);
|
||||||
|
} finally {
|
||||||
|
callback.onRedirectVerifyCallback(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1036,26 +1192,31 @@ vAPI.net = {};
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
vAPI.net.registerListeners = function() {
|
vAPI.net.registerListeners = function() {
|
||||||
var typeMap = {
|
|
||||||
2: 'script',
|
|
||||||
3: 'image',
|
|
||||||
4: 'stylesheet',
|
|
||||||
5: 'object',
|
|
||||||
6: 'main_frame',
|
|
||||||
7: 'sub_frame',
|
|
||||||
11: 'xmlhttprequest'
|
|
||||||
};
|
|
||||||
|
|
||||||
this.onBeforeRequest.types = new Set(this.onBeforeRequest.types);
|
this.onBeforeRequest.types = new Set(this.onBeforeRequest.types);
|
||||||
|
|
||||||
var shouldLoadListenerMessageName = location.host + ':shouldLoad';
|
var shouldLoadListenerMessageName = location.host + ':shouldLoad';
|
||||||
var shouldLoadListener = function(e) {
|
var shouldLoadListener = function(e) {
|
||||||
|
var details = e.data;
|
||||||
|
|
||||||
|
// data: and about:blank
|
||||||
|
if ( details.url.charAt(0) !== 'h' ) {
|
||||||
|
vAPI.net.onBeforeRequest.callback({
|
||||||
|
url: 'http://' + details.url.slice(0, details.url.indexOf(':')),
|
||||||
|
type: 'main_frame',
|
||||||
|
tabId: vAPI.tabs.getTabId(e.target),
|
||||||
|
frameId: details.frameId,
|
||||||
|
parentFrameId: details.parentFrameId
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var lastRequest = httpObserver.lastRequest;
|
var lastRequest = httpObserver.lastRequest;
|
||||||
lastRequest.url = e.data.url;
|
lastRequest.url = details.url;
|
||||||
lastRequest.type = typeMap[e.data.type] || 'other';
|
lastRequest.type = details.type;
|
||||||
lastRequest.tabId = vAPI.tabs.getTabId(e.target);
|
lastRequest.tabId = vAPI.tabs.getTabId(e.target);
|
||||||
lastRequest.frameId = e.data.frameId;
|
lastRequest.frameId = details.frameId;
|
||||||
lastRequest.parentFrameId = e.data.parentFrameId;
|
lastRequest.parentFrameId = details.parentFrameId;
|
||||||
|
lastRequest.opener = details.opener;
|
||||||
};
|
};
|
||||||
|
|
||||||
vAPI.messaging.globalMessageManager.addMessageListener(
|
vAPI.messaging.globalMessageManager.addMessageListener(
|
||||||
|
@ -1242,8 +1403,7 @@ window.addEventListener('unload', function() {
|
||||||
// frameModule needs to be cleared too
|
// frameModule needs to be cleared too
|
||||||
var frameModule = {};
|
var frameModule = {};
|
||||||
Cu['import'](vAPI.getURL('frameModule.js'), frameModule);
|
Cu['import'](vAPI.getURL('frameModule.js'), frameModule);
|
||||||
frameModule.contentPolicy.unregister();
|
frameModule.contentObserver.unregister();
|
||||||
frameModule.docObserver.unregister();
|
|
||||||
Cu.unload(vAPI.getURL('frameModule.js'));
|
Cu.unload(vAPI.getURL('frameModule.js'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue