[Cocoa] More tweaks and refactoring to prepare for ARC
[WebKit.git] / Tools / DumpRenderTree / mac / TestRunnerMac.mm
1 /*
2  * Copyright (C) 2007-2016 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 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 "TestRunner.h"
32
33 #import "DefaultPolicyDelegate.h"
34 #import "EditingDelegate.h"
35 #import "LayoutTestSpellChecker.h"
36 #import "MockGeolocationProvider.h"
37 #import "MockWebNotificationProvider.h"
38 #import "PolicyDelegate.h"
39 #import "UIDelegate.h"
40 #import "WorkQueue.h"
41 #import "WorkQueueItem.h"
42 #import <Foundation/Foundation.h>
43 #import <JavaScriptCore/JSRetainPtr.h>
44 #import <JavaScriptCore/JSStringRef.h>
45 #import <JavaScriptCore/JSStringRefCF.h>
46 #import <WebCore/GeolocationPosition.h>
47 #import <WebKit/DOMDocument.h>
48 #import <WebKit/DOMElement.h>
49 #import <WebKit/DOMHTMLInputElementPrivate.h>
50 #import <WebKit/WebApplicationCache.h>
51 #import <WebKit/WebBackForwardList.h>
52 #import <WebKit/WebCoreStatistics.h>
53 #import <WebKit/WebDOMOperationsPrivate.h>
54 #import <WebKit/WebDataSource.h>
55 #import <WebKit/WebDatabaseManagerPrivate.h>
56 #import <WebKit/WebDeviceOrientation.h>
57 #import <WebKit/WebDeviceOrientationProviderMock.h>
58 #import <WebKit/WebFrame.h>
59 #import <WebKit/WebFrameLoadDelegate.h>
60 #import <WebKit/WebFrameViewPrivate.h>
61 #import <WebKit/WebGeolocationPosition.h>
62 #import <WebKit/WebHTMLRepresentation.h>
63 #import <WebKit/WebHTMLViewPrivate.h>
64 #import <WebKit/WebHistory.h>
65 #import <WebKit/WebHistoryPrivate.h>
66 #import <WebKit/WebInspectorPrivate.h>
67 #import <WebKit/WebNSURLExtras.h>
68 #import <WebKit/WebKitErrors.h>
69 #import <WebKit/WebPreferences.h>
70 #import <WebKit/WebPreferencesPrivate.h>
71 #import <WebKit/WebQuotaManager.h>
72 #import <WebKit/WebScriptWorld.h>
73 #import <WebKit/WebSecurityOriginPrivate.h>
74 #import <WebKit/WebStorageManagerPrivate.h>
75 #import <WebKit/WebView.h>
76 #import <WebKit/WebViewPrivate.h>
77 #import <wtf/HashMap.h>
78 #import <wtf/RetainPtr.h>
79 #import <wtf/WallTime.h>
80
81 #if !PLATFORM(IOS)
82 #import <wtf/SoftLinking.h>
83 #endif
84
85 #if PLATFORM(IOS)
86 #import "UIKitSPI.h"
87 #import <WebKit/WebCoreThread.h>
88 #import <WebKit/WebCoreThreadMessage.h>
89 #import <WebKit/WebDOMOperationsPrivate.h>
90 #endif
91
92 #if !PLATFORM(IOS)
93 SOFT_LINK_STAGED_FRAMEWORK(WebInspectorUI, PrivateFrameworks, A)
94
95 @interface CommandValidationTarget : NSObject <NSValidatedUserInterfaceItem>
96 {
97     SEL _action;
98 }
99 - (id)initWithAction:(SEL)action;
100 @end
101
102 @implementation CommandValidationTarget
103
104 - (id)initWithAction:(SEL)action
105 {
106     self = [super init];
107     if (!self)
108         return nil;
109
110     _action = action;
111     return self;
112 }
113
114 - (SEL)action
115 {
116     return _action;
117 }
118
119 - (NSInteger)tag
120 {
121     return 0;
122 }
123
124 @end
125 #endif
126
127 @interface WebGeolocationPosition (Internal)
128 - (id)initWithGeolocationPosition:(WebCore::GeolocationPosition&&)coreGeolocationPosition;
129 @end
130
131 TestRunner::~TestRunner()
132 {
133 }
134
135 JSContextRef TestRunner::mainFrameJSContext()
136 {
137     return [mainFrame globalContext];
138 }
139
140 void TestRunner::addDisallowedURL(JSStringRef url)
141 {
142     RetainPtr<CFStringRef> urlCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, url));
143
144     if (!disallowedURLs)
145         disallowedURLs = CFSetCreateMutable(kCFAllocatorDefault, 0, NULL);
146
147     // Canonicalize the URL
148     NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:(__bridge NSString *)urlCF.get()]];
149     request = [NSURLProtocol canonicalRequestForRequest:request];
150
151     CFSetAddValue(disallowedURLs, (__bridge CFURLRef)[request URL]);
152 }
153
154 bool TestRunner::callShouldCloseOnWebView()
155 {
156     return [[mainFrame webView] shouldClose];
157 }
158
159 void TestRunner::clearAllApplicationCaches()
160 {
161     [WebApplicationCache deleteAllApplicationCaches];
162 }
163
164 long long TestRunner::applicationCacheDiskUsageForOrigin(JSStringRef url)
165 {
166     RetainPtr<CFStringRef> urlCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, url));
167     WebSecurityOrigin *origin = [[WebSecurityOrigin alloc] initWithURL:[NSURL URLWithString:(__bridge NSString *)urlCF.get()]];
168     long long usage = [WebApplicationCache diskUsageForOrigin:origin];
169     [origin release];
170     return usage;
171 }
172
173 void TestRunner::clearApplicationCacheForOrigin(JSStringRef url)
174 {
175     RetainPtr<CFStringRef> urlCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, url));
176
177     WebSecurityOrigin *origin = [[WebSecurityOrigin alloc] initWithURL:[NSURL URLWithString:(__bridge NSString *)urlCF.get()]];
178     [WebApplicationCache deleteCacheForOrigin:origin];
179     [origin release];
180 }
181
182 JSValueRef originsArrayToJS(JSContextRef context, NSArray *origins)
183 {
184     NSUInteger count = [origins count];
185
186     JSValueRef arrayResult = JSObjectMakeArray(context, 0, 0, 0);
187     JSObjectRef arrayObj = JSValueToObject(context, arrayResult, 0);
188     for (NSUInteger i = 0; i < count; i++) {
189         NSString *origin = [[origins objectAtIndex:i] databaseIdentifier];
190         JSRetainPtr<JSStringRef> originJS(Adopt, JSStringCreateWithCFString((__bridge CFStringRef)origin));
191         JSObjectSetPropertyAtIndex(context, arrayObj, i, JSValueMakeString(context, originJS.get()), 0);
192     }
193
194     return arrayResult;
195 }
196
197 JSValueRef TestRunner::originsWithApplicationCache(JSContextRef context)
198 {
199     return originsArrayToJS(context, [WebApplicationCache originsWithCache]);
200 }
201
202 void TestRunner::clearAllDatabases()
203 {
204     [[WebDatabaseManager sharedWebDatabaseManager] deleteAllDatabases];
205     [[WebDatabaseManager sharedWebDatabaseManager] deleteAllIndexedDatabases];
206 }
207
208 void TestRunner::setStorageDatabaseIdleInterval(double interval)
209 {
210     [WebStorageManager setStorageDatabaseIdleInterval:interval];
211 }
212
213 void TestRunner::setSpellCheckerLoggingEnabled(bool enabled)
214 {
215 #if PLATFORM(MAC)
216     [LayoutTestSpellChecker checker].spellCheckerLoggingEnabled = enabled;
217 #else
218     UNUSED_PARAM(enabled);
219 #endif
220 }
221
222 void TestRunner::setSpellCheckerResults(JSContextRef context, JSObjectRef results)
223 {
224 #if PLATFORM(MAC)
225     [[LayoutTestSpellChecker checker] setResultsFromJSObject:results inContext:context];
226 #else
227     UNUSED_PARAM(results);
228     UNUSED_PARAM(context);
229 #endif
230 }
231
232 void TestRunner::closeIdleLocalStorageDatabases()
233 {
234     [WebStorageManager closeIdleLocalStorageDatabases];
235 }
236
237 void TestRunner::clearBackForwardList()
238 {
239     WebBackForwardList *backForwardList = [[mainFrame webView] backForwardList];
240     WebHistoryItem *item = [[backForwardList currentItem] retain];
241
242     // We clear the history by setting the back/forward list's capacity to 0
243     // then restoring it back and adding back the current item.
244     int capacity = [backForwardList capacity];
245     [backForwardList setCapacity:0];
246     [backForwardList setCapacity:capacity];
247     [backForwardList addItem:item];
248     [backForwardList goToItem:item];
249     [item release];
250 }
251
252 JSStringRef TestRunner::copyDecodedHostName(JSStringRef name)
253 {
254     RetainPtr<CFStringRef> nameCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, name));
255     NSString *nameNS = (__bridge NSString *)nameCF.get();
256     return JSStringCreateWithCFString((__bridge CFStringRef)[nameNS _web_decodeHostName]);
257 }
258
259 JSStringRef TestRunner::copyEncodedHostName(JSStringRef name)
260 {
261     RetainPtr<CFStringRef> nameCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, name));
262     NSString *nameNS = (__bridge NSString *)nameCF.get();
263     return JSStringCreateWithCFString((__bridge CFStringRef)[nameNS _web_encodeHostName]);
264 }
265
266 void TestRunner::display()
267 {
268     displayWebView();
269 }
270
271 void TestRunner::displayAndTrackRepaints()
272 {
273     displayAndTrackRepaintsWebView();
274 }
275
276 void TestRunner::keepWebHistory()
277 {
278     if (![WebHistory optionalSharedHistory]) {
279         WebHistory *history = [[WebHistory alloc] init];
280         [WebHistory setOptionalSharedHistory:history];
281         [history release];
282     }
283 }
284
285 int TestRunner::numberOfPendingGeolocationPermissionRequests()
286 {
287     return [(UIDelegate *)[[mainFrame webView] UIDelegate] numberOfPendingGeolocationPermissionRequests];
288 }
289
290 bool TestRunner::isGeolocationProviderActive()
291 {
292     return MockGeolocationProvider.shared.isActive;
293 }
294
295 size_t TestRunner::webHistoryItemCount()
296 {
297     return [[[WebHistory optionalSharedHistory] allItems] count];
298 }
299
300 void TestRunner::notifyDone()
301 {
302     if (m_waitToDump && !topLoadingFrame && !WorkQueue::singleton().count())
303         dump();
304     m_waitToDump = false;
305 }
306
307 void TestRunner::forceImmediateCompletion()
308 {
309     if (m_waitToDump && !WorkQueue::singleton().count())
310         dump();
311     m_waitToDump = false;
312 }
313
314 static inline std::string stringFromJSString(JSStringRef jsString)
315 {
316     size_t maxBufferSize = JSStringGetMaximumUTF8CStringSize(jsString);
317     char* utf8Buffer = new char[maxBufferSize];
318     size_t bytesWrittenToUTF8Buffer = JSStringGetUTF8CString(jsString, utf8Buffer, maxBufferSize);
319     std::string stdString(utf8Buffer, bytesWrittenToUTF8Buffer - 1); // bytesWrittenToUTF8Buffer includes a trailing \0 which std::string doesn't need.
320     delete[] utf8Buffer;
321     return stdString;
322 }
323
324 static inline size_t indexOfSeparatorAfterDirectoryName(const std::string& directoryName, const std::string& fullPath)
325 {
326     std::string searchKey = "/" + directoryName + "/";
327     size_t indexOfSearchKeyStart = fullPath.rfind(searchKey);
328     if (indexOfSearchKeyStart == std::string::npos) {
329         ASSERT_NOT_REACHED();
330         return 0;
331     }
332     // Callers expect the return value not to end in "/", so searchKey.length() - 1.
333     return indexOfSearchKeyStart + searchKey.length() - 1;
334 }
335
336 static inline std::string resourceRootAbsolutePath(const std::string& testURL, const std::string& expectedRootName)
337 {
338     char* localResourceRootEnv = getenv("LOCAL_RESOURCE_ROOT");
339     if (localResourceRootEnv)
340         return std::string(localResourceRootEnv);
341
342     // This fallback approach works for non-http tests and is useful
343     // in the case when we're running DRT directly from the command line.
344     return testURL.substr(0, indexOfSeparatorAfterDirectoryName(expectedRootName, testURL));
345 }
346
347 JSStringRef TestRunner::pathToLocalResource(JSContextRef context, JSStringRef localResourceJSString)
348 {
349     // The passed in path will be an absolute path to the resource starting
350     // with "/tmp" or "/tmp/LayoutTests", optionally starting with the explicit file:// protocol.
351     // /tmp maps to DUMPRENDERTREE_TEMP, and /tmp/LayoutTests maps to LOCAL_RESOURCE_ROOT.
352     // FIXME: This code should work on all *nix platforms and can be moved into TestRunner.cpp.
353     std::string expectedRootName;
354     std::string absolutePathToResourceRoot;
355     std::string localResourceString = stringFromJSString(localResourceJSString);
356
357     if (localResourceString.find("LayoutTests") != std::string::npos) {
358         expectedRootName = "LayoutTests";
359         absolutePathToResourceRoot = resourceRootAbsolutePath(m_testURL, expectedRootName);
360     } else if (localResourceString.find("tmp") != std::string::npos) {
361         expectedRootName = "tmp";
362         absolutePathToResourceRoot = getenv("DUMPRENDERTREE_TEMP");
363     } else {
364         ASSERT_NOT_REACHED(); // pathToLocalResource was passed a path it doesn't know how to map.
365     }
366     ASSERT(!absolutePathToResourceRoot.empty());
367     size_t indexOfSeparatorAfterRootName = indexOfSeparatorAfterDirectoryName(expectedRootName, localResourceString);
368     std::string absolutePathToLocalResource = absolutePathToResourceRoot + localResourceString.substr(indexOfSeparatorAfterRootName);
369
370     // Note: It's important that we keep the file:// or http tests will get confused.
371     if (localResourceString.find("file://") != std::string::npos) {
372         ASSERT(absolutePathToLocalResource[0] == '/');
373         absolutePathToLocalResource = std::string("file://") + absolutePathToLocalResource;
374     }
375     return JSStringCreateWithUTF8CString(absolutePathToLocalResource.c_str());
376 }
377
378 void TestRunner::queueLoad(JSStringRef url, JSStringRef target)
379 {
380     RetainPtr<CFStringRef> urlCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, url));
381     NSString *urlNS = (__bridge NSString *)urlCF.get();
382
383     NSURL *nsurl = [NSURL URLWithString:urlNS relativeToURL:[[[mainFrame dataSource] response] URL]];
384     NSString *nsurlString = [nsurl absoluteString];
385
386     JSRetainPtr<JSStringRef> absoluteURL(Adopt, JSStringCreateWithUTF8CString([nsurlString UTF8String]));
387     WorkQueue::singleton().queue(new LoadItem(absoluteURL.get(), target));
388 }
389
390 void TestRunner::setAcceptsEditing(bool newAcceptsEditing)
391 {
392     [(EditingDelegate *)[[mainFrame webView] editingDelegate] setAcceptsEditing:newAcceptsEditing];
393 }
394
395 void TestRunner::setAlwaysAcceptCookies(bool alwaysAcceptCookies)
396 {
397     if (alwaysAcceptCookies == m_alwaysAcceptCookies)
398         return;
399
400     m_alwaysAcceptCookies = alwaysAcceptCookies;
401     NSHTTPCookieAcceptPolicy cookieAcceptPolicy = alwaysAcceptCookies ? NSHTTPCookieAcceptPolicyAlways : NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain;
402     [WebPreferences _setCurrentNetworkLoaderSessionCookieAcceptPolicy:cookieAcceptPolicy];
403 }
404
405 void TestRunner::setAppCacheMaximumSize(unsigned long long size)
406 {
407     [WebApplicationCache setMaximumSize:size];
408 }
409
410 void TestRunner::setAuthorAndUserStylesEnabled(bool flag)
411 {
412     [[[mainFrame webView] preferences] setAuthorAndUserStylesEnabled:flag];
413 }
414
415 void TestRunner::setCustomPolicyDelegate(bool setDelegate, bool permissive)
416 {
417     if (!setDelegate) {
418         [[mainFrame webView] setPolicyDelegate:defaultPolicyDelegate];
419         return;
420     }
421
422     [policyDelegate setPermissive:permissive];
423     [[mainFrame webView] setPolicyDelegate:policyDelegate];
424 }
425
426 void TestRunner::setDatabaseQuota(unsigned long long quota)
427 {    
428     WebSecurityOrigin *origin = [[WebSecurityOrigin alloc] initWithURL:[NSURL URLWithString:@"file:///"]];
429     [[origin databaseQuotaManager] setQuota:quota];
430     [origin release];
431 }
432
433 void TestRunner::goBack()
434 {
435     [[mainFrame webView] goBack];
436 }
437
438 void TestRunner::setDefersLoading(bool defers)
439 {
440     [[mainFrame webView] setDefersCallbacks:defers];
441 }
442
443 void TestRunner::setDomainRelaxationForbiddenForURLScheme(bool forbidden, JSStringRef scheme)
444 {
445     RetainPtr<CFStringRef> schemeCFString = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, scheme));
446     [WebView _setDomainRelaxationForbidden:forbidden forURLScheme:(__bridge NSString *)schemeCFString.get()];
447 }
448
449 void TestRunner::setMockDeviceOrientation(bool canProvideAlpha, double alpha, bool canProvideBeta, double beta, bool canProvideGamma, double gamma)
450 {
451     // DumpRenderTree configured the WebView to use WebDeviceOrientationProviderMock.
452     id<WebDeviceOrientationProvider> provider = [[mainFrame webView] _deviceOrientationProvider];
453     WebDeviceOrientationProviderMock *mockProvider = static_cast<WebDeviceOrientationProviderMock*>(provider);
454     WebDeviceOrientation *orientation = [[WebDeviceOrientation alloc] initWithCanProvideAlpha:canProvideAlpha alpha:alpha canProvideBeta:canProvideBeta beta:beta canProvideGamma:canProvideGamma gamma:gamma];
455     [mockProvider setOrientation:orientation];
456     [orientation release];
457 }
458
459 void TestRunner::setMockGeolocationPosition(double latitude, double longitude, double accuracy, bool providesAltitude, double altitude, bool providesAltitudeAccuracy, double altitudeAccuracy, bool providesHeading, double heading, bool providesSpeed, double speed, bool providesFloorLevel, double floorLevel)
460 {
461     WebGeolocationPosition *position = nil;
462     if (!providesAltitude && !providesAltitudeAccuracy && !providesHeading && !providesSpeed) {
463         // Test the exposed API.
464         position = [[WebGeolocationPosition alloc] initWithTimestamp:WallTime::now().secondsSinceEpoch().seconds() latitude:latitude longitude:longitude accuracy:accuracy];
465     } else {
466         WebCore::GeolocationPosition geolocationPosition { WallTime::now().secondsSinceEpoch().seconds(), latitude, longitude, accuracy };
467         if (providesAltitude)
468             geolocationPosition.altitude = altitude;
469         if (providesAltitudeAccuracy)
470             geolocationPosition.altitudeAccuracy = altitudeAccuracy;
471         if (providesHeading)
472             geolocationPosition.heading = heading;
473         if (providesSpeed)
474             geolocationPosition.speed = speed;
475         if (providesFloorLevel)
476             geolocationPosition.floorLevel = floorLevel;
477         position = [[WebGeolocationPosition alloc] initWithGeolocationPosition:(WTFMove(geolocationPosition))];
478     }
479     [[MockGeolocationProvider shared] setPosition:position];
480     [position release];
481 }
482
483 void TestRunner::setMockGeolocationPositionUnavailableError(JSStringRef message)
484 {
485     RetainPtr<CFStringRef> messageCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, message));
486     NSString *messageNS = (__bridge NSString *)messageCF.get();
487     [[MockGeolocationProvider shared] setPositionUnavailableErrorWithMessage:messageNS];
488 }
489
490 void TestRunner::setGeolocationPermission(bool allow)
491 {
492     setGeolocationPermissionCommon(allow);
493     [(UIDelegate *)[[mainFrame webView] UIDelegate] didSetMockGeolocationPermission];
494 }
495
496 void TestRunner::setIconDatabaseEnabled(bool iconDatabaseEnabled)
497 {
498     [WebView _setIconLoadingEnabled:iconDatabaseEnabled];
499 }
500
501 void TestRunner::setMainFrameIsFirstResponder(bool flag)
502 {
503 #if !PLATFORM(IOS)
504     NSView *documentView = [[mainFrame frameView] documentView];
505     
506     NSResponder *firstResponder = flag ? documentView : nil;
507     [[[mainFrame webView] window] makeFirstResponder:firstResponder];
508 #endif
509 }
510
511 void TestRunner::setPrivateBrowsingEnabled(bool privateBrowsingEnabled)
512 {
513     [[[mainFrame webView] preferences] setPrivateBrowsingEnabled:privateBrowsingEnabled];
514 }
515
516 void TestRunner::setXSSAuditorEnabled(bool enabled)
517 {
518     [[[mainFrame webView] preferences] setXSSAuditorEnabled:enabled];
519 }
520
521 void TestRunner::setSpatialNavigationEnabled(bool enabled)
522 {
523     [[[mainFrame webView] preferences] setSpatialNavigationEnabled:enabled];
524 }
525
526 void TestRunner::setAllowUniversalAccessFromFileURLs(bool enabled)
527 {
528     [[[mainFrame webView] preferences] setAllowUniversalAccessFromFileURLs:enabled];
529 }
530
531 void TestRunner::setAllowFileAccessFromFileURLs(bool enabled)
532 {
533     [[[mainFrame webView] preferences] setAllowFileAccessFromFileURLs:enabled];
534 }
535
536 void TestRunner::setNeedsStorageAccessFromFileURLsQuirk(bool needsQuirk)
537 {
538     [[[mainFrame webView] preferences] setNeedsStorageAccessFromFileURLsQuirk:needsQuirk];
539 }
540
541 void TestRunner::setPopupBlockingEnabled(bool popupBlockingEnabled)
542 {
543     [[[mainFrame webView] preferences] setJavaScriptCanOpenWindowsAutomatically:!popupBlockingEnabled];
544 }
545
546 void TestRunner::setPluginsEnabled(bool pluginsEnabled)
547 {
548     [[[mainFrame webView] preferences] setPlugInsEnabled:pluginsEnabled];
549 }
550
551 void TestRunner::setJavaScriptCanAccessClipboard(bool enabled)
552 {
553     [[[mainFrame webView] preferences] setJavaScriptCanAccessClipboard:enabled];
554 }
555
556 void TestRunner::setAutomaticLinkDetectionEnabled(bool enabled)
557 {
558 #if !PLATFORM(IOS)
559     [[mainFrame webView] setAutomaticLinkDetectionEnabled:enabled];
560 #endif
561 }
562
563 void TestRunner::setTabKeyCyclesThroughElements(bool cycles)
564 {
565     [[mainFrame webView] setTabKeyCyclesThroughElements:cycles];
566 }
567
568 #if PLATFORM(IOS)
569 void TestRunner::setTelephoneNumberParsingEnabled(bool enabled)
570 {
571     [[[mainFrame webView] preferences] _setTelephoneNumberParsingEnabled:enabled];
572 }
573
574 void TestRunner::setPagePaused(bool paused)
575 {
576     [gWebBrowserView setPaused:paused];
577 }
578 #endif
579
580 void TestRunner::setUseDashboardCompatibilityMode(bool flag)
581 {
582 #if !PLATFORM(IOS)
583     [[mainFrame webView] _setDashboardBehavior:WebDashboardBehaviorUseBackwardCompatibilityMode to:flag];
584 #endif
585 }
586
587 void TestRunner::setUserStyleSheetEnabled(bool flag)
588 {
589     [[WebPreferences standardPreferences] setUserStyleSheetEnabled:flag];
590 }
591
592 void TestRunner::setUserStyleSheetLocation(JSStringRef path)
593 {
594     RetainPtr<CFStringRef> pathCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, path));
595     NSURL *url = [NSURL URLWithString:(__bridge NSString *)pathCF.get()];
596     [[WebPreferences standardPreferences] setUserStyleSheetLocation:url];
597 }
598
599 void TestRunner::setValueForUser(JSContextRef context, JSValueRef nodeObject, JSStringRef value)
600 {
601     DOMElement *element = [DOMElement _DOMElementFromJSContext:context value:nodeObject];
602     if (!element || ![element isKindOfClass:[DOMHTMLInputElement class]])
603         return;
604
605     RetainPtr<CFStringRef> valueCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, value));
606     [(DOMHTMLInputElement *)element setValueForUser:(__bridge NSString *)valueCF.get()];
607 }
608
609 void TestRunner::dispatchPendingLoadRequests()
610 {
611     [[mainFrame webView] _dispatchPendingLoadRequests];
612 }
613
614 void TestRunner::overridePreference(JSStringRef key, JSStringRef value)
615 {
616     RetainPtr<CFStringRef> keyCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, key));
617     NSString *keyNS = (__bridge NSString *)keyCF.get();
618
619     RetainPtr<CFStringRef> valueCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, value));
620     NSString *valueNS = (__bridge NSString *)valueCF.get();
621
622     [[WebPreferences standardPreferences] _setPreferenceForTestWithValue:valueNS forKey:keyNS];
623 }
624
625 void TestRunner::removeAllVisitedLinks()
626 {
627     [WebHistory _removeAllVisitedLinks];
628 }
629
630 void TestRunner::setPersistentUserStyleSheetLocation(JSStringRef jsURL)
631 {
632     RetainPtr<CFStringRef> urlString = adoptCF(JSStringCopyCFString(0, jsURL));
633     ::setPersistentUserStyleSheetLocation(urlString.get());
634 }
635
636 void TestRunner::clearPersistentUserStyleSheet()
637 {
638     ::setPersistentUserStyleSheetLocation(0);
639 }
640
641 void TestRunner::setWindowIsKey(bool windowIsKey)
642 {
643     m_windowIsKey = windowIsKey;
644     [[mainFrame webView] _updateActiveState];
645 }
646
647 void TestRunner::setViewSize(double width, double height)
648 {
649     [[mainFrame webView] setFrameSize:NSMakeSize(width, height)];
650 }
651
652 static void waitUntilDoneWatchdogFired(CFRunLoopTimerRef timer, void* info)
653 {
654     gTestRunner->waitToDumpWatchdogTimerFired();
655 }
656
657 void TestRunner::setWaitToDump(bool waitUntilDone)
658 {
659     m_waitToDump = waitUntilDone;
660     if (m_waitToDump && m_timeout && shouldSetWaitToDumpWatchdog())
661         setWaitToDumpWatchdog(CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + m_timeout / 1000.0, 0, 0, 0, waitUntilDoneWatchdogFired, NULL));
662 }
663
664 int TestRunner::windowCount()
665 {
666     return CFArrayGetCount(openWindowsRef);
667 }
668
669 void TestRunner::execCommand(JSStringRef name, JSStringRef value)
670 {
671     RetainPtr<CFStringRef> nameCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, name));
672     NSString *nameNS = (__bridge NSString *)nameCF.get();
673
674     RetainPtr<CFStringRef> valueCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, value));
675     NSString *valueNS = (__bridge NSString *)valueCF.get();
676
677     [[mainFrame webView] _executeCoreCommandByName:nameNS value:valueNS];
678 }
679
680 bool TestRunner::findString(JSContextRef context, JSStringRef target, JSObjectRef optionsArray)
681 {
682     WebFindOptions options = 0;
683
684     JSRetainPtr<JSStringRef> lengthPropertyName(Adopt, JSStringCreateWithUTF8CString("length"));
685     JSValueRef lengthValue = JSObjectGetProperty(context, optionsArray, lengthPropertyName.get(), 0);
686     if (!JSValueIsNumber(context, lengthValue))
687         return false;
688
689     RetainPtr<CFStringRef> targetCFString = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, target));
690
691     size_t length = static_cast<size_t>(JSValueToNumber(context, lengthValue, 0));
692     for (size_t i = 0; i < length; ++i) {
693         JSValueRef value = JSObjectGetPropertyAtIndex(context, optionsArray, i, 0);
694         if (!JSValueIsString(context, value))
695             continue;
696
697         JSRetainPtr<JSStringRef> optionName(Adopt, JSValueToStringCopy(context, value, 0));
698
699         if (JSStringIsEqualToUTF8CString(optionName.get(), "CaseInsensitive"))
700             options |= WebFindOptionsCaseInsensitive;
701         else if (JSStringIsEqualToUTF8CString(optionName.get(), "AtWordStarts"))
702             options |= WebFindOptionsAtWordStarts;
703         else if (JSStringIsEqualToUTF8CString(optionName.get(), "TreatMedialCapitalAsWordStart"))
704             options |= WebFindOptionsTreatMedialCapitalAsWordStart;
705         else if (JSStringIsEqualToUTF8CString(optionName.get(), "Backwards"))
706             options |= WebFindOptionsBackwards;
707         else if (JSStringIsEqualToUTF8CString(optionName.get(), "WrapAround"))
708             options |= WebFindOptionsWrapAround;
709         else if (JSStringIsEqualToUTF8CString(optionName.get(), "StartInSelection"))
710             options |= WebFindOptionsStartInSelection;
711     }
712
713     return [[mainFrame webView] findString:(__bridge NSString *)targetCFString.get() options:options];
714 }
715
716 void TestRunner::setCacheModel(int cacheModel)
717 {
718     [[WebPreferences standardPreferences] setCacheModel:(WebCacheModel)cacheModel];
719 }
720
721 bool TestRunner::isCommandEnabled(JSStringRef name)
722 {
723 #if !PLATFORM(IOS)
724     RetainPtr<CFStringRef> nameCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, name));
725     NSString *nameNS = (__bridge NSString *)nameCF.get();
726
727     // Accept command strings with capital letters for first letter without trailing colon.
728     if (![nameNS hasSuffix:@":"] && [nameNS length]) {
729         nameNS = [[[[nameNS substringToIndex:1] lowercaseString]
730             stringByAppendingString:[nameNS substringFromIndex:1]]
731             stringByAppendingString:@":"];
732     }
733
734     SEL selector = NSSelectorFromString(nameNS);
735     RetainPtr<CommandValidationTarget> target = adoptNS([[CommandValidationTarget alloc] initWithAction:selector]);
736     id validator = [NSApp targetForAction:selector to:[mainFrame webView] from:target.get()];
737     if (!validator)
738         return false;
739     if (![validator respondsToSelector:selector])
740         return false;
741     if (![validator respondsToSelector:@selector(validateUserInterfaceItem:)])
742         return true;
743     return [validator validateUserInterfaceItem:target.get()];
744 #else
745     return false;
746 #endif
747 }
748
749 void TestRunner::waitForPolicyDelegate()
750 {
751     setWaitToDump(true);
752     [policyDelegate setControllerToNotifyDone:this];
753     [[mainFrame webView] setPolicyDelegate:policyDelegate];
754 }
755
756 void TestRunner::addOriginAccessWhitelistEntry(JSStringRef sourceOrigin, JSStringRef destinationProtocol, JSStringRef destinationHost, bool allowDestinationSubdomains)
757 {
758     RetainPtr<CFStringRef> sourceOriginCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, sourceOrigin));
759     NSString *sourceOriginNS = (__bridge NSString *)sourceOriginCF.get();
760     RetainPtr<CFStringRef> protocolCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, destinationProtocol));
761     NSString *destinationProtocolNS = (__bridge NSString *)protocolCF.get();
762     RetainPtr<CFStringRef> hostCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, destinationHost));
763     NSString *destinationHostNS = (__bridge NSString *)hostCF.get();
764     [WebView _addOriginAccessWhitelistEntryWithSourceOrigin:sourceOriginNS destinationProtocol:destinationProtocolNS destinationHost:destinationHostNS allowDestinationSubdomains:allowDestinationSubdomains];
765 }
766
767 void TestRunner::removeOriginAccessWhitelistEntry(JSStringRef sourceOrigin, JSStringRef destinationProtocol, JSStringRef destinationHost, bool allowDestinationSubdomains)
768 {
769     RetainPtr<CFStringRef> sourceOriginCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, sourceOrigin));
770     NSString *sourceOriginNS = (__bridge NSString *)sourceOriginCF.get();
771     RetainPtr<CFStringRef> protocolCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, destinationProtocol));
772     NSString *destinationProtocolNS = (__bridge NSString *)protocolCF.get();
773     RetainPtr<CFStringRef> hostCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, destinationHost));
774     NSString *destinationHostNS = (__bridge NSString *)hostCF.get();
775     [WebView _removeOriginAccessWhitelistEntryWithSourceOrigin:sourceOriginNS destinationProtocol:destinationProtocolNS destinationHost:destinationHostNS allowDestinationSubdomains:allowDestinationSubdomains];
776 }
777
778 void TestRunner::setScrollbarPolicy(JSStringRef orientation, JSStringRef policy)
779 {
780     // FIXME: implement
781 }
782
783 void TestRunner::addUserScript(JSStringRef source, bool runAtStart, bool allFrames)
784 {
785     RetainPtr<CFStringRef> sourceCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, source));
786     NSString *sourceNS = (__bridge NSString *)sourceCF.get();
787     [WebView _addUserScriptToGroup:@"org.webkit.DumpRenderTree" world:[WebScriptWorld world] source:sourceNS url:nil whitelist:nil blacklist:nil injectionTime:(runAtStart ? WebInjectAtDocumentStart : WebInjectAtDocumentEnd) injectedFrames:(allFrames ? WebInjectInAllFrames : WebInjectInTopFrameOnly)];
788 }
789
790 void TestRunner::addUserStyleSheet(JSStringRef source, bool allFrames)
791 {
792     RetainPtr<CFStringRef> sourceCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, source));
793     NSString *sourceNS = (__bridge NSString *)sourceCF.get();
794     [WebView _addUserStyleSheetToGroup:@"org.webkit.DumpRenderTree" world:[WebScriptWorld world] source:sourceNS url:nil whitelist:nil blacklist:nil injectedFrames:(allFrames ? WebInjectInAllFrames : WebInjectInTopFrameOnly)];
795 }
796
797 void TestRunner::setDeveloperExtrasEnabled(bool enabled)
798 {
799     [[[mainFrame webView] preferences] setDeveloperExtrasEnabled:enabled];
800 }
801
802 void TestRunner::showWebInspector()
803 {
804     [[[mainFrame webView] inspector] show:nil];
805 }
806
807 void TestRunner::closeWebInspector()
808 {
809     [[[mainFrame webView] inspector] close:nil];
810 }
811
812 void TestRunner::evaluateInWebInspector(JSStringRef script)
813 {
814     RetainPtr<CFStringRef> scriptCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, script));
815     NSString *scriptNS = (__bridge NSString *)scriptCF.get();
816     [[[mainFrame webView] inspector] evaluateInFrontend:nil script:scriptNS];
817 }
818
819 JSStringRef TestRunner::inspectorTestStubURL()
820 {
821 #if PLATFORM(IOS)
822     return nullptr;
823 #else
824     // Call the soft link framework function to dlopen it, then CFBundleGetBundleWithIdentifier will work.
825     WebInspectorUILibrary();
826
827     CFBundleRef inspectorBundle = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.WebInspectorUI"));
828     if (!inspectorBundle)
829         return nullptr;
830
831     RetainPtr<CFURLRef> url = adoptCF(CFBundleCopyResourceURL(inspectorBundle, CFSTR("TestStub"), CFSTR("html"), NULL));
832     if (!url)
833         return nullptr;
834
835     CFStringRef urlString = CFURLGetString(url.get());
836     return JSStringCreateWithCFString(urlString);
837 #endif
838 }
839
840 typedef HashMap<unsigned, RetainPtr<WebScriptWorld> > WorldMap;
841 static WorldMap& worldMap()
842 {
843     static WorldMap& map = *new WorldMap;
844     return map;
845 }
846
847 unsigned worldIDForWorld(WebScriptWorld *world)
848 {
849     WorldMap::const_iterator end = worldMap().end();
850     for (WorldMap::const_iterator it = worldMap().begin(); it != end; ++it) {
851         if (it->value == world)
852             return it->key;
853     }
854
855     return 0;
856 }
857
858 void TestRunner::evaluateScriptInIsolatedWorldAndReturnValue(unsigned worldID, JSObjectRef globalObject, JSStringRef script)
859 {
860     // FIXME: Implement this.
861 }
862
863 void TestRunner::evaluateScriptInIsolatedWorld(unsigned worldID, JSObjectRef globalObject, JSStringRef script)
864 {
865     RetainPtr<CFStringRef> scriptCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, script));
866     NSString *scriptNS = (__bridge NSString *)scriptCF.get();
867
868     // A worldID of 0 always corresponds to a new world. Any other worldID corresponds to a world
869     // that is created once and cached forever.
870     WebScriptWorld *world;
871     if (!worldID)
872         world = [WebScriptWorld world];
873     else {
874         RetainPtr<WebScriptWorld>& worldSlot = worldMap().add(worldID, nullptr).iterator->value;
875         if (!worldSlot)
876             worldSlot = adoptNS([[WebScriptWorld alloc] init]);
877         world = worldSlot.get();
878     }
879
880     [mainFrame _stringByEvaluatingJavaScriptFromString:scriptNS withGlobalObject:globalObject inScriptWorld:world];
881 }
882
883 @interface APITestDelegate : NSObject <WebFrameLoadDelegate>
884 {
885     bool* m_condition;
886 }
887 @end
888
889 @implementation APITestDelegate
890
891 - (id)initWithCompletionCondition:(bool*)condition
892 {
893     self = [super init];
894     if (!self)
895         return nil;
896     ASSERT(condition);
897     m_condition = condition;
898     *m_condition = false;
899     return self;
900 }
901
902 - (void)webView:(WebView *)sender didFailLoadWithError:(NSError *)error forFrame:(WebFrame *)frame
903 {
904     printf("API Test load failed\n");
905     *m_condition = true;
906 }
907
908 - (void)webView:(WebView *)sender didFailProvisionalLoadWithError:(NSError *)error forFrame:(WebFrame *)frame
909 {
910     printf("API Test load failed provisional\n");
911     *m_condition = true;
912 }
913
914 - (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
915 {
916     printf("API Test load succeeded\n");
917     *m_condition = true;
918 }
919
920 @end
921
922 #if PLATFORM(IOS)
923
924 @interface APITestDelegateIPhone : NSObject <WebFrameLoadDelegate>
925 {
926     TestRunner* testRunner;
927     NSData *data;
928     NSURL *baseURL;
929     WebView *webView;
930 }
931 - (id)initWithTestRunner:(TestRunner*)testRunner utf8Data:(JSStringRef)data baseURL:(JSStringRef)baseURL;
932 - (void)run;
933 @end
934
935 @implementation APITestDelegateIPhone
936
937 - (id)initWithTestRunner:(TestRunner*)runner utf8Data:(JSStringRef)dataString baseURL:(JSStringRef)baseURLString
938 {
939     self = [super init];
940     if (!self)
941         return nil;
942
943     testRunner = runner;
944     data = [[(__bridge NSString *)adoptCF(JSStringCopyCFString(kCFAllocatorDefault, dataString)).get() dataUsingEncoding:NSUTF8StringEncoding] retain];
945     baseURL = [[NSURL URLWithString:(__bridge NSString *)adoptCF(JSStringCopyCFString(kCFAllocatorDefault, baseURLString)).get()] retain];
946     return self;
947 }
948
949 - (void)dealloc
950 {
951     [data release];
952     [baseURL release];
953     [super dealloc];
954 }
955
956 - (void)run
957 {
958     if (webView)
959         return;
960
961     testRunner->setWaitToDump(true);
962
963     WebThreadLock();
964
965     webView = [[WebView alloc] initWithFrame:NSZeroRect frameName:@"" groupName:@""];
966     [webView setFrameLoadDelegate:self];
967     [[webView mainFrame] loadData:data MIMEType:@"text/html" textEncodingName:@"utf-8" baseURL:baseURL];
968 }
969
970 - (void)_cleanUp
971 {
972     if (!webView)
973         return;
974
975     WebThreadLock();
976
977     [webView _clearDelegates];
978     [webView close];
979     [webView release];
980     webView = nil;
981
982     testRunner->notifyDone();
983 }
984
985 - (void)webView:(WebView *)sender didFailLoadWithError:(NSError *)error forFrame:(WebFrame *)frame
986 {
987     printf("API Test load failed\n");
988     [self _cleanUp];
989 }
990
991 - (void)webView:(WebView *)sender didFailProvisionalLoadWithError:(NSError *)error forFrame:(WebFrame *)frame
992 {
993     printf("API Test load failed provisional\n");
994     [self _cleanUp];
995 }
996
997 - (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
998 {
999     printf("API Test load succeeded\n");
1000     [self _cleanUp];
1001 }
1002
1003 @end
1004
1005 #endif
1006
1007 void TestRunner::apiTestNewWindowDataLoadBaseURL(JSStringRef utf8Data, JSStringRef baseURL)
1008 {
1009 #if PLATFORM(IOS)
1010     // On iOS this gets called via JavaScript on the WebThread. But since it creates
1011     // and closes a WebView, it should be run on the main thread. Make the switch
1012     // from the web thread to the main thread and make the test asynchronous.
1013     if (WebThreadIsCurrent()) {
1014         APITestDelegateIPhone *dispatcher = [[APITestDelegateIPhone alloc] initWithTestRunner:this utf8Data:utf8Data baseURL:baseURL];
1015         NSInvocation *invocation = WebThreadMakeNSInvocation(dispatcher, @selector(run));
1016         WebThreadCallDelegate(invocation);
1017         return;
1018     }
1019 #endif
1020
1021     @autoreleasepool {
1022         RetainPtr<CFStringRef> utf8DataCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, utf8Data));
1023         RetainPtr<CFStringRef> baseURLCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, baseURL));
1024
1025         WebView *webView = [[WebView alloc] initWithFrame:NSZeroRect frameName:@"" groupName:@""];
1026
1027         bool done = false;
1028         APITestDelegate *delegate = [[APITestDelegate alloc] initWithCompletionCondition:&done];
1029         [webView setFrameLoadDelegate:delegate];
1030
1031         [[webView mainFrame] loadData:[(__bridge NSString *)utf8DataCF.get() dataUsingEncoding:NSUTF8StringEncoding] MIMEType:@"text/html" textEncodingName:@"utf-8" baseURL:[NSURL URLWithString:(__bridge NSString *)baseURLCF.get()]];
1032
1033         while (!done) {
1034             @autoreleasepool {
1035                 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]];
1036             }
1037         }
1038
1039 #if PLATFORM(IOS)
1040         [(DumpRenderTree *)[UIApplication sharedApplication] _waitForWebThread];
1041 #endif
1042
1043         [webView close];
1044         [webView release];
1045         [delegate release];
1046     }
1047 }
1048
1049 void TestRunner::apiTestGoToCurrentBackForwardItem()
1050 {
1051     WebView *view = [mainFrame webView];
1052     [view goToBackForwardItem:[[view backForwardList] currentItem]];
1053 }
1054
1055 void TestRunner::setWebViewEditable(bool editable)
1056 {
1057     WebView *view = [mainFrame webView];
1058     [view setEditable:editable];
1059 }
1060
1061 static NSString *SynchronousLoaderRunLoopMode = @"DumpRenderTreeSynchronousLoaderRunLoopMode";
1062
1063 @interface SynchronousLoader : NSObject <NSURLConnectionDelegate>
1064 {
1065     NSString *m_username;
1066     NSString *m_password;
1067     BOOL m_isDone;
1068 }
1069 + (void)makeRequest:(NSURLRequest *)request withUsername:(NSString *)username password:(NSString *)password;
1070 @end
1071
1072 @implementation SynchronousLoader : NSObject
1073 - (void)dealloc
1074 {
1075     [m_username release];
1076     [m_password release];
1077
1078     [super dealloc];
1079 }
1080
1081 - (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection
1082 {
1083     return YES;
1084 }
1085
1086 - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
1087 {
1088     if ([challenge previousFailureCount] == 0) {
1089         RetainPtr<NSURLCredential> credential = adoptNS([[NSURLCredential alloc]  initWithUser:m_username password:m_password persistence:NSURLCredentialPersistenceForSession]);
1090         [[challenge sender] useCredential:credential.get() forAuthenticationChallenge:challenge];
1091         return;
1092     }
1093     [[challenge sender] cancelAuthenticationChallenge:challenge];
1094 }
1095
1096 - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
1097 {
1098     printf("SynchronousLoader failed: %s\n", [[error description] UTF8String]);
1099     m_isDone = YES;
1100 }
1101
1102 - (void)connectionDidFinishLoading:(NSURLConnection *)connection
1103 {
1104     m_isDone = YES;
1105 }
1106
1107 + (void)makeRequest:(NSURLRequest *)request withUsername:(NSString *)username password:(NSString *)password
1108 {
1109     ASSERT(![[request URL] user]);
1110     ASSERT(![[request URL] password]);
1111
1112     SynchronousLoader *delegate = [[SynchronousLoader alloc] init];
1113     delegate->m_username = [username copy];
1114     delegate->m_password = [password copy];
1115
1116     NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:delegate startImmediately:NO];
1117     [connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:SynchronousLoaderRunLoopMode];
1118     [connection start];
1119     
1120     while (!delegate->m_isDone)
1121         [[NSRunLoop currentRunLoop] runMode:SynchronousLoaderRunLoopMode beforeDate:[NSDate distantFuture]];
1122
1123     [connection cancel];
1124     
1125     [connection release];
1126     [delegate release];
1127 }
1128
1129 @end
1130
1131 void TestRunner::authenticateSession(JSStringRef url, JSStringRef username, JSStringRef password)
1132 {
1133     // See <rdar://problem/7880699>.
1134     RetainPtr<CFStringRef> urlStringCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, url));
1135     RetainPtr<CFStringRef> usernameCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, username));
1136     RetainPtr<CFStringRef> passwordCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, password));
1137
1138     RetainPtr<NSURLRequest> request = adoptNS([[NSURLRequest alloc] initWithURL:[NSURL URLWithString:(__bridge NSString *)urlStringCF.get()]]);
1139
1140     [SynchronousLoader makeRequest:request.get() withUsername:(__bridge NSString *)usernameCF.get() password:(__bridge NSString *)passwordCF.get()];
1141 }
1142
1143 void TestRunner::abortModal()
1144 {
1145 #if !PLATFORM(IOS)
1146     [NSApp abortModal];
1147 #endif
1148 }
1149
1150 void TestRunner::setSerializeHTTPLoads(bool serialize)
1151 {
1152     [WebView _setLoadResourcesSerially:serialize];
1153 }
1154
1155 void TestRunner::setTextDirection(JSStringRef directionName)
1156 {
1157 #if !PLATFORM(IOS)
1158     if (JSStringIsEqualToUTF8CString(directionName, "ltr"))
1159         [[mainFrame webView] makeBaseWritingDirectionLeftToRight:0];
1160     else if (JSStringIsEqualToUTF8CString(directionName, "rtl"))
1161         [[mainFrame webView] makeBaseWritingDirectionRightToLeft:0];
1162     else
1163         ASSERT_NOT_REACHED();
1164 #endif
1165 }
1166
1167 void TestRunner::addChromeInputField()
1168 {
1169 #if !PLATFORM(IOS)
1170     NSTextField *textField = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 100, 20)];
1171     textField.tag = 1;
1172     [[[[mainFrame webView] window] contentView] addSubview:textField];
1173     [textField release];
1174     
1175     [textField setNextKeyView:[mainFrame webView]];
1176     [[mainFrame webView] setNextKeyView:textField];
1177 #endif
1178 }
1179
1180 void TestRunner::removeChromeInputField()
1181 {
1182 #if !PLATFORM(IOS)
1183     NSView* textField = [[[[mainFrame webView] window] contentView] viewWithTag:1];
1184     if (textField) {
1185         [textField removeFromSuperview];
1186         focusWebView();
1187     }
1188 #endif
1189 }
1190
1191 void TestRunner::focusWebView()
1192 {
1193 #if !PLATFORM(IOS)
1194     [[[mainFrame webView] window] makeFirstResponder:[mainFrame webView]];
1195 #endif
1196 }
1197
1198 void TestRunner::setBackingScaleFactor(double backingScaleFactor)
1199 {
1200 #if !PLATFORM(IOS)
1201     [[mainFrame webView] _setCustomBackingScaleFactor:backingScaleFactor];
1202 #endif
1203 }
1204
1205 void TestRunner::resetPageVisibility()
1206 {
1207     WebView *webView = [mainFrame webView];
1208     if ([webView respondsToSelector:@selector(_setVisibilityState:isInitialState:)])
1209         [webView _setVisibilityState:WebPageVisibilityStateVisible isInitialState:YES];
1210 }
1211
1212 void TestRunner::setPageVisibility(const char* newVisibility)
1213 {
1214     if (!newVisibility)
1215         return;
1216
1217     WebView *webView = [mainFrame webView];
1218     if (!strcmp(newVisibility, "visible"))
1219         [webView _setVisibilityState:WebPageVisibilityStateVisible isInitialState:NO];
1220     else if (!strcmp(newVisibility, "hidden"))
1221         [webView _setVisibilityState:WebPageVisibilityStateHidden isInitialState:NO];
1222     else if (!strcmp(newVisibility, "prerender"))
1223         [webView _setVisibilityState:WebPageVisibilityStatePrerender isInitialState:NO];
1224 }
1225
1226 void TestRunner::grantWebNotificationPermission(JSStringRef jsOrigin)
1227 {
1228     RetainPtr<CFStringRef> cfOrigin = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, jsOrigin));
1229     ASSERT([[mainFrame webView] _notificationProvider] == [MockWebNotificationProvider shared]);
1230     [[MockWebNotificationProvider shared] setWebNotificationOrigin:(__bridge NSString *)cfOrigin.get() permission:TRUE];
1231 }
1232
1233 void TestRunner::denyWebNotificationPermission(JSStringRef jsOrigin)
1234 {
1235     RetainPtr<CFStringRef> cfOrigin = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, jsOrigin));
1236     ASSERT([[mainFrame webView] _notificationProvider] == [MockWebNotificationProvider shared]);
1237     [[MockWebNotificationProvider shared] setWebNotificationOrigin:(__bridge NSString *)cfOrigin.get() permission:FALSE];
1238 }
1239
1240 void TestRunner::removeAllWebNotificationPermissions()
1241 {
1242     [[MockWebNotificationProvider shared] removeAllWebNotificationPermissions];
1243 }
1244
1245 void TestRunner::simulateWebNotificationClick(JSValueRef jsNotification)
1246 {
1247     uint64_t notificationID = [[mainFrame webView] _notificationIDForTesting:jsNotification];
1248     m_hasPendingWebNotificationClick = true;
1249     dispatch_async(dispatch_get_main_queue(), ^{
1250         if (!m_hasPendingWebNotificationClick)
1251             return;
1252
1253         [[MockWebNotificationProvider shared] simulateWebNotificationClick:notificationID];
1254         m_hasPendingWebNotificationClick = false;
1255     });
1256 }
1257
1258 void TestRunner::simulateLegacyWebNotificationClick(JSStringRef jsTitle)
1259 {
1260 }
1261
1262 static NSString * const WebArchivePboardType = @"Apple Web Archive pasteboard type";
1263 static NSString * const WebSubresourcesKey = @"WebSubresources";
1264 static NSString * const WebSubframeArchivesKey = @"WebResourceMIMEType like 'image*'";
1265
1266 unsigned TestRunner::imageCountInGeneralPasteboard() const
1267 {
1268 #if PLATFORM(MAC)
1269     NSData *data = [[NSPasteboard generalPasteboard] dataForType:WebArchivePboardType];
1270 #elif PLATFORM(IOS)
1271     NSData *data = [[UIPasteboard generalPasteboard] valueForPasteboardType:WebArchivePboardType];
1272 #endif
1273     if (!data)
1274         return 0;
1275     
1276     NSError *error = nil;
1277     id webArchive = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListImmutable format:NULL error:&error];
1278     if (error) {
1279         NSLog(@"Encountered error while serializing Web Archive pasteboard data: %@", error);
1280         return 0;
1281     }
1282     
1283     NSArray *subItems = [NSArray arrayWithArray:[webArchive objectForKey:WebSubresourcesKey]];
1284     NSPredicate *predicate = [NSPredicate predicateWithFormat:WebSubframeArchivesKey];
1285     NSArray *imagesArray = [subItems filteredArrayUsingPredicate:predicate];
1286     
1287     if (!imagesArray)
1288         return 0;
1289     
1290     return imagesArray.count;
1291 }