Browse files

Add RCTDevSettings module

This decouples non-UI logic from RCTDevMenu into a new module RCTDevSettings.

**Motivation**: This allows developers to change dev settings without depending on the built-in dev menu, e.g. if they want to introduce their own UI, or have other devtools logic that doesn't depend on an action sheet.

It also introduces the RCTDevSettingsDataSource protocol for storing dev tools preferences. This could allow a developer to implement alternative behaviors, e.g. loading the settings from some other config, changing settings based on the user, deciding not to persist some settings, or something else.

The included data source implementation, RCTDevSettingsUserDefaultsDataSource, uses NSUserDefaults and is backwards compatible with the older implementation, so **no workflows or dependent code will break, and old saved settings will persist.**

The RCTDevMenu interface has not changed and is therefore also backwards-compatible, though
some methods are now deprecated.

In order to ensure that RCTDevSettings
Closes #11613

Reviewed By: mmmulani

Differential Revision: D4571773

Pulled By: javache

fbshipit-source-id: 25555d0a6eaa81f694343e079ed02439e5845fbc
  • Loading branch information...
terribleben authored and facebook-github-bot committed Feb 24, 2017
1 parent bb1f851 commit 6a14f0b44942874cb72d1535a2df86fb75c3e2be
@@ -56,6 +56,7 @@ - (void)setUp
if (!_url) {
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
// TODO t16297016: this seems to be unused, remove?
NSInteger port = [standardDefaults integerForKey:@"websocket-executor-port"];
if (!port) {
port = [[[_bridge bundleURL] port] integerValue] ?: 8081;
@@ -23,7 +23,7 @@
#import <React/RCTCxxModule.h>
#import <React/RCTCxxUtils.h>
#import <React/RCTDevLoadingView.h>
#import <React/RCTDevMenu.h>
#import <React/RCTDevSettings.h>
#import <React/RCTDisplayLink.h>
#import <React/RCTJavaScriptLoader.h>
#import <React/RCTLog.h>
@@ -403,7 +403,7 @@ - (void)start
executorFactory.reset(new JSCExecutorFactory("", folly::dynamic::object
("UseCustomJSC", (bool)useCustomJSC)
("StartSamplingProfilerOnInit", (bool)self.devMenu.startSamplingProfilerOnLaunch)
("StartSamplingProfilerOnInit", (bool)self.devSettings.startSamplingProfilerOnLaunch)
} else {
@@ -25,6 +25,7 @@
#import "RCTBridge+Private.h"
#import "RCTDefines.h"
#import "RCTDevMenu.h"
#import "RCTDevSettings.h"
#import "RCTJSCErrorHandling.h"
#import "RCTJSCProfiler.h"
#import "RCTJavaScriptLoader.h"
@@ -38,8 +39,6 @@
RCT_EXTERN NSString *const RCTFBJSContextClassKey = @"_RCTFBJSContextClassKey";
RCT_EXTERN NSString *const RCTFBJSValueClassKey = @"_RCTFBJSValueClassKey";
static NSString *const RCTJSCProfilerEnabledDefaultsKey = @"RCTJSCProfilerEnabled";
struct __attribute__((packed)) ModuleData {
uint32_t offset;
uint32_t size;
@@ -168,15 +167,21 @@ @implementation RCTJSCExecutor
static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context)
__weak RCTBridge *weakBridge = bridge;
__weak RCTDevSettings *devSettings = bridge.devSettings;
if (RCTJSCProfilerIsSupported()) {
[bridge.devMenu addItem:[RCTDevMenuItem toggleItemWithKey:RCTJSCProfilerEnabledDefaultsKey title:@"Start Profiling" selectedTitle:@"Stop Profiling" handler:^(BOOL shouldStart) {
[bridge.devMenu addItem:[RCTDevMenuItem buttonItemWithTitleBlock:^NSString *{
return devSettings.isJSCProfilingEnabled ? @"Stop Profiling" : @"Start Profiling";
} handler:^{
BOOL shouldStart = !devSettings.isJSCProfilingEnabled;
devSettings.isJSCProfilingEnabled = shouldStart;
if (shouldStart != RCTJSCProfilerIsProfiling(context)) {
if (shouldStart) {
} else {
NSString *outputFile = RCTJSCProfilerStop(context);
NSData *profileData = [NSData dataWithContentsOfFile:outputFile options:NSDataReadingMappedIfSafe error:NULL];
RCTProfileSendResult(bridge, @"cpu-profile", profileData);
RCTProfileSendResult(weakBridge, @"cpu-profile", profileData);
@@ -309,7 +314,7 @@ - (void)setUp
} else {
if (self->_useCustomJSCLibrary) {
JSC_configureJSCForIOS(true, RCTJSONStringify(@{
@"StartSamplingProfilerOnInit": @(self->_bridge.devMenu.startSamplingProfilerOnLaunch)
@"StartSamplingProfilerOnInit": @(self->_bridge.devSettings.startSamplingProfilerOnLaunch)
}, NULL).UTF8String);
contextRef = JSC_JSGlobalContextCreateInGroup(self->_useCustomJSCLibrary, nullptr, nullptr);
@@ -401,16 +406,7 @@ - (void)setUp
if (!strongSelf.valid || !weakContext) {
// JSPokeSamplingProfiler() toggles the profiling process
JSGlobalContextRef ctx = weakContext.JSGlobalContextRef;
JSValueRef jsResult = JSC_JSPokeSamplingProfiler(ctx);
if (JSC_JSValueGetType(ctx, jsResult) != kJSTypeNull) {
NSString *results = [[JSC_JSValue(ctx) valueWithJSValueRef:jsResult inContext:weakContext] toObject];
JSCSamplingProfiler *profilerModule = [strongSelf->_bridge moduleForClass:[JSCSamplingProfiler class]];
[profilerModule operationCompletedWithResults:results];
[weakSelf.bridge.devSettings toggleJSCSamplingProfiler];
// Allow for the profiler to be poked from JS code as well
@@ -20,59 +20,44 @@
@interface RCTDevMenu : NSObject
* Is the menu enabled. The menu is enabled by default if RCT_DEV=1, but
* you may wish to disable it so that you can provide your own shake handler.
* Deprecated, use RCTDevSettings instead.
@property (nonatomic, assign) BOOL shakeToShow;
@property (nonatomic, assign) BOOL shakeToShow DEPRECATED_ATTRIBUTE;
* Enables performance profiling.
* Deprecated, use RCTDevSettings instead.
@property (nonatomic, assign) BOOL profilingEnabled;
@property (nonatomic, assign) BOOL profilingEnabled DEPRECATED_ATTRIBUTE;
* Enables starting of profiling sampler on launch
* Deprecated, use RCTDevSettings instead.
@property (nonatomic, assign) BOOL startSamplingProfilerOnLaunch;
@property (nonatomic, assign) BOOL liveReloadEnabled DEPRECATED_ATTRIBUTE;
* Enables automatic polling for JS code changes. Only applicable when
* running the app from a server.
* Deprecated, use RCTDevSettings instead.
@property (nonatomic, assign) BOOL liveReloadEnabled;
* Enables hot loading. Currently not supported in open source.
@property (nonatomic, assign) BOOL hotLoadingEnabled;
* Shows the FPS monitor for the JS and Main threads.
@property (nonatomic, assign) BOOL showFPS;
@property (nonatomic, assign) BOOL hotLoadingEnabled DEPRECATED_ATTRIBUTE;
* Presented items in development menu
@property (nonatomic, copy, readonly) NSArray<RCTDevMenuItem *> *presentedItems;
* Detect if actions sheet (development menu) is shown
- (BOOL)isActionSheetShown;
* Manually show the dev menu (can be called from JS).
- (void)show;
* Manually reload the application. Equivalent to calling [bridge reload]
* directly, but can be called from JS.
* Deprecated, use -[RCTBRidge reload] instead.
- (void)reload;
* Deprecated. Use the `-addItem:` method instead.
@@ -88,6 +73,8 @@
typedef NSString *(^RCTDevMenuItemTitleBlock)(void);
* Developer menu item, used to expose additional functionality via the menu.
@@ -98,18 +85,16 @@
* action.
+ (instancetype)buttonItemWithTitle:(NSString *)title
* This creates an item with a toggle behavior. The key is used to store the
* state of the toggle. For toggle items, the handler will be called immediately
* after the item is added if the item was already selected when the module was
* last loaded.
* This creates an item with a simple push-button interface, used to trigger an
* action. getTitleForPresentation is called each time the item is about to be
* presented, and should return the item's title.
+ (instancetype)toggleItemWithKey:(NSString *)key
title:(NSString *)title
selectedTitle:(NSString *)selectedTitle
handler:(void(^)(BOOL selected))handler;
+ (instancetype)buttonItemWithTitleBlock:(RCTDevMenuItemTitleBlock)titleBlock
Oops, something went wrong.

0 comments on commit 6a14f0b

Please sign in to comment.