Bug 1009116 - Work in progress. draft
authorPaolo Amadini <paolo.mozmail@amadzone.org>
Sun, 09 Oct 2016 09:55:23 +0100
changeset 566800 39ad77bc63a2de398accf07d7a02dd5ac915cc6c
parent 566799 9e615de5777df1067f414c0ce3dd62cb5bd27dc7
child 625431 04a2dda5ccd1e8f777dcb58a17c50b65a771518f
push id55340
push userpaolo.mozmail@amadzone.org
push dateSun, 23 Apr 2017 18:49:11 +0000
bugs1009116
milestone55.0a1
Bug 1009116 - Work in progress. MozReview-Commit-ID: CmnEAT39CZV
browser/base/content/browser.css
browser/components/controlcenter/content/panel.inc.xul
browser/components/customizableui/CustomizableWidgets.jsm
browser/components/customizableui/content/panelUI.css
browser/components/customizableui/content/panelUI.inc.xul
browser/components/customizableui/content/panelUI.xml
browser/components/downloads/content/downloadsOverlay.xul
browser/themes/shared/customizableui/panelUI.inc.css
browser/themes/shared/downloads/downloads.inc.css
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 @namespace html url("http://www.w3.org/1999/xhtml");
 @namespace svg url("http://www.w3.org/2000/svg");
 
 :root {
   --identity-popup-expander-width: 38px;
-  --panelui-subview-transition-duration: 150ms;
+  --panelui-subview-transition-duration: 1s;
   --lwt-additional-images: none;
   --lwt-background-alignment: right top;
   --lwt-background-tiling: no-repeat;
 }
 
 :root:-moz-lwtheme {
   color: var(--lwt-text-color) !important;
 }
--- a/browser/components/controlcenter/content/panel.inc.xul
+++ b/browser/components/controlcenter/content/panel.inc.xul
@@ -77,17 +77,17 @@
           <button id="tracking-action-block"
                   label="&trackingProtection.block2.label;"
                   accesskey="&trackingProtection.block2.accesskey;"
                   oncommand="TrackingProtection.enableForCurrentPage();" />
         </vbox>
       </hbox>
 
       <!-- Permissions Section -->
-      <hbox class="identity-popup-section">
+      <hbox class="identity-popup-section panel-subview-body">
         <vbox id="identity-popup-permissions-content" flex="1">
           <label id="identity-popup-permissions-headline"
                  class="identity-popup-headline"
                  value="&identity.permissions;"/>
           <vbox id="identity-popup-permission-list"/>
           <description id="identity-popup-permission-reload-hint">&identity.permissionsReloadHint;</description>
           <description id="identity-popup-permission-empty-hint">&identity.permissionsEmpty;</description>
         </vbox>
@@ -102,17 +102,17 @@
           <label class="identity-popup-headline identity-popup-hostless" crop="end"/>
         </label>
         <description class="identity-popup-connection-not-secure"
                      when-connection="not-secure secure-cert-user-overridden">&identity.connectionNotSecure;</description>
         <description class="identity-popup-connection-secure"
                      when-connection="secure secure-ev">&identity.connectionSecure;</description>
       </vbox>
 
