[WK2] WTR needs an implementation of setAsynchronousSpellCheckingEnabled
[WebKit-https.git] / Tools / WebKitTestRunner / InjectedBundle / TestRunner.cpp
1 /*
2  * Copyright (C) 2010, 2011, 2012 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 "TestRunner.h"
28
29 #include "InjectedBundle.h"
30 #include "InjectedBundlePage.h"
31 #include "JSTestRunner.h"
32 #include "PlatformWebView.h"
33 #include "StringFunctions.h"
34 #include "TestController.h"
35 #include <WebKit2/WKBundle.h>
36 #include <WebKit2/WKBundleBackForwardList.h>
37 #include <WebKit2/WKBundleFrame.h>
38 #include <WebKit2/WKBundleFramePrivate.h>
39 #include <WebKit2/WKBundleInspector.h>
40 #include <WebKit2/WKBundleNodeHandlePrivate.h>
41 #include <WebKit2/WKBundlePage.h>
42 #include <WebKit2/WKBundlePagePrivate.h>
43 #include <WebKit2/WKBundlePrivate.h>
44 #include <WebKit2/WKBundleScriptWorld.h>
45 #include <WebKit2/WKData.h>
46 #include <WebKit2/WKRetainPtr.h>
47 #include <WebKit2/WKSerializedScriptValue.h>
48 #include <WebKit2/WebKit2_C.h>
49 #include <wtf/CurrentTime.h>
50 #include <wtf/HashMap.h>
51 #include <wtf/OwnArrayPtr.h>
52 #include <wtf/PassOwnArrayPtr.h>
53 #include <wtf/text/CString.h>
54 #include <wtf/text/StringBuilder.h>
55
56 namespace WTR {
57
58 const double TestRunner::waitToDumpWatchdogTimerInterval = 30;
59
60 PassRefPtr<TestRunner> TestRunner::create()
61 {
62     return adoptRef(new TestRunner);
63 }
64
65 TestRunner::TestRunner()
66     : m_whatToDump(RenderTree)
67     , m_shouldDumpAllFrameScrollPositions(false)
68     , m_shouldDumpBackForwardListsForAllWindows(false)
69     , m_shouldAllowEditing(true)
70     , m_shouldCloseExtraWindows(false)
71     , m_dumpEditingCallbacks(false)
72     , m_dumpStatusCallbacks(false)
73     , m_dumpTitleChanges(false)
74     , m_dumpPixels(true)
75     , m_dumpSelectionRect(false)
76     , m_dumpFullScreenCallbacks(false)
77     , m_dumpFrameLoadCallbacks(false)
78     , m_dumpProgressFinishedCallback(false)
79     , m_dumpResourceLoadCallbacks(false)
80     , m_dumpResourceResponseMIMETypes(false)
81     , m_dumpWillCacheResponse(false)
82     , m_dumpApplicationCacheDelegateCallbacks(false)
83     , m_dumpDatabaseCallbacks(false)
84     , m_disallowIncreaseForApplicationCacheQuota(false)
85     , m_waitToDump(false)
86     , m_testRepaint(false)
87     , m_testRepaintSweepHorizontally(false)
88     , m_willSendRequestReturnsNull(false)
89     , m_willSendRequestReturnsNullOnRedirect(false)
90     , m_shouldStopProvisionalFrameLoads(false)
91     , m_policyDelegateEnabled(false)
92     , m_policyDelegatePermissive(false)
93     , m_globalFlag(false)
94     , m_customFullScreenBehavior(false)
95     , m_userStyleSheetEnabled(false)
96     , m_userStyleSheetLocation(adoptWK(WKStringCreateWithUTF8CString("")))
97 {
98     platformInitialize();
99 }
100
101 TestRunner::~TestRunner()
102 {
103 }
104
105 JSClassRef TestRunner::wrapperClass()
106 {
107     return JSTestRunner::testRunnerClass();
108 }
109
110 void TestRunner::display()
111 {
112     WKBundlePageRef page = InjectedBundle::shared().page()->page();
113     WKBundlePageForceRepaint(page);
114     WKBundlePageSetTracksRepaints(page, true);
115     WKBundlePageResetTrackedRepaints(page);
116 }
117
118 void TestRunner::dumpAsText(bool dumpPixels)
119 {
120     if (m_whatToDump < MainFrameText)
121         m_whatToDump = MainFrameText;
122     m_dumpPixels = dumpPixels;
123 }
124
125 void TestRunner::setCustomPolicyDelegate(bool enabled, bool permissive)
126 {
127     m_policyDelegateEnabled = enabled;
128     m_policyDelegatePermissive = permissive;
129
130     InjectedBundle::shared().setCustomPolicyDelegate(enabled, permissive);
131 }
132
133 void TestRunner::waitForPolicyDelegate()
134 {
135     setCustomPolicyDelegate(true);
136     waitUntilDone();
137 }
138
139 void TestRunner::waitUntilDone()
140 {
141     m_waitToDump = true;
142     if (InjectedBundle::shared().useWaitToDumpWatchdogTimer())
143         initializeWaitToDumpWatchdogTimerIfNeeded();
144 }
145
146 void TestRunner::waitToDumpWatchdogTimerFired()
147 {
148     invalidateWaitToDumpWatchdogTimer();
149     InjectedBundle::shared().outputText("FAIL: Timed out waiting for notifyDone to be called\n\n");
150     InjectedBundle::shared().done();
151 }
152
153 void TestRunner::notifyDone()
154 {
155     if (!InjectedBundle::shared().isTestRunning())
156         return;
157
158     if (m_waitToDump && !InjectedBundle::shared().topLoadingFrame())
159         InjectedBundle::shared().page()->dump();
160
161     m_waitToDump = false;
162 }
163
164 void TestRunner::setCustomTimeout(int timeout)
165 {
166     m_timeout = timeout;
167 }
168
169 void TestRunner::addUserScript(JSStringRef source, bool runAtStart, bool allFrames)
170 {
171     WKRetainPtr<WKStringRef> sourceWK = toWK(source);
172     WKRetainPtr<WKBundleScriptWorldRef> scriptWorld(AdoptWK, WKBundleScriptWorldCreateWorld());
173
174     WKBundleAddUserScript(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), scriptWorld.get(), sourceWK.get(), 0, 0, 0,
175         (runAtStart ? kWKInjectAtDocumentStart : kWKInjectAtDocumentEnd),
176         (allFrames ? kWKInjectInAllFrames : kWKInjectInTopFrameOnly));
177 }
178
179 void TestRunner::addUserStyleSheet(JSStringRef source, bool allFrames)
180 {
181     WKRetainPtr<WKStringRef> sourceWK = toWK(source);
182     WKRetainPtr<WKBundleScriptWorldRef> scriptWorld(AdoptWK, WKBundleScriptWorldCreateWorld());
183
184     WKBundleAddUserStyleSheet(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), scriptWorld.get(), sourceWK.get(), 0, 0, 0,
185         (allFrames ? kWKInjectInAllFrames : kWKInjectInTopFrameOnly));
186 }
187
188 void TestRunner::keepWebHistory()
189 {
190     WKBundleSetShouldTrackVisitedLinks(InjectedBundle::shared().bundle(), true);
191 }
192
193 void TestRunner::execCommand(JSStringRef name, JSStringRef argument)
194 {
195     WKBundlePageExecuteEditingCommand(InjectedBundle::shared().page()->page(), toWK(name).get(), toWK(argument).get());
196 }
197
198 bool TestRunner::findString(JSStringRef target, JSValueRef optionsArrayAsValue)
199 {
200     WKFindOptions options = 0;
201
202     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
203     JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
204     JSRetainPtr<JSStringRef> lengthPropertyName(Adopt, JSStringCreateWithUTF8CString("length"));
205     JSObjectRef optionsArray = JSValueToObject(context, optionsArrayAsValue, 0);
206     JSValueRef lengthValue = JSObjectGetProperty(context, optionsArray, lengthPropertyName.get(), 0);
207     if (!JSValueIsNumber(context, lengthValue))
208         return false;
209
210     size_t length = static_cast<size_t>(JSValueToNumber(context, lengthValue, 0));
211     for (size_t i = 0; i < length; ++i) {
212         JSValueRef value = JSObjectGetPropertyAtIndex(context, optionsArray, i, 0);
213         if (!JSValueIsString(context, value))
214             continue;
215
216         JSRetainPtr<JSStringRef> optionName(Adopt, JSValueToStringCopy(context, value, 0));
217
218         if (JSStringIsEqualToUTF8CString(optionName.get(), "CaseInsensitive"))
219             options |= kWKFindOptionsCaseInsensitive;
220         else if (JSStringIsEqualToUTF8CString(optionName.get(), "AtWordStarts"))
221             options |= kWKFindOptionsAtWordStarts;
222         else if (JSStringIsEqualToUTF8CString(optionName.get(), "TreatMedialCapitalAsWordStart"))
223             options |= kWKFindOptionsTreatMedialCapitalAsWordStart;
224         else if (JSStringIsEqualToUTF8CString(optionName.get(), "Backwards"))
225             options |= kWKFindOptionsBackwards;
226         else if (JSStringIsEqualToUTF8CString(optionName.get(), "WrapAround"))
227             options |= kWKFindOptionsWrapAround;
228         else if (JSStringIsEqualToUTF8CString(optionName.get(), "StartInSelection")) {
229             // FIXME: No kWKFindOptionsStartInSelection.
230         }
231     }
232
233     return WKBundlePageFindString(InjectedBundle::shared().page()->page(), toWK(target).get(), options);
234 }
235
236 void TestRunner::clearAllDatabases()
237 {
238     WKBundleClearAllDatabases(InjectedBundle::shared().bundle());
239 }
240
241 void TestRunner::setDatabaseQuota(uint64_t quota)
242 {
243     return WKBundleSetDatabaseQuota(InjectedBundle::shared().bundle(), quota);
244 }
245
246 void TestRunner::clearAllApplicationCaches()
247 {
248     WKBundleClearApplicationCache(InjectedBundle::shared().bundle());
249 }
250
251 void TestRunner::clearApplicationCacheForOrigin(JSStringRef origin)
252 {
253     WKBundleClearApplicationCacheForOrigin(InjectedBundle::shared().bundle(), toWK(origin).get());
254 }
255
256 void TestRunner::setAppCacheMaximumSize(uint64_t size)
257 {
258     WKBundleSetAppCacheMaximumSize(InjectedBundle::shared().bundle(), size);
259 }
260
261 long long TestRunner::applicationCacheDiskUsageForOrigin(JSStringRef origin)
262 {
263     return WKBundleGetAppCacheUsageForOrigin(InjectedBundle::shared().bundle(), toWK(origin).get());
264 }
265
266 void TestRunner::setApplicationCacheOriginQuota(unsigned long long bytes)
267 {
268     WKRetainPtr<WKStringRef> origin(AdoptWK, WKStringCreateWithUTF8CString("http://127.0.0.1:8000"));
269     WKBundleSetApplicationCacheOriginQuota(InjectedBundle::shared().bundle(), origin.get(), bytes);
270 }
271
272 void TestRunner::disallowIncreaseForApplicationCacheQuota()
273 {
274     m_disallowIncreaseForApplicationCacheQuota = true;
275 }
276
277 static inline JSValueRef stringArrayToJS(JSContextRef context, WKArrayRef strings)
278 {
279     const size_t count = WKArrayGetSize(strings);
280
281     OwnArrayPtr<JSValueRef> jsStringsArray = adoptArrayPtr(new JSValueRef[count]);
282     for (size_t i = 0; i < count; ++i) {
283         WKStringRef stringRef = static_cast<WKStringRef>(WKArrayGetItemAtIndex(strings, i));
284         JSRetainPtr<JSStringRef> stringJS = toJS(stringRef);
285         jsStringsArray[i] = JSValueMakeString(context, stringJS.get());
286     }
287
288     return JSObjectMakeArray(context, count, jsStringsArray.get(), 0);
289 }
290
291 JSValueRef TestRunner::originsWithApplicationCache()
292 {
293     WKRetainPtr<WKArrayRef> origins(AdoptWK, WKBundleCopyOriginsWithApplicationCache(InjectedBundle::shared().bundle()));
294
295     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
296     JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
297
298     return stringArrayToJS(context, origins.get());
299 }
300
301 bool TestRunner::isCommandEnabled(JSStringRef name)
302 {
303     return WKBundlePageIsEditingCommandEnabled(InjectedBundle::shared().page()->page(), toWK(name).get());
304 }
305
306 void TestRunner::setCanOpenWindows(bool)
307 {
308     // It's not clear if or why any tests require opening windows be forbidden.
309     // For now, just ignore this setting, and if we find later it's needed we can add it.
310 }
311
312 void TestRunner::setXSSAuditorEnabled(bool enabled)
313 {
314     WKRetainPtr<WKStringRef> key(AdoptWK, WKStringCreateWithUTF8CString("WebKitXSSAuditorEnabled"));
315     WKBundleOverrideBoolPreferenceForTestRunner(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), key.get(), enabled);
316 }
317
318 void TestRunner::setAllowUniversalAccessFromFileURLs(bool enabled)
319 {
320     WKBundleSetAllowUniversalAccessFromFileURLs(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), enabled);
321 }
322
323 void TestRunner::setAllowFileAccessFromFileURLs(bool enabled)
324 {
325     WKBundleSetAllowFileAccessFromFileURLs(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), enabled);
326 }
327
328 void TestRunner::setPluginsEnabled(bool enabled)
329 {
330     WKBundleSetPluginsEnabled(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), enabled);
331 }
332
333 void TestRunner::setJavaScriptCanAccessClipboard(bool enabled)
334 {
335      WKBundleSetJavaScriptCanAccessClipboard(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), enabled);
336 }
337
338 void TestRunner::setPrivateBrowsingEnabled(bool enabled)
339 {
340      WKBundleSetPrivateBrowsingEnabled(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), enabled);
341 }
342
343 void TestRunner::setPopupBlockingEnabled(bool enabled)
344 {
345      WKBundleSetPopupBlockingEnabled(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), enabled);
346 }
347
348 void TestRunner::setAuthorAndUserStylesEnabled(bool enabled)
349 {
350      WKBundleSetAuthorAndUserStylesEnabled(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), enabled);
351 }
352
353 void TestRunner::addOriginAccessWhitelistEntry(JSStringRef sourceOrigin, JSStringRef destinationProtocol, JSStringRef destinationHost, bool allowDestinationSubdomains)
354 {
355     WKBundleAddOriginAccessWhitelistEntry(InjectedBundle::shared().bundle(), toWK(sourceOrigin).get(), toWK(destinationProtocol).get(), toWK(destinationHost).get(), allowDestinationSubdomains);
356 }
357
358 void TestRunner::removeOriginAccessWhitelistEntry(JSStringRef sourceOrigin, JSStringRef destinationProtocol, JSStringRef destinationHost, bool allowDestinationSubdomains)
359 {
360     WKBundleRemoveOriginAccessWhitelistEntry(InjectedBundle::shared().bundle(), toWK(sourceOrigin).get(), toWK(destinationProtocol).get(), toWK(destinationHost).get(), allowDestinationSubdomains);
361 }
362
363 bool TestRunner::isPageBoxVisible(int pageIndex)
364 {
365     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
366     return WKBundleIsPageBoxVisible(InjectedBundle::shared().bundle(), mainFrame, pageIndex);
367 }
368
369 void TestRunner::setValueForUser(JSContextRef context, JSValueRef element, JSStringRef value)
370 {
371     if (!element || !JSValueIsObject(context, element))
372         return;
373
374     WKRetainPtr<WKBundleNodeHandleRef> nodeHandle(AdoptWK, WKBundleNodeHandleCreate(context, const_cast<JSObjectRef>(element)));
375     WKBundleNodeHandleSetHTMLInputElementValueForUser(nodeHandle.get(), toWK(value).get());
376 }
377
378 void TestRunner::setAudioData(JSContextRef context, JSValueRef data)
379 {
380     WKRetainPtr<WKDataRef> audioData(AdoptWK, WKBundleCreateWKDataFromUInt8Array(InjectedBundle::shared().bundle(), context, data));
381     InjectedBundle::shared().setAudioResult(audioData.get());
382     m_whatToDump = Audio;
383 }
384
385 unsigned TestRunner::windowCount()
386 {
387     return InjectedBundle::shared().pageCount();
388 }
389
390 void TestRunner::clearBackForwardList()
391 {
392     WKBundleBackForwardListClear(WKBundlePageGetBackForwardList(InjectedBundle::shared().page()->page()));
393 }
394
395 // Object Creation
396
397 void TestRunner::makeWindowObject(JSContextRef context, JSObjectRef windowObject, JSValueRef* exception)
398 {
399     setProperty(context, windowObject, "testRunner", this, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete, exception);
400 }
401
402 void TestRunner::showWebInspector()
403 {
404 #if ENABLE(INSPECTOR)
405     WKBundleInspectorShow(WKBundlePageGetInspector(InjectedBundle::shared().page()->page()));
406 #endif // ENABLE(INSPECTOR)
407 }
408
409 void TestRunner::closeWebInspector()
410 {
411 #if ENABLE(INSPECTOR)
412     WKBundleInspectorClose(WKBundlePageGetInspector(InjectedBundle::shared().page()->page()));
413 #endif // ENABLE(INSPECTOR)
414 }
415
416 void TestRunner::evaluateInWebInspector(long callID, JSStringRef script)
417 {
418 #if ENABLE(INSPECTOR)
419     WKRetainPtr<WKStringRef> scriptWK = toWK(script);
420     WKBundleInspectorEvaluateScriptForTest(WKBundlePageGetInspector(InjectedBundle::shared().page()->page()), callID, scriptWK.get());
421 #endif // ENABLE(INSPECTOR)
422 }
423
424 typedef WTF::HashMap<unsigned, WKRetainPtr<WKBundleScriptWorldRef> > WorldMap;
425 static WorldMap& worldMap()
426 {
427     static WorldMap& map = *new WorldMap;
428     return map;
429 }
430
431 unsigned TestRunner::worldIDForWorld(WKBundleScriptWorldRef world)
432 {
433     WorldMap::const_iterator end = worldMap().end();
434     for (WorldMap::const_iterator it = worldMap().begin(); it != end; ++it) {
435         if (it->value == world)
436             return it->key;
437     }
438
439     return 0;
440 }
441
442 void TestRunner::evaluateScriptInIsolatedWorld(JSContextRef context, unsigned worldID, JSStringRef script)
443 {
444     // A worldID of 0 always corresponds to a new world. Any other worldID corresponds to a world
445     // that is created once and cached forever.
446     WKRetainPtr<WKBundleScriptWorldRef> world;
447     if (!worldID)
448         world.adopt(WKBundleScriptWorldCreateWorld());
449     else {
450         WKRetainPtr<WKBundleScriptWorldRef>& worldSlot = worldMap().add(worldID, 0).iterator->value;
451         if (!worldSlot)
452             worldSlot.adopt(WKBundleScriptWorldCreateWorld());
453         world = worldSlot;
454     }
455
456     WKBundleFrameRef frame = WKBundleFrameForJavaScriptContext(context);
457     if (!frame)
458         frame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
459
460     JSGlobalContextRef jsContext = WKBundleFrameGetJavaScriptContextForWorld(frame, world.get());
461     JSEvaluateScript(jsContext, script, 0, 0, 0, 0); 
462 }
463
464 void TestRunner::setPOSIXLocale(JSStringRef locale)
465 {
466     char localeBuf[32];
467     JSStringGetUTF8CString(locale, localeBuf, sizeof(localeBuf));
468     setlocale(LC_ALL, localeBuf);
469 }
470
471 void TestRunner::setTextDirection(JSStringRef direction)
472 {
473     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
474     return WKBundleFrameSetTextDirection(mainFrame, toWK(direction).get());
475 }
476     
477 void TestRunner::setShouldStayOnPageAfterHandlingBeforeUnload(bool shouldStayOnPage)
478 {
479     InjectedBundle::shared().postNewBeforeUnloadReturnValue(!shouldStayOnPage);
480 }
481
482 void TestRunner::setDefersLoading(bool shouldDeferLoading)
483 {
484     WKBundlePageSetDefersLoading(InjectedBundle::shared().page()->page(), shouldDeferLoading);
485 }
486
487 void TestRunner::setPageVisibility(JSStringRef state)
488 {
489     WKPageVisibilityState visibilityState = kWKPageVisibilityStateVisible;
490
491     if (JSStringIsEqualToUTF8CString(state, "hidden"))
492         visibilityState = kWKPageVisibilityStateHidden;
493     else if (JSStringIsEqualToUTF8CString(state, "prerender"))
494         visibilityState = kWKPageVisibilityStatePrerender;
495     else if (JSStringIsEqualToUTF8CString(state, "preview"))
496         visibilityState = kWKPageVisibilityStatePreview;
497
498     InjectedBundle::shared().setVisibilityState(visibilityState, false);
499 }
500
501 void TestRunner::resetPageVisibility()
502 {
503     InjectedBundle::shared().setVisibilityState(kWKPageVisibilityStateVisible, true);
504 }
505
506 typedef WTF::HashMap<unsigned, JSValueRef> CallbackMap;
507 static CallbackMap& callbackMap()
508 {
509     static CallbackMap& map = *new CallbackMap;
510     return map;
511 }
512
513 enum {
514     AddChromeInputFieldCallbackID = 1,
515     RemoveChromeInputFieldCallbackID,
516     FocusWebViewCallbackID,
517     SetBackingScaleFactorCallbackID
518 };
519
520 static void cacheTestRunnerCallback(unsigned index, JSValueRef callback)
521 {
522     if (!callback)
523         return;
524
525     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
526     JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
527     JSValueProtect(context, callback);
528     callbackMap().add(index, callback);
529 }
530
531 static void callTestRunnerCallback(unsigned index)
532 {
533     if (!callbackMap().contains(index))
534         return;
535     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
536     JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
537     JSObjectRef callback = JSValueToObject(context, callbackMap().take(index), 0);
538     JSObjectCallAsFunction(context, callback, JSContextGetGlobalObject(context), 0, 0, 0);
539     JSValueUnprotect(context, callback);
540 }
541
542 void TestRunner::addChromeInputField(JSValueRef callback)
543 {
544     cacheTestRunnerCallback(AddChromeInputFieldCallbackID, callback);
545     InjectedBundle::shared().postAddChromeInputField();
546 }
547
548 void TestRunner::removeChromeInputField(JSValueRef callback)
549 {
550     cacheTestRunnerCallback(RemoveChromeInputFieldCallbackID, callback);
551     InjectedBundle::shared().postRemoveChromeInputField();
552 }
553
554 void TestRunner::focusWebView(JSValueRef callback)
555 {
556     cacheTestRunnerCallback(FocusWebViewCallbackID, callback);
557     InjectedBundle::shared().postFocusWebView();
558 }
559
560 void TestRunner::setBackingScaleFactor(double backingScaleFactor, JSValueRef callback)
561 {
562     cacheTestRunnerCallback(SetBackingScaleFactorCallbackID, callback);
563     InjectedBundle::shared().postSetBackingScaleFactor(backingScaleFactor);
564 }
565
566 void TestRunner::setWindowIsKey(bool isKey)
567 {
568     InjectedBundle::shared().postSetWindowIsKey(isKey);
569 }
570
571 void TestRunner::callAddChromeInputFieldCallback()
572 {
573     callTestRunnerCallback(AddChromeInputFieldCallbackID);
574 }
575
576 void TestRunner::callRemoveChromeInputFieldCallback()
577 {
578     callTestRunnerCallback(RemoveChromeInputFieldCallbackID);
579 }
580
581 void TestRunner::callFocusWebViewCallback()
582 {
583     callTestRunnerCallback(FocusWebViewCallbackID);
584 }
585
586 void TestRunner::callSetBackingScaleFactorCallback()
587 {
588     callTestRunnerCallback(SetBackingScaleFactorCallbackID);
589 }
590
591 static inline bool toBool(JSStringRef value)
592 {
593     return JSStringIsEqualToUTF8CString(value, "true") || JSStringIsEqualToUTF8CString(value, "1");
594 }
595
596 void TestRunner::overridePreference(JSStringRef preference, JSStringRef value)
597 {
598     // FIXME: handle non-boolean preferences.
599     WKBundleOverrideBoolPreferenceForTestRunner(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), toWK(preference).get(), toBool(value));
600 }
601
602 void TestRunner::setAlwaysAcceptCookies(bool accept)
603 {
604     WKBundleSetAlwaysAcceptCookies(InjectedBundle::shared().bundle(), accept);
605 }
606
607 double TestRunner::preciseTime()
608 {
609     return currentTime();
610 }
611
612 void TestRunner::setUserStyleSheetEnabled(bool enabled)
613 {
614     m_userStyleSheetEnabled = enabled;
615
616     WKRetainPtr<WKStringRef> emptyUrl = adoptWK(WKStringCreateWithUTF8CString(""));
617     WKStringRef location = enabled ? m_userStyleSheetLocation.get() : emptyUrl.get();
618     WKBundleSetUserStyleSheetLocation(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), location);
619 }
620
621 void TestRunner::setUserStyleSheetLocation(JSStringRef location)
622 {
623     m_userStyleSheetLocation = adoptWK(WKStringCreateWithJSString(location));
624
625     if (m_userStyleSheetEnabled)
626         setUserStyleSheetEnabled(true);
627 }
628
629 void TestRunner::setSpatialNavigationEnabled(bool enabled)
630 {
631     WKBundleSetSpatialNavigationEnabled(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), enabled);
632 }
633
634 void TestRunner::setTabKeyCyclesThroughElements(bool enabled)
635 {
636     WKBundleSetTabKeyCyclesThroughElements(InjectedBundle::shared().bundle(), InjectedBundle::shared().page()->page(), enabled);
637 }
638
639 void TestRunner::setSerializeHTTPLoads()
640 {
641     WKBundleSetSerialLoadingEnabled(InjectedBundle::shared().bundle(), true);
642 }
643
644 void TestRunner::dispatchPendingLoadRequests()
645 {
646     WKBundleDispatchPendingLoadRequests(InjectedBundle::shared().bundle());
647 }
648
649 void TestRunner::setCacheModel(int model)
650 {
651     WKBundleSetCacheModel(InjectedBundle::shared().bundle(), model);
652 }
653
654 void TestRunner::setAsynchronousSpellCheckingEnabled(bool enabled)
655 {
656     WKBundleSetAsynchronousSpellCheckingEnabled(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), enabled);
657 }
658
659 void TestRunner::grantWebNotificationPermission(JSStringRef origin)
660 {
661     WKRetainPtr<WKStringRef> originWK = toWK(origin);
662     WKBundleSetWebNotificationPermission(InjectedBundle::shared().bundle(), InjectedBundle::shared().page()->page(), originWK.get(), true);
663 }
664
665 void TestRunner::denyWebNotificationPermission(JSStringRef origin)
666 {
667     WKRetainPtr<WKStringRef> originWK = toWK(origin);
668     WKBundleSetWebNotificationPermission(InjectedBundle::shared().bundle(), InjectedBundle::shared().page()->page(), originWK.get(), false);
669 }
670
671 void TestRunner::removeAllWebNotificationPermissions()
672 {
673     WKBundleRemoveAllWebNotificationPermissions(InjectedBundle::shared().bundle(), InjectedBundle::shared().page()->page());
674 }
675
676 void TestRunner::simulateWebNotificationClick(JSValueRef notification)
677 {
678     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
679     JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
680     uint64_t notificationID = WKBundleGetWebNotificationID(InjectedBundle::shared().bundle(), context, notification);
681     InjectedBundle::shared().postSimulateWebNotificationClick(notificationID);
682 }
683
684 void TestRunner::setGeolocationPermission(bool enabled)
685 {
686     // FIXME: this should be done by frame.
687     InjectedBundle::shared().setGeolocationPermission(enabled);
688 }
689
690 void TestRunner::setMockGeolocationPosition(double latitude, double longitude, double accuracy, JSValueRef jsAltitude, JSValueRef jsAltitudeAccuracy, JSValueRef jsHeading, JSValueRef jsSpeed)
691 {
692     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
693     JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
694
695     bool providesAltitude = false;
696     double altitude = 0.;
697     if (!JSValueIsUndefined(context, jsAltitude)) {
698         providesAltitude = true;
699         altitude = JSValueToNumber(context, jsAltitude, 0);
700     }
701
702     bool providesAltitudeAccuracy = false;
703     double altitudeAccuracy = 0.;
704     if (!JSValueIsUndefined(context, jsAltitudeAccuracy)) {
705         providesAltitudeAccuracy = true;
706         altitudeAccuracy = JSValueToNumber(context, jsAltitudeAccuracy, 0);
707     }
708
709     bool providesHeading = false;
710     double heading = 0.;
711     if (!JSValueIsUndefined(context, jsHeading)) {
712         providesHeading = true;
713         heading = JSValueToNumber(context, jsHeading, 0);
714     }
715
716     bool providesSpeed = false;
717     double speed = 0.;
718     if (!JSValueIsUndefined(context, jsSpeed)) {
719         providesSpeed = true;
720         speed = JSValueToNumber(context, jsSpeed, 0);
721     }
722
723     InjectedBundle::shared().setMockGeolocationPosition(latitude, longitude, accuracy, providesAltitude, altitude, providesAltitudeAccuracy, altitudeAccuracy, providesHeading, heading, providesSpeed, speed);
724 }
725
726 void TestRunner::setMockGeolocationPositionUnavailableError(JSStringRef message)
727 {
728     WKRetainPtr<WKStringRef> messageWK = toWK(message);
729     InjectedBundle::shared().setMockGeolocationPositionUnavailableError(messageWK.get());
730 }
731
732 bool TestRunner::callShouldCloseOnWebView()
733 {
734     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
735     return WKBundleFrameCallShouldCloseOnWebView(mainFrame);
736 }
737
738 void TestRunner::queueBackNavigation(unsigned howFarBackward)
739 {
740     InjectedBundle::shared().queueBackNavigation(howFarBackward);
741 }
742
743 void TestRunner::queueForwardNavigation(unsigned howFarForward)
744 {
745     InjectedBundle::shared().queueForwardNavigation(howFarForward);
746 }
747
748 void TestRunner::queueLoad(JSStringRef url, JSStringRef target)
749 {
750     WKRetainPtr<WKURLRef> baseURLWK(AdoptWK, WKBundleFrameCopyURL(WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page())));
751     WKRetainPtr<WKURLRef> urlWK(AdoptWK, WKURLCreateWithBaseURL(baseURLWK.get(), toWTFString(toWK(url)).utf8().data()));
752     WKRetainPtr<WKStringRef> urlStringWK(AdoptWK, WKURLCopyString(urlWK.get()));
753
754     InjectedBundle::shared().queueLoad(urlStringWK.get(), toWK(target).get());
755 }
756
757 void TestRunner::queueLoadHTMLString(JSStringRef content, JSStringRef baseURL, JSStringRef unreachableURL)
758 {
759     WKRetainPtr<WKStringRef> contentWK = toWK(content);
760     WKRetainPtr<WKStringRef> baseURLWK = baseURL ? toWK(baseURL) : WKRetainPtr<WKStringRef>();
761     WKRetainPtr<WKStringRef> unreachableURLWK = unreachableURL ? toWK(unreachableURL) : WKRetainPtr<WKStringRef>();
762
763     InjectedBundle::shared().queueLoadHTMLString(contentWK.get(), baseURLWK.get(), unreachableURLWK.get());
764 }
765
766 void TestRunner::queueReload()
767 {
768     InjectedBundle::shared().queueReload();
769 }
770
771 void TestRunner::queueLoadingScript(JSStringRef script)
772 {
773     WKRetainPtr<WKStringRef> scriptWK = toWK(script);
774     InjectedBundle::shared().queueLoadingScript(scriptWK.get());
775 }
776
777 void TestRunner::queueNonLoadingScript(JSStringRef script)
778 {
779     WKRetainPtr<WKStringRef> scriptWK = toWK(script);
780     InjectedBundle::shared().queueNonLoadingScript(scriptWK.get());
781 }
782
783 void TestRunner::setViewModeMediaFeature(JSStringRef mode)
784 {
785     WKRetainPtr<WKStringRef> modeWK = toWK(mode);
786     WKBundlePageSetViewMode(InjectedBundle::shared().page()->page(), modeWK.get());
787 }
788
789 void TestRunner::setHandlesAuthenticationChallenges(bool handlesAuthenticationChallenges)
790 {
791     WKRetainPtr<WKStringRef> messageName(AdoptWK, WKStringCreateWithUTF8CString("SetHandlesAuthenticationChallenge"));
792     WKRetainPtr<WKBooleanRef> messageBody(AdoptWK, WKBooleanCreate(handlesAuthenticationChallenges));
793     WKBundlePostMessage(InjectedBundle::shared().bundle(), messageName.get(), messageBody.get());
794 }
795
796 void TestRunner::setAuthenticationUsername(JSStringRef username)
797 {
798     WKRetainPtr<WKStringRef> messageName(AdoptWK, WKStringCreateWithUTF8CString("SetAuthenticationUsername"));
799     WKRetainPtr<WKStringRef> messageBody(AdoptWK, WKStringCreateWithJSString(username));
800     WKBundlePostMessage(InjectedBundle::shared().bundle(), messageName.get(), messageBody.get());
801 }
802
803 void TestRunner::setAuthenticationPassword(JSStringRef password)
804 {
805     WKRetainPtr<WKStringRef> messageName(AdoptWK, WKStringCreateWithUTF8CString("SetAuthenticationPassword"));
806     WKRetainPtr<WKStringRef> messageBody(AdoptWK, WKStringCreateWithJSString(password));
807     WKBundlePostMessage(InjectedBundle::shared().bundle(), messageName.get(), messageBody.get());
808 }
809
810 } // namespace WTR