23a873286061b3a86c769acc40399b3a031c7776
[WebKit.git] / Tools / WebKitTestRunner / TestController.cpp
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 "TestController.h"
27
28 #include "PlatformWebView.h"
29 #include "StringFunctions.h"
30 #include "TestInvocation.h"
31 #include <cstdio>
32 #include <WebKit2/WKPageGroup.h>
33 #include <WebKit2/WKContextPrivate.h>
34 #include <WebKit2/WKPreferencesPrivate.h>
35 #include <WebKit2/WKRetainPtr.h>
36 #include <wtf/PassOwnPtr.h>
37
38 namespace WTR {
39
40 static const double defaultLongTimeout = 30;
41 static const double defaultShortTimeout = 5;
42
43 static WKURLRef blankURL()
44 {
45     static WKURLRef staticBlankURL = WKURLCreateWithUTF8CString("about:blank");
46     return staticBlankURL;
47 }
48
49 static TestController* controller;
50
51 TestController& TestController::shared()
52 {
53     ASSERT(controller);
54     return *controller;
55 }
56
57 TestController::TestController(int argc, const char* argv[])
58     : m_dumpPixels(false)
59     , m_verbose(false)
60     , m_printSeparators(false)
61     , m_usingServerMode(false)
62     , m_state(Initial)
63     , m_doneResetting(false)
64     , m_longTimeout(defaultLongTimeout)
65     , m_shortTimeout(defaultShortTimeout)
66 {
67     initialize(argc, argv);
68     controller = this;
69     run();
70     controller = 0;
71 }
72
73 TestController::~TestController()
74 {
75 }
76
77 static WKRect getWindowFrameMainPage(WKPageRef page, const void* clientInfo)
78 {
79     PlatformWebView* view = static_cast<TestController*>(const_cast<void*>(clientInfo))->mainWebView();
80     return view->windowFrame();
81 }
82
83 static void setWindowFrameMainPage(WKPageRef page, WKRect frame, const void* clientInfo)
84 {
85     PlatformWebView* view = static_cast<TestController*>(const_cast<void*>(clientInfo))->mainWebView();
86     view->setWindowFrame(frame);
87 }
88
89 static WKRect getWindowFrameOtherPage(WKPageRef page, const void* clientInfo)
90 {
91     PlatformWebView* view = static_cast<PlatformWebView*>(const_cast<void*>(clientInfo));
92     return view->windowFrame();
93 }
94
95 static void setWindowFrameOtherPage(WKPageRef page, WKRect frame, const void* clientInfo)
96 {
97     PlatformWebView* view = static_cast<PlatformWebView*>(const_cast<void*>(clientInfo));
98     view->setWindowFrame(frame);
99 }
100
101 static bool runBeforeUnloadConfirmPanel(WKPageRef page, WKStringRef message, WKFrameRef frame, const void *clientInfo)
102 {
103     printf("%s\n", toSTD(message).c_str());
104     return true;
105 }
106
107 void TestController::runModal(WKPageRef page, const void* clientInfo)
108 {
109     runModal(static_cast<PlatformWebView*>(const_cast<void*>(clientInfo)));
110 }
111
112 static void closeOtherPage(WKPageRef page, const void* clientInfo)
113 {
114     WKPageClose(page);
115     const PlatformWebView* view = static_cast<const PlatformWebView*>(clientInfo);
116     delete view;
117 }
118
119 WKPageRef TestController::createOtherPage(WKPageRef oldPage, WKDictionaryRef, WKEventModifiers, WKEventMouseButton, const void*)
120 {
121     PlatformWebView* view = new PlatformWebView(WKPageGetContext(oldPage), WKPageGetPageGroup(oldPage));
122     WKPageRef newPage = view->page();
123
124     view->resizeTo(800, 600);
125
126     WKPageUIClient otherPageUIClient = {
127         0,
128         view,
129         createOtherPage,
130         0, // showPage
131         closeOtherPage,
132         0, // runJavaScriptAlert        
133         0, // runJavaScriptConfirm
134         0, // runJavaScriptPrompt
135         0, // setStatusText
136         0, // mouseDidMoveOverElement
137         0, // missingPluginButtonClicked
138         0, // didNotHandleKeyEvent
139         0, // toolbarsAreVisible
140         0, // setToolbarsAreVisible
141         0, // menuBarIsVisible
142         0, // setMenuBarIsVisible
143         0, // statusBarIsVisible
144         0, // setStatusBarIsVisible
145         0, // isResizable
146         0, // setIsResizable
147         getWindowFrameOtherPage,
148         setWindowFrameOtherPage,
149         runBeforeUnloadConfirmPanel,
150         0, // didDraw
151         0, // pageDidScroll
152         0, // exceededDatabaseQuota
153         0, // runOpenPanel
154         0, // decidePolicyForGeolocationPermissionRequest
155         0, // headerHeight
156         0, // footerHeight
157         0, // drawHeader
158         0, // drawFooter
159         0, // printFrame
160         runModal,
161     };
162     WKPageSetPageUIClient(newPage, &otherPageUIClient);
163
164     WKRetain(newPage);
165     return newPage;
166 }
167
168 const char* TestController::libraryPathForTesting()
169 {
170     // FIXME: This may not be sufficient to prevent interactions/crashes
171     // when running more than one copy of DumpRenderTree.
172     // See https://bugs.webkit.org/show_bug.cgi?id=10906
173     char* dumpRenderTreeTemp = getenv("DUMPRENDERTREE_TEMP");
174     if (dumpRenderTreeTemp)
175         return dumpRenderTreeTemp;
176     return platformLibraryPathForTesting();
177 }
178
179
180 void TestController::initialize(int argc, const char* argv[])
181 {
182     platformInitialize();
183
184     bool printSupportedFeatures = false;
185
186     for (int i = 1; i < argc; ++i) {
187         std::string argument(argv[i]);
188
189         if (argument == "--timeout" && i + 1 < argc) {
190             m_longTimeout = atoi(argv[++i]);
191             // Scale up the short timeout to match.
192             m_shortTimeout = defaultShortTimeout * m_longTimeout / defaultLongTimeout;
193             continue;
194         }
195         if (argument == "--pixel-tests") {
196             m_dumpPixels = true;
197             continue;
198         }
199         if (argument == "--verbose") {
200             m_verbose = true;
201             continue;
202         }
203         if (argument == "--print-supported-features") {
204             printSupportedFeatures = true;
205             break;
206         }
207
208         // Skip any other arguments that begin with '--'.
209         if (argument.length() >= 2 && argument[0] == '-' && argument[1] == '-')
210             continue;
211
212         m_paths.push_back(argument);
213     }
214
215     if (printSupportedFeatures) {
216         // FIXME: On Windows, DumpRenderTree uses this to expose whether it supports 3d
217         // transforms and accelerated compositing. When we support those features, we
218         // should match DRT's behavior.
219         exit(0);
220     }
221
222     m_usingServerMode = (m_paths.size() == 1 && m_paths[0] == "-");
223     if (m_usingServerMode)
224         m_printSeparators = true;
225     else
226         m_printSeparators = m_paths.size() > 1;
227
228     initializeInjectedBundlePath();
229     initializeTestPluginDirectory();
230
231     WKRetainPtr<WKStringRef> pageGroupIdentifier(AdoptWK, WKStringCreateWithUTF8CString("WebKitTestRunnerPageGroup"));
232     m_pageGroup.adopt(WKPageGroupCreateWithIdentifier(pageGroupIdentifier.get()));
233
234     m_context.adopt(WKContextCreateWithInjectedBundlePath(injectedBundlePath()));
235
236     const char* path = libraryPathForTesting();
237     if (path) {
238         Vector<char> databaseDirectory(strlen(path) + strlen("/Databases"));
239         sprintf(databaseDirectory.data(), "%s%s", path, "/Databases");
240         WKRetainPtr<WKStringRef> databaseDirectoryWK(AdoptWK, WKStringCreateWithUTF8CString(databaseDirectory.data()));
241         WKContextSetDatabaseDirectory(m_context.get(), databaseDirectoryWK.get());
242     }
243
244     platformInitializeContext();
245
246     WKContextInjectedBundleClient injectedBundleClient = {
247         0,
248         this,
249         didReceiveMessageFromInjectedBundle,
250         didReceiveSynchronousMessageFromInjectedBundle
251     };
252     WKContextSetInjectedBundleClient(m_context.get(), &injectedBundleClient);
253
254     _WKContextSetAdditionalPluginsDirectory(m_context.get(), testPluginDirectory());
255
256     m_mainWebView = adoptPtr(new PlatformWebView(m_context.get(), m_pageGroup.get()));
257
258     WKPageUIClient pageUIClient = {
259         0,
260         this,
261         createOtherPage,
262         0, // showPage
263         0, // close
264         0, // runJavaScriptAlert        
265         0, // runJavaScriptConfirm
266         0, // runJavaScriptPrompt
267         0, // setStatusText
268         0, // mouseDidMoveOverElement
269         0, // missingPluginButtonClicked
270         0, // didNotHandleKeyEvent
271         0, // toolbarsAreVisible
272         0, // setToolbarsAreVisible
273         0, // menuBarIsVisible
274         0, // setMenuBarIsVisible
275         0, // statusBarIsVisible
276         0, // setStatusBarIsVisible
277         0, // isResizable
278         0, // setIsResizable
279         getWindowFrameMainPage,
280         setWindowFrameMainPage,
281         runBeforeUnloadConfirmPanel,
282         0, // didDraw
283         0, // pageDidScroll
284         0, // exceededDatabaseQuota
285         0, // runOpenPanel
286         0, // decidePolicyForGeolocationPermissionRequest
287         0, // headerHeight
288         0, // footerHeight
289         0, // drawHeader
290         0, // drawFooter
291         0, // printFrame
292         0, // runModal
293     };
294     WKPageSetPageUIClient(m_mainWebView->page(), &pageUIClient);
295
296     WKPageLoaderClient pageLoaderClient = {
297         0,
298         this,
299         0, // didStartProvisionalLoadForFrame
300         0, // didReceiveServerRedirectForProvisionalLoadForFrame
301         0, // didFailProvisionalLoadWithErrorForFrame
302         0, // didCommitLoadForFrame
303         0, // didFinishDocumentLoadForFrame
304         didFinishLoadForFrame,
305         0, // didFailLoadWithErrorForFrame
306         0, // didSameDocumentNavigationForFrame
307         0, // didReceiveTitleForFrame
308         0, // didFirstLayoutForFrame
309         0, // didFirstVisuallyNonEmptyLayoutForFrame
310         0, // didRemoveFrameFromHierarchy
311         0, // didDisplayInsecureContentForFrame
312         0, // didRunInsecureContentForFrame
313         0, // canAuthenticateAgainstProtectionSpaceInFrame
314         0, // didReceiveAuthenticationChallengeInFrame
315         0, // didStartProgress
316         0, // didChangeProgress
317         0, // didFinishProgress
318         0, // didBecomeUnresponsive
319         0, // didBecomeResponsive
320         processDidCrash, // processDidCrash
321         0 // didChangeBackForwardList
322     };
323     WKPageSetPageLoaderClient(m_mainWebView->page(), &pageLoaderClient);
324 }
325
326 bool TestController::resetStateToConsistentValues()
327 {
328     m_state = Resetting;
329
330     WKRetainPtr<WKStringRef> messageName(AdoptWK, WKStringCreateWithUTF8CString("Reset"));
331     WKContextPostMessageToInjectedBundle(TestController::shared().context(), messageName.get(), 0);
332
333     // FIXME: This function should also ensure that there is only one page open.
334
335     // Reset preferences
336     WKPreferencesRef preferences = WKPageGroupGetPreferences(m_pageGroup.get());
337     WKPreferencesSetOfflineWebApplicationCacheEnabled(preferences, true);
338     WKPreferencesSetFontSmoothingLevel(preferences, kWKFontSmoothingLevelNoSubpixelAntiAliasing);
339     WKPreferencesSetXSSAuditorEnabled(preferences, false);
340     WKPreferencesSetDeveloperExtrasEnabled(preferences, true);
341     WKPreferencesSetJavaScriptCanOpenWindowsAutomatically(preferences, true);
342
343     static WKStringRef standardFontFamily = WKStringCreateWithUTF8CString("Times");
344     static WKStringRef cursiveFontFamily = WKStringCreateWithUTF8CString("Apple Chancery");
345     static WKStringRef fantasyFontFamily = WKStringCreateWithUTF8CString("Papyrus");
346     static WKStringRef fixedFontFamily = WKStringCreateWithUTF8CString("Courier");
347     static WKStringRef sansSerifFontFamily = WKStringCreateWithUTF8CString("Helvetica");
348     static WKStringRef serifFontFamily = WKStringCreateWithUTF8CString("Times");
349
350     WKPreferencesSetStandardFontFamily(preferences, standardFontFamily);
351     WKPreferencesSetCursiveFontFamily(preferences, cursiveFontFamily);
352     WKPreferencesSetFantasyFontFamily(preferences, fantasyFontFamily);
353     WKPreferencesSetFixedFontFamily(preferences, fixedFontFamily);
354     WKPreferencesSetSansSerifFontFamily(preferences, sansSerifFontFamily);
355     WKPreferencesSetSerifFontFamily(preferences, serifFontFamily);
356
357     m_mainWebView->focus();
358
359     // Reset main page back to about:blank
360     m_doneResetting = false;
361
362     WKPageLoadURL(m_mainWebView->page(), blankURL());
363     runUntil(m_doneResetting, ShortTimeout);
364     return m_doneResetting;
365 }
366
367 bool TestController::runTest(const char* test)
368 {
369     if (!resetStateToConsistentValues()) {
370         fputs("#CRASHED - WebProcess\n", stderr);
371         fflush(stderr);
372         return false;
373     }
374
375     m_state = RunningTest;
376     m_currentInvocation.set(new TestInvocation(test));
377     m_currentInvocation->invoke();
378     m_currentInvocation.clear();
379
380     return true;
381 }
382
383 void TestController::runTestingServerLoop()
384 {
385     char filenameBuffer[2048];
386     while (fgets(filenameBuffer, sizeof(filenameBuffer), stdin)) {
387         char *newLineCharacter = strchr(filenameBuffer, '\n');
388         if (newLineCharacter)
389             *newLineCharacter = '\0';
390
391         if (strlen(filenameBuffer) == 0)
392             continue;
393
394         if (!runTest(filenameBuffer))
395             break;
396     }
397 }
398
399 void TestController::run()
400 {
401     if (m_usingServerMode)
402         runTestingServerLoop();
403     else {
404         for (size_t i = 0; i < m_paths.size(); ++i) {
405             if (!runTest(m_paths[i].c_str()))
406                 break;
407         }
408     }
409
410 }
411
412 void TestController::runUntil(bool& done, TimeoutDuration timeoutDuration)
413 {
414     platformRunUntil(done, timeoutDuration == ShortTimeout ? m_shortTimeout : m_longTimeout);
415 }
416
417 // WKContextInjectedBundleClient
418
419 void TestController::didReceiveMessageFromInjectedBundle(WKContextRef context, WKStringRef messageName, WKTypeRef messageBody, const void* clientInfo)
420 {
421     static_cast<TestController*>(const_cast<void*>(clientInfo))->didReceiveMessageFromInjectedBundle(messageName, messageBody);
422 }
423
424 void TestController::didReceiveSynchronousMessageFromInjectedBundle(WKContextRef context, WKStringRef messageName, WKTypeRef messageBody, WKTypeRef* returnData, const void* clientInfo)
425 {
426     *returnData = static_cast<TestController*>(const_cast<void*>(clientInfo))->didReceiveSynchronousMessageFromInjectedBundle(messageName, messageBody).leakRef();
427 }
428
429 void TestController::didReceiveMessageFromInjectedBundle(WKStringRef messageName, WKTypeRef messageBody)
430 {
431     if (!m_currentInvocation)
432         return;
433     m_currentInvocation->didReceiveMessageFromInjectedBundle(messageName, messageBody);
434 }
435
436 WKRetainPtr<WKTypeRef> TestController::didReceiveSynchronousMessageFromInjectedBundle(WKStringRef messageName, WKTypeRef messageBody)
437 {
438     return m_currentInvocation->didReceiveSynchronousMessageFromInjectedBundle(messageName, messageBody);
439 }
440
441 // WKPageLoaderClient
442
443 void TestController::didFinishLoadForFrame(WKPageRef page, WKFrameRef frame, WKTypeRef, const void* clientInfo)
444 {
445     static_cast<TestController*>(const_cast<void*>(clientInfo))->didFinishLoadForFrame(page, frame);
446 }
447
448 void TestController::processDidCrash(WKPageRef page, const void* clientInfo)
449 {
450     static_cast<TestController*>(const_cast<void*>(clientInfo))->processDidCrash(page);
451 }
452
453 void TestController::didFinishLoadForFrame(WKPageRef page, WKFrameRef frame)
454 {
455     if (m_state != Resetting)
456         return;
457
458     if (!WKFrameIsMainFrame(frame))
459         return;
460
461     WKRetainPtr<WKURLRef> wkURL(AdoptWK, WKFrameCopyURL(frame));
462     if (!WKURLIsEqual(wkURL.get(), blankURL()))
463         return;
464
465     m_doneResetting = true;
466     shared().notifyDone();
467 }
468
469 void TestController::processDidCrash(WKPageRef page)
470 {
471     fputs("#CRASHED - WebProcess\n", stderr);
472     fflush(stderr);
473 }
474
475 } // namespace WTR