-      <vbox id="identity-popup-securityView-body" flex="1">
+      <vbox id="identity-popup-securityView-body" class="panel-subview-body">
         <!-- (EV) Certificate Information -->
         <description id="identity-popup-content-verified-by"
                      when-connection="secure-ev">&identity.connectionVerified2;</description>
         <description id="identity-popup-content-owner"
                      when-connection="secure-ev"
                      class="header"/>
         <description id="identity-popup-content-supplemental"
                      when-connection="secure-ev"/>
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -171,16 +171,18 @@ const CustomizableWidgets = [
   {
     id: "history-panelmenu",
     type: "view",
     viewId: "PanelUI-history",
     shortcutId: "key_gotoHistory",
     tooltiptext: "history-panelmenu.tooltiptext2",
     defaultArea: CustomizableUI.AREA_PANEL,
     onViewShowing(aEvent) {
+      aEvent.detail.addBlocker(new Promise((resolve, reject) => {
+
       // Populate our list of history
       const kMaxResults = 15;
       let doc = aEvent.target.ownerDocument;
       let win = doc.defaultView;
 
       let options = PlacesUtils.history.getNewQueryOptions();
       options.excludeQueries = true;
       options.queryType = options.QUERY_TYPE_HISTORY;
@@ -227,19 +229,21 @@ const CustomizableWidgets = [
             item.addEventListener("click", onItemCommand);
             item.setAttribute("image", "page-icon:" + uri);
             fragment.appendChild(item);
           }
           items.appendChild(fragment);
         },
         handleError(aError) {
           log.debug("History view tried to show but had an error: " + aError);
+          reject();
         },
         handleCompletion(aReason) {
           log.debug("History view is being shown!");
+          resolve();
         },
       });
 
       let recentlyClosedTabs = doc.getElementById("PanelUI-recentlyClosedTabs");
       while (recentlyClosedTabs.firstChild) {
         recentlyClosedTabs.firstChild.remove();
       }
 
@@ -267,16 +271,18 @@ const CustomizableWidgets = [
       elementCount = windowsFragment.childElementCount;
       separator.hidden = !elementCount;
       while (--elementCount >= 0) {
         let element = windowsFragment.children[elementCount];
         CustomizableUI.addShortcut(element);
         element.classList.add("subviewbutton", "cui-withicon");
       }
       recentlyClosedWindows.appendChild(windowsFragment);
+
+      }));
     },
     onCreated(aNode) {
       // Middle clicking recently closed items won't close the panel - cope:
       let onRecentlyClosedClick = function(aEvent) {
         if (aEvent.button == 1) {
           CustomizableUI.hidePanelForNode(this);
         }
       };
--- a/browser/components/customizableui/content/panelUI.css
+++ b/browser/components/customizableui/content/panelUI.css
@@ -1,31 +1,27 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 .panel-viewstack[viewtype="main"] > .panel-clickcapturer {
   pointer-events: none;
 }
 
-.panel-mainview,
-.panel-viewcontainer,
-.panel-viewstack {
+.panel-viewcontainer {
   overflow: hidden;
 }
 
 .panel-viewstack {
-  position: relative;
+  transition: height var(--panelui-subview-transition-duration);
+}
+
+.panel-viewstack[viewtype="main"] > .panel-subviews {
+  -moz-stack-sizing: ignore;
 }
 
 .panel-subviews {
-  -moz-stack-sizing: ignore;
   transform: translateX(0);
-  overflow-y: auto;
 }
 
 .panel-subviews[panelopen] {
   transition: transform var(--panelui-subview-transition-duration);
 }
-
-.panel-viewcontainer[panelopen]:-moz-any(:not([viewtype="main"]),[transitioning="true"]) {
-  transition: height var(--panelui-subview-transition-duration);
-}
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -5,19 +5,21 @@
 <panel id="PanelUI-popup"
        role="group"
        type="arrow"
        hidden="true"
        flip="slide"
        position="bottomcenter topright"
        noautofocus="true">
   <panelmultiview id="PanelUI-multiView" mainViewId="PanelUI-mainView">
-    <panelview id="PanelUI-mainView" context="customizationPanelContextMenu">
-      <vbox id="PanelUI-contents-scroller">
-        <vbox id="PanelUI-contents" class="panelUI-grid"/>
+    <panelview id="PanelUI-mainView" orient="vertical" context="customizationPanelContextMenu">
+      <vbox class="panel-subview-body">
+        <vbox id="PanelUI-contents-scroller">
+          <vbox id="PanelUI-contents" class="panelUI-grid"/>
+        </vbox>
       </vbox>
 
       <footer id="PanelUI-footer">
         <vbox id="PanelUI-footer-addons"></vbox>
         <toolbarbutton id="PanelUI-update-available-menu-item"
                        wrap="true"
                        label="&updateAvailable.panelUI.label;"
                        hidden="true"/>
--- a/browser/components/customizableui/content/panelUI.xml
+++ b/browser/components/customizableui/content/panelUI.xml
@@ -8,17 +8,17 @@
           xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
           xmlns:xbl="http://www.mozilla.org/xbl">
 
   <binding id="panelmultiview">
     <resources>
       <stylesheet src="chrome://browser/content/customizableui/panelUI.css"/>
     </resources>
     <content>
-      <xul:box anonid="viewContainer" class="panel-viewcontainer" xbl:inherits="panelopen,viewtype,transitioning">
+      <xul:vbox anonid="viewContainer" class="panel-viewcontainer" xbl:inherits="panelopen,viewtype,transitioning">
         <xul:stack anonid="viewStack" xbl:inherits="viewtype,transitioning" viewtype="main" class="panel-viewstack">
           <xul:vbox anonid="mainViewContainer" class="panel-mainview" xbl:inherits="viewtype"/>
 
           <!-- Used to capture click events over the PanelUI-mainView if we're in
                subview mode. That way, any click on the PanelUI-mainView causes us
                to revert to the mainView mode, whereupon PanelUI-click-capture then
                allows click events to go through it. -->
           <xul:vbox anonid="clickCapturer" class="panel-clickcapturer"/>
@@ -27,17 +27,17 @@
                subviews that are not being displayed. We're using this over a deck
                because a deck assumes the size of its largest child, regardless of
                whether or not it is shown. That's not good for our case, since we
                want to allow each subview to be uniquely sized. -->
           <xul:vbox anonid="subViews" class="panel-subviews" xbl:inherits="panelopen">
             <children includes="panelview"/>
           </xul:vbox>
         </xul:stack>
-      </xul:box>
+      </xul:vbox>
     </content>
     <implementation implements="nsIDOMEventListener">
       <field name="_clickCapturer" readonly="true">
         document.getAnonymousElementByAttribute(this, "anonid", "clickCapturer");
       </field>
       <field name="_viewContainer" readonly="true">
         document.getAnonymousElementByAttribute(this, "anonid", "viewContainer");
       </field>
@@ -51,101 +51,54 @@
         document.getAnonymousElementByAttribute(this, "anonid", "viewStack");
       </field>
       <field name="_panel" readonly="true">
         this.parentNode;
       </field>
 
       <field name="_currentSubView">null</field>
       <field name="_anchorElement">null</field>
-      <field name="_mainViewHeight">0</field>
-      <field name="_subViewObserver">null</field>
-      <field name="__transitioning">false</field>
-      <field name="_ignoreMutations">false</field>
 
       <property name="showingSubView" readonly="true"
                 onget="return this._viewStack.getAttribute('viewtype') == 'subview'"/>
       <property name="_mainViewId" onget="return this.getAttribute('mainViewId');" onset="this.setAttribute('mainViewId', val); return val;"/>
       <property name="_mainView" readonly="true"
                 onget="return this._mainViewId ? document.getElementById(this._mainViewId) : null;"/>
       <property name="showingSubViewAsMainView" readonly="true"
                 onget="return this.getAttribute('mainViewIsSubView') == 'true'"/>
 
-      <property name="ignoreMutations">
-        <getter>
-          return this._ignoreMutations;
-        </getter>
-        <setter><![CDATA[
-          this._ignoreMutations = val;
-          if (!val && this._panel.state == "open") {
-            if (this.showingSubView) {
-              this._syncContainerWithSubView();
-            } else {
-              this._syncContainerWithMainView();
-            }
-          }
-        ]]></setter>
-      </property>
-
-      <property name="_transitioning">
-        <getter>
-          return this.__transitioning;
-        </getter>
-        <setter><![CDATA[
-          this.__transitioning = val;
-          if (val) {
-            this.setAttribute("transitioning", "true");
-          } else {
-            this.removeAttribute("transitioning");
-          }
-        ]]></setter>
-      </property>
       <constructor><![CDATA[
         this._clickCapturer.addEventListener("click", this);
         this._panel.addEventListener("popupshowing", this);
         this._panel.addEventListener("popupshown", this);
         this._panel.addEventListener("popuphidden", this);
-        this._subViews.addEventListener("overflow", this);
-        this._mainViewContainer.addEventListener("overflow", this);
-
-        // Get a MutationObserver ready to react to subview size changes. We
-        // only attach this MutationObserver when a subview is being displayed.
-        this._subViewObserver =
-          new MutationObserver(this._syncContainerWithSubView.bind(this));
-        this._mainViewObserver =
-          new MutationObserver(this._syncContainerWithMainView.bind(this));
 
         this._mainViewContainer.setAttribute("panelid",
                                              this._panel.id);
 
         if (this._mainView) {
           this.setMainView(this._mainView);
         }
         this.setAttribute("viewtype", "main");
       ]]></constructor>
 
       <destructor><![CDATA[
         if (this._mainView) {
           this._mainView.removeAttribute("mainview");
         }
-        this._mainViewObserver.disconnect();
-        this._subViewObserver.disconnect();
         this._panel.removeEventListener("popupshowing", this);
         this._panel.removeEventListener("popupshown", this);
         this._panel.removeEventListener("popuphidden", this);
-        this._subViews.removeEventListener("overflow", this);
-        this._mainViewContainer.removeEventListener("overflow", this);
         this._clickCapturer.removeEventListener("click", this);
       ]]></destructor>
 
       <method name="setMainView">
         <parameter name="aNewMainView"/>
         <body><![CDATA[
         if (this._mainView) {
-          this._mainViewObserver.disconnect();
           this._subViews.appendChild(this._mainView);
           this._mainView.removeAttribute("mainview");
         }
         this._mainViewId = aNewMainView.id;
         aNewMainView.setAttribute("mainview", "true");
         this._mainViewContainer.appendChild(aNewMainView);
         ]]></body>
       </method>
@@ -153,24 +106,21 @@
       <method name="showMainView">
         <body><![CDATA[
           if (this.showingSubView) {
             let viewNode = this._currentSubView;
             let evt = document.createEvent("CustomEvent");
             evt.initCustomEvent("ViewHiding", true, true, viewNode);
             viewNode.dispatchEvent(evt);
 
-            viewNode.removeAttribute("current");
-            this._currentSubView = null;
-
-            this._subViewObserver.disconnect();
-
-            this._setViewContainerHeight(this._mainViewHeight);
-
-            this.setAttribute("viewtype", "main");
+            this._transitionHeight(() => {
+              viewNode.removeAttribute("current");
+              this._currentSubView = null;
+              this.setAttribute("viewtype", "main");
+            });
           }
 
           this._shiftMainView();
         ]]></body>
       </method>
 
       <method name="showSubView">
         <parameter name="aViewId"/>
@@ -181,17 +131,17 @@
             if (!viewNode) {
               viewNode = document.getElementById(aViewId);
               if (viewNode) {
                 this._subViews.appendChild(viewNode);
               } else {
                 throw new Error(`Subview ${aViewId} doesn't exist!`);
               }
             }
-            viewNode.setAttribute("current", true);
+            //viewNode.setAttribute("current", true);
             // Emit the ViewShowing event so that the widget definition has a chance
             // to lazily populate the subview with things.
             let detail = {
               blockers: new Set(),
               addBlocker(aPromise) {
                 this.blockers.add(aPromise);
               },
             };
@@ -222,47 +172,94 @@
             //    node is at the left-most edge of the panel.
             // 2) The subview deck slides in so that it takes up almost all of the
             //    panel.
             // 3) If the subview is taller then the main panel contents, then the panel
             //    must grow to meet that new height. Otherwise, it must shrink.
             //
             // All three of these actions make use of CSS transformations, so they
             // should all occur simultaneously.
-            this.setAttribute("viewtype", "subview");
-            this._shiftMainView(aAnchor);
 
-            this._mainViewHeight = this._viewStack.clientHeight;
+            this._transitionHeight(() => {
+              viewNode.setAttribute("current", true);
 
-            let newHeight = this._heightOfSubview(viewNode, this._subViews);
-            this._setViewContainerHeight(newHeight);
+              this.setAttribute("viewtype", "subview");
+            });
 
-            this._subViewObserver.observe(viewNode, {
-              attributes: true,
-              characterData: true,
-              childList: true,
-              subtree: true
-            });
+            this._shiftMainView(aAnchor);
           }.bind(this));
         ]]></body>
       </method>
 
