Bug 1354082 - part 1: make toggling the photon structure pref at runtime not break the panels, r?mikedeboer
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Fri, 05 May 2017 19:25:37 +0100
changeset 575578 2b6df54d24d35294cc547be0b7abe5396ddec6b6
parent 575577 06b2b1e31efdf77b9eced71db930b6e291175e3d
child 575579 57c413620aff496069b8cf371b8980a1fa432d72
push id58111
push userbmo:gijskruitbosch+bugs@gmail.com
push dateWed, 10 May 2017 17:46:21 +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");
     }
   },