Bug 1389550 - Remove sync reflow during about:preferences loading and schedule them with animationend event, r=jaws, r=florian draft
authorTimothy Guan-tin Chien <timdream@gmail.com>
Thu, 17 Aug 2017 12:01:00 +0800
changeset 652947 2f9676786d892d4b4236dac96a7cd33fe8ab4e8d
parent 652924 b6b8e616de32af50c9a174006b3a7ed914130aa5
child 728248 d745eae1a96ed9fd8aea09300e5634f4830dbc05
push id76225
push usertimdream@gmail.com
push dateFri, 25 Aug 2017 14:07:32 +0000
reviewersjaws, florian
bugs1389550
milestone57.0a1
Bug 1389550 - Remove sync reflow during about:preferences loading and schedule them with animationend event, r=jaws, r=florian To remove the sync reflow during page load, the code that reads the layout would need to take place after the very first paint. Here the first paint is detected by listening to an animationend event triggered by a no-op CSS animation. Flash of misplaced content is avoid by keeping these elements visually hidden until the code puts them into the right place. MozReview-Commit-ID: 9MrVhcnnPG
browser/components/preferences/in-content-new/findInPage.js
browser/components/preferences/in-content-new/preferences.js
browser/components/preferences/in-content-new/preferences.xul
browser/themes/shared/incontentprefs/preferences.inc.css
--- a/browser/components/preferences/in-content-new/findInPage.js
+++ b/browser/components/preferences/in-content-new/findInPage.js
@@ -16,17 +16,17 @@ var gSearchResultsPane = {
     }
     this.inited = true;
     this.searchInput = document.getElementById("searchInput");
     this.searchInput.hidden = !Services.prefs.getBoolPref("browser.preferences.search");
     if (!this.searchInput.hidden) {
       this.searchInput.addEventListener("input", this);
       this.searchInput.addEventListener("command", this);
       window.addEventListener("DOMContentLoaded", () => {
-        this.searchInput.focus();
+        first_paint(() => this.searchInput.focus());
       });
       // Initialize other panes in an idle callback.
       window.requestIdleCallback(() => this.initializeCategories());
     }
     let strings = this.strings;
     this.searchInput.placeholder = AppConstants.platform == "win" ?
       strings.getString("searchInput.labelWin") :
       strings.getString("searchInput.labelUnix");
--- a/browser/components/preferences/in-content-new/preferences.js
+++ b/browser/components/preferences/in-content-new/preferences.js
@@ -44,16 +44,24 @@ function register_module(categoryName, c
       categoryObject.init();
       this.inited = true;
     }
   });
 }
 
 document.addEventListener("DOMContentLoaded", init_all, {once: true});
 
+function first_paint(callback) {
+  window.addEventListener("animationend", callback, { once: true });
+}
+
+first_paint(() => {
+  first_paint = (callback) => callback();
+});
+
 function init_all() {
   document.documentElement.instantApply = true;
 
   gSubDialog.init();
   register_module("paneGeneral", gMainPane);
   register_module("paneSearch", gSearchPane);
   register_module("panePrivacy", gPrivacyPane);
   register_module("paneContainers", gContainersPane);
@@ -71,17 +79,17 @@ function init_all() {
   });
   categories.addEventListener("mousedown", function() {
     this.removeAttribute("keyboard-navigation");
   });
 
   window.addEventListener("hashchange", onHashChange);
   gotoPref();
 
-  init_dynamic_padding();
+  first_paint(init_dynamic_padding);
 
   var initFinished = new CustomEvent("Initialized", {
     "bubbles": true,
     "cancelable": true
   });
   document.dispatchEvent(initFinished);
 
   let helpButton = document.querySelector(".help-button");
@@ -119,16 +127,19 @@ function init_dynamic_padding() {
       bottom: ${reducedHelpButtonBottom / 2}px;
     }
   }
   `;
   let mediaStyle = document.createElementNS("http://www.w3.org/1999/xhtml", "html:style");
   mediaStyle.setAttribute("type", "text/css");
   mediaStyle.appendChild(document.createCDATASection(mediaRule));
   document.documentElement.appendChild(mediaStyle);
+
+  categories.classList.remove("visually-hidden");
+  helpButton.classList.remove("visually-hidden");
 }
 
 function telemetryBucketForCategory(category) {
   category = category.toLowerCase();
   switch (category) {
     case "containers":
     case "general":
     case "privacy":
@@ -204,18 +215,20 @@ function gotoPref(aCategory) {
   if (item) {
     categories.selectedItem = item;
   } else {
     categories.clearSelection();
   }
   window.history.replaceState(category, document.title);
   search(category, "data-category", subcategory, "data-subcategory");
 
-  let mainContent = document.querySelector(".main-content");
-  mainContent.scrollTop = 0;
+  first_paint(() => {
+    let mainContent = document.querySelector(".main-content");
+    mainContent.scrollTop = 0;
+  });
 
   Services.telemetry
           .getHistogramById("FX_PREFERENCES_CATEGORY_OPENED_V2")
           .add(telemetryBucketForCategory(friendlyName));
 }
 
 function search(aQuery, aAttribute, aSubquery, aSubAttribute) {
   let mainPrefPane = document.getElementById("mainPrefPane");
--- a/browser/components/preferences/in-content-new/preferences.xul
+++ b/browser/components/preferences/in-content-new/preferences.xul
@@ -118,17 +118,17 @@
     <stringbundle id="appManagerBundle"
                   src="chrome://browser/locale/preferences/applicationManager.properties"/>
   </stringbundleset>
 
   <stack flex="1">
   <hbox flex="1">
 
     <!-- category list -->
-    <richlistbox id="categories">
+    <richlistbox id="categories" class="visually-hidden">
       <richlistitem id="category-general"
                     class="category"
                     value="paneGeneral"
                     helpTopic="prefs-main"
                     tooltiptext="&paneGeneral.title;"
                     align="center">
         <image class="category-icon"/>
         <label class="category-name" flex="1">&paneGeneral.title;</label>
@@ -170,17 +170,17 @@
         <label class="category-name" flex="1">&paneSync1.title;</label>
       </richlistitem>
     </richlistbox>
 
     <keyset>
       <key key="&focusSearch1.key;" modifiers="accel" id="focusSearch1" oncommand="gSearchResultsPane.searchInput.focus();"/>
     </keyset>
 
-    <html:a class="help-button" target="_blank" aria-label="&helpButton2.label;">&helpButton2.label;</html:a>
+    <html:a class="help-button visually-hidden" target="_blank" aria-label="&helpButton2.label;">&helpButton2.label;</html:a>
 
     <vbox class="main-content" flex="1" align="start">
       <vbox class="pane-container">
         <hbox class="search-container" pack="end">
           <textbox type="search" id="searchInput" hidden="true" clickSelectsAll="true"/>
         </hbox>
         <prefpane id="mainPrefPane">
 #include searchResults.xul
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -10,16 +10,25 @@
 }
 
 :root {
   --in-content-category-background: #fafafc;
 }
 
 .main-content {
   padding-top: 0;
+  /* XXX: A no-op animation solely for the purpose of triggering an animationend
+     event after the first paint. */
+  animation: noop 0s;
+}
+
+.main-content {
+}
+
+@keyframes noop {
 }
 
 .pane-container {
   /* A workaround to keep the container always float on the `top: 0` (Bug 1377009) */
   display: block;
   width: 664px;
   min-width: 530px;
 }