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
39namespace WTR {
40
41static const double defaultLongTimeout = 30;
42static const double defaultShortTimeout = 5;
43
44static WKURLRef blankURL()
45{
46    static WKURLRef staticBlankURL = WKURLCreateWithUTF8CString("about:blank");
47    return staticBlankURL;
48}
49
50static TestController* controller;
51
52TestController& TestController::shared()
53{
54    ASSERT(controller);
55    return *controller;
56}
57
58TestController::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
76TestController::~TestController()
77{
78}
79
80static 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
86static 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
92static WKRect getWindowFrameOtherPage(WKPageRef page, const void* clientInfo)
93{
94    PlatformWebView* view = static_cast<PlatformWebView*>(const_cast<void*>(clientInfo));
95    return view->windowFrame();
96}
97
98static 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
104static 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
110static 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
117void TestController::runModal(WKPageRef page, const void* clientInfo)
118{
119    runModal(static_cast<PlatformWebView*>(const_cast<void*>(clientInfo)));
120}
121
122static 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
129WKPageRef 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
183const 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
195void 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
347bool 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
395bool 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
423void 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
439void 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
451void TestController::runUntil(bool& done, TimeoutDuration timeoutDuration)
452{
453    platformRunUntil(done, timeoutDuration == ShortTimeout ? m_shortTimeout : m_longTimeout);
454}
455
456// WKContextInjectedBundleClient
457
458void 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
463void 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
468void TestController::didReceiveMessageFromInjectedBundle(WKStringRef messageName, WKTypeRef messageBody)
469{
470    if (!m_currentInvocation)
471        return;
472    m_currentInvocation->didReceiveMessageFromInjectedBundle(messageName, messageBody);
473}
474
475WKRetainPtr<WKTypeRef> TestController::didReceiveSynchronousMessageFromInjectedBundle(WKStringRef messageName, WKTypeRef messageBody)
476{
477    return m_currentInvocation->didReceiveSynchronousMessageFromInjectedBundle(messageName, messageBody);
478}
479
480// WKPageLoaderClient
481
482void TestController::didFinishLoadForFrame(WKPageRef page, WKFrameRef frame, WKTypeRef, const void* clientInfo)
483{
484    static_cast<TestController*>(const_cast<void*>(clientInfo))->didFinishLoadForFrame(page, frame);
485}
486
487void TestController::processDidCrash(WKPageRef page, const void* clientInfo)
488{
489    static_cast<TestController*>(const_cast<void*>(clientInfo))->processDidCrash();
490}
491
492void 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
508void 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