From 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 03:47:29 +0200 Subject: Adding upstream version 115.8.0esr. Signed-off-by: Daniel Baumann --- layout/tools/reftest/README.txt | 2 + layout/tools/reftest/ReftestFissionChild.jsm | 292 +++ layout/tools/reftest/ReftestFissionParent.jsm | 238 +++ layout/tools/reftest/api.js | 163 ++ layout/tools/reftest/chrome/userContent-import.css | 3 + layout/tools/reftest/chrome/userContent.css | 23 + layout/tools/reftest/clean-reftest-output.pl | 38 + layout/tools/reftest/fake-global.css | 1 + layout/tools/reftest/globals.jsm | 166 ++ layout/tools/reftest/jar.mn | 72 + layout/tools/reftest/mach_commands.py | 297 +++ layout/tools/reftest/mach_test_package_commands.py | 113 ++ layout/tools/reftest/manifest.jsm | 803 ++++++++ layout/tools/reftest/manifest.json | 22 + layout/tools/reftest/moz.build | 35 + layout/tools/reftest/output.py | 190 ++ .../reftest/reftest-analyzer-structured.xhtml | 649 +++++++ layout/tools/reftest/reftest-analyzer.xhtml | 934 +++++++++ layout/tools/reftest/reftest-content.js | 1530 +++++++++++++++ layout/tools/reftest/reftest-to-html.pl | 118 ++ layout/tools/reftest/reftest.jsm | 2020 ++++++++++++++++++++ layout/tools/reftest/reftest.xhtml | 13 + layout/tools/reftest/reftest/__init__.py | 164 ++ layout/tools/reftest/reftestcommandline.py | 645 +++++++ layout/tools/reftest/remotereftest.py | 545 ++++++ layout/tools/reftest/runreftest.py | 1198 ++++++++++++ layout/tools/reftest/schema.json | 1 + layout/tools/reftest/selftest/conftest.py | 147 ++ layout/tools/reftest/selftest/files/assert.html | 7 + layout/tools/reftest/selftest/files/crash.html | 7 + layout/tools/reftest/selftest/files/defaults.list | 7 + .../selftest/files/failure-type-interactions.list | 11 + layout/tools/reftest/selftest/files/green.html | 6 + .../selftest/files/invalid-defaults-include.list | 4 + .../reftest/selftest/files/invalid-defaults.list | 3 + .../reftest/selftest/files/invalid-include.list | 2 + layout/tools/reftest/selftest/files/leaks.log | 73 + layout/tools/reftest/selftest/files/red.html | 6 + .../reftest/selftest/files/reftest-assert.list | 1 + .../reftest/selftest/files/reftest-crash.list | 1 + .../tools/reftest/selftest/files/reftest-fail.list | 3 + .../tools/reftest/selftest/files/reftest-pass.list | 3 + .../reftest/selftest/files/scripttest-pass.html | 17 + layout/tools/reftest/selftest/files/types.list | 5 + layout/tools/reftest/selftest/python.ini | 7 + .../selftest/test_python_manifest_parser.py | 37 + .../selftest/test_reftest_manifest_parser.py | 72 + .../tools/reftest/selftest/test_reftest_output.py | 162 ++ 48 files changed, 10856 insertions(+) create mode 100644 layout/tools/reftest/README.txt create mode 100644 layout/tools/reftest/ReftestFissionChild.jsm create mode 100644 layout/tools/reftest/ReftestFissionParent.jsm create mode 100644 layout/tools/reftest/api.js create mode 100644 layout/tools/reftest/chrome/userContent-import.css create mode 100644 layout/tools/reftest/chrome/userContent.css create mode 100755 layout/tools/reftest/clean-reftest-output.pl create mode 100644 layout/tools/reftest/fake-global.css create mode 100644 layout/tools/reftest/globals.jsm create mode 100644 layout/tools/reftest/jar.mn create mode 100644 layout/tools/reftest/mach_commands.py create mode 100644 layout/tools/reftest/mach_test_package_commands.py create mode 100644 layout/tools/reftest/manifest.jsm create mode 100644 layout/tools/reftest/manifest.json create mode 100644 layout/tools/reftest/moz.build create mode 100644 layout/tools/reftest/output.py create mode 100644 layout/tools/reftest/reftest-analyzer-structured.xhtml create mode 100644 layout/tools/reftest/reftest-analyzer.xhtml create mode 100644 layout/tools/reftest/reftest-content.js create mode 100755 layout/tools/reftest/reftest-to-html.pl create mode 100644 layout/tools/reftest/reftest.jsm create mode 100644 layout/tools/reftest/reftest.xhtml create mode 100644 layout/tools/reftest/reftest/__init__.py create mode 100644 layout/tools/reftest/reftestcommandline.py create mode 100644 layout/tools/reftest/remotereftest.py create mode 100644 layout/tools/reftest/runreftest.py create mode 100644 layout/tools/reftest/schema.json create mode 100644 layout/tools/reftest/selftest/conftest.py create mode 100644 layout/tools/reftest/selftest/files/assert.html create mode 100644 layout/tools/reftest/selftest/files/crash.html create mode 100644 layout/tools/reftest/selftest/files/defaults.list create mode 100644 layout/tools/reftest/selftest/files/failure-type-interactions.list create mode 100644 layout/tools/reftest/selftest/files/green.html create mode 100644 layout/tools/reftest/selftest/files/invalid-defaults-include.list create mode 100644 layout/tools/reftest/selftest/files/invalid-defaults.list create mode 100644 layout/tools/reftest/selftest/files/invalid-include.list create mode 100644 layout/tools/reftest/selftest/files/leaks.log create mode 100644 layout/tools/reftest/selftest/files/red.html create mode 100644 layout/tools/reftest/selftest/files/reftest-assert.list create mode 100644 layout/tools/reftest/selftest/files/reftest-crash.list create mode 100644 layout/tools/reftest/selftest/files/reftest-fail.list create mode 100644 layout/tools/reftest/selftest/files/reftest-pass.list create mode 100644 layout/tools/reftest/selftest/files/scripttest-pass.html create mode 100644 layout/tools/reftest/selftest/files/types.list create mode 100644 layout/tools/reftest/selftest/python.ini create mode 100644 layout/tools/reftest/selftest/test_python_manifest_parser.py create mode 100644 layout/tools/reftest/selftest/test_reftest_manifest_parser.py create mode 100644 layout/tools/reftest/selftest/test_reftest_output.py (limited to 'layout/tools/reftest') diff --git a/layout/tools/reftest/README.txt b/layout/tools/reftest/README.txt new file mode 100644 index 0000000000..ebc77011dd --- /dev/null +++ b/layout/tools/reftest/README.txt @@ -0,0 +1,2 @@ +Reftest documentation has been moved to layout/docs/Reftest.rst and is rendered +at https://firefox-source-docs.mozilla.org/layout/Reftest.html 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 "); + 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; + } + + } + } +} diff --git a/layout/tools/reftest/ReftestFissionParent.jsm b/layout/tools/reftest/ReftestFissionParent.jsm new file mode 100644 index 0000000000..7cf61bdd55 --- /dev/null +++ b/layout/tools/reftest/ReftestFissionParent.jsm @@ -0,0 +1,238 @@ +var EXPORTED_SYMBOLS = ["ReftestFissionParent"]; + +class ReftestFissionParent extends JSWindowActorParent { + + tellChildrenToFlushRendering(browsingContext, ignoreThrottledAnimations, needsAnimationFrame) { + let promises = []; + this.tellChildrenToFlushRenderingRecursive(browsingContext, ignoreThrottledAnimations, needsAnimationFrame, promises); + return Promise.allSettled(promises); + } + + tellChildrenToFlushRenderingRecursive(browsingContext, ignoreThrottledAnimations, needsAnimationFrame, promises) { + let cwg = browsingContext.currentWindowGlobal; + if (cwg && cwg.isProcessRoot) { + let a = cwg.getActor("ReftestFission"); + if (a) { + let responsePromise = a.sendQuery("FlushRendering", {ignoreThrottledAnimations, needsAnimationFrame}); + promises.push(responsePromise); + } + } + + for (let context of browsingContext.children) { + this.tellChildrenToFlushRenderingRecursive(context, ignoreThrottledAnimations, needsAnimationFrame, promises); + } + } + + // not including browsingContext + getNearestProcessRootProperDescendants(browsingContext) { + let result = []; + for (let context of browsingContext.children) { + this.getNearestProcessRootProperDescendantsRecursive(context, result); + } + return result; + } + + getNearestProcessRootProperDescendantsRecursive(browsingContext, result) { + let cwg = browsingContext.currentWindowGlobal; + if (cwg && cwg.isProcessRoot) { + result.push(browsingContext); + return; + } + for (let context of browsingContext.children) { + this.getNearestProcessRootProperDescendantsRecursive(context, result); + } + } + + // tell children and itself + async tellChildrenToUpdateLayerTree(browsingContext) { + let errorStrings = []; + let infoStrings = []; + + let cwg = browsingContext.currentWindowGlobal; + if (!cwg || !cwg.isProcessRoot) { + if (cwg) { + errorStrings.push("tellChildrenToUpdateLayerTree called on a non process root?"); + } + return {errorStrings, infoStrings}; + } + + let actor = cwg.getActor("ReftestFission"); + if (!actor) { + return {errorStrings, infoStrings}; + } + + // When we paint a document we also update the EffectsInfo visible rect in + // nsSubDocumentFrame for any remote subdocuments. This visible rect is + // used to limit painting for the subdocument in the subdocument's process. + // So we want to ensure that the IPC message that updates the visible rect + // to the subdocument's process arrives before we paint the subdocument + // (otherwise our painting might not be up to date). We do this by sending, + // and waiting for reply, an "EmptyMessage" to every direct descendant that + // is in another process. Since we send the "EmptyMessage" after the + // visible rect update message we know that the visible rect will be + // updated by the time we hear back from the "EmptyMessage". Then we can + // ask the subdocument process to paint. + + try { + let result = await actor.sendQuery("UpdateLayerTree"); + errorStrings.push(...result.errorStrings); + } catch (e) { + infoStrings.push("tellChildrenToUpdateLayerTree UpdateLayerTree msg to child rejected: " + e); + } + + let descendants = actor.getNearestProcessRootProperDescendants(browsingContext); + for (let context of descendants) { + let cwg2 = context.currentWindowGlobal; + if (cwg2) { + if (!cwg2.isProcessRoot) { + errorStrings.push("getNearestProcessRootProperDescendants returned a non process root?"); + } + let actor2 = cwg2.getActor("ReftestFission"); + if (actor2) { + try { + await actor2.sendQuery("EmptyMessage"); + } catch(e) { + infoStrings.push("tellChildrenToUpdateLayerTree EmptyMessage msg to child rejected: " + e); + } + + try { + let result2 = await actor2.tellChildrenToUpdateLayerTree(context); + errorStrings.push(...result2.errorStrings); + infoStrings.push(...result2.infoStrings); + } catch (e) { + errorStrings.push("tellChildrenToUpdateLayerTree recursive tellChildrenToUpdateLayerTree call rejected: " + e); + } + + } + } + } + + return {errorStrings, infoStrings}; + } + + tellChildrenToSetupDisplayport(browsingContext, promises) { + let cwg = browsingContext.currentWindowGlobal; + if (cwg && cwg.isProcessRoot) { + let a = cwg.getActor("ReftestFission"); + if (a) { + let responsePromise = a.sendQuery("SetupDisplayport"); + promises.push(responsePromise); + } + } + + for (let context of browsingContext.children) { + this.tellChildrenToSetupDisplayport(context, promises); + } + } + + tellChildrenToSetupAsyncScrollOffsets(browsingContext, allowFailure, promises) { + let cwg = browsingContext.currentWindowGlobal; + if (cwg && cwg.isProcessRoot) { + let a = cwg.getActor("ReftestFission"); + if (a) { + let responsePromise = a.sendQuery("SetupAsyncScrollOffsets", {allowFailure}); + promises.push(responsePromise); + } + } + + for (let context of browsingContext.children) { + this.tellChildrenToSetupAsyncScrollOffsets(context, allowFailure, promises); + } + } + + + receiveMessage(msg) { + switch (msg.name) { + case "ForwardAfterPaintEvent": + { + let cwg = msg.data.toBrowsingContext.currentWindowGlobal; + if (cwg) { + let a = cwg.getActor("ReftestFission"); + if (a) { + a.sendAsyncMessage("ForwardAfterPaintEventToSelfAndParent", msg.data); + } + } + break; + } + case "FlushRendering": + { + let promise = this.tellChildrenToFlushRendering(msg.data.browsingContext, msg.data.ignoreThrottledAnimations, msg.data.needsAnimationFrame); + return promise.then(function (results) { + let errorStrings = []; + let warningStrings = []; + let infoStrings = []; + for (let r of results) { + if (r.status != "fulfilled") { + if (r.status == "pending") { + errorStrings.push("FlushRendering sendQuery to child promise still pending?"); + } else { + // We expect actors to go away causing sendQuery's to fail, so + // just note it. + infoStrings.push("FlushRendering sendQuery to child promise rejected: " + r.reason); + } + continue; + } + + errorStrings.push(...r.value.errorStrings); + warningStrings.push(...r.value.warningStrings); + infoStrings.push(...r.value.infoStrings); + } + return {errorStrings, warningStrings, infoStrings}; + }); + } + case "UpdateLayerTree": + { + return this.tellChildrenToUpdateLayerTree(msg.data.browsingContext); + } + case "TellChildrenToSetupDisplayport": + { + let promises = []; + this.tellChildrenToSetupDisplayport(msg.data.browsingContext, promises); + return Promise.allSettled(promises).then(function (results) { + let errorStrings = []; + let infoStrings = []; + for (let r of results) { + if (r.status != "fulfilled") { + // We expect actors to go away causing sendQuery's to fail, so + // just note it. + infoStrings.push("SetupDisplayport sendQuery to child promise rejected: " + r.reason); + continue; + } + + errorStrings.push(...r.value.errorStrings); + infoStrings.push(...r.value.infoStrings); + } + return {errorStrings, infoStrings} + }); + } + + case "SetupAsyncScrollOffsets": + { + let promises = []; + this.tellChildrenToSetupAsyncScrollOffsets(this.manager.browsingContext, msg.data.allowFailure, promises); + return Promise.allSettled(promises).then(function (results) { + let errorStrings = []; + let infoStrings = []; + let updatedAny = false; + for (let r of results) { + if (r.status != "fulfilled") { + // We expect actors to go away causing sendQuery's to fail, so + // just note it. + infoStrings.push("SetupAsyncScrollOffsets sendQuery to child promise rejected: " + r.reason); + continue; + } + + errorStrings.push(...r.value.errorStrings); + infoStrings.push(...r.value.infoStrings); + if (r.value.updatedAny) { + updatedAny = true; + } + } + return {errorStrings, infoStrings, updatedAny}; + }); + } + + } + } + +} diff --git a/layout/tools/reftest/api.js b/layout/tools/reftest/api.js new file mode 100644 index 0000000000..a0f20a77ad --- /dev/null +++ b/layout/tools/reftest/api.js @@ -0,0 +1,163 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const Cm = Components.manager; + +var OnRefTestLoad, OnRefTestUnload; + +XPCOMUtils.defineLazyServiceGetter( + this, + "resProto", + "@mozilla.org/network/protocol;1?name=resource", + "nsISubstitutingProtocolHandler" +); + +XPCOMUtils.defineLazyServiceGetter( + this, + "aomStartup", + "@mozilla.org/addons/addon-manager-startup;1", + "amIAddonManagerStartup" +); + +function processTerminated() { + return new Promise(resolve => { + Services.obs.addObserver(function observe(subject, topic) { + if (topic == "ipc:content-shutdown") { + Services.obs.removeObserver(observe, topic); + resolve(); + } + }, "ipc:content-shutdown"); + }); +} + +function startAndroid(win) { + // Add setTimeout here because windows.innerWidth/Height are not set yet. + win.setTimeout(function() { + OnRefTestLoad(win); + }, 0); +} + +function GetMainWindow() { + let win = Services.wm.getMostRecentWindow("navigator:browser"); + if (!win) { + // There is no navigator:browser in the geckoview TestRunnerActivity; + // try navigator.geckoview instead. + win = Services.wm.getMostRecentWindow("navigator:geckoview"); + } + return win; +} + +this.reftest = class extends ExtensionAPI { + onStartup() { + let uri = Services.io.newURI( + "chrome/reftest/res/", + null, + this.extension.rootURI + ); + resProto.setSubstitutionWithFlags( + "reftest", + uri, + resProto.ALLOW_CONTENT_ACCESS + ); + + const manifestURI = Services.io.newURI( + "manifest.json", + null, + this.extension.rootURI + ); + + let manifestDirectives = [ + [ + "content", + "reftest", + "chrome/reftest/content/", + "contentaccessible=yes", + ], + ]; + if (Services.appinfo.OS == "Android") { + manifestDirectives.push([ + "override", + "chrome://global/skin/global.css", + "chrome://reftest/content/fake-global.css", + ]); + } + this.chromeHandle = aomStartup.registerChrome( + manifestURI, + manifestDirectives + ); + + // Starting tests is handled quite differently on android and desktop. + // On Android, OnRefTestLoad() takes over the main browser window so + // we just need to call it as soon as the browser window is available. + // On desktop, a separate window (dummy) is created and explicitly given + // focus (see bug 859339 for details), then tests are launched in a new + // top-level window. + let win = GetMainWindow(); + if (Services.appinfo.OS == "Android") { + ({ OnRefTestLoad, OnRefTestUnload } = ChromeUtils.import( + "resource://reftest/reftest.jsm" + )); + if (win) { + startAndroid(win); + } else { + // The window type parameter is only available once the window's document + // element has been created. The main window has already been created + // however and it is in an in-between state which means that you can't + // find it by its type nor will domwindowcreated be fired. + // So we listen to either initial-document-element-inserted which + // indicates when it's okay to search for the main window by type again. + Services.obs.addObserver(function observer(aSubject, aTopic, aData) { + Services.obs.removeObserver(observer, aTopic); + startAndroid(GetMainWindow()); + }, "initial-document-element-inserted"); + } + return; + } + + Services.io.manageOfflineStatus = false; + Services.io.offline = false; + + let dummy = Services.ww.openWindow( + null, + "about:blank", + "dummy", + "chrome,dialog=no,left=800,height=200,width=200,all", + null + ); + dummy.onload = async function() { + // Close pre-existing window + win.close(); + + const { PerTestCoverageUtils } = ChromeUtils.importESModule( + "resource://reftest/PerTestCoverageUtils.sys.mjs" + ); + if (PerTestCoverageUtils.enabled) { + // In PerTestCoverage mode, wait for the process belonging to the window we just closed + // to be terminated, to avoid its shutdown interfering when we reset the counters. + await processTerminated(); + } + + dummy.focus(); + Services.ww.openWindow( + null, + "chrome://reftest/content/reftest.xhtml", + "_blank", + "chrome,dialog=no,all", + {} + ); + }; + } + + onShutdown() { + resProto.setSubstitution("reftest", null); + + this.chromeHandle.destruct(); + this.chromeHandle = null; + + if (Services.appinfo.OS == "Android") { + OnRefTestUnload(); + Cu.unload("resource://reftest/reftest.jsm"); + } + } +}; diff --git a/layout/tools/reftest/chrome/userContent-import.css b/layout/tools/reftest/chrome/userContent-import.css new file mode 100644 index 0000000000..e1936a02bc --- /dev/null +++ b/layout/tools/reftest/chrome/userContent-import.css @@ -0,0 +1,3 @@ +.reftest-usercss-import { + background-color: lime !important; +} diff --git a/layout/tools/reftest/chrome/userContent.css b/layout/tools/reftest/chrome/userContent.css new file mode 100644 index 0000000000..1106a6ed55 --- /dev/null +++ b/layout/tools/reftest/chrome/userContent.css @@ -0,0 +1,23 @@ +@import "invalid.css"; +@import "userContent-import.css"; + +.reftest-usercss { + background: lime !important; +} +.RefTest-upperCase { + background: lime !important; +} +/* + * file: URLs have an empty domain. + * Android uses a special loopback-to-host address. + */ +@-moz-document domain(), domain(10.0.2.2) { + .reftest-domain { + background: lime !important; + } +} +@-moz-document domain(example.invalid) { + .reftest-xdomain { + background: red !important; + } +} diff --git a/layout/tools/reftest/clean-reftest-output.pl b/layout/tools/reftest/clean-reftest-output.pl new file mode 100755 index 0000000000..b1959281d5 --- /dev/null +++ b/layout/tools/reftest/clean-reftest-output.pl @@ -0,0 +1,38 @@ +#!/usr/bin/perl +# vim: set shiftwidth=4 tabstop=8 autoindent expandtab: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# This script is intended to be run over the standard output of a +# reftest run. It will extract the parts of the output run relevant to +# reftest and HTML-ize the URLs. + +use strict; + +print < + +reftest output + + +
+EOM
+;
+
+while (<>) {
+    next unless /REFTEST/;
+    chomp;
+    chop if /\r$/;
+    s,(TEST-)([^\|]*) \| ([^\|]*) \|(.*),\1\2: \3\4,;
+    s,(IMAGE[^:]*): (data:.*),\1,;
+    print;
+    print "\n";
+}
+
+print <
+
+
+EOM
+;
diff --git a/layout/tools/reftest/fake-global.css b/layout/tools/reftest/fake-global.css
new file mode 100644
index 0000000000..66480b04a2
--- /dev/null
+++ b/layout/tools/reftest/fake-global.css
@@ -0,0 +1 @@
+/* This file deliberately left blank. See comment in jar.mn for rationale. */
diff --git a/layout/tools/reftest/globals.jsm b/layout/tools/reftest/globals.jsm
new file mode 100644
index 0000000000..7f1486d5f5
--- /dev/null
+++ b/layout/tools/reftest/globals.jsm
@@ -0,0 +1,166 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+var EXPORTED_SYMBOLS = [];
+
+for (let [key, val] of Object.entries({
+  /* Constants */
+  XHTML_NS: "http://www.w3.org/1999/xhtml",
+  XUL_NS: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+
+  NS_LOCAL_FILE_CONTRACTID: "@mozilla.org/file/local;1",
+  NS_GFXINFO_CONTRACTID: "@mozilla.org/gfx/info;1",
+  IO_SERVICE_CONTRACTID: "@mozilla.org/network/io-service;1",
+  DEBUG_CONTRACTID: "@mozilla.org/xpcom/debug;1",
+  NS_DIRECTORY_SERVICE_CONTRACTID: "@mozilla.org/file/directory_service;1",
+  NS_OBSERVER_SERVICE_CONTRACTID: "@mozilla.org/observer-service;1",
+
+  TYPE_REFTEST_EQUAL: '==',
+  TYPE_REFTEST_NOTEQUAL: '!=',
+  TYPE_LOAD: 'load',     // test without a reference (just test that it does
+                         // not assert, crash, hang, or leak)
+  TYPE_SCRIPT: 'script', // test contains individual test results
+  TYPE_PRINT: 'print',   // test and reference will be printed to PDF's and
+                         // compared structurally
+
+  // keep this in sync with reftest-content.js
+  URL_TARGET_TYPE_TEST: 0,      // first url
+  URL_TARGET_TYPE_REFERENCE: 1, // second url, if any
+
+  // The order of these constants matters, since when we have a status
+  // listed for a *manifest*, we combine the status with the status for
+  // the test by using the *larger*.
+  // FIXME: In the future, we may also want to use this rule for combining
+  // statuses that are on the same line (rather than making the last one
+  // win).
+  EXPECTED_PASS: 0,
+  EXPECTED_FAIL: 1,
+  EXPECTED_RANDOM: 2,
+  EXPECTED_FUZZY: 3,
+
+  // types of preference value we might want to set for a specific test
+  PREF_BOOLEAN: 0,
+  PREF_STRING: 1,
+  PREF_INTEGER: 2,
+
+  FOCUS_FILTER_ALL_TESTS: "all",
+  FOCUS_FILTER_NEEDS_FOCUS_TESTS: "needs-focus",
+  FOCUS_FILTER_NON_NEEDS_FOCUS_TESTS: "non-needs-focus",
+
+  // ""
+  BLANK_URL_FOR_CLEARING: "data:text/html;charset=UTF-8,%3C%21%2D%2DCLEAR%2D%2D%3E",
+
+  /* Globals */
+  g: {
+    loadTimeout: 0,
+    timeoutHook: null,
+    remote: false,
+    ignoreWindowSize: false,
+    shuffle: false,
+    repeat: null,
+    runUntilFailure: false,
+    cleanupPendingCrashes: false,
+    totalChunks: 0,
+    thisChunk: 0,
+    containingWindow: null,
+    urlFilterRegex: {},
+    contentGfxInfo: null,
+    focusFilterMode: "all",
+    compareRetainedDisplayLists: false,
+    isCoverageBuild: false,
+
+    browser: undefined,
+    // Are we testing web content loaded in a separate process?
+    browserIsRemote: undefined,        // bool
+    // Are we using