512 строки
13 KiB
Plaintext
512 строки
13 KiB
Plaintext
/*
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
#import "RCTDevSettings.h"
|
|
|
|
#import <objc/runtime.h>
|
|
|
|
#import <FBReactNativeSpec/FBReactNativeSpec.h>
|
|
#import <React/RCTBridge+Private.h>
|
|
#import <React/RCTBridgeModule.h>
|
|
#import <React/RCTEventDispatcher.h>
|
|
#import <React/RCTLog.h>
|
|
#import <React/RCTProfile.h>
|
|
#import <React/RCTReloadCommand.h>
|
|
#import <React/RCTUtils.h>
|
|
|
|
#import <React/RCTDevMenu.h>
|
|
|
|
#import "CoreModulesPlugins.h"
|
|
|
|
static NSString *const kRCTDevSettingProfilingEnabled = @"profilingEnabled";
|
|
static NSString *const kRCTDevSettingHotLoadingEnabled = @"hotLoadingEnabled";
|
|
static NSString *const kRCTDevSettingIsInspectorShown = @"showInspector";
|
|
static NSString *const kRCTDevSettingIsDebuggingRemotely = @"isDebuggingRemotely";
|
|
static NSString *const kRCTDevSettingExecutorOverrideClass = @"executor-override";
|
|
static NSString *const kRCTDevSettingShakeToShowDevMenu = @"shakeToShow";
|
|
static NSString *const kRCTDevSettingIsPerfMonitorShown = @"RCTPerfMonitorKey";
|
|
|
|
static NSString *const kRCTDevSettingsUserDefaultsKey = @"RCTDevMenu";
|
|
|
|
#if ENABLE_PACKAGER_CONNECTION
|
|
#import <React/RCTPackagerClient.h>
|
|
#import <React/RCTPackagerConnection.h>
|
|
#endif
|
|
|
|
#if RCT_ENABLE_INSPECTOR
|
|
#import <React/RCTInspectorDevServerHelper.h>
|
|
#endif
|
|
|
|
#if RCT_DEV
|
|
static BOOL devSettingsMenuEnabled = YES;
|
|
#else
|
|
static BOOL devSettingsMenuEnabled = NO;
|
|
#endif
|
|
|
|
void RCTDevSettingsSetEnabled(BOOL enabled) {
|
|
devSettingsMenuEnabled = enabled;
|
|
}
|
|
|
|
#if RCT_DEV_MENU
|
|
|
|
@interface RCTDevSettingsUserDefaultsDataSource : NSObject <RCTDevSettingsDataSource>
|
|
|
|
@end
|
|
|
|
@implementation RCTDevSettingsUserDefaultsDataSource {
|
|
NSMutableDictionary *_settings;
|
|
NSUserDefaults *_userDefaults;
|
|
}
|
|
|
|
- (instancetype)init
|
|
{
|
|
return [self initWithDefaultValues:nil];
|
|
}
|
|
|
|
- (instancetype)initWithDefaultValues:(NSDictionary *)defaultValues
|
|
{
|
|
if (self = [super init]) {
|
|
_userDefaults = [NSUserDefaults standardUserDefaults];
|
|
if (defaultValues) {
|
|
[self _reloadWithDefaults:defaultValues];
|
|
}
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)updateSettingWithValue:(id)value forKey:(NSString *)key
|
|
{
|
|
RCTAssert((key != nil), @"%@", [NSString stringWithFormat:@"%@: Tried to update nil key", [self class]]);
|
|
|
|
id currentValue = [self settingForKey:key];
|
|
if (currentValue == value || [currentValue isEqual:value]) {
|
|
return;
|
|
}
|
|
if (value) {
|
|
_settings[key] = value;
|
|
} else {
|
|
[_settings removeObjectForKey:key];
|
|
}
|
|
[_userDefaults setObject:_settings forKey:kRCTDevSettingsUserDefaultsKey];
|
|
}
|
|
|
|
- (id)settingForKey:(NSString *)key
|
|
{
|
|
return _settings[key];
|
|
}
|
|
|
|
- (void)_reloadWithDefaults:(NSDictionary *)defaultValues
|
|
{
|
|
NSDictionary *existingSettings = [_userDefaults objectForKey:kRCTDevSettingsUserDefaultsKey];
|
|
_settings = existingSettings ? [existingSettings mutableCopy] : [NSMutableDictionary dictionary];
|
|
for (NSString *key in [defaultValues keyEnumerator]) {
|
|
if (!_settings[key]) {
|
|
_settings[key] = defaultValues[key];
|
|
}
|
|
}
|
|
[_userDefaults setObject:_settings forKey:kRCTDevSettingsUserDefaultsKey];
|
|
}
|
|
|
|
@end
|
|
|
|
@interface RCTDevSettings () <RCTBridgeModule, RCTInvalidating> {
|
|
BOOL _isJSLoaded;
|
|
#if ENABLE_PACKAGER_CONNECTION
|
|
RCTHandlerToken _reloadToken;
|
|
#endif
|
|
}
|
|
|
|
@property (nonatomic, strong) Class executorClass;
|
|
@property (nonatomic, readwrite, strong) id<RCTDevSettingsDataSource> dataSource;
|
|
|
|
@end
|
|
|
|
@implementation RCTDevSettings
|
|
|
|
RCT_EXPORT_MODULE()
|
|
|
|
+ (BOOL)requiresMainQueueSetup
|
|
{
|
|
return YES; // RCT_DEV-only
|
|
}
|
|
|
|
- (instancetype)init
|
|
{
|
|
// default behavior is to use NSUserDefaults
|
|
NSDictionary *defaultValues = @{
|
|
kRCTDevSettingShakeToShowDevMenu : @YES,
|
|
kRCTDevSettingHotLoadingEnabled : @YES,
|
|
};
|
|
RCTDevSettingsUserDefaultsDataSource *dataSource =
|
|
[[RCTDevSettingsUserDefaultsDataSource alloc] initWithDefaultValues:defaultValues];
|
|
return [self initWithDataSource:dataSource];
|
|
}
|
|
|
|
- (instancetype)initWithDataSource:(id<RCTDevSettingsDataSource>)dataSource
|
|
{
|
|
if (self = [super init]) {
|
|
_dataSource = dataSource;
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(jsLoaded:)
|
|
name:RCTJavaScriptDidLoadNotification
|
|
object:nil];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)setBridge:(RCTBridge *)bridge
|
|
{
|
|
[super setBridge:bridge];
|
|
|
|
#if ENABLE_PACKAGER_CONNECTION
|
|
RCTBridge *__weak weakBridge = bridge;
|
|
_reloadToken = [[RCTPackagerConnection sharedPackagerConnection]
|
|
addNotificationHandler:^(id params) {
|
|
if (params != (id)kCFNull && [params[@"debug"] boolValue]) {
|
|
weakBridge.executorClass = objc_lookUpClass("RCTWebSocketExecutor");
|
|
}
|
|
RCTTriggerReloadCommandListeners(@"Global hotkey");
|
|
}
|
|
queue:dispatch_get_main_queue()
|
|
forMethod:@"reload"];
|
|
#endif
|
|
|
|
#if RCT_ENABLE_INSPECTOR && !TARGET_OS_UIKITFORMAC
|
|
// we need this dispatch back to the main thread because even though this
|
|
// is executed on the main thread, at this point the bridge is not yet
|
|
// finished with its initialisation. But it does finish by the time it
|
|
// relinquishes control of the main thread, so only queue on the JS thread
|
|
// after the current main thread operation is done.
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[bridge
|
|
dispatchBlock:^{
|
|
[RCTInspectorDevServerHelper connectWithBundleURL:bridge.bundleURL];
|
|
}
|
|
queue:RCTJSThread];
|
|
});
|
|
#endif
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[self _synchronizeAllSettings];
|
|
});
|
|
}
|
|
|
|
- (dispatch_queue_t)methodQueue
|
|
{
|
|
return dispatch_get_main_queue();
|
|
}
|
|
|
|
- (void)invalidate
|
|
{
|
|
#if ENABLE_PACKAGER_CONNECTION
|
|
[[RCTPackagerConnection sharedPackagerConnection] removeHandler:_reloadToken];
|
|
#endif
|
|
}
|
|
|
|
- (NSArray<NSString *> *)supportedEvents
|
|
{
|
|
return @[@"didPressMenuItem"];
|
|
}
|
|
|
|
- (void)_updateSettingWithValue:(id)value forKey:(NSString *)key
|
|
{
|
|
[_dataSource updateSettingWithValue:value forKey:key];
|
|
}
|
|
|
|
- (id)settingForKey:(NSString *)key
|
|
{
|
|
return [_dataSource settingForKey:key];
|
|
}
|
|
|
|
- (BOOL)isNuclideDebuggingAvailable
|
|
{
|
|
#if RCT_ENABLE_INSPECTOR
|
|
return self.bridge.isInspectable;
|
|
#else
|
|
return false;
|
|
#endif // RCT_ENABLE_INSPECTOR
|
|
}
|
|
|
|
- (BOOL)isRemoteDebuggingAvailable
|
|
{
|
|
if (RCTTurboModuleEnabled()) {
|
|
return NO;
|
|
}
|
|
Class jsDebuggingExecutorClass = objc_lookUpClass("RCTWebSocketExecutor");
|
|
return (jsDebuggingExecutorClass != nil);
|
|
}
|
|
|
|
- (BOOL)isHotLoadingAvailable
|
|
{
|
|
return self.bridge.bundleURL && !self.bridge.bundleURL.fileURL; // Only works when running from server
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(reload)
|
|
{
|
|
RCTTriggerReloadCommandListeners(@"Unknown From JS");
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(reloadWithReason : (NSString *) reason)
|
|
{
|
|
RCTTriggerReloadCommandListeners(reason);
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(onFastRefresh)
|
|
{
|
|
[self.bridge onFastRefresh];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(setIsShakeToShowDevMenuEnabled : (BOOL)enabled)
|
|
{
|
|
[self _updateSettingWithValue:@(enabled) forKey:kRCTDevSettingShakeToShowDevMenu];
|
|
}
|
|
|
|
- (BOOL)isShakeToShowDevMenuEnabled
|
|
{
|
|
return [[self settingForKey:kRCTDevSettingShakeToShowDevMenu] boolValue];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(setIsDebuggingRemotely : (BOOL)enabled)
|
|
{
|
|
[self _updateSettingWithValue:@(enabled) forKey:kRCTDevSettingIsDebuggingRemotely];
|
|
[self _remoteDebugSettingDidChange];
|
|
}
|
|
|
|
- (BOOL)isDebuggingRemotely
|
|
{
|
|
return [[self settingForKey:kRCTDevSettingIsDebuggingRemotely] boolValue];
|
|
}
|
|
|
|
- (void)_remoteDebugSettingDidChange
|
|
{
|
|
// This value is passed as a command-line argument, so fall back to reading from NSUserDefaults directly
|
|
NSString *executorOverride = [[NSUserDefaults standardUserDefaults] stringForKey:kRCTDevSettingExecutorOverrideClass];
|
|
Class executorOverrideClass = executorOverride ? NSClassFromString(executorOverride) : nil;
|
|
if (executorOverrideClass) {
|
|
self.executorClass = executorOverrideClass;
|
|
} else {
|
|
BOOL enabled = self.isRemoteDebuggingAvailable && self.isDebuggingRemotely;
|
|
self.executorClass = enabled ? objc_getClass("RCTWebSocketExecutor") : nil;
|
|
}
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(setProfilingEnabled : (BOOL)enabled)
|
|
{
|
|
[self _updateSettingWithValue:@(enabled) forKey:kRCTDevSettingProfilingEnabled];
|
|
[self _profilingSettingDidChange];
|
|
}
|
|
|
|
- (BOOL)isProfilingEnabled
|
|
{
|
|
return [[self settingForKey:kRCTDevSettingProfilingEnabled] boolValue];
|
|
}
|
|
|
|
- (void)_profilingSettingDidChange
|
|
{
|
|
BOOL enabled = self.isProfilingEnabled;
|
|
if (self.isHotLoadingAvailable && enabled != RCTProfileIsProfiling()) {
|
|
if (enabled) {
|
|
[self.bridge startProfiling];
|
|
} else {
|
|
[self.bridge stopProfiling:^(NSData *logData) {
|
|
RCTProfileSendResult(self.bridge, @"systrace", logData);
|
|
}];
|
|
}
|
|
}
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(setHotLoadingEnabled : (BOOL)enabled)
|
|
{
|
|
if (self.isHotLoadingEnabled != enabled) {
|
|
[self _updateSettingWithValue:@(enabled) forKey:kRCTDevSettingHotLoadingEnabled];
|
|
if (_isJSLoaded) {
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
if (enabled) {
|
|
[self.bridge enqueueJSCall:@"HMRClient" method:@"enable" args:@[] completion:NULL];
|
|
} else {
|
|
[self.bridge enqueueJSCall:@"HMRClient" method:@"disable" args:@[] completion:NULL];
|
|
}
|
|
#pragma clang diagnostic pop
|
|
}
|
|
}
|
|
}
|
|
|
|
- (BOOL)isHotLoadingEnabled
|
|
{
|
|
return [[self settingForKey:kRCTDevSettingHotLoadingEnabled] boolValue];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(toggleElementInspector)
|
|
{
|
|
BOOL value = [[self settingForKey:kRCTDevSettingIsInspectorShown] boolValue];
|
|
[self _updateSettingWithValue:@(!value) forKey:kRCTDevSettingIsInspectorShown];
|
|
|
|
if (_isJSLoaded) {
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
[self.bridge.eventDispatcher sendDeviceEventWithName:@"toggleElementInspector" body:nil];
|
|
#pragma clang diagnostic pop
|
|
}
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(addMenuItem:(NSString *)title)
|
|
{
|
|
__weak __typeof(self) weakSelf = self;
|
|
[self.bridge.devMenu addItem:[RCTDevMenuItem buttonItemWithTitle:title handler:^{
|
|
[weakSelf sendEventWithName:@"didPressMenuItem" body:@{@"title": title}];
|
|
}]];
|
|
}
|
|
|
|
- (BOOL)isElementInspectorShown
|
|
{
|
|
return [[self settingForKey:kRCTDevSettingIsInspectorShown] boolValue];
|
|
}
|
|
|
|
- (void)setIsPerfMonitorShown:(BOOL)isPerfMonitorShown
|
|
{
|
|
[self _updateSettingWithValue:@(isPerfMonitorShown) forKey:kRCTDevSettingIsPerfMonitorShown];
|
|
}
|
|
|
|
- (BOOL)isPerfMonitorShown
|
|
{
|
|
return [[self settingForKey:kRCTDevSettingIsPerfMonitorShown] boolValue];
|
|
}
|
|
|
|
- (void)setExecutorClass:(Class)executorClass
|
|
{
|
|
_executorClass = executorClass;
|
|
if (self.bridge.executorClass != executorClass) {
|
|
// TODO (6929129): we can remove this special case test once we have better
|
|
// support for custom executors in the dev menu. But right now this is
|
|
// needed to prevent overriding a custom executor with the default if a
|
|
// custom executor has been set directly on the bridge
|
|
if (executorClass == Nil && self.bridge.executorClass != objc_lookUpClass("RCTWebSocketExecutor")) {
|
|
return;
|
|
}
|
|
|
|
self.bridge.executorClass = executorClass;
|
|
RCTTriggerReloadCommandListeners(@"Custom executor class reset");
|
|
}
|
|
}
|
|
|
|
#if RCT_DEV_MENU
|
|
|
|
- (void)addHandler:(id<RCTPackagerClientMethod>)handler forPackagerMethod:(NSString *)name
|
|
{
|
|
#if ENABLE_PACKAGER_CONNECTION
|
|
[[RCTPackagerConnection sharedPackagerConnection] addHandler:handler forMethod:name];
|
|
#endif
|
|
}
|
|
|
|
#endif
|
|
|
|
#pragma mark - Internal
|
|
|
|
/**
|
|
* Query the data source for all possible settings and make sure we're doing the right
|
|
* thing for the state of each setting.
|
|
*/
|
|
- (void)_synchronizeAllSettings
|
|
{
|
|
[self _remoteDebugSettingDidChange];
|
|
[self _profilingSettingDidChange];
|
|
}
|
|
|
|
- (void)jsLoaded:(NSNotification *)notification
|
|
{
|
|
if (notification.userInfo[@"bridge"] != self.bridge) {
|
|
return;
|
|
}
|
|
|
|
_isJSLoaded = YES;
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
// update state again after the bridge has finished loading
|
|
[self _synchronizeAllSettings];
|
|
|
|
// Inspector can only be shown after JS has loaded
|
|
if ([self isElementInspectorShown]) {
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
[self.bridge.eventDispatcher sendDeviceEventWithName:@"toggleElementInspector" body:nil];
|
|
#pragma clang diagnostic pop
|
|
}
|
|
});
|
|
}
|
|
|
|
@end
|
|
|
|
#else // #if RCT_DEV
|
|
|
|
@implementation RCTDevSettings
|
|
|
|
- (instancetype)initWithDataSource:(id<RCTDevSettingsDataSource>)dataSource
|
|
{
|
|
return [super init];
|
|
}
|
|
- (BOOL)isHotLoadingAvailable
|
|
{
|
|
return NO;
|
|
}
|
|
- (BOOL)isRemoteDebuggingAvailable
|
|
{
|
|
return NO;
|
|
}
|
|
- (id)settingForKey:(NSString *)key
|
|
{
|
|
return nil;
|
|
}
|
|
- (void)reload
|
|
{
|
|
}
|
|
- (void)reloadWithReason:(NSString *)reason
|
|
{
|
|
}
|
|
- (void)onFastRefresh
|
|
{
|
|
}
|
|
- (void)setHotLoadingEnabled:(BOOL)isHotLoadingEnabled
|
|
{
|
|
}
|
|
- (void)setIsDebuggingRemotely:(BOOL)isDebuggingRemotelyEnabled
|
|
{
|
|
}
|
|
- (void)setProfilingEnabled:(BOOL)isProfilingEnabled
|
|
{
|
|
}
|
|
- (void)toggleElementInspector
|
|
{
|
|
}
|
|
- (void)addMenuItem:(NSString *)title
|
|
{
|
|
}
|
|
- (void)setIsShakeToShowDevMenuEnabled:(BOOL)enabled
|
|
{
|
|
}
|
|
|
|
@end
|
|
|
|
#endif
|
|
|
|
@implementation RCTBridge (RCTDevSettings)
|
|
|
|
- (RCTDevSettings *)devSettings
|
|
{
|
|
#if RCT_DEV_MENU
|
|
return devSettingsMenuEnabled ? [self moduleForClass:[RCTDevSettings class]] : nil;
|
|
#else
|
|
return nil;
|
|
#endif
|
|
}
|
|
|
|
@end
|
|
|
|
Class RCTDevSettingsCls(void) {
|
|
return RCTDevSettings.class;
|
|
}
|