Home | History | Annotate | Download | only in InjectedBundle
      1 /*
      2  * Copyright (C) 2010 Apple Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
     14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
     15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
     17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
     23  * THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #include "config.h"
     27 #include "LayoutTestController.h"
     28 
     29 #include "InjectedBundle.h"
     30 #include "InjectedBundlePage.h"
     31 #include "JSLayoutTestController.h"
     32 #include "PlatformWebView.h"
     33 #include "StringFunctions.h"
     34 #include "TestController.h"
     35 #include <WebKit2/WKBundleBackForwardList.h>
     36 #include <WebKit2/WKBundleFrame.h>
     37 #include <WebKit2/WKBundleFramePrivate.h>
     38 #include <WebKit2/WKBundleInspector.h>
     39 #include <WebKit2/WKBundleNodeHandlePrivate.h>
     40 #include <WebKit2/WKBundlePagePrivate.h>
     41 #include <WebKit2/WKBundlePrivate.h>
     42 #include <WebKit2/WKBundleScriptWorld.h>
     43 #include <WebKit2/WKRetainPtr.h>
     44 #include <WebKit2/WebKit2.h>
     45 #include <wtf/HashMap.h>
     46 
     47 namespace WTR {
     48 
     49 // This is lower than DumpRenderTree's timeout, to make it easier to work through the failures
     50 // Eventually it should be changed to match.
     51 const double LayoutTestController::waitToDumpWatchdogTimerInterval = 6;
     52 
     53 static JSValueRef propertyValue(JSContextRef context, JSObjectRef object, const char* propertyName)
     54 {
     55     if (!object)
     56         return 0;
     57     JSRetainPtr<JSStringRef> propertyNameString(Adopt, JSStringCreateWithUTF8CString(propertyName));
     58     JSValueRef exception;
     59     return JSObjectGetProperty(context, object, propertyNameString.get(), &exception);
     60 }
     61 
     62 static JSObjectRef propertyObject(JSContextRef context, JSObjectRef object, const char* propertyName)
     63 {
     64     JSValueRef value = propertyValue(context, object, propertyName);
     65     if (!value || !JSValueIsObject(context, value))
     66         return 0;
     67     return const_cast<JSObjectRef>(value);
     68 }
     69 
     70 static JSObjectRef getElementById(WKBundleFrameRef frame, JSStringRef elementId)
     71 {
     72     JSContextRef context = WKBundleFrameGetJavaScriptContext(frame);
     73     JSObjectRef document = propertyObject(context, JSContextGetGlobalObject(context), "document");
     74     if (!document)
     75         return 0;
     76     JSValueRef getElementById = propertyObject(context, document, "getElementById");
     77     if (!getElementById || !JSValueIsObject(context, getElementById))
     78         return 0;
     79     JSValueRef elementIdValue = JSValueMakeString(context, elementId);
     80     JSValueRef exception;
     81     JSValueRef element = JSObjectCallAsFunction(context, const_cast<JSObjectRef>(getElementById), document, 1, &elementIdValue, &exception);
     82     if (!element || !JSValueIsObject(context, element))
     83         return 0;
     84     return const_cast<JSObjectRef>(element);
     85 }
     86 
     87 PassRefPtr<LayoutTestController> LayoutTestController::create()
     88 {
     89     return adoptRef(new LayoutTestController);
     90 }
     91 
     92 LayoutTestController::LayoutTestController()
     93     : m_whatToDump(RenderTree)
     94     , m_shouldDumpAllFrameScrollPositions(false)
     95     , m_shouldDumpBackForwardListsForAllWindows(false)
     96     , m_shouldAllowEditing(true)
     97     , m_shouldCloseExtraWindows(false)
     98     , m_dumpEditingCallbacks(false)
     99     , m_dumpStatusCallbacks(false)
    100     , m_dumpTitleChanges(false)
    101     , m_dumpPixels(true)
    102     , m_dumpFullScreenCallbacks(false)
    103     , m_waitToDump(false)
    104     , m_testRepaint(false)
    105     , m_testRepaintSweepHorizontally(false)
    106     , m_willSendRequestReturnsNull(false)
    107 {
    108     platformInitialize();
    109 }
    110 
    111 LayoutTestController::~LayoutTestController()
    112 {
    113 }
    114 
    115 JSClassRef LayoutTestController::wrapperClass()
    116 {
    117     return JSLayoutTestController::layoutTestControllerClass();
    118 }
    119 
    120 void LayoutTestController::display()
    121 {
    122     // FIXME: actually implement, once we want pixel tests
    123 }
    124 
    125 void LayoutTestController::dumpAsText()
    126 {
    127     m_whatToDump = MainFrameText;
    128     m_dumpPixels = false;
    129 }
    130 
    131 void LayoutTestController::waitUntilDone()
    132 {
    133     m_waitToDump = true;
    134     initializeWaitToDumpWatchdogTimerIfNeeded();
    135 }
    136 
    137 void LayoutTestController::waitToDumpWatchdogTimerFired()
    138 {
    139     invalidateWaitToDumpWatchdogTimer();
    140     const char* message = "FAIL: Timed out waiting for notifyDone to be called\n";
    141     InjectedBundle::shared().os() << message << "\n";
    142     InjectedBundle::shared().done();
    143 }
    144 
    145 void LayoutTestController::notifyDone()
    146 {
    147     if (!InjectedBundle::shared().isTestRunning())
    148         return;
    149 
    150     if (m_waitToDump && !InjectedBundle::shared().topLoadingFrame())
    151         InjectedBundle::shared().page()->dump();
    152 
    153     m_waitToDump = false;
    154 }
    155 
    156 unsigned LayoutTestController::numberOfActiveAnimations() const
    157 {
    158     // FIXME: Is it OK this works only for the main frame?
    159     // FIXME: If this is needed only for the main frame, then why is the function on WKBundleFrame instead of WKBundlePage?
    160     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
    161     return WKBundleFrameGetNumberOfActiveAnimations(mainFrame);
    162 }
    163 
    164 bool LayoutTestController::pauseAnimationAtTimeOnElementWithId(JSStringRef animationName, double time, JSStringRef elementId)
    165 {
    166     // FIXME: Is it OK this works only for the main frame?
    167     // FIXME: If this is needed only for the main frame, then why is the function on WKBundleFrame instead of WKBundlePage?
    168     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
    169     return WKBundleFramePauseAnimationOnElementWithId(mainFrame, toWK(animationName).get(), toWK(elementId).get(), time);
    170 }
    171 
    172 void LayoutTestController::suspendAnimations()
    173 {
    174     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
    175     WKBundleFrameSuspendAnimations(mainFrame);
    176 }
    177 
    178 void LayoutTestController::resumeAnimations()
    179 {
    180     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
    181     WKBundleFrameResumeAnimations(mainFrame);
    182 }
    183 
    184 JSRetainPtr<JSStringRef> LayoutTestController::layerTreeAsText() const
    185 {
    186     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
    187     WKRetainPtr<WKStringRef> text(AdoptWK, WKBundleFrameCopyLayerTreeAsText(mainFrame));
    188     return toJS(text);
    189 }
    190 
    191 void LayoutTestController::addUserScript(JSStringRef source, bool runAtStart, bool allFrames)
    192 {
    193     WKRetainPtr<WKStringRef> sourceWK = toWK(source);
    194     WKRetainPtr<WKBundleScriptWorldRef> scriptWorld(AdoptWK, WKBundleScriptWorldCreateWorld());
    195 
    196     WKBundleAddUserScript(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), scriptWorld.get(), sourceWK.get(), 0, 0, 0,
    197         (runAtStart ? kWKInjectAtDocumentStart : kWKInjectAtDocumentEnd),
    198         (allFrames ? kWKInjectInAllFrames : kWKInjectInTopFrameOnly));
    199 }
    200 
    201 void LayoutTestController::addUserStyleSheet(JSStringRef source, bool allFrames)
    202 {
    203     WKRetainPtr<WKStringRef> sourceWK = toWK(source);
    204     WKRetainPtr<WKBundleScriptWorldRef> scriptWorld(AdoptWK, WKBundleScriptWorldCreateWorld());
    205 
    206     WKBundleAddUserStyleSheet(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), scriptWorld.get(), sourceWK.get(), 0, 0, 0,
    207         (allFrames ? kWKInjectInAllFrames : kWKInjectInTopFrameOnly));
    208 }
    209 
    210 void LayoutTestController::keepWebHistory()
    211 {
    212     WKBundleSetShouldTrackVisitedLinks(InjectedBundle::shared().bundle(), true);
    213 }
    214 
    215 JSValueRef LayoutTestController::computedStyleIncludingVisitedInfo(JSValueRef element)
    216 {
    217     // FIXME: Is it OK this works only for the main frame?
    218     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
    219     JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
    220     if (!JSValueIsObject(context, element))
    221         return JSValueMakeUndefined(context);
    222     JSValueRef value = WKBundleFrameGetComputedStyleIncludingVisitedInfo(mainFrame, const_cast<JSObjectRef>(element));
    223     if (!value)
    224         return JSValueMakeUndefined(context);
    225     return value;
    226 }
    227 
    228 JSRetainPtr<JSStringRef> LayoutTestController::counterValueForElementById(JSStringRef elementId)
    229 {
    230     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
    231     JSObjectRef element = getElementById(mainFrame, elementId);
    232     if (!element)
    233         return 0;
    234     WKRetainPtr<WKStringRef> value(AdoptWK, WKBundleFrameCopyCounterValue(mainFrame, const_cast<JSObjectRef>(element)));
    235     return toJS(value);
    236 }
    237 
    238 JSRetainPtr<JSStringRef> LayoutTestController::markerTextForListItem(JSValueRef element)
    239 {
    240     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
    241     JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
    242     if (!element || !JSValueIsObject(context, element))
    243         return 0;
    244     WKRetainPtr<WKStringRef> text(AdoptWK, WKBundleFrameCopyMarkerText(mainFrame, const_cast<JSObjectRef>(element)));
    245     if (WKStringIsEmpty(text.get()))
    246         return 0;
    247     return toJS(text);
    248 }
    249 
    250 void LayoutTestController::execCommand(JSStringRef name, JSStringRef argument)
    251 {
    252     WKBundlePageExecuteEditingCommand(InjectedBundle::shared().page()->page(), toWK(name).get(), toWK(argument).get());
    253 }
    254 
    255 bool LayoutTestController::findString(JSStringRef target, JSValueRef optionsArrayAsValue)
    256 {
    257     WKFindOptions options = 0;
    258 
    259     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
    260     JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
    261     JSRetainPtr<JSStringRef> lengthPropertyName(Adopt, JSStringCreateWithUTF8CString("length"));
    262     JSObjectRef optionsArray = JSValueToObject(context, optionsArrayAsValue, 0);
    263     JSValueRef lengthValue = JSObjectGetProperty(context, optionsArray, lengthPropertyName.get(), 0);
    264     if (!JSValueIsNumber(context, lengthValue))
    265         return false;
    266 
    267     size_t length = static_cast<size_t>(JSValueToNumber(context, lengthValue, 0));
    268     for (size_t i = 0; i < length; ++i) {
    269         JSValueRef value = JSObjectGetPropertyAtIndex(context, optionsArray, i, 0);
    270         if (!JSValueIsString(context, value))
    271             continue;
    272 
    273         JSRetainPtr<JSStringRef> optionName(Adopt, JSValueToStringCopy(context, value, 0));
    274 
    275         if (JSStringIsEqualToUTF8CString(optionName.get(), "CaseInsensitive"))
    276             options |= kWKFindOptionsCaseInsensitive;
    277         else if (JSStringIsEqualToUTF8CString(optionName.get(), "AtWordStarts"))
    278             options |= kWKFindOptionsAtWordStarts;
    279         else if (JSStringIsEqualToUTF8CString(optionName.get(), "TreatMedialCapitalAsWordStart"))
    280             options |= kWKFindOptionsTreatMedialCapitalAsWordStart;
    281         else if (JSStringIsEqualToUTF8CString(optionName.get(), "Backwards"))
    282             options |= kWKFindOptionsBackwards;
    283         else if (JSStringIsEqualToUTF8CString(optionName.get(), "WrapAround"))
    284             options |= kWKFindOptionsWrapAround;
    285         else if (JSStringIsEqualToUTF8CString(optionName.get(), "StartInSelection")) {
    286             // FIXME: No kWKFindOptionsStartInSelection.
    287         }
    288     }
    289 
    290     return WKBundlePageFindString(InjectedBundle::shared().page()->page(), toWK(target).get(), options);
    291 }
    292 
    293 void LayoutTestController::clearAllDatabases()
    294 {
    295     WKBundleClearAllDatabases(InjectedBundle::shared().bundle());
    296 }
    297 
    298 void LayoutTestController::setDatabaseQuota(uint64_t quota)
    299 {
    300     return WKBundleSetDatabaseQuota(InjectedBundle::shared().bundle(), quota);
    301 }
    302 
    303 bool LayoutTestController::isCommandEnabled(JSStringRef name)
    304 {
    305     return WKBundlePageIsEditingCommandEnabled(InjectedBundle::shared().page()->page(), toWK(name).get());
    306 }
    307 
    308 void LayoutTestController::setCanOpenWindows(bool)
    309 {
    310     // It's not clear if or why any tests require opening windows be forbidden.
    311     // For now, just ignore this setting, and if we find later it's needed we can add it.
    312 }
    313 
    314 void LayoutTestController::setXSSAuditorEnabled(bool enabled)
    315 {
    316     WKBundleOverrideXSSAuditorEnabledForTestRunner(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), true);
    317 }
    318 
    319 void LayoutTestController::setAllowUniversalAccessFromFileURLs(bool enabled)
    320 {
    321     WKBundleOverrideAllowUniversalAccessFromFileURLsForTestRunner(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), enabled);
    322 }
    323 
    324 void LayoutTestController::setAllowFileAccessFromFileURLs(bool enabled)
    325 {
    326     WKBundleSetAllowFileAccessFromFileURLs(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), enabled);
    327 }
    328 
    329 int LayoutTestController::numberOfPages(double pageWidthInPixels, double pageHeightInPixels)
    330 {
    331     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
    332     return WKBundleNumberOfPages(InjectedBundle::shared().bundle(), mainFrame, pageWidthInPixels, pageHeightInPixels);
    333 }
    334 
    335 int LayoutTestController::pageNumberForElementById(JSStringRef id, double pageWidthInPixels, double pageHeightInPixels)
    336 {
    337     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
    338     return WKBundlePageNumberForElementById(InjectedBundle::shared().bundle(), mainFrame, toWK(id).get(), pageWidthInPixels, pageHeightInPixels);
    339 }
    340 
    341 JSRetainPtr<JSStringRef> LayoutTestController::pageSizeAndMarginsInPixels(int pageIndex, int width, int height, int marginTop, int marginRight, int marginBottom, int marginLeft)
    342 {
    343     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
    344     return toJS(WKBundlePageSizeAndMarginsInPixels(InjectedBundle::shared().bundle(), mainFrame, pageIndex, width, height, marginTop, marginRight, marginBottom, marginLeft));
    345 }
    346 
    347 bool LayoutTestController::isPageBoxVisible(int pageIndex)
    348 {
    349     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
    350     return WKBundleIsPageBoxVisible(InjectedBundle::shared().bundle(), mainFrame, pageIndex);
    351 }
    352 
    353 unsigned LayoutTestController::windowCount()
    354 {
    355     return InjectedBundle::shared().pageCount();
    356 }
    357 
    358 JSValueRef LayoutTestController::shadowRoot(JSValueRef element)
    359 {
    360     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
    361     JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
    362 
    363     if (!element || !JSValueIsObject(context, element))
    364         return JSValueMakeNull(context);
    365 
    366     WKRetainPtr<WKBundleNodeHandleRef> domElement = adoptWK(WKBundleNodeHandleCreate(context, const_cast<JSObjectRef>(element)));
    367     if (!domElement)
    368         return JSValueMakeNull(context);
    369 
    370     WKRetainPtr<WKBundleNodeHandleRef> shadowRootDOMElement = adoptWK(WKBundleNodeHandleCopyElementShadowRoot(domElement.get()));
    371     if (!shadowRootDOMElement)
    372         return JSValueMakeNull(context);
    373 
    374     return WKBundleFrameGetJavaScriptWrapperForNodeForWorld(mainFrame, shadowRootDOMElement.get(), WKBundleScriptWorldNormalWorld());
    375 }
    376 
    377 void LayoutTestController::clearBackForwardList()
    378 {
    379     WKBundleBackForwardListClear(WKBundlePageGetBackForwardList(InjectedBundle::shared().page()->page()));
    380 }
    381 
    382 // Object Creation
    383 
    384 void LayoutTestController::makeWindowObject(JSContextRef context, JSObjectRef windowObject, JSValueRef* exception)
    385 {
    386     setProperty(context, windowObject, "layoutTestController", this, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete, exception);
    387 }
    388 
    389 void LayoutTestController::showWebInspector()
    390 {
    391     WKBundleInspectorShow(WKBundlePageGetInspector(InjectedBundle::shared().page()->page()));
    392 }
    393 
    394 void LayoutTestController::closeWebInspector()
    395 {
    396     WKBundleInspectorClose(WKBundlePageGetInspector(InjectedBundle::shared().page()->page()));
    397 }
    398 
    399 void LayoutTestController::evaluateInWebInspector(long callID, JSStringRef script)
    400 {
    401     WKRetainPtr<WKStringRef> scriptWK = toWK(script);
    402     WKBundleInspectorEvaluateScriptForTest(WKBundlePageGetInspector(InjectedBundle::shared().page()->page()), callID, scriptWK.get());
    403 }
    404 
    405 void LayoutTestController::setTimelineProfilingEnabled(bool enabled)
    406 {
    407     WKBundleInspectorSetPageProfilingEnabled(WKBundlePageGetInspector(InjectedBundle::shared().page()->page()), enabled);
    408 }
    409 
    410 typedef WTF::HashMap<unsigned, WKRetainPtr<WKBundleScriptWorldRef> > WorldMap;
    411 static WorldMap& worldMap()
    412 {
    413     static WorldMap& map = *new WorldMap;
    414     return map;
    415 }
    416 
    417 unsigned LayoutTestController::worldIDForWorld(WKBundleScriptWorldRef world)
    418 {
    419     WorldMap::const_iterator end = worldMap().end();
    420     for (WorldMap::const_iterator it = worldMap().begin(); it != end; ++it) {
    421         if (it->second == world)
    422             return it->first;
    423     }
    424 
    425     return 0;
    426 }
    427 
    428 void LayoutTestController::evaluateScriptInIsolatedWorld(JSContextRef context, unsigned worldID, JSStringRef script)
    429 {
    430     // A worldID of 0 always corresponds to a new world. Any other worldID corresponds to a world
    431     // that is created once and cached forever.
    432     WKRetainPtr<WKBundleScriptWorldRef> world;
    433     if (!worldID)
    434         world.adopt(WKBundleScriptWorldCreateWorld());
    435     else {
    436         WKRetainPtr<WKBundleScriptWorldRef>& worldSlot = worldMap().add(worldID, 0).first->second;
    437         if (!worldSlot)
    438             worldSlot.adopt(WKBundleScriptWorldCreateWorld());
    439         world = worldSlot;
    440     }
    441 
    442     WKBundleFrameRef frame = WKBundleFrameForJavaScriptContext(context);
    443     if (!frame)
    444         frame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
    445 
    446     JSGlobalContextRef jsContext = WKBundleFrameGetJavaScriptContextForWorld(frame, world.get());
    447     JSEvaluateScript(jsContext, script, 0, 0, 0, 0);
    448 }
    449 
    450 void LayoutTestController::setPOSIXLocale(JSStringRef locale)
    451 {
    452     char localeBuf[32];
    453     JSStringGetUTF8CString(locale, localeBuf, sizeof(localeBuf));
    454     setlocale(LC_ALL, localeBuf);
    455 }
    456 
    457 } // namespace WTR
    458