/* * Copyright (C) 2007-2016 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import "config.h" #import "DumpRenderTree.h" #import "TestRunner.h" #import "DefaultPolicyDelegate.h" #import "EditingDelegate.h" #import "JSBasics.h" #import "LayoutTestSpellChecker.h" #import "MockGeolocationProvider.h" #import "MockWebNotificationProvider.h" #import "PolicyDelegate.h" #import "UIDelegate.h" #import "WorkQueue.h" #import "WorkQueueItem.h" #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #if PLATFORM(IOS_FAMILY) #import "UIKitSPI.h" #import #import #import #endif #if !PLATFORM(IOS_FAMILY) @interface CommandValidationTarget : NSObject { SEL _action; } - (id)initWithAction:(SEL)action; @end @implementation CommandValidationTarget - (id)initWithAction:(SEL)action { self = [super init]; if (!self) return nil; _action = action; return self; } - (SEL)action { return _action; } - (NSInteger)tag { return 0; } @end #endif @interface WebGeolocationPosition (Internal) - (id)initWithGeolocationPosition:(WebCore::GeolocationPositionData&&)coreGeolocationPosition; @end TestRunner::~TestRunner() { } JSContextRef TestRunner::mainFrameJSContext() { return [mainFrame globalContext]; } void TestRunner::addDisallowedURL(JSStringRef url) { RetainPtr urlCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, url)); if (!disallowedURLs) disallowedURLs = adoptCF(CFSetCreateMutable(kCFAllocatorDefault, 0, NULL)); // Canonicalize the URL NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:(__bridge NSString *)urlCF.get()]]; request = [NSURLProtocol canonicalRequestForRequest:request]; CFSetAddValue(disallowedURLs.get(), (__bridge CFURLRef)[request URL]); } bool TestRunner::callShouldCloseOnWebView() { return [[mainFrame webView] shouldClose]; } void TestRunner::clearAllApplicationCaches() { [WebApplicationCache deleteAllApplicationCaches]; } long long TestRunner::applicationCacheDiskUsageForOrigin(JSStringRef url) { auto urlCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, url)); auto origin = adoptNS([[WebSecurityOrigin alloc] initWithURL:[NSURL URLWithString:(__bridge NSString *)urlCF.get()]]); long long usage = [WebApplicationCache diskUsageForOrigin:origin.get()]; return usage; } void TestRunner::clearApplicationCacheForOrigin(JSStringRef url) { auto urlCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, url)); auto origin = adoptNS([[WebSecurityOrigin alloc] initWithURL:[NSURL URLWithString:(__bridge NSString *)urlCF.get()]]); [WebApplicationCache deleteCacheForOrigin:origin.get()]; } static JSObjectRef originsArrayToJS(JSContextRef context, NSArray *origins) { auto count = [origins count]; auto array = JSObjectMakeArray(context, 0, nullptr, nullptr); for (NSUInteger i = 0; i < count; i++) { NSString *origin = [[origins objectAtIndex:i] databaseIdentifier]; auto originJS = adopt(JSStringCreateWithCFString((__bridge CFStringRef)origin)); JSObjectSetPropertyAtIndex(context, array, i, JSValueMakeString(context, originJS.get()), 0); } return array; } JSValueRef TestRunner::originsWithApplicationCache(JSContextRef context) { return originsArrayToJS(context, [WebApplicationCache originsWithCache]); } void TestRunner::clearAllDatabases() { [[WebDatabaseManager sharedWebDatabaseManager] deleteAllDatabases]; [[WebDatabaseManager sharedWebDatabaseManager] deleteAllIndexedDatabases]; } void TestRunner::setStorageDatabaseIdleInterval(double interval) { [WebStorageManager setStorageDatabaseIdleInterval:interval]; } void TestRunner::setSpellCheckerLoggingEnabled(bool enabled) { #if PLATFORM(MAC) [LayoutTestSpellChecker checker].spellCheckerLoggingEnabled = enabled; #else UNUSED_PARAM(enabled); #endif } void TestRunner::closeIdleLocalStorageDatabases() { [WebStorageManager closeIdleLocalStorageDatabases]; } void TestRunner::clearBackForwardList() { WebBackForwardList *backForwardList = [[mainFrame webView] backForwardList]; auto item = retainPtr([backForwardList currentItem]); // We clear the history by setting the back/forward list's capacity to 0 // then restoring it back and adding back the current item. int capacity = [backForwardList capacity]; [backForwardList setCapacity:0]; [backForwardList setCapacity:capacity]; [backForwardList addItem:item.get()]; [backForwardList goToItem:item.get()]; } JSRetainPtr TestRunner::copyDecodedHostName(JSStringRef name) { auto nameCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, name)); NSString *nameNS = (__bridge NSString *)nameCF.get(); return adopt(JSStringCreateWithCFString((__bridge CFStringRef)[nameNS _web_decodeHostName])); } JSRetainPtr TestRunner::copyEncodedHostName(JSStringRef name) { auto nameCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, name)); NSString *nameNS = (__bridge NSString *)nameCF.get(); return adopt(JSStringCreateWithCFString((__bridge CFStringRef)[nameNS _web_encodeHostName])); } void TestRunner::display() { displayWebView(); } void TestRunner::displayAndTrackRepaints() { displayAndTrackRepaintsWebView(); } void TestRunner::keepWebHistory() { if (![WebHistory optionalSharedHistory]) [WebHistory setOptionalSharedHistory:adoptNS([[WebHistory alloc] init]).get()]; } int TestRunner::numberOfPendingGeolocationPermissionRequests() { return [(UIDelegate *)[[mainFrame webView] UIDelegate] numberOfPendingGeolocationPermissionRequests]; } bool TestRunner::isGeolocationProviderActive() { return MockGeolocationProvider.shared.isActive; } size_t TestRunner::webHistoryItemCount() { return [[[WebHistory optionalSharedHistory] allItems] count]; } void TestRunner::notifyDone() { if (m_waitToDump) { m_waitToDump = false; if (!topLoadingFrame && !DRT::WorkQueue::singleton().count()) dump(); } else fprintf(stderr, "TestRunner::notifyDone() called unexpectedly."); } void TestRunner::forceImmediateCompletion() { if (m_waitToDump) { m_waitToDump = false; if (!DRT::WorkQueue::singleton().count()) dump(); } else fprintf(stderr, "TestRunner::forceImmediateCompletion() called unexpectedly."); } static inline std::string stringFromJSString(JSStringRef jsString) { size_t maxBufferSize = JSStringGetMaximumUTF8CStringSize(jsString); char* utf8Buffer = new char[maxBufferSize]; size_t bytesWrittenToUTF8Buffer = JSStringGetUTF8CString(jsString, utf8Buffer, maxBufferSize); std::string stdString(utf8Buffer, bytesWrittenToUTF8Buffer - 1); // bytesWrittenToUTF8Buffer includes a trailing \0 which std::string doesn't need. delete[] utf8Buffer; return stdString; } static inline size_t indexOfSeparatorAfterDirectoryName(const std::string& directoryName, const std::string& fullPath) { std::string searchKey = "/" + directoryName + "/"; size_t indexOfSearchKeyStart = fullPath.rfind(searchKey); if (indexOfSearchKeyStart == std::string::npos) { ASSERT_NOT_REACHED(); return 0; } // Callers expect the return value not to end in "/", so searchKey.length() - 1. return indexOfSearchKeyStart + searchKey.length() - 1; } static inline std::string resourceRootAbsolutePath(const std::string& testURL, const std::string& expectedRootName) { char* localResourceRootEnv = getenv("LOCAL_RESOURCE_ROOT"); if (localResourceRootEnv) return std::string(localResourceRootEnv); // This fallback approach works for non-http tests and is useful // in the case when we're running DRT directly from the command line. return testURL.substr(0, indexOfSeparatorAfterDirectoryName(expectedRootName, testURL)); } JSRetainPtr TestRunner::pathToLocalResource(JSContextRef context, JSStringRef localResourceJSString) { // The passed in path will be an absolute path to the resource starting // with "/tmp" or "/tmp/LayoutTests", optionally starting with the explicit file:// protocol. // /tmp maps to DUMPRENDERTREE_TEMP, and /tmp/LayoutTests maps to LOCAL_RESOURCE_ROOT. // FIXME: This code should work on all *nix platforms and can be moved into TestRunner.cpp. std::string expectedRootName; std::string absolutePathToResourceRoot; std::string localResourceString = stringFromJSString(localResourceJSString); if (localResourceString.find("LayoutTests") != std::string::npos) { expectedRootName = "LayoutTests"; absolutePathToResourceRoot = resourceRootAbsolutePath(m_testURL, expectedRootName); } else if (localResourceString.find("tmp") != std::string::npos) { expectedRootName = "tmp"; absolutePathToResourceRoot = getenv("DUMPRENDERTREE_TEMP"); } else { ASSERT_NOT_REACHED(); // pathToLocalResource was passed a path it doesn't know how to map. } ASSERT(!absolutePathToResourceRoot.empty()); size_t indexOfSeparatorAfterRootName = indexOfSeparatorAfterDirectoryName(expectedRootName, localResourceString); std::string absolutePathToLocalResource = absolutePathToResourceRoot + localResourceString.substr(indexOfSeparatorAfterRootName); // Note: It's important that we keep the file:// or http tests will get confused. if (localResourceString.find("file://") != std::string::npos) { ASSERT(absolutePathToLocalResource[0] == '/'); absolutePathToLocalResource = std::string("file://") + absolutePathToLocalResource; } return adopt(JSStringCreateWithUTF8CString(absolutePathToLocalResource.c_str())); } void TestRunner::queueLoad(JSStringRef url, JSStringRef target) { RetainPtr urlCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, url)); NSString *urlNS = (__bridge NSString *)urlCF.get(); NSURL *nsurl = [NSURL URLWithString:urlNS relativeToURL:[[[mainFrame dataSource] response] URL]]; NSString *nsurlString = [nsurl absoluteString]; auto absoluteURL = adopt(JSStringCreateWithUTF8CString([nsurlString UTF8String])); DRT::WorkQueue::singleton().queue(new LoadItem(absoluteURL.get(), target)); } void TestRunner::setAcceptsEditing(bool newAcceptsEditing) { [(EditingDelegate *)[[mainFrame webView] editingDelegate] setAcceptsEditing:newAcceptsEditing]; } void TestRunner::setAlwaysAcceptCookies(bool alwaysAcceptCookies) { if (alwaysAcceptCookies == m_alwaysAcceptCookies) return; m_alwaysAcceptCookies = alwaysAcceptCookies; NSHTTPCookieAcceptPolicy cookieAcceptPolicy = alwaysAcceptCookies ? NSHTTPCookieAcceptPolicyAlways : NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain; [WebPreferences _setCurrentNetworkLoaderSessionCookieAcceptPolicy:cookieAcceptPolicy]; } void TestRunner::setOnlyAcceptFirstPartyCookies(bool onlyAcceptFirstPartyCookies) { if (onlyAcceptFirstPartyCookies) m_alwaysAcceptCookies = NO; NSHTTPCookieAcceptPolicy cookieAcceptPolicy = onlyAcceptFirstPartyCookies ? static_cast(NSHTTPCookieAcceptPolicyExclusivelyFromMainDocumentDomain) : NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain; [WebPreferences _setCurrentNetworkLoaderSessionCookieAcceptPolicy:cookieAcceptPolicy]; } void TestRunner::setAppCacheMaximumSize(unsigned long long size) { [WebApplicationCache setMaximumSize:size]; } void TestRunner::setCustomPolicyDelegate(bool setDelegate, bool permissive) { if (!setDelegate) { [[mainFrame webView] setPolicyDelegate:defaultPolicyDelegate.get()]; return; } [policyDelegate setPermissive:permissive]; [[mainFrame webView] setPolicyDelegate:policyDelegate.get()]; } void TestRunner::setDatabaseQuota(unsigned long long quota) { auto origin = adoptNS([[WebSecurityOrigin alloc] initWithURL:[NSURL URLWithString:@"file:///"]]); [[origin databaseQuotaManager] setQuota:quota]; } void TestRunner::goBack() { [[mainFrame webView] goBack]; } void TestRunner::setDefersLoading(bool defers) { [[mainFrame webView] setDefersCallbacks:defers]; } void TestRunner::setDomainRelaxationForbiddenForURLScheme(bool forbidden, JSStringRef scheme) { RetainPtr schemeCFString = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, scheme)); [WebView _setDomainRelaxationForbidden:forbidden forURLScheme:(__bridge NSString *)schemeCFString.get()]; } void TestRunner::setMockDeviceOrientation(bool canProvideAlpha, double alpha, bool canProvideBeta, double beta, bool canProvideGamma, double gamma) { // DumpRenderTree configured the WebView to use WebDeviceOrientationProviderMock. id provider = [[mainFrame webView] _deviceOrientationProvider]; WebDeviceOrientationProviderMock *mockProvider = static_cast(provider); auto orientation = adoptNS([[WebDeviceOrientation alloc] initWithCanProvideAlpha:canProvideAlpha alpha:alpha canProvideBeta:canProvideBeta beta:beta canProvideGamma:canProvideGamma gamma:gamma]); [mockProvider setOrientation:orientation.get()]; } 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) { RetainPtr position; if (!providesAltitude && !providesAltitudeAccuracy && !providesHeading && !providesSpeed) { // Test the exposed API. position = adoptNS([[WebGeolocationPosition alloc] initWithTimestamp:WallTime::now().secondsSinceEpoch().seconds() latitude:latitude longitude:longitude accuracy:accuracy]); } else { WebCore::GeolocationPositionData geolocationPosition { WallTime::now().secondsSinceEpoch().seconds(), latitude, longitude, accuracy }; if (providesAltitude) geolocationPosition.altitude = altitude; if (providesAltitudeAccuracy) geolocationPosition.altitudeAccuracy = altitudeAccuracy; if (providesHeading) geolocationPosition.heading = heading; if (providesSpeed) geolocationPosition.speed = speed; if (providesFloorLevel) geolocationPosition.floorLevel = floorLevel; position = adoptNS([[WebGeolocationPosition alloc] initWithGeolocationPosition:(WTFMove(geolocationPosition))]); } [[MockGeolocationProvider shared] setPosition:position.get()]; } void TestRunner::setMockGeolocationPositionUnavailableError(JSStringRef message) { RetainPtr messageCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, message)); NSString *messageNS = (__bridge NSString *)messageCF.get(); [[MockGeolocationProvider shared] setPositionUnavailableErrorWithMessage:messageNS]; } void TestRunner::setGeolocationPermission(bool allow) { setGeolocationPermissionCommon(allow); [(UIDelegate *)[[mainFrame webView] UIDelegate] didSetMockGeolocationPermission]; } void TestRunner::setIconDatabaseEnabled(bool iconDatabaseEnabled) { [WebView _setIconLoadingEnabled:iconDatabaseEnabled]; } void TestRunner::setMainFrameIsFirstResponder(bool flag) { #if !PLATFORM(IOS_FAMILY) NSView *documentView = [[mainFrame frameView] documentView]; NSResponder *firstResponder = flag ? documentView : nil; [[[mainFrame webView] window] makeFirstResponder:firstResponder]; #endif } void TestRunner::setPrivateBrowsingEnabled(bool privateBrowsingEnabled) { [[[mainFrame webView] preferences] setPrivateBrowsingEnabled:privateBrowsingEnabled]; } void TestRunner::setAutomaticLinkDetectionEnabled(bool enabled) { #if !PLATFORM(IOS_FAMILY) [[mainFrame webView] setAutomaticLinkDetectionEnabled:enabled]; #endif } void TestRunner::setTabKeyCyclesThroughElements(bool cycles) { [[mainFrame webView] setTabKeyCyclesThroughElements:cycles]; } #if PLATFORM(IOS_FAMILY) void TestRunner::setPagePaused(bool paused) { [gWebBrowserView setPaused:paused]; } #endif void TestRunner::setUserStyleSheetEnabled(bool flag) { [[[mainFrame webView] preferences] setUserStyleSheetEnabled:flag]; } void TestRunner::setUserStyleSheetLocation(JSStringRef path) { RetainPtr pathCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, path)); NSURL *url = [NSURL URLWithString:(__bridge NSString *)pathCF.get()]; [[[mainFrame webView] preferences] setUserStyleSheetLocation:url]; } void TestRunner::setValueForUser(JSContextRef context, JSValueRef nodeObject, JSStringRef value) { DOMElement *element = [DOMElement _DOMElementFromJSContext:context value:nodeObject]; if (!element || ![element isKindOfClass:[DOMHTMLInputElement class]]) return; RetainPtr valueCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, value)); [(DOMHTMLInputElement *)element setValueForUser:(__bridge NSString *)valueCF.get()]; } void TestRunner::dispatchPendingLoadRequests() { [[mainFrame webView] _dispatchPendingLoadRequests]; } void TestRunner::removeAllVisitedLinks() { [WebHistory _removeAllVisitedLinks]; } void TestRunner::setPersistentUserStyleSheetLocation(JSStringRef jsURL) { RetainPtr urlString = adoptCF(JSStringCopyCFString(0, jsURL)); ::setPersistentUserStyleSheetLocation(urlString.get()); } void TestRunner::clearPersistentUserStyleSheet() { ::setPersistentUserStyleSheetLocation(0); } void TestRunner::setWindowIsKey(bool windowIsKey) { m_windowIsKey = windowIsKey; [[mainFrame webView] _updateActiveState]; } void TestRunner::setViewSize(double width, double height) { [[mainFrame webView] setFrameSize:NSMakeSize(width, height)]; } static void waitUntilDoneWatchdogFired(CFRunLoopTimerRef timer, void* info) { gTestRunner->waitToDumpWatchdogTimerFired(); } void TestRunner::setWaitToDump(bool waitUntilDone) { m_waitToDump = waitUntilDone; if (m_waitToDump && m_timeout && shouldSetWaitToDumpWatchdog()) setWaitToDumpWatchdog(adoptCF(CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + m_timeout / 1000.0, 0, 0, 0, waitUntilDoneWatchdogFired, NULL))); } int TestRunner::windowCount() { return CFArrayGetCount(openWindowsRef); } void TestRunner::execCommand(JSStringRef name, JSStringRef value) { RetainPtr nameCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, name)); NSString *nameNS = (__bridge NSString *)nameCF.get(); RetainPtr valueCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, value)); NSString *valueNS = (__bridge NSString *)valueCF.get(); [[mainFrame webView] _executeCoreCommandByName:nameNS value:valueNS]; } bool TestRunner::findString(JSContextRef context, JSStringRef target, JSObjectRef optionsArray) { WebFindOptions options = 0; auto length = WTR::arrayLength(context, optionsArray); auto targetCFString = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, target)); for (unsigned i = 0; i < length; ++i) { JSValueRef value = JSObjectGetPropertyAtIndex(context, optionsArray, i, 0); if (!JSValueIsString(context, value)) continue; auto optionName = adopt(JSValueToStringCopy(context, value, nullptr)); if (JSStringIsEqualToUTF8CString(optionName.get(), "CaseInsensitive")) options |= WebFindOptionsCaseInsensitive; else if (JSStringIsEqualToUTF8CString(optionName.get(), "AtWordStarts")) options |= WebFindOptionsAtWordStarts; else if (JSStringIsEqualToUTF8CString(optionName.get(), "TreatMedialCapitalAsWordStart")) options |= WebFindOptionsTreatMedialCapitalAsWordStart; else if (JSStringIsEqualToUTF8CString(optionName.get(), "Backwards")) options |= WebFindOptionsBackwards; else if (JSStringIsEqualToUTF8CString(optionName.get(), "WrapAround")) options |= WebFindOptionsWrapAround; else if (JSStringIsEqualToUTF8CString(optionName.get(), "StartInSelection")) options |= WebFindOptionsStartInSelection; } return [[mainFrame webView] findString:(__bridge NSString *)targetCFString.get() options:options]; } void TestRunner::setCacheModel(int cacheModel) { [[[mainFrame webView] preferences] setCacheModel:(WebCacheModel)cacheModel]; } bool TestRunner::isCommandEnabled(JSStringRef name) { #if !PLATFORM(IOS_FAMILY) RetainPtr nameCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, name)); NSString *nameNS = (__bridge NSString *)nameCF.get(); // Accept command strings with capital letters for first letter without trailing colon. if (![nameNS hasSuffix:@":"] && [nameNS length]) { nameNS = [[[[nameNS substringToIndex:1] lowercaseString] stringByAppendingString:[nameNS substringFromIndex:1]] stringByAppendingString:@":"]; } SEL selector = NSSelectorFromString(nameNS); RetainPtr target = adoptNS([[CommandValidationTarget alloc] initWithAction:selector]); id validator = [NSApp targetForAction:selector to:[mainFrame webView] from:target.get()]; if (!validator) return false; if (![validator respondsToSelector:selector]) return false; if (![validator respondsToSelector:@selector(validateUserInterfaceItem:)]) return true; return [validator validateUserInterfaceItem:target.get()]; #else return false; #endif } void TestRunner::waitForPolicyDelegate() { setWaitToDump(true); [policyDelegate setControllerToNotifyDone:this]; [[mainFrame webView] setPolicyDelegate:policyDelegate.get()]; } void TestRunner::addOriginAccessAllowListEntry(JSStringRef sourceOrigin, JSStringRef destinationProtocol, JSStringRef destinationHost, bool allowDestinationSubdomains) { RetainPtr sourceOriginCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, sourceOrigin)); NSString *sourceOriginNS = (__bridge NSString *)sourceOriginCF.get(); RetainPtr protocolCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, destinationProtocol)); NSString *destinationProtocolNS = (__bridge NSString *)protocolCF.get(); RetainPtr hostCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, destinationHost)); NSString *destinationHostNS = (__bridge NSString *)hostCF.get(); [WebView _addOriginAccessAllowListEntryWithSourceOrigin:sourceOriginNS destinationProtocol:destinationProtocolNS destinationHost:destinationHostNS allowDestinationSubdomains:allowDestinationSubdomains]; } void TestRunner::removeOriginAccessAllowListEntry(JSStringRef sourceOrigin, JSStringRef destinationProtocol, JSStringRef destinationHost, bool allowDestinationSubdomains) { RetainPtr sourceOriginCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, sourceOrigin)); NSString *sourceOriginNS = (__bridge NSString *)sourceOriginCF.get(); RetainPtr protocolCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, destinationProtocol)); NSString *destinationProtocolNS = (__bridge NSString *)protocolCF.get(); RetainPtr hostCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, destinationHost)); NSString *destinationHostNS = (__bridge NSString *)hostCF.get(); [WebView _removeOriginAccessAllowListEntryWithSourceOrigin:sourceOriginNS destinationProtocol:destinationProtocolNS destinationHost:destinationHostNS allowDestinationSubdomains:allowDestinationSubdomains]; } void TestRunner::setScrollbarPolicy(JSStringRef orientation, JSStringRef policy) { // FIXME: implement } void TestRunner::addUserScript(JSStringRef source, bool runAtStart, bool allFrames) { RetainPtr sourceCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, source)); NSString *sourceNS = (__bridge NSString *)sourceCF.get(); [WebView _addUserScriptToGroup:@"org.webkit.DumpRenderTree" world:[WebScriptWorld world] source:sourceNS url:nil includeMatchPatternStrings:nil excludeMatchPatternStrings:nil injectionTime:(runAtStart ? WebInjectAtDocumentStart : WebInjectAtDocumentEnd) injectedFrames:(allFrames ? WebInjectInAllFrames : WebInjectInTopFrameOnly)]; } void TestRunner::addUserStyleSheet(JSStringRef source, bool allFrames) { RetainPtr sourceCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, source)); NSString *sourceNS = (__bridge NSString *)sourceCF.get(); [WebView _addUserStyleSheetToGroup:@"org.webkit.DumpRenderTree" world:[WebScriptWorld world] source:sourceNS url:nil includeMatchPatternStrings:nil excludeMatchPatternStrings:nil injectedFrames:(allFrames ? WebInjectInAllFrames : WebInjectInTopFrameOnly)]; } void TestRunner::showWebInspector() { [[[mainFrame webView] inspector] show:nil]; } void TestRunner::closeWebInspector() { [[[mainFrame webView] inspector] close:nil]; } void TestRunner::evaluateInWebInspector(JSStringRef script) { RetainPtr scriptCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, script)); NSString *scriptNS = (__bridge NSString *)scriptCF.get(); [[[mainFrame webView] inspector] evaluateInFrontend:nil script:scriptNS]; } JSRetainPtr TestRunner::inspectorTestStubURL() { #if PLATFORM(IOS_FAMILY) return nullptr; #else CFBundleRef inspectorBundle = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.WebInspectorUI")); if (!inspectorBundle) return nullptr; RetainPtr url = adoptCF(CFBundleCopyResourceURL(inspectorBundle, CFSTR("TestStub"), CFSTR("html"), NULL)); if (!url) return nullptr; CFStringRef urlString = CFURLGetString(url.get()); return adopt(JSStringCreateWithCFString(urlString)); #endif } typedef HashMap > WorldMap; static WorldMap& worldMap() { static WorldMap& map = *new WorldMap; return map; } unsigned worldIDForWorld(WebScriptWorld *world) { WorldMap::const_iterator end = worldMap().end(); for (WorldMap::const_iterator it = worldMap().begin(); it != end; ++it) { if (it->value == world) return it->key; } return 0; } void TestRunner::evaluateScriptInIsolatedWorldAndReturnValue(unsigned worldID, JSObjectRef globalObject, JSStringRef script) { // FIXME: Implement this. } void TestRunner::evaluateScriptInIsolatedWorld(unsigned worldID, JSObjectRef globalObject, JSStringRef script) { RetainPtr scriptCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, script)); NSString *scriptNS = (__bridge NSString *)scriptCF.get(); // A worldID of 0 always corresponds to a new world. Any other worldID corresponds to a world // that is created once and cached forever. WebScriptWorld *world; if (!worldID) world = [WebScriptWorld world]; else { RetainPtr& worldSlot = worldMap().add(worldID, nullptr).iterator->value; if (!worldSlot) worldSlot = adoptNS([[WebScriptWorld alloc] init]); world = worldSlot.get(); } [mainFrame _stringByEvaluatingJavaScriptFromString:scriptNS withGlobalObject:globalObject inScriptWorld:world]; } @interface APITestDelegate : NSObject { bool* m_condition; } @end @implementation APITestDelegate - (id)initWithCompletionCondition:(bool*)condition { self = [super init]; if (!self) return nil; ASSERT(condition); m_condition = condition; *m_condition = false; return self; } - (void)webView:(WebView *)sender didFailLoadWithError:(NSError *)error forFrame:(WebFrame *)frame { printf("API Test load failed\n"); *m_condition = true; } - (void)webView:(WebView *)sender didFailProvisionalLoadWithError:(NSError *)error forFrame:(WebFrame *)frame { printf("API Test load failed provisional\n"); *m_condition = true; } - (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame { printf("API Test load succeeded\n"); *m_condition = true; } @end #if PLATFORM(IOS_FAMILY) @interface APITestDelegateIPhone : NSObject { TestRunner* testRunner; RetainPtr data; RetainPtr baseURL; RetainPtr webView; } - (id)initWithTestRunner:(TestRunner*)testRunner utf8Data:(JSStringRef)data baseURL:(JSStringRef)baseURL; - (void)run; @end @implementation APITestDelegateIPhone - (id)initWithTestRunner:(TestRunner*)runner utf8Data:(JSStringRef)dataString baseURL:(JSStringRef)baseURLString { self = [super init]; if (!self) return nil; testRunner = runner; data = [(__bridge NSString *)adoptCF(JSStringCopyCFString(kCFAllocatorDefault, dataString)).get() dataUsingEncoding:NSUTF8StringEncoding]; baseURL = [NSURL URLWithString:(__bridge NSString *)adoptCF(JSStringCopyCFString(kCFAllocatorDefault, baseURLString)).get()]; return self; } - (void)dealloc { [super dealloc]; } - (void)run { if (webView) return; testRunner->setWaitToDump(true); WebThreadLock(); webView = adoptNS([[WebView alloc] initWithFrame:NSZeroRect frameName:@"" groupName:@""]); [webView setFrameLoadDelegate:self]; [[webView mainFrame] loadData:data.get() MIMEType:@"text/html" textEncodingName:@"utf-8" baseURL:baseURL.get()]; } - (void)_cleanUp { if (!webView) return; WebThreadLock(); [webView _clearDelegates]; [webView close]; webView = nil; testRunner->notifyDone(); } - (void)webView:(WebView *)sender didFailLoadWithError:(NSError *)error forFrame:(WebFrame *)frame { printf("API Test load failed\n"); [self _cleanUp]; } - (void)webView:(WebView *)sender didFailProvisionalLoadWithError:(NSError *)error forFrame:(WebFrame *)frame { printf("API Test load failed provisional\n"); [self _cleanUp]; } - (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame { printf("API Test load succeeded\n"); [self _cleanUp]; } @end #endif void TestRunner::apiTestNewWindowDataLoadBaseURL(JSStringRef utf8Data, JSStringRef baseURL) { #if PLATFORM(IOS_FAMILY) // On iOS this gets called via JavaScript on the WebThread. But since it creates // and closes a WebView, it should be run on the main thread. Make the switch // from the web thread to the main thread and make the test asynchronous. if (WebThreadIsCurrent()) { auto dispatcher = adoptNS([[APITestDelegateIPhone alloc] initWithTestRunner:this utf8Data:utf8Data baseURL:baseURL]); NSInvocation *invocation = WebThreadMakeNSInvocation(dispatcher.get(), @selector(run)); WebThreadCallDelegate(invocation); return; } #endif @autoreleasepool { RetainPtr utf8DataCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, utf8Data)); RetainPtr baseURLCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, baseURL)); auto webView = adoptNS([[WebView alloc] initWithFrame:NSZeroRect frameName:@"" groupName:@""]); bool done = false; auto delegate = adoptNS([[APITestDelegate alloc] initWithCompletionCondition:&done]); [webView setFrameLoadDelegate:delegate.get()]; [[webView mainFrame] loadData:[(__bridge NSString *)utf8DataCF.get() dataUsingEncoding:NSUTF8StringEncoding] MIMEType:@"text/html" textEncodingName:@"utf-8" baseURL:[NSURL URLWithString:(__bridge NSString *)baseURLCF.get()]]; while (!done) { @autoreleasepool { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]]; } } #if PLATFORM(IOS_FAMILY) [(DumpRenderTree *)[UIApplication sharedApplication] _waitForWebThread]; #endif [webView close]; } } void TestRunner::apiTestGoToCurrentBackForwardItem() { WebView *view = [mainFrame webView]; [view goToBackForwardItem:[[view backForwardList] currentItem]]; } void TestRunner::setWebViewEditable(bool editable) { WebView *view = [mainFrame webView]; [view setEditable:editable]; } static NSString *SynchronousLoaderRunLoopMode = @"DumpRenderTreeSynchronousLoaderRunLoopMode"; @interface SynchronousLoader : NSObject { RetainPtr m_username; RetainPtr m_password; BOOL m_isDone; } + (void)makeRequest:(NSURLRequest *)request withUsername:(NSString *)username password:(NSString *)password; @end @implementation SynchronousLoader : NSObject - (void)dealloc { [super dealloc]; } - (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection { return YES; } IGNORE_WARNINGS_BEGIN("deprecated-implementations") - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge IGNORE_WARNINGS_END { if ([challenge previousFailureCount] == 0) { RetainPtr credential = adoptNS([[NSURLCredential alloc] initWithUser:m_username.get() password:m_password.get() persistence:NSURLCredentialPersistenceForSession]); [[challenge sender] useCredential:credential.get() forAuthenticationChallenge:challenge]; return; } [[challenge sender] cancelAuthenticationChallenge:challenge]; } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { printf("SynchronousLoader failed: %s\n", [[error description] UTF8String]); m_isDone = YES; } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { m_isDone = YES; } + (void)makeRequest:(NSURLRequest *)request withUsername:(NSString *)username password:(NSString *)password { ASSERT(![[request URL] user]); ASSERT(![[request URL] password]); auto delegate = adoptNS([[SynchronousLoader alloc] init]); delegate.get()->m_username = adoptNS([username copy]); delegate.get()->m_password = adoptNS([password copy]); auto connection = adoptNS([[NSURLConnection alloc] initWithRequest:request delegate:delegate.get() startImmediately:NO]); [connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:SynchronousLoaderRunLoopMode]; [connection start]; while (!delegate.get()->m_isDone) [[NSRunLoop currentRunLoop] runMode:SynchronousLoaderRunLoopMode beforeDate:[NSDate distantFuture]]; [connection cancel]; } @end void TestRunner::authenticateSession(JSStringRef url, JSStringRef username, JSStringRef password) { // See . RetainPtr urlStringCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, url)); RetainPtr usernameCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, username)); RetainPtr passwordCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, password)); RetainPtr request = adoptNS([[NSURLRequest alloc] initWithURL:[NSURL URLWithString:(__bridge NSString *)urlStringCF.get()]]); [SynchronousLoader makeRequest:request.get() withUsername:(__bridge NSString *)usernameCF.get() password:(__bridge NSString *)passwordCF.get()]; } void TestRunner::abortModal() { #if !PLATFORM(IOS_FAMILY) [NSApp abortModal]; #endif } void TestRunner::setSerializeHTTPLoads(bool serialize) { [WebView _setLoadResourcesSerially:serialize]; } void TestRunner::setTextDirection(JSStringRef directionName) { #if !PLATFORM(IOS_FAMILY) if (JSStringIsEqualToUTF8CString(directionName, "ltr")) [[mainFrame webView] makeBaseWritingDirectionLeftToRight:0]; else if (JSStringIsEqualToUTF8CString(directionName, "rtl")) [[mainFrame webView] makeBaseWritingDirectionRightToLeft:0]; else ASSERT_NOT_REACHED(); #endif } void TestRunner::addChromeInputField() { #if !PLATFORM(IOS_FAMILY) auto textField = adoptNS([[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 100, 20)]); [textField setTag:1]; [[[[mainFrame webView] window] contentView] addSubview:textField.get()]; [textField setNextKeyView:[mainFrame webView]]; [[mainFrame webView] setNextKeyView:textField.get()]; #endif } void TestRunner::removeChromeInputField() { #if !PLATFORM(IOS_FAMILY) NSView* textField = [[[[mainFrame webView] window] contentView] viewWithTag:1]; if (textField) { [textField removeFromSuperview]; focusWebView(); } #endif } void TestRunner::setTextInChromeInputField(const String&) { } void TestRunner::selectChromeInputField() { } String TestRunner::getSelectedTextInChromeInputField() { return { }; } void TestRunner::focusWebView() { #if !PLATFORM(IOS_FAMILY) [[[mainFrame webView] window] makeFirstResponder:[mainFrame webView]]; #endif } void TestRunner::setBackingScaleFactor(double backingScaleFactor) { #if !PLATFORM(IOS_FAMILY) [[mainFrame webView] _setCustomBackingScaleFactor:backingScaleFactor]; #endif } void TestRunner::resetPageVisibility() { WebView *webView = [mainFrame webView]; if ([webView respondsToSelector:@selector(_setVisibilityState:isInitialState:)]) [webView _setVisibilityState:WebPageVisibilityStateVisible isInitialState:YES]; } void TestRunner::setPageVisibility(const char* newVisibility) { if (!newVisibility) return; WebView *webView = [mainFrame webView]; if (!strcmp(newVisibility, "visible")) [webView _setVisibilityState:WebPageVisibilityStateVisible isInitialState:NO]; else if (!strcmp(newVisibility, "hidden")) [webView _setVisibilityState:WebPageVisibilityStateHidden isInitialState:NO]; else if (!strcmp(newVisibility, "prerender")) [webView _setVisibilityState:WebPageVisibilityStatePrerender isInitialState:NO]; } void TestRunner::grantWebNotificationPermission(JSStringRef jsOrigin) { RetainPtr cfOrigin = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, jsOrigin)); ASSERT([[mainFrame webView] _notificationProvider] == [MockWebNotificationProvider shared]); [[MockWebNotificationProvider shared] setWebNotificationOrigin:(__bridge NSString *)cfOrigin.get() permission:TRUE]; } void TestRunner::denyWebNotificationPermission(JSStringRef jsOrigin) { RetainPtr cfOrigin = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, jsOrigin)); ASSERT([[mainFrame webView] _notificationProvider] == [MockWebNotificationProvider shared]); [[MockWebNotificationProvider shared] setWebNotificationOrigin:(__bridge NSString *)cfOrigin.get() permission:FALSE]; } void TestRunner::removeAllWebNotificationPermissions() { [[MockWebNotificationProvider shared] removeAllWebNotificationPermissions]; } void TestRunner::simulateWebNotificationClick(JSValueRef jsNotification) { uint64_t notificationID = [[mainFrame webView] _notificationIDForTesting:jsNotification]; m_hasPendingWebNotificationClick = true; dispatch_async(dispatch_get_main_queue(), ^{ if (!m_hasPendingWebNotificationClick) return; [[MockWebNotificationProvider shared] simulateWebNotificationClick:notificationID]; m_hasPendingWebNotificationClick = false; }); } void TestRunner::simulateLegacyWebNotificationClick(JSStringRef jsTitle) { } static NSString * const WebSubresourcesKey = @"WebSubresources"; static NSString * const WebSubframeArchivesKey = @"WebResourceMIMEType like 'image*'"; unsigned TestRunner::imageCountInGeneralPasteboard() const { NSString *webArchivePboardType = @"Apple Web Archive pasteboard type"; #if PLATFORM(MAC) NSData *data = [[NSPasteboard generalPasteboard] dataForType:webArchivePboardType]; #elif PLATFORM(IOS_FAMILY) NSData *data = [[UIPasteboard generalPasteboard] valueForPasteboardType:webArchivePboardType]; #endif if (!data) return 0; NSError *error = nil; id webArchive = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListImmutable format:NULL error:&error]; if (error) { NSLog(@"Encountered error while serializing Web Archive pasteboard data: %@", error); return 0; } NSArray *subItems = [NSArray arrayWithArray:[webArchive objectForKey:WebSubresourcesKey]]; NSPredicate *predicate = [NSPredicate predicateWithFormat:WebSubframeArchivesKey]; NSArray *imagesArray = [subItems filteredArrayUsingPredicate:predicate]; if (!imagesArray) return 0; return imagesArray.count; }