-      <method name="_setViewContainerHeight">
-        <parameter name="aHeight"/>
+      <!---
+         - Applies the height transition for which <panelmultiview> is designed.
+         -
+         - The height transition involves two elements, the viewContainer and
+         - its only immediate child the viewStack. In order for this to work
+         - correctly, the viewContainer must have "overflow: hidden;" and the
+         - two elements must have no margins or padding. This means that the
+         - height of the viewStack is never limited by the viewContainer, but
+         - when the height of the container is not constrained it matches the
+         - height of the viewStack.
+         -
+         - @param aChangeFn
+         -        This synchronous function is called to make the DOM changes
+         -        that will result in a new height of the viewStack.
+         -->
+      <method name="_transitionHeight">
+        <parameter name="aChangeFn"/>
         <body><![CDATA[
-          let container = this._viewContainer;
-          this._transitioning = true;
+          if (this._panel.state != "open") {
+            aChangeFn();
+            return;
+          }
+
+          // Lock the dimensions of the window that hosts the popup panel. This
+          // in turn constrains the height of the viewContainer.
+          let rect = this._panel.popupBoxObject.getOuterScreenRect();
+          this._panel.setAttribute("width", rect.width);
+          this._panel.setAttribute("height", rect.height);
 
-          let onTransitionEnd = () => {
-            container.removeEventListener("transitionend", onTransitionEnd);
-            this._transitioning = false;
-          };
+          // Read the current height of the viewStack. If we are in the middle
+          // of a transition, this is the actual height of the element at this
+          // point in time.
+          let oldHeight = this._viewStack.getBoundingClientRect().height;
+
+          // Make the necessary DOM changes, then reset the "height" property of
+          // the viewStack to "auto" to ensure that we read its final value even
+          // if we are in the middle of a transition.
+          aChangeFn();
+          this._viewStack.style.height = "auto";
+          let newHeight = this._viewStack.getBoundingClientRect().height;
+
+          // Now we can allow the popup panel to resize again. This must occur
+          // in the same tick as the code below, but we can do this before
+          // setting the starting height in case the transition is not needed.
+          this._panel.removeAttribute("width");
+          this._panel.removeAttribute("height");
 
-          container.addEventListener("transitionend", onTransitionEnd);
-          container.style.height = `${aHeight}px`;
+          if (oldHeight != newHeight) {
+            // Height transitions can only occur between two numeric values, and
+            // cannot start if the height is set to "auto". In case a transition
+            // is needed, we need to set the height to the old value, then force
+            // a synchronous layout so the transition can be applied later.
+            this._viewStack.style.height = oldHeight + "px";
+            this._viewStack.getBoundingClientRect().height;
+
+            // We can now set the new height to start the transition, but before
+            // doing that we set up a listener to reset the height to "auto" at
+            // the end, so that DOM changes made after the transition ends are
+            // still reflected by the height of the panel.
+            let onTransitionEnd = () => {
+              this._viewStack.removeEventListener("transitionend", onTransitionEnd);
+              this._viewStack.style.height = "auto";
+            };
+            this._viewStack.addEventListener("transitionend", onTransitionEnd);
+            this._viewStack.style.height = newHeight + "px";
+          }
         ]]></body>
       </method>
 
       <method name="_shiftMainView">
         <parameter name="aAnchor"/>
         <body><![CDATA[
           if (aAnchor) {
             // We need to find the edge of the anchor, relative to the main panel.
@@ -308,201 +305,46 @@
             return;
           }
           switch (aEvent.type) {
             case "click":
               if (aEvent.originalTarget == this._clickCapturer) {
                 this.showMainView();
               }
               break;
-            case "overflow":
-              if (aEvent.target.localName == "vbox") {
-                // Resize the right view on the next tick.
-                if (this.showingSubView) {
-                  setTimeout(this._syncContainerWithSubView.bind(this), 0);
-                } else if (!this.transitioning) {
-                  setTimeout(this._syncContainerWithMainView.bind(this), 0);
-                }
-              }
-              break;
             case "popupshowing":
               this.setAttribute("panelopen", "true");
               // Bug 941196 - The panel can get taller when opening a subview. Disabling
               // autoPositioning means that the panel won't jump around if an opened
               // subview causes the panel to exceed the dimensions of the screen in the
               // direction that the panel originally opened in. This property resets
               // every time the popup closes, which is why we have to set it each time.
               this._panel.autoPosition = false;
-              this._syncContainerWithMainView();
-
-              this._mainViewObserver.observe(this._mainView, {
-                attributes: true,
-                characterData: true,
-                childList: true,
-                subtree: true
-              });
 
               break;
             case "popupshown":
-              this._setMaxHeight();
               break;
             case "popuphidden":
               this.removeAttribute("panelopen");
-              this._mainView.style.removeProperty("height");
               this.showMainView();
-              this._mainViewObserver.disconnect();
               break;
           }
         ]]></body>
       </method>
 
