Bug 1241085 - part 2: rip out userTypedClear and replace it with more self-documenting stuff, r=mconley draft
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Thu, 28 Apr 2016 19:51:36 +0100
changeset 364224 a877f7c81abff9626096895c7ed02714e6575246
parent 364223 dea0a728eec67a7c78ac57ce34e83f7d554136fa
child 364225 5ad003c9cd1d61092f653eb345d9658ed120a78c
push id17388
push usergijskruitbosch@gmail.com
push dateFri, 06 May 2016 08:12:53 +0000
reviewersmconley
bugs1241085
milestone49.0a1
Bug 1241085 - part 2: rip out userTypedClear and replace it with more self-documenting stuff, r=mconley userTypedClear was used for two cases: 1) to keep track of whether we were in the middle of a loadURI call. This use is replaced by inLoadURI, which is more sane when using e10s (though it's hard to be precise there because we're sending all web navigation calls to the content process and this introduces a degree of asynchronousness that we just have to live with...). 2) to keep track of whether we were between a network start and a corresponding network stop, and whether the user typed since the load properly started. This is now tracked on a small object on the browser binding, which has appropriately named method so we're not just incrementing some magic number but actually understand what we're saying, and so the information we get out (did the user type since this load started or not?) makes sense. Note that we're keeping userTypedClear in session store information in order to remain backwards compatible. It becomes a simple boolean-stored-as-int (1 or 0) that indicates whether we quit/crashed/stopped while a load was pending, or not. MozReview-Commit-ID: 5NbmVueocC7
browser/base/content/browser.js
browser/base/content/tabbrowser.xml
browser/components/sessionstore/SessionStore.jsm
browser/components/sessionstore/TabState.jsm
browser/components/sessionstore/test/browser_522545.js
toolkit/content/widgets/browser.xml
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -811,20 +811,16 @@ function _loadURIWithFlags(browser, uri,
   }
   let flags = params.flags || 0;
   let referrer = params.referrerURI;
   let referrerPolicy = ('referrerPolicy' in params ? params.referrerPolicy :
                         Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT);
   let charset = params.charset;
   let postData = params.postData;
 
-  if (!(flags & browser.webNavigation.LOAD_FLAGS_FROM_EXTERNAL)) {
-    browser.userTypedClear++;
-  }
-
   let wasRemote = browser.isRemoteBrowser;
 
   let process = browser.isRemoteBrowser ? Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT
                                         : Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
   let mustChangeProcess = gMultiProcessBrowser &&
                           !E10SUtils.canLoadURIInProcess(uri, process);
   if ((!wasRemote && !mustChangeProcess) ||
       (wasRemote && mustChangeProcess)) {
@@ -862,19 +858,16 @@ function _loadURIWithFlags(browser, uri,
     } else {
       throw e;
     }
   } finally {
     if ((!wasRemote && !mustChangeProcess) ||
         (wasRemote && mustChangeProcess)) {
       browser.inLoadURI = false;
     }
-    if (browser.userTypedClear) {
-      browser.userTypedClear--;
-    }
   }
 }
 
 // Starts a new load in the browser first switching the browser to the correct
 // process
 function LoadInOtherProcess(browser, loadOptions, historyIndex = -1) {
   let tab = gBrowser.getTabForBrowser(browser);
   SessionStore.navigateAndRestore(tab, loadOptions, historyIndex);
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -613,16 +613,22 @@
             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
@@ -631,26 +637,18 @@
                 }
                 // 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 counter gets
