2013-02-12 12:51:25 -08:00
// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
/ * T h i s S o u r c e C o d e F o r m i s s u b j e c t t o t h e t e r m s o f t h e M o z i l l a P u b l i c
* License , v . 2.0 . If a copy of the MPL was not distributed with this
* file , You can obtain one at http : //mozilla.org/MPL/2.0/. */
let Cc = Components . classes ;
let Ci = Components . interfaces ;
let Cu = Components . utils ;
let Cr = Components . results ;
2013-08-07 10:51:43 -07:00
Cu . import ( "resource://gre/modules/PageThumbs.jsm" ) ;
2013-08-09 02:50:58 -07:00
// Page for which the start UI is shown
2013-12-12 12:55:31 -08:00
const kStartURI = "about:newtab" ;
2013-08-09 02:50:58 -07:00
2013-02-12 12:51:25 -08:00
// allow panning after this timeout on pages with registered touch listeners
const kTouchTimeout = 300 ;
const kSetInactiveStateTimeout = 100 ;
2013-08-07 10:51:43 -07:00
const kTabThumbnailDelayCapture = 500 ;
2013-08-05 23:29:54 -07:00
const XUL _NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" ;
2013-08-09 02:50:59 -07:00
// See grid.xml, we use this to cache style info across loads of the startui.
var _richgridTileSizes = { } ;
2013-02-12 12:51:25 -08:00
// Override sizeToContent in the main window. It breaks things (bug 565887)
window . sizeToContent = function ( ) {
Cu . reportError ( "window.sizeToContent is not allowed in this window" ) ;
}
2013-08-05 23:29:54 -07:00
function getTabModalPromptBox ( aWindow ) {
let browser = Browser . getBrowserForWindow ( aWindow ) ;
return Browser . getTabModalPromptBox ( browser ) ;
}
2013-07-26 05:05:37 -07:00
/ *
* Returns the browser for the currently displayed tab .
* /
2013-02-12 12:51:25 -08:00
function getBrowser ( ) {
return Browser . selectedBrowser ;
}
var Browser = {
_debugEvents : false ,
_tabs : [ ] ,
_selectedTab : null ,
_tabId : 0 ,
windowUtils : window . QueryInterface ( Ci . nsIInterfaceRequestor )
. getInterface ( Ci . nsIDOMWindowUtils ) ,
get defaultBrowserWidth ( ) {
return window . innerWidth ;
} ,
startup : function startup ( ) {
var self = this ;
try {
messageManager . loadFrameScript ( "chrome://browser/content/Util.js" , true ) ;
messageManager . loadFrameScript ( "chrome://browser/content/contenthandlers/Content.js" , true ) ;
messageManager . loadFrameScript ( "chrome://browser/content/contenthandlers/FormHelper.js" , true ) ;
2013-06-17 05:46:52 -07:00
messageManager . loadFrameScript ( "chrome://browser/content/library/SelectionPrototype.js" , true ) ;
2013-02-12 12:51:25 -08:00
messageManager . loadFrameScript ( "chrome://browser/content/contenthandlers/SelectionHandler.js" , true ) ;
messageManager . loadFrameScript ( "chrome://browser/content/contenthandlers/ContextMenuHandler.js" , true ) ;
2013-08-28 13:58:25 -07:00
messageManager . loadFrameScript ( "chrome://browser/content/contenthandlers/ConsoleAPIObserver.js" , true ) ;
2013-02-12 12:51:25 -08:00
} catch ( e ) {
// XXX whatever is calling startup needs to dump errors!
dump ( "###########" + e + "\n" ) ;
}
2013-12-17 08:11:14 -08:00
if ( ! Services . metro ) {
// Services.metro is only available on Windows Metro. We want to be able
// to test metro on other platforms, too, so we provide a minimal shim.
Services . metro = {
activationURI : "" ,
pinTileAsync : function ( ) { } ,
unpinTileAsync : function ( ) { }
} ;
}
2013-02-12 12:51:25 -08:00
/* handles dispatching clicks on browser into clicks in content or zooms */
Elements . browsers . customDragger = new Browser . MainDragger ( ) ;
/* handles web progress management for open browsers */
Elements . browsers . webProgress = WebProgress . init ( ) ;
// Call InputSourceHelper first so global listeners get called before
// we start processing input in TouchModule.
InputSourceHelper . init ( ) ;
2013-11-12 18:05:55 -08:00
ClickEventHandler . init ( ) ;
2013-02-12 12:51:25 -08:00
TouchModule . init ( ) ;
GestureModule . init ( ) ;
BrowserTouchHandler . init ( ) ;
2013-03-08 14:28:52 -08:00
PopupBlockerObserver . init ( ) ;
2013-07-25 10:15:13 -07:00
APZCObserver . init ( ) ;
2013-02-12 12:51:25 -08:00
2013-07-08 13:45:03 -07:00
// Init the touch scrollbox
2013-02-12 12:51:25 -08:00
this . contentScrollbox = Elements . browsers ;
this . contentScrollboxScroller = {
scrollBy : function ( aDx , aDy ) {
let view = getBrowser ( ) . getRootView ( ) ;
view . scrollBy ( aDx , aDy ) ;
} ,
scrollTo : function ( aX , aY ) {
let view = getBrowser ( ) . getRootView ( ) ;
view . scrollTo ( aX , aY ) ;
} ,
getPosition : function ( aScrollX , aScrollY ) {
let view = getBrowser ( ) . getRootView ( ) ;
let scroll = view . getPosition ( ) ;
aScrollX . value = scroll . x ;
aScrollY . value = scroll . y ;
}
} ;
ContentAreaObserver . init ( ) ;
function fullscreenHandler ( ) {
2013-05-16 22:14:29 -07:00
if ( Browser . selectedBrowser . contentWindow . document . mozFullScreenElement )
Elements . stack . setAttribute ( "fullscreen" , "true" ) ;
2013-02-12 12:51:25 -08:00
else
2013-05-16 22:14:29 -07:00
Elements . stack . removeAttribute ( "fullscreen" ) ;
2013-02-12 12:51:25 -08:00
}
2013-05-16 22:14:29 -07:00
window . addEventListener ( "mozfullscreenchange" , fullscreenHandler , true ) ;
2013-02-12 12:51:25 -08:00
BrowserUI . init ( ) ;
window . controllers . appendController ( this ) ;
window . controllers . appendController ( BrowserUI ) ;
2013-09-24 13:17:03 -07:00
Services . obs . addObserver ( SessionHistoryObserver , "browser:purge-session-history" , false ) ;
2013-02-12 12:51:25 -08:00
window . QueryInterface ( Ci . nsIDOMChromeWindow ) . browserDOMWindow = new nsBrowserAccess ( ) ;
Elements . browsers . addEventListener ( "DOMUpdatePageReport" , PopupBlockerObserver . onUpdatePageReport , false ) ;
// Make sure we're online before attempting to load
Util . forceOnline ( ) ;
// If this is an intial window launch the commandline handler passes us the default
2013-07-26 05:05:37 -07:00
// page as an argument.
2013-02-12 12:51:25 -08:00
let commandURL = null ;
2013-07-26 05:05:37 -07:00
try {
let argsObj = window . arguments [ 0 ] . wrappedJSObject ;
if ( argsObj && argsObj . pageloadURL ) {
// Talos tp-cmdline parameter
commandURL = argsObj . pageloadURL ;
} else if ( window . arguments && window . arguments [ 0 ] ) {
// BrowserCLH paramerter
commandURL = window . arguments [ 0 ] ;
}
} catch ( ex ) {
Util . dumpLn ( ex ) ;
}
2013-02-12 12:51:25 -08:00
messageManager . addMessageListener ( "DOMLinkAdded" , this ) ;
messageManager . addMessageListener ( "Browser:FormSubmit" , this ) ;
messageManager . addMessageListener ( "Browser:CanUnload:Return" , this ) ;
messageManager . addMessageListener ( "scroll" , this ) ;
messageManager . addMessageListener ( "Browser:CertException" , this ) ;
messageManager . addMessageListener ( "Browser:BlockedSite" , this ) ;
messageManager . addMessageListener ( "Browser:TapOnSelection" , this ) ;
2013-03-26 12:41:46 -07:00
Task . spawn ( function ( ) {
// Activation URIs come from protocol activations, secondary tiles, and file activations
2013-08-28 10:27:32 -07:00
let activationURI = yield this . getShortcutOrURI ( Services . metro . activationURI ) ;
2013-03-26 12:41:46 -07:00
let self = this ;
function loadStartupURI ( ) {
2013-08-09 02:50:58 -07:00
if ( activationURI ) {
2014-01-12 12:02:27 -08:00
let webNav = Ci . nsIWebNavigation ;
let flags = webNav . LOAD _FLAGS _ALLOW _THIRD _PARTY _FIXUP |
webNav . LOAD _FLAGS _FIXUP _SCHEME _TYPOS ;
self . addTab ( activationURI , true , null , { flags : flags } ) ;
2013-03-26 12:41:46 -07:00
} else {
2013-08-09 02:50:58 -07:00
let uri = commandURL || Browser . getHomePage ( ) ;
2013-03-26 12:41:46 -07:00
self . addTab ( uri , true ) ;
}
}
// Should we restore the previous session (crash or some other event)
2014-01-16 15:38:12 -08:00
let ss = Cc [ "@mozilla.org/browser/sessionstore;1" ]
. getService ( Ci . nsISessionStore ) ;
2014-01-23 13:34:27 -08:00
let shouldRestore = ss . shouldRestore ( ) ;
2014-01-16 15:38:12 -08:00
if ( shouldRestore ) {
2013-03-26 12:41:46 -07:00
let bringFront = false ;
// First open any commandline URLs, except the homepage
2013-08-09 02:50:58 -07:00
if ( activationURI && activationURI != kStartURI ) {
2013-03-26 12:41:46 -07:00
this . addTab ( activationURI , true , null , { flags : Ci . nsIWebNavigation . LOAD _FLAGS _ALLOW _THIRD _PARTY _FIXUP } ) ;
2013-08-09 02:50:58 -07:00
} else if ( commandURL && commandURL != kStartURI ) {
2013-03-26 12:41:46 -07:00
this . addTab ( commandURL , true ) ;
} else {
bringFront = true ;
// Initial window resizes call functions that assume a tab is in the tab list
// and restored tabs are added too late. We add a dummy to to satisfy the resize
// code and then remove the dummy after the session has been restored.
let dummy = this . addTab ( "about:blank" , true ) ;
let dummyCleanup = {
observe : function ( aSubject , aTopic , aData ) {
Services . obs . removeObserver ( dummyCleanup , "sessionstore-windows-restored" ) ;
if ( aData == "fail" )
loadStartupURI ( ) ;
dummy . chromeTab . ignoreUndo = true ;
Browser . closeTab ( dummy , { forceClose : true } ) ;
}
} ;
Services . obs . addObserver ( dummyCleanup , "sessionstore-windows-restored" , false ) ;
}
ss . restoreLastSession ( bringFront ) ;
} else {
loadStartupURI ( ) ;
}
2013-07-08 13:45:03 -07:00
// Notify about our input type
InputSourceHelper . fireUpdate ( ) ;
2013-03-26 12:41:46 -07:00
// Broadcast a UIReady message so add-ons know we are finished with startup
let event = document . createEvent ( "Events" ) ;
event . initEvent ( "UIReady" , true , false ) ;
window . dispatchEvent ( event ) ;
} . bind ( this ) ) ;
2013-02-12 12:51:25 -08:00
} ,
shutdown : function shutdown ( ) {
2013-07-25 10:15:13 -07:00
APZCObserver . shutdown ( ) ;
2013-02-12 12:51:25 -08:00
BrowserUI . uninit ( ) ;
2013-11-12 18:05:55 -08:00
ClickEventHandler . uninit ( ) ;
2013-04-05 03:33:42 -07:00
ContentAreaObserver . shutdown ( ) ;
2013-08-22 14:42:54 -07:00
Appbar . shutdown ( ) ;
2013-02-12 12:51:25 -08:00
messageManager . removeMessageListener ( "Browser:FormSubmit" , this ) ;
messageManager . removeMessageListener ( "scroll" , this ) ;
messageManager . removeMessageListener ( "Browser:CertException" , this ) ;
messageManager . removeMessageListener ( "Browser:BlockedSite" , this ) ;
messageManager . removeMessageListener ( "Browser:TapOnSelection" , this ) ;
2013-09-24 13:17:03 -07:00
Services . obs . removeObserver ( SessionHistoryObserver , "browser:purge-session-history" ) ;
2013-02-12 12:51:25 -08:00
window . controllers . removeController ( this ) ;
window . controllers . removeController ( BrowserUI ) ;
} ,
getHomePage : function getHomePage ( aOptions ) {
aOptions = aOptions || { useDefault : false } ;
2013-08-09 02:50:58 -07:00
let url = kStartURI ;
2013-02-12 12:51:25 -08:00
try {
let prefs = aOptions . useDefault ? Services . prefs . getDefaultBranch ( null ) : Services . prefs ;
url = prefs . getComplexValue ( "browser.startup.homepage" , Ci . nsIPrefLocalizedString ) . data ;
}
catch ( e ) { }
return url ;
} ,
get browsers ( ) {
return this . _tabs . map ( function ( tab ) { return tab . browser ; } ) ;
} ,
/ * *
* Load a URI in the current tab , or a new tab if necessary .
* @ param aURI String
* @ param aParams Object with optional properties that will be passed to loadURIWithFlags :
* flags , referrerURI , charset , postData .
* /
loadURI : function loadURI ( aURI , aParams ) {
let browser = this . selectedBrowser ;
// We need to keep about: pages opening in new "local" tabs. We also want to spawn
// new "remote" tabs if opening web pages from a "local" about: page.
dump ( "loadURI=" + aURI + "\ncurrentURI=" + browser . currentURI . spec + "\n" ) ;
let params = aParams || { } ;
try {
let flags = params . flags || Ci . nsIWebNavigation . LOAD _FLAGS _NONE ;
let postData = ( "postData" in params && params . postData ) ? params . postData . value : null ;
let referrerURI = "referrerURI" in params ? params . referrerURI : null ;
let charset = "charset" in params ? params . charset : null ;
dump ( "loading tab: " + aURI + "\n" ) ;
browser . loadURIWithFlags ( aURI , flags , referrerURI , charset , postData ) ;
} catch ( e ) {
dump ( "Error: " + e + "\n" ) ;
}
} ,
/ * *
* Determine if the given URL is a shortcut / keyword and , if so , expand it
* @ param aURL String
* @ param aPostDataRef Out param contains any required post data for a search
2013-03-26 12:41:46 -07:00
* @ return { Promise }
* @ result the expanded shortcut , or the original URL if not a shortcut
2013-02-12 12:51:25 -08:00
* /
getShortcutOrURI : function getShortcutOrURI ( aURL , aPostDataRef ) {
2013-03-26 12:41:46 -07:00
return Task . spawn ( function ( ) {
if ( ! aURL )
throw new Task . Result ( aURL ) ;
let shortcutURL = null ;
let keyword = aURL ;
let param = "" ;
let offset = aURL . indexOf ( " " ) ;
if ( offset > 0 ) {
keyword = aURL . substr ( 0 , offset ) ;
param = aURL . substr ( offset + 1 ) ;
2013-02-12 12:51:25 -08:00
}
2013-03-26 12:41:46 -07:00
if ( ! aPostDataRef )
aPostDataRef = { } ;
2013-02-12 12:51:25 -08:00
2013-03-26 12:41:46 -07:00
let engine = Services . search . getEngineByAlias ( keyword ) ;
if ( engine ) {
let submission = engine . getSubmission ( param ) ;
aPostDataRef . value = submission . postData ;
throw new Task . Result ( submission . uri . spec ) ;
}
2013-02-12 12:51:25 -08:00
2013-03-26 12:41:46 -07:00
try {
[ shortcutURL , aPostDataRef . value ] = PlacesUtils . getURLAndPostDataForKeyword ( keyword ) ;
} catch ( e ) { }
if ( ! shortcutURL )
throw new Task . Result ( aURL ) ;
let postData = "" ;
if ( aPostDataRef . value )
postData = unescape ( aPostDataRef . value ) ;
if ( /%s/i . test ( shortcutURL ) || /%s/i . test ( postData ) ) {
let charset = "" ;
const re = /^(.*)\&mozcharset=([a-zA-Z][_\-a-zA-Z0-9]+)\s*$/ ;
let matches = shortcutURL . match ( re ) ;
if ( matches )
[ , shortcutURL , charset ] = matches ;
else {
// Try to get the saved character-set.
try {
// makeURI throws if URI is invalid.
// Will return an empty string if character-set is not found.
charset = yield PlacesUtils . getCharsetForURI ( Util . makeURI ( shortcutURL ) ) ;
} catch ( e ) { dump ( "--- error " + e + "\n" ) ; }
}
let encodedParam = "" ;
if ( charset )
encodedParam = escape ( convertFromUnicode ( charset , param ) ) ;
else // Default charset is UTF-8
encodedParam = encodeURIComponent ( param ) ;
shortcutURL = shortcutURL . replace ( /%s/g , encodedParam ) . replace ( /%S/g , param ) ;
if ( /%s/i . test ( postData ) ) // POST keyword
aPostDataRef . value = getPostDataStream ( postData , param , encodedParam , "application/x-www-form-urlencoded" ) ;
} else if ( param ) {
// This keyword doesn't take a parameter, but one was provided. Just return
// the original URL.
aPostDataRef . value = null ;
throw new Task . Result ( aURL ) ;
}
2013-02-12 12:51:25 -08:00
2013-03-26 12:41:46 -07:00
throw new Task . Result ( shortcutURL ) ;
} ) ;
2013-02-12 12:51:25 -08:00
} ,
/ * *
* Return the currently active < browser > object
* /
get selectedBrowser ( ) {
return ( this . _selectedTab && this . _selectedTab . browser ) ;
} ,
get tabs ( ) {
return this . _tabs ;
} ,
2013-08-05 23:29:54 -07:00
getTabModalPromptBox : function ( aBrowser ) {
let browser = ( aBrowser || getBrowser ( ) ) ;
let stack = browser . parentNode ;
let self = this ;
let promptBox = {
appendPrompt : function ( args , onCloseCallback ) {
let newPrompt = document . createElementNS ( XUL _NS , "tabmodalprompt" ) ;
2013-08-30 12:22:48 -07:00
newPrompt . setAttribute ( "promptType" , args . promptType ) ;
2013-08-05 23:29:54 -07:00
stack . appendChild ( newPrompt ) ;
browser . setAttribute ( "tabmodalPromptShowing" , true ) ;
newPrompt . clientTop ; // style flush to assure binding is attached
let tab = self . getTabForBrowser ( browser ) ;
tab = tab . chromeTab ;
2013-08-30 12:22:48 -07:00
newPrompt . metroInit ( args , tab , onCloseCallback ) ;
2013-08-05 23:29:54 -07:00
return newPrompt ;
} ,
removePrompt : function ( aPrompt ) {
stack . removeChild ( aPrompt ) ;
let prompts = this . listPrompts ( ) ;
if ( prompts . length ) {
let prompt = prompts [ prompts . length - 1 ] ;
prompt . Dialog . setDefaultFocus ( ) ;
} else {
browser . removeAttribute ( "tabmodalPromptShowing" ) ;
browser . focus ( ) ;
}
} ,
listPrompts : function ( aPrompt ) {
let els = stack . getElementsByTagNameNS ( XUL _NS , "tabmodalprompt" ) ;
// NodeList --> real JS array
let prompts = Array . slice ( els ) ;
return prompts ;
} ,
} ;
return promptBox ;
2013-02-12 12:51:25 -08:00
} ,
getBrowserForWindowId : function getBrowserForWindowId ( aWindowId ) {
for ( let i = 0 ; i < this . browsers . length ; i ++ ) {
if ( this . browsers [ i ] . contentWindowId == aWindowId )
return this . browsers [ i ] ;
}
return null ;
} ,
2013-08-05 23:29:54 -07:00
getBrowserForWindow : function ( aWindow ) {
let windowID = aWindow . QueryInterface ( Ci . nsIInterfaceRequestor )
. getInterface ( Ci . nsIDOMWindowUtils ) . currentInnerWindowID ;
return this . getBrowserForWindowId ( windowID ) ;
} ,
getTabForBrowser : function getTabForBrowser ( aBrowser ) {
let tabs = this . _tabs ;
for ( let i = 0 ; i < tabs . length ; i ++ ) {
if ( tabs [ i ] . browser == aBrowser )
return tabs [ i ] ;
}
return null ;
} ,
2013-02-12 12:51:25 -08:00
getTabAtIndex : function getTabAtIndex ( index ) {
if ( index >= this . _tabs . length || index < 0 )
return null ;
return this . _tabs [ index ] ;
} ,
getTabFromChrome : function getTabFromChrome ( chromeTab ) {
for ( var t = 0 ; t < this . _tabs . length ; t ++ ) {
if ( this . _tabs [ t ] . chromeTab == chromeTab )
return this . _tabs [ t ] ;
}
return null ;
} ,
2013-04-25 08:43:42 -07:00
2013-02-12 12:51:25 -08:00
createTabId : function createTabId ( ) {
return this . _tabId ++ ;
} ,
2013-11-08 15:39:14 -08:00
/ * *
* Create a new tab and add it to the tab list .
*
* If you are opening a new foreground tab in response to a user action , use
* BrowserUI . addAndShowTab which will also show the tab strip .
*
* @ param aURI String specifying the URL to load .
* @ param aBringFront Boolean ( optional ) Open the new tab in the foreground ?
* @ param aOwner Tab object ( optional ) The "parent" of the new tab .
* This is the tab responsible for opening the new tab . When the new tab
* is closed , we will return to a parent or "sibling" tab if possible .
* @ param aParams Object ( optional ) with optional properties :
* index : Number specifying where in the tab list to insert the new tab .
2014-01-27 15:42:50 -08:00
* private : If true , the new tab should be have Private Browsing active .
2013-11-08 15:39:14 -08:00
* flags , postData , charset , referrerURI : See loadURIWithFlags .
* /
2013-02-12 12:51:25 -08:00
addTab : function browser _addTab ( aURI , aBringFront , aOwner , aParams ) {
let params = aParams || { } ;
2013-11-08 15:39:14 -08:00
if ( aOwner && ! ( 'index' in params ) ) {
// Position the new tab to the right of its owner...
params . index = this . _tabs . indexOf ( aOwner ) + 1 ;
// ...and to the right of any siblings.
while ( this . _tabs [ params . index ] && this . _tabs [ params . index ] . owner == aOwner ) {
params . index ++ ;
}
}
2013-05-15 18:06:42 -07:00
let newTab = new Tab ( aURI , params , aOwner ) ;
2013-11-08 15:39:14 -08:00
if ( params . index >= 0 ) {
this . _tabs . splice ( params . index , 0 , newTab ) ;
} else {
this . _tabs . push ( newTab ) ;
}
2013-02-12 12:51:25 -08:00
if ( aBringFront )
this . selectedTab = newTab ;
2013-11-12 18:05:55 -08:00
this . _announceNewTab ( newTab ) ;
2013-02-12 12:51:25 -08:00
return newTab ;
} ,
closeTab : function closeTab ( aTab , aOptions ) {
let tab = aTab instanceof XULElement ? this . getTabFromChrome ( aTab ) : aTab ;
2013-04-17 10:40:38 -07:00
if ( ! tab ) {
2013-02-12 12:51:25 -08:00
return ;
2013-04-17 10:40:38 -07:00
}
2013-02-12 12:51:25 -08:00
if ( aOptions && "forceClose" in aOptions && aOptions . forceClose ) {
2013-04-17 10:40:38 -07:00
this . _doCloseTab ( tab ) ;
2013-02-12 12:51:25 -08:00
return ;
}
tab . browser . messageManager . sendAsyncMessage ( "Browser:CanUnload" , { } ) ;
} ,
2013-03-02 05:25:19 -08:00
savePage : function ( ) {
ContentAreaUtils . saveDocument ( this . selectedBrowser . contentWindow . document ) ;
} ,
2013-04-19 03:25:36 -07:00
/ *
* helper for addTab related methods . Fires events related to
* new tab creation .
* /
2013-11-12 18:05:55 -08:00
_announceNewTab : function ( aTab ) {
2013-04-19 03:25:36 -07:00
let event = document . createEvent ( "UIEvents" ) ;
2013-11-08 15:44:01 -08:00
event . initUIEvent ( "TabOpen" , true , false , window , 0 ) ;
2013-04-19 03:25:36 -07:00
aTab . chromeTab . dispatchEvent ( event ) ;
aTab . browser . messageManager . sendAsyncMessage ( "Browser:TabOpen" ) ;
} ,
2013-02-12 12:51:25 -08:00
_doCloseTab : function _doCloseTab ( aTab ) {
2013-04-17 10:40:38 -07:00
if ( this . _tabs . length === 1 ) {
Browser . addTab ( this . getHomePage ( ) ) ;
}
2013-04-03 09:34:55 -07:00
let nextTab = this . getNextTab ( aTab ) ;
2013-02-12 12:51:25 -08:00
// Tabs owned by the closed tab are now orphaned.
this . _tabs . forEach ( function ( item , index , array ) {
if ( item . owner == aTab )
item . owner = null ;
} ) ;
2013-08-09 02:50:59 -07:00
// tray tab
2013-02-12 12:51:25 -08:00
let event = document . createEvent ( "Events" ) ;
event . initEvent ( "TabClose" , true , false ) ;
aTab . chromeTab . dispatchEvent ( event ) ;
2013-08-09 02:50:59 -07:00
// tab window
event = document . createEvent ( "Events" ) ;
event . initEvent ( "TabClose" , true , false ) ;
aTab . browser . contentWindow . dispatchEvent ( event ) ;
2013-02-12 12:51:25 -08:00
aTab . browser . messageManager . sendAsyncMessage ( "Browser:TabClose" ) ;
let container = aTab . chromeTab . parentNode ;
aTab . destroy ( ) ;
this . _tabs . splice ( this . _tabs . indexOf ( aTab ) , 1 ) ;
this . selectedTab = nextTab ;
event = document . createEvent ( "Events" ) ;
event . initEvent ( "TabRemove" , true , false ) ;
container . dispatchEvent ( event ) ;
} ,
2013-04-03 09:34:55 -07:00
getNextTab : function getNextTab ( aTab ) {
2013-02-12 12:51:25 -08:00
let tabIndex = this . _tabs . indexOf ( aTab ) ;
if ( tabIndex == - 1 )
return null ;
2013-04-03 09:34:55 -07:00
if ( this . _selectedTab == aTab || this . _selectedTab . chromeTab . hasAttribute ( "closing" ) ) {
let nextTabIndex = tabIndex + 1 ;
let nextTab = null ;
while ( nextTabIndex < this . _tabs . length && ( ! nextTab || nextTab . chromeTab . hasAttribute ( "closing" ) ) ) {
nextTab = this . getTabAtIndex ( nextTabIndex ) ;
nextTabIndex ++ ;
}
nextTabIndex = tabIndex - 1 ;
while ( nextTabIndex >= 0 && ( ! nextTab || nextTab . chromeTab . hasAttribute ( "closing" ) ) ) {
nextTab = this . getTabAtIndex ( nextTabIndex ) ;
nextTabIndex -- ;
}
if ( ! nextTab || nextTab . chromeTab . hasAttribute ( "closing" ) )
return null ;
2013-02-12 12:51:25 -08:00
// If the next tab is not a sibling, switch back to the parent.
if ( aTab . owner && nextTab . owner != aTab . owner )
nextTab = aTab . owner ;
if ( ! nextTab )
return null ;
2013-04-03 09:34:55 -07:00
return nextTab ;
2013-02-12 12:51:25 -08:00
}
2013-04-03 09:34:55 -07:00
return this . _selectedTab ;
2013-02-12 12:51:25 -08:00
} ,
get selectedTab ( ) {
return this . _selectedTab ;
} ,
set selectedTab ( tab ) {
if ( tab instanceof XULElement )
tab = this . getTabFromChrome ( tab ) ;
if ( ! tab )
return ;
if ( this . _selectedTab == tab ) {
// Deck does not update its selectedIndex when children
// are removed. See bug 602708
Elements . browsers . selectedPanel = tab . notification ;
return ;
}
let isFirstTab = this . _selectedTab == null ;
let lastTab = this . _selectedTab ;
let browser = tab . browser ;
this . _selectedTab = tab ;
2013-04-25 08:43:42 -07:00
2013-02-12 12:51:25 -08:00
if ( lastTab )
lastTab . active = false ;
if ( tab )
tab . active = true ;
2013-11-08 11:55:18 -08:00
BrowserUI . update ( ) ;
2013-02-12 12:51:25 -08:00
if ( isFirstTab ) {
BrowserUI . _titleChanged ( browser ) ;
} else {
// Update all of our UI to reflect the new tab's location
BrowserUI . updateURI ( ) ;
let event = document . createEvent ( "Events" ) ;
event . initEvent ( "TabSelect" , true , false ) ;
event . lastTab = lastTab ;
tab . chromeTab . dispatchEvent ( event ) ;
}
tab . lastSelected = Date . now ( ) ;
} ,
supportsCommand : function ( cmd ) {
return false ;
} ,
isCommandEnabled : function ( cmd ) {
return false ;
} ,
doCommand : function ( cmd ) {
} ,
getNotificationBox : function getNotificationBox ( aBrowser ) {
let browser = aBrowser || this . selectedBrowser ;
2013-08-05 23:29:54 -07:00
return browser . parentNode . parentNode ;
2013-02-12 12:51:25 -08:00
} ,
/ * *
* Handle cert exception message from content .
* /
_handleCertException : function _handleCertException ( aMessage ) {
let json = aMessage . json ;
if ( json . action == "leave" ) {
// Get the start page from the *default* pref branch, not the user's
let url = Browser . getHomePage ( { useDefault : true } ) ;
this . loadURI ( url ) ;
} else {
// Handle setting an cert exception and reloading the page
try {
// Add a new SSL exception for this URL
let uri = Services . io . newURI ( json . url , null , null ) ;
let sslExceptions = new SSLExceptions ( ) ;
if ( json . action == "permanent" )
2013-05-28 10:51:17 -07:00
sslExceptions . addPermanentException ( uri , window ) ;
2013-02-12 12:51:25 -08:00
else
2013-05-28 10:51:17 -07:00
sslExceptions . addTemporaryException ( uri , window ) ;
2013-02-12 12:51:25 -08:00
} catch ( e ) {
dump ( "EXCEPTION handle content command: " + e + "\n" ) ;
}
// Automatically reload after the exception was added
aMessage . target . reload ( ) ;
}
} ,
/ * *
* Handle blocked site message from content .
* /
_handleBlockedSite : function _handleBlockedSite ( aMessage ) {
let formatter = Cc [ "@mozilla.org/toolkit/URLFormatterService;1" ] . getService ( Ci . nsIURLFormatter ) ;
let json = aMessage . json ;
switch ( json . action ) {
case "leave" : {
// Get the start page from the *default* pref branch, not the user's
let url = Browser . getHomePage ( { useDefault : true } ) ;
this . loadURI ( url ) ;
break ;
}
case "report-malware" : {
// Get the stop badware "why is this blocked" report url, append the current url, and go there.
try {
let reportURL = formatter . formatURLPref ( "browser.safebrowsing.malware.reportURL" ) ;
reportURL += json . url ;
this . loadURI ( reportURL ) ;
} catch ( e ) {
Cu . reportError ( "Couldn't get malware report URL: " + e ) ;
}
break ;
}
case "report-phishing" : {
2013-10-08 14:00:48 -07:00
// It's a phishing site, just link to the generic information page
let url = Services . urlFormatter . formatURLPref ( "app.support.baseURL" ) ;
this . loadURI ( url + "phishing-malware" ) ;
2013-02-12 12:51:25 -08:00
break ;
}
}
} ,
pinSite : function browser _pinSite ( ) {
// Get a path to our app tile
2013-04-25 08:43:42 -07:00
var file = Components . classes [ "@mozilla.org/file/directory_service;1" ] .
getService ( Components . interfaces . nsIProperties ) .
2013-02-12 12:51:25 -08:00
get ( "CurProcD" , Components . interfaces . nsIFile ) ;
// Get rid of the current working directory's metro subidr
file = file . parent ;
file . append ( "tileresources" ) ;
file . append ( "VisualElements_logo.png" ) ;
2013-04-25 08:43:42 -07:00
var ios = Components . classes [ "@mozilla.org/network/io-service;1" ] .
getService ( Components . interfaces . nsIIOService ) ;
2013-02-12 12:51:25 -08:00
var uriSpec = ios . newFileURI ( file ) . spec ;
2013-08-28 10:27:32 -07:00
Services . metro . pinTileAsync ( this . _currentPageTileID ,
Browser . selectedBrowser . contentTitle , // short name
Browser . selectedBrowser . contentTitle , // display name
2014-01-30 12:29:12 -08:00
"-url " + Browser . selectedBrowser . currentURI . spec ,
2013-04-25 08:43:42 -07:00
uriSpec , uriSpec ) ;
2013-02-12 12:51:25 -08:00
} ,
2013-04-25 08:43:42 -07:00
get _currentPageTileID ( ) {
2013-02-12 12:51:25 -08:00
// We use a unique ID per URL, so just use an MD5 hash of the URL as the ID.
let hasher = Cc [ "@mozilla.org/security/hash;1" ] .
createInstance ( Ci . nsICryptoHash ) ;
hasher . init ( Ci . nsICryptoHash . MD5 ) ;
let stringStream = Cc [ "@mozilla.org/io/string-input-stream;1" ] .
createInstance ( Ci . nsIStringInputStream ) ;
stringStream . data = Browser . selectedBrowser . currentURI . spec ;
hasher . updateFromStream ( stringStream , - 1 ) ;
let hashASCII = hasher . finish ( true ) ;
2013-04-25 08:43:42 -07:00
// Replace '/' with a valid filesystem character
return ( "FFTileID_" + hashASCII ) . replace ( '/' , '_' , 'g' ) ;
} ,
2013-02-12 12:51:25 -08:00
2013-04-25 08:43:42 -07:00
unpinSite : function browser _unpinSite ( ) {
2013-08-28 10:27:32 -07:00
if ( ! Services . metro . immersive )
2013-04-25 08:43:42 -07:00
return ;
2013-08-28 10:27:32 -07:00
Services . metro . unpinTileAsync ( this . _currentPageTileID ) ;
2013-02-12 12:51:25 -08:00
} ,
isSitePinned : function browser _isSitePinned ( ) {
2013-08-28 10:27:32 -07:00
if ( ! Services . metro . immersive )
2013-02-12 12:51:25 -08:00
return false ;
2013-08-28 10:27:32 -07:00
return Services . metro . isTilePinned ( this . _currentPageTileID ) ;
2013-02-12 12:51:25 -08:00
} ,
starSite : function browser _starSite ( callback ) {
let uri = this . selectedBrowser . currentURI ;
let title = this . selectedBrowser . contentTitle ;
Bookmarks . addForURI ( uri , title , callback ) ;
} ,
unstarSite : function browser _unstarSite ( callback ) {
let uri = this . selectedBrowser . currentURI ;
Bookmarks . removeForURI ( uri , callback ) ;
} ,
isSiteStarredAsync : function browser _isSiteStarredAsync ( callback ) {
let uri = this . selectedBrowser . currentURI ;
Bookmarks . isURIBookmarked ( uri , callback ) ;
} ,
/ * *
* Convenience function for getting the scrollbox position off of a
* scrollBoxObject interface . Returns the actual values instead of the
* wrapping objects .
*
* @ param scroller a scrollBoxObject on which to call scroller . getPosition ( )
* /
getScrollboxPosition : function getScrollboxPosition ( scroller ) {
let x = { } ;
let y = { } ;
scroller . getPosition ( x , y ) ;
return new Point ( x . value , y . value ) ;
} ,
forceChromeReflow : function forceChromeReflow ( ) {
let dummy = getComputedStyle ( document . documentElement , "" ) . width ;
} ,
receiveMessage : function receiveMessage ( aMessage ) {
let json = aMessage . json ;
let browser = aMessage . target ;
switch ( aMessage . name ) {
case "DOMLinkAdded" : {
// checks for an icon to use for a web app
// apple-touch-icon size is 57px and default size is 16px
let rel = json . rel . toLowerCase ( ) . split ( " " ) ;
if ( rel . indexOf ( "icon" ) != - 1 ) {
// We use the sizes attribute if available
// see http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#rel-icon
let size = 16 ;
if ( json . sizes ) {
let sizes = json . sizes . toLowerCase ( ) . split ( " " ) ;
sizes . forEach ( function ( item ) {
if ( item != "any" ) {
let [ w , h ] = item . split ( "x" ) ;
size = Math . max ( Math . min ( w , h ) , size ) ;
}
} ) ;
}
if ( size > browser . appIcon . size ) {
browser . appIcon . href = json . href ;
browser . appIcon . size = size ;
}
}
else if ( ( rel . indexOf ( "apple-touch-icon" ) != - 1 ) && ( browser . appIcon . size < 57 ) ) {
// XXX should we support apple-touch-icon-precomposed ?
// see http://developer.apple.com/safari/library/documentation/appleapplications/reference/safariwebcontent/configuringwebapplications/configuringwebapplications.html
browser . appIcon . href = json . href ;
browser . appIcon . size = 57 ;
}
break ;
}
case "Browser:FormSubmit" :
browser . lastLocation = null ;
break ;
case "Browser:CanUnload:Return" : {
2013-09-30 20:27:05 -07:00
if ( json . permit ) {
let tab = this . getTabForBrowser ( browser ) ;
BrowserUI . animateClosingTab ( tab ) ;
2013-02-12 12:51:25 -08:00
}
break ;
2013-09-30 20:27:05 -07:00
}
2013-02-12 12:51:25 -08:00
case "Browser:CertException" :
this . _handleCertException ( aMessage ) ;
break ;
case "Browser:BlockedSite" :
this . _handleBlockedSite ( aMessage ) ;
break ;
case "Browser:TapOnSelection" :
if ( ! InputSourceHelper . isPrecise ) {
2013-02-27 08:27:48 -08:00
if ( SelectionHelperUI . isActive ) {
2013-02-12 12:51:25 -08:00
SelectionHelperUI . shutdown ( ) ;
}
if ( SelectionHelperUI . canHandle ( aMessage ) ) {
SelectionHelperUI . openEditSession ( aMessage ) ;
}
}
break ;
}
} ,
} ;
Browser . MainDragger = function MainDragger ( ) {
this . _horizontalScrollbar = document . getElementById ( "horizontal-scroller" ) ;
this . _verticalScrollbar = document . getElementById ( "vertical-scroller" ) ;
this . _scrollScales = { x : 0 , y : 0 } ;
Elements . browsers . addEventListener ( "PanBegin" , this , false ) ;
Elements . browsers . addEventListener ( "PanFinished" , this , false ) ;
} ;
Browser . MainDragger . prototype = {
isDraggable : function isDraggable ( target , scroller ) {
return { x : true , y : true } ;
} ,
dragStart : function dragStart ( clientX , clientY , target , scroller ) {
let browser = getBrowser ( ) ;
let bcr = browser . getBoundingClientRect ( ) ;
this . _contentView = browser . getViewAt ( clientX - bcr . left , clientY - bcr . top ) ;
} ,
dragStop : function dragStop ( dx , dy , scroller ) {
if ( this . _contentView && this . _contentView . _updateCacheViewport )
this . _contentView . _updateCacheViewport ( ) ;
this . _contentView = null ;
} ,
dragMove : function dragMove ( dx , dy , scroller , aIsKinetic ) {
let doffset = new Point ( dx , dy ) ;
this . _panContent ( doffset ) ;
if ( aIsKinetic && doffset . x != 0 )
return false ;
this . _updateScrollbars ( ) ;
return ! doffset . equals ( dx , dy ) ;
} ,
handleEvent : function handleEvent ( aEvent ) {
let browser = getBrowser ( ) ;
switch ( aEvent . type ) {
case "PanBegin" : {
let width = ContentAreaObserver . width , height = ContentAreaObserver . height ;
let contentWidth = browser . contentDocumentWidth * browser . scale ;
let contentHeight = browser . contentDocumentHeight * browser . scale ;
// Allow a small margin on both sides to prevent adding scrollbars
// on small viewport approximation
const ALLOWED _MARGIN = 5 ;
const SCROLL _CORNER _SIZE = 8 ;
this . _scrollScales = {
x : ( width + ALLOWED _MARGIN ) < contentWidth ? ( width - SCROLL _CORNER _SIZE ) / contentWidth : 0 ,
y : ( height + ALLOWED _MARGIN ) < contentHeight ? ( height - SCROLL _CORNER _SIZE ) / contentHeight : 0
}
2013-04-25 08:43:42 -07:00
2013-02-12 12:51:25 -08:00
this . _showScrollbars ( ) ;
break ;
}
case "PanFinished" :
this . _hideScrollbars ( ) ;
// Update the scroll position of the content
browser . _updateCSSViewport ( ) ;
break ;
}
} ,
_panContent : function md _panContent ( aOffset ) {
if ( this . _contentView && ! this . _contentView . isRoot ( ) ) {
this . _panContentView ( this . _contentView , aOffset ) ;
// XXX we may need to have "escape borders" for iframe panning
// XXX does not deal with scrollables within scrollables
}
// Do content panning
this . _panContentView ( getBrowser ( ) . getRootView ( ) , aOffset ) ;
} ,
/** Pan scroller by the given amount. Updates doffset with leftovers. */
_panContentView : function _panContentView ( contentView , doffset ) {
let pos0 = contentView . getPosition ( ) ;
contentView . scrollBy ( doffset . x , doffset . y ) ;
let pos1 = contentView . getPosition ( ) ;
doffset . subtract ( pos1 . x - pos0 . x , pos1 . y - pos0 . y ) ;
} ,
_updateScrollbars : function _updateScrollbars ( ) {
let scaleX = this . _scrollScales . x , scaleY = this . _scrollScales . y ;
let contentScroll = Browser . getScrollboxPosition ( Browser . contentScrollboxScroller ) ;
2013-04-25 08:43:42 -07:00
2013-02-12 12:51:25 -08:00
if ( scaleX )
this . _horizontalScrollbar . style . MozTransform =
"translateX(" + Math . round ( contentScroll . x * scaleX ) + "px)" ;
if ( scaleY ) {
let y = Math . round ( contentScroll . y * scaleY ) ;
let x = 0 ;
this . _verticalScrollbar . style . MozTransform =
"translate(" + x + "px," + y + "px)" ;
}
} ,
_showScrollbars : function _showScrollbars ( ) {
this . _updateScrollbars ( ) ;
let scaleX = this . _scrollScales . x , scaleY = this . _scrollScales . y ;
if ( scaleX ) {
this . _horizontalScrollbar . width = ContentAreaObserver . width * scaleX ;
this . _horizontalScrollbar . setAttribute ( "panning" , "true" ) ;
}
if ( scaleY ) {
this . _verticalScrollbar . height = ContentAreaObserver . height * scaleY ;
this . _verticalScrollbar . setAttribute ( "panning" , "true" ) ;
}
} ,
_hideScrollbars : function _hideScrollbars ( ) {
2013-09-05 16:32:43 -07:00
this . _scrollScales . x = 0 ;
this . _scrollScales . y = 0 ;
2013-02-12 12:51:25 -08:00
this . _horizontalScrollbar . removeAttribute ( "panning" ) ;
this . _verticalScrollbar . removeAttribute ( "panning" ) ;
2013-09-05 16:32:43 -07:00
this . _horizontalScrollbar . removeAttribute ( "width" ) ;
this . _verticalScrollbar . removeAttribute ( "height" ) ;
2013-02-12 12:51:25 -08:00
this . _horizontalScrollbar . style . MozTransform = "" ;
this . _verticalScrollbar . style . MozTransform = "" ;
}
} ;
function nsBrowserAccess ( ) { }
nsBrowserAccess . prototype = {
QueryInterface : function ( aIID ) {
if ( aIID . equals ( Ci . nsIBrowserDOMWindow ) || aIID . equals ( Ci . nsISupports ) )
return this ;
throw Cr . NS _NOINTERFACE ;
} ,
2013-11-12 09:21:48 -08:00
_getOpenAction : function _getOpenAction ( aURI , aOpener , aWhere , aContext ) {
let where = aWhere ;
/ *
2014-01-09 07:58:25 -08:00
* aWhere :
2013-11-12 09:21:48 -08:00
* OPEN _DEFAULTWINDOW : default action
* OPEN _CURRENTWINDOW : current window / tab
* OPEN _NEWWINDOW : not allowed , converted to newtab below
* OPEN _NEWTAB : open a new tab
* OPEN _SWITCHTAB : open in an existing tab if it matches , otherwise open
* a new tab . afaict we always open these in the current tab .
* /
if ( where == Ci . nsIBrowserDOMWindow . OPEN _DEFAULTWINDOW ) {
// query standard browser prefs indicating what to do for default action
switch ( aContext ) {
// indicates this is an open request from a 3rd party app.
case Ci . nsIBrowserDOMWindow . OPEN _EXTERNAL :
where = Services . prefs . getIntPref ( "browser.link.open_external" ) ;
break ;
// internal request
default :
where = Services . prefs . getIntPref ( "browser.link.open_newwindow" ) ;
}
}
if ( where == Ci . nsIBrowserDOMWindow . OPEN _NEWWINDOW ) {
Util . dumpLn ( "Invalid request - we can't open links in new windows." ) ;
where = Ci . nsIBrowserDOMWindow . OPEN _NEWTAB ;
}
return where ;
} ,
2013-02-12 12:51:25 -08:00
_getBrowser : function _getBrowser ( aURI , aOpener , aWhere , aContext ) {
let isExternal = ( aContext == Ci . nsIBrowserDOMWindow . OPEN _EXTERNAL ) ;
2013-11-12 09:21:48 -08:00
// We don't allow externals apps opening chrome docs
2013-02-12 12:51:25 -08:00
if ( isExternal && aURI && aURI . schemeIs ( "chrome" ) )
return null ;
2013-11-12 09:21:48 -08:00
let location ;
let browser ;
2013-02-12 12:51:25 -08:00
let loadflags = isExternal ?
Ci . nsIWebNavigation . LOAD _FLAGS _FROM _EXTERNAL :
Ci . nsIWebNavigation . LOAD _FLAGS _NONE ;
2013-11-12 09:21:48 -08:00
let openAction = this . _getOpenAction ( aURI , aOpener , aWhere , aContext ) ;
2013-02-12 12:51:25 -08:00
2013-11-12 09:21:48 -08:00
if ( openAction == Ci . nsIBrowserDOMWindow . OPEN _NEWTAB ) {
2013-02-12 12:51:25 -08:00
let owner = isExternal ? null : Browser . selectedTab ;
2013-11-12 18:05:55 -08:00
let tab = BrowserUI . openLinkInNewTab ( "about:blank" , true , owner ) ;
2013-11-12 09:21:48 -08:00
browser = tab . browser ;
} else {
2013-02-12 12:51:25 -08:00
browser = Browser . selectedBrowser ;
}
try {
let referrer ;
if ( aURI && browser ) {
if ( aOpener ) {
location = aOpener . location ;
referrer = Services . io . newURI ( location , null , null ) ;
}
browser . loadURIWithFlags ( aURI . spec , loadflags , referrer , null , null ) ;
}
browser . focus ( ) ;
} catch ( e ) { }
return browser ;
} ,
openURI : function browser _openURI ( aURI , aOpener , aWhere , aContext ) {
let browser = this . _getBrowser ( aURI , aOpener , aWhere , aContext ) ;
return browser ? browser . contentWindow : null ;
} ,
openURIInFrame : function browser _openURIInFrame ( aURI , aOpener , aWhere , aContext ) {
let browser = this . _getBrowser ( aURI , aOpener , aWhere , aContext ) ;
return browser ? browser . QueryInterface ( Ci . nsIFrameLoaderOwner ) : null ;
} ,
isTabContentWindow : function ( aWindow ) {
return Browser . browsers . some ( function ( browser ) browser . contentWindow == aWindow ) ;
2013-05-08 14:16:46 -07:00
} ,
get contentWindow ( ) {
return Browser . selectedBrowser . contentWindow ;
2013-02-12 12:51:25 -08:00
}
} ;
/ * *
* Handler for blocked popups , triggered by DOMUpdatePageReport events in browser . xml
* /
var PopupBlockerObserver = {
2013-03-08 14:28:52 -08:00
init : function init ( ) {
Elements . browsers . addEventListener ( "mousedown" , this , true ) ;
} ,
handleEvent : function handleEvent ( aEvent ) {
switch ( aEvent . type ) {
case "mousedown" :
let box = Browser . getNotificationBox ( ) ;
let notification = box . getNotificationWithValue ( "popup-blocked" ) ;
2013-03-12 16:32:11 -07:00
if ( notification && ! notification . contains ( aEvent . target ) )
2013-03-08 14:28:52 -08:00
box . removeNotification ( notification ) ;
break ;
}
} ,
onUpdatePageReport : function onUpdatePageReport ( aEvent ) {
2013-02-12 12:51:25 -08:00
var cBrowser = Browser . selectedBrowser ;
if ( aEvent . originalTarget != cBrowser )
return ;
if ( ! cBrowser . pageReport )
return ;
let result = Services . perms . testExactPermission ( Browser . selectedBrowser . currentURI , "popup" ) ;
if ( result == Ci . nsIPermissionManager . DENY _ACTION )
return ;
// Only show the notification again if we've not already shown it. Since
// notifications are per-browser, we don't need to worry about re-adding
// it.
if ( ! cBrowser . pageReport . reported ) {
if ( Services . prefs . getBoolPref ( "privacy.popups.showBrowserMessage" ) ) {
var brandShortName = Strings . brand . GetStringFromName ( "brandShortName" ) ;
var popupCount = cBrowser . pageReport . length ;
let strings = Strings . browser ;
2013-03-22 15:27:18 -07:00
let message = PluralForm . get ( popupCount , strings . GetStringFromName ( "popupWarning.message" ) )
. replace ( "#1" , brandShortName )
. replace ( "#2" , popupCount ) ;
2013-02-12 12:51:25 -08:00
var notificationBox = Browser . getNotificationBox ( ) ;
var notification = notificationBox . getNotificationWithValue ( "popup-blocked" ) ;
if ( notification ) {
notification . label = message ;
}
else {
var buttons = [
{
2013-03-06 18:31:28 -08:00
isDefault : false ,
2013-03-08 14:28:53 -08:00
label : strings . GetStringFromName ( "popupButtonAllowOnce2" ) ,
2013-03-12 16:32:11 -07:00
accessKey : "" ,
2013-02-12 12:51:25 -08:00
callback : function ( ) { PopupBlockerObserver . showPopupsForSite ( ) ; }
} ,
{
2013-03-08 14:28:53 -08:00
label : strings . GetStringFromName ( "popupButtonAlwaysAllow3" ) ,
2013-03-12 16:32:11 -07:00
accessKey : "" ,
2013-02-12 12:51:25 -08:00
callback : function ( ) { PopupBlockerObserver . allowPopupsForSite ( true ) ; }
} ,
{
2013-03-08 14:28:53 -08:00
label : strings . GetStringFromName ( "popupButtonNeverWarn3" ) ,
2013-03-12 16:32:11 -07:00
accessKey : "" ,
2013-02-12 12:51:25 -08:00
callback : function ( ) { PopupBlockerObserver . allowPopupsForSite ( false ) ; }
}
] ;
const priority = notificationBox . PRIORITY _WARNING _MEDIUM ;
notificationBox . appendNotification ( message , "popup-blocked" ,
2013-03-08 14:28:53 -08:00
"chrome://browser/skin/images/infobar-popup.png" ,
2013-02-12 12:51:25 -08:00
priority , buttons ) ;
}
}
// Record the fact that we've reported this blocked popup, so we don't
// show it again.
cBrowser . pageReport . reported = true ;
}
} ,
allowPopupsForSite : function allowPopupsForSite ( aAllow ) {
var currentURI = Browser . selectedBrowser . currentURI ;
Services . perms . add ( currentURI , "popup" , aAllow
? Ci . nsIPermissionManager . ALLOW _ACTION
: Ci . nsIPermissionManager . DENY _ACTION ) ;
Browser . getNotificationBox ( ) . removeCurrentNotification ( ) ;
} ,
showPopupsForSite : function showPopupsForSite ( ) {
let uri = Browser . selectedBrowser . currentURI ;
let pageReport = Browser . selectedBrowser . pageReport ;
if ( pageReport ) {
for ( let i = 0 ; i < pageReport . length ; ++ i ) {
var popupURIspec = pageReport [ i ] . popupWindowURI . spec ;
// Sometimes the popup URI that we get back from the pageReport
// isn't useful (for instance, netscape.com's popup URI ends up
// being "http://www.netscape.com", which isn't really the URI of
// the popup they're trying to show). This isn't going to be
// useful to the user, so we won't create a menu item for it.
if ( popupURIspec == "" || ! Util . isURLMemorable ( popupURIspec ) || popupURIspec == uri . spec )
continue ;
let popupFeatures = pageReport [ i ] . popupWindowFeatures ;
let popupName = pageReport [ i ] . popupWindowName ;
Browser . addTab ( popupURIspec , false , Browser . selectedTab ) ;
}
}
}
} ;
var SessionHistoryObserver = {
observe : function sho _observe ( aSubject , aTopic , aData ) {
if ( aTopic != "browser:purge-session-history" )
return ;
2013-12-17 15:05:37 -08:00
let newTab = Browser . addTab ( "about:start" , true ) ;
let tab = Browser . _tabs [ 0 ] ;
while ( tab != newTab ) {
Browser . closeTab ( tab , { forceClose : true } ) ;
tab = Browser . _tabs [ 0 ] ;
2013-02-12 12:51:25 -08:00
}
2013-12-17 15:05:37 -08:00
PlacesUtils . history . removeAllPages ( ) ;
// Clear undo history of the URL bar
BrowserUI . _edit . editor . transactionManager . clear ( ) ;
2013-02-12 12:51:25 -08:00
}
} ;
function getNotificationBox ( aBrowser ) {
return Browser . getNotificationBox ( aBrowser ) ;
}
function showDownloadManager ( aWindowContext , aID , aReason ) {
2013-07-17 15:19:27 -07:00
// TODO: Bug 883962: Toggle the downloads infobar as our current "download manager".
2013-02-12 12:51:25 -08:00
}
2013-05-15 18:06:42 -07:00
function Tab ( aURI , aParams , aOwner ) {
2013-02-12 12:51:25 -08:00
this . _id = null ;
this . _browser = null ;
this . _notification = null ;
this . _loading = false ;
2013-10-08 14:08:42 -07:00
this . _progressActive = false ;
this . _progressCount = 0 ;
2013-02-12 12:51:25 -08:00
this . _chromeTab = null ;
2013-04-19 03:25:36 -07:00
this . _eventDeferred = null ;
2013-08-07 10:51:43 -07:00
this . _updateThumbnailTimeout = null ;
2013-02-12 12:51:25 -08:00
2014-01-27 15:42:50 -08:00
this . _private = false ;
if ( "private" in aParams ) {
this . _private = aParams . private ;
} else if ( aOwner ) {
2014-02-03 08:09:06 -08:00
this . _private = aOwner . _private ;
2014-01-27 15:42:50 -08:00
}
2013-05-15 18:06:42 -07:00
this . owner = aOwner || null ;
2013-02-12 12:51:25 -08:00
// Set to 0 since new tabs that have not been viewed yet are good tabs to
// toss if app needs more memory.
this . lastSelected = 0 ;
// aParams is an object that contains some properties for the initial tab
// loading like flags, a referrerURI, a charset or even a postData.
2013-05-15 18:06:42 -07:00
this . create ( aURI , aParams || { } , aOwner ) ;
2013-02-12 12:51:25 -08:00
// default tabs to inactive (i.e. no display port)
this . active = false ;
}
Tab . prototype = {
get browser ( ) {
return this . _browser ;
} ,
get notification ( ) {
return this . _notification ;
} ,
get chromeTab ( ) {
return this . _chromeTab ;
} ,
2014-01-27 15:42:50 -08:00
get isPrivate ( ) {
return this . _private ;
} ,
2013-04-19 03:25:36 -07:00
get pageShowPromise ( ) {
return this . _eventDeferred ? this . _eventDeferred . promise : null ;
} ,
2013-02-12 12:51:25 -08:00
startLoading : function startLoading ( ) {
2013-08-27 07:58:43 -07:00
if ( this . _loading ) {
let stack = new Error ( ) . stack ;
throw "Already Loading!\n" + stack ;
}
2013-02-12 12:51:25 -08:00
this . _loading = true ;
} ,
endLoading : function endLoading ( ) {
this . _loading = false ;
this . updateFavicon ( ) ;
} ,
isLoading : function isLoading ( ) {
return this . _loading ;
} ,
2013-05-15 18:06:42 -07:00
create : function create ( aURI , aParams , aOwner ) {
2013-04-19 03:25:36 -07:00
this . _eventDeferred = Promise . defer ( ) ;
2013-11-08 15:39:14 -08:00
this . _chromeTab = Elements . tabList . addTab ( aParams . index ) ;
2014-01-27 15:42:50 -08:00
if ( this . isPrivate ) {
this . _chromeTab . setAttribute ( "private" , "true" ) ;
}
2013-02-12 12:51:25 -08:00
this . _id = Browser . createTabId ( ) ;
let browser = this . _createBrowser ( aURI , null ) ;
2013-04-19 03:25:36 -07:00
let self = this ;
function onPageShowEvent ( aEvent ) {
browser . removeEventListener ( "pageshow" , onPageShowEvent ) ;
if ( self . _eventDeferred ) {
self . _eventDeferred . resolve ( self ) ;
}
self . _eventDeferred = null ;
2013-02-12 12:51:25 -08:00
}
2013-04-19 03:25:36 -07:00
browser . addEventListener ( "pageshow" , onPageShowEvent , true ) ;
2013-11-08 11:04:13 -08:00
browser . addEventListener ( "DOMWindowCreated" , this , false ) ;
2013-12-09 10:20:56 -08:00
browser . addEventListener ( "StartUIChange" , this , false ) ;
2013-11-08 11:04:13 -08:00
Elements . browsers . addEventListener ( "SizeChanged" , this , false ) ;
2013-08-07 10:51:43 -07:00
browser . messageManager . addMessageListener ( "Content:StateChange" , this ) ;
2013-04-19 03:25:36 -07:00
2013-05-15 18:06:42 -07:00
if ( aOwner )
this . _copyHistoryFrom ( aOwner ) ;
2013-04-19 03:25:36 -07:00
this . _loadUsingParams ( browser , aURI , aParams ) ;
2013-02-12 12:51:25 -08:00
} ,
2013-11-08 11:04:13 -08:00
updateViewport : function ( aEvent ) {
// <meta name=viewport> is not yet supported; just use the browser size.
2013-12-09 10:20:56 -08:00
let browser = this . browser ;
// On the start page we add padding to keep the browser above the navbar.
let paddingBottom = parseInt ( getComputedStyle ( browser ) . paddingBottom , 10 ) ;
let height = browser . clientHeight - paddingBottom ;
browser . setWindowSize ( browser . clientWidth , height ) ;
2013-11-08 11:04:13 -08:00
} ,
handleEvent : function ( aEvent ) {
switch ( aEvent . type ) {
case "DOMWindowCreated" :
2013-12-09 10:20:56 -08:00
case "StartUIChange" :
2013-12-02 10:32:38 -08:00
this . updateViewport ( ) ;
break ;
2013-11-08 11:04:13 -08:00
case "SizeChanged" :
this . updateViewport ( ) ;
2013-12-02 10:32:38 -08:00
this . _delayUpdateThumbnail ( ) ;
2013-11-08 11:04:13 -08:00
break ;
}
} ,
2013-08-07 10:51:43 -07:00
receiveMessage : function ( aMessage ) {
switch ( aMessage . name ) {
case "Content:StateChange" :
// update the thumbnail now...
this . updateThumbnail ( ) ;
// ...and in a little while to capture page after load.
if ( aMessage . json . stateFlags & Ci . nsIWebProgressListener . STATE _STOP ) {
2013-12-02 10:32:38 -08:00
this . _delayUpdateThumbnail ( ) ;
2013-08-07 10:51:43 -07:00
}
break ;
}
} ,
2013-12-02 10:32:38 -08:00
_delayUpdateThumbnail : function ( ) {
clearTimeout ( this . _updateThumbnailTimeout ) ;
this . _updateThumbnailTimeout = setTimeout ( ( ) => {
this . updateThumbnail ( ) ;
} , kTabThumbnailDelayCapture ) ;
2013-08-07 10:51:43 -07:00
} ,
2013-02-12 12:51:25 -08:00
destroy : function destroy ( ) {
2013-08-07 10:51:43 -07:00
this . _browser . messageManager . removeMessageListener ( "Content:StateChange" , this ) ;
2013-11-08 11:04:13 -08:00
this . _browser . removeEventListener ( "DOMWindowCreated" , this , false ) ;
2013-12-09 10:20:56 -08:00
this . _browser . removeEventListener ( "StartUIChange" , this , false ) ;
2013-11-08 11:04:13 -08:00
Elements . browsers . removeEventListener ( "SizeChanged" , this , false ) ;
2013-08-07 10:51:43 -07:00
clearTimeout ( this . _updateThumbnailTimeout ) ;
2013-02-12 12:51:25 -08:00
Elements . tabList . removeTab ( this . _chromeTab ) ;
this . _chromeTab = null ;
this . _destroyBrowser ( ) ;
} ,
resurrect : function resurrect ( ) {
let dead = this . _browser ;
let active = this . active ;
// Hold onto the session store data
let session = { data : dead . _ _SS _data , extra : dead . _ _SS _extdata } ;
// We need this data to correctly create and position the new browser
// If this browser is already a zombie, fallback to the session data
let currentURL = dead . _ _SS _restore ? session . data . entries [ 0 ] . url : dead . currentURI . spec ;
let sibling = dead . nextSibling ;
// Destory and re-create the browser
this . _destroyBrowser ( ) ;
let browser = this . _createBrowser ( currentURL , sibling ) ;
if ( active )
this . active = true ;
// Reattach session store data and flag this browser so it is restored on select
browser . _ _SS _data = session . data ;
browser . _ _SS _extdata = session . extra ;
browser . _ _SS _restore = true ;
} ,
2013-05-15 18:06:42 -07:00
_copyHistoryFrom : function _copyHistoryFrom ( tab ) {
let otherHistory = tab . _browser . _webNavigation . sessionHistory ;
let history = this . _browser . _webNavigation . sessionHistory ;
// Ensure that history is initialized
history . QueryInterface ( Ci . nsISHistoryInternal ) ;
2013-07-15 11:27:28 -07:00
2013-05-15 18:06:42 -07:00
for ( let i = 0 , length = otherHistory . index ; i <= length ; i ++ )
history . addEntry ( otherHistory . getEntryAtIndex ( i , false ) , true ) ;
} ,
2013-04-19 03:25:36 -07:00
_loadUsingParams : function _loadUsingParams ( aBrowser , aURI , aParams ) {
let flags = aParams . flags || Ci . nsIWebNavigation . LOAD _FLAGS _NONE ;
let postData = ( "postData" in aParams && aParams . postData ) ? aParams . postData . value : null ;
let referrerURI = "referrerURI" in aParams ? aParams . referrerURI : null ;
let charset = "charset" in aParams ? aParams . charset : null ;
aBrowser . loadURIWithFlags ( aURI , flags , referrerURI , charset , postData ) ;
} ,
2013-02-12 12:51:25 -08:00
_createBrowser : function _createBrowser ( aURI , aInsertBefore ) {
if ( this . _browser )
throw "Browser already exists" ;
// Create a notification box around the browser. Note this includes
// the input overlay we use to shade content from input events when
// we're intercepting touch input.
let notification = this . _notification = document . createElement ( "notificationbox" ) ;
let browser = this . _browser = document . createElement ( "browser" ) ;
browser . id = "browser-" + this . _id ;
this . _chromeTab . linkedBrowser = browser ;
browser . setAttribute ( "type" , "content" ) ;
2013-12-12 14:13:20 -08:00
let useRemote = Services . appinfo . browserTabsRemote ;
2013-02-12 12:51:25 -08:00
let useLocal = Util . isLocalScheme ( aURI ) ;
browser . setAttribute ( "remote" , ( ! useLocal && useRemote ) ? "true" : "false" ) ;
// Append the browser to the document, which should start the page load
2013-08-05 23:29:54 -07:00
let stack = document . createElementNS ( XUL _NS , "stack" ) ;
stack . className = "browserStack" ;
stack . appendChild ( browser ) ;
stack . setAttribute ( "flex" , "1" ) ;
notification . appendChild ( stack ) ;
2013-02-12 12:51:25 -08:00
Elements . browsers . insertBefore ( notification , aInsertBefore ) ;
2013-08-15 10:06:39 -07:00
notification . dir = "reverse" ;
// let the content area manager know about this browser.
ContentAreaObserver . onBrowserCreated ( browser ) ;
2014-01-27 15:42:50 -08:00
if ( this . isPrivate ) {
let ctx = browser . docShell . QueryInterface ( Ci . nsILoadContext ) ;
ctx . usePrivateBrowsing = true ;
}
2013-02-12 12:51:25 -08:00
// stop about:blank from loading
browser . stop ( ) ;
let fl = browser . QueryInterface ( Ci . nsIFrameLoaderOwner ) . frameLoader ;
fl . renderMode = Ci . nsIFrameLoader . RENDER _MODE _ASYNC _SCROLL ;
return browser ;
} ,
_destroyBrowser : function _destroyBrowser ( ) {
if ( this . _browser ) {
let notification = this . _notification ;
let browser = this . _browser ;
browser . active = false ;
this . _notification = null ;
this . _browser = null ;
this . _loading = false ;
Elements . browsers . removeChild ( notification ) ;
}
} ,
2013-08-07 10:51:43 -07:00
updateThumbnail : function updateThumbnail ( ) {
2014-01-27 15:42:50 -08:00
if ( ! this . isPrivate ) {
PageThumbs . captureToCanvas ( this . browser . contentWindow , this . _chromeTab . thumbnailCanvas ) ;
}
2013-02-12 12:51:25 -08:00
} ,
updateFavicon : function updateFavicon ( ) {
this . _chromeTab . updateFavicon ( this . _browser . mIconURL ) ;
} ,
set active ( aActive ) {
if ( ! this . _browser )
return ;
let notification = this . _notification ;
let browser = this . _browser ;
if ( aActive ) {
2014-01-14 12:35:53 -08:00
notification . classList . add ( "active-tab-notificationbox" ) ;
2013-02-12 12:51:25 -08:00
browser . setAttribute ( "type" , "content-primary" ) ;
Elements . browsers . selectedPanel = notification ;
browser . active = true ;
Elements . tabList . selectedTab = this . _chromeTab ;
browser . focus ( ) ;
} else {
2014-01-14 12:35:53 -08:00
notification . classList . remove ( "active-tab-notificationbox" ) ;
2013-02-12 12:51:25 -08:00
browser . messageManager . sendAsyncMessage ( "Browser:Blur" , { } ) ;
browser . setAttribute ( "type" , "content" ) ;
browser . active = false ;
}
} ,
get active ( ) {
if ( ! this . _browser )
return false ;
return this . _browser . getAttribute ( "type" ) == "content-primary" ;
} ,
toString : function ( ) {
return "[Tab " + ( this . _browser ? this . _browser . currentURI . spec : "(no browser)" ) + "]" ;
}
} ;
// Helper used to hide IPC / non-IPC differences for rendering to a canvas
function rendererFactory ( aBrowser , aCanvas ) {
let wrapper = { } ;
if ( aBrowser . contentWindow ) {
let ctx = aCanvas . getContext ( "2d" ) ;
let draw = function ( browser , aLeft , aTop , aWidth , aHeight , aColor , aFlags ) {
ctx . drawWindow ( browser . contentWindow , aLeft , aTop , aWidth , aHeight , aColor , aFlags ) ;
let e = document . createEvent ( "HTMLEvents" ) ;
e . initEvent ( "MozAsyncCanvasRender" , true , true ) ;
aCanvas . dispatchEvent ( e ) ;
} ;
wrapper . checkBrowser = function ( browser ) {
return browser . contentWindow ;
} ;
wrapper . drawContent = function ( callback ) {
callback ( ctx , draw ) ;
} ;
}
else {
let ctx = aCanvas . MozGetIPCContext ( "2d" ) ;
let draw = function ( browser , aLeft , aTop , aWidth , aHeight , aColor , aFlags ) {
ctx . asyncDrawXULElement ( browser , aLeft , aTop , aWidth , aHeight , aColor , aFlags ) ;
} ;
wrapper . checkBrowser = function ( browser ) {
return ! browser . contentWindow ;
} ;
wrapper . drawContent = function ( callback ) {
callback ( ctx , draw ) ;
} ;
}
return wrapper ;
} ;
2013-11-12 18:05:55 -08:00
// Based on ClickEventHandler from /browser/base/content/content.js
let ClickEventHandler = {
init : function ( ) {
gEventListenerService . addSystemEventListener ( Elements . browsers , "click" , this , true ) ;
} ,
uninit : function ( ) {
gEventListenerService . removeSystemEventListener ( Elements . browsers , "click" , this , true ) ;
} ,
handleEvent : function ( aEvent ) {
if ( ! aEvent . isTrusted || aEvent . defaultPrevented ) {
return ;
}
let [ href , node ] = this . _hrefAndLinkNodeForClickEvent ( aEvent ) ;
if ( href && ( aEvent . button == 1 || aEvent . ctrlKey ) ) {
// Open link in a new tab for middle-click or ctrl-click
BrowserUI . openLinkInNewTab ( href , aEvent . shiftKey , Browser . selectedTab ) ;
}
} ,
/ * *
* Extracts linkNode and href for the current click target .
*
* @ param event
* The click event .
* @ return [ href , linkNode ] .
*
* @ note linkNode will be null if the click wasn ' t on an anchor
* element ( or XLink ) .
* /
_hrefAndLinkNodeForClickEvent : function ( event ) {
function isHTMLLink ( aNode ) {
return ( ( aNode instanceof content . HTMLAnchorElement && aNode . href ) ||
( aNode instanceof content . HTMLAreaElement && aNode . href ) ||
aNode instanceof content . HTMLLinkElement ) ;
}
let node = event . target ;
while ( node && ! isHTMLLink ( node ) ) {
node = node . parentNode ;
}
if ( node )
return [ node . href , node ] ;
// If there is no linkNode, try simple XLink.
let href , baseURI ;
node = event . target ;
while ( node && ! href ) {
if ( node . nodeType == content . Node . ELEMENT _NODE ) {
href = node . getAttributeNS ( "http://www.w3.org/1999/xlink" , "href" ) ;
if ( href )
baseURI = node . ownerDocument . baseURIObject ;
}
node = node . parentNode ;
}
// In case of XLink, we don't return the node we got href from since
// callers expect <a>-like elements.
// Note: makeURI() will throw if aUri is not a valid URI.
return [ href ? Services . io . newURI ( href , null , baseURI ) . spec : null , null ] ;
}
} ;