Bug 1354082 - part 1: make toggling the photon structure pref at runtime not break the panels, r?mikedeboer draft
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Fri, 05 May 2017 19:25:37 +0100
changeset 575373 a2f9f1a0a52f4cc8a9b3d8d890d0772354a9cf50
parent 575372 39cca5e535e0a306accf1d5bbf7263379f46916d
child 575374 93a70811fdf64f5a65fccd9ad3892a79934ad373
child 575383 0da22498e8f57e13095215f1472c2b1a236d1dec
push id58048
push userbmo:gijskruitbosch+bugs@gmail.com
push dateWed, 10 May 2017 11:46:42 +0000
reviewersmikedeboer
bugs1354082
milestone55.0a1
Bug 1354082 - part 1: make toggling the photon structure pref at runtime not break the panels, r?mikedeboer This is unfortunate, but in order to keep automated tests working when we flip the pref, it would be nice to be able to flip the pref at runtime and have things Just Work. This tries to make that happen. We can remove most of this code post-Photon. MozReview-Commit-ID: 8zbgCGJautO
browser/base/content/browser-sync.js
browser/components/customizableui/CustomizableUI.jsm
browser/components/customizableui/CustomizableWidgets.jsm
browser/components/customizableui/DragPositionManager.jsm
browser/components/customizableui/content/panelUI.js
--- a/browser/base/content/browser-sync.js
+++ b/browser/base/content/browser-sync.js
@@ -463,16 +463,19 @@ var gSync = {
   /* After we are initialized we perform a once-only check for the sync
      button being in "customize purgatory" and if so, move it to the panel.
      This is done primarily for profiles created before SyncedTabs landed,
      where the button defaulted to being in that purgatory.
      We use a preference to ensure we only do it once, so people can still
      customize it away and have it stick.
   */
   maybeMoveSyncedTabsButton() {
+    if (gPhotonStructure) {
+      return;
+    }
     const prefName = "browser.migrated-sync-button";
     let migrated = Services.prefs.getBoolPref(prefName, false);
     if (migrated) {
       return;
     }
     if (!CustomizableUI.getPlacementOfWidget("sync-button")) {
       CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
     }
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -149,16 +149,18 @@ var gSingleWrapperCache = new WeakMap();
 var gListeners = new Set();
 
 var gUIStateBeforeReset = {
   uiCustomizationState: null,
   drawInTitlebar: null,
   currentTheme: null,
 };
 
+var gDefaultPanelPlacements = null;
+
 XPCOMUtils.defineLazyGetter(this, "log", () => {
   let scope = {};
   Cu.import("resource://gre/modules/Console.jsm", scope);
   let debug = Services.prefs.getBoolPref(kPrefCustomizationDebug, false);
   let consoleOptions = {
     maxLogLevel: debug ? "all" : "log",
     prefix: "CustomizableUI",
   };
@@ -221,29 +223,19 @@ var CustomizableUIInternal = {
     }
 
     if (AppConstants.NIGHTLY_BUILD) {
       if (Services.prefs.getBoolPref("extensions.webcompat-reporter.enabled")) {
         panelPlacements.push("webcompat-reporter-button");
       }
     }
 
-    this.registerArea(CustomizableUI.AREA_PANEL, {
-      anchor: "PanelUI-menu-button",
-      type: CustomizableUI.TYPE_MENU_PANEL,
-      defaultPlacements: panelPlacements
-    }, true);
-    PanelWideWidgetTracker.init();
-
-    if (Services.prefs.getBoolPref("browser.photon.structure.enabled")) {
-      this.registerArea(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL, {
-        type: CustomizableUI.TYPE_MENU_PANEL,
-        defaultPlacements: [],
-      }, true);
-    }
+    gDefaultPanelPlacements = panelPlacements;
+    this._updateAreasForPhoton();
+    Services.prefs.addObserver("browser.photon.structure.enabled", this);
 
     let navbarPlacements = [
       "urlbar-container",
       "search-container",
       "bookmarks-menu-button",
       "downloads-button",
       "home-button",
     ];
@@ -300,16 +292,52 @@ var CustomizableUIInternal = {
     this.registerArea(CustomizableUI.AREA_ADDONBAR, {
       type: CustomizableUI.TYPE_TOOLBAR,
       legacy: true,
       defaultPlacements: ["addonbar-closebutton", "status-bar"],
       defaultCollapsed: false,
     }, true);
   },
 
+  _updateAreasForPhoton() {
+    if (Services.prefs.getBoolPref("browser.photon.structure.enabled")) {
+      if (gAreas.has(CustomizableUI.AREA_PANEL)) {
+        this.unregisterArea(CustomizableUI.AREA_PANEL, true);
+      }
+      this.registerArea(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL, {
+        type: CustomizableUI.TYPE_MENU_PANEL,
+        defaultPlacements: [],
+      }, true);
+    } else {
+      if (gAreas.has(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL)) {
+        this.unregisterArea(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL, true);
+      }
+      // In tests we destroy some widgets. Those should be removed from the
+      // default placements when re-registering the panel.
+      let placementsToUse = Array.from(gDefaultPanelPlacements);
+      if (!gPalette.has("e10s-button") && placementsToUse.includes("e10s-button")) {
+        placementsToUse.splice(placementsToUse.indexOf("e10s-button"), 1);
+      }
+      this.registerArea(CustomizableUI.AREA_PANEL, {
+        anchor: "PanelUI-menu-button",
+        type: CustomizableUI.TYPE_MENU_PANEL,
+        defaultPlacements: placementsToUse,
+      }, true);
+      PanelWideWidgetTracker.init();
+    }
+  },
+
+  observe(subject, topic, data) {
+    // Currently only used for pref change observers.
+    if (data == "browser.photon.structure.enabled") {
+      this._updateAreasForPhoton();
+      this.notifyListeners("onPhotonChanged");
+    }
+  },
+
   get _builtinToolbars() {
     let toolbars = new Set([
       CustomizableUI.AREA_NAVBAR,
       CustomizableUI.AREA_BOOKMARKS,
       CustomizableUI.AREA_TABSTRIP,
       CustomizableUI.AREA_ADDONBAR,
     ]);
     if (AppConstants.platform != "macosx") {
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -745,17 +745,28 @@ const CustomizableWidgets = [
           CustomizableUI.removeListener(listener);
         },
 
         onWidgetDrag: (aWidgetId, aArea) => {
           if (aWidgetId != this.id)
             return;
           aArea = aArea || this.currentArea;
           updateCombinedWidgetStyle(node, aArea, true);
-        }
+        },
+
+        // Hack. This can go away when the old menu panel goes away (post photon).
+        // We need it right now for the case where we re-register the old-style
+        // main menu panel if photon is disabled at runtime, and we automatically
+        // put the widgets in there, so they get the right style in the panel.
+        onAreaNodeRegistered(aArea, aContainer) {
+          if (aContainer.ownerDocument == node.ownerDocument &&
+              aArea == CustomizableUI.AREA_PANEL) {
+            updateCombinedWidgetStyle(node, aArea, true);
+          }
+        },
       };
       CustomizableUI.addListener(listener);
 
       return node;
     }
   }, {
     id: "edit-controls",
     type: "custom",
@@ -841,17 +852,28 @@ const CustomizableWidgets = [
           CustomizableUI.removeListener(listener);
         },
 
         onWidgetDrag: (aWidgetId, aArea) => {
           if (aWidgetId != this.id)
             return;
           aArea = aArea || this.currentArea;
           updateCombinedWidgetStyle(node, aArea);
-        }
+        },
+
+        // Hack. This can go away when the old menu panel goes away (post photon).
+        // We need it right now for the case where we re-register the old-style
+        // main menu panel if photon is disabled at runtime, and we automatically
+        // put the widgets in there, so they get the right style in the panel.
+        onAreaNodeRegistered(aArea, aContainer) {
+          if (aContainer.ownerDocument == node.ownerDocument &&
+              aArea == CustomizableUI.AREA_PANEL) {
+            updateCombinedWidgetStyle(node, aArea);
+          }
+        },
       };
       CustomizableUI.addListener(listener);
 
       return node;
     }
   },
   {
     id: "feed-button",
--- a/browser/components/customizableui/DragPositionManager.jsm
+++ b/browser/components/customizableui/DragPositionManager.jsm
@@ -1,15 +1,19 @@
 /* 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/. */
 
 "use strict";
 
 Components.utils.import("resource:///modules/CustomizableUI.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyPreferenceGetter(this, "gPhotonStructure",
+  "browser.photon.structure.enabled", false);
 
 var gManagers = new WeakMap();
 
 const kPaletteId = "customization-palette";
 const kPlaceholderClass = "panel-customization-placeholder";
 
 this.EXPORTED_SYMBOLS = ["DragPositionManager"];
 
@@ -374,17 +378,17 @@ AreaPositionManager.prototype = {
       rv = rv[aDirection + "Sibling"];
     } while (rv && rv.getAttribute("hidden") == "true")
     return rv;
   }
 }
 
 var DragPositionManager = {
   start(aWindow) {
-    let areas = [CustomizableUI.AREA_PANEL];
+    let areas = gPhotonStructure ? [] : [CustomizableUI.AREA_PANEL];
     areas = areas.map((area) => CustomizableUI.getCustomizeTargetForArea(area, aWindow));
     areas.push(aWindow.document.getElementById(kPaletteId));
     for (let areaNode of areas) {
       let positionManager = gManagers.get(areaNode);
       if (positionManager) {
         positionManager.update(areaNode);
       } else {
         gManagers.set(areaNode, new AreaPositionManager(areaNode));
--- a/browser/components/customizableui/content/panelUI.js
+++ b/browser/components/customizableui/content/panelUI.js
@@ -40,28 +40,17 @@ const PanelUI = {
       addonNotificationContainer: gPhotonStructure ? "appMenu-addon-banners" : "PanelUI-footer-addons",
 
       overflowFixedList: gPhotonStructure ? "widget-overflow-fixed-list" : "",
     };
   },
 
   _initialized: false,
   init() {
-    for (let [k, v] of Object.entries(this.kElements)) {
-      if (!v) {
-        continue;
-      }
-      // Need to do fresh let-bindings per iteration
-      let getKey = k;
-      let id = v;
-      this.__defineGetter__(getKey, function() {
-        delete this[getKey];
-        return this[getKey] = document.getElementById(id);
-      });
-    }
+    this._initElements();
 
     this.notifications = [];
     this.menuButton.addEventListener("mousedown", this);
     this.menuButton.addEventListener("keypress", this);
     this._overlayScrollListenerBoundFn = this._overlayScrollListener.bind(this);
 
     Services.obs.addObserver(this, "fullscreen-nav-toolbox");
     Services.obs.addObserver(this, "panelUI-notification-main-action");
@@ -70,23 +59,53 @@ const PanelUI = {
     window.addEventListener("fullscreen", this);
     window.matchMedia("(-moz-overlay-scrollbars)").addListener(this._overlayScrollListenerBoundFn);
     CustomizableUI.addListener(this);
 
     for (let event of this.kEvents) {
       this.notificationPanel.addEventListener(event, this);
     }
 
+    this._initPhotonPanel();
+
+    this._initialized = true;
+  },
+
+  reinit() {
+    this._removeEventListeners();
+    // If the Photon pref changes, we need to re-init our element references.
+    this._initElements();
+    this._initPhotonPanel();
+    delete this._readyPromise;
+    this._isReady = false;
+  },
+
+  // We do this sync on init because in order to have the overflow button show up
+  // we need to know whether anything is in the permanent panel area.
+  _initPhotonPanel() {
     if (gPhotonStructure) {
       this.overflowFixedList.hidden = false;
       this.overflowFixedList.nextSibling.hidden = false;
       CustomizableUI.registerMenuPanel(this.overflowFixedList, CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
     }
+  },
 
-    this._initialized = true;
+  _initElements() {
+    for (let [k, v] of Object.entries(this.kElements)) {
+      if (!v) {
+        continue;
+      }
+      // Need to do fresh let-bindings per iteration
+      let getKey = k;
+      let id = v;
+      this.__defineGetter__(getKey, function() {
+        delete this[getKey];
+        return this[getKey] = document.getElementById(id);
+      });
+    }
   },
 
   _eventListenersAdded: false,
   _ensureEventListenersAdded() {
     if (this._eventListenersAdded)
       return;
     this._addEventListeners();
   },
@@ -95,28 +114,35 @@ const PanelUI = {
     for (let event of this.kEvents) {
       this.panel.addEventListener(event, this);
     }
 
     this.helpView.addEventListener("ViewShowing", this._onHelpViewShow);
     this._eventListenersAdded = true;
   },
 
-  uninit() {
+  _removeEventListeners() {
     for (let event of this.kEvents) {
       this.panel.removeEventListener(event, this);
+    }
+    this.helpView.removeEventListener("ViewShowing", this._onHelpViewShow);
+    this._eventListenersAdded = false;
+  },
+
+  uninit() {
+    this._removeEventListeners();
+    for (let event of this.kEvents) {
       this.notificationPanel.removeEventListener(event, this);
     }
 
     Services.obs.removeObserver(this, "fullscreen-nav-toolbox");
     Services.obs.removeObserver(this, "panelUI-notification-main-action");
     Services.obs.removeObserver(this, "panelUI-notification-dismissed");
 
     window.removeEventListener("fullscreen", this);
-    this.helpView.removeEventListener("ViewShowing", this._onHelpViewShow);
     this.menuButton.removeEventListener("mousedown", this);
     this.menuButton.removeEventListener("keypress", this);
     window.matchMedia("(-moz-overlay-scrollbars)").removeListener(this._overlayScrollListenerBoundFn);
     CustomizableUI.removeListener(this);
     this._overlayScrollListenerBoundFn = null;
   },
 
   /**
@@ -361,16 +387,23 @@ const PanelUI = {
    *        mode will handle calling beginBatchUpdate and endBatchUpdate.
    *
    * @return a Promise that resolves once the panel is ready to roll.
    */
   ensureReady(aCustomizing = false) {
     if (this._readyPromise) {
       return this._readyPromise;
     }
+    this._ensureEventListenersAdded();
+    if (gPhotonStructure) {
+      this.panel.hidden = false;
+      this._readyPromise = Promise.resolve();
+      this._isReady = true;
+      return this._readyPromise;
+    }
     this._readyPromise = Task.spawn(function*() {
       if (!this._initialized) {
         yield new Promise(resolve => {
           let delayedStartupObserver = (aSubject, aTopic, aData) => {
             if (aSubject == window) {
               Services.obs.removeObserver(delayedStartupObserver, "browser-delayed-startup-finished");
               resolve();
             }
@@ -554,16 +587,20 @@ const PanelUI = {
   disableSingleSubviewPanelAnimations() {
     this._disableAnimations = true;
   },
 
   enableSingleSubviewPanelAnimations() {
     this._disableAnimations = false;
   },
 
+  onPhotonChanged() {
+    this.reinit();
+  },
+
   onWidgetAfterDOMChange(aNode, aNextNode, aContainer, aWasRemoval) {
     if (aContainer != this.contents) {
       return;
     }
     if (aWasRemoval) {
       aNode.removeAttribute("auto-hyphens");
     }
   },