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/}.
2015-04-08 16:36:14 -06:00
Home : https : //github.com/chrisaljoudi/uBlock
2014-11-24 12:00:27 -07:00
* /
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-03-12 11:48:43 -06:00
name : 'uBlock' ,
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-24 11:57:30 -06:00
var expectedNumberOfCleanups = vAPI . fennec ? 7 : 9 ; // several instances of cleanupTasks.push, but one is unique to fennec, and three to desktop.
2015-03-18 05:47:07 -06:00
window . addEventListener ( 'unload' , function ( ) {
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
/******************************************************************************/
2014-11-27 12:45:54 -07:00
var SQLite = {
open : function ( ) {
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-11-27 12:45:54 -07:00
this . db = Services . storage . openDatabase ( path ) ;
this . db . executeSimpleSQL (
'CREATE TABLE IF NOT EXISTS settings' +
'(name TEXT PRIMARY KEY NOT NULL, value TEXT);'
) ;
2014-12-16 05:44:34 -07:00
2015-01-12 09:45:09 -07:00
cleanupTasks . push ( function ( ) {
2014-12-18 03:59:04 -07:00
// VACUUM somewhere else, instead on unload?
2014-12-16 05:44:34 -07:00
SQLite . run ( 'VACUUM' ) ;
SQLite . db . asyncClose ( ) ;
} ) ;
2014-11-27 12:45:54 -07:00
} ,
2014-12-28 13:26:06 -07:00
2014-11-27 12:45:54 -07:00
run : function ( query , values , callback ) {
2014-12-28 13:26:06 -07:00
if ( ! this . db ) {
2014-11-27 12:45:54 -07:00
this . open ( ) ;
}
var result = { } ;
query = this . db . createAsyncStatement ( query ) ;
2014-12-28 13:26:06 -07:00
if ( Array . isArray ( values ) && values . length ) {
2014-11-27 12:45:54 -07:00
var i = values . length ;
2014-12-28 13:26:06 -07:00
while ( i -- ) {
2014-11-27 12:45:54 -07:00
query . bindByIndex ( i , values [ i ] ) ;
}
}
query . executeAsync ( {
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 ) ;
}
} ) ;
}
} ;
/******************************************************************************/
vAPI . storage = {
QUOTA _BYTES : 100 * 1024 * 1024 ,
2014-12-28 13:26:06 -07:00
2014-12-02 00:35:25 -07:00
sqlWhere : function ( col , params ) {
2014-12-28 13:26:06 -07:00
if ( params > 0 ) {
2015-01-21 06:59:23 -07:00
params = new Array ( params + 1 ) . join ( '?, ' ) . slice ( 0 , - 2 ) ;
2014-12-02 00:35:25 -07:00
return ' WHERE ' + col + ' IN (' + params + ')' ;
2014-11-27 12:45:54 -07:00
}
return '' ;
} ,
2014-12-28 13:26:06 -07:00
2014-11-27 12:45:54 -07:00
get : function ( details , callback ) {
2014-12-28 13:26:06 -07:00
if ( typeof callback !== 'function' ) {
2014-11-27 12:45:54 -07:00
return ;
}
var values = [ ] , defaults = false ;
2014-12-28 13:26:06 -07:00
if ( details !== null ) {
if ( Array . isArray ( details ) ) {
2014-11-27 12:45:54 -07:00
values = details ;
2014-12-28 13:26:06 -07:00
} else if ( typeof details === 'object' ) {
2014-11-27 12:45:54 -07:00
defaults = true ;
values = Object . keys ( details ) ;
2014-12-28 13:26:06 -07:00
} else {
2014-11-27 12:45:54 -07:00
values = [ details . toString ( ) ] ;
}
}
SQLite . run (
'SELECT * FROM settings' + this . sqlWhere ( 'name' , values . length ) ,
values ,
function ( result ) {
var key ;
2014-12-28 13:26:06 -07:00
for ( key in result ) {
2014-11-27 12:45:54 -07:00
result [ key ] = JSON . parse ( result [ key ] ) ;
}
2014-12-28 13:26:06 -07:00
if ( defaults ) {
for ( key in details ) {
if ( result [ key ] === undefined ) {
2014-11-27 12:45:54 -07:00
result [ key ] = details [ key ] ;
}
}
}
callback ( result ) ;
}
) ;
} ,
2014-12-28 13:26:06 -07:00
2014-11-27 12:45:54 -07:00
set : function ( details , callback ) {
2014-12-02 00:35:25 -07:00
var key , values = [ ] , placeholders = [ ] ;
2014-11-27 12:45:54 -07:00
2014-12-28 13:26:06 -07:00
for ( key in details ) {
2015-01-18 11:50:20 -07:00
if ( ! details . hasOwnProperty ( key ) ) {
continue ;
}
2014-11-27 12:45:54 -07:00
values . push ( key ) ;
values . push ( JSON . stringify ( details [ key ] ) ) ;
2014-12-02 00:35:25 -07:00
placeholders . push ( '?, ?' ) ;
2014-11-27 12:45:54 -07:00
}
2014-12-28 13:26:06 -07:00
if ( ! values . length ) {
2014-11-27 12:45:54 -07:00
return ;
}
SQLite . run (
'INSERT OR REPLACE INTO settings (name, value) SELECT ' +
2014-12-02 00:35:25 -07:00
placeholders . join ( ' UNION SELECT ' ) ,
2014-11-27 12:45:54 -07:00
values ,
callback
) ;
} ,
2014-12-28 13:26:06 -07:00
2014-11-27 12:45:54 -07:00
remove : function ( keys , callback ) {
2014-12-28 13:26:06 -07:00
if ( typeof keys === 'string' ) {
2014-11-27 12:45:54 -07:00
keys = [ keys ] ;
}
SQLite . run (
'DELETE FROM settings' + this . sqlWhere ( 'name' , keys . length ) ,
keys ,
callback
) ;
} ,
2014-12-28 13:26:06 -07:00
2014-11-27 12:45:54 -07:00
clear : function ( callback ) {
2014-12-20 03:57:05 -07:00
SQLite . run ( 'DELETE FROM settings' ) ;
SQLite . run ( 'VACUUM' , null , callback ) ;
2014-11-27 12:45:54 -07:00
} ,
2014-12-28 13:26:06 -07:00
2014-11-27 12:45:54 -07:00
getBytesInUse : function ( keys , callback ) {
2014-12-28 13:26:06 -07:00
if ( typeof callback !== 'function' ) {
2014-11-27 12:45:54 -07:00
return ;
}
SQLite . run (
2014-12-02 00:35:25 -07:00
'SELECT "size" AS size, SUM(LENGTH(value)) FROM settings' +
2014-11-27 12:45:54 -07:00
this . sqlWhere ( 'name' , Array . isArray ( keys ) ? keys . length : 0 ) ,
keys ,
function ( result ) {
callback ( result . size ) ;
}
) ;
}
} ;
/******************************************************************************/
2014-12-02 00:35:25 -07:00
var windowWatcher = {
2014-12-16 10:09:55 -07:00
onReady : function ( e ) {
2014-12-28 13:26:06 -07:00
if ( e ) {
2014-12-16 10:09:55 -07:00
this . removeEventListener ( e . type , windowWatcher . onReady ) ;
2014-12-02 00:35:25 -07:00
}
2014-12-17 02:33:02 -07:00
var wintype = this . document . documentElement . getAttribute ( 'windowtype' ) ;
2014-12-02 00:35:25 -07:00
2014-12-28 13:26:06 -07:00
if ( wintype !== 'navigator:browser' ) {
2014-12-02 00:35:25 -07:00
return ;
}
2015-04-24 11:57:30 -06:00
var attachToTabBrowser = function ( window , tabBrowser ) {
if ( ! tabBrowser ) {
return ;
}
2014-12-02 00:35:25 -07:00
2015-04-24 11:57:30 -06:00
var tabContainer ;
if ( tabBrowser . deck ) {
// Fennec
tabContainer = tabBrowser . deck ;
} else if ( tabBrowser . tabContainer ) {
// desktop Firefox
tabContainer = tabBrowser . tabContainer ;
vAPI . contextMenu . register ( window . document ) ;
if ( vAPI . toolbarButton . attachToNewWindow ) {
vAPI . toolbarButton . attachToNewWindow ( window ) ;
}
} else {
return ;
}
tabContainer . addEventListener ( 'TabClose' , tabWatcher . onTabClose ) ;
tabContainer . addEventListener ( 'TabSelect' , tabWatcher . onTabSelect ) ;
// when new window is opened TabSelect doesn't run on the selected tab?
2015-02-28 12:18:58 -07:00
}
2014-12-02 00:35:25 -07:00
2015-04-24 11:57:30 -06:00
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 ) ;
2015-02-28 12:18:58 -07:00
} else {
2015-04-24 11:57:30 -06:00
attachToTabBrowser ( win , tabBrowser ) ;
2015-02-12 11:19:17 -07:00
}
2015-04-24 11:57:30 -06:00
2014-12-02 00:35:25 -07:00
} ,
2014-12-28 13:26:06 -07:00
2014-12-02 00:35:25 -07:00
observe : function ( win , topic ) {
2014-12-28 13:26:06 -07:00
if ( topic === 'domwindowopened' ) {
2014-12-16 10:09:55 -07:00
win . addEventListener ( 'DOMContentLoaded' , this . onReady ) ;
2014-12-02 00:35:25 -07:00
}
}
} ;
/******************************************************************************/
2015-01-27 03:13:33 -07:00
var tabWatcher = {
2015-02-28 12:18:58 -07:00
onTabClose : function ( { target } ) {
// target is tab in Firefox, browser in Fennec
var tabId = vAPI . tabs . getTabId ( target ) ;
2015-01-27 03:13:33 -07:00
vAPI . tabs . onClosed ( tabId ) ;
delete vAPI . toolbarButton . tabs [ tabId ] ;
} ,
2015-02-28 12:18:58 -07:00
onTabSelect : function ( { target } ) {
2015-03-30 09:19:07 -06:00
vAPI . setIcon ( vAPI . tabs . getTabId ( target ) , getOwnerWindow ( target ) ) ;
2015-01-27 03:13:33 -07:00
} ,
2014-12-02 00:35:25 -07:00
} ;
/******************************************************************************/
2015-04-13 18:58:50 -06:00
vAPI . isBehindTheSceneTabId = function ( tabId ) {
2015-01-26 12:26:45 -07:00
return tabId . toString ( ) === '-1' ;
2015-01-19 16:42:58 -07:00
} ;
2015-01-26 12:26:45 -07:00
vAPI . noTabId = '-1' ;
2015-01-19 16:42:58 -07:00
/******************************************************************************/
2014-12-02 00:35:25 -07:00
2015-02-28 12:18:58 -07:00
var getTabBrowser = function ( win ) {
return vAPI . fennec && win . BrowserApp || win . gBrowser || null ;
} ;
/******************************************************************************/
var getBrowserForTab = function ( tab ) {
2015-03-17 12:08:29 -06:00
if ( ! tab ) {
return null ;
}
2015-02-28 12:18:58 -07:00
return vAPI . fennec && tab . browser || tab . linkedBrowser || 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-01-27 03:13:33 -07:00
vAPI . tabs = { } ;
/******************************************************************************/
2014-12-02 00:35:25 -07:00
vAPI . tabs . registerListeners = function ( ) {
2015-01-27 03:13:33 -07:00
// onClosed - handled in tabWatcher.onTabClose
// onPopup - handled in httpObserver.handlePopup
2014-12-02 00:35:25 -07:00
2014-12-28 13:26:06 -07:00
for ( var win of this . getWindows ( ) ) {
2014-12-16 10:09:55 -07:00
windowWatcher . onReady . call ( win ) ;
2014-12-02 00:35:25 -07:00
}
2014-12-16 05:44:34 -07:00
2014-12-16 10:09:55 -07:00
Services . ww . registerNotification ( windowWatcher ) ;
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
Services . ww . unregisterNotification ( windowWatcher ) ;
2015-04-11 23:23:22 -06:00
vAPI . contextMenu . remove ( ) ;
2014-12-28 13:26:06 -07:00
for ( var win of vAPI . tabs . getWindows ( ) ) {
2014-12-16 12:49:11 -07:00
win . removeEventListener ( 'DOMContentLoaded' , windowWatcher . onReady ) ;
2014-12-16 05:44:34 -07:00
2015-02-28 12:18:58 -07:00
var tabContainer ;
var tabBrowser = getTabBrowser ( win ) ;
if ( ! tabBrowser ) {
continue ;
}
2014-12-16 05:44:34 -07:00
2015-03-02 11:49:34 -07:00
if ( tabBrowser . deck ) {
// Fennec
tabContainer = tabBrowser . deck ;
} else if ( tabBrowser . tabContainer ) {
2015-02-28 12:18:58 -07:00
tabContainer = tabBrowser . tabContainer ;
}
2015-02-12 11:19:17 -07:00
2015-02-28 12:18:58 -07:00
tabContainer . removeEventListener ( 'TabClose' , tabWatcher . onTabClose ) ;
tabContainer . removeEventListener ( 'TabSelect' , tabWatcher . onTabSelect ) ;
2014-12-16 05:44:34 -07:00
2015-02-28 12:18:58 -07:00
// Close extension tabs
for ( var tab of tabBrowser . tabs ) {
var browser = getBrowserForTab ( tab ) ;
if ( browser === null ) {
continue ;
}
var URI = browser . currentURI ;
if ( URI . schemeIs ( 'chrome' ) && URI . host === location . host ) {
2015-03-01 12:47:03 -07:00
vAPI . tabs . _remove ( tab , getTabBrowser ( win ) ) ;
2014-12-16 05:44:34 -07:00
}
}
}
} ) ;
2014-12-02 00:35:25 -07:00
} ;
/******************************************************************************/
2015-03-16 10:30:05 -06:00
vAPI . tabs . stack = new WeakMap ( ) ;
vAPI . tabs . stackId = 1 ;
/******************************************************************************/
2014-12-02 00:35:25 -07:00
vAPI . tabs . getTabId = function ( target ) {
2015-03-17 12:08:29 -06:00
if ( ! target ) {
return vAPI . noTabId ;
}
2015-02-12 11:19:17 -07:00
if ( vAPI . fennec ) {
if ( target . browser ) {
// target is a tab
2015-03-16 03:37:55 -06:00
target = target . browser ;
2015-02-12 11:19:17 -07:00
}
2015-03-16 10:30:05 -06:00
} else if ( target . linkedPanel ) {
2015-02-28 12:18:58 -07:00
// target is a tab
2015-03-16 10:30:05 -06:00
target = target . linkedBrowser ;
2015-02-28 12:18:58 -07:00
}
2015-03-16 10:30:05 -06:00
if ( target . localName !== 'browser' ) {
return vAPI . noTabId ;
2015-02-28 12:18:58 -07:00
}
2015-03-16 10:30:05 -06:00
var tabId = this . stack . get ( target ) ;
if ( ! tabId ) {
tabId = '' + this . stackId ++ ;
this . stack . set ( target , tabId ) ;
2015-02-28 12:18:58 -07:00
}
2015-03-16 10:30:05 -06:00
return tabId ;
2015-02-28 12:18:58 -07:00
} ;
2015-02-12 11:19:17 -07:00
2015-02-28 12:18:58 -07:00
/******************************************************************************/
// If tabIds is an array, then an array of tabs will be returned,
// otherwise a single tab
2015-03-16 10:30:05 -06:00
vAPI . tabs . getTabsForIds = function ( tabIds ) {
2015-02-28 12:18:58 -07:00
var tabs = [ ] ;
var singleTab = ! Array . isArray ( tabIds ) ;
if ( singleTab ) {
tabIds = [ tabIds ] ;
}
2015-03-16 10:30:05 -06:00
for ( var tab of this . getAll ( ) ) {
2015-03-18 05:47:07 -06:00
var tabId = this . stack . get ( getBrowserForTab ( tab ) ) ;
2015-03-16 10:30:05 -06:00
if ( ! tabId ) {
continue ;
2015-02-28 12:18:58 -07:00
}
2015-03-16 10:30:05 -06:00
if ( tabIds . indexOf ( tabId ) !== - 1 ) {
tabs . push ( tab ) ;
}
if ( tabs . length >= tabIds . length ) {
break ;
2015-02-28 12:18:58 -07:00
}
2015-02-12 11:19:17 -07:00
}
2015-02-28 12:18:58 -07:00
return singleTab ? tabs [ 0 ] || null : tabs ;
2014-12-02 00:35:25 -07:00
} ;
/******************************************************************************/
vAPI . tabs . get = function ( tabId , callback ) {
2015-03-16 10:30:05 -06:00
var tab , win ;
2014-12-02 00:35:25 -07:00
2014-12-28 13:26:06 -07:00
if ( tabId === null ) {
2015-02-12 11:19:17 -07:00
win = Services . wm . getMostRecentWindow ( 'navigator:browser' ) ;
2015-04-25 07:08:01 -06:00
var tabBrowser = getTabBrowser ( win ) ;
if ( tabBrowser ) {
tab = tabBrowser . selectedTab ;
tabId = this . getTabId ( tab ) ;
}
2014-12-28 13:26:06 -07:00
} else {
2015-03-16 10:30:05 -06:00
tab = this . getTabsForIds ( tabId ) ;
if ( tab ) {
win = getOwnerWindow ( tab ) ;
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' ) {
2014-12-16 14:31:03 -07:00
return tab ;
}
2014-12-28 13:26:06 -07:00
if ( ! tab ) {
2014-12-02 00:35:25 -07:00
callback ( ) ;
return ;
}
2015-03-16 10:30:05 -06:00
var windows = this . getWindows ( ) ;
2015-02-28 12:18:58 -07:00
var browser = getBrowserForTab ( tab ) ;
var tabBrowser = getTabBrowser ( win ) ;
2015-03-01 13:38:51 -07:00
var tabIndex , tabTitle ;
if ( vAPI . fennec ) {
tabIndex = tabBrowser . tabs . indexOf ( tab ) ;
tabTitle = browser . contentTitle ;
} else {
tabIndex = tabBrowser . browsers . indexOf ( browser ) ;
tabTitle = tab . label ;
}
2015-02-28 12:18:58 -07:00
callback ( {
id : tabId ,
index : tabIndex ,
windowId : windows . indexOf ( win ) ,
active : tab === tabBrowser . selectedTab ,
url : browser . currentURI . asciiSpec ,
2015-03-01 10:45:12 -07:00
title : tabTitle
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-02-28 12:18:58 -07:00
var win , tab , tabBrowser ;
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 ( ) ) {
var browser = getBrowserForTab ( 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-03-16 10:30:05 -06:00
tab = this . getTabsForIds ( details . tabId ) ;
if ( tab ) {
getBrowserForTab ( tab ) . loadURI ( details . url ) ;
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-02-28 12:18:58 -07:00
win = Services . wm . getMostRecentWindow ( 'navigator:browser' ) ;
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 ) ;
}
var tab = this . getTabsForIds ( tabId ) ;
if ( tab ) {
getBrowserForTab ( tab ) . loadURI ( targetURL ) ;
}
} ;
/******************************************************************************/
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-01-02 10:41:41 -07:00
vAPI . tabs . remove = function ( tabIds ) {
2014-12-28 13:26:06 -07:00
if ( ! Array . isArray ( tabIds ) ) {
2014-12-02 00:35:25 -07:00
tabIds = [ tabIds ] ;
}
2015-03-16 10:30:05 -06:00
var tabs = this . getTabsForIds ( tabIds ) ;
if ( tabs . length === 0 ) {
return ;
}
for ( var tab of tabs ) {
this . _remove ( tab , getTabBrowser ( getOwnerWindow ( tab ) ) ) ;
2014-12-02 00:35:25 -07:00
}
} ;
/******************************************************************************/
2015-01-08 13:11:43 -07:00
vAPI . tabs . reload = function ( tabId ) {
var tab = this . get ( tabId ) ;
2015-02-28 12:18:58 -07:00
if ( ! tab ) {
return ;
}
2015-03-12 08:46:33 -06:00
getBrowserForTab ( tab ) . 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 ) {
tab = typeof tab === 'object' ? tab : this . get ( 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-02-28 12:18:58 -07:00
var tab = this . get ( tabId ) ;
2014-12-16 05:44:34 -07:00
2014-12-28 13:26:06 -07:00
if ( ! tab ) {
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-03-02 11:49:34 -07:00
getBrowserForTab ( tab ) . 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' ) {
2014-12-16 14:31:03 -07:00
setTimeout ( callback , 13 ) ;
}
2014-12-16 05:44:34 -07: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-02-28 12:18:58 -07:00
var curTabId = vAPI . tabs . getTabId ( 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 : {
2014-12-25 06:53:30 -07:00
id : vAPI . tabs . getTabId ( 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
) ;
} ) ;
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-01-20 17:39:13 -07: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-01-20 17:39:13 -07:00
11 : 'xmlhttprequest' ,
12 : 'object' ,
2015-03-20 06:52:01 -06:00
14 : 'font' ,
21 : 'image'
2015-01-02 10:41:41 -07:00
} ,
2015-03-02 09:49:25 -07:00
lastRequest : [ { } , { } ] ,
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-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-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 ;
var type = this . typeMap [ details . type ] || 'other' ;
if ( onBeforeRequest . types . has ( type ) === false ) {
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-03-27 11:00:55 -06:00
/ * i f ( r e s u l t . r e d i r e c t U r l ) {
channel . redirectionLimit = 1 ;
2015-01-02 10:41:41 -07:00
channel . redirectTo (
Services . io . newURI ( result . redirectUrl , null , null )
) ;
2015-03-27 11:00:55 -06:00
return true ;
} * /
2014-12-24 15:11:36 -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-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-03-02 09:49:25 -07:00
var lastRequest = this . lastRequest [ 0 ] ;
if ( lastRequest . url !== URI . spec ) {
if ( this . lastRequest [ 1 ] . url === URI . spec ) {
lastRequest = this . lastRequest [ 1 ] ;
} else {
lastRequest . url = null ;
}
}
2014-12-24 15:11:36 -07:00
2015-01-26 12:26:45 -07:00
if ( lastRequest . url === null ) {
2015-03-02 09:49:25 -07:00
lastRequest . type = channel . loadInfo && channel . loadInfo . contentPolicyType || 1 ;
2015-03-02 04:52:04 -07:00
result = this . handleRequest ( channel , URI , {
2015-02-27 23:38:41 -07:00
tabId : vAPI . noTabId ,
2015-03-02 09:49:25 -07:00
type : lastRequest . type
2015-02-27 23:38:41 -07:00
} ) ;
2015-03-02 04:52:04 -07:00
if ( result === true ) {
return ;
}
if ( channel instanceof Ci . nsIWritablePropertyBag === false ) {
return ;
}
// Carry data for behind-the-scene redirects
2015-03-02 09:49:25 -07:00
channel . setProperty (
this . REQDATAKEY ,
[ lastRequest . type , vAPI . noTabId , null , 0 , - 1 ]
) ;
2014-12-24 15:11:36 -07:00
return ;
}
// Important! When loading file via XHR for mirroring,
// the URL will be the same, so it could fall into an infinite loop
lastRequest . url = null ;
2015-01-26 12:26:45 -07:00
if ( this . handleRequest ( channel , URI , lastRequest ) ) {
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-03-02 04:52:04 -07:00
lastRequest . frameId ,
2015-03-02 10:36:04 -07:00
lastRequest . parentFrameId ,
lastRequest . sourceTabId ,
lastRequest . tabId ,
lastRequest . type
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 ] ,
type : 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 ;
2014-12-24 15:11:36 -07:00
this . onBeforeRequest . types = new Set ( this . onBeforeRequest . types ) ;
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-01-02 10:41:41 -07:00
var details = e . data ;
2015-03-02 10:36:04 -07:00
var tabId = vAPI . tabs . getTabId ( e . target ) ;
var sourceTabId = null ;
// Popup candidate
if ( details . openerURL ) {
for ( var tab of vAPI . tabs . getAll ( ) ) {
2015-03-16 10:30:05 -06:00
var URI = getBrowserForTab ( tab ) . currentURI ;
2015-03-02 10:36:04 -07:00
// Probably isn't the best method to identify the source tab
if ( URI . spec !== details . openerURL ) {
continue ;
}
sourceTabId = vAPI . tabs . getTabId ( tab ) ;
if ( sourceTabId === tabId ) {
sourceTabId = null ;
continue ;
}
URI = Services . io . newURI ( details . url , null , null ) ;
if ( httpObserver . handlePopup ( URI , tabId , sourceTabId ) ) {
return ;
}
break ;
}
}
2014-12-24 15:11:36 -07:00
var lastRequest = httpObserver . lastRequest ;
2015-03-02 09:49:25 -07:00
lastRequest [ 1 ] = lastRequest [ 0 ] ;
lastRequest [ 0 ] = {
frameId : details . frameId ,
parentFrameId : details . parentFrameId ,
2015-03-02 10:36:04 -07:00
sourceTabId : sourceTabId ,
tabId : tabId ,
type : details . type ,
url : details . url
2015-03-02 09:49:25 -07:00
} ;
2015-03-28 13:17:40 -06:00
if ( details . attrSrc !== undefined ) {
lastRequest [ 0 ] . attrSrc = details . attrSrc ;
}
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-04-02 02:35:52 -06:00
if ( details . noRefresh && details . url === browser . currentURI . asciiSpec ) { // If the location changed message specified not to refresh, and the URL is the same, no need to do anything
//console.debug("nsIWebProgressListener: ignoring onLocationChange: " + details.url);
return ;
}
2015-03-26 15:00:56 -06:00
var tabId = vAPI . tabs . getTabId ( browser ) ;
2015-04-04 02:37:55 -06:00
if ( tabId === vAPI . noTabId ) {
return ; // Do not navigate for behind the scenes
}
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-08 16:36:14 -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' ) ;
var curTabId = vAPI . tabs . getTabId ( getTabBrowser ( win ) . selectedTab ) ;
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 ) {
try {
CustomizableUI = Cu . import ( 'resource:///modules/CustomizableUI.jsm' , null ) . CustomizableUI ;
} catch ( ex ) {
}
}
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 ) ;
return ;
}
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-19 09:43:21 -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-04-19 09:43:21 -06:00
'#' + this . viewId + ' {' ,
2015-01-11 10:41:52 -07:00
'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-03-02 11:49:34 -07:00
if ( buttonInPanel ) {
2015-02-28 03:58:09 -07:00
button . classList . remove ( 'badged-button' ) ;
continue ;
}
if ( button === null ) {
continue ;
}
button . classList . add ( 'badged-button' ) ;
}
if ( buttonInPanel ) {
return ;
}
// Anonymous elements need some time to be reachable
2015-03-12 11:48:43 -06:00
setTimeout ( this . updateBadgeStyle , 250 ) ;
2015-03-12 13:38:21 -06:00
} . bind ( this . CUIEvents ) ;
this . CUIEvents . onCustomizeEnd = updateBadge ;
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-03-12 13:38:21 -06:00
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-04-24 11:57:30 -06:00
2015-01-11 10:41:52 -07:00
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 ;
if ( attempts > 1000 ) {
console . error ( 'uBlock> delayedResize: giving up after too many attemps' ) ;
return ;
}
updateTimer = setTimeout ( resizePopup , 10 , attempts ) ;
2015-01-11 10:41:52 -07:00
} ;
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-08 16:36:14 -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-04-19 09:43:21 -06:00
var CustomizableUI = this . CustomizableUI ;
var widgetId = this . id ;
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 ) {
var placement = CustomizableUI . getPlacementOfWidget ( widgetId ) ;
if ( placement . area === CustomizableUI . AREA _PANEL ) {
// Add some overrides for displaying the popup correctly in a panel
win . QueryInterface ( Ci . nsIInterfaceRequestor ) . getInterface ( Ci . nsIDOMWindowUtils )
. loadSheet ( Services . io . newURI ( vAPI . getURL ( "css/popup-vertical.css" ) , null , null ) , Ci . nsIDOMWindowUtils . AUTHOR _SHEET ) ;
}
2015-04-19 09:43:21 -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 || '' ) ;
if ( ! icon || ! icon . img ) {
2015-04-11 12:12:10 -06:00
button . classList . add ( 'off' ) ;
2015-03-10 12:44:31 -06:00
}
else {
2015-04-11 12:12:10 -06:00
button . classList . remove ( 'off' ) ;
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-08 16:36:14 -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
/******************************************************************************/
2015-04-11 23:23:22 -06:00
vAPI . contextMenu . createContextMenuItem = function ( doc ) {
var menuitem = doc . createElement ( 'menuitem' ) ;
menuitem . setAttribute ( 'id' , this . menuItemId ) ;
menuitem . setAttribute ( 'label' , this . menuLabel ) ;
menuitem . setAttribute ( 'image' , vAPI . getURL ( 'img/browsericons/icon16.svg' ) ) ;
menuitem . setAttribute ( 'class' , 'menuitem-iconic' ) ;
return menuitem ;
}
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' ) ;
2015-04-11 23:23:22 -06:00
var menuitem = this . createContextMenuItem ( doc ) ;
2015-02-28 12:18:58 -07:00
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
} ;
/******************************************************************************/
2015-04-11 23:23:22 -06:00
vAPI . contextMenu . registerForWebInspector = function ( eventName , toolbox , panel ) {
var menuPopup = panel . panelDoc . getElementById ( "inspector-node-popup" ) ;
var deleteMenuItem = panel . panelDoc . getElementById ( "node-menu-delete" ) ;
var tiltButton = toolbox . toolboxButtons . filter ( tool => tool . id === "command-button-tilt" ) [ 0 ] ;
tiltButton = tiltButton && tiltButton . button ;
if ( menuPopup && deleteMenuItem ) {
var menuitem = vAPI . contextMenu . createContextMenuItem ( panel . panelDoc ) ;
menuitem . addEventListener ( 'command' , function ( ) {
var selectedNodeFront = panel . selection . nodeFront ;
while ( selectedNodeFront && selectedNodeFront . baseURI !== panel . walker . rootNode . baseURI ) {
// This is an iFrame, so we can't select it directly. Walk up the parent stack until we do
selectedNodeFront = selectedNodeFront . parentNode ( ) ;
}
if ( selectedNodeFront ) {
selectedNodeFront . getUniqueSelector ( ) . then ( selector => µBlock . elementPickerExec ( vAPI . tabs . getTabId ( panel . browser ) , selector ) ) ;
// Turn off 3D view, if it's turned on.
if ( tiltButton && tiltButton . checked ) {
tiltButton . click ( ) ;
}
}
} ) ;
menuPopup . insertBefore ( menuitem , deleteMenuItem ) ;
}
}
/******************************************************************************/
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
2014-12-19 13:24:30 -07:00
details . frameUrl = 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 , {
id : vAPI . tabs . getTabId ( gContextMenu . browser ) ,
2015-01-14 15:45:55 -07:00
url : gContextMenu . browser . currentURI . asciiSpec
2014-12-19 13:24:30 -07:00
} ) ;
} ;
2015-04-11 23:23:22 -06:00
// Also add a context menu to the web inspector
if ( ! vAPI . fennec ) {
try {
this . gDevTools = Cu . import ( 'resource:///modules/devtools/gDevTools.jsm' , null ) . gDevTools ;
} catch ( ex ) {
// console.error(ex);
}
if ( this . gDevTools ) {
this . gDevTools . on ( "inspector-ready" , this . registerForWebInspector ) ;
}
}
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
2015-04-11 23:23:22 -06:00
if ( ! vAPI . fennec && this . gDevTools ) {
this . gDevTools . off ( "inspector-ready" , this . registerForWebInspector ) ;
}
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-03-15 10:49:36 -06:00
addonId : '{2b10c1c8-a11f-4bad-fe9c-1c11e82cac42}' ,
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
var browser = getBrowserForTab ( vAPI . tabs . get ( null ) ) ;
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' ) ;
this . setupOptionsButton ( doc , 'showNetworkLogButton' , 'devtools.html' ) ;
}
} ;
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 ;
for ( var tab of this . tabs . getAll ( ) ) {
// We're insterested in only the tabs that were already loaded
2015-03-15 08:06:48 -06:00
if ( ! vAPI . fennec && tab . hasAttribute ( 'pending' ) ) {
2015-03-12 11:20:48 -06:00
continue ;
}
var tabId = this . tabs . getTabId ( tab ) ;
var browser = getBrowserForTab ( tab ) ;
2015-04-13 18:58:50 -06:00
µb . tabContextManager . commit ( tabId , browser . currentURI . asciiSpec ) ;
2015-03-15 08:06:48 -06:00
µb . bindTabToPageStats ( tabId , browser . currentURI . asciiSpec ) ;
2015-03-12 11:20:48 -06:00
browser . messageManager . sendAsyncMessage (
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
} ) ( ) ;
/******************************************************************************/