Bug 664897 - Details view should scroll down when it has inline settings and options button in list view is clicked; r=Unfocused, ui-r=Boriss
authorGeoff Lankow <geoff@darktrojan.net>
Tue, 30 Aug 2011 00:19:58 +1200
changeset 83909 b91361c61e7d287e2d267f73b8b836fd084c0db0
parent 83908 9136ddb5f8a14a680223281604ad138a7aea4171
child 83910 bdce0ad479d76b7ba943531658726a26d0b6342b
push id21812
push userbmo@edmorley.co.uk
push dateFri, 06 Jan 2012 22:28:53 +0000
treeherdermozilla-central@5a446202be5f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersUnfocused, Boriss
bugs664897
milestone12.0a1
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 664897 - Details view should scroll down when it has inline settings and options button in list view is clicked; r=Unfocused, ui-r=Boriss
toolkit/mozapps/extensions/content/extensions.js
toolkit/mozapps/extensions/test/browser/browser_inlinesettings.js
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -818,19 +818,20 @@ var gViewController = {
         gViewController.loadView("addons://updates/available");
       }
     },
 
     cmd_showItemDetails: {
       isEnabled: function(aAddon) {
         return !!aAddon && (gViewController.currentViewObj != gDetailView);
       },
-      doCommand: function(aAddon) {
+      doCommand: function(aAddon, aScrollToPreferences) {
         gViewController.loadView("addons://detail/" +
-                                 encodeURIComponent(aAddon.id));
+                                 encodeURIComponent(aAddon.id) +
+                                 (aScrollToPreferences ? "/preferences" : ""));
       }
     },
 
     cmd_findAllUpdates: {
       inProgress: false,
       isEnabled: function() !this.inProgress,
       doCommand: function() {
         this.inProgress = true;
@@ -959,17 +960,17 @@ var gViewController = {
         if (gViewController.currentViewObj == gDetailView &&
             aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE) {
           return false;
         }
         return true;
       },
       doCommand: function(aAddon) {
         if (aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE) {
-          gViewController.commands.cmd_showItemDetails.doCommand(aAddon);
+          gViewController.commands.cmd_showItemDetails.doCommand(aAddon, true);
           return;
         }
         var optionsURL = aAddon.optionsURL;
         if (aAddon.optionsType == AddonManager.OPTIONS_TYPE_TAB &&
             openOptionsInTab(optionsURL)) {
           return;
         }
         var windows = Services.wm.getEnumerator(null);
@@ -2560,17 +2561,17 @@ var gDetailView = {
   },
 
   observe: function(aSubject, aTopic, aData) {
     if (aTopic == "nsPref:changed" && aData == "autoUpdateDefault") {
       this.onPropertyChanged(["applyBackgroundUpdates"]);
     }
   },
 
-  _updateView: function(aAddon, aIsRemote) {
+  _updateView: function(aAddon, aIsRemote, aScrollToPreferences) {
     this._updatePrefs.addObserver("", this, false);
     this.clearLoading();
 
     this._addon = aAddon;
     gEventManager.registerAddonListener(this, aAddon.id);
     gEventManager.registerInstallListener(this);
 
     this.node.setAttribute("type", aAddon.type);
@@ -2733,38 +2734,45 @@ var gDetailView = {
       if (first && window.getComputedStyle(gridRows[i], null).getPropertyValue("display") != "none") {
         gridRows[i].setAttribute("first-row", true);
         first = false;
       } else {
         gridRows[i].removeAttribute("first-row");
       }
     }
 
-    this.fillSettingsRows();
+    this.fillSettingsRows(aScrollToPreferences);
 
     this.updateState();
 
     gViewController.updateCommands();
     gViewController.notifyViewChanged();
   },
 
   show: function(aAddonId, aRequest) {
+    let index = aAddonId.indexOf("/preferences");
+    let scrollToPreferences = false;
+    if (index >= 0) {
+      aAddonId = aAddonId.substring(0, index);
+      scrollToPreferences = true;
+    }
+
     var self = this;
     this._loadingTimer = setTimeout(function() {
       self.node.setAttribute("loading-extended", true);
     }, LOADING_MSG_DELAY);
 
     var view = gViewController.currentViewId;
 
     AddonManager.getAddonByID(aAddonId, function(aAddon) {
       if (gViewController && aRequest != gViewController.currentViewRequest)
         return;
 
       if (aAddon) {
-        self._updateView(aAddon, false);
+        self._updateView(aAddon, false, scrollToPreferences);
         return;
       }
 
       // Look for an add-on pending install
       AddonManager.getAllInstalls(function(aInstalls) {
         for (let i = 0; i < aInstalls.length; i++) {
           if (aInstalls[i].state == AddonManager.STATE_INSTALLED &&
               aInstalls[i].addon.id == aAddonId) {
@@ -2873,17 +2881,17 @@ var gDetailView = {
 
   emptySettingsRows: function () {
     var lastRow = document.getElementById("detail-downloads");
     var rows = lastRow.parentNode;
     while (lastRow.nextSibling)
       rows.removeChild(rows.lastChild);
   },
 
-  fillSettingsRows: function () {
+  fillSettingsRows: function (aScrollToPreferences) {
     this.emptySettingsRows();
     if (this._addon.optionsType != AddonManager.OPTIONS_TYPE_INLINE)
       return;
 
     // This function removes and returns the text content of aNode without
     // removing any child elements. Removing the text nodes ensures any XBL
     // bindings apply properly.
     function stripTextNodes(aNode) {
@@ -2947,21 +2955,41 @@ var gDetailView = {
 	// Ensure the page has loaded and force the XBL bindings to be synchronously applied,
 	// then notify observers.
     if (gViewController.viewPort.selectedPanel.hasAttribute("loading")) {
       gDetailView.node.addEventListener("ViewChanged", function viewChangedEventListener() {
         gDetailView.node.removeEventListener("ViewChanged", viewChangedEventListener, false);
         if (firstSetting)
           firstSetting.clientTop;
         Services.obs.notifyObservers(document, "addon-options-displayed", gDetailView._addon.id);
+        if (aScrollToPreferences)
+          gDetailView.scrollToPreferencesRows();
       }, false);
     } else {
       if (firstSetting)
         firstSetting.clientTop;
       Services.obs.notifyObservers(document, "addon-options-displayed", this._addon.id);
+      if (aScrollToPreferences)
+        gDetailView.scrollToPreferencesRows();
+    }
+  },
+
+  scrollToPreferencesRows: function() {
+    // We find this row, rather than remembering it from above,
+    // in case it has been changed by the observers.
+    let firstRow = gDetailView.node.querySelector('setting[first-row="true"]');
+    if (firstRow) {
+      let top = firstRow.boxObject.y;
+      top -= parseInt(window.getComputedStyle(firstRow, null).getPropertyValue("margin-top"));
+      
+      let detailViewBoxObject = gDetailView.node.boxObject;
+      top -= detailViewBoxObject.y;
+
+      detailViewBoxObject.QueryInterface(Ci.nsIScrollBoxObject);
+      detailViewBoxObject.scrollTo(0, top);
     }
   },
 
   getSelectedAddon: function() {
     return this._addon;
   },
 
   onEnabling: function() {
--- a/toolkit/mozapps/extensions/test/browser/browser_inlinesettings.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_inlinesettings.js
@@ -18,32 +18,46 @@ var observer = {
   observe: function(aSubject, aTopic, aData) {
     if (aTopic == "addon-options-displayed") {
       this.lastData = aData;
       // Test if the binding has applied before the observers are notified. We test the second setting here,
       // because the code operates on the first setting and we want to check it applies to all.
       var setting = aSubject.querySelector("rows > setting[first-row] ~ setting");
       var input = gManagerWindow.document.getAnonymousElementByAttribute(setting, "class", "setting-label");
       isnot(input, null, "XBL binding should be applied");
+
+      // Add some extra height to the scrolling pane to ensure that it needs to scroll when appropriate.
+      gManagerWindow.document.getElementById("detail-controls").style.marginBottom = "1000px";
     }
   }
 };
 
 function installAddon(aCallback) {
   AddonManager.getInstallForURL(TESTROOT + "addons/browser_inlinesettings1.xpi",
                                 function(aInstall) {
     aInstall.addListener({
       onInstallEnded: function() {
         executeSoon(aCallback);
       }
     });
     aInstall.install();
   }, "application/x-xpinstall");
 }
 
+function checkScrolling(aShouldHaveScrolled) {
+  var detailView = gManagerWindow.document.getElementById("detail-view");
+  var boxObject = detailView.boxObject;
+  boxObject.QueryInterface(Ci.nsIScrollBoxObject);
+  ok(detailView.scrollHeight > boxObject.height, "Page should require scrolling");
+  if (aShouldHaveScrolled)
+    isnot(detailView.scrollTop, 0, "Page should have scrolled");
+  else
+    is(detailView.scrollTop, 0, "Page should not have scrolled");
+}
+
 function test() {
   waitForExplicitFinish();
 
   gProvider = new MockProvider();
 
   gProvider.createAddons([{
     id: "inlinesettings2@tests.mozilla.org",
     name: "Inline Settings (Regular)",
@@ -142,24 +156,25 @@ add_test(function() {
   var addon = get_addon_element(gManagerWindow, "inlinesettings1@tests.mozilla.org");
   addon.parentNode.ensureElementIsVisible(addon);
 
   var button = gManagerWindow.document.getAnonymousElementByAttribute(addon, "anonid", "preferences-btn");
   EventUtils.synthesizeMouseAtCenter(button, { clickCount: 1 }, gManagerWindow);
 
   wait_for_view_load(gManagerWindow, function() {
     is(observer.lastData, "inlinesettings1@tests.mozilla.org", "Observer notification should have fired");
+    is(gManagerWindow.gViewController.currentViewId,
+       "addons://detail/inlinesettings1%40tests.mozilla.org/preferences",
+       "Current view should scroll to preferences");
+    checkScrolling(true);
 
     var grid = gManagerWindow.document.getElementById("detail-grid");
     var settings = grid.querySelectorAll("rows > setting");
     is(settings.length, SETTINGS_ROWS, "Grid should have settings children");
 
-    // Force bindings to apply
-    settings[0].clientTop;
-
     ok(settings[0].hasAttribute("first-row"), "First visible row should have first-row attribute");
     Services.prefs.setBoolPref("extensions.inlinesettings1.bool", false);
     var input = gManagerWindow.document.getAnonymousElementByAttribute(settings[0], "anonid", "input");
     isnot(input.checked, true, "Checkbox should have initial value");
     EventUtils.synthesizeMouseAtCenter(input, { clickCount: 1 }, gManagerWindow);
     is(input.checked, true, "Checkbox should have updated value");
     is(Services.prefs.getBoolPref("extensions.inlinesettings1.bool"), true, "Bool pref should have been updated");
     EventUtils.synthesizeMouseAtCenter(input, { clickCount: 1 }, gManagerWindow);
@@ -283,19 +298,16 @@ add_test(function() {
 
   wait_for_view_load(gManagerWindow, function() {
     is(observer.lastData, "inlinesettings3@tests.mozilla.org", "Observer notification should have fired");
 
     var grid = gManagerWindow.document.getElementById("detail-grid");
     var settings = grid.querySelectorAll("rows > setting");
     is(settings.length, 4, "Grid should have settings children");
 
-    // Force bindings to apply
-    settings[0].clientTop;
-
     ok(settings[0].hasAttribute("first-row"), "First visible row should have first-row attribute");
     Services.prefs.setBoolPref("extensions.inlinesettings3.radioBool", false);
     var radios = settings[0].getElementsByTagName("radio");
     isnot(radios[0].selected, true, "Correct radio button should be selected");
     is(radios[1].selected, true, "Correct radio button should be selected");
     EventUtils.synthesizeMouseAtCenter(radios[0], { clickCount: 1 }, gManagerWindow);
     is(Services.prefs.getBoolPref("extensions.inlinesettings3.radioBool"), true, "Radio pref should have been updated");
     EventUtils.synthesizeMouseAtCenter(radios[1], { clickCount: 1 }, gManagerWindow);
@@ -349,19 +361,16 @@ add_test(function() {
 
   wait_for_view_load(gManagerWindow, function() {
     is(observer.lastData, "inlinesettings2@tests.mozilla.org", "Observer notification should have fired");
 
     var grid = gManagerWindow.document.getElementById("detail-grid");
     var settings = grid.querySelectorAll("rows > setting");
     is(settings.length, 5, "Grid should have settings children");
 
-    // Force bindings to apply
-    settings[0].clientTop;
-
     var node = settings[0];
     node = settings[0];
     is_element_hidden(node, "Unsupported settings should not be visible");
     ok(!node.hasAttribute("first-row"), "Hidden row is not the first row");
 
     node = settings[1];
     is(node.nodeName, "setting", "Should be a setting node");
     ok(node.hasAttribute("first-row"), "First visible row should have first-row attribute");
@@ -434,20 +443,25 @@ add_test(function() {
   });
 });
 
 // Addon with options.xul, disabling and enabling should hide and show settings UI
 add_test(function() {
   var addon = get_addon_element(gManagerWindow, "inlinesettings1@tests.mozilla.org");
   addon.parentNode.ensureElementIsVisible(addon);
 
-  var button = gManagerWindow.document.getAnonymousElementByAttribute(addon, "anonid", "preferences-btn");
+  var button = gManagerWindow.document.getAnonymousElementByAttribute(addon, "anonid", "details-btn");
   EventUtils.synthesizeMouseAtCenter(button, { clickCount: 1 }, gManagerWindow);
 
   wait_for_view_load(gManagerWindow, function() {
+    is(gManagerWindow.gViewController.currentViewId,
+       "addons://detail/inlinesettings1%40tests.mozilla.org",
+       "Current view should not scroll to preferences");
+    checkScrolling(false);
+
     var grid = gManagerWindow.document.getElementById("detail-grid");
     var settings = grid.querySelectorAll("rows > setting");
     is(settings.length, SETTINGS_ROWS, "Grid should have settings children");
 
     // disable
     var button = gManagerWindow.document.getElementById("detail-disable-btn");
     button.focus(); // make sure it's in view
     EventUtils.synthesizeMouseAtCenter(button, { clickCount: 1 }, gManagerWindow);