@onekeyfe/react-native-text-input 3.0.157-alpha.251 → 3.0.157-alpha.253

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,7 +12,12 @@ Pod::Spec.new do |s|
12
12
  s.platforms = { :ios => "15.5" }
13
13
  s.source = { :git => "https://github.com/OneKeyHQ/app-modules.git", :tag => "#{s.version}" }
14
14
  s.source_files = "ios/**/*.{h,m,mm}"
15
+ s.exclude_files = "ios/tests/**/*"
15
16
  s.frameworks = "UIKit", "UniformTypeIdentifiers"
16
17
 
17
18
  s.dependency "React-Core"
19
+
20
+ s.test_spec "Tests" do |test_spec|
21
+ test_spec.source_files = "ios/tests/**/*.{m,mm}"
22
+ end
18
23
  end
@@ -7,11 +7,80 @@
7
7
  #import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
8
8
  #import <objc/runtime.h>
9
9
 
10
+ #include <atomic>
11
+
10
12
  static NSString *const OneKeyTextInputPasteEvent = @"OneKeyTextInputPaste";
11
13
  static __weak OneKeyTextInputPasteObserver *OneKeyPasteObserver = nil;
12
14
  static BOOL OneKeyPasteObserverHasListeners = NO;
13
15
  static const void *OneKeyPasteInFlightKey = &OneKeyPasteInFlightKey;
14
16
 
17
+ // canPerformAction: runs on the main thread, and iOS 27 also calls it from
18
+ // system command validation without any user action. Reading UIPasteboard
19
+ // there is a synchronous XPC call that can block until the watchdog kills the
20
+ // app, so the image check reads this cache and refreshes it off the main thread.
21
+ enum class OneKeyPasteboardImageState { Unknown, NoImages, HasImages };
22
+ enum class OneKeyPasteboardRefreshState { Idle, Scheduled, Dirty };
23
+ static std::atomic<OneKeyPasteboardImageState> OneKeyPasteboardImageCache{OneKeyPasteboardImageState::Unknown};
24
+ static std::atomic<OneKeyPasteboardRefreshState> OneKeyPasteboardRefresh{OneKeyPasteboardRefreshState::Idle};
25
+
26
+ static void OneKeyRefreshPasteboardImageState(void)
27
+ {
28
+ for (;;) {
29
+ OneKeyPasteboardRefreshState state = OneKeyPasteboardRefresh.load();
30
+ if (state == OneKeyPasteboardRefreshState::Dirty) {
31
+ return;
32
+ }
33
+ if (state == OneKeyPasteboardRefreshState::Scheduled) {
34
+ if (OneKeyPasteboardRefresh.compare_exchange_weak(state, OneKeyPasteboardRefreshState::Dirty)) {
35
+ return;
36
+ }
37
+ } else if (OneKeyPasteboardRefresh.compare_exchange_weak(state, OneKeyPasteboardRefreshState::Scheduled)) {
38
+ break;
39
+ }
40
+ }
41
+ static dispatch_queue_t queue;
42
+ static dispatch_once_t onceToken;
43
+ dispatch_once(&onceToken, ^{
44
+ queue = dispatch_queue_create("so.onekey.textinput.pasteboard", DISPATCH_QUEUE_SERIAL);
45
+ });
46
+ dispatch_async(queue, ^{
47
+ for (;;) {
48
+ BOOL hasImages = UIPasteboard.generalPasteboard.hasImages;
49
+ OneKeyPasteboardImageCache.store(hasImages ? OneKeyPasteboardImageState::HasImages
50
+ : OneKeyPasteboardImageState::NoImages);
51
+ OneKeyPasteboardRefreshState expected = OneKeyPasteboardRefreshState::Scheduled;
52
+ if (OneKeyPasteboardRefresh.compare_exchange_strong(expected, OneKeyPasteboardRefreshState::Idle)) {
53
+ return;
54
+ }
55
+ // A notification arrived during the read. Keep Paste available until
56
+ // another read observes the latest clipboard state.
57
+ OneKeyPasteboardRefresh.store(OneKeyPasteboardRefreshState::Scheduled);
58
+ }
59
+ });
60
+ }
61
+
62
+ static void OneKeyStartObservingPasteboard(void)
63
+ {
64
+ static dispatch_once_t onceToken;
65
+ dispatch_once(&onceToken, ^{
66
+ NSNotificationCenter *center = NSNotificationCenter.defaultCenter;
67
+ for (NSNotificationName name in @[
68
+ UIPasteboardChangedNotification,
69
+ UIApplicationDidBecomeActiveNotification,
70
+ UITextFieldTextDidBeginEditingNotification,
71
+ UITextViewTextDidBeginEditingNotification,
72
+ ]) {
73
+ [center addObserverForName:name
74
+ object:nil
75
+ queue:nil
76
+ usingBlock:^(__unused NSNotification *note) {
77
+ OneKeyRefreshPasteboardImageState();
78
+ }];
79
+ }
80
+ OneKeyRefreshPasteboardImageState();
81
+ });
82
+ }
83
+
15
84
  @implementation OneKeyTextInputPasteObserver
