diff options
Diffstat (limited to 'layout/tools/reftest/ReftestFissionChild.jsm')
-rw-r--r-- | layout/tools/reftest/ReftestFissionChild.jsm | 292 |
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; + } + + } + } +} |