2014-12-17 21:33:53 +01: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/}.
Home : https : //github.com/gorhill/uBlock
* /
2014-12-09 21:56:17 +01:00
'use strict' ;
2014-12-17 21:33:53 +01:00
/******************************************************************************/
2015-03-26 22:00:56 +01:00
this . EXPORTED _SYMBOLS = [ 'contentObserver' , 'LocationChangeListener' ] ;
2014-12-09 21:56:17 +01:00
2015-01-08 21:18:05 +01:00
const { interfaces : Ci , utils : Cu } = Components ;
const { Services } = Cu . import ( 'resource://gre/modules/Services.jsm' , null ) ;
2015-03-26 22:00:56 +01:00
const { XPCOMUtils } = Cu . import ( 'resource://gre/modules/XPCOMUtils.jsm' , null ) ;
2015-01-08 21:18:05 +01:00
const hostName = Services . io . newURI ( Components . stack . filename , null , null ) . host ;
2014-12-09 21:56:17 +01:00
2015-05-18 00:27:53 +02:00
//Cu.import('resource://gre/modules/devtools/Console.jsm');
2015-01-26 20:26:45 +01:00
2014-12-17 21:33:53 +01:00
/******************************************************************************/
2015-01-08 21:18:05 +01:00
const getMessageManager = function ( win ) {
2015-05-22 18:19:17 +02:00
let iface = win
2014-12-24 23:11:36 +01:00
. QueryInterface ( Ci . nsIInterfaceRequestor )
. getInterface ( Ci . nsIDocShell )
. sameTypeRootTreeItem
. QueryInterface ( Ci . nsIDocShell )
2015-05-22 18:19:17 +02:00
. QueryInterface ( Ci . nsIInterfaceRequestor ) ;
try {
return iface . getInterface ( Ci . nsIContentFrameMessageManager ) ;
} catch ( ex ) {
// This can throw. It appears `shouldLoad` can be called *after* a
// tab has been closed. For example, a case where this happens all
// the time (FF38):
// - Open twitter.com (assuming you have an account and are logged in)
// - Close twitter.com
// There will be an exception raised when `shouldLoad` is called
// to process a XMLHttpRequest with URL `https://twitter.com/i/jot`
// fired from `https://twitter.com/`, *after* the tab is closed.
// In such case, `win` is `about:blank`.
}
return null ;
2014-12-24 23:11:36 +01:00
} ;
2014-12-09 21:56:17 +01:00
2014-12-17 21:33:53 +01:00
/******************************************************************************/
2015-01-02 18:41:41 +01:00
const contentObserver = {
2015-01-08 21:18:05 +01:00
classDescription : 'content-policy for ' + hostName ,
2015-04-13 01:15:58 +02:00
classID : Components . ID ( '{7afbd130-cbaf-46c2-b944-f5d24305f484}' ) ,
2015-01-08 21:18:05 +01:00
contractID : '@' + hostName + '/content-policy;1' ,
2014-12-09 21:56:17 +01:00
ACCEPT : Ci . nsIContentPolicy . ACCEPT ,
2015-01-02 18:41:41 +01:00
MAIN _FRAME : Ci . nsIContentPolicy . TYPE _DOCUMENT ,
2015-05-17 16:32:40 +02:00
SUB _FRAME : Ci . nsIContentPolicy . TYPE _SUBDOCUMENT ,
2015-01-08 21:18:05 +01:00
contentBaseURI : 'chrome://' + hostName + '/content/js/' ,
cpMessageName : hostName + ':shouldLoad' ,
2015-01-28 21:08:24 +01:00
ignoredPopups : new WeakMap ( ) ,
2015-02-05 18:05:41 +01:00
uniqueSandboxId : 1 ,
2014-12-28 21:26:06 +01:00
2014-12-09 21:56:17 +01:00
get componentRegistrar ( ) {
return Components . manager . QueryInterface ( Ci . nsIComponentRegistrar ) ;
} ,
2014-12-28 21:26:06 +01:00
2014-12-09 21:56:17 +01:00
get categoryManager ( ) {
return Components . classes [ '@mozilla.org/categorymanager;1' ]
. getService ( Ci . nsICategoryManager ) ;
} ,
2014-12-28 21:26:06 +01:00
2015-03-26 22:00:56 +01:00
QueryInterface : XPCOMUtils . generateQI ( [
2015-01-04 13:58:17 +01:00
Ci . nsIFactory ,
Ci . nsIObserver ,
Ci . nsIContentPolicy ,
Ci . nsISupportsWeakReference
2015-03-26 22:00:56 +01:00
] ) ,
2014-12-28 21:26:06 +01:00
2014-12-09 21:56:17 +01:00
createInstance : function ( outer , iid ) {
2014-12-28 21:26:06 +01:00
if ( outer ) {
2014-12-09 21:56:17 +01:00
throw Components . results . NS _ERROR _NO _AGGREGATION ;
}
return this . QueryInterface ( iid ) ;
} ,
2015-01-02 18:41:41 +01:00
2014-12-09 21:56:17 +01:00
register : function ( ) {
2015-01-02 18:41:41 +01:00
Services . obs . addObserver ( this , 'document-element-inserted' , true ) ;
2014-12-09 21:56:17 +01: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 21:26:06 +01:00
2014-12-09 21:56:17 +01:00
unregister : function ( ) {
2015-01-02 18:41:41 +01:00
Services . obs . removeObserver ( this , 'document-element-inserted' ) ;
2014-12-09 21:56:17 +01:00
this . componentRegistrar . unregisterFactory ( this . classID , this ) ;
this . categoryManager . deleteCategoryEntry (
'content-policy' ,
this . contractID ,
false
) ;
} ,
2014-12-28 21:26:06 +01:00
2015-01-27 16:37:02 +01:00
getFrameId : function ( win ) {
return win
. QueryInterface ( Ci . nsIInterfaceRequestor )
. getInterface ( Ci . nsIDOMWindowUtils )
. outerWindowID ;
} ,
2014-12-24 23:11:36 +01:00
// https://bugzil.la/612921
2014-12-09 21:56:17 +01:00
shouldLoad : function ( type , location , origin , context ) {
2014-12-28 10:56:09 +01:00
if ( ! context ) {
2015-01-26 20:38:22 +01:00
return this . ACCEPT ;
2014-12-28 10:56:09 +01:00
}
2015-01-15 13:24:35 +01:00
if ( ! location . schemeIs ( 'http' ) && ! location . schemeIs ( 'https' ) ) {
2015-01-26 20:26:45 +01:00
return this . ACCEPT ;
}
2015-01-02 18:41:41 +01:00
2015-01-27 16:37:02 +01:00
let openerURL = null ;
2015-01-02 18:41:41 +01:00
2015-01-26 20:26:45 +01:00
if ( type === this . MAIN _FRAME ) {
2015-01-02 18:41:41 +01:00
context = context . contentWindow || context ;
2015-05-17 16:32:40 +02:00
if (
context . opener &&
context . opener !== context &&
this . ignoredPopups . has ( context ) === false
) {
2015-01-28 21:08:24 +01:00
openerURL = context . opener . location . href ;
}
2015-05-17 16:32:40 +02:00
} else if ( type === this . SUB _FRAME ) {
2015-02-15 19:25:11 +01:00
context = context . contentWindow ;
2015-01-02 18:41:41 +01:00
} else {
context = ( context . ownerDocument || context ) . defaultView ;
2014-12-09 21:56:17 +01:00
}
2015-01-15 13:24:35 +01:00
// The context for the toolbar popup is an iframe element here,
// so check context.top instead of context
2015-01-26 20:26:45 +01:00
if ( ! context . top || ! context . location ) {
return this . ACCEPT ;
}
2015-01-16 11:42:34 +01:00
2015-02-15 17:16:48 +01:00
let isTopLevel = context === context . top ;
2015-01-27 16:37:02 +01:00
let parentFrameId ;
2015-02-15 19:25:11 +01:00
if ( isTopLevel ) {
2015-01-27 16:37:02 +01:00
parentFrameId = - 1 ;
} else if ( context . parent === context . top ) {
parentFrameId = 0 ;
} else {
parentFrameId = this . getFrameId ( context . parent ) ;
}
2015-01-26 20:26:45 +01:00
let messageManager = getMessageManager ( context ) ;
2015-05-22 18:19:17 +02:00
if ( messageManager === null ) {
return this . ACCEPT ;
}
2015-01-26 20:26:45 +01:00
let details = {
2015-01-27 17:56:04 +01:00
frameId : isTopLevel ? 0 : this . getFrameId ( context ) ,
2015-01-27 16:37:02 +01:00
openerURL : openerURL ,
parentFrameId : parentFrameId ,
2015-05-17 16:32:40 +02:00
rawtype : type ,
2015-01-26 20:26:45 +01:00
url : location . spec
} ;
2015-01-16 11:42:34 +01:00
2015-01-26 20:26:45 +01: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 21:56:17 +01:00
}
return this . ACCEPT ;
2015-01-02 18:41:41 +01:00
} ,
2014-12-28 21:26:06 +01:00
2015-01-02 18:41:41 +01:00
initContentScripts : function ( win , sandbox ) {
2014-12-24 23:11:36 +01:00
let messager = getMessageManager ( win ) ;
2015-02-05 18:05:41 +01:00
let sandboxId = hostName + ':sb:' + this . uniqueSandboxId ++ ;
2014-12-09 21:56:17 +01:00
2014-12-28 21:26:06 +01:00
if ( sandbox ) {
2015-01-08 21:18:05 +01: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 21:56:17 +01:00
sandboxPrototype : win ,
wantComponents : false ,
wantXHRConstructor : false
} ) ;
2014-12-16 13:44:34 +01:00
2015-01-27 13:31:17 +01:00
sandbox . injectScript = function ( script ) {
2015-05-28 20:49:36 +02:00
if ( Services !== undefined ) {
Services . scriptloader . loadSubScript ( script , sandbox ) ;
} else {
// Sandbox appears void.
// I've seen this happens, need to investigate why.
}
2015-01-27 13:31:17 +01:00
} ;
2015-01-08 21:18:05 +01:00
}
else {
sandbox = win ;
}
2014-12-17 08:46:18 +01:00
2015-01-08 21:18:05 +01:00
sandbox . _sandboxId _ = sandboxId ;
sandbox . sendAsyncMessage = messager . sendAsyncMessage ;
2015-01-26 20:26:45 +01:00
2015-01-08 21:18:05 +01:00
sandbox . addMessageListener = function ( callback ) {
2015-01-27 13:31:17 +01:00
if ( sandbox . _messageListener _ ) {
2015-02-05 18:05:41 +01:00
sandbox . removeMessageListener ( ) ;
2015-01-08 21:18:05 +01:00
}
2014-12-09 21:56:17 +01:00
2015-01-27 13:31:17 +01:00
sandbox . _messageListener _ = function ( message ) {
2015-01-08 21:18:05 +01:00
callback ( message . data ) ;
} ;
messager . addMessageListener (
2015-01-27 13:31:17 +01:00
sandbox . _sandboxId _ ,
sandbox . _messageListener _
2015-01-08 21:18:05 +01:00
) ;
messager . addMessageListener (
hostName + ':broadcast' ,
2015-01-27 13:31:17 +01:00
sandbox . _messageListener _
2015-01-08 21:18:05 +01:00
) ;
2015-01-27 13:31:17 +01:00
} ;
2015-01-26 20:26:45 +01:00
2015-01-08 21:18:05 +01:00
sandbox . removeMessageListener = function ( ) {
2015-01-11 18:41:52 +01:00
try {
messager . removeMessageListener (
2015-01-27 13:31:17 +01:00
sandbox . _sandboxId _ ,
sandbox . _messageListener _
2015-01-11 18:41:52 +01:00
) ;
messager . removeMessageListener (
hostName + ':broadcast' ,
2015-01-27 13:31:17 +01:00
sandbox . _messageListener _
2015-01-11 18:41:52 +01:00
) ;
} catch ( ex ) {
// It throws sometimes, mostly when the popup closes
}
2015-01-27 13:31:17 +01:00
sandbox . _messageListener _ = null ;
} ;
2015-01-08 21:18:05 +01:00
return sandbox ;
2014-12-09 21:56:17 +01:00
} ,
2014-12-28 21:26:06 +01:00
2015-01-28 21:08:24 +01: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 13:06:59 +01:00
observe : function ( doc ) {
let win = doc . defaultView ;
2014-12-09 21:56:17 +01:00
2014-12-28 21:26:06 +01:00
if ( ! win ) {
2014-12-09 21:56:17 +01:00
return ;
}
2015-01-28 21:08:24 +01:00
if ( win . opener && this . ignoredPopups . has ( win ) === false ) {
win . addEventListener ( 'keydown' , this . ignorePopup , true ) ;
win . addEventListener ( 'mousedown' , this . ignorePopup , true ) ;
}
2015-05-29 22:40:59 +02:00
// https://github.com/gorhill/uBlock/issues/260
// https://developer.mozilla.org/en-US/docs/Web/API/Document/contentType
// "Non-standard, only supported by Gecko. To be used in
// "chrome code (i.e. Extensions and XUL applications)."
// TODO: We may have to exclude more types, for now let's be
// conservative and focus only on the one issue reported, i.e. let's
// not test against 'text/html'.
if ( doc . contentType . startsWith ( 'image/' ) ) {
return ;
}
2014-12-28 10:56:09 +01:00
let loc = win . location ;
2015-04-02 14:54:06 +02:00
if ( loc . protocol !== 'http:' && loc . protocol !== 'https:' && loc . protocol !== 'file:' ) {
2015-01-08 21:18:05 +01:00
if ( loc . protocol === 'chrome:' && loc . host === hostName ) {
2015-01-02 18:41:41 +01:00
this . initContentScripts ( win ) ;
2014-12-09 21:56:17 +01:00
}
2015-01-04 13:58:17 +01:00
// What about data: and about:blank?
2014-12-09 21:56:17 +01:00
return ;
}
let lss = Services . scriptloader . loadSubScript ;
2015-01-08 21:18:05 +01:00
let sandbox = this . initContentScripts ( win , true ) ;
2014-12-09 21:56:17 +01:00
2015-05-16 22:31:44 +02:00
try {
lss ( this . contentBaseURI + 'vapi-client.js' , sandbox ) ;
lss ( this . contentBaseURI + 'contentscript-start.js' , sandbox ) ;
} catch ( ex ) {
2015-06-04 15:37:53 +02:00
//console.exception(ex.msg, ex.stack);
2015-05-16 22:31:44 +02:00
return ;
}
2014-12-09 21:56:17 +01:00
2015-03-10 13:06:59 +01:00
let docReady = ( e ) => {
let doc = e . target ;
doc . removeEventListener ( e . type , docReady , true ) ;
2015-04-25 15:08:01 +02:00
if ( doc . docShell ) {
// 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
let messageManager = doc . docShell . getInterface ( Ci . nsIContentFrameMessageManager ) ;
messageManager . sendSyncMessage ( locationChangedMessageName , {
url : loc . href ,
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-03-10 13:06:59 +01:00
lss ( this . contentBaseURI + 'contentscript-end.js' , sandbox ) ;
2015-03-09 17:57:52 +01:00
2015-03-10 13:06:59 +01:00
if ( doc . querySelector ( 'a[href^="abp:"]' ) ) {
lss ( this . contentBaseURI + 'subscriber.js' , sandbox ) ;
}
2015-03-09 17:57:52 +01:00
} ;
2015-03-12 18:20:48 +01:00
if ( doc . readyState === 'loading' ) {
doc . addEventListener ( 'DOMContentLoaded' , docReady , true ) ;
} else {
docReady ( { target : doc , type : 'DOMContentLoaded' } ) ;
}
2014-12-09 21:56:17 +01:00
}
} ;
2014-12-17 21:33:53 +01:00
/******************************************************************************/
2015-03-26 22:00:56 +01:00
const locationChangedMessageName = hostName + ':locationChanged' ;
const LocationChangeListener = function ( docShell ) {
2015-05-18 00:27:53 +02:00
if ( ! docShell ) {
return ;
}
2015-03-26 22:00:56 +01:00
2015-05-18 00:27:53 +02:00
var requestor = docShell . QueryInterface ( Ci . nsIInterfaceRequestor ) ;
var ds = requestor . getInterface ( Ci . nsIWebProgress ) ;
var mm = requestor . getInterface ( Ci . nsIContentFrameMessageManager ) ;
2015-03-26 22:00:56 +01:00
2015-05-18 00:27:53 +02:00
if ( ds && mm && typeof mm . sendAsyncMessage === 'function' ) {
this . docShell = ds ;
this . messageManager = mm ;
ds . addProgressListener ( this , Ci . nsIWebProgress . NOTIFY _LOCATION ) ;
2015-03-26 22:00:56 +01:00
}
2015-03-31 13:03:35 +02:00
} ;
2015-03-26 22:00:56 +01:00
2015-05-18 00:27:53 +02:00
LocationChangeListener . prototype . QueryInterface = XPCOMUtils . generateQI ( [
'nsIWebProgressListener' ,
'nsISupportsWeakReference'
] ) ;
2015-03-26 22:00:56 +01:00
LocationChangeListener . prototype . onLocationChange = function ( webProgress , request , location , flags ) {
if ( ! webProgress . isTopLevel ) {
return ;
}
this . messageManager . sendAsyncMessage ( locationChangedMessageName , {
url : location . asciiSpec ,
flags : flags ,
} ) ;
} ;
/******************************************************************************/
2015-01-02 18:41:41 +01:00
contentObserver . register ( ) ;
2014-12-17 21:33:53 +01:00
/******************************************************************************/