diff --git a/WebDriverAgent.xcodeproj/project.pbxproj b/WebDriverAgent.xcodeproj/project.pbxproj index 2e08880f1..e406f790f 100644 --- a/WebDriverAgent.xcodeproj/project.pbxproj +++ b/WebDriverAgent.xcodeproj/project.pbxproj @@ -41,6 +41,8 @@ 71B49EC71ED1A58100D51AD6 /* XCUIElement+FBUID.h in Headers */ = {isa = PBXBuildFile; fileRef = 71B49EC51ED1A58100D51AD6 /* XCUIElement+FBUID.h */; }; 71B49EC81ED1A58100D51AD6 /* XCUIElement+FBUID.m in Sources */ = {isa = PBXBuildFile; fileRef = 71B49EC61ED1A58100D51AD6 /* XCUIElement+FBUID.m */; }; 71E95ADF1DC101BA002D0364 /* libxml2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 7174AF031D9D39AF008C8AD5 /* libxml2.tbd */; }; + 77D3117D2A08DC33BED2277D /* FBPasteboard.h in Headers */ = {isa = PBXBuildFile; fileRef = 77D3114969541530478E01FD /* FBPasteboard.h */; }; + 77D3177888989A4B5EDA6C06 /* FBPasteboard.m in Sources */ = {isa = PBXBuildFile; fileRef = 77D31C2F44395BE1F21099D7 /* FBPasteboard.m */; }; AD35D01A1CF1418E00870A75 /* RoutingHTTPServer.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = AD42DD2B1CF1238500806E5D /* RoutingHTTPServer.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; AD35D0641CF1C2C300870A75 /* RoutingHTTPServer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AD42DD2B1CF1238500806E5D /* RoutingHTTPServer.framework */; }; AD35D06C1CF1C35500870A75 /* WebDriverAgentLib.framework in Copy frameworks */ = {isa = PBXBuildFile; fileRef = EE158A991CBD452B00A3E3F0 /* WebDriverAgentLib.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -321,8 +323,8 @@ EEE3764A1D59FAE900ED88DD /* XCUIElement+FBWebDriverAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = EEE376481D59FAE900ED88DD /* XCUIElement+FBWebDriverAttributes.m */; }; EEE9B4721CD02B88009D2030 /* FBRunLoopSpinner.h in Headers */ = {isa = PBXBuildFile; fileRef = EEE9B4701CD02B88009D2030 /* FBRunLoopSpinner.h */; settings = {ATTRIBUTES = (Public, ); }; }; EEE9B4731CD02B88009D2030 /* FBRunLoopSpinner.m in Sources */ = {isa = PBXBuildFile; fileRef = EEE9B4711CD02B88009D2030 /* FBRunLoopSpinner.m */; }; - EEEA70152110605600C8ADE3 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE8980D321105B49001789EE /* XCTest.framework */; }; EEEA70152110605600C8ADE2 /* XCTAutomationSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE8980D321105B49001789ED /* XCTAutomationSupport.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; + EEEA70152110605600C8ADE3 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE8980D321105B49001789EE /* XCTest.framework */; }; EEEC7C921F21F27A0053426C /* FBPredicate.h in Headers */ = {isa = PBXBuildFile; fileRef = EEEC7C901F21F27A0053426C /* FBPredicate.h */; }; EEEC7C931F21F27A0053426C /* FBPredicate.m in Sources */ = {isa = PBXBuildFile; fileRef = EEEC7C911F21F27A0053426C /* FBPredicate.m */; }; /* End PBXBuildFile section */ @@ -447,6 +449,8 @@ 71B49EC51ED1A58100D51AD6 /* XCUIElement+FBUID.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "XCUIElement+FBUID.h"; sourceTree = ""; }; 71B49EC61ED1A58100D51AD6 /* XCUIElement+FBUID.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "XCUIElement+FBUID.m"; sourceTree = ""; }; 71E504941DF59BAD0020C32A /* XCUIElementAttributesTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XCUIElementAttributesTests.m; sourceTree = ""; }; + 77D3114969541530478E01FD /* FBPasteboard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBPasteboard.h; sourceTree = ""; }; + 77D31C2F44395BE1F21099D7 /* FBPasteboard.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBPasteboard.m; sourceTree = ""; }; AD42DD2A1CF121E600806E5D /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; AD42DD2B1CF1238500806E5D /* RoutingHTTPServer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RoutingHTTPServer.framework; path = Carthage/Build/iOS/RoutingHTTPServer.framework; sourceTree = ""; }; AD6C26921CF2379700F8B5FF /* FBAlert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FBAlert.h; path = WebDriverAgentLib/FBAlert.h; sourceTree = SOURCE_ROOT; }; @@ -620,8 +624,8 @@ EE7E271B1D06C69F001BEC7B /* FBXCTestCaseImplementationFailureHoldingProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBXCTestCaseImplementationFailureHoldingProxy.m; sourceTree = ""; }; EE7E27211D06CA91001BEC7B /* libAccessibility.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libAccessibility.tbd; path = usr/lib/libAccessibility.tbd; sourceTree = SDKROOT; }; EE836C021C0F118600D87246 /* UnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - EE8980D321105B49001789EE /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; EE8980D321105B49001789ED /* XCTAutomationSupport.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTAutomationSupport.framework; path = Platforms/iPhoneOS.platform/Developer/Library/PrivateFrameworks/XCTAutomationSupport.framework; sourceTree = DEVELOPER_DIR; }; + EE8980D321105B49001789EE /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; EE8BA9781DCCED9A00A9DEF8 /* FBNavigationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBNavigationController.h; sourceTree = ""; }; EE8BA9791DCCED9A00A9DEF8 /* FBNavigationController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBNavigationController.m; sourceTree = ""; }; EE8DDD7820C565FB004D4925 /* XCUIApplicationFBHelpersTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XCUIApplicationFBHelpersTests.m; sourceTree = ""; }; @@ -1069,6 +1073,8 @@ EE9AB7991CAEDF0C008C271F /* FBXPathCreator.m */, EE6B64FB1D0F86EF00E85F5D /* XCTestPrivateSymbols.h */, EE6B64FC1D0F86EF00E85F5D /* XCTestPrivateSymbols.m */, + 77D3114969541530478E01FD /* FBPasteboard.h */, + 77D31C2F44395BE1F21099D7 /* FBPasteboard.m */, ); name = Utilities; path = WebDriverAgentLib/Utilities; @@ -1530,6 +1536,7 @@ EE35AD091E3B77D600A02D78 /* _XCInternalTestRun.h in Headers */, 712A0C871DA3E55D007D02E5 /* FBXPath-Private.h in Headers */, EE35AD321E3B77D600A02D78 /* XCKeyMappingPath.h in Headers */, + 77D3117D2A08DC33BED2277D /* FBPasteboard.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1845,6 +1852,7 @@ EE35AD7C1E3B80C000A02D78 /* FBXCTestDaemonsProxy.m in Sources */, EE158AB51CBD456F00A3E3F0 /* XCUIElement+FBTap.m in Sources */, EE18883B1DA661C400307AA8 /* FBMathUtils.m in Sources */, + 77D3177888989A4B5EDA6C06 /* FBPasteboard.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/WebDriverAgentLib/Commands/FBCustomCommands.m b/WebDriverAgentLib/Commands/FBCustomCommands.m index c562f8e83..82240da4b 100644 --- a/WebDriverAgentLib/Commands/FBCustomCommands.m +++ b/WebDriverAgentLib/Commands/FBCustomCommands.m @@ -14,6 +14,7 @@ #import "FBApplication.h" #import "FBConfiguration.h" #import "FBExceptionHandler.h" +#import "FBPasteboard.h" #import "FBKeyboard.h" #import "FBResponsePayload.h" #import "FBRoute.h" @@ -40,6 +41,8 @@ + (NSArray *)routes [[FBRoute POST:@"/wda/keyboard/dismiss"] respondWithTarget:self action:@selector(handleDismissKeyboardCommand:)], [[FBRoute GET:@"/wda/elementCache/size"] respondWithTarget:self action:@selector(handleGetElementCacheSizeCommand:)], [[FBRoute POST:@"/wda/elementCache/clear"] respondWithTarget:self action:@selector(handleClearElementCacheCommand:)], + [[FBRoute POST:@"/wda/setPasteboard"] respondWithTarget:self action:@selector(handleSetPasteboard:)], + [[FBRoute POST:@"/wda/getPasteboard"] respondWithTarget:self action:@selector(handleGetPasteboard:)], ]; } @@ -107,4 +110,32 @@ + (NSArray *)routes [elementCache clear]; return FBResponseWithOK(); } + ++ (id)handleSetPasteboard:(FBRouteRequest *)request +{ + NSString *contentType = request.arguments[@"contentType"] ?: @"plaintext"; + NSData *content = [[NSData alloc] initWithBase64EncodedString:(NSString *)request.arguments[@"content"] + options:NSDataBase64DecodingIgnoreUnknownCharacters]; + if (nil == content) { + return FBResponseWithStatus(FBCommandStatusInvalidArgument, @"Cannot decode the pasteboard content from base64"); + } + NSError *error; + if (![FBPasteboard setData:content forType:contentType error:&error]) { + return FBResponseWithError(error); + } + return FBResponseWithOK(); +} + ++ (id)handleGetPasteboard:(FBRouteRequest *)request +{ + NSString *contentType = request.arguments[@"contentType"] ?: @"plaintext"; + NSError *error; + id result = [FBPasteboard dataForType:contentType error:&error]; + if (nil == result) { + return FBResponseWithError(error); + } + return FBResponseWithStatus(FBCommandStatusNoError, + [result base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength]); +} + @end diff --git a/WebDriverAgentLib/Utilities/FBPasteboard.h b/WebDriverAgentLib/Utilities/FBPasteboard.h new file mode 100644 index 000000000..0fdd93d8a --- /dev/null +++ b/WebDriverAgentLib/Utilities/FBPasteboard.h @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface FBPasteboard : NSObject + +/** + Sets data to the general pasteboard + + @param data base64-encoded string containing the data chunk which is going to be written to the pasteboard + @param type one of the possible data types to set: plaintext, url, image + @param error If there is an error, upon return contains an NSError object that describes the problem + @return YES if the operation was successful + */ ++ (BOOL)setData:(NSData *)data forType:(NSString *)type error:(NSError **)error; + +/** + Gets the data contained in the general pasteboard + + @param type one of the possible data types to get: plaintext, url, image + @param error If there is an error, upon return contains an NSError object that describes the problem + @return NSData object, containing the pasteboard content or an empty string if the pasteboard is empty. + nil is returned if there was an error while getting the data from the pasteboard + */ ++ (nullable NSData *)dataForType:(NSString *)type error:(NSError **)error; + +@end + +NS_ASSUME_NONNULL_END + diff --git a/WebDriverAgentLib/Utilities/FBPasteboard.m b/WebDriverAgentLib/Utilities/FBPasteboard.m new file mode 100644 index 000000000..3d8df6015 --- /dev/null +++ b/WebDriverAgentLib/Utilities/FBPasteboard.m @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "FBPasteboard.h" + +#import "FBErrorBuilder.h" + +@implementation FBPasteboard + ++ (BOOL)setData:(NSData *)data forType:(NSString *)type error:(NSError **)error +{ + UIPasteboard *pb = UIPasteboard.generalPasteboard; + if ([type.lowercaseString isEqualToString:@"plaintext"]) { + pb.string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + } else if ([type.lowercaseString isEqualToString:@"image"]) { + UIImage *image = [UIImage imageWithData:data]; + if (nil == image) { + NSString *description = @"No image can be parsed from the given pasteboard data"; + if (error) { + *error = [[FBErrorBuilder.builder withDescription:description] build]; + } + return NO; + } + pb.image = image; + } else if ([type.lowercaseString isEqualToString:@"url"]) { + NSString *urlString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + NSURL *url = [[NSURL alloc] initWithString:urlString]; + if (nil == url) { + NSString *description = @"No URL can be parsed from the given pasteboard data"; + if (error) { + *error = [[FBErrorBuilder.builder withDescription:description] build]; + } + return NO; + } + pb.URL = url; + } else { + NSString *description = [NSString stringWithFormat:@"Unsupported content type: %@", type]; + if (error) { + *error = [[FBErrorBuilder.builder withDescription:description] build]; + } + return NO; + } + return YES; +} + ++ (NSData *)dataForType:(NSString *)type error:(NSError **)error +{ + UIPasteboard *pb = UIPasteboard.generalPasteboard; + if ([type.lowercaseString isEqualToString:@"plaintext"]) { + if (pb.hasStrings) { + return [[pb.strings componentsJoinedByString:@"\n"] dataUsingEncoding:NSUTF8StringEncoding]; + } + } else if ([type.lowercaseString isEqualToString:@"image"]) { + if (pb.hasImages) { + return UIImagePNGRepresentation((UIImage *)pb.image); + } + } else if ([type.lowercaseString isEqualToString:@"url"]) { + if (pb.hasURLs) { + NSMutableArray *urls = [NSMutableArray array]; + for (NSURL *url in pb.URLs) { + if (nil != url.absoluteString) { + [urls addObject:(id)url.absoluteString]; + } + } + return [[urls componentsJoinedByString:@"\n"] dataUsingEncoding:NSUTF8StringEncoding]; + } + } else { + NSString *description = [NSString stringWithFormat:@"Unsupported content type: %@", type]; + if (error) { + *error = [[FBErrorBuilder.builder withDescription:description] build]; + } + return nil; + } + return [@"" dataUsingEncoding:NSUTF8StringEncoding]; +} + +@end