fast/borders/border-radius-on-subpixel-position-non-hidpi.html fails on Retina machines
[WebKit-https.git] / Tools / WebKitTestRunner / TestController.cpp
1 /*
2  * Copyright (C) 2010, 2014 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 "EventSenderProxy.h"
30 #include "Options.h"
31 #include "PlatformWebView.h"
32 #include "StringFunctions.h"
33 #include "TestInvocation.h"
34 #include <WebKit/WKAuthenticationChallenge.h>
35 #include <WebKit/WKAuthenticationDecisionListener.h>
36 #include <WebKit/WKContextConfigurationRef.h>
37 #include <WebKit/WKContextPrivate.h>
38 #include <WebKit/WKCredential.h>
39 #include <WebKit/WKIconDatabase.h>
40 #include <WebKit/WKNotification.h>
41 #include <WebKit/WKNotificationManager.h>
42 #include <WebKit/WKNotificationPermissionRequest.h>
43 #include <WebKit/WKNumber.h>
44 #include <WebKit/WKPageGroup.h>
45 #include <WebKit/WKPagePrivate.h>
46 #include <WebKit/WKPreferencesRefPrivate.h>
47 #include <WebKit/WKProtectionSpace.h>
48 #include <WebKit/WKRetainPtr.h>
49 #include <algorithm>
50 #include <cstdio>
51 #include <ctype.h>
52 #include <stdlib.h>
53 #include <string>
54 #include <wtf/text/CString.h>
55
56 #if PLATFORM(COCOA)
57 #include <WebKit/WKPagePrivateMac.h>
58 #endif
59
60 #if !PLATFORM(COCOA)
61 #include <WebKit/WKTextChecker.h>
62 #endif
63
64 namespace WTR {
65
66 const unsigned TestController::viewWidth = 800;
67 const unsigned TestController::viewHeight = 600;
68
69 const unsigned TestController::w3cSVGViewWidth = 480;
70 const unsigned TestController::w3cSVGViewHeight = 360;
71
72 // defaultLongTimeout + defaultShortTimeout should be less than 80,
73 // the default timeout value of the test harness so we can detect an
74 // unresponsive web process.
75 static const double defaultLongTimeout = 60;
76 static const double defaultShortTimeout = 15;
77 static const double defaultNoTimeout = -1;
78
79 static WKURLRef blankURL()
80 {
81     static WKURLRef staticBlankURL = WKURLCreateWithUTF8CString("about:blank");
82     return staticBlankURL;
83 }
84
85 static WKDataRef copyWebCryptoMasterKey(WKContextRef, const void*)
86 {
87     // Any 128 bit key would do, all we need for testing is to implement the callback.
88     return WKDataCreate((const uint8_t*)"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", 16);
89 }
90
91 static TestController* controller;
92
93 TestController& TestController::shared()
94 {
95     ASSERT(controller);
96     return *controller;
97 }
98
99 TestController::TestController(int argc, const char* argv[])
100     : m_verbose(false)
101     , m_printSeparators(false)
102     , m_usingServerMode(false)
103     , m_gcBetweenTests(false)
104     , m_shouldDumpPixelsForAllTests(false)
105     , m_state(Initial)
106     , m_doneResetting(false)
107     , m_longTimeout(defaultLongTimeout)
108     , m_shortTimeout(defaultShortTimeout)
109     , m_noTimeout(defaultNoTimeout)
110     , m_useWaitToDumpWatchdogTimer(true)
111     , m_forceNoTimeout(false)
112     , m_timeout(0)
113     , m_didPrintWebProcessCrashedMessage(false)
114     , m_shouldExitWhenWebProcessCrashes(true)
115     , m_beforeUnloadReturnValue(true)
116     , m_isGeolocationPermissionSet(false)
117     , m_isGeolocationPermissionAllowed(false)
118     , m_policyDelegateEnabled(false)
119     , m_policyDelegatePermissive(false)
120     , m_handlesAuthenticationChallenges(false)
121     , m_shouldBlockAllPlugins(false)
122     , m_forceComplexText(false)
123     , m_shouldUseAcceleratedDrawing(false)
124     , m_shouldUseRemoteLayerTree(false)
125     , m_shouldLogHistoryClientCallbacks(false)
126 {
127     initialize(argc, argv);
128     controller = this;
129     run();
130     controller = 0;
131 }
132
133 TestController::~TestController()
134 {
135     WKIconDatabaseClose(WKContextGetIconDatabase(m_context.get()));
136
137     platformDestroy();
138 }
139
140 static WKRect getWindowFrame(WKPageRef page, const void* clientInfo)
141 {
142     PlatformWebView* view = static_cast<PlatformWebView*>(const_cast<void*>(clientInfo));
143     return view->windowFrame();
144 }
145
146 static void setWindowFrame(WKPageRef page, WKRect frame, const void* clientInfo)
147 {
148     PlatformWebView* view = static_cast<PlatformWebView*>(const_cast<void*>(clientInfo));
149     view->setWindowFrame(frame);
150 }
151
152 static bool runBeforeUnloadConfirmPanel(WKPageRef page, WKStringRef message, WKFrameRef frame, const void*)
153 {
154     printf("CONFIRM NAVIGATION: %s\n", toSTD(message).c_str());
155     return TestController::shared().beforeUnloadReturnValue();
156 }
157
158 void TestController::runModal(WKPageRef page, const void* clientInfo)
159 {
160     PlatformWebView* view = static_cast<PlatformWebView*>(const_cast<void*>(clientInfo));
161     view->setWindowIsKey(false);
162     runModal(view);
163     view->setWindowIsKey(true);
164 }
165
166 static void closeOtherPage(WKPageRef page, const void* clientInfo)
167 {
168     WKPageClose(page);
169     PlatformWebView* view = static_cast<PlatformWebView*>(const_cast<void*>(clientInfo));
170     delete view;
171 }
172
173 static void focus(WKPageRef page, const void* clientInfo)
174 {
175     PlatformWebView* view = static_cast<PlatformWebView*>(const_cast<void*>(clientInfo));
176     view->focus();
177     view->setWindowIsKey(true);
178 }
179
180 static void unfocus(WKPageRef page, const void* clientInfo)
181 {
182     PlatformWebView* view = static_cast<PlatformWebView*>(const_cast<void*>(clientInfo));
183     view->setWindowIsKey(false);
184 }
185
186 static void decidePolicyForGeolocationPermissionRequest(WKPageRef, WKFrameRef, WKSecurityOriginRef, WKGeolocationPermissionRequestRef permissionRequest, const void* clientInfo)
187 {
188     TestController::shared().handleGeolocationPermissionRequest(permissionRequest);
189 }
190
191 int TestController::getCustomTimeout()
192 {
193     return m_timeout;
194 }
195
196 WKPageRef TestController::createOtherPage(WKPageRef oldPage, WKURLRequestRef, WKDictionaryRef, WKEventModifiers, WKEventMouseButton, const void* clientInfo)
197 {
198     PlatformWebView* parentView = static_cast<PlatformWebView*>(const_cast<void*>(clientInfo));
199
200     PlatformWebView* view = new PlatformWebView(WKPageGetContext(oldPage), WKPageGetPageGroup(oldPage), oldPage, parentView->options());
201     WKPageRef newPage = view->page();
202
203     view->resizeTo(800, 600);
204
205     WKPageUIClientV2 otherPageUIClient = {
206         { 2, view },
207         0, // createNewPage_deprecatedForUseWithV0
208         0, // showPage
209         closeOtherPage,
210         0, // takeFocus
211         focus,
212         unfocus,
213         0, // runJavaScriptAlert
214         0, // runJavaScriptConfirm
215         0, // runJavaScriptPrompt
216         0, // setStatusText
217         0, // mouseDidMoveOverElement_deprecatedForUseWithV0
218         0, // missingPluginButtonClicked
219         0, // didNotHandleKeyEvent
220         0, // didNotHandleWheelEvent
221         0, // toolbarsAreVisible
222         0, // setToolbarsAreVisible
223         0, // menuBarIsVisible
224         0, // setMenuBarIsVisible
225         0, // statusBarIsVisible
226         0, // setStatusBarIsVisible
227         0, // isResizable
228         0, // setIsResizable
229         getWindowFrame,
230         setWindowFrame,
231         runBeforeUnloadConfirmPanel,
232         0, // didDraw
233         0, // pageDidScroll
234         0, // exceededDatabaseQuota
235         0, // runOpenPanel
236         decidePolicyForGeolocationPermissionRequest,
237         0, // headerHeight
238         0, // footerHeight
239         0, // drawHeader
240         0, // drawFooter
241         0, // printFrame
242         runModal,
243         0, // didCompleteRubberBandForMainFrame
244         0, // saveDataToFileInDownloadsFolder
245         0, // shouldInterruptJavaScript
246         createOtherPage,
247         0, // mouseDidMoveOverElement
248         0, // decidePolicyForNotificationPermissionRequest
249         0, // unavailablePluginButtonClicked_deprecatedForUseWithV1
250         0, // showColorPicker
251         0, // hideColorPicker
252         0, // unavailablePluginButtonClicked
253     };
254     WKPageSetPageUIClient(newPage, &otherPageUIClient.base);
255
256     view->didInitializeClients();
257
258     WKRetain(newPage);
259     return newPage;
260 }
261
262 const char* TestController::libraryPathForTesting()
263 {
264     // FIXME: This may not be sufficient to prevent interactions/crashes
265     // when running more than one copy of DumpRenderTree.
266     // See https://bugs.webkit.org/show_bug.cgi?id=10906
267     char* dumpRenderTreeTemp = getenv("DUMPRENDERTREE_TEMP");
268     if (dumpRenderTreeTemp)
269         return dumpRenderTreeTemp;
270     return platformLibraryPathForTesting();
271 }
272
273
274 void TestController::initialize(int argc, const char* argv[])
275 {
276     platformInitialize();
277
278     Options options(defaultLongTimeout, defaultShortTimeout);
279     OptionsHandler optionsHandler(options);
280
281     if (argc < 2) {
282         optionsHandler.printHelp();
283         exit(1);
284     }
285     if (!optionsHandler.parse(argc, argv))
286         exit(1);
287
288     m_longTimeout = options.longTimeout;
289     m_shortTimeout = options.shortTimeout;
290     m_useWaitToDumpWatchdogTimer = options.useWaitToDumpWatchdogTimer;
291     m_forceNoTimeout = options.forceNoTimeout;
292     m_verbose = options.verbose;
293     m_gcBetweenTests = options.gcBetweenTests;
294     m_shouldDumpPixelsForAllTests = options.shouldDumpPixelsForAllTests;
295     m_forceComplexText = options.forceComplexText;
296     m_shouldUseAcceleratedDrawing = options.shouldUseAcceleratedDrawing;
297     m_shouldUseRemoteLayerTree = options.shouldUseRemoteLayerTree;
298     m_paths = options.paths;
299
300     if (options.printSupportedFeatures) {
301         // FIXME: On Windows, DumpRenderTree uses this to expose whether it supports 3d
302         // transforms and accelerated compositing. When we support those features, we
303         // should match DRT's behavior.
304         exit(0);
305     }
306
307     m_usingServerMode = (m_paths.size() == 1 && m_paths[0] == "-");
308     if (m_usingServerMode)
309         m_printSeparators = true;
310     else
311         m_printSeparators = m_paths.size() > 1;
312
313     initializeInjectedBundlePath();
314     initializeTestPluginDirectory();
315
316     WKRetainPtr<WKStringRef> pageGroupIdentifier(AdoptWK, WKStringCreateWithUTF8CString("WebKitTestRunnerPageGroup"));
317     m_pageGroup.adopt(WKPageGroupCreateWithIdentifier(pageGroupIdentifier.get()));
318
319     auto configuration = adoptWK(WKContextConfigurationCreate());
320     WKContextConfigurationSetInjectedBundlePath(configuration.get(), injectedBundlePath());
321
322     if (const char* dumpRenderTreeTemp = libraryPathForTesting()) {
323         String temporaryFolder = String::fromUTF8(dumpRenderTreeTemp);
324
325         const char separator = '/';
326
327         WKContextConfigurationSetIndexedDBDatabaseDirectory(configuration.get(), toWK(temporaryFolder + separator + "Databases" + separator + "IndexedDB").get());
328         WKContextConfigurationSetLocalStorageDirectory(configuration.get(), toWK(temporaryFolder + separator + "LocalStorage").get());
329         WKContextConfigurationSetWebSQLDatabaseDirectory(configuration.get(), toWK(temporaryFolder + separator + "Databases" + separator + "WebSQL").get());
330     }
331
332     m_context = adoptWK(WKContextCreateWithConfiguration(configuration.get()));
333     m_geolocationProvider = std::make_unique<GeolocationProviderMock>(m_context.get());
334
335 #if PLATFORM(IOS) || (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED > 1080)
336     WKContextSetUsesNetworkProcess(m_context.get(), true);
337     WKContextSetProcessModel(m_context.get(), kWKProcessModelMultipleSecondaryProcesses);
338 #endif
339
340     if (const char* dumpRenderTreeTemp = libraryPathForTesting()) {
341         String temporaryFolder = String::fromUTF8(dumpRenderTreeTemp);
342
343         const char separator = '/';
344
345         // FIXME: These should be migrated to WKContextConfigurationRef.
346         WKContextSetApplicationCacheDirectory(m_context.get(), toWK(temporaryFolder + separator + "ApplicationCache").get());
347         WKContextSetDiskCacheDirectory(m_context.get(), toWK(temporaryFolder + separator + "Cache").get());
348         WKContextSetCookieStorageDirectory(m_context.get(), toWK(temporaryFolder + separator + "Cookies").get());
349         WKContextSetIconDatabasePath(m_context.get(), toWK(temporaryFolder + separator + "IconDatabase" + separator + "WebpageIcons.db").get());
350     }
351
352     WKContextUseTestingNetworkSession(m_context.get());
353     WKContextSetCacheModel(m_context.get(), kWKCacheModelDocumentBrowser);
354
355     platformInitializeContext();
356
357     WKContextClientV1 contextClient = {
358         { 1, this },
359         nullptr, // plugInAutoStartOriginHashesChanged
360         nullptr, // networkProcessDidCrash,
361         nullptr, // plugInInformationBecameAvailable,
362         copyWebCryptoMasterKey
363     };
364     WKContextSetClient(m_context.get(), &contextClient.base);
365
366     WKContextInjectedBundleClientV1 injectedBundleClient = {
367         { 1, this },
368         didReceiveMessageFromInjectedBundle,
369         didReceiveSynchronousMessageFromInjectedBundle,
370         0 // getInjectedBundleInitializationUserData
371     };
372     WKContextSetInjectedBundleClient(m_context.get(), &injectedBundleClient.base);
373
374     WKContextHistoryClientV0 historyClient = {
375         { 0, this },
376         didNavigateWithNavigationData,
377         didPerformClientRedirect,
378         didPerformServerRedirect,
379         didUpdateHistoryTitle,
380         0, // populateVisitedLinks
381     };
382     WKContextSetHistoryClient(m_context.get(), &historyClient.base);
383
384     WKNotificationManagerRef notificationManager = WKContextGetNotificationManager(m_context.get());
385     WKNotificationProviderV0 notificationKit = m_webNotificationProvider.provider();
386     WKNotificationManagerSetProvider(notificationManager, &notificationKit.base);
387
388     if (testPluginDirectory())
389         WKContextSetAdditionalPluginsDirectory(m_context.get(), testPluginDirectory());
390
391     if (m_forceComplexText)
392         WKContextSetAlwaysUsesComplexTextCodePath(m_context.get(), true);
393
394     // Some preferences (notably mock scroll bars setting) currently cannot be re-applied to an existing view, so we need to set them now.
395     resetPreferencesToConsistentValues();
396
397     WKRetainPtr<WKMutableDictionaryRef> viewOptions;
398     if (m_shouldUseRemoteLayerTree) {
399         viewOptions = adoptWK(WKMutableDictionaryCreate());
400         WKRetainPtr<WKStringRef> useRemoteLayerTreeKey = adoptWK(WKStringCreateWithUTF8CString("RemoteLayerTree"));
401         WKRetainPtr<WKBooleanRef> useRemoteLayerTreeValue = adoptWK(WKBooleanCreate(m_shouldUseRemoteLayerTree));
402         WKDictionarySetItem(viewOptions.get(), useRemoteLayerTreeKey.get(), useRemoteLayerTreeValue.get());
403     }
404
405     createWebViewWithOptions(viewOptions.get());
406 }
407
408 void TestController::createWebViewWithOptions(WKDictionaryRef options)
409 {
410     m_mainWebView = std::make_unique<PlatformWebView>(m_context.get(), m_pageGroup.get(), nullptr, options);
411     WKPageUIClientV2 pageUIClient = {
412         { 2, m_mainWebView.get() },
413         0, // createNewPage_deprecatedForUseWithV0
414         0, // showPage
415         0, // close
416         0, // takeFocus
417         focus,
418         unfocus,
419         0, // runJavaScriptAlert
420         0, // runJavaScriptConfirm
421         0, // runJavaScriptPrompt
422         0, // setStatusText
423         0, // mouseDidMoveOverElement_deprecatedForUseWithV0
424         0, // missingPluginButtonClicked
425         0, // didNotHandleKeyEvent
426         0, // didNotHandleWheelEvent
427         0, // toolbarsAreVisible
428         0, // setToolbarsAreVisible
429         0, // menuBarIsVisible
430         0, // setMenuBarIsVisible
431         0, // statusBarIsVisible
432         0, // setStatusBarIsVisible
433         0, // isResizable
434         0, // setIsResizable
435         getWindowFrame,
436         setWindowFrame,
437         runBeforeUnloadConfirmPanel,
438         0, // didDraw
439         0, // pageDidScroll
440         0, // exceededDatabaseQuota,
441         0, // runOpenPanel
442         decidePolicyForGeolocationPermissionRequest,
443         0, // headerHeight
444         0, // footerHeight
445         0, // drawHeader
446         0, // drawFooter
447         0, // printFrame
448         runModal,
449         0, // didCompleteRubberBandForMainFrame
450         0, // saveDataToFileInDownloadsFolder
451         0, // shouldInterruptJavaScript
452         createOtherPage,
453         0, // mouseDidMoveOverElement
454         decidePolicyForNotificationPermissionRequest, // decidePolicyForNotificationPermissionRequest
455         0, // unavailablePluginButtonClicked_deprecatedForUseWithV1
456         0, // showColorPicker
457         0, // hideColorPicker
458         unavailablePluginButtonClicked,
459     };
460     WKPageSetPageUIClient(m_mainWebView->page(), &pageUIClient.base);
461
462     WKPageLoaderClientV5 pageLoaderClient = {
463         { 5, this },
464         0, // didStartProvisionalLoadForFrame
465         0, // didReceiveServerRedirectForProvisionalLoadForFrame
466         0, // didFailProvisionalLoadWithErrorForFrame
467         didCommitLoadForFrame,
468         0, // didFinishDocumentLoadForFrame
469         didFinishLoadForFrame,
470         0, // didFailLoadWithErrorForFrame
471         0, // didSameDocumentNavigationForFrame
472         0, // didReceiveTitleForFrame
473         0, // didFirstLayoutForFrame
474         0, // didFirstVisuallyNonEmptyLayoutForFrame
475         0, // didRemoveFrameFromHierarchy
476         0, // didFailToInitializePlugin
477         0, // didDisplayInsecureContentForFrame
478         canAuthenticateAgainstProtectionSpaceInFrame,
479         didReceiveAuthenticationChallengeInFrame,
480         0, // didStartProgress
481         0, // didChangeProgress
482         0, // didFinishProgress
483         0, // didBecomeUnresponsive
484         0, // didBecomeResponsive
485         processDidCrash,
486         0, // didChangeBackForwardList
487         0, // shouldGoToBackForwardListItem
488         0, // didRunInsecureContentForFrame
489         0, // didDetectXSSForFrame
490         0, // didNewFirstVisuallyNonEmptyLayout_unavailable
491         0, // willGoToBackForwardListItem
492         0, // interactionOccurredWhileProcessUnresponsive
493         0, // pluginDidFail_deprecatedForUseWithV1
494         0, // didReceiveIntentForFrame
495         0, // registerIntentServiceForFrame
496         0, // didLayout
497         0, // pluginLoadPolicy_deprecatedForUseWithV2
498         0, // pluginDidFail
499         pluginLoadPolicy, // pluginLoadPolicy
500         0, // webGLLoadPolicy
501         0, // resolveWebGLLoadPolicy
502         0, // shouldKeepCurrentBackForwardListItemInList
503     };
504     WKPageSetPageLoaderClient(m_mainWebView->page(), &pageLoaderClient.base);
505
506     WKPagePolicyClientV1 pagePolicyClient = {
507         { 1, this },
508         0, // decidePolicyForNavigationAction_deprecatedForUseWithV0
509         0, // decidePolicyForNewWindowAction
510         0, // decidePolicyForResponse_deprecatedForUseWithV0
511         0, // unableToImplementPolicy
512         decidePolicyForNavigationAction,
513         decidePolicyForResponse,
514     };
515     WKPageSetPagePolicyClient(m_mainWebView->page(), &pagePolicyClient.base);
516
517     m_mainWebView->didInitializeClients();
518 }
519
520 void TestController::ensureViewSupportsOptions(WKDictionaryRef options)
521 {
522     if (m_mainWebView && !m_mainWebView->viewSupportsOptions(options)) {
523         WKPageSetPageUIClient(m_mainWebView->page(), 0);
524         WKPageSetPageLoaderClient(m_mainWebView->page(), 0);
525         WKPageSetPagePolicyClient(m_mainWebView->page(), 0);
526         WKPageClose(m_mainWebView->page());
527         
528         m_mainWebView = nullptr;
529
530         createWebViewWithOptions(options);
531         resetStateToConsistentValues();
532     }
533 }
534
535 void TestController::resetPreferencesToConsistentValues()
536 {
537     // Reset preferences
538     WKPreferencesRef preferences = WKPageGroupGetPreferences(m_pageGroup.get());
539     WKPreferencesResetTestRunnerOverrides(preferences);
540     WKPreferencesSetOfflineWebApplicationCacheEnabled(preferences, true);
541     WKPreferencesSetFontSmoothingLevel(preferences, kWKFontSmoothingLevelNoSubpixelAntiAliasing);
542     WKPreferencesSetXSSAuditorEnabled(preferences, false);
543     WKPreferencesSetWebAudioEnabled(preferences, true);
544     WKPreferencesSetDeveloperExtrasEnabled(preferences, true);
545     WKPreferencesSetJavaScriptExperimentsEnabled(preferences, true);
546     WKPreferencesSetJavaScriptCanOpenWindowsAutomatically(preferences, true);
547     WKPreferencesSetJavaScriptCanAccessClipboard(preferences, true);
548     WKPreferencesSetDOMPasteAllowed(preferences, true);
549     WKPreferencesSetUniversalAccessFromFileURLsAllowed(preferences, true);
550     WKPreferencesSetFileAccessFromFileURLsAllowed(preferences, true);
551 #if ENABLE(FULLSCREEN_API)
552     WKPreferencesSetFullScreenEnabled(preferences, true);
553 #endif
554     WKPreferencesSetPageCacheEnabled(preferences, false);
555     WKPreferencesSetAsynchronousPluginInitializationEnabled(preferences, false);
556     WKPreferencesSetAsynchronousPluginInitializationEnabledForAllPlugins(preferences, false);
557     WKPreferencesSetArtificialPluginInitializationDelayEnabled(preferences, false);
558     WKPreferencesSetTabToLinksEnabled(preferences, false);
559     WKPreferencesSetInteractiveFormValidationEnabled(preferences, true);
560     WKPreferencesSetMockScrollbarsEnabled(preferences, true);
561
562     static WKStringRef standardFontFamily = WKStringCreateWithUTF8CString("Times");
563     static WKStringRef cursiveFontFamily = WKStringCreateWithUTF8CString("Apple Chancery");
564     static WKStringRef fantasyFontFamily = WKStringCreateWithUTF8CString("Papyrus");
565     static WKStringRef fixedFontFamily = WKStringCreateWithUTF8CString("Courier");
566     static WKStringRef pictographFontFamily = WKStringCreateWithUTF8CString("Apple Color Emoji");
567     static WKStringRef sansSerifFontFamily = WKStringCreateWithUTF8CString("Helvetica");
568     static WKStringRef serifFontFamily = WKStringCreateWithUTF8CString("Times");
569
570     WKPreferencesSetStandardFontFamily(preferences, standardFontFamily);
571     WKPreferencesSetCursiveFontFamily(preferences, cursiveFontFamily);
572     WKPreferencesSetFantasyFontFamily(preferences, fantasyFontFamily);
573     WKPreferencesSetFixedFontFamily(preferences, fixedFontFamily);
574     WKPreferencesSetPictographFontFamily(preferences, pictographFontFamily);
575     WKPreferencesSetSansSerifFontFamily(preferences, sansSerifFontFamily);
576     WKPreferencesSetSerifFontFamily(preferences, serifFontFamily);
577     WKPreferencesSetScreenFontSubstitutionEnabled(preferences, true);
578     WKPreferencesSetAsynchronousSpellCheckingEnabled(preferences, false);
579 #if ENABLE(WEB_AUDIO)
580     WKPreferencesSetMediaSourceEnabled(preferences, true);
581 #endif
582
583     WKPreferencesSetAcceleratedDrawingEnabled(preferences, m_shouldUseAcceleratedDrawing);
584 }
585
586 bool TestController::resetStateToConsistentValues()
587 {
588     m_state = Resetting;
589
590     m_beforeUnloadReturnValue = true;
591
592     WKRetainPtr<WKStringRef> messageName = adoptWK(WKStringCreateWithUTF8CString("Reset"));
593     WKRetainPtr<WKMutableDictionaryRef> resetMessageBody = adoptWK(WKMutableDictionaryCreate());
594
595     WKRetainPtr<WKStringRef> shouldGCKey = adoptWK(WKStringCreateWithUTF8CString("ShouldGC"));
596     WKRetainPtr<WKBooleanRef> shouldGCValue = adoptWK(WKBooleanCreate(m_gcBetweenTests));
597     WKDictionarySetItem(resetMessageBody.get(), shouldGCKey.get(), shouldGCValue.get());
598
599     WKContextPostMessageToInjectedBundle(TestController::shared().context(), messageName.get(), resetMessageBody.get());
600
601     WKContextSetShouldUseFontSmoothing(TestController::shared().context(), false);
602
603     WKContextSetCacheModel(TestController::shared().context(), kWKCacheModelDocumentBrowser);
604
605     // FIXME: This function should also ensure that there is only one page open.
606
607     // Reset the EventSender for each test.
608     m_eventSenderProxy = std::make_unique<EventSenderProxy>(this);
609
610     // FIXME: Is this needed? Nothing in TestController changes preferences during tests, and if there is
611     // some other code doing this, it should probably be responsible for cleanup too.
612     resetPreferencesToConsistentValues();
613
614 #if !PLATFORM(COCOA)
615     WKTextCheckerContinuousSpellCheckingEnabledStateChanged(true);
616 #endif
617
618     // in the case that a test using the chrome input field failed, be sure to clean up for the next test
619     m_mainWebView->removeChromeInputField();
620     m_mainWebView->focus();
621
622     // Re-set to the default backing scale factor by setting the custom scale factor to 0.
623     WKPageSetCustomBackingScaleFactor(m_mainWebView->page(), 0);
624
625 #if PLATFORM(EFL)
626     // EFL use a real window while other ports such as Qt don't.
627     // In EFL, we need to resize the window to the original size after calls to window.resizeTo.
628     WKRect rect = m_mainWebView->windowFrame();
629     m_mainWebView->setWindowFrame(WKRectMake(rect.origin.x, rect.origin.y, TestController::viewWidth, TestController::viewHeight));
630 #endif
631
632     // Reset notification permissions
633     m_webNotificationProvider.reset();
634
635     // Reset Geolocation permissions.
636     m_geolocationPermissionRequests.clear();
637     m_isGeolocationPermissionSet = false;
638     m_isGeolocationPermissionAllowed = false;
639
640     // Reset Custom Policy Delegate.
641     setCustomPolicyDelegate(false, false);
642
643     m_workQueueManager.clearWorkQueue();
644
645     m_handlesAuthenticationChallenges = false;
646     m_authenticationUsername = String();
647     m_authenticationPassword = String();
648
649     m_shouldBlockAllPlugins = false;
650
651     m_shouldLogHistoryClientCallbacks = false;
652
653     // Reset main page back to about:blank
654     m_doneResetting = false;
655
656     WKPageLoadURL(m_mainWebView->page(), blankURL());
657     runUntil(m_doneResetting, ShortTimeout);
658     return m_doneResetting;
659 }
660
661 void TestController::terminateWebContentProcess()
662 {
663     WKPageTerminate(m_mainWebView->page());
664 }
665
666 void TestController::updateWebViewSizeForTest(const TestInvocation& test)
667 {
668     bool isSVGW3CTest = strstr(test.pathOrURL(), "svg/W3C-SVG-1.1") || strstr(test.pathOrURL(), "svg\\W3C-SVG-1.1");
669
670     unsigned width = viewWidth;
671     unsigned height = viewHeight;
672     if (isSVGW3CTest) {
673         width = w3cSVGViewWidth;
674         height = w3cSVGViewHeight;
675     }
676
677     mainWebView()->resizeTo(width, height);
678 }
679
680 void TestController::updateWindowScaleForTest(const TestInvocation& test)
681 {
682     WTF::String localPathOrUrl = String(test.pathOrURL());
683     bool needsHighDPIWindow = localPathOrUrl.findIgnoringCase("/hidpi-") != notFound;
684     mainWebView()->changeWindowScaleIfNeeded(needsHighDPIWindow ? 2 : 1);
685 }
686
687 // FIXME: move into relevant platformConfigureViewForTest()?
688 static bool shouldUseFixedLayout(const char* pathOrURL)
689 {
690 #if ENABLE(CSS_DEVICE_ADAPTATION)
691     if (strstr(pathOrURL, "device-adapt/") || strstr(pathOrURL, "device-adapt\\"))
692         return true;
693 #endif
694
695 #if USE(TILED_BACKING_STORE) && PLATFORM(EFL)
696     if (strstr(pathOrURL, "sticky/") || strstr(pathOrURL, "sticky\\"))
697         return true;
698 #endif
699     return false;
700
701     UNUSED_PARAM(pathOrURL);
702 }
703
704 void TestController::updateLayoutTypeForTest(const TestInvocation& test)
705 {
706     auto viewOptions = adoptWK(WKMutableDictionaryCreate());
707     auto useFixedLayoutKey = adoptWK(WKStringCreateWithUTF8CString("UseFixedLayout"));
708     auto useFixedLayoutValue = adoptWK(WKBooleanCreate(shouldUseFixedLayout(test.pathOrURL())));
709     WKDictionarySetItem(viewOptions.get(), useFixedLayoutKey.get(), useFixedLayoutValue.get());
710
711     ensureViewSupportsOptions(viewOptions.get());
712 }
713
714 #if !PLATFORM(COCOA)
715 void TestController::platformConfigureViewForTest(const TestInvocation&)
716 {
717 }
718 #endif
719
720 void TestController::configureViewForTest(const TestInvocation& test)
721 {
722     updateWebViewSizeForTest(test);
723     updateWindowScaleForTest(test);
724     updateLayoutTypeForTest(test);
725
726     platformConfigureViewForTest(test);
727 }
728
729 struct TestCommand {
730     TestCommand() : shouldDumpPixels(false), timeout(0) { }
731
732     std::string pathOrURL;
733     bool shouldDumpPixels;
734     std::string expectedPixelHash;
735     int timeout;
736 };
737
738 class CommandTokenizer {
739 public:
740     explicit CommandTokenizer(const std::string& input)
741         : m_input(input)
742         , m_posNextSeparator(0)
743     {
744         pump();
745     }
746
747     bool hasNext() const;
748     std::string next();
749
750 private:
751     void pump();
752     static const char kSeparator = '\'';
753     const std::string& m_input;
754     std::string m_next;
755     size_t m_posNextSeparator;
756 };
757
758 void CommandTokenizer::pump()
759 {
760     if (m_posNextSeparator == std::string::npos || m_posNextSeparator == m_input.size()) {
761         m_next = std::string();
762         return;
763     }
764     size_t start = m_posNextSeparator ? m_posNextSeparator + 1 : 0;
765     m_posNextSeparator = m_input.find(kSeparator, start);
766     size_t size = m_posNextSeparator == std::string::npos ? std::string::npos : m_posNextSeparator - start;
767     m_next = std::string(m_input, start, size);
768 }
769
770 std::string CommandTokenizer::next()
771 {
772     ASSERT(hasNext());
773
774     std::string oldNext = m_next;
775     pump();
776     return oldNext;
777 }
778
779 bool CommandTokenizer::hasNext() const
780 {
781     return !m_next.empty();
782 }
783
784 NO_RETURN static void die(const std::string& inputLine)
785 {
786     fprintf(stderr, "Unexpected input line: %s\n", inputLine.c_str());
787     exit(1);
788 }
789
790 TestCommand parseInputLine(const std::string& inputLine)
791 {
792     TestCommand result;
793     CommandTokenizer tokenizer(inputLine);
794     if (!tokenizer.hasNext())
795         die(inputLine);
796
797     std::string arg = tokenizer.next();
798     result.pathOrURL = arg;
799     while (tokenizer.hasNext()) {
800         arg = tokenizer.next();
801         if (arg == std::string("--timeout")) {
802             std::string timeoutToken = tokenizer.next();
803             result.timeout = atoi(timeoutToken.c_str());
804         } else if (arg == std::string("-p") || arg == std::string("--pixel-test")) {
805             result.shouldDumpPixels = true;
806             if (tokenizer.hasNext())
807                 result.expectedPixelHash = tokenizer.next();
808         } else
809             die(inputLine);
810     }
811     return result;
812 }
813
814 bool TestController::runTest(const char* inputLine)
815 {
816     TestCommand command = parseInputLine(std::string(inputLine));
817
818     m_state = RunningTest;
819
820     m_currentInvocation = std::make_unique<TestInvocation>(command.pathOrURL);
821     if (command.shouldDumpPixels || m_shouldDumpPixelsForAllTests)
822         m_currentInvocation->setIsPixelTest(command.expectedPixelHash);
823     if (command.timeout > 0)
824         m_currentInvocation->setCustomTimeout(command.timeout);
825
826     platformWillRunTest(*m_currentInvocation);
827
828     m_currentInvocation->invoke();
829     m_currentInvocation = nullptr;
830
831     return true;
832 }
833
834 void TestController::runTestingServerLoop()
835 {
836     char filenameBuffer[2048];
837     while (fgets(filenameBuffer, sizeof(filenameBuffer), stdin)) {
838         char* newLineCharacter = strchr(filenameBuffer, '\n');
839         if (newLineCharacter)
840             *newLineCharacter = '\0';
841
842         if (strlen(filenameBuffer) == 0)
843             continue;
844
845         if (!runTest(filenameBuffer))
846             break;
847     }
848 }
849
850 void TestController::run()
851 {
852     if (!resetStateToConsistentValues()) {
853         TestInvocation::dumpWebProcessUnresponsiveness("<unknown> - TestController::run - Failed to reset state to consistent values\n");
854         return;
855     }
856
857     if (m_usingServerMode)
858         runTestingServerLoop();
859     else {
860         for (size_t i = 0; i < m_paths.size(); ++i) {
861             if (!runTest(m_paths[i].c_str()))
862                 break;
863         }
864     }
865 }
866
867 void TestController::runUntil(bool& done, TimeoutDuration timeoutDuration)
868 {
869     double timeout = m_noTimeout;
870     if (!m_forceNoTimeout) {
871         switch (timeoutDuration) {
872         case ShortTimeout:
873             timeout = m_shortTimeout;
874             break;
875         case LongTimeout:
876             timeout = m_longTimeout;
877             break;
878         case CustomTimeout:
879             timeout = m_timeout;
880             break;
881         case NoTimeout:
882         default:
883             timeout = m_noTimeout;
884             break;
885         }
886     }
887
888     platformRunUntil(done, timeout);
889 }
890
891 // WKContextInjectedBundleClient
892
893 void TestController::didReceiveMessageFromInjectedBundle(WKContextRef context, WKStringRef messageName, WKTypeRef messageBody, const void* clientInfo)
894 {
895     static_cast<TestController*>(const_cast<void*>(clientInfo))->didReceiveMessageFromInjectedBundle(messageName, messageBody);
896 }
897
898 void TestController::didReceiveSynchronousMessageFromInjectedBundle(WKContextRef context, WKStringRef messageName, WKTypeRef messageBody, WKTypeRef* returnData, const void* clientInfo)
899 {
900     *returnData = static_cast<TestController*>(const_cast<void*>(clientInfo))->didReceiveSynchronousMessageFromInjectedBundle(messageName, messageBody).leakRef();
901 }
902
903 void TestController::didReceiveKeyDownMessageFromInjectedBundle(WKDictionaryRef messageBodyDictionary, bool synchronous)
904 {
905     WKRetainPtr<WKStringRef> keyKey = adoptWK(WKStringCreateWithUTF8CString("Key"));
906     WKStringRef key = static_cast<WKStringRef>(WKDictionaryGetItemForKey(messageBodyDictionary, keyKey.get()));
907
908     WKRetainPtr<WKStringRef> modifiersKey = adoptWK(WKStringCreateWithUTF8CString("Modifiers"));
909     WKEventModifiers modifiers = static_cast<WKEventModifiers>(WKUInt64GetValue(static_cast<WKUInt64Ref>(WKDictionaryGetItemForKey(messageBodyDictionary, modifiersKey.get()))));
910
911     WKRetainPtr<WKStringRef> locationKey = adoptWK(WKStringCreateWithUTF8CString("Location"));
912     unsigned location = static_cast<unsigned>(WKUInt64GetValue(static_cast<WKUInt64Ref>(WKDictionaryGetItemForKey(messageBodyDictionary, locationKey.get()))));
913
914     if (synchronous)
915         WKPageSetShouldSendEventsSynchronously(mainWebView()->page(), true);
916
917     m_eventSenderProxy->keyDown(key, modifiers, location);
918
919     if (synchronous)
920         WKPageSetShouldSendEventsSynchronously(mainWebView()->page(), false);
921 }
922
923 void TestController::didReceiveMessageFromInjectedBundle(WKStringRef messageName, WKTypeRef messageBody)
924 {
925     if (WKStringIsEqualToUTF8CString(messageName, "EventSender")) {
926         ASSERT(WKGetTypeID(messageBody) == WKDictionaryGetTypeID());
927         WKDictionaryRef messageBodyDictionary = static_cast<WKDictionaryRef>(messageBody);
928
929         WKRetainPtr<WKStringRef> subMessageKey(AdoptWK, WKStringCreateWithUTF8CString("SubMessage"));
930         WKStringRef subMessageName = static_cast<WKStringRef>(WKDictionaryGetItemForKey(messageBodyDictionary, subMessageKey.get()));
931
932         if (WKStringIsEqualToUTF8CString(subMessageName, "MouseDown") || WKStringIsEqualToUTF8CString(subMessageName, "MouseUp")) {
933             WKRetainPtr<WKStringRef> buttonKey = adoptWK(WKStringCreateWithUTF8CString("Button"));
934             unsigned button = static_cast<unsigned>(WKUInt64GetValue(static_cast<WKUInt64Ref>(WKDictionaryGetItemForKey(messageBodyDictionary, buttonKey.get()))));
935
936             WKRetainPtr<WKStringRef> modifiersKey = adoptWK(WKStringCreateWithUTF8CString("Modifiers"));
937             WKEventModifiers modifiers = static_cast<WKEventModifiers>(WKUInt64GetValue(static_cast<WKUInt64Ref>(WKDictionaryGetItemForKey(messageBodyDictionary, modifiersKey.get()))));
938
939             // Forward to WebProcess
940             WKPageSetShouldSendEventsSynchronously(mainWebView()->page(), false);
941             if (WKStringIsEqualToUTF8CString(subMessageName, "MouseDown"))
942                 m_eventSenderProxy->mouseDown(button, modifiers);
943             else
944                 m_eventSenderProxy->mouseUp(button, modifiers);
945
946             return;
947         }
948
949         if (WKStringIsEqualToUTF8CString(subMessageName, "KeyDown")) {
950             didReceiveKeyDownMessageFromInjectedBundle(messageBodyDictionary, false);
951
952             return;
953         }
954
955         if (WKStringIsEqualToUTF8CString(subMessageName, "MouseScrollByWithWheelAndMomentumPhases")) {
956             WKRetainPtr<WKStringRef> xKey = adoptWK(WKStringCreateWithUTF8CString("X"));
957             double x = WKDoubleGetValue(static_cast<WKDoubleRef>(WKDictionaryGetItemForKey(messageBodyDictionary, xKey.get())));
958             
959             WKRetainPtr<WKStringRef> yKey = adoptWK(WKStringCreateWithUTF8CString("Y"));
960             double y = WKDoubleGetValue(static_cast<WKDoubleRef>(WKDictionaryGetItemForKey(messageBodyDictionary, yKey.get())));
961             
962             WKRetainPtr<WKStringRef> phaseKey = adoptWK(WKStringCreateWithUTF8CString("Phase"));
963             int phase = static_cast<int>(WKUInt64GetValue(static_cast<WKUInt64Ref>(WKDictionaryGetItemForKey(messageBodyDictionary, phaseKey.get()))));
964             WKRetainPtr<WKStringRef> momentumKey = adoptWK(WKStringCreateWithUTF8CString("Momentum"));
965             int momentum = static_cast<int>(WKUInt64GetValue(static_cast<WKUInt64Ref>(WKDictionaryGetItemForKey(messageBodyDictionary, momentumKey.get()))));
966             
967             // Forward to WebProcess
968             WKPageSetShouldSendEventsSynchronously(mainWebView()->page(), false);
969             m_eventSenderProxy->mouseScrollByWithWheelAndMomentumPhases(x, y, phase, momentum);
970
971             return;
972         }
973
974         ASSERT_NOT_REACHED();
975     }
976
977     if (!m_currentInvocation)
978         return;
979
980     m_currentInvocation->didReceiveMessageFromInjectedBundle(messageName, messageBody);
981 }
982
983 WKRetainPtr<WKTypeRef> TestController::didReceiveSynchronousMessageFromInjectedBundle(WKStringRef messageName, WKTypeRef messageBody)
984 {
985     if (WKStringIsEqualToUTF8CString(messageName, "EventSender")) {
986         ASSERT(WKGetTypeID(messageBody) == WKDictionaryGetTypeID());
987         WKDictionaryRef messageBodyDictionary = static_cast<WKDictionaryRef>(messageBody);
988
989         WKRetainPtr<WKStringRef> subMessageKey(AdoptWK, WKStringCreateWithUTF8CString("SubMessage"));
990         WKStringRef subMessageName = static_cast<WKStringRef>(WKDictionaryGetItemForKey(messageBodyDictionary, subMessageKey.get()));
991
992         if (WKStringIsEqualToUTF8CString(subMessageName, "KeyDown")) {
993             didReceiveKeyDownMessageFromInjectedBundle(messageBodyDictionary, true);
994
995             return 0;
996         }
997
998         if (WKStringIsEqualToUTF8CString(subMessageName, "MouseDown") || WKStringIsEqualToUTF8CString(subMessageName, "MouseUp")) {
999             WKRetainPtr<WKStringRef> buttonKey = adoptWK(WKStringCreateWithUTF8CString("Button"));
1000             unsigned button = static_cast<unsigned>(WKUInt64GetValue(static_cast<WKUInt64Ref>(WKDictionaryGetItemForKey(messageBodyDictionary, buttonKey.get()))));
1001
1002             WKRetainPtr<WKStringRef> modifiersKey = adoptWK(WKStringCreateWithUTF8CString("Modifiers"));
1003             WKEventModifiers modifiers = static_cast<WKEventModifiers>(WKUInt64GetValue(static_cast<WKUInt64Ref>(WKDictionaryGetItemForKey(messageBodyDictionary, modifiersKey.get()))));
1004
1005             // Forward to WebProcess
1006             WKPageSetShouldSendEventsSynchronously(mainWebView()->page(), true);
1007             if (WKStringIsEqualToUTF8CString(subMessageName, "MouseDown"))
1008                 m_eventSenderProxy->mouseDown(button, modifiers);
1009             else
1010                 m_eventSenderProxy->mouseUp(button, modifiers);
1011             WKPageSetShouldSendEventsSynchronously(mainWebView()->page(), false);
1012             return 0;
1013         }
1014
1015         if (WKStringIsEqualToUTF8CString(subMessageName, "MouseMoveTo")) {
1016             WKRetainPtr<WKStringRef> xKey = adoptWK(WKStringCreateWithUTF8CString("X"));
1017             double x = WKDoubleGetValue(static_cast<WKDoubleRef>(WKDictionaryGetItemForKey(messageBodyDictionary, xKey.get())));
1018
1019             WKRetainPtr<WKStringRef> yKey = adoptWK(WKStringCreateWithUTF8CString("Y"));
1020             double y = WKDoubleGetValue(static_cast<WKDoubleRef>(WKDictionaryGetItemForKey(messageBodyDictionary, yKey.get())));
1021
1022             // Forward to WebProcess
1023             WKPageSetShouldSendEventsSynchronously(mainWebView()->page(), true);
1024             m_eventSenderProxy->mouseMoveTo(x, y);
1025             WKPageSetShouldSendEventsSynchronously(mainWebView()->page(), false);
1026             return 0;
1027         }
1028
1029         if (WKStringIsEqualToUTF8CString(subMessageName, "MouseScrollBy")) {
1030             WKRetainPtr<WKStringRef> xKey = adoptWK(WKStringCreateWithUTF8CString("X"));
1031             double x = WKDoubleGetValue(static_cast<WKDoubleRef>(WKDictionaryGetItemForKey(messageBodyDictionary, xKey.get())));
1032
1033             WKRetainPtr<WKStringRef> yKey = adoptWK(WKStringCreateWithUTF8CString("Y"));
1034             double y = WKDoubleGetValue(static_cast<WKDoubleRef>(WKDictionaryGetItemForKey(messageBodyDictionary, yKey.get())));
1035
1036             // Forward to WebProcess
1037             WKPageSetShouldSendEventsSynchronously(mainWebView()->page(), true);
1038             m_eventSenderProxy->mouseScrollBy(x, y);
1039             WKPageSetShouldSendEventsSynchronously(mainWebView()->page(), false);
1040             return 0;
1041         }
1042
1043         if (WKStringIsEqualToUTF8CString(subMessageName, "MouseScrollByWithWheelAndMomentumPhases")) {
1044             WKRetainPtr<WKStringRef> xKey = adoptWK(WKStringCreateWithUTF8CString("X"));
1045             double x = WKDoubleGetValue(static_cast<WKDoubleRef>(WKDictionaryGetItemForKey(messageBodyDictionary, xKey.get())));
1046             
1047             WKRetainPtr<WKStringRef> yKey = adoptWK(WKStringCreateWithUTF8CString("Y"));
1048             double y = WKDoubleGetValue(static_cast<WKDoubleRef>(WKDictionaryGetItemForKey(messageBodyDictionary, yKey.get())));
1049             
1050             WKRetainPtr<WKStringRef> phaseKey = adoptWK(WKStringCreateWithUTF8CString("Phase"));
1051             int phase = static_cast<int>(WKUInt64GetValue(static_cast<WKUInt64Ref>(WKDictionaryGetItemForKey(messageBodyDictionary, phaseKey.get()))));
1052             WKRetainPtr<WKStringRef> momentumKey = adoptWK(WKStringCreateWithUTF8CString("Momentum"));
1053             int momentum = static_cast<int>(WKUInt64GetValue(static_cast<WKUInt64Ref>(WKDictionaryGetItemForKey(messageBodyDictionary, momentumKey.get()))));
1054
1055             // Forward to WebProcess
1056             WKPageSetShouldSendEventsSynchronously(mainWebView()->page(), true);
1057             m_eventSenderProxy->mouseScrollByWithWheelAndMomentumPhases(x, y, phase, momentum);
1058             WKPageSetShouldSendEventsSynchronously(mainWebView()->page(), false);
1059             return 0;
1060         }
1061         
1062         if (WKStringIsEqualToUTF8CString(subMessageName, "ContinuousMouseScrollBy")) {
1063             WKRetainPtr<WKStringRef> xKey = adoptWK(WKStringCreateWithUTF8CString("X"));
1064             double x = WKDoubleGetValue(static_cast<WKDoubleRef>(WKDictionaryGetItemForKey(messageBodyDictionary, xKey.get())));
1065
1066             WKRetainPtr<WKStringRef> yKey = adoptWK(WKStringCreateWithUTF8CString("Y"));
1067             double y = WKDoubleGetValue(static_cast<WKDoubleRef>(WKDictionaryGetItemForKey(messageBodyDictionary, yKey.get())));
1068
1069             WKRetainPtr<WKStringRef> pagedKey = adoptWK(WKStringCreateWithUTF8CString("Paged"));
1070             bool paged = static_cast<bool>(WKUInt64GetValue(static_cast<WKUInt64Ref>(WKDictionaryGetItemForKey(messageBodyDictionary, pagedKey.get()))));
1071
1072             // Forward to WebProcess
1073             WKPageSetShouldSendEventsSynchronously(mainWebView()->page(), true);
1074             m_eventSenderProxy->continuousMouseScrollBy(x, y, paged);
1075             WKPageSetShouldSendEventsSynchronously(mainWebView()->page(), false);
1076             return 0;
1077         }
1078
1079         if (WKStringIsEqualToUTF8CString(subMessageName, "LeapForward")) {
1080             WKRetainPtr<WKStringRef> timeKey = adoptWK(WKStringCreateWithUTF8CString("TimeInMilliseconds"));
1081             unsigned time = static_cast<unsigned>(WKUInt64GetValue(static_cast<WKUInt64Ref>(WKDictionaryGetItemForKey(messageBodyDictionary, timeKey.get()))));
1082
1083             m_eventSenderProxy->leapForward(time);
1084             return 0;
1085         }
1086
1087 #if ENABLE(TOUCH_EVENTS)
1088         if (WKStringIsEqualToUTF8CString(subMessageName, "AddTouchPoint")) {
1089             WKRetainPtr<WKStringRef> xKey = adoptWK(WKStringCreateWithUTF8CString("X"));
1090             int x = static_cast<int>(WKUInt64GetValue(static_cast<WKUInt64Ref>(WKDictionaryGetItemForKey(messageBodyDictionary, xKey.get()))));
1091
1092             WKRetainPtr<WKStringRef> yKey = adoptWK(WKStringCreateWithUTF8CString("Y"));
1093             int y = static_cast<int>(WKUInt64GetValue(static_cast<WKUInt64Ref>(WKDictionaryGetItemForKey(messageBodyDictionary, yKey.get()))));
1094
1095             m_eventSenderProxy->addTouchPoint(x, y);
1096             return 0;
1097         }
1098
1099         if (WKStringIsEqualToUTF8CString(subMessageName, "UpdateTouchPoint")) {
1100             WKRetainPtr<WKStringRef> indexKey = adoptWK(WKStringCreateWithUTF8CString("Index"));
1101             int index = static_cast<int>(WKUInt64GetValue(static_cast<WKUInt64Ref>(WKDictionaryGetItemForKey(messageBodyDictionary, indexKey.get()))));
1102
1103             WKRetainPtr<WKStringRef> xKey = adoptWK(WKStringCreateWithUTF8CString("X"));
1104             int x = static_cast<int>(WKUInt64GetValue(static_cast<WKUInt64Ref>(WKDictionaryGetItemForKey(messageBodyDictionary, xKey.get()))));
1105
1106             WKRetainPtr<WKStringRef> yKey = adoptWK(WKStringCreateWithUTF8CString("Y"));
1107             int y = static_cast<int>(WKUInt64GetValue(static_cast<WKUInt64Ref>(WKDictionaryGetItemForKey(messageBodyDictionary, yKey.get()))));
1108
1109             m_eventSenderProxy->updateTouchPoint(index, x, y);
1110             return 0;
1111         }
1112
1113         if (WKStringIsEqualToUTF8CString(subMessageName, "SetTouchModifier")) {
1114             WKRetainPtr<WKStringRef> modifierKey = adoptWK(WKStringCreateWithUTF8CString("Modifier"));
1115             WKEventModifiers modifier = static_cast<WKEventModifiers>(WKUInt64GetValue(static_cast<WKUInt64Ref>(WKDictionaryGetItemForKey(messageBodyDictionary, modifierKey.get()))));
1116
1117             WKRetainPtr<WKStringRef> enableKey = adoptWK(WKStringCreateWithUTF8CString("Enable"));
1118             bool enable = static_cast<bool>(WKUInt64GetValue(static_cast<WKUInt64Ref>(WKDictionaryGetItemForKey(messageBodyDictionary, enableKey.get()))));
1119
1120             m_eventSenderProxy->setTouchModifier(modifier, enable);
1121             return 0;
1122         }
1123
1124         if (WKStringIsEqualToUTF8CString(subMessageName, "SetTouchPointRadius")) {
1125             WKRetainPtr<WKStringRef> xKey = adoptWK(WKStringCreateWithUTF8CString("RadiusX"));
1126             int x = static_cast<int>(WKUInt64GetValue(static_cast<WKUInt64Ref>(WKDictionaryGetItemForKey(messageBodyDictionary, xKey.get()))));
1127
1128             WKRetainPtr<WKStringRef> yKey = adoptWK(WKStringCreateWithUTF8CString("RadiusY"));
1129             int y = static_cast<int>(WKUInt64GetValue(static_cast<WKUInt64Ref>(WKDictionaryGetItemForKey(messageBodyDictionary, yKey.get()))));
1130
1131             m_eventSenderProxy->setTouchPointRadius(x, y);
1132             return 0;
1133         }
1134
1135         if (WKStringIsEqualToUTF8CString(subMessageName, "TouchStart")) {
1136             WKPageSetShouldSendEventsSynchronously(mainWebView()->page(), true);
1137             m_eventSenderProxy->touchStart();
1138             WKPageSetShouldSendEventsSynchronously(mainWebView()->page(), false);
1139             return 0;
1140         }
1141
1142         if (WKStringIsEqualToUTF8CString(subMessageName, "TouchMove")) {
1143             WKPageSetShouldSendEventsSynchronously(mainWebView()->page(), true);
1144             m_eventSenderProxy->touchMove();
1145             WKPageSetShouldSendEventsSynchronously(mainWebView()->page(), false);
1146             return 0;
1147         }
1148
1149         if (WKStringIsEqualToUTF8CString(subMessageName, "TouchEnd")) {
1150             WKPageSetShouldSendEventsSynchronously(mainWebView()->page(), true);
1151             m_eventSenderProxy->touchEnd();
1152             WKPageSetShouldSendEventsSynchronously(mainWebView()->page(), false);
1153             return 0;
1154         }
1155
1156         if (WKStringIsEqualToUTF8CString(subMessageName, "TouchCancel")) {
1157             WKPageSetShouldSendEventsSynchronously(mainWebView()->page(), true);
1158             m_eventSenderProxy->touchCancel();
1159             WKPageSetShouldSendEventsSynchronously(mainWebView()->page(), false);
1160             return 0;
1161         }
1162
1163         if (WKStringIsEqualToUTF8CString(subMessageName, "ClearTouchPoints")) {
1164             m_eventSenderProxy->clearTouchPoints();
1165             return 0;
1166         }
1167
1168         if (WKStringIsEqualToUTF8CString(subMessageName, "ReleaseTouchPoint")) {
1169             WKRetainPtr<WKStringRef> indexKey = adoptWK(WKStringCreateWithUTF8CString("Index"));
1170             int index = static_cast<int>(WKUInt64GetValue(static_cast<WKUInt64Ref>(WKDictionaryGetItemForKey(messageBodyDictionary, indexKey.get()))));
1171             m_eventSenderProxy->releaseTouchPoint(index);
1172             return 0;
1173         }
1174
1175         if (WKStringIsEqualToUTF8CString(subMessageName, "CancelTouchPoint")) {
1176             WKRetainPtr<WKStringRef> indexKey = adoptWK(WKStringCreateWithUTF8CString("Index"));
1177             int index = static_cast<int>(WKUInt64GetValue(static_cast<WKUInt64Ref>(WKDictionaryGetItemForKey(messageBodyDictionary, indexKey.get()))));
1178             m_eventSenderProxy->cancelTouchPoint(index);
1179             return 0;
1180         }
1181 #endif
1182         ASSERT_NOT_REACHED();
1183     }
1184     return m_currentInvocation->didReceiveSynchronousMessageFromInjectedBundle(messageName, messageBody);
1185 }
1186
1187 // WKPageLoaderClient
1188
1189 void TestController::didCommitLoadForFrame(WKPageRef page, WKFrameRef frame, WKTypeRef, const void* clientInfo)
1190 {
1191     static_cast<TestController*>(const_cast<void*>(clientInfo))->didCommitLoadForFrame(page, frame);
1192 }
1193
1194 void TestController::didFinishLoadForFrame(WKPageRef page, WKFrameRef frame, WKTypeRef, const void* clientInfo)
1195 {
1196     static_cast<TestController*>(const_cast<void*>(clientInfo))->didFinishLoadForFrame(page, frame);
1197 }
1198
1199 bool TestController::canAuthenticateAgainstProtectionSpaceInFrame(WKPageRef, WKFrameRef, WKProtectionSpaceRef protectionSpace, const void*)
1200 {
1201     return WKProtectionSpaceGetAuthenticationScheme(protectionSpace) <= kWKProtectionSpaceAuthenticationSchemeHTTPDigest;
1202 }
1203
1204 void TestController::didReceiveAuthenticationChallengeInFrame(WKPageRef page, WKFrameRef frame, WKAuthenticationChallengeRef authenticationChallenge, const void *clientInfo)
1205 {
1206     static_cast<TestController*>(const_cast<void*>(clientInfo))->didReceiveAuthenticationChallengeInFrame(page, frame, authenticationChallenge);
1207 }
1208
1209 void TestController::processDidCrash(WKPageRef page, const void* clientInfo)
1210 {
1211     static_cast<TestController*>(const_cast<void*>(clientInfo))->processDidCrash();
1212 }
1213
1214 WKPluginLoadPolicy TestController::pluginLoadPolicy(WKPageRef page, WKPluginLoadPolicy currentPluginLoadPolicy, WKDictionaryRef pluginInformation, WKStringRef* unavailabilityDescription, const void* clientInfo)
1215 {
1216     return static_cast<TestController*>(const_cast<void*>(clientInfo))->pluginLoadPolicy(page, currentPluginLoadPolicy, pluginInformation, unavailabilityDescription);
1217 }
1218
1219 WKPluginLoadPolicy TestController::pluginLoadPolicy(WKPageRef, WKPluginLoadPolicy currentPluginLoadPolicy, WKDictionaryRef pluginInformation, WKStringRef* unavailabilityDescription)
1220 {
1221     if (m_shouldBlockAllPlugins)
1222         return kWKPluginLoadPolicyBlocked;
1223     return currentPluginLoadPolicy;
1224 }
1225
1226 void TestController::didCommitLoadForFrame(WKPageRef page, WKFrameRef frame)
1227 {
1228     if (!WKFrameIsMainFrame(frame))
1229         return;
1230
1231     mainWebView()->focus();
1232 }
1233
1234 void TestController::didFinishLoadForFrame(WKPageRef page, WKFrameRef frame)
1235 {
1236     if (m_state != Resetting)
1237         return;
1238
1239     if (!WKFrameIsMainFrame(frame))
1240         return;
1241
1242     WKRetainPtr<WKURLRef> wkURL(AdoptWK, WKFrameCopyURL(frame));
1243     if (!WKURLIsEqual(wkURL.get(), blankURL()))
1244         return;
1245
1246     m_doneResetting = true;
1247     shared().notifyDone();
1248 }
1249
1250 void TestController::didReceiveAuthenticationChallengeInFrame(WKPageRef page, WKFrameRef frame, WKAuthenticationChallengeRef authenticationChallenge)
1251 {
1252     String message;
1253     if (!m_handlesAuthenticationChallenges)
1254         message = "didReceiveAuthenticationChallenge - Simulating cancelled authentication sheet\n";
1255     else
1256         message = String::format("didReceiveAuthenticationChallenge - Responding with %s:%s\n", m_authenticationUsername.utf8().data(), m_authenticationPassword.utf8().data());
1257     m_currentInvocation->outputText(message);
1258
1259     WKAuthenticationDecisionListenerRef decisionListener = WKAuthenticationChallengeGetDecisionListener(authenticationChallenge);
1260     if (!m_handlesAuthenticationChallenges) {
1261         WKAuthenticationDecisionListenerUseCredential(decisionListener, 0);
1262         return;
1263     }
1264     WKRetainPtr<WKStringRef> username(AdoptWK, WKStringCreateWithUTF8CString(m_authenticationUsername.utf8().data()));
1265     WKRetainPtr<WKStringRef> password(AdoptWK, WKStringCreateWithUTF8CString(m_authenticationPassword.utf8().data()));
1266     WKRetainPtr<WKCredentialRef> credential(AdoptWK, WKCredentialCreate(username.get(), password.get(), kWKCredentialPersistenceForSession));
1267     WKAuthenticationDecisionListenerUseCredential(decisionListener, credential.get());
1268 }
1269
1270 void TestController::processDidCrash()
1271 {
1272     // This function can be called multiple times when crash logs are being saved on Windows, so
1273     // ensure we only print the crashed message once.
1274     if (!m_didPrintWebProcessCrashedMessage) {
1275 #if PLATFORM(COCOA)
1276         pid_t pid = WKPageGetProcessIdentifier(m_mainWebView->page());
1277         // FIXME: Find a way to not hardcode the process name.
1278 #if PLATFORM(IOS)
1279         const char* processName = "com.apple.WebKit.WebContent";
1280 #elif PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED > 1080
1281         const char* processName = "com.apple.WebKit.WebContent.Development";
1282 #else
1283         const char* processName = "WebProcess";
1284 #endif
1285         fprintf(stderr, "#CRASHED - %s (pid %ld)\n", processName, static_cast<long>(pid));
1286 #else
1287         fputs("#CRASHED - WebProcess\n", stderr);
1288 #endif
1289         fflush(stderr);
1290         m_didPrintWebProcessCrashedMessage = true;
1291     }
1292
1293     if (m_shouldExitWhenWebProcessCrashes)
1294         exit(1);
1295 }
1296
1297 void TestController::simulateWebNotificationClick(uint64_t notificationID)
1298 {
1299     m_webNotificationProvider.simulateWebNotificationClick(notificationID);
1300 }
1301
1302 void TestController::setGeolocationPermission(bool enabled)
1303 {
1304     m_isGeolocationPermissionSet = true;
1305     m_isGeolocationPermissionAllowed = enabled;
1306     decidePolicyForGeolocationPermissionRequestIfPossible();
1307 }
1308
1309 void TestController::setMockGeolocationPosition(double latitude, double longitude, double accuracy, bool providesAltitude, double altitude, bool providesAltitudeAccuracy, double altitudeAccuracy, bool providesHeading, double heading, bool providesSpeed, double speed)
1310 {
1311     m_geolocationProvider->setPosition(latitude, longitude, accuracy, providesAltitude, altitude, providesAltitudeAccuracy, altitudeAccuracy, providesHeading, heading, providesSpeed, speed);
1312 }
1313
1314 void TestController::setMockGeolocationPositionUnavailableError(WKStringRef errorMessage)
1315 {
1316     m_geolocationProvider->setPositionUnavailableError(errorMessage);
1317 }
1318
1319 void TestController::handleGeolocationPermissionRequest(WKGeolocationPermissionRequestRef geolocationPermissionRequest)
1320 {
1321     m_geolocationPermissionRequests.append(geolocationPermissionRequest);
1322     decidePolicyForGeolocationPermissionRequestIfPossible();
1323 }
1324
1325 void TestController::setCustomPolicyDelegate(bool enabled, bool permissive)
1326 {
1327     m_policyDelegateEnabled = enabled;
1328     m_policyDelegatePermissive = permissive;
1329 }
1330
1331 void TestController::decidePolicyForGeolocationPermissionRequestIfPossible()
1332 {
1333     if (!m_isGeolocationPermissionSet)
1334         return;
1335
1336     for (size_t i = 0; i < m_geolocationPermissionRequests.size(); ++i) {
1337         WKGeolocationPermissionRequestRef permissionRequest = m_geolocationPermissionRequests[i].get();
1338         if (m_isGeolocationPermissionAllowed)
1339             WKGeolocationPermissionRequestAllow(permissionRequest);
1340         else
1341             WKGeolocationPermissionRequestDeny(permissionRequest);
1342     }
1343     m_geolocationPermissionRequests.clear();
1344 }
1345
1346 void TestController::decidePolicyForNotificationPermissionRequest(WKPageRef page, WKSecurityOriginRef origin, WKNotificationPermissionRequestRef request, const void*)
1347 {
1348     TestController::shared().decidePolicyForNotificationPermissionRequest(page, origin, request);
1349 }
1350
1351 void TestController::decidePolicyForNotificationPermissionRequest(WKPageRef, WKSecurityOriginRef, WKNotificationPermissionRequestRef request)
1352 {
1353     WKNotificationPermissionRequestAllow(request);
1354 }
1355
1356 void TestController::unavailablePluginButtonClicked(WKPageRef, WKPluginUnavailabilityReason, WKDictionaryRef, const void*)
1357 {
1358     printf("MISSING PLUGIN BUTTON PRESSED\n");
1359 }
1360
1361 void TestController::decidePolicyForNavigationAction(WKPageRef, WKFrameRef, WKFrameNavigationType, WKEventModifiers, WKEventMouseButton, WKFrameRef, WKURLRequestRef, WKFramePolicyListenerRef listener, WKTypeRef, const void* clientInfo)
1362 {
1363     static_cast<TestController*>(const_cast<void*>(clientInfo))->decidePolicyForNavigationAction(listener);
1364 }
1365
1366 void TestController::decidePolicyForNavigationAction(WKFramePolicyListenerRef listener)
1367 {
1368     if (m_policyDelegateEnabled && !m_policyDelegatePermissive) {
1369         WKFramePolicyListenerIgnore(listener);
1370         return;
1371     }
1372
1373     WKFramePolicyListenerUse(listener);
1374 }
1375
1376 void TestController::decidePolicyForResponse(WKPageRef, WKFrameRef frame, WKURLResponseRef response, WKURLRequestRef, bool canShowMIMEType, WKFramePolicyListenerRef listener, WKTypeRef, const void* clientInfo)
1377 {
1378     static_cast<TestController*>(const_cast<void*>(clientInfo))->decidePolicyForResponse(frame, response, listener);
1379 }
1380
1381 void TestController::decidePolicyForResponse(WKFrameRef frame, WKURLResponseRef response, WKFramePolicyListenerRef listener)
1382 {
1383     // Even though Response was already checked by WKBundlePagePolicyClient, the check did not include plugins
1384     // so we have to re-check again.
1385     WKRetainPtr<WKStringRef> wkMIMEType(AdoptWK, WKURLResponseCopyMIMEType(response));
1386     if (WKFrameCanShowMIMEType(frame, wkMIMEType.get())) {
1387         WKFramePolicyListenerUse(listener);
1388         return;
1389     }
1390
1391     WKFramePolicyListenerIgnore(listener);
1392 }
1393
1394 void TestController::didNavigateWithNavigationData(WKContextRef, WKPageRef, WKNavigationDataRef navigationData, WKFrameRef frame, const void* clientInfo)
1395 {
1396     static_cast<TestController*>(const_cast<void*>(clientInfo))->didNavigateWithNavigationData(navigationData, frame);
1397 }
1398
1399 void TestController::didNavigateWithNavigationData(WKNavigationDataRef navigationData, WKFrameRef)
1400 {
1401     if (m_state != RunningTest)
1402         return;
1403
1404     if (!m_shouldLogHistoryClientCallbacks)
1405         return;
1406
1407     // URL
1408     WKRetainPtr<WKURLRef> urlWK = adoptWK(WKNavigationDataCopyURL(navigationData));
1409     WKRetainPtr<WKStringRef> urlStringWK = adoptWK(WKURLCopyString(urlWK.get()));
1410     // Title
1411     WKRetainPtr<WKStringRef> titleWK = adoptWK(WKNavigationDataCopyTitle(navigationData));
1412     // HTTP method
1413     WKRetainPtr<WKURLRequestRef> requestWK = adoptWK(WKNavigationDataCopyOriginalRequest(navigationData));
1414     WKRetainPtr<WKStringRef> methodWK = adoptWK(WKURLRequestCopyHTTPMethod(requestWK.get()));
1415
1416     // FIXME: Determine whether the navigation was successful / a client redirect rather than hard-coding the message here.
1417     m_currentInvocation->outputText(String::format("WebView navigated to url \"%s\" with title \"%s\" with HTTP equivalent method \"%s\".  The navigation was successful and was not a client redirect.\n",
1418         toSTD(urlStringWK).c_str(), toSTD(titleWK).c_str(), toSTD(methodWK).c_str()));
1419 }
1420
1421 void TestController::didPerformClientRedirect(WKContextRef, WKPageRef, WKURLRef sourceURL, WKURLRef destinationURL, WKFrameRef frame, const void* clientInfo)
1422 {
1423     static_cast<TestController*>(const_cast<void*>(clientInfo))->didPerformClientRedirect(sourceURL, destinationURL, frame);
1424 }
1425
1426 void TestController::didPerformClientRedirect(WKURLRef sourceURL, WKURLRef destinationURL, WKFrameRef)
1427 {
1428     if (m_state != RunningTest)
1429         return;
1430
1431     if (!m_shouldLogHistoryClientCallbacks)
1432         return;
1433
1434     WKRetainPtr<WKStringRef> sourceStringWK = adoptWK(WKURLCopyString(sourceURL));
1435     WKRetainPtr<WKStringRef> destinationStringWK = adoptWK(WKURLCopyString(destinationURL));
1436
1437     m_currentInvocation->outputText(String::format("WebView performed a client redirect from \"%s\" to \"%s\".\n", toSTD(sourceStringWK).c_str(), toSTD(destinationStringWK).c_str()));
1438 }
1439
1440 void TestController::didPerformServerRedirect(WKContextRef, WKPageRef, WKURLRef sourceURL, WKURLRef destinationURL, WKFrameRef frame, const void* clientInfo)
1441 {
1442     static_cast<TestController*>(const_cast<void*>(clientInfo))->didPerformServerRedirect(sourceURL, destinationURL, frame);
1443 }
1444
1445 void TestController::didPerformServerRedirect(WKURLRef sourceURL, WKURLRef destinationURL, WKFrameRef)
1446 {
1447     if (m_state != RunningTest)
1448         return;
1449
1450     if (!m_shouldLogHistoryClientCallbacks)
1451         return;
1452
1453     WKRetainPtr<WKStringRef> sourceStringWK = adoptWK(WKURLCopyString(sourceURL));
1454     WKRetainPtr<WKStringRef> destinationStringWK = adoptWK(WKURLCopyString(destinationURL));
1455
1456     m_currentInvocation->outputText(String::format("WebView performed a server redirect from \"%s\" to \"%s\".\n", toSTD(sourceStringWK).c_str(), toSTD(destinationStringWK).c_str()));
1457 }
1458
1459 void TestController::didUpdateHistoryTitle(WKContextRef, WKPageRef, WKStringRef title, WKURLRef URL, WKFrameRef frame, const void* clientInfo)
1460 {
1461     static_cast<TestController*>(const_cast<void*>(clientInfo))->didUpdateHistoryTitle(title, URL, frame);
1462 }
1463
1464 void TestController::didUpdateHistoryTitle(WKStringRef title, WKURLRef URL, WKFrameRef)
1465 {
1466     if (m_state != RunningTest)
1467         return;
1468
1469     if (!m_shouldLogHistoryClientCallbacks)
1470         return;
1471
1472     WKRetainPtr<WKStringRef> urlStringWK(AdoptWK, WKURLCopyString(URL));
1473     m_currentInvocation->outputText(String::format("WebView updated the title for history URL \"%s\" to \"%s\".\n", toSTD(urlStringWK).c_str(), toSTD(title).c_str()));
1474 }
1475
1476 } // namespace WTR