Bug 1383367: Part 2 - Add promise helpers to defer operations until after a reflow. r?mconley draft
authorKris Maglione <maglione.k@gmail.com>
Mon, 24 Jul 2017 20:16:07 -0700
changeset 614857 f5b7850725bca7aa7f4ff4d23cf3b516be368d4e
parent 614856 ca8ed45e1008225fe00f3cedcabc45f57ee2b153
child 614858 218661e7a72abb84793945e01b65d032bb70cdec
push id70146
push usermaglione.k@gmail.com
push dateTue, 25 Jul 2017 03:29:21 +0000
reviewersmconley
bugs1383367
milestone56.0a1
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 + " ");
@@ -585,9 +627,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);
+  },
 };