Bug 1272401 - Port Firefox Bug 1241085 Parts 2 and 3 and 4. r=IanN a=IanN
authorFrank-Rainer Grahl <frgrahl@gmx.net>
Sat, 23 Jul 2016 10:13:56 +0200
changeset 27313 4fcea516d2a9a5ad173fcceb769f03b2f9cd7a0c
parent 27312 22a1466a02a37edb284ef8b4f18c1daa445a221d
child 27314 19a5f2defd01d78be1d0497a1d860870af4e180e
push id1850
push userclokep@gmail.com
push dateWed, 08 Mar 2017 19:29:12 +0000
treeherdercomm-esr52@028df196b2d9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersIanN, IanN
bugs1272401, 1241085
Bug 1272401 - Port Firefox Bug 1241085 Parts 2 and 3 and 4. r=IanN a=IanN Replace userTypedClear and other location bar fixes.
suite/browser/tabbrowser.xml
suite/common/src/nsSessionStore.js
--- a/suite/browser/tabbrowser.xml
+++ b/suite/browser/tabbrowser.xml
@@ -413,16 +413,41 @@
             mFeeds: [],
             mRequest: null,
             mStateFlags: 0,
             mStatus: 0,
             mMessage: "",
             mCurProgress: 0,
             mMaxProgress: 0,
 
