Source code

Revision control

Copy as Markdown

Other Tools

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