summaryrefslogtreecommitdiffstats
path: root/layout/tools/reftest/ReftestFissionChild.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'layout/tools/reftest/ReftestFissionChild.sys.mjs')
-rw-r--r--layout/tools/reftest/ReftestFissionChild.sys.mjs399
1 files changed, 399 insertions, 0 deletions
diff --git a/layout/tools/reftest/ReftestFissionChild.sys.mjs b/layout/tools/reftest/ReftestFissionChild.sys.mjs
new file mode 100644
index 0000000000..6eeece6639
--- /dev/null
+++ b/layout/tools/reftest/ReftestFissionChild.sys.mjs
@@ -0,0 +1,399 @@
+export class ReftestFissionChild extends JSWindowActorChild {
+ forwardAfterPaintEventToParent(
+ rects,
+ originalTargetUri,
+ dispatchToSelfAsWell
+ ) {
+ if (dispatchToSelfAsWell && this.contentWindow) {
+ 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.
+ console.error(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 undefined;
+ }
+
+ let translate = new DOMMatrixReadOnly().translate(0, 0);
+ if (this.contentWindow) {
+ // 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
+ );
+ 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) {
+ console.error(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;
+ }
+ }
+ return undefined;
+ }
+}