-                // set to zero, if the document load ends without an
-                // onLocationChange, this counter gets decremented
-                // (so we keep it while switching tabs after failed loads)
-                // 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;
+                  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");
@@ -671,47 +669,39 @@
                   this.mTab.removeAttribute("busy");
                   this.mTabBrowser._tabAttrModified(this.mTab, ["busy"]);
                   if (!this.mTab.selected)
                     this.mTab.setAttribute("unread", "true");
                 }
                 this.mTab.removeAttribute("progress");
 
                 if (aWebProgress.isTopLevel) {
-                  if (!Components.isSuccessCode(aStatus) &&
-                      !isTabEmpty(this.mTab)) {
+                  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 {
-                    // The document is done loading, we no longer want the
-                    // value cleared.
-
-                    if (this.mBrowser.userTypedClear > 1)
-                      this.mBrowser.userTypedClear -= 2;
-                    else if (this.mBrowser.userTypedClear > 0)
-                      this.mBrowser.userTypedClear--;
+                  } else if (isSuccessful) {
+                    this.mBrowser.urlbarChangeTracker.finishedLoad();
                   }
 
                   if (!this.mBrowser.mIconURL)
                     this.mTabBrowser.useDefaultIcon(this.mTab);
                 }
 
                 if (this.mBlank)
                   this.mBlank = false;
 
-                var location = aRequest.QueryInterface(nsIChannel).URI;
-
                 // 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.connecting"))
                   this.mTabBrowser.setTabTitle(this.mTab);
 
                 if (this.mTab.selected)
@@ -746,28 +736,27 @@
                                         aFlags) {
               // OnLocationChange is called for both the top-level content
               // and the subframes.
               let topLevel = aWebProgress.isTopLevel;
 
               if (topLevel) {
                 let isSameDocument =
                   !!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT);
-                // If userTypedClear > 0, the document loaded correctly and we should be
-                // clearing the user typed value. We also need to clear the typed value
+                // 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).
                 // Another reason to clear the userTypedValue is if this was an anchor
                 // navigation initiated by the user.
-                if (this.mBrowser.userTypedClear > 0 ||
+                if (this.mBrowser.didStartLoadSinceLastUserTyping() ||
                     ((aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) &&
                      aLocation.spec != "about:blank") ||
-                     (isSameDocument && this.mBrowser.inLoadURI)) {
+                    (isSameDocument && this.mBrowser.inLoadURI)) {
                   this.mBrowser.userTypedValue = null;
                 }
 
                 // If the browser was playing audio, we should remove the playing state.
                 if (this.mTab.hasAttribute("soundplaying") && !isSameDocument) {
                   this.mTab.removeAttribute("soundplaying");
                   this.mTabBrowser._tabAttrModified(this.mTab, ["soundplaying"]);
                 }
@@ -4152,20 +4141,16 @@
                   offset *= -1;
                 this.tabContainer.advanceSelectedTab(offset, true);
                 aEvent.preventDefault();
             }
           }
         ]]></body>
       </method>
 
