Bug 1383367: Part 2 - Add promise helpers to defer operations until after a reflow. r=mconley
☠☠ backed out by c23906663721 ☠ ☠
authorKris Maglione <maglione.k@gmail.com>
Mon, 24 Jul 2017 20:16:07 -0700
changeset 420568 9c966265dc4834242369f56bf7bac91abf44998e
parent 420567 46d1fa12a0829046f2bee4ffd10d7af38616bba9
child 420569 ecaae0733ca1dc08db7e8defe5c572f8d708d8a6
push id7566
push usermtabara@mozilla.com
push dateWed, 02 Aug 2017 08:25:16 +0000
treeherdermozilla-beta@86913f512c3c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmconley
bugs1383367
milestone56.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 1383367: Part 2 - Add promise helpers to defer operations until after a reflow. r=mconley The main use of these helpers is to defer operations which would cause a layout flush until after the next reflow if, and only if, a flush is currently pending. MozReview-Commit-ID: 6VwMioldQ2O
toolkit/modules/BrowserUtils.jsm
--- a/toolkit/modules/BrowserUtils.jsm
+++ b/toolkit/modules/BrowserUtils.jsm
@@ -11,16 +11,58 @@ const {interfaces: Ci, utils: Cu, classe
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
   "resource://gre/modules/PlacesUtils.jsm");
 
 Cu.importGlobalProperties(["URL"]);
 
+let reflowObservers = new WeakMap();
+
+function ReflowObserver(doc) {
+  this._doc = doc;
+
+  doc.docShell.addWeakReflowObserver(this);
+  reflowObservers.set(this._doc, this);
+
+  this.callbacks = [];
+}
+
+ReflowObserver.prototype = {
+  QueryInterface: XPCOMUtils.generateQI(["nsIReflowObserver", "nsISupportsWeakReference"]),
+
+  _onReflow() {
+    reflowObservers.delete(this._doc);
+    this._doc.docShell.removeWeakReflowObserver(this);
+
+    for (let callback of this.callbacks) {
+      try {
+        callback();
+      } catch (e) {
+        Cu.reportError(e);
+      }
+    }
+  },
+
+  reflow() {
+    this._onReflow();
+  },
+
+  reflowInterruptible() {
+    this._onReflow();
+  },
+};
+
+const FLUSH_TYPES = {
+  "style": Ci.nsIDOMWindowUtils.FLUSH_STYLE,
+  "layout": Ci.nsIDOMWindowUtils.FLUSH_LAYOUT,
+  "display": Ci.nsIDOMWindowUtils.FLUSH_DISPLAY,
+};
+
 this.BrowserUtils = {
 
   /**
    * Prints arguments separated by a space and appends a new line.
    */
   dumpLn(...args) {
     for (let a of args)
       dump(a + " ");
@@ -621,9 +663,68 @@ this.BrowserUtils = {
 
     url = url.replace(/%s/g, encodedParam).replace(/%S/g, param);
     if (hasPOSTParam) {
       postData = decodedPostData.replace(/%s/g, encodedParam)
                                 .replace(/%S/g, param);
     }
     return [url, postData];
   },
+
+  /**
+   * Calls the given function when the given document has just reflowed,
+   * and returns a promise which resolves to its return value after it
+   * has been called.
+   *
+   * The function *must not trigger any reflows*, or make any changes
+   * which would require a layout flush.
+   *
+   * @param {Document} doc
+   * @param {function} callback
+   * @returns {Promise}
+   */
+  promiseReflowed(doc, callback) {
+    let observer = reflowObservers.get(doc);
+    if (!observer) {
+      observer = new ReflowObserver(doc);
+      reflowObservers.set(doc, observer);
+    }
+
+    return new Promise((resolve, reject) => {
+      observer.callbacks.push(() => {
+        try {
+          resolve(callback());
+        } catch (e) {
+          reject(e);
+        }
+      });
+    });
+  },
+
+  /**
+   * Calls the given function as soon as a layout flush of the given
+   * type is not necessary, and returns a promise which resolves to the
+   * callback's return value after it executes.
+   *
+   * The function *must not trigger any reflows*, or make any changes
+   * which would require a layout flush.
+   *
+   * @param {Document} doc
+   * @param {string} flushType
+   *        The flush type required. Must be one of:
+   *
+   *          - "style"
+   *          - "layout"
+   *          - "display"
+   * @param {function} callback
+   * @returns {Promise}
+   */
+  async promiseLayoutFlushed(doc, flushType, callback) {
+    let utils = doc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
+                   .getInterface(Ci.nsIDOMWindowUtils);
+
+    if (!utils.needsFlush(FLUSH_TYPES[flushType])) {
+      return callback();
+    }
+
+    return this.promiseReflowed(doc, callback);
+  },
 };