1/* 2 * Copyright (C) 2007, 2008, 2009 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 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 14 * its contributors may be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29#import "config.h" 30#import "DumpRenderTree.h" 31#import "LayoutTestController.h" 32 33#import "EditingDelegate.h" 34#import "PolicyDelegate.h" 35#import "WorkQueue.h" 36#import "WorkQueueItem.h" 37#import <Foundation/Foundation.h> 38#import <JavaScriptCore/JSRetainPtr.h> 39#import <JavaScriptCore/JSStringRef.h> 40#import <JavaScriptCore/JSStringRefCF.h> 41#import <WebKit/DOMDocument.h> 42#import <WebKit/DOMElement.h> 43#import <WebKit/WebApplicationCache.h> 44#import <WebKit/WebBackForwardList.h> 45#import <WebKit/WebDatabaseManagerPrivate.h> 46#import <WebKit/WebDataSource.h> 47#import <WebKit/WebFrame.h> 48#import <WebKit/WebFrameViewPrivate.h> 49#import <WebKit/WebIconDatabasePrivate.h> 50#import <WebKit/WebHTMLRepresentation.h> 51#import <WebKit/WebHTMLViewPrivate.h> 52#import <WebKit/WebHistory.h> 53#import <WebKit/WebHistoryPrivate.h> 54#import <WebKit/WebInspector.h> 55#import <WebKit/WebNSURLExtras.h> 56#import <WebKit/WebPreferences.h> 57#import <WebKit/WebPreferencesPrivate.h> 58#import <WebKit/WebSecurityOriginPrivate.h> 59#import <WebKit/WebTypesInternal.h> 60#import <WebKit/WebView.h> 61#import <WebKit/WebViewPrivate.h> 62#import <wtf/RetainPtr.h> 63 64@interface CommandValidationTarget : NSObject <NSValidatedUserInterfaceItem> 65{ 66 SEL _action; 67} 68- (id)initWithAction:(SEL)action; 69@end 70 71@implementation CommandValidationTarget 72 73- (id)initWithAction:(SEL)action 74{ 75 self = [super init]; 76 if (!self) 77 return nil; 78 79 _action = action; 80 return self; 81} 82 83- (SEL)action 84{ 85 return _action; 86} 87 88- (NSInteger)tag 89{ 90 return 0; 91} 92 93@end 94 95LayoutTestController::~LayoutTestController() 96{ 97} 98 99void LayoutTestController::addDisallowedURL(JSStringRef url) 100{ 101 RetainPtr<CFStringRef> urlCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, url)); 102 103 if (!disallowedURLs) 104 disallowedURLs = CFSetCreateMutable(kCFAllocatorDefault, 0, NULL); 105 106 // Canonicalize the URL 107 NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:(NSString *)urlCF.get()]]; 108 request = [NSURLProtocol canonicalRequestForRequest:request]; 109 110 CFSetAddValue(disallowedURLs, [request URL]); 111} 112 113void LayoutTestController::clearAllDatabases() 114{ 115 [[WebDatabaseManager sharedWebDatabaseManager] deleteAllDatabases]; 116} 117 118void LayoutTestController::clearBackForwardList() 119{ 120 WebBackForwardList *backForwardList = [[mainFrame webView] backForwardList]; 121 WebHistoryItem *item = [[backForwardList currentItem] retain]; 122 123 // We clear the history by setting the back/forward list's capacity to 0 124 // then restoring it back and adding back the current item. 125 int capacity = [backForwardList capacity]; 126 [backForwardList setCapacity:0]; 127 [backForwardList setCapacity:capacity]; 128 [backForwardList addItem:item]; 129 [backForwardList goToItem:item]; 130 [item release]; 131} 132 133JSStringRef LayoutTestController::copyDecodedHostName(JSStringRef name) 134{ 135 RetainPtr<CFStringRef> nameCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, name)); 136 NSString *nameNS = (NSString *)nameCF.get(); 137 return JSStringCreateWithCFString((CFStringRef)[nameNS _web_decodeHostName]); 138} 139 140JSStringRef LayoutTestController::copyEncodedHostName(JSStringRef name) 141{ 142 RetainPtr<CFStringRef> nameCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, name)); 143 NSString *nameNS = (NSString *)nameCF.get(); 144 return JSStringCreateWithCFString((CFStringRef)[nameNS _web_encodeHostName]); 145} 146 147void LayoutTestController::display() 148{ 149 displayWebView(); 150} 151 152void LayoutTestController::keepWebHistory() 153{ 154 if (![WebHistory optionalSharedHistory]) { 155 WebHistory *history = [[WebHistory alloc] init]; 156 [WebHistory setOptionalSharedHistory:history]; 157 [history release]; 158 } 159} 160 161size_t LayoutTestController::webHistoryItemCount() 162{ 163 return [[[WebHistory optionalSharedHistory] allItems] count]; 164} 165 166void LayoutTestController::notifyDone() 167{ 168 if (m_waitToDump && !topLoadingFrame && !WorkQueue::shared()->count()) 169 dump(); 170 m_waitToDump = false; 171} 172 173JSStringRef LayoutTestController::pathToLocalResource(JSContextRef context, JSStringRef url) 174{ 175 return JSStringRetain(url); // Do nothing on mac. 176} 177 178void LayoutTestController::queueLoad(JSStringRef url, JSStringRef target) 179{ 180 RetainPtr<CFStringRef> urlCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, url)); 181 NSString *urlNS = (NSString *)urlCF.get(); 182 183 NSURL *nsurl = [NSURL URLWithString:urlNS relativeToURL:[[[mainFrame dataSource] response] URL]]; 184 NSString* nsurlString = [nsurl absoluteString]; 185 186 JSRetainPtr<JSStringRef> absoluteURL(Adopt, JSStringCreateWithUTF8CString([nsurlString UTF8String])); 187 WorkQueue::shared()->queue(new LoadItem(absoluteURL.get(), target)); 188} 189 190void LayoutTestController::setAcceptsEditing(bool newAcceptsEditing) 191{ 192 [(EditingDelegate *)[[mainFrame webView] editingDelegate] setAcceptsEditing:newAcceptsEditing]; 193} 194 195void LayoutTestController::setAppCacheMaximumSize(unsigned long long size) 196{ 197 [WebApplicationCache setMaximumSize:size]; 198} 199 200void LayoutTestController::setAuthorAndUserStylesEnabled(bool flag) 201{ 202 [[[mainFrame webView] preferences] setAuthorAndUserStylesEnabled:flag]; 203} 204 205void LayoutTestController::setCustomPolicyDelegate(bool setDelegate, bool permissive) 206{ 207 if (setDelegate) { 208 [policyDelegate setPermissive:permissive]; 209 [[mainFrame webView] setPolicyDelegate:policyDelegate]; 210 } else 211 [[mainFrame webView] setPolicyDelegate:nil]; 212} 213 214void LayoutTestController::setDatabaseQuota(unsigned long long quota) 215{ 216 WebSecurityOrigin *origin = [[WebSecurityOrigin alloc] initWithURL:[NSURL URLWithString:@"file:///"]]; 217 [origin setQuota:quota]; 218 [origin release]; 219} 220 221void LayoutTestController::setIconDatabaseEnabled(bool iconDatabaseEnabled) 222{ 223 // FIXME: Workaround <rdar://problem/6480108> 224 static WebIconDatabase* sharedWebIconDatabase = NULL; 225 if (!sharedWebIconDatabase) { 226 if (!iconDatabaseEnabled) 227 return; 228 sharedWebIconDatabase = [WebIconDatabase sharedIconDatabase]; 229 if ([sharedWebIconDatabase isEnabled] == iconDatabaseEnabled) 230 return; 231 } 232 [sharedWebIconDatabase setEnabled:iconDatabaseEnabled]; 233} 234 235void LayoutTestController::setJavaScriptProfilingEnabled(bool profilingEnabled) 236{ 237 [[[mainFrame webView] preferences] setDeveloperExtrasEnabled:profilingEnabled]; 238 [[[mainFrame webView] inspector] setJavaScriptProfilingEnabled:profilingEnabled]; 239} 240 241void LayoutTestController::setMainFrameIsFirstResponder(bool flag) 242{ 243 NSView *documentView = [[mainFrame frameView] documentView]; 244 245 NSResponder *firstResponder = flag ? documentView : nil; 246 [[[mainFrame webView] window] makeFirstResponder:firstResponder]; 247} 248 249void LayoutTestController::setPrivateBrowsingEnabled(bool privateBrowsingEnabled) 250{ 251 [[[mainFrame webView] preferences] setPrivateBrowsingEnabled:privateBrowsingEnabled]; 252} 253 254void LayoutTestController::setXSSAuditorEnabled(bool enabled) 255{ 256 [[[mainFrame webView] preferences] setXSSAuditorEnabled:enabled]; 257} 258 259void LayoutTestController::setPopupBlockingEnabled(bool popupBlockingEnabled) 260{ 261 [[[mainFrame webView] preferences] setJavaScriptCanOpenWindowsAutomatically:!popupBlockingEnabled]; 262} 263 264void LayoutTestController::setTabKeyCyclesThroughElements(bool cycles) 265{ 266 [[mainFrame webView] setTabKeyCyclesThroughElements:cycles]; 267} 268 269void LayoutTestController::setUseDashboardCompatibilityMode(bool flag) 270{ 271 [[mainFrame webView] _setDashboardBehavior:WebDashboardBehaviorUseBackwardCompatibilityMode to:flag]; 272} 273 274void LayoutTestController::setUserStyleSheetEnabled(bool flag) 275{ 276 [[WebPreferences standardPreferences] setUserStyleSheetEnabled:flag]; 277} 278 279void LayoutTestController::setUserStyleSheetLocation(JSStringRef path) 280{ 281 RetainPtr<CFStringRef> pathCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, path)); 282 NSURL *url = [NSURL URLWithString:(NSString *)pathCF.get()]; 283 [[WebPreferences standardPreferences] setUserStyleSheetLocation:url]; 284} 285 286void LayoutTestController::disableImageLoading() 287{ 288 [[WebPreferences standardPreferences] setLoadsImagesAutomatically:NO]; 289} 290 291void LayoutTestController::dispatchPendingLoadRequests() 292{ 293 [[mainFrame webView] _dispatchPendingLoadRequests]; 294} 295 296void LayoutTestController::setPersistentUserStyleSheetLocation(JSStringRef jsURL) 297{ 298 RetainPtr<CFStringRef> urlString(AdoptCF, JSStringCopyCFString(0, jsURL)); 299 ::setPersistentUserStyleSheetLocation(urlString.get()); 300} 301 302void LayoutTestController::clearPersistentUserStyleSheet() 303{ 304 ::setPersistentUserStyleSheetLocation(0); 305} 306 307void LayoutTestController::setWindowIsKey(bool windowIsKey) 308{ 309 m_windowIsKey = windowIsKey; 310 [[mainFrame webView] _updateActiveState]; 311} 312 313void LayoutTestController::setSmartInsertDeleteEnabled(bool flag) 314{ 315 [[mainFrame webView] setSmartInsertDeleteEnabled:flag]; 316} 317 318void LayoutTestController::setSelectTrailingWhitespaceEnabled(bool flag) 319{ 320 [[mainFrame webView] setSelectTrailingWhitespaceEnabled:flag]; 321} 322 323static const CFTimeInterval waitToDumpWatchdogInterval = 10.0; 324 325static void waitUntilDoneWatchdogFired(CFRunLoopTimerRef timer, void* info) 326{ 327 const char* message = "FAIL: Timed out waiting for notifyDone to be called\n"; 328 fprintf(stderr, "%s", message); 329 fprintf(stdout, "%s", message); 330 dump(); 331} 332 333void LayoutTestController::setWaitToDump(bool waitUntilDone) 334{ 335 m_waitToDump = waitUntilDone; 336 if (m_waitToDump && !waitToDumpWatchdog) { 337 waitToDumpWatchdog = CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + waitToDumpWatchdogInterval, 0, 0, 0, waitUntilDoneWatchdogFired, NULL); 338 CFRunLoopAddTimer(CFRunLoopGetCurrent(), waitToDumpWatchdog, kCFRunLoopCommonModes); 339 } 340} 341 342int LayoutTestController::windowCount() 343{ 344 return CFArrayGetCount(openWindowsRef); 345} 346 347bool LayoutTestController::elementDoesAutoCompleteForElementWithId(JSStringRef id) 348{ 349 RetainPtr<CFStringRef> idCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, id)); 350 NSString *idNS = (NSString *)idCF.get(); 351 352 DOMElement *element = [[mainFrame DOMDocument] getElementById:idNS]; 353 id rep = [[mainFrame dataSource] representation]; 354 355 if ([rep class] == [WebHTMLRepresentation class]) 356 return [(WebHTMLRepresentation *)rep elementDoesAutoComplete:element]; 357 358 return false; 359} 360 361void LayoutTestController::execCommand(JSStringRef name, JSStringRef value) 362{ 363 RetainPtr<CFStringRef> nameCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, name)); 364 NSString *nameNS = (NSString *)nameCF.get(); 365 366 RetainPtr<CFStringRef> valueCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, value)); 367 NSString *valueNS = (NSString *)valueCF.get(); 368 369 [[mainFrame webView] _executeCoreCommandByName:nameNS value:valueNS]; 370} 371 372void LayoutTestController::setCacheModel(int cacheModel) 373{ 374 [[WebPreferences standardPreferences] setCacheModel:cacheModel]; 375} 376 377bool LayoutTestController::isCommandEnabled(JSStringRef name) 378{ 379 RetainPtr<CFStringRef> nameCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, name)); 380 NSString *nameNS = reinterpret_cast<const NSString *>(nameCF.get()); 381 382 // Accept command strings with capital letters for first letter without trailing colon. 383 if (![nameNS hasSuffix:@":"] && [nameNS length]) { 384 nameNS = [[[[nameNS substringToIndex:1] lowercaseString] 385 stringByAppendingString:[nameNS substringFromIndex:1]] 386 stringByAppendingString:@":"]; 387 } 388 389 SEL selector = NSSelectorFromString(nameNS); 390 RetainPtr<CommandValidationTarget> target(AdoptNS, [[CommandValidationTarget alloc] initWithAction:selector]); 391 id validator = [NSApp targetForAction:selector to:[mainFrame webView] from:target.get()]; 392 if (!validator) 393 return false; 394 if (![validator respondsToSelector:selector]) 395 return false; 396 if (![validator respondsToSelector:@selector(validateUserInterfaceItem:)]) 397 return true; 398 return [validator validateUserInterfaceItem:target.get()]; 399} 400 401bool LayoutTestController::pauseAnimationAtTimeOnElementWithId(JSStringRef animationName, double time, JSStringRef elementId) 402{ 403 RetainPtr<CFStringRef> idCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, elementId)); 404 NSString *idNS = (NSString *)idCF.get(); 405 RetainPtr<CFStringRef> nameCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, animationName)); 406 NSString *nameNS = (NSString *)nameCF.get(); 407 408 return [mainFrame _pauseAnimation:nameNS onNode:[[mainFrame DOMDocument] getElementById:idNS] atTime:time]; 409} 410 411bool LayoutTestController::pauseTransitionAtTimeOnElementWithId(JSStringRef propertyName, double time, JSStringRef elementId) 412{ 413 RetainPtr<CFStringRef> idCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, elementId)); 414 NSString *idNS = (NSString *)idCF.get(); 415 RetainPtr<CFStringRef> nameCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, propertyName)); 416 NSString *nameNS = (NSString *)nameCF.get(); 417 418 return [mainFrame _pauseTransitionOfProperty:nameNS onNode:[[mainFrame DOMDocument] getElementById:idNS] atTime:time]; 419} 420 421unsigned LayoutTestController::numberOfActiveAnimations() const 422{ 423 return [mainFrame _numberOfActiveAnimations]; 424} 425 426void LayoutTestController::waitForPolicyDelegate() 427{ 428 setWaitToDump(true); 429 [policyDelegate setControllerToNotifyDone:this]; 430 [[mainFrame webView] setPolicyDelegate:policyDelegate]; 431} 432