2014-12-17 13:33:53 -07:00
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
µBlock - a browser extension to block requests .
Copyright ( C ) 2014 The µBlock 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
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/}.
2015-04-08 16:36:14 -06:00
Home : https : //github.com/chrisaljoudi/uBlock
2014-12-17 13:33:53 -07:00
* /
2014-12-09 13:56:17 -07:00
'use strict' ;
2014-12-17 13:33:53 -07:00
/******************************************************************************/
2015-03-26 15:00:56 -06:00
this . EXPORTED _SYMBOLS = [ 'contentObserver' , 'LocationChangeListener' ] ;
2014-12-09 13:56:17 -07:00
2015-01-08 13:18:05 -07:00
const { interfaces : Ci , utils : Cu } = Components ;
const { Services } = Cu . import ( 'resource://gre/modules/Services.jsm' , null ) ;
2015-03-26 15:00:56 -06:00
const { XPCOMUtils } = Cu . import ( 'resource://gre/modules/XPCOMUtils.jsm' , null ) ;
2015-01-08 13:18:05 -07:00
const hostName = Services . io . newURI ( Components . stack . filename , null , null ) . host ;
2014-12-09 13:56:17 -07:00
2015-01-27 08:37:02 -07:00
// Cu.import('resource://gre/modules/devtools/Console.jsm');
2015-01-26 12:26:45 -07:00
2014-12-17 13:33:53 -07:00
/******************************************************************************/
2015-01-08 13:18:05 -07:00
const getMessageManager = function ( win ) {
return win
2014-12-24 15:11:36 -07:00
. QueryInterface ( Ci . nsIInterfaceRequestor )
. getInterface ( Ci . nsIDocShell )
. sameTypeRootTreeItem
. QueryInterface ( Ci . nsIDocShell )
2014-12-20 09:43:10 -07:00
. QueryInterface ( Ci . nsIInterfaceRequestor )
. getInterface ( Ci . nsIContentFrameMessageManager ) ;
2014-12-24 15:11:36 -07:00
} ;
2014-12-09 13:56:17 -07:00
2014-12-17 13:33:53 -07:00
/******************************************************************************/
2015-01-02 10:41:41 -07:00
const contentObserver = {
2015-01-08 13:18:05 -07:00
classDescription : 'content-policy for ' + hostName ,
2014-12-09 13:56:17 -07:00
classID : Components . ID ( '{e6d173c8-8dbf-4189-a6fd-189e8acffd27}' ) ,
2015-01-08 13:18:05 -07:00
contractID : '@' + hostName + '/content-policy;1' ,
2014-12-09 13:56:17 -07:00
ACCEPT : Ci . nsIContentPolicy . ACCEPT ,
2015-01-02 10:41:41 -07:00
MAIN _FRAME : Ci . nsIContentPolicy . TYPE _DOCUMENT ,
2015-01-08 13:18:05 -07:00
contentBaseURI : 'chrome://' + hostName + '/content/js/' ,
cpMessageName : hostName + ':shouldLoad' ,
2015-01-28 13:08:24 -07:00
ignoredPopups : new WeakMap ( ) ,
2015-02-05 10:05:41 -07:00
uniqueSandboxId : 1 ,
2014-12-28 13:26:06 -07:00
2014-12-09 13:56:17 -07:00
get componentRegistrar ( ) {
return Components . manager . QueryInterface ( Ci . nsIComponentRegistrar ) ;
} ,
2014-12-28 13:26:06 -07:00
2014-12-09 13:56:17 -07:00
get categoryManager ( ) {
return Components . classes [ '@mozilla.org/categorymanager;1' ]
. getService ( Ci . nsICategoryManager ) ;
} ,
2014-12-28 13:26:06 -07:00
2015-03-26 15:00:56 -06:00
QueryInterface : XPCOMUtils . generateQI ( [
2015-01-04 05:58:17 -07:00
Ci . nsIFactory ,
Ci . nsIObserver ,
Ci . nsIContentPolicy ,
Ci . nsISupportsWeakReference
2015-03-26 15:00:56 -06:00
] ) ,
2014-12-28 13:26:06 -07:00
2014-12-09 13:56:17 -07:00
createInstance : function ( outer , iid ) {
2014-12-28 13:26:06 -07:00
if ( outer ) {
2014-12-09 13:56:17 -07:00
throw Components . results . NS _ERROR _NO _AGGREGATION ;
}
return this . QueryInterface ( iid ) ;
} ,
2015-01-02 10:41:41 -07:00
2014-12-09 13:56:17 -07:00
register : function ( ) {
2015-01-02 10:41:41 -07:00
Services . obs . addObserver ( this , 'document-element-inserted' , true ) ;
2014-12-09 13:56:17 -07:00
this . componentRegistrar . registerFactory (
this . classID ,
this . classDescription ,
this . contractID ,
this
) ;
this . categoryManager . addCategoryEntry (
'content-policy' ,
this . contractID ,
this . contractID ,
false ,
true
) ;
} ,
2014-12-28 13:26:06 -07:00
2014-12-09 13:56:17 -07:00
unregister : function ( ) {
2015-01-02 10:41:41 -07:00
Services . obs . removeObserver ( this , 'document-element-inserted' ) ;
2014-12-09 13:56:17 -07:00
this . componentRegistrar . unregisterFactory ( this . classID , this ) ;
this . categoryManager . deleteCategoryEntry (
'content-policy' ,
this . contractID ,
false
) ;
} ,
2014-12-28 13:26:06 -07:00
2015-01-27 08:37:02 -07:00
getFrameId : function ( win ) {
return win
. QueryInterface ( Ci . nsIInterfaceRequestor )
. getInterface ( Ci . nsIDOMWindowUtils )
. outerWindowID ;
} ,
2014-12-24 15:11:36 -07:00
// https://bugzil.la/612921
2014-12-09 13:56:17 -07:00
shouldLoad : function ( type , location , origin , context ) {
2014-12-28 02:56:09 -07:00
if ( ! context ) {
2015-01-26 12:38:22 -07:00
return this . ACCEPT ;
2014-12-28 02:56:09 -07:00
}
2015-01-15 05:24:35 -07:00
if ( ! location . schemeIs ( 'http' ) && ! location . schemeIs ( 'https' ) ) {
2015-01-26 12:26:45 -07:00
return this . ACCEPT ;
}
2015-01-02 10:41:41 -07:00
2015-01-27 08:37:02 -07:00
let openerURL = null ;
2015-01-02 10:41:41 -07:00
2015-01-26 12:26:45 -07:00
if ( type === this . MAIN _FRAME ) {
2015-01-02 10:41:41 -07:00
context = context . contentWindow || context ;
2015-01-28 13:08:24 -07:00
if ( context . opener && context . opener !== context
&& this . ignoredPopups . has ( context ) === false ) {
openerURL = context . opener . location . href ;
}
2015-02-15 11:25:11 -07:00
} else if ( type === 7 ) { // SUB_DOCUMENT
context = context . contentWindow ;
2015-01-02 10:41:41 -07:00
} else {
context = ( context . ownerDocument || context ) . defaultView ;
2014-12-09 13:56:17 -07:00
}
2015-01-15 05:24:35 -07:00
// The context for the toolbar popup is an iframe element here,
// so check context.top instead of context
2015-01-26 12:26:45 -07:00
if ( ! context . top || ! context . location ) {
return this . ACCEPT ;
}
2015-01-16 03:42:34 -07:00
2015-02-15 09:16:48 -07:00
let isTopLevel = context === context . top ;
2015-01-27 08:37:02 -07:00
let parentFrameId ;
2015-02-15 11:25:11 -07:00
if ( isTopLevel ) {
2015-01-27 08:37:02 -07:00
parentFrameId = - 1 ;
} else if ( context . parent === context . top ) {
parentFrameId = 0 ;
} else {
parentFrameId = this . getFrameId ( context . parent ) ;
}
2015-01-26 12:26:45 -07:00
let messageManager = getMessageManager ( context ) ;
let details = {
2015-01-27 09:56:04 -07:00
frameId : isTopLevel ? 0 : this . getFrameId ( context ) ,
2015-01-27 08:37:02 -07:00
openerURL : openerURL ,
parentFrameId : parentFrameId ,
2015-01-26 12:26:45 -07:00
type : type ,
url : location . spec
} ;
2015-01-16 03:42:34 -07:00
2015-01-26 12:26:45 -07:00
if ( typeof messageManager . sendRpcMessage === 'function' ) {
// https://bugzil.la/1092216
messageManager . sendRpcMessage ( this . cpMessageName , details ) ;
} else {
// Compatibility for older versions
messageManager . sendSyncMessage ( this . cpMessageName , details ) ;
2014-12-09 13:56:17 -07:00
}
return this . ACCEPT ;
2015-01-02 10:41:41 -07:00
} ,
2014-12-28 13:26:06 -07:00
2015-01-02 10:41:41 -07:00
initContentScripts : function ( win , sandbox ) {
2014-12-24 15:11:36 -07:00
let messager = getMessageManager ( win ) ;
2015-02-05 10:05:41 -07:00
let sandboxId = hostName + ':sb:' + this . uniqueSandboxId ++ ;
2014-12-09 13:56:17 -07:00
2014-12-28 13:26:06 -07:00
if ( sandbox ) {
2015-01-08 13:18:05 -07:00
let sandboxName = [
win . location . href . slice ( 0 , 100 ) ,
win . document . title . slice ( 0 , 100 )
] . join ( ' | ' ) ;
sandbox = Cu . Sandbox ( [ win ] , {
sandboxName : sandboxId + '[' + sandboxName + ']' ,
2014-12-09 13:56:17 -07:00
sandboxPrototype : win ,
wantComponents : false ,
wantXHRConstructor : false
} ) ;
2014-12-16 05:44:34 -07:00
2015-01-27 05:31:17 -07:00
sandbox . injectScript = function ( script ) {
Services . scriptloader . loadSubScript ( script , sandbox ) ;
} ;
2015-01-08 13:18:05 -07:00
}
else {
sandbox = win ;
}
2014-12-17 00:46:18 -07:00
2015-01-08 13:18:05 -07:00
sandbox . _sandboxId _ = sandboxId ;
sandbox . sendAsyncMessage = messager . sendAsyncMessage ;
2015-01-26 12:26:45 -07:00
2015-01-08 13:18:05 -07:00
sandbox . addMessageListener = function ( callback ) {
2015-01-27 05:31:17 -07:00
if ( sandbox . _messageListener _ ) {
2015-02-05 10:05:41 -07:00
sandbox . removeMessageListener ( ) ;
2015-01-08 13:18:05 -07:00
}
2014-12-09 13:56:17 -07:00
2015-01-27 05:31:17 -07:00
sandbox . _messageListener _ = function ( message ) {
2015-01-08 13:18:05 -07:00
callback ( message . data ) ;
} ;
messager . addMessageListener (
2015-01-27 05:31:17 -07:00
sandbox . _sandboxId _ ,
sandbox . _messageListener _
2015-01-08 13:18:05 -07:00
) ;
messager . addMessageListener (
hostName + ':broadcast' ,
2015-01-27 05:31:17 -07:00
sandbox . _messageListener _
2015-01-08 13:18:05 -07:00
) ;
2015-01-27 05:31:17 -07:00
} ;
2015-01-26 12:26:45 -07:00
2015-01-08 13:18:05 -07:00
sandbox . removeMessageListener = function ( ) {
2015-01-11 10:41:52 -07:00
try {
messager . removeMessageListener (
2015-01-27 05:31:17 -07:00
sandbox . _sandboxId _ ,
sandbox . _messageListener _
2015-01-11 10:41:52 -07:00
) ;
messager . removeMessageListener (
hostName + ':broadcast' ,
2015-01-27 05:31:17 -07:00
sandbox . _messageListener _
2015-01-11 10:41:52 -07:00
) ;
} catch ( ex ) {
// It throws sometimes, mostly when the popup closes
}
2015-01-27 05:31:17 -07:00
sandbox . _messageListener _ = null ;
} ;
2015-01-08 13:18:05 -07:00
return sandbox ;
2014-12-09 13:56:17 -07:00
} ,
2014-12-28 13:26:06 -07:00
2015-01-28 13:08:24 -07:00
ignorePopup : function ( e ) {
if ( e . isTrusted === false ) {
return ;
}
let contObs = contentObserver ;
contObs . ignoredPopups . set ( this , true ) ;
this . removeEventListener ( 'keydown' , contObs . ignorePopup , true ) ;
this . removeEventListener ( 'mousedown' , contObs . ignorePopup , true ) ;
} ,
2015-03-10 06:06:59 -06:00
observe : function ( doc ) {
let win = doc . defaultView ;
2014-12-09 13:56:17 -07:00
2014-12-28 13:26:06 -07:00
if ( ! win ) {
2014-12-09 13:56:17 -07:00
return ;
}
2015-01-28 13:08:24 -07:00
if ( win . opener && this . ignoredPopups . has ( win ) === false ) {
win . addEventListener ( 'keydown' , this . ignorePopup , true ) ;
win . addEventListener ( 'mousedown' , this . ignorePopup , true ) ;
}
2014-12-28 02:56:09 -07:00
let loc = win . location ;
2014-12-28 13:26:06 -07:00
if ( loc . protocol !== 'http:' && loc . protocol !== 'https:' ) {
2015-01-08 13:18:05 -07:00
if ( loc . protocol === 'chrome:' && loc . host === hostName ) {
2015-01-02 10:41:41 -07:00
this . initContentScripts ( win ) ;
2014-12-09 13:56:17 -07:00
}
2015-01-04 05:58:17 -07:00
// What about data: and about:blank?
2014-12-09 13:56:17 -07:00
return ;
}
let lss = Services . scriptloader . loadSubScript ;
2015-01-08 13:18:05 -07:00
let sandbox = this . initContentScripts ( win , true ) ;
2014-12-09 13:56:17 -07:00
2015-01-08 13:18:05 -07:00
lss ( this . contentBaseURI + 'vapi-client.js' , sandbox ) ;
lss ( this . contentBaseURI + 'contentscript-start.js' , sandbox ) ;
2014-12-09 13:56:17 -07:00
2015-03-10 06:06:59 -06:00
let docReady = ( e ) => {
let doc = e . target ;
doc . removeEventListener ( e . type , docReady , true ) ;
2015-04-01 08:09:22 -06:00
2015-04-02 02:35:52 -06:00
// It is possible, in some cases (#1140) for document-element-inserted to occur *before* nsIWebProgressListener.onLocationChange, so ensure that the URL is correct before continuing
2015-04-01 08:09:22 -06:00
let messageManager = doc . docShell . getInterface ( Ci . nsIContentFrameMessageManager ) ;
messageManager . sendSyncMessage ( locationChangedMessageName , {
url : loc . href ,
2015-04-02 02:35:52 -06:00
noRefresh : true , // If the URL is the same, then don't refresh it so that if this occurs after onLocationChange, no the block count isn't reset
2015-04-01 08:09:22 -06:00
} ) ;
2015-03-10 06:06:59 -06:00
lss ( this . contentBaseURI + 'contentscript-end.js' , sandbox ) ;
2015-03-09 10:57:52 -06:00
2015-03-10 06:06:59 -06:00
if ( doc . querySelector ( 'a[href^="abp:"]' ) ) {
lss ( this . contentBaseURI + 'subscriber.js' , sandbox ) ;
}
2015-03-09 10:57:52 -06:00
} ;
2015-03-12 11:20:48 -06:00
if ( doc . readyState === 'loading' ) {
doc . addEventListener ( 'DOMContentLoaded' , docReady , true ) ;
} else {
docReady ( { target : doc , type : 'DOMContentLoaded' } ) ;
}
2014-12-09 13:56:17 -07:00
}
} ;
2014-12-17 13:33:53 -07:00
/******************************************************************************/
2015-03-26 15:00:56 -06:00
const locationChangedMessageName = hostName + ':locationChanged' ;
const LocationChangeListener = function ( docShell ) {
if ( docShell ) {
docShell . QueryInterface ( Ci . nsIInterfaceRequestor ) ;
this . docShell = docShell . getInterface ( Ci . nsIWebProgress ) ;
this . messageManager = docShell . getInterface ( Ci . nsIContentFrameMessageManager ) ;
if ( this . messageManager && typeof this . messageManager . sendAsyncMessage === 'function' ) {
2015-03-26 15:11:54 -06:00
this . docShell . addProgressListener ( this , Ci . nsIWebProgress . NOTIFY _LOCATION ) ;
2015-03-26 15:00:56 -06:00
}
}
2015-03-31 05:03:35 -06:00
} ;
2015-03-26 15:00:56 -06:00
LocationChangeListener . prototype . QueryInterface = XPCOMUtils . generateQI ( [ "nsIWebProgressListener" , "nsISupportsWeakReference" ] ) ;
LocationChangeListener . prototype . onLocationChange = function ( webProgress , request , location , flags ) {
if ( ! webProgress . isTopLevel ) {
return ;
}
this . messageManager . sendAsyncMessage ( locationChangedMessageName , {
url : location . asciiSpec ,
flags : flags ,
} ) ;
} ;
/******************************************************************************/
2015-01-02 10:41:41 -07:00
contentObserver . register ( ) ;
2014-12-17 13:33:53 -07:00
/******************************************************************************/