react-native/React/Base/RCTDevMenu.m

229 lines
6.2 KiB
Mathematica
Raw Normal View History

/**
* 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-04-19 19:55:46 +00:00
#import "RCTBridge.h"
#import "RCTLog.h"
#import "RCTProfile.h"
#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"
2015-04-19 19:55:46 +00:00
@interface RCTBridge (Profiling)
- (void)startProfiling;
- (void)stopProfiling;
@end
2015-04-19 19:55:46 +00:00
static NSString *const RCTShowDevMenuNotification = @"RCTShowDevMenuNotification";
@implementation UIWindow (RCTDevMenu)
- (void)RCT_motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if (event.subtype == UIEventSubtypeMotionShake) {
[[NSNotificationCenter defaultCenter] postNotificationName:RCTShowDevMenuNotification object:nil];
}
}
@end
2015-04-11 22:08:00 +00:00
@interface RCTDevMenu () <UIActionSheetDelegate>
@end
@implementation RCTDevMenu
2015-04-11 22:08:00 +00:00
{
2015-04-19 19:55:46 +00:00
NSTimer *_updateTimer;
UIActionSheet *_actionSheet;
2015-04-11 22:08:00 +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-04-19 19:55:46 +00:00
if ((self = [super init])) {
_shakeToShow = YES;
_liveReloadPeriod = 1.0; // 1 second
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(showOnShake)
name:RCTShowDevMenuNotification
object:nil];
}
return self;
}
2015-04-19 19:55:46 +00:00
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)showOnShake
{
if (_shakeToShow) {
[self show];
}
}
- (void)show
{
2015-04-19 19:55:46 +00:00
if (_actionSheet) {
return;
}
NSString *debugTitleChrome = _bridge.executorClass && _bridge.executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Enable Chrome Debugging";
NSString *debugTitleSafari = _bridge.executorClass && _bridge.executorClass == NSClassFromString(@"RCTWebViewExecutor") ? @"Disable Safari Debugging" : @"Enable Safari Debugging";
NSString *liveReloadTitle = _liveReloadEnabled ? @"Disable Live Reload" : @"Enable Live Reload";
NSString *profilingTitle = RCTProfileIsProfiling() ? @"Stop Profiling" : @"Start Profiling";
2015-04-19 19:55:46 +00:00
UIActionSheet *actionSheet =
[[UIActionSheet alloc] initWithTitle:@"React Native: Development"
delegate:self
cancelButtonTitle:@"Cancel"
destructiveButtonTitle:nil
otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, liveReloadTitle, profilingTitle, nil];
actionSheet.actionSheetStyle = UIBarStyleBlack;
2015-04-19 19:55:46 +00:00
[actionSheet showInView:[UIApplication sharedApplication].keyWindow.rootViewController.view];
}
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
2015-04-19 19:55:46 +00:00
_actionSheet = nil;
switch (buttonIndex) {
case 0: {
[_bridge reload];
break;
}
case 1: {
Class cls = NSClassFromString(@"RCTWebSocketExecutor");
_bridge.executorClass = (_bridge.executorClass != cls) ? cls : nil;
[_bridge reload];
break;
}
case 2: {
Class cls = NSClassFromString(@"RCTWebViewExecutor");
_bridge.executorClass = (_bridge.executorClass != cls) ? cls : Nil;
[_bridge reload];
break;
}
case 3: {
self.liveReloadEnabled = !_liveReloadEnabled;
break;
}
case 4: {
self.profilingEnabled = !_profilingEnabled;
break;
}
2015-04-19 19:55:46 +00:00
default:
break;
}
}
2015-04-19 19:55:46 +00:00
- (void)setProfilingEnabled:(BOOL)enabled
{
2015-04-19 19:55:46 +00:00
if (_profilingEnabled == enabled) {
return;
}
_profilingEnabled = enabled;
if (RCTProfileIsProfiling()) {
2015-04-19 19:55:46 +00:00
[_bridge stopProfiling];
} else {
[_bridge startProfiling];
}
}
2015-04-19 19:55:46 +00:00
- (void)setLiveReloadEnabled:(BOOL)enabled
{
2015-04-19 19:55:46 +00:00
if (_liveReloadEnabled == enabled) {
return;
}
_liveReloadEnabled = enabled;
if (_liveReloadEnabled) {
_updateTimer = [NSTimer scheduledTimerWithTimeInterval:_liveReloadPeriod
target:self
selector:@selector(pollForUpdates)
userInfo:nil
repeats:YES];
} else {
[_updateTimer invalidate];
_updateTimer = nil;
}
}
- (void)setLiveReloadPeriod:(NSTimeInterval)liveReloadPeriod
{
_liveReloadPeriod = liveReloadPeriod;
if (_liveReloadEnabled) {
self.liveReloadEnabled = NO;
self.liveReloadEnabled = YES;
}
}
- (void)pollForUpdates
{
RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
if (!sourceCodeModule) {
RCTLogError(@"RCTSourceCode module not found");
self.liveReloadEnabled = NO;
}
NSURL *longPollURL = [[NSURL alloc] initWithString:@"/onchange" relativeToURL:sourceCodeModule.scriptURL];
[NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:longPollURL]
queue:[[NSOperationQueue alloc] init]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
NSHTTPURLResponse *HTTPResponse = (NSHTTPURLResponse *)response;
if (_liveReloadEnabled && HTTPResponse.statusCode == 205) {
[_bridge reload];
}
}];
}
- (BOOL)isValid
{
return !_liveReloadEnabled || _updateTimer != nil;
}
- (void)invalidate
{
[_actionSheet dismissWithClickedButtonIndex:_actionSheet.cancelButtonIndex animated:YES];
[_updateTimer invalidate];
_updateTimer = nil;
}
@end
@implementation RCTBridge (RCTDevMenu)
- (RCTDevMenu *)devMenu
{
return self.modules[RCTBridgeModuleNameForClass([RCTDevMenu class])];
}
@end