Bug 591905: Update back/forward buttons correctly after inner-frame navigations. r=Unfocused, a=blocks-betaN
authorDave Townsend <dtownsend@oxymoronical.com>
Wed, 09 Feb 2011 10:25:41 -0800
changeset 62229 e2c895fafa2718d66138ad7b060867fbf569d67b
parent 62228 c99cefaa4c8e0cff713fd9ef3a30c5b11fc2eee3
child 62230 d37fedadd4ec2028dd0fa933c19e74e2a4b88e0f
push id18653
push userdtownsend@mozilla.com
push dateWed, 09 Feb 2011 19:12:39 +0000
treeherdermozilla-central@d37fedadd4ec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersUnfocused, blocks-betaN
bugs591905
milestone2.0b12pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 591905: Update back/forward buttons correctly after inner-frame navigations. r=Unfocused, a=blocks-betaN
toolkit/mozapps/extensions/content/extensions.js
toolkit/mozapps/extensions/test/browser/browser_bug562797.js
toolkit/mozapps/extensions/test/browser/browser_discovery.js
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -500,17 +500,17 @@ var gViewController = {
     }
 
     window.controllers.removeController(this);
   },
 
   statePopped: function(e) {
     // If this is a navigation to a previous state then load that state
     if (e.state) {
-      this.loadViewInternal(e.state.view, e.state.previousView);
+      this.loadViewInternal(e.state.view, e.state.previousView, e.state);
       return;
     }
 
     // If the initial view has already been selected (by a call to loadView) then
     // bail out now
     if (this.initialViewSelected)
       return;
 
@@ -537,48 +537,51 @@ var gViewController = {
   get isLoading() {
     return !this.currentViewObj || this.currentViewObj.node.hasAttribute("loading");
   },
 
   loadView: function(aViewId) {
     if (aViewId == this.currentViewId)
       return;
 
-    gHistory.pushState({
+    var state = {
       view: aViewId,
       previousView: this.currentViewId
-    }, document.title);
-    this.loadViewInternal(aViewId, this.currentViewId);
+    };
+    gHistory.pushState(state);
+    this.loadViewInternal(aViewId, this.currentViewId, state);
   },
 
   // Replaces the existing view with a new one, rewriting the current history
   // entry to match.
   replaceView: function(aViewId) {
     if (aViewId == this.currentViewId)
       return;
 
-    gHistory.replaceState({
+    var state = {
       view: aViewId,
       previousView: null
-    }, document.title);
-    this.loadViewInternal(aViewId, null);
+    };
+    gHistory.replaceState(state);
+    this.loadViewInternal(aViewId, null, state);
   },
 
   loadInitialView: function(aViewId) {
-    gHistory.replaceState({
+    var state = {
       view: aViewId,
       previousView: null
-    }, document.title);
-
-    this.loadViewInternal(aViewId, null);
+    };
+    gHistory.replaceState(state);
+
+    this.loadViewInternal(aViewId, null, state);
     this.initialViewSelected = true;
     notifyInitialized();
   },
 
-  loadViewInternal: function(aViewId, aPreviousView) {
+  loadViewInternal: function(aViewId, aPreviousView, aState) {
     var view = this.parseViewId(aViewId);
 
     if (!view.type || !(view.type in this.viewObjects))
       throw new Error("Invalid view: " + view.type);
 
     var viewObj = this.viewObjects[view.type];
     if (!viewObj.node)
       throw new Error("Root node doesn't exist for '" + view.type + "' view");
@@ -597,17 +600,17 @@ var gViewController = {
 
     gCategories.select(aViewId, aPreviousView);
 
     this.currentViewId = aViewId;
     this.currentViewObj = viewObj;
 
     this.viewPort.selectedPanel = this.currentViewObj.node;
     this.viewPort.selectedPanel.setAttribute("loading", "true");
-    this.currentViewObj.show(view.param, ++this.currentViewRequest);
+    this.currentViewObj.show(view.param, ++this.currentViewRequest, aState);
   },
 
   // Moves back in the document history and removes the current history entry
   popState: function(aCallback) {
     this.viewChangeCallback = aCallback;
     gHistory.popState();
   },
 
@@ -1635,17 +1638,17 @@ var gDiscoverView = {
         return;
       }
 
       self._browser.homePage = self.homepageURL.spec;
       self._browser.addProgressListener(self, Ci.nsIWebProgress.NOTIFY_ALL |
                                               Ci.nsIWebProgress.NOTIFY_STATE_ALL);
 
       if (self.loaded)
-        self._loadBrowser(notifyInitialized);
+        self._loadURL(self.homepageURL.spec, notifyInitialized);
       else
         notifyInitialized();
     }
 
     if (Services.prefs.getBoolPref(PREF_BACKGROUND_UPDATE) == false) {
       setURL(url);
       return;
     }
@@ -1668,60 +1671,84 @@ var gDiscoverView = {
           isBlocklisted: aAddon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED
         }
       });
 
       setURL(url + "#" + JSON.stringify(list));
     });
   },
 
