2011-01-18 Maciej Stachowiak <mjs@apple.com>
[WebKit.git] / Tools / DumpRenderTree / mac / LayoutTestControllerMac.mm
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 "MockGeolocationProvider.h"
35 #import "PolicyDelegate.h"
36 #import "UIDelegate.h"
37 #import "WorkQueue.h"
38 #import "WorkQueueItem.h"
39 #import <Foundation/Foundation.h>
40 #import <JavaScriptCore/JSRetainPtr.h>
41 #import <JavaScriptCore/JSStringRef.h>
42 #import <JavaScriptCore/JSStringRefCF.h>
43 #import <WebKit/DOMDocument.h>
44 #import <WebKit/DOMElement.h>
45 #import <WebKit/WebApplicationCache.h>
46 #import <WebKit/WebBackForwardList.h>
47 #import <WebKit/WebCoreStatistics.h>
48 #import <WebKit/WebDOMOperationsPrivate.h>
49 #import <WebKit/WebDataSource.h>
50 #import <WebKit/WebDatabaseManagerPrivate.h>
51 #import <WebKit/WebDeviceOrientation.h>
52 #import <WebKit/WebDeviceOrientationProviderMock.h>
53 #import <WebKit/WebFrame.h>
54 #import <WebKit/WebFrameViewPrivate.h>
55 #import <WebKit/WebGeolocationPosition.h>
56 #import <WebKit/WebHTMLRepresentation.h>
57 #import <WebKit/WebHTMLViewPrivate.h>
58 #import <WebKit/WebHistory.h>
59 #import <WebKit/WebHistoryPrivate.h>
60 #import <WebKit/WebIconDatabasePrivate.h>
61 #import <WebKit/WebInspectorPrivate.h>
62 #import <WebKit/WebNSURLExtras.h>
63 #import <WebKit/WebKitErrors.h>
64 #import <WebKit/WebPreferences.h>
65 #import <WebKit/WebPreferencesPrivate.h>
66 #import <WebKit/WebQuotaManager.h>
67 #import <WebKit/WebScriptWorld.h>
68 #import <WebKit/WebSecurityOriginPrivate.h>
69 #import <WebKit/WebTypesInternal.h>
70 #import <WebKit/WebView.h>
71 #import <WebKit/WebViewPrivate.h>
72 #import <WebKit/WebWorkersPrivate.h>
73 #import <wtf/CurrentTime.h>
74 #import <wtf/HashMap.h>
75 #import <wtf/RetainPtr.h>
76
77 @interface CommandValidationTarget : NSObject <NSValidatedUserInterfaceItem>
78 {
79     SEL _action;
80 }
81 - (id)initWithAction:(SEL)action;
82 @end
83
84 @implementation CommandValidationTarget
85
86 - (id)initWithAction:(SEL)action
87 {
88     self = [super init];
89     if (!self)
90         return nil;
91
92     _action = action;
93     return self;
94 }
95
96 - (SEL)action
97 {
98     return _action;
99 }
100
101 - (NSInteger)tag
102 {
103     return 0;
104 }
105
106 @end
107
108 LayoutTestController::~LayoutTestController()
109 {
110 }
111
112 void LayoutTestController::addDisallowedURL(JSStringRef url)
113 {
114     RetainPtr<CFStringRef> urlCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, url));
115
116     if (!disallowedURLs)
117         disallowedURLs = CFSetCreateMutable(kCFAllocatorDefault, 0, NULL);
118
119     // Canonicalize the URL
120     NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:(NSString *)urlCF.get()]];
121     request = [NSURLProtocol canonicalRequestForRequest:request];
122
123     CFSetAddValue(disallowedURLs, [request URL]);
124 }
125
126 bool LayoutTestController::callShouldCloseOnWebView()
127 {
128     return [[mainFrame webView] shouldClose];
129 }
130
131 void LayoutTestController::clearAllApplicationCaches()
132 {
133     [WebApplicationCache deleteAllApplicationCaches];
134 }
135
136 void LayoutTestController::clearAllDatabases()
137 {
138     [[WebDatabaseManager sharedWebDatabaseManager] deleteAllDatabases];
139 }
140
141 void LayoutTestController::clearBackForwardList()
142 {
143     WebBackForwardList *backForwardList = [[mainFrame webView] backForwardList];
144     WebHistoryItem *item = [[backForwardList currentItem] retain];
145
146     // We clear the history by setting the back/forward list's capacity to 0
147     // then restoring it back and adding back the current item.
148     int capacity = [backForwardList capacity];
149     [backForwardList setCapacity:0];
150     [backForwardList setCapacity:capacity];
151     [backForwardList addItem:item];
152     [backForwardList goToItem:item];
153     [item release];
154 }
155
156 JSStringRef LayoutTestController::copyDecodedHostName(JSStringRef name)
157 {
158     RetainPtr<CFStringRef> nameCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, name));
159     NSString *nameNS = (NSString *)nameCF.get();
160     return JSStringCreateWithCFString((CFStringRef)[nameNS _web_decodeHostName]);
161 }
162
163 JSStringRef LayoutTestController::copyEncodedHostName(JSStringRef name)
164 {
165     RetainPtr<CFStringRef> nameCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, name));
166     NSString *nameNS = (NSString *)nameCF.get();
167     return JSStringCreateWithCFString((CFStringRef)[nameNS _web_encodeHostName]);
168 }
169
170 void LayoutTestController::display()
171 {
172     displayWebView();
173 }
174
175 JSRetainPtr<JSStringRef> LayoutTestController::counterValueForElementById(JSStringRef id)
176 {
177     RetainPtr<CFStringRef> idCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, id));
178     NSString *idNS = (NSString *)idCF.get();
179
180     DOMElement *element = [[mainFrame DOMDocument] getElementById:idNS];
181     if (!element)
182         return 0;
183
184     JSRetainPtr<JSStringRef> counterValue(Adopt, JSStringCreateWithCFString((CFStringRef)[mainFrame counterValueForElement:element]));
185     return counterValue;
186 }
187
188 void LayoutTestController::keepWebHistory()
189 {
190     if (![WebHistory optionalSharedHistory]) {
191         WebHistory *history = [[WebHistory alloc] init];
192         [WebHistory setOptionalSharedHistory:history];
193         [history release];
194     }
195 }
196
197 JSValueRef LayoutTestController::computedStyleIncludingVisitedInfo(JSContextRef context, JSValueRef value)
198 {   
199     return [[mainFrame webView] _computedStyleIncludingVisitedInfo:context forElement:value];
200 }
201
202 JSValueRef LayoutTestController::nodesFromRect(JSContextRef context, JSValueRef value, int x, int y, unsigned top, unsigned right, unsigned bottom, unsigned left, bool ignoreClipping)
203 {
204     return [[mainFrame webView] _nodesFromRect:context forDocument:value x:x y:y top:top right:right bottom:bottom left:left ignoreClipping:ignoreClipping];
205 }
206
207 JSRetainPtr<JSStringRef> LayoutTestController::layerTreeAsText() const
208 {
209     JSRetainPtr<JSStringRef> string(Adopt, JSStringCreateWithCFString((CFStringRef)[mainFrame _layerTreeAsText]));
210     return string;
211 }
212
213 JSRetainPtr<JSStringRef> LayoutTestController::markerTextForListItem(JSContextRef context, JSValueRef nodeObject) const
214 {
215     DOMElement *element = [DOMElement _DOMElementFromJSContext:context value:nodeObject];
216     if (!element)
217         return JSRetainPtr<JSStringRef>();
218
219     JSRetainPtr<JSStringRef> markerText(Adopt, JSStringCreateWithCFString((CFStringRef)[element _markerTextForListItem]));
220     return markerText;
221 }
222
223 int LayoutTestController::pageNumberForElementById(JSStringRef id, float pageWidthInPixels, float pageHeightInPixels)
224 {
225     RetainPtr<CFStringRef> idCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, id));
226     NSString *idNS = (NSString *)idCF.get();
227
228     DOMElement *element = [[mainFrame DOMDocument] getElementById:idNS];
229     if (!element)
230         return -1;
231
232     return [mainFrame pageNumberForElement:element:pageWidthInPixels:pageHeightInPixels];
233 }
234
235 JSRetainPtr<JSStringRef> LayoutTestController::pageProperty(const char* propertyName, int pageNumber) const
236 {
237     JSRetainPtr<JSStringRef> propertyValue(Adopt, JSStringCreateWithCFString((CFStringRef)[mainFrame pageProperty:propertyName:pageNumber]));
238     return propertyValue;
239 }
240
241 bool LayoutTestController::isPageBoxVisible(int pageNumber) const
242 {
243     return [mainFrame isPageBoxVisible:pageNumber];
244 }
245
246 JSRetainPtr<JSStringRef> LayoutTestController::pageSizeAndMarginsInPixels(int pageNumber, int width, int height, int marginTop, int marginRight, int marginBottom, int marginLeft) const
247 {
248     JSRetainPtr<JSStringRef> propertyValue(Adopt, JSStringCreateWithCFString((CFStringRef)[mainFrame pageSizeAndMarginsInPixels:pageNumber:width:height:marginTop:marginRight:marginBottom:marginLeft]));
249     return propertyValue;
250 }
251
252 int LayoutTestController::numberOfPages(float pageWidthInPixels, float pageHeightInPixels)
253 {
254     return [mainFrame numberOfPages:pageWidthInPixels:pageHeightInPixels];
255 }
256
257 size_t LayoutTestController::webHistoryItemCount()
258 {
259     return [[[WebHistory optionalSharedHistory] allItems] count];
260 }
261
262 unsigned LayoutTestController::workerThreadCount() const
263 {
264     return [WebWorkersPrivate workerThreadCount];
265 }
266
267 void LayoutTestController::notifyDone()
268 {
269     puts("notifyDone");
270
271     if (m_waitToDump && !topLoadingFrame && !WorkQueue::shared()->count())
272         dump();
273     m_waitToDump = false;
274 }
275
276 JSStringRef LayoutTestController::pathToLocalResource(JSContextRef context, JSStringRef url)
277 {
278     return JSStringRetain(url); // Do nothing on mac.
279 }
280
281 void LayoutTestController::queueLoad(JSStringRef url, JSStringRef target)
282 {
283     RetainPtr<CFStringRef> urlCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, url));
284     NSString *urlNS = (NSString *)urlCF.get();
285
286     NSURL *nsurl = [NSURL URLWithString:urlNS relativeToURL:[[[mainFrame dataSource] response] URL]];
287     NSString* nsurlString = [nsurl absoluteString];
288
289     JSRetainPtr<JSStringRef> absoluteURL(Adopt, JSStringCreateWithUTF8CString([nsurlString UTF8String]));
290     WorkQueue::shared()->queue(new LoadItem(absoluteURL.get(), target));
291 }
292
293 void LayoutTestController::setAcceptsEditing(bool newAcceptsEditing)
294 {
295     [(EditingDelegate *)[[mainFrame webView] editingDelegate] setAcceptsEditing:newAcceptsEditing];
296 }
297
298 void LayoutTestController::setAlwaysAcceptCookies(bool alwaysAcceptCookies)
299 {
300     if (alwaysAcceptCookies == m_alwaysAcceptCookies)
301         return;
302
303     m_alwaysAcceptCookies = alwaysAcceptCookies;
304     NSHTTPCookieAcceptPolicy cookieAcceptPolicy = alwaysAcceptCookies ? NSHTTPCookieAcceptPolicyAlways : NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain;
305     [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookieAcceptPolicy:cookieAcceptPolicy];
306 }
307
308 void LayoutTestController::setAppCacheMaximumSize(unsigned long long size)
309 {
310     [WebApplicationCache setMaximumSize:size];
311 }
312
313 void LayoutTestController::setApplicationCacheOriginQuota(unsigned long long quota)
314 {
315     WebSecurityOrigin *origin = [[WebSecurityOrigin alloc] initWithURL:[NSURL URLWithString:@"http://127.0.0.1:8000"]];
316     [[origin applicationCacheQuotaManager] setQuota:quota];
317     [origin release];
318 }
319
320 void LayoutTestController::setAuthorAndUserStylesEnabled(bool flag)
321 {
322     [[[mainFrame webView] preferences] setAuthorAndUserStylesEnabled:flag];
323 }
324
325 void LayoutTestController::setCustomPolicyDelegate(bool setDelegate, bool permissive)
326 {
327     if (setDelegate) {
328         [policyDelegate setPermissive:permissive];
329         [[mainFrame webView] setPolicyDelegate:policyDelegate];
330     } else
331         [[mainFrame webView] setPolicyDelegate:nil];
332 }
333
334 void LayoutTestController::setDatabaseQuota(unsigned long long quota)
335 {    
336     WebSecurityOrigin *origin = [[WebSecurityOrigin alloc] initWithURL:[NSURL URLWithString:@"file:///"]];
337     [[origin databaseQuotaManager] setQuota:quota];
338     [origin release];
339 }
340
341 void LayoutTestController::setDomainRelaxationForbiddenForURLScheme(bool forbidden, JSStringRef scheme)
342 {
343     RetainPtr<CFStringRef> schemeCFString(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, scheme));
344     [WebView _setDomainRelaxationForbidden:forbidden forURLScheme:(NSString *)schemeCFString.get()];
345 }
346
347 void LayoutTestController::setMockDeviceOrientation(bool canProvideAlpha, double alpha, bool canProvideBeta, double beta, bool canProvideGamma, double gamma)
348 {
349     // DumpRenderTree configured the WebView to use WebDeviceOrientationProviderMock.
350     id<WebDeviceOrientationProvider> provider = [[mainFrame webView] _deviceOrientationProvider];
351     WebDeviceOrientationProviderMock* mockProvider = static_cast<WebDeviceOrientationProviderMock*>(provider);
352     WebDeviceOrientation* orientation = [[WebDeviceOrientation alloc] initWithCanProvideAlpha:canProvideAlpha alpha:alpha canProvideBeta:canProvideBeta beta:beta canProvideGamma:canProvideGamma gamma:gamma];
353     [mockProvider setOrientation:orientation];
354     [orientation release];
355 }
356
357 void LayoutTestController::setMockGeolocationPosition(double latitude, double longitude, double accuracy)
358 {
359     WebGeolocationPosition *position = [[WebGeolocationPosition alloc] initWithTimestamp:currentTime() latitude:latitude longitude:longitude accuracy:accuracy];
360     [[MockGeolocationProvider shared] setPosition:position];
361     [position release];
362 }
363
364 void LayoutTestController::setMockGeolocationError(int code, JSStringRef message)
365 {
366     RetainPtr<CFStringRef> messageCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, message));
367     NSString *messageNS = (NSString *)messageCF.get();
368     NSError *error = [NSError errorWithDomain:WebKitErrorDomain code:code userInfo:[NSDictionary dictionaryWithObject:messageNS forKey:NSLocalizedDescriptionKey]];
369     [[MockGeolocationProvider shared] setError:error];
370 }
371
372 void LayoutTestController::setGeolocationPermission(bool allow)
373 {
374     setGeolocationPermissionCommon(allow);
375     [[[mainFrame webView] UIDelegate] didSetMockGeolocationPermission];
376 }
377
378 void LayoutTestController::addMockSpeechInputResult(JSStringRef result, double confidence, JSStringRef language)
379 {
380     // FIXME: Implement for speech input layout tests.
381     // See https://bugs.webkit.org/show_bug.cgi?id=39485.
382 }
383
384 void LayoutTestController::setIconDatabaseEnabled(bool iconDatabaseEnabled)
385 {
386     // FIXME: Workaround <rdar://problem/6480108>
387     static WebIconDatabase* sharedWebIconDatabase = NULL;
388     if (!sharedWebIconDatabase) {
389         if (!iconDatabaseEnabled)
390             return;
391         sharedWebIconDatabase = [WebIconDatabase sharedIconDatabase];
392         if ([sharedWebIconDatabase isEnabled] == iconDatabaseEnabled)
393             return;
394     }
395     [sharedWebIconDatabase setEnabled:iconDatabaseEnabled];
396 }
397
398 void LayoutTestController::setJavaScriptProfilingEnabled(bool profilingEnabled)
399 {
400     setDeveloperExtrasEnabled(profilingEnabled);
401     [[[mainFrame webView] inspector] setJavaScriptProfilingEnabled:profilingEnabled];
402 }
403
404 void LayoutTestController::setMainFrameIsFirstResponder(bool flag)
405 {
406     NSView *documentView = [[mainFrame frameView] documentView];
407     
408     NSResponder *firstResponder = flag ? documentView : nil;
409     [[[mainFrame webView] window] makeFirstResponder:firstResponder];
410 }
411
412 void LayoutTestController::setPrivateBrowsingEnabled(bool privateBrowsingEnabled)
413 {
414     [[[mainFrame webView] preferences] setPrivateBrowsingEnabled:privateBrowsingEnabled];
415 }
416
417 void LayoutTestController::setXSSAuditorEnabled(bool enabled)
418 {
419     [[[mainFrame webView] preferences] setXSSAuditorEnabled:enabled];
420 }
421
422 void LayoutTestController::setFrameFlatteningEnabled(bool enabled)
423 {
424     [[[mainFrame webView] preferences] setFrameFlatteningEnabled:enabled];
425 }
426
427 void LayoutTestController::setSpatialNavigationEnabled(bool enabled)
428 {
429     [[[mainFrame webView] preferences] setSpatialNavigationEnabled:enabled];
430 }
431
432 void LayoutTestController::setAllowUniversalAccessFromFileURLs(bool enabled)
433 {
434     [[[mainFrame webView] preferences] setAllowUniversalAccessFromFileURLs:enabled];
435 }
436
437 void LayoutTestController::setAllowFileAccessFromFileURLs(bool enabled)
438 {
439     [[[mainFrame webView] preferences] setAllowFileAccessFromFileURLs:enabled];
440 }
441
442 void LayoutTestController::setPopupBlockingEnabled(bool popupBlockingEnabled)
443 {
444     [[[mainFrame webView] preferences] setJavaScriptCanOpenWindowsAutomatically:!popupBlockingEnabled];
445 }
446
447 void LayoutTestController::setPluginsEnabled(bool pluginsEnabled)
448 {
449     [[[mainFrame webView] preferences] setPlugInsEnabled:pluginsEnabled];
450 }
451
452 void LayoutTestController::setJavaScriptCanAccessClipboard(bool enabled)
453 {
454     [[[mainFrame webView] preferences] setJavaScriptCanAccessClipboard:enabled];
455 }
456
457 void LayoutTestController::setTabKeyCyclesThroughElements(bool cycles)
458 {
459     [[mainFrame webView] setTabKeyCyclesThroughElements:cycles];
460 }
461
462 void LayoutTestController::setTimelineProfilingEnabled(bool enabled)
463 {
464     [[[mainFrame webView] inspector] setTimelineProfilingEnabled:enabled];
465 }
466
467 void LayoutTestController::setUseDashboardCompatibilityMode(bool flag)
468 {
469     [[mainFrame webView] _setDashboardBehavior:WebDashboardBehaviorUseBackwardCompatibilityMode to:flag];
470 }
471
472 void LayoutTestController::setUserStyleSheetEnabled(bool flag)
473 {
474     [[WebPreferences standardPreferences] setUserStyleSheetEnabled:flag];
475 }
476
477 void LayoutTestController::setUserStyleSheetLocation(JSStringRef path)
478 {
479     RetainPtr<CFStringRef> pathCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, path));
480     NSURL *url = [NSURL URLWithString:(NSString *)pathCF.get()];
481     [[WebPreferences standardPreferences] setUserStyleSheetLocation:url];
482 }
483
484 void LayoutTestController::setViewModeMediaFeature(JSStringRef mode)
485 {
486     // FIXME: implement
487 }
488
489 void LayoutTestController::disableImageLoading()
490 {
491     [[WebPreferences standardPreferences] setLoadsImagesAutomatically:NO];
492 }
493
494 void LayoutTestController::dispatchPendingLoadRequests()
495 {
496     [[mainFrame webView] _dispatchPendingLoadRequests];
497 }
498
499 void LayoutTestController::overridePreference(JSStringRef key, JSStringRef value)
500 {
501     RetainPtr<CFStringRef> keyCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, key));
502     NSString *keyNS = (NSString *)keyCF.get();
503
504     RetainPtr<CFStringRef> valueCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, value));
505     NSString *valueNS = (NSString *)valueCF.get();
506
507     [[WebPreferences standardPreferences] _setPreferenceForTestWithValue:valueNS forKey:keyNS];
508 }
509
510 void LayoutTestController::removeAllVisitedLinks()
511 {
512     [WebHistory _removeAllVisitedLinks];
513 }
514
515 void LayoutTestController::setPersistentUserStyleSheetLocation(JSStringRef jsURL)
516 {
517     RetainPtr<CFStringRef> urlString(AdoptCF, JSStringCopyCFString(0, jsURL));
518     ::setPersistentUserStyleSheetLocation(urlString.get());
519 }
520
521 void LayoutTestController::clearPersistentUserStyleSheet()
522 {
523     ::setPersistentUserStyleSheetLocation(0);
524 }
525
526 void LayoutTestController::setWindowIsKey(bool windowIsKey)
527 {
528     m_windowIsKey = windowIsKey;
529     [[mainFrame webView] _updateActiveState];
530 }
531
532 void LayoutTestController::setSmartInsertDeleteEnabled(bool flag)
533 {
534     [[mainFrame webView] setSmartInsertDeleteEnabled:flag];
535 }
536
537 void LayoutTestController::setSelectTrailingWhitespaceEnabled(bool flag)
538 {
539     [[mainFrame webView] setSelectTrailingWhitespaceEnabled:flag];
540 }
541
542 static const CFTimeInterval waitToDumpWatchdogInterval = 30.0;
543
544 static void waitUntilDoneWatchdogFired(CFRunLoopTimerRef timer, void* info)
545 {
546     gLayoutTestController->waitToDumpWatchdogTimerFired();
547 }
548
549 void LayoutTestController::setWaitToDump(bool waitUntilDone)
550 {
551     m_waitToDump = waitUntilDone;
552     if (m_waitToDump && !waitToDumpWatchdog) {
553         waitToDumpWatchdog = CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + waitToDumpWatchdogInterval, 0, 0, 0, waitUntilDoneWatchdogFired, NULL);
554         CFRunLoopAddTimer(CFRunLoopGetCurrent(), waitToDumpWatchdog, kCFRunLoopCommonModes);
555     }
556 }
557
558 int LayoutTestController::windowCount()
559 {
560     return CFArrayGetCount(openWindowsRef);
561 }
562
563 bool LayoutTestController::elementDoesAutoCompleteForElementWithId(JSStringRef jsString)
564 {
565     RetainPtr<CFStringRef> idCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, jsString));
566     NSString *idNS = (NSString *)idCF.get();
567     
568     DOMElement *element = [[mainFrame DOMDocument] getElementById:idNS];
569     id rep = [[mainFrame dataSource] representation];
570     
571     if ([rep class] == [WebHTMLRepresentation class])
572         return [(WebHTMLRepresentation *)rep elementDoesAutoComplete:element];
573
574     return false;
575 }
576
577 void LayoutTestController::execCommand(JSStringRef name, JSStringRef value)
578 {
579     RetainPtr<CFStringRef> nameCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, name));
580     NSString *nameNS = (NSString *)nameCF.get();
581
582     RetainPtr<CFStringRef> valueCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, value));
583     NSString *valueNS = (NSString *)valueCF.get();
584
585     [[mainFrame webView] _executeCoreCommandByName:nameNS value:valueNS];
586 }
587
588 bool LayoutTestController::findString(JSContextRef context, JSStringRef target, JSObjectRef optionsArray)
589 {
590     WebFindOptions options = 0;
591
592     JSRetainPtr<JSStringRef> lengthPropertyName(Adopt, JSStringCreateWithUTF8CString("length"));
593     JSValueRef lengthValue = JSObjectGetProperty(context, optionsArray, lengthPropertyName.get(), 0);
594     if (!JSValueIsNumber(context, lengthValue))
595         return false;
596
597     RetainPtr<CFStringRef> targetCFString(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, target));
598
599     size_t length = static_cast<size_t>(JSValueToNumber(context, lengthValue, 0));
600     for (size_t i = 0; i < length; ++i) {
601         JSValueRef value = JSObjectGetPropertyAtIndex(context, optionsArray, i, 0);
602         if (!JSValueIsString(context, value))
603             continue;
604
605         JSRetainPtr<JSStringRef> optionName(Adopt, JSValueToStringCopy(context, value, 0));
606
607         if (JSStringIsEqualToUTF8CString(optionName.get(), "CaseInsensitive"))
608             options |= WebFindOptionsCaseInsensitive;
609         else if (JSStringIsEqualToUTF8CString(optionName.get(), "AtWordStarts"))
610             options |= WebFindOptionsAtWordStarts;
611         else if (JSStringIsEqualToUTF8CString(optionName.get(), "TreatMedialCapitalAsWordStart"))
612             options |= WebFindOptionsTreatMedialCapitalAsWordStart;
613         else if (JSStringIsEqualToUTF8CString(optionName.get(), "Backwards"))
614             options |= WebFindOptionsBackwards;
615         else if (JSStringIsEqualToUTF8CString(optionName.get(), "WrapAround"))
616             options |= WebFindOptionsWrapAround;
617         else if (JSStringIsEqualToUTF8CString(optionName.get(), "StartInSelection"))
618             options |= WebFindOptionsStartInSelection;
619     }
620
621     return [[mainFrame webView] findString:(NSString *)targetCFString.get() options:options];
622 }
623
624 void LayoutTestController::setCacheModel(int cacheModel)
625 {
626     [[WebPreferences standardPreferences] setCacheModel:cacheModel];
627 }
628
629 bool LayoutTestController::isCommandEnabled(JSStringRef name)
630 {
631     RetainPtr<CFStringRef> nameCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, name));
632     NSString *nameNS = (NSString *)nameCF.get();
633
634     // Accept command strings with capital letters for first letter without trailing colon.
635     if (![nameNS hasSuffix:@":"] && [nameNS length]) {
636         nameNS = [[[[nameNS substringToIndex:1] lowercaseString]
637             stringByAppendingString:[nameNS substringFromIndex:1]]
638             stringByAppendingString:@":"];
639     }
640
641     SEL selector = NSSelectorFromString(nameNS);
642     RetainPtr<CommandValidationTarget> target(AdoptNS, [[CommandValidationTarget alloc] initWithAction:selector]);
643     id validator = [NSApp targetForAction:selector to:[mainFrame webView] from:target.get()];
644     if (!validator)
645         return false;
646     if (![validator respondsToSelector:selector])
647         return false;
648     if (![validator respondsToSelector:@selector(validateUserInterfaceItem:)])
649         return true;
650     return [validator validateUserInterfaceItem:target.get()];
651 }
652
653 bool LayoutTestController::pauseAnimationAtTimeOnElementWithId(JSStringRef animationName, double time, JSStringRef elementId)
654 {
655     RetainPtr<CFStringRef> idCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, elementId));
656     NSString *idNS = (NSString *)idCF.get();
657     RetainPtr<CFStringRef> nameCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, animationName));
658     NSString *nameNS = (NSString *)nameCF.get();
659     
660     return [mainFrame _pauseAnimation:nameNS onNode:[[mainFrame DOMDocument] getElementById:idNS] atTime:time];
661 }
662
663 bool LayoutTestController::pauseTransitionAtTimeOnElementWithId(JSStringRef propertyName, double time, JSStringRef elementId)
664 {
665     RetainPtr<CFStringRef> idCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, elementId));
666     NSString *idNS = (NSString *)idCF.get();
667     RetainPtr<CFStringRef> nameCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, propertyName));
668     NSString *nameNS = (NSString *)nameCF.get();
669     
670     return [mainFrame _pauseTransitionOfProperty:nameNS onNode:[[mainFrame DOMDocument] getElementById:idNS] atTime:time];
671 }
672
673 bool LayoutTestController::sampleSVGAnimationForElementAtTime(JSStringRef animationId, double time, JSStringRef elementId)
674 {
675     RetainPtr<CFStringRef> animationIDCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, animationId));
676     NSString *animationIDNS = (NSString *)animationIDCF.get();
677     RetainPtr<CFStringRef> elementIDCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, elementId));
678     NSString *elementIDNS = (NSString *)elementIDCF.get();
679
680     return [mainFrame _pauseSVGAnimation:elementIDNS onSMILNode:[[mainFrame DOMDocument] getElementById:animationIDNS] atTime:time];
681 }
682
683 unsigned LayoutTestController::numberOfActiveAnimations() const
684 {
685     return [mainFrame _numberOfActiveAnimations];
686 }
687
688 void LayoutTestController::suspendAnimations() const
689 {
690     return [mainFrame _suspendAnimations];
691 }
692
693 void LayoutTestController::resumeAnimations() const
694 {
695     return [mainFrame _resumeAnimations];
696 }
697
698 void LayoutTestController::waitForPolicyDelegate()
699 {
700     setWaitToDump(true);
701     [policyDelegate setControllerToNotifyDone:this];
702     [[mainFrame webView] setPolicyDelegate:policyDelegate];
703 }
704
705 void LayoutTestController::addOriginAccessWhitelistEntry(JSStringRef sourceOrigin, JSStringRef destinationProtocol, JSStringRef destinationHost, bool allowDestinationSubdomains)
706 {
707     RetainPtr<CFStringRef> sourceOriginCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, sourceOrigin));
708     NSString *sourceOriginNS = (NSString *)sourceOriginCF.get();
709     RetainPtr<CFStringRef> protocolCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, destinationProtocol));
710     NSString *destinationProtocolNS = (NSString *)protocolCF.get();
711     RetainPtr<CFStringRef> hostCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, destinationHost));
712     NSString *destinationHostNS = (NSString *)hostCF.get();
713     [WebView _addOriginAccessWhitelistEntryWithSourceOrigin:sourceOriginNS destinationProtocol:destinationProtocolNS destinationHost:destinationHostNS allowDestinationSubdomains:allowDestinationSubdomains];
714 }
715
716 void LayoutTestController::removeOriginAccessWhitelistEntry(JSStringRef sourceOrigin, JSStringRef destinationProtocol, JSStringRef destinationHost, bool allowDestinationSubdomains)
717 {
718     RetainPtr<CFStringRef> sourceOriginCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, sourceOrigin));
719     NSString *sourceOriginNS = (NSString *)sourceOriginCF.get();
720     RetainPtr<CFStringRef> protocolCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, destinationProtocol));
721     NSString *destinationProtocolNS = (NSString *)protocolCF.get();
722     RetainPtr<CFStringRef> hostCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, destinationHost));
723     NSString *destinationHostNS = (NSString *)hostCF.get();
724     [WebView _removeOriginAccessWhitelistEntryWithSourceOrigin:sourceOriginNS destinationProtocol:destinationProtocolNS destinationHost:destinationHostNS allowDestinationSubdomains:allowDestinationSubdomains];
725 }
726
727 void LayoutTestController::setScrollbarPolicy(JSStringRef orientation, JSStringRef policy)
728 {
729     // FIXME: implement
730 }
731
732 void LayoutTestController::addUserScript(JSStringRef source, bool runAtStart, bool allFrames)
733 {
734     RetainPtr<CFStringRef> sourceCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, source));
735     NSString *sourceNS = (NSString *)sourceCF.get();
736     [WebView _addUserScriptToGroup:@"org.webkit.DumpRenderTree" world:[WebScriptWorld world] source:sourceNS url:nil whitelist:nil blacklist:nil injectionTime:(runAtStart ? WebInjectAtDocumentStart : WebInjectAtDocumentEnd) injectedFrames:(allFrames ? WebInjectInAllFrames : WebInjectInTopFrameOnly)];
737 }
738
739 void LayoutTestController::addUserStyleSheet(JSStringRef source, bool allFrames)
740 {
741     RetainPtr<CFStringRef> sourceCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, source));
742     NSString *sourceNS = (NSString *)sourceCF.get();
743     [WebView _addUserStyleSheetToGroup:@"org.webkit.DumpRenderTree" world:[WebScriptWorld world] source:sourceNS url:nil whitelist:nil blacklist:nil injectedFrames:(allFrames ? WebInjectInAllFrames : WebInjectInTopFrameOnly)];
744 }
745
746 void LayoutTestController::setDeveloperExtrasEnabled(bool enabled)
747 {
748     [[[mainFrame webView] preferences] setDeveloperExtrasEnabled:enabled];
749 }
750
751 void LayoutTestController::setAsynchronousSpellCheckingEnabled(bool enabled)
752 {
753     [[[mainFrame webView] preferences] setAsynchronousSpellCheckingEnabled:enabled];
754 }
755
756 void LayoutTestController::showWebInspector()
757 {
758     [[[mainFrame webView] inspector] show:nil];
759 }
760
761 void LayoutTestController::closeWebInspector()
762 {
763     [[[mainFrame webView] inspector] close:nil];
764 }
765
766 void LayoutTestController::evaluateInWebInspector(long callId, JSStringRef script)
767 {
768     RetainPtr<CFStringRef> scriptCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, script));
769     NSString *scriptNS = (NSString *)scriptCF.get();
770     [[[mainFrame webView] inspector] evaluateInFrontend:nil callId:callId script:scriptNS];
771 }
772
773 typedef HashMap<unsigned, RetainPtr<WebScriptWorld> > WorldMap;
774 static WorldMap& worldMap()
775 {
776     static WorldMap& map = *new WorldMap;
777     return map;
778 }
779
780 unsigned worldIDForWorld(WebScriptWorld *world)
781 {
782     WorldMap::const_iterator end = worldMap().end();
783     for (WorldMap::const_iterator it = worldMap().begin(); it != end; ++it) {
784         if (it->second == world)
785             return it->first;
786     }
787
788     return 0;
789 }
790
791 void LayoutTestController::evaluateScriptInIsolatedWorld(unsigned worldID, JSObjectRef globalObject, JSStringRef script)
792 {
793     RetainPtr<CFStringRef> scriptCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, script));
794     NSString *scriptNS = (NSString *)scriptCF.get();
795
796     // A worldID of 0 always corresponds to a new world. Any other worldID corresponds to a world
797     // that is created once and cached forever.
798     WebScriptWorld *world;
799     if (!worldID)
800         world = [WebScriptWorld world];
801     else {
802         RetainPtr<WebScriptWorld>& worldSlot = worldMap().add(worldID, 0).first->second;
803         if (!worldSlot)
804             worldSlot.adoptNS([[WebScriptWorld alloc] init]);
805         world = worldSlot.get();
806     }
807
808     [mainFrame _stringByEvaluatingJavaScriptFromString:scriptNS withGlobalObject:globalObject inScriptWorld:world];
809 }
810
811 @interface APITestDelegate : NSObject
812 {
813     bool* m_condition;
814 }
815 @end
816
817 @implementation APITestDelegate
818
819 - (id)initWithCompletionCondition:(bool*)condition
820 {
821     [super init];
822     ASSERT(condition);
823     m_condition = condition;
824     *m_condition = false;
825     return self;
826 }
827
828 - (void)webView:(WebView *)sender didFailLoadWithError:(NSError *)error forFrame:(WebFrame *)frame
829 {
830     printf("API Test load failed\n");
831     *m_condition = true;
832 }
833
834 - (void)webView:(WebView *)sender didFailProvisionalLoadWithError:(NSError *)error forFrame:(WebFrame *)frame
835 {
836     printf("API Test load failed provisional\n");
837     *m_condition = true;
838 }
839
840 - (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
841 {
842     printf("API Test load succeeded\n");
843     *m_condition = true;
844 }
845
846 @end
847
848 void LayoutTestController::apiTestNewWindowDataLoadBaseURL(JSStringRef utf8Data, JSStringRef baseURL)
849 {
850     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
851
852     RetainPtr<CFStringRef> utf8DataCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, utf8Data));
853     RetainPtr<CFStringRef> baseURLCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, baseURL));
854     
855     WebView *webView = [[WebView alloc] initWithFrame:NSZeroRect frameName:@"" groupName:@""];
856
857     bool done = false;
858     APITestDelegate *delegate = [[APITestDelegate alloc] initWithCompletionCondition:&done];
859     [webView setFrameLoadDelegate:delegate];
860
861     [[webView mainFrame] loadData:[(NSString *)utf8DataCF.get() dataUsingEncoding:NSUTF8StringEncoding] MIMEType:@"text/html" textEncodingName:@"utf-8" baseURL:[NSURL URLWithString:(NSString *)baseURLCF.get()]];
862     
863     while (!done) {
864         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
865         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]];
866         [pool release];
867     }
868         
869     [webView close];
870     [webView release];
871     [delegate release];
872     [pool release];
873 }
874
875 void LayoutTestController::apiTestGoToCurrentBackForwardItem()
876 {
877     WebView *view = [mainFrame webView];
878     [view goToBackForwardItem:[[view backForwardList] currentItem]];
879 }
880
881 void LayoutTestController::setWebViewEditable(bool editable)
882 {
883     WebView *view = [mainFrame webView];
884     [view setEditable:editable];
885 }
886
887 #ifndef BUILDING_ON_TIGER
888 static NSString *SynchronousLoaderRunLoopMode = @"DumpRenderTreeSynchronousLoaderRunLoopMode";
889
890 #if defined(BUILDING_ON_LEOPARD) || defined(BUILDING_ON_SNOW_LEOPARD)
891 @protocol NSURLConnectionDelegate <NSObject>
892 @end
893 #endif
894
895 @interface SynchronousLoader : NSObject <NSURLConnectionDelegate>
896 {
897     NSString *m_username;
898     NSString *m_password;
899     BOOL m_isDone;
900 }
901 + (void)makeRequest:(NSURLRequest *)request withUsername:(NSString *)username password:(NSString *)password;
902 @end
903
904 @implementation SynchronousLoader : NSObject
905 - (void)dealloc
906 {
907     [m_username release];
908     [m_password release];
909
910     [super dealloc];
911 }
912
913 - (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection
914 {
915     return YES;
916 }
917
918 - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
919 {
920     if ([challenge previousFailureCount] == 0) {
921         NSURLCredential *credential = [[NSURLCredential alloc]  initWithUser:m_username password:m_password persistence:NSURLCredentialPersistenceForSession];
922         [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
923         return;
924     }
925     [[challenge sender] cancelAuthenticationChallenge:challenge];
926 }
927
928 - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
929 {
930     printf("SynchronousLoader failed: %s\n", [[error description] UTF8String]);
931     m_isDone = YES;
932 }
933
934 - (void)connectionDidFinishLoading:(NSURLConnection *)connection
935 {
936     m_isDone = YES;
937 }
938
939 + (void)makeRequest:(NSURLRequest *)request withUsername:(NSString *)username password:(NSString *)password
940 {
941     ASSERT(![[request URL] user]);
942     ASSERT(![[request URL] password]);
943
944     SynchronousLoader *delegate = [[SynchronousLoader alloc] init];
945     delegate->m_username = [username copy];
946     delegate->m_password = [password copy];
947
948     NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:delegate startImmediately:NO];
949     [connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:SynchronousLoaderRunLoopMode];
950     [connection start];
951     
952     while (!delegate->m_isDone)
953         [[NSRunLoop currentRunLoop] runMode:SynchronousLoaderRunLoopMode beforeDate:[NSDate distantFuture]];
954
955     [connection cancel];
956     
957     [connection release];
958     [delegate release];
959 }
960
961 @end
962 #endif
963
964 void LayoutTestController::authenticateSession(JSStringRef url, JSStringRef username, JSStringRef password)
965 {
966     // See <rdar://problem/7880699>.
967 #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
968     RetainPtr<CFStringRef> urlStringCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, url));
969     RetainPtr<CFStringRef> usernameCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, username));
970     RetainPtr<CFStringRef> passwordCF(AdoptCF, JSStringCopyCFString(kCFAllocatorDefault, password));
971
972     NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:(NSString *)urlStringCF.get()]];
973
974     [SynchronousLoader makeRequest:request withUsername:(NSString *)usernameCF.get() password:(NSString *)passwordCF.get()];
975 #endif
976 }
977
978 void LayoutTestController::setEditingBehavior(const char* editingBehavior)
979 {
980     NSString* editingBehaviorNS = [[NSString alloc] initWithUTF8String:editingBehavior];
981     if ([editingBehaviorNS isEqualToString:@"mac"])
982         [[WebPreferences standardPreferences] setEditingBehavior:WebKitEditingMacBehavior];
983     else if ([editingBehaviorNS isEqualToString:@"win"])
984         [[WebPreferences standardPreferences] setEditingBehavior:WebKitEditingWinBehavior];
985     else if ([editingBehaviorNS isEqualToString:@"unix"])
986         [[WebPreferences standardPreferences] setEditingBehavior:WebKitEditingUnixBehavior];
987     [editingBehaviorNS release];
988 }
989
990 void LayoutTestController::abortModal()
991 {
992     [NSApp abortModal];
993 }
994
995 bool LayoutTestController::hasSpellingMarker(int from, int length)
996 {
997     return [mainFrame hasSpellingMarker:from length:length];
998 }
999 void LayoutTestController::dumpConfigurationForViewport(int /*availableWidth*/, int /*availableHeight*/)
1000 {
1001
1002 }
1003
1004 void LayoutTestController::setSerializeHTTPLoads(bool serialize)
1005 {
1006     [WebView _setLoadResourcesSerially:serialize];
1007 }