Bug 1354155 - use photon panelmultiview for individual subviews, r=mikedeboer
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Tue, 30 May 2017 16:30:25 +0100
changeset 361681 27e39a0dead1a034e2bbc08fa6e88e2840fc15b8
parent 361680 44c831aee2d0dc9a96c7a3f792420d9af0998b65
child 361682 718fc2e88bb1daff46725c30c73dec2366d7bc33
push id31939
push usercbook@mozilla.com
push dateThu, 01 Jun 2017 11:49:28 +0000
treeherdermozilla-central@d96110d76619 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmikedeboer
bugs1354155
milestone55.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 1354155 - use photon panelmultiview for individual subviews, r=mikedeboer MozReview-Commit-ID: 9iEHcGDLbJt
browser/base/content/browser.css
browser/base/content/browser.xul
browser/components/customizableui/PanelMultiView.jsm
browser/components/customizableui/content/panelUI.inc.xul
browser/components/customizableui/content/panelUI.js
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -108,17 +108,17 @@ panelview {
   transition: transform var(--panelui-subview-transition-duration);
 }
 
 panelview:not([mainview]):not([current]) {
   transition: visibility 0s linear var(--panelui-subview-transition-duration);
   visibility: collapse;
 }
 
-panelview:not([title]) > .panel-header {
+panelview[mainview] > .panel-header {
   display: none;
 }
 
 tabbrowser {
   -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser");
 }
 
 .tabbrowser-tabs {
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -606,16 +606,17 @@
                 accesskey="&selectAllCmd.accesskey;"
                 cmd="cmd_selectAll"/>
       <menuseparator/>
       <menuitem label="&syncSyncNowItem.label;"
                 accesskey="&syncSyncNowItem.accesskey;"
                 id="syncedTabsRefreshFilter"/>
     </menupopup>
   </popupset>
+  <box id="appMenu-viewCache" hidden="true"/>
 
 #ifdef CAN_DRAW_IN_TITLEBAR
 <vbox id="titlebar">
   <hbox id="titlebar-content">
     <spacer id="titlebar-spacer" flex="1"/>
     <hbox id="titlebar-buttonbox-container">
 #ifdef XP_WIN
       <hbox id="private-browsing-indicator-titlebar">
--- a/browser/components/customizableui/PanelMultiView.jsm
+++ b/browser/components/customizableui/PanelMultiView.jsm
@@ -242,16 +242,21 @@ this.PanelMultiView = class {
       document.getAnonymousElementByAttribute(this.node, "anonid", "viewContainer");
     this._mainViewContainer =
       document.getAnonymousElementByAttribute(this.node, "anonid", "mainViewContainer");
     this._subViews =
       document.getAnonymousElementByAttribute(this.node, "anonid", "subViews");
     this._viewStack =
       document.getAnonymousElementByAttribute(this.node, "anonid", "viewStack");
 
+    XPCOMUtils.defineLazyGetter(this, "_panelViewCache", () => {
+      let viewCacheId = this.node.getAttribute("viewCacheId");
+      return viewCacheId ? document.getElementById(viewCacheId) : null;
+    });
+
     this._panel.addEventListener("popupshowing", this);
     this._panel.addEventListener("popuphidden", this);
     this._panel.addEventListener("popupshown", this);
     if (this.panelViews) {
       let cs = window.getComputedStyle(document.documentElement);
       // Set CSS-determined attributes now to prevent a layout flush when we do
       // it when transitioning between panels.
       this._dir = cs.direction;
@@ -283,47 +288,76 @@ this.PanelMultiView = class {
       Object.defineProperty(this.node, method, {
         enumerable: true,
         value: (...args) => this[method](...args)
       });
     });
   }
 
   destructor() {
+    // Guard against re-entrancy.
+    if (!this.node)
+      return;
+
     if (this._mainView) {
-      this._mainView.removeAttribute("mainview");
+      let mainView = this._mainView;
+      if (this._panelViewCache)
+        this._panelViewCache.appendChild(mainView);
+      mainView.removeAttribute("mainview");
     }
+    if (this._subViews)
+      this._moveOutKids(this._subViews);
+
     if (this.panelViews) {
+      this._moveOutKids(this._viewStack);
       this.panelViews.clear();
     } else {
       this._clickCapturer.removeEventListener("click", this);
     }
     this._panel.removeEventListener("popupshowing", this);
     this._panel.removeEventListener("popupshown", this);
     this._panel.removeEventListener("popuphidden", this);
     this.node = this._clickCapturer = this._viewContainer = this._mainViewContainer =
-      this._subViews = this._viewStack = this.__dwu = null;
+      this._subViews = this._viewStack = this.__dwu = this._panelViewCache = null;
+  }
+
+  /**
+   * Remove any child subviews into the panelViewCache, to ensure
+   * they remain usable even if this panelmultiview instance is removed
+   * from the DOM.
+   * @param viewNodeContainer the container from which to remove subviews
+   */
+  _moveOutKids(viewNodeContainer) {
+    if (!this._panelViewCache)
+      return;
+
+    // Node.children and Node.childNodes is live to DOM changes like the
+    // ones we're about to do, so iterate over a static copy:
+    let subviews = Array.from(viewNodeContainer.childNodes);
+    for (let subview of subviews) {
+      // XBL lists the 'children' XBL element explicitly. :-(
+      if (subview.nodeName != "children")
+        this._panelViewCache.appendChild(subview);
+    }
   }
 
   goBack(target) {
     let [current, previous] = this.panelViews.back();
     return this.showSubView(current, target, previous);
   }
 
   /**
-   * Checks whether it is possible to navigate backwards currently.
-   * Since the visibility of the back button is dependent - right now - on the
-   * fact that there's a view title set, we use that heuristic to determine this
-   * capability.
+   * Checks whether it is possible to navigate backwards currently. Returns
+   * false if this is the panelmultiview's mainview, true otherwise.
    *
    * @param  {panelview} view View to check, defaults to the currently active view.
    * @return {Boolean}
    */
   _canGoBack(view = this._currentSubView) {
-    return !!view.getAttribute("title");
+    return view != this._mainView;
   }
 
   setMainView(aNewMainView) {
     if (this._mainView) {
       if (!this.panelViews)
         this._subViews.appendChild(this._mainView);
       this._mainView.removeAttribute("mainview");
     }
@@ -355,17 +389,17 @@ this.PanelMultiView = class {
           this.node.setAttribute("viewtype", "main");
         });
       }
 
       this._shiftMainView();
     }
   }
 
