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 44 static WKURLRef blankURL() 45 { 46 static WKURLRef staticBlankURL = WKURLCreateWithUTF8CString("about:blank"); 47 return staticBlankURL; 48 } 49 50 static TestController* controller; 51 52 TestController& TestController::shared() 53 { 54 ASSERT(controller); 55 return *controller; 56 } 57 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 76 TestController::~TestController() 77 { 78 } 79 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 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 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 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 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 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 117 void TestController::runModal(WKPageRef page, const void* clientInfo) 118 { 119 runModal(static_cast<PlatformWebView*>(const_cast<void*>(clientInfo))); 120 } 121 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 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 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 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 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 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 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 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 451 void TestController::runUntil(bool& done, TimeoutDuration timeoutDuration) 452 { 453 platformRunUntil(done, timeoutDuration == ShortTimeout ? m_shortTimeout : m_longTimeout); 454 } 455 456 // WKContextInjectedBundleClient 457 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 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 468 void TestController::didReceiveMessageFromInjectedBundle(WKStringRef messageName, WKTypeRef messageBody) 469 { 470 if (!m_currentInvocation) 471 return; 472 m_currentInvocation->didReceiveMessageFromInjectedBundle(messageName, messageBody); 473 } 474 475 WKRetainPtr<WKTypeRef> TestController::didReceiveSynchronousMessageFromInjectedBundle(WKStringRef messageName, WKTypeRef messageBody) 476 { 477 return m_currentInvocation->didReceiveSynchronousMessageFromInjectedBundle(messageName, messageBody); 478 } 479 480 // WKPageLoaderClient 481 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 487 void TestController::processDidCrash(WKPageRef page, const void* clientInfo) 488 { 489 static_cast<TestController*>(const_cast<void*>(clientInfo))->processDidCrash(); 490 } 491 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 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