1 /*
2 * Copyright (C) 2010 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26 #include "config.h"
27 #include "TestController.h"
28
29 #include "PlatformWebView.h"
30 #include "StringFunctions.h"
31 #include "TestInvocation.h"
32 #include <cstdio>
33 #include <WebKit2/WKContextPrivate.h>
34 #include <WebKit2/WKPageGroup.h>
35 #include <WebKit2/WKPreferencesPrivate.h>
36 #include <WebKit2/WKRetainPtr.h>
37 #include <wtf/PassOwnPtr.h>
38
39 namespace WTR {
40
41 static const double defaultLongTimeout = 30;
42 static const double defaultShortTimeout = 5;
43
blankURL()44 static WKURLRef blankURL()
45 {
46 static WKURLRef staticBlankURL = WKURLCreateWithUTF8CString("about:blank");
47 return staticBlankURL;
48 }
49
50 static TestController* controller;
51
shared()52 TestController& TestController::shared()
53 {
54 ASSERT(controller);
55 return *controller;
56 }
57
TestController(int argc,const char * argv[])58 TestController::TestController(int argc, const char* argv[])
59 : m_dumpPixels(false)
60 , m_verbose(false)
61 , m_printSeparators(false)
62 , m_usingServerMode(false)
63 , m_state(Initial)
64 , m_doneResetting(false)
65 , m_longTimeout(defaultLongTimeout)
66 , m_shortTimeout(defaultShortTimeout)
67 , m_didPrintWebProcessCrashedMessage(false)
68 , m_shouldExitWhenWebProcessCrashes(true)
69 {
70 initialize(argc, argv);
71 controller = this;
72 run();
73 controller = 0;
74 }
75
~TestController()76 TestController::~TestController()
77 {
78 }
79
getWindowFrameMainPage(WKPageRef page,const void * clientInfo)80 static WKRect getWindowFrameMainPage(WKPageRef page, const void* clientInfo)
81 {
82 PlatformWebView* view = static_cast<TestController*>(const_cast<void*>(clientInfo))->mainWebView();
83 return view->windowFrame();
84 }
85
setWindowFrameMainPage(WKPageRef page,WKRect frame,const void * clientInfo)86 static void setWindowFrameMainPage(WKPageRef page, WKRect frame, const void* clientInfo)
87 {
88 PlatformWebView* view = static_cast<TestController*>(const_cast<void*>(clientInfo))->mainWebView();
89 view->setWindowFrame(frame);
90 }
91
getWindowFrameOtherPage(WKPageRef page,const void * clientInfo)92 static WKRect getWindowFrameOtherPage(WKPageRef page, const void* clientInfo)
93 {
94 PlatformWebView* view = static_cast<PlatformWebView*>(const_cast<void*>(clientInfo));
95 return view->windowFrame();
96 }
97
setWindowFrameOtherPage(WKPageRef page,WKRect frame,const void * clientInfo)98 static void setWindowFrameOtherPage(WKPageRef page, WKRect frame, const void* clientInfo)
99 {
100 PlatformWebView* view = static_cast<PlatformWebView*>(const_cast<void*>(clientInfo));
101 view->setWindowFrame(frame);
102 }
103
runBeforeUnloadConfirmPanel(WKPageRef page,WKStringRef message,WKFrameRef frame,const void * clientInfo)104 static bool runBeforeUnloadConfirmPanel(WKPageRef page, WKStringRef message, WKFrameRef frame, const void *clientInfo)
105 {
106 printf("%s\n", toSTD(message).c_str());
107 return true;
108 }
109
exceededDatabaseQuota(WKPageRef,WKFrameRef,WKSecurityOriginRef,WKStringRef,WKStringRef,unsigned long long,unsigned long long,unsigned long long,const void *)110 static unsigned long long exceededDatabaseQuota(WKPageRef, WKFrameRef, WKSecurityOriginRef, WKStringRef, WKStringRef, unsigned long long, unsigned long long, unsigned long long, const void*)
111 {
112 static const unsigned long long defaultQuota = 5 * 1024 * 1024;
113 return defaultQuota;
114 }
115
116
runModal(WKPageRef page,const void * clientInfo)117 void TestController::runModal(WKPageRef page, const void* clientInfo)
118 {
119 runModal(static_cast<PlatformWebView*>(const_cast<void*>(clientInfo)));
120 }
121
closeOtherPage(WKPageRef page,const void * clientInfo)122 static void closeOtherPage(WKPageRef page, const void* clientInfo)
123 {
124 WKPageClose(page);
125 const PlatformWebView* view = static_cast<const PlatformWebView*>(clientInfo);
126 delete view;
127 }
128
createOtherPage(WKPageRef oldPage,WKDictionaryRef,WKEventModifiers,WKEventMouseButton,const void *)129 WKPageRef TestController::createOtherPage(WKPageRef oldPage, WKDictionaryRef, WKEventModifiers, WKEventMouseButton, const void*)
130 {
131 PlatformWebView* view = new PlatformWebView(WKPageGetContext(oldPage), WKPageGetPageGroup(oldPage));
132 WKPageRef newPage = view->page();
133
134 view->resizeTo(800, 600);
135
136 WKPageUIClient otherPageUIClient = {
137 0,
138 view,
139 createOtherPage,
140 0, // showPage
141 closeOtherPage,
142 0, // takeFocus
143 0, // focus
144 0, // unfocus
145 0, // runJavaScriptAlert
146 0, // runJavaScriptConfirm
147 0, // runJavaScriptPrompt
148 0, // setStatusText
149 0, // mouseDidMoveOverElement
150 0, // missingPluginButtonClicked
151 0, // didNotHandleKeyEvent
152 0, // toolbarsAreVisible
153 0, // setToolbarsAreVisible
154 0, // menuBarIsVisible
155 0, // setMenuBarIsVisible
156 0, // statusBarIsVisible
157 0, // setStatusBarIsVisible
158 0, // isResizable
159 0, // setIsResizable
160 getWindowFrameOtherPage,
161 setWindowFrameOtherPage,
162 runBeforeUnloadConfirmPanel,
163 0, // didDraw
164 0, // pageDidScroll
165 exceededDatabaseQuota,
166 0, // runOpenPanel
167 0, // decidePolicyForGeolocationPermissionRequest
168 0, // headerHeight
169 0, // footerHeight
170 0, // drawHeader
171 0, // drawFooter
172 0, // printFrame
173 runModal,
174 0, // didCompleteRubberBandForMainFrame
175 0, // saveDataToFileInDownloadsFolder
176 };
177 WKPageSetPageUIClient(newPage, &otherPageUIClient);
178
179 WKRetain(newPage);
180 return newPage;
181 }
182
libraryPathForTesting()183 const char* TestController::libraryPathForTesting()
184 {
185 // FIXME: This may not be sufficient to prevent interactions/crashes
186 // when running more than one copy of DumpRenderTree.
187 // See https://bugs.webkit.org/show_bug.cgi?id=10906
188 char* dumpRenderTreeTemp = getenv("DUMPRENDERTREE_TEMP");
189 if (dumpRenderTreeTemp)
190 return dumpRenderTreeTemp;
191 return platformLibraryPathForTesting();
192 }
193
194
initialize(int argc,const char * argv[])195 void TestController::initialize(int argc, const char* argv[])
196 {
197 platformInitialize();
198
199 bool printSupportedFeatures = false;
200
201 for (int i = 1; i < argc; ++i) {
202 std::string argument(argv[i]);
203
204 if (argument == "--timeout" && i + 1 < argc) {
205 m_longTimeout = atoi(argv[++i]);
206 // Scale up the short timeout to match.
207 m_shortTimeout = defaultShortTimeout * m_longTimeout / defaultLongTimeout;
208 continue;
209 }
210 if (argument == "--pixel-tests") {
211 m_dumpPixels = true;
212 continue;
213 }
214 if (argument == "--verbose") {
215 m_verbose = true;
216 continue;
217 }
218 if (argument == "--print-supported-features") {
219 printSupportedFeatures = true;
220 break;
221 }
222
223 // Skip any other arguments that begin with '--'.
224 if (argument.length() >= 2 && argument[0] == '-' && argument[1] == '-')
225 continue;
226
227 m_paths.push_back(argument);
228 }
229
230 if (printSupportedFeatures) {
231 // FIXME: On Windows, DumpRenderTree uses this to expose whether it supports 3d
232 // transforms and accelerated compositing. When we support those features, we
233 // should match DRT's behavior.
234 exit(0);
235 }
236
237 m_usingServerMode = (m_paths.size() == 1 && m_paths[0] == "-");
238 if (m_usingServerMode)
239 m_printSeparators = true;
240 else
241 m_printSeparators = m_paths.size() > 1;
242
243 initializeInjectedBundlePath();
244 initializeTestPluginDirectory();
245
246 WKRetainPtr<WKStringRef> pageGroupIdentifier(AdoptWK, WKStringCreateWithUTF8CString("WebKitTestRunnerPageGroup"));
247 m_pageGroup.adopt(WKPageGroupCreateWithIdentifier(pageGroupIdentifier.get()));
248
249 m_context.adopt(WKContextCreateWithInjectedBundlePath(injectedBundlePath()));
250
251 const char* path = libraryPathForTesting();
252 if (path) {
253 Vector<char> databaseDirectory(strlen(path) + strlen("/Databases") + 1);
254 sprintf(databaseDirectory.data(), "%s%s", path, "/Databases");
255 WKRetainPtr<WKStringRef> databaseDirectoryWK(AdoptWK, WKStringCreateWithUTF8CString(databaseDirectory.data()));
256 WKContextSetDatabaseDirectory(m_context.get(), databaseDirectoryWK.get());
257 }
258
259 platformInitializeContext();
260
261 WKContextInjectedBundleClient injectedBundleClient = {
262 0,
263 this,
264 didReceiveMessageFromInjectedBundle,
265 didReceiveSynchronousMessageFromInjectedBundle
266 };
267 WKContextSetInjectedBundleClient(m_context.get(), &injectedBundleClient);
268
269 _WKContextSetAdditionalPluginsDirectory(m_context.get(), testPluginDirectory());
270
271 m_mainWebView = adoptPtr(new PlatformWebView(m_context.get(), m_pageGroup.get()));
272
273 WKPageUIClient pageUIClient = {
274 0,
275 this,
276 createOtherPage,
277 0, // showPage
278 0, // close
279 0, // takeFocus
280 0, // focus
281 0, // unfocus
282 0, // runJavaScriptAlert
283 0, // runJavaScriptConfirm
284 0, // runJavaScriptPrompt
285 0, // setStatusText
286 0, // mouseDidMoveOverElement
287 0, // missingPluginButtonClicked
288 0, // didNotHandleKeyEvent
289 0, // toolbarsAreVisible
290 0, // setToolbarsAreVisible
291 0, // menuBarIsVisible
292 0, // setMenuBarIsVisible
293 0, // statusBarIsVisible
294 0, // setStatusBarIsVisible
295 0, // isResizable
296 0, // setIsResizable
297 getWindowFrameMainPage,
298 setWindowFrameMainPage,
299 runBeforeUnloadConfirmPanel,
300 0, // didDraw
301 0, // pageDidScroll
302 exceededDatabaseQuota,
303 0, // runOpenPanel
304 0, // decidePolicyForGeolocationPermissionRequest
305 0, // headerHeight
306 0, // footerHeight
307 0, // drawHeader
308 0, // drawFooter
309 0, // printFrame
310 0, // runModal
311 0, // didCompleteRubberBandForMainFrame
312 0, // saveDataToFileInDownloadsFolder
313 };
314 WKPageSetPageUIClient(m_mainWebView->page(), &pageUIClient);
315
316 WKPageLoaderClient pageLoaderClient = {
317 0,
318 this,
319 0, // didStartProvisionalLoadForFrame
320 0, // didReceiveServerRedirectForProvisionalLoadForFrame
321 0, // didFailProvisionalLoadWithErrorForFrame
322 0, // didCommitLoadForFrame
323 0, // didFinishDocumentLoadForFrame
324 didFinishLoadForFrame,
325 0, // didFailLoadWithErrorForFrame
326 0, // didSameDocumentNavigationForFrame
327 0, // didReceiveTitleForFrame
328 0, // didFirstLayoutForFrame
329 0, // didFirstVisuallyNonEmptyLayoutForFrame
330 0, // didRemoveFrameFromHierarchy
331 0, // didDisplayInsecureContentForFrame
332 0, // didRunInsecureContentForFrame
333 0, // canAuthenticateAgainstProtectionSpaceInFrame
334 0, // didReceiveAuthenticationChallengeInFrame
335 0, // didStartProgress
336 0, // didChangeProgress
337 0, // didFinishProgress
338 0, // didBecomeUnresponsive
339 0, // didBecomeResponsive
340 processDidCrash, // processDidCrash
341 0, // didChangeBackForwardList
342 0 // shouldGoToBackForwardListItem
343 };
344 WKPageSetPageLoaderClient(m_mainWebView->page(), &pageLoaderClient);
345 }
346
resetStateToConsistentValues()347 bool TestController::resetStateToConsistentValues()
348 {
349 m_state = Resetting;
350
351 WKRetainPtr<WKStringRef> messageName(AdoptWK, WKStringCreateWithUTF8CString("Reset"));
352 WKContextPostMessageToInjectedBundle(TestController::shared().context(), messageName.get(), 0);
353
354 // FIXME: This function should also ensure that there is only one page open.
355
356 // Reset preferences
357 WKPreferencesRef preferences = WKPageGroupGetPreferences(m_pageGroup.get());
358 WKPreferencesSetOfflineWebApplicationCacheEnabled(preferences, true);
359 WKPreferencesSetFontSmoothingLevel(preferences, kWKFontSmoothingLevelNoSubpixelAntiAliasing);
360 WKPreferencesSetXSSAuditorEnabled(preferences, false);
361 WKPreferencesSetDeveloperExtrasEnabled(preferences, true);
362 WKPreferencesSetJavaScriptCanOpenWindowsAutomatically(preferences, true);
363 WKPreferencesSetJavaScriptCanAccessClipboard(preferences, true);
364 WKPreferencesSetDOMPasteAllowed(preferences, true);
365 WKPreferencesSetUniversalAccessFromFileURLsAllowed(preferences, true);
366 WKPreferencesSetFileAccessFromFileURLsAllowed(preferences, true);
367 #if ENABLE(FULLSCREEN_API)
368 WKPreferencesSetFullScreenEnabled(preferences, true);
369 #endif
370
371 static WKStringRef standardFontFamily = WKStringCreateWithUTF8CString("Times");
372 static WKStringRef cursiveFontFamily = WKStringCreateWithUTF8CString("Apple Chancery");
373 static WKStringRef fantasyFontFamily = WKStringCreateWithUTF8CString("Papyrus");
374 static WKStringRef fixedFontFamily = WKStringCreateWithUTF8CString("Courier");
375 static WKStringRef sansSerifFontFamily = WKStringCreateWithUTF8CString("Helvetica");
376 static WKStringRef serifFontFamily = WKStringCreateWithUTF8CString("Times");
377
378 WKPreferencesSetStandardFontFamily(preferences, standardFontFamily);
379 WKPreferencesSetCursiveFontFamily(preferences, cursiveFontFamily);
380 WKPreferencesSetFantasyFontFamily(preferences, fantasyFontFamily);
381 WKPreferencesSetFixedFontFamily(preferences, fixedFontFamily);
382 WKPreferencesSetSansSerifFontFamily(preferences, sansSerifFontFamily);
383 WKPreferencesSetSerifFontFamily(preferences, serifFontFamily);
384
385 m_mainWebView->focus();
386
387 // Reset main page back to about:blank
388 m_doneResetting = false;
389
390 WKPageLoadURL(m_mainWebView->page(), blankURL());
391 runUntil(m_doneResetting, ShortTimeout);
392 return m_doneResetting;
393 }
394
runTest(const char * test)395 bool TestController::runTest(const char* test)
396 {
397 if (!resetStateToConsistentValues()) {
398 fputs("#CRASHED - WebProcess\n", stderr);
399 fflush(stderr);
400 return false;
401 }
402
403 std::string pathOrURL(test);
404 std::string expectedPixelHash;
405 size_t separatorPos = pathOrURL.find("'");
406 if (separatorPos != std::string::npos) {
407 pathOrURL = std::string(std::string(test), 0, separatorPos);
408 expectedPixelHash = std::string(std::string(test), separatorPos + 1);
409 }
410
411 m_state = RunningTest;
412
413 m_currentInvocation.set(new TestInvocation(pathOrURL));
414 if (m_dumpPixels)
415 m_currentInvocation->setIsPixelTest(expectedPixelHash);
416
417 m_currentInvocation->invoke();
418 m_currentInvocation.clear();
419
420 return true;
421 }
422
runTestingServerLoop()423 void TestController::runTestingServerLoop()
424 {
425 char filenameBuffer[2048];
426 while (fgets(filenameBuffer, sizeof(filenameBuffer), stdin)) {
427 char* newLineCharacter = strchr(filenameBuffer, '\n');
428 if (newLineCharacter)
429 *newLineCharacter = '\0';
430
431 if (strlen(filenameBuffer) == 0)
432 continue;
433
434 if (!runTest(filenameBuffer))
435 break;
436 }
437 }
438
run()439 void TestController::run()
440 {
441 if (m_usingServerMode)
442 runTestingServerLoop();
443 else {
444 for (size_t i = 0; i < m_paths.size(); ++i) {
445 if (!runTest(m_paths[i].c_str()))
446 break;
447 }
448 }
449 }
450
runUntil(bool & done,TimeoutDuration timeoutDuration)451 void TestController::runUntil(bool& done, TimeoutDuration timeoutDuration)
452 {
453 platformRunUntil(done, timeoutDuration == ShortTimeout ? m_shortTimeout : m_longTimeout);
454 }
455
456 // WKContextInjectedBundleClient
457
didReceiveMessageFromInjectedBundle(WKContextRef context,WKStringRef messageName,WKTypeRef messageBody,const void * clientInfo)458 void TestController::didReceiveMessageFromInjectedBundle(WKContextRef context, WKStringRef messageName, WKTypeRef messageBody, const void* clientInfo)
459 {
460 static_cast<TestController*>(const_cast<void*>(clientInfo))->didReceiveMessageFromInjectedBundle(messageName, messageBody);
461 }
462
didReceiveSynchronousMessageFromInjectedBundle(WKContextRef context,WKStringRef messageName,WKTypeRef messageBody,WKTypeRef * returnData,const void * clientInfo)463 void TestController::didReceiveSynchronousMessageFromInjectedBundle(WKContextRef context, WKStringRef messageName, WKTypeRef messageBody, WKTypeRef* returnData, const void* clientInfo)
464 {
465 *returnData = static_cast<TestController*>(const_cast<void*>(clientInfo))->didReceiveSynchronousMessageFromInjectedBundle(messageName, messageBody).leakRef();
466 }
467
didReceiveMessageFromInjectedBundle(WKStringRef messageName,WKTypeRef messageBody)468 void TestController::didReceiveMessageFromInjectedBundle(WKStringRef messageName, WKTypeRef messageBody)
469 {
470 if (!m_currentInvocation)
471 return;
472 m_currentInvocation->didReceiveMessageFromInjectedBundle(messageName, messageBody);
473 }
474
didReceiveSynchronousMessageFromInjectedBundle(WKStringRef messageName,WKTypeRef messageBody)475 WKRetainPtr<WKTypeRef> TestController::didReceiveSynchronousMessageFromInjectedBundle(WKStringRef messageName, WKTypeRef messageBody)
476 {
477 return m_currentInvocation->didReceiveSynchronousMessageFromInjectedBundle(messageName, messageBody);
478 }
479
480 // WKPageLoaderClient
481
didFinishLoadForFrame(WKPageRef page,WKFrameRef frame,WKTypeRef,const void * clientInfo)482 void TestController::didFinishLoadForFrame(WKPageRef page, WKFrameRef frame, WKTypeRef, const void* clientInfo)
483 {
484 static_cast<TestController*>(const_cast<void*>(clientInfo))->didFinishLoadForFrame(page, frame);
485 }
486
processDidCrash(WKPageRef page,const void * clientInfo)487 void TestController::processDidCrash(WKPageRef page, const void* clientInfo)
488 {
489 static_cast<TestController*>(const_cast<void*>(clientInfo))->processDidCrash();
490 }
491
didFinishLoadForFrame(WKPageRef page,WKFrameRef frame)492 void TestController::didFinishLoadForFrame(WKPageRef page, WKFrameRef frame)
493 {
494 if (m_state != Resetting)
495 return;
496
497 if (!WKFrameIsMainFrame(frame))
498 return;
499
500 WKRetainPtr<WKURLRef> wkURL(AdoptWK, WKFrameCopyURL(frame));
501 if (!WKURLIsEqual(wkURL.get(), blankURL()))
502 return;
503
504 m_doneResetting = true;
505 shared().notifyDone();
506 }
507
processDidCrash()508 void TestController::processDidCrash()
509 {
510 // This function can be called multiple times when crash logs are being saved on Windows, so
511 // ensure we only print the crashed message once.
512 if (!m_didPrintWebProcessCrashedMessage) {
513 fputs("#CRASHED - WebProcess\n", stderr);
514 fflush(stderr);
515 m_didPrintWebProcessCrashedMessage = true;
516 }
517
518 if (m_shouldExitWhenWebProcessCrashes)
519 exit(1);
520 }
521
522 } // namespace WTR
523