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