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.
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#import <AdSupport/ASIdentifierManager.h>
|
|
||||||
|
|
||||||
#import "RCTBridgeModule.h"
|
#import "RCTBridgeModule.h"
|
||||||
|
|
||||||
@interface RCTAdSupport : NSObject <RCTBridgeModule>
|
@interface RCTAdSupport : NSObject <RCTBridgeModule>
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
* of patent rights can be found in the PATENTS file in the same directory.
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#import <AdSupport/ASIdentifierManager.h>
|
||||||
|
|
||||||
#import "RCTAdSupport.h"
|
#import "RCTAdSupport.h"
|
||||||
|
|
||||||
@implementation RCTAdSupport
|
@implementation RCTAdSupport
|
||||||
|
|
|
@ -217,6 +217,11 @@ RCT_EXPORT_METHOD(startAnimation:(NSNumber *)reactTag
|
||||||
}
|
}
|
||||||
|
|
||||||
NSValue *fromValue = [view.layer.presentationLayer valueForKeyPath:keypath];
|
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];
|
CGFloat fromFields[count];
|
||||||
[fromValue getValue:fromFields];
|
[fromValue getValue:fromFields];
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,7 @@ var AndroidTextInputAttributes = {
|
||||||
multiline: true,
|
multiline: true,
|
||||||
password: true,
|
password: true,
|
||||||
placeholder: true,
|
placeholder: true,
|
||||||
|
placeholderTextColor: true,
|
||||||
text: true,
|
text: true,
|
||||||
testID: true,
|
testID: true,
|
||||||
underlineColorAndroid: true,
|
underlineColorAndroid: true,
|
||||||
|
@ -514,6 +515,7 @@ var TextInput = React.createClass({
|
||||||
onLayout={this.props.onLayout}
|
onLayout={this.props.onLayout}
|
||||||
password={this.props.password || this.props.secureTextEntry}
|
password={this.props.password || this.props.secureTextEntry}
|
||||||
placeholder={this.props.placeholder}
|
placeholder={this.props.placeholder}
|
||||||
|
placeholderTextColor={this.props.placeholderTextColor}
|
||||||
text={this.state.bufferedValue}
|
text={this.state.bufferedValue}
|
||||||
underlineColorAndroid={this.props.underlineColorAndroid}
|
underlineColorAndroid={this.props.underlineColorAndroid}
|
||||||
children={children}
|
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 */
|
/* globals GLOBAL: true, window: true */
|
||||||
|
|
||||||
// Just to make sure the JS gets packaged up.
|
// Just to make sure the JS gets packaged up.
|
||||||
|
require('RCTDebugComponentOwnership');
|
||||||
require('RCTDeviceEventEmitter');
|
require('RCTDeviceEventEmitter');
|
||||||
require('PerformanceLogger');
|
require('PerformanceLogger');
|
||||||
|
|
||||||
|
|
|
@ -285,7 +285,7 @@ RCT_EXPORT_MODULE()
|
||||||
}];
|
}];
|
||||||
id<RCTURLRequestHandler> handler = [handlers lastObject];
|
id<RCTURLRequestHandler> handler = [handlers lastObject];
|
||||||
if (!handler) {
|
if (!handler) {
|
||||||
RCTLogError(@"No suitable request handler found for %@", request);
|
RCTLogError(@"No suitable request handler found for %@", request.URL);
|
||||||
}
|
}
|
||||||
return handler;
|
return handler;
|
||||||
}
|
}
|
||||||
|
@ -322,6 +322,9 @@ RCT_EXPORT_MODULE()
|
||||||
NSURLRequest *request = [RCTConvert NSURLRequest:query[@"uri"]];
|
NSURLRequest *request = [RCTConvert NSURLRequest:query[@"uri"]];
|
||||||
if (request) {
|
if (request) {
|
||||||
id<RCTURLRequestHandler> handler = [self handlerForRequest:request];
|
id<RCTURLRequestHandler> handler = [self handlerForRequest:request];
|
||||||
|
if (!handler) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
(void)[[RCTDataLoader alloc] initWithRequest:request handler:handler callback:^(NSData *data, NSString *MIMEType, NSError *error) {
|
(void)[[RCTDataLoader alloc] initWithRequest:request handler:handler callback:^(NSData *data, NSString *MIMEType, NSError *error) {
|
||||||
if (data) {
|
if (data) {
|
||||||
callback(nil, @{@"body": data, @"contentType": MIMEType});
|
callback(nil, @{@"body": data, @"contentType": MIMEType});
|
||||||
|
|
|
@ -99,7 +99,14 @@ RCT_NOT_IMPLEMENTED(-init)
|
||||||
error = [[RCTRedBox sharedInstance] currentErrorMessage];
|
error = [[RCTRedBox sharedInstance] currentErrorMessage];
|
||||||
}
|
}
|
||||||
[rootView removeFromSuperview];
|
[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;
|
vc.view = nil;
|
||||||
[[RCTRedBox sharedInstance] dismiss];
|
[[RCTRedBox sharedInstance] dismiss];
|
||||||
if (expectErrorBlock) {
|
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;
|
@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.
|
* 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
|
* and the bridge will populate the methodQueue property for you automatically
|
||||||
* when it initializes the module.
|
* 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
|
* Place this macro in your class implementation to automatically register
|
||||||
|
|
|
@ -635,17 +635,24 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (RCT_DEBUG && ![json isKindOfClass:[NSString class]]) {
|
if (RCT_DEBUG && ![json isKindOfClass:[NSString class]] && ![json isKindOfClass:[NSDictionary class]]) {
|
||||||
RCTLogConvertError(json, "an image");
|
RCTLogConvertError(json, "an image");
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ([json length] == 0) {
|
UIImage *image;
|
||||||
return nil;
|
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:"]) {
|
if ([path hasPrefix:@"data:"]) {
|
||||||
NSURL *url = [NSURL URLWithString:path];
|
NSURL *url = [NSURL URLWithString:path];
|
||||||
NSData *imageData = [NSData dataWithContentsOfURL:url];
|
NSData *imageData = [NSData dataWithContentsOfURL:url];
|
||||||
|
@ -658,6 +665,11 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
|
||||||
image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:path ofType:nil]];
|
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
|
// 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
|
// case where we find out if a string is an image by using this method
|
||||||
return image;
|
return image;
|
||||||
|
|
|
@ -7,6 +7,10 @@
|
||||||
* of patent rights can be found in the PATENTS file in the same directory.
|
* 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.
|
* Interface containing the information about the last screen refresh.
|
||||||
*/
|
*/
|
||||||
|
@ -22,6 +26,8 @@
|
||||||
*/
|
*/
|
||||||
@property (nonatomic, readonly) NSTimeInterval deltaTime;
|
@property (nonatomic, readonly) NSTimeInterval deltaTime;
|
||||||
|
|
||||||
|
- (instancetype)initWithDisplayLink:(CADisplayLink *)displayLink NS_DESIGNATED_INITIALIZER;
|
||||||
|
|
||||||
@end
|
@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 "RCTAssert.h"
|
||||||
#import "RCTBridge.h"
|
#import "RCTBridge.h"
|
||||||
#import "RCTDefines.h"
|
#import "RCTDefines.h"
|
||||||
|
#import "RCTModuleData.h"
|
||||||
#import "RCTUtils.h"
|
#import "RCTUtils.h"
|
||||||
|
|
||||||
NSString *const RCTProfileDidStartProfiling = @"RCTProfileDidStartProfiling";
|
NSString *const RCTProfileDidStartProfiling = @"RCTProfileDidStartProfiling";
|
||||||
|
@ -98,12 +99,6 @@ NSDictionary *RCTProfileGetMemoryUsage(void)
|
||||||
|
|
||||||
#pragma mark - Module hooks
|
#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);
|
||||||
static const char *RCTProfileProxyClassName(Class 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 *);
|
||||||
static void RCTProfileHookModules(RCTBridge *bridge)
|
static void RCTProfileHookModules(RCTBridge *bridge)
|
||||||
{
|
{
|
||||||
for (id<RCTBridgeModule> module in bridge.modules.allValues) {
|
for (RCTModuleData *moduleData in [bridge valueForKey:@"_modules"]) {
|
||||||
[bridge dispatchBlock:^{
|
[moduleData dispatchBlock:^{
|
||||||
Class moduleClass = object_getClass(module);
|
Class moduleClass = moduleData.cls;
|
||||||
Class proxyClass = objc_allocateClassPair(moduleClass, RCTProfileProxyClassName(moduleClass), 0);
|
Class proxyClass = objc_allocateClassPair(moduleClass, RCTProfileProxyClassName(moduleClass), 0);
|
||||||
|
|
||||||
unsigned int methodCount;
|
unsigned int methodCount;
|
||||||
|
@ -167,7 +162,7 @@ static void RCTProfileHookModules(RCTBridge *bridge)
|
||||||
}
|
}
|
||||||
IMP originalIMP = method_getImplementation(method);
|
IMP originalIMP = method_getImplementation(method);
|
||||||
const char *returnType = method_getTypeEncoding(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);
|
class_addMethod(proxyClass, RCTProfileProxySelector(selector), originalIMP, returnType);
|
||||||
}
|
}
|
||||||
free(methods);
|
free(methods);
|
||||||
|
@ -185,24 +180,24 @@ static void RCTProfileHookModules(RCTBridge *bridge)
|
||||||
}
|
}
|
||||||
|
|
||||||
objc_registerClassPair(proxyClass);
|
objc_registerClassPair(proxyClass);
|
||||||
object_setClass(module, proxyClass);
|
object_setClass(moduleData.instance, proxyClass);
|
||||||
} forModule:module];
|
}];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RCTProfileUnhookModules(RCTBridge *);
|
void RCTProfileUnhookModules(RCTBridge *);
|
||||||
void RCTProfileUnhookModules(RCTBridge *bridge)
|
void RCTProfileUnhookModules(RCTBridge *bridge)
|
||||||
{
|
{
|
||||||
for (id<RCTBridgeModule> module in bridge.modules.allValues) {
|
for (RCTModuleData *moduleData in [bridge valueForKey:@"_modules"]) {
|
||||||
[bridge dispatchBlock:^{
|
[moduleData dispatchBlock:^{
|
||||||
RCTProfileLock(
|
RCTProfileLock(
|
||||||
Class proxyClass = object_getClass(module);
|
Class proxyClass = object_getClass(moduleData.instance);
|
||||||
if (module.class != proxyClass) {
|
if (moduleData.cls != proxyClass) {
|
||||||
object_setClass(module, module.class);
|
object_setClass(moduleData.instance, moduleData.cls);
|
||||||
objc_disposeClassPair(proxyClass);
|
objc_disposeClassPair(proxyClass);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} forModule:module];
|
}];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,3 +61,5 @@ RCT_EXTERN NSError *RCTErrorWithMessage(NSString *message);
|
||||||
// Convert nil values to NSNull, and vice-versa
|
// Convert nil values to NSNull, and vice-versa
|
||||||
RCT_EXTERN id RCTNullIfNil(id value);
|
RCT_EXTERN id RCTNullIfNil(id value);
|
||||||
RCT_EXTERN id RCTNilIfNull(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;
|
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
|
RCT_EXPORT_METHOD(createView:(NSNumber *)reactTag
|
||||||
viewName:(NSString *)viewName
|
viewName:(NSString *)viewName
|
||||||
rootTag:(NSNumber *)rootTag
|
rootTag:(__unused NSNumber *)rootTag
|
||||||
props:(NSDictionary *)props)
|
props:(NSDictionary *)props)
|
||||||
{
|
{
|
||||||
RCTViewManager *manager = _viewManagers[viewName];
|
RCTViewManager *manager = _viewManagers[viewName];
|
||||||
|
@ -1222,7 +1222,7 @@ RCT_EXPORT_METHOD(zoomToRect:(NSNumber *)reactTag
|
||||||
* this in order to determine if scrolling is appropriate.
|
* this in order to determine if scrolling is appropriate.
|
||||||
*/
|
*/
|
||||||
RCT_EXPORT_METHOD(setJSResponder:(NSNumber *)reactTag
|
RCT_EXPORT_METHOD(setJSResponder:(NSNumber *)reactTag
|
||||||
blockNativeResponder:(BOOL)blockNativeResponder)
|
blockNativeResponder:(__unused BOOL)blockNativeResponder)
|
||||||
{
|
{
|
||||||
[self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
[self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||||
_jsResponder = viewRegistry[reactTag];
|
_jsResponder = viewRegistry[reactTag];
|
||||||
|
|
|
@ -52,6 +52,10 @@
|
||||||
14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE21AAC4AE100FC20F4 /* RCTMap.m */; };
|
14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE21AAC4AE100FC20F4 /* RCTMap.m */; };
|
||||||
14435CE61AAC4AE100FC20F4 /* RCTMapManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */; };
|
14435CE61AAC4AE100FC20F4 /* RCTMapManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */; };
|
||||||
146459261B06C49500B389AA /* RCTFPSGraph.m in Sources */ = {isa = PBXBuildFile; fileRef = 146459251B06C49500B389AA /* RCTFPSGraph.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 */; };
|
14F3620D1AABD06A001CE568 /* RCTSwitch.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F362081AABD06A001CE568 /* RCTSwitch.m */; };
|
||||||
14F3620E1AABD06A001CE568 /* RCTSwitchManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F3620A1AABD06A001CE568 /* RCTSwitchManager.m */; };
|
14F3620E1AABD06A001CE568 /* RCTSwitchManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F3620A1AABD06A001CE568 /* RCTSwitchManager.m */; };
|
||||||
14F484561AABFCE100FDF6B9 /* RCTSliderManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F484551AABFCE100FDF6B9 /* RCTSliderManager.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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
14F362091AABD06A001CE568 /* RCTSwitchManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSwitchManager.h; sourceTree = "<group>"; };
|
||||||
|
@ -444,6 +454,12 @@
|
||||||
1403F2B21B0AE60700C2A9A4 /* RCTPerfStats.m */,
|
1403F2B21B0AE60700C2A9A4 /* RCTPerfStats.m */,
|
||||||
142014171B32094000CC17BA /* RCTPerformanceLogger.m */,
|
142014171B32094000CC17BA /* RCTPerformanceLogger.m */,
|
||||||
142014181B32094000CC17BA /* RCTPerformanceLogger.h */,
|
142014181B32094000CC17BA /* RCTPerformanceLogger.h */,
|
||||||
|
14C2CA6F1B3AC63800E6CBB2 /* RCTModuleMethod.h */,
|
||||||
|
14C2CA701B3AC63800E6CBB2 /* RCTModuleMethod.m */,
|
||||||
|
14C2CA721B3AC64300E6CBB2 /* RCTModuleData.h */,
|
||||||
|
14C2CA731B3AC64300E6CBB2 /* RCTModuleData.m */,
|
||||||
|
14C2CA751B3AC64F00E6CBB2 /* RCTFrameUpdate.m */,
|
||||||
|
14C2CA771B3ACB0400E6CBB2 /* RCTBatchedBridge.m */,
|
||||||
);
|
);
|
||||||
path = Base;
|
path = Base;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -526,12 +542,14 @@
|
||||||
13723B501A82FD3C00F88898 /* RCTStatusBarManager.m in Sources */,
|
13723B501A82FD3C00F88898 /* RCTStatusBarManager.m in Sources */,
|
||||||
000E6CEB1AB0E980000CDF4D /* RCTSourceCode.m in Sources */,
|
000E6CEB1AB0E980000CDF4D /* RCTSourceCode.m in Sources */,
|
||||||
13B0801E1A69489C00A75B9A /* RCTTextField.m in Sources */,
|
13B0801E1A69489C00A75B9A /* RCTTextField.m in Sources */,
|
||||||
|
14C2CA761B3AC64F00E6CBB2 /* RCTFrameUpdate.m in Sources */,
|
||||||
13B07FEF1A69327A00A75B9A /* RCTAlertManager.m in Sources */,
|
13B07FEF1A69327A00A75B9A /* RCTAlertManager.m in Sources */,
|
||||||
83CBBACC1A6023D300E9B192 /* RCTConvert.m in Sources */,
|
83CBBACC1A6023D300E9B192 /* RCTConvert.m in Sources */,
|
||||||
131B6AF41AF1093D00FFC3E0 /* RCTSegmentedControl.m in Sources */,
|
131B6AF41AF1093D00FFC3E0 /* RCTSegmentedControl.m in Sources */,
|
||||||
830A229E1A66C68A008503DA /* RCTRootView.m in Sources */,
|
830A229E1A66C68A008503DA /* RCTRootView.m in Sources */,
|
||||||
13B07FF01A69327A00A75B9A /* RCTExceptionsManager.m in Sources */,
|
13B07FF01A69327A00A75B9A /* RCTExceptionsManager.m in Sources */,
|
||||||
83CBBA5A1A601E9000E9B192 /* RCTRedBox.m in Sources */,
|
83CBBA5A1A601E9000E9B192 /* RCTRedBox.m in Sources */,
|
||||||
|
14C2CA711B3AC63800E6CBB2 /* RCTModuleMethod.m in Sources */,
|
||||||
13CC8A821B17642100940AE7 /* RCTBorderDrawing.m in Sources */,
|
13CC8A821B17642100940AE7 /* RCTBorderDrawing.m in Sources */,
|
||||||
83CBBA511A601E3B00E9B192 /* RCTAssert.m in Sources */,
|
83CBBA511A601E3B00E9B192 /* RCTAssert.m in Sources */,
|
||||||
13AF20451AE707F9005F5298 /* RCTSlider.m in Sources */,
|
13AF20451AE707F9005F5298 /* RCTSlider.m in Sources */,
|
||||||
|
@ -554,8 +572,10 @@
|
||||||
1372B70A1AB030C200659ED6 /* RCTAppState.m in Sources */,
|
1372B70A1AB030C200659ED6 /* RCTAppState.m in Sources */,
|
||||||
13B0801F1A69489C00A75B9A /* RCTTextFieldManager.m in Sources */,
|
13B0801F1A69489C00A75B9A /* RCTTextFieldManager.m in Sources */,
|
||||||
134FCB3D1A6E7F0800051CC8 /* RCTContextExecutor.m in Sources */,
|
134FCB3D1A6E7F0800051CC8 /* RCTContextExecutor.m in Sources */,
|
||||||
|
14C2CA781B3ACB0400E6CBB2 /* RCTBatchedBridge.m in Sources */,
|
||||||
13E067591A70F44B002CDEE1 /* UIView+React.m in Sources */,
|
13E067591A70F44B002CDEE1 /* UIView+React.m in Sources */,
|
||||||
14F484561AABFCE100FDF6B9 /* RCTSliderManager.m in Sources */,
|
14F484561AABFCE100FDF6B9 /* RCTSliderManager.m in Sources */,
|
||||||
|
14C2CA741B3AC64300E6CBB2 /* RCTModuleData.m in Sources */,
|
||||||
142014191B32094000CC17BA /* RCTPerformanceLogger.m in Sources */,
|
142014191B32094000CC17BA /* RCTPerformanceLogger.m in Sources */,
|
||||||
83CBBA981A6020BB00E9B192 /* RCTTouchHandler.m in Sources */,
|
83CBBA981A6020BB00E9B192 /* RCTTouchHandler.m in Sources */,
|
||||||
83CBBA521A601E3B00E9B192 /* RCTLog.m in Sources */,
|
83CBBA521A601E3B00E9B192 /* RCTLog.m in Sources */,
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
@interface RCTTabBarItem : UIView
|
@interface RCTTabBarItem : UIView
|
||||||
|
|
||||||
@property (nonatomic, copy) NSString *icon;
|
@property (nonatomic, copy) id icon;
|
||||||
@property (nonatomic, assign, getter=isSelected) BOOL selected;
|
@property (nonatomic, assign, getter=isSelected) BOOL selected;
|
||||||
@property (nonatomic, readonly) UITabBarItem *barItem;
|
@property (nonatomic, readonly) UITabBarItem *barItem;
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
return _barItem;
|
return _barItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setIcon:(NSString *)icon
|
- (void)setIcon:(id)icon
|
||||||
{
|
{
|
||||||
static NSDictionary *systemIcons;
|
static NSDictionary *systemIcons;
|
||||||
static dispatch_once_t onceToken;
|
static dispatch_once_t onceToken;
|
||||||
|
@ -54,7 +54,7 @@
|
||||||
UIImage *image = [RCTConvert UIImage:_icon];
|
UIImage *image = [RCTConvert UIImage:_icon];
|
||||||
UITabBarItem *oldItem = _barItem;
|
UITabBarItem *oldItem = _barItem;
|
||||||
if (image) {
|
if (image) {
|
||||||
|
|
||||||
// Recreate barItem if previous item was a system icon
|
// Recreate barItem if previous item was a system icon
|
||||||
if (wasSystemIcon) {
|
if (wasSystemIcon) {
|
||||||
_barItem = nil;
|
_barItem = nil;
|
||||||
|
|
|
@ -22,7 +22,7 @@ RCT_EXPORT_MODULE()
|
||||||
}
|
}
|
||||||
|
|
||||||
RCT_EXPORT_VIEW_PROPERTY(selected, BOOL);
|
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(selectedIcon, barItem.selectedImage, UIImage);
|
||||||
RCT_REMAP_VIEW_PROPERTY(badge, barItem.badgeValue, NSString);
|
RCT_REMAP_VIEW_PROPERTY(badge, barItem.badgeValue, NSString);
|
||||||
RCT_CUSTOM_VIEW_PROPERTY(title, NSString, RCTTabBarItem)
|
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(password, secureTextEntry, BOOL) // backwards compatibility
|
||||||
RCT_REMAP_VIEW_PROPERTY(color, textColor, UIColor)
|
RCT_REMAP_VIEW_PROPERTY(color, textColor, UIColor)
|
||||||
RCT_REMAP_VIEW_PROPERTY(autoCapitalize, autocapitalizationType, UITextAutocapitalizationType)
|
RCT_REMAP_VIEW_PROPERTY(autoCapitalize, autocapitalizationType, UITextAutocapitalizationType)
|
||||||
|
RCT_REMAP_VIEW_PROPERTY(textAlign, textAlignment, NSTextAlignment)
|
||||||
RCT_CUSTOM_VIEW_PROPERTY(fontSize, CGFloat, RCTTextField)
|
RCT_CUSTOM_VIEW_PROPERTY(fontSize, CGFloat, RCTTextField)
|
||||||
{
|
{
|
||||||
view.font = [RCTConvert UIFont:view.font withSize:json ?: @(defaultView.font.pointSize)];
|
view.font = [RCTConvert UIFont:view.font withSize:json ?: @(defaultView.font.pointSize)];
|
||||||
|
|
|
@ -47,6 +47,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"absolute-path": "0.0.0",
|
"absolute-path": "0.0.0",
|
||||||
"babel": "5.4.3",
|
"babel": "5.4.3",
|
||||||
|
"babel-core": "^5.6.4",
|
||||||
"bluebird": "^2.9.21",
|
"bluebird": "^2.9.21",
|
||||||
"chalk": "^1.0.0",
|
"chalk": "^1.0.0",
|
||||||
"connect": "2.8.3",
|
"connect": "2.8.3",
|
||||||
|
|
|
@ -13,6 +13,8 @@ jest
|
||||||
.dontMock('crypto')
|
.dontMock('crypto')
|
||||||
.dontMock('absolute-path')
|
.dontMock('absolute-path')
|
||||||
.dontMock('../docblock')
|
.dontMock('../docblock')
|
||||||
|
.dontMock('../../crawlers')
|
||||||
|
.dontMock('../../crawlers/node')
|
||||||
.dontMock('../../replacePatterns')
|
.dontMock('../../replacePatterns')
|
||||||
.dontMock('../../../lib/getAssetDataFromName')
|
.dontMock('../../../lib/getAssetDataFromName')
|
||||||
.dontMock('../../fastfs')
|
.dontMock('../../fastfs')
|
||||||
|
@ -22,6 +24,8 @@ jest
|
||||||
.dontMock('../../Package')
|
.dontMock('../../Package')
|
||||||
.dontMock('../../ModuleCache');
|
.dontMock('../../ModuleCache');
|
||||||
|
|
||||||
|
const Promise = require('promise');
|
||||||
|
|
||||||
jest.mock('fs');
|
jest.mock('fs');
|
||||||
|
|
||||||
describe('DependencyGraph', function() {
|
describe('DependencyGraph', function() {
|
||||||
|
@ -36,7 +40,8 @@ describe('DependencyGraph', function() {
|
||||||
fileWatcher = {
|
fileWatcher = {
|
||||||
on: function() {
|
on: function() {
|
||||||
return this;
|
return this;
|
||||||
}
|
},
|
||||||
|
isWatchman: () => Promise.resolve(false)
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -8,17 +8,19 @@
|
||||||
*/
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const path = require('path');
|
const Activity = require('../../Activity');
|
||||||
|
const AssetModule_DEPRECATED = require('../AssetModule_DEPRECATED');
|
||||||
const Fastfs = require('../fastfs');
|
const Fastfs = require('../fastfs');
|
||||||
const ModuleCache = require('../ModuleCache');
|
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 Promise = require('promise');
|
||||||
const _ = require('underscore');
|
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({
|
const validateOpts = declareOpts({
|
||||||
roots: {
|
roots: {
|
||||||
|
@ -68,13 +70,18 @@ class DependencyGraph {
|
||||||
return this._loading;
|
return this._loading;
|
||||||
}
|
}
|
||||||
|
|
||||||
const modulePattern = new RegExp(
|
const crawlActivity = Activity.startEvent('fs crawl');
|
||||||
'\.(' + ['js', 'json'].concat(this._assetExts).join('|') + ')$'
|
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, {
|
this._fastfs = new Fastfs(this._opts.roots,this._opts.fileWatcher, {
|
||||||
pattern: modulePattern,
|
|
||||||
ignore: this._opts.ignoreFilePath,
|
ignore: this._opts.ignoreFilePath,
|
||||||
|
crawling: this._crawling,
|
||||||
});
|
});
|
||||||
|
|
||||||
this._fastfs.on('change', this._processFileChange.bind(this));
|
this._fastfs.on('change', this._processFileChange.bind(this));
|
||||||
|
@ -454,14 +461,10 @@ class DependencyGraph {
|
||||||
|
|
||||||
this._assetMap_DEPRECATED = Object.create(null);
|
this._assetMap_DEPRECATED = Object.create(null);
|
||||||
|
|
||||||
const pattern = new RegExp(
|
|
||||||
'\.(' + this._opts.assetExts.join('|') + ')$'
|
|
||||||
);
|
|
||||||
|
|
||||||
const fastfs = new Fastfs(
|
const fastfs = new Fastfs(
|
||||||
this._opts.assetRoots_DEPRECATED,
|
this._opts.assetRoots_DEPRECATED,
|
||||||
this._opts.fileWatcher,
|
this._opts.fileWatcher,
|
||||||
{ pattern, ignore: this._opts.ignoreFilePath }
|
{ ignore: this._opts.ignoreFilePath, crawling: this._crawling }
|
||||||
);
|
);
|
||||||
|
|
||||||
fastfs.on('change', this._processAssetChange_DEPRECATED.bind(this));
|
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 {EventEmitter} = require('events');
|
||||||
|
|
||||||
const _ = require('underscore');
|
const _ = require('underscore');
|
||||||
const debug = require('debug')('DependencyGraph');
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
const readDir = Promise.denodeify(fs.readdir);
|
|
||||||
const readFile = Promise.denodeify(fs.readFile);
|
const readFile = Promise.denodeify(fs.readFile);
|
||||||
const stat = Promise.denodeify(fs.stat);
|
const stat = Promise.denodeify(fs.stat);
|
||||||
|
|
||||||
const hasOwn = Object.prototype.hasOwnProperty;
|
const hasOwn = Object.prototype.hasOwnProperty;
|
||||||
|
|
||||||
class Fastfs extends EventEmitter {
|
class Fastfs extends EventEmitter {
|
||||||
constructor(roots, fileWatcher, {ignore, pattern}) {
|
constructor(roots, fileWatcher, {ignore, crawling}) {
|
||||||
super();
|
super();
|
||||||
this._fileWatcher = fileWatcher;
|
this._fileWatcher = fileWatcher;
|
||||||
this._ignore = ignore;
|
this._ignore = ignore;
|
||||||
this._pattern = pattern;
|
|
||||||
this._roots = roots.map(root => new File(root, { isDir: true }));
|
this._roots = roots.map(root => new File(root, { isDir: true }));
|
||||||
this._fastPaths = Object.create(null);
|
this._fastPaths = Object.create(null);
|
||||||
|
this._crawling = crawling;
|
||||||
}
|
}
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
const queue = this._roots.slice();
|
const rootsPattern = new RegExp(
|
||||||
return this._search(queue).then(() => {
|
'^(' + 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));
|
this._fileWatcher.on('all', this._processFileChange.bind(this));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -134,32 +152,6 @@ class Fastfs extends EventEmitter {
|
||||||
this._getAndAssertRoot(file.path).addChild(file);
|
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) {
|
_processFileChange(type, filePath, root, fstat) {
|
||||||
const absPath = path.join(root, filePath);
|
const absPath = path.join(root, filePath);
|
||||||
|
@ -182,10 +174,7 @@ class Fastfs extends EventEmitter {
|
||||||
delete this._fastPaths[path.normalize(absPath)];
|
delete this._fastPaths[path.normalize(absPath)];
|
||||||
|
|
||||||
if (type !== 'delete') {
|
if (type !== 'delete') {
|
||||||
this._add(new File(absPath, {
|
this._add(new File(absPath, { isDir: false }));
|
||||||
isDir: false,
|
|
||||||
fstat
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.emit('change', type, filePath, root, fstat);
|
this.emit('change', type, filePath, root, fstat);
|
||||||
|
@ -193,16 +182,12 @@ class Fastfs extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
class File {
|
class File {
|
||||||
constructor(filePath, {isDir, fstat}) {
|
constructor(filePath, { isDir }) {
|
||||||
this.path = filePath;
|
this.path = filePath;
|
||||||
this.isDir = Boolean(isDir);
|
this.isDir = Boolean(isDir);
|
||||||
if (this.isDir) {
|
if (this.isDir) {
|
||||||
this.children = Object.create(null);
|
this.children = Object.create(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fstat) {
|
|
||||||
this._stat = Promise.resolve(fstat);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
read() {
|
read() {
|
||||||
|
@ -290,21 +275,8 @@ function isDescendant(root, child) {
|
||||||
return path.relative(root, child).indexOf('..') !== 0;
|
return path.relative(root, child).indexOf('..') !== 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function readAndStatDir(dir) {
|
function escapeRegExp(str) {
|
||||||
return readDir(dir)
|
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
|
||||||
.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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Fastfs;
|
module.exports = Fastfs;
|
||||||
|
|
|
@ -1,2 +1,5 @@
|
||||||
/* eslint global-strict:0 */
|
/* eslint global-strict:0 */
|
||||||
__DEV__ = false;
|
__DEV__ = false;
|
||||||
|
|
||||||
|
/* global __BUNDLE_START_TIME__:true */
|
||||||
|
__BUNDLE_START_TIME__ = Date.now();
|
||||||
|
|
|
@ -1,2 +1,5 @@
|
||||||
/* eslint global-strict:0 */
|
/* eslint global-strict:0 */
|
||||||
__DEV__ = true;
|
__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() {
|
pit('it should get the watcher instance when ready', function() {
|
||||||
var fileWatcher = new FileWatcher(['rootDir']);
|
var fileWatcher = new FileWatcher(['rootDir']);
|
||||||
return fileWatcher._loading.then(function(watchers) {
|
return fileWatcher.getWatchers().then(function(watchers) {
|
||||||
watchers.forEach(function(watcher) {
|
watchers.forEach(function(watcher) {
|
||||||
expect(watcher instanceof Watcher).toBe(true);
|
expect(watcher instanceof Watcher).toBe(true);
|
||||||
});
|
});
|
||||||
|
@ -48,7 +48,7 @@ describe('FileWatcher', function() {
|
||||||
var fileWatcher = new FileWatcher(['rootDir']);
|
var fileWatcher = new FileWatcher(['rootDir']);
|
||||||
var handler = jest.genMockFn();
|
var handler = jest.genMockFn();
|
||||||
fileWatcher.on('all', handler);
|
fileWatcher.on('all', handler);
|
||||||
return fileWatcher._loading.then(function(){
|
return fileWatcher.getWatchers().then(function(){
|
||||||
cb(1, 2, 3, 4);
|
cb(1, 2, 3, 4);
|
||||||
jest.runAllTimers();
|
jest.runAllTimers();
|
||||||
expect(handler.mock.calls[0]).toEqual([1, 2, 3, 4]);
|
expect(handler.mock.calls[0]).toEqual([1, 2, 3, 4]);
|
||||||
|
|
|
@ -8,13 +8,14 @@
|
||||||
*/
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var EventEmitter = require('events').EventEmitter;
|
const EventEmitter = require('events').EventEmitter;
|
||||||
var sane = require('sane');
|
const sane = require('sane');
|
||||||
var Promise = require('promise');
|
const Promise = require('promise');
|
||||||
var util = require('util');
|
const exec = require('child_process').exec;
|
||||||
var 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) {
|
exec('which watchman', function(err, out) {
|
||||||
if (err || out.length === 0) {
|
if (err || out.length === 0) {
|
||||||
resolve(sane.NodeWatcher);
|
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
|
constructor(rootConfigs) {
|
||||||
var fileWatcher = null;
|
if (inited) {
|
||||||
|
throw new Error('FileWatcher can only be instantiated once');
|
||||||
|
}
|
||||||
|
inited = true;
|
||||||
|
|
||||||
function FileWatcher(rootConfigs) {
|
super();
|
||||||
if (fileWatcher) {
|
this._watcherByRoot = Object.create(null);
|
||||||
// This allows us to optimize watching in the future by merging roots etc.
|
|
||||||
throw new Error('FileWatcher can only be instantiated once');
|
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(
|
getWatcherForRoot(root) {
|
||||||
rootConfigs.map(createWatcher)
|
return this._loading.then(() => this._watcherByRoot[root]);
|
||||||
).then(function(watchers) {
|
}
|
||||||
watchers.forEach(function(watcher) {
|
|
||||||
watcher.on('all', function(type, filepath, root, stat) {
|
isWatchman() {
|
||||||
fileWatcher.emit('all', type, filepath, root, stat);
|
return detectingWatcherClass.then(
|
||||||
});
|
Watcher => Watcher === sane.WatchmanWatcher
|
||||||
});
|
);
|
||||||
return watchers;
|
}
|
||||||
});
|
|
||||||
this._loading.done();
|
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) {
|
function createWatcher(rootConfig) {
|
||||||
return detectingWatcherClass.then(function(Watcher) {
|
return detectingWatcherClass.then(function(Watcher) {
|
||||||
var watcher = new Watcher(rootConfig.dir, {
|
const watcher = new Watcher(rootConfig.dir, {
|
||||||
glob: rootConfig.globs,
|
glob: rootConfig.globs,
|
||||||
dot: false,
|
dot: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
var rejectTimeout = setTimeout(function() {
|
const rejectTimeout = setTimeout(function() {
|
||||||
reject(new Error([
|
reject(new Error([
|
||||||
'Watcher took too long to load',
|
'Watcher took too long to load',
|
||||||
'Try running `watchman version` from your terminal',
|
'Try running `watchman version` from your terminal',
|
||||||
|
@ -86,10 +110,4 @@ function createWatcher(rootConfig) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
FileWatcher.createDummyWatcher = function() {
|
module.exports = FileWatcher;
|
||||||
var ev = new EventEmitter();
|
|
||||||
ev.end = function() {
|
|
||||||
return Promise.resolve();
|
|
||||||
};
|
|
||||||
return ev;
|
|
||||||
};
|
|
||||||
|
|
Loading…
Reference in New Issue