diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m index c201f5799..54a58787e 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m @@ -54,10 +54,22 @@ RCT_EXPORT_MODULE() return YES; } -- (void)executeJSCall:(__unused NSString *)name - method:(__unused NSString *)method - arguments:(__unused NSArray *)arguments - callback:(RCTJavaScriptCallback)onComplete +- (void)flushedQueue:(RCTJavaScriptCallback)onComplete +{ + onComplete(nil, nil); +} + +- (void)callFunctionOnModule:(NSString *)module + method:(NSString *)method + arguments:(NSArray *)args + callback:(RCTJavaScriptCallback)onComplete +{ + onComplete(nil, nil); +} + +- (void)invokeCallbackID:(NSNumber *)cbID + arguments:(NSArray *)args + callback:(RCTJavaScriptCallback)onComplete { onComplete(nil, nil); } diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m index d00d65046..c1b96b13e 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m @@ -123,8 +123,13 @@ static uint64_t _get_time_nanoseconds(void) } \ } \ }; \ + var Bridge = { \ + callFunctionReturnFlushedQueue: function(module, method, args) { \ + modules[module].apply(modules[module], args); \ + } \ + }; \ function require(module) { \ - return modules[module]; \ + return Bridge; \ } \ "; @@ -138,12 +143,11 @@ static uint64_t _get_time_nanoseconds(void) for (int j = 0; j < runs; j++) { @autoreleasepool { double start = _get_time_nanoseconds(); - [_executor executeJSCall:@"module" - method:@"method" - arguments:params - callback:^(id json, __unused NSError *unused) { - XCTAssert([json isEqual:@YES], @"Invalid return"); - }]; + [_executor callFunctionOnModule:@"module" + method:@"method" + arguments:params + callback:^(__unused id json, __unused NSError *unused) { + }]; double run = _get_time_nanoseconds() - start; if ((j % frequency) == frequency - 1) { // Warmup total += run; diff --git a/IntegrationTests/LoggingTestModule.js b/IntegrationTests/LoggingTestModule.js index 96e59298d..5d3073a37 100644 --- a/IntegrationTests/LoggingTestModule.js +++ b/IntegrationTests/LoggingTestModule.js @@ -10,10 +10,12 @@ */ 'use strict'; +var BatchedBridge = require('BatchedBridge'); + var warning = require('warning'); var invariant = require('invariant'); -module.exports = { +var LoggingTestModule = { logToConsole: function(str) { console.log(str); }, @@ -30,3 +32,10 @@ module.exports = { throw new Error(str); } }; + +BatchedBridge.registerCallableModule( + 'LoggingTestModule', + LoggingTestModule +); + +module.exports = LoggingTestModule; diff --git a/Libraries/AppRegistry/AppRegistry.js b/Libraries/AppRegistry/AppRegistry.js index 3cf7f3ab0..57bafd2da 100644 --- a/Libraries/AppRegistry/AppRegistry.js +++ b/Libraries/AppRegistry/AppRegistry.js @@ -11,6 +11,9 @@ */ 'use strict'; +var BatchedBridge = require('BatchedBridge'); +var ReactNative = require('ReactNative'); + var invariant = require('invariant'); var renderApplication = require('renderApplication'); @@ -37,6 +40,10 @@ type AppConfig = { * for the app and then actually run the app when it's ready by invoking * `AppRegistry.runApplication`. * + * To "stop" an application when a view should be destroyed, call + * `AppRegistry.unmountApplicationComponentAtRootTag` with the tag that was + * pass into `runApplication`. These should always be used as a pair. + * * `AppRegistry` should be `require`d early in the `require` sequence to make * sure the JS execution environment is setup before other modules are * `require`d. @@ -87,6 +94,16 @@ var AppRegistry = { ); runnables[appKey].run(appParameters); }, + + unmountApplicationComponentAtRootTag: function(rootTag : number) { + ReactNative.unmountComponentAtNodeAndRemoveContainer(rootTag); + }, + }; +BatchedBridge.registerCallableModule( + 'AppRegistry', + AppRegistry +); + module.exports = AppRegistry; diff --git a/Libraries/BatchedBridge/BatchedBridge.js b/Libraries/BatchedBridge/BatchedBridge.js index ea49b202f..e4269735a 100644 --- a/Libraries/BatchedBridge/BatchedBridge.js +++ b/Libraries/BatchedBridge/BatchedBridge.js @@ -10,11 +10,27 @@ */ 'use strict'; -let MessageQueue = require('MessageQueue'); +const MessageQueue = require('MessageQueue'); -let BatchedBridge = new MessageQueue( +const BatchedBridge = new MessageQueue( __fbBatchedBridgeConfig.remoteModuleConfig, __fbBatchedBridgeConfig.localModulesConfig, ); +// TODO: Move these around to solve the cycle in a cleaner way. + +const BridgeProfiling = require('BridgeProfiling'); +const JSTimersExecution = require('JSTimersExecution'); + +BatchedBridge.registerCallableModule('BridgeProfiling', BridgeProfiling); +BatchedBridge.registerCallableModule('JSTimersExecution', JSTimersExecution); + +// Wire up the batched bridge on the global object so that we can call into it. +// Ideally, this would be the inverse relationship. I.e. the native environment +// provides this global directly with its script embedded. Then this module +// would export it. A possible fix would be to trim the dependencies in +// MessageQueue to its minimal features and embed that in the native runtime. + +Object.defineProperty(global, '__fbBatchedBridge', { value: BatchedBridge }); + module.exports = BatchedBridge; diff --git a/Libraries/BatchedBridge/BatchedBridgedModules/RCTEventEmitter.js b/Libraries/BatchedBridge/BatchedBridgedModules/RCTEventEmitter.js index 8c66aac9a..2b8f578e7 100644 --- a/Libraries/BatchedBridge/BatchedBridgedModules/RCTEventEmitter.js +++ b/Libraries/BatchedBridge/BatchedBridgedModules/RCTEventEmitter.js @@ -11,7 +11,13 @@ */ 'use strict'; +var BatchedBridge = require('BatchedBridge'); var ReactNativeEventEmitter = require('ReactNativeEventEmitter'); +BatchedBridge.registerCallableModule( + 'RCTEventEmitter', + ReactNativeEventEmitter +); + // Completely locally implemented - no native hooks. module.exports = ReactNativeEventEmitter; diff --git a/Libraries/DebugComponentHierarchy/RCTDebugComponentOwnership.js b/Libraries/DebugComponentHierarchy/RCTDebugComponentOwnership.js index 4ee5a1f03..d7462bf23 100644 --- a/Libraries/DebugComponentHierarchy/RCTDebugComponentOwnership.js +++ b/Libraries/DebugComponentHierarchy/RCTDebugComponentOwnership.js @@ -15,6 +15,7 @@ 'use strict'; +var BatchedBridge = require('BatchedBridge'); var DebugComponentOwnershipModule = require('NativeModules').DebugComponentOwnershipModule; var InspectorUtils = require('InspectorUtils'); var ReactNativeTagHandles = require('ReactNativeTagHandles'); @@ -35,7 +36,7 @@ function getRootTagForTag(tag: number): ?number { return ReactNativeTagHandles.rootNodeIDToTag[rootID]; } -module.exports = { +var RCTDebugComponentOwnership = { /** * Asynchronously returns the owner hierarchy as an array of strings. Request id is @@ -53,3 +54,10 @@ module.exports = { DebugComponentOwnershipModule.receiveOwnershipHierarchy(requestID, tag, ownerHierarchy); }, }; + +BatchedBridge.registerCallableModule( + 'RCTDebugComponentOwnership', + RCTDebugComponentOwnership +); + +module.exports = RCTDebugComponentOwnership; diff --git a/Libraries/Device/RCTDeviceEventEmitter.js b/Libraries/Device/RCTDeviceEventEmitter.js index 586663e34..41e4d408f 100644 --- a/Libraries/Device/RCTDeviceEventEmitter.js +++ b/Libraries/Device/RCTDeviceEventEmitter.js @@ -12,7 +12,13 @@ 'use strict'; var EventEmitter = require('EventEmitter'); +var BatchedBridge = require('BatchedBridge'); var RCTDeviceEventEmitter = new EventEmitter(); +BatchedBridge.registerCallableModule( + 'RCTDeviceEventEmitter', + RCTDeviceEventEmitter +); + module.exports = RCTDeviceEventEmitter; diff --git a/Libraries/NativeApp/RCTNativeAppEventEmitter.js b/Libraries/NativeApp/RCTNativeAppEventEmitter.js index 38ccb0dcf..9d4de85f3 100644 --- a/Libraries/NativeApp/RCTNativeAppEventEmitter.js +++ b/Libraries/NativeApp/RCTNativeAppEventEmitter.js @@ -11,8 +11,14 @@ */ 'use strict'; +var BatchedBridge = require('BatchedBridge'); var EventEmitter = require('EventEmitter'); var RCTNativeAppEventEmitter = new EventEmitter(); +BatchedBridge.registerCallableModule( + 'RCTNativeAppEventEmitter', + RCTNativeAppEventEmitter +); + module.exports = RCTNativeAppEventEmitter; diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js index 940b3b459..b7dd38a24 100644 --- a/Libraries/Utilities/MessageQueue.js +++ b/Libraries/Utilities/MessageQueue.js @@ -43,10 +43,10 @@ var guard = (fn) => { class MessageQueue { - constructor(remoteModules, localModules, customRequire) { + constructor(remoteModules, localModules) { this.RemoteModules = {}; - this._require = customRequire || require; + this._callableModules = {}; this._queue = [[],[],[]]; this._moduleTable = {}; this._methodTable = {}; @@ -154,8 +154,22 @@ class MessageQueue { if (__DEV__ && SPY_MODE) { console.log('N->JS : ' + module + '.' + method + '(' + JSON.stringify(args) + ')'); } - module = this._require(module); - module[method].apply(module, args); + var moduleMethods = this._callableModules[module]; + if (!moduleMethods) { + // TODO: Register all the remaining test modules and make this an invariant. #9317773 + // Fallback to require temporarily. A follow up diff will clean up the remaining + // modules and make this an invariant. + console.warn('Module is not registered:', module); + moduleMethods = require(module); + /* + invariant( + !!moduleMethods, + 'Module %s is not a registered callable module.', + module + ); + */ + } + moduleMethods[method].apply(moduleMethods, args); BridgeProfiling.profileEnd(); } @@ -337,6 +351,10 @@ class MessageQueue { return fn; } + registerCallableModule(name, methods) { + this._callableModules[name] = methods; + } + } function moduleHasConstants(moduleArray: Array>): boolean { diff --git a/Libraries/Utilities/PerformanceLogger.js b/Libraries/Utilities/PerformanceLogger.js index daafe46a5..e2d0e5c81 100644 --- a/Libraries/Utilities/PerformanceLogger.js +++ b/Libraries/Utilities/PerformanceLogger.js @@ -10,6 +10,7 @@ */ 'use strict'; +var BatchedBridge = require('BatchedBridge'); var performanceNow = require('performanceNow'); @@ -140,4 +141,9 @@ var PerformanceLogger = { } }; +BatchedBridge.registerCallableModule( + 'PerformanceLogger', + PerformanceLogger +); + module.exports = PerformanceLogger; diff --git a/Libraries/Utilities/RCTLog.js b/Libraries/Utilities/RCTLog.js index 5b280dd66..65abc5883 100644 --- a/Libraries/Utilities/RCTLog.js +++ b/Libraries/Utilities/RCTLog.js @@ -11,6 +11,8 @@ */ 'use strict'; +var BatchedBridge = require('BatchedBridge'); + var invariant = require('invariant'); var levelsMap = { @@ -39,4 +41,9 @@ class RCTLog { } } +BatchedBridge.registerCallableModule( + 'RCTLog', + RCTLog +); + module.exports = RCTLog; diff --git a/Libraries/Utilities/__tests__/MessageQueue-test.js b/Libraries/Utilities/__tests__/MessageQueue-test.js index dc0bab73b..8b9b689fd 100644 --- a/Libraries/Utilities/__tests__/MessageQueue-test.js +++ b/Libraries/Utilities/__tests__/MessageQueue-test.js @@ -39,10 +39,11 @@ describe('MessageQueue', () => { beforeEach(() => { queue = new MessageQueue( remoteModulesConfig, - localModulesConfig, - customRequire, + localModulesConfig ); + queue.registerCallableModule('one', TestModule); + TestModule.testHook1 = jasmine.createSpy(); TestModule.testHook2 = jasmine.createSpy(); }); diff --git a/Libraries/WebSocket/RCTWebSocketExecutor.m b/Libraries/WebSocket/RCTWebSocketExecutor.m index 6ef4b4999..c5813bc5c 100644 --- a/Libraries/WebSocket/RCTWebSocketExecutor.m +++ b/Libraries/WebSocket/RCTWebSocketExecutor.m @@ -161,13 +161,31 @@ RCT_EXPORT_MODULE() }]; } -- (void)executeJSCall:(NSString *)name method:(NSString *)method arguments:(NSArray *)arguments callback:(RCTJavaScriptCallback)onComplete +- (void)flushedQueue:(RCTJavaScriptCallback)onComplete +{ + [self _executeJSCall:@"flushedQueue" arguments:@[] callback:onComplete]; +} + +- (void)callFunctionOnModule:(NSString *)module + method:(NSString *)method + arguments:(NSArray *)args + callback:(RCTJavaScriptCallback)onComplete +{ + [self _executeJSCall:@"callFunctionReturnFlushedQueue" arguments:@[module, method, args] callback:onComplete]; +} + +- (void)invokeCallbackID:(NSNumber *)cbID + arguments:(NSArray *)args + callback:(RCTJavaScriptCallback)onComplete +{ + [self _executeJSCall:@"invokeCallbackAndReturnFlushedQueue" arguments:@[cbID, args] callback:onComplete]; +} + +- (void)_executeJSCall:(NSString *)method arguments:(NSArray *)arguments callback:(RCTJavaScriptCallback)onComplete { RCTAssert(onComplete != nil, @"callback was missing for exec JS call"); NSDictionary *message = @{ - @"method": @"executeJSCall", - @"moduleName": name, - @"moduleMethod": method, + @"method": method, @"arguments": arguments }; [self sendMessage:message waitForReply:^(NSError *socketError, NSDictionary *reply) { diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index bb6aff8e4..696de22ad 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -63,7 +63,7 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void); BOOL _valid; BOOL _wasBatchActive; __weak id _javaScriptExecutor; - NSMutableArray *_pendingCalls; + NSMutableArray *_pendingCalls; NSMutableDictionary *_moduleDataByName; NSArray *_moduleDataByID; NSDictionary> *_modulesByName_DEPRECATED; @@ -436,10 +436,8 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void); { _loading = NO; [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{ - for (NSArray *call in _pendingCalls) { - [self _actuallyInvokeAndProcessModule:call[0] - method:call[1] - arguments:call[2]]; + for (dispatch_block_t call in _pendingCalls) { + call(); } }]; } @@ -579,10 +577,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR - (void)logMessage:(NSString *)message level:(NSString *)level { if (RCT_DEBUG) { - [_javaScriptExecutor executeJSCall:@"RCTLog" - method:@"logIfNoNativeHook" - arguments:@[level, message] - callback:^(__unused id json, __unused NSError *error) {}]; + [self enqueueJSCall:@"RCTLog.logIfNoNativeHook" + args:@[level, message]]; } } @@ -593,11 +589,66 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR */ - (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args { + /** + * AnyThread + */ + NSArray *ids = [moduleDotMethod componentsSeparatedByString:@"."]; - [self _invokeAndProcessModule:@"BatchedBridge" - method:@"callFunctionReturnFlushedQueue" - arguments:@[ids[0], ids[1], args ?: @[]]]; + NSString *module = ids[0]; + NSString *method = ids[1]; + + RCTProfileBeginFlowEvent(); + + __weak RCTBatchedBridge *weakSelf = self; + [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{ + RCTProfileEndFlowEvent(); + + RCTBatchedBridge *strongSelf = weakSelf; + if (!strongSelf || !strongSelf.valid) { + return; + } + + if (strongSelf.loading) { + dispatch_block_t pendingCall = ^{ + [weakSelf _actuallyInvokeAndProcessModule:module method:method arguments:args ?: @[]]; + }; + [strongSelf->_pendingCalls addObject:pendingCall]; + } else { + [strongSelf _actuallyInvokeAndProcessModule:module method:method arguments:args ?: @[]]; + } + }]; +} + +/** + * Called by RCTModuleMethod from any thread. + */ +- (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args +{ + /** + * AnyThread + */ + + RCTProfileBeginFlowEvent(); + + __weak RCTBatchedBridge *weakSelf = self; + [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{ + RCTProfileEndFlowEvent(); + + RCTBatchedBridge *strongSelf = weakSelf; + if (!strongSelf || !strongSelf.valid) { + return; + } + + if (strongSelf.loading) { + dispatch_block_t pendingCall = ^{ + [weakSelf _actuallyInvokeCallback:cbID arguments:args ?: @[]]; + }; + [strongSelf->_pendingCalls addObject:pendingCall]; + } else { + [strongSelf _actuallyInvokeCallback:cbID arguments:args]; + } + }]; } /** @@ -608,9 +659,9 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR RCTAssertJSThread(); dispatch_block_t block = ^{ - [self _actuallyInvokeAndProcessModule:@"BatchedBridge" - method:@"callFunctionReturnFlushedQueue" - arguments:@[@"JSTimersExecution", @"callTimers", @[@[timer]]]]; + [self _actuallyInvokeAndProcessModule:@"JSTimersExecution" + method:@"callTimers" + arguments:@[@[timer]]]; }; if ([_javaScriptExecutor respondsToSelector:@selector(executeAsyncBlockOnJavaScriptQueue:)]) { @@ -637,10 +688,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR } RCT_PROFILE_BEGIN_EVENT(0, @"FetchApplicationScriptCallbacks", nil); - [_javaScriptExecutor executeJSCall:@"BatchedBridge" - method:@"flushedQueue" - arguments:@[] - callback:^(id json, NSError *error) + [_javaScriptExecutor flushedQueue:^(id json, NSError *error) { RCT_PROFILE_END_EVENT(0, @"js_call,init", @{ @"json": RCTNullIfNil(json), @@ -656,35 +704,6 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR #pragma mark - Payload Generation -/** - * Called by enqueueJSCall from any thread, or from _immediatelyCallTimer, - * on the JS thread, but only in non-batched mode. - */ -- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args -{ - /** - * AnyThread - */ - - RCTProfileBeginFlowEvent(); - - __weak RCTBatchedBridge *weakSelf = self; - [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{ - RCTProfileEndFlowEvent(); - - RCTBatchedBridge *strongSelf = weakSelf; - if (!strongSelf || !strongSelf.valid) { - return; - } - - if (strongSelf.loading) { - [strongSelf->_pendingCalls addObject:@[module, method, args]]; - } else { - [strongSelf _actuallyInvokeAndProcessModule:module method:method arguments:args]; - } - }]; -} - - (void)_actuallyInvokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args @@ -705,10 +724,34 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR [self handleBuffer:json batchEnded:YES]; }; - [_javaScriptExecutor executeJSCall:module - method:method - arguments:args - callback:processResponse]; + [_javaScriptExecutor callFunctionOnModule:module + method:method + arguments:args + callback:processResponse]; +} + +- (void)_actuallyInvokeCallback:(NSNumber *)cbID + arguments:(NSArray *)args +{ + RCTAssertJSThread(); + + [[NSNotificationCenter defaultCenter] postNotificationName:RCTEnqueueNotification object:nil userInfo:nil]; + + RCTJavaScriptCallback processResponse = ^(id json, NSError *error) { + if (error) { + RCTFatal(error); + } + + if (!self.isValid) { + return; + } + [[NSNotificationCenter defaultCenter] postNotificationName:RCTDequeueNotification object:nil userInfo:nil]; + [self handleBuffer:json batchEnded:YES]; + }; + + [_javaScriptExecutor invokeCallbackID:cbID + arguments:args + callback:processResponse]; } #pragma mark - Payload Processing diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 013a54136..3e4a1bc5c 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -310,9 +310,11 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) [self.batchedBridge enqueueJSCall:moduleDotMethod args:args]; } -RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module - method:(__unused NSString *)method - arguments:(__unused NSArray *)args); +- (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args +{ + [self.batchedBridge enqueueCallback:cbID args:args]; +} + @end @implementation RCTBridge(Deprecated) diff --git a/React/Base/RCTJavaScriptExecutor.h b/React/Base/RCTJavaScriptExecutor.h index 5506ddfa3..832b78e3c 100644 --- a/React/Base/RCTJavaScriptExecutor.h +++ b/React/Base/RCTJavaScriptExecutor.h @@ -35,13 +35,29 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error); @property (nonatomic, readonly, getter=isValid) BOOL valid; /** - * Executes given method with arguments on JS thread and calls the given callback - * with JSValue and JSContext as a result of the JS module call. + * Executes BatchedBridge.flushedQueue on JS thread and calls the given callback + * with JSValue, containing the next queue, and JSContext. */ -- (void)executeJSCall:(NSString *)name - method:(NSString *)method - arguments:(NSArray *)arguments - callback:(RCTJavaScriptCallback)onComplete; +- (void)flushedQueue:(RCTJavaScriptCallback)onComplete; + +/** + * Executes BatchedBridge.callFunctionReturnFlushedQueue with the module name, + * method name and optional additional arguments on the JS thread and calls the + * given callback with JSValue, containing the next queue, and JSContext. + */ +- (void)callFunctionOnModule:(NSString *)module + method:(NSString *)method + arguments:(NSArray *)args + callback:(RCTJavaScriptCallback)onComplete; + +/** + * Executes BatchedBridge.invokeCallbackAndReturnFlushedQueue with the cbID, + * and optional additional arguments on the JS thread and calls the + * given callback with JSValue, containing the next queue, and JSContext. + */ +- (void)invokeCallbackID:(NSNumber *)cbID + arguments:(NSArray *)args + callback:(RCTJavaScriptCallback)onComplete; /** * Runs an application script, and notifies of the script load being complete via `onComplete`. diff --git a/React/Base/RCTModuleMethod.m b/React/Base/RCTModuleMethod.m index c35017c98..0182fe93d 100644 --- a/React/Base/RCTModuleMethod.m +++ b/React/Base/RCTModuleMethod.m @@ -37,9 +37,11 @@ typedef BOOL (^RCTArgumentBlock)(RCTBridge *, NSUInteger, id); @interface RCTBridge (RCTModuleMethod) -- (void)_invokeAndProcessModule:(NSString *)module - method:(NSString *)method - arguments:(NSArray *)args; +/** + * This method is used to invoke a callback that was registered in the + * JavaScript application context. Safe to call from any thread. + */ +- (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args; @end @@ -191,9 +193,7 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray_context.ctx); JSObjectRef globalObjectJSRef = JSContextGetGlobalObject(strongSelf->_context.ctx); - // get require - JSStringRef requireNameJSStringRef = JSStringCreateWithUTF8CString("require"); - JSValueRef requireJSRef = JSObjectGetProperty(contextJSRef, globalObjectJSRef, requireNameJSStringRef, &errorJSRef); - JSStringRelease(requireNameJSStringRef); + // get the BatchedBridge object + JSStringRef moduleNameJSStringRef = JSStringCreateWithUTF8CString("__fbBatchedBridge"); + JSValueRef moduleJSRef = JSObjectGetProperty(contextJSRef, globalObjectJSRef, moduleNameJSStringRef, &errorJSRef); + JSStringRelease(moduleNameJSStringRef); - if (requireJSRef != NULL && !JSValueIsUndefined(contextJSRef, requireJSRef) && errorJSRef == NULL) { + if (moduleJSRef != NULL && errorJSRef == NULL && !JSValueIsUndefined(contextJSRef, moduleJSRef)) { - // get module - JSStringRef moduleNameJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)name); - JSValueRef moduleNameJSRef = JSValueMakeString(contextJSRef, moduleNameJSStringRef); - JSValueRef moduleJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)requireJSRef, NULL, 1, (const JSValueRef *)&moduleNameJSRef, &errorJSRef); - JSStringRelease(moduleNameJSStringRef); + // get method + JSStringRef methodNameJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)method); + JSValueRef methodJSRef = JSObjectGetProperty(contextJSRef, (JSObjectRef)moduleJSRef, methodNameJSStringRef, &errorJSRef); + JSStringRelease(methodNameJSStringRef); - if (moduleJSRef != NULL && errorJSRef == NULL && !JSValueIsUndefined(contextJSRef, moduleJSRef)) { + if (methodJSRef != NULL && errorJSRef == NULL) { - // get method - JSStringRef methodNameJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)method); - JSValueRef methodJSRef = JSObjectGetProperty(contextJSRef, (JSObjectRef)moduleJSRef, methodNameJSStringRef, &errorJSRef); - JSStringRelease(methodNameJSStringRef); + // direct method invoke with no arguments + if (arguments.count == 0) { + resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)methodJSRef, (JSObjectRef)moduleJSRef, 0, NULL, &errorJSRef); + } - if (methodJSRef != NULL && errorJSRef == NULL) { + // direct method invoke with 1 argument + else if(arguments.count == 1) { + JSStringRef argsJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)argsString); + JSValueRef argsJSRef = JSValueMakeFromJSONString(contextJSRef, argsJSStringRef); + resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)methodJSRef, (JSObjectRef)moduleJSRef, 1, &argsJSRef, &errorJSRef); + JSStringRelease(argsJSStringRef); - // direct method invoke with no arguments - if (arguments.count == 0) { - resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)methodJSRef, (JSObjectRef)moduleJSRef, 0, NULL, &errorJSRef); - } + } else { + // apply invoke with array of arguments + JSStringRef applyNameJSStringRef = JSStringCreateWithUTF8CString("apply"); + JSValueRef applyJSRef = JSObjectGetProperty(contextJSRef, (JSObjectRef)methodJSRef, applyNameJSStringRef, &errorJSRef); + JSStringRelease(applyNameJSStringRef); - // direct method invoke with 1 argument - else if(arguments.count == 1) { + if (applyJSRef != NULL && errorJSRef == NULL) { + // invoke apply JSStringRef argsJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)argsString); JSValueRef argsJSRef = JSValueMakeFromJSONString(contextJSRef, argsJSStringRef); - resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)methodJSRef, (JSObjectRef)moduleJSRef, 1, &argsJSRef, &errorJSRef); + + JSValueRef args[2]; + args[0] = JSValueMakeNull(contextJSRef); + args[1] = argsJSRef; + + resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)applyJSRef, (JSObjectRef)methodJSRef, 2, args, &errorJSRef); JSStringRelease(argsJSStringRef); - - } else { - // apply invoke with array of arguments - JSStringRef applyNameJSStringRef = JSStringCreateWithUTF8CString("apply"); - JSValueRef applyJSRef = JSObjectGetProperty(contextJSRef, (JSObjectRef)methodJSRef, applyNameJSStringRef, &errorJSRef); - JSStringRelease(applyNameJSStringRef); - - if (applyJSRef != NULL && errorJSRef == NULL) { - // invoke apply - JSStringRef argsJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)argsString); - JSValueRef argsJSRef = JSValueMakeFromJSONString(contextJSRef, argsJSStringRef); - - JSValueRef args[2]; - args[0] = JSValueMakeNull(contextJSRef); - args[1] = argsJSRef; - - resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)applyJSRef, (JSObjectRef)methodJSRef, 2, args, &errorJSRef); - JSStringRelease(argsJSStringRef); - } } } } @@ -539,7 +553,7 @@ static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context) } onComplete(objcValue, nil); - }), 0, @"js_call", (@{@"module":name, @"method": method, @"args": arguments}))]; + }), 0, @"js_call", (@{@"method": method, @"args": arguments}))]; } - (void)executeApplicationScript:(NSData *)script diff --git a/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java b/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java index b770b3b75..28bc76f82 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java @@ -27,7 +27,6 @@ import com.facebook.react.modules.debug.AnimationsDebugModule; import com.facebook.react.modules.debug.SourceCodeModule; import com.facebook.react.modules.systeminfo.AndroidInfoModule; import com.facebook.react.uimanager.AppRegistry; -import com.facebook.react.uimanager.ReactNative; import com.facebook.react.uimanager.UIImplementationProvider; import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.ViewManager; @@ -97,7 +96,6 @@ import com.facebook.systrace.Systrace; RCTNativeAppEventEmitter.class, AppRegistry.class, BridgeProfiling.class, - ReactNative.class, DebugComponentOwnershipModule.RCTDebugComponentOwnership.class); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java index 1169034ee..7953691c0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java @@ -52,7 +52,6 @@ import com.facebook.react.devsupport.ReactInstanceDevCommandsHandler; import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; import com.facebook.react.modules.core.DeviceEventManagerModule; import com.facebook.react.uimanager.AppRegistry; -import com.facebook.react.uimanager.ReactNative; import com.facebook.react.uimanager.UIImplementationProvider; import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.ViewManager; @@ -581,8 +580,8 @@ import com.facebook.systrace.Systrace; ReactRootView rootView, CatalystInstance catalystInstance) { UiThreadUtil.assertOnUiThread(); - catalystInstance.getJSModule(ReactNative.class) - .unmountComponentAtNodeAndRemoveContainer(rootView.getId()); + catalystInstance.getJSModule(AppRegistry.class) + .unmountApplicationComponentAtRootTag(rootView.getId()); } private void tearDownReactContext(ReactContext reactContext) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JSDebuggerWebSocketClient.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JSDebuggerWebSocketClient.java index 8940ffc08..13c99c043 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/JSDebuggerWebSocketClient.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JSDebuggerWebSocketClient.java @@ -126,7 +126,6 @@ public class JSDebuggerWebSocketClient implements WebSocketListener { } public void executeJSCall( - String moduleName, String methodName, String jsonArgsArray, JSDebuggerCallback callback) { @@ -136,9 +135,7 @@ public class JSDebuggerWebSocketClient implements WebSocketListener { try { JsonGenerator jg = startMessageObject(requestID); - jg.writeStringField("method","executeJSCall"); - jg.writeStringField("moduleName", moduleName); - jg.writeStringField("moduleMethod", methodName); + jg.writeStringField("method", methodName); jg.writeFieldName("arguments"); jg.writeRawValue(jsonArgsArray); sendMessage(requestID, endMessageObject(jg)); diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaJSExecutor.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaJSExecutor.java index 76b8a1cb5..3c8eb4574 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaJSExecutor.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaJSExecutor.java @@ -40,13 +40,12 @@ public interface JavaJSExecutor { /** * Execute javascript method within js context - * @param modulename name of the common-js like module to execute the method from * @param methodName name of the method to be executed * @param jsonArgsArray json encoded array of arguments provided for the method call * @return json encoded value returned from the method call */ @DoNotStrip - String executeJSCall(String modulename, String methodName, String jsonArgsArray) + String executeJSCall(String methodName, String jsonArgsArray) throws ProxyExecutorException; @DoNotStrip diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/WebsocketJavaScriptExecutor.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/WebsocketJavaScriptExecutor.java index e044ca8cd..0fba2b0a3 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/WebsocketJavaScriptExecutor.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/WebsocketJavaScriptExecutor.java @@ -160,11 +160,10 @@ public class WebsocketJavaScriptExecutor implements JavaJSExecutor { } @Override - public @Nullable String executeJSCall(String moduleName, String methodName, String jsonArgsArray) + public @Nullable String executeJSCall(String methodName, String jsonArgsArray) throws ProxyExecutorException { JSExecutorCallbackFuture callback = new JSExecutorCallbackFuture(); Assertions.assertNotNull(mWebSocketClient).executeJSCall( - moduleName, methodName, jsonArgsArray, callback); diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/AppRegistry.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/AppRegistry.java index f0cd49855..d9a387b7f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/AppRegistry.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/AppRegistry.java @@ -18,4 +18,6 @@ import com.facebook.react.bridge.WritableMap; public interface AppRegistry extends JavaScriptModule { void runApplication(String appKey, WritableMap appParameters); + void unmountApplicationComponentAtRootTag(int rootNodeTag); + } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactNative.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactNative.java deleted file mode 100644 index 2d99e79f5..000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactNative.java +++ /dev/null @@ -1,19 +0,0 @@ -/** - * 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. - */ - -package com.facebook.react.uimanager; - -import com.facebook.react.bridge.JavaScriptModule; - -/** - * JS module interface - used by UIManager to communicate with main React JS module methods - */ -public interface ReactNative extends JavaScriptModule { - void unmountComponentAtNodeAndRemoveContainer(int rootNodeTag); -} diff --git a/ReactAndroid/src/main/jni/react/Bridge.cpp b/ReactAndroid/src/main/jni/react/Bridge.cpp index 84fb525b0..90a50d098 100644 --- a/ReactAndroid/src/main/jni/react/Bridge.cpp +++ b/ReactAndroid/src/main/jni/react/Bridge.cpp @@ -27,11 +27,18 @@ public: m_jsExecutor->executeApplicationScript(script, sourceURL); } - void executeJSCall( - const std::string& moduleName, - const std::string& methodName, - const std::vector& arguments) { - auto returnedJSON = m_jsExecutor->executeJSCall(moduleName, methodName, arguments); + void flush() { + auto returnedJSON = m_jsExecutor->flush(); + m_callback(parseMethodCalls(returnedJSON), true /* = isEndOfBatch */); + } + + void callFunction(const double moduleId, const double methodId, const folly::dynamic& arguments) { + auto returnedJSON = m_jsExecutor->callFunction(moduleId, methodId, arguments); + m_callback(parseMethodCalls(returnedJSON), true /* = isEndOfBatch */); + } + + void invokeCallback(const double callbackId, const folly::dynamic& arguments) { + auto returnedJSON = m_jsExecutor->invokeCallback(callbackId, arguments); m_callback(parseMethodCalls(returnedJSON), true /* = isEndOfBatch */); } @@ -88,17 +95,31 @@ void Bridge::executeApplicationScript(const std::string& script, const std::stri m_threadState->executeApplicationScript(script, sourceURL); } -void Bridge::executeJSCall( - const std::string& script, - const std::string& sourceURL, - const std::vector& arguments) { +void Bridge::flush() { + if (*m_destroyed) { + return; + } + m_threadState->flush(); +} + +void Bridge::callFunction(const double moduleId, const double methodId, const folly::dynamic& arguments) { if (*m_destroyed) { return; } #ifdef WITH_FBSYSTRACE - FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "Bridge.executeJSCall"); + FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "Bridge.callFunction"); #endif - m_threadState->executeJSCall(script, sourceURL, arguments); + m_threadState->callFunction(moduleId, methodId, arguments); +} + +void Bridge::invokeCallback(const double callbackId, const folly::dynamic& arguments) { + if (*m_destroyed) { + return; + } + #ifdef WITH_FBSYSTRACE + FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "Bridge.invokeCallback"); + #endif + m_threadState->invokeCallback(callbackId, arguments); } void Bridge::setGlobalVariable(const std::string& propName, const std::string& jsonValue) { diff --git a/ReactAndroid/src/main/jni/react/Bridge.h b/ReactAndroid/src/main/jni/react/Bridge.h index 23843de42..1d0f15945 100644 --- a/ReactAndroid/src/main/jni/react/Bridge.h +++ b/ReactAndroid/src/main/jni/react/Bridge.h @@ -29,10 +29,22 @@ public: Bridge(const RefPtr& jsExecutorFactory, Callback callback); virtual ~Bridge(); - void executeJSCall( - const std::string& moduleName, - const std::string& methodName, - const std::vector& values); + /** + * Flush get the next queue of changes. + */ + void flush(); + + /** + * Executes a function with the module ID and method ID and any additional + * arguments in JS. + */ + void callFunction(const double moduleId, const double methodId, const folly::dynamic& args); + + /** + * Invokes a callback with the cbID, and optional additional arguments in JS. + */ + void invokeCallback(const double callbackId, const folly::dynamic& args); + void executeApplicationScript(const std::string& script, const std::string& sourceURL); void setGlobalVariable(const std::string& propName, const std::string& jsonValue); bool supportsProfiling(); diff --git a/ReactAndroid/src/main/jni/react/Executor.h b/ReactAndroid/src/main/jni/react/Executor.h index 6a59bff42..bef2d0724 100644 --- a/ReactAndroid/src/main/jni/react/Executor.h +++ b/ReactAndroid/src/main/jni/react/Executor.h @@ -28,13 +28,31 @@ public: class JSExecutor { public: + /** + * Execute an application script bundle in the JS context. + */ virtual void executeApplicationScript( const std::string& script, const std::string& sourceURL) = 0; - virtual std::string executeJSCall( - const std::string& moduleName, - const std::string& methodName, - const std::vector& arguments) = 0; + + /** + * Executes BatchedBridge.flushedQueue in JS to get the next queue of changes. + */ + virtual std::string flush() = 0; + + /** + * Executes BatchedBridge.callFunctionReturnFlushedQueue with the module ID, + * method ID and optional additional arguments in JS, and returns the next + * queue. + */ + virtual std::string callFunction(const double moduleId, const double methodId, const folly::dynamic& arguments) = 0; + + /** + * Executes BatchedBridge.invokeCallbackAndReturnFlushedQueue with the cbID, + * and optional additional arguments in JS and returns the next queue. + */ + virtual std::string invokeCallback(const double callbackId, const folly::dynamic& arguments) = 0; + virtual void setGlobalVariable( const std::string& propName, const std::string& jsonValue) = 0; diff --git a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp index 5f23c2b88..de9dffc05 100644 --- a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp +++ b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp @@ -80,6 +80,26 @@ static JSValueRef evaluateScriptWithJSC( return result; } +static std::string executeJSCallWithJSC( + JSGlobalContextRef ctx, + const std::string& methodName, + const std::vector& arguments) { + #ifdef WITH_FBSYSTRACE + FbSystraceSection s( + TRACE_TAG_REACT_CXX_BRIDGE, "JSCExecutor.executeJSCall", + "method", methodName); + #endif + + // Evaluate script with JSC + folly::dynamic jsonArgs(arguments.begin(), arguments.end()); + auto js = folly::to( + "__fbBatchedBridge.", methodName, ".apply(null, ", + folly::toJson(jsonArgs), ")"); + auto result = evaluateScriptWithJSC(ctx, String(js.c_str()), nullptr); + JSValueProtect(ctx, result); + return Value(ctx, result).toJSONString(); +} + std::unique_ptr JSCExecutorFactory::createJSExecutor(FlushImmediateCallback cb) { return std::unique_ptr(new JSCExecutor(cb)); } @@ -130,25 +150,28 @@ void JSCExecutor::executeApplicationScript( evaluateScriptWithJSC(m_context, jsScript, jsSourceURL); } -std::string JSCExecutor::executeJSCall( - const std::string& moduleName, - const std::string& methodName, - const std::vector& arguments) { - #ifdef WITH_FBSYSTRACE - FbSystraceSection s( - TRACE_TAG_REACT_CXX_BRIDGE, "JSCExecutor.executeJSCall", - "module", moduleName, - "method", methodName); - #endif +std::string JSCExecutor::flush() { + // TODO: Make this a first class function instead of evaling. #9317773 + return executeJSCallWithJSC(m_context, "flushedQueue", std::vector()); +} - // Evaluate script with JSC - folly::dynamic jsonArgs(arguments.begin(), arguments.end()); - auto js = folly::to( - "require('", moduleName, "').", methodName, ".apply(null, ", - folly::toJson(jsonArgs), ")"); - auto result = evaluateScriptWithJSC(m_context, String(js.c_str()), nullptr); - JSValueProtect(m_context, result); - return Value(m_context, result).toJSONString(); +std::string JSCExecutor::callFunction(const double moduleId, const double methodId, const folly::dynamic& arguments) { + // TODO: Make this a first class function instead of evaling. #9317773 + std::vector call{ + (double) moduleId, + (double) methodId, + std::move(arguments), + }; + return executeJSCallWithJSC(m_context, "callFunctionReturnFlushedQueue", std::move(call)); +} + +std::string JSCExecutor::invokeCallback(const double callbackId, const folly::dynamic& arguments) { + // TODO: Make this a first class function instead of evaling. #9317773 + std::vector call{ + (double) callbackId, + std::move(arguments) + }; + return executeJSCallWithJSC(m_context, "invokeCallbackAndReturnFlushedQueue", std::move(call)); } void JSCExecutor::setGlobalVariable(const std::string& propName, const std::string& jsonValue) { diff --git a/ReactAndroid/src/main/jni/react/JSCExecutor.h b/ReactAndroid/src/main/jni/react/JSCExecutor.h index a319a9036..507424735 100644 --- a/ReactAndroid/src/main/jni/react/JSCExecutor.h +++ b/ReactAndroid/src/main/jni/react/JSCExecutor.h @@ -18,13 +18,18 @@ class JSCExecutor : public JSExecutor { public: explicit JSCExecutor(FlushImmediateCallback flushImmediateCallback); ~JSCExecutor() override; + virtual void executeApplicationScript( const std::string& script, const std::string& sourceURL) override; - virtual std::string executeJSCall( - const std::string& moduleName, - const std::string& methodName, - const std::vector& arguments) override; + virtual std::string flush() override; + virtual std::string callFunction( + const double moduleId, + const double methodId, + const folly::dynamic& arguments) override; + virtual std::string invokeCallback( + const double callbackId, + const folly::dynamic& arguments) override; virtual void setGlobalVariable( const std::string& propName, const std::string& jsonValue) override; diff --git a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp index 610cd160d..8867b0c9e 100644 --- a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp +++ b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp @@ -628,7 +628,7 @@ static void executeApplicationScript( try { // Execute the application script and collect/dispatch any native calls that might have occured bridge->executeApplicationScript(script, sourceUri); - bridge->executeJSCall("BatchedBridge", "flushedQueue", std::vector()); + bridge->flush(); } catch (...) { translatePendingCppExceptionToJavaException(); } @@ -682,13 +682,12 @@ static void callFunction(JNIEnv* env, jobject obj, jint moduleId, jint methodId, NativeArray::jhybridobject args) { auto bridge = extractRefPtr(env, obj); auto arguments = cthis(wrap_alias(args)); - std::vector call{ - (double) moduleId, - (double) methodId, - std::move(arguments->array), - }; try { - bridge->executeJSCall("BatchedBridge", "callFunctionReturnFlushedQueue", std::move(call)); + bridge->callFunction( + (double) moduleId, + (double) methodId, + std::move(arguments->array) + ); } catch (...) { translatePendingCppExceptionToJavaException(); } @@ -698,12 +697,11 @@ static void invokeCallback(JNIEnv* env, jobject obj, jint callbackId, NativeArray::jhybridobject args) { auto bridge = extractRefPtr(env, obj); auto arguments = cthis(wrap_alias(args)); - std::vector call{ - (double) callbackId, - std::move(arguments->array) - }; try { - bridge->executeJSCall("BatchedBridge", "invokeCallbackAndReturnFlushedQueue", std::move(call)); + bridge->invokeCallback( + (double) callbackId, + std::move(arguments->array) + ); } catch (...) { translatePendingCppExceptionToJavaException(); } diff --git a/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.cpp b/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.cpp index ab86a6698..ad4e245d1 100644 --- a/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.cpp +++ b/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.cpp @@ -12,6 +12,20 @@ namespace react { const auto EXECUTOR_BASECLASS = "com/facebook/react/bridge/JavaJSExecutor"; +static std::string executeJSCallWithProxy( + jobject executor, + const std::string& methodName, + const std::vector& arguments) { + static auto executeJSCall = + jni::findClassStatic(EXECUTOR_BASECLASS)->getMethod("executeJSCall"); + + auto result = executeJSCall( + executor, + jni::make_jstring(methodName).get(), + jni::make_jstring(folly::toJson(arguments).c_str()).get()); + return result->toString(); +} + std::unique_ptr ProxyExecutorOneTimeFactory::createJSExecutor(FlushImmediateCallback ignoredCallback) { FBASSERTMSGF( m_executor.get() != nullptr, @@ -36,19 +50,26 @@ void ProxyExecutor::executeApplicationScript( jni::make_jstring(sourceURL).get()); } -std::string ProxyExecutor::executeJSCall( - const std::string& moduleName, - const std::string& methodName, - const std::vector& arguments) { - static auto executeJSCall = - jni::findClassStatic(EXECUTOR_BASECLASS)->getMethod("executeJSCall"); - auto result = executeJSCall( - m_executor.get(), - jni::make_jstring(moduleName).get(), - jni::make_jstring(methodName).get(), - jni::make_jstring(folly::toJson(arguments).c_str()).get()); - return result->toString(); +std::string ProxyExecutor::flush() { + return executeJSCallWithProxy(m_executor.get(), "flushedQueue", std::vector()); +} + +std::string ProxyExecutor::callFunction(const double moduleId, const double methodId, const folly::dynamic& arguments) { + std::vector call{ + (double) moduleId, + (double) methodId, + std::move(arguments), + }; + return executeJSCallWithProxy(m_executor.get(), "callFunctionReturnFlushedQueue", std::move(call)); +} + +std::string ProxyExecutor::invokeCallback(const double callbackId, const folly::dynamic& arguments) { + std::vector call{ + (double) callbackId, + std::move(arguments) + }; + return executeJSCallWithProxy(m_executor.get(), "invokeCallbackAndReturnFlushedQueue", std::move(call)); } void ProxyExecutor::setGlobalVariable(const std::string& propName, const std::string& jsonValue) { diff --git a/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.h b/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.h index 805eda532..2f5d21980 100644 --- a/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.h +++ b/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.h @@ -32,10 +32,14 @@ public: virtual void executeApplicationScript( const std::string& script, const std::string& sourceURL) override; - virtual std::string executeJSCall( - const std::string& moduleName, - const std::string& methodName, - const std::vector& arguments) override; + virtual std::string flush() override; + virtual std::string callFunction( + const double moduleId, + const double methodId, + const folly::dynamic& arguments) override; + virtual std::string invokeCallback( + const double callbackId, + const folly::dynamic& arguments) override; virtual void setGlobalVariable( const std::string& propName, const std::string& jsonValue) override; diff --git a/ReactAndroid/src/main/jni/react/test/jscexecutor.cpp b/ReactAndroid/src/main/jni/react/test/jscexecutor.cpp index 40d217974..9091c1125 100644 --- a/ReactAndroid/src/main/jni/react/test/jscexecutor.cpp +++ b/ReactAndroid/src/main/jni/react/test/jscexecutor.cpp @@ -20,17 +20,13 @@ static std::vector executeForMethodCalls( int moduleId, int methodId, std::vector args = std::vector()) { - std::vector call; - call.emplace_back((double) moduleId); - call.emplace_back((double) methodId); - call.emplace_back(std::move(args)); - return parseMethodCalls(e.executeJSCall("Bridge", "callFunction", call)); + return parseMethodCalls(e.callFunction(moduleId, methodId, std::move(args))); } TEST(JSCExecutor, CallFunction) { auto jsText = "" "var Bridge = {" - " callFunction: function (module, method, args) {" + " callFunctionReturnFlushedQueue: function (module, method, args) {" " return [[module + 1], [method + 1], [args]];" " }," "};" @@ -58,7 +54,7 @@ TEST(JSCExecutor, CallFunction) { TEST(JSCExecutor, CallFunctionWithMap) { auto jsText = "" "var Bridge = {" - " callFunction: function (module, method, args) {" + " callFunctionReturnFlushedQueue: function (module, method, args) {" " var s = args[0].foo + args[0].bar + args[0].baz;" " return [[module], [method], [[s]]];" " }," @@ -85,7 +81,7 @@ TEST(JSCExecutor, CallFunctionWithMap) { TEST(JSCExecutor, CallFunctionReturningMap) { auto jsText = "" "var Bridge = {" - " callFunction: function (module, method, args) {" + " callFunctionReturnFlushedQueue: function (module, method, args) {" " var s = { foo: 4, bar: true };" " return [[module], [method], [[s]]];" " }," @@ -111,7 +107,7 @@ TEST(JSCExecutor, CallFunctionReturningMap) { TEST(JSCExecutor, CallFunctionWithArray) { auto jsText = "" "var Bridge = {" - " callFunction: function (module, method, args) {" + " callFunctionReturnFlushedQueue: function (module, method, args) {" " var s = args[0][0]+ args[0][1] + args[0][2] + args[0].length;" " return [[module], [method], [[s]]];" " }," @@ -138,7 +134,7 @@ TEST(JSCExecutor, CallFunctionWithArray) { TEST(JSCExecutor, CallFunctionReturningNumberArray) { auto jsText = "" "var Bridge = {" - " callFunction: function (module, method, args) {" + " callFunctionReturnFlushedQueue: function (module, method, args) {" " var s = [3, 1, 4];" " return [[module], [method], [[s]]];" " }," @@ -162,7 +158,7 @@ TEST(JSCExecutor, CallFunctionReturningNumberArray) { TEST(JSCExecutor, SetSimpleGlobalVariable) { auto jsText = "" "var Bridge = {" - " callFunction: function (module, method, args) {" + " callFunctionReturnFlushedQueue: function (module, method, args) {" " return [[module], [method], [[__foo]]];" " }," "};" @@ -182,7 +178,7 @@ TEST(JSCExecutor, SetSimpleGlobalVariable) { TEST(JSCExecutor, SetObjectGlobalVariable) { auto jsText = "" "var Bridge = {" - " callFunction: function (module, method, args) {" + " callFunctionReturnFlushedQueue: function (module, method, args) {" " return [[module], [method], [[__foo]]];" " }," "};" diff --git a/local-cli/server/util/debugger.html b/local-cli/server/util/debugger.html index a69424ea9..289b1f53b 100644 --- a/local-cli/server/util/debugger.html +++ b/local-cli/server/util/debugger.html @@ -58,12 +58,6 @@ var messageHandlers = { window.onbeforeunload = undefined; window.localStorage.setItem('sessionID', message.id); window.location.reload(); - }, - 'executeApplicationScript': function(message) { - worker.postMessage(message); - }, - 'executeJSCall': function(message) { - worker.postMessage(message); } }; @@ -87,9 +81,11 @@ function connectToDebuggerProxy() { var handler = messageHandlers[object.method]; if (handler) { + // If we have a local handler, use it. handler(object); } else { - console.warn('Unknown method: ' + object.method); + // Otherwise, pass through to the worker. + worker.postMessage(object); } }; diff --git a/local-cli/server/util/debuggerWorker.js b/local-cli/server/util/debuggerWorker.js index 94f3b7d58..fc72c5713 100644 --- a/local-cli/server/util/debuggerWorker.js +++ b/local-cli/server/util/debuggerWorker.js @@ -6,7 +6,7 @@ * 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. */ -/* global self, importScripts, postMessage, onmessage: true */ +/* global __fbBatchedBridge, self, importScripts, postMessage, onmessage: true */ /* eslint no-unused-vars: 0 */ 'use strict'; @@ -17,16 +17,6 @@ var messageHandlers = { } importScripts(message.url); sendReply(); - }, - 'executeJSCall': function(message, sendReply) { - var returnValue = [[], [], [], [], []]; - try { - if (typeof require === 'function') { - returnValue = require(message.moduleName)[message.moduleMethod].apply(null, message.arguments); - } - } finally { - sendReply(JSON.stringify(returnValue)); - } } }; @@ -39,8 +29,17 @@ onmessage = function(message) { var handler = messageHandlers[object.method]; if (handler) { + // Special cased handlers handler(object, sendReply); } else { - console.warn('Unknown method: ' + object.method); + // Other methods get called on the bridge + var returnValue = [[], [], [], [], []]; + try { + if (typeof __fbBatchedBridge === 'object') { + returnValue = __fbBatchedBridge[object.method].apply(null, object.arguments); + } + } finally { + sendReply(JSON.stringify(returnValue)); + } } };