browser/metro/base/content/browser-ui.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/. */
     5 "use strict";
     7 Cu.import("resource://gre/modules/devtools/dbg-server.jsm")
     8 Cu.import("resource://gre/modules/WindowsPrefSync.jsm");
    10 /**
    11  * Constants
    12  */
    14 // Devtools Messages
    15 const debugServerStateChanged = "devtools.debugger.remote-enabled";
    16 const debugServerPortChanged = "devtools.debugger.remote-port";
    18 // delay when showing the tab bar briefly after a new foreground tab opens
    19 const kForegroundTabAnimationDelay = 1000;
    20 // delay when showing the tab bar after opening a new background tab opens
    21 const kBackgroundTabAnimationDelay = 3000;
    22 // delay before closing tab bar after closing or selecting a tab
    23 const kChangeTabAnimationDelay = 500;
    25 /**
    26  * Cache of commonly used elements.
    27  */
    29 let Elements = {};
    30 [
    31   ["contentShowing",     "bcast_contentShowing"],
    32   ["urlbarState",        "bcast_urlbarState"],
    33   ["loadingState",       "bcast_loadingState"],
    34   ["windowState",        "bcast_windowState"],
    35   ["chromeState",        "bcast_chromeState"],
    36   ["mainKeyset",         "mainKeyset"],
    37   ["stack",              "stack"],
    38   ["tabList",            "tabs"],
    39   ["tabs",               "tabs-container"],
    40   ["controls",           "browser-controls"],
    41   ["panelUI",            "panel-container"],
    42   ["tray",               "tray"],
    43   ["toolbar",            "toolbar"],
    44   ["browsers",           "browsers"],
    45   ["navbar",             "navbar"],
    46   ["autocomplete",       "urlbar-autocomplete"],
    47   ["contextappbar",      "contextappbar"],
    48   ["findbar",            "findbar"],
    49   ["contentViewport",    "content-viewport"],
    50   ["progress",           "progress-control"],
    51   ["progressContainer",  "progress-container"],
    52   ["feedbackLabel",  "feedback-label"],
    53 ].forEach(function (aElementGlobal) {
    54   let [name, id] = aElementGlobal;
    55   XPCOMUtils.defineLazyGetter(Elements, name, function() {
    56     return document.getElementById(id);
    57   });
    58 });
    60 /**
    61  * Cache of commonly used string bundles.
    62  */
    64 var Strings = {};
    65 [
    66   ["browser",    "chrome://browser/locale/browser.properties"],
    67   ["brand",      "chrome://branding/locale/brand.properties"]
    68 ].forEach(function (aStringBundle) {
    69   let [name, bundle] = aStringBundle;
    70   XPCOMUtils.defineLazyGetter(Strings, name, function() {
    71     return Services.strings.createBundle(bundle);
    72   });
    73 });
    75 var BrowserUI = {
    76   get _edit() { return document.getElementById("urlbar-edit"); },
    77   get _back() { return document.getElementById("cmd_back"); },
    78   get _forward() { return document.getElementById("cmd_forward"); },
    80   lastKnownGoodURL: "", // used when the user wants to escape unfinished url entry
    81   ready: false, // used for tests to determine when delayed initialization is done
    83   init: function() {
    84     // start the debugger now so we can use it on the startup code as well
    85     if (Services.prefs.getBoolPref(debugServerStateChanged)) {
    86       this.runDebugServer();
    87     }
    88     Services.prefs.addObserver(debugServerStateChanged, this, false);
    89     Services.prefs.addObserver(debugServerPortChanged, this, false);
    90     Services.prefs.addObserver("app.crashreporter.autosubmit", this, false);
    91     Services.prefs.addObserver("metro.private_browsing.enabled", this, false);
    92     this.updatePrivateBrowsingUI();
    94     Services.obs.addObserver(this, "handle-xul-text-link", false);
    96     // listen content messages
    97     messageManager.addMessageListener("DOMTitleChanged", this);
    98     messageManager.addMessageListener("DOMWillOpenModalDialog", this);
    99     messageManager.addMessageListener("DOMWindowClose", this);
   101     messageManager.addMessageListener("Browser:OpenURI", this);
   102     messageManager.addMessageListener("Browser:SaveAs:Return", this);
   103     messageManager.addMessageListener("Content:StateChange", this);
   105     // listening escape to dismiss dialog on VK_ESCAPE
   106     window.addEventListener("keypress", this, true);
   108     window.addEventListener("MozPrecisePointer", this, true);
   109     window.addEventListener("MozImprecisePointer", this, true);
   111     window.addEventListener("AppCommand", this, true);
   113     Services.prefs.addObserver("browser.cache.disk_cache_ssl", this, false);
   115     // Init core UI modules
   116     ContextUI.init();
   117     PanelUI.init();
   118     FlyoutPanelsUI.init();
   119     PageThumbs.init();
   120     NewTabUtils.init();
   121     SettingsCharm.init();
   122     NavButtonSlider.init();
   123     SelectionHelperUI.init();
   124 #ifdef NIGHTLY_BUILD
   125     ShumwayUtils.init();
   126 #endif
   128     // We can delay some initialization until after startup.  We wait until
   129     // the first page is shown, then dispatch a UIReadyDelayed event.
   130     messageManager.addMessageListener("pageshow", function onPageShow() {
   131       if (getBrowser().currentURI.spec == "about:blank")
   132         return;
   134       messageManager.removeMessageListener("pageshow", onPageShow);
   136       setTimeout(function() {
   137         let event = document.createEvent("Events");
   138         event.initEvent("UIReadyDelayed", true, false);
   139         window.dispatchEvent(event);
   140         BrowserUI.ready = true;
   141       }, 0);
   142     });
   144     // Only load IndexedDB.js when we actually need it. A general fix will happen in bug 647079.
   145     messageManager.addMessageListener("IndexedDB:Prompt", function(aMessage) {
   146       return IndexedDB.receiveMessage(aMessage);
   147     });
   149     // hook up telemetry ping for UI data
   150     try {
   151       UITelemetry.addSimpleMeasureFunction("metro-ui",
   152                                            BrowserUI._getMeasures.bind(BrowserUI));
   153     } catch (ex) {
   154       // swallow exception that occurs if metro-appbar measure is already set up
   155       dump("Failed to addSimpleMeasureFunction in browser-ui: " + ex.message + "\n");
   156     }
   158     // Delay the panel UI and Sync initialization
   159     window.addEventListener("UIReadyDelayed", function delayedInit(aEvent) {
   160       Util.dumpLn("* delay load started...");
   161       window.removeEventListener("UIReadyDelayed",  delayedInit, false);
   163       // Login Manager and Form History initialization
   164       Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
   165       messageManager.addMessageListener("Browser:MozApplicationManifest", OfflineApps);
   167       try {
   168         MetroDownloadsView.init();
   169         DialogUI.init();
   170         FormHelperUI.init();
   171         FindHelperUI.init();
   172 #ifdef NIGHTLY_BUILD
   173         PdfJs.init();
   174 #endif
   175       } catch(ex) {
   176         Util.dumpLn("Exception in delay load module:", ex.message);
   177       }
   179       BrowserUI._initFirstRunContent();
   181       // check for left over crash reports and submit them if found.
   182       BrowserUI.startupCrashCheck();
   184       Util.dumpLn("* delay load complete.");
   185     }, false);
   187 #ifndef MOZ_OFFICIAL_BRANDING
   188     setTimeout(function() {
   189       let startup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup).getStartupInfo();
   190       for (let name in startup) {
   191         if (name != "process")
   192           Services.console.logStringMessage("[timing] " + name + ": " + (startup[name] - startup.process) + "ms");
   193       }
   194     }, 3000);
   195 #endif
   196   },
   198   uninit: function() {
   199     messageManager.removeMessageListener("DOMTitleChanged", this);
   200     messageManager.removeMessageListener("DOMWillOpenModalDialog", this);
   201     messageManager.removeMessageListener("DOMWindowClose", this);
   203     messageManager.removeMessageListener("Browser:OpenURI", this);
   204     messageManager.removeMessageListener("Browser:SaveAs:Return", this);
   205     messageManager.removeMessageListener("Content:StateChange", this);
   207     messageManager.removeMessageListener("Browser:MozApplicationManifest", OfflineApps);
   209     Services.prefs.removeObserver(debugServerStateChanged, this);
   210     Services.prefs.removeObserver(debugServerPortChanged, this);
   211     Services.prefs.removeObserver("app.crashreporter.autosubmit", this);
   212     Services.prefs.removeObserver("metro.private_browsing.enabled", this);
   214     Services.obs.removeObserver(this, "handle-xul-text-link");
   216     PanelUI.uninit();
   217     FlyoutPanelsUI.uninit();
   218     MetroDownloadsView.uninit();
   219     SettingsCharm.uninit();
   220     PageThumbs.uninit();
   221     if (WindowsPrefSync) {
   222       WindowsPrefSync.uninit();
   223     }
   224     this.stopDebugServer();
   225   },
   227   /************************************
   228    * Devtools Debugger
   229    */
   230   runDebugServer: function runDebugServer(aPort) {
   231     let port = aPort || Services.prefs.getIntPref(debugServerPortChanged);
   232     if (!DebuggerServer.initialized) {
   233       DebuggerServer.init();
   234       DebuggerServer.addBrowserActors();
   235       DebuggerServer.addActors('chrome://browser/content/dbg-metro-actors.js');
   236     }
   237     DebuggerServer.openListener(port);
   238   },
   240   stopDebugServer: function stopDebugServer() {
   241     if (DebuggerServer.initialized) {
   242       DebuggerServer.destroy();
   243     }
   244   },
   246   // If the server is not on, port changes have nothing to effect. The new value
   247   //    will be picked up if the server is started.
   248   // To be consistent with desktop fx, if the port is changed while the server
   249   //    is running, restart server.
   250   changeDebugPort:function changeDebugPort(aPort) {
   251     if (DebuggerServer.initialized) {
   252       this.stopDebugServer();
   253       this.runDebugServer(aPort);
   254     }
   255   },
   257   /*********************************
   258    * Content visibility
   259    */
   261   get isContentShowing() {
   262     return Elements.contentShowing.getAttribute("disabled") != true;
   263   },
   265   showContent: function showContent(aURI) {
   266     ContextUI.dismissTabs();
   267     ContextUI.dismissContextAppbar();
   268     FlyoutPanelsUI.hide();
   269     PanelUI.hide();
   270   },
   272   /*********************************
   273    * Crash reporting
   274    */
   276   get CrashSubmit() {
   277     delete this.CrashSubmit;
   278     Cu.import("resource://gre/modules/CrashSubmit.jsm", this);
   279     return this.CrashSubmit;
   280   },
   282   get lastCrashID() {
   283     return Cc["@mozilla.org/xre/runtime;1"].getService(Ci.nsIXULRuntime).lastRunCrashID;
   284   },
   286   startupCrashCheck: function startupCrashCheck() {
   287 #ifdef MOZ_CRASHREPORTER
   288     if (!CrashReporter.enabled) {
   289       return;
   290     }
   292     // Ensure that CrashReporter state matches pref
   293     CrashReporter.submitReports = Services.prefs.getBoolPref("app.crashreporter.autosubmit");
   295     BrowserUI.submitLastCrashReportOrShowPrompt();
   296 #endif
   297   },
   300   /*********************************
   301    * Navigation
   302    */
   304   // BrowserUI update bit flags
   305   NO_STARTUI_VISIBILITY:  1, // don't change the start ui visibility
   307   /*
   308    * Updates the overall state of startui visibility and the toolbar, but not
   309    * the URL bar.
   310    */
   311   update: function(aFlags) {
   312     let flags = aFlags || 0;
   313     if (!(flags & this.NO_STARTUI_VISIBILITY)) {
   314       let uri = this.getDisplayURI(Browser.selectedBrowser);
   315       this.updateStartURIAttributes(uri);
   316     }
   317     this._updateButtons();
   318     this._updateToolbar();
   319   },
   321   /* Updates the URL bar. */
   322   updateURI: function(aOptions) {
   323     let uri = this.getDisplayURI(Browser.selectedBrowser);
   324     let cleanURI = Util.isURLEmpty(uri) ? "" : uri;
   325     this._edit.value = cleanURI;
   326   },
   328   get isStartTabVisible() {
   329     return this.isStartURI();
   330   },
   332   isStartURI: function isStartURI(aURI) {
   333     aURI = aURI || Browser.selectedBrowser.currentURI.spec;
   334     return aURI.startsWith(kStartURI) || aURI == "about:start" || aURI == "about:home";
   335   },
   337   updateStartURIAttributes: function (aURI) {
   338     let wasStart = Elements.windowState.hasAttribute("startpage");
   339     aURI = aURI || Browser.selectedBrowser.currentURI.spec;
   340     if (this.isStartURI(aURI)) {
   341       ContextUI.displayNavbar();
   342       Elements.windowState.setAttribute("startpage", "true");
   343     } else if (aURI != "about:blank") { // about:blank is loaded briefly for new tabs; ignore it
   344       Elements.windowState.removeAttribute("startpage");
   345     }
   347     let isStart = Elements.windowState.hasAttribute("startpage");
   348     if (wasStart != isStart) {
   349       let event = document.createEvent("Events");
   350       event.initEvent("StartUIChange", true, true);
   351       Browser.selectedBrowser.dispatchEvent(event);
   352     }
   353   },
   355   getDisplayURI: function(browser) {
   356     let uri = browser.currentURI;
   357     let spec = uri.spec;
   359     try {
   360       spec = gURIFixup.createExposableURI(uri).spec;
   361     } catch (ex) {}
   363     try {
   364       let charset = browser.characterSet;
   365       let textToSubURI = Cc["@mozilla.org/intl/texttosuburi;1"].
   366                          getService(Ci.nsITextToSubURI);
   367       spec = textToSubURI.unEscapeNonAsciiURI(charset, spec);
   368     } catch (ex) {}
   370     return spec;
   371   },
   373   goToURI: function(aURI) {
   374     aURI = aURI || this._edit.value;
   375     if (!aURI)
   376       return;
   378     this._edit.value = aURI;
   380     // Make sure we're online before attempting to load
   381     Util.forceOnline();
   383     BrowserUI.showContent(aURI);
   384     Browser.selectedBrowser.focus();
   386     Task.spawn(function() {
   387       let postData = {};
   388       let webNav = Ci.nsIWebNavigation;
   389       let flags = webNav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP |
   390                   webNav.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
   391       aURI = yield Browser.getShortcutOrURI(aURI, postData);
   392       Browser.loadURI(aURI, { flags: flags, postData: postData });
   394       // Delay doing the fixup so the raw URI is passed to loadURIWithFlags
   395       // and the proper third-party fixup can be done
   396       let fixupFlags = Ci.nsIURIFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP |
   397                        Ci.nsIURIFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS;
   398       let uri = gURIFixup.createFixupURI(aURI, fixupFlags);
   399       gHistSvc.markPageAsTyped(uri);
   401       BrowserUI._titleChanged(Browser.selectedBrowser);
   402     });
   403   },
   405   doOpenSearch: function doOpenSearch(aName) {
   406     // save the current value of the urlbar
   407     let searchValue = this._edit.value;
   408     let engine = Services.search.getEngineByName(aName);
   409     let submission = engine.getSubmission(searchValue, null);
   411     this._edit.value = submission.uri.spec;
   413     // Make sure we're online before attempting to load
   414     Util.forceOnline();
   416     BrowserUI.showContent();
   417     Browser.selectedBrowser.focus();
   419     Task.spawn(function () {
   420       Browser.loadURI(submission.uri.spec, { postData: submission.postData });
   422       // loadURI may open a new tab, so get the selectedBrowser afterward.
   423       Browser.selectedBrowser.userTypedValue = submission.uri.spec;
   424       BrowserUI._titleChanged(Browser.selectedBrowser);
   425     });
   426   },
   428   /*********************************
   429    * Tab management
   430    */
   432   /**
   433    * Open a new tab in the foreground in response to a user action.
   434    * See Browser.addTab for more documentation.
   435    */
   436   addAndShowTab: function (aURI, aOwner, aParams) {
   437     ContextUI.peekTabs(kForegroundTabAnimationDelay);
   438     return Browser.addTab(aURI || kStartURI, true, aOwner, aParams);
   439   },
   441   addAndShowPrivateTab: function (aURI, aOwner) {
   442     return this.addAndShowTab(aURI, aOwner, { private: true });
   443   },
   445   get isPrivateBrowsingEnabled() {
   446     return Services.prefs.getBoolPref("metro.private_browsing.enabled");
   447   },
   449   updatePrivateBrowsingUI: function () {
   450     let command = document.getElementById("cmd_newPrivateTab");
   451     if (this.isPrivateBrowsingEnabled) {
   452       command.removeAttribute("disabled");
   453     } else {
   454       command.setAttribute("disabled", "true");
   455     }
   456   },
   458   /**
   459    * Open a new tab in response to clicking a link in an existing tab.
   460    * See Browser.addTab for more documentation.
   461    */
   462   openLinkInNewTab: function (aURI, aBringFront, aOwner) {
   463     ContextUI.peekTabs(aBringFront ? kForegroundTabAnimationDelay
   464                                    : kBackgroundTabAnimationDelay);
   465     let params = null;
   466     if (aOwner) {
   467       params = {
   468         referrerURI: aOwner.browser.documentURI,
   469         charset: aOwner.browser.characterSet,
   470       };
   471     }
   472     let tab = Browser.addTab(aURI, aBringFront, aOwner, params);
   473     Elements.tabList.strip.ensureElementIsVisible(tab.chromeTab);
   474     return tab;
   475   },
   477   setOnTabAnimationEnd: function setOnTabAnimationEnd(aCallback) {
   478     Elements.tabs.addEventListener("animationend", function onAnimationEnd() {
   479       Elements.tabs.removeEventListener("animationend", onAnimationEnd);
   480       aCallback();
   481     });
   482   },
   484   closeTab: function closeTab(aTab) {
   485     // If no tab is passed in, assume the current tab
   486     let tab = aTab || Browser.selectedTab;
   487     Browser.closeTab(tab);
   488   },
   490   animateClosingTab: function animateClosingTab(tabToClose) {
   491     tabToClose.chromeTab.setAttribute("closing", "true");
   493     let wasCollapsed = !ContextUI.tabbarVisible;
   494     if (wasCollapsed) {
   495       ContextUI.displayTabs();
   496     }
   498     this.setOnTabAnimationEnd(function() {
   499       Browser.closeTab(tabToClose, { forceClose: true } );
   500       if (wasCollapsed)
   501         ContextUI.dismissTabsWithDelay(kChangeTabAnimationDelay);
   502     });
   503   },
   505   /**
   506     * Re-open a closed tab.
   507     * @param aIndex
   508     *        The index of the tab (via nsSessionStore.getClosedTabData)
   509     * @returns a reference to the reopened tab.
   510     */
   511   undoCloseTab: function undoCloseTab(aIndex) {
   512     var tab = null;
   513     aIndex = aIndex || 0;
   514     var ss = Cc["@mozilla.org/browser/sessionstore;1"].
   515                 getService(Ci.nsISessionStore);
   516     if (ss.getClosedTabCount(window) > (aIndex)) {
   517       tab = ss.undoCloseTab(window, aIndex);
   518     }
   519     return tab;
   520   },
   522   // Useful for when we've received an event to close a particular DOM window.
   523   // Since we don't have windows, we want to close the corresponding tab.
   524   closeTabForBrowser: function closeTabForBrowser(aBrowser) {
   525     // Find the relevant tab, and close it.
   526     let browsers = Browser.browsers;
   527     for (let i = 0; i < browsers.length; i++) {
   528       if (browsers[i] == aBrowser) {
   529         Browser.closeTab(Browser.getTabAtIndex(i));
   530         return { preventDefault: true };
   531       }
   532     }
   534     return {};
   535   },
   537   selectTab: function selectTab(aTab) {
   538     Browser.selectedTab = aTab;
   539   },
   541   selectTabAndDismiss: function selectTabAndDismiss(aTab) {
   542     this.selectTab(aTab);
   543     ContextUI.dismissTabsWithDelay(kChangeTabAnimationDelay);
   544   },
   546   selectTabAtIndex: function selectTabAtIndex(aIndex) {
   547     // count backwards for aIndex < 0
   548     if (aIndex < 0)
   549       aIndex += Browser._tabs.length;
   551     if (aIndex >= 0 && aIndex < Browser._tabs.length)
   552       Browser.selectedTab = Browser._tabs[aIndex];
   553   },
   555   selectNextTab: function selectNextTab() {
   556     if (Browser._tabs.length == 1 || !Browser.selectedTab) {
   557      return;
   558     }
   560     let tabIndex = Browser._tabs.indexOf(Browser.selectedTab) + 1;
   561     if (tabIndex >= Browser._tabs.length) {
   562       tabIndex = 0;
   563     }
   565     Browser.selectedTab = Browser._tabs[tabIndex];
   566   },
   568   selectPreviousTab: function selectPreviousTab() {
   569     if (Browser._tabs.length == 1 || !Browser.selectedTab) {
   570       return;
   571     }
   573     let tabIndex = Browser._tabs.indexOf(Browser.selectedTab) - 1;
   574     if (tabIndex < 0) {
   575       tabIndex = Browser._tabs.length - 1;
   576     }
   578     Browser.selectedTab = Browser._tabs[tabIndex];
   579   },
   581   // Used for when we're about to open a modal dialog,
   582   // and want to ensure the opening tab is in front.
   583   selectTabForBrowser: function selectTabForBrowser(aBrowser) {
   584     for (let i = 0; i < Browser.tabs.length; i++) {
   585       if (Browser._tabs[i].browser == aBrowser) {
   586         Browser.selectedTab = Browser.tabs[i];
   587         break;
   588       }
   589     }
   590   },
   592   updateUIFocus: function _updateUIFocus() {
   593     if (Elements.contentShowing.getAttribute("disabled") == "true" && Browser.selectedBrowser)
   594       Browser.selectedBrowser.messageManager.sendAsyncMessage("Browser:Blur", { });
   595   },
   597   blurFocusedElement: function blurFocusedElement() {
   598     let focusedElement = document.commandDispatcher.focusedElement;
   599     if (focusedElement)
   600       focusedElement.blur();
   601   },
   603   blurNavBar: function blurNavBar() {
   604     if (this._edit.focused) {
   605       this._edit.blur();
   607       // Advanced notice to CAO, so we can shuffle the nav bar in advance
   608       // of the keyboard transition.
   609       ContentAreaObserver.navBarWillBlur();
   611       return true;
   612     }
   613     return false;
   614   },
   616   observe: function BrowserUI_observe(aSubject, aTopic, aData) {
   617     switch (aTopic) {
   618       case "handle-xul-text-link":
   619         let handled = aSubject.QueryInterface(Ci.nsISupportsPRBool);
   620         if (!handled.data) {
   621           this.addAndShowTab(aData, Browser.selectedTab);
   622           handled.data = true;
   623         }
   624         break;
   625       case "nsPref:changed":
   626         switch (aData) {
   627           case "browser.cache.disk_cache_ssl":
   628             this._sslDiskCacheEnabled = Services.prefs.getBoolPref(aData);
   629             break;
   630           case debugServerStateChanged:
   631             if (Services.prefs.getBoolPref(aData)) {
   632               this.runDebugServer();
   633             } else {
   634               this.stopDebugServer();
   635             }
   636             break;
   637           case debugServerPortChanged:
   638             this.changeDebugPort(Services.prefs.getIntPref(aData));
   639             break;
   640           case "app.crashreporter.autosubmit":
   641 #ifdef MOZ_CRASHREPORTER
   642             CrashReporter.submitReports = Services.prefs.getBoolPref(aData);
   644             // The user explicitly set the autosubmit option, so there is no
   645             // need to prompt them about crash reporting in the future
   646             Services.prefs.setBoolPref("app.crashreporter.prompted", true);
   648             BrowserUI.submitLastCrashReportOrShowPrompt;
   649 #endif
   650             break;
   651           case "metro.private_browsing.enabled":
   652             this.updatePrivateBrowsingUI();
   653             break;
   654         }
   655         break;
   656     }
   657   },
   659   submitLastCrashReportOrShowPrompt: function() {
   660 #ifdef MOZ_CRASHREPORTER
   661     let lastCrashID = this.lastCrashID;
   662     if (lastCrashID && lastCrashID.length) {
   663       if (Services.prefs.getBoolPref("app.crashreporter.autosubmit")) {
   664         Util.dumpLn("Submitting last crash id:", lastCrashID);
   665         let params = {};
   666         if (!Services.prefs.getBoolPref("app.crashreporter.submitURLs")) {
   667           params['extraExtraKeyVals'] = { URL: '' };
   668         }
   669         try {
   670           this.CrashSubmit.submit(lastCrashID, params);
   671         } catch (ex) {
   672           Util.dumpLn(ex);
   673         }
   674       } else if (!Services.prefs.getBoolPref("app.crashreporter.prompted")) {
   675         BrowserUI.addAndShowTab("about:crashprompt", null);
   676       }
   677     }
   678 #endif
   679   },
   683   /*********************************
   684    * Internal utils
   685    */
   687   _titleChanged: function(aBrowser) {
   688     let url = this.getDisplayURI(aBrowser);
   690     let tabCaption;
   691     if (aBrowser.contentTitle) {
   692       tabCaption = aBrowser.contentTitle;
   693     } else if (!Util.isURLEmpty(aBrowser.userTypedValue)) {
   694       tabCaption = aBrowser.userTypedValue;
   695     } else if (!Util.isURLEmpty(url)) {
   696       tabCaption = url;
   697     } else {
   698       tabCaption = Util.getEmptyURLTabTitle();
   699     }
   701     let tab = Browser.getTabForBrowser(aBrowser);
   702     if (tab)
   703       tab.chromeTab.updateTitle(tabCaption);
   704   },
   706   _updateButtons: function _updateButtons() {
   707     let browser = Browser.selectedBrowser;
   708     if (!browser) {
   709       return;
   710     }
   711     if (browser.canGoBack) {
   712       this._back.removeAttribute("disabled");
   713     } else {
   714       this._back.setAttribute("disabled", true);
   715     }
   716     if (browser.canGoForward) {
   717       this._forward.removeAttribute("disabled");
   718     } else {
   719       this._forward.setAttribute("disabled", true);
   720     }
   721   },
   723   _updateToolbar: function _updateToolbar() {
   724     if (Browser.selectedTab.isLoading()) {
   725       Elements.loadingState.setAttribute("loading", true);
   726     } else {
   727       Elements.loadingState.removeAttribute("loading");
   728     }
   729   },
   731   _closeOrQuit: function _closeOrQuit() {
   732     // Close active dialog, if we have one. If not then close the application.
   733     if (!BrowserUI.isContentShowing()) {
   734       BrowserUI.showContent();
   735     } else {
   736       // Check to see if we should really close the window
   737       if (Browser.closing()) {
   738         window.close();
   739         let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup);
   740         appStartup.quit(Ci.nsIAppStartup.eForceQuit);
   741       }
   742     }
   743   },
   745   _onPreciseInput: function _onPreciseInput() {
   746     document.getElementById("bcast_preciseInput").setAttribute("input", "precise");
   747     let uri = Util.makeURI("chrome://browser/content/cursor.css");
   748     if (StyleSheetSvc.sheetRegistered(uri, Ci.nsIStyleSheetService.AGENT_SHEET)) {
   749       StyleSheetSvc.unregisterSheet(uri,
   750                                     Ci.nsIStyleSheetService.AGENT_SHEET);
   751     }
   752   },
   754   _onImpreciseInput: function _onImpreciseInput() {
   755     document.getElementById("bcast_preciseInput").setAttribute("input", "imprecise");
   756     let uri = Util.makeURI("chrome://browser/content/cursor.css");
   757     if (!StyleSheetSvc.sheetRegistered(uri, Ci.nsIStyleSheetService.AGENT_SHEET)) {
   758       StyleSheetSvc.loadAndRegisterSheet(uri,
   759                                          Ci.nsIStyleSheetService.AGENT_SHEET);
   760     }
   761   },
   763   _getMeasures: function() {
   764     let dimensions = {
   765       "window-width": ContentAreaObserver.width,
   766       "window-height": ContentAreaObserver.height
   767     };
   768     return dimensions;
   769   },
   771   /*********************************
   772    * Event handling
   773    */
   775   handleEvent: function handleEvent(aEvent) {
   776     var target = aEvent.target;
   777     switch (aEvent.type) {
   778       // Window events
   779       case "keypress":
   780         if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE)
   781           this.handleEscape(aEvent);
   782         break;
   783       case "MozPrecisePointer":
   784         this._onPreciseInput();
   785         break;
   786       case "MozImprecisePointer":
   787         this._onImpreciseInput();
   788         break;
   789       case "AppCommand":
   790         this.handleAppCommandEvent(aEvent);
   791         break;
   792     }
   793   },
   795   // Checks if various different parts of the UI is visible and closes
   796   // them one at a time.
   797   handleEscape: function (aEvent) {
   798     aEvent.stopPropagation();
   799     aEvent.preventDefault();
   801     if (this._edit.popupOpen) {
   802       this._edit.endEditing(true);
   803       return;
   804     }
   806     // Check open popups
   807     if (DialogUI._popup) {
   808       DialogUI._hidePopup();
   809       return;
   810     }
   812     // Check open panel
   813     if (PanelUI.isVisible) {
   814       PanelUI.hide();
   815       return;
   816     }
   818     // Check content helper
   819     if (FindHelperUI.isActive) {
   820       FindHelperUI.hide();
   821       return;
   822     }
   824     if (Browser.selectedTab.isLoading()) {
   825       Browser.selectedBrowser.stop();
   826       return;
   827     }
   829     if (ContextUI.dismiss()) {
   830       return;
   831     }
   832   },
   834   handleBackspace: function handleBackspace() {
   835     switch (Services.prefs.getIntPref("browser.backspace_action")) {
   836       case 0:
   837         CommandUpdater.doCommand("cmd_back");
   838         break;
   839       case 1:
   840         CommandUpdater.doCommand("cmd_scrollPageUp");
   841         break;
   842     }
   843   },
   845   handleShiftBackspace: function handleShiftBackspace() {
   846     switch (Services.prefs.getIntPref("browser.backspace_action")) {
   847       case 0:
   848         CommandUpdater.doCommand("cmd_forward");
   849         break;
   850       case 1:
   851         CommandUpdater.doCommand("cmd_scrollPageDown");
   852         break;
   853     }
   854   },
   856   openFile: function() {
   857     try {
   858       const nsIFilePicker = Ci.nsIFilePicker;
   859       let fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
   860       let self = this;
   861       let fpCallback = function fpCallback_done(aResult) {
   862         if (aResult == nsIFilePicker.returnOK) {
   863           self.goToURI(fp.fileURL.spec);
   864         }
   865       };
   867       let windowTitle = Strings.browser.GetStringFromName("browserForOpenLocation");
   868       fp.init(window, windowTitle, nsIFilePicker.modeOpen);
   869       fp.appendFilters(nsIFilePicker.filterAll | nsIFilePicker.filterText |
   870                        nsIFilePicker.filterImages | nsIFilePicker.filterXML |
   871                        nsIFilePicker.filterHTML);
   872       fp.open(fpCallback);
   873     } catch (ex) {
   874       dump ('BrowserUI openFile exception: ' + ex + '\n');
   875     }
   876   },
   878   savePage: function() {
   879     Browser.savePage();
   880   },
   882   receiveMessage: function receiveMessage(aMessage) {
   883     let browser = aMessage.target;
   884     let json = aMessage.json;
   885     switch (aMessage.name) {
   886       case "DOMTitleChanged":
   887         this._titleChanged(browser);
   888         break;
   889       case "DOMWillOpenModalDialog":
   890         this.selectTabForBrowser(browser);
   891         break;
   892       case "DOMWindowClose":
   893         return this.closeTabForBrowser(browser);
   894         break;
   895       // XXX this and content's sender are a little warped
   896       case "Browser:OpenURI":
   897         let referrerURI = null;
   898         if (json.referrer)
   899           referrerURI = Services.io.newURI(json.referrer, null, null);
   900         this.goToURI(json.uri);
   901         break;
   902       case "Content:StateChange": {
   903         let tab = Browser.selectedTab;
   904         if (this.shouldCaptureThumbnails(tab)) {
   905           PageThumbs.captureAndStore(tab.browser);
   906           let currPage = tab.browser.currentURI.spec;
   907           Services.obs.notifyObservers(null, "Metro:RefreshTopsiteThumbnail", currPage);
   908         }
   909         break;
   910       }
   911     }
   913     return {};
   914   },
   916   shouldCaptureThumbnails: function shouldCaptureThumbnails(aTab) {
   917     // Capture only if it's the currently selected tab.
   918     if (aTab != Browser.selectedTab) {
   919       return false;
   920     }
   921     // Skip private tabs
   922     if (aTab.isPrivate) {
   923       return false;
   924     }
   925     // FIXME Bug 720575 - Don't capture thumbnails for SVG or XML documents as
   926     //       that currently regresses Talos SVG tests.
   927     let browser = aTab.browser;
   928     let doc = browser.contentDocument;
   929     if (doc instanceof SVGDocument || doc instanceof XMLDocument) {
   930       return false;
   931     }
   933     // Don't capture pages in snapped mode, this produces 2/3 black
   934     // thumbs or stretched out ones
   935     //   Ci.nsIWinMetroUtils.snapped is inaccessible on
   936     //   desktop/nonwindows systems
   937     if(Elements.windowState.getAttribute("viewstate") == "snapped") {
   938       return false;
   939     }
   940     // There's no point in taking screenshot of loading pages.
   941     if (browser.docShell.busyFlags != Ci.nsIDocShell.BUSY_FLAGS_NONE) {
   942       return false;
   943     }
   945     // Don't take screenshots of about: pages.
   946     if (browser.currentURI.schemeIs("about")) {
   947       return false;
   948     }
   950     // No valid document channel. We shouldn't take a screenshot.
   951     let channel = browser.docShell.currentDocumentChannel;
   952     if (!channel) {
   953       return false;
   954     }
   956     // Don't take screenshots of internally redirecting about: pages.
   957     // This includes error pages.
   958     let uri = channel.originalURI;
   959     if (uri.schemeIs("about")) {
   960       return false;
   961     }
   963     // http checks
   964     let httpChannel;
   965     try {
   966       httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
   967     } catch (e) { /* Not an HTTP channel. */ }
   969     if (httpChannel) {
   970       // Continue only if we have a 2xx status code.
   971       try {
   972         if (Math.floor(httpChannel.responseStatus / 100) != 2) {
   973           return false;
   974         }
   975       } catch (e) {
   976         // Can't get response information from the httpChannel
   977         // because mResponseHead is not available.
   978         return false;
   979       }
   981       // Cache-Control: no-store.
   982       if (httpChannel.isNoStoreResponse()) {
   983         return false;
   984       }
   986       // Don't capture HTTPS pages unless the user enabled it.
   987       if (uri.schemeIs("https") && !this.sslDiskCacheEnabled) {
   988         return false;
   989       }
   990     }
   992     return true;
   993   },
   995   _sslDiskCacheEnabled: null,
   997   get sslDiskCacheEnabled() {
   998     if (this._sslDiskCacheEnabled === null) {
   999       this._sslDiskCacheEnabled = Services.prefs.getBoolPref("browser.cache.disk_cache_ssl");
  1001     return this._sslDiskCacheEnabled;
  1002   },
  1004   supportsCommand : function(cmd) {
  1005     var isSupported = false;
  1006     switch (cmd) {
  1007       case "cmd_back":
  1008       case "cmd_forward":
  1009       case "cmd_reload":
  1010       case "cmd_forceReload":
  1011       case "cmd_stop":
  1012       case "cmd_go":
  1013       case "cmd_home":
  1014       case "cmd_openLocation":
  1015       case "cmd_addBookmark":
  1016       case "cmd_bookmarks":
  1017       case "cmd_history":
  1018       case "cmd_remoteTabs":
  1019       case "cmd_quit":
  1020       case "cmd_close":
  1021       case "cmd_newTab":
  1022       case "cmd_newTabKey":
  1023       case "cmd_closeTab":
  1024       case "cmd_undoCloseTab":
  1025       case "cmd_actions":
  1026       case "cmd_panel":
  1027       case "cmd_reportingCrashesSubmitURLs":
  1028       case "cmd_flyout_back":
  1029       case "cmd_sanitize":
  1030       case "cmd_volumeLeft":
  1031       case "cmd_volumeRight":
  1032       case "cmd_openFile":
  1033       case "cmd_savePage":
  1034         isSupported = true;
  1035         break;
  1036       default:
  1037         isSupported = false;
  1038         break;
  1040     return isSupported;
  1041   },
  1043   isCommandEnabled : function(cmd) {
  1044     let elem = document.getElementById(cmd);
  1045     if (elem && elem.getAttribute("disabled") == "true")
  1046       return false;
  1047     return true;
  1048   },
  1050   doCommand : function(cmd) {
  1051     if (!this.isCommandEnabled(cmd))
  1052       return;
  1053     let browser = getBrowser();
  1054     switch (cmd) {
  1055       case "cmd_back":
  1056         browser.goBack();
  1057         break;
  1058       case "cmd_forward":
  1059         browser.goForward();
  1060         break;
  1061       case "cmd_reload":
  1062         browser.reload();
  1063         break;
  1064       case "cmd_forceReload":
  1066         // Simulate a new page
  1067         browser.lastLocation = null;
  1069         const reloadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY |
  1070                             Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
  1071         browser.reloadWithFlags(reloadFlags);
  1072         break;
  1074       case "cmd_stop":
  1075         browser.stop();
  1076         break;
  1077       case "cmd_go":
  1078         this.goToURI();
  1079         break;
  1080       case "cmd_home":
  1081         this.goToURI(Browser.getHomePage());
  1082         break;
  1083       case "cmd_openLocation":
  1084         ContextUI.displayNavbar();
  1085         this._edit.beginEditing(true);
  1086         this._edit.select();
  1087         break;
  1088       case "cmd_addBookmark":
  1089         ContextUI.displayNavbar();
  1090         Appbar.onStarButton(true);
  1091         break;
  1092       case "cmd_bookmarks":
  1093         PanelUI.show("bookmarks-container");
  1094         break;
  1095       case "cmd_history":
  1096         PanelUI.show("history-container");
  1097         break;
  1098       case "cmd_remoteTabs":
  1099 #ifdef MOZ_SERVICES_SYNC
  1100         if (Weave.Status.checkSetup() == Weave.CLIENT_NOT_CONFIGURED) {
  1101           FlyoutPanelsUI.show('SyncFlyoutPanel');
  1102         } else {
  1103           PanelUI.show("remotetabs-container");
  1105 #endif
  1106         break;
  1107       case "cmd_quit":
  1108         // Only close one window
  1109         this._closeOrQuit();
  1110         break;
  1111       case "cmd_close":
  1112         this._closeOrQuit();
  1113         break;
  1114       case "cmd_newTab":
  1115         this.addAndShowTab();
  1116         break;
  1117       case "cmd_newTabKey":
  1118         this.addAndShowTab();
  1119         // Make sure navbar is displayed before setting focus on url bar. Bug 907244
  1120         ContextUI.displayNavbar();
  1121         this._edit.beginEditing(false);
  1122         break;
  1123       case "cmd_closeTab":
  1124         this.closeTab();
  1125         break;
  1126       case "cmd_undoCloseTab":
  1127         this.undoCloseTab();
  1128         break;
  1129       case "cmd_sanitize":
  1130         this.confirmSanitizeDialog();
  1131         break;
  1132       case "cmd_flyout_back":
  1133         FlyoutPanelsUI.onBackButton();
  1134         break;
  1135       case "cmd_reportingCrashesSubmitURLs":
  1136         let urlCheckbox = document.getElementById("prefs-reporting-submitURLs");
  1137         Services.prefs.setBoolPref('app.crashreporter.submitURLs', urlCheckbox.checked);
  1138         break;
  1139       case "cmd_panel":
  1140         PanelUI.toggle();
  1141         break;
  1142       case "cmd_openFile":
  1143         this.openFile();
  1144         break;
  1145       case "cmd_savePage":
  1146         this.savePage();
  1147         break;
  1149   },
  1151   handleAppCommandEvent: function (aEvent) {
  1152     switch (aEvent.command) {
  1153       case "Back":
  1154         this.doCommand("cmd_back");
  1155         break;
  1156       case "Forward":
  1157         this.doCommand("cmd_forward");
  1158         break;
  1159       case "Reload":
  1160         this.doCommand("cmd_reload");
  1161         break;
  1162       case "Stop":
  1163         this.doCommand("cmd_stop");
  1164         break;
  1165       case "Home":
  1166         this.doCommand("cmd_home");
  1167         break;
  1168       case "New":
  1169         this.doCommand("cmd_newTab");
  1170         break;
  1171       case "Close":
  1172         this.doCommand("cmd_closeTab");
  1173         break;
  1174       case "Find":
  1175         FindHelperUI.show();
  1176         break;
  1177       case "Open":
  1178         this.doCommand("cmd_openFile");
  1179         break;
  1180       case "Save":
  1181         this.doCommand("cmd_savePage");
  1182         break;
  1183       case "Search":
  1184         this.doCommand("cmd_openLocation");
  1185         break;
  1186       default:
  1187         return;
  1189     aEvent.stopPropagation();
  1190     aEvent.preventDefault();
  1191   },
  1193   confirmSanitizeDialog: function () {
  1194     let bundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
  1195     let title = bundle.GetStringFromName("clearPrivateData.title2");
  1196     let message = bundle.GetStringFromName("clearPrivateData.message3");
  1197     let clearbutton = bundle.GetStringFromName("clearPrivateData.clearButton");
  1199     let prefsClearButton = document.getElementById("prefs-clear-data");
  1200     prefsClearButton.disabled = true;
  1202     let buttonPressed = Services.prompt.confirmEx(
  1203                           null,
  1204                           title,
  1205                           message,
  1206                           Ci.nsIPrompt.BUTTON_POS_0 * Ci.nsIPrompt.BUTTON_TITLE_IS_STRING +
  1207                           Ci.nsIPrompt.BUTTON_POS_1 * Ci.nsIPrompt.BUTTON_TITLE_CANCEL,
  1208                           clearbutton,
  1209                           null,
  1210                           null,
  1211                           null,
  1212                           { value: false });
  1214     // Clicking 'Clear' will call onSanitize().
  1215     if (buttonPressed === 0) {
  1216       SanitizeUI.onSanitize();
  1219     prefsClearButton.disabled = false;
  1220   },
  1222   _initFirstRunContent: function () {
  1223     let dismissed = Services.prefs.getBoolPref("browser.firstrun-content.dismissed");
  1224     let firstRunCount = Services.prefs.getIntPref("browser.firstrun.count");
  1226     if (!dismissed && firstRunCount > 0) {
  1227       document.loadOverlay("chrome://browser/content/FirstRunContentOverlay.xul", null);
  1229   },
  1231   firstRunContentDismiss: function() {
  1232     let firstRunElements = Elements.stack.querySelectorAll(".firstrun-content");
  1233     for (let node of firstRunElements) {
  1234       node.parentNode.removeChild(node);
  1237     Services.prefs.setBoolPref("browser.firstrun-content.dismissed", true);
  1238   },
  1239 };
  1241 var PanelUI = {
  1242   get _panels() { return document.getElementById("panel-items"); },
  1244   get isVisible() {
  1245     return !Elements.panelUI.hidden;
  1246   },
  1248   views: {
  1249     "console-container": "ConsolePanelView",
  1250   },
  1252   init: function() {
  1253     // Perform core init soon
  1254     setTimeout(function () {
  1255       for each (let viewName in this.views) {
  1256         let view = window[viewName];
  1257         if (view.init)
  1258           view.init();
  1260     }.bind(this), 0);
  1262     // Lazily run other initialization tasks when the views are shown
  1263     this._panels.addEventListener("ToolPanelShown", function(aEvent) {
  1264       let viewName = this.views[this._panels.selectedPanel.id];
  1265       let view = window[viewName];
  1266       if (view.show)
  1267         view.show();
  1268     }.bind(this), true);
  1269   },
  1271   uninit: function() {
  1272     for each (let viewName in this.views) {
  1273       let view = window[viewName];
  1274       if (view.uninit)
  1275         view.uninit();
  1277   },
  1279   switchPane: function switchPane(aPanelId) {
  1280     BrowserUI.blurFocusedElement();
  1282     let panel = aPanelId ? document.getElementById(aPanelId) : this._panels.selectedPanel;
  1283     let oldPanel = this._panels.selectedPanel;
  1285     if (oldPanel != panel) {
  1286       this._panels.selectedPanel = panel;
  1288       this._fire("ToolPanelHidden", oldPanel);
  1291     this._fire("ToolPanelShown", panel);
  1292   },
  1294   isPaneVisible: function isPaneVisible(aPanelId) {
  1295     return this.isVisible && this._panels.selectedPanel.id == aPanelId;
  1296   },
  1298   show: function show(aPanelId) {
  1299     Elements.panelUI.hidden = false;
  1300     Elements.contentShowing.setAttribute("disabled", "true");
  1302     this.switchPane(aPanelId);
  1303   },
  1305   hide: function hide() {
  1306     if (!this.isVisible)
  1307       return;
  1309     Elements.panelUI.hidden = true;
  1310     Elements.contentShowing.removeAttribute("disabled");
  1311     BrowserUI.blurFocusedElement();
  1313     this._fire("ToolPanelHidden", this._panels);
  1314   },
  1316   toggle: function toggle() {
  1317     if (this.isVisible) {
  1318       this.hide();
  1319     } else {
  1320       this.show();
  1322   },
  1324   _fire: function _fire(aName, anElement) {
  1325     let event = document.createEvent("Events");
  1326     event.initEvent(aName, true, true);
  1327     anElement.dispatchEvent(event);
  1329 };
  1331 var DialogUI = {
  1332   _popup: null,
  1334   init: function() {
  1335     window.addEventListener("mousedown", this, true);
  1336   },
  1338   /*******************************************
  1339    * Popups
  1340    */
  1342   pushPopup: function pushPopup(aPanel, aElements, aParent) {
  1343     this._hidePopup();
  1344     this._popup =  { "panel": aPanel,
  1345                      "elements": (aElements instanceof Array) ? aElements : [aElements] };
  1346     this._dispatchPopupChanged(true, aPanel);
  1347   },
  1349   popPopup: function popPopup(aPanel) {
  1350     if (!this._popup || aPanel != this._popup.panel)
  1351       return;
  1352     this._popup = null;
  1353     this._dispatchPopupChanged(false, aPanel);
  1354   },
  1356   _hidePopup: function _hidePopup() {
  1357     if (!this._popup)
  1358       return;
  1359     let panel = this._popup.panel;
  1360     if (panel.hide)
  1361       panel.hide();
  1362   },
  1364   /*******************************************
  1365    * Events
  1366    */
  1368   handleEvent: function (aEvent) {
  1369     switch (aEvent.type) {
  1370       case "mousedown":
  1371         if (!this._isEventInsidePopup(aEvent))
  1372           this._hidePopup();
  1373         break;
  1374       default:
  1375         break;
  1377   },
  1379   _dispatchPopupChanged: function _dispatchPopupChanged(aVisible, aElement) {
  1380     let event = document.createEvent("UIEvents");
  1381     event.initUIEvent("PopupChanged", true, true, window, aVisible);
  1382     aElement.dispatchEvent(event);
  1383   },
  1385   _isEventInsidePopup: function _isEventInsidePopup(aEvent) {
  1386     if (!this._popup)
  1387       return false;
  1388     let elements = this._popup.elements;
  1389     let targetNode = aEvent.target;
  1390     while (targetNode && elements.indexOf(targetNode) == -1) {
  1391       if (targetNode instanceof Element && targetNode.hasAttribute("for"))
  1392         targetNode = document.getElementById(targetNode.getAttribute("for"));
  1393       else
  1394         targetNode = targetNode.parentNode;
  1396     return targetNode ? true : false;
  1398 };

mercurial