2015-03-26 00:33:54 +00:00
/ * *
* 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 "RCTDevMenu.h"
2015-06-15 14:53:45 +00:00
# import "RCTAssert.h"
2015-12-15 13:39:30 +00:00
# import "RCTBridge+Private.h"
2015-04-30 16:50:22 +00:00
# import "RCTDefines.h"
2015-05-26 18:16:25 +00:00
# import "RCTEventDispatcher.h"
2015-04-30 16:50:22 +00:00
# import "RCTKeyCommands.h"
2015-04-19 19:55:46 +00:00
# import "RCTLog.h"
2015-04-20 11:55:05 +00:00
# import "RCTProfile.h"
2015-03-26 00:33:54 +00:00
# import "RCTRootView.h"
2015-04-02 14:33:21 +00:00
# import "RCTSourceCode.h"
2015-04-19 19:55:46 +00:00
# import "RCTUtils.h"
2016-02-17 07:04:18 +00:00
# import "RCTWebSocketProxy.h"
2015-03-26 00:33:54 +00:00
2015-04-30 16:50:22 +00:00
# if RCT_DEV
2015-04-19 19:55:46 +00:00
static NSString * const RCTShowDevMenuNotification = @ "RCTShowDevMenuNotification" ;
2015-05-01 13:21:03 +00:00
static NSString * const RCTDevMenuSettingsKey = @ "RCTDevMenu" ;
2015-04-19 19:55:46 +00:00
@ implementation UIWindow ( RCTDevMenu )
2015-06-15 14:53:45 +00:00
- ( void ) RCT_motionEnded : ( __unused UIEventSubtype ) motion withEvent : ( UIEvent * ) event
2015-04-19 19:55:46 +00:00
{
if ( event . subtype = = UIEventSubtypeMotionShake ) {
[ [ NSNotificationCenter defaultCenter ] postNotificationName : RCTShowDevMenuNotification object : nil ] ;
}
}
@ end
2015-09-14 16:34:33 +00:00
typedef NS_ENUM ( NSInteger , RCTDevMenuType ) {
RCTDevMenuTypeButton ,
RCTDevMenuTypeToggle
} ;
2015-06-03 23:40:14 +00:00
2015-09-14 16:34:33 +00:00
@ interface RCTDevMenuItem ( )
2015-06-03 23:40:14 +00:00
2015-09-14 16:34:33 +00:00
@ property ( nonatomic , assign , readonly ) RCTDevMenuType type ;
@ property ( nonatomic , copy , readonly ) NSString * key ;
@ property ( nonatomic , copy , readonly ) NSString * title ;
@ property ( nonatomic , copy , readonly ) NSString * selectedTitle ;
@ property ( nonatomic , copy ) id value ;
2015-06-03 23:40:14 +00:00
@ end
@ implementation RCTDevMenuItem
2015-09-14 16:34:33 +00:00
{
id _handler ; // block
}
2015-06-03 23:40:14 +00:00
2015-09-14 16:34:33 +00:00
- ( instancetype ) initWithType : ( RCTDevMenuType ) type
key : ( NSString * ) key
title : ( NSString * ) title
selectedTitle : ( NSString * ) selectedTitle
handler : ( id / * block * / ) handler
2015-06-03 23:40:14 +00:00
{
2015-06-15 14:53:45 +00:00
if ( ( self = [ super init ] ) ) {
2015-09-14 16:34:33 +00:00
_type = type ;
_key = [ key copy ] ;
2015-06-15 14:53:45 +00:00
_title = [ title copy ] ;
2015-09-14 16:34:33 +00:00
_selectedTitle = [ selectedTitle copy ] ;
2015-06-15 14:53:45 +00:00
_handler = [ handler copy ] ;
2015-09-14 16:34:33 +00:00
_value = nil ;
2015-06-03 23:40:14 +00:00
}
return self ;
}
2015-08-24 10:14:33 +00:00
RCT_NOT _IMPLEMENTED ( - ( instancetype ) init )
2015-06-15 14:53:45 +00:00
2015-09-14 16:34:33 +00:00
+ ( instancetype ) buttonItemWithTitle : ( NSString * ) title
handler : ( void ( ^ ) ( void ) ) handler
{
return [ [ self alloc ] initWithType : RCTDevMenuTypeButton
key : nil
title : title
selectedTitle : nil
handler : handler ] ;
}
+ ( instancetype ) toggleItemWithKey : ( NSString * ) key
title : ( NSString * ) title
selectedTitle : ( NSString * ) selectedTitle
handler : ( void ( ^ ) ( BOOL selected ) ) handler
{
return [ [ self alloc ] initWithType : RCTDevMenuTypeToggle
key : key
title : title
selectedTitle : selectedTitle
handler : handler ] ;
}
- ( void ) callHandler
{
switch ( _type ) {
case RCTDevMenuTypeButton : {
if ( _handler ) {
( ( void ( ^ ) ( ) ) _handler ) ( ) ;
}
break ;
}
case RCTDevMenuTypeToggle : {
if ( _handler ) {
( ( void ( ^ ) ( BOOL selected ) ) _handler ) ( [ _value boolValue ] ) ;
}
break ;
}
}
}
2015-06-03 23:40:14 +00:00
@ end
2016-02-17 07:04:18 +00:00
@ interface RCTDevMenu ( ) < RCTBridgeModule , UIActionSheetDelegate , RCTInvalidating , RCTWebSocketProxyDelegate >
2015-05-01 13:21:03 +00:00
@ property ( nonatomic , strong ) Class executorClass ;
2015-03-26 00:33:54 +00:00
@ end
@ implementation RCTDevMenu
2015-04-11 22:08:00 +00:00
{
2015-04-19 19:55:46 +00:00
UIActionSheet * _actionSheet ;
2015-05-01 13:21:03 +00:00
NSUserDefaults * _defaults ;
NSMutableDictionary * _settings ;
NSURLSessionDataTask * _updateTask ;
NSURL * _liveReloadURL ;
BOOL _jsLoaded ;
2015-11-03 22:45:46 +00:00
NSArray < RCTDevMenuItem * > * _presentedItems ;
NSMutableArray < RCTDevMenuItem * > * _extraMenuItems ;
2015-11-06 22:52:43 +00:00
NSString * _webSocketExecutorName ;
NSString * _executorOverride ;
2015-04-11 22:08:00 +00:00
}
2015-03-26 00:33:54 +00:00
2015-04-19 19:55:46 +00:00
@ synthesize bridge = _bridge ;
RCT_EXPORT _MODULE ( )
+ ( void ) initialize
{
// We ' re swizzling here because it ' s poor form to override methods in a category ,
// however UIWindow doesn ' t actually implement motionEnded : withEvent : , so there ' s
// no need to call the original implementation .
RCTSwapInstanceMethods ( [ UIWindow class ] , @ selector ( motionEnded : withEvent : ) , @ selector ( RCT_motionEnded : withEvent : ) ) ;
}
- ( instancetype ) init
2015-03-26 00:33:54 +00:00
{
2015-04-19 19:55:46 +00:00
if ( ( self = [ super init ] ) ) {
2015-05-01 13:21:03 +00:00
NSNotificationCenter * notificationCenter = [ NSNotificationCenter defaultCenter ] ;
[ notificationCenter addObserver : self
selector : @ selector ( showOnShake )
name : RCTShowDevMenuNotification
object : nil ] ;
[ notificationCenter addObserver : self
2015-05-14 16:28:09 +00:00
selector : @ selector ( settingsDidChange )
2015-05-01 13:21:03 +00:00
name : NSUserDefaultsDidChangeNotification
object : nil ] ;
[ notificationCenter addObserver : self
2015-05-12 14:44:46 +00:00
selector : @ selector ( jsLoaded : )
2015-05-01 13:21:03 +00:00
name : RCTJavaScriptDidLoadNotification
object : nil ] ;
2015-04-30 16:50:22 +00:00
2015-05-07 10:53:35 +00:00
_defaults = [ NSUserDefaults standardUserDefaults ] ;
2015-09-14 16:34:33 +00:00
_settings = [ [ NSMutableDictionary alloc ] initWithDictionary : [ _defaults objectForKey : RCTDevMenuSettingsKey ] ] ;
_extraMenuItems = [ NSMutableArray new ] ;
__weak RCTDevMenu * weakSelf = self ;
[ _extraMenuItems addObject : [ RCTDevMenuItem toggleItemWithKey : @ "showInspector"
title : @ "Show Inspector"
selectedTitle : @ "Hide Inspector"
handler : ^ ( __unused BOOL enabled )
{
[ weakSelf . bridge . eventDispatcher sendDeviceEventWithName : @ "toggleElementInspector" body : nil ] ;
} ] ] ;
2015-04-30 16:50:22 +00:00
2015-11-06 22:52:43 +00:00
_webSocketExecutorName = [ _defaults objectForKey : @ "websocket-executor-name" ] ? : @ "Chrome" ;
static dispatch_once _t onceToken ;
dispatch_once ( & onceToken , ^ {
_executorOverride = [ _defaults objectForKey : @ "executor-override" ] ;
} ) ;
2015-05-07 10:53:35 +00:00
// Delay setup until after Bridge init
2015-09-14 16:34:33 +00:00
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
[ weakSelf updateSettings : _settings ] ;
2016-02-17 07:04:18 +00:00
[ weakSelf connectPackager ] ;
2015-09-14 16:34:33 +00:00
} ) ;
2015-05-07 10:53:35 +00:00
# if TARGET_IPHONE _SIMULATOR
2015-04-30 16:50:22 +00:00
RCTKeyCommands * commands = [ RCTKeyCommands sharedInstance ] ;
2015-05-07 10:53:35 +00:00
// Toggle debug menu
2015-05-01 13:21:03 +00:00
[ commands registerKeyCommandWithInput : @ "d"
2015-04-30 16:50:22 +00:00
modifierFlags : UIKeyModifierCommand
2015-06-15 14:53:45 +00:00
action : ^ ( __unused UIKeyCommand * command ) {
2015-05-01 13:21:03 +00:00
[ weakSelf toggle ] ;
} ] ;
2015-04-30 16:50:22 +00:00
2015-06-03 17:25:53 +00:00
// Toggle element inspector
[ commands registerKeyCommandWithInput : @ "i"
modifierFlags : UIKeyModifierCommand
2015-06-15 14:53:45 +00:00
action : ^ ( __unused UIKeyCommand * command ) {
2015-06-19 14:37:48 +00:00
[ weakSelf . bridge . eventDispatcher
2015-06-15 14:53:45 +00:00
sendDeviceEventWithName : @ "toggleElementInspector"
body : nil ] ;
2015-06-03 17:25:53 +00:00
} ] ;
2015-05-07 10:53:35 +00:00
// Reload in normal mode
2015-05-01 13:21:03 +00:00
[ commands registerKeyCommandWithInput : @ "n"
2015-04-30 16:50:22 +00:00
modifierFlags : UIKeyModifierCommand
2015-06-15 14:53:45 +00:00
action : ^ ( __unused UIKeyCommand * command ) {
2015-05-01 13:21:03 +00:00
weakSelf . executorClass = Nil ;
2015-04-30 16:50:22 +00:00
} ] ;
# endif
2015-03-26 00:33:54 +00:00
}
return self ;
}
2016-02-17 07:04:18 +00:00
- ( NSURL * ) packagerURL
{
NSString * host = [ _bridge . bundleURL host ] ;
if ( ! host ) {
return nil ;
}
NSString * scheme = [ _bridge . bundleURL scheme ] ;
NSNumber * port = [ _bridge . bundleURL port ] ;
2016-03-30 19:37:16 +00:00
if ( ! port ) {
port = @ 8081 ; // Packager default port
}
2016-02-27 02:17:22 +00:00
return [ NSURL URLWithString : [ NSString stringWithFormat : @ "%@://%@:%@/message?role=shell" , scheme , host , port ] ] ;
2016-02-17 07:04:18 +00:00
}
// TODO : Move non - UI logic into separate RCTDevSettings module
- ( void ) connectPackager
{
Class webSocketManagerClass = NSClassFromString ( @ "RCTWebSocketManager" ) ;
id < RCTWebSocketProxy > webSocketManager = ( id < RCTWebSocketProxy > ) [ webSocketManagerClass sharedInstance ] ;
NSURL * url = [ self packagerURL ] ;
if ( url ) {
[ webSocketManager setDelegate : self forURL : url ] ;
}
}
- ( BOOL ) isSupportedVersion : ( NSNumber * ) version
{
NSArray < NSNumber * > * const kSupportedVersions = @ [ @ 1 ] ;
return [ kSupportedVersions containsObject : version ] ;
}
2016-02-17 15:38:11 +00:00
- ( void ) socketProxy : ( __unused id < RCTWebSocketProxy > ) sender didReceiveMessage : ( NSDictionary < NSString * , id > * ) message
2016-02-17 07:04:18 +00:00
{
if ( [ self isSupportedVersion : message [ @ "version" ] ] ) {
[ self processTarget : message [ @ "target" ] action : message [ @ "action" ] options : message [ @ "options" ] ] ;
}
}
- ( void ) processTarget : ( NSString * ) target action : ( NSString * ) action options : ( NSDictionary < NSString * , id > * ) options
{
if ( [ target isEqualToString : @ "bridge" ] ) {
if ( [ action isEqualToString : @ "reload" ] ) {
if ( [ options [ @ "debug" ] boolValue ] ) {
_bridge . executorClass = NSClassFromString ( @ "RCTWebSocketExecutor" ) ;
}
[ _bridge reload ] ;
}
}
}
2015-05-07 10:53:35 +00:00
- ( dispatch_queue _t ) methodQueue
2015-05-01 13:21:03 +00:00
{
2015-05-07 10:53:35 +00:00
return dispatch_get _main _queue ( ) ;
}
2015-05-05 14:27:40 +00:00
2015-05-14 16:28:09 +00:00
- ( void ) settingsDidChange
{
// Needed to prevent a race condition when reloading
__weak RCTDevMenu * weakSelf = self ;
2015-09-14 16:34:33 +00:00
NSDictionary * settings = [ _defaults objectForKey : RCTDevMenuSettingsKey ] ;
2015-05-14 16:28:09 +00:00
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
2015-09-14 16:34:33 +00:00
[ weakSelf updateSettings : settings ] ;
2015-05-14 16:28:09 +00:00
} ) ;
}
2015-09-14 16:34:33 +00:00
/ * *
* This method loads the settings from NSUserDefaults and overrides any local
* settings with them . It should only be called on app launch , or after the app
* has returned from the background , when the settings might have been edited
* outside of the app .
* /
- ( void ) updateSettings : ( NSDictionary * ) settings
2015-05-07 10:53:35 +00:00
{
2015-09-15 18:11:28 +00:00
[ _settings setDictionary : settings ] ;
2015-09-14 16:34:33 +00:00
// Fire handlers for items whose values have changed
for ( RCTDevMenuItem * item in _extraMenuItems ) {
if ( item . key ) {
id value = settings [ item . key ] ;
if ( value ! = item . value && ! [ value isEqual : item . value ] ) {
item . value = value ;
[ item callHandler ] ;
}
}
}
2015-05-07 10:53:35 +00:00
self . shakeToShow = [ _settings [ @ "shakeToShow" ] ? : @ YES boolValue ] ;
self . profilingEnabled = [ _settings [ @ "profilingEnabled" ] ? : @ NO boolValue ] ;
self . liveReloadEnabled = [ _settings [ @ "liveReloadEnabled" ] ? : @ NO boolValue ] ;
2016-01-21 22:04:15 +00:00
self . hotLoadingEnabled = [ _settings [ @ "hotLoadingEnabled" ] ? : @ NO boolValue ] ;
2015-05-21 01:24:34 +00:00
self . showFPS = [ _settings [ @ "showFPS" ] ? : @ NO boolValue ] ;
2015-11-06 22:52:43 +00:00
self . executorClass = NSClassFromString ( _executorOverride ? : _settings [ @ "executorClass" ] ) ;
2015-05-01 13:21:03 +00:00
}
2015-09-14 16:34:33 +00:00
/ * *
* This updates a particular setting , and then saves the settings . Because all
* settings are overwritten by this , it ' s important that this is not called
* before settings have been loaded initially , otherwise the other settings
* will be reset .
* /
- ( void ) updateSetting : ( NSString * ) name value : ( id ) value
{
// Fire handler for item whose values has changed
for ( RCTDevMenuItem * item in _extraMenuItems ) {
if ( [ item . key isEqualToString : name ] ) {
if ( value ! = item . value && ! [ value isEqual : item . value ] ) {
item . value = value ;
[ item callHandler ] ;
}
break ;
}
}
// Save the setting
id currentValue = _settings [ name ] ;
if ( currentValue = = value || [ currentValue isEqual : value ] ) {
return ;
}
if ( value ) {
_settings [ name ] = value ;
} else {
[ _settings removeObjectForKey : name ] ;
}
[ _defaults setObject : _settings forKey : RCTDevMenuSettingsKey ] ;
[ _defaults synchronize ] ;
}
2015-05-12 14:44:46 +00:00
- ( void ) jsLoaded : ( NSNotification * ) notification
2015-05-01 13:21:03 +00:00
{
2015-05-12 14:44:46 +00:00
if ( notification . userInfo [ @ "bridge" ] ! = _bridge ) {
return ;
}
2015-05-01 13:21:03 +00:00
_jsLoaded = YES ;
// Check if live reloading is available
_liveReloadURL = nil ;
2015-11-25 11:09:00 +00:00
RCTSourceCode * sourceCodeModule = [ _bridge moduleForClass : [ RCTSourceCode class ] ] ;
2015-05-01 13:21:03 +00:00
if ( ! sourceCodeModule . scriptURL ) {
if ( ! sourceCodeModule ) {
RCTLogWarn ( @ "RCTSourceCode module not found" ) ;
2016-03-01 17:44:05 +00:00
} else if ( ! RCTRunningInTestEnvironment ( ) ) {
2015-05-01 13:21:03 +00:00
RCTLogWarn ( @ "RCTSourceCode module scriptURL has not been set" ) ;
}
2016-01-04 18:39:07 +00:00
} else if ( ! sourceCodeModule . scriptURL . fileURL ) {
2015-05-01 13:21:03 +00:00
// Live reloading is disabled when running from bundled JS file
_liveReloadURL = [ [ NSURL alloc ] initWithString : @ "/onchange" relativeToURL : sourceCodeModule . scriptURL ] ;
}
2015-05-04 17:35:49 +00:00
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
// Hit these setters again after bridge has finished loading
self . profilingEnabled = _profilingEnabled ;
self . liveReloadEnabled = _liveReloadEnabled ;
self . executorClass = _executorClass ;
2015-09-14 16:34:33 +00:00
// Inspector can only be shown after JS has loaded
if ( [ _settings [ @ "showInspector" ] boolValue ] ) {
[ self . bridge . eventDispatcher sendDeviceEventWithName : @ "toggleElementInspector" body : nil ] ;
}
2015-05-04 17:35:49 +00:00
} ) ;
2015-05-01 13:21:03 +00:00
}
2015-09-14 16:34:33 +00:00
- ( void ) invalidate
2015-04-19 19:55:46 +00:00
{
2015-09-14 16:34:33 +00:00
_presentedItems = nil ;
2015-05-01 13:21:03 +00:00
[ _updateTask cancel ] ;
[ _actionSheet dismissWithClickedButtonIndex : _actionSheet . cancelButtonIndex animated : YES ] ;
2015-04-19 19:55:46 +00:00
[ [ NSNotificationCenter defaultCenter ] removeObserver : self ] ;
}
- ( void ) showOnShake
{
if ( _shakeToShow ) {
[ self show ] ;
}
}
2015-05-01 13:21:03 +00:00
- ( void ) toggle
2015-03-26 00:33:54 +00:00
{
2015-04-19 19:55:46 +00:00
if ( _actionSheet ) {
2015-05-01 13:21:03 +00:00
[ _actionSheet dismissWithClickedButtonIndex : _actionSheet . cancelButtonIndex animated : YES ] ;
_actionSheet = nil ;
} else {
[ self show ] ;
}
}
2015-09-14 16:34:33 +00:00
- ( void ) addItem : ( NSString * ) title handler : ( void ( ^ ) ( void ) ) handler
2015-05-01 13:21:03 +00:00
{
2015-09-14 16:34:33 +00:00
[ self addItem : [ RCTDevMenuItem buttonItemWithTitle : title handler : handler ] ] ;
}
- ( void ) addItem : ( RCTDevMenuItem * ) item
{
[ _extraMenuItems addObject : item ] ;
// Fire handler for items whose saved value doesn ' t match the default
[ self settingsDidChange ] ;
2015-06-03 23:40:14 +00:00
}
2015-11-03 22:45:46 +00:00
- ( NSArray < RCTDevMenuItem * > * ) menuItems
2015-06-03 23:40:14 +00:00
{
2015-11-03 22:45:46 +00:00
NSMutableArray < RCTDevMenuItem * > * items = [ NSMutableArray new ] ;
2015-06-03 23:40:14 +00:00
2015-09-14 16:34:33 +00:00
// Add built - in items
__weak RCTDevMenu * weakSelf = self ;
[ items addObject : [ RCTDevMenuItem buttonItemWithTitle : @ "Reload" handler : ^ {
[ weakSelf reload ] ;
2015-06-03 23:40:14 +00:00
} ] ] ;
Class chromeExecutorClass = NSClassFromString ( @ "RCTWebSocketExecutor" ) ;
if ( ! chromeExecutorClass ) {
2015-11-06 22:52:43 +00:00
[ items addObject : [ RCTDevMenuItem buttonItemWithTitle : [ NSString stringWithFormat : @ "%@ Debugger Unavailable" , _webSocketExecutorName ] handler : ^ {
UIAlertView * alert = RCTAlertView (
[ NSString stringWithFormat : @ "%@ Debugger Unavailable" , _webSocketExecutorName ] ,
[ NSString stringWithFormat : @ "You need to include the RCTWebSocket library to enable %@ debugging" , _webSocketExecutorName ] ,
nil ,
@ "OK" ,
nil ) ;
2015-09-22 17:43:56 +00:00
[ alert show ] ;
2015-06-03 23:40:14 +00:00
} ] ] ;
} else {
BOOL isDebuggingInChrome = _executorClass && _executorClass = = chromeExecutorClass ;
2015-11-06 22:52:43 +00:00
NSString * debugTitleChrome = isDebuggingInChrome ? [ NSString stringWithFormat : @ "Disable %@ Debugging" , _webSocketExecutorName ] : [ NSString stringWithFormat : @ "Debug in %@" , _webSocketExecutorName ] ;
2015-09-14 16:34:33 +00:00
[ items addObject : [ RCTDevMenuItem buttonItemWithTitle : debugTitleChrome handler : ^ {
weakSelf . executorClass = isDebuggingInChrome ? Nil : chromeExecutorClass ;
2015-06-03 23:40:14 +00:00
} ] ] ;
2015-04-19 19:55:46 +00:00
}
2015-05-01 13:21:03 +00:00
if ( _liveReloadURL ) {
NSString * liveReloadTitle = _liveReloadEnabled ? @ "Disable Live Reload" : @ "Enable Live Reload" ;
2015-09-14 16:34:33 +00:00
[ items addObject : [ RCTDevMenuItem buttonItemWithTitle : liveReloadTitle handler : ^ {
weakSelf . liveReloadEnabled = ! _liveReloadEnabled ;
2015-06-03 23:40:14 +00:00
} ] ] ;
2015-08-28 17:11:02 +00:00
NSString * profilingTitle = RCTProfileIsProfiling ( ) ? @ "Stop Systrace" : @ "Start Systrace" ;
2015-09-14 16:34:33 +00:00
[ items addObject : [ RCTDevMenuItem buttonItemWithTitle : profilingTitle handler : ^ {
weakSelf . profilingEnabled = ! _profilingEnabled ;
2015-06-03 23:40:14 +00:00
} ] ] ;
}
2016-01-04 18:39:07 +00:00
if ( [ self hotLoadingAvailable ] ) {
2016-03-06 18:11:52 +00:00
NSString * hotLoadingTitle = _hotLoadingEnabled ? @ "Disable Hot Reloading" : @ "Enable Hot Reloading" ;
2016-01-04 18:39:07 +00:00
[ items addObject : [ RCTDevMenuItem buttonItemWithTitle : hotLoadingTitle handler : ^ {
weakSelf . hotLoadingEnabled = ! _hotLoadingEnabled ;
} ] ] ;
}
2015-06-03 23:40:14 +00:00
[ items addObjectsFromArray : _extraMenuItems ] ;
return items ;
}
2015-05-01 13:21:03 +00:00
2015-06-03 23:40:14 +00:00
RCT_EXPORT _METHOD ( show )
{
2015-09-22 17:43:56 +00:00
if ( _actionSheet || ! _bridge || RCTRunningInAppExtension ( ) ) {
2015-06-03 23:40:14 +00:00
return ;
}
2015-08-17 14:35:34 +00:00
UIActionSheet * actionSheet = [ UIActionSheet new ] ;
2015-06-03 23:40:14 +00:00
actionSheet . title = @ "React Native: Development" ;
actionSheet . delegate = self ;
2015-11-03 22:45:46 +00:00
NSArray < RCTDevMenuItem * > * items = [ self menuItems ] ;
2015-06-03 23:40:14 +00:00
for ( RCTDevMenuItem * item in items ) {
2015-09-14 16:34:33 +00:00
switch ( item . type ) {
case RCTDevMenuTypeButton : {
[ actionSheet addButtonWithTitle : item . title ] ;
break ;
}
case RCTDevMenuTypeToggle : {
BOOL selected = [ item . value boolValue ] ;
[ actionSheet addButtonWithTitle : selected ? item . selectedTitle : item . title ] ;
break ;
}
}
2015-05-01 13:21:03 +00:00
}
[ actionSheet addButtonWithTitle : @ "Cancel" ] ;
2015-08-24 10:14:33 +00:00
actionSheet . cancelButtonIndex = actionSheet . numberOfButtons - 1 ;
2015-04-19 19:55:46 +00:00
2015-03-26 00:33:54 +00:00
actionSheet . actionSheetStyle = UIBarStyleBlack ;
2015-11-05 11:49:41 +00:00
[ actionSheet showInView : RCTKeyWindow ( ) . rootViewController . view ] ;
2015-04-30 16:50:22 +00:00
_actionSheet = actionSheet ;
2015-06-03 23:40:14 +00:00
_presentedItems = items ;
2015-05-01 13:21:03 +00:00
}
2015-03-26 00:33:54 +00:00
- ( void ) actionSheet : ( UIActionSheet * ) actionSheet clickedButtonAtIndex : ( NSInteger ) buttonIndex
{
2015-04-19 19:55:46 +00:00
_actionSheet = nil ;
2015-05-07 10:53:35 +00:00
if ( buttonIndex = = actionSheet . cancelButtonIndex ) {
return ;
}
2015-04-19 19:55:46 +00:00
2015-06-03 23:40:14 +00:00
RCTDevMenuItem * item = _presentedItems [ buttonIndex ] ;
2015-09-14 16:34:33 +00:00
switch ( item . type ) {
case RCTDevMenuTypeButton : {
[ item callHandler ] ;
break ;
}
case RCTDevMenuTypeToggle : {
BOOL value = [ _settings [ item . key ] boolValue ] ;
[ self updateSetting : item . key value : @ ( ! value ) ] ; // will call handler
break ;
}
}
2015-06-03 23:40:14 +00:00
return ;
}
RCT_EXPORT _METHOD ( reload )
{
2016-03-15 12:00:28 +00:00
[ [ NSNotificationCenter defaultCenter ] postNotificationName : RCTReloadNotification
object : nil
userInfo : nil ] ;
2015-03-26 00:33:54 +00:00
}
2015-05-01 13:21:03 +00:00
- ( void ) setShakeToShow : ( BOOL ) shakeToShow
{
2015-09-14 16:34:33 +00:00
_shakeToShow = shakeToShow ;
[ self updateSetting : @ "shakeToShow" value : @ ( _shakeToShow ) ] ;
2015-05-01 13:21:03 +00:00
}
2015-04-19 19:55:46 +00:00
- ( void ) setProfilingEnabled : ( BOOL ) enabled
2015-03-26 00:33:54 +00:00
{
2015-09-14 16:34:33 +00:00
_profilingEnabled = enabled ;
[ self updateSetting : @ "profilingEnabled" value : @ ( _profilingEnabled ) ] ;
2015-04-19 19:55:46 +00:00
2015-05-01 13:21:03 +00:00
if ( _liveReloadURL && enabled ! = RCTProfileIsProfiling ( ) ) {
if ( enabled ) {
[ _bridge startProfiling ] ;
} else {
2015-10-12 16:42:21 +00:00
[ _bridge stopProfiling : ^ ( NSData * logData ) {
RCTProfileSendResult ( _bridge , @ "systrace" , logData ) ;
} ] ;
2015-05-01 13:21:03 +00:00
}
2015-03-26 00:33:54 +00:00
}
}
2015-04-19 19:55:46 +00:00
- ( void ) setLiveReloadEnabled : ( BOOL ) enabled
2015-03-26 00:33:54 +00:00
{
2015-09-14 16:34:33 +00:00
_liveReloadEnabled = enabled ;
[ self updateSetting : @ "liveReloadEnabled" value : @ ( _liveReloadEnabled ) ] ;
2015-04-19 19:55:46 +00:00
if ( _liveReloadEnabled ) {
2015-05-01 13:21:03 +00:00
[ self checkForUpdates ] ;
2015-04-19 19:55:46 +00:00
} else {
2015-05-01 13:21:03 +00:00
[ _updateTask cancel ] ;
_updateTask = nil ;
2015-04-19 19:55:46 +00:00
}
}
2016-01-04 18:39:07 +00:00
- ( BOOL ) hotLoadingAvailable
{
2016-02-29 17:24:19 +00:00
return _bridge . bundleURL && ! _bridge . bundleURL . fileURL ; // Only works when running from server
2016-01-04 18:39:07 +00:00
}
- ( void ) setHotLoadingEnabled : ( BOOL ) enabled
{
_hotLoadingEnabled = enabled ;
[ self updateSetting : @ "hotLoadingEnabled" value : @ ( _hotLoadingEnabled ) ] ;
BOOL actuallyEnabled = [ self hotLoadingAvailable ] && _hotLoadingEnabled ;
if ( RCTGetURLQueryParam ( _bridge . bundleURL , @ "hot" ) . boolValue ! = actuallyEnabled ) {
_bridge . bundleURL = RCTURLByReplacingQueryParam ( _bridge . bundleURL , @ "hot" ,
2016-01-06 19:54:25 +00:00
actuallyEnabled ? @ "true" : nil ) ;
2016-01-04 18:39:07 +00:00
[ _bridge reload ] ;
}
}
2015-05-01 13:21:03 +00:00
- ( void ) setExecutorClass : ( Class ) executorClass
2015-04-19 19:55:46 +00:00
{
2015-05-01 13:21:03 +00:00
if ( _executorClass ! = executorClass ) {
_executorClass = executorClass ;
2015-11-06 22:52:43 +00:00
_executorOverride = nil ;
2015-09-14 16:34:33 +00:00
[ self updateSetting : @ "executorClass" value : NSStringFromClass ( executorClass ) ] ;
2015-04-19 19:55:46 +00:00
}
2015-05-01 13:21:03 +00:00
if ( _bridge . executorClass ! = executorClass ) {
2015-04-19 19:55:46 +00:00
2015-05-01 13:21:03 +00:00
// 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 &&
2015-11-10 13:24:54 +00:00
_bridge . executorClass ! = NSClassFromString ( @ "RCTWebSocketExecutor" ) ) {
2015-09-14 16:34:33 +00:00
return ;
}
2015-04-19 19:55:46 +00:00
2015-05-01 13:21:03 +00:00
_bridge . executorClass = executorClass ;
2016-01-04 18:39:07 +00:00
[ _bridge reload ] ;
2015-05-01 13:21:03 +00:00
}
2015-04-19 19:55:46 +00:00
}
2015-05-21 01:24:34 +00:00
- ( void ) setShowFPS : ( BOOL ) showFPS
{
2015-09-14 16:34:33 +00:00
_showFPS = showFPS ;
[ self updateSetting : @ "showFPS" value : @ ( showFPS ) ] ;
2015-05-21 01:24:34 +00:00
}
2015-05-01 13:21:03 +00:00
- ( void ) checkForUpdates
2015-04-19 19:55:46 +00:00
{
2015-05-01 13:21:03 +00:00
if ( ! _jsLoaded || ! _liveReloadEnabled || ! _liveReloadURL ) {
return ;
}
2015-04-19 19:55:46 +00:00
2015-05-01 13:21:03 +00:00
if ( _updateTask ) {
[ _updateTask cancel ] ;
_updateTask = nil ;
return ;
}
__weak RCTDevMenu * weakSelf = self ;
2015-06-15 14:53:45 +00:00
_updateTask = [ [ NSURLSession sharedSession ] dataTaskWithURL : _liveReloadURL completionHandler :
^ ( __unused NSData * data , NSURLResponse * response , NSError * error ) {
2015-05-01 13:21:03 +00:00
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
2015-08-11 11:15:59 +00:00
RCTDevMenu * strongSelf = weakSelf ;
2015-05-01 13:21:03 +00:00
if ( strongSelf && strongSelf -> _liveReloadEnabled ) {
NSHTTPURLResponse * HTTPResponse = ( NSHTTPURLResponse * ) response ;
if ( ! error && HTTPResponse . statusCode = = 205 ) {
[ strongSelf reload ] ;
} else {
strongSelf -> _updateTask = nil ;
[ strongSelf checkForUpdates ] ;
}
}
} ) ;
} ] ;
[ _updateTask resume ] ;
2015-04-19 19:55:46 +00:00
}
@ end
2015-05-01 13:21:03 +00:00
# else // Unavailable when not in dev mode
2015-04-30 16:50:22 +00:00
@ implementation RCTDevMenu
- ( void ) show { }
2015-05-01 13:21:03 +00:00
- ( void ) reload { }
2015-06-03 23:40:14 +00:00
- ( void ) addItem : ( NSString * ) title handler : ( dispatch_block _t ) handler { }
2015-09-14 16:34:33 +00:00
- ( void ) addItem : ( RCTDevMenu * ) item { }
2015-04-30 16:50:22 +00:00
@ end
# endif
2015-04-19 19:55:46 +00:00
@ implementation RCTBridge ( RCTDevMenu )
- ( RCTDevMenu * ) devMenu
{
2015-10-16 19:12:44 +00:00
# if RCT_DEV
2015-11-25 11:09:00 +00:00
return [ self moduleForClass : [ RCTDevMenu class ] ] ;
2015-10-16 19:12:44 +00:00
# else
return nil ;
# endif
2015-03-26 00:33:54 +00:00
}
@ end