@onekeyfe/react-native-text-input 3.0.157-alpha.250 → 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.
package/OneKeyTextInput.podspec
CHANGED
|
@@ -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:) &&
|
|
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:) &&
|
|
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