browser/metro/base/content/browser.js

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

     1 // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 let Cc = Components.classes;
     7 let Ci = Components.interfaces;
     8 let Cu = Components.utils;
     9 let Cr = Components.results;
    11 Cu.import("resource://gre/modules/PageThumbs.jsm");
    13 // Page for which the start UI is shown
    14 const kStartURI = "about:newtab";
    16 // allow panning after this timeout on pages with registered touch listeners
    17 const kTouchTimeout = 300;
    18 const kSetInactiveStateTimeout = 100;
    20 const kTabThumbnailDelayCapture = 500;
    22 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
    24 // See grid.xml, we use this to cache style info across loads of the startui.
    25 var _richgridTileSizes = {};
    27 // Override sizeToContent in the main window. It breaks things (bug 565887)
    28 window.sizeToContent = function() {
    29   Cu.reportError("window.sizeToContent is not allowed in this window");
    30 }
    32 function getTabModalPromptBox(aWindow) {
    33   let browser = Browser.getBrowserForWindow(aWindow);
    34   return Browser.getTabModalPromptBox(browser);
    35 }
    37 /*
    38  * Returns the browser for the currently displayed tab.
    39  */
    40 function getBrowser() {
    41   return Browser.selectedBrowser;
    42 }
    44 var Browser = {
    45   _debugEvents: false,
    46   _tabs: [],
    47   _selectedTab: null,
    48   _tabId: 0,
    49   windowUtils: window.QueryInterface(Ci.nsIInterfaceRequestor)
    50                      .getInterface(Ci.nsIDOMWindowUtils),
    52   get defaultBrowserWidth() {
    53     return window.innerWidth;
    54   },
    56   startup: function startup() {
    57     var self = this;
    59     try {
    60       messageManager.loadFrameScript("chrome://browser/content/Util.js", true);
    61       messageManager.loadFrameScript("chrome://browser/content/contenthandlers/Content.js", true);
    62       messageManager.loadFrameScript("chrome://browser/content/contenthandlers/FormHelper.js", true);
    63       messageManager.loadFrameScript("chrome://browser/content/library/SelectionPrototype.js", true);
    64       messageManager.loadFrameScript("chrome://browser/content/contenthandlers/SelectionHandler.js", true);
    65       messageManager.loadFrameScript("chrome://browser/content/contenthandlers/ContextMenuHandler.js", true);
    66       messageManager.loadFrameScript("chrome://browser/content/contenthandlers/ConsoleAPIObserver.js", true);
    67       messageManager.loadFrameScript("chrome://browser/content/contenthandlers/PluginHelper.js", true);
    68     } catch (e) {
    69       // XXX whatever is calling startup needs to dump errors!
    70       dump("###########" + e + "\n");
    71     }
    73     if (!Services.metro) {
    74       // Services.metro is only available on Windows Metro. We want to be able
    75       // to test metro on other platforms, too, so we provide a minimal shim.
    76       Services.metro = {
    77         activationURI: "",
    78         pinTileAsync: function () {},
    79         unpinTileAsync: function () {}
    80       };
    81     }
    83     /* handles dispatching clicks on browser into clicks in content or zooms */
    84     Elements.browsers.customDragger = new Browser.MainDragger();
    86     /* handles web progress management for open browsers */
    87     Elements.browsers.webProgress = WebProgress.init();
    89     // Call InputSourceHelper first so global listeners get called before
    90     // we start processing input in TouchModule.
    91     InputSourceHelper.init();
    92     ClickEventHandler.init();
    94     TouchModule.init();
    95     GestureModule.init();
    96     BrowserTouchHandler.init();
    97     PopupBlockerObserver.init();
    98     APZCObserver.init();
   100     // Init the touch scrollbox
   101     this.contentScrollbox = Elements.browsers;
   102     this.contentScrollboxScroller = {
   103       scrollBy: function(aDx, aDy) {
   104         let view = getBrowser().getRootView();
   105         view.scrollBy(aDx, aDy);
   106       },
   108       scrollTo: function(aX, aY) {
   109         let view = getBrowser().getRootView();
   110         view.scrollTo(aX, aY);
   111       },
   113       getPosition: function(aScrollX, aScrollY) {
   114         let view = getBrowser().getRootView();
   115         let scroll = view.getPosition();
   116         aScrollX.value = scroll.x;
   117         aScrollY.value = scroll.y;
   118       }
   119     };
   121     ContentAreaObserver.init();
   123     function fullscreenHandler() {
   124       if (Browser.selectedBrowser.contentWindow.document.mozFullScreenElement)
   125         Elements.stack.setAttribute("fullscreen", "true");
   126       else
   127         Elements.stack.removeAttribute("fullscreen");
   128     }
   129     window.addEventListener("mozfullscreenchange", fullscreenHandler, true);
   131     BrowserUI.init();
   133     window.controllers.appendController(this);
   134     window.controllers.appendController(BrowserUI);
   136     Services.obs.addObserver(SessionHistoryObserver, "browser:purge-session-history", false);
   138     window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow = new nsBrowserAccess();
   140     Elements.browsers.addEventListener("DOMUpdatePageReport", PopupBlockerObserver.onUpdatePageReport, false);
   142     // Make sure we're online before attempting to load
   143     Util.forceOnline();
   145     // If this is an intial window launch the commandline handler passes us the default
   146     // page as an argument.
   147     let commandURL = null;
   148     try {
   149       let argsObj = window.arguments[0].wrappedJSObject;
   150       if (argsObj && argsObj.pageloadURL) {
   151         // Talos tp-cmdline parameter
   152         commandURL = argsObj.pageloadURL;
   153       } else if (window.arguments && window.arguments[0]) {
   154         // BrowserCLH paramerter
   155         commandURL = window.arguments[0];
   156       }
   157     } catch (ex) {
   158       Util.dumpLn(ex);
   159     }
   161     messageManager.addMessageListener("DOMLinkAdded", this);
   162     messageManager.addMessageListener("Browser:FormSubmit", this);
   163     messageManager.addMessageListener("Browser:CanUnload:Return", this);
   164     messageManager.addMessageListener("scroll", this);
   165     messageManager.addMessageListener("Browser:CertException", this);
   166     messageManager.addMessageListener("Browser:BlockedSite", this);
   168     Task.spawn(function() {
   169       // Activation URIs come from protocol activations, secondary tiles, and file activations
   170       let activationURI = yield this.getShortcutOrURI(Services.metro.activationURI);
   172       let self = this;
   173       function loadStartupURI() {
   174         if (activationURI) {
   175           let webNav = Ci.nsIWebNavigation;
   176           let flags = webNav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP |
   177                       webNav.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
   178           self.addTab(activationURI, true, null, { flags: flags });
   179         } else {
   180           let uri = commandURL || Browser.getHomePage();
   181           self.addTab(uri, true);
   182         }
   183       }
   185       // Should we restore the previous session (crash or some other event)
   186       let ss = Cc["@mozilla.org/browser/sessionstore;1"]
   187                .getService(Ci.nsISessionStore);
   188       let shouldRestore = ss.shouldRestore();
   189       if (shouldRestore) {
   190         let bringFront = false;
   191         // First open any commandline URLs, except the homepage
   192         if (activationURI && activationURI != kStartURI) {
   193           this.addTab(activationURI, true, null, { flags: Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP });
   194         } else if (commandURL && commandURL != kStartURI) {
   195           this.addTab(commandURL, true);
   196         } else {
   197           bringFront = true;
   198           // Initial window resizes call functions that assume a tab is in the tab list
   199           // and restored tabs are added too late. We add a dummy to to satisfy the resize
   200           // code and then remove the dummy after the session has been restored.
   201           let dummy = this.addTab("about:blank", true);
   202           let dummyCleanup = {
   203             observe: function(aSubject, aTopic, aData) {
   204               Services.obs.removeObserver(dummyCleanup, "sessionstore-windows-restored");
   205               if (aData == "fail")
   206                 loadStartupURI();
   207               dummy.chromeTab.ignoreUndo = true;
   208               Browser.closeTab(dummy, { forceClose: true });
   209             }
   210           };
   211           Services.obs.addObserver(dummyCleanup, "sessionstore-windows-restored", false);
   212         }
   213         ss.restoreLastSession(bringFront);
   214       } else {
   215         loadStartupURI();
   216       }
   218       // Notify about our input type
   219       InputSourceHelper.fireUpdate();
   221       // Broadcast a UIReady message so add-ons know we are finished with startup
   222       let event = document.createEvent("Events");
   223       event.initEvent("UIReady", true, false);
   224       window.dispatchEvent(event);
   225     }.bind(this));
   226   },
   228   shutdown: function shutdown() {
   229     APZCObserver.shutdown();
   230     BrowserUI.uninit();
   231     ClickEventHandler.uninit();
   232     ContentAreaObserver.shutdown();
   233     Appbar.shutdown();
   235     messageManager.removeMessageListener("Browser:FormSubmit", this);
   236     messageManager.removeMessageListener("scroll", this);
   237     messageManager.removeMessageListener("Browser:CertException", this);
   238     messageManager.removeMessageListener("Browser:BlockedSite", this);
   240     Services.obs.removeObserver(SessionHistoryObserver, "browser:purge-session-history");
   242     window.controllers.removeController(this);
   243     window.controllers.removeController(BrowserUI);
   244   },
   246   getHomePage: function getHomePage(aOptions) {
   247     aOptions = aOptions || { useDefault: false };
   249     let url = kStartURI;
   250     try {
   251       let prefs = aOptions.useDefault ? Services.prefs.getDefaultBranch(null) : Services.prefs;
   252       url = prefs.getComplexValue("browser.startup.homepage", Ci.nsIPrefLocalizedString).data;
   253     }
   254     catch(e) { }
   256     return url;
   257   },
   259   get browsers() {
   260     return this._tabs.map(function(tab) { return tab.browser; });
   261   },
   263   /**
   264    * Load a URI in the current tab, or a new tab if necessary.
   265    * @param aURI String
   266    * @param aParams Object with optional properties that will be passed to loadURIWithFlags:
   267    *    flags, referrerURI, charset, postData.
   268    */
   269   loadURI: function loadURI(aURI, aParams) {
   270     let browser = this.selectedBrowser;
   272     // We need to keep about: pages opening in new "local" tabs. We also want to spawn
   273     // new "remote" tabs if opening web pages from a "local" about: page.
   274     dump("loadURI=" + aURI + "\ncurrentURI=" + browser.currentURI.spec + "\n");
   276     let params = aParams || {};
   277     try {
   278       let flags = params.flags || Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
   279       let postData = ("postData" in params && params.postData) ? params.postData.value : null;
   280       let referrerURI = "referrerURI" in params ? params.referrerURI : null;
   281       let charset = "charset" in params ? params.charset : null;
   282       dump("loading tab: " + aURI + "\n");
   283       browser.loadURIWithFlags(aURI, flags, referrerURI, charset, postData);
   284     } catch(e) {
   285       dump("Error: " + e + "\n");
   286     }
   287   },
   289   /**
   290    * Determine if the given URL is a shortcut/keyword and, if so, expand it
   291    * @param aURL String
   292    * @param aPostDataRef Out param contains any required post data for a search
   293    * @return {Promise}
   294    * @result the expanded shortcut, or the original URL if not a shortcut
   295    */
   296   getShortcutOrURI: function getShortcutOrURI(aURL, aPostDataRef) {
   297     return Task.spawn(function() {
   298       if (!aURL)
   299         throw new Task.Result(aURL);
   301       let shortcutURL = null;
   302       let keyword = aURL;
   303       let param = "";
   305       let offset = aURL.indexOf(" ");
   306       if (offset > 0) {
   307         keyword = aURL.substr(0, offset);
   308         param = aURL.substr(offset + 1);
   309       }
   311       if (!aPostDataRef)
   312         aPostDataRef = {};
   314       let engine = Services.search.getEngineByAlias(keyword);
   315       if (engine) {
   316         let submission = engine.getSubmission(param);
   317         aPostDataRef.value = submission.postData;
   318         throw new Task.Result(submission.uri.spec);
   319       }
   321       try {
   322         [shortcutURL, aPostDataRef.value] = PlacesUtils.getURLAndPostDataForKeyword(keyword);
   323       } catch (e) {}
   325       if (!shortcutURL)
   326         throw new Task.Result(aURL);
   328       let postData = "";
   329       if (aPostDataRef.value)
   330         postData = unescape(aPostDataRef.value);
   332       if (/%s/i.test(shortcutURL) || /%s/i.test(postData)) {
   333         let charset = "";
   334         const re = /^(.*)\&mozcharset=([a-zA-Z][_\-a-zA-Z0-9]+)\s*$/;
   335         let matches = shortcutURL.match(re);
   336         if (matches)
   337           [, shortcutURL, charset] = matches;
   338         else {
   339           // Try to get the saved character-set.
   340           try {
   341             // makeURI throws if URI is invalid.
   342             // Will return an empty string if character-set is not found.
   343             charset = yield PlacesUtils.getCharsetForURI(Util.makeURI(shortcutURL));
   344           } catch (e) { dump("--- error " + e + "\n"); }
   345         }
   347         let encodedParam = "";
   348         if (charset)
   349           encodedParam = escape(convertFromUnicode(charset, param));
   350         else // Default charset is UTF-8
   351           encodedParam = encodeURIComponent(param);
   353         shortcutURL = shortcutURL.replace(/%s/g, encodedParam).replace(/%S/g, param);
   355         if (/%s/i.test(postData)) // POST keyword
   356           aPostDataRef.value = getPostDataStream(postData, param, encodedParam, "application/x-www-form-urlencoded");
   357       } else if (param) {
   358         // This keyword doesn't take a parameter, but one was provided. Just return
   359         // the original URL.
   360         aPostDataRef.value = null;
   362         throw new Task.Result(aURL);
   363       }
   365       throw new Task.Result(shortcutURL);
   366     });
   367   },
   369   /**
   370    * Return the currently active <browser> object
   371    */
   372   get selectedBrowser() {
   373     return (this._selectedTab && this._selectedTab.browser);
   374   },
   376   get tabs() {
   377     return this._tabs;
   378   },
   380   getTabModalPromptBox: function(aBrowser) {
   381     let browser = (aBrowser || getBrowser());
   382     let stack = browser.parentNode;
   383     let self = this;
   385     let promptBox = {
   386       appendPrompt : function(args, onCloseCallback) {
   387           let newPrompt = document.createElementNS(XUL_NS, "tabmodalprompt");
   388           newPrompt.setAttribute("promptType", args.promptType);
   389           stack.appendChild(newPrompt);
   390           browser.setAttribute("tabmodalPromptShowing", true);
   391           newPrompt.clientTop; // style flush to assure binding is attached
   393           let tab = self.getTabForBrowser(browser);
   394           tab = tab.chromeTab;
   396           newPrompt.metroInit(args, tab, onCloseCallback);
   397           return newPrompt;
   398       },
   400       removePrompt : function(aPrompt) {
   401           stack.removeChild(aPrompt);
   403           let prompts = this.listPrompts();
   404           if (prompts.length) {
   405           let prompt = prompts[prompts.length - 1];
   406               prompt.Dialog.setDefaultFocus();
   407           } else {
   408               browser.removeAttribute("tabmodalPromptShowing");
   409               browser.focus();
   410           }
   411       },
   413       listPrompts : function(aPrompt) {
   414           let els = stack.getElementsByTagNameNS(XUL_NS, "tabmodalprompt");
   415           // NodeList --> real JS array
   416           let prompts = Array.slice(els);
   417           return prompts;
   418       },
   419     };
   421     return promptBox;
   422   },
   424   getBrowserForWindowId: function getBrowserForWindowId(aWindowId) {
   425     for (let i = 0; i < this.browsers.length; i++) {
   426       if (this.browsers[i].contentWindowId == aWindowId)
   427         return this.browsers[i];
   428     }
   429     return null;
   430   },
   432   getBrowserForWindow: function(aWindow) {
   433     let windowID = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
   434                           .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
   435     return this.getBrowserForWindowId(windowID);
   436   },
   438   getTabForBrowser: function getTabForBrowser(aBrowser) {
   439     let tabs = this._tabs;
   440     for (let i = 0; i < tabs.length; i++) {
   441       if (tabs[i].browser == aBrowser)
   442         return tabs[i];
   443     }
   444     return null;
   445   },
   447   getTabAtIndex: function getTabAtIndex(index) {
   448     if (index >= this._tabs.length || index < 0)
   449       return null;
   450     return this._tabs[index];
   451   },
   453   getTabFromChrome: function getTabFromChrome(chromeTab) {
   454     for (var t = 0; t < this._tabs.length; t++) {
   455       if (this._tabs[t].chromeTab == chromeTab)
   456         return this._tabs[t];
   457     }
   458     return null;
   459   },
   461   createTabId: function createTabId() {
   462     return this._tabId++;
   463   },
   465   /**
   466    * Create a new tab and add it to the tab list.
   467    *
   468    * If you are opening a new foreground tab in response to a user action, use
   469    * BrowserUI.addAndShowTab which will also show the tab strip.
   470    *
   471    * @param aURI String specifying the URL to load.
   472    * @param aBringFront Boolean (optional) Open the new tab in the foreground?
   473    * @param aOwner Tab object (optional) The "parent" of the new tab.
   474    *   This is the tab responsible for opening the new tab.  When the new tab
   475    *   is closed, we will return to a parent or "sibling" tab if possible.
   476    * @param aParams Object (optional) with optional properties:
   477    *   index: Number specifying where in the tab list to insert the new tab.
   478    *   private: If true, the new tab should be have Private Browsing active.
   479    *   flags, postData, charset, referrerURI: See loadURIWithFlags.
   480    */
   481   addTab: function browser_addTab(aURI, aBringFront, aOwner, aParams) {
   482     let params = aParams || {};
   484     if (aOwner && !('index' in params)) {
   485       // Position the new tab to the right of its owner...
   486       params.index = this._tabs.indexOf(aOwner) + 1;
   487       // ...and to the right of any siblings.
   488       while (this._tabs[params.index] && this._tabs[params.index].owner == aOwner) {
   489         params.index++;
   490       }
   491     }
   493     let newTab = new Tab(aURI, params, aOwner);
   495     if (params.index >= 0) {
   496       this._tabs.splice(params.index, 0, newTab);
   497     } else {
   498       this._tabs.push(newTab);
   499     }
   501     if (aBringFront)
   502       this.selectedTab = newTab;
   504     this._announceNewTab(newTab);
   505     return newTab;
   506   },
   508   closeTab: function closeTab(aTab, aOptions) {
   509     let tab = aTab instanceof XULElement ? this.getTabFromChrome(aTab) : aTab;
   510     if (!tab) {
   511       return;
   512     }
   514     if (aOptions && "forceClose" in aOptions && aOptions.forceClose) {
   515       this._doCloseTab(tab);
   516       return;
   517     }
   519     tab.browser.messageManager.sendAsyncMessage("Browser:CanUnload", {});
   520   },
   522   savePage: function() {
   523     ContentAreaUtils.saveDocument(this.selectedBrowser.contentWindow.document);
   524   },
   526   /*
   527    * helper for addTab related methods. Fires events related to
   528    * new tab creation.
   529    */
   530   _announceNewTab: function (aTab) {
   531     let event = document.createEvent("UIEvents");
   532     event.initUIEvent("TabOpen", true, false, window, 0);
   533     aTab.chromeTab.dispatchEvent(event);
   534     aTab.browser.messageManager.sendAsyncMessage("Browser:TabOpen");
   535   },
   537   _doCloseTab: function _doCloseTab(aTab) {
   538     if (this._tabs.length === 1) {
   539       Browser.addTab(this.getHomePage());
   540     }
   542     let nextTab = this.getNextTab(aTab);
   544     // Tabs owned by the closed tab are now orphaned.
   545     this._tabs.forEach(function(item, index, array) {
   546       if (item.owner == aTab)
   547         item.owner = null;
   548     });
   550     // tray tab
   551     let event = document.createEvent("Events");
   552     event.initEvent("TabClose", true, false);
   553     aTab.chromeTab.dispatchEvent(event);
   555     // tab window
   556     event = document.createEvent("Events");
   557     event.initEvent("TabClose", true, false);
   558     aTab.browser.contentWindow.dispatchEvent(event);
   560     aTab.browser.messageManager.sendAsyncMessage("Browser:TabClose");
   562     let container = aTab.chromeTab.parentNode;
   563     aTab.destroy();
   564     this._tabs.splice(this._tabs.indexOf(aTab), 1);
   566     this.selectedTab = nextTab;
   568     event = document.createEvent("Events");
   569     event.initEvent("TabRemove", true, false);
   570     container.dispatchEvent(event);
   571   },
   573   getNextTab: function getNextTab(aTab) {
   574     let tabIndex = this._tabs.indexOf(aTab);
   575     if (tabIndex == -1)
   576       return null;
   578     if (this._selectedTab == aTab || this._selectedTab.chromeTab.hasAttribute("closing")) {
   579       let nextTabIndex = tabIndex + 1;
   580       let nextTab = null;
   582       while (nextTabIndex < this._tabs.length && (!nextTab || nextTab.chromeTab.hasAttribute("closing"))) {
   583         nextTab = this.getTabAtIndex(nextTabIndex);
   584         nextTabIndex++;
   585       }
   587       nextTabIndex = tabIndex - 1;
   588       while (nextTabIndex >= 0 && (!nextTab || nextTab.chromeTab.hasAttribute("closing"))) {
   589         nextTab = this.getTabAtIndex(nextTabIndex);
   590         nextTabIndex--;
   591       }
   593       if (!nextTab || nextTab.chromeTab.hasAttribute("closing"))
   594         return null;
   596       // If the next tab is not a sibling, switch back to the parent.
   597       if (aTab.owner && nextTab.owner != aTab.owner)
   598         nextTab = aTab.owner;
   600       if (!nextTab)
   601         return null;
   603       return nextTab;
   604     }
   606     return this._selectedTab;
   607   },
   609   get selectedTab() {
   610     return this._selectedTab;
   611   },
   613   set selectedTab(tab) {
   614     if (tab instanceof XULElement)
   615       tab = this.getTabFromChrome(tab);
   617     if (!tab)
   618       return;
   620     if (this._selectedTab == tab) {
   621       // Deck does not update its selectedIndex when children
   622       // are removed. See bug 602708
   623       Elements.browsers.selectedPanel = tab.notification;
   624       return;
   625     }
   627     let isFirstTab = this._selectedTab == null;
   628     let lastTab = this._selectedTab;
   629     let browser = tab.browser;
   631     this._selectedTab = tab;
   633     if (lastTab)
   634       lastTab.active = false;
   636     if (tab)
   637       tab.active = true;
   639     BrowserUI.update();
   641     if (isFirstTab) {
   642       BrowserUI._titleChanged(browser);
   643     } else {
   644       // Update all of our UI to reflect the new tab's location
   645       BrowserUI.updateURI();
   647       let event = document.createEvent("Events");
   648       event.initEvent("TabSelect", true, false);
   649       event.lastTab = lastTab;
   650       tab.chromeTab.dispatchEvent(event);
   651     }
   653     tab.lastSelected = Date.now();
   654   },
   656   supportsCommand: function(cmd) {
   657     return false;
   658   },
   660   isCommandEnabled: function(cmd) {
   661     return false;
   662   },
   664   doCommand: function(cmd) {
   665   },
   667   getNotificationBox: function getNotificationBox(aBrowser) {
   668     let browser = aBrowser || this.selectedBrowser;
   669     return browser.parentNode.parentNode;
   670   },
   672   /**
   673    * Handle cert exception message from content.
   674    */
   675   _handleCertException: function _handleCertException(aMessage) {
   676     let json = aMessage.json;
   677     if (json.action == "leave") {
   678       // Get the start page from the *default* pref branch, not the user's
   679       let url = Browser.getHomePage({ useDefault: true });
   680       this.loadURI(url);
   681     } else {
   682       // Handle setting an cert exception and reloading the page
   683       try {
   684         // Add a new SSL exception for this URL
   685         let uri = Services.io.newURI(json.url, null, null);
   686         let sslExceptions = new SSLExceptions();
   688         if (json.action == "permanent")
   689           sslExceptions.addPermanentException(uri, window);
   690         else
   691           sslExceptions.addTemporaryException(uri, window);
   692       } catch (e) {
   693         dump("EXCEPTION handle content command: " + e + "\n" );
   694       }
   696       // Automatically reload after the exception was added
   697       aMessage.target.reload();
   698     }
   699   },
   701   /**
   702    * Handle blocked site message from content.
   703    */
   704   _handleBlockedSite: function _handleBlockedSite(aMessage) {
   705     let formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter);
   706     let json = aMessage.json;
   707     switch (json.action) {
   708       case "leave": {
   709         // Get the start page from the *default* pref branch, not the user's
   710         let url = Browser.getHomePage({ useDefault: true });
   711         this.loadURI(url);
   712         break;
   713       }
   714       case "report-malware": {
   715         // Get the stop badware "why is this blocked" report url, append the current url, and go there.
   716         try {
   717           let reportURL = formatter.formatURLPref("browser.safebrowsing.malware.reportURL");
   718           reportURL += json.url;
   719           this.loadURI(reportURL);
   720         } catch (e) {
   721           Cu.reportError("Couldn't get malware report URL: " + e);
   722         }
   723         break;
   724       }
   725       case "report-phishing": {
   726         // It's a phishing site, just link to the generic information page
   727         let url = Services.urlFormatter.formatURLPref("app.support.baseURL");
   728         this.loadURI(url + "phishing-malware");
   729         break;
   730       }
   731     }
   732   },
   734   pinSite: function browser_pinSite() {
   735     // Get a path to our app tile
   736     var file = Components.classes["@mozilla.org/file/directory_service;1"].
   737            getService(Components.interfaces.nsIProperties).
   738            get("CurProcD", Components.interfaces.nsIFile);
   739     // Get rid of the current working directory's metro subidr
   740     file = file.parent;
   741     file.append("tileresources");
   742     file.append("VisualElements_logo.png");
   743     var ios = Components.classes["@mozilla.org/network/io-service;1"].
   744               getService(Components.interfaces.nsIIOService);
   745     var uriSpec = ios.newFileURI(file).spec;
   746     Services.metro.pinTileAsync(this._currentPageTileID,
   747                                 Browser.selectedBrowser.contentTitle, // short name
   748                                 Browser.selectedBrowser.contentTitle, // display name
   749                                 "-url " + Browser.selectedBrowser.currentURI.spec,
   750                             uriSpec, uriSpec);
   751   },
   753   get _currentPageTileID() {
   754     // We use a unique ID per URL, so just use an MD5 hash of the URL as the ID.
   755     let hasher = Cc["@mozilla.org/security/hash;1"].
   756                  createInstance(Ci.nsICryptoHash);
   757     hasher.init(Ci.nsICryptoHash.MD5);
   758     let stringStream = Cc["@mozilla.org/io/string-input-stream;1"].
   759                        createInstance(Ci.nsIStringInputStream);
   760     stringStream.data = Browser.selectedBrowser.currentURI.spec;
   761     hasher.updateFromStream(stringStream, -1);
   762     let hashASCII = hasher.finish(true);
   763     // Replace '/' with a valid filesystem character
   764     return ("FFTileID_" + hashASCII).replace('/', '_', 'g');
   765   },
   767   unpinSite: function browser_unpinSite() {
   768     if (!Services.metro.immersive)
   769       return;
   771     Services.metro.unpinTileAsync(this._currentPageTileID);
   772   },
   774   isSitePinned: function browser_isSitePinned() {
   775     if (!Services.metro.immersive)
   776       return false;
   778     return Services.metro.isTilePinned(this._currentPageTileID);
   779   },
   781   starSite: function browser_starSite(callback) {
   782     let uri = this.selectedBrowser.currentURI;
   783     let title = this.selectedBrowser.contentTitle;
   785     Bookmarks.addForURI(uri, title, callback);
   786   },
   788   unstarSite: function browser_unstarSite(callback) {
   789     let uri = this.selectedBrowser.currentURI;
   790     Bookmarks.removeForURI(uri, callback);
   791   },
   793   isSiteStarredAsync: function browser_isSiteStarredAsync(callback) {
   794     let uri = this.selectedBrowser.currentURI;
   795     Bookmarks.isURIBookmarked(uri, callback);
   796   },
   798   /**
   799    * Convenience function for getting the scrollbox position off of a
   800    * scrollBoxObject interface.  Returns the actual values instead of the
   801    * wrapping objects.
   802    *
   803    * @param scroller a scrollBoxObject on which to call scroller.getPosition()
   804    */
   805   getScrollboxPosition: function getScrollboxPosition(scroller) {
   806     let x = {};
   807     let y = {};
   808     scroller.getPosition(x, y);
   809     return new Point(x.value, y.value);
   810   },
   812   forceChromeReflow: function forceChromeReflow() {
   813     let dummy = getComputedStyle(document.documentElement, "").width;
   814   },
   816   receiveMessage: function receiveMessage(aMessage) {
   817     let json = aMessage.json;
   818     let browser = aMessage.target;
   820     switch (aMessage.name) {
   821       case "DOMLinkAdded": {
   822         // checks for an icon to use for a web app
   823         // apple-touch-icon size is 57px and default size is 16px
   824         let rel = json.rel.toLowerCase().split(" ");
   825         if (rel.indexOf("icon") != -1) {
   826           // We use the sizes attribute if available
   827           // see http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#rel-icon
   828           let size = 16;
   829           if (json.sizes) {
   830             let sizes = json.sizes.toLowerCase().split(" ");
   831             sizes.forEach(function(item) {
   832               if (item != "any") {
   833                 let [w, h] = item.split("x");
   834                 size = Math.max(Math.min(w, h), size);
   835               }
   836             });
   837           }
   838           if (size > browser.appIcon.size) {
   839             browser.appIcon.href = json.href;
   840             browser.appIcon.size = size;
   841           }
   842         }
   843         else if ((rel.indexOf("apple-touch-icon") != -1) && (browser.appIcon.size < 57)) {
   844           // XXX should we support apple-touch-icon-precomposed ?
   845           // see http://developer.apple.com/safari/library/documentation/appleapplications/reference/safariwebcontent/configuringwebapplications/configuringwebapplications.html
   846           browser.appIcon.href = json.href;
   847           browser.appIcon.size = 57;
   848         }
   849         break;
   850       }
   851       case "Browser:FormSubmit":
   852         browser.lastLocation = null;
   853         break;
   855       case "Browser:CanUnload:Return": {
   856         if (json.permit) {
   857           let tab = this.getTabForBrowser(browser);
   858           BrowserUI.animateClosingTab(tab);
   859         }
   860         break;
   861       }
   862       case "Browser:CertException":
   863         this._handleCertException(aMessage);
   864         break;
   865       case "Browser:BlockedSite":
   866         this._handleBlockedSite(aMessage);
   867         break;
   868     }
   869   },
   870 };
   872 Browser.MainDragger = function MainDragger() {
   873   this._horizontalScrollbar = document.getElementById("horizontal-scroller");
   874   this._verticalScrollbar = document.getElementById("vertical-scroller");
   875   this._scrollScales = { x: 0, y: 0 };
   877   Elements.browsers.addEventListener("PanBegin", this, false);
   878   Elements.browsers.addEventListener("PanFinished", this, false);
   879 };
   881 Browser.MainDragger.prototype = {
   882   isDraggable: function isDraggable(target, scroller) {
   883     return { x: true, y: true };
   884   },
   886   dragStart: function dragStart(clientX, clientY, target, scroller) {
   887     let browser = getBrowser();
   888     let bcr = browser.getBoundingClientRect();
   889     this._contentView = browser.getViewAt(clientX - bcr.left, clientY - bcr.top);
   890   },
   892   dragStop: function dragStop(dx, dy, scroller) {
   893     if (this._contentView && this._contentView._updateCacheViewport)
   894       this._contentView._updateCacheViewport();
   895     this._contentView = null;
   896   },
   898   dragMove: function dragMove(dx, dy, scroller, aIsKinetic) {
   899     let doffset = new Point(dx, dy);
   901     this._panContent(doffset);
   903     if (aIsKinetic && doffset.x != 0)
   904       return false;
   906     this._updateScrollbars();
   908     return !doffset.equals(dx, dy);
   909   },
   911   handleEvent: function handleEvent(aEvent) {
   912     let browser = getBrowser();
   913     switch (aEvent.type) {
   914       case "PanBegin": {
   915         let width = ContentAreaObserver.width, height = ContentAreaObserver.height;
   916         let contentWidth = browser.contentDocumentWidth * browser.scale;
   917         let contentHeight = browser.contentDocumentHeight * browser.scale;
   919         // Allow a small margin on both sides to prevent adding scrollbars
   920         // on small viewport approximation
   921         const ALLOWED_MARGIN = 5;
   922         const SCROLL_CORNER_SIZE = 8;
   923         this._scrollScales = {
   924           x: (width + ALLOWED_MARGIN) < contentWidth ? (width - SCROLL_CORNER_SIZE) / contentWidth : 0,
   925           y: (height + ALLOWED_MARGIN) < contentHeight ? (height - SCROLL_CORNER_SIZE) / contentHeight : 0
   926         }
   928         this._showScrollbars();
   929         break;
   930       }
   931       case "PanFinished":
   932         this._hideScrollbars();
   934         // Update the scroll position of the content
   935         browser._updateCSSViewport();
   936         break;
   937     }
   938   },
   940   _panContent: function md_panContent(aOffset) {
   941     if (this._contentView && !this._contentView.isRoot()) {
   942       this._panContentView(this._contentView, aOffset);
   943       // XXX we may need to have "escape borders" for iframe panning
   944       // XXX does not deal with scrollables within scrollables
   945     }
   946     // Do content panning
   947     this._panContentView(getBrowser().getRootView(), aOffset);
   948   },
   950   /** Pan scroller by the given amount. Updates doffset with leftovers. */
   951   _panContentView: function _panContentView(contentView, doffset) {
   952     let pos0 = contentView.getPosition();
   953     contentView.scrollBy(doffset.x, doffset.y);
   954     let pos1 = contentView.getPosition();
   955     doffset.subtract(pos1.x - pos0.x, pos1.y - pos0.y);
   956   },
   958   _updateScrollbars: function _updateScrollbars() {
   959     let scaleX = this._scrollScales.x, scaleY = this._scrollScales.y;
   960     let contentScroll = Browser.getScrollboxPosition(Browser.contentScrollboxScroller);
   962     if (scaleX)
   963       this._horizontalScrollbar.style.MozTransform =
   964         "translateX(" + Math.round(contentScroll.x * scaleX) + "px)";
   966     if (scaleY) {
   967       let y = Math.round(contentScroll.y * scaleY);
   968       let x = 0;
   970       this._verticalScrollbar.style.MozTransform =
   971         "translate(" + x + "px," + y + "px)";
   972     }
   973   },
   975   _showScrollbars: function _showScrollbars() {
   976     this._updateScrollbars();
   977     let scaleX = this._scrollScales.x, scaleY = this._scrollScales.y;
   978     if (scaleX) {
   979       this._horizontalScrollbar.width = ContentAreaObserver.width * scaleX;
   980       this._horizontalScrollbar.setAttribute("panning", "true");
   981     }
   983     if (scaleY) {
   984       this._verticalScrollbar.height = ContentAreaObserver.height * scaleY;
   985       this._verticalScrollbar.setAttribute("panning", "true");
   986     }
   987   },
   989   _hideScrollbars: function _hideScrollbars() {
   990     this._scrollScales.x = 0;
   991     this._scrollScales.y = 0;
   992     this._horizontalScrollbar.removeAttribute("panning");
   993     this._verticalScrollbar.removeAttribute("panning");
   994     this._horizontalScrollbar.removeAttribute("width");
   995     this._verticalScrollbar.removeAttribute("height");
   996     this._horizontalScrollbar.style.MozTransform = "";
   997     this._verticalScrollbar.style.MozTransform = "";
   998   }
   999 };
  1002 function nsBrowserAccess() { }
  1004 nsBrowserAccess.prototype = {
  1005   QueryInterface: function(aIID) {
  1006     if (aIID.equals(Ci.nsIBrowserDOMWindow) || aIID.equals(Ci.nsISupports))
  1007       return this;
  1008     throw Cr.NS_NOINTERFACE;
  1009   },
  1011   _getOpenAction: function _getOpenAction(aURI, aOpener, aWhere, aContext) {
  1012     let where = aWhere;
  1013     /*
  1014      * aWhere:
  1015      * OPEN_DEFAULTWINDOW: default action
  1016      * OPEN_CURRENTWINDOW: current window/tab
  1017      * OPEN_NEWWINDOW: not allowed, converted to newtab below
  1018      * OPEN_NEWTAB: open a new tab
  1019      * OPEN_SWITCHTAB: open in an existing tab if it matches, otherwise open
  1020      * a new tab. afaict we always open these in the current tab.
  1021      */
  1022     if (where == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) {
  1023       // query standard browser prefs indicating what to do for default action
  1024       switch (aContext) {
  1025         // indicates this is an open request from a 3rd party app.
  1026         case Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL :
  1027           where = Services.prefs.getIntPref("browser.link.open_external");
  1028           break;
  1029         // internal request
  1030         default :
  1031           where = Services.prefs.getIntPref("browser.link.open_newwindow");
  1034     if (where == Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW) {
  1035       Util.dumpLn("Invalid request - we can't open links in new windows.");
  1036       where = Ci.nsIBrowserDOMWindow.OPEN_NEWTAB;
  1038     return where;
  1039   },
  1041   _getBrowser: function _getBrowser(aURI, aOpener, aWhere, aContext) {
  1042     let isExternal = (aContext == Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
  1043     // We don't allow externals apps opening chrome docs
  1044     if (isExternal && aURI && aURI.schemeIs("chrome"))
  1045       return null;
  1047     let location;
  1048     let browser;
  1049     let loadflags = isExternal ?
  1050                       Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL :
  1051                       Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
  1052     let openAction = this._getOpenAction(aURI, aOpener, aWhere, aContext);
  1054     if (openAction == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB) {
  1055       let owner = isExternal ? null : Browser.selectedTab;
  1056       let tab = BrowserUI.openLinkInNewTab("about:blank", true, owner);
  1057       browser = tab.browser;
  1058     } else {
  1059       browser = Browser.selectedBrowser;
  1062     try {
  1063       let referrer;
  1064       if (aURI && browser) {
  1065         if (aOpener) {
  1066           location = aOpener.location;
  1067           referrer = Services.io.newURI(location, null, null);
  1069         browser.loadURIWithFlags(aURI.spec, loadflags, referrer, null, null);
  1071       browser.focus();
  1072     } catch(e) { }
  1074     return browser;
  1075   },
  1077   openURI: function browser_openURI(aURI, aOpener, aWhere, aContext) {
  1078     let browser = this._getBrowser(aURI, aOpener, aWhere, aContext);
  1079     return browser ? browser.contentWindow : null;
  1080   },
  1082   openURIInFrame: function browser_openURIInFrame(aURI, aOpener, aWhere, aContext) {
  1083     let browser = this._getBrowser(aURI, aOpener, aWhere, aContext);
  1084     return browser ? browser.QueryInterface(Ci.nsIFrameLoaderOwner) : null;
  1085   },
  1087   isTabContentWindow: function(aWindow) {
  1088     return Browser.browsers.some(function (browser) browser.contentWindow == aWindow);
  1089   },
  1091   get contentWindow() {
  1092     return Browser.selectedBrowser.contentWindow;
  1094 };
  1096 /**
  1097  * Handler for blocked popups, triggered by DOMUpdatePageReport events in browser.xml
  1098  */
  1099 var PopupBlockerObserver = {
  1100   init: function init() {
  1101     Elements.browsers.addEventListener("mousedown", this, true);
  1102   },
  1104   handleEvent: function handleEvent(aEvent) {
  1105     switch (aEvent.type) {
  1106       case "mousedown":
  1107         let box = Browser.getNotificationBox();
  1108         let notification = box.getNotificationWithValue("popup-blocked");
  1109         if (notification && !notification.contains(aEvent.target))
  1110           box.removeNotification(notification);
  1111         break;
  1113   },
  1115   onUpdatePageReport: function onUpdatePageReport(aEvent) {
  1116     var cBrowser = Browser.selectedBrowser;
  1117     if (aEvent.originalTarget != cBrowser)
  1118       return;
  1120     if (!cBrowser.pageReport)
  1121       return;
  1123     let result = Services.perms.testExactPermission(Browser.selectedBrowser.currentURI, "popup");
  1124     if (result == Ci.nsIPermissionManager.DENY_ACTION)
  1125       return;
  1127     // Only show the notification again if we've not already shown it. Since
  1128     // notifications are per-browser, we don't need to worry about re-adding
  1129     // it.
  1130     if (!cBrowser.pageReport.reported) {
  1131       if (Services.prefs.getBoolPref("privacy.popups.showBrowserMessage")) {
  1132         var brandShortName = Strings.brand.GetStringFromName("brandShortName");
  1133         var popupCount = cBrowser.pageReport.length;
  1135         let strings = Strings.browser;
  1136         let message = PluralForm.get(popupCount, strings.GetStringFromName("popupWarning.message"))
  1137                                 .replace("#1", brandShortName)
  1138                                 .replace("#2", popupCount);
  1140         var notificationBox = Browser.getNotificationBox();
  1141         var notification = notificationBox.getNotificationWithValue("popup-blocked");
  1142         if (notification) {
  1143           notification.label = message;
  1145         else {
  1146           var buttons = [
  1148               isDefault: false,
  1149               label: strings.GetStringFromName("popupButtonAllowOnce2"),
  1150               accessKey: "",
  1151               callback: function() { PopupBlockerObserver.showPopupsForSite(); }
  1152             },
  1154               label: strings.GetStringFromName("popupButtonAlwaysAllow3"),
  1155               accessKey: "",
  1156               callback: function() { PopupBlockerObserver.allowPopupsForSite(true); }
  1157             },
  1159               label: strings.GetStringFromName("popupButtonNeverWarn3"),
  1160               accessKey: "",
  1161               callback: function() { PopupBlockerObserver.allowPopupsForSite(false); }
  1163           ];
  1165           const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
  1166           notificationBox.appendNotification(message, "popup-blocked",
  1167                                              "chrome://browser/skin/images/infobar-popup.png",
  1168                                              priority, buttons);
  1171       // Record the fact that we've reported this blocked popup, so we don't
  1172       // show it again.
  1173       cBrowser.pageReport.reported = true;
  1175   },
  1177   allowPopupsForSite: function allowPopupsForSite(aAllow) {
  1178     var currentURI = Browser.selectedBrowser.currentURI;
  1179     Services.perms.add(currentURI, "popup", aAllow
  1180                        ?  Ci.nsIPermissionManager.ALLOW_ACTION
  1181                        :  Ci.nsIPermissionManager.DENY_ACTION);
  1183     Browser.getNotificationBox().removeCurrentNotification();
  1184   },
  1186   showPopupsForSite: function showPopupsForSite() {
  1187     let uri = Browser.selectedBrowser.currentURI;
  1188     let pageReport = Browser.selectedBrowser.pageReport;
  1189     if (pageReport) {
  1190       for (let i = 0; i < pageReport.length; ++i) {
  1191         var popupURIspec = pageReport[i].popupWindowURI.spec;
  1193         // Sometimes the popup URI that we get back from the pageReport
  1194         // isn't useful (for instance, netscape.com's popup URI ends up
  1195         // being "http://www.netscape.com", which isn't really the URI of
  1196         // the popup they're trying to show).  This isn't going to be
  1197         // useful to the user, so we won't create a menu item for it.
  1198         if (popupURIspec == "" || !Util.isURLMemorable(popupURIspec) || popupURIspec == uri.spec)
  1199           continue;
  1201         let popupFeatures = pageReport[i].popupWindowFeatures;
  1202         let popupName = pageReport[i].popupWindowName;
  1204         Browser.addTab(popupURIspec, false, Browser.selectedTab);
  1208 };
  1210 var SessionHistoryObserver = {
  1211   observe: function sho_observe(aSubject, aTopic, aData) {
  1212     if (aTopic != "browser:purge-session-history")
  1213       return;
  1215     let newTab = Browser.addTab("about:start", true);
  1216     let tab = Browser._tabs[0];
  1217     while(tab != newTab) {
  1218       Browser.closeTab(tab, { forceClose: true } );
  1219       tab = Browser._tabs[0];
  1222     PlacesUtils.history.removeAllPages();
  1224     // Clear undo history of the URL bar
  1225     BrowserUI._edit.editor.transactionManager.clear();
  1227 };
  1229 function getNotificationBox(aBrowser) {
  1230   return Browser.getNotificationBox(aBrowser);
  1233 function showDownloadManager(aWindowContext, aID, aReason) {
  1234   // TODO: Bug 883962: Toggle the downloads infobar as our current "download manager".
  1237 function Tab(aURI, aParams, aOwner) {
  1238   this._id = null;
  1239   this._browser = null;
  1240   this._notification = null;
  1241   this._loading = false;
  1242   this._progressActive = false;
  1243   this._progressCount = 0;
  1244   this._chromeTab = null;
  1245   this._eventDeferred = null;
  1246   this._updateThumbnailTimeout = null;
  1248   this._private = false;
  1249   if ("private" in aParams) {
  1250     this._private = aParams.private;
  1251   } else if (aOwner) {
  1252     this._private = aOwner._private;
  1255   this.owner = aOwner || null;
  1257   // Set to 0 since new tabs that have not been viewed yet are good tabs to
  1258   // toss if app needs more memory.
  1259   this.lastSelected = 0;
  1261   // aParams is an object that contains some properties for the initial tab
  1262   // loading like flags, a referrerURI, a charset or even a postData.
  1263   this.create(aURI, aParams || {}, aOwner);
  1265   // default tabs to inactive (i.e. no display port)
  1266   this.active = false;
  1269 Tab.prototype = {
  1270   get browser() {
  1271     return this._browser;
  1272   },
  1274   get notification() {
  1275     return this._notification;
  1276   },
  1278   get chromeTab() {
  1279     return this._chromeTab;
  1280   },
  1282   get isPrivate() {
  1283     return this._private;
  1284   },
  1286   get pageShowPromise() {
  1287     return this._eventDeferred ? this._eventDeferred.promise : null;
  1288   },
  1290   startLoading: function startLoading() {
  1291     if (this._loading) {
  1292       let stack = new Error().stack;
  1293       throw "Already Loading!\n" + stack;
  1295     this._loading = true;
  1296   },
  1298   endLoading: function endLoading() {
  1299     this._loading = false;
  1300     this.updateFavicon();
  1301   },
  1303   isLoading: function isLoading() {
  1304     return this._loading;
  1305   },
  1307   create: function create(aURI, aParams, aOwner) {
  1308     this._eventDeferred = Promise.defer();
  1310     this._chromeTab = Elements.tabList.addTab(aParams.index);
  1311     if (this.isPrivate) {
  1312       this._chromeTab.setAttribute("private", "true");
  1315     this._id = Browser.createTabId();
  1316     let browser = this._createBrowser(aURI, null);
  1318     let self = this;
  1319     function onPageShowEvent(aEvent) {
  1320       browser.removeEventListener("pageshow", onPageShowEvent);
  1321       if (self._eventDeferred) {
  1322         self._eventDeferred.resolve(self);
  1324       self._eventDeferred = null;
  1326     browser.addEventListener("pageshow", onPageShowEvent, true);
  1327     browser.addEventListener("DOMWindowCreated", this, false);
  1328     browser.addEventListener("StartUIChange", this, false);
  1329     Elements.browsers.addEventListener("SizeChanged", this, false);
  1331     browser.messageManager.addMessageListener("Content:StateChange", this);
  1333     if (aOwner)
  1334       this._copyHistoryFrom(aOwner);
  1335     this._loadUsingParams(browser, aURI, aParams);
  1336   },
  1338   updateViewport: function (aEvent) {
  1339     // <meta name=viewport> is not yet supported; just use the browser size.
  1340     let browser = this.browser;
  1342     // On the start page we add padding to keep the browser above the navbar.
  1343     let paddingBottom = parseInt(getComputedStyle(browser).paddingBottom, 10);
  1344     let height = browser.clientHeight - paddingBottom;
  1346     browser.setWindowSize(browser.clientWidth, height);
  1347   },
  1349   handleEvent: function (aEvent) {
  1350     switch (aEvent.type) {
  1351       case "DOMWindowCreated":
  1352       case "StartUIChange":
  1353         this.updateViewport();
  1354         break;
  1355       case "SizeChanged":
  1356         this.updateViewport();
  1357         this._delayUpdateThumbnail();
  1358         break;
  1359       case "AlertClose": {
  1360         if (this == Browser.selectedTab) {
  1361           this.updateViewport();
  1363         break;
  1366   },
  1368   receiveMessage: function(aMessage) {
  1369     switch (aMessage.name) {
  1370       case "Content:StateChange":
  1371         // update the thumbnail now...
  1372         this.updateThumbnail();
  1373         // ...and in a little while to capture page after load.
  1374         if (aMessage.json.stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
  1375           this._delayUpdateThumbnail();
  1377         break;
  1379   },
  1381   _delayUpdateThumbnail: function() {
  1382     clearTimeout(this._updateThumbnailTimeout);
  1383     this._updateThumbnailTimeout = setTimeout(() => {
  1384       this.updateThumbnail();
  1385     }, kTabThumbnailDelayCapture);
  1386   },
  1388   destroy: function destroy() {
  1389     this._browser.messageManager.removeMessageListener("Content:StateChange", this);
  1390     this._browser.removeEventListener("DOMWindowCreated", this, false);
  1391     this._browser.removeEventListener("StartUIChange", this, false);
  1392     Elements.browsers.removeEventListener("SizeChanged", this, false);
  1393     clearTimeout(this._updateThumbnailTimeout);
  1395     Elements.tabList.removeTab(this._chromeTab);
  1396     this._chromeTab = null;
  1397     this._destroyBrowser();
  1398   },
  1400   resurrect: function resurrect() {
  1401     let dead = this._browser;
  1402     let active = this.active;
  1404     // Hold onto the session store data
  1405     let session = { data: dead.__SS_data, extra: dead.__SS_extdata };
  1407     // We need this data to correctly create and position the new browser
  1408     // If this browser is already a zombie, fallback to the session data
  1409     let currentURL = dead.__SS_restore ? session.data.entries[0].url : dead.currentURI.spec;
  1410     let sibling = dead.nextSibling;
  1412     // Destory and re-create the browser
  1413     this._destroyBrowser();
  1414     let browser = this._createBrowser(currentURL, sibling);
  1415     if (active)
  1416       this.active = true;
  1418     // Reattach session store data and flag this browser so it is restored on select
  1419     browser.__SS_data = session.data;
  1420     browser.__SS_extdata = session.extra;
  1421     browser.__SS_restore = true;
  1422   },
  1424   _copyHistoryFrom: function _copyHistoryFrom(tab) {
  1425     let otherHistory = tab._browser._webNavigation.sessionHistory;
  1426     let history = this._browser._webNavigation.sessionHistory;
  1428     // Ensure that history is initialized
  1429     history.QueryInterface(Ci.nsISHistoryInternal);
  1431     for (let i = 0, length = otherHistory.index; i <= length; i++)
  1432       history.addEntry(otherHistory.getEntryAtIndex(i, false), true);
  1433   },
  1435   _loadUsingParams: function _loadUsingParams(aBrowser, aURI, aParams) {
  1436     let flags = aParams.flags || Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
  1437     let postData = ("postData" in aParams && aParams.postData) ? aParams.postData.value : null;
  1438     let referrerURI = "referrerURI" in aParams ? aParams.referrerURI : null;
  1439     let charset = "charset" in aParams ? aParams.charset : null;
  1440     aBrowser.loadURIWithFlags(aURI, flags, referrerURI, charset, postData);
  1441   },
  1443   _createBrowser: function _createBrowser(aURI, aInsertBefore) {
  1444     if (this._browser)
  1445       throw "Browser already exists";
  1447     // Create a notification box around the browser. Note this includes
  1448     // the input overlay we use to shade content from input events when
  1449     // we're intercepting touch input.
  1450     let notification = this._notification = document.createElement("notificationbox");
  1452     let browser = this._browser = document.createElement("browser");
  1453     browser.id = "browser-" + this._id;
  1454     this._chromeTab.linkedBrowser = browser;
  1456     browser.setAttribute("type", "content-targetable");
  1458     let useRemote = Services.appinfo.browserTabsRemote;
  1459     let useLocal = Util.isLocalScheme(aURI);
  1460     browser.setAttribute("remote", (!useLocal && useRemote) ? "true" : "false");
  1462     // Append the browser to the document, which should start the page load
  1463     let stack = document.createElementNS(XUL_NS, "stack");
  1464     stack.className = "browserStack";
  1465     stack.appendChild(browser);
  1466     stack.setAttribute("flex", "1");
  1467     notification.appendChild(stack);
  1468     Elements.browsers.insertBefore(notification, aInsertBefore);
  1470     notification.dir = "reverse";
  1471     notification.addEventListener("AlertClose", this);
  1473      // let the content area manager know about this browser.
  1474     ContentAreaObserver.onBrowserCreated(browser);
  1476     if (this.isPrivate) {
  1477       let ctx = browser.docShell.QueryInterface(Ci.nsILoadContext);
  1478       ctx.usePrivateBrowsing = true;
  1481     // stop about:blank from loading
  1482     browser.stop();
  1484     let fl = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
  1485     fl.renderMode = Ci.nsIFrameLoader.RENDER_MODE_ASYNC_SCROLL;
  1487     return browser;
  1488   },
  1490   _destroyBrowser: function _destroyBrowser() {
  1491     if (this._browser) {
  1492       let notification = this._notification;
  1493       notification.removeEventListener("AlertClose", this);
  1494       let browser = this._browser;
  1495       browser.active = false;
  1497       this._notification = null;
  1498       this._browser = null;
  1499       this._loading = false;
  1501       Elements.browsers.removeChild(notification);
  1503   },
  1505   updateThumbnail: function updateThumbnail() {
  1506     if (!this.isPrivate) {
  1507       PageThumbs.captureToCanvas(this.browser.contentWindow, this._chromeTab.thumbnailCanvas);
  1509   },
  1511   updateFavicon: function updateFavicon() {
  1512     this._chromeTab.updateFavicon(this._browser.mIconURL);
  1513   },
  1515   set active(aActive) {
  1516     if (!this._browser)
  1517       return;
  1519     let notification = this._notification;
  1520     let browser = this._browser;
  1522     if (aActive) {
  1523       notification.classList.add("active-tab-notificationbox");
  1524       browser.setAttribute("type", "content-primary");
  1525       Elements.browsers.selectedPanel = notification;
  1526       browser.active = true;
  1527       Elements.tabList.selectedTab = this._chromeTab;
  1528       browser.focus();
  1529     } else {
  1530       notification.classList.remove("active-tab-notificationbox");
  1531       browser.messageManager.sendAsyncMessage("Browser:Blur", { });
  1532       browser.setAttribute("type", "content-targetable");
  1533       browser.active = false;
  1535   },
  1537   get active() {
  1538     if (!this._browser)
  1539       return false;
  1540     return this._browser.getAttribute("type") == "content-primary";
  1541   },
  1543   toString: function() {
  1544     return "[Tab " + (this._browser ? this._browser.currentURI.spec : "(no browser)") + "]";
  1546 };
  1548 // Helper used to hide IPC / non-IPC differences for rendering to a canvas
  1549 function rendererFactory(aBrowser, aCanvas) {
  1550   let wrapper = {};
  1552   if (aBrowser.contentWindow) {
  1553     let ctx = aCanvas.getContext("2d");
  1554     let draw = function(browser, aLeft, aTop, aWidth, aHeight, aColor, aFlags) {
  1555       ctx.drawWindow(browser.contentWindow, aLeft, aTop, aWidth, aHeight, aColor, aFlags);
  1556       let e = document.createEvent("HTMLEvents");
  1557       e.initEvent("MozAsyncCanvasRender", true, true);
  1558       aCanvas.dispatchEvent(e);
  1559     };
  1560     wrapper.checkBrowser = function(browser) {
  1561       return browser.contentWindow;
  1562     };
  1563     wrapper.drawContent = function(callback) {
  1564       callback(ctx, draw);
  1565     };
  1567   else {
  1568     let ctx = aCanvas.MozGetIPCContext("2d");
  1569     let draw = function(browser, aLeft, aTop, aWidth, aHeight, aColor, aFlags) {
  1570       ctx.asyncDrawXULElement(browser, aLeft, aTop, aWidth, aHeight, aColor, aFlags);
  1571     };
  1572     wrapper.checkBrowser = function(browser) {
  1573       return !browser.contentWindow;
  1574     };
  1575     wrapper.drawContent = function(callback) {
  1576       callback(ctx, draw);
  1577     };
  1580   return wrapper;
  1581 };
  1583 // Based on ClickEventHandler from /browser/base/content/content.js
  1584 let ClickEventHandler = {
  1585   init: function () {
  1586     gEventListenerService.addSystemEventListener(Elements.browsers, "click", this, true);
  1587   },
  1589   uninit: function () {
  1590     gEventListenerService.removeSystemEventListener(Elements.browsers, "click", this, true);
  1591   },
  1593   handleEvent: function (aEvent) {
  1594     if (!aEvent.isTrusted || aEvent.defaultPrevented) {
  1595       return;
  1597     let [href, node] = this._hrefAndLinkNodeForClickEvent(aEvent);
  1598     if (href && (aEvent.button == 1 || aEvent.ctrlKey)) {
  1599       // Open link in a new tab for middle-click or ctrl-click
  1600       BrowserUI.openLinkInNewTab(href, aEvent.shiftKey, Browser.selectedTab);
  1602   },
  1604   /**
  1605    * Extracts linkNode and href for the current click target.
  1607    * @param event
  1608    *        The click event.
  1609    * @return [href, linkNode].
  1611    * @note linkNode will be null if the click wasn't on an anchor
  1612    *       element (or XLink).
  1613    */
  1614   _hrefAndLinkNodeForClickEvent: function(event) {
  1615     function isHTMLLink(aNode) {
  1616       return ((aNode instanceof content.HTMLAnchorElement && aNode.href) ||
  1617               (aNode instanceof content.HTMLAreaElement && aNode.href) ||
  1618               aNode instanceof content.HTMLLinkElement);
  1621     let node = event.target;
  1622     while (node && !isHTMLLink(node)) {
  1623       node = node.parentNode;
  1626     if (node)
  1627       return [node.href, node];
  1629     // If there is no linkNode, try simple XLink.
  1630     let href, baseURI;
  1631     node = event.target;
  1632     while (node && !href) {
  1633       if (node.nodeType == content.Node.ELEMENT_NODE) {
  1634         href = node.getAttributeNS("http://www.w3.org/1999/xlink", "href");
  1635         if (href)
  1636           baseURI = node.ownerDocument.baseURIObject;
  1638       node = node.parentNode;
  1641     // In case of XLink, we don't return the node we got href from since
  1642     // callers expect <a>-like elements.
  1643     // Note: makeURI() will throw if aUri is not a valid URI.
  1644     return [href ? Services.io.newURI(href, null, baseURI).spec : null, null];
  1646 };

mercurial