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
--- 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);
+ },
};