-      <method name="_shouldSetPosition">
-        <body><![CDATA[
-          return this.getAttribute("nosubviews") == "true";
-        ]]></body>
-      </method>
-
-      <method name="_shouldSetHeight">
-        <body><![CDATA[
-          return this.getAttribute("nosubviews") != "true";
-        ]]></body>
-      </method>
-
-      <method name="_setMaxHeight">
-        <body><![CDATA[
-          if (!this._shouldSetHeight())
-            return;
-
-          // Ignore the mutation that'll fire when we set the height of
-          // the main view.
-          this.ignoreMutations = true;
-          this._mainView.style.height =
-            this.getBoundingClientRect().height + "px";
-          this.ignoreMutations = false;
-        ]]></body>
-      </method>
-      <method name="_adjustContainerHeight">
-        <body><![CDATA[
-          if (!this.ignoreMutations && !this.showingSubView && !this._transitioning) {
-            let height;
-            if (this.showingSubViewAsMainView) {
-              height = this._heightOfSubview(this._mainView);
-            } else {
-              height = this._mainView.scrollHeight;
-            }
-            this._viewContainer.style.height = height + "px";
-          }
-        ]]></body>
-      </method>
-      <method name="_syncContainerWithSubView">
-        <body><![CDATA[
-          // Check that this panel is still alive:
-          if (!this._panel || !this._panel.parentNode) {
-            return;
-          }
-
-          if (!this.ignoreMutations && this.showingSubView) {
-            let newHeight = this._heightOfSubview(this._currentSubView, this._subViews);
-            this._viewContainer.style.height = newHeight + "px";
-          }
-        ]]></body>
-      </method>
-      <method name="_syncContainerWithMainView">
-        <body><![CDATA[
-          // Check that this panel is still alive:
-          if (!this._panel || !this._panel.parentNode) {
-            return;
-          }
-
-          if (this._shouldSetPosition()) {
-            this._panel.adjustArrowPosition();
-          }
-
-          if (this._shouldSetHeight()) {
-            this._adjustContainerHeight();
-          }
-        ]]></body>
-      </method>
-
       <!-- Call this when the height of one of your views (the main view or a
            subview) changes and you want the heights of the multiview and panel
            to be the same as the view's height.
            If the caller can give a hint of the expected height change with the
            optional aExpectedChange parameter, it prevents flicker. -->
       <method name="setHeightToFit">
         <parameter name="aExpectedChange"/>
         <body><![CDATA[
-          // Set the max-height to zero, wait until the height is actually
-          // updated, and then remove it.  If it's not removed, weird things can
-          // happen, like widgets in the panel won't respond to clicks even
-          // though they're visible.
-          let count = 5;
-          let height = getComputedStyle(this).height;
-          if (aExpectedChange)
-            this.style.maxHeight = (parseInt(height) + aExpectedChange) + "px";
-          else
-            this.style.maxHeight = "0";
-          let interval = setInterval(() => {
-            if (height != getComputedStyle(this).height || --count == 0) {
-              clearInterval(interval);
-              this.style.removeProperty("max-height");
-            }
-          }, 0);
         ]]></body>
       </method>
