2014-11-24 12:00:27 -07:00
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2014-12-17 13:33:53 -07:00
µBlock - a browser extension to block requests .
2014-11-24 12:00:27 -07:00
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
* /
2015-01-21 06:59:23 -07:00
/* jshint esnext: true, bitwise: false */
2015-03-16 10:30:05 -06:00
/* global self, Components, punycode, µBlock */
2015-01-15 05:24:35 -07:00
2014-11-24 12:00:27 -07:00
// For background page
/******************************************************************************/
( function ( ) {
'use strict' ;
/******************************************************************************/
const { classes : Cc , interfaces : Ci , utils : Cu } = Components ;
2015-01-08 23:58:07 -07:00
const { Services } = Cu . import ( 'resource://gre/modules/Services.jsm' , null ) ;
2014-11-24 12:00:27 -07:00
/******************************************************************************/
2015-01-14 15:45:55 -07:00
var vAPI = self . vAPI = self . vAPI || { } ;
2014-11-24 12:00:27 -07:00
vAPI . firefox = true ;
2015-02-28 12:18:58 -07:00
vAPI . fennec = Services . appinfo . ID === '{aa3c5121-dab2-40e2-81ca-7ea25febc110}' ;
2015-02-12 11:19:17 -07:00
2014-11-24 12:00:27 -07:00
/******************************************************************************/
2014-12-02 00:35:25 -07:00
vAPI . app = {
2015-04-15 21:25:30 -06:00
name : 'uBlock Origin' ,
2015-01-29 06:12:21 -07:00
version : location . hash . slice ( 1 )
2014-12-02 00:35:25 -07:00
} ;
/******************************************************************************/
2014-12-29 09:54:18 -07:00
vAPI . app . restart = function ( ) {
2015-01-04 05:58:17 -07:00
// Listening in bootstrap.js
Cc [ '@mozilla.org/childprocessmessagemanager;1' ]
. getService ( Ci . nsIMessageSender )
. sendAsyncMessage ( location . host + '-restart' ) ;
2014-12-29 09:54:18 -07:00
} ;
2014-12-07 12:51:49 -07:00
2014-12-16 05:44:34 -07:00
/******************************************************************************/
2015-04-24 11:57:30 -06:00
// Set default preferences for user to find in about:config
vAPI . localStorage . setDefaultBool ( "forceLegacyToolbarButton" , false ) ;
/******************************************************************************/
2014-12-28 13:26:06 -07:00
// List of things that needs to be destroyed when disabling the extension
// Only functions should be added to it
2014-12-16 05:44:34 -07:00
2015-01-12 12:39:23 -07:00
var cleanupTasks = [ ] ;
2014-12-07 12:51:49 -07:00
2015-03-18 05:47:07 -06:00
// This must be updated manually, every time a new task is added/removed
2015-04-09 19:50:23 -06:00
// Fixed by github.com/AlexVallat:
// https://github.com/AlexVallat/uBlock/commit/7b781248f00cbe3d61b1cc367c440db80fa06049
2015-04-24 11:57:30 -06:00
// several instances of cleanupTasks.push, but one is unique to fennec, and three to desktop.
var expectedNumberOfCleanups = vAPI . fennec ? 7 : 9 ;
2015-03-18 05:47:07 -06:00
window . addEventListener ( 'unload' , function ( ) {
2015-05-31 15:43:19 -06:00
if ( typeof vAPI . app . onShutdown === 'function' ) {
vAPI . app . onShutdown ( ) ;
}
2015-03-18 05:47:07 -06:00
for ( var cleanup of cleanupTasks ) {
cleanup ( ) ;
}
if ( cleanupTasks . length < expectedNumberOfCleanups ) {
console . error (
'uBlock> Cleanup tasks performed: %s (out of %s)' ,
cleanupTasks . length ,
expectedNumberOfCleanups
) ;
}
// frameModule needs to be cleared too
var frameModule = { } ;
Cu . import ( vAPI . getURL ( 'frameModule.js' ) , frameModule ) ;
frameModule . contentObserver . unregister ( ) ;
Cu . unload ( vAPI . getURL ( 'frameModule.js' ) ) ;
} ) ;
2014-12-07 12:51:49 -07:00
/******************************************************************************/
2015-06-04 14:26:57 -06:00
// For now, only booleans.
2015-06-01 13:03:22 -06:00
vAPI . browserSettings = {
2015-06-04 14:26:57 -06:00
originalValues : { } ,
rememberOriginalValue : function ( branch , setting ) {
var key = branch + '.' + setting ;
if ( this . originalValues . hasOwnProperty ( key ) ) {
return ;
}
var hasUserValue = false ;
try {
hasUserValue = Services . prefs . getBranch ( branch + '.' ) . prefHasUserValue ( setting ) ;
} catch ( ex ) {
}
this . originalValues [ key ] = hasUserValue ? this . getBool ( branch , setting ) : undefined ;
} ,
clear : function ( branch , setting ) {
2015-06-04 14:50:46 -06:00
var key = branch + '.' + setting ;
// Value was not overriden -- nothing to restore
if ( this . originalValues . hasOwnProperty ( key ) === false ) {
return ;
}
var value = this . originalValues [ key ] ;
2015-06-06 12:09:59 -06:00
// https://github.com/gorhill/uBlock/issues/292#issuecomment-109621979
// Forget the value immediately, it may change outside of
// uBlock control.
delete this . originalValues [ key ] ;
2015-06-04 14:50:46 -06:00
// Original value was a default one
if ( value === undefined ) {
2015-06-04 14:26:57 -06:00
try {
Services . prefs . getBranch ( branch + '.' ) . clearUserPref ( setting ) ;
} catch ( ex ) {
}
return ;
}
2015-06-04 14:50:46 -06:00
// Current value is same as original
2015-06-04 14:26:57 -06:00
if ( this . getBool ( branch , setting ) === value ) {
return ;
}
2015-06-04 14:50:46 -06:00
// Reset to original value
2015-06-04 14:26:57 -06:00
try {
Services . prefs . getBranch ( branch + '.' ) . setBoolPref ( setting , value ) ;
} catch ( ex ) {
}
} ,
getBool : function ( branch , setting ) {
try {
return Services . prefs . getBranch ( branch + '.' ) . getBoolPref ( setting ) ;
} catch ( ex ) {
}
return undefined ;
} ,
2015-06-02 06:59:25 -06:00
setBool : function ( branch , setting , value ) {
try {
2015-06-04 14:26:57 -06:00
Services . prefs . getBranch ( branch + '.' ) . setBoolPref ( setting , value ) ;
2015-06-02 06:59:25 -06:00
} catch ( ex ) {
}
} ,
2015-06-01 13:03:22 -06:00
set : function ( details ) {
2015-06-04 14:26:57 -06:00
var value ;
2015-06-01 13:03:22 -06:00
for ( var setting in details ) {
if ( details . hasOwnProperty ( setting ) === false ) {
continue ;
}
switch ( setting ) {
case 'prefetching' :
2015-06-04 14:26:57 -06:00
this . rememberOriginalValue ( 'network' , 'prefetch-next' ) ;
value = ! ! details [ setting ] ;
// https://github.com/gorhill/uBlock/issues/292
// "true" means "do not disable", i.e. leave entry alone
if ( value === true ) {
this . clear ( 'network' , 'prefetch-next' ) ;
} else {
this . setBool ( 'network' , 'prefetch-next' , false ) ;
}
2015-06-01 13:03:22 -06:00
break ;
2015-06-02 06:26:35 -06:00
case 'hyperlinkAuditing' :
2015-06-04 14:26:57 -06:00
this . rememberOriginalValue ( 'browser' , 'send_pings' ) ;
this . rememberOriginalValue ( 'beacon' , 'enabled' ) ;
value = ! ! details [ setting ] ;
// https://github.com/gorhill/uBlock/issues/292
// "true" means "do not disable", i.e. leave entry alone
if ( value === true ) {
this . clear ( 'browser' , 'send_pings' ) ;
this . clear ( 'beacon' , 'enabled' ) ;
} else {
this . setBool ( 'browser' , 'send_pings' , false ) ;
this . setBool ( 'beacon' , 'enabled' , false ) ;
}
2015-06-02 06:26:35 -06:00
break ;
2015-06-01 13:03:22 -06:00
default :
break ;
}
}
2015-06-04 14:26:57 -06:00
} ,
restoreAll : function ( ) {
var pos ;
for ( var key in this . originalValues ) {
if ( this . originalValues . hasOwnProperty ( key ) === false ) {
continue ;
}
2015-06-05 07:36:24 -06:00
pos = key . lastIndexOf ( '.' ) ;
2015-06-04 14:26:57 -06:00
this . clear ( key . slice ( 0 , pos ) , key . slice ( pos + 1 ) ) ;
}
2015-06-01 13:03:22 -06:00
}
} ;
2015-06-04 14:26:57 -06:00
cleanupTasks . push ( vAPI . browserSettings . restoreAll . bind ( vAPI . browserSettings ) ) ;
2015-06-01 13:03:22 -06:00
/******************************************************************************/
2015-05-27 13:31:36 -06:00
// API matches that of chrome.storage.local:
// https://developer.chrome.com/extensions/storage
vAPI . storage = ( function ( ) {
var db = null ;
2015-05-30 09:36:37 -06:00
var vacuumTimer = null ;
2015-05-27 13:31:36 -06:00
var close = function ( ) {
2015-05-30 09:36:37 -06:00
if ( vacuumTimer !== null ) {
clearTimeout ( vacuumTimer ) ;
vacuumTimer = null ;
}
2015-05-27 13:31:36 -06:00
if ( db === null ) {
return ;
}
db . asyncClose ( ) ;
db = null ;
} ;
var open = function ( ) {
if ( db !== null ) {
2015-05-28 12:49:01 -06:00
return db ;
2015-05-27 13:31:36 -06:00
}
// Create path
2014-11-27 12:45:54 -07:00
var path = Services . dirsvc . get ( 'ProfD' , Ci . nsIFile ) ;
path . append ( 'extension-data' ) ;
2014-12-28 13:26:06 -07:00
if ( ! path . exists ( ) ) {
2014-11-27 12:45:54 -07:00
path . create ( Ci . nsIFile . DIRECTORY _TYPE , parseInt ( '0774' , 8 ) ) ;
}
2014-12-28 13:26:06 -07:00
if ( ! path . isDirectory ( ) ) {
2014-11-27 12:45:54 -07:00
throw Error ( 'Should be a directory...' ) ;
}
2014-12-18 03:59:04 -07:00
path . append ( location . host + '.sqlite' ) ;
2014-12-16 05:44:34 -07:00
2015-05-27 13:31:36 -06:00
// Open database
2015-05-28 12:49:01 -06:00
try {
db = Services . storage . openDatabase ( path ) ;
if ( db . connectionReady === false ) {
db . asyncClose ( ) ;
db = null ;
}
} catch ( ex ) {
}
if ( db === null ) {
return null ;
}
2014-11-27 12:45:54 -07:00
2015-05-27 13:31:36 -06:00
// Database was opened, register cleanup task
cleanupTasks . push ( close ) ;
2014-11-27 12:45:54 -07:00
2015-05-27 13:31:36 -06:00
// Setup database
2015-05-30 09:36:37 -06:00
db . createAsyncStatement ( 'CREATE TABLE IF NOT EXISTS "settings" ("name" TEXT PRIMARY KEY NOT NULL, "value" TEXT);' )
2015-05-27 13:31:36 -06:00
. executeAsync ( ) ;
2015-05-28 12:49:01 -06:00
2015-05-30 09:36:37 -06:00
if ( vacuum !== null ) {
vacuumTimer = vAPI . setTimeout ( vacuum , 60000 ) ;
}
2015-05-28 12:49:01 -06:00
return db ;
2015-05-27 13:31:36 -06:00
} ;
2014-11-27 12:45:54 -07:00
2015-05-30 09:36:37 -06:00
// https://developer.mozilla.org/en-US/docs/Storage/Performance#Vacuuming_and_zero-fill
// Vacuum only once, and only while idle
var vacuum = function ( ) {
vacuumTimer = null ;
if ( db === null ) {
return ;
}
var idleSvc = Cc [ '@mozilla.org/widget/idleservice;1' ]
. getService ( Ci . nsIIdleService ) ;
if ( idleSvc . idleTime < 60000 ) {
vacuumTimer = vAPI . setTimeout ( vacuum , 60000 ) ;
return ;
}
db . createAsyncStatement ( 'VACUUM' ) . executeAsync ( ) ;
vacuum = null ;
} ;
2015-05-27 13:31:36 -06:00
// Execute a query
var runStatement = function ( stmt , callback ) {
var result = { } ;
2014-11-27 12:45:54 -07:00
2015-05-27 13:31:36 -06:00
stmt . executeAsync ( {
2014-11-27 12:45:54 -07:00
handleResult : function ( rows ) {
2014-12-28 13:26:06 -07:00
if ( ! rows || typeof callback !== 'function' ) {
2014-11-27 12:45:54 -07:00
return ;
}
var row ;
2014-12-28 13:26:06 -07:00
while ( row = rows . getNextRow ( ) ) {
2014-11-27 12:45:54 -07:00
// we assume that there will be two columns, since we're
// using it only for preferences
result [ row . getResultByIndex ( 0 ) ] = row . getResultByIndex ( 1 ) ;
}
} ,
handleCompletion : function ( reason ) {
2014-12-28 13:26:06 -07:00
if ( typeof callback === 'function' && reason === 0 ) {
2014-11-27 12:45:54 -07:00
callback ( result ) ;
}
} ,
handleError : function ( error ) {
console . error ( 'SQLite error ' , error . result , error . message ) ;
2015-05-28 12:49:01 -06:00
// Caller expects an answer regardless of failure.
if ( typeof callback === 'function' ) {
callback ( null ) ;
}
2014-11-27 12:45:54 -07:00
}
} ) ;
2015-05-27 13:31:36 -06:00
} ;
2014-11-27 12:45:54 -07:00
2015-05-27 13:31:36 -06:00
var bindNames = function ( stmt , names ) {
if ( Array . isArray ( names ) === false || names . length === 0 ) {
return ;
}
var params = stmt . newBindingParamsArray ( ) ;
var i = names . length , bp ;
while ( i -- ) {
bp = params . newBindingParams ( ) ;
bp . bindByName ( 'name' , names [ i ] ) ;
params . addParams ( bp ) ;
}
stmt . bindParameters ( params ) ;
} ;
var clear = function ( callback ) {
2015-05-28 12:49:01 -06:00
if ( open ( ) === null ) {
if ( typeof callback === 'function' ) {
callback ( ) ;
}
return ;
2015-05-27 13:31:36 -06:00
}
2015-05-30 09:36:37 -06:00
runStatement ( db . createAsyncStatement ( 'DELETE FROM "settings";' ) , callback ) ;
2015-05-27 13:31:36 -06:00
} ;
var getBytesInUse = function ( keys , callback ) {
if ( typeof callback !== 'function' ) {
return ;
}
2014-11-27 12:45:54 -07:00
2015-05-28 12:49:01 -06:00
if ( open ( ) === null ) {
callback ( 0 ) ;
return ;
2015-05-27 13:31:36 -06:00
}
2014-12-28 13:26:06 -07:00
2015-05-27 13:31:36 -06:00
var stmt ;
if ( Array . isArray ( keys ) ) {
2015-05-30 09:36:37 -06:00
stmt = db . createAsyncStatement ( 'SELECT "size" AS "size", SUM(LENGTH("value")) FROM "settings" WHERE "name" = :name' ) ;
2015-05-27 13:31:36 -06:00
bindNames ( keys ) ;
} else {
2015-05-30 09:36:37 -06:00
stmt = db . createAsyncStatement ( 'SELECT "size" AS "size", SUM(LENGTH("value")) FROM "settings"' ) ;
2014-11-27 12:45:54 -07:00
}
2015-05-27 13:31:36 -06:00
runStatement ( stmt , function ( result ) {
callback ( result . size ) ;
} ) ;
} ;
2014-12-28 13:26:06 -07:00
2015-05-27 13:31:36 -06:00
var read = function ( details , callback ) {
2014-12-28 13:26:06 -07:00
if ( typeof callback !== 'function' ) {
2014-11-27 12:45:54 -07:00
return ;
}
2015-05-28 12:49:01 -06:00
var prepareResult = function ( result ) {
var key ;
for ( key in result ) {
if ( result . hasOwnProperty ( key ) === false ) {
continue ;
}
result [ key ] = JSON . parse ( result [ key ] ) ;
}
if ( typeof details === 'object' && details !== null ) {
for ( key in details ) {
if ( result . hasOwnProperty ( key ) === false ) {
result [ key ] = details [ key ] ;
}
}
}
callback ( result ) ;
} ;
if ( open ( ) === null ) {
prepareResult ( { } ) ;
return ;
2015-05-27 13:31:36 -06:00
}
2014-11-27 12:45:54 -07:00
2015-05-27 13:31:36 -06:00
var names = [ ] ;
2014-12-28 13:26:06 -07:00
if ( details !== null ) {
if ( Array . isArray ( details ) ) {
2015-05-27 13:31:36 -06:00
names = details ;
2014-12-28 13:26:06 -07:00
} else if ( typeof details === 'object' ) {
2015-05-27 13:31:36 -06:00
names = Object . keys ( details ) ;
2014-12-28 13:26:06 -07:00
} else {
2015-05-27 13:31:36 -06:00
names = [ details . toString ( ) ] ;
2014-11-27 12:45:54 -07:00
}
}
2015-05-27 13:31:36 -06:00
var stmt ;
if ( names . length === 0 ) {
2015-05-30 09:36:37 -06:00
stmt = db . createAsyncStatement ( 'SELECT * FROM "settings"' ) ;
2015-05-27 13:31:36 -06:00
} else {
2015-05-30 09:36:37 -06:00
stmt = db . createAsyncStatement ( 'SELECT * FROM "settings" WHERE "name" = :name' ) ;
2015-05-27 13:31:36 -06:00
bindNames ( stmt , names ) ;
}
2014-11-27 12:45:54 -07:00
2015-05-28 12:49:01 -06:00
runStatement ( stmt , prepareResult ) ;
2015-05-27 13:31:36 -06:00
} ;
2014-11-27 12:45:54 -07:00
2015-05-27 13:31:36 -06:00
var remove = function ( keys , callback ) {
2015-05-28 12:49:01 -06:00
if ( open ( ) === null ) {
if ( typeof callback === 'function' ) {
callback ( ) ;
}
return ;
2014-11-27 12:45:54 -07:00
}
2015-05-30 09:36:37 -06:00
var stmt = db . createAsyncStatement ( 'DELETE FROM "settings" WHERE "name" = :name' ) ;
2015-05-27 13:31:36 -06:00
bindNames ( stmt , typeof keys === 'string' ? [ keys ] : keys ) ;
runStatement ( stmt , callback ) ;
} ;
var write = function ( details , callback ) {
2015-05-28 12:49:01 -06:00
if ( open ( ) === null ) {
if ( typeof callback === 'function' ) {
callback ( ) ;
}
return ;
2014-11-27 12:45:54 -07:00
}
2015-05-30 09:36:37 -06:00
var stmt = db . createAsyncStatement ( 'INSERT OR REPLACE INTO "settings" ("name", "value") VALUES(:name, :value)' ) ;
2015-05-27 13:31:36 -06:00
var params = stmt . newBindingParamsArray ( ) , bp ;
for ( var key in details ) {
if ( details . hasOwnProperty ( key ) === false ) {
continue ;
}
bp = params . newBindingParams ( ) ;
bp . bindByName ( 'name' , key ) ;
bp . bindByName ( 'value' , JSON . stringify ( details [ key ] ) ) ;
params . addParams ( bp ) ;
2014-11-27 12:45:54 -07:00
}
2015-05-27 13:31:36 -06:00
if ( params . length === 0 ) {
2014-11-27 12:45:54 -07:00
return ;
}
2015-05-27 13:31:36 -06:00
stmt . bindParameters ( params ) ;
runStatement ( stmt , callback ) ;
} ;
// Export API
var api = {
QUOTA _BYTES : 100 * 1024 * 1024 ,
clear : clear ,
get : read ,
getBytesInUse : getBytesInUse ,
remove : remove ,
set : write
} ;
return api ;
} ) ( ) ;
2014-11-27 12:45:54 -07:00
/******************************************************************************/
2015-04-24 11:57:30 -06:00
var windowWatcher = {
onReady : function ( e ) {
if ( e ) {
this . removeEventListener ( e . type , windowWatcher . onReady ) ;
}
var wintype = this . document . documentElement . getAttribute ( 'windowtype' ) ;
if ( wintype !== 'navigator:browser' ) {
return ;
}
var attachToTabBrowser = function ( window , tabBrowser ) {
if ( ! tabBrowser ) {
2015-05-30 05:12:00 -06:00
return ;
}
2015-04-24 11:57:30 -06:00
var tabContainer ;
2015-05-30 05:12:00 -06:00
if ( tabBrowser . deck ) {
// Fennec
tabContainer = tabBrowser . deck ;
} else if ( tabBrowser . tabContainer ) {
// desktop Firefox
tabContainer = tabBrowser . tabContainer ;
2015-04-24 11:57:30 -06:00
vAPI . contextMenu . register ( window . document ) ;
if ( vAPI . toolbarButton . attachToNewWindow ) {
vAPI . toolbarButton . attachToNewWindow ( window ) ;
}
2015-05-30 05:12:00 -06:00
} else {
return ;
}
2015-04-24 11:57:30 -06:00
2015-05-30 05:12:00 -06:00
tabContainer . addEventListener ( 'TabClose' , tabWatcher . onTabClose ) ;
tabContainer . addEventListener ( 'TabSelect' , tabWatcher . onTabSelect ) ;
2015-04-24 11:57:30 -06:00
// when new window is opened TabSelect doesn't run on the selected tab?
}
var win = this ;
var tabBrowser = getTabBrowser ( win ) ;
if ( ! tabBrowser ) {
// On some platforms, the tab browser isn't immediately available, try waiting a bit
win . setTimeout ( function ( ) {
attachToTabBrowser ( win , getTabBrowser ( win ) ) ;
} , 250 ) ;
} else {
attachToTabBrowser ( win , tabBrowser ) ;
}
} ,
observe : function ( win , topic ) {
if ( topic === 'domwindowopened' ) {
win . addEventListener ( 'DOMContentLoaded' , this . onReady ) ;
}
}
} ;
/******************************************************************************/
var tabWatcher = {
onTabClose : function ( { target } ) {
// target is tab in Firefox, browser in Fennec
var tabId = vAPI . tabs . getTabId ( target ) ;
vAPI . tabs . onClosed ( tabId ) ;
delete vAPI . toolbarButton . tabs [ tabId ] ;
} ,
onTabSelect : function ( { target } ) {
vAPI . setIcon ( vAPI . tabs . getTabId ( target ) , getOwnerWindow ( target ) ) ;
} ,
} ;
/******************************************************************************/
vAPI . isBehindTheSceneTabId = function ( tabId ) {
return tabId . toString ( ) === '-1' ;
} ;
vAPI . noTabId = '-1' ;
/******************************************************************************/
2015-02-28 12:18:58 -07:00
var getTabBrowser = function ( win ) {
return vAPI . fennec && win . BrowserApp || win . gBrowser || null ;
} ;
/******************************************************************************/
var getOwnerWindow = function ( target ) {
if ( target . ownerDocument ) {
return target . ownerDocument . defaultView ;
}
// Fennec
for ( var win of vAPI . tabs . getWindows ( ) ) {
for ( var tab of win . BrowserApp . tabs ) {
if ( tab === target || tab . window === target ) {
return win ;
}
}
}
return null ;
} ;
/******************************************************************************/
2015-05-30 22:35:53 -06:00
vAPI . isBehindTheSceneTabId = function ( tabId ) {
return tabId . toString ( ) === '-1' ;
} ;
vAPI . noTabId = '-1' ;
/******************************************************************************/
2015-01-27 03:13:33 -07:00
vAPI . tabs = { } ;
/******************************************************************************/
2014-12-02 00:35:25 -07:00
vAPI . tabs . registerListeners = function ( ) {
2015-05-31 15:43:19 -06:00
tabWatcher . start ( ) ;
2014-12-02 00:35:25 -07:00
} ;
/******************************************************************************/
2015-05-31 15:43:19 -06:00
// Firefox:
2015-06-01 13:03:22 -06:00
// https://developer.mozilla.org/en-US/Add-ons/Code_snippets/Tabbed_browser
2015-05-31 15:43:19 -06:00
//
// browser --> ownerDocument --> defaultView --> gBrowser --> browsers --+
// ^ |
// | |
// +-------------------------------------------------------------------
//
// browser (browser)
// contentTitle
// currentURI
// ownerDocument (XULDocument)
// defaultView (ChromeWindow)
2015-06-01 06:11:25 -06:00
// gBrowser (tabbrowser OR browser)
2015-05-31 15:43:19 -06:00
// browsers (browser)
// selectedBrowser
// selectedTab
// tabs (tab.tabbrowser-tab)
//
2015-06-01 06:11:25 -06:00
// Fennec: (what I figured so far)
2015-05-31 15:43:19 -06:00
//
2015-06-01 06:11:25 -06:00
// tab --> browser windows --> window --> BrowserApp --> tabs --+
// ^ window |
// | |
// +---------------------------------------------------------------+
//
// tab
// browser
// [manual search to go back to tab from list of windows]
2015-05-31 15:43:19 -06:00
2014-12-02 00:35:25 -07:00
vAPI . tabs . get = function ( tabId , callback ) {
2015-06-03 07:01:58 -06:00
var browser ;
2014-12-02 00:35:25 -07:00
2014-12-28 13:26:06 -07:00
if ( tabId === null ) {
2015-04-25 07:08:01 -06:00
win = Services . wm . getMostRecentWindow ( 'navigator:browser' ) ;
var tabBrowser = getTabBrowser ( win ) ;
if ( tabBrowser ) {
tab = tabBrowser . selectedTab ;
tabId = this . getTabId ( tab ) ;
}
2014-12-28 13:26:06 -07:00
} else {
2015-05-31 15:43:19 -06:00
browser = tabWatcher . browserFromTabId ( tabId ) ;
2014-12-02 00:35:25 -07:00
}
2015-03-01 13:38:51 -07:00
// For internal use
2015-01-08 13:11:43 -07:00
if ( typeof callback !== 'function' ) {
2015-05-31 15:43:19 -06:00
return browser ;
2014-12-16 14:31:03 -07:00
}
2015-05-31 15:43:19 -06:00
if ( ! browser ) {
2014-12-02 00:35:25 -07:00
callback ( ) ;
return ;
}
2015-06-03 07:01:58 -06:00
var win = getOwnerWindow ( browser ) ;
2015-02-28 12:18:58 -07:00
var tabBrowser = getTabBrowser ( win ) ;
2015-06-03 07:01:58 -06:00
var windows = this . getWindows ( ) ;
2015-02-28 12:18:58 -07:00
callback ( {
id : tabId ,
2015-05-31 15:43:19 -06:00
index : tabWatcher . indexFromTarget ( browser ) ,
2015-02-28 12:18:58 -07:00
windowId : windows . indexOf ( win ) ,
2015-05-31 15:43:19 -06:00
active : browser === tabBrowser . selectedBrowser ,
2015-02-28 12:18:58 -07:00
url : browser . currentURI . asciiSpec ,
2015-05-31 15:43:19 -06:00
title : browser . contentTitle
2015-02-28 12:18:58 -07:00
} ) ;
2014-12-02 00:35:25 -07:00
} ;
/******************************************************************************/
vAPI . tabs . getAll = function ( window ) {
2015-02-28 12:18:58 -07:00
var win , tab ;
var tabs = [ ] ;
2014-12-02 00:35:25 -07:00
2014-12-28 13:26:06 -07:00
for ( win of this . getWindows ( ) ) {
if ( window && window !== win ) {
2014-12-02 00:35:25 -07:00
continue ;
}
2015-02-28 12:18:58 -07:00
var tabBrowser = getTabBrowser ( win ) ;
if ( tabBrowser === null ) {
continue ;
2015-02-12 11:19:17 -07:00
}
2015-02-28 12:18:58 -07:00
for ( tab of tabBrowser . tabs ) {
2014-12-02 00:35:25 -07:00
tabs . push ( tab ) ;
}
}
return tabs ;
} ;
/******************************************************************************/
vAPI . tabs . getWindows = function ( ) {
var winumerator = Services . wm . getEnumerator ( 'navigator:browser' ) ;
var windows = [ ] ;
2014-12-28 13:26:06 -07:00
while ( winumerator . hasMoreElements ( ) ) {
2014-12-02 00:35:25 -07:00
var win = winumerator . getNext ( ) ;
2014-12-28 13:26:06 -07:00
if ( ! win . closed ) {
2014-12-02 00:35:25 -07:00
windows . push ( win ) ;
}
}
return windows ;
} ;
/******************************************************************************/
// properties of the details object:
// url: 'URL', // the address that will be opened
// tabId: 1, // the tab is used if set, instead of creating a new one
// index: -1, // undefined: end of the list, -1: following tab, or after index
// active: false, // opens the tab in background - true and undefined: foreground
// select: true // if a tab is already opened with that url, then select it instead of opening a new one
vAPI . tabs . open = function ( details ) {
2014-12-28 13:26:06 -07:00
if ( ! details . url ) {
2014-12-02 00:35:25 -07:00
return null ;
}
// extension pages
2014-12-28 13:26:06 -07:00
if ( /^[\w-]{2,}:/ . test ( details . url ) === false ) {
2014-12-02 00:35:25 -07:00
details . url = vAPI . getURL ( details . url ) ;
}
2015-06-01 07:00:10 -06:00
var tab ;
2014-12-02 00:35:25 -07:00
2014-12-28 13:26:06 -07:00
if ( details . select ) {
2015-01-15 05:24:35 -07:00
var URI = Services . io . newURI ( details . url , null , null ) ;
2014-12-02 00:35:25 -07:00
2015-02-28 12:18:58 -07:00
for ( tab of this . getAll ( ) ) {
2015-05-31 15:43:19 -06:00
var browser = tabWatcher . browserFromTarget ( tab ) ;
2014-12-02 00:35:25 -07:00
2015-01-15 05:24:35 -07:00
// Or simply .equals if we care about the fragment
2015-02-28 12:18:58 -07:00
if ( URI . equalsExceptRef ( browser . currentURI ) === false ) {
continue ;
2014-12-02 00:35:25 -07:00
}
2015-02-28 12:18:58 -07:00
2015-03-12 11:48:43 -06:00
this . select ( tab ) ;
2015-02-28 12:18:58 -07:00
return ;
2014-12-02 00:35:25 -07:00
}
}
2014-12-28 13:26:06 -07:00
if ( details . active === undefined ) {
2014-12-02 00:35:25 -07:00
details . active = true ;
}
2015-02-28 12:18:58 -07:00
if ( details . tabId ) {
2015-05-31 15:43:19 -06:00
tab = tabWatcher . browserFromTabId ( details . tabId ) ;
2015-03-16 10:30:05 -06:00
if ( tab ) {
2015-05-31 15:43:19 -06:00
tabWatcher . browserFromTarget ( tab ) . loadURI ( details . url ) ;
2015-03-16 10:30:05 -06:00
return ;
2015-02-12 11:19:17 -07:00
}
2015-02-28 12:18:58 -07:00
}
2015-02-12 11:19:17 -07:00
2015-06-01 07:00:10 -06:00
var win = Services . wm . getMostRecentWindow ( 'navigator:browser' ) ;
var tabBrowser = getTabBrowser ( win ) ;
2015-02-12 11:19:17 -07:00
2015-02-28 12:18:58 -07:00
if ( vAPI . fennec ) {
tabBrowser . addTab ( details . url , { selected : details . active !== false } ) ;
// Note that it's impossible to move tabs on Fennec, so don't bother
return ;
}
2015-02-12 11:19:17 -07:00
2015-02-28 12:18:58 -07:00
if ( details . index === - 1 ) {
details . index = tabBrowser . browsers . indexOf ( tabBrowser . selectedBrowser ) + 1 ;
}
2015-02-12 11:19:17 -07:00
2015-02-28 12:18:58 -07:00
tab = tabBrowser . loadOneTab ( details . url , { inBackground : ! details . active } ) ;
2015-02-12 11:19:17 -07:00
2015-02-28 12:18:58 -07:00
if ( details . index !== undefined ) {
tabBrowser . moveTabTo ( tab , details . index ) ;
}
} ;
2014-12-02 00:35:25 -07:00
2015-02-28 12:18:58 -07:00
/******************************************************************************/
2014-12-02 00:35:25 -07:00
2015-03-27 11:00:55 -06:00
// Replace the URL of a tab. Noop if the tab does not exist.
vAPI . tabs . replace = function ( tabId , url ) {
var targetURL = url ;
// extension pages
if ( /^[\w-]{2,}:/ . test ( targetURL ) !== true ) {
targetURL = vAPI . getURL ( targetURL ) ;
}
2015-06-01 07:00:10 -06:00
var browser = tabWatcher . browserFromTabId ( tabId ) ;
if ( browser ) {
browser . loadURI ( targetURL ) ;
2015-03-27 11:00:55 -06:00
}
} ;
/******************************************************************************/
2015-02-28 12:18:58 -07:00
vAPI . tabs . _remove = function ( tab , tabBrowser ) {
if ( vAPI . fennec ) {
tabBrowser . closeTab ( tab ) ;
return ;
2014-12-02 00:35:25 -07:00
}
2015-02-28 12:18:58 -07:00
tabBrowser . removeTab ( tab ) ;
2014-12-02 00:35:25 -07:00
} ;
/******************************************************************************/
2015-05-30 22:35:53 -06:00
vAPI . tabs . remove = function ( tabId ) {
2015-06-01 06:49:41 -06:00
var browser = tabWatcher . browserFromTabId ( tabId ) ;
if ( ! browser ) {
2015-03-16 10:30:05 -06:00
return ;
}
2015-06-01 06:49:41 -06:00
var tab = tabWatcher . tabFromBrowser ( browser ) ;
if ( ! tab ) {
return ;
2014-12-02 00:35:25 -07:00
}
2015-06-01 06:49:41 -06:00
this . _remove ( tab , getTabBrowser ( getOwnerWindow ( browser ) ) ) ;
2014-12-02 00:35:25 -07:00
} ;
/******************************************************************************/
2015-01-08 13:11:43 -07:00
vAPI . tabs . reload = function ( tabId ) {
2015-06-03 07:01:58 -06:00
var browser = tabWatcher . browserFromTabId ( tabId ) ;
if ( ! browser ) {
2015-02-28 12:18:58 -07:00
return ;
}
2015-06-03 07:01:58 -06:00
browser . webNavigation . reload ( Ci . nsIWebNavigation . LOAD _FLAGS _BYPASS _CACHE ) ;
2015-01-08 13:11:43 -07:00
} ;
/******************************************************************************/
2015-03-12 11:48:43 -06:00
vAPI . tabs . select = function ( tab ) {
2015-06-03 07:01:58 -06:00
if ( typeof tab !== 'object' ) {
tab = tabWatcher . tabFromBrowser ( tabWatcher . browserFromTabId ( tab ) ) ;
}
2015-03-02 11:49:34 -07:00
if ( ! tab ) {
return ;
}
var tabBrowser = getTabBrowser ( getOwnerWindow ( tab ) ) ;
2015-03-12 11:48:43 -06:00
if ( vAPI . fennec ) {
2015-03-02 11:49:34 -07:00
tabBrowser . selectTab ( tab ) ;
} else {
tabBrowser . selectedTab = tab ;
}
} ;
/******************************************************************************/
2014-12-16 05:44:34 -07:00
vAPI . tabs . injectScript = function ( tabId , details , callback ) {
2015-06-03 07:01:58 -06:00
var browser = tabWatcher . browserFromTabId ( tabId ) ;
if ( ! browser ) {
2014-12-16 14:31:03 -07:00
return ;
}
2015-01-27 05:31:17 -07:00
if ( typeof details . file !== 'string' ) {
return ;
2014-12-28 02:56:09 -07:00
}
2015-01-27 05:31:17 -07:00
details . file = vAPI . getURL ( details . file ) ;
2015-06-03 07:01:58 -06:00
browser . messageManager . sendAsyncMessage (
2014-12-18 03:59:04 -07:00
location . host + ':broadcast' ,
2014-12-16 14:31:03 -07:00
JSON . stringify ( {
broadcast : true ,
2014-12-28 13:26:06 -07:00
channelName : 'vAPI' ,
2014-12-16 14:31:03 -07:00
msg : {
cmd : 'injectScript' ,
details : details
}
} )
) ;
2014-12-28 13:26:06 -07:00
if ( typeof callback === 'function' ) {
2015-05-17 11:02:56 -06:00
vAPI . setTimeout ( callback , 13 ) ;
2014-12-16 14:31:03 -07:00
}
2014-12-16 05:44:34 -07:00
} ;
/******************************************************************************/
2015-05-31 15:43:19 -06:00
var tabWatcher = ( function ( ) {
// TODO: find out whether we need a janitor to take care of stale entries.
var browserToTabIdMap = new Map ( ) ;
var tabIdToBrowserMap = new Map ( ) ;
var tabIdGenerator = 1 ;
var indexFromBrowser = function ( browser ) {
var win = getOwnerWindow ( browser ) ;
if ( ! win ) {
return - 1 ;
}
var tabbrowser = getTabBrowser ( win ) ;
if ( ! tabbrowser ) {
return - 1 ;
}
2015-06-01 06:11:25 -06:00
// This can happen, for example, the `view-source:` window, there is
// no tabbrowser object, the browser object sits directly in the
// window.
if ( tabbrowser === browser ) {
return 0 ;
}
2015-06-02 16:12:28 -06:00
// Fennec
// https://developer.mozilla.org/en-US/Add-ons/Firefox_for_Android/API/BrowserApp
if ( vAPI . fennec ) {
return tabbrowser . tabs . indexOf ( tabbrowser . getTabForBrowser ( browser ) ) ;
}
return tabbrowser . browsers . indexOf ( browser ) ;
2015-05-31 15:43:19 -06:00
} ;
var indexFromTarget = function ( target ) {
return indexFromBrowser ( browserFromTarget ( target ) ) ;
} ;
2015-06-01 06:49:41 -06:00
var tabFromBrowser = function ( browser ) {
var i = indexFromBrowser ( browser ) ;
if ( i === - 1 ) {
return null ;
}
var win = getOwnerWindow ( browser ) ;
if ( ! win ) {
return null ;
}
var tabbrowser = getTabBrowser ( win ) ;
if ( ! tabbrowser ) {
return null ;
}
if ( ! tabbrowser . tabs || i >= tabbrowser . tabs . length ) {
return null ;
}
return tabbrowser . tabs [ i ] ;
} ;
2015-05-31 15:43:19 -06:00
var browserFromTarget = function ( target ) {
if ( ! target ) {
return null ;
2015-05-30 22:35:53 -06:00
}
2015-05-31 15:43:19 -06:00
if ( vAPI . fennec ) {
if ( target . browser ) { // target is a tab
target = target . browser ;
}
} else if ( target . linkedPanel ) { // target is a tab
target = target . linkedBrowser ;
}
if ( target . localName !== 'browser' ) {
return null ;
}
return target ;
} ;
2015-05-30 22:35:53 -06:00
2015-05-31 15:43:19 -06:00
var tabIdFromTarget = function ( target ) {
var browser = browserFromTarget ( target ) ;
if ( browser === null ) {
return vAPI . noTabId ;
}
var tabId = browserToTabIdMap . get ( browser ) ;
if ( tabId === undefined ) {
2015-06-04 07:37:53 -06:00
tabId = '' + tabIdGenerator ++ ;
2015-05-31 15:43:19 -06:00
browserToTabIdMap . set ( browser , tabId ) ;
tabIdToBrowserMap . set ( tabId , browser ) ;
}
return tabId ;
} ;
var browserFromTabId = function ( tabId ) {
var browser = tabIdToBrowserMap . get ( tabId ) ;
if ( browser === undefined ) {
return null ;
}
// Verify that the browser is still live
if ( indexFromBrowser ( browser ) !== - 1 ) {
return browser ;
}
removeBrowserEntry ( tabId , browser ) ;
return null ;
} ;
2015-06-03 07:01:58 -06:00
var currentBrowser = function ( ) {
var win = Services . wm . getMostRecentWindow ( 'navigator:browser' ) ;
return browserFromTarget ( getTabBrowser ( win ) . selectedTab ) ;
} ;
2015-05-31 15:43:19 -06:00
var removeBrowserEntry = function ( tabId , browser ) {
if ( tabId && tabId !== vAPI . noTabId ) {
vAPI . tabs . onClosed ( tabId ) ;
delete vAPI . toolbarButton . tabs [ tabId ] ;
tabIdToBrowserMap . delete ( tabId ) ;
}
if ( browser ) {
browserToTabIdMap . delete ( browser ) ;
}
} ;
// https://developer.mozilla.org/en-US/docs/Web/Events/TabOpen
var onOpen = function ( { target } ) {
var tabId = tabIdFromTarget ( target ) ;
var browser = browserFromTabId ( tabId ) ;
vAPI . tabs . onNavigation ( {
frameId : 0 ,
tabId : tabId ,
url : browser . currentURI . asciiSpec ,
} ) ;
} ;
2015-05-30 22:35:53 -06:00
2015-05-31 15:43:19 -06:00
// https://developer.mozilla.org/en-US/docs/Web/Events/TabShow
var onShow = function ( { target } ) {
tabIdFromTarget ( target ) ;
} ;
// https://developer.mozilla.org/en-US/docs/Web/Events/TabClose
var onClose = function ( { target } ) {
// target is tab in Firefox, browser in Fennec
var browser = browserFromTarget ( target ) ;
var tabId = browserToTabIdMap . get ( browser ) ;
removeBrowserEntry ( tabId , browser ) ;
} ;
// https://developer.mozilla.org/en-US/docs/Web/Events/TabSelect
var onSelect = function ( { target } ) {
vAPI . setIcon ( tabIdFromTarget ( target ) , getOwnerWindow ( target ) ) ;
} ;
var onWindowLoad = function ( ev ) {
if ( ev ) {
this . removeEventListener ( ev . type , onWindowLoad ) ;
}
var wintype = this . document . documentElement . getAttribute ( 'windowtype' ) ;
2015-05-30 22:35:53 -06:00
if ( wintype !== 'navigator:browser' ) {
return ;
}
var tabBrowser = getTabBrowser ( this ) ;
if ( ! tabBrowser ) {
return ;
}
2015-05-31 15:43:19 -06:00
var tabContainer ;
2015-05-30 22:35:53 -06:00
if ( tabBrowser . deck ) {
// Fennec
tabContainer = tabBrowser . deck ;
} else if ( tabBrowser . tabContainer ) {
// desktop Firefox
tabContainer = tabBrowser . tabContainer ;
vAPI . contextMenu . register ( this . document ) ;
} else {
return ;
}
2015-05-31 15:43:19 -06:00
tabContainer . addEventListener ( 'TabOpen' , onOpen ) ;
tabContainer . addEventListener ( 'TabShow' , onShow ) ;
tabContainer . addEventListener ( 'TabClose' , onClose ) ;
tabContainer . addEventListener ( 'TabSelect' , onSelect ) ;
2015-05-30 22:35:53 -06:00
// when new window is opened TabSelect doesn't run on the selected tab?
2015-05-31 15:43:19 -06:00
} ;
2015-05-30 22:35:53 -06:00
2015-05-31 15:43:19 -06:00
var onWindowUnload = function ( ) {
vAPI . contextMenu . unregister ( this . document ) ;
this . removeEventListener ( 'DOMContentLoaded' , onWindowLoad ) ;
2015-05-30 22:35:53 -06:00
2015-05-31 15:43:19 -06:00
var tabBrowser = getTabBrowser ( this ) ;
if ( ! tabBrowser ) {
return ;
2015-05-30 22:35:53 -06:00
}
2015-05-31 15:43:19 -06:00
var tabContainer = null ;
if ( tabBrowser . deck ) {
// Fennec
tabContainer = tabBrowser . deck ;
} else if ( tabBrowser . tabContainer ) {
tabContainer = tabBrowser . tabContainer ;
2015-05-30 22:35:53 -06:00
}
2015-05-31 15:43:19 -06:00
if ( tabContainer ) {
tabContainer . removeEventListener ( 'TabOpen' , onOpen ) ;
tabContainer . removeEventListener ( 'TabShow' , onShow ) ;
tabContainer . removeEventListener ( 'TabClose' , onClose ) ;
tabContainer . removeEventListener ( 'TabSelect' , onSelect ) ;
2015-05-30 22:35:53 -06:00
}
2015-05-31 15:43:19 -06:00
// Close extension tabs
var browser , URI , tabId ;
for ( var tab of tabBrowser . tabs ) {
browser = tabWatcher . browserFromTarget ( tab ) ;
if ( browser === null ) {
continue ;
}
URI = browser . currentURI ;
if ( URI . schemeIs ( 'chrome' ) && URI . host === location . host ) {
vAPI . tabs . _remove ( tab , getTabBrowser ( this ) ) ;
}
browser = browserFromTarget ( tab ) ;
tabId = browserToTabIdMap . get ( browser ) ;
if ( tabId !== undefined ) {
tabIdToBrowserMap . delete ( tabId ) ;
}
browserToTabIdMap . delete ( browser ) ;
2015-05-30 22:35:53 -06:00
}
} ;
2015-05-31 15:43:19 -06:00
// https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIWindowWatcher
var windowWatcher = {
observe : function ( win , topic ) {
if ( topic === 'domwindowopened' ) {
win . addEventListener ( 'DOMContentLoaded' , onWindowLoad ) ;
2015-05-30 22:35:53 -06:00
}
}
} ;
// Initialize map with existing active tabs
2015-05-31 15:43:19 -06:00
var start = function ( ) {
var tabBrowser , tab ;
for ( var win of vAPI . tabs . getWindows ( ) ) {
onWindowLoad . call ( win ) ;
tabBrowser = getTabBrowser ( win ) ;
if ( tabBrowser === null ) {
2015-05-30 22:35:53 -06:00
continue ;
}
2015-05-31 15:43:19 -06:00
for ( tab of tabBrowser . tabs ) {
if ( vAPI . fennec || ! tab . hasAttribute ( 'pending' ) ) {
tabIdFromTarget ( tab ) ;
}
}
2015-05-30 22:35:53 -06:00
}
2015-05-31 15:43:19 -06:00
Services . ww . registerNotification ( windowWatcher ) ;
2015-05-30 22:35:53 -06:00
} ;
2015-05-31 15:43:19 -06:00
var stop = function ( ) {
Services . ww . unregisterNotification ( windowWatcher ) ;
2015-05-30 22:35:53 -06:00
2015-05-31 15:43:19 -06:00
for ( var win of vAPI . tabs . getWindows ( ) ) {
onWindowUnload . call ( win ) ;
}
2015-05-30 22:35:53 -06:00
2015-05-31 15:43:19 -06:00
browserToTabIdMap . clear ( ) ;
tabIdToBrowserMap . clear ( ) ;
2015-05-30 22:35:53 -06:00
} ;
2015-05-31 15:43:19 -06:00
cleanupTasks . push ( stop ) ;
2015-05-30 22:35:53 -06:00
return {
2015-06-02 16:12:28 -06:00
browsers : function ( ) { return browserToTabIdMap . keys ( ) ; } ,
2015-05-31 15:43:19 -06:00
browserFromTabId : browserFromTabId ,
2015-06-03 07:01:58 -06:00
browserFromTarget : browserFromTarget ,
currentBrowser : currentBrowser ,
2015-06-01 06:49:41 -06:00
indexFromTarget : indexFromTarget ,
2015-06-03 07:01:58 -06:00
start : start ,
tabFromBrowser : tabFromBrowser ,
tabIdFromTarget : tabIdFromTarget
2015-05-30 22:35:53 -06:00
} ;
} ) ( ) ;
/******************************************************************************/
2014-12-26 13:41:44 -07:00
vAPI . setIcon = function ( tabId , iconStatus , badge ) {
// If badge is undefined, then setIcon was called from the TabSelect event
2015-01-11 10:41:52 -07:00
var win = badge === undefined
2014-12-26 13:41:44 -07:00
? iconStatus
: Services . wm . getMostRecentWindow ( 'navigator:browser' ) ;
2015-05-31 15:43:19 -06:00
var curTabId = tabWatcher . tabIdFromTarget ( getTabBrowser ( win ) . selectedTab ) ;
2015-01-11 10:41:52 -07:00
var tb = vAPI . toolbarButton ;
2014-12-16 05:44:34 -07:00
// from 'TabSelect' event
2014-12-28 13:26:06 -07:00
if ( tabId === undefined ) {
2014-12-16 05:44:34 -07:00
tabId = curTabId ;
2014-12-28 13:26:06 -07:00
} else if ( badge !== undefined ) {
2015-01-27 03:13:33 -07:00
tb . tabs [ tabId ] = { badge : badge , img : iconStatus === 'on' } ;
2014-12-16 05:44:34 -07:00
}
2015-03-12 11:48:43 -06:00
if ( tabId === curTabId ) {
tb . updateState ( win , tabId ) ;
2014-12-16 05:44:34 -07:00
}
2014-12-07 12:51:49 -07:00
} ;
/******************************************************************************/
2014-11-24 12:00:27 -07:00
vAPI . messaging = {
2014-12-16 05:44:34 -07:00
get globalMessageManager ( ) {
return Cc [ '@mozilla.org/globalmessagemanager;1' ]
. getService ( Ci . nsIMessageListenerManager ) ;
} ,
frameScript : vAPI . getURL ( 'frameScript.js' ) ,
2014-11-24 12:00:27 -07:00
listeners : { } ,
defaultHandler : null ,
NOOPFUNC : function ( ) { } ,
UNHANDLED : 'vAPI.messaging.notHandled'
} ;
/******************************************************************************/
vAPI . messaging . listen = function ( listenerName , callback ) {
this . listeners [ listenerName ] = callback ;
} ;
/******************************************************************************/
2014-12-25 06:53:30 -07:00
vAPI . messaging . onMessage = function ( { target , data } ) {
var messageManager = target . messageManager ;
2014-12-16 05:44:34 -07:00
2014-12-28 13:26:06 -07:00
if ( ! messageManager ) {
2014-12-16 05:44:34 -07:00
// Message came from a popup, and its message manager is not usable.
// So instead we broadcast to the parent window.
2015-02-28 12:18:58 -07:00
messageManager = getOwnerWindow (
target . webNavigation . QueryInterface ( Ci . nsIDocShell ) . chromeEventHandler
) . messageManager ;
2014-12-16 05:44:34 -07:00
}
2015-02-01 08:15:35 -07:00
var channelNameRaw = data . channelName ;
var pos = channelNameRaw . indexOf ( '|' ) ;
var channelName = channelNameRaw . slice ( pos + 1 ) ;
2014-11-24 12:00:27 -07:00
var callback = vAPI . messaging . NOOPFUNC ;
2015-02-01 08:15:35 -07:00
if ( data . requestId !== undefined ) {
callback = CallbackWrapper . factory (
messageManager ,
channelName ,
channelNameRaw . slice ( 0 , pos ) ,
data . requestId
) . callback ;
2014-11-24 12:00:27 -07:00
}
var sender = {
tab : {
2015-05-31 15:43:19 -06:00
id : tabWatcher . tabIdFromTarget ( target )
2014-11-24 12:00:27 -07:00
}
} ;
// Specific handler
var r = vAPI . messaging . UNHANDLED ;
2014-12-28 13:26:06 -07:00
var listener = vAPI . messaging . listeners [ channelName ] ;
2014-11-24 12:00:27 -07:00
if ( typeof listener === 'function' ) {
2014-12-25 06:53:30 -07:00
r = listener ( data . msg , sender , callback ) ;
2014-11-24 12:00:27 -07:00
}
if ( r !== vAPI . messaging . UNHANDLED ) {
return ;
}
// Default handler
2014-12-25 06:53:30 -07:00
r = vAPI . messaging . defaultHandler ( data . msg , sender , callback ) ;
2014-11-24 12:00:27 -07:00
if ( r !== vAPI . messaging . UNHANDLED ) {
return ;
}
2015-03-18 05:47:07 -06:00
console . error ( 'uBlock> messaging > unknown request: %o' , data ) ;
2014-11-24 12:00:27 -07:00
// Unhandled:
// Need to callback anyways in case caller expected an answer, or
// else there is a memory leak on caller's side
callback ( ) ;
} ;
/******************************************************************************/
vAPI . messaging . setup = function ( defaultHandler ) {
// Already setup?
if ( this . defaultHandler !== null ) {
return ;
}
if ( typeof defaultHandler !== 'function' ) {
defaultHandler = function ( ) { return vAPI . messaging . UNHANDLED ; } ;
}
this . defaultHandler = defaultHandler ;
2014-12-16 05:44:34 -07:00
this . globalMessageManager . addMessageListener (
2014-12-18 03:59:04 -07:00
location . host + ':background' ,
2014-12-07 12:51:49 -07:00
this . onMessage
) ;
2014-12-16 05:44:34 -07:00
2014-12-28 02:56:09 -07:00
this . globalMessageManager . loadFrameScript ( this . frameScript , true ) ;
2014-12-16 05:44:34 -07:00
2015-01-12 09:45:09 -07:00
cleanupTasks . push ( function ( ) {
2014-12-16 05:44:34 -07:00
var gmm = vAPI . messaging . globalMessageManager ;
gmm . removeDelayedFrameScript ( vAPI . messaging . frameScript ) ;
gmm . removeMessageListener (
2014-12-18 03:59:04 -07:00
location . host + ':background' ,
2014-12-16 05:44:34 -07:00
vAPI . messaging . onMessage
) ;
2015-05-31 15:43:19 -06:00
vAPI . messaging . defaultHandler = null ;
2014-12-16 05:44:34 -07:00
} ) ;
2014-11-24 12:00:27 -07:00
} ;
/******************************************************************************/
2014-11-27 12:45:54 -07:00
vAPI . messaging . broadcast = function ( message ) {
2014-12-16 05:44:34 -07:00
this . globalMessageManager . broadcastAsyncMessage (
2014-12-18 03:59:04 -07:00
location . host + ':broadcast' ,
2014-11-27 12:45:54 -07:00
JSON . stringify ( { broadcast : true , msg : message } )
) ;
2014-11-24 12:00:27 -07:00
} ;
/******************************************************************************/
2015-02-01 08:15:35 -07:00
// This allows to avoid creating a closure for every single message which
// expects an answer. Having a closure created each time a message is processed
2015-02-17 14:05:23 -07:00
// has been always bothering me. Another benefit of the implementation here
2015-02-01 08:15:35 -07:00
// is to reuse the callback proxy object, so less memory churning.
//
// https://developers.google.com/speed/articles/optimizing-javascript
// "Creating a closure is significantly slower then creating an inner
2015-02-17 14:05:23 -07:00
// function without a closure, and much slower than reusing a static
2015-02-01 08:15:35 -07:00
// function"
//
// http://hacksoflife.blogspot.ca/2015/01/the-four-horsemen-of-performance.html
2015-02-17 14:05:23 -07:00
// "the dreaded 'uniformly slow code' case where every function takes 1%
// of CPU and you have to make one hundred separate performance optimizations
2015-02-01 08:15:35 -07:00
// to improve performance at all"
2015-02-01 10:25:14 -07:00
//
// http://jsperf.com/closure-no-closure/2
2015-02-01 08:15:35 -07:00
var CallbackWrapper = function ( messageManager , channelName , listenerId , requestId ) {
this . callback = this . proxy . bind ( this ) ; // bind once
this . init ( messageManager , channelName , listenerId , requestId ) ;
} ;
CallbackWrapper . junkyard = [ ] ;
CallbackWrapper . factory = function ( messageManager , channelName , listenerId , requestId ) {
var wrapper = CallbackWrapper . junkyard . pop ( ) ;
if ( wrapper ) {
wrapper . init ( messageManager , channelName , listenerId , requestId ) ;
return wrapper ;
}
return new CallbackWrapper ( messageManager , channelName , listenerId , requestId ) ;
} ;
CallbackWrapper . prototype . init = function ( messageManager , channelName , listenerId , requestId ) {
this . messageManager = messageManager ;
this . channelName = channelName ;
this . listenerId = listenerId ;
this . requestId = requestId ;
} ;
CallbackWrapper . prototype . proxy = function ( response ) {
var message = JSON . stringify ( {
requestId : this . requestId ,
channelName : this . channelName ,
msg : response !== undefined ? response : null
} ) ;
if ( this . messageManager . sendAsyncMessage ) {
this . messageManager . sendAsyncMessage ( this . listenerId , message ) ;
} else {
this . messageManager . broadcastAsyncMessage ( this . listenerId , message ) ;
}
// Mark for reuse
2015-02-17 14:05:23 -07:00
this . messageManager =
this . channelName =
this . requestId =
2015-02-01 08:15:35 -07:00
this . listenerId = null ;
CallbackWrapper . junkyard . push ( this ) ;
} ;
/******************************************************************************/
2014-12-24 15:11:36 -07:00
var httpObserver = {
2015-01-02 10:41:41 -07:00
classDescription : 'net-channel-event-sinks for ' + location . host ,
classID : Components . ID ( '{dc8d6319-5f6e-4438-999e-53722db99e84}' ) ,
contractID : '@' + location . host + '/net-channel-event-sinks;1' ,
2015-03-02 11:49:34 -07:00
REQDATAKEY : location . host + 'reqdata' ,
2014-12-24 15:11:36 -07:00
ABORT : Components . results . NS _BINDING _ABORTED ,
2015-01-02 10:41:41 -07:00
ACCEPT : Components . results . NS _SUCCEEDED ,
2015-05-17 08:32:40 -06:00
// Request types:
// https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIContentPolicy#Constants
2015-01-02 10:41:41 -07:00
MAIN _FRAME : Ci . nsIContentPolicy . TYPE _DOCUMENT ,
2015-01-13 09:54:54 -07:00
VALID _CSP _TARGETS : 1 << Ci . nsIContentPolicy . TYPE _DOCUMENT |
1 << Ci . nsIContentPolicy . TYPE _SUBDOCUMENT ,
2015-01-02 10:41:41 -07:00
typeMap : {
2015-01-26 12:26:45 -07:00
1 : 'other' ,
2015-01-02 10:41:41 -07:00
2 : 'script' ,
3 : 'image' ,
4 : 'stylesheet' ,
5 : 'object' ,
6 : 'main_frame' ,
7 : 'sub_frame' ,
2015-06-04 07:37:53 -06:00
10 : 'ping' ,
2015-01-20 17:39:13 -07:00
11 : 'xmlhttprequest' ,
12 : 'object' ,
2015-03-20 06:52:01 -06:00
14 : 'font' ,
2015-06-04 07:37:53 -06:00
15 : 'media' ,
16 : 'websocket' ,
19 : 'beacon' ,
2015-03-20 06:52:01 -06:00
21 : 'image'
2015-01-02 10:41:41 -07:00
} ,
get componentRegistrar ( ) {
return Components . manager . QueryInterface ( Ci . nsIComponentRegistrar ) ;
} ,
get categoryManager ( ) {
return Cc [ '@mozilla.org/categorymanager;1' ]
. getService ( Ci . nsICategoryManager ) ;
2014-12-24 15:11:36 -07:00
} ,
2014-12-28 13:26:06 -07:00
2015-01-04 05:58:17 -07:00
QueryInterface : ( function ( ) {
2015-01-11 10:41:52 -07:00
var { XPCOMUtils } = Cu . import ( 'resource://gre/modules/XPCOMUtils.jsm' , null ) ;
2015-01-04 05:58:17 -07:00
return XPCOMUtils . generateQI ( [
Ci . nsIFactory ,
Ci . nsIObserver ,
Ci . nsIChannelEventSink ,
Ci . nsISupportsWeakReference
] ) ;
} ) ( ) ,
2015-01-02 10:41:41 -07:00
createInstance : function ( outer , iid ) {
if ( outer ) {
throw Components . results . NS _ERROR _NO _AGGREGATION ;
}
return this . QueryInterface ( iid ) ;
} ,
2014-12-28 13:26:06 -07:00
2014-12-28 02:56:09 -07:00
register : function ( ) {
2015-05-17 08:32:40 -06:00
this . pendingRingBufferInit ( ) ;
2015-01-02 10:41:41 -07:00
Services . obs . addObserver ( this , 'http-on-opening-request' , true ) ;
Services . obs . addObserver ( this , 'http-on-examine-response' , true ) ;
2015-03-17 12:08:29 -06:00
// Guard against stale instances not having been unregistered
if ( this . componentRegistrar . isCIDRegistered ( this . classID ) ) {
try {
2015-03-18 00:04:30 -06:00
this . componentRegistrar . unregisterFactory ( this . classID , Components . manager . getClassObject ( this . classID , Ci . nsIFactory ) ) ;
2015-03-17 12:08:29 -06:00
} catch ( ex ) {
2015-03-18 05:47:07 -06:00
console . error ( 'uBlock> httpObserver > unable to unregister stale instance: ' , ex ) ;
2015-03-17 12:08:29 -06:00
}
}
2015-01-02 10:41:41 -07:00
this . componentRegistrar . registerFactory (
this . classID ,
this . classDescription ,
this . contractID ,
this
) ;
this . categoryManager . addCategoryEntry (
'net-channel-event-sinks' ,
this . contractID ,
this . contractID ,
false ,
true
) ;
2014-12-28 02:56:09 -07:00
} ,
2014-12-28 13:26:06 -07:00
2014-12-28 02:56:09 -07:00
unregister : function ( ) {
2015-01-02 10:41:41 -07:00
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
) ;
2014-12-28 02:56:09 -07:00
} ,
2014-12-28 13:26:06 -07:00
2015-05-17 08:32:40 -06:00
PendingRequest : function ( ) {
this . frameId = 0 ;
this . parentFrameId = 0 ;
this . rawtype = 0 ;
this . sourceTabId = null ;
this . tabId = 0 ;
this . _key = '' ; // key is url, from URI.spec
} ,
2015-05-28 12:49:01 -06:00
2015-05-17 08:32:40 -06:00
// If all work fine, this map should not grow indefinitely. It can have
// stale items in it, but these will be taken care of when entries in
// the ring buffer are overwritten.
pendingURLToIndex : new Map ( ) ,
pendingWritePointer : 0 ,
pendingRingBuffer : new Array ( 32 ) ,
pendingRingBufferInit : function ( ) {
// Use and reuse pre-allocated PendingRequest objects = less memory
// churning.
var i = this . pendingRingBuffer . length ;
while ( i -- ) {
this . pendingRingBuffer [ i ] = new this . PendingRequest ( ) ;
}
} ,
2015-05-28 12:49:01 -06:00
2015-05-17 08:32:40 -06:00
createPendingRequest : function ( url ) {
var bucket ;
var i = this . pendingWritePointer ;
this . pendingWritePointer = i + 1 & 31 ;
var preq = this . pendingRingBuffer [ i ] ;
// Cleanup unserviced pending request
if ( preq . _key !== '' ) {
bucket = this . pendingURLToIndex . get ( preq . _key ) ;
if ( Array . isArray ( bucket ) ) {
// Assuming i in array
var pos = bucket . indexOf ( i ) ;
bucket . splice ( pos , 1 ) ;
if ( bucket . length === 1 ) {
this . pendingURLToIndex . set ( preq . _key , bucket [ 0 ] ) ;
}
} else if ( typeof bucket === 'number' ) {
// Assuming bucket === i
this . pendingURLToIndex . delete ( preq . _key ) ;
}
}
// Would be much simpler if a url could not appear more than once.
bucket = this . pendingURLToIndex . get ( url ) ;
if ( bucket === undefined ) {
this . pendingURLToIndex . set ( url , i ) ;
} else if ( Array . isArray ( bucket ) ) {
bucket = bucket . push ( i ) ;
} else {
bucket = [ bucket , i ] ;
}
preq . _key = url ;
return preq ;
} ,
2015-05-28 12:49:01 -06:00
2015-05-17 08:32:40 -06:00
lookupPendingRequest : function ( url ) {
var i = this . pendingURLToIndex . get ( url ) ;
if ( i === undefined ) {
return null ;
}
if ( Array . isArray ( i ) ) {
var bucket = i ;
i = bucket . shift ( ) ;
if ( bucket . length === 1 ) {
this . pendingURLToIndex . set ( url , bucket [ 0 ] ) ;
}
} else {
this . pendingURLToIndex . delete ( url ) ;
}
var preq = this . pendingRingBuffer [ i ] ;
preq . _key = '' ; // mark as "serviced"
return preq ;
} ,
2015-01-02 10:41:41 -07:00
handlePopup : function ( URI , tabId , sourceTabId ) {
if ( ! sourceTabId ) {
return false ;
}
2015-01-15 05:24:35 -07:00
if ( ! URI . schemeIs ( 'http' ) && ! URI . schemeIs ( 'https' ) ) {
2015-01-02 10:41:41 -07:00
return false ;
}
var result = vAPI . tabs . onPopup ( {
2015-03-08 09:06:36 -06:00
targetTabId : tabId ,
openerTabId : sourceTabId ,
targetURL : URI . asciiSpec
2015-01-02 10:41:41 -07:00
} ) ;
return result === true ;
} ,
2015-01-26 12:26:45 -07:00
handleRequest : function ( channel , URI , details ) {
2015-01-02 10:41:41 -07:00
var onBeforeRequest = vAPI . net . onBeforeRequest ;
2015-05-17 08:32:40 -06:00
var type = this . typeMap [ details . rawtype ] || 'other' ;
2015-01-02 10:41:41 -07:00
2015-05-08 23:16:27 -06:00
if ( onBeforeRequest . types && onBeforeRequest . types . has ( type ) === false ) {
2015-01-02 10:41:41 -07:00
return false ;
2014-12-24 15:11:36 -07:00
}
2015-01-02 10:41:41 -07:00
var result = onBeforeRequest . callback ( {
frameId : details . frameId ,
2015-01-26 12:26:45 -07:00
hostname : URI . asciiHost ,
2015-01-20 17:39:13 -07:00
parentFrameId : details . parentFrameId ,
tabId : details . tabId ,
type : type ,
2015-01-26 12:26:45 -07:00
url : URI . asciiSpec
2015-01-02 10:41:41 -07:00
} ) ;
if ( ! result || typeof result !== 'object' ) {
return false ;
}
if ( result . cancel === true ) {
channel . cancel ( this . ABORT ) ;
return true ;
2015-01-20 17:39:13 -07:00
}
2015-01-02 10:41:41 -07:00
return false ;
} ,
2014-12-24 15:11:36 -07:00
2015-01-02 10:41:41 -07:00
observe : function ( channel , topic ) {
2015-03-12 11:48:43 -06:00
if ( channel instanceof Ci . nsIHttpChannel === false ) {
2014-12-24 15:11:36 -07:00
return ;
}
2015-03-10 12:44:31 -06:00
2015-01-02 10:41:41 -07:00
var URI = channel . URI ;
var channelData , result ;
if ( topic === 'http-on-examine-response' ) {
if ( ! ( channel instanceof Ci . nsIWritablePropertyBag ) ) {
return ;
}
2014-12-24 15:11:36 -07:00
try {
2015-03-02 11:49:34 -07:00
channelData = channel . getProperty ( this . REQDATAKEY ) ;
2014-12-24 15:11:36 -07:00
} catch ( ex ) {
return ;
}
2015-01-13 09:54:54 -07:00
if ( ! channelData ) {
return ;
}
2015-03-02 10:36:04 -07:00
if ( ( 1 << channelData [ 4 ] & this . VALID _CSP _TARGETS ) === 0 ) {
2014-12-24 15:11:36 -07:00
return ;
}
2014-12-26 13:41:44 -07:00
topic = 'Content-Security-Policy' ;
2014-12-24 15:11:36 -07:00
try {
2015-01-02 10:41:41 -07:00
result = channel . getResponseHeader ( topic ) ;
2014-12-24 15:11:36 -07:00
} catch ( ex ) {
result = null ;
}
result = vAPI . net . onHeadersReceived . callback ( {
2015-01-20 17:39:13 -07:00
hostname : URI . asciiHost ,
2015-03-02 10:36:04 -07:00
parentFrameId : channelData [ 1 ] ,
2015-01-20 17:39:13 -07:00
responseHeaders : result ? [ { name : topic , value : result } ] : [ ] ,
2015-03-02 10:36:04 -07:00
tabId : channelData [ 3 ] ,
2015-05-28 12:49:01 -06:00
type : this . typeMap [ channelData [ 4 ] ] || 'other' ,
2015-01-20 17:39:13 -07:00
url : URI . asciiSpec
2014-12-24 15:11:36 -07:00
} ) ;
2014-12-28 13:26:06 -07:00
if ( result ) {
2015-01-02 10:41:41 -07:00
channel . setResponseHeader (
2014-12-26 13:41:44 -07:00
topic ,
result . responseHeaders . pop ( ) . value ,
2014-12-24 15:11:36 -07:00
true
) ;
}
return ;
}
// http-on-opening-request
2015-05-17 08:32:40 -06:00
//console.log('http-on-opening-request:', URI.spec);
2014-12-24 15:11:36 -07:00
2015-05-17 08:32:40 -06:00
var pendingRequest = this . lookupPendingRequest ( URI . spec ) ;
2015-03-02 04:52:04 -07:00
2015-05-17 08:32:40 -06:00
// Behind-the-scene request
if ( pendingRequest === null ) {
var rawtype = channel . loadInfo && channel . loadInfo . contentPolicyType || 1 ;
if ( this . handleRequest ( channel , URI , { tabId : vAPI . noTabId , rawtype : rawtype } ) ) {
2015-03-02 04:52:04 -07:00
return ;
}
2015-05-17 08:32:40 -06:00
// Carry data for behind-the-scene redirects
if ( channel instanceof Ci . nsIWritablePropertyBag ) {
channel . setProperty ( this . REQDATAKEY , [ 0 , - 1 , null , vAPI . noTabId , rawtype ] ) ;
2015-03-02 04:52:04 -07:00
}
2014-12-24 15:11:36 -07:00
return ;
}
2015-05-17 08:32:40 -06:00
if ( this . handleRequest ( channel , URI , pendingRequest ) ) {
2014-12-24 15:11:36 -07:00
return ;
2015-01-02 10:41:41 -07:00
}
2014-12-24 15:11:36 -07:00
2015-01-12 09:45:09 -07:00
// If request is not handled we may use the data in on-modify-request
2015-01-02 10:41:41 -07:00
if ( channel instanceof Ci . nsIWritablePropertyBag ) {
2015-03-02 09:49:25 -07:00
channel . setProperty ( this . REQDATAKEY , [
2015-05-17 08:32:40 -06:00
pendingRequest . frameId ,
pendingRequest . parentFrameId ,
pendingRequest . sourceTabId ,
pendingRequest . tabId ,
pendingRequest . rawtype
2015-03-02 09:49:25 -07:00
] ) ;
2014-12-24 15:11:36 -07:00
}
2015-01-02 10:41:41 -07:00
} ,
// 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 {
2015-01-15 05:24:35 -07:00
var URI = newChannel . URI ;
2015-01-02 10:41:41 -07:00
2015-01-15 05:24:35 -07:00
if ( ! URI . schemeIs ( 'http' ) && ! URI . schemeIs ( 'https' ) ) {
2015-01-02 10:41:41 -07:00
return ;
}
if ( ! ( oldChannel instanceof Ci . nsIWritablePropertyBag ) ) {
return ;
}
2015-03-02 11:49:34 -07:00
var channelData = oldChannel . getProperty ( this . REQDATAKEY ) ;
2015-01-02 10:41:41 -07:00
2015-03-02 10:36:04 -07:00
if ( this . handlePopup ( URI , channelData [ 3 ] , channelData [ 2 ] ) ) {
2015-01-02 10:41:41 -07:00
result = this . ABORT ;
return ;
}
var details = {
2015-03-02 10:36:04 -07:00
frameId : channelData [ 0 ] ,
parentFrameId : channelData [ 1 ] ,
tabId : channelData [ 3 ] ,
2015-05-17 08:32:40 -06:00
rawtype : channelData [ 4 ]
2015-01-02 10:41:41 -07:00
} ;
2015-01-26 12:26:45 -07:00
if ( this . handleRequest ( newChannel , URI , details ) ) {
2015-01-02 10:41:41 -07:00
result = this . ABORT ;
return ;
}
2015-01-12 09:45:09 -07:00
// Carry the data on in case of multiple redirects
2015-01-02 10:41:41 -07:00
if ( newChannel instanceof Ci . nsIWritablePropertyBag ) {
2015-03-02 11:49:34 -07:00
newChannel . setProperty ( this . REQDATAKEY , channelData ) ;
2015-01-02 10:41:41 -07:00
}
} catch ( ex ) {
// console.error(ex);
} finally {
callback . onRedirectVerifyCallback ( result ) ;
}
2014-12-24 15:11:36 -07:00
}
2014-12-02 00:35:25 -07:00
} ;
/******************************************************************************/
2014-12-28 02:56:09 -07:00
vAPI . net = { } ;
/******************************************************************************/
2014-12-09 13:56:17 -07:00
vAPI . net . registerListeners = function ( ) {
2015-01-11 10:41:52 -07:00
// Since it's not used
this . onBeforeSendHeaders = null ;
2015-05-08 23:16:27 -06:00
this . onBeforeRequest . types = this . onBeforeRequest . types ?
new Set ( this . onBeforeRequest . types ) :
null ;
2014-12-07 12:51:49 -07:00
2014-12-24 15:11:36 -07:00
var shouldLoadListenerMessageName = location . host + ':shouldLoad' ;
var shouldLoadListener = function ( e ) {
2015-05-17 08:32:40 -06:00
// Non blocking: it is assumed that the http observer is fired after
// shouldLoad recorded the pending requests. If this is not the case,
// a request would end up being categorized as a behind-the-scene
// requests.
2015-01-02 10:41:41 -07:00
var details = e . data ;
2015-05-31 15:43:19 -06:00
var tabId = tabWatcher . tabIdFromTarget ( e . target ) ;
2015-03-02 10:36:04 -07:00
var sourceTabId = null ;
// Popup candidate
if ( details . openerURL ) {
2015-06-02 16:12:28 -06:00
for ( var browser of tabWatcher . browsers ( ) ) {
var URI = browser . currentURI ;
2015-03-02 10:36:04 -07:00
2015-06-01 16:12:33 -06:00
// Probably isn't the best method to identify the source tab.
// Apparently URI can be undefined under some circumstances: I
// believe this may have to do with those very temporary
// browser objects created when opening a new tab, i.e. related
// to https://github.com/gorhill/uBlock/issues/212
if ( URI && URI . spec !== details . openerURL ) {
2015-03-02 10:36:04 -07:00
continue ;
}
2015-06-02 16:12:28 -06:00
sourceTabId = tabWatcher . tabIdFromTarget ( browser ) ;
2015-03-02 10:36:04 -07:00
if ( sourceTabId === tabId ) {
sourceTabId = null ;
continue ;
}
URI = Services . io . newURI ( details . url , null , null ) ;
if ( httpObserver . handlePopup ( URI , tabId , sourceTabId ) ) {
return ;
}
break ;
}
}
2015-05-17 08:32:40 -06:00
//console.log('shouldLoadListener:', details.url);
var pendingReq = httpObserver . createPendingRequest ( details . url ) ;
pendingReq . frameId = details . frameId ;
pendingReq . parentFrameId = details . parentFrameId ;
pendingReq . rawtype = details . rawtype ;
pendingReq . sourceTabId = sourceTabId ;
pendingReq . tabId = tabId ;
2014-12-09 13:56:17 -07:00
} ;
2014-12-07 12:51:49 -07:00
2014-12-16 05:44:34 -07:00
vAPI . messaging . globalMessageManager . addMessageListener (
2014-12-24 15:11:36 -07:00
shouldLoadListenerMessageName ,
shouldLoadListener
2014-12-09 13:56:17 -07:00
) ;
2014-12-16 05:44:34 -07:00
2015-03-26 15:00:56 -06:00
var locationChangedListenerMessageName = location . host + ':locationChanged' ;
var locationChangedListener = function ( e ) {
var details = e . data ;
var browser = e . target ;
2015-05-31 15:43:19 -06:00
var tabId = tabWatcher . tabIdFromTarget ( browser ) ;
2015-05-16 14:31:44 -06:00
2015-05-28 12:49:01 -06:00
// Ignore notifications related to our popup
if ( details . url . lastIndexOf ( vAPI . getURL ( 'popup.html' ) , 0 ) === 0 ) {
return ;
}
2015-03-26 15:00:56 -06:00
//console.debug("nsIWebProgressListener: onLocationChange: " + details.url + " (" + details.flags + ")");
// LOCATION_CHANGE_SAME_DOCUMENT = "did not load a new document"
if ( details . flags & Ci . nsIWebProgressListener . LOCATION _CHANGE _SAME _DOCUMENT ) {
vAPI . tabs . onUpdated ( tabId , { url : details . url } , {
frameId : 0 ,
tabId : tabId ,
url : browser . currentURI . asciiSpec
} ) ;
return ;
}
2015-04-06 19:26:05 -06:00
// https://github.com/chrisaljoudi/uBlock/issues/105
2015-03-26 15:00:56 -06:00
// Allow any kind of pages
vAPI . tabs . onNavigation ( {
frameId : 0 ,
tabId : tabId ,
url : details . url ,
} ) ;
2015-03-31 05:03:35 -06:00
} ;
2015-03-26 15:00:56 -06:00
vAPI . messaging . globalMessageManager . addMessageListener (
locationChangedListenerMessageName ,
locationChangedListener
) ;
2014-12-28 02:56:09 -07:00
httpObserver . register ( ) ;
2014-12-24 15:11:36 -07:00
2015-01-12 09:45:09 -07:00
cleanupTasks . push ( function ( ) {
2014-12-16 05:44:34 -07:00
vAPI . messaging . globalMessageManager . removeMessageListener (
2014-12-24 15:11:36 -07:00
shouldLoadListenerMessageName ,
shouldLoadListener
2014-12-16 05:44:34 -07:00
) ;
2014-12-24 15:11:36 -07:00
2015-03-26 15:00:56 -06:00
vAPI . messaging . globalMessageManager . removeMessageListener (
locationChangedListenerMessageName ,
locationChangedListener
) ;
2014-12-28 02:56:09 -07:00
httpObserver . unregister ( ) ;
2014-12-16 05:44:34 -07:00
} ) ;
2014-12-09 13:56:17 -07:00
} ;
2014-12-07 12:51:49 -07:00
/******************************************************************************/
2015-01-11 10:41:52 -07:00
vAPI . toolbarButton = {
id : location . host + '-button' ,
type : 'view' ,
viewId : location . host + '-panel' ,
label : vAPI . app . name ,
tooltiptext : vAPI . app . name ,
tabs : { /*tabId: {badge: 0, img: boolean}*/ }
} ;
2015-03-12 11:48:43 -06:00
/******************************************************************************/
// Toolbar button UI for desktop Firefox
vAPI . toolbarButton . init = function ( ) {
if ( vAPI . fennec ) {
// Menu UI for Fennec
var tb = {
menuItemIds : new WeakMap ( ) ,
label : vAPI . app . name ,
tabs : { }
} ;
vAPI . toolbarButton = tb ;
2015-03-02 11:49:34 -07:00
2015-03-12 11:48:43 -06:00
tb . getMenuItemLabel = function ( tabId ) {
var label = this . label ;
if ( tabId === undefined ) {
return label ;
}
2015-03-02 11:49:34 -07:00
var tabDetails = this . tabs [ tabId ] ;
2015-03-12 11:48:43 -06:00
if ( ! tabDetails ) {
return label ;
2015-03-02 11:49:34 -07:00
}
2015-03-12 11:48:43 -06:00
if ( ! tabDetails . img ) {
label += ' (' + vAPI . i18n ( 'fennecMenuItemBlockingOff' ) + ')' ;
} else if ( tabDetails . badge ) {
label += ' (' + tabDetails . badge + ')' ;
}
return label ;
} ;
tb . onClick = function ( ) {
var win = Services . wm . getMostRecentWindow ( 'navigator:browser' ) ;
2015-05-31 15:43:19 -06:00
var curTabId = tabWatcher . tabIdFromTarget ( getTabBrowser ( win ) . selectedTab ) ;
2015-03-12 11:48:43 -06:00
vAPI . tabs . open ( {
url : 'popup.html?tabId=' + curTabId ,
index : - 1 ,
select : true
} ) ;
} ;
tb . updateState = function ( win , tabId ) {
var id = this . menuItemIds . get ( win ) ;
if ( ! id ) {
return ;
}
win . NativeWindow . menu . update ( id , {
name : this . getMenuItemLabel ( tabId )
} ) ;
} ;
2015-01-11 10:41:52 -07:00
2015-03-02 11:49:34 -07:00
// Only actually expecting one window under Fennec (note, not tabs, windows)
2015-03-12 11:48:43 -06:00
for ( var win of vAPI . tabs . getWindows ( ) ) {
var label = tb . getMenuItemLabel ( ) ;
var id = win . NativeWindow . menu . add ( {
name : label ,
callback : tb . onClick
} ) ;
tb . menuItemIds . set ( win , id ) ;
2015-03-02 11:49:34 -07:00
}
2015-03-12 11:48:43 -06:00
cleanupTasks . push ( function ( ) {
for ( var win of vAPI . tabs . getWindows ( ) ) {
var id = tb . menuItemIds . get ( win ) ;
if ( id ) {
win . NativeWindow . menu . remove ( id ) ;
tb . menuItemIds . delete ( win ) ;
}
}
2015-03-02 11:49:34 -07:00
} ) ;
2015-02-28 12:18:58 -07:00
2015-03-12 11:48:43 -06:00
return ;
}
2015-02-28 03:58:09 -07:00
2015-04-24 11:57:30 -06:00
vAPI . messaging . globalMessageManager . addMessageListener (
location . host + ':closePopup' ,
vAPI . toolbarButton . onPopupCloseRequested
) ;
cleanupTasks . push ( function ( ) {
vAPI . messaging . globalMessageManager . removeMessageListener (
location . host + ':closePopup' ,
vAPI . toolbarButton . onPopupCloseRequested
) ;
} ) ;
2015-02-28 12:18:58 -07:00
var CustomizableUI ;
2015-04-24 11:57:30 -06:00
var forceLegacyToolbarButton = vAPI . localStorage . getBool ( "forceLegacyToolbarButton" ) ;
if ( ! forceLegacyToolbarButton ) {
2015-05-30 05:12:00 -06:00
try {
CustomizableUI = Cu . import ( 'resource:///modules/CustomizableUI.jsm' , null ) . CustomizableUI ;
} catch ( ex ) {
2015-04-24 11:57:30 -06:00
}
}
if ( ! CustomizableUI ) {
// Create a fallback non-customizable UI button
var sss = Cc [ "@mozilla.org/content/style-sheet-service;1" ] . getService ( Ci . nsIStyleSheetService ) ;
var styleSheetUri = Services . io . newURI ( vAPI . getURL ( "css/legacy-toolbar-button.css" ) , null , null ) ;
var legacyButtonId = "uBlock-legacy-button" ; // NOTE: must match legacy-toolbar-button.css
this . id = legacyButtonId ;
this . viewId = legacyButtonId + "-panel" ;
if ( ! sss . sheetRegistered ( styleSheetUri , sss . AUTHOR _SHEET ) ) {
sss . loadAndRegisterSheet ( styleSheetUri , sss . AUTHOR _SHEET ) ; // Register global so it works in all windows, including palette
}
var addLegacyToolbarButton = function ( window ) {
var document = window . document ;
var toolbox = document . getElementById ( 'navigator-toolbox' ) || document . getElementById ( 'mail-toolbox' ) ;
if ( toolbox ) {
var palette = toolbox . palette ;
if ( ! palette ) {
// palette might take a little longer to appear on some platforms, give it a small delay and try again
window . setTimeout ( function ( ) {
if ( toolbox . palette ) {
addLegacyToolbarButton ( window ) ;
}
} , 250 ) ;
2015-05-30 05:12:00 -06:00
return ;
}
2015-04-24 11:57:30 -06:00
var toolbarButton = document . createElement ( 'toolbarbutton' ) ;
toolbarButton . setAttribute ( 'id' , legacyButtonId ) ;
2015-04-25 07:08:01 -06:00
toolbarButton . setAttribute ( 'type' , 'menu' ) ; // type = panel would be more accurate, but doesn't look as good
2015-04-24 11:57:30 -06:00
toolbarButton . setAttribute ( 'removable' , 'true' ) ;
toolbarButton . setAttribute ( 'class' , 'toolbarbutton-1 chromeclass-toolbar-additional' ) ;
toolbarButton . setAttribute ( 'label' , vAPI . toolbarButton . label ) ;
var toolbarButtonPanel = document . createElement ( "panel" ) ;
2015-04-25 07:08:01 -06:00
// toolbarButtonPanel.setAttribute('level', 'parent'); NOTE: Setting level to parent breaks the popup for PaleMoon under linux (mouse pointer misaligned with content). For some reason.
2015-04-24 11:57:30 -06:00
vAPI . toolbarButton . populatePanel ( document , toolbarButtonPanel ) ;
toolbarButtonPanel . addEventListener ( 'popupshowing' , vAPI . toolbarButton . onViewShowing ) ;
toolbarButtonPanel . addEventListener ( 'popuphiding' , vAPI . toolbarButton . onViewHiding ) ;
toolbarButton . appendChild ( toolbarButtonPanel ) ;
palette . appendChild ( toolbarButton ) ;
vAPI . toolbarButton . closePopup = function ( ) {
toolbarButtonPanel . hidePopup ( ) ;
}
if ( ! vAPI . localStorage . getBool ( 'legacyToolbarButtonAdded' ) ) {
// No button yet so give it a default location. If forcing the button, just put in in the palette rather than on any specific toolbar (who knows what toolbars will be available or visible!)
var toolbar = ! forceLegacyToolbarButton && document . getElementById ( 'nav-bar' ) ;
if ( toolbar ) {
toolbar . appendChild ( toolbarButton ) ;
toolbar . setAttribute ( 'currentset' , toolbar . currentSet ) ;
document . persist ( toolbar . id , 'currentset' ) ;
}
vAPI . localStorage . setBool ( 'legacyToolbarButtonAdded' , 'true' ) ;
} else {
// Find the place to put the button
var toolbars = toolbox . externalToolbars . slice ( ) ;
for ( var child of toolbox . children ) {
if ( child . localName === 'toolbar' ) {
toolbars . push ( child ) ;
}
}
for ( var toolbar of toolbars ) {
var currentsetString = toolbar . getAttribute ( 'currentset' ) ;
if ( currentsetString ) {
var currentset = currentsetString . split ( ',' ) ;
var index = currentset . indexOf ( legacyButtonId ) ;
if ( index >= 0 ) {
// Found our button on this toolbar - but where on it?
var before = null ;
for ( var i = index + 1 ; i < currentset . length ; i ++ ) {
before = document . getElementById ( currentset [ i ] ) ;
if ( before ) {
toolbar . insertItem ( legacyButtonId , before ) ;
break ;
}
}
if ( ! before ) {
toolbar . insertItem ( legacyButtonId ) ;
}
}
}
}
}
}
}
vAPI . toolbarButton . attachToNewWindow = function ( win ) {
addLegacyToolbarButton ( win ) ;
}
cleanupTasks . push ( function ( ) {
for ( var win of vAPI . tabs . getWindows ( ) ) {
var toolbarButton = win . document . getElementById ( legacyButtonId ) ;
if ( toolbarButton ) {
toolbarButton . parentNode . removeChild ( toolbarButton ) ;
}
}
if ( sss . sheetRegistered ( styleSheetUri , sss . AUTHOR _SHEET ) ) {
sss . unregisterSheet ( styleSheetUri , sss . AUTHOR _SHEET ) ;
}
} . bind ( this ) ) ;
2015-01-16 03:42:34 -07:00
return ;
}
2015-03-02 11:49:34 -07:00
2015-04-24 11:57:30 -06:00
this . CustomizableUI = CustomizableUI ;
2015-01-11 10:41:52 -07:00
this . defaultArea = CustomizableUI . AREA _NAVBAR ;
this . styleURI = [
2015-04-11 12:12:10 -06:00
'#' + this . id + '.off {' ,
2015-01-11 10:41:52 -07:00
'list-style-image: url(' ,
2015-03-15 06:17:38 -06:00
vAPI . getURL ( 'img/browsericons/icon16-off.svg' ) ,
2015-01-11 10:41:52 -07:00
');' ,
'}' ,
2015-04-11 12:12:10 -06:00
'#' + this . id + ' {' ,
'list-style-image: url(' ,
vAPI . getURL ( 'img/browsericons/icon16.svg' ) ,
');' ,
'}' ,
2015-01-11 10:41:52 -07:00
'#' + this . viewId + ', #' + this . viewId + ' > iframe {' ,
'width: 160px;' ,
'height: 290px;' ,
'overflow: hidden !important;' ,
'}'
] ;
var platformVersion = Services . appinfo . platformVersion ;
if ( Services . vc . compare ( platformVersion , '36.0' ) < 0 ) {
this . styleURI . push (
'#' + this . id + '[badge]:not([badge=""])::after {' ,
'position: absolute;' ,
'margin-left: -16px;' ,
'margin-top: 3px;' ,
'padding: 1px 2px;' ,
'font-size: 9px;' ,
'font-weight: bold;' ,
'color: #fff;' ,
'background: #666;' ,
'content: attr(badge);' ,
2015-03-02 11:49:34 -07:00
'}'
2015-01-11 10:41:52 -07:00
) ;
2015-03-10 12:44:31 -06:00
} else {
2015-02-28 03:58:09 -07:00
this . CUIEvents = { } ;
2015-03-12 13:38:21 -06:00
var updateBadge = function ( ) {
2015-02-28 03:58:09 -07:00
var wId = vAPI . toolbarButton . id ;
var buttonInPanel = CustomizableUI . getWidget ( wId ) . areaType === CustomizableUI . TYPE _MENU _PANEL ;
2015-03-02 11:49:34 -07:00
2015-02-28 03:58:09 -07:00
for ( var win of vAPI . tabs . getWindows ( ) ) {
var button = win . document . getElementById ( wId ) ;
2015-06-11 07:33:39 -06:00
if ( button === null ) {
2015-02-28 03:58:09 -07:00
continue ;
}
2015-06-11 07:33:39 -06:00
if ( buttonInPanel ) {
button . classList . remove ( 'badged-button' ) ;
2015-02-28 03:58:09 -07:00
continue ;
}
button . classList . add ( 'badged-button' ) ;
}
if ( buttonInPanel ) {
return ;
}
// Anonymous elements need some time to be reachable
2015-05-17 11:02:56 -06:00
vAPI . setTimeout ( this . updateBadgeStyle , 250 ) ;
2015-03-12 13:38:21 -06:00
} . bind ( this . CUIEvents ) ;
2015-04-24 08:55:11 -06:00
// https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/CustomizableUI.jsm#Listeners
2015-03-12 13:38:21 -06:00
this . CUIEvents . onCustomizeEnd = updateBadge ;
2015-04-24 08:55:11 -06:00
this . CUIEvents . onWidgetAdded = updateBadge ;
2015-03-12 13:38:21 -06:00
this . CUIEvents . onWidgetUnderflow = updateBadge ;
2015-03-07 23:36:30 -07:00
2015-02-28 03:58:09 -07:00
this . CUIEvents . updateBadgeStyle = function ( ) {
var css = [
'background: #666' ,
'color: #fff'
] . join ( ';' ) ;
2015-01-11 10:41:52 -07:00
2015-02-28 03:58:09 -07:00
for ( var win of vAPI . tabs . getWindows ( ) ) {
var button = win . document . getElementById ( vAPI . toolbarButton . id ) ;
if ( button === null ) {
continue ;
}
var badge = button . ownerDocument . getAnonymousElementByAttribute (
button ,
'class' ,
'toolbarbutton-badge'
) ;
if ( ! badge ) {
return ;
2015-03-02 11:49:34 -07:00
}
2015-01-11 10:41:52 -07:00
2015-02-28 03:58:09 -07:00
badge . style . cssText = css ;
}
} ;
2015-01-11 10:41:52 -07:00
2015-02-28 03:58:09 -07:00
this . onCreated = function ( button ) {
button . setAttribute ( 'badge' , '' ) ;
2015-05-17 11:02:56 -06:00
vAPI . setTimeout ( updateBadge , 250 ) ;
2015-02-28 03:58:09 -07:00
} ;
CustomizableUI . addListener ( this . CUIEvents ) ;
2015-01-11 10:41:52 -07:00
}
2015-02-28 03:58:09 -07:00
2015-01-11 10:41:52 -07:00
this . styleURI = Services . io . newURI (
'data:text/css,' + encodeURIComponent ( this . styleURI . join ( '' ) ) ,
null ,
null
) ;
2015-04-24 11:57:30 -06:00
this . closePopup = function ( tabBrowser ) {
2015-01-11 10:41:52 -07:00
CustomizableUI . hidePanelForNode (
2015-04-24 11:57:30 -06:00
tabBrowser . ownerDocument . getElementById ( vAPI . toolbarButton . viewId )
2015-03-02 11:49:34 -07:00
) ;
2015-01-11 10:41:52 -07:00
} ;
2015-03-02 11:49:34 -07:00
2015-01-11 10:41:52 -07:00
CustomizableUI . createWidget ( this ) ;
2015-01-12 09:45:09 -07:00
cleanupTasks . push ( function ( ) {
2015-02-28 03:58:09 -07:00
if ( this . CUIEvents ) {
CustomizableUI . removeListener ( this . CUIEvents ) ;
}
2015-03-02 11:49:34 -07:00
2015-01-11 10:41:52 -07:00
CustomizableUI . destroyWidget ( this . id ) ;
2015-04-24 11:57:30 -06:00
2015-01-11 10:41:52 -07:00
for ( var win of vAPI . tabs . getWindows ( ) ) {
var panel = win . document . getElementById ( this . viewId ) ;
panel . parentNode . removeChild ( panel ) ;
win . QueryInterface ( Ci . nsIInterfaceRequestor )
. getInterface ( Ci . nsIDOMWindowUtils )
. removeSheet ( this . styleURI , 1 ) ;
}
} . bind ( this ) ) ;
2015-03-12 11:48:43 -06:00
this . init = null ;
2015-01-11 10:41:52 -07:00
} ;
/******************************************************************************/
2015-04-24 11:57:30 -06:00
vAPI . toolbarButton . onPopupCloseRequested = function ( { target } ) {
if ( vAPI . toolbarButton . closePopup ) {
vAPI . toolbarButton . closePopup ( target ) ;
}
}
/******************************************************************************/
2015-01-11 10:41:52 -07:00
vAPI . toolbarButton . onBeforeCreated = function ( doc ) {
var panel = doc . createElement ( 'panelview' ) ;
2015-04-24 11:57:30 -06:00
vAPI . toolbarButton . populatePanel ( doc , panel ) ;
doc . getElementById ( 'PanelUI-multiView' ) . appendChild ( panel ) ;
doc . defaultView . QueryInterface ( Ci . nsIInterfaceRequestor )
. getInterface ( Ci . nsIDOMWindowUtils )
. loadSheet ( this . styleURI , 1 ) ;
} ;
vAPI . toolbarButton . populatePanel = function ( doc , panel ) {
2015-01-11 10:41:52 -07:00
panel . setAttribute ( 'id' , this . viewId ) ;
var iframe = doc . createElement ( 'iframe' ) ;
iframe . setAttribute ( 'type' , 'content' ) ;
2015-04-24 11:57:30 -06:00
panel . appendChild ( iframe ) ;
2015-01-11 10:41:52 -07:00
var updateTimer = null ;
2015-04-24 11:57:30 -06:00
var delayedResize = function ( attempts ) {
2015-01-11 10:41:52 -07:00
if ( updateTimer ) {
return ;
}
2015-04-24 11:57:30 -06:00
// Sanity check
attempts = ( attempts || 0 ) + 1 ;
2015-05-30 05:12:00 -06:00
if ( attempts > 1 /*000*/ ) {
debugger ;
console . error ( 'uBlock> delayedResize: giving up after too many attempts' ) ;
2015-04-24 11:57:30 -06:00
return ;
}
2015-05-30 05:12:00 -06:00
updateTimer = vAPI . setTimeout ( resizePopup , 10 , attempts ) ;
} ;
2015-04-24 11:57:30 -06:00
var resizePopup = function ( attempts ) {
2015-02-12 07:24:45 -07:00
updateTimer = null ;
2015-01-11 10:41:52 -07:00
var body = iframe . contentDocument . body ;
panel . parentNode . style . maxWidth = 'none' ;
2015-04-06 19:26:05 -06:00
// https://github.com/chrisaljoudi/uBlock/issues/730
2015-02-12 07:24:45 -07:00
// Voodoo programming: this recipe works
2015-04-24 11:57:30 -06:00
var toPixelString = pixels => pixels . toString ( ) + 'px' ;
var clientHeight = body . clientHeight ;
iframe . style . height = toPixelString ( clientHeight ) ;
panel . style . height = toPixelString ( clientHeight + ( panel . boxObject . height - panel . clientHeight ) ) ;
var clientWidth = body . clientWidth ;
iframe . style . width = toPixelString ( clientWidth ) ;
panel . style . width = toPixelString ( clientWidth + ( panel . boxObject . width - panel . clientWidth ) ) ;
2015-02-12 07:24:45 -07:00
if ( iframe . clientHeight !== body . clientHeight || iframe . clientWidth !== body . clientWidth ) {
2015-04-24 11:57:30 -06:00
delayedResize ( attempts ) ;
2015-02-12 07:24:45 -07:00
}
2015-01-11 10:41:52 -07:00
} ;
2015-05-30 05:12:00 -06:00
var CustomizableUI = this . CustomizableUI ;
2015-01-11 10:41:52 -07:00
var onPopupReady = function ( ) {
var win = this . contentWindow ;
if ( ! win || win . location . host !== location . host ) {
return ;
}
2015-04-24 11:57:30 -06:00
if ( CustomizableUI ) {
// https://github.com/gorhill/uBlock/issues/83
// Add `portrait` class if width is constrained.
try {
iframe . contentDocument . body . classList . toggle (
'portrait' ,
CustomizableUI . getWidget ( vAPI . toolbarButton . id ) . areaType === CustomizableUI . TYPE _MENU _PANEL
) ;
} catch ( ex ) {
/* noop */
}
2015-04-24 08:55:11 -06:00
}
2015-01-13 09:20:16 -07:00
new win . MutationObserver ( delayedResize ) . observe ( win . document . body , {
2015-01-11 10:41:52 -07:00
attributes : true ,
characterData : true ,
subtree : true
} ) ;
delayedResize ( ) ;
2015-03-02 11:49:34 -07:00
} ;
2015-01-11 10:41:52 -07:00
iframe . addEventListener ( 'load' , onPopupReady , true ) ;
} ;
2015-03-02 11:49:34 -07:00
2015-01-11 10:41:52 -07:00
/******************************************************************************/
2015-03-02 11:49:34 -07:00
2015-01-11 10:41:52 -07:00
vAPI . toolbarButton . onViewShowing = function ( { target } ) {
target . firstChild . setAttribute ( 'src' , vAPI . getURL ( 'popup.html' ) ) ;
} ;
2015-03-02 11:49:34 -07:00
2015-01-11 10:41:52 -07:00
/******************************************************************************/
vAPI . toolbarButton . onViewHiding = function ( { target } ) {
target . parentNode . style . maxWidth = '' ;
target . firstChild . setAttribute ( 'src' , 'about:blank' ) ;
} ;
2015-03-12 11:48:43 -06:00
/******************************************************************************/
2015-03-10 12:44:31 -06:00
vAPI . toolbarButton . updateState = function ( win , tabId ) {
var button = win . document . getElementById ( this . id ) ;
if ( ! button ) {
return ;
}
var icon = this . tabs [ tabId ] ;
2015-04-11 12:12:10 -06:00
2015-03-10 12:44:31 -06:00
button . setAttribute ( 'badge' , icon && icon . badge || '' ) ;
2015-06-15 13:57:12 -06:00
button . classList . toggle ( 'off' , ! icon || ! icon . img ) ;
2015-03-10 12:44:31 -06:00
} ;
2015-01-11 10:41:52 -07:00
/******************************************************************************/
vAPI . toolbarButton . init ( ) ;
/******************************************************************************/
2014-12-19 13:24:30 -07:00
vAPI . contextMenu = {
contextMap : {
frame : 'inFrame' ,
link : 'onLink' ,
image : 'onImage' ,
audio : 'onAudio' ,
video : 'onVideo' ,
editable : 'onEditableArea'
}
} ;
/******************************************************************************/
2015-02-28 12:18:58 -07:00
vAPI . contextMenu . displayMenuItem = function ( { target } ) {
var doc = target . ownerDocument ;
2014-12-19 13:24:30 -07:00
var gContextMenu = doc . defaultView . gContextMenu ;
2015-01-11 10:41:52 -07:00
if ( ! gContextMenu . browser ) {
return ;
}
2014-12-19 13:24:30 -07:00
var menuitem = doc . getElementById ( vAPI . contextMenu . menuItemId ) ;
2015-01-15 05:24:35 -07:00
var currentURI = gContextMenu . browser . currentURI ;
2014-12-19 13:24:30 -07:00
2015-04-06 19:26:05 -06:00
// https://github.com/chrisaljoudi/uBlock/issues/105
2015-01-21 09:13:32 -07:00
// TODO: Should the element picker works on any kind of pages?
2015-01-15 05:24:35 -07:00
if ( ! currentURI . schemeIs ( 'http' ) && ! currentURI . schemeIs ( 'https' ) ) {
2014-12-19 13:24:30 -07:00
menuitem . hidden = true ;
return ;
}
var ctx = vAPI . contextMenu . contexts ;
2014-12-28 13:26:06 -07:00
if ( ! ctx ) {
2014-12-19 13:24:30 -07:00
menuitem . hidden = false ;
return ;
}
var ctxMap = vAPI . contextMenu . contextMap ;
2014-12-28 13:26:06 -07:00
for ( var context of ctx ) {
if ( context === 'page' && ! gContextMenu . onLink && ! gContextMenu . onImage
2014-12-19 13:24:30 -07:00
&& ! gContextMenu . onEditableArea && ! gContextMenu . inFrame
2014-12-28 13:26:06 -07:00
&& ! gContextMenu . onVideo && ! gContextMenu . onAudio ) {
2014-12-19 13:24:30 -07:00
menuitem . hidden = false ;
return ;
}
2014-12-28 13:26:06 -07:00
if ( gContextMenu [ ctxMap [ context ] ] ) {
2014-12-19 13:24:30 -07:00
menuitem . hidden = false ;
return ;
}
}
menuitem . hidden = true ;
} ;
2014-12-16 05:44:34 -07:00
/******************************************************************************/
2014-12-19 13:24:30 -07:00
vAPI . contextMenu . register = function ( doc ) {
2014-12-28 13:26:06 -07:00
if ( ! this . menuItemId ) {
2014-12-19 13:24:30 -07:00
return ;
}
2015-02-12 11:19:17 -07:00
if ( vAPI . fennec ) {
2015-02-28 12:18:58 -07:00
// TODO https://developer.mozilla.org/en-US/Add-ons/Firefox_for_Android/API/NativeWindow/contextmenus/add
/ * v a r n a t i v e W i n d o w = d o c . d e f a u l t V i e w . N a t i v e W i n d o w ;
2015-02-12 11:19:17 -07:00
contextId = nativeWindow . contextmenus . add (
2015-02-28 12:18:58 -07:00
this . menuLabel ,
nativeWindow . contextmenus . linkOpenableContext ,
this . onCommand
) ; * /
return ;
2015-02-12 11:19:17 -07:00
}
2015-02-28 12:18:58 -07:00
var contextMenu = doc . getElementById ( 'contentAreaContextMenu' ) ;
var menuitem = doc . createElement ( 'menuitem' ) ;
menuitem . setAttribute ( 'id' , this . menuItemId ) ;
menuitem . setAttribute ( 'label' , this . menuLabel ) ;
2015-03-15 06:17:38 -06:00
menuitem . setAttribute ( 'image' , vAPI . getURL ( 'img/browsericons/icon16.svg' ) ) ;
2015-02-28 12:18:58 -07:00
menuitem . setAttribute ( 'class' , 'menuitem-iconic' ) ;
menuitem . addEventListener ( 'command' , this . onCommand ) ;
contextMenu . addEventListener ( 'popupshowing' , this . displayMenuItem ) ;
contextMenu . insertBefore ( menuitem , doc . getElementById ( 'inspect-separator' ) ) ;
2014-12-19 13:24:30 -07:00
} ;
2014-12-16 05:44:34 -07:00
/******************************************************************************/
2014-12-19 13:24:30 -07:00
vAPI . contextMenu . unregister = function ( doc ) {
2014-12-28 13:26:06 -07:00
if ( ! this . menuItemId ) {
2014-12-19 13:24:30 -07:00
return ;
}
2015-02-12 11:19:17 -07:00
if ( vAPI . fennec ) {
// TODO
2015-02-28 12:18:58 -07:00
return ;
2015-02-12 11:19:17 -07:00
}
2015-02-28 12:18:58 -07:00
var menuitem = doc . getElementById ( this . menuItemId ) ;
var contextMenu = menuitem . parentNode ;
menuitem . removeEventListener ( 'command' , this . onCommand ) ;
contextMenu . removeEventListener ( 'popupshowing' , this . displayMenuItem ) ;
contextMenu . removeChild ( menuitem ) ;
2014-12-19 13:24:30 -07:00
} ;
/******************************************************************************/
vAPI . contextMenu . create = function ( details , callback ) {
this . menuItemId = details . id ;
this . menuLabel = details . title ;
this . contexts = details . contexts ;
2014-12-28 13:26:06 -07:00
if ( Array . isArray ( this . contexts ) && this . contexts . length ) {
2014-12-19 13:24:30 -07:00
this . contexts = this . contexts . indexOf ( 'all' ) === - 1 ? this . contexts : null ;
2014-12-28 13:26:06 -07:00
} else {
2014-12-19 13:24:30 -07:00
// default in Chrome
this . contexts = [ 'page' ] ;
}
this . onCommand = function ( ) {
2015-02-28 12:18:58 -07:00
var gContextMenu = getOwnerWindow ( this ) . gContextMenu ;
2014-12-19 13:24:30 -07:00
var details = {
2015-01-16 01:01:40 -07:00
menuItemId : this . id
2014-12-19 13:24:30 -07:00
} ;
2014-12-28 13:26:06 -07:00
if ( gContextMenu . inFrame ) {
2015-01-16 01:01:40 -07:00
details . tagName = 'iframe' ;
2015-03-01 12:21:05 -07:00
// Probably won't work with e10s
2015-06-01 06:11:25 -06:00
details . frameUrl = gContextMenu . focusedWindow && gContextMenu . focusedWindow . location . href || '' ;
2015-01-16 01:01:40 -07:00
} else if ( gContextMenu . onImage ) {
details . tagName = 'img' ;
details . srcUrl = gContextMenu . mediaURL ;
} else if ( gContextMenu . onAudio ) {
details . tagName = 'audio' ;
details . srcUrl = gContextMenu . mediaURL ;
} else if ( gContextMenu . onVideo ) {
details . tagName = 'video' ;
2014-12-19 13:24:30 -07:00
details . srcUrl = gContextMenu . mediaURL ;
2014-12-28 13:26:06 -07:00
} else if ( gContextMenu . onLink ) {
2015-01-16 01:01:40 -07:00
details . tagName = 'a' ;
2014-12-19 13:24:30 -07:00
details . linkUrl = gContextMenu . linkURL ;
}
callback ( details , {
2015-05-31 15:43:19 -06:00
id : tabWatcher . tabIdFromTarget ( gContextMenu . browser ) ,
2015-01-14 15:45:55 -07:00
url : gContextMenu . browser . currentURI . asciiSpec
2014-12-19 13:24:30 -07:00
} ) ;
} ;
2014-12-28 13:26:06 -07:00
for ( var win of vAPI . tabs . getWindows ( ) ) {
2014-12-19 13:24:30 -07:00
this . register ( win . document ) ;
}
} ;
/******************************************************************************/
2015-03-20 17:17:07 -06:00
vAPI . contextMenu . remove = function ( ) {
for ( var win of vAPI . tabs . getWindows ( ) ) {
this . unregister ( win . document ) ;
}
2015-03-21 06:22:34 -06:00
this . menuItemId = null ;
this . menuLabel = null ;
this . contexts = null ;
this . onCommand = null ;
2015-03-20 17:17:07 -06:00
} ;
/******************************************************************************/
2014-12-19 13:24:30 -07:00
2015-03-12 11:48:43 -06:00
var optionsObserver = {
2015-04-09 05:15:14 -06:00
addonId : 'uBlock0@raymondhill.net' ,
2015-03-15 10:49:36 -06:00
2015-03-12 11:48:43 -06:00
register : function ( ) {
Services . obs . addObserver ( this , 'addon-options-displayed' , false ) ;
cleanupTasks . push ( this . unregister . bind ( this ) ) ;
2015-03-15 10:49:36 -06:00
2015-06-03 07:01:58 -06:00
var browser = tabWatcher . currentBrowser ( ) ;
2015-03-17 12:08:29 -06:00
if ( browser && browser . currentURI && browser . currentURI . spec === 'about:addons' ) {
this . observe ( browser . contentDocument , 'addon-enabled' , this . addonId ) ;
2015-03-15 10:49:36 -06:00
}
2015-03-12 11:48:43 -06:00
} ,
unregister : function ( ) {
Services . obs . removeObserver ( this , 'addon-options-displayed' ) ;
} ,
setupOptionsButton : function ( doc , id , page ) {
var button = doc . getElementById ( id ) ;
2015-03-18 00:04:30 -06:00
if ( button === null ) {
return ;
}
2015-03-12 11:48:43 -06:00
button . addEventListener ( 'command' , function ( ) {
vAPI . tabs . open ( { url : page , index : - 1 } ) ;
} ) ;
button . label = vAPI . i18n ( id ) ;
} ,
2015-03-15 10:49:36 -06:00
observe : function ( doc , topic , addonId ) {
if ( addonId !== this . addonId ) {
2015-03-12 11:48:43 -06:00
return ;
}
this . setupOptionsButton ( doc , 'showDashboardButton' , 'dashboard.html' ) ;
2015-05-08 16:28:01 -06:00
this . setupOptionsButton ( doc , 'showNetworkLogButton' , 'logger-ui.html' ) ;
2015-03-12 11:48:43 -06:00
}
} ;
optionsObserver . register ( ) ;
/******************************************************************************/
2014-12-09 13:56:17 -07:00
vAPI . lastError = function ( ) {
return null ;
2014-12-07 12:51:49 -07:00
} ;
/******************************************************************************/
2014-12-20 03:08:33 -07:00
// This is called only once, when everything has been loaded in memory after
// the extension was launched. It can be used to inject content scripts
// in already opened web pages, to remove whatever nuisance could make it to
// the web pages before uBlock was ready.
2015-03-12 11:20:48 -06:00
vAPI . onLoadAllCompleted = function ( ) {
var µb = µBlock ;
2015-05-31 15:43:19 -06:00
var tabId ;
2015-06-02 16:12:28 -06:00
for ( var browser of tabWatcher . browsers ( ) ) {
tabId = tabWatcher . tabIdFromTarget ( browser ) ;
µb . tabContextManager . commit ( tabId , browser . currentURI . asciiSpec ) ;
2015-04-29 08:29:23 -06:00
µb . bindTabToPageStats ( tabId ) ;
2015-06-02 16:12:28 -06:00
browser . messageManager . sendAsyncMessage (
2015-03-12 11:20:48 -06:00
location . host + '-load-completed'
) ;
}
} ;
2014-12-20 03:08:33 -07:00
/******************************************************************************/
2015-01-14 17:43:10 -07:00
// Likelihood is that we do not have to punycode: given punycode overhead,
// it's faster to check and skip than do it unconditionally all the time.
var punycodeHostname = punycode . toASCII ;
var isNotASCII = /[^\x21-\x7F]/ ;
vAPI . punycodeHostname = function ( hostname ) {
return isNotASCII . test ( hostname ) ? punycodeHostname ( hostname ) : hostname ;
} ;
2015-01-14 15:45:55 -07:00
vAPI . punycodeURL = function ( url ) {
2015-01-15 05:24:35 -07:00
if ( isNotASCII . test ( url ) ) {
return Services . io . newURI ( url , null , null ) . asciiSpec ;
2015-01-14 17:43:10 -07:00
}
2015-01-15 05:24:35 -07:00
return url ;
2015-01-14 15:45:55 -07:00
} ;
/******************************************************************************/
2014-11-24 12:00:27 -07:00
} ) ( ) ;
/******************************************************************************/