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 " ); 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; } }