-  show: function() {
+  show: function(aParam, aRequest, aState) {
+    gViewController.updateCommands();
+
+    // If we're being told to load a specific URL then just do that
+    if (aState && "url" in aState) {
+      this.loaded = true;
+      this._loadURL(aState.url);
+    }
+
     // If the view has loaded before and the error page is not visible then
     // there is nothing else to do
     if (this.loaded && this.node.selectedPanel != this._error) {
-      gViewController.updateCommands();
       gViewController.notifyViewChanged();
       return;
     }
 
     this.loaded = true;
 
     // No homepage means initialization isn't complete, the browser will get
     // loaded once initialization is complete
     if (!this.homepageURL) {
       this._loadListeners.push(gViewController.notifyViewChanged.bind(gViewController));
       return;
     }
 
-    this._loadBrowser(gViewController.notifyViewChanged.bind(gViewController));
+    this._loadURL(this.homepageURL.spec,
+                  gViewController.notifyViewChanged.bind(gViewController));
   },
 
   hide: function() { },
 
   showError: function() {
     this.node.selectedPanel = this._error;
   },
 
-  _loadBrowser: function(aCallback) {
-    this.node.selectedPanel = this._loading;
-
+  _loadURL: function(aURL, aCallback) {
     if (aCallback)
       this._loadListeners.push(aCallback);
 
-    if (this._browser.currentURI.equals(this.homepageURL))
-      this._browser.reload();
-    else
-      this._browser.goHome();
+    this._browser.loadURIWithFlags(aURL,
+                                   Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY);
   },
 
   onLocationChange: function(aWebProgress, aRequest, aLocation) {
     // Ignore the about:blank load
     if (aLocation.spec == "about:blank")
       return;
 
+    // When using the real session history the inner-frame will update the
+    // session history automatically, if using the fake history though it must
+    // be manually updated
+    if (gHistory == FakeHistory) {
+      var docshell = aWebProgress.QueryInterface(Ci.nsIDocShell);
+
+      var state = {
+        view: "addons://discover/",
+        url: aLocation.spec
+      };
+
+      var replaceHistory = Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY << 16;
+      if (docshell.loadType & replaceHistory)
+        gHistory.replaceState(state);
+      else
+        gHistory.pushState(state);
+    }
+
+    gViewController.updateCommands();
+
     // If the hostname is the same as the new location's host and either the
     // default scheme is insecure or the new location is secure then continue
     // with the load
     if (aLocation.host == this.homepageURL.host &&
         (!this.homepageURL.schemeIs("https") || aLocation.schemeIs("https")))
       return;
 
     // Canceling the request will send an error to onStateChange which will show
@@ -1739,36 +1766,39 @@ var gDiscoverView = {
       return;
 
     // Canceling the request will send an error to onStateChange which will show
     // the error page
     aRequest.cancel(Components.results.NS_BINDING_ABORTED);
   },
 
   onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {
-    // Only care about the network stop status events
-    if (!(aStateFlags & (Ci.nsIWebProgressListener.STATE_IS_NETWORK)) ||
-        !(aStateFlags & (Ci.nsIWebProgressListener.STATE_STOP)))
+    // Only care about the network events
+    if (!(aStateFlags & (Ci.nsIWebProgressListener.STATE_IS_NETWORK)))
       return;
 
-    // Sometimes we stop getting onLocationChange events so we must redo the
-    // url tests here (bug 602256)
+    // If this is the start of network activity then show the loading page
+    if (aStateFlags & (Ci.nsIWebProgressListener.STATE_START))
+      this.node.selectedPanel = this._loading;
+
+    // Ignore anything except stop events
+    if (!(aStateFlags & (Ci.nsIWebProgressListener.STATE_STOP)))
+      return;
+
     var location = this._browser.currentURI;
 
     // Consider the successful load of about:blank as still loading
     if (Components.isSuccessCode(aStatus) && location && location.spec == "about:blank")
       return;
 
     // If there was an error loading the page or the new hostname is not the
     // same as the default hostname or the default scheme is secure and the new
     // scheme is insecure then show the error page
     if (!Components.isSuccessCode(aStatus) ||
-        (aRequest && aRequest instanceof Ci.nsIHttpChannel && !aRequest.requestSucceeded) ||
-        location.host != this.homepageURL.host ||
-        (this.homepageURL.schemeIs("https") && !location.schemeIs("https"))) {
+        (aRequest && aRequest instanceof Ci.nsIHttpChannel && !aRequest.requestSucceeded)) {
       this.showError();
     } else {
       // Got a successful load, make sure the browser is visible
       this.node.selectedPanel = this._browser;
       gViewController.updateCommands();
     }
 
     var listeners = this._loadListeners;
--- a/toolkit/mozapps/extensions/test/browser/browser_bug562797.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_bug562797.js
@@ -1,21 +1,72 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 /**
  * Tests that history navigation works for the add-ons manager.
  */
 
+const PREF_DISCOVERURL = "extensions.webservice.discoverURL";
+const MAIN_URL = "https://example.com/" + RELATIVE_DIR + "discovery.html";
+const SECOND_URL = "https://example.com/" + RELATIVE_DIR + "releaseNotes.xhtml";
+
+var gLoadCompleteCallback = null;
+
+var gProgressListener = {
+  onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {
+    // Only care about the network stop status events
+    if (!(aStateFlags & (Ci.nsIWebProgressListener.STATE_IS_NETWORK)) ||
+        !(aStateFlags & (Ci.nsIWebProgressListener.STATE_STOP)))
+      return;
+
+    if (gLoadCompleteCallback)
+      executeSoon(gLoadCompleteCallback);
+    gLoadCompleteCallback = null;
+  },
+
+  onLocationChange: function() { },
+  onSecurityChange: function() { },
+  onProgressChange: function() { },
+  onStatusChange: function() { },
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+                                         Ci.nsISupportsWeakReference]),
+};
+
+function waitForLoad(aManager, aCallback) {
+  var browser = aManager.document.getElementById("discover-browser");
+  browser.addProgressListener(gProgressListener);
+
+  gLoadCompleteCallback = function() {
+    browser.removeProgressListener(gProgressListener);
+    aCallback();
+  };
+}
+
+function clickLink(aManager, aId, aCallback) {
+  waitForLoad(aManager, aCallback);
+
+  var browser = aManager.document.getElementById("discover-browser");
+
+  var link = browser.contentDocument.getElementById(aId);
+  EventUtils.sendMouseEvent({type: "click"}, link);
+}
+
 function test() {
   requestLongerTimeout(2);
 
   waitForExplicitFinish();
 
+  Services.prefs.setCharPref(PREF_DISCOVERURL, MAIN_URL);
+  registerCleanupFunction(function() {
+    Services.prefs.clearUserPref(PREF_DISCOVERURL);
+  });
+
   var gProvider = new MockProvider();
   gProvider.createAddons([{
     id: "test1@tests.mozilla.org",
     name: "Test add-on 1",
     description: "foo"
   },
   {
     id: "test2@tests.mozilla.org",
@@ -91,16 +142,32 @@ function is_in_detail(aManager, view, ca
   var doc = aManager.document;
 
   is(doc.getElementById("categories").selectedItem.value, view, "Should be on the right category");
   is(doc.getElementById("view-port").selectedPanel.id, "detail-view", "Should be on the right view");
 
   check_state(aManager, canGoBack, canGoForward);
 }
 
+function is_in_discovery(aManager, url, canGoBack, canGoForward) {
+  var browser = aManager.document.getElementById("discover-browser");
+
+  is(aManager.document.getElementById("discover-view").selectedPanel, browser,
+     "Browser should be visible");
+
+  var spec = browser.currentURI.spec;
+  var pos = spec.indexOf("#");
+  if (pos != -1)
+    spec = spec.substring(0, pos);
+
+  is(spec, url, "Should have loaded the right url");
+
+  check_state(aManager, canGoBack, canGoForward);
+}
+
 function double_click_addon_element(aManager, aId) {
   var addon = get_addon_element(aManager, aId);
   addon.parentNode.ensureElementIsVisible(addon);
   EventUtils.synthesizeMouseAtCenter(addon, { clickCount: 1 }, aManager);
   EventUtils.synthesizeMouseAtCenter(addon, { clickCount: 2 }, aManager);
 }
 
 // Tests simple forward and back navigation and that the right heading and
@@ -615,25 +682,138 @@ add_test(function() {
 // Tests that opening the manager opens the last view
 add_test(function() {
   open_manager("addons://list/plugin", function(aManager) {
     info("Part 1");
     is_in_list(aManager, "addons://list/plugin", false, false);
 
     close_manager(aManager, function() {
       open_manager(null, function(aManager) {
-        info("Part 1");
+        info("Part 2");
         is_in_list(aManager, "addons://list/plugin", false, false);
 
         close_manager(aManager, run_next_test);
       });
     });
   });
 });
 
+// Tests that navigating the discovery page works when that was the first view
+add_test(function() {
+  open_manager("addons://discover/", function(aManager) {
+    info("1");
+    is_in_discovery(aManager, MAIN_URL, false, false);
+
+    clickLink(aManager, "link-good", function() {
+      info("2");
+      is_in_discovery(aManager, SECOND_URL, true, false);
+
+      waitForLoad(aManager, function() {
+        info("3");
+        is_in_discovery(aManager, MAIN_URL, false, true);
+
+        waitForLoad(aManager, function() {
+          is_in_discovery(aManager, SECOND_URL, true, false);
+
+          EventUtils.synthesizeMouseAtCenter(aManager.document.getElementById("category-plugins"), { }, aManager);
+
+          wait_for_view_load(aManager, function(aManager) {
+            is_in_list(aManager, "addons://list/plugin", true, false);
+
+            go_back(aManager);
+
+            wait_for_view_load(aManager, function(aManager) {
+              is_in_discovery(aManager, SECOND_URL, true, true);
+
+              go_back(aManager);
+
+              waitForLoad(aManager, function() {
+                is_in_discovery(aManager, MAIN_URL, false, true);
+
+                close_manager(aManager, run_next_test);
+              });
+            });
+          });
+        });
+
+        go_forward(aManager);
+      });
+
+      go_back(aManager);
+    });
+  });
+});
+
+// Tests that navigating the discovery page works when that was the second view
+add_test(function() {
+  open_manager("addons://list/plugin", function(aManager) {
+    is_in_list(aManager, "addons://list/plugin", false, false);
+
+    EventUtils.synthesizeMouseAtCenter(aManager.document.getElementById("category-discover"), { }, aManager);
+
+    wait_for_view_load(aManager, function(aManager) {
+      is_in_discovery(aManager, MAIN_URL, true, false);
+
+      clickLink(aManager, "link-good", function() {
+        is_in_discovery(aManager, SECOND_URL, true, false);
+
+        waitForLoad(aManager, function() {
+          is_in_discovery(aManager, MAIN_URL, true, true);
+
+          waitForLoad(aManager, function() {
+            is_in_discovery(aManager, SECOND_URL, true, false);
+
+            EventUtils.synthesizeMouseAtCenter(aManager.document.getElementById("category-plugins"), { }, aManager);
+
+            wait_for_view_load(aManager, function(aManager) {
+              is_in_list(aManager, "addons://list/plugin", true, false);
+
+              go_back(aManager);
+
+              wait_for_view_load(aManager, function(aManager) {
+                is_in_discovery(aManager, SECOND_URL, true, true);
+
+                go_back(aManager);
+
+                waitForLoad(aManager, function() {
+                  is_in_discovery(aManager, MAIN_URL, true, true);
+
+                  go_back(aManager);
+
+                  wait_for_view_load(aManager, function(aManager) {
+                    is_in_list(aManager, "addons://list/plugin", false, true);
+
+                    go_forward(aManager);
+
+                    wait_for_view_load(aManager, function(aManager) {
+                      is_in_discovery(aManager, MAIN_URL, true, true);
+
+                      waitForLoad(aManager, function() {
+                        is_in_discovery(aManager, SECOND_URL, true, true);
+
+                        close_manager(aManager, run_next_test);
+                      });
+
+                      go_forward(aManager);
+                    });
+                  });
+                });
+              });
+            });
+          });
+
+          go_forward(aManager);
+        });
+
+        go_back(aManager);
+      });
+    });
+  });
+});
+
 // Tests that when displaying in-content and opened in the background the back
 // and forward buttons still appear when switching tabs
 add_test(function() {
   if (!gUseInContentUI) {
     run_next_test();
     return;
   }
 
--- a/toolkit/mozapps/extensions/test/browser/browser_discovery.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_discovery.js
@@ -173,16 +173,20 @@ function clickLink(aId, aCallback) {
 
   gLoadCompleteCallback = function() {
     browser.removeProgressListener(gProgressListener);
     aCallback();
   };
 
   var link = browser.contentDocument.getElementById(aId);
   EventUtils.sendMouseEvent({type: "click"}, link);
+
+  executeSoon(function() {
+    ok(isLoading(), "Clicking a link should show the loading pane");
+  });
 }
 
 // Tests that switching to the discovery view displays the right url
 add_test(function() {
   open_manager("addons://list/extension", function(aWindow) {
     gManagerWindow = aWindow;
     gCategoryUtilities = new CategoryUtilities(gManagerWindow);