-
-      <method name="_heightOfSubview">
-        <parameter name="aSubview"/>
-        <parameter name="aContainerToCheck"/>
-        <body><![CDATA[
-          function getFullHeight(element) {
-            // XXXgijs: unfortunately, scrollHeight rounds values, and there's no alternative
-            // that works with overflow: auto elements. Fortunately for us,
-            // we have exactly 1 (potentially) scrolling element in here (the subview body),
-            // and rounding 1 value is OK - rounding more than 1 and adding them means we get
-            // off-by-1 errors. Now we might be off by a subpixel, but we care less about that.
-            // So, use scrollHeight *only* if the element is vertically scrollable.
-            let height;
-            let elementCS;
-            if (element.scrollTopMax) {
-              height = element.scrollHeight;
-              // Bounding client rects include borders, scrollHeight doesn't:
-              elementCS = win.getComputedStyle(element);
-              height += parseFloat(elementCS.borderTopWidth) +
-                        parseFloat(elementCS.borderBottomWidth);
-            } else {
-              height = element.getBoundingClientRect().height;
-              if (height > 0) {
-                elementCS = win.getComputedStyle(element);
-              }
-            }
-            if (elementCS) {
-              // Include margins - but not borders or paddings because they
-              // were dealt with above.
-              height += parseFloat(elementCS.marginTop) + parseFloat(elementCS.marginBottom);
-            }
-            return height;
-          }
-          let win = aSubview.ownerGlobal;
-          let body = aSubview.querySelector(".panel-subview-body");
-          let height = getFullHeight(body || aSubview);
-          if (body) {
-            let header = aSubview.querySelector(".panel-subview-header");
-            let footer = aSubview.querySelector(".panel-subview-footer");
-            height += (header ? getFullHeight(header) : 0) +
-                      (footer ? getFullHeight(footer) : 0);
-          }
-          if (aContainerToCheck) {
-            let containerCS = win.getComputedStyle(aContainerToCheck);
-            height += parseFloat(containerCS.paddingTop) + parseFloat(containerCS.paddingBottom);
-          }
-          return Math.ceil(height);
-        ]]></body>
-      </method>
-
     </implementation>
   </binding>
 
   <binding id="panelview">
     <implementation>
       <property name="panelMultiView" readonly="true">
         <getter><![CDATA[
           if (this.parentNode.localName != "panelmultiview") {
--- a/browser/components/downloads/content/downloadsOverlay.xul
+++ b/browser/components/downloads/content/downloadsOverlay.xul
@@ -47,17 +47,17 @@
     <!-- The panel has level="top" to ensure that it is never hidden by the
          taskbar on Windows.  See bug 672365.  For accessibility to screen
          readers, we use a label on the panel instead of the anchor because the
          panel can also be displayed without an anchor. -->
     <panel id="downloadsPanel"
            aria-label="&downloads.title;"
            role="group"
            type="arrow"
-           orient="vertical"
+           orient="horizontal"
            level="top"
            onpopupshown="DownloadsPanel.onPopupShown(event);"
            onpopuphidden="DownloadsPanel.onPopupHidden(event);">
       <!-- The following popup menu should be a child of the panel element,
            otherwise flickering may occur when the cursor is moved over the area
            of a disabled menu item that overlaps the panel.  See bug 492960. -->
       <menupopup id="downloadsContextMenu"
                  onpopupshown="DownloadsView.onContextPopupShown(event);"
@@ -102,33 +102,31 @@
                   label="&cmd.removeFromHistory.label;"
                   accesskey="&cmd.removeFromHistory.accesskey;"/>
         <menuitem command="downloadsCmd_clearList"
                   label="&cmd.clearList2.label;"
                   accesskey="&cmd.clearList2.accesskey;"/>
       </menupopup>
 
       <panelmultiview id="downloadsPanel-multiView"
-                      mainViewId="downloadsPanel-mainView"
-                      align="stretch">
+                      mainViewId="downloadsPanel-mainView">
 
-        <panelview id="downloadsPanel-mainView"
-                   flex="1"
-                   align="stretch">
-          <richlistbox id="downloadsListBox"
-                       context="downloadsContextMenu"
-                       onmouseover="DownloadsView.onDownloadMouseOver(event);"
-                       onmouseout="DownloadsView.onDownloadMouseOut(event);"
-                       oncontextmenu="DownloadsView.onDownloadContextMenu(event);"
-                       ondragstart="DownloadsView.onDownloadDragStart(event);"/>
-          <description id="emptyDownloads"
-                       mousethrough="always">
-             &downloadsPanelEmpty.label;
-          </description>
-          <spacer flex="1"/>
+        <panelview id="downloadsPanel-mainView">
+          <vbox class="panel-subview-body">
+            <richlistbox id="downloadsListBox"
+                         context="downloadsContextMenu"
+                         onmouseover="DownloadsView.onDownloadMouseOver(event);"
+                         onmouseout="DownloadsView.onDownloadMouseOut(event);"
+                         oncontextmenu="DownloadsView.onDownloadContextMenu(event);"
+                         ondragstart="DownloadsView.onDownloadDragStart(event);"/>
+            <description id="emptyDownloads"
+                         mousethrough="always">
+               &downloadsPanelEmpty.label;
+            </description>
+          </vbox>
           <vbox id="downloadsFooter"
                 class="downloadsPanelFooter">
             <stack>
               <hbox id="downloadsSummary"
                     align="center"
                     orient="horizontal"
                     onkeydown="DownloadsSummary.onKeyDown(event);"
                     onclick="DownloadsSummary.onClick(event);">
@@ -171,23 +169,22 @@
                               label="&openDownloadsFolder.label;"/>
                   </menupopup>
                 </button>
               </hbox>
             </stack>
           </vbox>
         </panelview>
 
-        <panelview id="downloadsPanel-blockedSubview"
-                   orient="vertical"
-                   flex="1">
-          <description id="downloadsPanel-blockedSubview-title"/>
-          <description id="downloadsPanel-blockedSubview-details1"/>
-          <description id="downloadsPanel-blockedSubview-details2"/>
-          <spacer flex="1"/>
+        <panelview id="downloadsPanel-blockedSubview">
+          <vbox class="panel-subview-body">
+            <description id="downloadsPanel-blockedSubview-title"/>
+            <description id="downloadsPanel-blockedSubview-details1"/>
+            <description id="downloadsPanel-blockedSubview-details2"/>
+          </vbox>
           <hbox id="downloadsPanel-blockedSubview-buttons"
                 class="downloadsPanelFooter"
                 align="stretch">
             <button id="downloadsPanel-blockedSubview-openButton"
                     class="downloadsPanelFooterButton"
                     command="downloadsCmd_unblockAndOpen"
                     flex="1"/>
             <toolbarseparator/>
--- a/browser/themes/shared/customizableui/panelUI.inc.css
+++ b/browser/themes/shared/customizableui/panelUI.inc.css
@@ -193,17 +193,18 @@
 .panel-viewstack[viewtype="main"] > .panel-subviews:-moz-locale-dir(rtl) {
   transform: translateX(-@menuPanelWidth@);
 }
 
 panelmultiview[nosubviews=true] > .panel-viewcontainer > .panel-viewstack > .panel-subviews {
   display: none;
 }
 
-.panel-viewstack:not([viewtype="main"]) > .panel-mainview > #PanelUI-mainView {
+panelview {
+  -moz-box-orient: vertical;
   -moz-box-flex: 1;
 }
 
 .panel-subview-body {
   overflow-y: auto;
   overflow-x: hidden;
   -moz-box-flex: 1;
 }
@@ -231,21 +232,16 @@ panelmultiview[nosubviews=true] > .panel
   display: none;
 }
 
 .cui-widget-panelview .subviewbutton.panel-subview-footer {
   margin: 4px 0 0;
   -moz-box-pack: center;
 }
 
-#PanelUI-mainView {
-  display: flex;
-  flex-direction: column;
-}
-
 #appMenu-popup > arrowscrollbox > autorepeatbutton,
 #PanelUI-popup > arrowscrollbox > autorepeatbutton {
   display: none;
 }
 
 #appMenu-popup > arrowscrollbox > scrollbox,
 #PanelUI-popup > arrowscrollbox > scrollbox {
   overflow: visible;
