|
1 /** |
|
2 * Any copyright is dedicated to the Public Domain. |
|
3 * http://creativecommons.org/publicdomain/zero/1.0/ |
|
4 */ |
|
5 |
|
6 #include "TestHarness.h" |
|
7 |
|
8 #include "nsIAppShell.h" |
|
9 #include "nsIAppShellService.h" |
|
10 #include "nsIDocument.h" |
|
11 #include "nsIDOMEvent.h" |
|
12 #include "nsIDOMEventListener.h" |
|
13 #include "nsIDOMEventTarget.h" |
|
14 #include "nsIDOMWindow.h" |
|
15 #include "nsIDOMWindowUtils.h" |
|
16 #include "nsIInterfaceRequestor.h" |
|
17 #include "nsIRunnable.h" |
|
18 #include "nsIURI.h" |
|
19 #include "nsIWebBrowserChrome.h" |
|
20 #include "nsIXULWindow.h" |
|
21 |
|
22 #include "nsAppShellCID.h" |
|
23 #include "nsIInterfaceRequestorUtils.h" |
|
24 #include "nsNetUtil.h" |
|
25 #include "nsThreadUtils.h" |
|
26 #include "mozilla/Attributes.h" |
|
27 |
|
28 #ifdef XP_WIN |
|
29 #include <windows.h> |
|
30 #endif |
|
31 |
|
32 using namespace mozilla; |
|
33 |
|
34 typedef void (*TestFunc)(nsIAppShell*); |
|
35 |
|
36 bool gStableStateEventHasRun = false; |
|
37 |
|
38 class ExitAppShellRunnable : public nsRunnable |
|
39 { |
|
40 nsCOMPtr<nsIAppShell> mAppShell; |
|
41 |
|
42 public: |
|
43 ExitAppShellRunnable(nsIAppShell* aAppShell) |
|
44 : mAppShell(aAppShell) |
|
45 { } |
|
46 |
|
47 NS_IMETHOD |
|
48 Run() |
|
49 { |
|
50 return mAppShell->Exit(); |
|
51 } |
|
52 }; |
|
53 |
|
54 class StableStateRunnable : public nsRunnable |
|
55 { |
|
56 public: |
|
57 NS_IMETHOD |
|
58 Run() |
|
59 { |
|
60 if (gStableStateEventHasRun) { |
|
61 fail("StableStateRunnable already ran"); |
|
62 } |
|
63 gStableStateEventHasRun = true; |
|
64 return NS_OK; |
|
65 } |
|
66 }; |
|
67 |
|
68 class CheckStableStateRunnable : public nsRunnable |
|
69 { |
|
70 bool mShouldHaveRun; |
|
71 |
|
72 public: |
|
73 CheckStableStateRunnable(bool aShouldHaveRun) |
|
74 : mShouldHaveRun(aShouldHaveRun) |
|
75 { } |
|
76 |
|
77 NS_IMETHOD |
|
78 Run() |
|
79 { |
|
80 if (mShouldHaveRun == gStableStateEventHasRun) { |
|
81 passed("StableStateRunnable state correct (%s)", |
|
82 mShouldHaveRun ? "true" : "false"); |
|
83 } else { |
|
84 fail("StableStateRunnable ran at wrong time"); |
|
85 } |
|
86 return NS_OK; |
|
87 } |
|
88 }; |
|
89 |
|
90 class ScheduleStableStateRunnable : public CheckStableStateRunnable |
|
91 { |
|
92 protected: |
|
93 nsCOMPtr<nsIAppShell> mAppShell; |
|
94 |
|
95 public: |
|
96 ScheduleStableStateRunnable(nsIAppShell* aAppShell) |
|
97 : CheckStableStateRunnable(false), mAppShell(aAppShell) |
|
98 { } |
|
99 |
|
100 NS_IMETHOD |
|
101 Run() |
|
102 { |
|
103 CheckStableStateRunnable::Run(); |
|
104 |
|
105 nsCOMPtr<nsIRunnable> runnable = new StableStateRunnable(); |
|
106 nsresult rv = mAppShell->RunBeforeNextEvent(runnable); |
|
107 if (NS_FAILED(rv)) { |
|
108 fail("RunBeforeNextEvent returned failure code %u", rv); |
|
109 } |
|
110 |
|
111 return rv; |
|
112 } |
|
113 }; |
|
114 |
|
115 class NextTestRunnable : public nsRunnable |
|
116 { |
|
117 nsCOMPtr<nsIAppShell> mAppShell; |
|
118 |
|
119 public: |
|
120 NextTestRunnable(nsIAppShell* aAppShell) |
|
121 : mAppShell(aAppShell) |
|
122 { } |
|
123 |
|
124 NS_IMETHOD Run(); |
|
125 }; |
|
126 |
|
127 class ScheduleNestedStableStateRunnable : public ScheduleStableStateRunnable |
|
128 { |
|
129 public: |
|
130 ScheduleNestedStableStateRunnable(nsIAppShell* aAppShell) |
|
131 : ScheduleStableStateRunnable(aAppShell) |
|
132 { } |
|
133 |
|
134 NS_IMETHOD |
|
135 Run() |
|
136 { |
|
137 ScheduleStableStateRunnable::Run(); |
|
138 |
|
139 nsCOMPtr<nsIRunnable> runnable = new CheckStableStateRunnable(false); |
|
140 if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) { |
|
141 fail("Failed to dispatch check runnable"); |
|
142 } |
|
143 |
|
144 if (NS_FAILED(NS_ProcessPendingEvents(nullptr))) { |
|
145 fail("Failed to process all pending events"); |
|
146 } |
|
147 |
|
148 runnable = new CheckStableStateRunnable(true); |
|
149 if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) { |
|
150 fail("Failed to dispatch check runnable"); |
|
151 } |
|
152 |
|
153 runnable = new NextTestRunnable(mAppShell); |
|
154 if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) { |
|
155 fail("Failed to dispatch next test runnable"); |
|
156 } |
|
157 |
|
158 return NS_OK; |
|
159 } |
|
160 }; |
|
161 |
|
162 class EventListener MOZ_FINAL : public nsIDOMEventListener |
|
163 { |
|
164 nsCOMPtr<nsIAppShell> mAppShell; |
|
165 |
|
166 static nsIDOMWindowUtils* sWindowUtils; |
|
167 static nsIAppShell* sAppShell; |
|
168 |
|
169 public: |
|
170 NS_DECL_ISUPPORTS |
|
171 |
|
172 EventListener(nsIAppShell* aAppShell) |
|
173 : mAppShell(aAppShell) |
|
174 { } |
|
175 |
|
176 NS_IMETHOD |
|
177 HandleEvent(nsIDOMEvent* aEvent) |
|
178 { |
|
179 nsString type; |
|
180 if (NS_FAILED(aEvent->GetType(type))) { |
|
181 fail("Failed to get event type"); |
|
182 return NS_ERROR_FAILURE; |
|
183 } |
|
184 |
|
185 if (type.EqualsLiteral("load")) { |
|
186 passed("Got load event"); |
|
187 |
|
188 nsCOMPtr<nsIDOMEventTarget> target; |
|
189 if (NS_FAILED(aEvent->GetTarget(getter_AddRefs(target)))) { |
|
190 fail("Failed to get event type"); |
|
191 return NS_ERROR_FAILURE; |
|
192 } |
|
193 |
|
194 nsCOMPtr<nsIDocument> document = do_QueryInterface(target); |
|
195 if (!document) { |
|
196 fail("Failed to QI to nsIDocument!"); |
|
197 return NS_ERROR_FAILURE; |
|
198 } |
|
199 |
|
200 nsCOMPtr<nsPIDOMWindow> window = document->GetWindow(); |
|
201 if (!window) { |
|
202 fail("Failed to get window from document!"); |
|
203 return NS_ERROR_FAILURE; |
|
204 } |
|
205 |
|
206 nsCOMPtr<nsIDOMWindowUtils> utils = do_GetInterface(window); |
|
207 if (!utils) { |
|
208 fail("Failed to get DOMWindowUtils!"); |
|
209 return NS_ERROR_FAILURE; |
|
210 } |
|
211 |
|
212 if (!ScheduleTimer(utils)) { |
|
213 return NS_ERROR_FAILURE; |
|
214 } |
|
215 |
|
216 return NS_OK; |
|
217 } |
|
218 |
|
219 if (type.EqualsLiteral("keypress")) { |
|
220 passed("Got keypress event"); |
|
221 |
|
222 nsCOMPtr<nsIRunnable> runnable = new StableStateRunnable(); |
|
223 nsresult rv = mAppShell->RunBeforeNextEvent(runnable); |
|
224 if (NS_FAILED(rv)) { |
|
225 fail("RunBeforeNextEvent returned failure code %u", rv); |
|
226 return NS_ERROR_FAILURE; |
|
227 } |
|
228 |
|
229 return NS_OK; |
|
230 } |
|
231 |
|
232 fail("Got an unexpected event: %s", NS_ConvertUTF16toUTF8(type).get()); |
|
233 return NS_OK; |
|
234 } |
|
235 |
|
236 #ifdef XP_WIN |
|
237 static VOID CALLBACK |
|
238 TimerCallback(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime) |
|
239 { |
|
240 if (sWindowUtils) { |
|
241 nsCOMPtr<nsIDOMWindowUtils> utils = dont_AddRef(sWindowUtils); |
|
242 sWindowUtils = nullptr; |
|
243 |
|
244 if (gStableStateEventHasRun) { |
|
245 fail("StableStateRunnable ran at wrong time"); |
|
246 } else { |
|
247 passed("StableStateRunnable state correct (false)"); |
|
248 } |
|
249 |
|
250 int32_t layout = 0x409; // US |
|
251 int32_t keyCode = 0x41; // VK_A |
|
252 NS_NAMED_LITERAL_STRING(a, "a"); |
|
253 |
|
254 if (NS_FAILED(utils->SendNativeKeyEvent(layout, keyCode, 0, a, a))) { |
|
255 fail("Failed to synthesize native event"); |
|
256 } |
|
257 |
|
258 return; |
|
259 } |
|
260 |
|
261 KillTimer(nullptr, idEvent); |
|
262 |
|
263 nsCOMPtr<nsIAppShell> appShell = dont_AddRef(sAppShell); |
|
264 |
|
265 if (!gStableStateEventHasRun) { |
|
266 fail("StableStateRunnable didn't run yet"); |
|
267 } else { |
|
268 passed("StableStateRunnable state correct (true)"); |
|
269 } |
|
270 |
|
271 nsCOMPtr<nsIRunnable> runnable = new NextTestRunnable(appShell); |
|
272 if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) { |
|
273 fail("Failed to dispatch next test runnable"); |
|
274 } |
|
275 |
|
276 } |
|
277 #endif |
|
278 |
|
279 bool |
|
280 ScheduleTimer(nsIDOMWindowUtils* aWindowUtils) |
|
281 { |
|
282 #ifdef XP_WIN |
|
283 UINT_PTR timerId = SetTimer(nullptr, 0, 1000, (TIMERPROC)TimerCallback); |
|
284 if (!timerId) { |
|
285 fail("SetTimer failed!"); |
|
286 return false; |
|
287 } |
|
288 |
|
289 nsCOMPtr<nsIDOMWindowUtils> utils = aWindowUtils; |
|
290 utils.forget(&sWindowUtils); |
|
291 |
|
292 nsCOMPtr<nsIAppShell> appShell = mAppShell; |
|
293 appShell.forget(&sAppShell); |
|
294 |
|
295 return true; |
|
296 #else |
|
297 return false; |
|
298 #endif |
|
299 } |
|
300 }; |
|
301 |
|
302 nsIDOMWindowUtils* EventListener::sWindowUtils = nullptr; |
|
303 nsIAppShell* EventListener::sAppShell = nullptr; |
|
304 |
|
305 NS_IMPL_ISUPPORTS(EventListener, nsIDOMEventListener) |
|
306 |
|
307 already_AddRefed<nsIAppShell> |
|
308 GetAppShell() |
|
309 { |
|
310 static const char* platforms[] = { |
|
311 "android", "mac", "gonk", "gtk", "qt", "win" |
|
312 }; |
|
313 |
|
314 NS_NAMED_LITERAL_CSTRING(contractPrefix, "@mozilla.org/widget/appshell/"); |
|
315 NS_NAMED_LITERAL_CSTRING(contractSuffix, ";1"); |
|
316 |
|
317 for (size_t index = 0; index < ArrayLength(platforms); index++) { |
|
318 nsAutoCString contractID(contractPrefix); |
|
319 contractID.AppendASCII(platforms[index]); |
|
320 contractID.Append(contractSuffix); |
|
321 |
|
322 nsCOMPtr<nsIAppShell> appShell = do_GetService(contractID.get()); |
|
323 if (appShell) { |
|
324 return appShell.forget(); |
|
325 } |
|
326 } |
|
327 |
|
328 return nullptr; |
|
329 } |
|
330 |
|
331 void |
|
332 Test1(nsIAppShell* aAppShell) |
|
333 { |
|
334 // Schedule stable state runnable to be run before next event. |
|
335 |
|
336 nsCOMPtr<nsIRunnable> runnable = new StableStateRunnable(); |
|
337 if (NS_FAILED(aAppShell->RunBeforeNextEvent(runnable))) { |
|
338 fail("RunBeforeNextEvent failed"); |
|
339 } |
|
340 |
|
341 runnable = new CheckStableStateRunnable(true); |
|
342 if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) { |
|
343 fail("Failed to dispatch check runnable"); |
|
344 } |
|
345 |
|
346 runnable = new NextTestRunnable(aAppShell); |
|
347 if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) { |
|
348 fail("Failed to dispatch next test runnable"); |
|
349 } |
|
350 } |
|
351 |
|
352 void |
|
353 Test2(nsIAppShell* aAppShell) |
|
354 { |
|
355 // Schedule stable state runnable to be run before next event from another |
|
356 // runnable. |
|
357 |
|
358 nsCOMPtr<nsIRunnable> runnable = new ScheduleStableStateRunnable(aAppShell); |
|
359 if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) { |
|
360 fail("Failed to dispatch schedule runnable"); |
|
361 } |
|
362 |
|
363 runnable = new CheckStableStateRunnable(true); |
|
364 if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) { |
|
365 fail("Failed to dispatch check runnable"); |
|
366 } |
|
367 |
|
368 runnable = new NextTestRunnable(aAppShell); |
|
369 if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) { |
|
370 fail("Failed to dispatch next test runnable"); |
|
371 } |
|
372 } |
|
373 |
|
374 void |
|
375 Test3(nsIAppShell* aAppShell) |
|
376 { |
|
377 // Schedule steadystate runnable to be run before next event with nested loop. |
|
378 |
|
379 nsCOMPtr<nsIRunnable> runnable = |
|
380 new ScheduleNestedStableStateRunnable(aAppShell); |
|
381 if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) { |
|
382 fail("Failed to dispatch schedule runnable"); |
|
383 } |
|
384 } |
|
385 |
|
386 bool |
|
387 Test4Internal(nsIAppShell* aAppShell) |
|
388 { |
|
389 #ifndef XP_WIN |
|
390 // Not sure how to test on other platforms. |
|
391 return false; |
|
392 #endif |
|
393 |
|
394 nsCOMPtr<nsIAppShellService> appService = |
|
395 do_GetService(NS_APPSHELLSERVICE_CONTRACTID); |
|
396 if (!appService) { |
|
397 fail("Failed to get appshell service!"); |
|
398 return false; |
|
399 } |
|
400 |
|
401 nsCOMPtr<nsIURI> uri; |
|
402 if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), "about:", nullptr))) { |
|
403 fail("Failed to create new uri"); |
|
404 return false; |
|
405 } |
|
406 |
|
407 uint32_t flags = nsIWebBrowserChrome::CHROME_DEFAULT; |
|
408 |
|
409 nsCOMPtr<nsIXULWindow> xulWindow; |
|
410 if (NS_FAILED(appService->CreateTopLevelWindow(nullptr, uri, flags, 100, 100, |
|
411 getter_AddRefs(xulWindow)))) { |
|
412 fail("Failed to create new window"); |
|
413 return false; |
|
414 } |
|
415 |
|
416 nsCOMPtr<nsIDOMWindow> window = do_GetInterface(xulWindow); |
|
417 if (!window) { |
|
418 fail("Can't get dom window!"); |
|
419 return false; |
|
420 } |
|
421 |
|
422 nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(window); |
|
423 if (!target) { |
|
424 fail("Can't QI to nsIDOMEventTarget!"); |
|
425 return false; |
|
426 } |
|
427 |
|
428 nsCOMPtr<nsIDOMEventListener> listener = new EventListener(aAppShell); |
|
429 if (NS_FAILED(target->AddEventListener(NS_LITERAL_STRING("keypress"), |
|
430 listener, false, false)) || |
|
431 NS_FAILED(target->AddEventListener(NS_LITERAL_STRING("load"), listener, |
|
432 false, false))) { |
|
433 fail("Can't add event listeners!"); |
|
434 return false; |
|
435 } |
|
436 |
|
437 return true; |
|
438 } |
|
439 |
|
440 void |
|
441 Test4(nsIAppShell* aAppShell) |
|
442 { |
|
443 if (!Test4Internal(aAppShell)) { |
|
444 nsCOMPtr<nsIRunnable> runnable = new NextTestRunnable(aAppShell); |
|
445 if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) { |
|
446 fail("Failed to dispatch next test runnable"); |
|
447 } |
|
448 } |
|
449 } |
|
450 |
|
451 const TestFunc gTests[] = { |
|
452 Test1, Test2, Test3, Test4 |
|
453 }; |
|
454 |
|
455 size_t gTestIndex = 0; |
|
456 |
|
457 NS_IMETHODIMP |
|
458 NextTestRunnable::Run() |
|
459 { |
|
460 if (gTestIndex > 0) { |
|
461 passed("Finished test %u", gTestIndex); |
|
462 } |
|
463 |
|
464 gStableStateEventHasRun = false; |
|
465 |
|
466 if (gTestIndex < ArrayLength(gTests)) { |
|
467 gTests[gTestIndex++](mAppShell); |
|
468 } |
|
469 else { |
|
470 nsCOMPtr<nsIRunnable> exitRunnable = new ExitAppShellRunnable(mAppShell); |
|
471 |
|
472 nsresult rv = NS_DispatchToCurrentThread(exitRunnable); |
|
473 if (NS_FAILED(rv)) { |
|
474 fail("Failed to dispatch exit runnable!"); |
|
475 } |
|
476 } |
|
477 |
|
478 return NS_OK; |
|
479 } |
|
480 |
|
481 int main(int argc, char** argv) |
|
482 { |
|
483 ScopedLogging log; |
|
484 ScopedXPCOM xpcom("TestAppShellSteadyState"); |
|
485 |
|
486 if (!xpcom.failed()) { |
|
487 nsCOMPtr<nsIAppShell> appShell = GetAppShell(); |
|
488 if (!appShell) { |
|
489 fail("Couldn't get appshell!"); |
|
490 } else { |
|
491 nsCOMPtr<nsIRunnable> runnable = new NextTestRunnable(appShell); |
|
492 if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) { |
|
493 fail("Failed to dispatch next test runnable"); |
|
494 } else if (NS_FAILED(appShell->Run())) { |
|
495 fail("Failed to run appshell"); |
|
496 } |
|
497 } |
|
498 } |
|
499 |
|
500 return gFailCount != 0; |
|
501 } |