michael@0: /* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- / michael@0: /* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: const CC = Components.classes; michael@0: const CI = Components.interfaces; michael@0: const CR = Components.results; michael@0: const CU = Components.utils; michael@0: michael@0: const XHTML_NS = "http://www.w3.org/1999/xhtml"; michael@0: michael@0: const DEBUG_CONTRACTID = "@mozilla.org/xpcom/debug;1"; michael@0: const PRINTSETTINGS_CONTRACTID = "@mozilla.org/gfx/printsettings-service;1"; michael@0: const ENVIRONMENT_CONTRACTID = "@mozilla.org/process/environment;1"; michael@0: michael@0: // "" michael@0: const BLANK_URL_FOR_CLEARING = "data:text/html;charset=UTF-8,%3C%21%2D%2DCLEAR%2D%2D%3E"; michael@0: michael@0: CU.import("resource://gre/modules/Timer.jsm"); michael@0: CU.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm"); michael@0: michael@0: var gBrowserIsRemote; michael@0: var gHaveCanvasSnapshot = false; michael@0: // Plugin layers can be updated asynchronously, so to make sure that all michael@0: // layer surfaces have the right content, we need to listen for explicit michael@0: // "MozPaintWait" and "MozPaintWaitFinished" events that signal when it's OK michael@0: // to take snapshots. We cannot take a snapshot while the number of michael@0: // "MozPaintWait" events fired exceeds the number of "MozPaintWaitFinished" michael@0: // events fired. We count the number of such excess events here. When michael@0: // the counter reaches zero we call gExplicitPendingPaintsCompleteHook. michael@0: var gExplicitPendingPaintCount = 0; michael@0: var gExplicitPendingPaintsCompleteHook; michael@0: var gCurrentURL; michael@0: var gCurrentTestType; michael@0: var gTimeoutHook = null; michael@0: var gFailureTimeout = null; michael@0: var gFailureReason; michael@0: var gAssertionCount = 0; michael@0: michael@0: var gDebug; michael@0: var gVerbose = false; michael@0: michael@0: var gCurrentTestStartTime; michael@0: var gClearingForAssertionCheck = false; michael@0: michael@0: const TYPE_LOAD = 'load'; // test without a reference (just test that it does michael@0: // not assert, crash, hang, or leak) michael@0: const TYPE_SCRIPT = 'script'; // test contains individual test results michael@0: michael@0: function markupDocumentViewer() { michael@0: return docShell.contentViewer.QueryInterface(CI.nsIMarkupDocumentViewer); michael@0: } michael@0: michael@0: function webNavigation() { michael@0: return docShell.QueryInterface(CI.nsIWebNavigation); michael@0: } michael@0: michael@0: function windowUtils() { michael@0: return content.QueryInterface(CI.nsIInterfaceRequestor) michael@0: .getInterface(CI.nsIDOMWindowUtils); michael@0: } michael@0: michael@0: function IDForEventTarget(event) michael@0: { michael@0: try { michael@0: return "'" + event.target.getAttribute('id') + "'"; michael@0: } catch (ex) { michael@0: return ""; michael@0: } michael@0: } michael@0: michael@0: function PaintWaitListener(event) michael@0: { michael@0: LogInfo("MozPaintWait received for ID " + IDForEventTarget(event)); michael@0: gExplicitPendingPaintCount++; michael@0: } michael@0: michael@0: function PaintWaitFinishedListener(event) michael@0: { michael@0: LogInfo("MozPaintWaitFinished received for ID " + IDForEventTarget(event)); michael@0: gExplicitPendingPaintCount--; michael@0: if (gExplicitPendingPaintCount < 0) { michael@0: LogWarning("Underrun in gExplicitPendingPaintCount\n"); michael@0: gExplicitPendingPaintCount = 0; michael@0: } michael@0: if (gExplicitPendingPaintCount == 0 && michael@0: gExplicitPendingPaintsCompleteHook) { michael@0: gExplicitPendingPaintsCompleteHook(); michael@0: } michael@0: } michael@0: michael@0: function OnInitialLoad() michael@0: { michael@0: #ifndef REFTEST_B2G michael@0: removeEventListener("load", OnInitialLoad, true); michael@0: #endif michael@0: michael@0: gDebug = CC[DEBUG_CONTRACTID].getService(CI.nsIDebug2); michael@0: var env = CC[ENVIRONMENT_CONTRACTID].getService(CI.nsIEnvironment); michael@0: gVerbose = !!env.get("MOZ_REFTEST_VERBOSE"); michael@0: michael@0: RegisterMessageListeners(); michael@0: michael@0: var initInfo = SendContentReady(); michael@0: gBrowserIsRemote = initInfo.remote; michael@0: michael@0: addEventListener("load", OnDocumentLoad, true); michael@0: michael@0: addEventListener("MozPaintWait", PaintWaitListener, true); michael@0: addEventListener("MozPaintWaitFinished", PaintWaitFinishedListener, true); michael@0: michael@0: LogWarning("Using browser remote="+ gBrowserIsRemote +"\n"); michael@0: } michael@0: michael@0: function StartTestURI(type, uri, timeout) michael@0: { michael@0: // Reset gExplicitPendingPaintCount in case there was a timeout or michael@0: // the count is out of sync for some other reason michael@0: if (gExplicitPendingPaintCount != 0) { michael@0: LogWarning("Resetting gExplicitPendingPaintCount to zero (currently " + michael@0: gExplicitPendingPaintCount + "\n"); michael@0: gExplicitPendingPaintCount = 0; michael@0: } michael@0: michael@0: gCurrentTestType = type; michael@0: gCurrentURL = uri; michael@0: michael@0: gCurrentTestStartTime = Date.now(); michael@0: if (gFailureTimeout != null) { michael@0: SendException("program error managing timeouts\n"); michael@0: } michael@0: gFailureTimeout = setTimeout(LoadFailed, timeout); michael@0: michael@0: LoadURI(gCurrentURL); michael@0: } michael@0: michael@0: function setupZoom(contentRootElement) { michael@0: if (!contentRootElement || !contentRootElement.hasAttribute('reftest-zoom')) michael@0: return; michael@0: markupDocumentViewer().fullZoom = michael@0: contentRootElement.getAttribute('reftest-zoom'); michael@0: } michael@0: michael@0: function resetZoom() { michael@0: markupDocumentViewer().fullZoom = 1.0; michael@0: } michael@0: michael@0: function doPrintMode(contentRootElement) { michael@0: #if REFTEST_B2G michael@0: // nsIPrintSettings not available in B2G michael@0: return false; michael@0: #else michael@0: // use getAttribute because className works differently in HTML and SVG michael@0: return contentRootElement && michael@0: contentRootElement.hasAttribute('class') && michael@0: contentRootElement.getAttribute('class').split(/\s+/) michael@0: .indexOf("reftest-print") != -1; michael@0: #endif michael@0: } michael@0: michael@0: function setupPrintMode() { michael@0: var PSSVC = michael@0: CC[PRINTSETTINGS_CONTRACTID].getService(CI.nsIPrintSettingsService); michael@0: var ps = PSSVC.newPrintSettings; michael@0: ps.paperWidth = 5; michael@0: ps.paperHeight = 3; michael@0: michael@0: // Override any os-specific unwriteable margins michael@0: ps.unwriteableMarginTop = 0; michael@0: ps.unwriteableMarginLeft = 0; michael@0: ps.unwriteableMarginBottom = 0; michael@0: ps.unwriteableMarginRight = 0; michael@0: michael@0: ps.headerStrLeft = ""; michael@0: ps.headerStrCenter = ""; michael@0: ps.headerStrRight = ""; michael@0: ps.footerStrLeft = ""; michael@0: ps.footerStrCenter = ""; michael@0: ps.footerStrRight = ""; michael@0: docShell.contentViewer.setPageMode(true, ps); michael@0: } michael@0: michael@0: function attrOrDefault(element, attr, def) { michael@0: return element.hasAttribute(attr) ? Number(element.getAttribute(attr)) : def; michael@0: } michael@0: michael@0: function setupViewport(contentRootElement) { michael@0: if (!contentRootElement) { michael@0: return; michael@0: } michael@0: michael@0: var vw = attrOrDefault(contentRootElement, "reftest-viewport-w", 0); michael@0: var vh = attrOrDefault(contentRootElement, "reftest-viewport-h", 0); michael@0: if (vw !== 0 || vh !== 0) { michael@0: LogInfo("Setting viewport to "); michael@0: windowUtils().setCSSViewport(vw, vh); michael@0: } michael@0: michael@0: // XXX support resolution when needed michael@0: michael@0: // XXX support viewconfig when needed michael@0: } michael@0: michael@0: function setupDisplayport(contentRootElement) { michael@0: if (!contentRootElement) { michael@0: return; michael@0: } michael@0: michael@0: function setupDisplayportForElement(element) { michael@0: var dpw = attrOrDefault(element, "reftest-displayport-w", 0); michael@0: var dph = attrOrDefault(element, "reftest-displayport-h", 0); michael@0: var dpx = attrOrDefault(element, "reftest-displayport-x", 0); michael@0: var dpy = attrOrDefault(element, "reftest-displayport-y", 0); michael@0: if (dpw !== 0 || dph !== 0 || dpx != 0 || dpy != 0) { michael@0: LogInfo("Setting displayport to "); michael@0: windowUtils().setDisplayPortForElement(dpx, dpy, dpw, dph, element, 1); michael@0: } michael@0: } michael@0: michael@0: function setupDisplayportForElementSubtree(element) { michael@0: setupDisplayportForElement(element); michael@0: for (var c = element.firstElementChild; c; c = c.nextElementSibling) { michael@0: setupDisplayportForElementSubtree(c); michael@0: } michael@0: } michael@0: michael@0: if (contentRootElement.hasAttribute("reftest-async-scroll")) { michael@0: SendEnableAsyncScroll(); michael@0: setupDisplayportForElementSubtree(contentRootElement); michael@0: } else { michael@0: setupDisplayportForElement(contentRootElement); michael@0: } michael@0: } michael@0: michael@0: function setupAsyncScrollOffsets(options) { michael@0: var currentDoc = content.document; michael@0: var contentRootElement = currentDoc ? currentDoc.documentElement : null; michael@0: michael@0: if (!contentRootElement) { michael@0: return; michael@0: } michael@0: michael@0: function setupAsyncScrollOffsetsForElement(element) { michael@0: var sx = attrOrDefault(element, "reftest-async-scroll-x", 0); michael@0: var sy = attrOrDefault(element, "reftest-async-scroll-y", 0); michael@0: if (sx != 0 || sy != 0) { michael@0: try { michael@0: // This might fail when called from RecordResult since layers michael@0: // may not have been constructed yet michael@0: windowUtils().setAsyncScrollOffset(element, sx, sy); michael@0: } catch (e) { michael@0: if (!options.allowFailure) { michael@0: throw e; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: function setupAsyncScrollOffsetsForElementSubtree(element) { michael@0: setupAsyncScrollOffsetsForElement(element); michael@0: for (var c = element.firstElementChild; c; c = c.nextElementSibling) { michael@0: setupAsyncScrollOffsetsForElementSubtree(c); michael@0: } michael@0: } michael@0: michael@0: var asyncScroll = contentRootElement.hasAttribute("reftest-async-scroll"); michael@0: if (asyncScroll) { michael@0: setupAsyncScrollOffsetsForElementSubtree(contentRootElement); michael@0: } michael@0: } michael@0: michael@0: function resetDisplayportAndViewport() { michael@0: // XXX currently the displayport configuration lives on the michael@0: // presshell and so is "reset" on nav when we get a new presshell. michael@0: } michael@0: michael@0: function shouldWaitForExplicitPaintWaiters() { michael@0: return gExplicitPendingPaintCount > 0; michael@0: } michael@0: michael@0: function shouldWaitForPendingPaints() { michael@0: // if gHaveCanvasSnapshot is false, we're not taking snapshots so michael@0: // there is no need to wait for pending paints to be flushed. michael@0: return gHaveCanvasSnapshot && windowUtils().isMozAfterPaintPending; michael@0: } michael@0: michael@0: function shouldWaitForReftestWaitRemoval(contentRootElement) { michael@0: // use getAttribute because className works differently in HTML and SVG michael@0: return contentRootElement && michael@0: contentRootElement.hasAttribute('class') && michael@0: contentRootElement.getAttribute('class').split(/\s+/) michael@0: .indexOf("reftest-wait") != -1; michael@0: } michael@0: michael@0: function shouldSnapshotWholePage(contentRootElement) { michael@0: // use getAttribute because className works differently in HTML and SVG michael@0: return contentRootElement && michael@0: contentRootElement.hasAttribute('class') && michael@0: contentRootElement.getAttribute('class').split(/\s+/) michael@0: .indexOf("reftest-snapshot-all") != -1; michael@0: } michael@0: michael@0: function getNoPaintElements(contentRootElement) { michael@0: return contentRootElement.getElementsByClassName('reftest-no-paint'); michael@0: } michael@0: michael@0: // Initial state. When the document has loaded and all MozAfterPaint events and michael@0: // all explicit paint waits are flushed, we can fire the MozReftestInvalidate michael@0: // event and move to the next state. michael@0: const STATE_WAITING_TO_FIRE_INVALIDATE_EVENT = 0; michael@0: // When reftest-wait has been removed from the root element, we can move to the michael@0: // next state. michael@0: const STATE_WAITING_FOR_REFTEST_WAIT_REMOVAL = 1; michael@0: // When spell checking is done on all spell-checked elements, we can move to the michael@0: // next state. michael@0: const STATE_WAITING_FOR_SPELL_CHECKS = 2; michael@0: // When all MozAfterPaint events and all explicit paint waits are flushed, we're michael@0: // done and can move to the COMPLETED state. michael@0: const STATE_WAITING_TO_FINISH = 3; michael@0: const STATE_COMPLETED = 4; michael@0: michael@0: function FlushRendering() { michael@0: var anyPendingPaintsGeneratedInDescendants = false; michael@0: michael@0: function flushWindow(win) { michael@0: var utils = win.QueryInterface(CI.nsIInterfaceRequestor) michael@0: .getInterface(CI.nsIDOMWindowUtils); michael@0: var afterPaintWasPending = utils.isMozAfterPaintPending; michael@0: michael@0: if (win.document.documentElement) { michael@0: try { michael@0: // Flush pending restyles and reflows for this window michael@0: win.document.documentElement.getBoundingClientRect(); michael@0: } catch (e) { michael@0: LogWarning("flushWindow failed: " + e + "\n"); michael@0: } michael@0: } michael@0: michael@0: if (!afterPaintWasPending && utils.isMozAfterPaintPending) { michael@0: LogInfo("FlushRendering generated paint for window " + win.location.href); michael@0: anyPendingPaintsGeneratedInDescendants = true; michael@0: } michael@0: michael@0: for (var i = 0; i < win.frames.length; ++i) { michael@0: flushWindow(win.frames[i]); michael@0: } michael@0: } michael@0: michael@0: flushWindow(content); michael@0: michael@0: if (anyPendingPaintsGeneratedInDescendants && michael@0: !windowUtils().isMozAfterPaintPending) { michael@0: LogWarning("Internal error: descendant frame generated a MozAfterPaint event, but the root document doesn't have one!"); michael@0: } michael@0: } michael@0: michael@0: function WaitForTestEnd(contentRootElement, inPrintMode, spellCheckedElements) { michael@0: var stopAfterPaintReceived = false; michael@0: var currentDoc = content.document; michael@0: var state = STATE_WAITING_TO_FIRE_INVALIDATE_EVENT; michael@0: michael@0: function AfterPaintListener(event) { michael@0: LogInfo("AfterPaintListener in " + event.target.document.location.href); michael@0: if (event.target.document != currentDoc) { michael@0: // ignore paint events for subframes or old documents in the window. michael@0: // Invalidation in subframes will cause invalidation in the toplevel document anyway. michael@0: return; michael@0: } michael@0: michael@0: SendUpdateCanvasForEvent(event, contentRootElement); michael@0: // These events are fired immediately after a paint. Don't michael@0: // confuse ourselves by firing synchronously if we triggered the michael@0: // paint ourselves. michael@0: setTimeout(MakeProgress, 0); michael@0: } michael@0: michael@0: function AttrModifiedListener() { michael@0: LogInfo("AttrModifiedListener fired"); michael@0: // Wait for the next return-to-event-loop before continuing --- for michael@0: // example, the attribute may have been modified in an subdocument's michael@0: // load event handler, in which case we need load event processing michael@0: // to complete and unsuppress painting before we check isMozAfterPaintPending. michael@0: setTimeout(MakeProgress, 0); michael@0: } michael@0: michael@0: function ExplicitPaintsCompleteListener() { michael@0: LogInfo("ExplicitPaintsCompleteListener fired"); michael@0: // Since this can fire while painting, don't confuse ourselves by michael@0: // firing synchronously. It's fine to do this asynchronously. michael@0: setTimeout(MakeProgress, 0); michael@0: } michael@0: michael@0: function RemoveListeners() { michael@0: // OK, we can end the test now. michael@0: removeEventListener("MozAfterPaint", AfterPaintListener, false); michael@0: if (contentRootElement) { michael@0: contentRootElement.removeEventListener("DOMAttrModified", AttrModifiedListener, false); michael@0: } michael@0: gExplicitPendingPaintsCompleteHook = null; michael@0: gTimeoutHook = null; michael@0: // Make sure we're in the COMPLETED state just in case michael@0: // (this may be called via the test-timeout hook) michael@0: state = STATE_COMPLETED; michael@0: } michael@0: michael@0: // Everything that could cause shouldWaitForXXX() to michael@0: // change from returning true to returning false is monitored via some kind michael@0: // of event listener which eventually calls this function. michael@0: function MakeProgress() { michael@0: if (state >= STATE_COMPLETED) { michael@0: LogInfo("MakeProgress: STATE_COMPLETED"); michael@0: return; michael@0: } michael@0: michael@0: FlushRendering(); michael@0: michael@0: switch (state) { michael@0: case STATE_WAITING_TO_FIRE_INVALIDATE_EVENT: { michael@0: LogInfo("MakeProgress: STATE_WAITING_TO_FIRE_INVALIDATE_EVENT"); michael@0: if (shouldWaitForExplicitPaintWaiters() || shouldWaitForPendingPaints()) { michael@0: gFailureReason = "timed out waiting for pending paint count to reach zero"; michael@0: if (shouldWaitForExplicitPaintWaiters()) { michael@0: gFailureReason += " (waiting for MozPaintWaitFinished)"; michael@0: LogInfo("MakeProgress: waiting for MozPaintWaitFinished"); michael@0: } michael@0: if (shouldWaitForPendingPaints()) { michael@0: gFailureReason += " (waiting for MozAfterPaint)"; michael@0: LogInfo("MakeProgress: waiting for MozAfterPaint"); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: state = STATE_WAITING_FOR_REFTEST_WAIT_REMOVAL; michael@0: var hasReftestWait = shouldWaitForReftestWaitRemoval(contentRootElement); michael@0: // Notify the test document that now is a good time to test some invalidation michael@0: LogInfo("MakeProgress: dispatching MozReftestInvalidate"); michael@0: if (contentRootElement) { michael@0: var elements = getNoPaintElements(contentRootElement); michael@0: for (var i = 0; i < elements.length; ++i) { michael@0: windowUtils().checkAndClearPaintedState(elements[i]); michael@0: } michael@0: var notification = content.document.createEvent("Events"); michael@0: notification.initEvent("MozReftestInvalidate", true, false); michael@0: contentRootElement.dispatchEvent(notification); michael@0: } michael@0: michael@0: if (!inPrintMode && doPrintMode(contentRootElement)) { michael@0: LogInfo("MakeProgress: setting up print mode"); michael@0: setupPrintMode(); michael@0: } michael@0: michael@0: if (hasReftestWait && !shouldWaitForReftestWaitRemoval(contentRootElement)) { michael@0: // MozReftestInvalidate handler removed reftest-wait. michael@0: // We expect something to have been invalidated... michael@0: FlushRendering(); michael@0: if (!shouldWaitForPendingPaints() && !shouldWaitForExplicitPaintWaiters()) { michael@0: LogWarning("MozInvalidateEvent didn't invalidate"); michael@0: } michael@0: } michael@0: // Try next state michael@0: MakeProgress(); michael@0: return; michael@0: } michael@0: michael@0: case STATE_WAITING_FOR_REFTEST_WAIT_REMOVAL: michael@0: LogInfo("MakeProgress: STATE_WAITING_FOR_REFTEST_WAIT_REMOVAL"); michael@0: if (shouldWaitForReftestWaitRemoval(contentRootElement)) { michael@0: gFailureReason = "timed out waiting for reftest-wait to be removed"; michael@0: LogInfo("MakeProgress: waiting for reftest-wait to be removed"); michael@0: return; michael@0: } michael@0: michael@0: // Try next state michael@0: state = STATE_WAITING_FOR_SPELL_CHECKS; michael@0: MakeProgress(); michael@0: return; michael@0: michael@0: case STATE_WAITING_FOR_SPELL_CHECKS: michael@0: LogInfo("MakeProgress: STATE_WAITING_FOR_SPELL_CHECKS"); michael@0: if (numPendingSpellChecks) { michael@0: gFailureReason = "timed out waiting for spell checks to end"; michael@0: LogInfo("MakeProgress: waiting for spell checks to end"); michael@0: return; michael@0: } michael@0: michael@0: state = STATE_WAITING_TO_FINISH; michael@0: // Try next state michael@0: MakeProgress(); michael@0: return; michael@0: michael@0: case STATE_WAITING_TO_FINISH: michael@0: LogInfo("MakeProgress: STATE_WAITING_TO_FINISH"); michael@0: if (shouldWaitForExplicitPaintWaiters() || shouldWaitForPendingPaints()) { michael@0: gFailureReason = "timed out waiting for pending paint count to " + michael@0: "reach zero (after reftest-wait removed and switch to print mode)"; michael@0: if (shouldWaitForExplicitPaintWaiters()) { michael@0: gFailureReason += " (waiting for MozPaintWaitFinished)"; michael@0: LogInfo("MakeProgress: waiting for MozPaintWaitFinished"); michael@0: } michael@0: if (shouldWaitForPendingPaints()) { michael@0: gFailureReason += " (waiting for MozAfterPaint)"; michael@0: LogInfo("MakeProgress: waiting for MozAfterPaint"); michael@0: } michael@0: return; michael@0: } michael@0: if (contentRootElement) { michael@0: var elements = getNoPaintElements(contentRootElement); michael@0: for (var i = 0; i < elements.length; ++i) { michael@0: if (windowUtils().checkAndClearPaintedState(elements[i])) { michael@0: SendFailedNoPaint(); michael@0: } michael@0: } michael@0: } michael@0: LogInfo("MakeProgress: Completed"); michael@0: state = STATE_COMPLETED; michael@0: gFailureReason = "timed out while taking snapshot (bug in harness?)"; michael@0: RemoveListeners(); michael@0: CheckForProcessCrashExpectation(); michael@0: setTimeout(RecordResult, 0); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: LogInfo("WaitForTestEnd: Adding listeners"); michael@0: addEventListener("MozAfterPaint", AfterPaintListener, false); michael@0: // If contentRootElement is null then shouldWaitForReftestWaitRemoval will michael@0: // always return false so we don't need a listener anyway michael@0: if (contentRootElement) { michael@0: contentRootElement.addEventListener("DOMAttrModified", AttrModifiedListener, false); michael@0: } michael@0: gExplicitPendingPaintsCompleteHook = ExplicitPaintsCompleteListener; michael@0: gTimeoutHook = RemoveListeners; michael@0: michael@0: // Listen for spell checks on spell-checked elements. michael@0: var numPendingSpellChecks = spellCheckedElements.length; michael@0: function decNumPendingSpellChecks() { michael@0: --numPendingSpellChecks; michael@0: MakeProgress(); michael@0: } michael@0: for (let editable of spellCheckedElements) { michael@0: try { michael@0: onSpellCheck(editable, decNumPendingSpellChecks); michael@0: } catch (err) { michael@0: // The element may not have an editor, so ignore it. michael@0: setTimeout(decNumPendingSpellChecks, 0); michael@0: } michael@0: } michael@0: michael@0: // Take a full snapshot now that all our listeners are set up. This michael@0: // ensures it's impossible for us to miss updates between taking the snapshot michael@0: // and adding our listeners. michael@0: SendInitCanvasWithSnapshot(); michael@0: MakeProgress(); michael@0: } michael@0: michael@0: function OnDocumentLoad(event) michael@0: { michael@0: var currentDoc = content.document; michael@0: if (event.target != currentDoc) michael@0: // Ignore load events for subframes. michael@0: return; michael@0: michael@0: if (gClearingForAssertionCheck && michael@0: currentDoc.location.href == BLANK_URL_FOR_CLEARING) { michael@0: DoAssertionCheck(); michael@0: return; michael@0: } michael@0: michael@0: if (currentDoc.location.href != gCurrentURL) { michael@0: LogInfo("OnDocumentLoad fired for previous document"); michael@0: // Ignore load events for previous documents. michael@0: return; michael@0: } michael@0: michael@0: // Collect all editable, spell-checked elements. It may be the case that michael@0: // not all the elements that match this selector will be spell checked: for michael@0: // example, a textarea without a spellcheck attribute may have a parent with michael@0: // spellcheck=false, or script may set spellcheck=false on an element whose michael@0: // markup sets it to true. But that's OK since onSpellCheck detects the michael@0: // absence of spell checking, too. michael@0: var querySelector = michael@0: '*[class~="spell-checked"],' + michael@0: 'textarea:not([spellcheck="false"]),' + michael@0: 'input[spellcheck]:-moz-any([spellcheck=""],[spellcheck="true"]),' + michael@0: '*[contenteditable]:-moz-any([contenteditable=""],[contenteditable="true"])'; michael@0: var spellCheckedElements = currentDoc.querySelectorAll(querySelector); michael@0: michael@0: var contentRootElement = currentDoc ? currentDoc.documentElement : null; michael@0: currentDoc = null; michael@0: setupZoom(contentRootElement); michael@0: setupViewport(contentRootElement); michael@0: setupDisplayport(contentRootElement); michael@0: var inPrintMode = false; michael@0: michael@0: function AfterOnLoadScripts() { michael@0: // Regrab the root element, because the document may have changed. michael@0: var contentRootElement = michael@0: content.document ? content.document.documentElement : null; michael@0: michael@0: // Flush the document in case it got modified in a load event handler. michael@0: FlushRendering(); michael@0: michael@0: // Take a snapshot now. We need to do this before we check whether michael@0: // we should wait, since this might trigger dispatching of michael@0: // MozPaintWait events and make shouldWaitForExplicitPaintWaiters() true michael@0: // below. michael@0: var painted = SendInitCanvasWithSnapshot(); michael@0: michael@0: if (shouldWaitForExplicitPaintWaiters() || michael@0: (!inPrintMode && doPrintMode(contentRootElement)) || michael@0: // If we didn't force a paint above, in michael@0: // InitCurrentCanvasWithSnapshot, so we should wait for a michael@0: // paint before we consider them done. michael@0: !painted) { michael@0: LogInfo("AfterOnLoadScripts belatedly entering WaitForTestEnd"); michael@0: // Go into reftest-wait mode belatedly. michael@0: WaitForTestEnd(contentRootElement, inPrintMode, []); michael@0: } else { michael@0: CheckForProcessCrashExpectation(); michael@0: RecordResult(); michael@0: } michael@0: } michael@0: michael@0: if (shouldWaitForReftestWaitRemoval(contentRootElement) || michael@0: shouldWaitForExplicitPaintWaiters() || michael@0: spellCheckedElements.length) { michael@0: // Go into reftest-wait mode immediately after painting has been michael@0: // unsuppressed, after the onload event has finished dispatching. michael@0: gFailureReason = "timed out waiting for test to complete (trying to get into WaitForTestEnd)"; michael@0: LogInfo("OnDocumentLoad triggering WaitForTestEnd"); michael@0: setTimeout(function () { WaitForTestEnd(contentRootElement, inPrintMode, spellCheckedElements); }, 0); michael@0: } else { michael@0: if (doPrintMode(contentRootElement)) { michael@0: LogInfo("OnDocumentLoad setting up print mode"); michael@0: setupPrintMode(); michael@0: inPrintMode = true; michael@0: } michael@0: michael@0: // Since we can't use a bubbling-phase load listener from chrome, michael@0: // this is a capturing phase listener. So do setTimeout twice, the michael@0: // first to get us after the onload has fired in the content, and michael@0: // the second to get us after any setTimeout(foo, 0) in the content. michael@0: gFailureReason = "timed out waiting for test to complete (waiting for onload scripts to complete)"; michael@0: LogInfo("OnDocumentLoad triggering AfterOnLoadScripts"); michael@0: setTimeout(function () { setTimeout(AfterOnLoadScripts, 0); }, 0); michael@0: } michael@0: } michael@0: michael@0: function CheckForProcessCrashExpectation() michael@0: { michael@0: var contentRootElement = content.document.documentElement; michael@0: if (contentRootElement && michael@0: contentRootElement.hasAttribute('class') && michael@0: contentRootElement.getAttribute('class').split(/\s+/) michael@0: .indexOf("reftest-expect-process-crash") != -1) { michael@0: SendExpectProcessCrash(); michael@0: } michael@0: } michael@0: michael@0: function RecordResult() michael@0: { michael@0: LogInfo("RecordResult fired"); michael@0: michael@0: var currentTestRunTime = Date.now() - gCurrentTestStartTime; michael@0: michael@0: clearTimeout(gFailureTimeout); michael@0: gFailureReason = null; michael@0: gFailureTimeout = null; michael@0: michael@0: if (gCurrentTestType == TYPE_SCRIPT) { michael@0: var error = ''; michael@0: var testwindow = content; michael@0: michael@0: if (testwindow.wrappedJSObject) michael@0: testwindow = testwindow.wrappedJSObject; michael@0: michael@0: var testcases; michael@0: if (!testwindow.getTestCases || typeof testwindow.getTestCases != "function") { michael@0: // Force an unexpected failure to alert the test author to fix the test. michael@0: error = "test must provide a function getTestCases(). (SCRIPT)\n"; michael@0: } michael@0: else if (!(testcases = testwindow.getTestCases())) { michael@0: // Force an unexpected failure to alert the test author to fix the test. michael@0: error = "test's getTestCases() must return an Array-like Object. (SCRIPT)\n"; michael@0: } michael@0: else if (testcases.length == 0) { michael@0: // This failure may be due to a JavaScript Engine bug causing michael@0: // early termination of the test. If we do not allow silent michael@0: // failure, the driver will report an error. michael@0: } michael@0: michael@0: var results = [ ]; michael@0: if (!error) { michael@0: // FIXME/bug 618176: temporary workaround michael@0: for (var i = 0; i < testcases.length; ++i) { michael@0: var test = testcases[i]; michael@0: results.push({ passed: test.testPassed(), michael@0: description: test.testDescription() }); michael@0: } michael@0: //results = testcases.map(function(test) { michael@0: // return { passed: test.testPassed(), michael@0: // description: test.testDescription() }; michael@0: } michael@0: michael@0: SendScriptResults(currentTestRunTime, error, results); michael@0: FinishTestItem(); michael@0: return; michael@0: } michael@0: michael@0: // Setup async scroll offsets now in case SynchronizeForSnapshot is not michael@0: // called (due to reftest-no-sync-layers being supplied). michael@0: setupAsyncScrollOffsets({allowFailure:true}); michael@0: SendTestDone(currentTestRunTime); michael@0: FinishTestItem(); michael@0: } michael@0: michael@0: function LoadFailed() michael@0: { michael@0: if (gTimeoutHook) { michael@0: gTimeoutHook(); michael@0: } michael@0: gFailureTimeout = null; michael@0: SendFailedLoad(gFailureReason); michael@0: } michael@0: michael@0: function FinishTestItem() michael@0: { michael@0: gHaveCanvasSnapshot = false; michael@0: } michael@0: michael@0: function DoAssertionCheck() michael@0: { michael@0: gClearingForAssertionCheck = false; michael@0: michael@0: var numAsserts = 0; michael@0: if (gDebug.isDebugBuild) { michael@0: var newAssertionCount = gDebug.assertionCount; michael@0: numAsserts = newAssertionCount - gAssertionCount; michael@0: gAssertionCount = newAssertionCount; michael@0: } michael@0: SendAssertionCount(numAsserts); michael@0: } michael@0: michael@0: function LoadURI(uri) michael@0: { michael@0: var flags = webNavigation().LOAD_FLAGS_NONE; michael@0: webNavigation().loadURI(uri, flags, null, null, null); michael@0: } michael@0: michael@0: function LogWarning(str) michael@0: { michael@0: if (gVerbose) { michael@0: sendSyncMessage("reftest:Log", { type: "warning", msg: str }); michael@0: } else { michael@0: sendAsyncMessage("reftest:Log", { type: "warning", msg: str }); michael@0: } michael@0: } michael@0: michael@0: function LogInfo(str) michael@0: { michael@0: if (gVerbose) { michael@0: sendSyncMessage("reftest:Log", { type: "info", msg: str }); michael@0: } else { michael@0: sendAsyncMessage("reftest:Log", { type: "info", msg: str }); michael@0: } michael@0: } michael@0: michael@0: const SYNC_DEFAULT = 0x0; michael@0: const SYNC_ALLOW_DISABLE = 0x1; michael@0: function SynchronizeForSnapshot(flags) michael@0: { michael@0: if (gCurrentTestType == TYPE_SCRIPT || michael@0: gCurrentTestType == TYPE_LOAD) { michael@0: // Script tests or load-only tests do not need any snapshotting michael@0: return; michael@0: } michael@0: michael@0: if (flags & SYNC_ALLOW_DISABLE) { michael@0: var docElt = content.document.documentElement; michael@0: if (docElt && docElt.hasAttribute("reftest-no-sync-layers")) { michael@0: LogInfo("Test file chose to skip SynchronizeForSnapshot"); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: var dummyCanvas = content.document.createElementNS(XHTML_NS, "canvas"); michael@0: dummyCanvas.setAttribute("width", 1); michael@0: dummyCanvas.setAttribute("height", 1); michael@0: michael@0: var ctx = dummyCanvas.getContext("2d"); michael@0: var flags = ctx.DRAWWINDOW_DRAW_CARET | ctx.DRAWWINDOW_DRAW_VIEW | ctx.DRAWWINDOW_USE_WIDGET_LAYERS; michael@0: ctx.drawWindow(content, 0, 0, 1, 1, "rgb(255,255,255)", flags); michael@0: michael@0: // Setup async scroll offsets now, because any scrollable layers should michael@0: // have had their AsyncPanZoomControllers created. michael@0: setupAsyncScrollOffsets({allowFailure:false}); michael@0: } michael@0: michael@0: function RegisterMessageListeners() michael@0: { michael@0: addMessageListener( michael@0: "reftest:Clear", michael@0: function (m) { RecvClear() } michael@0: ); michael@0: addMessageListener( michael@0: "reftest:LoadScriptTest", michael@0: function (m) { RecvLoadScriptTest(m.json.uri, m.json.timeout); } michael@0: ); michael@0: addMessageListener( michael@0: "reftest:LoadTest", michael@0: function (m) { RecvLoadTest(m.json.type, m.json.uri, m.json.timeout); } michael@0: ); michael@0: addMessageListener( michael@0: "reftest:ResetRenderingState", michael@0: function (m) { RecvResetRenderingState(); } michael@0: ); michael@0: } michael@0: michael@0: function RecvClear() michael@0: { michael@0: gClearingForAssertionCheck = true; michael@0: LoadURI(BLANK_URL_FOR_CLEARING); michael@0: } michael@0: michael@0: function RecvLoadTest(type, uri, timeout) michael@0: { michael@0: StartTestURI(type, uri, timeout); michael@0: } michael@0: michael@0: function RecvLoadScriptTest(uri, timeout) michael@0: { michael@0: StartTestURI(TYPE_SCRIPT, uri, timeout); michael@0: } michael@0: michael@0: function RecvResetRenderingState() michael@0: { michael@0: resetZoom(); michael@0: resetDisplayportAndViewport(); michael@0: } michael@0: michael@0: function SendAssertionCount(numAssertions) michael@0: { michael@0: sendAsyncMessage("reftest:AssertionCount", { count: numAssertions }); michael@0: } michael@0: michael@0: function SendContentReady() michael@0: { michael@0: return sendSyncMessage("reftest:ContentReady")[0]; michael@0: } michael@0: michael@0: function SendException(what) michael@0: { michael@0: sendAsyncMessage("reftest:Exception", { what: what }); michael@0: } michael@0: michael@0: function SendFailedLoad(why) michael@0: { michael@0: sendAsyncMessage("reftest:FailedLoad", { why: why }); michael@0: } michael@0: michael@0: function SendFailedNoPaint() michael@0: { michael@0: sendAsyncMessage("reftest:FailedNoPaint"); michael@0: } michael@0: michael@0: function SendEnableAsyncScroll() michael@0: { michael@0: sendAsyncMessage("reftest:EnableAsyncScroll"); michael@0: } michael@0: michael@0: // Return true if a snapshot was taken. michael@0: function SendInitCanvasWithSnapshot() michael@0: { michael@0: // If we're in the same process as the top-level XUL window, then michael@0: // drawing that window will also update our layers, so no michael@0: // synchronization is needed. michael@0: // michael@0: // NB: this is a test-harness optimization only, it must not michael@0: // affect the validity of the tests. michael@0: if (gBrowserIsRemote) { michael@0: SynchronizeForSnapshot(SYNC_DEFAULT); michael@0: } michael@0: michael@0: // For in-process browser, we have to make a synchronous request michael@0: // here to make the above optimization valid, so that MozWaitPaint michael@0: // events dispatched (synchronously) during painting are received michael@0: // before we check the paint-wait counter. For out-of-process michael@0: // browser though, it doesn't wrt correctness whether this request michael@0: // is sync or async. michael@0: var ret = sendSyncMessage("reftest:InitCanvasWithSnapshot")[0]; michael@0: michael@0: gHaveCanvasSnapshot = ret.painted; michael@0: return ret.painted; michael@0: } michael@0: michael@0: function SendScriptResults(runtimeMs, error, results) michael@0: { michael@0: sendAsyncMessage("reftest:ScriptResults", michael@0: { runtimeMs: runtimeMs, error: error, results: results }); michael@0: } michael@0: michael@0: function SendExpectProcessCrash(runtimeMs) michael@0: { michael@0: sendAsyncMessage("reftest:ExpectProcessCrash"); michael@0: } michael@0: michael@0: function SendTestDone(runtimeMs) michael@0: { michael@0: sendAsyncMessage("reftest:TestDone", { runtimeMs: runtimeMs }); michael@0: } michael@0: michael@0: function roundTo(x, fraction) michael@0: { michael@0: return Math.round(x/fraction)*fraction; michael@0: } michael@0: michael@0: function SendUpdateCanvasForEvent(event, contentRootElement) michael@0: { michael@0: var win = content; michael@0: var scale = markupDocumentViewer().fullZoom; michael@0: michael@0: var rects = [ ]; michael@0: if (shouldSnapshotWholePage(contentRootElement)) { michael@0: // See comments in SendInitCanvasWithSnapshot() re: the split michael@0: // logic here. michael@0: if (!gBrowserIsRemote) { michael@0: sendSyncMessage("reftest:UpdateWholeCanvasForInvalidation"); michael@0: } else { michael@0: SynchronizeForSnapshot(SYNC_ALLOW_DISABLE); michael@0: sendAsyncMessage("reftest:UpdateWholeCanvasForInvalidation"); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: var rectList = event.clientRects; michael@0: LogInfo("SendUpdateCanvasForEvent with " + rectList.length + " rects"); michael@0: for (var i = 0; i < rectList.length; ++i) { michael@0: var r = rectList[i]; michael@0: // Set left/top/right/bottom to "device pixel" boundaries michael@0: var left = Math.floor(roundTo(r.left*scale, 0.001)); michael@0: var top = Math.floor(roundTo(r.top*scale, 0.001)); michael@0: var right = Math.ceil(roundTo(r.right*scale, 0.001)); michael@0: var bottom = Math.ceil(roundTo(r.bottom*scale, 0.001)); michael@0: LogInfo("Rect: " + left + " " + top + " " + right + " " + bottom); michael@0: michael@0: rects.push({ left: left, top: top, right: right, bottom: bottom }); michael@0: } michael@0: michael@0: // See comments in SendInitCanvasWithSnapshot() re: the split michael@0: // logic here. michael@0: if (!gBrowserIsRemote) { michael@0: sendSyncMessage("reftest:UpdateCanvasForInvalidation", { rects: rects }); michael@0: } else { michael@0: SynchronizeForSnapshot(SYNC_ALLOW_DISABLE); michael@0: sendAsyncMessage("reftest:UpdateCanvasForInvalidation", { rects: rects }); michael@0: } michael@0: } michael@0: #if REFTEST_B2G michael@0: OnInitialLoad(); michael@0: #else michael@0: addEventListener("load", OnInitialLoad, true); michael@0: #endif