-      <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;"/>
 
       <method name="createTooltip">
         <parameter name="event"/>
         <body><![CDATA[
           event.stopPropagation();
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -816,17 +816,17 @@ var SessionStoreInternal = {
           // If the user was typing into the URL bar when we crashed, but hadn't hit
           // enter yet, then we just need to write that value to the URL bar without
           // loading anything. This must happen after the load, as the load will clear
           // userTypedValue.
           let tabData = TabState.collect(tab);
           if (tabData.userTypedValue && !tabData.userTypedClear) {
             browser.userTypedValue = tabData.userTypedValue;
             if (data.didStartLoad) {
-              browser.userTypedClear++;
+              browser.urlbarChangeTracker.startedLoad();
             }
             win.URLBarSetURI();
           }
 
           // Remove state we don't need any longer.
           TabStateCache.update(browser, {
             userTypedValue: null, userTypedClear: null
           });
--- a/browser/components/sessionstore/TabState.jsm
+++ b/browser/components/sessionstore/TabState.jsm
@@ -124,22 +124,27 @@ var TabStateInternal = {
 
     // Store the tab icon.
     if (!("image" in tabData)) {
       let tabbrowser = tab.ownerDocument.defaultView.gBrowser;
       tabData.image = tabbrowser.getIcon(tab);
     }
 
     // If there is a userTypedValue set, then either the user has typed something
-    // in the URL bar, or a new tab was opened with a URI to load. userTypedClear
-    // is used to indicate whether the tab was in some sort of loading state with
-    // userTypedValue.
+    // in the URL bar, or a new tab was opened with a URI to load.
+    // If so, we also track whether we were still in the process of loading something.
     if (!("userTypedValue" in tabData) && browser.userTypedValue) {
       tabData.userTypedValue = browser.userTypedValue;
-      tabData.userTypedClear = browser.userTypedClear;
+      // We always used to keep track of the loading state as an integer, where
+      // '0' indicated the user had typed since the last load (or no load was
+      // ongoing), and any positive value indicated we had started a load since
+      // the last time the user typed in the URL bar. Mimic this to keep the
+      // session store representation in sync, even though we now represent this
+      // more explicitly:
+      tabData.userTypedClear = browser.didStartLoadSinceLastUserTyping() ? 1 : 0;
     }
 
     return tabData;
   },
 
   /**
    * Copy data for the given |browser| from the cache to |tabData|.
    *
--- a/browser/components/sessionstore/test/browser_522545.js
+++ b/browser/components/sessionstore/test/browser_522545.js
@@ -23,18 +23,18 @@ function test() {
     };
 
     waitForBrowserState(state, function() {
       let browser = gBrowser.selectedBrowser;
       is(browser.currentURI.spec, "about:blank",
          "No history entries still sets currentURI to about:blank");
       is(browser.userTypedValue, "example.com",
          "userTypedValue was correctly restored");
-      is(browser.userTypedClear, 0,
-         "userTypeClear restored as expected");
+      ok(!browser.didStartLoadSinceLastUserTyping(),
+         "We still know that no load is ongoing");
       is(gURLBar.value, "example.com",
          "Address bar's value correctly restored");
       // Change tabs to make sure address bar value gets updated
       gBrowser.selectedTab = gBrowser.tabContainer.getItemAtIndex(0);
       is(gURLBar.value, "about:mozilla",
          "Address bar's value correctly updated");
       runNextTest();
     });
@@ -55,18 +55,18 @@ function test() {
     };
 
     waitForBrowserState(state, function() {
       let browser = gBrowser.getBrowserAtIndex(1);
       is(browser.currentURI.spec, "about:blank",
          "No history entries still sets currentURI to about:blank");
       is(browser.userTypedValue, "example.org",
          "userTypedValue was correctly restored");
-      is(browser.userTypedClear, 0,
-         "userTypeClear restored as expected");
+      ok(!browser.didStartLoadSinceLastUserTyping(),
+         "We still know that no load is ongoing");
       is(gURLBar.value, "about:mozilla",
          "Address bar's value correctly restored");
       // Change tabs to make sure address bar value gets updated
       gBrowser.selectedTab = gBrowser.tabContainer.getItemAtIndex(1);
       is(gURLBar.value, "example.org",
          "Address bar's value correctly updated");
       runNextTest();
     });
@@ -88,18 +88,18 @@ function test() {
     };
 
     waitForBrowserState(state, function() {
       let browser = gBrowser.selectedBrowser;
       is(browser.currentURI.spec, "about:config",
          "browser.currentURI set to current entry in SH");
       is(browser.userTypedValue, "example.com",
          "userTypedValue was correctly restored");
-      is(browser.userTypedClear, 0,
-         "userTypeClear restored as expected");
+      ok(!browser.didStartLoadSinceLastUserTyping(),
+         "We still know that no load is ongoing");
       is(gURLBar.value, "example.com",
          "Address bar's value correctly restored to userTypedValue");
       runNextTest();
     });
   }
 
   // This tests the following use case:
   // User is in a tab with session history, presses back at some point, then
@@ -117,18 +117,18 @@ function test() {
     };
 
     waitForBrowserState(state, function() {
       let browser = gBrowser.selectedBrowser;
       is(browser.currentURI.spec, "about:mozilla",
          "browser.currentURI set to current entry in SH");
       is(browser.userTypedValue, "example.org",
          "userTypedValue was correctly restored");
-      is(browser.userTypedClear, 0,
-         "userTypeClear restored as expected");
+      ok(!browser.didStartLoadSinceLastUserTyping(),
+         "We still know that no load is ongoing");
       is(gURLBar.value, "example.org",
          "Address bar's value correctly restored to userTypedValue");
       runNextTest();
     });
   }
 
   // This test simulates lots of tabs opening at once and then quitting/crashing.
   function test_getBrowserState_lotsOfTabsOpening() {
@@ -181,28 +181,29 @@ function test() {
         tabs: [{ entries: [] }]
       }]
     };
 
     waitForBrowserState(state, function() {
       let browser = gBrowser.selectedBrowser;
       // Make sure this tab isn't loading and state is clear before we test.
       is(browser.userTypedValue, null, "userTypedValue is empty to start");
-      is(browser.userTypedClear, 0, "userTypedClear is 0 to start");
+      ok(!browser.didStartLoadSinceLastUserTyping(),
+         "Initially, no load should be ongoing");
 
       let inputText = "example.org";
       gURLBar.focus();
       gURLBar.value = inputText.slice(0, -1);
       EventUtils.synthesizeKey(inputText.slice(-1) , {});
 
       executeSoon(function () {
         is(browser.userTypedValue, "example.org",
            "userTypedValue was set when changing URLBar value");
-        is(browser.userTypedClear, 0,
-           "userTypedClear was not changed when changing URLBar value");
+        ok(!browser.didStartLoadSinceLastUserTyping(),
+           "No load started since changing URLBar value");
 
         // Now make sure ss gets these values too
         let newState = JSON.parse(ss.getBrowserState());
         is(newState.windows[0].tabs[0].userTypedValue, "example.org",
            "sessionstore got correct userTypedValue");
         is(newState.windows[0].tabs[0].userTypedClear, 0,
            "sessionstore got correct userTypedClear");
         runNextTest();
@@ -223,18 +224,18 @@ function test() {
     };
 
     waitForBrowserState(state, function() {
       let browser = gBrowser.selectedBrowser;
       is(browser.currentURI.spec, "http://example.com/",
          "userTypedClear=2 caused userTypedValue to be loaded");
       is(browser.userTypedValue, null,
          "userTypedValue was null after loading a URI");
-      is(browser.userTypedClear, 0,
-         "userTypeClear reset to 0");
+      ok(!browser.didStartLoadSinceLastUserTyping(),
+         "We should have reset the load state when the tab loaded");
       is(gURLBar.textValue, gURLBar.trimValue("http://example.com/"),
          "Address bar's value set after loading URI");
       runNextTest();
     });
   }
 
 
   let tests = [test_newTabFocused, test_newTabNotFocused,
--- a/toolkit/content/widgets/browser.xml
+++ b/toolkit/content/widgets/browser.xml
@@ -57,42 +57,28 @@
         </body>
       </method>
 
 
       <method name="goBack">
         <body>
           <![CDATA[
             var webNavigation = this.webNavigation;
-            if (webNavigation.canGoBack) {
-              try {
-                this.userTypedClear++;
-                this._wrapURIChangeCall(() => webNavigation.goBack());
-              } finally {
-                if (this.userTypedClear)
-                  this.userTypedClear--;
-              }
-            }
+            if (webNavigation.canGoBack)
+              this._wrapURIChangeCall(() => webNavigation.goBack());
           ]]>
         </body>
       </method>
 
       <method name="goForward">
         <body>
           <![CDATA[
             var webNavigation = this.webNavigation;
-            if (webNavigation.canGoForward) {
-              try {
-                this.userTypedClear++;
-                this._wrapURIChangeCall(() => webNavigation.goForward());
-              } finally {
-                if (this.userTypedClear)
-                  this.userTypedClear--;
-              }
-            }
+            if (webNavigation.canGoForward)
+              this._wrapURIChangeCall(() => webNavigation.goForward());
           ]]>
         </body>
       </method>
 
       <method name="reload">
         <body>
           <![CDATA[
             const nsIWebNavigation = Components.interfaces.nsIWebNavigation;
@@ -157,28 +143,20 @@
               aReferrerURI = params.referrerURI;
               if ('referrerPolicy' in params) {
                 aReferrerPolicy = params.referrerPolicy;
               }
               aCharset = params.charset;
               aPostData = params.postData;
             }
 
-            if (!(aFlags & this.webNavigation.LOAD_FLAGS_FROM_EXTERNAL))
-              this.userTypedClear++;
-
-            try {
             this._wrapURIChangeCall(() =>
               this.webNavigation.loadURIWithOptions(
                   aURI, aFlags, aReferrerURI, aReferrerPolicy,
                   aPostData, null, null));
-            } finally {
-              if (this.userTypedClear)
-                this.userTypedClear--;
-            }
           ]]>
         </body>
       </method>
 
       <method name="goHome">
         <body>
           <![CDATA[
             try {
@@ -210,23 +188,17 @@
           ]]>
         </setter>
       </property>
 
       <method name="gotoIndex">
         <parameter name="aIndex"/>
         <body>
           <![CDATA[
-            try {
-              this.userTypedClear++;
-              this._wrapURIChangeCall(() => this.webNavigation.gotoIndex(aIndex));
-            } finally {
-              if (this.userTypedClear)
-                this.userTypedClear--;
-            }
+            this._wrapURIChangeCall(() => this.webNavigation.gotoIndex(aIndex));
           ]]>
         </body>
       </method>
 
       <property name="currentURI" readonly="true">
        <getter><![CDATA[
           if (this.webNavigation) {
             return this.webNavigation.currentURI;
@@ -833,67 +805,47 @@
         <parameter name="priority"/>
         <body><![CDATA[
           let loadGroup = this.webNavigation.QueryInterface(Components.interfaces.nsIDocumentLoader)
                               .loadGroup.QueryInterface(Components.interfaces.nsISupportsPriority);
           loadGroup.priority = priority;
         ]]></body>
       </method>
 
-      <!--
-        This field tracks the location bar state. The value that the user typed
-        in to the location bar may not be changed while this field is zero.
-        However invoking a load will temporarily increase this field to allow
-        the location bar to be updated to the new URL.
-
-        Case 1: Anchor scroll
-          The user appends the anchor to the URL. This sets the location bar
-          into typed state, and disables changes to the location bar. The user
-          then requests the scroll. loadURIWithFlags temporarily increases the
-          flag by 1 so that the anchor scroll's location change resets the
-          location bar state.
-
-        Case 2: Interrupted load
-          The user types in and submits the URL. This triggers an asynchronous
-          network load which increases the flag by 2. (The temporary increase
-          from loadURIWithFlags is not noticeable in this case.) When the load
-          is interrupted the flag returns to zero, and the location bar stays
-          in typed state.
+      <field name="urlbarChangeTracker">
+        ({
+          _startedLoadSinceLastUserTyping: false,
 
-        Case 3: New load
-          This works like case 2, but as the load is not interrupted the
-          location changes while the flag is still 2 thus resetting the
-          location bar state.
+          startedLoad() {
+            this._startedLoadSinceLastUserTyping = true;
+          },
+          finishedLoad() {
+            this._startedLoadSinceLastUserTyping = false;
+          },
+          userTyped() {
+            this._startedLoadSinceLastUserTyping = false;
+          },
+        })
+      </field>
 
-        Case 4: Corrected load
-          This is a combination of case 2 and case 3, except that the original
-          load is interrupted by the new load. Normally cancelling and starting
-          a new load would reset the flag to 0 and then increase it to 2 again.
-          However both actions occur as a consequence of the loadURIWithFlags
-          invocation, which adds its temporary increase in to the mix. Since
-          the new URL would have been typed in the flag would have been reset
-          before loadURIWithFlags incremented it. The interruption resets the
-          flag to 0 and increases it to 2. Although loadURIWithFlags will
-          decrement the flag it remains at 1 thus allowing the location bar
-          state to be reset when the new load changes the location.
-          This case also applies when loading into a new browser, as this
-          interrupts the default load of about:blank.
-      -->
-      <field name="userTypedClear">
-        1
-      </field>
+      <method name="didStartLoadSinceLastUserTyping">
+        <body><![CDATA[
+          return !this.inLoadURI &&
+                 this.urlbarChangeTracker._startedLoadSinceLastUserTyping;
+        ]]></body>
+      </method>
 
       <field name="_userTypedValue">
         null
       </field>
 
       <property name="userTypedValue"
                 onget="return this._userTypedValue;">
         <setter><![CDATA[
-          this.userTypedClear = 0;
+          this.urlbarChangeTracker.userTyped();
           this._userTypedValue = val;
           return val;
         ]]></setter>
       </property>
 
       <field name="mFormFillAttached">
         false
       </field>