[ReactNative] Ensure JS calls scheduled by a deallocated context don't fire
This commit is contained in:
parent
0b21df4a34
commit
0e67e33534
|
@ -82,7 +82,7 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
|
|||
{
|
||||
__block NSError *initError;
|
||||
dispatch_semaphore_t s = dispatch_semaphore_create(0);
|
||||
[self sendMessage:@{@"method": @"prepareJSRuntime"} waitForReply:^(NSError *error, NSDictionary *reply) {
|
||||
[self sendMessage:@{@"method": @"prepareJSRuntime"} context:nil waitForReply:^(NSError *error, NSDictionary *reply) {
|
||||
initError = error;
|
||||
dispatch_semaphore_signal(s);
|
||||
}];
|
||||
|
@ -111,7 +111,7 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
|
|||
RCTLogError(@"WebSocket connection failed with error %@", error);
|
||||
}
|
||||
|
||||
- (void)sendMessage:(NSDictionary *)message waitForReply:(WSMessageCallback)callback
|
||||
- (void)sendMessage:(NSDictionary *)message context:(NSNumber *)executorID waitForReply:(WSMessageCallback)callback
|
||||
{
|
||||
static NSUInteger lastID = 10000;
|
||||
|
||||
|
@ -122,6 +122,8 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
|
|||
}];
|
||||
callback(error, nil);
|
||||
return;
|
||||
} else if (executorID && ![RCTGetExecutorID(self) isEqualToNumber:executorID]) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSNumber *expectedID = @(lastID++);
|
||||
|
@ -135,12 +137,12 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
|
|||
- (void)executeApplicationScript:(NSString *)script sourceURL:(NSURL *)URL onComplete:(RCTJavaScriptCompleteBlock)onComplete
|
||||
{
|
||||
NSDictionary *message = @{@"method": NSStringFromSelector(_cmd), @"url": [URL absoluteString], @"inject": _injectedObjects};
|
||||
[self sendMessage:message waitForReply:^(NSError *error, NSDictionary *reply) {
|
||||
[self sendMessage:message context:nil waitForReply:^(NSError *error, NSDictionary *reply) {
|
||||
onComplete(error);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)executeJSCall:(NSString *)name method:(NSString *)method arguments:(NSArray *)arguments callback:(RCTJavaScriptCallback)onComplete
|
||||
- (void)executeJSCall:(NSString *)name method:(NSString *)method arguments:(NSArray *)arguments context:(NSNumber *)executorID callback:(RCTJavaScriptCallback)onComplete
|
||||
{
|
||||
RCTAssert(onComplete != nil, @"callback was missing for exec JS call");
|
||||
NSDictionary *message = @{
|
||||
|
@ -149,7 +151,7 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
|
|||
@"moduleMethod": method,
|
||||
@"arguments": arguments
|
||||
};
|
||||
[self sendMessage:message waitForReply:^(NSError *socketError, NSDictionary *reply) {
|
||||
[self sendMessage:message context:executorID waitForReply:^(NSError *socketError, NSDictionary *reply) {
|
||||
if (socketError) {
|
||||
onComplete(nil, socketError);
|
||||
return;
|
||||
|
|
|
@ -234,12 +234,13 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
|
|||
|
||||
- (void)_invokeAndProcessModule:(NSString *)module
|
||||
method:(NSString *)method
|
||||
arguments:(NSArray *)args;
|
||||
arguments:(NSArray *)args
|
||||
context:(NSNumber *)context;
|
||||
|
||||
- (void)_actuallyInvokeAndProcessModule:(NSString *)module
|
||||
method:(NSString *)method
|
||||
arguments:(NSArray *)args;
|
||||
|
||||
arguments:(NSArray *)args
|
||||
context:(NSNumber *)context;
|
||||
@end
|
||||
|
||||
/**
|
||||
|
@ -338,7 +339,7 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
|
|||
NSMutableArray *argumentBlocks = [[NSMutableArray alloc] initWithCapacity:numberOfArguments - 2];
|
||||
|
||||
#define RCT_ARG_BLOCK(_logic) \
|
||||
[argumentBlocks addObject:^(RCTBridge *bridge, NSInvocation *invocation, NSUInteger index, id json) { \
|
||||
[argumentBlocks addObject:^(RCTBridge *bridge, NSNumber *context, NSInvocation *invocation, NSUInteger index, id json) { \
|
||||
_logic \
|
||||
[invocation setArgument:&value atIndex:index]; \
|
||||
}]; \
|
||||
|
@ -355,7 +356,8 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
|
|||
__autoreleasing id value = (json ? ^(NSArray *args) {
|
||||
[bridge _invokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"invokeCallbackAndReturnFlushedQueue"
|
||||
arguments:@[json, args]];
|
||||
arguments:@[json, args]
|
||||
context:context];
|
||||
} : ^(NSArray *unused) {});
|
||||
)
|
||||
};
|
||||
|
@ -477,6 +479,7 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
|
|||
- (void)invokeWithBridge:(RCTBridge *)bridge
|
||||
module:(id)module
|
||||
arguments:(NSArray *)arguments
|
||||
context:(NSNumber *)context
|
||||
{
|
||||
|
||||
#if DEBUG
|
||||
|
@ -503,8 +506,8 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
|
|||
NSUInteger index = 0;
|
||||
for (id json in arguments) {
|
||||
id arg = (json == [NSNull null]) ? nil : json;
|
||||
void (^block)(RCTBridge *, NSInvocation *, NSUInteger, id) = _argumentBlocks[index];
|
||||
block(bridge, invocation, index + 2, arg);
|
||||
void (^block)(RCTBridge *, NSNumber *, NSInvocation *, NSUInteger, id) = _argumentBlocks[index];
|
||||
block(bridge, context, invocation, index + 2, arg);
|
||||
index++;
|
||||
}
|
||||
|
||||
|
@ -653,7 +656,6 @@ static NSDictionary *RCTRemoteModulesConfig(NSDictionary *modulesByName)
|
|||
return moduleConfig;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* As above, but for local modules/methods, which represent JS classes
|
||||
* and methods that will be called by the native code via the bridge.
|
||||
|
@ -801,7 +803,7 @@ static NSDictionary *RCTLocalModulesConfig()
|
|||
RCTDisplayLink *_displayLink;
|
||||
NSMutableSet *_frameUpdateObservers;
|
||||
NSMutableArray *_scheduledCalls;
|
||||
NSMutableArray *_scheduledCallbacks;
|
||||
RCTSparseArray *_scheduledCallbacks;
|
||||
BOOL _loading;
|
||||
|
||||
NSUInteger _startingTime;
|
||||
|
@ -829,13 +831,13 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
- (void)setUp
|
||||
{
|
||||
Class executorClass = _executorClass ?: _globalExecutorClass ?: [RCTContextExecutor class];
|
||||
_javaScriptExecutor = [[executorClass alloc] init];
|
||||
_javaScriptExecutor = RCTCreateExecutor(executorClass);
|
||||
_latestJSExecutor = _javaScriptExecutor;
|
||||
_eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
|
||||
_displayLink = [[RCTDisplayLink alloc] initWithBridge:self];
|
||||
_frameUpdateObservers = [[NSMutableSet alloc] init];
|
||||
_scheduledCalls = [[NSMutableArray alloc] init];
|
||||
_scheduledCallbacks = [[NSMutableArray alloc] init];
|
||||
_scheduledCallbacks = [[RCTSparseArray alloc] init];
|
||||
|
||||
// Register passed-in module instances
|
||||
NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
|
||||
|
@ -991,7 +993,6 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
|
||||
}
|
||||
|
||||
|
||||
- (NSDictionary *)modules
|
||||
{
|
||||
RCTAssert(_modulesByName != nil, @"Bridge modules have not yet been initialized. "
|
||||
|
@ -1072,7 +1073,8 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
if (!_loading) {
|
||||
[self _invokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"callFunctionReturnFlushedQueue"
|
||||
arguments:@[moduleID, methodID, args ?: @[]]];
|
||||
arguments:@[moduleID, methodID, args ?: @[]]
|
||||
context:RCTGetExecutorID(_javaScriptExecutor)];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1093,13 +1095,15 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
#if BATCHED_BRIDGE
|
||||
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"callFunctionReturnFlushedQueue"
|
||||
arguments:@[moduleID, methodID, @[@[timer]]]];
|
||||
arguments:@[moduleID, methodID, @[@[timer]]]
|
||||
context:RCTGetExecutorID(_javaScriptExecutor)];
|
||||
|
||||
#else
|
||||
|
||||
[self _invokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"callFunctionReturnFlushedQueue"
|
||||
arguments:@[moduleID, methodID, @[@[timer]]]];
|
||||
arguments:@[moduleID, methodID, @[@[timer]]]
|
||||
context:RCTGetExecutorID(_javaScriptExecutor)];
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
@ -1108,6 +1112,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
{
|
||||
RCTAssert(onComplete != nil, @"onComplete block passed in should be non-nil");
|
||||
RCT_PROFILE_START();
|
||||
NSNumber *context = RCTGetExecutorID(_javaScriptExecutor);
|
||||
[_javaScriptExecutor executeApplicationScript:script sourceURL:url onComplete:^(NSError *scriptLoadError) {
|
||||
RCT_PROFILE_END(js_call, scriptLoadError, @"initial_script");
|
||||
if (scriptLoadError) {
|
||||
|
@ -1119,10 +1124,11 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
[_javaScriptExecutor executeJSCall:@"BatchedBridge"
|
||||
method:@"flushedQueue"
|
||||
arguments:@[]
|
||||
context:context
|
||||
callback:^(id json, NSError *error) {
|
||||
RCT_PROFILE_END(js_call, error, @"initial_call", @"BatchedBridge.flushedQueue");
|
||||
RCT_PROFILE_START();
|
||||
[self _handleBuffer:json];
|
||||
[self _handleBuffer:json context:context];
|
||||
RCT_PROFILE_END(objc_call, json, @"batched_js_calls");
|
||||
onComplete(error);
|
||||
}];
|
||||
|
@ -1131,7 +1137,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
|
||||
#pragma mark - Payload Generation
|
||||
|
||||
- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args
|
||||
- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context
|
||||
{
|
||||
#if BATCHED_BRIDGE
|
||||
RCT_PROFILE_START();
|
||||
|
@ -1148,10 +1154,11 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
@"module": module,
|
||||
@"method": method,
|
||||
@"args": args,
|
||||
@"context": context ?: @0,
|
||||
};
|
||||
|
||||
if ([method isEqualToString:@"invokeCallbackAndReturnFlushedQueue"]) {
|
||||
[_scheduledCallbacks addObject:call];
|
||||
_scheduledCallbacks[args[0]] = call;
|
||||
} else {
|
||||
[_scheduledCalls addObject:call];
|
||||
}
|
||||
|
@ -1159,7 +1166,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
RCT_PROFILE_END(js_call, args, @"schedule", module, method);
|
||||
}
|
||||
|
||||
- (void)_actuallyInvokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args
|
||||
- (void)_actuallyInvokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context
|
||||
{
|
||||
#endif
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTEnqueueNotification object:nil userInfo:nil];
|
||||
|
@ -1171,19 +1178,20 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
RCT_PROFILE_END(js_call, args, moduleDotMethod);
|
||||
|
||||
RCT_PROFILE_START();
|
||||
[self _handleBuffer:json];
|
||||
[self _handleBuffer:json context:context];
|
||||
RCT_PROFILE_END(objc_call, json, @"batched_js_calls");
|
||||
};
|
||||
|
||||
[_javaScriptExecutor executeJSCall:module
|
||||
method:method
|
||||
arguments:args
|
||||
context:context
|
||||
callback:processResponse];
|
||||
}
|
||||
|
||||
#pragma mark - Payload Processing
|
||||
|
||||
- (void)_handleBuffer:(id)buffer
|
||||
- (void)_handleBuffer:(id)buffer context:(NSNumber *)context
|
||||
{
|
||||
if (buffer == nil || buffer == (id)kCFNull) {
|
||||
return;
|
||||
|
@ -1228,7 +1236,8 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
[self _handleRequestNumber:i
|
||||
moduleID:[moduleIDs[i] integerValue]
|
||||
methodID:[methodIDs[i] integerValue]
|
||||
params:paramsArrays[i]];
|
||||
params:paramsArrays[i]
|
||||
context:context];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1247,6 +1256,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
moduleID:(NSUInteger)moduleID
|
||||
methodID:(NSUInteger)methodID
|
||||
params:(NSArray *)params
|
||||
context:(NSNumber *)context
|
||||
{
|
||||
if (![params isKindOfClass:[NSArray class]]) {
|
||||
RCTLogError(@"Invalid module/method/params tuple for request #%zd", i);
|
||||
|
@ -1280,7 +1290,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
}
|
||||
|
||||
@try {
|
||||
[method invokeWithBridge:strongSelf module:module arguments:params];
|
||||
[method invokeWithBridge:strongSelf module:module arguments:params context:context];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
RCTLogError(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, module, params, exception);
|
||||
|
@ -1313,13 +1323,18 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
{
|
||||
#if BATCHED_BRIDGE
|
||||
|
||||
NSArray *calls = [_scheduledCallbacks arrayByAddingObjectsFromArray:_scheduledCalls];
|
||||
NSArray *calls = [_scheduledCallbacks.allObjects arrayByAddingObjectsFromArray:_scheduledCalls];
|
||||
NSNumber *currentExecutorID = RCTGetExecutorID(_javaScriptExecutor);
|
||||
calls = [calls filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSDictionary *call, NSDictionary *bindings) {
|
||||
return [call[@"context"] isEqualToNumber:currentExecutorID];
|
||||
}]];
|
||||
if (calls.count > 0) {
|
||||
_scheduledCalls = [[NSMutableArray alloc] init];
|
||||
_scheduledCallbacks = [[NSMutableArray alloc] init];
|
||||
_scheduledCallbacks = [[RCTSparseArray alloc] init];
|
||||
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"processBatch"
|
||||
arguments:@[calls]];
|
||||
method:@"processBatch"
|
||||
arguments:@[calls]
|
||||
context:RCTGetExecutorID(_javaScriptExecutor)];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -1357,6 +1372,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
[_latestJSExecutor executeJSCall:@"RCTLog"
|
||||
method:@"logIfNoNativeHook"
|
||||
arguments:@[level, message]
|
||||
context:RCTGetExecutorID(_latestJSExecutor)
|
||||
callback:^(id json, NSError *error) {}];
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <objc/runtime.h>
|
||||
|
||||
#import <JavaScriptCore/JavaScriptCore.h>
|
||||
|
||||
#import "RCTInvalidating.h"
|
||||
|
@ -27,6 +29,7 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error);
|
|||
- (void)executeJSCall:(NSString *)name
|
||||
method:(NSString *)method
|
||||
arguments:(NSArray *)arguments
|
||||
context:(NSNumber *)executorID
|
||||
callback:(RCTJavaScriptCallback)onComplete;
|
||||
|
||||
/**
|
||||
|
@ -40,3 +43,17 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error);
|
|||
asGlobalObjectNamed:(NSString *)objectName
|
||||
callback:(RCTJavaScriptCompleteBlock)onComplete;
|
||||
@end
|
||||
|
||||
static const char *RCTJavaScriptExecutorID = "RCTJavaScriptExecutorID";
|
||||
__used static id<RCTJavaScriptExecutor> RCTCreateExecutor(Class executorClass)
|
||||
{
|
||||
static NSUInteger executorID = 0;
|
||||
id<RCTJavaScriptExecutor> executor = [[executorClass alloc] init];
|
||||
objc_setAssociatedObject(executor, RCTJavaScriptExecutorID, @(++executorID), OBJC_ASSOCIATION_RETAIN);
|
||||
return executor;
|
||||
}
|
||||
|
||||
__used static NSNumber *RCTGetExecutorID(id<RCTJavaScriptExecutor> executor)
|
||||
{
|
||||
return objc_getAssociatedObject(executor, RCTJavaScriptExecutorID);
|
||||
}
|
||||
|
|
|
@ -140,9 +140,9 @@
|
|||
sourceCodeModule.scriptURL = scriptURL;
|
||||
sourceCodeModule.scriptText = rawText;
|
||||
|
||||
[_bridge enqueueApplicationScript:rawText url:scriptURL onComplete:^(NSError *_error) {
|
||||
[_bridge enqueueApplicationScript:rawText url:scriptURL onComplete:^(NSError *scriptError) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
onComplete(_error);
|
||||
onComplete(scriptError);
|
||||
});
|
||||
}];
|
||||
}];
|
||||
|
|
|
@ -229,13 +229,14 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
|
|||
- (void)executeJSCall:(NSString *)name
|
||||
method:(NSString *)method
|
||||
arguments:(NSArray *)arguments
|
||||
context:(NSNumber *)executorID
|
||||
callback:(RCTJavaScriptCallback)onComplete
|
||||
{
|
||||
RCTAssert(onComplete != nil, @"onComplete block should not be nil");
|
||||
__weak RCTContextExecutor *weakSelf = self;
|
||||
[self executeBlockOnJavaScriptQueue:^{
|
||||
RCTContextExecutor *strongSelf = weakSelf;
|
||||
if (!strongSelf || !strongSelf.isValid) {
|
||||
if (!strongSelf || !strongSelf.isValid || ![RCTGetExecutorID(strongSelf) isEqualToNumber:executorID]) {
|
||||
return;
|
||||
}
|
||||
NSError *error;
|
||||
|
|
|
@ -76,10 +76,15 @@ static void RCTReportError(RCTJavaScriptCallback callback, NSString *fmt, ...)
|
|||
- (void)executeJSCall:(NSString *)name
|
||||
method:(NSString *)method
|
||||
arguments:(NSArray *)arguments
|
||||
context:(NSNumber *)executorID
|
||||
callback:(RCTJavaScriptCallback)onComplete
|
||||
{
|
||||
RCTAssert(onComplete != nil, @"");
|
||||
[self executeBlockOnJavaScriptQueue:^{
|
||||
if (!self.isValid || ![RCTGetExecutorID(self) isEqualToNumber:executorID]) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSError *error;
|
||||
NSString *argsString = RCTJSONStringify(arguments, &error);
|
||||
if (!argsString) {
|
||||
|
|
Loading…
Reference in New Issue