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 47namespace 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. 51const double LayoutTestController::waitToDumpWatchdogTimerInterval = 6; 52 53static 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 62static 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 70static 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 87PassRefPtr<LayoutTestController> LayoutTestController::create() 88{ 89 return adoptRef(new LayoutTestController); 90} 91 92LayoutTestController::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 111LayoutTestController::~LayoutTestController() 112{ 113} 114 115JSClassRef LayoutTestController::wrapperClass() 116{ 117 return JSLayoutTestController::layoutTestControllerClass(); 118} 119 120void LayoutTestController::display() 121{ 122 // FIXME: actually implement, once we want pixel tests 123} 124 125void LayoutTestController::dumpAsText() 126{ 127 m_whatToDump = MainFrameText; 128 m_dumpPixels = false; 129} 130 131void LayoutTestController::waitUntilDone() 132{ 133 m_waitToDump = true; 134 initializeWaitToDumpWatchdogTimerIfNeeded(); 135} 136 137void 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 145void 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 156unsigned 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 164bool 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 172void LayoutTestController::suspendAnimations() 173{ 174 WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page()); 175 WKBundleFrameSuspendAnimations(mainFrame); 176} 177 178void LayoutTestController::resumeAnimations() 179{ 180 WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page()); 181 WKBundleFrameResumeAnimations(mainFrame); 182} 183 184JSRetainPtr<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 191void 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 201void 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 210void LayoutTestController::keepWebHistory() 211{ 212 WKBundleSetShouldTrackVisitedLinks(InjectedBundle::shared().bundle(), true); 213} 214 215JSValueRef 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 228JSRetainPtr<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 238JSRetainPtr<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 250void LayoutTestController::execCommand(JSStringRef name, JSStringRef argument) 251{ 252 WKBundlePageExecuteEditingCommand(InjectedBundle::shared().page()->page(), toWK(name).get(), toWK(argument).get()); 253} 254 255bool 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 293void LayoutTestController::clearAllDatabases() 294{ 295 WKBundleClearAllDatabases(InjectedBundle::shared().bundle()); 296} 297 298void LayoutTestController::setDatabaseQuota(uint64_t quota) 299{ 300 return WKBundleSetDatabaseQuota(InjectedBundle::shared().bundle(), quota); 301} 302 303bool LayoutTestController::isCommandEnabled(JSStringRef name) 304{ 305 return WKBundlePageIsEditingCommandEnabled(InjectedBundle::shared().page()->page(), toWK(name).get()); 306} 307 308void 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 314void LayoutTestController::setXSSAuditorEnabled(bool enabled) 315{ 316 WKBundleOverrideXSSAuditorEnabledForTestRunner(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), true); 317} 318 319void LayoutTestController::setAllowUniversalAccessFromFileURLs(bool enabled) 320{ 321 WKBundleOverrideAllowUniversalAccessFromFileURLsForTestRunner(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), enabled); 322} 323 324void LayoutTestController::setAllowFileAccessFromFileURLs(bool enabled) 325{ 326 WKBundleSetAllowFileAccessFromFileURLs(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), enabled); 327} 328 329int 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 335int 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 341JSRetainPtr<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 347bool LayoutTestController::isPageBoxVisible(int pageIndex) 348{ 349 WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page()); 350 return WKBundleIsPageBoxVisible(InjectedBundle::shared().bundle(), mainFrame, pageIndex); 351} 352 353unsigned LayoutTestController::windowCount() 354{ 355 return InjectedBundle::shared().pageCount(); 356} 357 358JSValueRef 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 377void LayoutTestController::clearBackForwardList() 378{ 379 WKBundleBackForwardListClear(WKBundlePageGetBackForwardList(InjectedBundle::shared().page()->page())); 380} 381 382// Object Creation 383 384void LayoutTestController::makeWindowObject(JSContextRef context, JSObjectRef windowObject, JSValueRef* exception) 385{ 386 setProperty(context, windowObject, "layoutTestController", this, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete, exception); 387} 388 389void LayoutTestController::showWebInspector() 390{ 391 WKBundleInspectorShow(WKBundlePageGetInspector(InjectedBundle::shared().page()->page())); 392} 393 394void LayoutTestController::closeWebInspector() 395{ 396 WKBundleInspectorClose(WKBundlePageGetInspector(InjectedBundle::shared().page()->page())); 397} 398 399void 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 405void LayoutTestController::setTimelineProfilingEnabled(bool enabled) 406{ 407 WKBundleInspectorSetPageProfilingEnabled(WKBundlePageGetInspector(InjectedBundle::shared().page()->page()), enabled); 408} 409 410typedef WTF::HashMap<unsigned, WKRetainPtr<WKBundleScriptWorldRef> > WorldMap; 411static WorldMap& worldMap() 412{ 413 static WorldMap& map = *new WorldMap; 414 return map; 415} 416 417unsigned 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 428void 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 450void 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