layout/tools/reftest/reftest-content.js

Wed, 31 Dec 2014 13:27:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 13:27:57 +0100
branch
TOR_BUG_3246
changeset 6
8bccb770b82d
permissions
-rw-r--r--

Ignore runtime configuration files generated during quality assurance.

michael@0 1 /* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- /
michael@0 2 /* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 const CC = Components.classes;
michael@0 8 const CI = Components.interfaces;
michael@0 9 const CR = Components.results;
michael@0 10 const CU = Components.utils;
michael@0 11
michael@0 12 const XHTML_NS = "http://www.w3.org/1999/xhtml";
michael@0 13
michael@0 14 const DEBUG_CONTRACTID = "@mozilla.org/xpcom/debug;1";
michael@0 15 const PRINTSETTINGS_CONTRACTID = "@mozilla.org/gfx/printsettings-service;1";
michael@0 16 const ENVIRONMENT_CONTRACTID = "@mozilla.org/process/environment;1";
michael@0 17
michael@0 18 // "<!--CLEAR-->"
michael@0 19 const BLANK_URL_FOR_CLEARING = "data:text/html;charset=UTF-8,%3C%21%2D%2DCLEAR%2D%2D%3E";
michael@0 20
michael@0 21 CU.import("resource://gre/modules/Timer.jsm");
michael@0 22 CU.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm");
michael@0 23
michael@0 24 var gBrowserIsRemote;
michael@0 25 var gHaveCanvasSnapshot = false;
michael@0 26 // Plugin layers can be updated asynchronously, so to make sure that all
michael@0 27 // layer surfaces have the right content, we need to listen for explicit
michael@0 28 // "MozPaintWait" and "MozPaintWaitFinished" events that signal when it's OK
michael@0 29 // to take snapshots. We cannot take a snapshot while the number of
michael@0 30 // "MozPaintWait" events fired exceeds the number of "MozPaintWaitFinished"
michael@0 31 // events fired. We count the number of such excess events here. When
michael@0 32 // the counter reaches zero we call gExplicitPendingPaintsCompleteHook.
michael@0 33 var gExplicitPendingPaintCount = 0;
michael@0 34 var gExplicitPendingPaintsCompleteHook;
michael@0 35 var gCurrentURL;
michael@0 36 var gCurrentTestType;
michael@0 37 var gTimeoutHook = null;
michael@0 38 var gFailureTimeout = null;
michael@0 39 var gFailureReason;
michael@0 40 var gAssertionCount = 0;
michael@0 41
michael@0 42 var gDebug;
michael@0 43 var gVerbose = false;
michael@0 44
michael@0 45 var gCurrentTestStartTime;
michael@0 46 var gClearingForAssertionCheck = false;
michael@0 47
michael@0 48 const TYPE_LOAD = 'load'; // test without a reference (just test that it does
michael@0 49 // not assert, crash, hang, or leak)
michael@0 50 const TYPE_SCRIPT = 'script'; // test contains individual test results
michael@0 51
michael@0 52 function markupDocumentViewer() {
michael@0 53 return docShell.contentViewer.QueryInterface(CI.nsIMarkupDocumentViewer);
michael@0 54 }
michael@0 55
michael@0 56 function webNavigation() {
michael@0 57 return docShell.QueryInterface(CI.nsIWebNavigation);
michael@0 58 }
michael@0 59
michael@0 60 function windowUtils() {
michael@0 61 return content.QueryInterface(CI.nsIInterfaceRequestor)
michael@0 62 .getInterface(CI.nsIDOMWindowUtils);
michael@0 63 }
michael@0 64
michael@0 65 function IDForEventTarget(event)
michael@0 66 {
michael@0 67 try {
michael@0 68 return "'" + event.target.getAttribute('id') + "'";
michael@0 69 } catch (ex) {
michael@0 70 return "<unknown>";
michael@0 71 }
michael@0 72 }
michael@0 73
michael@0 74 function PaintWaitListener(event)
michael@0 75 {
michael@0 76 LogInfo("MozPaintWait received for ID " + IDForEventTarget(event));
michael@0 77 gExplicitPendingPaintCount++;
michael@0 78 }
michael@0 79
michael@0 80 function PaintWaitFinishedListener(event)
michael@0 81 {
michael@0 82 LogInfo("MozPaintWaitFinished received for ID " + IDForEventTarget(event));
michael@0 83 gExplicitPendingPaintCount--;
michael@0 84 if (gExplicitPendingPaintCount < 0) {
michael@0 85 LogWarning("Underrun in gExplicitPendingPaintCount\n");
michael@0 86 gExplicitPendingPaintCount = 0;
michael@0 87 }
michael@0 88 if (gExplicitPendingPaintCount == 0 &&
michael@0 89 gExplicitPendingPaintsCompleteHook) {
michael@0 90 gExplicitPendingPaintsCompleteHook();
michael@0 91 }
michael@0 92 }
michael@0 93
michael@0 94 function OnInitialLoad()
michael@0 95 {
michael@0 96 #ifndef REFTEST_B2G
michael@0 97 removeEventListener("load", OnInitialLoad, true);
michael@0 98 #endif
michael@0 99
michael@0 100 gDebug = CC[DEBUG_CONTRACTID].getService(CI.nsIDebug2);
michael@0 101 var env = CC[ENVIRONMENT_CONTRACTID].getService(CI.nsIEnvironment);
michael@0 102 gVerbose = !!env.get("MOZ_REFTEST_VERBOSE");
michael@0 103
michael@0 104 RegisterMessageListeners();
michael@0 105
michael@0 106 var initInfo = SendContentReady();
michael@0 107 gBrowserIsRemote = initInfo.remote;
michael@0 108
michael@0 109 addEventListener("load", OnDocumentLoad, true);
michael@0 110
michael@0 111 addEventListener("MozPaintWait", PaintWaitListener, true);
michael@0 112 addEventListener("MozPaintWaitFinished", PaintWaitFinishedListener, true);
michael@0 113
michael@0 114 LogWarning("Using browser remote="+ gBrowserIsRemote +"\n");
michael@0 115 }
michael@0 116
michael@0 117 function StartTestURI(type, uri, timeout)
michael@0 118 {
michael@0 119 // Reset gExplicitPendingPaintCount in case there was a timeout or
michael@0 120 // the count is out of sync for some other reason
michael@0 121 if (gExplicitPendingPaintCount != 0) {
michael@0 122 LogWarning("Resetting gExplicitPendingPaintCount to zero (currently " +
michael@0 123 gExplicitPendingPaintCount + "\n");
michael@0 124 gExplicitPendingPaintCount = 0;
michael@0 125 }
michael@0 126
michael@0 127 gCurrentTestType = type;
michael@0 128 gCurrentURL = uri;
michael@0 129
michael@0 130 gCurrentTestStartTime = Date.now();
michael@0 131 if (gFailureTimeout != null) {
michael@0 132 SendException("program error managing timeouts\n");
michael@0 133 }
michael@0 134 gFailureTimeout = setTimeout(LoadFailed, timeout);
michael@0 135
michael@0 136 LoadURI(gCurrentURL);
michael@0 137 }
michael@0 138
michael@0 139 function setupZoom(contentRootElement) {
michael@0 140 if (!contentRootElement || !contentRootElement.hasAttribute('reftest-zoom'))
michael@0 141 return;
michael@0 142 markupDocumentViewer().fullZoom =
michael@0 143 contentRootElement.getAttribute('reftest-zoom');
michael@0 144 }
michael@0 145
michael@0 146 function resetZoom() {
michael@0 147 markupDocumentViewer().fullZoom = 1.0;
michael@0 148 }
michael@0 149
michael@0 150 function doPrintMode(contentRootElement) {
michael@0 151 #if REFTEST_B2G
michael@0 152 // nsIPrintSettings not available in B2G
michael@0 153 return false;
michael@0 154 #else
michael@0 155 // use getAttribute because className works differently in HTML and SVG
michael@0 156 return contentRootElement &&
michael@0 157 contentRootElement.hasAttribute('class') &&
michael@0 158 contentRootElement.getAttribute('class').split(/\s+/)
michael@0 159 .indexOf("reftest-print") != -1;
michael@0 160 #endif
michael@0 161 }
michael@0 162
michael@0 163 function setupPrintMode() {
michael@0 164 var PSSVC =
michael@0 165 CC[PRINTSETTINGS_CONTRACTID].getService(CI.nsIPrintSettingsService);
michael@0 166 var ps = PSSVC.newPrintSettings;
michael@0 167 ps.paperWidth = 5;
michael@0 168 ps.paperHeight = 3;
michael@0 169
michael@0 170 // Override any os-specific unwriteable margins
michael@0 171 ps.unwriteableMarginTop = 0;
michael@0 172 ps.unwriteableMarginLeft = 0;
michael@0 173 ps.unwriteableMarginBottom = 0;
michael@0 174 ps.unwriteableMarginRight = 0;
michael@0 175
michael@0 176 ps.headerStrLeft = "";
michael@0 177 ps.headerStrCenter = "";
michael@0 178 ps.headerStrRight = "";
michael@0 179 ps.footerStrLeft = "";
michael@0 180 ps.footerStrCenter = "";
michael@0 181 ps.footerStrRight = "";
michael@0 182 docShell.contentViewer.setPageMode(true, ps);
michael@0 183 }
michael@0 184
michael@0 185 function attrOrDefault(element, attr, def) {
michael@0 186 return element.hasAttribute(attr) ? Number(element.getAttribute(attr)) : def;
michael@0 187 }
michael@0 188
michael@0 189 function setupViewport(contentRootElement) {
michael@0 190 if (!contentRootElement) {
michael@0 191 return;
michael@0 192 }
michael@0 193
michael@0 194 var vw = attrOrDefault(contentRootElement, "reftest-viewport-w", 0);
michael@0 195 var vh = attrOrDefault(contentRootElement, "reftest-viewport-h", 0);
michael@0 196 if (vw !== 0 || vh !== 0) {
michael@0 197 LogInfo("Setting viewport to <w="+ vw +", h="+ vh +">");
michael@0 198 windowUtils().setCSSViewport(vw, vh);
michael@0 199 }
michael@0 200
michael@0 201 // XXX support resolution when needed
michael@0 202
michael@0 203 // XXX support viewconfig when needed
michael@0 204 }
michael@0 205
michael@0 206 function setupDisplayport(contentRootElement) {
michael@0 207 if (!contentRootElement) {
michael@0 208 return;
michael@0 209 }
michael@0 210
michael@0 211 function setupDisplayportForElement(element) {
michael@0 212 var dpw = attrOrDefault(element, "reftest-displayport-w", 0);
michael@0 213 var dph = attrOrDefault(element, "reftest-displayport-h", 0);
michael@0 214 var dpx = attrOrDefault(element, "reftest-displayport-x", 0);
michael@0 215 var dpy = attrOrDefault(element, "reftest-displayport-y", 0);
michael@0 216 if (dpw !== 0 || dph !== 0 || dpx != 0 || dpy != 0) {
michael@0 217 LogInfo("Setting displayport to <x="+ dpx +", y="+ dpy +", w="+ dpw +", h="+ dph +">");
michael@0 218 windowUtils().setDisplayPortForElement(dpx, dpy, dpw, dph, element, 1);
michael@0 219 }
michael@0 220 }
michael@0 221
michael@0 222 function setupDisplayportForElementSubtree(element) {
michael@0 223 setupDisplayportForElement(element);
michael@0 224 for (var c = element.firstElementChild; c; c = c.nextElementSibling) {
michael@0 225 setupDisplayportForElementSubtree(c);
michael@0 226 }
michael@0 227 }
michael@0 228
michael@0 229 if (contentRootElement.hasAttribute("reftest-async-scroll")) {
michael@0 230 SendEnableAsyncScroll();
michael@0 231 setupDisplayportForElementSubtree(contentRootElement);
michael@0 232 } else {
michael@0 233 setupDisplayportForElement(contentRootElement);
michael@0 234 }
michael@0 235 }
michael@0 236
michael@0 237 function setupAsyncScrollOffsets(options) {
michael@0 238 var currentDoc = content.document;
michael@0 239 var contentRootElement = currentDoc ? currentDoc.documentElement : null;
michael@0 240
michael@0 241 if (!contentRootElement) {
michael@0 242 return;
michael@0 243 }
michael@0 244
michael@0 245 function setupAsyncScrollOffsetsForElement(element) {
michael@0 246 var sx = attrOrDefault(element, "reftest-async-scroll-x", 0);
michael@0 247 var sy = attrOrDefault(element, "reftest-async-scroll-y", 0);
michael@0 248 if (sx != 0 || sy != 0) {
michael@0 249 try {
michael@0 250 // This might fail when called from RecordResult since layers
michael@0 251 // may not have been constructed yet
michael@0 252 windowUtils().setAsyncScrollOffset(element, sx, sy);
michael@0 253 } catch (e) {
michael@0 254 if (!options.allowFailure) {
michael@0 255 throw e;
michael@0 256 }
michael@0 257 }
michael@0 258 }
michael@0 259 }
michael@0 260
michael@0 261 function setupAsyncScrollOffsetsForElementSubtree(element) {
michael@0 262 setupAsyncScrollOffsetsForElement(element);
michael@0 263 for (var c = element.firstElementChild; c; c = c.nextElementSibling) {
michael@0 264 setupAsyncScrollOffsetsForElementSubtree(c);
michael@0 265 }
michael@0 266 }
michael@0 267
michael@0 268 var asyncScroll = contentRootElement.hasAttribute("reftest-async-scroll");
michael@0 269 if (asyncScroll) {
michael@0 270 setupAsyncScrollOffsetsForElementSubtree(contentRootElement);
michael@0 271 }
michael@0 272 }
michael@0 273
michael@0 274 function resetDisplayportAndViewport() {
michael@0 275 // XXX currently the displayport configuration lives on the
michael@0 276 // presshell and so is "reset" on nav when we get a new presshell.
michael@0 277 }
michael@0 278
michael@0 279 function shouldWaitForExplicitPaintWaiters() {
michael@0 280 return gExplicitPendingPaintCount > 0;
michael@0 281 }
michael@0 282
michael@0 283 function shouldWaitForPendingPaints() {
michael@0 284 // if gHaveCanvasSnapshot is false, we're not taking snapshots so
michael@0 285 // there is no need to wait for pending paints to be flushed.
michael@0 286 return gHaveCanvasSnapshot && windowUtils().isMozAfterPaintPending;
michael@0 287 }
michael@0 288
michael@0 289 function shouldWaitForReftestWaitRemoval(contentRootElement) {
michael@0 290 // use getAttribute because className works differently in HTML and SVG
michael@0 291 return contentRootElement &&
michael@0 292 contentRootElement.hasAttribute('class') &&
michael@0 293 contentRootElement.getAttribute('class').split(/\s+/)
michael@0 294 .indexOf("reftest-wait") != -1;
michael@0 295 }
michael@0 296
michael@0 297 function shouldSnapshotWholePage(contentRootElement) {
michael@0 298 // use getAttribute because className works differently in HTML and SVG
michael@0 299 return contentRootElement &&
michael@0 300 contentRootElement.hasAttribute('class') &&
michael@0 301 contentRootElement.getAttribute('class').split(/\s+/)
michael@0 302 .indexOf("reftest-snapshot-all") != -1;
michael@0 303 }
michael@0 304
michael@0 305 function getNoPaintElements(contentRootElement) {
michael@0 306 return contentRootElement.getElementsByClassName('reftest-no-paint');
michael@0 307 }
michael@0 308
michael@0 309 // Initial state. When the document has loaded and all MozAfterPaint events and
michael@0 310 // all explicit paint waits are flushed, we can fire the MozReftestInvalidate
michael@0 311 // event and move to the next state.
michael@0 312 const STATE_WAITING_TO_FIRE_INVALIDATE_EVENT = 0;
michael@0 313 // When reftest-wait has been removed from the root element, we can move to the
michael@0 314 // next state.
michael@0 315 const STATE_WAITING_FOR_REFTEST_WAIT_REMOVAL = 1;
michael@0 316 // When spell checking is done on all spell-checked elements, we can move to the
michael@0 317 // next state.
michael@0 318 const STATE_WAITING_FOR_SPELL_CHECKS = 2;
michael@0 319 // When all MozAfterPaint events and all explicit paint waits are flushed, we're
michael@0 320 // done and can move to the COMPLETED state.
michael@0 321 const STATE_WAITING_TO_FINISH = 3;
michael@0 322 const STATE_COMPLETED = 4;
michael@0 323
michael@0 324 function FlushRendering() {
michael@0 325 var anyPendingPaintsGeneratedInDescendants = false;
michael@0 326
michael@0 327 function flushWindow(win) {
michael@0 328 var utils = win.QueryInterface(CI.nsIInterfaceRequestor)
michael@0 329 .getInterface(CI.nsIDOMWindowUtils);
michael@0 330 var afterPaintWasPending = utils.isMozAfterPaintPending;
michael@0 331
michael@0 332 if (win.document.documentElement) {
michael@0 333 try {
michael@0 334 // Flush pending restyles and reflows for this window
michael@0 335 win.document.documentElement.getBoundingClientRect();
michael@0 336 } catch (e) {
michael@0 337 LogWarning("flushWindow failed: " + e + "\n");
michael@0 338 }
michael@0 339 }
michael@0 340
michael@0 341 if (!afterPaintWasPending && utils.isMozAfterPaintPending) {
michael@0 342 LogInfo("FlushRendering generated paint for window " + win.location.href);
michael@0 343 anyPendingPaintsGeneratedInDescendants = true;
michael@0 344 }
michael@0 345
michael@0 346 for (var i = 0; i < win.frames.length; ++i) {
michael@0 347 flushWindow(win.frames[i]);
michael@0 348 }
michael@0 349 }
michael@0 350
michael@0 351 flushWindow(content);
michael@0 352
michael@0 353 if (anyPendingPaintsGeneratedInDescendants &&
michael@0 354 !windowUtils().isMozAfterPaintPending) {
michael@0 355 LogWarning("Internal error: descendant frame generated a MozAfterPaint event, but the root document doesn't have one!");
michael@0 356 }
michael@0 357 }
michael@0 358
michael@0 359 function WaitForTestEnd(contentRootElement, inPrintMode, spellCheckedElements) {
michael@0 360 var stopAfterPaintReceived = false;
michael@0 361 var currentDoc = content.document;
michael@0 362 var state = STATE_WAITING_TO_FIRE_INVALIDATE_EVENT;
michael@0 363
michael@0 364 function AfterPaintListener(event) {
michael@0 365 LogInfo("AfterPaintListener in " + event.target.document.location.href);
michael@0 366 if (event.target.document != currentDoc) {
michael@0 367 // ignore paint events for subframes or old documents in the window.
michael@0 368 // Invalidation in subframes will cause invalidation in the toplevel document anyway.
michael@0 369 return;
michael@0 370 }
michael@0 371
michael@0 372 SendUpdateCanvasForEvent(event, contentRootElement);
michael@0 373 // These events are fired immediately after a paint. Don't
michael@0 374 // confuse ourselves by firing synchronously if we triggered the
michael@0 375 // paint ourselves.
michael@0 376 setTimeout(MakeProgress, 0);
michael@0 377 }
michael@0 378
michael@0 379 function AttrModifiedListener() {
michael@0 380 LogInfo("AttrModifiedListener fired");
michael@0 381 // Wait for the next return-to-event-loop before continuing --- for
michael@0 382 // example, the attribute may have been modified in an subdocument's
michael@0 383 // load event handler, in which case we need load event processing
michael@0 384 // to complete and unsuppress painting before we check isMozAfterPaintPending.
michael@0 385 setTimeout(MakeProgress, 0);
michael@0 386 }
michael@0 387
michael@0 388 function ExplicitPaintsCompleteListener() {
michael@0 389 LogInfo("ExplicitPaintsCompleteListener fired");
michael@0 390 // Since this can fire while painting, don't confuse ourselves by
michael@0 391 // firing synchronously. It's fine to do this asynchronously.
michael@0 392 setTimeout(MakeProgress, 0);
michael@0 393 }
michael@0 394
michael@0 395 function RemoveListeners() {
michael@0 396 // OK, we can end the test now.
michael@0 397 removeEventListener("MozAfterPaint", AfterPaintListener, false);
michael@0 398 if (contentRootElement) {
michael@0 399 contentRootElement.removeEventListener("DOMAttrModified", AttrModifiedListener, false);
michael@0 400 }
michael@0 401 gExplicitPendingPaintsCompleteHook = null;
michael@0 402 gTimeoutHook = null;
michael@0 403 // Make sure we're in the COMPLETED state just in case
michael@0 404 // (this may be called via the test-timeout hook)
michael@0 405 state = STATE_COMPLETED;
michael@0 406 }
michael@0 407
michael@0 408 // Everything that could cause shouldWaitForXXX() to
michael@0 409 // change from returning true to returning false is monitored via some kind
michael@0 410 // of event listener which eventually calls this function.
michael@0 411 function MakeProgress() {
michael@0 412 if (state >= STATE_COMPLETED) {
michael@0 413 LogInfo("MakeProgress: STATE_COMPLETED");
michael@0 414 return;
michael@0 415 }
michael@0 416
michael@0 417 FlushRendering();
michael@0 418
michael@0 419 switch (state) {
michael@0 420 case STATE_WAITING_TO_FIRE_INVALIDATE_EVENT: {
michael@0 421 LogInfo("MakeProgress: STATE_WAITING_TO_FIRE_INVALIDATE_EVENT");
michael@0 422 if (shouldWaitForExplicitPaintWaiters() || shouldWaitForPendingPaints()) {
michael@0 423 gFailureReason = "timed out waiting for pending paint count to reach zero";
michael@0 424 if (shouldWaitForExplicitPaintWaiters()) {
michael@0 425 gFailureReason += " (waiting for MozPaintWaitFinished)";
michael@0 426 LogInfo("MakeProgress: waiting for MozPaintWaitFinished");
michael@0 427 }
michael@0 428 if (shouldWaitForPendingPaints()) {
michael@0 429 gFailureReason += " (waiting for MozAfterPaint)";
michael@0 430 LogInfo("MakeProgress: waiting for MozAfterPaint");
michael@0 431 }
michael@0 432 return;
michael@0 433 }
michael@0 434
michael@0 435 state = STATE_WAITING_FOR_REFTEST_WAIT_REMOVAL;
michael@0 436 var hasReftestWait = shouldWaitForReftestWaitRemoval(contentRootElement);
michael@0 437 // Notify the test document that now is a good time to test some invalidation
michael@0 438 LogInfo("MakeProgress: dispatching MozReftestInvalidate");
michael@0 439 if (contentRootElement) {
michael@0 440 var elements = getNoPaintElements(contentRootElement);
michael@0 441 for (var i = 0; i < elements.length; ++i) {
michael@0 442 windowUtils().checkAndClearPaintedState(elements[i]);
michael@0 443 }
michael@0 444 var notification = content.document.createEvent("Events");
michael@0 445 notification.initEvent("MozReftestInvalidate", true, false);
michael@0 446 contentRootElement.dispatchEvent(notification);
michael@0 447 }
michael@0 448
michael@0 449 if (!inPrintMode && doPrintMode(contentRootElement)) {
michael@0 450 LogInfo("MakeProgress: setting up print mode");
michael@0 451 setupPrintMode();
michael@0 452 }
michael@0 453
michael@0 454 if (hasReftestWait && !shouldWaitForReftestWaitRemoval(contentRootElement)) {
michael@0 455 // MozReftestInvalidate handler removed reftest-wait.
michael@0 456 // We expect something to have been invalidated...
michael@0 457 FlushRendering();
michael@0 458 if (!shouldWaitForPendingPaints() && !shouldWaitForExplicitPaintWaiters()) {
michael@0 459 LogWarning("MozInvalidateEvent didn't invalidate");
michael@0 460 }
michael@0 461 }
michael@0 462 // Try next state
michael@0 463 MakeProgress();
michael@0 464 return;
michael@0 465 }
michael@0 466
michael@0 467 case STATE_WAITING_FOR_REFTEST_WAIT_REMOVAL:
michael@0 468 LogInfo("MakeProgress: STATE_WAITING_FOR_REFTEST_WAIT_REMOVAL");
michael@0 469 if (shouldWaitForReftestWaitRemoval(contentRootElement)) {
michael@0 470 gFailureReason = "timed out waiting for reftest-wait to be removed";
michael@0 471 LogInfo("MakeProgress: waiting for reftest-wait to be removed");
michael@0 472 return;
michael@0 473 }
michael@0 474
michael@0 475 // Try next state
michael@0 476 state = STATE_WAITING_FOR_SPELL_CHECKS;
michael@0 477 MakeProgress();
michael@0 478 return;
michael@0 479
michael@0 480 case STATE_WAITING_FOR_SPELL_CHECKS:
michael@0 481 LogInfo("MakeProgress: STATE_WAITING_FOR_SPELL_CHECKS");
michael@0 482 if (numPendingSpellChecks) {
michael@0 483 gFailureReason = "timed out waiting for spell checks to end";
michael@0 484 LogInfo("MakeProgress: waiting for spell checks to end");
michael@0 485 return;
michael@0 486 }
michael@0 487
michael@0 488 state = STATE_WAITING_TO_FINISH;
michael@0 489 // Try next state
michael@0 490 MakeProgress();
michael@0 491 return;
michael@0 492
michael@0 493 case STATE_WAITING_TO_FINISH:
michael@0 494 LogInfo("MakeProgress: STATE_WAITING_TO_FINISH");
michael@0 495 if (shouldWaitForExplicitPaintWaiters() || shouldWaitForPendingPaints()) {
michael@0 496 gFailureReason = "timed out waiting for pending paint count to " +
michael@0 497 "reach zero (after reftest-wait removed and switch to print mode)";
michael@0 498 if (shouldWaitForExplicitPaintWaiters()) {
michael@0 499 gFailureReason += " (waiting for MozPaintWaitFinished)";
michael@0 500 LogInfo("MakeProgress: waiting for MozPaintWaitFinished");
michael@0 501 }
michael@0 502 if (shouldWaitForPendingPaints()) {
michael@0 503 gFailureReason += " (waiting for MozAfterPaint)";
michael@0 504 LogInfo("MakeProgress: waiting for MozAfterPaint");
michael@0 505 }
michael@0 506 return;
michael@0 507 }
michael@0 508 if (contentRootElement) {
michael@0 509 var elements = getNoPaintElements(contentRootElement);
michael@0 510 for (var i = 0; i < elements.length; ++i) {
michael@0 511 if (windowUtils().checkAndClearPaintedState(elements[i])) {
michael@0 512 SendFailedNoPaint();
michael@0 513 }
michael@0 514 }
michael@0 515 }
michael@0 516 LogInfo("MakeProgress: Completed");
michael@0 517 state = STATE_COMPLETED;
michael@0 518 gFailureReason = "timed out while taking snapshot (bug in harness?)";
michael@0 519 RemoveListeners();
michael@0 520 CheckForProcessCrashExpectation();
michael@0 521 setTimeout(RecordResult, 0);
michael@0 522 return;
michael@0 523 }
michael@0 524 }
michael@0 525
michael@0 526 LogInfo("WaitForTestEnd: Adding listeners");
michael@0 527 addEventListener("MozAfterPaint", AfterPaintListener, false);
michael@0 528 // If contentRootElement is null then shouldWaitForReftestWaitRemoval will
michael@0 529 // always return false so we don't need a listener anyway
michael@0 530 if (contentRootElement) {
michael@0 531 contentRootElement.addEventListener("DOMAttrModified", AttrModifiedListener, false);
michael@0 532 }
michael@0 533 gExplicitPendingPaintsCompleteHook = ExplicitPaintsCompleteListener;
michael@0 534 gTimeoutHook = RemoveListeners;
michael@0 535
michael@0 536 // Listen for spell checks on spell-checked elements.
michael@0 537 var numPendingSpellChecks = spellCheckedElements.length;
michael@0 538 function decNumPendingSpellChecks() {
michael@0 539 --numPendingSpellChecks;
michael@0 540 MakeProgress();
michael@0 541 }
michael@0 542 for (let editable of spellCheckedElements) {
michael@0 543 try {
michael@0 544 onSpellCheck(editable, decNumPendingSpellChecks);
michael@0 545 } catch (err) {
michael@0 546 // The element may not have an editor, so ignore it.
michael@0 547 setTimeout(decNumPendingSpellChecks, 0);
michael@0 548 }
michael@0 549 }
michael@0 550
michael@0 551 // Take a full snapshot now that all our listeners are set up. This
michael@0 552 // ensures it's impossible for us to miss updates between taking the snapshot
michael@0 553 // and adding our listeners.
michael@0 554 SendInitCanvasWithSnapshot();
michael@0 555 MakeProgress();
michael@0 556 }
michael@0 557
michael@0 558 function OnDocumentLoad(event)
michael@0 559 {
michael@0 560 var currentDoc = content.document;
michael@0 561 if (event.target != currentDoc)
michael@0 562 // Ignore load events for subframes.
michael@0 563 return;
michael@0 564
michael@0 565 if (gClearingForAssertionCheck &&
michael@0 566 currentDoc.location.href == BLANK_URL_FOR_CLEARING) {
michael@0 567 DoAssertionCheck();
michael@0 568 return;
michael@0 569 }
michael@0 570
michael@0 571 if (currentDoc.location.href != gCurrentURL) {
michael@0 572 LogInfo("OnDocumentLoad fired for previous document");
michael@0 573 // Ignore load events for previous documents.
michael@0 574 return;
michael@0 575 }
michael@0 576
michael@0 577 // Collect all editable, spell-checked elements. It may be the case that
michael@0 578 // not all the elements that match this selector will be spell checked: for
michael@0 579 // example, a textarea without a spellcheck attribute may have a parent with
michael@0 580 // spellcheck=false, or script may set spellcheck=false on an element whose
michael@0 581 // markup sets it to true. But that's OK since onSpellCheck detects the
michael@0 582 // absence of spell checking, too.
michael@0 583 var querySelector =
michael@0 584 '*[class~="spell-checked"],' +
michael@0 585 'textarea:not([spellcheck="false"]),' +
michael@0 586 'input[spellcheck]:-moz-any([spellcheck=""],[spellcheck="true"]),' +
michael@0 587 '*[contenteditable]:-moz-any([contenteditable=""],[contenteditable="true"])';
michael@0 588 var spellCheckedElements = currentDoc.querySelectorAll(querySelector);
michael@0 589
michael@0 590 var contentRootElement = currentDoc ? currentDoc.documentElement : null;
michael@0 591 currentDoc = null;
michael@0 592 setupZoom(contentRootElement);
michael@0 593 setupViewport(contentRootElement);
michael@0 594 setupDisplayport(contentRootElement);
michael@0 595 var inPrintMode = false;
michael@0 596
michael@0 597 function AfterOnLoadScripts() {
michael@0 598 // Regrab the root element, because the document may have changed.
michael@0 599 var contentRootElement =
michael@0 600 content.document ? content.document.documentElement : null;
michael@0 601
michael@0 602 // Flush the document in case it got modified in a load event handler.
michael@0 603 FlushRendering();
michael@0 604
michael@0 605 // Take a snapshot now. We need to do this before we check whether
michael@0 606 // we should wait, since this might trigger dispatching of
michael@0 607 // MozPaintWait events and make shouldWaitForExplicitPaintWaiters() true
michael@0 608 // below.
michael@0 609 var painted = SendInitCanvasWithSnapshot();
michael@0 610
michael@0 611 if (shouldWaitForExplicitPaintWaiters() ||
michael@0 612 (!inPrintMode && doPrintMode(contentRootElement)) ||
michael@0 613 // If we didn't force a paint above, in
michael@0 614 // InitCurrentCanvasWithSnapshot, so we should wait for a
michael@0 615 // paint before we consider them done.
michael@0 616 !painted) {
michael@0 617 LogInfo("AfterOnLoadScripts belatedly entering WaitForTestEnd");
michael@0 618 // Go into reftest-wait mode belatedly.
michael@0 619 WaitForTestEnd(contentRootElement, inPrintMode, []);
michael@0 620 } else {
michael@0 621 CheckForProcessCrashExpectation();
michael@0 622 RecordResult();
michael@0 623 }
michael@0 624 }
michael@0 625
michael@0 626 if (shouldWaitForReftestWaitRemoval(contentRootElement) ||
michael@0 627 shouldWaitForExplicitPaintWaiters() ||
michael@0 628 spellCheckedElements.length) {
michael@0 629 // Go into reftest-wait mode immediately after painting has been
michael@0 630 // unsuppressed, after the onload event has finished dispatching.
michael@0 631 gFailureReason = "timed out waiting for test to complete (trying to get into WaitForTestEnd)";
michael@0 632 LogInfo("OnDocumentLoad triggering WaitForTestEnd");
michael@0 633 setTimeout(function () { WaitForTestEnd(contentRootElement, inPrintMode, spellCheckedElements); }, 0);
michael@0 634 } else {
michael@0 635 if (doPrintMode(contentRootElement)) {
michael@0 636 LogInfo("OnDocumentLoad setting up print mode");
michael@0 637 setupPrintMode();
michael@0 638 inPrintMode = true;
michael@0 639 }
michael@0 640
michael@0 641 // Since we can't use a bubbling-phase load listener from chrome,
michael@0 642 // this is a capturing phase listener. So do setTimeout twice, the
michael@0 643 // first to get us after the onload has fired in the content, and
michael@0 644 // the second to get us after any setTimeout(foo, 0) in the content.
michael@0 645 gFailureReason = "timed out waiting for test to complete (waiting for onload scripts to complete)";
michael@0 646 LogInfo("OnDocumentLoad triggering AfterOnLoadScripts");
michael@0 647 setTimeout(function () { setTimeout(AfterOnLoadScripts, 0); }, 0);
michael@0 648 }
michael@0 649 }
michael@0 650
michael@0 651 function CheckForProcessCrashExpectation()
michael@0 652 {
michael@0 653 var contentRootElement = content.document.documentElement;
michael@0 654 if (contentRootElement &&
michael@0 655 contentRootElement.hasAttribute('class') &&
michael@0 656 contentRootElement.getAttribute('class').split(/\s+/)
michael@0 657 .indexOf("reftest-expect-process-crash") != -1) {
michael@0 658 SendExpectProcessCrash();
michael@0 659 }
michael@0 660 }
michael@0 661
michael@0 662 function RecordResult()
michael@0 663 {
michael@0 664 LogInfo("RecordResult fired");
michael@0 665
michael@0 666 var currentTestRunTime = Date.now() - gCurrentTestStartTime;
michael@0 667
michael@0 668 clearTimeout(gFailureTimeout);
michael@0 669 gFailureReason = null;
michael@0 670 gFailureTimeout = null;
michael@0 671
michael@0 672 if (gCurrentTestType == TYPE_SCRIPT) {
michael@0 673 var error = '';
michael@0 674 var testwindow = content;
michael@0 675
michael@0 676 if (testwindow.wrappedJSObject)
michael@0 677 testwindow = testwindow.wrappedJSObject;
michael@0 678
michael@0 679 var testcases;
michael@0 680 if (!testwindow.getTestCases || typeof testwindow.getTestCases != "function") {
michael@0 681 // Force an unexpected failure to alert the test author to fix the test.
michael@0 682 error = "test must provide a function getTestCases(). (SCRIPT)\n";
michael@0 683 }
michael@0 684 else if (!(testcases = testwindow.getTestCases())) {
michael@0 685 // Force an unexpected failure to alert the test author to fix the test.
michael@0 686 error = "test's getTestCases() must return an Array-like Object. (SCRIPT)\n";
michael@0 687 }
michael@0 688 else if (testcases.length == 0) {
michael@0 689 // This failure may be due to a JavaScript Engine bug causing
michael@0 690 // early termination of the test. If we do not allow silent
michael@0 691 // failure, the driver will report an error.
michael@0 692 }
michael@0 693
michael@0 694 var results = [ ];
michael@0 695 if (!error) {
michael@0 696 // FIXME/bug 618176: temporary workaround
michael@0 697 for (var i = 0; i < testcases.length; ++i) {
michael@0 698 var test = testcases[i];
michael@0 699 results.push({ passed: test.testPassed(),
michael@0 700 description: test.testDescription() });
michael@0 701 }
michael@0 702 //results = testcases.map(function(test) {
michael@0 703 // return { passed: test.testPassed(),
michael@0 704 // description: test.testDescription() };
michael@0 705 }
michael@0 706
michael@0 707 SendScriptResults(currentTestRunTime, error, results);
michael@0 708 FinishTestItem();
michael@0 709 return;
michael@0 710 }
michael@0 711
michael@0 712 // Setup async scroll offsets now in case SynchronizeForSnapshot is not
michael@0 713 // called (due to reftest-no-sync-layers being supplied).
michael@0 714 setupAsyncScrollOffsets({allowFailure:true});
michael@0 715 SendTestDone(currentTestRunTime);
michael@0 716 FinishTestItem();
michael@0 717 }
michael@0 718
michael@0 719 function LoadFailed()
michael@0 720 {
michael@0 721 if (gTimeoutHook) {
michael@0 722 gTimeoutHook();
michael@0 723 }
michael@0 724 gFailureTimeout = null;
michael@0 725 SendFailedLoad(gFailureReason);
michael@0 726 }
michael@0 727
michael@0 728 function FinishTestItem()
michael@0 729 {
michael@0 730 gHaveCanvasSnapshot = false;
michael@0 731 }
michael@0 732
michael@0 733 function DoAssertionCheck()
michael@0 734 {
michael@0 735 gClearingForAssertionCheck = false;
michael@0 736
michael@0 737 var numAsserts = 0;
michael@0 738 if (gDebug.isDebugBuild) {
michael@0 739 var newAssertionCount = gDebug.assertionCount;
michael@0 740 numAsserts = newAssertionCount - gAssertionCount;
michael@0 741 gAssertionCount = newAssertionCount;
michael@0 742 }
michael@0 743 SendAssertionCount(numAsserts);
michael@0 744 }
michael@0 745
michael@0 746 function LoadURI(uri)
michael@0 747 {
michael@0 748 var flags = webNavigation().LOAD_FLAGS_NONE;
michael@0 749 webNavigation().loadURI(uri, flags, null, null, null);
michael@0 750 }
michael@0 751
michael@0 752 function LogWarning(str)
michael@0 753 {
michael@0 754 if (gVerbose) {
michael@0 755 sendSyncMessage("reftest:Log", { type: "warning", msg: str });
michael@0 756 } else {
michael@0 757 sendAsyncMessage("reftest:Log", { type: "warning", msg: str });
michael@0 758 }
michael@0 759 }
michael@0 760
michael@0 761 function LogInfo(str)
michael@0 762 {
michael@0 763 if (gVerbose) {
michael@0 764 sendSyncMessage("reftest:Log", { type: "info", msg: str });
michael@0 765 } else {
michael@0 766 sendAsyncMessage("reftest:Log", { type: "info", msg: str });
michael@0 767 }
michael@0 768 }
michael@0 769
michael@0 770 const SYNC_DEFAULT = 0x0;
michael@0 771 const SYNC_ALLOW_DISABLE = 0x1;
michael@0 772 function SynchronizeForSnapshot(flags)
michael@0 773 {
michael@0 774 if (gCurrentTestType == TYPE_SCRIPT ||
michael@0 775 gCurrentTestType == TYPE_LOAD) {
michael@0 776 // Script tests or load-only tests do not need any snapshotting
michael@0 777 return;
michael@0 778 }
michael@0 779
michael@0 780 if (flags & SYNC_ALLOW_DISABLE) {
michael@0 781 var docElt = content.document.documentElement;
michael@0 782 if (docElt && docElt.hasAttribute("reftest-no-sync-layers")) {
michael@0 783 LogInfo("Test file chose to skip SynchronizeForSnapshot");
michael@0 784 return;
michael@0 785 }
michael@0 786 }
michael@0 787
michael@0 788 var dummyCanvas = content.document.createElementNS(XHTML_NS, "canvas");
michael@0 789 dummyCanvas.setAttribute("width", 1);
michael@0 790 dummyCanvas.setAttribute("height", 1);
michael@0 791
michael@0 792 var ctx = dummyCanvas.getContext("2d");
michael@0 793 var flags = ctx.DRAWWINDOW_DRAW_CARET | ctx.DRAWWINDOW_DRAW_VIEW | ctx.DRAWWINDOW_USE_WIDGET_LAYERS;
michael@0 794 ctx.drawWindow(content, 0, 0, 1, 1, "rgb(255,255,255)", flags);
michael@0 795
michael@0 796 // Setup async scroll offsets now, because any scrollable layers should
michael@0 797 // have had their AsyncPanZoomControllers created.
michael@0 798 setupAsyncScrollOffsets({allowFailure:false});
michael@0 799 }
michael@0 800
michael@0 801 function RegisterMessageListeners()
michael@0 802 {
michael@0 803 addMessageListener(
michael@0 804 "reftest:Clear",
michael@0 805 function (m) { RecvClear() }
michael@0 806 );
michael@0 807 addMessageListener(
michael@0 808 "reftest:LoadScriptTest",
michael@0 809 function (m) { RecvLoadScriptTest(m.json.uri, m.json.timeout); }
michael@0 810 );
michael@0 811 addMessageListener(
michael@0 812 "reftest:LoadTest",
michael@0 813 function (m) { RecvLoadTest(m.json.type, m.json.uri, m.json.timeout); }
michael@0 814 );
michael@0 815 addMessageListener(
michael@0 816 "reftest:ResetRenderingState",
michael@0 817 function (m) { RecvResetRenderingState(); }
michael@0 818 );
michael@0 819 }
michael@0 820
michael@0 821 function RecvClear()
michael@0 822 {
michael@0 823 gClearingForAssertionCheck = true;
michael@0 824 LoadURI(BLANK_URL_FOR_CLEARING);
michael@0 825 }
michael@0 826
michael@0 827 function RecvLoadTest(type, uri, timeout)
michael@0 828 {
michael@0 829 StartTestURI(type, uri, timeout);
michael@0 830 }
michael@0 831
michael@0 832 function RecvLoadScriptTest(uri, timeout)
michael@0 833 {
michael@0 834 StartTestURI(TYPE_SCRIPT, uri, timeout);
michael@0 835 }
michael@0 836
michael@0 837 function RecvResetRenderingState()
michael@0 838 {
michael@0 839 resetZoom();
michael@0 840 resetDisplayportAndViewport();
michael@0 841 }
michael@0 842
michael@0 843 function SendAssertionCount(numAssertions)
michael@0 844 {
michael@0 845 sendAsyncMessage("reftest:AssertionCount", { count: numAssertions });
michael@0 846 }
michael@0 847
michael@0 848 function SendContentReady()
michael@0 849 {
michael@0 850 return sendSyncMessage("reftest:ContentReady")[0];
michael@0 851 }
michael@0 852
michael@0 853 function SendException(what)
michael@0 854 {
michael@0 855 sendAsyncMessage("reftest:Exception", { what: what });
michael@0 856 }
michael@0 857
michael@0 858 function SendFailedLoad(why)
michael@0 859 {
michael@0 860 sendAsyncMessage("reftest:FailedLoad", { why: why });
michael@0 861 }
michael@0 862
michael@0 863 function SendFailedNoPaint()
michael@0 864 {
michael@0 865 sendAsyncMessage("reftest:FailedNoPaint");
michael@0 866 }
michael@0 867
michael@0 868 function SendEnableAsyncScroll()
michael@0 869 {
michael@0 870 sendAsyncMessage("reftest:EnableAsyncScroll");
michael@0 871 }
michael@0 872
michael@0 873 // Return true if a snapshot was taken.
michael@0 874 function SendInitCanvasWithSnapshot()
michael@0 875 {
michael@0 876 // If we're in the same process as the top-level XUL window, then
michael@0 877 // drawing that window will also update our layers, so no
michael@0 878 // synchronization is needed.
michael@0 879 //
michael@0 880 // NB: this is a test-harness optimization only, it must not
michael@0 881 // affect the validity of the tests.
michael@0 882 if (gBrowserIsRemote) {
michael@0 883 SynchronizeForSnapshot(SYNC_DEFAULT);
michael@0 884 }
michael@0 885
michael@0 886 // For in-process browser, we have to make a synchronous request
michael@0 887 // here to make the above optimization valid, so that MozWaitPaint
michael@0 888 // events dispatched (synchronously) during painting are received
michael@0 889 // before we check the paint-wait counter. For out-of-process
michael@0 890 // browser though, it doesn't wrt correctness whether this request
michael@0 891 // is sync or async.
michael@0 892 var ret = sendSyncMessage("reftest:InitCanvasWithSnapshot")[0];
michael@0 893
michael@0 894 gHaveCanvasSnapshot = ret.painted;
michael@0 895 return ret.painted;
michael@0 896 }
michael@0 897
michael@0 898 function SendScriptResults(runtimeMs, error, results)
michael@0 899 {
michael@0 900 sendAsyncMessage("reftest:ScriptResults",
michael@0 901 { runtimeMs: runtimeMs, error: error, results: results });
michael@0 902 }
michael@0 903
michael@0 904 function SendExpectProcessCrash(runtimeMs)
michael@0 905 {
michael@0 906 sendAsyncMessage("reftest:ExpectProcessCrash");
michael@0 907 }
michael@0 908
michael@0 909 function SendTestDone(runtimeMs)
michael@0 910 {
michael@0 911 sendAsyncMessage("reftest:TestDone", { runtimeMs: runtimeMs });
michael@0 912 }
michael@0 913
michael@0 914 function roundTo(x, fraction)
michael@0 915 {
michael@0 916 return Math.round(x/fraction)*fraction;
michael@0 917 }
michael@0 918
michael@0 919 function SendUpdateCanvasForEvent(event, contentRootElement)
michael@0 920 {
michael@0 921 var win = content;
michael@0 922 var scale = markupDocumentViewer().fullZoom;
michael@0 923
michael@0 924 var rects = [ ];
michael@0 925 if (shouldSnapshotWholePage(contentRootElement)) {
michael@0 926 // See comments in SendInitCanvasWithSnapshot() re: the split
michael@0 927 // logic here.
michael@0 928 if (!gBrowserIsRemote) {
michael@0 929 sendSyncMessage("reftest:UpdateWholeCanvasForInvalidation");
michael@0 930 } else {
michael@0 931 SynchronizeForSnapshot(SYNC_ALLOW_DISABLE);
michael@0 932 sendAsyncMessage("reftest:UpdateWholeCanvasForInvalidation");
michael@0 933 }
michael@0 934 return;
michael@0 935 }
michael@0 936
michael@0 937 var rectList = event.clientRects;
michael@0 938 LogInfo("SendUpdateCanvasForEvent with " + rectList.length + " rects");
michael@0 939 for (var i = 0; i < rectList.length; ++i) {
michael@0 940 var r = rectList[i];
michael@0 941 // Set left/top/right/bottom to "device pixel" boundaries
michael@0 942 var left = Math.floor(roundTo(r.left*scale, 0.001));
michael@0 943 var top = Math.floor(roundTo(r.top*scale, 0.001));
michael@0 944 var right = Math.ceil(roundTo(r.right*scale, 0.001));
michael@0 945 var bottom = Math.ceil(roundTo(r.bottom*scale, 0.001));
michael@0 946 LogInfo("Rect: " + left + " " + top + " " + right + " " + bottom);
michael@0 947
michael@0 948 rects.push({ left: left, top: top, right: right, bottom: bottom });
michael@0 949 }
michael@0 950
michael@0 951 // See comments in SendInitCanvasWithSnapshot() re: the split
michael@0 952 // logic here.
michael@0 953 if (!gBrowserIsRemote) {
michael@0 954 sendSyncMessage("reftest:UpdateCanvasForInvalidation", { rects: rects });
michael@0 955 } else {
michael@0 956 SynchronizeForSnapshot(SYNC_ALLOW_DISABLE);
michael@0 957 sendAsyncMessage("reftest:UpdateCanvasForInvalidation", { rects: rects });
michael@0 958 }
michael@0 959 }
michael@0 960 #if REFTEST_B2G
michael@0 961 OnInitialLoad();
michael@0 962 #else
michael@0 963 addEventListener("load", OnInitialLoad, true);
michael@0 964 #endif

mercurial