@@ -290,16 +286,20 @@ panelmultiview[nosubviews=true] > .panel
   margin: -1px 0 0;
 }
 
 #wrapper-edit-controls:-moz-any([place="palette"],[place="panel"]) > #edit-controls,
 #wrapper-zoom-controls:-moz-any([place="palette"],[place="panel"]) > #zoom-controls {
   margin-inline-start: 0;
 }
 
+#PanelUI-multiView > .panel-viewcontainer > .panel-viewstack {
+  max-width: calc(@menuPanelWidth@ + 10px);
+}
+
 #PanelUI-contents {
   max-width: @menuPanelWidth@;
 }
 
 #BMB_bookmarksPopup,
 .panel-mainview:not([panelid="PanelUI-popup"]) {
   max-width: @standaloneSubviewWidth@;
 }
@@ -336,22 +336,19 @@ panelview:not([mainview]) .toolbarbutton
   flex: 1 0 auto;
   margin-left: auto;
   margin-right: auto;
   padding: .5em 0;
   max-width: @menuPanelWidth@;
 }
 
 #PanelUI-contents-scroller {
-  overflow-y: auto;
-  overflow-x: hidden;
   width: @menuPanelWidth@;
   padding-left: 5px;
   padding-right: 5px;
-  flex: auto;
 }
 
 .toolbaritem-combined-buttons@inAnyPanel@ > toolbarbutton > .toolbarbutton-icon {
   min-width: 0;
   min-height: 0;
   margin: 0;
 }
 
