Bug 1593170. Make the reftest-content.js functions FlushRendering and SynchronizeForSnapshot work on Fission child oop iframes. r=mattwoodrow,kmag
authorTimothy Nikkel <tnikkel@gmail.com>
Mon, 18 Nov 2019 11:25:11 +0000
changeset 502487 06f14f474f284543abd637543cecfa4d6079be50
parent 502486 561598bb2f394a1301650aa606df06cb310cdaac
child 502488 449d87c4759340bd61e4ae3ab0bc67f826ac02cf
push id114172
push userdluca@mozilla.com
push dateTue, 19 Nov 2019 11:31:10 +0000
treeherdermozilla-inbound@b5c5ba07d3db [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmattwoodrow, kmag
bugs1593170
milestone72.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 1593170. Make the reftest-content.js functions FlushRendering and SynchronizeForSnapshot work on Fission child oop iframes. r=mattwoodrow,kmag This changes them to return a promise that resolves when the work is done, but we still need to change the callers to handle this new return type and do the right thing when these functions do their work async-ly. To do this we add a JSWindowActor called ReftestFission. reftest-content.js communicates with this actor via reftest.jsm. Differential Revision: https://phabricator.services.mozilla.com/D51343
layout/tools/reftest/ReftestFissionChild.jsm
layout/tools/reftest/ReftestFissionParent.jsm
layout/tools/reftest/jar.mn
layout/tools/reftest/reftest-content.js
layout/tools/reftest/reftest.jsm
new file mode 100644
--- /dev/null
+++ b/layout/tools/reftest/ReftestFissionChild.jsm
@@ -0,0 +1,75 @@
+var EXPORTED_SYMBOLS = ["ReftestFissionChild"];
+
+class ReftestFissionChild extends JSWindowActorChild {
+
+  receiveMessage(msg) {
+    switch (msg.name) {
+      case "UpdateLayerTree":
+        let errorString = null;
+        try {
+          if (this.manager.isProcessRoot) {
+            this.contentWindow.windowUtils.updateLayerTree();
+          }
+        } catch (e) {
+          errorString = "updateLayerTree failed: " + e;
+        }
+        return Promise.resolve({errorString});
+      case "FlushRendering":
+        let errorStrings = [];
+        let warningStrings = [];
+        let infoStrings = [];
+
+        try {
+          let ignoreThrottledAnimations = msg.data.ignoreThrottledAnimations;
+
+          if (this.manager.isProcessRoot) {
+            var anyPendingPaintsGeneratedInDescendants = false;
+
+            function flushWindow(win) {
+              var utils = win.windowUtils;
+              var afterPaintWasPending = utils.isMozAfterPaintPending;
+
+              var root = win.document.documentElement;
+              if (root && !root.classList.contains("reftest-no-flush")) {
+                try {
+                  if (ignoreThrottledAnimations) {
+                    utils.flushLayoutWithoutThrottledAnimations();
+                  } else {
+                    root.getBoundingClientRect();
+                  }
+                } catch (e) {
+                  warningStrings.push("flushWindow failed: " + e + "\n")
+                }
+              }
+
+              if (!afterPaintWasPending && utils.isMozAfterPaintPending) {
+                infoStrings.push("FlushRendering generated paint for window " + win.location.href)
+                anyPendingPaintsGeneratedInDescendants = true;
+              }
+
+              for (let i = 0; i < win.frames.length; ++i) {
+                try {
+                  if (!Cu.isRemoteProxy(win.frames[i])) {
+                    flushWindow(win.frames[i]);
+                  }
+                } catch (e) {
+                  Cu.reportError(e);
+                }
+              }
+            }
+
+            flushWindow(this.contentWindow);
+
+            if (anyPendingPaintsGeneratedInDescendants &&
+                !this.contentWindow.windowUtils.isMozAfterPaintPending) {
+              warningStrings.push("Internal error: descendant frame generated a MozAfterPaint event, but the root document doesn't have one!");
+            }
+
+          }
+        } catch (e) {
+          errorStrings.push("flushWindow failed: " + e);
+        }
+        return Promise.resolve({errorStrings, warningStrings, infoStrings});
+    }
+  }
+}
new file mode 100644
--- /dev/null
+++ b/layout/tools/reftest/ReftestFissionParent.jsm
@@ -0,0 +1,102 @@
+var EXPORTED_SYMBOLS = ["ReftestFissionParent"];
+
+class ReftestFissionParent extends JSWindowActorParent {
+
+  tellChildrenToFlushRendering(browsingContext, ignoreThrottledAnimations) {
+    let promises = [];
+    this.tellChildrenToFlushRenderingRecursive(browsingContext, ignoreThrottledAnimations, promises);
+    return Promise.allSettled(promises);
+  }
+
+  tellChildrenToFlushRenderingRecursive(browsingContext, ignoreThrottledAnimations, promises) {
+    let cwg = browsingContext.currentWindowGlobal;
+    if (cwg && cwg.isProcessRoot) {
+      let a = cwg.getActor("ReftestFission");
+      if (a) {
+        let responsePromise = a.sendQuery("FlushRendering", {ignoreThrottledAnimations});
+        promises.push(responsePromise);
+      }
+    }
+
+    for (let context of browsingContext.getChildren()) {
+      this.tellChildrenToFlushRenderingRecursive(context, ignoreThrottledAnimations, promises);
+    }
+  }
+
+  tellChildrenToUpdateLayerTree(browsingContext) {
+    let promises = [];
+    this.tellChildrenToUpdateLayerTreeRecursive(browsingContext, promises);
+    return Promise.allSettled(promises);
+  }
+
+  tellChildrenToUpdateLayerTreeRecursive(browsingContext, promises) {
+    let cwg = browsingContext.currentWindowGlobal;
+    if (cwg && cwg.isProcessRoot) {
+      let a = cwg.getActor("ReftestFission");
+      if (a) {
+        let responsePromise = a.sendQuery("UpdateLayerTree");
+        promises.push(responsePromise);
+      }
+    }
+
+    for (let context of browsingContext.getChildren()) {
+      this.tellChildrenToUpdateLayerTreeRecursive(context, promises);
+    }
+  }
+
+  receiveMessage(msg) {
+    switch (msg.name) {
+      case "FlushRendering":
+      {
+        let promise = this.tellChildrenToFlushRendering(msg.data.browsingContext, msg.data.ignoreThrottledAnimations);
+        return promise.then(function (results) {
+          let errorStrings = [];
+          let warningStrings = [];
+          let infoStrings = [];
+          for (let r of results) {
+            if (r.status != "fulfilled") {
+              if (r.status == "pending") {
+                errorStrings.push("FlushRendering sendQuery to child promise still pending?");
+              } else {
+                // We expect actors to go away causing sendQuery's to fail, so
+                // just note it.
+                infoStrings.push("FlushRendering sendQuery to child promise rejected: " + r.reason);
+              }
+              continue;
+            }
+
+            errorStrings.concat(r.value.errorStrings);
+            warningStrings.concat(r.value.warningStrings);
+            infoStrings.concat(r.value.infoStrings);
+          }
+          return {errorStrings, warningStrings, infoStrings};
+        });
+      }
+      case "UpdateLayerTree":
+      {
+        let promise = this.tellChildrenToUpdateLayerTree(msg.data.browsingContext);
+        return promise.then(function (results) {
+          let errorStrings = [];
+          for (let r of results) {
+            if (r.status != "fulfilled") {
+              if (r.status == "pending") {
+                errorStrings.push("UpdateLayerTree sendQuery to child promise still pending?");
+              } else {
+                // We expect actors to go away causing sendQuery's to fail, so
+                // just note it.
+                infoStrings.push("UpdateLayerTree sendQuery to child promise rejected: " + r.reason);
+              }
+              continue;
+            }
+
+            if (r.value.errorString != null) {
+              errorStrings.push(r.value.errorString);
+            }
+          }
+          return errorStrings;
+        });
+      }
+    }
+  }
+
+}
--- a/layout/tools/reftest/jar.mn
+++ b/layout/tools/reftest/jar.mn
@@ -43,16 +43,18 @@ reftest.jar:
   content/crashtests/layout/style/crashtests (../../../layout/style/crashtests/*)
   content/crashtests/gfx/tests/crashtests (../../../gfx/tests/crashtests/*)
   content/crashtests/accessible/tests/crashtests (../../../accessible/tests/crashtests/*)
   content/crashtests/view/crashtests (../../../view/crashtests/*)
   content/crashtests/widget/cocoa/crashtests (../../../widget/cocoa/crashtests/*)
 
   res/globals.jsm (globals.jsm)
   res/reftest-content.js (reftest-content.js)
+  res/ReftestFissionParent.jsm (ReftestFissionParent.jsm)
+  res/ReftestFissionChild.jsm (ReftestFissionChild.jsm)
   res/AsyncSpellCheckTestHelper.jsm (../../../editor/AsyncSpellCheckTestHelper.jsm)
   res/httpd.jsm (../../../netwerk/test/httpserver/httpd.js)
   res/StructuredLog.jsm (../../../testing/modules/StructuredLog.jsm)
   res/PerTestCoverageUtils.jsm (../../../tools/code-coverage/PerTestCoverageUtils.jsm)
   res/input.css (../../../editor/reftests/xul/input.css)
   res/progress.css (../../../layout/reftests/forms/progress/style.css)
 *  res/manifest.jsm (manifest.jsm)
 *  res/reftest.jsm (reftest.jsm)
--- a/layout/tools/reftest/reftest-content.js
+++ b/layout/tools/reftest/reftest-content.js
@@ -504,55 +504,34 @@ const STATE_WAITING_FOR_SPELL_CHECKS = 2
 // move to the next state.
 const STATE_WAITING_FOR_APZ_FLUSH = 3;
 // When all MozAfterPaint events and all explicit paint waits are flushed, we're
 // done and can move to the COMPLETED state.
 const STATE_WAITING_TO_FINISH = 4;
 const STATE_COMPLETED = 5;
 
 function FlushRendering(aFlushMode) {
-    var anyPendingPaintsGeneratedInDescendants = false;
-
-    function flushWindow(win) {
-        var utils = win.windowUtils;
-        var afterPaintWasPending = utils.isMozAfterPaintPending;
-
-        var root = win.document.documentElement;
-        if (root && !root.classList.contains("reftest-no-flush")) {
-            try {
-                if (aFlushMode === FlushMode.IGNORE_THROTTLED_ANIMATIONS) {
-                    utils.flushLayoutWithoutThrottledAnimations();
-                } else {
-                    root.getBoundingClientRect();
-                }
-            } catch (e) {
-                LogWarning("flushWindow failed: " + e + "\n");
-            }
+    let browsingContext = content.docShell.browsingContext;
+    let ignoreThrottledAnimations = (aFlushMode === FlushMode.IGNORE_THROTTLED_ANIMATIONS);
+    let promise = content.getWindowGlobalChild().getActor("ReftestFission").sendQuery("FlushRendering", {browsingContext, ignoreThrottledAnimations});
+    return promise.then(function(result) {
+        for (let errorString of result.errorStrings) {
+            LogError(errorString);
         }
-
-        if (!afterPaintWasPending && utils.isMozAfterPaintPending) {
-            LogInfo("FlushRendering generated paint for window " + win.location.href);
-            anyPendingPaintsGeneratedInDescendants = true;
+        for (let warningString of result.warningStrings) {
+            LogWarning(warningString);
+        }
+        for (let infoString of result.infoStrings) {
+            LogInfo(infoString);
         }
-
-        for (var i = 0; i < win.frames.length; ++i) {
-            try {
-                flushWindow(win.frames[i]);
-            } catch (e) {
-                Cu.reportError(e);
-            }
-        }
-    }
-
-    flushWindow(content);
-
-    if (anyPendingPaintsGeneratedInDescendants &&
-        !windowUtils().isMozAfterPaintPending) {
-        LogWarning("Internal error: descendant frame generated a MozAfterPaint event, but the root document doesn't have one!");
-    }
+    }, function(reason) {
+        // We expect actors to go away causing sendQuery's to fail, so
+        // just note it.
+        LogInfo("FlushRendering sendQuery to parent rejected: " + reason);
+    });
 }
 
 function WaitForTestEnd(contentRootElement, inPrintMode, spellCheckedElements, forURL) {
     var stopAfterPaintReceived = false;
     var currentDoc = content.document;
     var state = STATE_WAITING_TO_FIRE_INVALIDATE_EVENT;
 
     function AfterPaintListener(event) {
@@ -1117,16 +1096,25 @@ function DoAssertionCheck()
 function LoadURI(uri)
 {
     let loadURIOptions = {
       triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
     };
     webNavigation().loadURI(uri, loadURIOptions);
 }
 
+function LogError(str)
+{
+    if (gVerbose) {
+        sendSyncMessage("reftest:Log", { type: "error", msg: str });
+    } else {
+        sendAsyncMessage("reftest:Log", { type: "error", msg: str });
+    }
+}
+
 function LogWarning(str)
 {
     if (gVerbose) {
         sendSyncMessage("reftest:Log", { type: "warning", msg: str });
     } else {
         sendAsyncMessage("reftest:Log", { type: "warning", msg: str });
     }
 }
@@ -1161,22 +1149,37 @@ function SynchronizeForSnapshot(flags)
         if (docElt &&
             (docElt.hasAttribute("reftest-no-sync-layers") ||
              docElt.classList.contains("reftest-no-flush"))) {
             LogInfo("Test file chose to skip SynchronizeForSnapshot");
             return;
         }
     }
 
-    windowUtils().updateLayerTree();
+    let browsingContext = content.docShell.browsingContext;
+    let promise = content.getWindowGlobalChild().getActor("ReftestFission").sendQuery("UpdateLayerTree", {browsingContext});
+    return promise.then(function (result) {
+        for (let errorString of result) {
+            LogError(errorString);
+        }
 
-    // Setup async scroll offsets now, because any scrollable layers should
-    // have had their AsyncPanZoomControllers created.
-    setupAsyncScrollOffsets({allowFailure:false});
-    setupAsyncZoom({allowFailure:false});
+        // Setup async scroll offsets now, because any scrollable layers should
+        // have had their AsyncPanZoomControllers created.
+        setupAsyncScrollOffsets({allowFailure:false});
+        setupAsyncZoom({allowFailure:false});
+    }, function(reason) {
+        // We expect actors to go away causing sendQuery's to fail, so
+        // just note it.
+        LogInfo("UpdateLayerTree sendQuery to parent rejected: " + reason);
+
+        // Setup async scroll offsets now, because any scrollable layers should
+        // have had their AsyncPanZoomControllers created.
+        setupAsyncScrollOffsets({allowFailure:false});
+        setupAsyncZoom({allowFailure:false});
+    });
 }
 
 function RegisterMessageListeners()
 {
     addMessageListener(
         "reftest:Clear",
         function (m) { RecvClear() }
     );
--- a/layout/tools/reftest/reftest.jsm
+++ b/layout/tools/reftest/reftest.jsm
@@ -1505,16 +1505,27 @@ function RegisterMessageListenersAndLoad
         function (m) { RecvUpdateWholeCanvasForInvalidation(); }
     );
     g.browserMessageManager.addMessageListener(
         "reftest:ExpectProcessCrash",
         function (m) { RecvExpectProcessCrash(); }
     );
 
     g.browserMessageManager.loadFrameScript("resource://reftest/reftest-content.js", true, true);
+
+    ChromeUtils.registerWindowActor("ReftestFission", {
+        parent: {
+          moduleURI: "resource://reftest/ReftestFissionParent.jsm",
+        },
+        child: {
+          moduleURI: "resource://reftest/ReftestFissionChild.jsm",
+        },
+        allFrames: true,
+        includeChrome: true,
+    });
 }
 
 function RecvAssertionCount(count)
 {
     DoAssertionCheck(count);
 }
 
 function RecvContentReady(info)
@@ -1568,16 +1579,19 @@ function RecvInitCanvasWithSnapshot()
 
 function RecvLog(type, msg)
 {
     msg = "[CONTENT] " + msg;
     if (type == "info") {
         TestBuffer(msg);
     } else if (type == "warning") {
         logger.warning(msg);
+    } else if (type == "error") {
+        logger.error("REFTEST TEST-UNEXPECTED-FAIL | " + g.currentURL + " | " + msg + "\n");
+        ++g.testResults.Exception;
     } else {
         logger.error("REFTEST TEST-UNEXPECTED-FAIL | " + g.currentURL + " | unknown log type " + type + "\n");
         ++g.testResults.Exception;
     }
 }
 
 function RecvScriptResults(runtimeMs, error, results)
 {