+            // cache flags for correct status UI update after tab switching
+            mTotalProgress: 0,
+
+            // count of open requests (should always be 0 or 1)
+            mRequestCount: 0,
+
+            _callProgressListeners: function () {
+              Array.unshift(arguments, this.mBrowser);
+              return this.mTabBrowser._callProgressListeners.apply(this.mTabBrowser, arguments);
+            },
+
+            _shouldShowProgress: function (aRequest) {
+              if (this.mBlank)
+                return false;
+
+              // Don't show progress indicators in tabs for about: URIs
+              // pointing to local resources.
+              if ((aRequest instanceof Components.interfaces.nsIChannel) &&
+                  aRequest.originalURI.schemeIs("about") &&
+                  (aRequest.URI.schemeIs("jar") || aRequest.URI.schemeIs("file")))
+                return false;
+
+              return true;
+            },
+
             onProgressChange: function (aWebProgress, aRequest,
                                         aCurSelfProgress, aMaxSelfProgress,
                                         aCurTotalProgress, aMaxTotalProgress) {
               if (aMaxTotalProgress > 0)
                 this.mTab.setAttribute("progress", Math.floor(aCurTotalProgress * 9.9 / aMaxTotalProgress));
 
               if (this.mBlank)
                 return;
@@ -447,148 +472,177 @@
             onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus) {
               if (!aRequest)
                 return;
 
               var oldBlank = this.mBlank;
 
               const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
               const nsIChannel = Components.interfaces.nsIChannel;
+              let location, originalLocation;
+              try {
+                aRequest.QueryInterface(nsIChannel)
+                location = aRequest.URI;
+                originalLocation = aRequest.originalURI;
+              } catch (ex) {}
+
+              if (aStateFlags & nsIWebProgressListener.STATE_START) {
+                this.mRequestCount++;
+              }
+              else if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
+                const NS_ERROR_UNKNOWN_HOST = 2152398878;
+                if (--this.mRequestCount > 0 && aStatus == NS_ERROR_UNKNOWN_HOST) {
+                  // to prevent bug 235825: wait for the request handled
+                  // by the automatic keyword resolver
+                  return;
+                }
+                // since we (try to) only handle STATE_STOP of the last request,
+                // the count of open requests should now be 0
+                this.mRequestCount = 0;
+              }
 
               if (aStateFlags & nsIWebProgressListener.STATE_START &&
                   aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
-                // It's okay to clear what the user typed when we start
-                // loading a document. If the user types, this flag gets
-                // set to false, if the document load ends without an
-                // onLocationChange, this flag also gets set to false
-                // (so we keep it while switching tabs after a failed load).
-                // We need to add 2 because loadURIWithFlags may have
-                // cancelled a pending load which would have cleared
-                // its anchor scroll detection temporary increment.
-                if (aWebProgress.isTopLevel)
-                  this.mBrowser.userTypedClear += 2;
-
-                if (!this.mBlank && !(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
-                  this.mTab.removeAttribute("progress");
-                  this.mTab.setAttribute("busy", "true");
-                  this.mTab.label = this.mTabBrowser.mStringBundle.getString("tabs.loading");
-                  this.mTab.removeAttribute("image");
+                if (aWebProgress.isTopLevel) {
+                  // Need to use originalLocation rather than location because things
+                  // like about:privatebrowsing arrive with nsIRequest pointing to
+                  // their resolved jar: or file: URIs.
+                  if (!(originalLocation && gInitialPages.has(originalLocation.spec) &&
+                        originalLocation != "about:blank" &&
+                        this.mBrowser.currentURI && this.mBrowser.currentURI.spec == "about:blank")) {
+                    // This will trigger clearing the location bar. Don't do it if
+                    // we loaded off a blank browser and this is an initial page load
+                    // (e.g. about:privatebrowsing, etc.) so we avoid clearing the
+                    // location bar in case the user is typing in it.
+                    // Loading about:blank shouldn't trigger this, either, because its
+                    // loads are "special".
+                    this.mBrowser.urlbarChangeTracker.startedLoad();
+                  }
+                  // If the browser is loading it must not be crashed anymore.
+                  this.mTab.removeAttribute("crashed");
+                }
+
+                if (this._shouldShowProgress(aRequest)) {
+                  if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
+                    this.mTab.setAttribute("busy", "true");
+
+                    if (aWebProgress.isTopLevel &&
+                        !(aWebProgress.loadType & Components.interfaces.nsIDocShell.LOAD_CMD_RELOAD))
+                      this.mTabBrowser.setTabTitleLoading(this.mTab);
+                  }
+
+                  if (this.mTab.selected)
+                    this.mTabBrowser.mIsBusy = true;
                 }
               }
               else if (aStateFlags & nsIWebProgressListener.STATE_STOP &&
                        aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
-                // The document is done loading, it's okay to clear
-                // the value again.
-                if (aWebProgress.isTopLevel)
-                  if (this.mBrowser.userTypedClear > 1)
-                    this.mBrowser.userTypedClear -= 2;
-                  else if (this.mBrowser.userTypedClear > 0)
-                    this.mBrowser.userTypedClear--;
-
-                if (this.mBlank)
-                  this.mBlank = false;
 
                 if (this.mTab.hasAttribute("busy")) {
                   this.mTab.removeAttribute("busy");
-                  this.mTabBrowser._tabAttrModified(this.mTab);
+                  this.mTabBrowser._tabAttrModified(this.mTab, ["busy"]);
                   if (!this.mTab.selected)
                     this.mTab.setAttribute("unread", "true");
                 }
-
-                var location = this.mBrowser.currentURI;
-                if (this.mBrowser.mIconURL) {
-                  this.mTab.setAttribute("image", this.mBrowser.mIconURL);
+                this.mTab.removeAttribute("progress");
+
+                if (aWebProgress.isTopLevel) {
+                  let isSuccessful = Components.isSuccessCode(aStatus);
+                  if (!isSuccessful && !isTabEmpty(this.mTab)) {
+                    // Restore the current document's location in case the
+                    // request was stopped (possibly from a content script)
+                    // before the location changed.
+                    this.mBrowser.userTypedValue = null;
+
+                    let inLoadURI = this.mBrowser.inLoadURI;
+                    if (this.mTab.selected && gURLBar && !inLoadURI)
+                      URLBarSetURI();
+                  } else if (isSuccessful) {
+                    this.mBrowser.urlbarChangeTracker.finishedLoad();
+                  }
+
+                  if (!this.mBrowser.mIconURL)
+                    this.mTabBrowser.useDefaultIcon(this.mTab);
                 }
-                else if (this.mBrowser.contentDocument instanceof ImageDocument &&
-                         this.mTabBrowser.mPrefs.getBoolPref("browser.chrome.site_icons")) {
-                  var req = this.mBrowser.contentDocument.imageRequest;
-                  if (req && !(req.imageStatus & Components.interfaces.imgIRequest.STATUS_ERROR)) {
-                    try {
-                      var canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
-                      var tabImg = document.getAnonymousElementByAttribute(this.mTab, "anonid", "tab-icon");
-                      var w = tabImg.boxObject.width;
-                      var h = tabImg.boxObject.height;
-                      canvas.width = w;
-                      canvas.height = h;
-                      var ctx = canvas.getContext('2d');
-                      ctx.drawImage(this.mBrowser.contentDocument.body.firstChild, 0, 0, w, h);
-                      this.mTab.setAttribute("image", canvas.toDataURL());
-                    } catch (e) { // non-canvas build, fall back to the old method
-                      var sz = this.mTabBrowser.mPrefs.getIntPref("browser.chrome.image_icons.max_size");
-                      if (req.image.width <= sz && req.image.height <= sz)
-                        this.mTab.setAttribute("image", this.mBrowser.currentURI.spec);
-                    }
-                  }
-                }
-                else if (this.mTabBrowser.shouldLoadFavIcon(location))
-                  this.mTabBrowser.loadFavIcon(location, "image", this.mTab,
-                                               this.mBrowser.contentPrincipal);
-                else
-                  this.mTab.removeAttribute("image");
+
+                if (this.mBlank)
+                  this.mBlank = false;
+
+                // For keyword URIs clear the user typed value since they will be changed into real URIs.
+                if (location.scheme == "keyword")
+                  this.mBrowser.userTypedValue = null;
 
                 if (this.mTab.label == this.mTabBrowser.mStringBundle.getString("tabs.loading"))
                   this.mTabBrowser.setTabTitle(this.mTab);
+
+                if (this.mTab.selected)
+                  this.mTabBrowser.mIsBusy = false;
               }
 
-              if (oldBlank)
-                return;
-
-              this.mRequest = aRequest;
+              if (oldBlank) {
+                this._callProgressListeners("onUpdateCurrentBrowser",
+                                            [aStateFlags, aStatus, "", 0],
+                                            true, false);
+              } else {
+                this._callProgressListeners("onStateChange",
+                                            [aWebProgress, aRequest, aStateFlags, aStatus],
+                                            true, false);
+              }
+
+              this._callProgressListeners("onStateChange",
+                                          [aWebProgress, aRequest, aStateFlags, aStatus],
+                                          false);
+
+              if (aStateFlags & (nsIWebProgressListener.STATE_START |
+                                 nsIWebProgressListener.STATE_STOP)) {
+                // reset cached temporary values at beginning and end
+                this.mMessage = "";
+                this.mTotalProgress = 0;
+              }
               this.mStateFlags = aStateFlags;
               this.mStatus = aStatus;
-              this.mMessage = "";
-
-              this.mTabBrowser._callProgressListeners(this.mBrowser, "onStateChange",
-                                                      [aWebProgress, aRequest, aStateFlags, aStatus]);
             },
 
-            // The first location change is gotoIndex called from mInstallSH,
-            // the second one is considered a user action.
-            mLocationChangeCount : 0,
-
-            onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) {
-              const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
-              if (aWebProgress.isTopLevel) {
-                if (!(aFlags & nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) {
-                  this.mTabBrowser.setIcon(this.mTab);
-                  this.mFeeds = [];
-                }
-
-                if (this.mLocationChangeCount > 0 ||
-                    aLocation.spec != "about:blank")
-                  ++this.mLocationChangeCount;
-
-                if (this.mLocationChangeCount == 2) {
-                  this.mTabBrowser.backBrowserGroup = [];
-                  this.mTabBrowser.forwardBrowserGroup = [];
-                }
-
-                // If userTypedClear > 0, the document loaded correctly and we should be
-                // clearing the user typed value. We also need to clear the typed value
+            onLocationChange: function (aWebProgress, aRequest, aLocation, aFlags) {
+              // OnLocationChange is called for both the top-level content
+              // and the subframes.
+              let topLevel = aWebProgress.isTopLevel;
+
+              if (topLevel) {
+                let isSameDocument =
+                  !!(aFlags & Components.interfaces.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT);
+                // We need to clear the typed value
                 // if the document failed to load, to make sure the urlbar reflects the
                 // failed URI (particularly for SSL errors). However, don't clear the value
                 // if the error page's URI is about:blank, because that causes complete
                 // loss of urlbar contents for invalid URI errors (see bug 867957).
-                if (this.mBrowser.userTypedClear > 0 ||
-                    (aFlags & nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE &&
-                     aLocation.spec != "about:blank"))
+                // Another reason to clear the userTypedValue is if this was an anchor
+                // navigation initiated by the user.
+                if (this.mBrowser.didStartLoadSinceLastUserTyping() ||
+                    ((aFlags & Components.interfaces.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) &&
+                     aLocation.spec != "about:blank") ||
+                    (isSameDocument && this.mBrowser.inLoadURI))
                   this.mBrowser.userTypedValue = null;
+
+                // Don't clear the favicon if this onLocationChange was
+                // triggered by a pushState or a replaceState (bug 550565) or
+                // a hash change (bug 408415).
+                if (aWebProgress.isLoadingDocument && !isSameDocument)
+                  this.mBrowser.mIconURL = null;
               }
 
-              if (this.mBlank)
-                return;
-
-              if (this.mTabBrowser.mCurrentTab == this.mTab)
-                this.mTabBrowser.updateUrlBar(aWebProgress, aRequest, aLocation,
-                                              aFlags, null, this.mBrowser,
-                                              this.mFeeds);
-
-              this.mTabBrowser._callProgressListeners(this.mBrowser, "onLocationChange",
-                                                      [aWebProgress, aRequest, aLocation, aFlags],
-                                                      false);
+              if (!this.mBlank)
+                this._callProgressListeners("onLocationChange",
+                                            [aWebProgress, aRequest, aLocation,
+                                             aFlags]);
+
+              if (topLevel) {
+                this.mBrowser.lastURI = aLocation;
+                this.mBrowser.lastLocationChange = Date.now();
+              }
             },
 
             onStatusChange: function(aWebProgress, aRequest, aStatus, aMessage) {
               if (this.mBlank)
                 return;
 
               this.mMessage = aMessage;
 
@@ -709,47 +763,47 @@
       </method>
 
       <method name="setIcon">
         <parameter name="aTab"/>
         <parameter name="aURI"/>
         <parameter name="aLoadingPrincipal"/>
         <body>
           <![CDATA[
-            var browser = aTab.linkedBrowser;
-            if (!aURI)
-              browser.mIconURL = "";
-            else {
-              if (!(aURI instanceof Components.interfaces.nsIURI))
-                aURI = this.mIOService.newURI(aURI, null, null);
-              browser.mIconURL = aURI.spec;
-            }
+            let browser = this.getBrowserForTab(aTab);
+            browser.mIconURL = aURI instanceof Components.interfaces.nsIURI ? aURI.spec : aURI;
 
             if (aURI && this.mFaviconService) {
-              var faviconFlags = this.usePrivateBrowsing ?
-                  this.mFaviconService.FAVICON_LOAD_PRIVATE :
-                  this.mFaviconService.FAVICON_LOAD_NON_PRIVATE;
-              var loadingPrincipal = aLoadingPrincipal ||
-                                     this.mSecMan.getSystemPrincipal();
-
-              this.mFaviconService
-                  .setAndFetchFaviconForPage(browser.currentURI,
-                                             aURI, false,
-                                             faviconFlags, null,
-                                             loadingPrincipal);
+              if (!(aURI instanceof Components.interfaces.nsIURI))
+                aURI = makeURI(aURI);
+
+              // We do not serialize the principal from within nsSessionStore.js,
+              // hence if aLoadingPrincipal is null we default to the
+              // systemPrincipal which will allow the favicon to load.
+              let loadingPrincipal = aLoadingPrincipal
+                ? aLoadingPrincipal
+                : Services.scriptSecurityManager.getSystemPrincipal();
+              let loadType = PrivateBrowsingUtils.isWindowPrivate(window)
+                ? this.mFaviconService.FAVICON_LOAD_PRIVATE
+                : this.mFaviconService.FAVICON_LOAD_NON_PRIVATE;
+
+              this.mFaviconService.setAndFetchFaviconForPage(
+                browser.currentURI, aURI, false, loadType, null, loadingPrincipal);
             }
 
-            if (!aTab.hasAttribute("busy") && browser.mIconURL)
-              aTab.setAttribute("image", browser.mIconURL);
-            else
-              aTab.removeAttribute("image");
-            this._tabAttrModified(aTab);
-
-            this._callProgressListeners(browser, "onLinkIconAvailable",
-                                        [browser.mIconURL]);
+            let sizedIconUrl = browser.mIconURL || "";
+            if (sizedIconUrl != aTab.getAttribute("image")) {
+              if (sizedIconUrl)
+                aTab.setAttribute("image", sizedIconUrl);
+              else
+                aTab.removeAttribute("image");
+              this._tabAttrModified(aTab, ["image"]);
+            }
+
+            this._callProgressListeners(browser, "onLinkIconAvailable", [browser.mIconURL]);
           ]]>
         </body>
       </method>
 
       <method name="getIcon">
         <parameter name="aTab"/>
         <body>
           <![CDATA[
@@ -782,17 +836,61 @@
             }
             return (aURI && this.mPrefs.getBoolPref("browser.chrome.site_icons") &&
                     this.mPrefs.getBoolPref("browser.chrome.favicons") &&
                     ("schemeIs" in aURI) && (aURI.schemeIs("http") || aURI.schemeIs("https")));
           ]]>
         </body>
       </method>
 
-      <method name="loadFavIcon">
+      <method name="useDefaultIcon">
+        <parameter name="aTab"/>
+        <body>
+          <![CDATA[
+            var browser = this.getBrowserForTab(aTab);
+            var documentURI = browser.documentURI;
+            var icon = null;
+
+            if (browser.imageDocument) {
+              if (Services.prefs.getBoolPref("browser.chrome.site_icons")) {
+                let sz = Services.prefs.getIntPref("browser.chrome.image_icons.max_size");
+                if (browser.imageDocument.width <= sz &&
+                    browser.imageDocument.height <= sz) {
+                  icon = browser.currentURI;
+                }
+              }
+            }
+
+            // Use documentURIObject in the check for shouldLoadFavIcon so that we
+            // do the right thing with about:-style error pages.  Bug 453442
+            if (!icon && this.shouldLoadFavIcon(documentURI)) {
+              let url = documentURI.prePath + "/favicon.ico";
+              if (!this.isFailedIcon(url))
+                icon = url;
+            }
+            this.setIcon(aTab, icon, browser.contentPrincipal);
+          ]]>
+        </body>
+      </method>
+
+      <method name="isFailedIcon">
+        <parameter name="aURI"/>
+        <body>
+          <![CDATA[
+            if (this.mFaviconService) {
+              if (!(aURI instanceof Components.interfaces.nsIURI))
+                aURI = makeURI(aURI);
+              return this.mFaviconService.isFailedFavicon(aURI);
+            }
+            return null;
+          ]]>
+        </body>
+      </method>
+
+     <method name="loadFavIcon">
         <parameter name="aURI"/>
         <parameter name="aAttr"/>
         <parameter name="aElt"/>
         <parameter name="aLoadingPrincipal"/>
         <body>
           <![CDATA[
             var iconURL = this.buildFavIconString(aURI);
             if (this.mFaviconService) {
@@ -802,17 +900,17 @@
                   this.mFaviconService.FAVICON_LOAD_NON_PRIVATE;
               var loadingPrincipal = aLoadingPrincipal ||
                                      this.mSecMan.getSystemPrincipal();
 
               this.mFaviconService
                   .setAndFetchFaviconForPage(aURI, iconURI, false,
                                              faviconFlags, null,
                                              loadingPrincipal);
-              if (this.mFaviconService.isFailedFavicon(uri))
+              if (this.mFaviconService.isFailedFavicon(aURI))
                 return;
             }
             aElt.setAttribute(aAttr, iconURL);
           ]]>
         </body>
       </method>
 
       <method name="addToMissedIconCache">
@@ -1205,24 +1303,42 @@
             return null;
           }
           return uri;
         ]]></body>
       </method>
 
       <method name="_tabAttrModified">
         <parameter name="aTab"/>