16
85
 
17
86
  RCT_EXPORT_MODULE(OneKeyTextInputPasteObserver)
@@ -145,6 +214,7 @@ static void OneKeySwizzle(Class cls, SEL originalSelector, SEL replacementSelect
145
214
  dispatch_once(&onceToken, ^{
146
215
  OneKeySwizzle(self, @selector(paste:), @selector(onekey_paste:));
147
216
  OneKeySwizzle(self, @selector(canPerformAction:withSender:), @selector(onekey_canPerformAction:withSender:));
217
+ OneKeyStartObservingPasteboard();
148
218
  });
149
219
  }
150
220
 
@@ -162,7 +232,9 @@ static void OneKeySwizzle(Class cls, SEL originalSelector, SEL replacementSelect
162
232
 
163
233
  - (BOOL)onekey_canPerformAction:(SEL)action withSender:(id)sender
164
234
  {
165
- if (action == @selector(paste:) && UIPasteboard.generalPasteboard.hasImages) {
235
+ if (action == @selector(paste:) &&
236
+ (OneKeyPasteboardRefresh.load() != OneKeyPasteboardRefreshState::Idle ||
237
+ OneKeyPasteboardImageCache.load() != OneKeyPasteboardImageState::NoImages)) {
166
238
  return YES;
167
239
  }
168
240
  return [self onekey_canPerformAction:action withSender:sender];
@@ -178,6 +250,7 @@ static void OneKeySwizzle(Class cls, SEL originalSelector, SEL replacementSelect
178
250
  dispatch_once(&onceToken, ^{
179
251
  OneKeySwizzle(self, @selector(paste:), @selector(onekey_paste:));
180
252
  OneKeySwizzle(self, @selector(canPerformAction:withSender:), @selector(onekey_canPerformAction:withSender:));
253
+ OneKeyStartObservingPasteboard();
181
254
  });
182
255
  }
183
256
 
@@ -195,7 +268,9 @@ static void OneKeySwizzle(Class cls, SEL originalSelector, SEL replacementSelect
195
268
 
196
269
  - (BOOL)onekey_canPerformAction:(SEL)action withSender:(id)sender
197
270
  {
198
- if (action == @selector(paste:) && UIPasteboard.generalPasteboard.hasImages) {
271
+ if (action == @selector(paste:) &&
272
+ (OneKeyPasteboardRefresh.load() != OneKeyPasteboardRefreshState::Idle ||
273
+ OneKeyPasteboardImageCache.load() != OneKeyPasteboardImageState::NoImages)) {
199
274
  return YES;
200
275
  }
201
276
  return [self onekey_canPerformAction:action withSender:sender];
@@ -0,0 +1,95 @@
1
+ #import <React/RCTUITextField.h>
2
+ #import <XCTest/XCTest.h>
3
+ #import <objc/runtime.h>
4
+
5
+ static dispatch_semaphore_t OneKeyTestReadStarted;
6
+ static dispatch_semaphore_t OneKeyTestReleaseRead;
7
+
8
+ @interface UIPasteboard (OneKeyPasteCacheTests)
9
+ - (BOOL)onekey_test_hasImages;
10
+ @end
11
+
12
+ @implementation UIPasteboard (OneKeyPasteCacheTests)
13
+
14
+ - (BOOL)onekey_test_hasImages
15
+ {
16
+ dispatch_semaphore_t releaseRead;
17
+ dispatch_semaphore_t readStarted;
18
+ @synchronized (UIPasteboard.class) {
19
+ releaseRead = OneKeyTestReleaseRead;
20
+ readStarted = OneKeyTestReadStarted;
21
+ }
22
+ if (!NSThread.isMainThread && releaseRead != nil) {
23
+ dispatch_semaphore_signal(readStarted);
24
+ dispatch_semaphore_wait(releaseRead, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
25
+ }
26
+ return [self onekey_test_hasImages];
27
+ }
28
+
29
+ @end
30
+
31
+ @interface OneKeyTextInputPasteObserverTests : XCTestCase
32
+ @end
33
+
34
+ @implementation OneKeyTextInputPasteObserverTests
35
+
36
+ - (void)assertImagePasteAvailableWhileRefreshingAfterNotification:(NSNotificationName)name
37
+ {
38
+ UIPasteboard *pasteboard = UIPasteboard.generalPasteboard;
39
+ NSArray<NSDictionary<NSString *, id> *> *previousItems = pasteboard.items;
40
+ RCTUITextField *field = [[RCTUITextField alloc] initWithFrame:CGRectZero];
41
+ SEL paste = @selector(paste:);
42
+
43
+ @try {
44
+ pasteboard.items = @[];
45
+ NSDate *deadline = [NSDate dateWithTimeIntervalSinceNow:5];
46
+ while ([field canPerformAction:paste withSender:nil] && [deadline timeIntervalSinceNow] > 0) {
47
+ [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
48
+ }
49
+ XCTAssertFalse([field canPerformAction:paste withSender:nil]);
50
+
51
+ @synchronized (UIPasteboard.class) {
52
+ OneKeyTestReadStarted = dispatch_semaphore_create(0);
53
+ OneKeyTestReleaseRead = dispatch_semaphore_create(0);
54
+ }
55
+ static dispatch_once_t onceToken;
56
+ dispatch_once(&onceToken, ^{
57
+ Method original = class_getInstanceMethod(UIPasteboard.class, @selector(hasImages));
58
+ Method replacement = class_getInstanceMethod(UIPasteboard.class, @selector(onekey_test_hasImages));
59
+ method_exchangeImplementations(original, replacement);
60
+ });
61
+
62
+ UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:CGSizeMake(1, 1)];
63
+ pasteboard.image = [renderer imageWithActions:^(UIGraphicsImageRendererContext *context) {
64
+ [UIColor.blackColor setFill];
65
+ UIRectFill(CGRectMake(0, 0, 1, 1));
66
+ }];
67
+ XCTAssertEqual(dispatch_semaphore_wait(OneKeyTestReadStarted, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC)), 0L);
68
+
69
+ [NSNotificationCenter.defaultCenter postNotificationName:name object:field];
70
+ XCTAssertTrue([field canPerformAction:paste withSender:nil]);
71
+ } @finally {
72
+ dispatch_semaphore_t releaseRead;
73
+ @synchronized (UIPasteboard.class) {
74
+ releaseRead = OneKeyTestReleaseRead;
75
+ OneKeyTestReleaseRead = nil;
76
+ OneKeyTestReadStarted = nil;
77
+ }
78
+ if (releaseRead != nil) {
79
+ dispatch_semaphore_signal(releaseRead);
80
+ }
81
+ pasteboard.items = previousItems;
82
+ }
83
+ }
84
+
85
+ - (void)testFirstFocusKeepsImagePasteAvailableDuringRefresh
86
+ {
87
+ [self assertImagePasteAvailableWhileRefreshingAfterNotification:UITextFieldTextDidBeginEditingNotification];
88
+ }
89
+
90
+ - (void)testForegroundResumeKeepsImagePasteAvailableDuringRefresh
91
+ {
92
+ [self assertImagePasteAvailableWhileRefreshingAfterNotification:UIApplicationDidBecomeActiveNotification];
93
+ }
94
+
95
+ @end
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/react-native-text-input",
3
- "version": "3.0.157-alpha.251",
3
+ "version": "3.0.157-alpha.253",
4
4
  "description": "React Native TextInput with native paste events",
5
5
  "source": "./src/index.tsx",
6
6
  "main": "./lib/module/index.js",