Bug 1448303 - Refactor TabsInTitlebar initial update handling. r=florian draft
authorDão Gottwald <dao@mozilla.com>
Wed, 28 Mar 2018 20:36:21 +0200
changeset 773963 fdc23cfe4902ffd831adf0f9418177342906b241
parent 773797 a456475502b80a1264642d9eaee9394a8fad8315
child 773965 555b9e07b0612793b7021f9ea42cebe09efebedc
push id104361
push userdgottwald@mozilla.com
push dateWed, 28 Mar 2018 18:36:50 +0000
reviewersflorian
bugs1448303
milestone61.0a1
Bug 1448303 - Refactor TabsInTitlebar initial update handling. r=florian MozReview-Commit-ID: BQTe6cVyHo1
browser/base/content/browser-tabsintitlebar-stub.js
browser/base/content/browser-tabsintitlebar.js
browser/base/content/browser.js
browser/base/content/tabbrowser.js
browser/base/content/test/performance/browser_windowopen.js
--- a/browser/base/content/browser-tabsintitlebar-stub.js
+++ b/browser/base/content/browser-tabsintitlebar-stub.js
@@ -3,13 +3,14 @@
  * 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/. */
 
 // This file is used as a stub object for platforms which
 // don't have CAN_DRAW_IN_TITLEBAR defined.
 
 var TabsInTitlebar = {
   init() {},
+  whenWindowLayoutReady() {},
   uninit() {},
   allowedBy() {},
   update() {},
   enabled: false,
 };
--- a/browser/base/content/browser-tabsintitlebar.js
+++ b/browser/base/content/browser-tabsintitlebar.js
@@ -40,17 +40,23 @@ var TabsInTitlebar = {
     };
     CustomizableUI.addListener(this);
 
     window.addEventListener("resolutionchange", this);
     window.addEventListener("resize", this);
 
     gDragSpaceObserver.init();
 