-  showSubView(aViewId, aAnchor, aPreviousView, aAdopted = false) {
+  showSubView(aViewId, aAnchor, aPreviousView) {
     const {document, window} = this;
     return (async () => {
       // Support passing in the node directly.
       let viewNode = typeof aViewId == "string" ? this.node.querySelector("#" + aViewId) : aViewId;
       if (!viewNode) {
         viewNode = document.getElementById(aViewId);
         if (viewNode) {
           if (this.panelViews) {
@@ -410,17 +444,17 @@ this.PanelMultiView = class {
       let detail = {
         blockers: new Set(),
         addBlocker(aPromise) {
           this.blockers.add(aPromise);
         },
       };
 
       // Make sure that new panels always have a title set.
-      if (this.panelViews && aAdopted && aAnchor) {
+      if (this.panelViews && aAnchor) {
         if (aAnchor && !viewNode.hasAttribute("title"))
           viewNode.setAttribute("title", aAnchor.getAttribute("label"));
         viewNode.classList.add("PanelUI-subView");
         let custWidget = CustomizableWidgets.find(widget => widget.viewId == viewNode.id);
         if (custWidget) {
           if (custWidget.onInit)
             custWidget.onInit(aAnchor);
           custWidget.onViewShowing({ target: aAnchor, detail });
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -4,17 +4,18 @@
 
 <panel id="PanelUI-popup"
        role="group"
        type="arrow"
        hidden="true"
        flip="slide"
        position="bottomcenter topright"
        noautofocus="true">
-  <panelmultiview id="PanelUI-multiView" mainViewId="PanelUI-mainView">
+  <panelmultiview id="PanelUI-multiView" mainViewId="PanelUI-mainView"
+                  viewCacheId="appMenu-viewCache">
     <panelview id="PanelUI-mainView" context="customizationPanelContextMenu">
       <vbox id="PanelUI-contents-scroller">
         <vbox id="PanelUI-contents" class="panelUI-grid"/>
       </vbox>
 
       <footer id="PanelUI-footer">
         <vbox id="PanelUI-footer-addons"></vbox>
         <toolbarbutton class="panel-banner-item"
@@ -498,17 +499,19 @@
 <panel id="appMenu-popup"
        class="cui-widget-panel"
        role="group"
        type="arrow"
        hidden="true"
        flip="slide"
        position="bottomcenter topright"
        noautofocus="true">
-  <photonpanelmultiview id="appMenu-multiView" mainViewId="appMenu-mainView" descriptionheightworkaround="true">
+  <photonpanelmultiview id="appMenu-multiView" mainViewId="appMenu-mainView"
+                        descriptionheightworkaround="true"
+                        viewCacheId="appMenu-viewCache">
     <panelview id="appMenu-mainView" class="PanelUI-subView">
       <vbox class="panel-subview-body">
         <vbox id="appMenu-addon-banners"/>
         <toolbarbutton class="panel-banner-item"
                        label-update-available="&updateAvailable.panelUI.label;"
                        label-update-manual="&updateManual.panelUI.label;"
                        label-update-restart="&updateRestart.panelUI.label2;"
                        oncommand="PanelUI._onBannerItemSelected(event)"
@@ -632,22 +635,22 @@
                        class="subviewbutton subviewbutton-nav"
                        label="&moreMenu.label;"
                        closemenu="none"
                        oncommand="PanelUI.showSubView('appMenu-moreView', this)"/>
         <toolbarbutton id="appMenu-developer-button"
                        class="subviewbutton subviewbutton-nav"
                        label="&webDeveloperMenu.label;"
                        closemenu="none"
-                       oncommand="PanelUI.showSubView('PanelUI-developer', this, null, true)"/>
+                       oncommand="PanelUI.showSubView('PanelUI-developer', this)"/>
         <toolbarbutton id="appMenu-help-button"
                        class="subviewbutton subviewbutton-iconic subviewbutton-nav"
                        label="&appMenuHelp.label;"
                        closemenu="none"
-                       oncommand="PanelUI.showSubView('PanelUI-helpView', this, null, true)"/>
+                       oncommand="PanelUI.showSubView('PanelUI-helpView', this)"/>
 #ifndef XP_MACOSX
         <toolbarseparator/>
         <toolbarbutton id="appMenu-quit-button"
                        class="subviewbutton subviewbutton-iconic"
 #ifdef XP_WIN
                        label="&quitApplicationCmdWin2.label;"
                        tooltiptext="&quitApplicationCmdWin2.tooltip;"
 #else
@@ -659,17 +662,17 @@
       </vbox>
     </panelview>
     <panelview id="appMenu-moreView" title="&moreMenu.label;" class="PanelUI-subView">
       <vbox class="panel-subview-body">
         <toolbarbutton id="appMenu-characterencoding-button"
                        class="subviewbutton subviewbutton-nav"
                        label="&charsetMenu2.label;"
                        closemenu="none"
-                       oncommand="PanelUI.showSubView('PanelUI-characterEncodingView', this, null, true)"/>
+                       oncommand="PanelUI.showSubView('PanelUI-characterEncodingView', this)"/>
         <toolbarbutton id="appMenu-workoffline-button"
                        class="subviewbutton"
                        label="&goOfflineCmd.label;"
                        type="checkbox"
                        observes="workOfflineMenuitemState"
                        oncommand="BrowserOffline.toggleOfflineStatus();"/>
       </vbox>
     </panelview>
--- a/browser/components/customizableui/content/panelUI.js
+++ b/browser/components/customizableui/content/panelUI.js
@@ -439,32 +439,32 @@ const PanelUI = {
 
   /**
    * Shows a subview in the panel with a given ID.
    *
    * @param aViewId the ID of the subview to show.
    * @param aAnchor the element that spawned the subview.
    * @param aPlacementArea the CustomizableUI area that aAnchor is in.
    */
-  async showSubView(aViewId, aAnchor, aPlacementArea, aAdopted = false) {
+  async showSubView(aViewId, aAnchor, aPlacementArea) {
     this._ensureEventListenersAdded();
     let viewNode = document.getElementById(aViewId);
     if (!viewNode) {
       Cu.reportError("Could not show panel subview with id: " + aViewId);
       return;
     }
 
     if (!aAnchor) {
       Cu.reportError("Expected an anchor when opening subview with id: " + aViewId);
       return;
     }
 
     let container = aAnchor.closest("panelmultiview,photonpanelmultiview");
     if (container) {
-      container.showSubView(aViewId, aAnchor, null, aAdopted);
+      container.showSubView(aViewId, aAnchor);
     } else if (!aAnchor.open) {
       aAnchor.open = true;
 
       let tempPanel = document.createElement("panel");
       tempPanel.setAttribute("type", "arrow");
       tempPanel.setAttribute("id", "customizationui-widget-panel");
       tempPanel.setAttribute("class", "cui-widget-panel");
       tempPanel.setAttribute("viewId", aViewId);
@@ -475,19 +475,24 @@ const PanelUI = {
         tempPanel.setAttribute("animate", "false");
       }
       tempPanel.setAttribute("context", "");
       document.getElementById(CustomizableUI.AREA_NAVBAR).appendChild(tempPanel);
       // If the view has a footer, set a convenience class on the panel.
       tempPanel.classList.toggle("cui-widget-panelWithFooter",
                                  viewNode.querySelector(".panel-subview-footer"));
 
-      let multiView = document.createElement("panelmultiview");
+      let multiView = document.createElement(gPhotonStructure ? "photonpanelmultiview" : "panelmultiview");
       multiView.setAttribute("id", "customizationui-widget-multiview");
       multiView.setAttribute("nosubviews", "true");
+      multiView.setAttribute("viewCacheId", "appMenu-viewCache");
+      if (gPhotonStructure) {
+        multiView.setAttribute("mainViewId", viewNode.id);
+        multiView.appendChild(viewNode);
+      }
       tempPanel.appendChild(multiView);
       multiView.setAttribute("mainViewIsSubView", "true");
       multiView.setMainView(viewNode);
       viewNode.classList.add("cui-widget-panelview");
 
       let viewShown = false;
       let panelRemover = () => {
         viewNode.classList.remove("cui-widget-panelview");
@@ -495,17 +500,19 @@ const PanelUI = {
           CustomizableUI.removePanelCloseListeners(tempPanel);
           tempPanel.removeEventListener("popuphidden", panelRemover);
 
           let evt = new CustomEvent("ViewHiding", {detail: viewNode});
           viewNode.dispatchEvent(evt);
         }
         aAnchor.open = false;
 
-        this.multiView.appendChild(viewNode);
+        // Ensure we run the destructor:
+        multiView.instance.destructor();
+
         tempPanel.remove();
       };
 
       // 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) {