+        <parameter name="aChanged"/>
         <body><![CDATA[
-          // This event should be dispatched when any of these attributes change:
-          // label, crop, busy, image, selected
-          aTab.dispatchEvent(new Event("TabAttrModified",
-            { bubbles: true, cancelable: false }));
+          if (aTab.closing)
+            return;
+
+          let event = new CustomEvent("TabAttrModified", {
+            bubbles: true,
+            cancelable: false,
+            detail: {
+              changed: aChanged,
+            }
+          });
+          aTab.dispatchEvent(event);
         ]]></body>
       </method>
 
+      <method name="setTabTitleLoading">
+        <parameter name="aTab"/>
+        <body>
+          <![CDATA[
+            aTab.label = this.mStringBundle.getString("tabs.loading");
+            aTab.crop = "end";
+            this._tabAttrModified(aTab, ["label", "crop"]);
+          ]]>
+        </body>
+      </method>
       <method name="onTitleChanged">
         <parameter name="evt"/>
         <body>
           <![CDATA[
             if (evt.target != this.contentDocument)
               return;
 
             var tabBrowser = this.parentNode.parentNode.parentNode.parentNode;
@@ -2717,20 +2833,16 @@
       <property name="contentTitle"
                 onget="return this.mCurrentBrowser.contentTitle;"
                 readonly="true"/>
 
       <property name="securityUI"
                 onget="return this.mCurrentBrowser.securityUI;"
                 readonly="true"/>
 
-      <property name="userTypedClear"
-                onget="return this.mCurrentBrowser.userTypedClear;"
-                onset="return this.mCurrentBrowser.userTypedClear = val;"/>
-
       <property name="userTypedValue"
                 onget="return this.mCurrentBrowser.userTypedValue;"
                 onset="return this.mCurrentBrowser.userTypedValue = val;"/>
 
       <property name="droppedLinkHandler"
                 onget="return this.mCurrentBrowser.droppedLinkHandler;"
                 onset="return this.mCurrentBrowser.droppedLinkHandler = val;"/>
 
--- a/suite/common/src/nsSessionStore.js
+++ b/suite/common/src/nsSessionStore.js
@@ -2493,17 +2493,26 @@ SessionStoreService.prototype = {
 
       browser.stop(); // in case about:blank isn't done yet
 
       // wall-paper fix for bug 439675: make sure that the URL to be loaded
       // is always visible in the address bar
       let activeIndex = (tabData.index || tabData.entries.length) - 1;
       let activePageData = tabData.entries[activeIndex] || null;
       let uri = activePageData ? activePageData.url || null : null;
-      browser.userTypedValue = uri;
+
+      // NB: we won't set initial URIs (about:blank, about:privatebrowsing, etc.)
+      // here because their load will not normally trigger a location bar clearing
+      // when they finish loading (to avoid race conditions where we then
+      // clear user input instead), so we shouldn't set them here either.
+      // They also don't fall under the issues in bug 439675 where user input
+      // needs to be preserved if the load doesn't succeed.
+      if (!browser.userTypedValue && uri && !aWindow.gInitialPages.has(uri)) {
+        browser.userTypedValue = uri;
+      }
 
       // Also make sure currentURI is set so that switch-to-tab works before
       // the tab is restored. We'll reset this to about:blank when we try to
       // restore the tab to ensure that docshell doeesn't get confused.
       if (uri)
         browser.docShell.setCurrentURI(this._getURIFromString(uri));
 
       // If the page has a title, set it.