2 * Copyright (C) 2010 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "TestController.h"
29 #include "PlatformWebView.h"
30 #include "StringFunctions.h"
31 #include "TestInvocation.h"
32 #include <WebKit2/WKContextPrivate.h>
33 #include <WebKit2/WKNumber.h>
34 #include <WebKit2/WKPageGroup.h>
35 #include <WebKit2/WKPreferencesPrivate.h>
36 #include <WebKit2/WKRetainPtr.h>
38 #include <wtf/PassOwnPtr.h>
41 #include "EventSenderProxy.h"
46 static const double defaultLongTimeout = 30;
47 static const double defaultShortTimeout = 5;
49 static WKURLRef blankURL()
51 static WKURLRef staticBlankURL = WKURLCreateWithUTF8CString("about:blank");
52 return staticBlankURL;
55 static TestController* controller;
57 TestController& TestController::shared()
63 TestController::TestController(int argc, const char* argv[])
66 , m_printSeparators(false)
67 , m_usingServerMode(false)
68 , m_gcBetweenTests(false)
70 , m_doneResetting(false)
71 , m_longTimeout(defaultLongTimeout)
72 , m_shortTimeout(defaultShortTimeout)
73 , m_didPrintWebProcessCrashedMessage(false)
74 , m_shouldExitWhenWebProcessCrashes(true)
75 , m_beforeUnloadReturnValue(true)
77 , m_eventSenderProxy(new EventSenderProxy(this))
80 initialize(argc, argv);
86 TestController::~TestController()
90 static WKRect getWindowFrameMainPage(WKPageRef page, const void* clientInfo)
92 PlatformWebView* view = static_cast<TestController*>(const_cast<void*>(clientInfo))->mainWebView();
93 return view->windowFrame();
96 static void setWindowFrameMainPage(WKPageRef page, WKRect frame, const void* clientInfo)
98 PlatformWebView* view = static_cast<TestController*>(const_cast<void*>(clientInfo))->mainWebView();
99 view->setWindowFrame(frame);
102 static WKRect getWindowFrameOtherPage(WKPageRef page, const void* clientInfo)
104 PlatformWebView* view = static_cast<PlatformWebView*>(const_cast<void*>(clientInfo));
105 return view->windowFrame();
108 static void setWindowFrameOtherPage(WKPageRef page, WKRect frame, const void* clientInfo)
110 PlatformWebView* view = static_cast<PlatformWebView*>(const_cast<void*>(clientInfo));
111 view->setWindowFrame(frame);
114 static bool runBeforeUnloadConfirmPanel(WKPageRef page, WKStringRef message, WKFrameRef frame, const void *clientInfo)
116 TestController* testController = static_cast<TestController*>(const_cast<void*>(clientInfo));
117 printf("CONFIRM NAVIGATION: %s\n", toSTD(message).c_str());
118 return testController->beforeUnloadReturnValue();
121 static unsigned long long exceededDatabaseQuota(WKPageRef, WKFrameRef, WKSecurityOriginRef, WKStringRef, WKStringRef, unsigned long long, unsigned long long, unsigned long long, unsigned long long, const void*)
123 static const unsigned long long defaultQuota = 5 * 1024 * 1024;
128 void TestController::runModal(WKPageRef page, const void* clientInfo)
130 runModal(static_cast<PlatformWebView*>(const_cast<void*>(clientInfo)));
133 static void closeOtherPage(WKPageRef page, const void* clientInfo)
136 const PlatformWebView* view = static_cast<const PlatformWebView*>(clientInfo);
140 WKPageRef TestController::createOtherPage(WKPageRef oldPage, WKURLRequestRef, WKDictionaryRef, WKEventModifiers, WKEventMouseButton, const void*)
142 PlatformWebView* view = new PlatformWebView(WKPageGetContext(oldPage), WKPageGetPageGroup(oldPage));
143 WKPageRef newPage = view->page();
145 view->resizeTo(800, 600);
147 WKPageUIClient otherPageUIClient = {
148 kWKPageUIClientCurrentVersion,
150 0, // createNewPage_deprecatedForUseWithV0
156 0, // runJavaScriptAlert
157 0, // runJavaScriptConfirm
158 0, // runJavaScriptPrompt
160 0, // mouseDidMoveOverElement
161 0, // missingPluginButtonClicked
162 0, // didNotHandleKeyEvent
163 0, // didNotHandleWheelEvent
164 0, // toolbarsAreVisible
165 0, // setToolbarsAreVisible
166 0, // menuBarIsVisible
167 0, // setMenuBarIsVisible
168 0, // statusBarIsVisible
169 0, // setStatusBarIsVisible
172 getWindowFrameOtherPage,
173 setWindowFrameOtherPage,
174 runBeforeUnloadConfirmPanel,
177 exceededDatabaseQuota,
179 0, // decidePolicyForGeolocationPermissionRequest
186 0, // didCompleteRubberBandForMainFrame
187 0, // saveDataToFileInDownloadsFolder
188 0, // shouldInterruptJavaScript
191 WKPageSetPageUIClient(newPage, &otherPageUIClient);
197 const char* TestController::libraryPathForTesting()
199 // FIXME: This may not be sufficient to prevent interactions/crashes
200 // when running more than one copy of DumpRenderTree.
201 // See https://bugs.webkit.org/show_bug.cgi?id=10906
202 char* dumpRenderTreeTemp = getenv("DUMPRENDERTREE_TEMP");
203 if (dumpRenderTreeTemp)
204 return dumpRenderTreeTemp;
205 return platformLibraryPathForTesting();
209 void TestController::initialize(int argc, const char* argv[])
211 platformInitialize();
213 bool printSupportedFeatures = false;
215 for (int i = 1; i < argc; ++i) {
216 std::string argument(argv[i]);
218 if (argument == "--timeout" && i + 1 < argc) {
219 m_longTimeout = atoi(argv[++i]);
220 // Scale up the short timeout to match.
221 m_shortTimeout = defaultShortTimeout * m_longTimeout / defaultLongTimeout;
224 if (argument == "--pixel-tests") {
228 if (argument == "--verbose") {
232 if (argument == "--gc-between-tests") {
233 m_gcBetweenTests = true;
236 if (argument == "--print-supported-features") {
237 printSupportedFeatures = true;
241 // Skip any other arguments that begin with '--'.
242 if (argument.length() >= 2 && argument[0] == '-' && argument[1] == '-')
245 m_paths.push_back(argument);
248 if (printSupportedFeatures) {
249 // FIXME: On Windows, DumpRenderTree uses this to expose whether it supports 3d
250 // transforms and accelerated compositing. When we support those features, we
251 // should match DRT's behavior.
255 m_usingServerMode = (m_paths.size() == 1 && m_paths[0] == "-");
256 if (m_usingServerMode)
257 m_printSeparators = true;
259 m_printSeparators = m_paths.size() > 1;
261 initializeInjectedBundlePath();
262 initializeTestPluginDirectory();
264 WKRetainPtr<WKStringRef> pageGroupIdentifier(AdoptWK, WKStringCreateWithUTF8CString("WebKitTestRunnerPageGroup"));
265 m_pageGroup.adopt(WKPageGroupCreateWithIdentifier(pageGroupIdentifier.get()));
267 m_context.adopt(WKContextCreateWithInjectedBundlePath(injectedBundlePath()));
269 const char* path = libraryPathForTesting();
271 Vector<char> databaseDirectory(strlen(path) + strlen("/Databases") + 1);
272 sprintf(databaseDirectory.data(), "%s%s", path, "/Databases");
273 WKRetainPtr<WKStringRef> databaseDirectoryWK(AdoptWK, WKStringCreateWithUTF8CString(databaseDirectory.data()));
274 WKContextSetDatabaseDirectory(m_context.get(), databaseDirectoryWK.get());
277 platformInitializeContext();
279 WKContextInjectedBundleClient injectedBundleClient = {
280 kWKContextInjectedBundleClientCurrentVersion,
282 didReceiveMessageFromInjectedBundle,
283 didReceiveSynchronousMessageFromInjectedBundle
285 WKContextSetInjectedBundleClient(m_context.get(), &injectedBundleClient);
287 _WKContextSetAdditionalPluginsDirectory(m_context.get(), testPluginDirectory());
289 m_mainWebView = adoptPtr(new PlatformWebView(m_context.get(), m_pageGroup.get()));
291 WKPageUIClient pageUIClient = {
292 kWKPageUIClientCurrentVersion,
294 0, // createNewPage_deprecatedForUseWithV0
300 0, // runJavaScriptAlert
301 0, // runJavaScriptConfirm
302 0, // runJavaScriptPrompt
304 0, // mouseDidMoveOverElement
305 0, // missingPluginButtonClicked
306 0, // didNotHandleKeyEvent
307 0, // didNotHandleWheelEvent
308 0, // toolbarsAreVisible
309 0, // setToolbarsAreVisible
310 0, // menuBarIsVisible
311 0, // setMenuBarIsVisible
312 0, // statusBarIsVisible
313 0, // setStatusBarIsVisible
316 getWindowFrameMainPage,
317 setWindowFrameMainPage,
318 runBeforeUnloadConfirmPanel,
321 exceededDatabaseQuota,
323 0, // decidePolicyForGeolocationPermissionRequest
330 0, // didCompleteRubberBandForMainFrame
331 0, // saveDataToFileInDownloadsFolder
332 0, // shouldInterruptJavaScript
335 WKPageSetPageUIClient(m_mainWebView->page(), &pageUIClient);
337 WKPageLoaderClient pageLoaderClient = {
338 kWKPageLoaderClientCurrentVersion,
340 0, // didStartProvisionalLoadForFrame
341 0, // didReceiveServerRedirectForProvisionalLoadForFrame
342 0, // didFailProvisionalLoadWithErrorForFrame
343 0, // didCommitLoadForFrame
344 0, // didFinishDocumentLoadForFrame
345 didFinishLoadForFrame,
346 0, // didFailLoadWithErrorForFrame
347 0, // didSameDocumentNavigationForFrame
348 0, // didReceiveTitleForFrame
349 0, // didFirstLayoutForFrame
350 0, // didFirstVisuallyNonEmptyLayoutForFrame
351 0, // didRemoveFrameFromHierarchy
352 0, // didDisplayInsecureContentForFrame
353 0, // didRunInsecureContentForFrame
354 0, // canAuthenticateAgainstProtectionSpaceInFrame
355 0, // didReceiveAuthenticationChallengeInFrame
356 0, // didStartProgress
357 0, // didChangeProgress
358 0, // didFinishProgress
359 0, // didBecomeUnresponsive
360 0, // didBecomeResponsive
362 0, // didChangeBackForwardList
363 0, // shouldGoToBackForwardListItem
364 0 // didFailToInitializePlugin
366 WKPageSetPageLoaderClient(m_mainWebView->page(), &pageLoaderClient);
369 bool TestController::resetStateToConsistentValues()
373 m_beforeUnloadReturnValue = true;
375 WKRetainPtr<WKStringRef> messageName = adoptWK(WKStringCreateWithUTF8CString("Reset"));
376 WKRetainPtr<WKMutableDictionaryRef> resetMessageBody = adoptWK(WKMutableDictionaryCreate());
378 WKRetainPtr<WKStringRef> shouldGCKey = adoptWK(WKStringCreateWithUTF8CString("ShouldGC"));
379 WKRetainPtr<WKBooleanRef> shouldGCValue = adoptWK(WKBooleanCreate(m_gcBetweenTests));
380 WKDictionaryAddItem(resetMessageBody.get(), shouldGCKey.get(), shouldGCValue.get());
382 WKContextPostMessageToInjectedBundle(TestController::shared().context(), messageName.get(), resetMessageBody.get());
384 // FIXME: This function should also ensure that there is only one page open.
387 WKPreferencesRef preferences = WKPageGroupGetPreferences(m_pageGroup.get());
388 WKPreferencesSetOfflineWebApplicationCacheEnabled(preferences, true);
389 WKPreferencesSetFontSmoothingLevel(preferences, kWKFontSmoothingLevelNoSubpixelAntiAliasing);
390 WKPreferencesSetXSSAuditorEnabled(preferences, false);
391 WKPreferencesSetDeveloperExtrasEnabled(preferences, true);
392 WKPreferencesSetJavaScriptCanOpenWindowsAutomatically(preferences, true);
393 WKPreferencesSetJavaScriptCanAccessClipboard(preferences, true);
394 WKPreferencesSetDOMPasteAllowed(preferences, true);
395 WKPreferencesSetUniversalAccessFromFileURLsAllowed(preferences, true);
396 WKPreferencesSetFileAccessFromFileURLsAllowed(preferences, true);
397 #if ENABLE(FULLSCREEN_API)
398 WKPreferencesSetFullScreenEnabled(preferences, true);
402 static WKStringRef standardFontFamily = WKStringCreateWithUTF8CString("Times");
403 static WKStringRef cursiveFontFamily = WKStringCreateWithUTF8CString("Apple Chancery");
404 static WKStringRef fantasyFontFamily = WKStringCreateWithUTF8CString("Papyrus");
405 static WKStringRef fixedFontFamily = WKStringCreateWithUTF8CString("Courier");
406 static WKStringRef pictographFontFamily = WKStringCreateWithUTF8CString("Apple Color Emoji");
407 static WKStringRef sansSerifFontFamily = WKStringCreateWithUTF8CString("Helvetica");
408 static WKStringRef serifFontFamily = WKStringCreateWithUTF8CString("Times");
410 WKPreferencesSetStandardFontFamily(preferences, standardFontFamily);
411 WKPreferencesSetCursiveFontFamily(preferences, cursiveFontFamily);
412 WKPreferencesSetFantasyFontFamily(preferences, fantasyFontFamily);
413 WKPreferencesSetFixedFontFamily(preferences, fixedFontFamily);
414 WKPreferencesSetPictographFontFamily(preferences, pictographFontFamily);
415 WKPreferencesSetSansSerifFontFamily(preferences, sansSerifFontFamily);
416 WKPreferencesSetSerifFontFamily(preferences, serifFontFamily);
419 m_mainWebView->focus();
421 // Reset main page back to about:blank
422 m_doneResetting = false;
424 WKPageLoadURL(m_mainWebView->page(), blankURL());
425 runUntil(m_doneResetting, ShortTimeout);
426 return m_doneResetting;
429 bool TestController::runTest(const char* test)
431 if (!resetStateToConsistentValues()) {
432 fputs("#CRASHED - WebProcess\n", stderr);
437 std::string pathOrURL(test);
438 std::string expectedPixelHash;
439 size_t separatorPos = pathOrURL.find("'");
440 if (separatorPos != std::string::npos) {
441 pathOrURL = std::string(std::string(test), 0, separatorPos);
442 expectedPixelHash = std::string(std::string(test), separatorPos + 1);
445 m_state = RunningTest;
447 m_currentInvocation = adoptPtr(new TestInvocation(pathOrURL));
449 m_currentInvocation->setIsPixelTest(expectedPixelHash);
451 m_currentInvocation->invoke();
452 m_currentInvocation.clear();
457 void TestController::runTestingServerLoop()
459 char filenameBuffer[2048];
460 while (fgets(filenameBuffer, sizeof(filenameBuffer), stdin)) {
461 char* newLineCharacter = strchr(filenameBuffer, '\n');
462 if (newLineCharacter)
463 *newLineCharacter = '\0';
465 if (strlen(filenameBuffer) == 0)
468 if (!runTest(filenameBuffer))
473 void TestController::run()
475 if (m_usingServerMode)
476 runTestingServerLoop();
478 for (size_t i = 0; i < m_paths.size(); ++i) {
479 if (!runTest(m_paths[i].c_str()))
485 void TestController::runUntil(bool& done, TimeoutDuration timeoutDuration)
487 platformRunUntil(done, timeoutDuration == ShortTimeout ? m_shortTimeout : m_longTimeout);
490 // WKContextInjectedBundleClient
492 void TestController::didReceiveMessageFromInjectedBundle(WKContextRef context, WKStringRef messageName, WKTypeRef messageBody, const void* clientInfo)
494 static_cast<TestController*>(const_cast<void*>(clientInfo))->didReceiveMessageFromInjectedBundle(messageName, messageBody);
497 void TestController::didReceiveSynchronousMessageFromInjectedBundle(WKContextRef context, WKStringRef messageName, WKTypeRef messageBody, WKTypeRef* returnData, const void* clientInfo)
499 *returnData = static_cast<TestController*>(const_cast<void*>(clientInfo))->didReceiveSynchronousMessageFromInjectedBundle(messageName, messageBody).leakRef();
502 void TestController::didReceiveMessageFromInjectedBundle(WKStringRef messageName, WKTypeRef messageBody)
504 if (!m_currentInvocation)
506 m_currentInvocation->didReceiveMessageFromInjectedBundle(messageName, messageBody);
509 WKRetainPtr<WKTypeRef> TestController::didReceiveSynchronousMessageFromInjectedBundle(WKStringRef messageName, WKTypeRef messageBody)
512 if (WKStringIsEqualToUTF8CString(messageName, "EventSender")) {
513 ASSERT(WKGetTypeID(messageBody) == WKDictionaryGetTypeID());
514 WKDictionaryRef messageBodyDictionary = static_cast<WKDictionaryRef>(messageBody);
516 WKRetainPtr<WKStringRef> subMessageKey(AdoptWK, WKStringCreateWithUTF8CString("SubMessage"));
517 WKStringRef subMessageName = static_cast<WKStringRef>(WKDictionaryGetItemForKey(messageBodyDictionary, subMessageKey.get()));
519 if (WKStringIsEqualToUTF8CString(subMessageName, "KeyDown")) {
520 WKRetainPtr<WKStringRef> keyKey = adoptWK(WKStringCreateWithUTF8CString("Key"));
521 WKStringRef key = static_cast<WKStringRef>(WKDictionaryGetItemForKey(messageBodyDictionary, keyKey.get()));
523 WKRetainPtr<WKStringRef> modifiersKey = adoptWK(WKStringCreateWithUTF8CString("Modifiers"));
524 WKEventModifiers modifiers = static_cast<WKEventModifiers>(WKUInt64GetValue(static_cast<WKUInt64Ref>(WKDictionaryGetItemForKey(messageBodyDictionary, modifiersKey.get()))));
526 WKRetainPtr<WKStringRef> locationKey = adoptWK(WKStringCreateWithUTF8CString("Location"));
527 unsigned location = static_cast<unsigned>(WKUInt64GetValue(static_cast<WKUInt64Ref>(WKDictionaryGetItemForKey(messageBodyDictionary, locationKey.get()))));
529 WKRetainPtr<WKStringRef> timestampKey = adoptWK(WKStringCreateWithUTF8CString("Timestamp"));
530 double timestamp = WKDoubleGetValue(static_cast<WKDoubleRef>(WKDictionaryGetItemForKey(messageBodyDictionary, timestampKey.get())));
532 // Forward to WebProcess
533 m_eventSenderProxy->keyDown(key, modifiers, location, timestamp);
536 ASSERT_NOT_REACHED();
539 return m_currentInvocation->didReceiveSynchronousMessageFromInjectedBundle(messageName, messageBody);
542 // WKPageLoaderClient
544 void TestController::didFinishLoadForFrame(WKPageRef page, WKFrameRef frame, WKTypeRef, const void* clientInfo)
546 static_cast<TestController*>(const_cast<void*>(clientInfo))->didFinishLoadForFrame(page, frame);
549 void TestController::processDidCrash(WKPageRef page, const void* clientInfo)
551 static_cast<TestController*>(const_cast<void*>(clientInfo))->processDidCrash();
554 void TestController::didFinishLoadForFrame(WKPageRef page, WKFrameRef frame)
556 if (m_state != Resetting)
559 if (!WKFrameIsMainFrame(frame))
562 WKRetainPtr<WKURLRef> wkURL(AdoptWK, WKFrameCopyURL(frame));
563 if (!WKURLIsEqual(wkURL.get(), blankURL()))
566 m_doneResetting = true;
567 shared().notifyDone();
570 void TestController::processDidCrash()
572 // This function can be called multiple times when crash logs are being saved on Windows, so
573 // ensure we only print the crashed message once.
574 if (!m_didPrintWebProcessCrashedMessage) {
575 fputs("#CRASHED - WebProcess\n", stderr);
577 m_didPrintWebProcessCrashedMessage = true;
580 if (m_shouldExitWhenWebProcessCrashes)