|
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/. */ |
|
6 |
|
7 const CC = Components.classes; |
|
8 const CI = Components.interfaces; |
|
9 const CR = Components.results; |
|
10 const CU = Components.utils; |
|
11 |
|
12 const XHTML_NS = "http://www.w3.org/1999/xhtml"; |
|
13 |
|
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"; |
|
17 |
|
18 // "<!--CLEAR-->" |
|
19 const BLANK_URL_FOR_CLEARING = "data:text/html;charset=UTF-8,%3C%21%2D%2DCLEAR%2D%2D%3E"; |
|
20 |
|
21 CU.import("resource://gre/modules/Timer.jsm"); |
|
22 CU.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm"); |
|
23 |
|
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; |
|
41 |
|
42 var gDebug; |
|
43 var gVerbose = false; |
|
44 |
|
45 var gCurrentTestStartTime; |
|
46 var gClearingForAssertionCheck = false; |
|
47 |
|
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 |
|
51 |
|
52 function markupDocumentViewer() { |
|
53 return docShell.contentViewer.QueryInterface(CI.nsIMarkupDocumentViewer); |
|
54 } |
|
55 |
|
56 function webNavigation() { |
|
57 return docShell.QueryInterface(CI.nsIWebNavigation); |
|
58 } |
|
59 |
|
60 function windowUtils() { |
|
61 return content.QueryInterface(CI.nsIInterfaceRequestor) |
|
62 .getInterface(CI.nsIDOMWindowUtils); |
|
63 } |
|
64 |
|
65 function IDForEventTarget(event) |
|
66 { |
|
67 try { |
|
68 return "'" + event.target.getAttribute('id') + "'"; |
|
69 } catch (ex) { |
|
70 return "<unknown>"; |
|
71 } |
|
72 } |
|
73 |
|
74 function PaintWaitListener(event) |
|
75 { |
|
76 LogInfo("MozPaintWait received for ID " + IDForEventTarget(event)); |
|
77 gExplicitPendingPaintCount++; |
|
78 } |
|
79 |
|
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 } |
|
93 |
|
94 function OnInitialLoad() |
|
95 { |
|
96 #ifndef REFTEST_B2G |
|
97 removeEventListener("load", OnInitialLoad, true); |
|
98 #endif |
|
99 |
|
100 gDebug = CC[DEBUG_CONTRACTID].getService(CI.nsIDebug2); |
|
101 var env = CC[ENVIRONMENT_CONTRACTID].getService(CI.nsIEnvironment); |
|
102 gVerbose = !!env.get("MOZ_REFTEST_VERBOSE"); |
|
103 |
|
104 RegisterMessageListeners(); |
|
105 |
|
106 var initInfo = SendContentReady(); |
|
107 gBrowserIsRemote = initInfo.remote; |
|
108 |
|
109 addEventListener("load", OnDocumentLoad, true); |
|
110 |
|
111 addEventListener("MozPaintWait", PaintWaitListener, true); |
|
112 addEventListener("MozPaintWaitFinished", PaintWaitFinishedListener, true); |
|
113 |
|
114 LogWarning("Using browser remote="+ gBrowserIsRemote +"\n"); |
|
115 } |
|
116 |
|
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 } |
|
126 |
|
127 gCurrentTestType = type; |
|
128 gCurrentURL = uri; |
|
129 |
|
130 gCurrentTestStartTime = Date.now(); |
|
131 if (gFailureTimeout != null) { |
|
132 SendException("program error managing timeouts\n"); |
|
133 } |
|
134 gFailureTimeout = setTimeout(LoadFailed, timeout); |
|
135 |
|
136 LoadURI(gCurrentURL); |
|
137 } |
|
138 |
|
139 function setupZoom(contentRootElement) { |
|
140 if (!contentRootElement || !contentRootElement.hasAttribute('reftest-zoom')) |
|
141 return; |
|
142 markupDocumentViewer().fullZoom = |
|
143 contentRootElement.getAttribute('reftest-zoom'); |
|
144 } |
|
145 |
|
146 function resetZoom() { |
|
147 markupDocumentViewer().fullZoom = 1.0; |
|
148 } |
|
149 |
|
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 } |
|
162 |
|
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; |
|
169 |
|
170 // Override any os-specific unwriteable margins |
|
171 ps.unwriteableMarginTop = 0; |
|
172 ps.unwriteableMarginLeft = 0; |
|
173 ps.unwriteableMarginBottom = 0; |
|
174 ps.unwriteableMarginRight = 0; |
|
175 |
|
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 } |
|
184 |
|
185 function attrOrDefault(element, attr, def) { |
|
186 return element.hasAttribute(attr) ? Number(element.getAttribute(attr)) : def; |
|
187 } |
|
188 |
|
189 function setupViewport(contentRootElement) { |
|
190 if (!contentRootElement) { |
|
191 return; |
|
192 } |
|
193 |
|
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 } |
|
200 |
|
201 // XXX support resolution when needed |
|
202 |
|
203 // XXX support viewconfig when needed |
|
204 } |
|
205 |
|
206 function setupDisplayport(contentRootElement) { |
|
207 if (!contentRootElement) { |
|
208 return; |
|
209 } |
|
210 |
|
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 } |
|
221 |
|
222 function setupDisplayportForElementSubtree(element) { |
|
223 setupDisplayportForElement(element); |
|
224 for (var c = element.firstElementChild; c; c = c.nextElementSibling) { |
|
225 setupDisplayportForElementSubtree(c); |
|
226 } |
|
227 } |
|
228 |
|
229 if (contentRootElement.hasAttribute("reftest-async-scroll")) { |
|
230 SendEnableAsyncScroll(); |
|
231 setupDisplayportForElementSubtree(contentRootElement); |
|
232 } else { |
|
233 setupDisplayportForElement(contentRootElement); |
|
234 } |
|
235 } |
|
236 |
|
237 function setupAsyncScrollOffsets(options) { |
|
238 var currentDoc = content.document; |
|
239 var contentRootElement = currentDoc ? currentDoc.documentElement : null; |
|
240 |
|
241 if (!contentRootElement) { |
|
242 return; |
|
243 } |
|
244 |
|
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 } |
|
260 |
|
261 function setupAsyncScrollOffsetsForElementSubtree(element) { |
|
262 setupAsyncScrollOffsetsForElement(element); |
|
263 for (var c = element.firstElementChild; c; c = c.nextElementSibling) { |
|
264 setupAsyncScrollOffsetsForElementSubtree(c); |
|
265 } |
|
266 } |
|
267 |
|
268 var asyncScroll = contentRootElement.hasAttribute("reftest-async-scroll"); |
|
269 if (asyncScroll) { |
|
270 setupAsyncScrollOffsetsForElementSubtree(contentRootElement); |
|
271 } |
|
272 } |
|
273 |
|
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 } |
|
278 |
|
279 function shouldWaitForExplicitPaintWaiters() { |
|
280 return gExplicitPendingPaintCount > 0; |
|
281 } |
|
282 |
|
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 } |
|
288 |
|
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 } |
|
296 |
|
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 } |
|
304 |
|
305 function getNoPaintElements(contentRootElement) { |
|
306 return contentRootElement.getElementsByClassName('reftest-no-paint'); |
|
307 } |
|
308 |
|
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; |
|
323 |
|
324 function FlushRendering() { |
|
325 var anyPendingPaintsGeneratedInDescendants = false; |
|
326 |
|
327 function flushWindow(win) { |
|
328 var utils = win.QueryInterface(CI.nsIInterfaceRequestor) |
|
329 .getInterface(CI.nsIDOMWindowUtils); |
|
330 var afterPaintWasPending = utils.isMozAfterPaintPending; |
|
331 |
|
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 } |
|
340 |
|
341 if (!afterPaintWasPending && utils.isMozAfterPaintPending) { |
|
342 LogInfo("FlushRendering generated paint for window " + win.location.href); |
|
343 anyPendingPaintsGeneratedInDescendants = true; |
|
344 } |
|
345 |
|
346 for (var i = 0; i < win.frames.length; ++i) { |
|
347 flushWindow(win.frames[i]); |
|
348 } |
|
349 } |
|
350 |
|
351 flushWindow(content); |
|
352 |
|
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 } |
|
358 |
|
359 function WaitForTestEnd(contentRootElement, inPrintMode, spellCheckedElements) { |
|
360 var stopAfterPaintReceived = false; |
|
361 var currentDoc = content.document; |
|
362 var state = STATE_WAITING_TO_FIRE_INVALIDATE_EVENT; |
|
363 |
|
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 } |
|
371 |
|
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 } |
|
378 |
|
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 } |
|
387 |
|
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 } |
|
394 |
|
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 } |
|
407 |
|
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 } |
|
416 |
|
417 FlushRendering(); |
|
418 |
|
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 } |
|
434 |
|
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 } |
|
448 |
|
449 if (!inPrintMode && doPrintMode(contentRootElement)) { |
|
450 LogInfo("MakeProgress: setting up print mode"); |
|
451 setupPrintMode(); |
|
452 } |
|
453 |
|
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 } |
|
466 |
|
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 } |
|
474 |
|
475 // Try next state |
|
476 state = STATE_WAITING_FOR_SPELL_CHECKS; |
|
477 MakeProgress(); |
|
478 return; |
|
479 |
|
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 } |
|
487 |
|
488 state = STATE_WAITING_TO_FINISH; |
|
489 // Try next state |
|
490 MakeProgress(); |
|
491 return; |
|
492 |
|
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 } |
|
525 |
|
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; |
|
535 |
|
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 } |
|
550 |
|
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 } |
|
557 |
|
558 function OnDocumentLoad(event) |
|
559 { |
|
560 var currentDoc = content.document; |
|
561 if (event.target != currentDoc) |
|
562 // Ignore load events for subframes. |
|
563 return; |
|
564 |
|
565 if (gClearingForAssertionCheck && |
|
566 currentDoc.location.href == BLANK_URL_FOR_CLEARING) { |
|
567 DoAssertionCheck(); |
|
568 return; |
|
569 } |
|
570 |
|
571 if (currentDoc.location.href != gCurrentURL) { |
|
572 LogInfo("OnDocumentLoad fired for previous document"); |
|
573 // Ignore load events for previous documents. |
|
574 return; |
|
575 } |
|
576 |
|
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); |
|
589 |
|
590 var contentRootElement = currentDoc ? currentDoc.documentElement : null; |
|
591 currentDoc = null; |
|
592 setupZoom(contentRootElement); |
|
593 setupViewport(contentRootElement); |
|
594 setupDisplayport(contentRootElement); |
|
595 var inPrintMode = false; |
|
596 |
|
597 function AfterOnLoadScripts() { |
|
598 // Regrab the root element, because the document may have changed. |
|
599 var contentRootElement = |
|
600 content.document ? content.document.documentElement : null; |
|
601 |
|
602 // Flush the document in case it got modified in a load event handler. |
|
603 FlushRendering(); |
|
604 |
|
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(); |
|
610 |
|
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 } |
|
625 |
|
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 } |
|
640 |
|
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 } |
|
650 |
|
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 } |
|
661 |
|
662 function RecordResult() |
|
663 { |
|
664 LogInfo("RecordResult fired"); |
|
665 |
|
666 var currentTestRunTime = Date.now() - gCurrentTestStartTime; |
|
667 |
|
668 clearTimeout(gFailureTimeout); |
|
669 gFailureReason = null; |
|
670 gFailureTimeout = null; |
|
671 |
|
672 if (gCurrentTestType == TYPE_SCRIPT) { |
|
673 var error = ''; |
|
674 var testwindow = content; |
|
675 |
|
676 if (testwindow.wrappedJSObject) |
|
677 testwindow = testwindow.wrappedJSObject; |
|
678 |
|
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 } |
|
693 |
|
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 } |
|
706 |
|
707 SendScriptResults(currentTestRunTime, error, results); |
|
708 FinishTestItem(); |
|
709 return; |
|
710 } |
|
711 |
|
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 } |
|
718 |
|
719 function LoadFailed() |
|
720 { |
|
721 if (gTimeoutHook) { |
|
722 gTimeoutHook(); |
|
723 } |
|
724 gFailureTimeout = null; |
|
725 SendFailedLoad(gFailureReason); |
|
726 } |
|
727 |
|
728 function FinishTestItem() |
|
729 { |
|
730 gHaveCanvasSnapshot = false; |
|
731 } |
|
732 |
|
733 function DoAssertionCheck() |
|
734 { |
|
735 gClearingForAssertionCheck = false; |
|
736 |
|
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 } |
|
745 |
|
746 function LoadURI(uri) |
|
747 { |
|
748 var flags = webNavigation().LOAD_FLAGS_NONE; |
|
749 webNavigation().loadURI(uri, flags, null, null, null); |
|
750 } |
|
751 |
|
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 } |
|
760 |
|
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 } |
|
769 |
|
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 } |
|
779 |
|
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 } |
|
787 |
|
788 var dummyCanvas = content.document.createElementNS(XHTML_NS, "canvas"); |
|
789 dummyCanvas.setAttribute("width", 1); |
|
790 dummyCanvas.setAttribute("height", 1); |
|
791 |
|
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); |
|
795 |
|
796 // Setup async scroll offsets now, because any scrollable layers should |
|
797 // have had their AsyncPanZoomControllers created. |
|
798 setupAsyncScrollOffsets({allowFailure:false}); |
|
799 } |
|
800 |
|
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 } |
|
820 |
|
821 function RecvClear() |
|
822 { |
|
823 gClearingForAssertionCheck = true; |
|
824 LoadURI(BLANK_URL_FOR_CLEARING); |
|
825 } |
|
826 |
|
827 function RecvLoadTest(type, uri, timeout) |
|
828 { |
|
829 StartTestURI(type, uri, timeout); |
|
830 } |
|
831 |
|
832 function RecvLoadScriptTest(uri, timeout) |
|
833 { |
|
834 StartTestURI(TYPE_SCRIPT, uri, timeout); |
|
835 } |
|
836 |
|
837 function RecvResetRenderingState() |
|
838 { |
|
839 resetZoom(); |
|
840 resetDisplayportAndViewport(); |
|
841 } |
|
842 |
|
843 function SendAssertionCount(numAssertions) |
|
844 { |
|
845 sendAsyncMessage("reftest:AssertionCount", { count: numAssertions }); |
|
846 } |
|
847 |
|
848 function SendContentReady() |
|
849 { |
|
850 return sendSyncMessage("reftest:ContentReady")[0]; |
|
851 } |
|
852 |
|
853 function SendException(what) |
|
854 { |
|
855 sendAsyncMessage("reftest:Exception", { what: what }); |
|
856 } |
|
857 |
|
858 function SendFailedLoad(why) |
|
859 { |
|
860 sendAsyncMessage("reftest:FailedLoad", { why: why }); |
|
861 } |
|
862 |
|
863 function SendFailedNoPaint() |
|
864 { |
|
865 sendAsyncMessage("reftest:FailedNoPaint"); |
|
866 } |
|
867 |
|
868 function SendEnableAsyncScroll() |
|
869 { |
|
870 sendAsyncMessage("reftest:EnableAsyncScroll"); |
|
871 } |
|
872 |
|
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 } |
|
885 |
|
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]; |
|
893 |
|
894 gHaveCanvasSnapshot = ret.painted; |
|
895 return ret.painted; |
|
896 } |
|
897 |
|
898 function SendScriptResults(runtimeMs, error, results) |
|
899 { |
|
900 sendAsyncMessage("reftest:ScriptResults", |
|
901 { runtimeMs: runtimeMs, error: error, results: results }); |
|
902 } |
|
903 |
|
904 function SendExpectProcessCrash(runtimeMs) |
|
905 { |
|
906 sendAsyncMessage("reftest:ExpectProcessCrash"); |
|
907 } |
|
908 |
|
909 function SendTestDone(runtimeMs) |
|
910 { |
|
911 sendAsyncMessage("reftest:TestDone", { runtimeMs: runtimeMs }); |
|
912 } |
|
913 |
|
914 function roundTo(x, fraction) |
|
915 { |
|
916 return Math.round(x/fraction)*fraction; |
|
917 } |
|
918 |
|
919 function SendUpdateCanvasForEvent(event, contentRootElement) |
|
920 { |
|
921 var win = content; |
|
922 var scale = markupDocumentViewer().fullZoom; |
|
923 |
|
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 } |
|
936 |
|
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); |
|
947 |
|
948 rects.push({ left: left, top: top, right: right, bottom: bottom }); |
|
949 } |
|
950 |
|
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 |