1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/layout/tools/reftest/reftest-content.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,964 @@ 1.4 +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- / 1.5 +/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +const CC = Components.classes; 1.11 +const CI = Components.interfaces; 1.12 +const CR = Components.results; 1.13 +const CU = Components.utils; 1.14 + 1.15 +const XHTML_NS = "http://www.w3.org/1999/xhtml"; 1.16 + 1.17 +const DEBUG_CONTRACTID = "@mozilla.org/xpcom/debug;1"; 1.18 +const PRINTSETTINGS_CONTRACTID = "@mozilla.org/gfx/printsettings-service;1"; 1.19 +const ENVIRONMENT_CONTRACTID = "@mozilla.org/process/environment;1"; 1.20 + 1.21 +// "<!--CLEAR-->" 1.22 +const BLANK_URL_FOR_CLEARING = "data:text/html;charset=UTF-8,%3C%21%2D%2DCLEAR%2D%2D%3E"; 1.23 + 1.24 +CU.import("resource://gre/modules/Timer.jsm"); 1.25 +CU.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm"); 1.26 + 1.27 +var gBrowserIsRemote; 1.28 +var gHaveCanvasSnapshot = false; 1.29 +// Plugin layers can be updated asynchronously, so to make sure that all 1.30 +// layer surfaces have the right content, we need to listen for explicit 1.31 +// "MozPaintWait" and "MozPaintWaitFinished" events that signal when it's OK 1.32 +// to take snapshots. We cannot take a snapshot while the number of 1.33 +// "MozPaintWait" events fired exceeds the number of "MozPaintWaitFinished" 1.34 +// events fired. We count the number of such excess events here. When 1.35 +// the counter reaches zero we call gExplicitPendingPaintsCompleteHook. 1.36 +var gExplicitPendingPaintCount = 0; 1.37 +var gExplicitPendingPaintsCompleteHook; 1.38 +var gCurrentURL; 1.39 +var gCurrentTestType; 1.40 +var gTimeoutHook = null; 1.41 +var gFailureTimeout = null; 1.42 +var gFailureReason; 1.43 +var gAssertionCount = 0; 1.44 + 1.45 +var gDebug; 1.46 +var gVerbose = false; 1.47 + 1.48 +var gCurrentTestStartTime; 1.49 +var gClearingForAssertionCheck = false; 1.50 + 1.51 +const TYPE_LOAD = 'load'; // test without a reference (just test that it does 1.52 + // not assert, crash, hang, or leak) 1.53 +const TYPE_SCRIPT = 'script'; // test contains individual test results 1.54 + 1.55 +function markupDocumentViewer() { 1.56 + return docShell.contentViewer.QueryInterface(CI.nsIMarkupDocumentViewer); 1.57 +} 1.58 + 1.59 +function webNavigation() { 1.60 + return docShell.QueryInterface(CI.nsIWebNavigation); 1.61 +} 1.62 + 1.63 +function windowUtils() { 1.64 + return content.QueryInterface(CI.nsIInterfaceRequestor) 1.65 + .getInterface(CI.nsIDOMWindowUtils); 1.66 +} 1.67 + 1.68 +function IDForEventTarget(event) 1.69 +{ 1.70 + try { 1.71 + return "'" + event.target.getAttribute('id') + "'"; 1.72 + } catch (ex) { 1.73 + return "<unknown>"; 1.74 + } 1.75 +} 1.76 + 1.77 +function PaintWaitListener(event) 1.78 +{ 1.79 + LogInfo("MozPaintWait received for ID " + IDForEventTarget(event)); 1.80 + gExplicitPendingPaintCount++; 1.81 +} 1.82 + 1.83 +function PaintWaitFinishedListener(event) 1.84 +{ 1.85 + LogInfo("MozPaintWaitFinished received for ID " + IDForEventTarget(event)); 1.86 + gExplicitPendingPaintCount--; 1.87 + if (gExplicitPendingPaintCount < 0) { 1.88 + LogWarning("Underrun in gExplicitPendingPaintCount\n"); 1.89 + gExplicitPendingPaintCount = 0; 1.90 + } 1.91 + if (gExplicitPendingPaintCount == 0 && 1.92 + gExplicitPendingPaintsCompleteHook) { 1.93 + gExplicitPendingPaintsCompleteHook(); 1.94 + } 1.95 +} 1.96 + 1.97 +function OnInitialLoad() 1.98 +{ 1.99 +#ifndef REFTEST_B2G 1.100 + removeEventListener("load", OnInitialLoad, true); 1.101 +#endif 1.102 + 1.103 + gDebug = CC[DEBUG_CONTRACTID].getService(CI.nsIDebug2); 1.104 + var env = CC[ENVIRONMENT_CONTRACTID].getService(CI.nsIEnvironment); 1.105 + gVerbose = !!env.get("MOZ_REFTEST_VERBOSE"); 1.106 + 1.107 + RegisterMessageListeners(); 1.108 + 1.109 + var initInfo = SendContentReady(); 1.110 + gBrowserIsRemote = initInfo.remote; 1.111 + 1.112 + addEventListener("load", OnDocumentLoad, true); 1.113 + 1.114 + addEventListener("MozPaintWait", PaintWaitListener, true); 1.115 + addEventListener("MozPaintWaitFinished", PaintWaitFinishedListener, true); 1.116 + 1.117 + LogWarning("Using browser remote="+ gBrowserIsRemote +"\n"); 1.118 +} 1.119 + 1.120 +function StartTestURI(type, uri, timeout) 1.121 +{ 1.122 + // Reset gExplicitPendingPaintCount in case there was a timeout or 1.123 + // the count is out of sync for some other reason 1.124 + if (gExplicitPendingPaintCount != 0) { 1.125 + LogWarning("Resetting gExplicitPendingPaintCount to zero (currently " + 1.126 + gExplicitPendingPaintCount + "\n"); 1.127 + gExplicitPendingPaintCount = 0; 1.128 + } 1.129 + 1.130 + gCurrentTestType = type; 1.131 + gCurrentURL = uri; 1.132 + 1.133 + gCurrentTestStartTime = Date.now(); 1.134 + if (gFailureTimeout != null) { 1.135 + SendException("program error managing timeouts\n"); 1.136 + } 1.137 + gFailureTimeout = setTimeout(LoadFailed, timeout); 1.138 + 1.139 + LoadURI(gCurrentURL); 1.140 +} 1.141 + 1.142 +function setupZoom(contentRootElement) { 1.143 + if (!contentRootElement || !contentRootElement.hasAttribute('reftest-zoom')) 1.144 + return; 1.145 + markupDocumentViewer().fullZoom = 1.146 + contentRootElement.getAttribute('reftest-zoom'); 1.147 +} 1.148 + 1.149 +function resetZoom() { 1.150 + markupDocumentViewer().fullZoom = 1.0; 1.151 +} 1.152 + 1.153 +function doPrintMode(contentRootElement) { 1.154 +#if REFTEST_B2G 1.155 + // nsIPrintSettings not available in B2G 1.156 + return false; 1.157 +#else 1.158 + // use getAttribute because className works differently in HTML and SVG 1.159 + return contentRootElement && 1.160 + contentRootElement.hasAttribute('class') && 1.161 + contentRootElement.getAttribute('class').split(/\s+/) 1.162 + .indexOf("reftest-print") != -1; 1.163 +#endif 1.164 +} 1.165 + 1.166 +function setupPrintMode() { 1.167 + var PSSVC = 1.168 + CC[PRINTSETTINGS_CONTRACTID].getService(CI.nsIPrintSettingsService); 1.169 + var ps = PSSVC.newPrintSettings; 1.170 + ps.paperWidth = 5; 1.171 + ps.paperHeight = 3; 1.172 + 1.173 + // Override any os-specific unwriteable margins 1.174 + ps.unwriteableMarginTop = 0; 1.175 + ps.unwriteableMarginLeft = 0; 1.176 + ps.unwriteableMarginBottom = 0; 1.177 + ps.unwriteableMarginRight = 0; 1.178 + 1.179 + ps.headerStrLeft = ""; 1.180 + ps.headerStrCenter = ""; 1.181 + ps.headerStrRight = ""; 1.182 + ps.footerStrLeft = ""; 1.183 + ps.footerStrCenter = ""; 1.184 + ps.footerStrRight = ""; 1.185 + docShell.contentViewer.setPageMode(true, ps); 1.186 +} 1.187 + 1.188 +function attrOrDefault(element, attr, def) { 1.189 + return element.hasAttribute(attr) ? Number(element.getAttribute(attr)) : def; 1.190 +} 1.191 + 1.192 +function setupViewport(contentRootElement) { 1.193 + if (!contentRootElement) { 1.194 + return; 1.195 + } 1.196 + 1.197 + var vw = attrOrDefault(contentRootElement, "reftest-viewport-w", 0); 1.198 + var vh = attrOrDefault(contentRootElement, "reftest-viewport-h", 0); 1.199 + if (vw !== 0 || vh !== 0) { 1.200 + LogInfo("Setting viewport to <w="+ vw +", h="+ vh +">"); 1.201 + windowUtils().setCSSViewport(vw, vh); 1.202 + } 1.203 + 1.204 + // XXX support resolution when needed 1.205 + 1.206 + // XXX support viewconfig when needed 1.207 +} 1.208 + 1.209 +function setupDisplayport(contentRootElement) { 1.210 + if (!contentRootElement) { 1.211 + return; 1.212 + } 1.213 + 1.214 + function setupDisplayportForElement(element) { 1.215 + var dpw = attrOrDefault(element, "reftest-displayport-w", 0); 1.216 + var dph = attrOrDefault(element, "reftest-displayport-h", 0); 1.217 + var dpx = attrOrDefault(element, "reftest-displayport-x", 0); 1.218 + var dpy = attrOrDefault(element, "reftest-displayport-y", 0); 1.219 + if (dpw !== 0 || dph !== 0 || dpx != 0 || dpy != 0) { 1.220 + LogInfo("Setting displayport to <x="+ dpx +", y="+ dpy +", w="+ dpw +", h="+ dph +">"); 1.221 + windowUtils().setDisplayPortForElement(dpx, dpy, dpw, dph, element, 1); 1.222 + } 1.223 + } 1.224 + 1.225 + function setupDisplayportForElementSubtree(element) { 1.226 + setupDisplayportForElement(element); 1.227 + for (var c = element.firstElementChild; c; c = c.nextElementSibling) { 1.228 + setupDisplayportForElementSubtree(c); 1.229 + } 1.230 + } 1.231 + 1.232 + if (contentRootElement.hasAttribute("reftest-async-scroll")) { 1.233 + SendEnableAsyncScroll(); 1.234 + setupDisplayportForElementSubtree(contentRootElement); 1.235 + } else { 1.236 + setupDisplayportForElement(contentRootElement); 1.237 + } 1.238 +} 1.239 + 1.240 +function setupAsyncScrollOffsets(options) { 1.241 + var currentDoc = content.document; 1.242 + var contentRootElement = currentDoc ? currentDoc.documentElement : null; 1.243 + 1.244 + if (!contentRootElement) { 1.245 + return; 1.246 + } 1.247 + 1.248 + function setupAsyncScrollOffsetsForElement(element) { 1.249 + var sx = attrOrDefault(element, "reftest-async-scroll-x", 0); 1.250 + var sy = attrOrDefault(element, "reftest-async-scroll-y", 0); 1.251 + if (sx != 0 || sy != 0) { 1.252 + try { 1.253 + // This might fail when called from RecordResult since layers 1.254 + // may not have been constructed yet 1.255 + windowUtils().setAsyncScrollOffset(element, sx, sy); 1.256 + } catch (e) { 1.257 + if (!options.allowFailure) { 1.258 + throw e; 1.259 + } 1.260 + } 1.261 + } 1.262 + } 1.263 + 1.264 + function setupAsyncScrollOffsetsForElementSubtree(element) { 1.265 + setupAsyncScrollOffsetsForElement(element); 1.266 + for (var c = element.firstElementChild; c; c = c.nextElementSibling) { 1.267 + setupAsyncScrollOffsetsForElementSubtree(c); 1.268 + } 1.269 + } 1.270 + 1.271 + var asyncScroll = contentRootElement.hasAttribute("reftest-async-scroll"); 1.272 + if (asyncScroll) { 1.273 + setupAsyncScrollOffsetsForElementSubtree(contentRootElement); 1.274 + } 1.275 +} 1.276 + 1.277 +function resetDisplayportAndViewport() { 1.278 + // XXX currently the displayport configuration lives on the 1.279 + // presshell and so is "reset" on nav when we get a new presshell. 1.280 +} 1.281 + 1.282 +function shouldWaitForExplicitPaintWaiters() { 1.283 + return gExplicitPendingPaintCount > 0; 1.284 +} 1.285 + 1.286 +function shouldWaitForPendingPaints() { 1.287 + // if gHaveCanvasSnapshot is false, we're not taking snapshots so 1.288 + // there is no need to wait for pending paints to be flushed. 1.289 + return gHaveCanvasSnapshot && windowUtils().isMozAfterPaintPending; 1.290 +} 1.291 + 1.292 +function shouldWaitForReftestWaitRemoval(contentRootElement) { 1.293 + // use getAttribute because className works differently in HTML and SVG 1.294 + return contentRootElement && 1.295 + contentRootElement.hasAttribute('class') && 1.296 + contentRootElement.getAttribute('class').split(/\s+/) 1.297 + .indexOf("reftest-wait") != -1; 1.298 +} 1.299 + 1.300 +function shouldSnapshotWholePage(contentRootElement) { 1.301 + // use getAttribute because className works differently in HTML and SVG 1.302 + return contentRootElement && 1.303 + contentRootElement.hasAttribute('class') && 1.304 + contentRootElement.getAttribute('class').split(/\s+/) 1.305 + .indexOf("reftest-snapshot-all") != -1; 1.306 +} 1.307 + 1.308 +function getNoPaintElements(contentRootElement) { 1.309 + return contentRootElement.getElementsByClassName('reftest-no-paint'); 1.310 +} 1.311 + 1.312 +// Initial state. When the document has loaded and all MozAfterPaint events and 1.313 +// all explicit paint waits are flushed, we can fire the MozReftestInvalidate 1.314 +// event and move to the next state. 1.315 +const STATE_WAITING_TO_FIRE_INVALIDATE_EVENT = 0; 1.316 +// When reftest-wait has been removed from the root element, we can move to the 1.317 +// next state. 1.318 +const STATE_WAITING_FOR_REFTEST_WAIT_REMOVAL = 1; 1.319 +// When spell checking is done on all spell-checked elements, we can move to the 1.320 +// next state. 1.321 +const STATE_WAITING_FOR_SPELL_CHECKS = 2; 1.322 +// When all MozAfterPaint events and all explicit paint waits are flushed, we're 1.323 +// done and can move to the COMPLETED state. 1.324 +const STATE_WAITING_TO_FINISH = 3; 1.325 +const STATE_COMPLETED = 4; 1.326 + 1.327 +function FlushRendering() { 1.328 + var anyPendingPaintsGeneratedInDescendants = false; 1.329 + 1.330 + function flushWindow(win) { 1.331 + var utils = win.QueryInterface(CI.nsIInterfaceRequestor) 1.332 + .getInterface(CI.nsIDOMWindowUtils); 1.333 + var afterPaintWasPending = utils.isMozAfterPaintPending; 1.334 + 1.335 + if (win.document.documentElement) { 1.336 + try { 1.337 + // Flush pending restyles and reflows for this window 1.338 + win.document.documentElement.getBoundingClientRect(); 1.339 + } catch (e) { 1.340 + LogWarning("flushWindow failed: " + e + "\n"); 1.341 + } 1.342 + } 1.343 + 1.344 + if (!afterPaintWasPending && utils.isMozAfterPaintPending) { 1.345 + LogInfo("FlushRendering generated paint for window " + win.location.href); 1.346 + anyPendingPaintsGeneratedInDescendants = true; 1.347 + } 1.348 + 1.349 + for (var i = 0; i < win.frames.length; ++i) { 1.350 + flushWindow(win.frames[i]); 1.351 + } 1.352 + } 1.353 + 1.354 + flushWindow(content); 1.355 + 1.356 + if (anyPendingPaintsGeneratedInDescendants && 1.357 + !windowUtils().isMozAfterPaintPending) { 1.358 + LogWarning("Internal error: descendant frame generated a MozAfterPaint event, but the root document doesn't have one!"); 1.359 + } 1.360 +} 1.361 + 1.362 +function WaitForTestEnd(contentRootElement, inPrintMode, spellCheckedElements) { 1.363 + var stopAfterPaintReceived = false; 1.364 + var currentDoc = content.document; 1.365 + var state = STATE_WAITING_TO_FIRE_INVALIDATE_EVENT; 1.366 + 1.367 + function AfterPaintListener(event) { 1.368 + LogInfo("AfterPaintListener in " + event.target.document.location.href); 1.369 + if (event.target.document != currentDoc) { 1.370 + // ignore paint events for subframes or old documents in the window. 1.371 + // Invalidation in subframes will cause invalidation in the toplevel document anyway. 1.372 + return; 1.373 + } 1.374 + 1.375 + SendUpdateCanvasForEvent(event, contentRootElement); 1.376 + // These events are fired immediately after a paint. Don't 1.377 + // confuse ourselves by firing synchronously if we triggered the 1.378 + // paint ourselves. 1.379 + setTimeout(MakeProgress, 0); 1.380 + } 1.381 + 1.382 + function AttrModifiedListener() { 1.383 + LogInfo("AttrModifiedListener fired"); 1.384 + // Wait for the next return-to-event-loop before continuing --- for 1.385 + // example, the attribute may have been modified in an subdocument's 1.386 + // load event handler, in which case we need load event processing 1.387 + // to complete and unsuppress painting before we check isMozAfterPaintPending. 1.388 + setTimeout(MakeProgress, 0); 1.389 + } 1.390 + 1.391 + function ExplicitPaintsCompleteListener() { 1.392 + LogInfo("ExplicitPaintsCompleteListener fired"); 1.393 + // Since this can fire while painting, don't confuse ourselves by 1.394 + // firing synchronously. It's fine to do this asynchronously. 1.395 + setTimeout(MakeProgress, 0); 1.396 + } 1.397 + 1.398 + function RemoveListeners() { 1.399 + // OK, we can end the test now. 1.400 + removeEventListener("MozAfterPaint", AfterPaintListener, false); 1.401 + if (contentRootElement) { 1.402 + contentRootElement.removeEventListener("DOMAttrModified", AttrModifiedListener, false); 1.403 + } 1.404 + gExplicitPendingPaintsCompleteHook = null; 1.405 + gTimeoutHook = null; 1.406 + // Make sure we're in the COMPLETED state just in case 1.407 + // (this may be called via the test-timeout hook) 1.408 + state = STATE_COMPLETED; 1.409 + } 1.410 + 1.411 + // Everything that could cause shouldWaitForXXX() to 1.412 + // change from returning true to returning false is monitored via some kind 1.413 + // of event listener which eventually calls this function. 1.414 + function MakeProgress() { 1.415 + if (state >= STATE_COMPLETED) { 1.416 + LogInfo("MakeProgress: STATE_COMPLETED"); 1.417 + return; 1.418 + } 1.419 + 1.420 + FlushRendering(); 1.421 + 1.422 + switch (state) { 1.423 + case STATE_WAITING_TO_FIRE_INVALIDATE_EVENT: { 1.424 + LogInfo("MakeProgress: STATE_WAITING_TO_FIRE_INVALIDATE_EVENT"); 1.425 + if (shouldWaitForExplicitPaintWaiters() || shouldWaitForPendingPaints()) { 1.426 + gFailureReason = "timed out waiting for pending paint count to reach zero"; 1.427 + if (shouldWaitForExplicitPaintWaiters()) { 1.428 + gFailureReason += " (waiting for MozPaintWaitFinished)"; 1.429 + LogInfo("MakeProgress: waiting for MozPaintWaitFinished"); 1.430 + } 1.431 + if (shouldWaitForPendingPaints()) { 1.432 + gFailureReason += " (waiting for MozAfterPaint)"; 1.433 + LogInfo("MakeProgress: waiting for MozAfterPaint"); 1.434 + } 1.435 + return; 1.436 + } 1.437 + 1.438 + state = STATE_WAITING_FOR_REFTEST_WAIT_REMOVAL; 1.439 + var hasReftestWait = shouldWaitForReftestWaitRemoval(contentRootElement); 1.440 + // Notify the test document that now is a good time to test some invalidation 1.441 + LogInfo("MakeProgress: dispatching MozReftestInvalidate"); 1.442 + if (contentRootElement) { 1.443 + var elements = getNoPaintElements(contentRootElement); 1.444 + for (var i = 0; i < elements.length; ++i) { 1.445 + windowUtils().checkAndClearPaintedState(elements[i]); 1.446 + } 1.447 + var notification = content.document.createEvent("Events"); 1.448 + notification.initEvent("MozReftestInvalidate", true, false); 1.449 + contentRootElement.dispatchEvent(notification); 1.450 + } 1.451 + 1.452 + if (!inPrintMode && doPrintMode(contentRootElement)) { 1.453 + LogInfo("MakeProgress: setting up print mode"); 1.454 + setupPrintMode(); 1.455 + } 1.456 + 1.457 + if (hasReftestWait && !shouldWaitForReftestWaitRemoval(contentRootElement)) { 1.458 + // MozReftestInvalidate handler removed reftest-wait. 1.459 + // We expect something to have been invalidated... 1.460 + FlushRendering(); 1.461 + if (!shouldWaitForPendingPaints() && !shouldWaitForExplicitPaintWaiters()) { 1.462 + LogWarning("MozInvalidateEvent didn't invalidate"); 1.463 + } 1.464 + } 1.465 + // Try next state 1.466 + MakeProgress(); 1.467 + return; 1.468 + } 1.469 + 1.470 + case STATE_WAITING_FOR_REFTEST_WAIT_REMOVAL: 1.471 + LogInfo("MakeProgress: STATE_WAITING_FOR_REFTEST_WAIT_REMOVAL"); 1.472 + if (shouldWaitForReftestWaitRemoval(contentRootElement)) { 1.473 + gFailureReason = "timed out waiting for reftest-wait to be removed"; 1.474 + LogInfo("MakeProgress: waiting for reftest-wait to be removed"); 1.475 + return; 1.476 + } 1.477 + 1.478 + // Try next state 1.479 + state = STATE_WAITING_FOR_SPELL_CHECKS; 1.480 + MakeProgress(); 1.481 + return; 1.482 + 1.483 + case STATE_WAITING_FOR_SPELL_CHECKS: 1.484 + LogInfo("MakeProgress: STATE_WAITING_FOR_SPELL_CHECKS"); 1.485 + if (numPendingSpellChecks) { 1.486 + gFailureReason = "timed out waiting for spell checks to end"; 1.487 + LogInfo("MakeProgress: waiting for spell checks to end"); 1.488 + return; 1.489 + } 1.490 + 1.491 + state = STATE_WAITING_TO_FINISH; 1.492 + // Try next state 1.493 + MakeProgress(); 1.494 + return; 1.495 + 1.496 + case STATE_WAITING_TO_FINISH: 1.497 + LogInfo("MakeProgress: STATE_WAITING_TO_FINISH"); 1.498 + if (shouldWaitForExplicitPaintWaiters() || shouldWaitForPendingPaints()) { 1.499 + gFailureReason = "timed out waiting for pending paint count to " + 1.500 + "reach zero (after reftest-wait removed and switch to print mode)"; 1.501 + if (shouldWaitForExplicitPaintWaiters()) { 1.502 + gFailureReason += " (waiting for MozPaintWaitFinished)"; 1.503 + LogInfo("MakeProgress: waiting for MozPaintWaitFinished"); 1.504 + } 1.505 + if (shouldWaitForPendingPaints()) { 1.506 + gFailureReason += " (waiting for MozAfterPaint)"; 1.507 + LogInfo("MakeProgress: waiting for MozAfterPaint"); 1.508 + } 1.509 + return; 1.510 + } 1.511 + if (contentRootElement) { 1.512 + var elements = getNoPaintElements(contentRootElement); 1.513 + for (var i = 0; i < elements.length; ++i) { 1.514 + if (windowUtils().checkAndClearPaintedState(elements[i])) { 1.515 + SendFailedNoPaint(); 1.516 + } 1.517 + } 1.518 + } 1.519 + LogInfo("MakeProgress: Completed"); 1.520 + state = STATE_COMPLETED; 1.521 + gFailureReason = "timed out while taking snapshot (bug in harness?)"; 1.522 + RemoveListeners(); 1.523 + CheckForProcessCrashExpectation(); 1.524 + setTimeout(RecordResult, 0); 1.525 + return; 1.526 + } 1.527 + } 1.528 + 1.529 + LogInfo("WaitForTestEnd: Adding listeners"); 1.530 + addEventListener("MozAfterPaint", AfterPaintListener, false); 1.531 + // If contentRootElement is null then shouldWaitForReftestWaitRemoval will 1.532 + // always return false so we don't need a listener anyway 1.533 + if (contentRootElement) { 1.534 + contentRootElement.addEventListener("DOMAttrModified", AttrModifiedListener, false); 1.535 + } 1.536 + gExplicitPendingPaintsCompleteHook = ExplicitPaintsCompleteListener; 1.537 + gTimeoutHook = RemoveListeners; 1.538 + 1.539 + // Listen for spell checks on spell-checked elements. 1.540 + var numPendingSpellChecks = spellCheckedElements.length; 1.541 + function decNumPendingSpellChecks() { 1.542 + --numPendingSpellChecks; 1.543 + MakeProgress(); 1.544 + } 1.545 + for (let editable of spellCheckedElements) { 1.546 + try { 1.547 + onSpellCheck(editable, decNumPendingSpellChecks); 1.548 + } catch (err) { 1.549 + // The element may not have an editor, so ignore it. 1.550 + setTimeout(decNumPendingSpellChecks, 0); 1.551 + } 1.552 + } 1.553 + 1.554 + // Take a full snapshot now that all our listeners are set up. This 1.555 + // ensures it's impossible for us to miss updates between taking the snapshot 1.556 + // and adding our listeners. 1.557 + SendInitCanvasWithSnapshot(); 1.558 + MakeProgress(); 1.559 +} 1.560 + 1.561 +function OnDocumentLoad(event) 1.562 +{ 1.563 + var currentDoc = content.document; 1.564 + if (event.target != currentDoc) 1.565 + // Ignore load events for subframes. 1.566 + return; 1.567 + 1.568 + if (gClearingForAssertionCheck && 1.569 + currentDoc.location.href == BLANK_URL_FOR_CLEARING) { 1.570 + DoAssertionCheck(); 1.571 + return; 1.572 + } 1.573 + 1.574 + if (currentDoc.location.href != gCurrentURL) { 1.575 + LogInfo("OnDocumentLoad fired for previous document"); 1.576 + // Ignore load events for previous documents. 1.577 + return; 1.578 + } 1.579 + 1.580 + // Collect all editable, spell-checked elements. It may be the case that 1.581 + // not all the elements that match this selector will be spell checked: for 1.582 + // example, a textarea without a spellcheck attribute may have a parent with 1.583 + // spellcheck=false, or script may set spellcheck=false on an element whose 1.584 + // markup sets it to true. But that's OK since onSpellCheck detects the 1.585 + // absence of spell checking, too. 1.586 + var querySelector = 1.587 + '*[class~="spell-checked"],' + 1.588 + 'textarea:not([spellcheck="false"]),' + 1.589 + 'input[spellcheck]:-moz-any([spellcheck=""],[spellcheck="true"]),' + 1.590 + '*[contenteditable]:-moz-any([contenteditable=""],[contenteditable="true"])'; 1.591 + var spellCheckedElements = currentDoc.querySelectorAll(querySelector); 1.592 + 1.593 + var contentRootElement = currentDoc ? currentDoc.documentElement : null; 1.594 + currentDoc = null; 1.595 + setupZoom(contentRootElement); 1.596 + setupViewport(contentRootElement); 1.597 + setupDisplayport(contentRootElement); 1.598 + var inPrintMode = false; 1.599 + 1.600 + function AfterOnLoadScripts() { 1.601 + // Regrab the root element, because the document may have changed. 1.602 + var contentRootElement = 1.603 + content.document ? content.document.documentElement : null; 1.604 + 1.605 + // Flush the document in case it got modified in a load event handler. 1.606 + FlushRendering(); 1.607 + 1.608 + // Take a snapshot now. We need to do this before we check whether 1.609 + // we should wait, since this might trigger dispatching of 1.610 + // MozPaintWait events and make shouldWaitForExplicitPaintWaiters() true 1.611 + // below. 1.612 + var painted = SendInitCanvasWithSnapshot(); 1.613 + 1.614 + if (shouldWaitForExplicitPaintWaiters() || 1.615 + (!inPrintMode && doPrintMode(contentRootElement)) || 1.616 + // If we didn't force a paint above, in 1.617 + // InitCurrentCanvasWithSnapshot, so we should wait for a 1.618 + // paint before we consider them done. 1.619 + !painted) { 1.620 + LogInfo("AfterOnLoadScripts belatedly entering WaitForTestEnd"); 1.621 + // Go into reftest-wait mode belatedly. 1.622 + WaitForTestEnd(contentRootElement, inPrintMode, []); 1.623 + } else { 1.624 + CheckForProcessCrashExpectation(); 1.625 + RecordResult(); 1.626 + } 1.627 + } 1.628 + 1.629 + if (shouldWaitForReftestWaitRemoval(contentRootElement) || 1.630 + shouldWaitForExplicitPaintWaiters() || 1.631 + spellCheckedElements.length) { 1.632 + // Go into reftest-wait mode immediately after painting has been 1.633 + // unsuppressed, after the onload event has finished dispatching. 1.634 + gFailureReason = "timed out waiting for test to complete (trying to get into WaitForTestEnd)"; 1.635 + LogInfo("OnDocumentLoad triggering WaitForTestEnd"); 1.636 + setTimeout(function () { WaitForTestEnd(contentRootElement, inPrintMode, spellCheckedElements); }, 0); 1.637 + } else { 1.638 + if (doPrintMode(contentRootElement)) { 1.639 + LogInfo("OnDocumentLoad setting up print mode"); 1.640 + setupPrintMode(); 1.641 + inPrintMode = true; 1.642 + } 1.643 + 1.644 + // Since we can't use a bubbling-phase load listener from chrome, 1.645 + // this is a capturing phase listener. So do setTimeout twice, the 1.646 + // first to get us after the onload has fired in the content, and 1.647 + // the second to get us after any setTimeout(foo, 0) in the content. 1.648 + gFailureReason = "timed out waiting for test to complete (waiting for onload scripts to complete)"; 1.649 + LogInfo("OnDocumentLoad triggering AfterOnLoadScripts"); 1.650 + setTimeout(function () { setTimeout(AfterOnLoadScripts, 0); }, 0); 1.651 + } 1.652 +} 1.653 + 1.654 +function CheckForProcessCrashExpectation() 1.655 +{ 1.656 + var contentRootElement = content.document.documentElement; 1.657 + if (contentRootElement && 1.658 + contentRootElement.hasAttribute('class') && 1.659 + contentRootElement.getAttribute('class').split(/\s+/) 1.660 + .indexOf("reftest-expect-process-crash") != -1) { 1.661 + SendExpectProcessCrash(); 1.662 + } 1.663 +} 1.664 + 1.665 +function RecordResult() 1.666 +{ 1.667 + LogInfo("RecordResult fired"); 1.668 + 1.669 + var currentTestRunTime = Date.now() - gCurrentTestStartTime; 1.670 + 1.671 + clearTimeout(gFailureTimeout); 1.672 + gFailureReason = null; 1.673 + gFailureTimeout = null; 1.674 + 1.675 + if (gCurrentTestType == TYPE_SCRIPT) { 1.676 + var error = ''; 1.677 + var testwindow = content; 1.678 + 1.679 + if (testwindow.wrappedJSObject) 1.680 + testwindow = testwindow.wrappedJSObject; 1.681 + 1.682 + var testcases; 1.683 + if (!testwindow.getTestCases || typeof testwindow.getTestCases != "function") { 1.684 + // Force an unexpected failure to alert the test author to fix the test. 1.685 + error = "test must provide a function getTestCases(). (SCRIPT)\n"; 1.686 + } 1.687 + else if (!(testcases = testwindow.getTestCases())) { 1.688 + // Force an unexpected failure to alert the test author to fix the test. 1.689 + error = "test's getTestCases() must return an Array-like Object. (SCRIPT)\n"; 1.690 + } 1.691 + else if (testcases.length == 0) { 1.692 + // This failure may be due to a JavaScript Engine bug causing 1.693 + // early termination of the test. If we do not allow silent 1.694 + // failure, the driver will report an error. 1.695 + } 1.696 + 1.697 + var results = [ ]; 1.698 + if (!error) { 1.699 + // FIXME/bug 618176: temporary workaround 1.700 + for (var i = 0; i < testcases.length; ++i) { 1.701 + var test = testcases[i]; 1.702 + results.push({ passed: test.testPassed(), 1.703 + description: test.testDescription() }); 1.704 + } 1.705 + //results = testcases.map(function(test) { 1.706 + // return { passed: test.testPassed(), 1.707 + // description: test.testDescription() }; 1.708 + } 1.709 + 1.710 + SendScriptResults(currentTestRunTime, error, results); 1.711 + FinishTestItem(); 1.712 + return; 1.713 + } 1.714 + 1.715 + // Setup async scroll offsets now in case SynchronizeForSnapshot is not 1.716 + // called (due to reftest-no-sync-layers being supplied). 1.717 + setupAsyncScrollOffsets({allowFailure:true}); 1.718 + SendTestDone(currentTestRunTime); 1.719 + FinishTestItem(); 1.720 +} 1.721 + 1.722 +function LoadFailed() 1.723 +{ 1.724 + if (gTimeoutHook) { 1.725 + gTimeoutHook(); 1.726 + } 1.727 + gFailureTimeout = null; 1.728 + SendFailedLoad(gFailureReason); 1.729 +} 1.730 + 1.731 +function FinishTestItem() 1.732 +{ 1.733 + gHaveCanvasSnapshot = false; 1.734 +} 1.735 + 1.736 +function DoAssertionCheck() 1.737 +{ 1.738 + gClearingForAssertionCheck = false; 1.739 + 1.740 + var numAsserts = 0; 1.741 + if (gDebug.isDebugBuild) { 1.742 + var newAssertionCount = gDebug.assertionCount; 1.743 + numAsserts = newAssertionCount - gAssertionCount; 1.744 + gAssertionCount = newAssertionCount; 1.745 + } 1.746 + SendAssertionCount(numAsserts); 1.747 +} 1.748 + 1.749 +function LoadURI(uri) 1.750 +{ 1.751 + var flags = webNavigation().LOAD_FLAGS_NONE; 1.752 + webNavigation().loadURI(uri, flags, null, null, null); 1.753 +} 1.754 + 1.755 +function LogWarning(str) 1.756 +{ 1.757 + if (gVerbose) { 1.758 + sendSyncMessage("reftest:Log", { type: "warning", msg: str }); 1.759 + } else { 1.760 + sendAsyncMessage("reftest:Log", { type: "warning", msg: str }); 1.761 + } 1.762 +} 1.763 + 1.764 +function LogInfo(str) 1.765 +{ 1.766 + if (gVerbose) { 1.767 + sendSyncMessage("reftest:Log", { type: "info", msg: str }); 1.768 + } else { 1.769 + sendAsyncMessage("reftest:Log", { type: "info", msg: str }); 1.770 + } 1.771 +} 1.772 + 1.773 +const SYNC_DEFAULT = 0x0; 1.774 +const SYNC_ALLOW_DISABLE = 0x1; 1.775 +function SynchronizeForSnapshot(flags) 1.776 +{ 1.777 + if (gCurrentTestType == TYPE_SCRIPT || 1.778 + gCurrentTestType == TYPE_LOAD) { 1.779 + // Script tests or load-only tests do not need any snapshotting 1.780 + return; 1.781 + } 1.782 + 1.783 + if (flags & SYNC_ALLOW_DISABLE) { 1.784 + var docElt = content.document.documentElement; 1.785 + if (docElt && docElt.hasAttribute("reftest-no-sync-layers")) { 1.786 + LogInfo("Test file chose to skip SynchronizeForSnapshot"); 1.787 + return; 1.788 + } 1.789 + } 1.790 + 1.791 + var dummyCanvas = content.document.createElementNS(XHTML_NS, "canvas"); 1.792 + dummyCanvas.setAttribute("width", 1); 1.793 + dummyCanvas.setAttribute("height", 1); 1.794 + 1.795 + var ctx = dummyCanvas.getContext("2d"); 1.796 + var flags = ctx.DRAWWINDOW_DRAW_CARET | ctx.DRAWWINDOW_DRAW_VIEW | ctx.DRAWWINDOW_USE_WIDGET_LAYERS; 1.797 + ctx.drawWindow(content, 0, 0, 1, 1, "rgb(255,255,255)", flags); 1.798 + 1.799 + // Setup async scroll offsets now, because any scrollable layers should 1.800 + // have had their AsyncPanZoomControllers created. 1.801 + setupAsyncScrollOffsets({allowFailure:false}); 1.802 +} 1.803 + 1.804 +function RegisterMessageListeners() 1.805 +{ 1.806 + addMessageListener( 1.807 + "reftest:Clear", 1.808 + function (m) { RecvClear() } 1.809 + ); 1.810 + addMessageListener( 1.811 + "reftest:LoadScriptTest", 1.812 + function (m) { RecvLoadScriptTest(m.json.uri, m.json.timeout); } 1.813 + ); 1.814 + addMessageListener( 1.815 + "reftest:LoadTest", 1.816 + function (m) { RecvLoadTest(m.json.type, m.json.uri, m.json.timeout); } 1.817 + ); 1.818 + addMessageListener( 1.819 + "reftest:ResetRenderingState", 1.820 + function (m) { RecvResetRenderingState(); } 1.821 + ); 1.822 +} 1.823 + 1.824 +function RecvClear() 1.825 +{ 1.826 + gClearingForAssertionCheck = true; 1.827 + LoadURI(BLANK_URL_FOR_CLEARING); 1.828 +} 1.829 + 1.830 +function RecvLoadTest(type, uri, timeout) 1.831 +{ 1.832 + StartTestURI(type, uri, timeout); 1.833 +} 1.834 + 1.835 +function RecvLoadScriptTest(uri, timeout) 1.836 +{ 1.837 + StartTestURI(TYPE_SCRIPT, uri, timeout); 1.838 +} 1.839 + 1.840 +function RecvResetRenderingState() 1.841 +{ 1.842 + resetZoom(); 1.843 + resetDisplayportAndViewport(); 1.844 +} 1.845 + 1.846 +function SendAssertionCount(numAssertions) 1.847 +{ 1.848 + sendAsyncMessage("reftest:AssertionCount", { count: numAssertions }); 1.849 +} 1.850 + 1.851 +function SendContentReady() 1.852 +{ 1.853 + return sendSyncMessage("reftest:ContentReady")[0]; 1.854 +} 1.855 + 1.856 +function SendException(what) 1.857 +{ 1.858 + sendAsyncMessage("reftest:Exception", { what: what }); 1.859 +} 1.860 + 1.861 +function SendFailedLoad(why) 1.862 +{ 1.863 + sendAsyncMessage("reftest:FailedLoad", { why: why }); 1.864 +} 1.865 + 1.866 +function SendFailedNoPaint() 1.867 +{ 1.868 + sendAsyncMessage("reftest:FailedNoPaint"); 1.869 +} 1.870 + 1.871 +function SendEnableAsyncScroll() 1.872 +{ 1.873 + sendAsyncMessage("reftest:EnableAsyncScroll"); 1.874 +} 1.875 + 1.876 +// Return true if a snapshot was taken. 1.877 +function SendInitCanvasWithSnapshot() 1.878 +{ 1.879 + // If we're in the same process as the top-level XUL window, then 1.880 + // drawing that window will also update our layers, so no 1.881 + // synchronization is needed. 1.882 + // 1.883 + // NB: this is a test-harness optimization only, it must not 1.884 + // affect the validity of the tests. 1.885 + if (gBrowserIsRemote) { 1.886 + SynchronizeForSnapshot(SYNC_DEFAULT); 1.887 + } 1.888 + 1.889 + // For in-process browser, we have to make a synchronous request 1.890 + // here to make the above optimization valid, so that MozWaitPaint 1.891 + // events dispatched (synchronously) during painting are received 1.892 + // before we check the paint-wait counter. For out-of-process 1.893 + // browser though, it doesn't wrt correctness whether this request 1.894 + // is sync or async. 1.895 + var ret = sendSyncMessage("reftest:InitCanvasWithSnapshot")[0]; 1.896 + 1.897 + gHaveCanvasSnapshot = ret.painted; 1.898 + return ret.painted; 1.899 +} 1.900 + 1.901 +function SendScriptResults(runtimeMs, error, results) 1.902 +{ 1.903 + sendAsyncMessage("reftest:ScriptResults", 1.904 + { runtimeMs: runtimeMs, error: error, results: results }); 1.905 +} 1.906 + 1.907 +function SendExpectProcessCrash(runtimeMs) 1.908 +{ 1.909 + sendAsyncMessage("reftest:ExpectProcessCrash"); 1.910 +} 1.911 + 1.912 +function SendTestDone(runtimeMs) 1.913 +{ 1.914 + sendAsyncMessage("reftest:TestDone", { runtimeMs: runtimeMs }); 1.915 +} 1.916 + 1.917 +function roundTo(x, fraction) 1.918 +{ 1.919 + return Math.round(x/fraction)*fraction; 1.920 +} 1.921 + 1.922 +function SendUpdateCanvasForEvent(event, contentRootElement) 1.923 +{ 1.924 + var win = content; 1.925 + var scale = markupDocumentViewer().fullZoom; 1.926 + 1.927 + var rects = [ ]; 1.928 + if (shouldSnapshotWholePage(contentRootElement)) { 1.929 + // See comments in SendInitCanvasWithSnapshot() re: the split 1.930 + // logic here. 1.931 + if (!gBrowserIsRemote) { 1.932 + sendSyncMessage("reftest:UpdateWholeCanvasForInvalidation"); 1.933 + } else { 1.934 + SynchronizeForSnapshot(SYNC_ALLOW_DISABLE); 1.935 + sendAsyncMessage("reftest:UpdateWholeCanvasForInvalidation"); 1.936 + } 1.937 + return; 1.938 + } 1.939 + 1.940 + var rectList = event.clientRects; 1.941 + LogInfo("SendUpdateCanvasForEvent with " + rectList.length + " rects"); 1.942 + for (var i = 0; i < rectList.length; ++i) { 1.943 + var r = rectList[i]; 1.944 + // Set left/top/right/bottom to "device pixel" boundaries 1.945 + var left = Math.floor(roundTo(r.left*scale, 0.001)); 1.946 + var top = Math.floor(roundTo(r.top*scale, 0.001)); 1.947 + var right = Math.ceil(roundTo(r.right*scale, 0.001)); 1.948 + var bottom = Math.ceil(roundTo(r.bottom*scale, 0.001)); 1.949 + LogInfo("Rect: " + left + " " + top + " " + right + " " + bottom); 1.950 + 1.951 + rects.push({ left: left, top: top, right: right, bottom: bottom }); 1.952 + } 1.953 + 1.954 + // See comments in SendInitCanvasWithSnapshot() re: the split 1.955 + // logic here. 1.956 + if (!gBrowserIsRemote) { 1.957 + sendSyncMessage("reftest:UpdateCanvasForInvalidation", { rects: rects }); 1.958 + } else { 1.959 + SynchronizeForSnapshot(SYNC_ALLOW_DISABLE); 1.960 + sendAsyncMessage("reftest:UpdateCanvasForInvalidation", { rects: rects }); 1.961 + } 1.962 +} 1.963 +#if REFTEST_B2G 1.964 +OnInitialLoad(); 1.965 +#else 1.966 +addEventListener("load", OnInitialLoad, true); 1.967 +#endif