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