@@ -554,17 +551,16 @@ toolbarpaletteitem[place="palette"] > to
 #zoom-in-button > .toolbarbutton-text,
 #zoom-out-button > .toolbarbutton-text,
 #zoom-reset-button > .toolbarbutton-icon {
   display: none;
 }
 
 #PanelUI-footer {
   display: flex;
-  flex-shrink: 0;
   flex-direction: column;
   background-color: var(--arrowpanel-dimmed);
   padding: 0;
   margin: 0;
 }
 
 #main-window[customizing] #PanelUI-footer-fxa {
   display: none;
--- a/browser/themes/shared/downloads/downloads.inc.css
+++ b/browser/themes/shared/downloads/downloads.inc.css
@@ -10,17 +10,16 @@
 %define itemFinished @item@[state="1"]
 %define itemNotFinished @item@:not([state="1"])
 %define itemFocused #downloadsListBox:focus > @item@[selected]
 
 /*** Panel and outer controls ***/
 
 #downloadsPanel > .panel-arrowcontainer > .panel-arrowcontent {
   overflow: hidden;
-  display: block;
 }
 
 #downloadsPanel > .panel-arrowcontainer > .panel-arrowcontent,
 #downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack > .panel-subviews {
   padding: 0;
 }
 
 #downloadsListBox {
@@ -348,16 +347,20 @@ richlistitem[type="download"]:last-child
 }
 
 .downloadShowBlockedInfo > .button-box > .button-icon:-moz-locale-dir(rtl) {
   list-style-image: url("chrome://browser/skin/panel-icons.svg#arrow-left");
 }
 
 /*** Blocked subview ***/
 
+#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack {
+  max-width: 30em;
+}
+
 #downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype=main] > .panel-subviews {
   /* When the main view is showing, the shadow on the left edge of the subview is
      barely visible on the right edge of the main view, so set it to none. */
   box-shadow: none;
 }
 
 /* When the subview is showing, turn the download button into an arrow pointing
    back to the main view. */