@ -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 . clos e = function ( tabIds ) {
vAPI . tabs . remov e = 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' ) ;
channelDa ta = c hannel. getProperty ( location . host + 'reqda ta') ;
} 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 = httpC hannel. getResponseHeader ( topic ) ;
result = c hannel. 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 ) {
httpC hannel. setResponseHeader (
c hannel. 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?
/ * i f ( f l a g s & 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' ) ) ;
} ) ;