FBPasteboard.m 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. /**
  2. * Copyright (c) 2015-present, Facebook, Inc.
  3. * All rights reserved.
  4. *
  5. * This source code is licensed under the BSD-style license found in the
  6. * LICENSE file in the root directory of this source tree. An additional grant
  7. * of patent rights can be found in the PATENTS file in the same directory.
  8. */
  9. #import "FBPasteboard.h"
  10. #import <mach/mach_time.h>
  11. #import "FBAlert.h"
  12. #import "FBErrorBuilder.h"
  13. #import "FBMacros.h"
  14. #import "XCUIApplication+FBHelpers.h"
  15. #import "XCUIApplication+FBAlert.h"
  16. #define ALERT_TIMEOUT_SEC 30
  17. // Must not be less than FB_MONTORING_INTERVAL in FBAlertsMonitor
  18. #define ALERT_CHECK_INTERVAL_SEC 2
  19. #if !TARGET_OS_TV
  20. @implementation FBPasteboard
  21. + (BOOL)setData:(NSData *)data forType:(NSString *)type error:(NSError **)error
  22. {
  23. UIPasteboard *pb = UIPasteboard.generalPasteboard;
  24. if ([type.lowercaseString isEqualToString:@"plaintext"]) {
  25. pb.string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  26. } else if ([type.lowercaseString isEqualToString:@"image"]) {
  27. UIImage *image = [UIImage imageWithData:data];
  28. if (nil == image) {
  29. NSString *description = @"No image can be parsed from the given pasteboard data";
  30. if (error) {
  31. *error = [[FBErrorBuilder.builder withDescription:description] build];
  32. }
  33. return NO;
  34. }
  35. pb.image = image;
  36. } else if ([type.lowercaseString isEqualToString:@"url"]) {
  37. NSString *urlString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  38. NSURL *url = [[NSURL alloc] initWithString:urlString];
  39. if (nil == url) {
  40. NSString *description = @"No URL can be parsed from the given pasteboard data";
  41. if (error) {
  42. *error = [[FBErrorBuilder.builder withDescription:description] build];
  43. }
  44. return NO;
  45. }
  46. pb.URL = url;
  47. } else {
  48. NSString *description = [NSString stringWithFormat:@"Unsupported content type: %@", type];
  49. if (error) {
  50. *error = [[FBErrorBuilder.builder withDescription:description] build];
  51. }
  52. return NO;
  53. }
  54. return YES;
  55. }
  56. + (nullable id)pasteboardContentForItem:(NSString *)item
  57. instance:(UIPasteboard *)pbInstance
  58. timeout:(NSTimeInterval)timeout
  59. error:(NSError **)error
  60. {
  61. SEL selector = NSSelectorFromString(item);
  62. NSMethodSignature *methodSignature = [pbInstance methodSignatureForSelector:selector];
  63. if (nil == methodSignature) {
  64. NSString *description = [NSString stringWithFormat:@"Cannot retrieve '%@' from a UIPasteboard instance", item];
  65. if (error) {
  66. *error = [[FBErrorBuilder.builder withDescription:description] build];
  67. }
  68. return nil;
  69. }
  70. NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
  71. [invocation setSelector:selector];
  72. [invocation setTarget:pbInstance];
  73. if (SYSTEM_VERSION_LESS_THAN(@"16.0")) {
  74. [invocation invoke];
  75. id __unsafe_unretained result;
  76. [invocation getReturnValue:&result];
  77. return result;
  78. }
  79. // https://github.com/appium/appium/issues/17392
  80. __block id pasteboardContent;
  81. dispatch_queue_t backgroundQueue = dispatch_queue_create("GetPasteboard", NULL);
  82. __block BOOL didFinishGetPasteboard = NO;
  83. dispatch_async(backgroundQueue, ^{
  84. [invocation invoke];
  85. id __unsafe_unretained result;
  86. [invocation getReturnValue:&result];
  87. pasteboardContent = result;
  88. didFinishGetPasteboard = YES;
  89. });
  90. uint64_t timeStarted = clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW);
  91. while (!didFinishGetPasteboard) {
  92. [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:ALERT_CHECK_INTERVAL_SEC]];
  93. if (didFinishGetPasteboard) {
  94. break;
  95. }
  96. XCUIElement *alertElement = XCUIApplication.fb_systemApplication.fb_alertElement;
  97. if (nil != alertElement) {
  98. FBAlert *alert = [FBAlert alertWithElement:alertElement];
  99. [alert acceptWithError:nil];
  100. }
  101. uint64_t timeElapsed = clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW) - timeStarted;
  102. if (timeElapsed / NSEC_PER_SEC > timeout) {
  103. NSString *description = [NSString stringWithFormat:@"Cannot handle pasteboard alert within %@s timeout", @(timeout)];
  104. if (error) {
  105. *error = [[FBErrorBuilder.builder withDescription:description] build];
  106. }
  107. return nil;
  108. }
  109. }
  110. return pasteboardContent;
  111. }
  112. + (NSData *)dataForType:(NSString *)type error:(NSError **)error
  113. {
  114. UIPasteboard *pb = UIPasteboard.generalPasteboard;
  115. if ([type.lowercaseString isEqualToString:@"plaintext"]) {
  116. if (pb.hasStrings) {
  117. id result = [self.class pasteboardContentForItem:@"strings"
  118. instance:pb
  119. timeout:ALERT_TIMEOUT_SEC
  120. error:error
  121. ];
  122. return nil == result
  123. ? nil
  124. : [[(NSArray *)result componentsJoinedByString:@"\n"] dataUsingEncoding:NSUTF8StringEncoding];
  125. }
  126. } else if ([type.lowercaseString isEqualToString:@"image"]) {
  127. if (pb.hasImages) {
  128. id result = [self.class pasteboardContentForItem:@"image"
  129. instance:pb
  130. timeout:ALERT_TIMEOUT_SEC
  131. error:error
  132. ];
  133. return nil == result ? nil : UIImagePNGRepresentation((UIImage *)result);
  134. }
  135. } else if ([type.lowercaseString isEqualToString:@"url"]) {
  136. if (pb.hasURLs) {
  137. id result = [self.class pasteboardContentForItem:@"URLs"
  138. instance:pb
  139. timeout:ALERT_TIMEOUT_SEC
  140. error:error
  141. ];
  142. if (nil == result) {
  143. return nil;
  144. }
  145. NSMutableArray<NSString *> *urls = [NSMutableArray array];
  146. for (NSURL *url in (NSArray *)result) {
  147. if (nil != url.absoluteString) {
  148. [urls addObject:(id)url.absoluteString];
  149. }
  150. }
  151. return [[urls componentsJoinedByString:@"\n"] dataUsingEncoding:NSUTF8StringEncoding];
  152. }
  153. } else {
  154. NSString *description = [NSString stringWithFormat:@"Unsupported content type: %@", type];
  155. if (error) {
  156. *error = [[FBErrorBuilder.builder withDescription:description] build];
  157. }
  158. return nil;
  159. }
  160. return [@"" dataUsingEncoding:NSUTF8StringEncoding];
  161. }
  162. @end
  163. #endif