-    this.update(true);
+    this._initialized = true;
+    this.update();
+  },
+
+  whenWindowLayoutReady() {
+    this._windowLayoutReady = true;
+    this.update();
   },
 
   allowedBy(condition, allow) {
     if (allow) {
       if (condition in this._disallowed) {
         delete this._disallowed[condition];
         this.update();
       }
@@ -97,185 +103,182 @@ var TabsInTitlebar = {
         if (oldSizeMode == "fullscreen") {
           break;
         }
         this.update();
         break;
     }
   },
 
-  onDOMContentLoaded() {
-    this._domLoaded = true;
-    this.update();
-  },
-
   _onMenuMutate(aMutations) {
     for (let mutation of aMutations) {
       if (mutation.attributeName == "inactive" ||
           mutation.attributeName == "autohide") {
         TabsInTitlebar.update();
         return;
       }
     }
   },
 
+  _initialized: false,
+  _windowLayoutReady: false,
   _disallowed: {},
   _prefName: "browser.tabs.drawInTitlebar",
   _lastSizeMode: null,
-  _domLoaded: false,
 
   _readPref() {
     this.allowedBy("pref",
                    Services.prefs.getBoolPref(this._prefName));
   },
 
-  update(aFromInit = false) {
-    let $ = id => document.getElementById(id);
-    let rect = ele => ele.getBoundingClientRect();
-    let verticalMargins = cstyle => parseFloat(cstyle.marginBottom) + parseFloat(cstyle.marginTop);
-
-    if (window.fullScreen)
-      return;
-
-    // We want to do this from initialization anyway, so that the
-    // "chromemargin" attributes are all correctly setup, but we don't want to
-    // do this before the DOM loads again, to prevent spurious reflows that
-    // will be superseded by the one on onDOMContentLoaded.
-    if (!this._domLoaded && !aFromInit) {
+  update() {
+    if (!this._initialized || window.fullScreen) {
       return;
     }
 
     let allowed = (Object.keys(this._disallowed)).length == 0;
+    if (allowed) {
+      document.documentElement.setAttribute("tabsintitlebar", "true");
+    } else {
+      document.documentElement.removeAttribute("tabsintitlebar");
+    }
+
+    updateTitlebarDisplay();
+
+    this._layOutTitlebar(allowed);
+
+    ToolbarIconColor.inferFromText("tabsintitlebar", allowed);
+  },
+
+  _layOutTitlebar(drawInTitlebar) {
+    if (!this._windowLayoutReady) {
+      return;
+    }
+
+    let $ = id => document.getElementById(id);
+    let rect = ele => ele.getBoundingClientRect();
+    let verticalMargins = cstyle => parseFloat(cstyle.marginBottom) + parseFloat(cstyle.marginTop);
 
     let titlebar = $("titlebar");
-    let titlebarContent = $("titlebar-content");
-    let titlebarButtons = $("titlebar-buttonbox");
     let menubar = $("toolbar-menubar");
 
-    if (allowed) {
-      // We set the tabsintitlebar attribute first so that our CSS for
-      // tabsintitlebar manifests before we do our measurements.
-      document.documentElement.setAttribute("tabsintitlebar", "true");
-      updateTitlebarDisplay();
-
-      // Reset the custom titlebar height if the menubar is shown,
-      // because we will want to calculate its original height.
-      let buttonsShouldMatchTabHeight =
-        AppConstants.isPlatformAndVersionAtLeast("win", "10.0") ||
-        AppConstants.platform == "linux";
-      if (buttonsShouldMatchTabHeight &&
-          (menubar.getAttribute("inactive") != "true" ||
-           menubar.getAttribute("autohide") != "true")) {
-        titlebarButtons.style.removeProperty("height");
-      }
-
-      // Try to avoid reflows in this code by calculating dimensions first and
-      // then later set the properties affecting layout together in a batch.
-
-      // Get the height of the tabs toolbar:
-      let fullTabsHeight = rect($("TabsToolbar")).height;
-
-      // Buttons first:
-      let captionButtonsBoxWidth = rect(titlebarButtons).width;
-
-      let secondaryButtonsWidth, menuHeight, fullMenuHeight, menuStyles;
-      if (AppConstants.platform == "macosx") {
-        secondaryButtonsWidth = rect($("titlebar-secondary-buttonbox")).width;
-        // No need to look up the menubar stuff on OS X:
-        menuHeight = 0;
-        fullMenuHeight = 0;
-      } else {
-        // Otherwise, get the height and margins separately for the menubar
-        menuHeight = rect(menubar).height;
-        menuStyles = window.getComputedStyle(menubar);
-        fullMenuHeight = verticalMargins(menuStyles) + menuHeight;
-      }
-
-      // And get the height of what's in the titlebar:
-      let titlebarContentHeight = rect(titlebarContent).height;
-
-      // Begin setting CSS properties which will cause a reflow
-
-      // Adjust the window controls to span the entire
-      // tab strip height if we're not showing a menu bar.
-      if (buttonsShouldMatchTabHeight && !menuHeight) {
-        titlebarContentHeight = fullTabsHeight;
-        titlebarButtons.style.height = titlebarContentHeight + "px";
-      }
-
-      // If the menubar is around (menuHeight is non-zero), try to adjust
-      // its full height (i.e. including margins) to match the titlebar,
-      // by changing the menubar's bottom padding
-      if (menuHeight) {
-        // Calculate the difference between the titlebar's height and that of the menubar
-        let menuTitlebarDelta = titlebarContentHeight - fullMenuHeight;
-        let paddingBottom;
-        // The titlebar is bigger:
-        if (menuTitlebarDelta > 0) {
-          fullMenuHeight += menuTitlebarDelta;
-          // If there is already padding on the menubar, we need to add that
-          // to the difference so the total padding is correct:
-          if ((paddingBottom = menuStyles.paddingBottom)) {
-            menuTitlebarDelta += parseFloat(paddingBottom);
-          }
-          menubar.style.paddingBottom = menuTitlebarDelta + "px";
-        // The menubar is bigger, but has bottom padding we can remove:
-        } else if (menuTitlebarDelta < 0 && (paddingBottom = menuStyles.paddingBottom)) {
-          let existingPadding = parseFloat(paddingBottom);
-          // menuTitlebarDelta is negative; work out what's left, but don't set negative padding:
-          let desiredPadding = Math.max(0, existingPadding + menuTitlebarDelta);
-          menubar.style.paddingBottom = desiredPadding + "px";
-          // We've changed the menu height now:
-          fullMenuHeight += desiredPadding - existingPadding;
-        }
-      }
-
-      // Next, we calculate how much we need to stretch the titlebar down to
-      // go all the way to the bottom of the tab strip, if necessary.
-      let tabAndMenuHeight = fullTabsHeight + fullMenuHeight;
-
-      if (tabAndMenuHeight > titlebarContentHeight) {
-        // We need to increase the titlebar content's outer height (ie including margins)
-        // to match the tab and menu height:
-        let extraMargin = tabAndMenuHeight - titlebarContentHeight;
-        if (AppConstants.platform != "macosx") {
-          titlebarContent.style.marginBottom = extraMargin + "px";
-        }
-
-        titlebarContentHeight += extraMargin;
-      } else {
-        titlebarContent.style.removeProperty("margin-bottom");
-      }
-
-      // Then add a negative margin to the titlebar, so that the following elements
-      // will overlap it by the greater of the titlebar height or the tabstrip+menu.
-      let maxTitlebarOrTabsHeight = Math.max(titlebarContentHeight, tabAndMenuHeight);
-      titlebar.style.marginBottom = "-" + maxTitlebarOrTabsHeight + "px";
-
-      // Finally, size the placeholders:
-      if (AppConstants.platform == "macosx") {
-        this._sizePlaceholder("fullscreen-button", secondaryButtonsWidth);
-      }
-      this._sizePlaceholder("caption-buttons", captionButtonsBoxWidth);
-
-    } else {
-      document.documentElement.removeAttribute("tabsintitlebar");
-      updateTitlebarDisplay();
-
+    if (!drawInTitlebar) {
       if (AppConstants.platform == "macosx") {
         let secondaryButtonsWidth = rect($("titlebar-secondary-buttonbox")).width;
         this._sizePlaceholder("fullscreen-button", secondaryButtonsWidth);
       }
 
       // Reset styles that might have been modified:
       titlebar.style.marginBottom = "";
       menubar.style.paddingBottom = "";
+      return;
+    }
+
+    let titlebarContent = $("titlebar-content");
+    let titlebarButtons = $("titlebar-buttonbox");
+
+    // Reset the custom titlebar height if the menubar is shown,
+    // because we will want to calculate its original height.
+    let buttonsShouldMatchTabHeight =
+      AppConstants.isPlatformAndVersionAtLeast("win", "10.0") ||
+      AppConstants.platform == "linux";
+    if (buttonsShouldMatchTabHeight &&
+        (menubar.getAttribute("inactive") != "true" ||
+         menubar.getAttribute("autohide") != "true")) {
+      titlebarButtons.style.removeProperty("height");
+    }
+
+    // Try to avoid reflows in this code by calculating dimensions first and
+    // then later set the properties affecting layout together in a batch.
+
+    // Get the height of the tabs toolbar:
+    let fullTabsHeight = rect($("TabsToolbar")).height;
+
+    // Buttons first:
+    let captionButtonsBoxWidth = rect(titlebarButtons).width;
+
+    let secondaryButtonsWidth, menuHeight, fullMenuHeight, menuStyles;
+    if (AppConstants.platform == "macosx") {
+      secondaryButtonsWidth = rect($("titlebar-secondary-buttonbox")).width;
+      // No need to look up the menubar stuff on OS X:
+      menuHeight = 0;
+      fullMenuHeight = 0;
+    } else {
+      // Otherwise, get the height and margins separately for the menubar
+      menuHeight = rect(menubar).height;
+      menuStyles = window.getComputedStyle(menubar);
+      fullMenuHeight = verticalMargins(menuStyles) + menuHeight;
+    }
+
+    // And get the height of what's in the titlebar:
+    let titlebarContentHeight = rect(titlebarContent).height;
+
+    // Begin setting CSS properties which will cause a reflow
+
+    // Adjust the window controls to span the entire
+    // tab strip height if we're not showing a menu bar.
+    if (buttonsShouldMatchTabHeight && !menuHeight) {
+      titlebarContentHeight = fullTabsHeight;
+      titlebarButtons.style.height = titlebarContentHeight + "px";
     }
 
-    ToolbarIconColor.inferFromText("tabsintitlebar", TabsInTitlebar.enabled);
+    // If the menubar is around (menuHeight is non-zero), try to adjust
+    // its full height (i.e. including margins) to match the titlebar,
+    // by changing the menubar's bottom padding
+    if (menuHeight) {
+      // Calculate the difference between the titlebar's height and that of the menubar
+      let menuTitlebarDelta = titlebarContentHeight - fullMenuHeight;
+      let paddingBottom;
+      // The titlebar is bigger:
+      if (menuTitlebarDelta > 0) {
+        fullMenuHeight += menuTitlebarDelta;
+        // If there is already padding on the menubar, we need to add that
+        // to the difference so the total padding is correct:
+        if ((paddingBottom = menuStyles.paddingBottom)) {
+          menuTitlebarDelta += parseFloat(paddingBottom);
+        }
+        menubar.style.paddingBottom = menuTitlebarDelta + "px";
+      // The menubar is bigger, but has bottom padding we can remove:
+      } else if (menuTitlebarDelta < 0 && (paddingBottom = menuStyles.paddingBottom)) {
+        let existingPadding = parseFloat(paddingBottom);
+        // menuTitlebarDelta is negative; work out what's left, but don't set negative padding:
+        let desiredPadding = Math.max(0, existingPadding + menuTitlebarDelta);
+        menubar.style.paddingBottom = desiredPadding + "px";
+        // We've changed the menu height now:
+        fullMenuHeight += desiredPadding - existingPadding;
+      }
+    }
+
+    // Next, we calculate how much we need to stretch the titlebar down to
+    // go all the way to the bottom of the tab strip, if necessary.
+    let tabAndMenuHeight = fullTabsHeight + fullMenuHeight;
+
+    if (tabAndMenuHeight > titlebarContentHeight) {
+      // We need to increase the titlebar content's outer height (ie including margins)
+      // to match the tab and menu height:
+      let extraMargin = tabAndMenuHeight - titlebarContentHeight;
+      if (AppConstants.platform != "macosx") {
+        titlebarContent.style.marginBottom = extraMargin + "px";
+      }
+
+      titlebarContentHeight += extraMargin;
+    } else {
+      titlebarContent.style.removeProperty("margin-bottom");
+    }
+
+    // Then add a negative margin to the titlebar, so that the following elements
+    // will overlap it by the greater of the titlebar height or the tabstrip+menu.
+    let maxTitlebarOrTabsHeight = Math.max(titlebarContentHeight, tabAndMenuHeight);
+    titlebar.style.marginBottom = "-" + maxTitlebarOrTabsHeight + "px";
+
+    // Finally, size the placeholders:
+    if (AppConstants.platform == "macosx") {
+      this._sizePlaceholder("fullscreen-button", secondaryButtonsWidth);
+    }
+    this._sizePlaceholder("caption-buttons", captionButtonsBoxWidth);
   },
 
   _sizePlaceholder(type, width) {
     Array.forEach(document.querySelectorAll(".titlebar-placeholder[type='" + type + "']"),
                   function(node) { node.style.width = width + "px"; });
   },
 
   uninit() {
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1198,21 +1198,22 @@ var gBrowserInit = {
       document.documentElement.setAttribute("width", width);
       document.documentElement.setAttribute("height", height);
 
       if (width < TARGET_WIDTH && height < TARGET_HEIGHT) {
         document.documentElement.setAttribute("sizemode", "maximized");
       }
     }
 
+    // Update the chromemargin attribute so the window can be sized correctly.
+    window.TabBarVisibility.update();
+    TabsInTitlebar.init();
+
     new LightweightThemeConsumer(document);
     CompactTheme.init();
-
-    TabsInTitlebar.init();
-
     if (window.matchMedia("(-moz-os-version: windows-win8)").matches &&
         window.matchMedia("(-moz-windows-default-theme)").matches) {
       let windowFrameColor = new Color(...ChromeUtils.import("resource:///modules/Windows8WindowFrameColor.jsm", {})
                                             .Windows8WindowFrameColor.get());
       // Default to black for foreground text.
       if (!windowFrameColor.isContrastRatioAcceptable(new Color(0, 0, 0))) {
         document.documentElement.setAttribute("darkwindowframe", "true");
       }
@@ -1262,32 +1263,31 @@ var gBrowserInit = {
       }
       initBrowser.removeAttribute("blank");
     }
 
     gBrowser.updateBrowserRemoteness(initBrowser, isRemote, {
       remoteType, sameProcessAsFrameLoader
     });
 
-    gUIDensity.init();
-
     // Hack to ensure that the about:home favicon is loaded
     // instantaneously, to avoid flickering and improve perceived performance.
     this._callWithURIToLoad(uriToLoad => {
       if (uriToLoad == "about:home") {
         gBrowser.setIcon(gBrowser.selectedTab, "chrome://branding/content/icon32.png");
       } else if (uriToLoad == "about:privatebrowsing") {
         gBrowser.setIcon(gBrowser.selectedTab, "chrome://browser/skin/privatebrowsing/favicon.svg");
       }
     });
 
     this._setInitialFocus();
 
-    window.TabBarVisibility.update();
-    TabsInTitlebar.onDOMContentLoaded();
+    // Update the UI density before TabsInTitlebar lays out the titlbar.
+    gUIDensity.init();
+    TabsInTitlebar.whenWindowLayoutReady();
   },
 
   onLoad() {
     gBrowser.addEventListener("DOMUpdateBlockedPopups", gPopupBlockerObserver);
 
     Services.obs.addObserver(gPluginHandler.NPAPIPluginCrashed, "plugin-crashed");
 
     window.addEventListener("AppCommand", HandleAppCommandEvent, true);
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -4706,17 +4706,18 @@ var StatusPanel = {
 };
 
 var TabBarVisibility = {
   _initialUpdateDone: false,
 
   update() {
     let toolbar = document.getElementById("TabsToolbar");
     let collapse = false;
-    if (gBrowser.tabs.length - gBrowser._removingTabs.length == 1) {
+    if (!gBrowser /* gBrowser isn't initialized yet */ ||
+        gBrowser.tabs.length - gBrowser._removingTabs.length == 1) {
       collapse = !window.toolbar.visible;
     }
 
     if (collapse == toolbar.collapsed && this._initialUpdateDone) {
       return;
     }
     this._initialUpdateDone = true;
 
--- a/browser/base/content/test/performance/browser_windowopen.js
+++ b/browser/base/content/test/performance/browser_windowopen.js
@@ -18,33 +18,33 @@ const EXPECTED_REFLOWS = [
    */
 ];
 
 if (Services.appinfo.OS == "WINNT") {
   EXPECTED_REFLOWS.push(
     {
       stack: [
         "verticalMargins@chrome://browser/content/browser-tabsintitlebar.js",
+        "_layOutTitlebar@chrome://browser/content/browser-tabsintitlebar.js",
         "update@chrome://browser/content/browser-tabsintitlebar.js",
-        "onDOMContentLoaded@chrome://browser/content/browser-tabsintitlebar.js",
-        "onDOMContentLoaded@chrome://browser/content/browser.js",
+        "whenWindowLayoutReady@chrome://browser/content/browser-tabsintitlebar.js",
       ],
       maxCount: 2, // This number should only ever go down - never up.
     },
   );
 }
 
 if (Services.appinfo.OS == "WINNT" || Services.appinfo.OS == "Darwin") {
   EXPECTED_REFLOWS.push(
     {
       stack: [
         "rect@chrome://browser/content/browser-tabsintitlebar.js",
+        "_layOutTitlebar@chrome://browser/content/browser-tabsintitlebar.js",
         "update@chrome://browser/content/browser-tabsintitlebar.js",
-        "onDOMContentLoaded@chrome://browser/content/browser-tabsintitlebar.js",
-        "onDOMContentLoaded@chrome://browser/content/browser.js",
+        "whenWindowLayoutReady@chrome://browser/content/browser-tabsintitlebar.js",
       ],
       // These numbers should only ever go down - never up.
       maxCount: Services.appinfo.OS == "WINNT" ? 5 : 4,
     },
   );
 }
 
 /*
@@ -117,24 +117,26 @@ add_task(async function() {
 
     if (Services.appinfo.OS == "WINNT" && win.windowState == win.STATE_MAXIMIZED) {
       // The reflows below are triggered by maximizing the window after
       // layout. They should be fixed by bug 1447864.
       EXPECTED_REFLOWS.push(
         {
           stack: [
             "rect@chrome://browser/content/browser-tabsintitlebar.js",
+            "_layOutTitlebar@chrome://browser/content/browser-tabsintitlebar.js",
             "update@chrome://browser/content/browser-tabsintitlebar.js",
             "handleEvent@chrome://browser/content/browser-tabsintitlebar.js",
           ],
           maxCount: 4,
         },
         {
           stack: [
             "verticalMargins@chrome://browser/content/browser-tabsintitlebar.js",
+            "_layOutTitlebar@chrome://browser/content/browser-tabsintitlebar.js",
             "update@chrome://browser/content/browser-tabsintitlebar.js",
             "handleEvent@chrome://browser/content/browser-tabsintitlebar.js",
           ],
           maxCount: 2,
         },
       );
     }