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