From 8d397b4cbc05ad801cfafb421cee39bcfe89711d Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Tue, 8 Dec 2015 15:57:34 -0800 Subject: [PATCH] Decouple Module System from Native Calls Summary: The JavaScript ecosystem doesn't have the notion of a built-in native module loader. Even Node is decoupled from its module loader. The module loader system is just JS that runs on top of the global `process` object which has all the built-in goodies. Additionally there is no such thing as a global require. That is something unique to our providesModule system. In other module systems such as node, every require is contextual. Even registered npm names are localized by version. The only global namespace that is accessible to the host environment is the global object. Normally module systems attaches itself onto the hooks provided by the host environment on the global object. Currently, we have two forms of dispatch that reaches directly into the module system. executeJSCall which reaches directly into require. Everything now calls through the BatchedBridge module (except one RCTLog edge case that I will fix). I propose that the executors calls directly onto `BatchedBridge` through an instance on the global so that everything is guaranteed to go through it. It becomes the main communication hub. I also propose that we drop the dynamic requires inside of MessageQueue/BatchBridge and instead have the modules register themselves with the bridge. executeJSCall was originally modeled after the XHP equivalent. The XHP equivalent was designed that way because the act of doing the call was the thing that defined a dependency on the module from the page. However, that is not how React Native works. The JS side is driving the dependencies by virtue of requiring new modules and frameworks and the existence of dependencies is driven by the JS side, so this design doesn't make as much sense. The main driver for this is to be able to introduce a new module system like Prepack's module system. However, it also unlocks the possibility to do dead module elimination even in our current module system. It is currently not possible because we don't know which module might be called from native. Since the module system now becomes decoupled we could publish all our providesModule modules as npm/CommonJS modules using a rewrite script. That's what React Core does. That way people could use any CommonJS bundler such as Webpack, Closure Compiler, Rollup or some new innovation to create a JS bundle. This diff expands the executeJSCalls to the BatchedBridge's three individual pieces to make them first class instead of being dynamic. This removes one layer of abstraction. Hopefully we can also remove more of the things that register themselves with the BatchedBridge (various EventEmitters) and instead have everything go through the public protocol. ReactMethod/RCT_EXPORT_METHOD. public Reviewed By: vjeux Differential Revision: D2717535 fb-gh-sync-id: 70114f05483124f5ac5c4570422bb91a60a727f6 --- .../UIExplorerUnitTests/RCTBridgeTests.m | 20 ++- .../RCTContextExecutorTests.m | 18 ++- IntegrationTests/LoggingTestModule.js | 11 +- Libraries/AppRegistry/AppRegistry.js | 17 ++ Libraries/BatchedBridge/BatchedBridge.js | 20 ++- .../BatchedBridgedModules/RCTEventEmitter.js | 6 + .../RCTDebugComponentOwnership.js | 10 +- Libraries/Device/RCTDeviceEventEmitter.js | 6 + .../NativeApp/RCTNativeAppEventEmitter.js | 6 + Libraries/Utilities/MessageQueue.js | 26 +++- Libraries/Utilities/PerformanceLogger.js | 6 + Libraries/Utilities/RCTLog.js | 7 + .../Utilities/__tests__/MessageQueue-test.js | 5 +- Libraries/WebSocket/RCTWebSocketExecutor.m | 26 +++- React/Base/RCTBatchedBridge.m | 147 +++++++++++------- React/Base/RCTBridge.m | 8 +- React/Base/RCTJavaScriptExecutor.h | 28 +++- React/Base/RCTModuleMethod.m | 24 ++- React/Base/RCTRootView.m | 2 +- React/Executors/RCTContextExecutor.m | 108 +++++++------ .../facebook/react/CoreModulesPackage.java | 2 - .../react/ReactInstanceManagerImpl.java | 5 +- .../bridge/JSDebuggerWebSocketClient.java | 5 +- .../facebook/react/bridge/JavaJSExecutor.java | 3 +- .../bridge/WebsocketJavaScriptExecutor.java | 3 +- .../facebook/react/uimanager/AppRegistry.java | 2 + .../facebook/react/uimanager/ReactNative.java | 19 --- ReactAndroid/src/main/jni/react/Bridge.cpp | 43 +++-- ReactAndroid/src/main/jni/react/Bridge.h | 20 ++- ReactAndroid/src/main/jni/react/Executor.h | 26 +++- .../src/main/jni/react/JSCExecutor.cpp | 59 ++++--- ReactAndroid/src/main/jni/react/JSCExecutor.h | 13 +- .../src/main/jni/react/jni/OnLoad.cpp | 22 ++- .../src/main/jni/react/jni/ProxyExecutor.cpp | 45 ++++-- .../src/main/jni/react/jni/ProxyExecutor.h | 12 +- .../src/main/jni/react/test/jscexecutor.cpp | 20 +-- local-cli/server/util/debugger.html | 10 +- local-cli/server/util/debuggerWorker.js | 23 ++- 38 files changed, 552 insertions(+), 281 deletions(-) delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactNative.java 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)); + } } };