text: /*
   * Copyright (C) 2010, 2011, 2012, 2013 Apple Inc. All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in the
   *    documentation and/or other materials provided with the distribution.
   *
   * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
   * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
   * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
   * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
   * THE POSSIBILITY OF SUCH DAMAGE.
   */
  
  #include "config.h"
  #include "TestRunner.h"
  
  #include "InjectedBundle.h"
  #include "InjectedBundlePage.h"
  #include "JSTestRunner.h"
  #include "PlatformWebView.h"
  #include "StringFunctions.h"
  #include "TestController.h"
  #include <WebKit2/WKBundle.h>
  #include <WebKit2/WKBundleBackForwardList.h>
  #include <WebKit2/WKBundleFrame.h>
  #include <WebKit2/WKBundleFramePrivate.h>
  #include <WebKit2/WKBundleInspector.h>
  #include <WebKit2/WKBundleNodeHandlePrivate.h>
  #include <WebKit2/WKBundlePage.h>
  #include <WebKit2/WKBundlePagePrivate.h>
  #include <WebKit2/WKBundlePrivate.h>
  #include <WebKit2/WKBundleScriptWorld.h>
  #include <WebKit2/WKData.h>
  #include <WebKit2/WKRetainPtr.h>
  #include <WebKit2/WKSerializedScriptValue.h>
  #include <WebKit2/WebKit2_C.h>
  #include <wtf/CurrentTime.h>
  #include <wtf/HashMap.h>
  #include <wtf/OwnArrayPtr.h>
  #include <wtf/PassOwnArrayPtr.h>
  #include <wtf/text/CString.h>
  #include <wtf/text/StringBuilder.h>
  
  namespace WTR {
  
  const double TestRunner::waitToDumpWatchdogTimerInterval = 30;
  
  PassRefPtr<TestRunner> TestRunner::create()
  {
      return adoptRef(new TestRunner);
  }
  
  TestRunner::TestRunner()
      : m_whatToDump(RenderTree)
      , m_shouldDumpAllFrameScrollPositions(false)
      , m_shouldDumpBackForwardListsForAllWindows(false)
      , m_shouldAllowEditing(true)
      , m_shouldCloseExtraWindows(false)
      , m_dumpEditingCallbacks(false)
      , m_dumpStatusCallbacks(false)
      , m_dumpTitleChanges(false)
      , m_dumpPixels(true)
      , m_dumpSelectionRect(false)
      , m_dumpFullScreenCallbacks(false)
      , m_dumpFrameLoadCallbacks(false)
      , m_dumpProgressFinishedCallback(false)
      , m_dumpResourceLoadCallbacks(false)
      , m_dumpResourceResponseMIMETypes(false)
      , m_dumpWillCacheResponse(false)
      , m_dumpApplicationCacheDelegateCallbacks(false)
      , m_dumpDatabaseCallbacks(false)
      , m_disallowIncreaseForApplicationCacheQuota(false)
      , m_waitToDump(false)
      , m_testRepaint(false)
      , m_testRepaintSweepHorizontally(false)
      , m_isPrinting(false)
      , m_willSendRequestReturnsNull(false)
      , m_willSendRequestReturnsNullOnRedirect(false)
      , m_shouldStopProvisionalFrameLoads(false)
      , m_policyDelegateEnabled(false)
      , m_policyDelegatePermissive(false)
      , m_globalFlag(false)
      , m_customFullScreenBehavior(false)
      , m_userStyleSheetEnabled(false)
      , m_userStyleSheetLocation(adoptWK(WKStringCreateWithUTF8CString("")))
  {
      platformInitialize();
  }
  
  TestRunner::~TestRunner()
  {
  }
  
  JSClassRef TestRunner::wrapperClass()
  {
      return JSTestRunner::testRunnerClass();
  }
  
  void TestRunner::display()
  {
      WKBundlePageRef page = InjectedBundle::shared().page()->page();
      WKBundlePageForceRepaint(page);
      WKBundlePageSetTracksRepaints(page, true);
      WKBundlePageResetTrackedRepaints(page);
  }
  
  void TestRunner::dumpAsText(bool dumpPixels)
  {
      if (m_whatToDump < MainFrameText)
          m_whatToDump = MainFrameText;
      m_dumpPixels = dumpPixels;
  }
  
  void TestRunner::setCustomPolicyDelegate(bool enabled, bool permissive)
  {
      m_policyDelegateEnabled = enabled;
      m_policyDelegatePermissive = permissive;
  
      InjectedBundle::shared().setCustomPolicyDelegate(enabled, permissive);
  }
  
  void TestRunner::waitForPolicyDelegate()
  {
      setCustomPolicyDelegate(true);
      waitUntilDone();
  }
  
  void TestRunner::waitUntilDone()
  {
      m_waitToDump = true;
      if (InjectedBundle::shared().useWaitToDumpWatchdogTimer())
          initializeWaitToDumpWatchdogTimerIfNeeded();
  }
  
  void TestRunner::waitToDumpWatchdogTimerFired()
  {
      invalidateWaitToDumpWatchdogTimer();
      InjectedBundle::shared().outputText("FAIL: Timed out waiting for notifyDone to be called\n\n");
      InjectedBundle::shared().done();
  }
  
  void TestRunner::notifyDone()
  {
      if (!InjectedBundle::shared().isTestRunning())
          return;
  
      if (m_waitToDump && !InjectedBundle::shared().topLoadingFrame())
          InjectedBundle::shared().page()->dump();
  
      m_waitToDump = false;
  }
  
  void TestRunner::setCustomTimeout(int timeout)
  {
      m_timeout = timeout;
  }
  
  void TestRunner::addUserScript(JSStringRef source, bool runAtStart, bool allFrames)
  {
      WKRetainPtr<WKStringRef> sourceWK = toWK(source);
      WKRetainPtr<WKBundleScriptWorldRef> scriptWorld(AdoptWK, WKBundleScriptWorldCreateWorld());
  
      WKBundleAddUserScript(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), scriptWorld.get(), sourceWK.get(), 0, 0, 0,
          (runAtStart ? kWKInjectAtDocumentStart : kWKInjectAtDocumentEnd),
          (allFrames ? kWKInjectInAllFrames : kWKInjectInTopFrameOnly));
  }
  
  void TestRunner::addUserStyleSheet(JSStringRef source, bool allFrames)
  {
      WKRetainPtr<WKStringRef> sourceWK = toWK(source);
      WKRetainPtr<WKBundleScriptWorldRef> scriptWorld(AdoptWK, WKBundleScriptWorldCreateWorld());
  
      WKBundleAddUserStyleSheet(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), scriptWorld.get(), sourceWK.get(), 0, 0, 0,
          (allFrames ? kWKInjectInAllFrames : kWKInjectInTopFrameOnly));
  }
  
  void TestRunner::keepWebHistory()
  {
      WKBundleSetShouldTrackVisitedLinks(InjectedBundle::shared().bundle(), true);
  }
  
  void TestRunner::execCommand(JSStringRef name, JSStringRef argument)
  {
      WKBundlePageExecuteEditingCommand(InjectedBundle::shared().page()->page(), toWK(name).get(), toWK(argument).get());
  }
  
  bool TestRunner::findString(JSStringRef target, JSValueRef optionsArrayAsValue)
  {
      WKFindOptions options = 0;
  
      WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
      JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
      JSRetainPtr<JSStringRef> lengthPropertyName(Adopt, JSStringCreateWithUTF8CString("length"));
      JSObjectRef optionsArray = JSValueToObject(context, optionsArrayAsValue, 0);
      JSValueRef lengthValue = JSObjectGetProperty(context, optionsArray, lengthPropertyName.get(), 0);
      if (!JSValueIsNumber(context, lengthValue))
          return false;
  
      size_t length = static_cast<size_t>(JSValueToNumber(context, lengthValue, 0));
      for (size_t i = 0; i < length; ++i) {
          JSValueRef value = JSObjectGetPropertyAtIndex(context, optionsArray, i, 0);
          if (!JSValueIsString(context, value))
              continue;
  
          JSRetainPtr<JSStringRef> optionName(Adopt, JSValueToStringCopy(context, value, 0));
  
          if (JSStringIsEqualToUTF8CString(optionName.get(), "CaseInsensitive"))
              options |= kWKFindOptionsCaseInsensitive;
          else if (JSStringIsEqualToUTF8CString(optionName.get(), "AtWordStarts"))
              options |= kWKFindOptionsAtWordStarts;
          else if (JSStringIsEqualToUTF8CString(optionName.get(), "TreatMedialCapitalAsWordStart"))
              options |= kWKFindOptionsTreatMedialCapitalAsWordStart;
          else if (JSStringIsEqualToUTF8CString(optionName.get(), "Backwards"))
              options |= kWKFindOptionsBackwards;
          else if (JSStringIsEqualToUTF8CString(optionName.get(), "WrapAround"))
              options |= kWKFindOptionsWrapAround;
          else if (JSStringIsEqualToUTF8CString(optionName.get(), "StartInSelection")) {
              // FIXME: No kWKFindOptionsStartInSelection.
          }
      }
  
      return WKBundlePageFindString(InjectedBundle::shared().page()->page(), toWK(target).get(), options);
  }
  
  void TestRunner::clearAllDatabases()
  {
      WKBundleClearAllDatabases(InjectedBundle::shared().bundle());
  }
  
  void TestRunner::setDatabaseQuota(uint64_t quota)
  {
      return WKBundleSetDatabaseQuota(InjectedBundle::shared().bundle(), quota);
  }
  
  void TestRunner::clearAllApplicationCaches()
  {
      WKBundleClearApplicationCache(InjectedBundle::shared().bundle());
  }
  
  void TestRunner::clearApplicationCacheForOrigin(JSStringRef origin)
  {
      WKBundleClearApplicationCacheForOrigin(InjectedBundle::shared().bundle(), toWK(origin).get());
  }
  
  void TestRunner::setAppCacheMaximumSize(uint64_t size)
  {
      WKBundleSetAppCacheMaximumSize(InjectedBundle::shared().bundle(), size);
  }
  
  long long TestRunner::applicationCacheDiskUsageForOrigin(JSStringRef origin)
  {
      return WKBundleGetAppCacheUsageForOrigin(InjectedBundle::shared().bundle(), toWK(origin).get());
  }
  
  void TestRunner::setApplicationCacheOriginQuota(unsigned long long bytes)
  {
      WKRetainPtr<WKStringRef> origin(AdoptWK, WKStringCreateWithUTF8CString("http://127.0.0.1:8000"));
      WKBundleSetApplicationCacheOriginQuota(InjectedBundle::shared().bundle(), origin.get(), bytes);
  }
  
  void TestRunner::disallowIncreaseForApplicationCacheQuota()
  {
      m_disallowIncreaseForApplicationCacheQuota = true;
  }
  
  static inline JSValueRef stringArrayToJS(JSContextRef context, WKArrayRef strings)
  {
      const size_t count = WKArrayGetSize(strings);
  
      OwnArrayPtr<JSValueRef> jsStringsArray = adoptArrayPtr(new JSValueRef[count]);
      for (size_t i = 0; i < count; ++i) {
          WKStringRef stringRef = static_cast<WKStringRef>(WKArrayGetItemAtIndex(strings, i));
          JSRetainPtr<JSStringRef> stringJS = toJS(stringRef);
          jsStringsArray[i] = JSValueMakeString(context, stringJS.get());
      }
  
      return JSObjectMakeArray(context, count, jsStringsArray.get(), 0);
  }
  
  JSValueRef TestRunner::originsWithApplicationCache()
  {
      WKRetainPtr<WKArrayRef> origins(AdoptWK, WKBundleCopyOriginsWithApplicationCache(InjectedBundle::shared().bundle()));
  
      WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
      JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
  
      return stringArrayToJS(context, origins.get());
  }
  
  bool TestRunner::isCommandEnabled(JSStringRef name)
  {
      return WKBundlePageIsEditingCommandEnabled(InjectedBundle::shared().page()->page(), toWK(name).get());
  }
  
  void TestRunner::setCanOpenWindows(bool)
  {
      // It's not clear if or why any tests require opening windows be forbidden.
      // For now, just ignore this setting, and if we find later it's needed we can add it.
  }
  
  void TestRunner::setXSSAuditorEnabled(bool enabled)
  {
      WKRetainPtr<WKStringRef> key(AdoptWK, WKStringCreateWithUTF8CString("WebKitXSSAuditorEnabled"));
      WKBundleOverrideBoolPreferenceForTestRunner(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), key.get(), enabled);
  }
  
  void TestRunner::setAllowUniversalAccessFromFileURLs(bool enabled)
  {
      WKBundleSetAllowUniversalAccessFromFileURLs(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), enabled);
  }
  
  void TestRunner::setAllowFileAccessFromFileURLs(bool enabled)
  {
      WKBundleSetAllowFileAccessFromFileURLs(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), enabled);
  }
  
  void TestRunner::setPluginsEnabled(bool enabled)
  {
      WKBundleSetPluginsEnabled(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), enabled);
  }
  
  void TestRunner::setJavaScriptCanAccessClipboard(bool enabled)
  {
       WKBundleSetJavaScriptCanAccessClipboard(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), enabled);
  }
  
  void TestRunner::setPrivateBrowsingEnabled(bool enabled)
  {
       WKBundleSetPrivateBrowsingEnabled(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), enabled);
  }
  
  void TestRunner::setPopupBlockingEnabled(bool enabled)
  {
       WKBundleSetPopupBlockingEnabled(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), enabled);
  }
  
  void TestRunner::setAuthorAndUserStylesEnabled(bool enabled)
  {
       WKBundleSetAuthorAndUserStylesEnabled(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), enabled);
  }
  
  void TestRunner::addOriginAccessWhitelistEntry(JSStringRef sourceOrigin, JSStringRef destinationProtocol, JSStringRef destinationHost, bool allowDestinationSubdomains)
  {
      WKBundleAddOriginAccessWhitelistEntry(InjectedBundle::shared().bundle(), toWK(sourceOrigin).get(), toWK(destinationProtocol).get(), toWK(destinationHost).get(), allowDestinationSubdomains);
  }
  
  void TestRunner::removeOriginAccessWhitelistEntry(JSStringRef sourceOrigin, JSStringRef destinationProtocol, JSStringRef destinationHost, bool allowDestinationSubdomains)
  {
      WKBundleRemoveOriginAccessWhitelistEntry(InjectedBundle::shared().bundle(), toWK(sourceOrigin).get(), toWK(destinationProtocol).get(), toWK(destinationHost).get(), allowDestinationSubdomains);
  }
  
  bool TestRunner::isPageBoxVisible(int pageIndex)
  {
      WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
      return WKBundleIsPageBoxVisible(InjectedBundle::shared().bundle(), mainFrame, pageIndex);
  }
  
  void TestRunner::setValueForUser(JSContextRef context, JSValueRef element, JSStringRef value)
  {
      if (!element || !JSValueIsObject(context, element))
          return;
  
      WKRetainPtr<WKBundleNodeHandleRef> nodeHandle(AdoptWK, WKBundleNodeHandleCreate(context, const_cast<JSObjectRef>(element)));
      WKBundleNodeHandleSetHTMLInputElementValueForUser(nodeHandle.get(), toWK(value).get());
  }
  
  void TestRunner::setAudioResult(JSContextRef context, JSValueRef data)
  {
      WKRetainPtr<WKDataRef> audioData(AdoptWK, WKBundleCreateWKDataFromUInt8Array(InjectedBundle::shared().bundle(), context, data));
      InjectedBundle::shared().setAudioResult(audioData.get());
      m_whatToDump = Audio;
      m_dumpPixels = false;
  }
  
  unsigned TestRunner::windowCount()
  {
      return InjectedBundle::shared().pageCount();
  }
  
  void TestRunner::clearBackForwardList()
  {
      WKBundleBackForwardListClear(WKBundlePageGetBackForwardList(InjectedBundle::shared().page()->page()));
  }
  
  // Object Creation
  
  void TestRunner::makeWindowObject(JSContextRef context, JSObjectRef windowObject, JSValueRef* exception)
  {
      setProperty(context, windowObject, "testRunner", this, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete, exception);
  }
  
  void TestRunner::showWebInspector()
  {
  #if ENABLE(INSPECTOR)
      WKBundleInspectorShow(WKBundlePageGetInspector(InjectedBundle::shared().page()->page()));
  #endif // ENABLE(INSPECTOR)
  }
  
  void TestRunner::closeWebInspector()
  {
  #if ENABLE(INSPECTOR)
      WKBundleInspectorClose(WKBundlePageGetInspector(InjectedBundle::shared().page()->page()));
  #endif // ENABLE(INSPECTOR)
  }
  
  void TestRunner::evaluateInWebInspector(long callID, JSStringRef script)
  {
  #if ENABLE(INSPECTOR)
      WKRetainPtr<WKStringRef> scriptWK = toWK(script);
      WKBundleInspectorEvaluateScriptForTest(WKBundlePageGetInspector(InjectedBundle::shared().page()->page()), callID, scriptWK.get());
  #endif // ENABLE(INSPECTOR)
  }
  
  typedef WTF::HashMap<unsigned, WKRetainPtr<WKBundleScriptWorldRef> > WorldMap;
  static WorldMap& worldMap()
  {
      static WorldMap& map = *new WorldMap;
      return map;
  }
  
  unsigned TestRunner::worldIDForWorld(WKBundleScriptWorldRef world)
  {
      WorldMap::const_iterator end = worldMap().end();
      for (WorldMap::const_iterator it = worldMap().begin(); it != end; ++it) {
          if (it->value == world)
              return it->key;
      }
  
      return 0;
  }
  
  void TestRunner::evaluateScriptInIsolatedWorld(JSContextRef context, unsigned worldID, JSStringRef script)
  {
      // A worldID of 0 always corresponds to a new world. Any other worldID corresponds to a world
      // that is created once and cached forever.
      WKRetainPtr<WKBundleScriptWorldRef> world;
      if (!worldID)
          world.adopt(WKBundleScriptWorldCreateWorld());
      else {
          WKRetainPtr<WKBundleScriptWorldRef>& worldSlot = worldMap().add(worldID, 0).iterator->value;
          if (!worldSlot)
              worldSlot.adopt(WKBundleScriptWorldCreateWorld());
          world = worldSlot;
      }
  
      WKBundleFrameRef frame = WKBundleFrameForJavaScriptContext(context);
      if (!frame)
          frame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
  
      JSGlobalContextRef jsContext = WKBundleFrameGetJavaScriptContextForWorld(frame, world.get());
      JSEvaluateScript(jsContext, script, 0, 0, 0, 0); 
  }
  
  void TestRunner::setPOSIXLocale(JSStringRef locale)
  {
      char localeBuf[32];
      JSStringGetUTF8CString(locale, localeBuf, sizeof(localeBuf));
      setlocale(LC_ALL, localeBuf);
  }
  
  void TestRunner::setTextDirection(JSStringRef direction)
  {
      WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
      return WKBundleFrameSetTextDirection(mainFrame, toWK(direction).get());
  }
      
  void TestRunner::setShouldStayOnPageAfterHandlingBeforeUnload(bool shouldStayOnPage)
  {
      InjectedBundle::shared().postNewBeforeUnloadReturnValue(!shouldStayOnPage);
  }
  
  void TestRunner::setDefersLoading(bool shouldDeferLoading)
  {
      WKBundlePageSetDefersLoading(InjectedBundle::shared().page()->page(), shouldDeferLoading);
  }
  
  void TestRunner::setPageVisibility(JSStringRef state)
  {
      WKPageVisibilityState visibilityState = kWKPageVisibilityStateVisible;
  
      if (JSStringIsEqualToUTF8CString(state, "hidden"))
          visibilityState = kWKPageVisibilityStateHidden;
      else if (JSStringIsEqualToUTF8CString(state, "prerender"))
          visibilityState = kWKPageVisibilityStatePrerender;
      else if (JSStringIsEqualToUTF8CString(state, "unloaded"))
          visibilityState = kWKPageVisibilityStateUnloaded;
  
      InjectedBundle::shared().setVisibilityState(visibilityState, false);
  }
  
  void TestRunner::resetPageVisibility()
  {
      InjectedBundle::shared().setVisibilityState(kWKPageVisibilityStateVisible, true);
  }
  
  typedef WTF::HashMap<unsigned, JSValueRef> CallbackMap;
  static CallbackMap& callbackMap()
  {
      static CallbackMap& map = *new CallbackMap;
      return map;
  }
  
  enum {
      AddChromeInputFieldCallbackID = 1,
      RemoveChromeInputFieldCallbackID,
      FocusWebViewCallbackID,
      SetBackingScaleFactorCallbackID
  };
  
  static void cacheTestRunnerCallback(unsigned index, JSValueRef callback)
  {
      if (!callback)
          return;
  
      WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
      JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
      JSValueProtect(context, callback);
      callbackMap().add(index, callback);
  }
  
  static void callTestRunnerCallback(unsigned index)
  {
      if (!callbackMap().contains(index))
          return;
      WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
      JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
      JSObjectRef callback = JSValueToObject(context, callbackMap().take(index), 0);
      JSObjectCallAsFunction(context, callback, JSContextGetGlobalObject(context), 0, 0, 0);
      JSValueUnprotect(context, callback);
  }
  
  void TestRunner::addChromeInputField(JSValueRef callback)
  {
      cacheTestRunnerCallback(AddChromeInputFieldCallbackID, callback);
      InjectedBundle::shared().postAddChromeInputField();
  }
  
  void TestRunner::removeChromeInputField(JSValueRef callback)
  {
      cacheTestRunnerCallback(RemoveChromeInputFieldCallbackID, callback);
      InjectedBundle::shared().postRemoveChromeInputField();
  }
  
  void TestRunner::focusWebView(JSValueRef callback)
  {
      cacheTestRunnerCallback(FocusWebViewCallbackID, callback);
      InjectedBundle::shared().postFocusWebView();
  }
  
  void TestRunner::setBackingScaleFactor(double backingScaleFactor, JSValueRef callback)
  {
      cacheTestRunnerCallback(SetBackingScaleFactorCallbackID, callback);
      InjectedBundle::shared().postSetBackingScaleFactor(backingScaleFactor);
  }
  
  void TestRunner::setWindowIsKey(bool isKey)
  {
      InjectedBundle::shared().postSetWindowIsKey(isKey);
  }
  
  void TestRunner::callAddChromeInputFieldCallback()
  {
      callTestRunnerCallback(AddChromeInputFieldCallbackID);
  }
  
  void TestRunner::callRemoveChromeInputFieldCallback()
  {
      callTestRunnerCallback(RemoveChromeInputFieldCallbackID);
  }
  
  void TestRunner::callFocusWebViewCallback()
  {
      callTestRunnerCallback(FocusWebViewCallbackID);
  }
  
  void TestRunner::callSetBackingScaleFactorCallback()
  {
      callTestRunnerCallback(SetBackingScaleFactorCallbackID);
  }
  
  static inline bool toBool(JSStringRef value)
  {
      return JSStringIsEqualToUTF8CString(value, "true") || JSStringIsEqualToUTF8CString(value, "1");
  }
  
  void TestRunner::overridePreference(JSStringRef preference, JSStringRef value)
  {
      // FIXME: handle non-boolean preferences.
      WKBundleOverrideBoolPreferenceForTestRunner(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), toWK(preference).get(), toBool(value));
  }
  
  void TestRunner::setAlwaysAcceptCookies(bool accept)
  {
      WKBundleSetAlwaysAcceptCookies(InjectedBundle::shared().bundle(), accept);
  }
  
  double TestRunner::preciseTime()
  {
      return currentTime();
  }
  
  void TestRunner::setUserStyleSheetEnabled(bool enabled)
  {
      m_userStyleSheetEnabled = enabled;
  
      WKRetainPtr<WKStringRef> emptyUrl = adoptWK(WKStringCreateWithUTF8CString(""));
      WKStringRef location = enabled ? m_userStyleSheetLocation.get() : emptyUrl.get();
      WKBundleSetUserStyleSheetLocation(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), location);
  }
  
  void TestRunner::setUserStyleSheetLocation(JSStringRef location)
  {
      m_userStyleSheetLocation = adoptWK(WKStringCreateWithJSString(location));
  
      if (m_userStyleSheetEnabled)
          setUserStyleSheetEnabled(true);
  }
  
  void TestRunner::setSpatialNavigationEnabled(bool enabled)
  {
      WKBundleSetSpatialNavigationEnabled(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), enabled);
  }
  
  void TestRunner::setTabKeyCyclesThroughElements(bool enabled)
  {
      WKBundleSetTabKeyCyclesThroughElements(InjectedBundle::shared().bundle(), InjectedBundle::shared().page()->page(), enabled);
  }
  
  void TestRunner::setSerializeHTTPLoads()
  {
      WKBundleSetSerialLoadingEnabled(InjectedBundle::shared().bundle(), true);
  }
  
  void TestRunner::dispatchPendingLoadRequests()
  {
      WKBundleDispatchPendingLoadRequests(InjectedBundle::shared().bundle());
  }
  
  void TestRunner::setCacheModel(int model)
  {
      WKBundleSetCacheModel(InjectedBundle::shared().bundle(), model);
  }
  
  void TestRunner::setAsynchronousSpellCheckingEnabled(bool enabled)
  {
      WKBundleSetAsynchronousSpellCheckingEnabled(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), enabled);
  }
  
  void TestRunner::grantWebNotificationPermission(JSStringRef origin)
  {
      WKRetainPtr<WKStringRef> originWK = toWK(origin);
      WKBundleSetWebNotificationPermission(InjectedBundle::shared().bundle(), InjectedBundle::shared().page()->page(), originWK.get(), true);
  }
  
  void TestRunner::denyWebNotificationPermission(JSStringRef origin)
  {
      WKRetainPtr<WKStringRef> originWK = toWK(origin);
      WKBundleSetWebNotificationPermission(InjectedBundle::shared().bundle(), InjectedBundle::shared().page()->page(), originWK.get(), false);
  }
  
  void TestRunner::removeAllWebNotificationPermissions()
  {
      WKBundleRemoveAllWebNotificationPermissions(InjectedBundle::shared().bundle(), InjectedBundle::shared().page()->page());
  }
  
  void TestRunner::simulateWebNotificationClick(JSValueRef notification)
  {
      WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
      JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
      uint64_t notificationID = WKBundleGetWebNotificationID(InjectedBundle::shared().bundle(), context, notification);
      InjectedBundle::shared().postSimulateWebNotificationClick(notificationID);
  }
  
  void TestRunner::setGeolocationPermission(bool enabled)
  {
      // FIXME: this should be done by frame.
      InjectedBundle::shared().setGeolocationPermission(enabled);
  }
  
  void TestRunner::setMockGeolocationPosition(double latitude, double longitude, double accuracy, JSValueRef jsAltitude, JSValueRef jsAltitudeAccuracy, JSValueRef jsHeading, JSValueRef jsSpeed)
  {
      WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
      JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
  
      bool providesAltitude = false;
      double altitude = 0.;
      if (!JSValueIsUndefined(context, jsAltitude)) {
          providesAltitude = true;
          altitude = JSValueToNumber(context, jsAltitude, 0);
      }
  
      bool providesAltitudeAccuracy = false;
      double altitudeAccuracy = 0.;
      if (!JSValueIsUndefined(context, jsAltitudeAccuracy)) {
          providesAltitudeAccuracy = true;
          altitudeAccuracy = JSValueToNumber(context, jsAltitudeAccuracy, 0);
      }
  
      bool providesHeading = false;
      double heading = 0.;
      if (!JSValueIsUndefined(context, jsHeading)) {
          providesHeading = true;
          heading = JSValueToNumber(context, jsHeading, 0);
      }
  
      bool providesSpeed = false;
      double speed = 0.;
      if (!JSValueIsUndefined(context, jsSpeed)) {
          providesSpeed = true;
          speed = JSValueToNumber(context, jsSpeed, 0);
      }
  
      InjectedBundle::shared().setMockGeolocationPosition(latitude, longitude, accuracy, providesAltitude, altitude, providesAltitudeAccuracy, altitudeAccuracy, providesHeading, heading, providesSpeed, speed);
  }
  
  void TestRunner::setMockGeolocationPositionUnavailableError(JSStringRef message)
  {
      WKRetainPtr<WKStringRef> messageWK = toWK(message);
      InjectedBundle::shared().setMockGeolocationPositionUnavailableError(messageWK.get());
  }
  
  bool TestRunner::callShouldCloseOnWebView()
  {
      WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
      return WKBundleFrameCallShouldCloseOnWebView(mainFrame);
  }
  
  void TestRunner::queueBackNavigation(unsigned howFarBackward)
  {
      InjectedBundle::shared().queueBackNavigation(howFarBackward);
  }
  
  void TestRunner::queueForwardNavigation(unsigned howFarForward)
  {
      InjectedBundle::shared().queueForwardNavigation(howFarForward);
  }
  
  void TestRunner::queueLoad(JSStringRef url, JSStringRef target)
  {
      WKRetainPtr<WKURLRef> baseURLWK(AdoptWK, WKBundleFrameCopyURL(WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page())));
      WKRetainPtr<WKURLRef> urlWK(AdoptWK, WKURLCreateWithBaseURL(baseURLWK.get(), toWTFString(toWK(url)).utf8().data()));
      WKRetainPtr<WKStringRef> urlStringWK(AdoptWK, WKURLCopyString(urlWK.get()));
  
      InjectedBundle::shared().queueLoad(urlStringWK.get(), toWK(target).get());
  }
  
  void TestRunner::queueLoadHTMLString(JSStringRef content, JSStringRef baseURL, JSStringRef unreachableURL)
  {
      WKRetainPtr<WKStringRef> contentWK = toWK(content);
      WKRetainPtr<WKStringRef> baseURLWK = baseURL ? toWK(baseURL) : WKRetainPtr<WKStringRef>();
      WKRetainPtr<WKStringRef> unreachableURLWK = unreachableURL ? toWK(unreachableURL) : WKRetainPtr<WKStringRef>();
  
      InjectedBundle::shared().queueLoadHTMLString(contentWK.get(), baseURLWK.get(), unreachableURLWK.get());
  }
  
  void TestRunner::queueReload()
  {
      InjectedBundle::shared().queueReload();
  }
  
  void TestRunner::queueLoadingScript(JSStringRef script)
  {
      WKRetainPtr<WKStringRef> scriptWK = toWK(script);
      InjectedBundle::shared().queueLoadingScript(scriptWK.get());
  }
  
  void TestRunner::queueNonLoadingScript(JSStringRef script)
  {
      WKRetainPtr<WKStringRef> scriptWK = toWK(script);
      InjectedBundle::shared().queueNonLoadingScript(scriptWK.get());
  }
  
  void TestRunner::setHandlesAuthenticationChallenges(bool handlesAuthenticationChallenges)
  {
      WKRetainPtr<WKStringRef> messageName(AdoptWK, WKStringCreateWithUTF8CString("SetHandlesAuthenticationChallenge"));
      WKRetainPtr<WKBooleanRef> messageBody(AdoptWK, WKBooleanCreate(handlesAuthenticationChallenges));
      WKBundlePostMessage(InjectedBundle::shared().bundle(), messageName.get(), messageBody.get());
  }
  
  void TestRunner::setAuthenticationUsername(JSStringRef username)
  {
      WKRetainPtr<WKStringRef> messageName(AdoptWK, WKStringCreateWithUTF8CString("SetAuthenticationUsername"));
      WKRetainPtr<WKStringRef> messageBody(AdoptWK, WKStringCreateWithJSString(username));
      WKBundlePostMessage(InjectedBundle::shared().bundle(), messageName.get(), messageBody.get());
  }
  
  void TestRunner::setAuthenticationPassword(JSStringRef password)
  {
      WKRetainPtr<WKStringRef> messageName(AdoptWK, WKStringCreateWithUTF8CString("SetAuthenticationPassword"));
      WKRetainPtr<WKStringRef> messageBody(AdoptWK, WKStringCreateWithJSString(password));
      WKBundlePostMessage(InjectedBundle::shared().bundle(), messageName.get(), messageBody.get());
  }
  
  bool TestRunner::secureEventInputIsEnabled() const
  {
      WKRetainPtr<WKStringRef> messageName(AdoptWK, WKStringCreateWithUTF8CString("SecureEventInputIsEnabled"));
      WKTypeRef returnData = 0;
  
      WKBundlePostSynchronousMessage(InjectedBundle::shared().bundle(), messageName.get(), 0, &returnData);
      return WKBooleanGetValue(static_cast<WKBooleanRef>(returnData));
  }
  
  void TestRunner::setBlockAllPlugins(bool shouldBlock)
  {
      WKRetainPtr<WKStringRef> messageName(AdoptWK, WKStringCreateWithUTF8CString("SetBlockAllPlugins"));
      WKRetainPtr<WKBooleanRef> messageBody(AdoptWK, WKBooleanCreate(shouldBlock));
      WKBundlePostMessage(InjectedBundle::shared().bundle(), messageName.get(), messageBody.get());
  }
  
  } // namespace WTR
  