aboutsummaryrefslogtreecommitdiffstats
path: root/platform
diff options
context:
space:
mode:
authorDeathamns <deathamns@gmail.com>2015-01-02 18:41:41 +0100
committerDeathamns <deathamns@gmail.com>2015-01-13 07:29:56 +0100
commit8a9165daa28714d456b15adff8565e4931c5024d (patch)
treebd45d2aebe4a741b68cc3110f47ec27addc2bf6e /platform
parent05bcc070a992a33625ad40b68a351ff525bb3151 (diff)
downloaduBlock-8a9165daa28714d456b15adff8565e4931c5024d.zip
uBlock-8a9165daa28714d456b15adff8565e4931c5024d.tar.gz
uBlock-8a9165daa28714d456b15adff8565e4931c5024d.tar.bz2
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
Diffstat (limited to 'platform')
-rw-r--r--platform/firefox/frameModule.js86
-rw-r--r--platform/firefox/vapi-background.js296
2 files changed, 277 insertions, 105 deletions
diff --git a/platform/firefox/frameModule.js b/platform/firefox/frameModule.js
index 7a27931..ea6845c 100644
--- a/platform/firefox/frameModule.js
+++ b/platform/firefox/frameModule.js
@@ -25,7 +25,7 @@
/******************************************************************************/
-this.EXPORTED_SYMBOLS = ['contentPolicy', 'docObserver'];
+this.EXPORTED_SYMBOLS = ['contentObserver'];
const {interfaces: Ci, utils: Cu} = Components;
const appName = __URI__.match(/:\/\/([^\/]+)/)[1];
@@ -48,11 +48,13 @@ const getMessageManager = function(context) {
/******************************************************************************/
-const contentPolicy = {
- classDescription: 'content-policy implementation for ' + appName,
+const contentObserver = {
+ classDescription: 'content-policy for ' + appName,
classID: Components.ID('{e6d173c8-8dbf-4189-a6fd-189e8acffd27}'),
contractID: '@' + appName + '/content-policy;1',
ACCEPT: Ci.nsIContentPolicy.ACCEPT,
+ MAIN_FRAME: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ contentBaseURI: 'chrome://' + appName + '/content/js/',
messageName: appName + ':shouldLoad',
get componentRegistrar() {
@@ -66,6 +68,7 @@ const contentPolicy = {
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIFactory,
+ Ci.nsIObserver,
Ci.nsIContentPolicy,
Ci.nsISupportsWeakReference
]),
@@ -77,7 +80,10 @@ const contentPolicy = {
return this.QueryInterface(iid);
},
+
register: function() {
+ Services.obs.addObserver(this, 'document-element-inserted', true);
+
this.componentRegistrar.registerFactory(
this.classID,
this.classDescription,
@@ -94,6 +100,8 @@ const contentPolicy = {
},
unregister: function() {
+ Services.obs.removeObserver(this, 'document-element-inserted');
+
this.componentRegistrar.unregisterFactory(this.classID, this);
this.categoryManager.deleteCategoryEntry(
'content-policy',
@@ -109,38 +117,51 @@ const contentPolicy = {
return this.ACCEPT;
}
+ let opener;
+
if ( location.scheme !== 'http' && location.scheme !== 'https' ) {
- return this.ACCEPT;
+ if ( type !== this.MAIN_FRAME ) {
+ return this.ACCEPT;
+ }
+
+ context = context.contentWindow || context;
+
+ try {
+ opener = context.opener.location.href;
+ } catch (ex) {}
+
+ let isPopup = location.spec === 'about:blank' && opener;
+
+ if ( location.scheme !== 'data' && !isPopup ) {
+ return this.ACCEPT;
+ }
+ } else if ( type === this.MAIN_FRAME ) {
+ context = context.contentWindow || context;
+
+ try {
+ opener = context.opener.location.href;
+ } catch (ex) {}
+ } else {
+ context = (context.ownerDocument || context).defaultView;
}
- let win = type === 6
- ? context.contentWindow || context
- : (context.ownerDocument || context).defaultView;
+ // The context for the popups is an iframe element here,
+ // so check context.top instead
- if ( win ) {
- getMessageManager(win).sendSyncMessage(this.messageName, {
+ if ( context.top && context.location ) {
+ getMessageManager(context).sendSyncMessage(this.messageName, {
+ opener: opener || null,
url: location.spec,
type: type,
- frameId: type === 6 ? -1 : (win === win.top ? 0 : 1),
- parentFrameId: win === win.top ? -1 : 0
+ frameId: type === this.MAIN_FRAME ? -1 : (context === context.top ? 0 : 1),
+ parentFrameId: context === context.top ? -1 : 0
});
}
return this.ACCEPT;
- }
-};
-
-/******************************************************************************/
-
-const docObserver = {
- contentBaseURI: 'chrome://' + appName + '/content/js/',
-
- QueryInterface: XPCOMUtils.generateQI([
- Ci.nsIObserver,
- Ci.nsISupportsWeakReference
- ]),
+ },
- initContext: function(win, sandbox) {
+ initContentScripts: function(win, sandbox) {
let messager = getMessageManager(win);
if ( sandbox ) {
@@ -173,14 +194,6 @@ const docObserver = {
return win;
},
- register: function() {
- Services.obs.addObserver(this, 'document-element-inserted', true);
- },
-
- unregister: function() {
- Services.obs.removeObserver(this, 'document-element-inserted');
- },
-
observe: function(doc) {
let win = doc.defaultView;
@@ -192,21 +205,21 @@ const docObserver = {
if ( loc.protocol !== 'http:' && loc.protocol !== 'https:' ) {
if ( loc.protocol === 'chrome:' && loc.host === appName ) {
- this.initContext(win);
+ this.initContentScripts(win);
}
return;
}
let lss = Services.scriptloader.loadSubScript;
- win = this.initContext(win, true);
+ win = this.initContentScripts(win, true);
lss(this.contentBaseURI + 'vapi-client.js', win);
lss(this.contentBaseURI + 'contentscript-start.js', win);
let docReady = function(e) {
this.removeEventListener(e.type, docReady, true);
- lss(docObserver.contentBaseURI + 'contentscript-end.js', win);
+ lss(contentObserver.contentBaseURI + 'contentscript-end.js', win);
};
win.document.addEventListener('DOMContentLoaded', docReady, true);
@@ -215,7 +228,6 @@ const docObserver = {
/******************************************************************************/
-contentPolicy.register();
-docObserver.register();
+contentObserver.register();
/******************************************************************************/
diff --git a/platform/firefox/vapi-background.js b/platform/firefox/vapi-background.js
index 563b688..c076b3d 100644
--- a/platform/firefox/vapi-background.js
+++ b/platform/firefox/vapi-background.js
@@ -19,7 +19,7 @@
Home: https://github.com/gorhill/uBlock
*/
-/* global Services, CustomizableUI */
+/* global Services, XPCOMUtils, CustomizableUI */
// For background page
@@ -34,6 +34,7 @@
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu['import']('resource://gre/modules/Services.jsm');
+Cu['import']('resource://gre/modules/XPCOMUtils.jsm');
Cu['import']('resource:///modules/CustomizableUI.jsm');
/******************************************************************************/
@@ -310,7 +311,7 @@ var tabsProgressListener = {
tabId: tabId,
url: browser.currentURI.spec
});
- } else {
+ } else if ( location.scheme === 'http' || location.scheme === 'https' ) {
vAPI.tabs.onNavigation({
frameId: 0,
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) ) {
tabIds = [tabIds];
}
@@ -902,77 +903,178 @@ vAPI.messaging.broadcast = function(message) {
/******************************************************************************/
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,
+ 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: {
url: null,
type: null,
tabId: null,
frameId: null,
- parentFrameId: null
+ parentFrameId: null,
+ opener: null
+ },
+
+ get componentRegistrar() {
+ return Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+ },
+
+ get categoryManager() {
+ return Cc['@mozilla.org/categorymanager;1']
+ .getService(Ci.nsICategoryManager);
},
- QueryInterface: (function() {
- var {XPCOMUtils} = Cu['import']('resource://gre/modules/XPCOMUtils.jsm', {});
- return XPCOMUtils.generateQI([
- Ci.nsIObserver,
- Ci.nsISupportsWeakReference
- ]);
- })(),
+ 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() {
- Services.obs.addObserver(httpObserver, 'http-on-opening-request', true);
- // Services.obs.addObserver(httpObserver, 'http-on-modify-request', true);
- Services.obs.addObserver(httpObserver, 'http-on-examine-response', true);
+ Services.obs.addObserver(this, 'http-on-opening-request', true);
+ Services.obs.addObserver(this, '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() {
- Services.obs.removeObserver(httpObserver, 'http-on-opening-request');
- // Services.obs.removeObserver(httpObserver, 'http-on-modify-request');
- Services.obs.removeObserver(httpObserver, 'http-on-examine-response');
+ Services.obs.removeObserver(this, 'http-on-opening-request');
+ Services.obs.removeObserver(this, 'http-on-examine-response');
+
+ this.componentRegistrar.unregisterFactory(this.classID, this);
+ this.categoryManager.deleteCategoryEntry(
+ 'net-channel-event-sinks',
+ this.contractID,
+ false
+ );
},
- observe: function(httpChannel, topic) {
- // No need for QueryInterface if this check is performed?
- if ( !(httpChannel instanceof Ci.nsIHttpChannel) ) {
- return;
+ handlePopup: function(URI, tabId, sourceTabId) {
+ if ( !sourceTabId ) {
+ 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 URI = httpChannel.URI, tabId, result;
+ 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;
+ }
- if ( topic === 'http-on-modify-request' ) {
- // var onHeadersReceived = vAPI.net.onHeadersReceived;
+ return false;
+ },
+ observe: function(channel, topic) {
+ if ( !(channel instanceof Ci.nsIHttpChannel) ) {
return;
}
- if ( topic === 'http-on-examine-request' ) {
+ var URI = channel.URI;
+ var channelData, result;
+
+ if ( topic === 'http-on-examine-response' ) {
+ if ( !(channel instanceof Ci.nsIWritablePropertyBag) ) {
+ return;
+ }
+
try {
- tabId = httpChannel.getProperty('tabId');
+ channelData = channel.getProperty(location.host + 'reqdata');
} catch (ex) {
return;
}
- if ( !tabId ) {
+ // [tabId, type, sourceTabId - given if it was a popup]
+ if ( !channelData || channelData[0] !== this.MAIN_FRAME ) {
return;
}
topic = 'Content-Security-Policy';
try {
- result = httpChannel.getResponseHeader(topic);
+ result = channel.getResponseHeader(topic);
} catch (ex) {
result = null;
}
result = vAPI.net.onHeadersReceived.callback({
url: URI.spec,
- tabId: tabId,
+ tabId: channelData[1],
parentFrameId: -1,
responseHeaders: result ? [{name: topic, value: result}] : []
});
if ( result ) {
- httpChannel.setResponseHeader(
+ channel.setResponseHeader(
topic,
result.responseHeaders.pop().value,
true
@@ -995,37 +1097,91 @@ var httpObserver = {
// 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 sourceTabId = null;
- var onBeforeRequest = vAPI.net.onBeforeRequest;
+ // popup candidate (only for main_frame type)
+ if ( lastRequest.opener ) {
+ for ( var tab of vAPI.tabs.getAll() ) {
+ var tabURI = tab.linkedBrowser.currentURI;
- if ( !onBeforeRequest.types.has(lastRequest.type) ) {
- return;
- }
+ // not the best approach
+ if ( tabURI.spec === this.lastRequest.opener ) {
+ sourceTabId = vAPI.tabs.getTabId(tab);
+ break;
+ }
+ }
- result = onBeforeRequest.callback({
- url: URI.spec,
- type: lastRequest.type,
- tabId: lastRequest.tabId,
- frameId: lastRequest.frameId,
- parentFrameId: lastRequest.parentFrameId
- });
+ if ( this.handlePopup(channel.URI, lastRequest.tabId, sourceTabId) ) {
+ channel.cancel(this.ABORT);
+ return;
+ }
+ }
- if ( !result || typeof result !== 'object' ) {
+ if ( this.handleRequest(channel, lastRequest) ) {
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)
+ // if request is not handled we may use the data in on-modify-request
+ if ( channel instanceof Ci.nsIWritablePropertyBag ) {
+ channel.setProperty(
+ location.host + 'reqdata',
+ [lastRequest.type, lastRequest.tabId, sourceTabId]
);
}
+ },
+
+ // 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() {
- 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);
var shouldLoadListenerMessageName = location.host + ':shouldLoad';
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;
- lastRequest.url = e.data.url;
- lastRequest.type = typeMap[e.data.type] || 'other';
+ lastRequest.url = details.url;
+ lastRequest.type = details.type;
lastRequest.tabId = vAPI.tabs.getTabId(e.target);
- lastRequest.frameId = e.data.frameId;
- lastRequest.parentFrameId = e.data.parentFrameId;
+ lastRequest.frameId = details.frameId;
+ lastRequest.parentFrameId = details.parentFrameId;
+ lastRequest.opener = details.opener;
};
vAPI.messaging.globalMessageManager.addMessageListener(
@@ -1242,8 +1403,7 @@ window.addEventListener('unload', function() {
// frameModule needs to be cleared too
var frameModule = {};
Cu['import'](vAPI.getURL('frameModule.js'), frameModule);
- frameModule.contentPolicy.unregister();
- frameModule.docObserver.unregister();
+ frameModule.contentObserver.unregister();
Cu.unload(vAPI.getURL('frameModule.js'));
});