summaryrefslogtreecommitdiffstats
path: root/layout/tools/reftest/ReftestFissionChild.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'layout/tools/reftest/ReftestFissionChild.jsm')
-rw-r--r--layout/tools/reftest/ReftestFissionChild.jsm292
1 files changed, 292 insertions, 0 deletions
diff --git a/layout/tools/reftest/ReftestFissionChild.jsm b/layout/tools/reftest/ReftestFissionChild.jsm
new file mode 100644
index 0000000000..97037d1faf
--- /dev/null
+++ b/layout/tools/reftest/ReftestFissionChild.jsm
@@ -0,0 +1,292 @@
+var EXPORTED_SYMBOLS = ["ReftestFissionChild"];
+
+class ReftestFissionChild extends JSWindowActorChild {
+
+ forwardAfterPaintEventToParent(rects, originalTargetUri, dispatchToSelfAsWell) {
+ if (dispatchToSelfAsWell) {
+ let event = new this.contentWindow.CustomEvent("Reftest:MozAfterPaintFromChild",
+ {bubbles: true, detail: {rects, originalTargetUri}});
+ this.contentWindow.dispatchEvent(event);
+ }
+
+ let parentContext = this.browsingContext.parent;
+ if (parentContext) {
+ try {
+ this.sendAsyncMessage("ForwardAfterPaintEvent",
+ {toBrowsingContext: parentContext, fromBrowsingContext: this.browsingContext,
+ rects, originalTargetUri});
+ } catch (e) {
+ // |this| can be destroyed here and unable to send messages, which is
+ // not a problem, the reftest harness probably torn down the page and
+ // moved on to the next test.
+ Cu.reportError(e);
+ }
+ }
+ }
+
+ handleEvent(evt) {
+ switch (evt.type) {
+ case "MozAfterPaint":
+ // We want to forward any after paint events to our parent document so that
+ // that it reaches the root content document where the main reftest harness
+ // code (reftest-content.js) will process it and update the canvas.
+ var rects = [];
+ for (let r of evt.clientRects) {
+ rects.push({ left: r.left, top: r.top, right: r.right, bottom: r.bottom });
+ }
+ this.forwardAfterPaintEventToParent(rects, this.document.documentURI, /* dispatchToSelfAsWell */ false);
+ break;
+ }
+ }
+
+ transformRect(transform, rect) {
+ let p1 = transform.transformPoint({x: rect.left, y: rect.top});
+ let p2 = transform.transformPoint({x: rect.right, y: rect.top});
+ let p3 = transform.transformPoint({x: rect.left, y: rect.bottom});
+ let p4 = transform.transformPoint({x: rect.right, y: rect.bottom});
+ let quad = new DOMQuad(p1, p2, p3, p4);
+ return quad.getBounds();
+ }
+
+ SetupDisplayportRoot() {
+ let returnStrings = {infoStrings: [], errorStrings: []};
+
+ let contentRootElement = this.contentWindow.document.documentElement;
+ if (!contentRootElement) {
+ return Promise.resolve(returnStrings);
+ }
+
+ // If we don't have the reftest-async-scroll attribute we only look at
+ // the root element for potential display ports to set.
+ if (!contentRootElement.hasAttribute("reftest-async-scroll")) {
+ let winUtils = this.contentWindow.windowUtils;
+ this.setupDisplayportForElement(contentRootElement, winUtils, returnStrings);
+ return Promise.resolve(returnStrings);
+ }
+
+ // Send a msg to the parent side to get the parent side to tell all
+ // process roots to do the displayport setting.
+ let browsingContext = this.browsingContext;
+ let promise = this.sendQuery("TellChildrenToSetupDisplayport", {browsingContext});
+ return promise.then(function(result) {
+ for (let errorString of result.errorStrings) {
+ returnStrings.errorStrings.push(errorString);
+ }
+ for (let infoString of result.infoStrings) {
+ returnStrings.infoStrings.push(infoString);
+ }
+ return returnStrings;
+ },
+ function(reason) {
+ returnStrings.errorStrings.push("SetupDisplayport SendQuery to parent promise rejected: " + reason);
+ return returnStrings;
+ });
+ }
+
+ attrOrDefault(element, attr, def) {
+ return element.hasAttribute(attr) ? Number(element.getAttribute(attr)) : def;
+ }
+
+ setupDisplayportForElement(element, winUtils, returnStrings) {
+ var dpw = this.attrOrDefault(element, "reftest-displayport-w", 0);
+ var dph = this.attrOrDefault(element, "reftest-displayport-h", 0);
+ var dpx = this.attrOrDefault(element, "reftest-displayport-x", 0);
+ var dpy = this.attrOrDefault(element, "reftest-displayport-y", 0);
+ if (dpw !== 0 || dph !== 0 || dpx != 0 || dpy != 0) {
+ returnStrings.infoStrings.push("Setting displayport to <x="+ dpx +", y="+ dpy +", w="+ dpw +", h="+ dph +">");
+ winUtils.setDisplayPortForElement(dpx, dpy, dpw, dph, element, 1);
+ }
+ }
+
+ setupDisplayportForElementSubtree(element, winUtils, returnStrings) {
+ this.setupDisplayportForElement(element, winUtils, returnStrings);
+ for (let c = element.firstElementChild; c; c = c.nextElementSibling) {
+ this.setupDisplayportForElementSubtree(c, winUtils, returnStrings);
+ }
+ if (typeof element.contentDocument !== "undefined" &&
+ element.contentDocument) {
+ returnStrings.infoStrings.push("setupDisplayportForElementSubtree descending into subdocument");
+ this.setupDisplayportForElementSubtree(element.contentDocument.documentElement,
+ element.contentWindow.windowUtils, returnStrings);
+ }
+ }
+
+ setupAsyncScrollOffsetsForElement(element, winUtils, allowFailure, returnStrings) {
+ let sx = this.attrOrDefault(element, "reftest-async-scroll-x", 0);
+ let sy = this.attrOrDefault(element, "reftest-async-scroll-y", 0);
+ if (sx != 0 || sy != 0) {
+ try {
+ // This might fail when called from RecordResult since layers
+ // may not have been constructed yet
+ winUtils.setAsyncScrollOffset(element, sx, sy);
+ return true;
+ } catch (e) {
+ if (allowFailure) {
+ returnStrings.infoStrings.push("setupAsyncScrollOffsetsForElement error calling setAsyncScrollOffset: " + e);
+ } else {
+ returnStrings.errorStrings.push("setupAsyncScrollOffsetsForElement error calling setAsyncScrollOffset: " + e);
+ }
+ }
+ }
+ return false;
+ }
+
+ setupAsyncScrollOffsetsForElementSubtree(element, winUtils, allowFailure, returnStrings) {
+ let updatedAny = this.setupAsyncScrollOffsetsForElement(element, winUtils, returnStrings);
+ for (let c = element.firstElementChild; c; c = c.nextElementSibling) {
+ if (this.setupAsyncScrollOffsetsForElementSubtree(c, winUtils, allowFailure, returnStrings)) {
+ updatedAny = true;
+ }
+ }
+ if (typeof element.contentDocument !== "undefined" &&
+ element.contentDocument) {
+ returnStrings.infoStrings.push("setupAsyncScrollOffsetsForElementSubtree Descending into subdocument");
+ if (this.setupAsyncScrollOffsetsForElementSubtree(element.contentDocument.documentElement,
+ element.contentWindow.windowUtils, allowFailure, returnStrings)) {
+ updatedAny = true;
+ }
+ }
+ return updatedAny;
+ }
+
+ async receiveMessage(msg) {
+ switch (msg.name) {
+ case "ForwardAfterPaintEventToSelfAndParent":
+ {
+ // The embedderElement can be null if the child we got this from was removed.
+ // Not much we can do to transform the rects, but it doesn't matter, the rects
+ // won't reach reftest-content.js.
+ if (msg.data.fromBrowsingContext.embedderElement == null) {
+ this.forwardAfterPaintEventToParent(msg.data.rects, msg.data.originalTargetUri,
+ /* dispatchToSelfAsWell */ true);
+ return;
+ }
+
+ // Transform the rects from fromBrowsingContext to us.
+ // We first translate from the content rect to the border rect of the iframe.
+ let style = this.contentWindow.getComputedStyle(msg.data.fromBrowsingContext.embedderElement);
+ let translate = new DOMMatrixReadOnly().translate(
+ parseFloat(style.paddingLeft) + parseFloat(style.borderLeftWidth),
+ parseFloat(style.paddingTop) + parseFloat(style.borderTopWidth));
+
+ // Then we transform from the iframe to our root frame.
+ // We are guaranteed to be the process with the embedderElement for fromBrowsingContext.
+ let transform = msg.data.fromBrowsingContext.embedderElement.getTransformToViewport();
+ let combined = translate.multiply(transform);
+
+ let newrects = msg.data.rects.map(r => this.transformRect(combined, r))
+
+ this.forwardAfterPaintEventToParent(newrects, msg.data.originalTargetUri, /* dispatchToSelfAsWell */ true);
+ break;
+ }
+
+ case "EmptyMessage":
+ return undefined;
+ case "UpdateLayerTree":
+ {
+ let errorStrings = [];
+ try {
+ if (this.manager.isProcessRoot) {
+ this.contentWindow.windowUtils.updateLayerTree();
+ }
+ } catch (e) {
+ errorStrings.push("updateLayerTree failed: " + e);
+ }
+ return {errorStrings};
+ }
+ case "FlushRendering":
+ {
+ let errorStrings = [];
+ let warningStrings = [];
+ let infoStrings = [];
+
+ try {
+ let {ignoreThrottledAnimations, needsAnimationFrame} = msg.data;
+
+ if (this.manager.isProcessRoot) {
+ var anyPendingPaintsGeneratedInDescendants = false;
+
+ if (needsAnimationFrame) {
+ await new Promise(resolve => this.contentWindow.requestAnimationFrame(resolve));
+ }
+
+ 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);
+ }
+ }
+ }
+
+ // `contentWindow` will be null if the inner window for this actor
+ // has been navigated away from.
+ if (this.contentWindow) {
+ 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 {errorStrings, warningStrings, infoStrings};
+ }
+
+ case "SetupDisplayport":
+ {
+ let contentRootElement = this.document.documentElement;
+ let winUtils = this.contentWindow.windowUtils;
+ let returnStrings = {infoStrings: [], errorStrings: []};
+ if (contentRootElement) {
+ this.setupDisplayportForElementSubtree(contentRootElement, winUtils, returnStrings);
+ }
+ return returnStrings;
+ }
+
+ case "SetupAsyncScrollOffsets":
+ {
+ let returns = {infoStrings: [], errorStrings: [], updatedAny: false};
+ let contentRootElement = this.document.documentElement;
+
+ if (!contentRootElement) {
+ return returns;
+ }
+
+ let winUtils = this.contentWindow.windowUtils;
+
+ returns.updatedAny = this.setupAsyncScrollOffsetsForElementSubtree(contentRootElement, winUtils, msg.data.allowFailure, returns);
+ return returns;
+ }
+
+ }
+ }
+}