Updates Thu, 25 Jun
This commit is contained in:
commit
cfe13f2c4e
|
@ -7,8 +7,6 @@
|
|||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <AdSupport/ASIdentifierManager.h>
|
||||
|
||||
#import "RCTBridgeModule.h"
|
||||
|
||||
@interface RCTAdSupport : NSObject <RCTBridgeModule>
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <AdSupport/ASIdentifierManager.h>
|
||||
|
||||
#import "RCTAdSupport.h"
|
||||
|
||||
@implementation RCTAdSupport
|
||||
|
|
|
@ -217,6 +217,11 @@ RCT_EXPORT_METHOD(startAnimation:(NSNumber *)reactTag
|
|||
}
|
||||
|
||||
NSValue *fromValue = [view.layer.presentationLayer valueForKeyPath:keypath];
|
||||
#if !CGFLOAT_IS_DOUBLE
|
||||
if ([fromValue isKindOfClass:[NSNumber class]]) {
|
||||
fromValue = [NSNumber numberWithFloat:[(NSNumber *)fromValue doubleValue]];
|
||||
}
|
||||
#endif
|
||||
CGFloat fromFields[count];
|
||||
[fromValue getValue:fromFields];
|
||||
|
||||
|
|
|
@ -50,6 +50,7 @@ var AndroidTextInputAttributes = {
|
|||
multiline: true,
|
||||
password: true,
|
||||
placeholder: true,
|
||||
placeholderTextColor: true,
|
||||
text: true,
|
||||
testID: true,
|
||||
underlineColorAndroid: true,
|
||||
|
@ -514,6 +515,7 @@ var TextInput = React.createClass({
|
|||
onLayout={this.props.onLayout}
|
||||
password={this.props.password || this.props.secureTextEntry}
|
||||
placeholder={this.props.placeholder}
|
||||
placeholderTextColor={this.props.placeholderTextColor}
|
||||
text={this.state.bufferedValue}
|
||||
underlineColorAndroid={this.props.underlineColorAndroid}
|
||||
children={children}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* 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.
|
||||
*
|
||||
* Utility class to provide the component owner hierarchy to native code for
|
||||
* debugging purposes.
|
||||
*
|
||||
* @providesModule RCTDebugComponentOwnership
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var DebugComponentOwnershipModule = require('NativeModules').DebugComponentOwnershipModule;
|
||||
var InspectorUtils = require('InspectorUtils');
|
||||
var ReactNativeTagHandles = require('ReactNativeTagHandles');
|
||||
|
||||
function componentToString(component) {
|
||||
return component.getName ? component.getName() : 'Unknown';
|
||||
}
|
||||
|
||||
function getRootTagForTag(tag: number): ?number {
|
||||
var rootNodeID = ReactNativeTagHandles.tagToRootNodeID[tag];
|
||||
if (!rootNodeID) {
|
||||
return null;
|
||||
}
|
||||
var rootID = ReactNativeTagHandles.getNativeTopRootIDFromNodeID(rootNodeID);
|
||||
if (!rootID) {
|
||||
return null;
|
||||
}
|
||||
return ReactNativeTagHandles.rootNodeIDToTag[rootID];
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
/**
|
||||
* Asynchronously returns the owner hierarchy as an array of strings. Request id is
|
||||
* passed along to the native module so that the native module can identify the
|
||||
* particular call instance.
|
||||
*
|
||||
* Example returned owner hierarchy: ['RootView', 'Dialog', 'TitleView', 'Text']
|
||||
*/
|
||||
getOwnerHierarchy: function(requestID: number, tag: number) {
|
||||
var rootTag = getRootTagForTag(tag);
|
||||
var instance = InspectorUtils.findInstanceByNativeTag(rootTag, tag);
|
||||
var ownerHierarchy = instance ?
|
||||
InspectorUtils.getOwnerHierarchy(instance).map(componentToString) :
|
||||
null;
|
||||
DebugComponentOwnershipModule.receiveOwnershipHierarchy(requestID, tag, ownerHierarchy);
|
||||
},
|
||||
};
|
|
@ -23,6 +23,7 @@
|
|||
/* globals GLOBAL: true, window: true */
|
||||
|
||||
// Just to make sure the JS gets packaged up.
|
||||
require('RCTDebugComponentOwnership');
|
||||
require('RCTDeviceEventEmitter');
|
||||
require('PerformanceLogger');
|
||||
|
||||
|
|
|
@ -285,7 +285,7 @@ RCT_EXPORT_MODULE()
|
|||
}];
|
||||
id<RCTURLRequestHandler> handler = [handlers lastObject];
|
||||
if (!handler) {
|
||||
RCTLogError(@"No suitable request handler found for %@", request);
|
||||
RCTLogError(@"No suitable request handler found for %@", request.URL);
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
|
@ -322,6 +322,9 @@ RCT_EXPORT_MODULE()
|
|||
NSURLRequest *request = [RCTConvert NSURLRequest:query[@"uri"]];
|
||||
if (request) {
|
||||
id<RCTURLRequestHandler> handler = [self handlerForRequest:request];
|
||||
if (!handler) {
|
||||
return;
|
||||
}
|
||||
(void)[[RCTDataLoader alloc] initWithRequest:request handler:handler callback:^(NSData *data, NSString *MIMEType, NSError *error) {
|
||||
if (data) {
|
||||
callback(nil, @{@"body": data, @"contentType": MIMEType});
|
||||
|
|
|
@ -99,7 +99,14 @@ RCT_NOT_IMPLEMENTED(-init)
|
|||
error = [[RCTRedBox sharedInstance] currentErrorMessage];
|
||||
}
|
||||
[rootView removeFromSuperview];
|
||||
RCTAssert(vc.view.subviews.count == 0, @"There shouldn't be any other views: %@", vc.view);
|
||||
|
||||
|
||||
NSArray *nonLayoutSubviews = [vc.view.subviews filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id subview, NSDictionary *bindings) {
|
||||
return ![NSStringFromClass([subview class]) isEqualToString:@"_UILayoutGuide"];
|
||||
}]];
|
||||
RCTAssert(nonLayoutSubviews.count == 0, @"There shouldn't be any other views: %@", nonLayoutSubviews);
|
||||
|
||||
|
||||
vc.view = nil;
|
||||
[[RCTRedBox sharedInstance] dismiss];
|
||||
if (expectErrorBlock) {
|
||||
|
|
|
@ -0,0 +1,785 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTContextExecutor.h"
|
||||
#import "RCTFrameUpdate.h"
|
||||
#import "RCTJavaScriptLoader.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTModuleData.h"
|
||||
#import "RCTModuleMethod.h"
|
||||
#import "RCTPerformanceLogger.h"
|
||||
#import "RCTPerfStats.h"
|
||||
#import "RCTProfile.h"
|
||||
#import "RCTRedBox.h"
|
||||
#import "RCTSourceCode.h"
|
||||
#import "RCTSparseArray.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
#define RCTAssertJSThread() \
|
||||
RCTAssert(![NSStringFromClass([_javaScriptExecutor class]) isEqualToString:@"RCTContextExecutor"] || \
|
||||
[[[NSThread currentThread] name] isEqualToString:@"com.facebook.React.JavaScript"], \
|
||||
@"This method must be called on JS thread")
|
||||
|
||||
NSString *const RCTEnqueueNotification = @"RCTEnqueueNotification";
|
||||
NSString *const RCTDequeueNotification = @"RCTDequeueNotification";
|
||||
|
||||
/**
|
||||
* Must be kept in sync with `MessageQueue.js`.
|
||||
*/
|
||||
typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
|
||||
RCTBridgeFieldRequestModuleIDs = 0,
|
||||
RCTBridgeFieldMethodIDs,
|
||||
RCTBridgeFieldParamss,
|
||||
};
|
||||
|
||||
RCT_EXTERN NSArray *RCTGetModuleClasses(void);
|
||||
|
||||
static id<RCTJavaScriptExecutor> RCTLatestExecutor = nil;
|
||||
id<RCTJavaScriptExecutor> RCTGetLatestExecutor(void);
|
||||
id<RCTJavaScriptExecutor> RCTGetLatestExecutor(void)
|
||||
{
|
||||
return RCTLatestExecutor;
|
||||
}
|
||||
|
||||
@interface RCTBatchedBridge : RCTBridge
|
||||
|
||||
@property (nonatomic, weak) RCTBridge *parentBridge;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTBatchedBridge
|
||||
{
|
||||
BOOL _loading;
|
||||
__weak id<RCTJavaScriptExecutor> _javaScriptExecutor;
|
||||
NSMutableArray *_modules;
|
||||
NSDictionary *_modulesByName;
|
||||
CADisplayLink *_mainDisplayLink;
|
||||
CADisplayLink *_jsDisplayLink;
|
||||
NSMutableSet *_frameUpdateObservers;
|
||||
NSMutableArray *_scheduledCalls;
|
||||
RCTSparseArray *_scheduledCallbacks;
|
||||
}
|
||||
|
||||
@synthesize valid = _valid;
|
||||
|
||||
- (instancetype)initWithParentBridge:(RCTBridge *)bridge
|
||||
{
|
||||
RCTAssertMainThread();
|
||||
RCTAssertParam(bridge);
|
||||
|
||||
if ((self = [super initWithBundleURL:bridge.bundleURL
|
||||
moduleProvider:bridge.moduleProvider
|
||||
launchOptions:bridge.launchOptions])) {
|
||||
|
||||
_parentBridge = bridge;
|
||||
|
||||
/**
|
||||
* Set Initial State
|
||||
*/
|
||||
_valid = YES;
|
||||
_loading = YES;
|
||||
_modules = [[NSMutableArray alloc] init];
|
||||
_frameUpdateObservers = [[NSMutableSet alloc] init];
|
||||
_scheduledCalls = [[NSMutableArray alloc] init];
|
||||
_scheduledCallbacks = [[RCTSparseArray alloc] init];
|
||||
_jsDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_jsThreadUpdate:)];
|
||||
|
||||
if (RCT_DEV) {
|
||||
_mainDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_mainThreadUpdate:)];
|
||||
[_mainDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize and register bridge modules *before* adding the display link
|
||||
* so we don't have threading issues
|
||||
*/
|
||||
[self registerModules];
|
||||
|
||||
/**
|
||||
* Start the application script
|
||||
*/
|
||||
[self initJS];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL
|
||||
moduleProvider:(__unused RCTBridgeModuleProviderBlock)block
|
||||
launchOptions:(__unused NSDictionary *)launchOptions)
|
||||
|
||||
- (void)setUp {}
|
||||
- (void)bindKeys {}
|
||||
|
||||
- (void)reload
|
||||
{
|
||||
[_parentBridge reload];
|
||||
}
|
||||
|
||||
- (Class)executorClass
|
||||
{
|
||||
return _parentBridge.executorClass ?: [RCTContextExecutor class];
|
||||
}
|
||||
|
||||
- (void)setExecutorClass:(Class)executorClass
|
||||
{
|
||||
RCTAssertMainThread();
|
||||
|
||||
_parentBridge.executorClass = executorClass;
|
||||
}
|
||||
|
||||
- (BOOL)isLoading
|
||||
{
|
||||
return _loading;
|
||||
}
|
||||
|
||||
- (BOOL)isValid
|
||||
{
|
||||
return _valid;
|
||||
}
|
||||
|
||||
- (void)registerModules
|
||||
{
|
||||
RCTAssertMainThread();
|
||||
|
||||
// Register passed-in module instances
|
||||
NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
|
||||
for (id<RCTBridgeModule> module in self.moduleProvider ? self.moduleProvider() : nil) {
|
||||
preregisteredModules[RCTBridgeModuleNameForClass([module class])] = module;
|
||||
}
|
||||
|
||||
// Instantiate modules
|
||||
_modules = [[NSMutableArray alloc] init];
|
||||
NSMutableDictionary *modulesByName = [preregisteredModules mutableCopy];
|
||||
for (Class moduleClass in RCTGetModuleClasses()) {
|
||||
NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
|
||||
|
||||
// Check if module instance has already been registered for this name
|
||||
id<RCTBridgeModule> module = modulesByName[moduleName];
|
||||
|
||||
if (module) {
|
||||
// Preregistered instances takes precedence, no questions asked
|
||||
if (!preregisteredModules[moduleName]) {
|
||||
// It's OK to have a name collision as long as the second instance is nil
|
||||
RCTAssert([[moduleClass alloc] init] == nil,
|
||||
@"Attempted to register RCTBridgeModule class %@ for the name "
|
||||
"'%@', but name was already registered by class %@", moduleClass,
|
||||
moduleName, [modulesByName[moduleName] class]);
|
||||
}
|
||||
if ([module class] != moduleClass) {
|
||||
RCTLogInfo(@"RCTBridgeModule of class %@ with name '%@' was encountered "
|
||||
"in the project, but name was already registered by class %@."
|
||||
"That's fine if it's intentional - just letting you know.",
|
||||
moduleClass, moduleName, [modulesByName[moduleName] class]);
|
||||
}
|
||||
} else {
|
||||
// Module name hasn't been used before, so go ahead and instantiate
|
||||
module = [[moduleClass alloc] init];
|
||||
}
|
||||
if (module) {
|
||||
modulesByName[moduleName] = module;
|
||||
}
|
||||
}
|
||||
|
||||
// Store modules
|
||||
_modulesByName = [modulesByName copy];
|
||||
|
||||
/**
|
||||
* The executor is a bridge module, wait for it to be created and set it before
|
||||
* any other module has access to the bridge
|
||||
*/
|
||||
_javaScriptExecutor = _modulesByName[RCTBridgeModuleNameForClass(self.executorClass)];
|
||||
RCTLatestExecutor = _javaScriptExecutor;
|
||||
RCTSetExecutorID(_javaScriptExecutor);
|
||||
|
||||
[_javaScriptExecutor setUp];
|
||||
|
||||
// Set bridge
|
||||
for (id<RCTBridgeModule> module in _modulesByName.allValues) {
|
||||
if ([module respondsToSelector:@selector(setBridge:)]) {
|
||||
module.bridge = self;
|
||||
}
|
||||
|
||||
RCTModuleData *moduleData = [[RCTModuleData alloc] initWithExecutor:_javaScriptExecutor
|
||||
uid:@(_modules.count)
|
||||
instance:module];
|
||||
[_modules addObject:moduleData];
|
||||
|
||||
if ([module conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) {
|
||||
[_frameUpdateObservers addObject:moduleData];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)initJS
|
||||
{
|
||||
RCTAssertMainThread();
|
||||
|
||||
// Inject module data into JS context
|
||||
NSMutableDictionary *config = [[NSMutableDictionary alloc] init];
|
||||
for (RCTModuleData *moduleData in _modules) {
|
||||
config[moduleData.name] = moduleData.config;
|
||||
}
|
||||
NSString *configJSON = RCTJSONStringify(@{
|
||||
@"remoteModuleConfig": config,
|
||||
}, NULL);
|
||||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||||
[_javaScriptExecutor injectJSONText:configJSON
|
||||
asGlobalObjectNamed:@"__fbBatchedBridgeConfig" callback:
|
||||
^(__unused id err) {
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
}];
|
||||
|
||||
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW);
|
||||
|
||||
NSURL *bundleURL = _parentBridge.bundleURL;
|
||||
if (_javaScriptExecutor == nil) {
|
||||
|
||||
/**
|
||||
* HACK (tadeu): If it failed to connect to the debugger, set loading to NO
|
||||
* so we can attempt to reload again.
|
||||
*/
|
||||
_loading = NO;
|
||||
|
||||
} else if (!bundleURL) {
|
||||
|
||||
// Allow testing without a script
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
_loading = NO;
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
|
||||
object:_parentBridge
|
||||
userInfo:@{ @"bridge": self }];
|
||||
});
|
||||
} else {
|
||||
|
||||
RCTProfileBeginEvent();
|
||||
RCTPerformanceLoggerStart(RCTPLScriptDownload);
|
||||
RCTJavaScriptLoader *loader = [[RCTJavaScriptLoader alloc] initWithBridge:self];
|
||||
[loader loadBundleAtURL:bundleURL onComplete:^(NSError *error, NSString *script) {
|
||||
RCTPerformanceLoggerEnd(RCTPLScriptDownload);
|
||||
RCTProfileEndEvent(@"JavaScript dowload", @"init,download", @[]);
|
||||
|
||||
_loading = NO;
|
||||
if (!self.isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
[[RCTRedBox sharedInstance] dismiss];
|
||||
|
||||
RCTSourceCode *sourceCodeModule = self.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
|
||||
sourceCodeModule.scriptURL = bundleURL;
|
||||
sourceCodeModule.scriptText = script;
|
||||
if (error) {
|
||||
|
||||
NSArray *stack = [error userInfo][@"stack"];
|
||||
if (stack) {
|
||||
[[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription]
|
||||
withStack:stack];
|
||||
} else {
|
||||
[[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription]
|
||||
withDetails:[error localizedFailureReason]];
|
||||
}
|
||||
|
||||
NSDictionary *userInfo = @{@"bridge": self, @"error": error};
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidFailToLoadNotification
|
||||
object:_parentBridge
|
||||
userInfo:userInfo];
|
||||
|
||||
} else {
|
||||
|
||||
[self enqueueApplicationScript:script url:bundleURL onComplete:^(NSError *loadError) {
|
||||
|
||||
if (!loadError) {
|
||||
|
||||
/**
|
||||
* Register the display link to start sending js calls after everything
|
||||
* is setup
|
||||
*/
|
||||
NSRunLoop *targetRunLoop = [_javaScriptExecutor isKindOfClass:[RCTContextExecutor class]] ? [NSRunLoop currentRunLoop] : [NSRunLoop mainRunLoop];
|
||||
[_jsDisplayLink addToRunLoop:targetRunLoop forMode:NSRunLoopCommonModes];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
|
||||
object:_parentBridge
|
||||
userInfo:@{ @"bridge": self }];
|
||||
} else {
|
||||
[[RCTRedBox sharedInstance] showErrorMessage:[loadError localizedDescription]
|
||||
withDetails:[loadError localizedFailureReason]];
|
||||
}
|
||||
}];
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSDictionary *)modules
|
||||
{
|
||||
RCTAssert(!self.isValid || _modulesByName != nil, @"Bridge modules have not yet been initialized. "
|
||||
"You may be trying to access a module too early in the startup procedure.");
|
||||
|
||||
return _modulesByName;
|
||||
}
|
||||
|
||||
#pragma mark - RCTInvalidating
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
if (!self.isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
RCTAssertMainThread();
|
||||
|
||||
_valid = NO;
|
||||
if (RCTLatestExecutor == _javaScriptExecutor) {
|
||||
RCTLatestExecutor = nil;
|
||||
}
|
||||
|
||||
void (^mainThreadInvalidate)(void) = ^{
|
||||
RCTAssertMainThread();
|
||||
|
||||
[_mainDisplayLink invalidate];
|
||||
_mainDisplayLink = nil;
|
||||
|
||||
// Invalidate modules
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
for (RCTModuleData *moduleData in _modules) {
|
||||
if ([moduleData.instance respondsToSelector:@selector(invalidate)]) {
|
||||
[moduleData dispatchBlock:^{
|
||||
[(id<RCTInvalidating>)moduleData.instance invalidate];
|
||||
} dispatchGroup:group];
|
||||
}
|
||||
moduleData.queue = nil;
|
||||
}
|
||||
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
|
||||
_modules = nil;
|
||||
_modulesByName = nil;
|
||||
_frameUpdateObservers = nil;
|
||||
});
|
||||
};
|
||||
|
||||
if (!_javaScriptExecutor) {
|
||||
|
||||
// No JS thread running
|
||||
mainThreadInvalidate();
|
||||
return;
|
||||
}
|
||||
|
||||
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
|
||||
|
||||
/**
|
||||
* JS Thread deallocations
|
||||
*/
|
||||
[_javaScriptExecutor invalidate];
|
||||
_javaScriptExecutor = nil;
|
||||
|
||||
[_jsDisplayLink invalidate];
|
||||
_jsDisplayLink = nil;
|
||||
|
||||
/**
|
||||
* Main Thread deallocations
|
||||
*/
|
||||
dispatch_async(dispatch_get_main_queue(), mainThreadInvalidate);
|
||||
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - RCTBridge methods
|
||||
|
||||
/**
|
||||
* Public. Can be invoked from any thread.
|
||||
*/
|
||||
- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args
|
||||
{
|
||||
NSArray *ids = [moduleDotMethod componentsSeparatedByString:@"."];
|
||||
|
||||
[self _invokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"callFunctionReturnFlushedQueue"
|
||||
arguments:@[ids[0], ids[1], args ?: @[]]
|
||||
context:RCTGetExecutorID(_javaScriptExecutor)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Private hack to support `setTimeout(fn, 0)`
|
||||
*/
|
||||
- (void)_immediatelyCallTimer:(NSNumber *)timer
|
||||
{
|
||||
RCTAssertJSThread();
|
||||
|
||||
dispatch_block_t block = ^{
|
||||
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"callFunctionReturnFlushedQueue"
|
||||
arguments:@[@"JSTimersExecution", @"callTimers", @[@[timer]]]
|
||||
context:RCTGetExecutorID(_javaScriptExecutor)];
|
||||
};
|
||||
|
||||
if ([_javaScriptExecutor respondsToSelector:@selector(executeAsyncBlockOnJavaScriptQueue:)]) {
|
||||
[_javaScriptExecutor executeAsyncBlockOnJavaScriptQueue:block];
|
||||
} else {
|
||||
[_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete
|
||||
{
|
||||
RCTAssert(onComplete != nil, @"onComplete block passed in should be non-nil");
|
||||
|
||||
RCTProfileBeginFlowEvent();
|
||||
[_javaScriptExecutor executeApplicationScript:script sourceURL:url onComplete:^(NSError *scriptLoadError) {
|
||||
RCTProfileEndFlowEvent();
|
||||
RCTAssertJSThread();
|
||||
|
||||
if (scriptLoadError) {
|
||||
onComplete(scriptLoadError);
|
||||
return;
|
||||
}
|
||||
|
||||
RCTProfileBeginEvent();
|
||||
NSNumber *context = RCTGetExecutorID(_javaScriptExecutor);
|
||||
[_javaScriptExecutor executeJSCall:@"BatchedBridge"
|
||||
method:@"flushedQueue"
|
||||
arguments:@[]
|
||||
context:context
|
||||
callback:^(id json, NSError *error) {
|
||||
RCTProfileEndEvent(@"FetchApplicationScriptCallbacks", @"js_call,init", @{
|
||||
@"json": RCTNullIfNil(json),
|
||||
@"error": RCTNullIfNil(error),
|
||||
});
|
||||
|
||||
[self _handleBuffer:json context:context];
|
||||
|
||||
onComplete(error);
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Payload Generation
|
||||
|
||||
/**
|
||||
* TODO: Completely remove `context` - no longer needed
|
||||
*/
|
||||
- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args
|
||||
{
|
||||
[self _invokeAndProcessModule:module
|
||||
method:method
|
||||
arguments:args
|
||||
context:RCTGetExecutorID(_javaScriptExecutor)];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 context:(NSNumber *)context
|
||||
{
|
||||
/**
|
||||
* AnyThread
|
||||
*/
|
||||
|
||||
RCTProfileBeginFlowEvent();
|
||||
|
||||
__weak RCTBatchedBridge *weakSelf = self;
|
||||
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
|
||||
RCTProfileEndFlowEvent();
|
||||
RCTProfileBeginEvent();
|
||||
|
||||
RCTBatchedBridge *strongSelf = weakSelf;
|
||||
if (!strongSelf.isValid || !strongSelf->_scheduledCallbacks || !strongSelf->_scheduledCalls) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
RCT_IF_DEV(NSNumber *callID = _RCTProfileBeginFlowEvent();)
|
||||
id call = @{
|
||||
@"js_args": @{
|
||||
@"module": module,
|
||||
@"method": method,
|
||||
@"args": args,
|
||||
},
|
||||
@"context": context ?: @0,
|
||||
RCT_IF_DEV(@"call_id": callID,)
|
||||
};
|
||||
if ([method isEqualToString:@"invokeCallbackAndReturnFlushedQueue"]) {
|
||||
strongSelf->_scheduledCallbacks[args[0]] = call;
|
||||
} else {
|
||||
[strongSelf->_scheduledCalls addObject:call];
|
||||
}
|
||||
|
||||
RCTProfileEndEvent(@"enqueue_call", @"objc_call", call);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)_actuallyInvokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context
|
||||
{
|
||||
RCTAssertJSThread();
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTEnqueueNotification object:nil userInfo:nil];
|
||||
|
||||
RCTJavaScriptCallback processResponse = ^(id json, __unused NSError *error) {
|
||||
if (!self.isValid) {
|
||||
return;
|
||||
}
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTDequeueNotification object:nil userInfo:nil];
|
||||
[self _handleBuffer:json context:context];
|
||||
};
|
||||
|
||||
[_javaScriptExecutor executeJSCall:module
|
||||
method:method
|
||||
arguments:args
|
||||
context:context
|
||||
callback:processResponse];
|
||||
}
|
||||
|
||||
#pragma mark - Payload Processing
|
||||
|
||||
- (void)_handleBuffer:(id)buffer context:(NSNumber *)context
|
||||
{
|
||||
RCTAssertJSThread();
|
||||
|
||||
if (buffer == nil || buffer == (id)kCFNull) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSArray *requestsArray = [RCTConvert NSArray:buffer];
|
||||
|
||||
#if RCT_DEBUG
|
||||
|
||||
if (![buffer isKindOfClass:[NSArray class]]) {
|
||||
RCTLogError(@"Buffer must be an instance of NSArray, got %@", NSStringFromClass([buffer class]));
|
||||
return;
|
||||
}
|
||||
|
||||
for (NSUInteger fieldIndex = RCTBridgeFieldRequestModuleIDs; fieldIndex <= RCTBridgeFieldParamss; fieldIndex++) {
|
||||
id field = [requestsArray objectAtIndex:fieldIndex];
|
||||
if (![field isKindOfClass:[NSArray class]]) {
|
||||
RCTLogError(@"Field at index %zd in buffer must be an instance of NSArray, got %@", fieldIndex, NSStringFromClass([field class]));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
NSArray *moduleIDs = requestsArray[RCTBridgeFieldRequestModuleIDs];
|
||||
NSArray *methodIDs = requestsArray[RCTBridgeFieldMethodIDs];
|
||||
NSArray *paramsArrays = requestsArray[RCTBridgeFieldParamss];
|
||||
|
||||
NSUInteger numRequests = [moduleIDs count];
|
||||
|
||||
if (RCT_DEBUG && (numRequests != methodIDs.count || numRequests != paramsArrays.count)) {
|
||||
RCTLogError(@"Invalid data message - all must be length: %zd", numRequests);
|
||||
return;
|
||||
}
|
||||
|
||||
NSMapTable *buckets = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsStrongMemory capacity:_modules.count];
|
||||
for (NSUInteger i = 0; i < numRequests; i++) {
|
||||
RCTModuleData *moduleData = _modules[[moduleIDs[i] integerValue]];
|
||||
NSMutableOrderedSet *set = [buckets objectForKey:moduleData];
|
||||
if (!set) {
|
||||
set = [[NSMutableOrderedSet alloc] init];
|
||||
[buckets setObject:set forKey:moduleData];
|
||||
}
|
||||
[set addObject:@(i)];
|
||||
}
|
||||
|
||||
for (RCTModuleData *moduleData in buckets) {
|
||||
RCTProfileBeginFlowEvent();
|
||||
|
||||
[moduleData dispatchBlock:^{
|
||||
RCTProfileEndFlowEvent();
|
||||
RCTProfileBeginEvent();
|
||||
|
||||
NSOrderedSet *calls = [buckets objectForKey:moduleData];
|
||||
@autoreleasepool {
|
||||
for (NSNumber *indexObj in calls) {
|
||||
NSUInteger index = indexObj.unsignedIntegerValue;
|
||||
[self _handleRequestNumber:index
|
||||
moduleID:[moduleIDs[index] integerValue]
|
||||
methodID:[methodIDs[index] integerValue]
|
||||
params:paramsArrays[index]
|
||||
context:context];
|
||||
}
|
||||
}
|
||||
RCTProfileEndEvent(RCTCurrentThreadName(), @"objc_call,dispatch_async", @{ @"calls": @(calls.count) });
|
||||
}];
|
||||
}
|
||||
|
||||
// TODO: batchDidComplete is only used by RCTUIManager - can we eliminate this special case?
|
||||
for (RCTModuleData *moduleData in _modules) {
|
||||
if ([moduleData.instance respondsToSelector:@selector(batchDidComplete)]) {
|
||||
[moduleData dispatchBlock:^{
|
||||
[moduleData.instance batchDidComplete];
|
||||
}];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)_handleRequestNumber:(NSUInteger)i
|
||||
moduleID:(NSUInteger)moduleID
|
||||
methodID:(NSUInteger)methodID
|
||||
params:(NSArray *)params
|
||||
context:(NSNumber *)context
|
||||
{
|
||||
if (!self.isValid) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (RCT_DEBUG && ![params isKindOfClass:[NSArray class]]) {
|
||||
RCTLogError(@"Invalid module/method/params tuple for request #%zd", i);
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
||||
RCTProfileBeginEvent();
|
||||
|
||||
RCTModuleData *moduleData = _modules[moduleID];
|
||||
if (RCT_DEBUG && !moduleData) {
|
||||
RCTLogError(@"No module found for id '%zd'", moduleID);
|
||||
return NO;
|
||||
}
|
||||
|
||||
RCTModuleMethod *method = moduleData.methods[methodID];
|
||||
if (RCT_DEBUG && !method) {
|
||||
RCTLogError(@"Unknown methodID: %zd for module: %zd (%@)", methodID, moduleID, moduleData.name);
|
||||
return NO;
|
||||
}
|
||||
|
||||
@try {
|
||||
[method invokeWithBridge:self module:moduleData.instance arguments:params context:context];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
RCTLogError(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, moduleData.name, params, exception);
|
||||
if (!RCT_DEBUG && [exception.name rangeOfString:@"Unhandled JS Exception"].location != NSNotFound) {
|
||||
@throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
RCTProfileEndEvent(@"Invoke callback", @"objc_call", @{
|
||||
@"module": method.moduleClassName,
|
||||
@"method": method.JSMethodName,
|
||||
@"selector": NSStringFromSelector(method.selector),
|
||||
@"args": RCTJSONStringify(RCTNullIfNil(params), NULL),
|
||||
});
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)_jsThreadUpdate:(CADisplayLink *)displayLink
|
||||
{
|
||||
RCTAssertJSThread();
|
||||
RCTProfileBeginEvent();
|
||||
|
||||
RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink];
|
||||
for (RCTModuleData *moduleData in _frameUpdateObservers) {
|
||||
id<RCTFrameUpdateObserver> observer = (id<RCTFrameUpdateObserver>)moduleData.instance;
|
||||
if (![observer respondsToSelector:@selector(isPaused)] || ![observer isPaused]) {
|
||||
RCT_IF_DEV(NSString *name = [NSString stringWithFormat:@"[%@ didUpdateFrame:%f]", observer, displayLink.timestamp];)
|
||||
RCTProfileBeginFlowEvent();
|
||||
|
||||
[moduleData dispatchBlock:^{
|
||||
RCTProfileEndFlowEvent();
|
||||
RCTProfileBeginEvent();
|
||||
[observer didUpdateFrame:frameUpdate];
|
||||
RCTProfileEndEvent(name, @"objc_call,fps", nil);
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
NSArray *calls = [_scheduledCallbacks.allObjects arrayByAddingObjectsFromArray:_scheduledCalls];
|
||||
NSNumber *currentExecutorID = RCTGetExecutorID(_javaScriptExecutor);
|
||||
calls = [calls filteredArrayUsingPredicate:
|
||||
[NSPredicate predicateWithBlock:
|
||||
^BOOL(NSDictionary *call, __unused NSDictionary *bindings) {
|
||||
return [call[@"context"] isEqualToNumber:currentExecutorID];
|
||||
}]];
|
||||
|
||||
RCT_IF_DEV(
|
||||
RCTProfileImmediateEvent(@"JS Thread Tick", displayLink.timestamp, @"g");
|
||||
|
||||
for (NSDictionary *call in calls) {
|
||||
_RCTProfileEndFlowEvent(call[@"call_id"]);
|
||||
}
|
||||
)
|
||||
|
||||
if (calls.count > 0) {
|
||||
_scheduledCalls = [[NSMutableArray alloc] init];
|
||||
_scheduledCallbacks = [[RCTSparseArray alloc] init];
|
||||
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"processBatch"
|
||||
arguments:@[[calls valueForKey:@"js_args"]]
|
||||
context:RCTGetExecutorID(_javaScriptExecutor)];
|
||||
}
|
||||
|
||||
RCTProfileEndEvent(@"DispatchFrameUpdate", @"objc_call", nil);
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.perfStats.jsGraph tick:displayLink.timestamp];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)_mainThreadUpdate:(CADisplayLink *)displayLink
|
||||
{
|
||||
RCTAssertMainThread();
|
||||
|
||||
RCTProfileImmediateEvent(@"VSYNC", displayLink.timestamp, @"g");
|
||||
|
||||
[self.perfStats.uiGraph tick:displayLink.timestamp];
|
||||
}
|
||||
|
||||
- (void)startProfiling
|
||||
{
|
||||
RCTAssertMainThread();
|
||||
|
||||
if (![_parentBridge.bundleURL.scheme isEqualToString:@"http"]) {
|
||||
RCTLogError(@"To run the profiler you must be running from the dev server");
|
||||
return;
|
||||
}
|
||||
|
||||
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
|
||||
RCTProfileInit(self);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)stopProfiling
|
||||
{
|
||||
RCTAssertMainThread();
|
||||
|
||||
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
|
||||
NSString *log = RCTProfileEnd(self);
|
||||
NSURL *bundleURL = _parentBridge.bundleURL;
|
||||
NSString *URLString = [NSString stringWithFormat:@"%@://%@:%@/profile", bundleURL.scheme, bundleURL.host, bundleURL.port];
|
||||
NSURL *URL = [NSURL URLWithString:URLString];
|
||||
NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:URL];
|
||||
URLRequest.HTTPMethod = @"POST";
|
||||
[URLRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
||||
NSURLSessionTask *task =
|
||||
[[NSURLSession sharedSession] uploadTaskWithRequest:URLRequest
|
||||
fromData:[log dataUsingEncoding:NSUTF8StringEncoding]
|
||||
completionHandler:
|
||||
^(__unused NSData *data, __unused NSURLResponse *response, NSError *error) {
|
||||
if (error) {
|
||||
RCTLogError(@"%@", error.localizedDescription);
|
||||
} else {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[[[UIAlertView alloc] initWithTitle:@"Profile"
|
||||
message:@"The profile has been generated, check the dev server log for instructions."
|
||||
delegate:nil
|
||||
cancelButtonTitle:@"OK"
|
||||
otherButtonTitles:nil] show];
|
||||
});
|
||||
}
|
||||
}];
|
||||
|
||||
[task resume];
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
|
@ -118,6 +118,11 @@ RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass);
|
|||
*/
|
||||
@property (nonatomic, readonly, getter=isLoading) BOOL loading;
|
||||
|
||||
/**
|
||||
* The block passed in the constructor with pre-initialized modules
|
||||
*/
|
||||
@property (nonatomic, copy, readonly) RCTBridgeModuleProviderBlock moduleProvider;
|
||||
|
||||
/**
|
||||
* Reload the bundle and reset executor & modules. Safe to call from any thread.
|
||||
*/
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -78,7 +78,7 @@ extern dispatch_queue_t RCTJSThread;
|
|||
* and the bridge will populate the methodQueue property for you automatically
|
||||
* when it initializes the module.
|
||||
*/
|
||||
@property (nonatomic, weak, readonly) dispatch_queue_t methodQueue;
|
||||
@property (nonatomic, strong, readonly) dispatch_queue_t methodQueue;
|
||||
|
||||
/**
|
||||
* Place this macro in your class implementation to automatically register
|
||||
|
|
|
@ -635,17 +635,24 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
|
|||
return nil;
|
||||
}
|
||||
|
||||
if (RCT_DEBUG && ![json isKindOfClass:[NSString class]]) {
|
||||
if (RCT_DEBUG && ![json isKindOfClass:[NSString class]] && ![json isKindOfClass:[NSDictionary class]]) {
|
||||
RCTLogConvertError(json, "an image");
|
||||
return nil;
|
||||
}
|
||||
|
||||
if ([json length] == 0) {
|
||||
return nil;
|
||||
UIImage *image;
|
||||
NSString *path;
|
||||
CGFloat scale = 0.0;
|
||||
if ([json isKindOfClass:[NSString class]]) {
|
||||
if ([json length] == 0) {
|
||||
return nil;
|
||||
}
|
||||
path = json;
|
||||
} else {
|
||||
path = [self NSString:json[@"uri"]];
|
||||
scale = [self CGFloat:json[@"scale"]];
|
||||
}
|
||||
|
||||
UIImage *image = nil;
|
||||
NSString *path = json;
|
||||
if ([path hasPrefix:@"data:"]) {
|
||||
NSURL *url = [NSURL URLWithString:path];
|
||||
NSData *imageData = [NSData dataWithContentsOfURL:url];
|
||||
|
@ -658,6 +665,11 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
|
|||
image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:path ofType:nil]];
|
||||
}
|
||||
}
|
||||
|
||||
if (scale > 0) {
|
||||
image = [UIImage imageWithCGImage:image.CGImage scale:scale orientation:image.imageOrientation];
|
||||
}
|
||||
|
||||
// NOTE: we don't warn about nil images because there are legitimate
|
||||
// case where we find out if a string is an image by using this method
|
||||
return image;
|
||||
|
|
|
@ -7,6 +7,10 @@
|
|||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class CADisplayLink;
|
||||
|
||||
/**
|
||||
* Interface containing the information about the last screen refresh.
|
||||
*/
|
||||
|
@ -22,6 +26,8 @@
|
|||
*/
|
||||
@property (nonatomic, readonly) NSTimeInterval deltaTime;
|
||||
|
||||
- (instancetype)initWithDisplayLink:(CADisplayLink *)displayLink NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import <QuartzCore/CADisplayLink.h>
|
||||
|
||||
#import "RCTFrameUpdate.h"
|
||||
|
||||
#import "RCTUtils.h"
|
||||
|
||||
@implementation RCTFrameUpdate
|
||||
|
||||
RCT_NOT_IMPLEMENTED(-init)
|
||||
|
||||
- (instancetype)initWithDisplayLink:(CADisplayLink *)displayLink
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_timestamp = displayLink.timestamp;
|
||||
_deltaTime = displayLink.duration;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "RCTJavaScriptExecutor.h"
|
||||
|
||||
@interface RCTModuleData : NSObject
|
||||
|
||||
@property (nonatomic, weak, readonly) id<RCTJavaScriptExecutor> javaScriptExecutor;
|
||||
@property (nonatomic, strong, readonly) NSNumber *uid;
|
||||
@property (nonatomic, strong, readonly) id<RCTBridgeModule> instance;
|
||||
|
||||
@property (nonatomic, strong, readonly) Class cls;
|
||||
@property (nonatomic, copy, readonly) NSString *name;
|
||||
@property (nonatomic, copy, readonly) NSArray *methods;
|
||||
@property (nonatomic, copy, readonly) NSDictionary *config;
|
||||
|
||||
@property (nonatomic, strong) dispatch_queue_t queue;
|
||||
|
||||
- (instancetype)initWithExecutor:(id<RCTJavaScriptExecutor>)javaScriptExecutor
|
||||
uid:(NSNumber *)uid
|
||||
instance:(id<RCTBridgeModule>)instance NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
|
||||
- (void)dispatchBlock:(dispatch_block_t)block;
|
||||
- (void)dispatchBlock:(dispatch_block_t)block dispatchGroup:(dispatch_group_t)group;
|
||||
|
||||
@end
|
|
@ -0,0 +1,146 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import "RCTModuleData.h"
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTModuleMethod.h"
|
||||
#import "RCTLog.h"
|
||||
|
||||
@implementation RCTModuleData
|
||||
|
||||
- (instancetype)initWithExecutor:(id<RCTJavaScriptExecutor>)javaScriptExecutor
|
||||
uid:(NSNumber *)uid
|
||||
instance:(id<RCTBridgeModule>)instance
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_javaScriptExecutor = javaScriptExecutor;
|
||||
_uid = uid;
|
||||
_instance = instance;
|
||||
_cls = [instance class];
|
||||
_name = RCTBridgeModuleNameForClass(_cls);
|
||||
|
||||
[self loadMethods];
|
||||
[self generateConfig];
|
||||
[self setQueue];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
RCT_NOT_IMPLEMENTED(-init);
|
||||
|
||||
- (void)loadMethods
|
||||
{
|
||||
NSMutableArray *moduleMethods = [[NSMutableArray alloc] init];
|
||||
unsigned int methodCount;
|
||||
Method *methods = class_copyMethodList(object_getClass(_cls), &methodCount);
|
||||
|
||||
for (unsigned int i = 0; i < methodCount; i++) {
|
||||
Method method = methods[i];
|
||||
SEL selector = method_getName(method);
|
||||
if ([NSStringFromSelector(selector) hasPrefix:@"__rct_export__"]) {
|
||||
IMP imp = method_getImplementation(method);
|
||||
NSArray *entries = ((NSArray *(*)(id, SEL))imp)(_cls, selector);
|
||||
RCTModuleMethod *moduleMethod =
|
||||
[[RCTModuleMethod alloc] initWithObjCMethodName:entries[1]
|
||||
JSMethodName:entries[0]
|
||||
moduleClass:_cls];
|
||||
|
||||
[moduleMethods addObject:moduleMethod];
|
||||
}
|
||||
}
|
||||
|
||||
free(methods);
|
||||
|
||||
_methods = [moduleMethods copy];
|
||||
}
|
||||
|
||||
- (void)generateConfig
|
||||
{
|
||||
NSMutableDictionary *config = [[NSMutableDictionary alloc] init];
|
||||
config[@"moduleID"] = _uid;
|
||||
config[@"methods"] = [[NSMutableDictionary alloc] init];
|
||||
|
||||
if ([_instance respondsToSelector:@selector(constantsToExport)]) {
|
||||
id consts = [_instance constantsToExport];
|
||||
if (consts) {
|
||||
config[@"constants"] = consts;
|
||||
}
|
||||
}
|
||||
|
||||
[_methods enumerateObjectsUsingBlock:^(RCTModuleMethod *method, NSUInteger idx, __unused BOOL *stop) {
|
||||
config[@"methods"][method.JSMethodName] = @{
|
||||
@"methodID": @(idx),
|
||||
@"type": method.functionKind == RCTJavaScriptFunctionKindAsync ? @"remoteAsync" : @"remote",
|
||||
};
|
||||
}];
|
||||
|
||||
_config = [config copy];
|
||||
}
|
||||
|
||||
- (void)setQueue
|
||||
{
|
||||
dispatch_queue_t queue = nil;
|
||||
BOOL implementsMethodQueue = [_instance respondsToSelector:@selector(methodQueue)];
|
||||
if (implementsMethodQueue) {
|
||||
queue = [_instance methodQueue];
|
||||
}
|
||||
if (!queue) {
|
||||
|
||||
// Need to cache queueNames because they aren't retained by dispatch_queue
|
||||
static NSMutableDictionary *queueNames;
|
||||
if (!queueNames) {
|
||||
queueNames = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
NSString *queueName = queueNames[_name];
|
||||
if (!queueName) {
|
||||
queueName = [NSString stringWithFormat:@"com.facebook.React.%@Queue", _name];
|
||||
queueNames[_name] = queueName;
|
||||
}
|
||||
|
||||
// Create new queue
|
||||
queue = dispatch_queue_create(queueName.UTF8String, DISPATCH_QUEUE_SERIAL);
|
||||
|
||||
// assign it to the module
|
||||
if (implementsMethodQueue) {
|
||||
@try {
|
||||
[(id)_instance setValue:queue forKey:@"methodQueue"];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
RCTLogError(@"%@ is returning nil for it's methodQueue, which is not "
|
||||
"permitted. You must either return a pre-initialized "
|
||||
"queue, or @synthesize the methodQueue to let the bridge "
|
||||
"create a queue for you.", _name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_queue = queue;
|
||||
}
|
||||
|
||||
- (void)dispatchBlock:(dispatch_block_t)block
|
||||
{
|
||||
[self dispatchBlock:block dispatchGroup:NULL];
|
||||
}
|
||||
|
||||
- (void)dispatchBlock:(dispatch_block_t)block
|
||||
dispatchGroup:(dispatch_group_t)group
|
||||
{
|
||||
if (_queue == RCTJSThread) {
|
||||
[_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
|
||||
} else if (_queue) {
|
||||
if (group != NULL) {
|
||||
dispatch_group_async(group, _queue, block);
|
||||
} else {
|
||||
dispatch_async(_queue, block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class RCTBridge;
|
||||
|
||||
typedef NS_ENUM(NSUInteger, RCTJavaScriptFunctionKind) {
|
||||
RCTJavaScriptFunctionKindNormal,
|
||||
RCTJavaScriptFunctionKindAsync,
|
||||
};
|
||||
|
||||
@interface RCTModuleMethod : NSObject
|
||||
|
||||
@property (nonatomic, copy, readonly) NSString *moduleClassName;
|
||||
@property (nonatomic, copy, readonly) NSString *JSMethodName;
|
||||
@property (nonatomic, assign, readonly) SEL selector;
|
||||
@property (nonatomic, assign, readonly) RCTJavaScriptFunctionKind functionKind;
|
||||
|
||||
- (instancetype)initWithObjCMethodName:(NSString *)objCMethodName
|
||||
JSMethodName:(NSString *)JSMethodName
|
||||
moduleClass:(Class)moduleClass NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (void)invokeWithBridge:(RCTBridge *)bridge
|
||||
module:(id)module
|
||||
arguments:(NSArray *)arguments
|
||||
context:(NSNumber *)context;
|
||||
|
||||
@end
|
|
@ -0,0 +1,288 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import "RCTModuleMethod.h"
|
||||
|
||||
#import <objc/message.h>
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
@interface RCTBridge (RCTModuleMethod)
|
||||
|
||||
- (void)_invokeAndProcessModule:(NSString *)module
|
||||
method:(NSString *)method
|
||||
arguments:(NSArray *)args;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTModuleMethod
|
||||
{
|
||||
Class _moduleClass;
|
||||
SEL _selector;
|
||||
NSMethodSignature *_methodSignature;
|
||||
NSArray *_argumentBlocks;
|
||||
}
|
||||
|
||||
- (instancetype)initWithObjCMethodName:(NSString *)objCMethodName
|
||||
JSMethodName:(NSString *)JSMethodName
|
||||
moduleClass:(Class)moduleClass
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
static NSRegularExpression *typeRegex;
|
||||
static NSRegularExpression *selectorRegex;
|
||||
if (!typeRegex) {
|
||||
NSString *unusedPattern = @"(?:(?:__unused|__attribute__\\(\\(unused\\)\\)))";
|
||||
NSString *constPattern = @"(?:const)";
|
||||
NSString *constUnusedPattern = [NSString stringWithFormat:@"(?:(?:%@|%@)\\s*)", unusedPattern, constPattern];
|
||||
NSString *pattern = [NSString stringWithFormat:@"\\(%1$@?(\\w+?)(?:\\s*\\*)?%1$@?\\)", constUnusedPattern];
|
||||
typeRegex = [[NSRegularExpression alloc] initWithPattern:pattern options:0 error:NULL];
|
||||
|
||||
selectorRegex = [[NSRegularExpression alloc] initWithPattern:@"(?<=:).*?(?=[a-zA-Z_]+:|$)" options:0 error:NULL];
|
||||
}
|
||||
|
||||
NSMutableArray *argumentNames = [NSMutableArray array];
|
||||
[typeRegex enumerateMatchesInString:objCMethodName options:0 range:NSMakeRange(0, objCMethodName.length) usingBlock:^(NSTextCheckingResult *result, __unused NSMatchingFlags flags, __unused BOOL *stop) {
|
||||
NSString *argumentName = [objCMethodName substringWithRange:[result rangeAtIndex:1]];
|
||||
[argumentNames addObject:argumentName];
|
||||
}];
|
||||
|
||||
// Remove the parameters' type and name
|
||||
objCMethodName = [selectorRegex stringByReplacingMatchesInString:objCMethodName
|
||||
options:0
|
||||
range:NSMakeRange(0, objCMethodName.length)
|
||||
withTemplate:@""];
|
||||
// Remove any spaces since `selector : (Type)name` is a valid syntax
|
||||
objCMethodName = [objCMethodName stringByReplacingOccurrencesOfString:@" " withString:@""];
|
||||
|
||||
_moduleClass = moduleClass;
|
||||
_moduleClassName = NSStringFromClass(_moduleClass);
|
||||
_selector = NSSelectorFromString(objCMethodName);
|
||||
_JSMethodName = JSMethodName.length > 0 ? JSMethodName : ({
|
||||
NSString *methodName = NSStringFromSelector(_selector);
|
||||
NSRange colonRange = [methodName rangeOfString:@":"];
|
||||
if (colonRange.length) {
|
||||
methodName = [methodName substringToIndex:colonRange.location];
|
||||
}
|
||||
methodName;
|
||||
});
|
||||
|
||||
|
||||
// Get method signature
|
||||
_methodSignature = [_moduleClass instanceMethodSignatureForSelector:_selector];
|
||||
|
||||
// Process arguments
|
||||
NSUInteger numberOfArguments = _methodSignature.numberOfArguments;
|
||||
NSMutableArray *argumentBlocks = [[NSMutableArray alloc] initWithCapacity:numberOfArguments - 2];
|
||||
|
||||
#define RCT_ARG_BLOCK(_logic) \
|
||||
[argumentBlocks addObject:^(__unused RCTBridge *bridge, __unused NSNumber *context, NSInvocation *invocation, NSUInteger index, id json) { \
|
||||
_logic \
|
||||
[invocation setArgument:&value atIndex:index]; \
|
||||
}]; \
|
||||
|
||||
void (^addBlockArgument)(void) = ^{
|
||||
RCT_ARG_BLOCK(
|
||||
|
||||
if (RCT_DEBUG && json && ![json isKindOfClass:[NSNumber class]]) {
|
||||
RCTLogError(@"Argument %tu (%@) of %@.%@ should be a number", index,
|
||||
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName);
|
||||
return;
|
||||
}
|
||||
|
||||
// Marked as autoreleasing, because NSInvocation doesn't retain arguments
|
||||
__autoreleasing id value = (json ? ^(NSArray *args) {
|
||||
[bridge _invokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"invokeCallbackAndReturnFlushedQueue"
|
||||
arguments:@[json, args]];
|
||||
} : ^(__unused NSArray *unused) {});
|
||||
)
|
||||
};
|
||||
|
||||
void (^defaultCase)(const char *) = ^(const char *argumentType) {
|
||||
static const char *blockType = @encode(typeof(^{}));
|
||||
if (!strcmp(argumentType, blockType)) {
|
||||
addBlockArgument();
|
||||
} else {
|
||||
RCT_ARG_BLOCK( id value = json; )
|
||||
}
|
||||
};
|
||||
|
||||
for (NSUInteger i = 2; i < numberOfArguments; i++) {
|
||||
const char *argumentType = [_methodSignature getArgumentTypeAtIndex:i];
|
||||
|
||||
NSString *argumentName = argumentNames[i - 2];
|
||||
SEL selector = NSSelectorFromString([argumentName stringByAppendingString:@":"]);
|
||||
if ([RCTConvert respondsToSelector:selector]) {
|
||||
switch (argumentType[0]) {
|
||||
|
||||
#define RCT_CONVERT_CASE(_value, _type) \
|
||||
case _value: { \
|
||||
_type (*convert)(id, SEL, id) = (typeof(convert))objc_msgSend; \
|
||||
RCT_ARG_BLOCK( _type value = convert([RCTConvert class], selector, json); ) \
|
||||
break; \
|
||||
}
|
||||
|
||||
RCT_CONVERT_CASE(':', SEL)
|
||||
RCT_CONVERT_CASE('*', const char *)
|
||||
RCT_CONVERT_CASE('c', char)
|
||||
RCT_CONVERT_CASE('C', unsigned char)
|
||||
RCT_CONVERT_CASE('s', short)
|
||||
RCT_CONVERT_CASE('S', unsigned short)
|
||||
RCT_CONVERT_CASE('i', int)
|
||||
RCT_CONVERT_CASE('I', unsigned int)
|
||||
RCT_CONVERT_CASE('l', long)
|
||||
RCT_CONVERT_CASE('L', unsigned long)
|
||||
RCT_CONVERT_CASE('q', long long)
|
||||
RCT_CONVERT_CASE('Q', unsigned long long)
|
||||
RCT_CONVERT_CASE('f', float)
|
||||
RCT_CONVERT_CASE('d', double)
|
||||
RCT_CONVERT_CASE('B', BOOL)
|
||||
RCT_CONVERT_CASE('@', id)
|
||||
RCT_CONVERT_CASE('^', void *)
|
||||
|
||||
case '{': {
|
||||
[argumentBlocks addObject:^(__unused RCTBridge *bridge, __unused NSNumber *context, NSInvocation *invocation, NSUInteger index, id json) {
|
||||
NSMethodSignature *methodSignature = [RCTConvert methodSignatureForSelector:selector];
|
||||
void *returnValue = malloc(methodSignature.methodReturnLength);
|
||||
NSInvocation *_invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
|
||||
[_invocation setTarget:[RCTConvert class]];
|
||||
[_invocation setSelector:selector];
|
||||
[_invocation setArgument:&json atIndex:2];
|
||||
[_invocation invoke];
|
||||
[_invocation getReturnValue:returnValue];
|
||||
|
||||
[invocation setArgument:returnValue atIndex:index];
|
||||
|
||||
free(returnValue);
|
||||
}];
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
defaultCase(argumentType);
|
||||
}
|
||||
} else if ([argumentName isEqualToString:@"RCTResponseSenderBlock"]) {
|
||||
addBlockArgument();
|
||||
} else if ([argumentName isEqualToString:@"RCTPromiseResolveBlock"]) {
|
||||
RCTAssert(i == numberOfArguments - 2,
|
||||
@"The RCTPromiseResolveBlock must be the second to last parameter in -[%@ %@]",
|
||||
_moduleClassName, objCMethodName);
|
||||
RCT_ARG_BLOCK(
|
||||
if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) {
|
||||
RCTLogError(@"Argument %tu (%@) of %@.%@ must be a promise resolver ID", index,
|
||||
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName);
|
||||
return;
|
||||
}
|
||||
|
||||
// Marked as autoreleasing, because NSInvocation doesn't retain arguments
|
||||
__autoreleasing RCTPromiseResolveBlock value = (^(id result) {
|
||||
NSArray *arguments = result ? @[result] : @[];
|
||||
[bridge _invokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"invokeCallbackAndReturnFlushedQueue"
|
||||
arguments:@[json, arguments]];
|
||||
});
|
||||
)
|
||||
_functionKind = RCTJavaScriptFunctionKindAsync;
|
||||
} else if ([argumentName isEqualToString:@"RCTPromiseRejectBlock"]) {
|
||||
RCTAssert(i == numberOfArguments - 1,
|
||||
@"The RCTPromiseRejectBlock must be the last parameter in -[%@ %@]",
|
||||
_moduleClassName, objCMethodName);
|
||||
RCT_ARG_BLOCK(
|
||||
if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) {
|
||||
RCTLogError(@"Argument %tu (%@) of %@.%@ must be a promise rejecter ID", index,
|
||||
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName);
|
||||
return;
|
||||
}
|
||||
|
||||
// Marked as autoreleasing, because NSInvocation doesn't retain arguments
|
||||
__autoreleasing RCTPromiseRejectBlock value = (^(NSError *error) {
|
||||
NSDictionary *errorJSON = RCTJSErrorFromNSError(error);
|
||||
[bridge _invokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"invokeCallbackAndReturnFlushedQueue"
|
||||
arguments:@[json, @[errorJSON]]];
|
||||
});
|
||||
)
|
||||
_functionKind = RCTJavaScriptFunctionKindAsync;
|
||||
} else {
|
||||
|
||||
// Unknown argument type
|
||||
RCTLogError(@"Unknown argument type '%@' in method %@. Extend RCTConvert"
|
||||
" to support this type.", argumentName, [self methodName]);
|
||||
}
|
||||
}
|
||||
|
||||
_argumentBlocks = [argumentBlocks copy];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)invokeWithBridge:(RCTBridge *)bridge
|
||||
module:(id)module
|
||||
arguments:(NSArray *)arguments
|
||||
context:(NSNumber *)context
|
||||
{
|
||||
if (RCT_DEBUG) {
|
||||
|
||||
// Sanity check
|
||||
RCTAssert([module class] == _moduleClass, @"Attempted to invoke method \
|
||||
%@ on a module of class %@", [self methodName], [module class]);
|
||||
|
||||
// Safety check
|
||||
if (arguments.count != _argumentBlocks.count) {
|
||||
NSInteger actualCount = arguments.count;
|
||||
NSInteger expectedCount = _argumentBlocks.count;
|
||||
|
||||
// Subtract the implicit Promise resolver and rejecter functions for implementations of async functions
|
||||
if (_functionKind == RCTJavaScriptFunctionKindAsync) {
|
||||
actualCount -= 2;
|
||||
expectedCount -= 2;
|
||||
}
|
||||
|
||||
RCTLogError(@"%@.%@ was called with %zd arguments, but expects %zd",
|
||||
RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName,
|
||||
actualCount, expectedCount);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Create invocation (we can't re-use this as it wouldn't be thread-safe)
|
||||
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:_methodSignature];
|
||||
[invocation setArgument:&_selector atIndex:1];
|
||||
[invocation retainArguments];
|
||||
|
||||
// Set arguments
|
||||
NSUInteger index = 0;
|
||||
for (id json in arguments) {
|
||||
id arg = RCTNilIfNull(json);
|
||||
void (^block)(RCTBridge *, NSNumber *, NSInvocation *, NSUInteger, id) = _argumentBlocks[index];
|
||||
block(bridge, context, invocation, index + 2, arg);
|
||||
index++;
|
||||
}
|
||||
|
||||
// Invoke method
|
||||
[invocation invokeWithTarget:module];
|
||||
}
|
||||
|
||||
- (NSString *)methodName
|
||||
{
|
||||
return [NSString stringWithFormat:@"-[%@ %@]", _moduleClass,
|
||||
NSStringFromSelector(_selector)];
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<%@: %p; exports %@ as %@();>",
|
||||
[self class], self, [self methodName], _JSMethodName];
|
||||
}
|
||||
|
||||
@end
|
|
@ -18,6 +18,7 @@
|
|||
#import "RCTAssert.h"
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTDefines.h"
|
||||
#import "RCTModuleData.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
NSString *const RCTProfileDidStartProfiling = @"RCTProfileDidStartProfiling";
|
||||
|
@ -98,12 +99,6 @@ NSDictionary *RCTProfileGetMemoryUsage(void)
|
|||
|
||||
#pragma mark - Module hooks
|
||||
|
||||
@interface RCTBridge (Private)
|
||||
|
||||
- (void)dispatchBlock:(dispatch_block_t)block forModule:(id<RCTBridgeModule>)module;
|
||||
|
||||
@end
|
||||
|
||||
static const char *RCTProfileProxyClassName(Class);
|
||||
static const char *RCTProfileProxyClassName(Class class)
|
||||
{
|
||||
|
@ -152,9 +147,9 @@ static IMP RCTProfileMsgForward(NSObject *self, SEL selector)
|
|||
static void RCTProfileHookModules(RCTBridge *);
|
||||
static void RCTProfileHookModules(RCTBridge *bridge)
|
||||
{
|
||||
for (id<RCTBridgeModule> module in bridge.modules.allValues) {
|
||||
[bridge dispatchBlock:^{
|
||||
Class moduleClass = object_getClass(module);
|
||||
for (RCTModuleData *moduleData in [bridge valueForKey:@"_modules"]) {
|
||||
[moduleData dispatchBlock:^{
|
||||
Class moduleClass = moduleData.cls;
|
||||
Class proxyClass = objc_allocateClassPair(moduleClass, RCTProfileProxyClassName(moduleClass), 0);
|
||||
|
||||
unsigned int methodCount;
|
||||
|
@ -167,7 +162,7 @@ static void RCTProfileHookModules(RCTBridge *bridge)
|
|||
}
|
||||
IMP originalIMP = method_getImplementation(method);
|
||||
const char *returnType = method_getTypeEncoding(method);
|
||||
class_addMethod(proxyClass, selector, RCTProfileMsgForward(module, selector), returnType);
|
||||
class_addMethod(proxyClass, selector, RCTProfileMsgForward(moduleData.instance, selector), returnType);
|
||||
class_addMethod(proxyClass, RCTProfileProxySelector(selector), originalIMP, returnType);
|
||||
}
|
||||
free(methods);
|
||||
|
@ -185,24 +180,24 @@ static void RCTProfileHookModules(RCTBridge *bridge)
|
|||
}
|
||||
|
||||
objc_registerClassPair(proxyClass);
|
||||
object_setClass(module, proxyClass);
|
||||
} forModule:module];
|
||||
object_setClass(moduleData.instance, proxyClass);
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
void RCTProfileUnhookModules(RCTBridge *);
|
||||
void RCTProfileUnhookModules(RCTBridge *bridge)
|
||||
{
|
||||
for (id<RCTBridgeModule> module in bridge.modules.allValues) {
|
||||
[bridge dispatchBlock:^{
|
||||
RCTProfileLock(
|
||||
Class proxyClass = object_getClass(module);
|
||||
if (module.class != proxyClass) {
|
||||
object_setClass(module, module.class);
|
||||
objc_disposeClassPair(proxyClass);
|
||||
}
|
||||
);
|
||||
} forModule:module];
|
||||
for (RCTModuleData *moduleData in [bridge valueForKey:@"_modules"]) {
|
||||
[moduleData dispatchBlock:^{
|
||||
RCTProfileLock(
|
||||
Class proxyClass = object_getClass(moduleData.instance);
|
||||
if (moduleData.cls != proxyClass) {
|
||||
object_setClass(moduleData.instance, moduleData.cls);
|
||||
objc_disposeClassPair(proxyClass);
|
||||
}
|
||||
);
|
||||
}];
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -61,3 +61,5 @@ RCT_EXTERN NSError *RCTErrorWithMessage(NSString *message);
|
|||
// Convert nil values to NSNull, and vice-versa
|
||||
RCT_EXTERN id RCTNullIfNil(id value);
|
||||
RCT_EXTERN id RCTNilIfNull(id value);
|
||||
|
||||
RCT_EXTERN NSDictionary *RCTJSErrorFromNSError(NSError *error);
|
||||
|
|
|
@ -285,3 +285,24 @@ id RCTNilIfNull(id value)
|
|||
{
|
||||
return value == (id)kCFNull ? nil : value;
|
||||
}
|
||||
|
||||
// TODO: Can we just replace RCTMakeError with this function instead?
|
||||
NSDictionary *RCTJSErrorFromNSError(NSError *error)
|
||||
{
|
||||
NSString *errorMessage;
|
||||
NSArray *stackTrace = [NSThread callStackSymbols];
|
||||
NSMutableDictionary *errorInfo =
|
||||
[NSMutableDictionary dictionaryWithObject:stackTrace forKey:@"nativeStackIOS"];
|
||||
|
||||
if (error) {
|
||||
errorMessage = error.localizedDescription ?: @"Unknown error from a native module";
|
||||
errorInfo[@"domain"] = error.domain ?: RCTErrorDomain;
|
||||
errorInfo[@"code"] = @(error.code);
|
||||
} else {
|
||||
errorMessage = @"Unknown error from a native module";
|
||||
errorInfo[@"domain"] = RCTErrorDomain;
|
||||
errorInfo[@"code"] = @-1;
|
||||
}
|
||||
|
||||
return RCTMakeError(errorMessage, nil, errorInfo);
|
||||
}
|
||||
|
|
|
@ -810,7 +810,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
|
|||
|
||||
RCT_EXPORT_METHOD(createView:(NSNumber *)reactTag
|
||||
viewName:(NSString *)viewName
|
||||
rootTag:(NSNumber *)rootTag
|
||||
rootTag:(__unused NSNumber *)rootTag
|
||||
props:(NSDictionary *)props)
|
||||
{
|
||||
RCTViewManager *manager = _viewManagers[viewName];
|
||||
|
@ -1222,7 +1222,7 @@ RCT_EXPORT_METHOD(zoomToRect:(NSNumber *)reactTag
|
|||
* this in order to determine if scrolling is appropriate.
|
||||
*/
|
||||
RCT_EXPORT_METHOD(setJSResponder:(NSNumber *)reactTag
|
||||
blockNativeResponder:(BOOL)blockNativeResponder)
|
||||
blockNativeResponder:(__unused BOOL)blockNativeResponder)
|
||||
{
|
||||
[self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||
_jsResponder = viewRegistry[reactTag];
|
||||
|
|
|
@ -52,6 +52,10 @@
|
|||
14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE21AAC4AE100FC20F4 /* RCTMap.m */; };
|
||||
14435CE61AAC4AE100FC20F4 /* RCTMapManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */; };
|
||||
146459261B06C49500B389AA /* RCTFPSGraph.m in Sources */ = {isa = PBXBuildFile; fileRef = 146459251B06C49500B389AA /* RCTFPSGraph.m */; };
|
||||
14C2CA711B3AC63800E6CBB2 /* RCTModuleMethod.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C2CA701B3AC63800E6CBB2 /* RCTModuleMethod.m */; };
|
||||
14C2CA741B3AC64300E6CBB2 /* RCTModuleData.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C2CA731B3AC64300E6CBB2 /* RCTModuleData.m */; };
|
||||
14C2CA761B3AC64F00E6CBB2 /* RCTFrameUpdate.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C2CA751B3AC64F00E6CBB2 /* RCTFrameUpdate.m */; };
|
||||
14C2CA781B3ACB0400E6CBB2 /* RCTBatchedBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C2CA771B3ACB0400E6CBB2 /* RCTBatchedBridge.m */; };
|
||||
14F3620D1AABD06A001CE568 /* RCTSwitch.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F362081AABD06A001CE568 /* RCTSwitch.m */; };
|
||||
14F3620E1AABD06A001CE568 /* RCTSwitchManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F3620A1AABD06A001CE568 /* RCTSwitchManager.m */; };
|
||||
14F484561AABFCE100FDF6B9 /* RCTSliderManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F484551AABFCE100FDF6B9 /* RCTSliderManager.m */; };
|
||||
|
@ -187,6 +191,12 @@
|
|||
14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMapManager.m; sourceTree = "<group>"; };
|
||||
146459241B06C49500B389AA /* RCTFPSGraph.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTFPSGraph.h; sourceTree = "<group>"; };
|
||||
146459251B06C49500B389AA /* RCTFPSGraph.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTFPSGraph.m; sourceTree = "<group>"; };
|
||||
14C2CA6F1B3AC63800E6CBB2 /* RCTModuleMethod.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTModuleMethod.h; sourceTree = "<group>"; };
|
||||
14C2CA701B3AC63800E6CBB2 /* RCTModuleMethod.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleMethod.m; sourceTree = "<group>"; };
|
||||
14C2CA721B3AC64300E6CBB2 /* RCTModuleData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTModuleData.h; sourceTree = "<group>"; };
|
||||
14C2CA731B3AC64300E6CBB2 /* RCTModuleData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleData.m; sourceTree = "<group>"; };
|
||||
14C2CA751B3AC64F00E6CBB2 /* RCTFrameUpdate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTFrameUpdate.m; sourceTree = "<group>"; };
|
||||
14C2CA771B3ACB0400E6CBB2 /* RCTBatchedBridge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBatchedBridge.m; sourceTree = "<group>"; };
|
||||
14F362071AABD06A001CE568 /* RCTSwitch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSwitch.h; sourceTree = "<group>"; };
|
||||
14F362081AABD06A001CE568 /* RCTSwitch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSwitch.m; sourceTree = "<group>"; };
|
||||
14F362091AABD06A001CE568 /* RCTSwitchManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSwitchManager.h; sourceTree = "<group>"; };
|
||||
|
@ -444,6 +454,12 @@
|
|||
1403F2B21B0AE60700C2A9A4 /* RCTPerfStats.m */,
|
||||
142014171B32094000CC17BA /* RCTPerformanceLogger.m */,
|
||||
142014181B32094000CC17BA /* RCTPerformanceLogger.h */,
|
||||
14C2CA6F1B3AC63800E6CBB2 /* RCTModuleMethod.h */,
|
||||
14C2CA701B3AC63800E6CBB2 /* RCTModuleMethod.m */,
|
||||
14C2CA721B3AC64300E6CBB2 /* RCTModuleData.h */,
|
||||
14C2CA731B3AC64300E6CBB2 /* RCTModuleData.m */,
|
||||
14C2CA751B3AC64F00E6CBB2 /* RCTFrameUpdate.m */,
|
||||
14C2CA771B3ACB0400E6CBB2 /* RCTBatchedBridge.m */,
|
||||
);
|
||||
path = Base;
|
||||
sourceTree = "<group>";
|
||||
|
@ -526,12 +542,14 @@
|
|||
13723B501A82FD3C00F88898 /* RCTStatusBarManager.m in Sources */,
|
||||
000E6CEB1AB0E980000CDF4D /* RCTSourceCode.m in Sources */,
|
||||
13B0801E1A69489C00A75B9A /* RCTTextField.m in Sources */,
|
||||
14C2CA761B3AC64F00E6CBB2 /* RCTFrameUpdate.m in Sources */,
|
||||
13B07FEF1A69327A00A75B9A /* RCTAlertManager.m in Sources */,
|
||||
83CBBACC1A6023D300E9B192 /* RCTConvert.m in Sources */,
|
||||
131B6AF41AF1093D00FFC3E0 /* RCTSegmentedControl.m in Sources */,
|
||||
830A229E1A66C68A008503DA /* RCTRootView.m in Sources */,
|
||||
13B07FF01A69327A00A75B9A /* RCTExceptionsManager.m in Sources */,
|
||||
83CBBA5A1A601E9000E9B192 /* RCTRedBox.m in Sources */,
|
||||
14C2CA711B3AC63800E6CBB2 /* RCTModuleMethod.m in Sources */,
|
||||
13CC8A821B17642100940AE7 /* RCTBorderDrawing.m in Sources */,
|
||||
83CBBA511A601E3B00E9B192 /* RCTAssert.m in Sources */,
|
||||
13AF20451AE707F9005F5298 /* RCTSlider.m in Sources */,
|
||||
|
@ -554,8 +572,10 @@
|
|||
1372B70A1AB030C200659ED6 /* RCTAppState.m in Sources */,
|
||||
13B0801F1A69489C00A75B9A /* RCTTextFieldManager.m in Sources */,
|
||||
134FCB3D1A6E7F0800051CC8 /* RCTContextExecutor.m in Sources */,
|
||||
14C2CA781B3ACB0400E6CBB2 /* RCTBatchedBridge.m in Sources */,
|
||||
13E067591A70F44B002CDEE1 /* UIView+React.m in Sources */,
|
||||
14F484561AABFCE100FDF6B9 /* RCTSliderManager.m in Sources */,
|
||||
14C2CA741B3AC64300E6CBB2 /* RCTModuleData.m in Sources */,
|
||||
142014191B32094000CC17BA /* RCTPerformanceLogger.m in Sources */,
|
||||
83CBBA981A6020BB00E9B192 /* RCTTouchHandler.m in Sources */,
|
||||
83CBBA521A601E3B00E9B192 /* RCTLog.m in Sources */,
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
@interface RCTTabBarItem : UIView
|
||||
|
||||
@property (nonatomic, copy) NSString *icon;
|
||||
@property (nonatomic, copy) id icon;
|
||||
@property (nonatomic, assign, getter=isSelected) BOOL selected;
|
||||
@property (nonatomic, readonly) UITabBarItem *barItem;
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
return _barItem;
|
||||
}
|
||||
|
||||
- (void)setIcon:(NSString *)icon
|
||||
- (void)setIcon:(id)icon
|
||||
{
|
||||
static NSDictionary *systemIcons;
|
||||
static dispatch_once_t onceToken;
|
||||
|
@ -54,7 +54,7 @@
|
|||
UIImage *image = [RCTConvert UIImage:_icon];
|
||||
UITabBarItem *oldItem = _barItem;
|
||||
if (image) {
|
||||
|
||||
|
||||
// Recreate barItem if previous item was a system icon
|
||||
if (wasSystemIcon) {
|
||||
_barItem = nil;
|
||||
|
|
|
@ -22,7 +22,7 @@ RCT_EXPORT_MODULE()
|
|||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(selected, BOOL);
|
||||
RCT_EXPORT_VIEW_PROPERTY(icon, NSString);
|
||||
RCT_EXPORT_VIEW_PROPERTY(icon, id);
|
||||
RCT_REMAP_VIEW_PROPERTY(selectedIcon, barItem.selectedImage, UIImage);
|
||||
RCT_REMAP_VIEW_PROPERTY(badge, barItem.badgeValue, NSString);
|
||||
RCT_CUSTOM_VIEW_PROPERTY(title, NSString, RCTTabBarItem)
|
||||
|
|
|
@ -39,6 +39,7 @@ RCT_EXPORT_VIEW_PROPERTY(secureTextEntry, BOOL)
|
|||
RCT_REMAP_VIEW_PROPERTY(password, secureTextEntry, BOOL) // backwards compatibility
|
||||
RCT_REMAP_VIEW_PROPERTY(color, textColor, UIColor)
|
||||
RCT_REMAP_VIEW_PROPERTY(autoCapitalize, autocapitalizationType, UITextAutocapitalizationType)
|
||||
RCT_REMAP_VIEW_PROPERTY(textAlign, textAlignment, NSTextAlignment)
|
||||
RCT_CUSTOM_VIEW_PROPERTY(fontSize, CGFloat, RCTTextField)
|
||||
{
|
||||
view.font = [RCTConvert UIFont:view.font withSize:json ?: @(defaultView.font.pointSize)];
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
"dependencies": {
|
||||
"absolute-path": "0.0.0",
|
||||
"babel": "5.4.3",
|
||||
"babel-core": "^5.6.4",
|
||||
"bluebird": "^2.9.21",
|
||||
"chalk": "^1.0.0",
|
||||
"connect": "2.8.3",
|
||||
|
|
|
@ -13,6 +13,8 @@ jest
|
|||
.dontMock('crypto')
|
||||
.dontMock('absolute-path')
|
||||
.dontMock('../docblock')
|
||||
.dontMock('../../crawlers')
|
||||
.dontMock('../../crawlers/node')
|
||||
.dontMock('../../replacePatterns')
|
||||
.dontMock('../../../lib/getAssetDataFromName')
|
||||
.dontMock('../../fastfs')
|
||||
|
@ -22,6 +24,8 @@ jest
|
|||
.dontMock('../../Package')
|
||||
.dontMock('../../ModuleCache');
|
||||
|
||||
const Promise = require('promise');
|
||||
|
||||
jest.mock('fs');
|
||||
|
||||
describe('DependencyGraph', function() {
|
||||
|
@ -36,7 +40,8 @@ describe('DependencyGraph', function() {
|
|||
fileWatcher = {
|
||||
on: function() {
|
||||
return this;
|
||||
}
|
||||
},
|
||||
isWatchman: () => Promise.resolve(false)
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -8,17 +8,19 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const Activity = require('../../Activity');
|
||||
const AssetModule_DEPRECATED = require('../AssetModule_DEPRECATED');
|
||||
const Fastfs = require('../fastfs');
|
||||
const ModuleCache = require('../ModuleCache');
|
||||
const AssetModule_DEPRECATED = require('../AssetModule_DEPRECATED');
|
||||
const declareOpts = require('../../lib/declareOpts');
|
||||
const isAbsolutePath = require('absolute-path');
|
||||
const debug = require('debug')('DependencyGraph');
|
||||
const getAssetDataFromName = require('../../lib/getAssetDataFromName');
|
||||
const util = require('util');
|
||||
const Promise = require('promise');
|
||||
const _ = require('underscore');
|
||||
const crawl = require('../crawlers');
|
||||
const debug = require('debug')('DependencyGraph');
|
||||
const declareOpts = require('../../lib/declareOpts');
|
||||
const getAssetDataFromName = require('../../lib/getAssetDataFromName');
|
||||
const isAbsolutePath = require('absolute-path');
|
||||
const path = require('path');
|
||||
const util = require('util');
|
||||
|
||||
const validateOpts = declareOpts({
|
||||
roots: {
|
||||
|
@ -68,13 +70,18 @@ class DependencyGraph {
|
|||
return this._loading;
|
||||
}
|
||||
|
||||
const modulePattern = new RegExp(
|
||||
'\.(' + ['js', 'json'].concat(this._assetExts).join('|') + ')$'
|
||||
);
|
||||
const crawlActivity = Activity.startEvent('fs crawl');
|
||||
const allRoots = this._opts.roots.concat(this._opts.assetRoots_DEPRECATED);
|
||||
this._crawling = crawl(allRoots, {
|
||||
ignore: this._opts.ignoreFilePath,
|
||||
exts: ['js', 'json'].concat(this._opts.assetExts),
|
||||
fileWatcher: this._opts.fileWatcher,
|
||||
});
|
||||
this._crawling.then((files) => Activity.endEvent(crawlActivity));
|
||||
|
||||
this._fastfs = new Fastfs(this._opts.roots,this._opts.fileWatcher, {
|
||||
pattern: modulePattern,
|
||||
ignore: this._opts.ignoreFilePath,
|
||||
crawling: this._crawling,
|
||||
});
|
||||
|
||||
this._fastfs.on('change', this._processFileChange.bind(this));
|
||||
|
@ -454,14 +461,10 @@ class DependencyGraph {
|
|||
|
||||
this._assetMap_DEPRECATED = Object.create(null);
|
||||
|
||||
const pattern = new RegExp(
|
||||
'\.(' + this._opts.assetExts.join('|') + ')$'
|
||||
);
|
||||
|
||||
const fastfs = new Fastfs(
|
||||
this._opts.assetRoots_DEPRECATED,
|
||||
this._opts.fileWatcher,
|
||||
{ pattern, ignore: this._opts.ignoreFilePath }
|
||||
{ ignore: this._opts.ignoreFilePath, crawling: this._crawling }
|
||||
);
|
||||
|
||||
fastfs.on('change', this._processAssetChange_DEPRECATED.bind(this));
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
'use strict';
|
||||
|
||||
const nodeCrawl = require('./node');
|
||||
//const watchmanCrawl = require('./watchman');
|
||||
|
||||
function crawl(roots, options) {
|
||||
return nodeCrawl(roots, options);
|
||||
|
||||
// Although, in theory, watchman should be much faster;
|
||||
// there is currently a bottleneck somewhere in the
|
||||
// encoding/decoding that is causing it to be slower
|
||||
// than node crawling. However, this should be fixed soon.
|
||||
// https://github.com/facebook/watchman/issues/113
|
||||
/*
|
||||
const {fileWatcher} = options;
|
||||
return fileWatcher.isWatchman().then(isWatchman => {
|
||||
|
||||
console.log(isWatchman);
|
||||
if (!isWatchman) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure we're dealing with a version of watchman
|
||||
// that's using `watch-project`
|
||||
// TODO(amasad): properly expose (and document) used sane internals.
|
||||
return fileWatcher.getWatchers().then(([watcher]) => !!watcher.watchProjectInfo.root);
|
||||
}).then(isWatchman => {
|
||||
if (isWatchman) {
|
||||
return watchmanCrawl(roots, options);
|
||||
}
|
||||
|
||||
return nodeCrawl(roots, options);
|
||||
});*/
|
||||
}
|
||||
|
||||
module.exports = crawl;
|
|
@ -0,0 +1,61 @@
|
|||
'use strict';
|
||||
|
||||
const Promise = require('promise');
|
||||
const debug = require('debug')('DependencyGraph');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const readDir = Promise.denodeify(fs.readdir);
|
||||
const stat = Promise.denodeify(fs.stat);
|
||||
|
||||
function nodeRecReadDir(roots, {ignore, exts}) {
|
||||
const queue = roots.slice();
|
||||
const retFiles = [];
|
||||
const extPattern = new RegExp(
|
||||
'\.(' + exts.join('|') + ')$'
|
||||
);
|
||||
|
||||
function search() {
|
||||
const currDir = queue.shift();
|
||||
if (!currDir) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return readDir(currDir)
|
||||
.then(files => files.map(f => path.join(currDir, f)))
|
||||
.then(files => Promise.all(
|
||||
files.map(f => stat(f).catch(handleBrokenLink))
|
||||
).then(stats => [
|
||||
// Remove broken links.
|
||||
files.filter((file, i) => !!stats[i]),
|
||||
stats.filter(Boolean),
|
||||
]))
|
||||
.then(([files, stats]) => {
|
||||
files.forEach((filePath, i) => {
|
||||
if (ignore(filePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (stats[i].isDirectory()) {
|
||||
queue.push(filePath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (filePath.match(extPattern)) {
|
||||
retFiles.push(filePath);
|
||||
}
|
||||
});
|
||||
|
||||
return search();
|
||||
});
|
||||
}
|
||||
|
||||
return search().then(() => retFiles);
|
||||
}
|
||||
|
||||
function handleBrokenLink(e) {
|
||||
debug('WARNING: error stating, possibly broken symlink', e.message);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
module.exports = nodeRecReadDir;
|
|
@ -0,0 +1,70 @@
|
|||
'use strict';
|
||||
|
||||
const Promise = require('promise');
|
||||
const path = require('path');
|
||||
|
||||
function watchmanRecReadDir(roots, {ignore, fileWatcher, exts}) {
|
||||
const files = [];
|
||||
return Promise.all(
|
||||
roots.map(
|
||||
root => fileWatcher.getWatcherForRoot(root)
|
||||
)
|
||||
).then(
|
||||
watchers => {
|
||||
// All watchman roots for all watches we have.
|
||||
const watchmanRoots = watchers.map(
|
||||
watcher => watcher.watchProjectInfo.root
|
||||
);
|
||||
|
||||
// Actual unique watchers (because we use watch-project we may end up with
|
||||
// duplicate "real" watches, and that's by design).
|
||||
// TODO(amasad): push this functionality into the `FileWatcher`.
|
||||
const uniqueWatchers = watchers.filter(
|
||||
(watcher, i) => watchmanRoots.indexOf(watcher.watchProjectInfo.root) === i
|
||||
);
|
||||
|
||||
return Promise.all(
|
||||
uniqueWatchers.map(watcher => {
|
||||
const watchedRoot = watcher.watchProjectInfo.root;
|
||||
|
||||
// Build up an expression to filter the output by the relevant roots.
|
||||
const dirExpr = ['anyof'];
|
||||
for (let i = 0; i < roots.length; i++) {
|
||||
const root = roots[i];
|
||||
if (isDescendant(watchedRoot, root)) {
|
||||
dirExpr.push(['dirname', path.relative(watchedRoot, root)]);
|
||||
}
|
||||
}
|
||||
|
||||
const cmd = Promise.promisify(watcher.client.command.bind(watcher.client));
|
||||
return cmd(['query', watchedRoot, {
|
||||
'suffix': exts,
|
||||
'expression': ['allof', ['type', 'f'], 'exists', dirExpr],
|
||||
'fields': ['name'],
|
||||
}]).then(resp => {
|
||||
if ('warning' in resp) {
|
||||
console.warn('watchman warning: ', resp.warning);
|
||||
}
|
||||
|
||||
resp.files.forEach(filePath => {
|
||||
filePath = path.join(
|
||||
watchedRoot,
|
||||
filePath
|
||||
);
|
||||
|
||||
if (!ignore(filePath)) {
|
||||
files.push(filePath);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
}).then(() => files);
|
||||
}
|
||||
|
||||
function isDescendant(root, child) {
|
||||
return path.relative(root, child).indexOf('..') !== 0;
|
||||
}
|
||||
|
||||
module.exports = watchmanRecReadDir;
|
|
@ -4,28 +4,46 @@ const Promise = require('promise');
|
|||
const {EventEmitter} = require('events');
|
||||
|
||||
const _ = require('underscore');
|
||||
const debug = require('debug')('DependencyGraph');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const readDir = Promise.denodeify(fs.readdir);
|
||||
const readFile = Promise.denodeify(fs.readFile);
|
||||
const stat = Promise.denodeify(fs.stat);
|
||||
|
||||
const hasOwn = Object.prototype.hasOwnProperty;
|
||||
|
||||
class Fastfs extends EventEmitter {
|
||||
constructor(roots, fileWatcher, {ignore, pattern}) {
|
||||
constructor(roots, fileWatcher, {ignore, crawling}) {
|
||||
super();
|
||||
this._fileWatcher = fileWatcher;
|
||||
this._ignore = ignore;
|
||||
this._pattern = pattern;
|
||||
this._roots = roots.map(root => new File(root, { isDir: true }));
|
||||
this._fastPaths = Object.create(null);
|
||||
this._crawling = crawling;
|
||||
}
|
||||
|
||||
build() {
|
||||
const queue = this._roots.slice();
|
||||
return this._search(queue).then(() => {
|
||||
const rootsPattern = new RegExp(
|
||||
'^(' + this._roots.map(root => escapeRegExp(root.path)).join('|') + ')'
|
||||
);
|
||||
|
||||
return this._crawling.then(files => {
|
||||
files.forEach(filePath => {
|
||||
if (filePath.match(rootsPattern)) {
|
||||
const newFile = new File(filePath, { isDir: false });
|
||||
const parent = this._fastPaths[path.dirname(filePath)];
|
||||
if (parent) {
|
||||
parent.addChild(newFile);
|
||||
} else {
|
||||
this._add(newFile);
|
||||
for (let file = newFile; file; file = file.parent) {
|
||||
if (!this._fastPaths[file.path]) {
|
||||
this._fastPaths[file.path] = file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
this._fileWatcher.on('all', this._processFileChange.bind(this));
|
||||
});
|
||||
}
|
||||
|
@ -134,32 +152,6 @@ class Fastfs extends EventEmitter {
|
|||
this._getAndAssertRoot(file.path).addChild(file);
|
||||
}
|
||||
|
||||
_search(queue) {
|
||||
const dir = queue.shift();
|
||||
if (!dir) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return readAndStatDir(dir.path).then(([filePaths, stats]) => {
|
||||
filePaths.forEach((filePath, i) => {
|
||||
if (this._ignore(filePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (stats[i].isDirectory()) {
|
||||
queue.push(
|
||||
new File(filePath, { isDir: true, fstat: stats[i] })
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (filePath.match(this._pattern)) {
|
||||
this._add(new File(filePath, { fstat: stats[i] }));
|
||||
}
|
||||
});
|
||||
return this._search(queue);
|
||||
});
|
||||
}
|
||||
|
||||
_processFileChange(type, filePath, root, fstat) {
|
||||
const absPath = path.join(root, filePath);
|
||||
|
@ -182,10 +174,7 @@ class Fastfs extends EventEmitter {
|
|||
delete this._fastPaths[path.normalize(absPath)];
|
||||
|
||||
if (type !== 'delete') {
|
||||
this._add(new File(absPath, {
|
||||
isDir: false,
|
||||
fstat
|
||||
}));
|
||||
this._add(new File(absPath, { isDir: false }));
|
||||
}
|
||||
|
||||
this.emit('change', type, filePath, root, fstat);
|
||||
|
@ -193,16 +182,12 @@ class Fastfs extends EventEmitter {
|
|||
}
|
||||
|
||||
class File {
|
||||
constructor(filePath, {isDir, fstat}) {
|
||||
constructor(filePath, { isDir }) {
|
||||
this.path = filePath;
|
||||
this.isDir = Boolean(isDir);
|
||||
if (this.isDir) {
|
||||
this.children = Object.create(null);
|
||||
}
|
||||
|
||||
if (fstat) {
|
||||
this._stat = Promise.resolve(fstat);
|
||||
}
|
||||
}
|
||||
|
||||
read() {
|
||||
|
@ -290,21 +275,8 @@ function isDescendant(root, child) {
|
|||
return path.relative(root, child).indexOf('..') !== 0;
|
||||
}
|
||||
|
||||
function readAndStatDir(dir) {
|
||||
return readDir(dir)
|
||||
.then(files => Promise.all(files.map(f => path.join(dir, f))))
|
||||
.then(files => Promise.all(
|
||||
files.map(f => stat(f).catch(handleBrokenLink))
|
||||
).then(stats => [
|
||||
// Remove broken links.
|
||||
files.filter((file, i ) => !!stats[i]),
|
||||
stats.filter(Boolean),
|
||||
]));
|
||||
}
|
||||
|
||||
function handleBrokenLink(e) {
|
||||
debug('WARNING: error stating, possibly broken symlink', e.message);
|
||||
return Promise.resolve();
|
||||
function escapeRegExp(str) {
|
||||
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
|
||||
}
|
||||
|
||||
module.exports = Fastfs;
|
||||
|
|
|
@ -1,2 +1,5 @@
|
|||
/* eslint global-strict:0 */
|
||||
__DEV__ = false;
|
||||
|
||||
/* global __BUNDLE_START_TIME__:true */
|
||||
__BUNDLE_START_TIME__ = Date.now();
|
||||
|
|
|
@ -1,2 +1,5 @@
|
|||
/* eslint global-strict:0 */
|
||||
__DEV__ = true;
|
||||
|
||||
/* global __BUNDLE_START_TIME__:true */
|
||||
__BUNDLE_START_TIME__ = Date.now();
|
||||
|
|
|
@ -33,7 +33,7 @@ describe('FileWatcher', function() {
|
|||
|
||||
pit('it should get the watcher instance when ready', function() {
|
||||
var fileWatcher = new FileWatcher(['rootDir']);
|
||||
return fileWatcher._loading.then(function(watchers) {
|
||||
return fileWatcher.getWatchers().then(function(watchers) {
|
||||
watchers.forEach(function(watcher) {
|
||||
expect(watcher instanceof Watcher).toBe(true);
|
||||
});
|
||||
|
@ -48,7 +48,7 @@ describe('FileWatcher', function() {
|
|||
var fileWatcher = new FileWatcher(['rootDir']);
|
||||
var handler = jest.genMockFn();
|
||||
fileWatcher.on('all', handler);
|
||||
return fileWatcher._loading.then(function(){
|
||||
return fileWatcher.getWatchers().then(function(){
|
||||
cb(1, 2, 3, 4);
|
||||
jest.runAllTimers();
|
||||
expect(handler.mock.calls[0]).toEqual([1, 2, 3, 4]);
|
||||
|
|
|
@ -8,13 +8,14 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var sane = require('sane');
|
||||
var Promise = require('promise');
|
||||
var util = require('util');
|
||||
var exec = require('child_process').exec;
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const sane = require('sane');
|
||||
const Promise = require('promise');
|
||||
const exec = require('child_process').exec;
|
||||
|
||||
var detectingWatcherClass = new Promise(function(resolve) {
|
||||
const MAX_WAIT_TIME = 25000;
|
||||
|
||||
const detectingWatcherClass = new Promise(function(resolve) {
|
||||
exec('which watchman', function(err, out) {
|
||||
if (err || out.length === 0) {
|
||||
resolve(sane.NodeWatcher);
|
||||
|
@ -24,53 +25,76 @@ var detectingWatcherClass = new Promise(function(resolve) {
|
|||
});
|
||||
});
|
||||
|
||||
module.exports = FileWatcher;
|
||||
let inited = false;
|
||||
|
||||
var MAX_WAIT_TIME = 25000;
|
||||
class FileWatcher extends EventEmitter {
|
||||
|
||||
// Singleton
|
||||
var fileWatcher = null;
|
||||
constructor(rootConfigs) {
|
||||
if (inited) {
|
||||
throw new Error('FileWatcher can only be instantiated once');
|
||||
}
|
||||
inited = true;
|
||||
|
||||
function FileWatcher(rootConfigs) {
|
||||
if (fileWatcher) {
|
||||
// This allows us to optimize watching in the future by merging roots etc.
|
||||
throw new Error('FileWatcher can only be instantiated once');
|
||||
super();
|
||||
this._watcherByRoot = Object.create(null);
|
||||
|
||||
this._loading = Promise.all(
|
||||
rootConfigs.map(createWatcher)
|
||||
).then(watchers => {
|
||||
watchers.forEach((watcher, i) => {
|
||||
this._watcherByRoot[rootConfigs[i].dir] = watcher;
|
||||
watcher.on(
|
||||
'all',
|
||||
// args = (type, filePath, root, stat)
|
||||
(...args) => this.emit('all', ...args)
|
||||
);
|
||||
});
|
||||
return watchers;
|
||||
});
|
||||
|
||||
this._loading.done();
|
||||
}
|
||||
|
||||
fileWatcher = this;
|
||||
getWatchers() {
|
||||
return this._loading;
|
||||
}
|
||||
|
||||
this._loading = Promise.all(
|
||||
rootConfigs.map(createWatcher)
|
||||
).then(function(watchers) {
|
||||
watchers.forEach(function(watcher) {
|
||||
watcher.on('all', function(type, filepath, root, stat) {
|
||||
fileWatcher.emit('all', type, filepath, root, stat);
|
||||
});
|
||||
});
|
||||
return watchers;
|
||||
});
|
||||
this._loading.done();
|
||||
getWatcherForRoot(root) {
|
||||
return this._loading.then(() => this._watcherByRoot[root]);
|
||||
}
|
||||
|
||||
isWatchman() {
|
||||
return detectingWatcherClass.then(
|
||||
Watcher => Watcher === sane.WatchmanWatcher
|
||||
);
|
||||
}
|
||||
|
||||
end() {
|
||||
return this._loading.then(
|
||||
(watchers) => watchers.map(
|
||||
watcher => Promise.denodeify(watcher.close).call(watcher)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
static createDummyWatcher() {
|
||||
const ev = new EventEmitter();
|
||||
ev.end = function() {
|
||||
return Promise.resolve();
|
||||
};
|
||||
return ev;
|
||||
}
|
||||
}
|
||||
|
||||
util.inherits(FileWatcher, EventEmitter);
|
||||
|
||||
FileWatcher.prototype.end = function() {
|
||||
return this._loading.then(function(watchers) {
|
||||
watchers.forEach(function(watcher) {
|
||||
return Promise.denodeify(watcher.close).call(watcher);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function createWatcher(rootConfig) {
|
||||
return detectingWatcherClass.then(function(Watcher) {
|
||||
var watcher = new Watcher(rootConfig.dir, {
|
||||
const watcher = new Watcher(rootConfig.dir, {
|
||||
glob: rootConfig.globs,
|
||||
dot: false,
|
||||
});
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
var rejectTimeout = setTimeout(function() {
|
||||
const rejectTimeout = setTimeout(function() {
|
||||
reject(new Error([
|
||||
'Watcher took too long to load',
|
||||
'Try running `watchman version` from your terminal',
|
||||
|
@ -86,10 +110,4 @@ function createWatcher(rootConfig) {
|
|||
});
|
||||
}
|
||||
|
||||
FileWatcher.createDummyWatcher = function() {
|
||||
var ev = new EventEmitter();
|
||||
ev.end = function() {
|
||||
return Promise.resolve();
|
||||
};
|
||||
return ev;
|
||||
};
|
||||
module.exports = FileWatcher;
|
||||
|
|
Loading…
Reference in New Issue