// // FLEXGlobalsTableViewController.m // Flipboard // // Created by Ryan Olson on 2014-05-03. // Copyright (c) 2014 Flipboard. All rights reserved. // #import "FLEXGlobalsTableViewController.h" #import "FLEXUtility.h" #import "FLEXRuntimeUtility.h" #import "FLEXLibrariesTableViewController.h" #import "FLEXClassesTableViewController.h" #import "FLEXObjectExplorerViewController.h" #import "FLEXObjectExplorerFactory.h" #import "FLEXLiveObjectsTableViewController.h" #import "FLEXFileBrowserTableViewController.h" #import "FLEXCookiesTableViewController.h" #import "FLEXGlobalsTableViewControllerEntry.h" #import "FLEXManager+Private.h" #import "FLEXSystemLogTableViewController.h" #import "FLEXNetworkHistoryTableViewController.h" static __weak UIWindow *s_applicationWindow = nil; typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) { FLEXGlobalsRowNetworkHistory, FLEXGlobalsRowSystemLog, FLEXGlobalsRowLiveObjects, FLEXGlobalsRowAddressInspector, FLEXGlobalsRowFileBrowser, FLEXGlobalsCookies, FLEXGlobalsRowSystemLibraries, FLEXGlobalsRowAppClasses, FLEXGlobalsRowAppDelegate, FLEXGlobalsRowRootViewController, FLEXGlobalsRowUserDefaults, FLEXGlobalsRowMainBundle, FLEXGlobalsRowApplication, FLEXGlobalsRowKeyWindow, FLEXGlobalsRowMainScreen, FLEXGlobalsRowCurrentDevice, FLEXGlobalsRowCount }; @interface FLEXGlobalsTableViewController () @property (nonatomic, readonly, copy) NSArray *entries; @end @implementation FLEXGlobalsTableViewController + (NSArray *)defaultGlobalEntries { NSMutableArray *defaultGlobalEntries = [NSMutableArray array]; for (FLEXGlobalsRow defaultRowIndex = 0; defaultRowIndex < FLEXGlobalsRowCount; defaultRowIndex++) { FLEXGlobalsTableViewControllerEntryNameFuture titleFuture = nil; FLEXGlobalsTableViewControllerViewControllerFuture viewControllerFuture = nil; FLEXGlobalsTableViewControllerRowAction rowAction = nil; switch (defaultRowIndex) { case FLEXGlobalsRowAppClasses: titleFuture = ^NSString *{ return [NSString stringWithFormat:@"📕 %@ Classes", [FLEXUtility applicationName]]; }; viewControllerFuture = ^UIViewController *{ FLEXClassesTableViewController *classesViewController = [[FLEXClassesTableViewController alloc] init]; classesViewController.binaryImageName = [FLEXUtility applicationImageName]; return classesViewController; }; break; case FLEXGlobalsRowAddressInspector: titleFuture = ^NSString *{ return @"🔎 Address Explorer"; }; rowAction = ^(FLEXGlobalsTableViewController *host) { NSString *title = @"Explore Object at Address"; NSString *message = @"Paste a hexadecimal address below, starting with '0x'. " "Use the unsafe option if you need to bypass pointer validation, " "but know that it may crash the app if the address is invalid."; UIAlertController *addressInput = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; void (^handler)(UIAlertAction *) = ^(UIAlertAction *action) { if (action.style == UIAlertActionStyleCancel) { [host deselectSelectedRow]; return; } NSString *address = addressInput.textFields.firstObject.text; [host tryExploreAddress:address safely:action.style == UIAlertActionStyleDefault]; }; [addressInput addTextFieldWithConfigurationHandler:^(UITextField *textField) { NSString *copied = [UIPasteboard generalPasteboard].string; textField.placeholder = @"0x00000070deadbeef"; // Go ahead and paste our clipboard if we have an address copied if ([copied hasPrefix:@"0x"]) { textField.text = copied; [textField selectAll:nil]; } }]; [addressInput addAction:[UIAlertAction actionWithTitle:@"Explore" style:UIAlertActionStyleDefault handler:handler]]; [addressInput addAction:[UIAlertAction actionWithTitle:@"Unsafe Explore" style:UIAlertActionStyleDestructive handler:handler]]; [addressInput addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:handler]]; [host presentViewController:addressInput animated:YES completion:nil]; }; break; case FLEXGlobalsRowSystemLibraries: { NSString *titleString = @"📚 System Libraries"; titleFuture = ^NSString *{ return titleString; }; viewControllerFuture = ^UIViewController *{ FLEXLibrariesTableViewController *librariesViewController = [[FLEXLibrariesTableViewController alloc] init]; librariesViewController.title = titleString; return librariesViewController; }; break; } case FLEXGlobalsRowLiveObjects: titleFuture = ^NSString *{ return @"💩 Heap Objects"; }; viewControllerFuture = ^UIViewController *{ return [[FLEXLiveObjectsTableViewController alloc] init]; }; break; case FLEXGlobalsRowAppDelegate: titleFuture = ^NSString *{ return [NSString stringWithFormat:@"👉 %@", [[[UIApplication sharedApplication] delegate] class]]; }; viewControllerFuture = ^UIViewController *{ id appDelegate = [[UIApplication sharedApplication] delegate]; return [FLEXObjectExplorerFactory explorerViewControllerForObject:appDelegate]; }; break; case FLEXGlobalsRowRootViewController: titleFuture = ^NSString *{ return [NSString stringWithFormat:@"🌴 %@", [[s_applicationWindow rootViewController] class]]; }; viewControllerFuture = ^UIViewController *{ UIViewController *rootViewController = [s_applicationWindow rootViewController]; return [FLEXObjectExplorerFactory explorerViewControllerForObject:rootViewController]; }; break; case FLEXGlobalsRowUserDefaults: titleFuture = ^NSString *{ return @"🚶 +[NSUserDefaults standardUserDefaults]"; }; viewControllerFuture = ^UIViewController *{ NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults]; return [FLEXObjectExplorerFactory explorerViewControllerForObject:standardUserDefaults]; }; break; case FLEXGlobalsRowMainBundle: titleFuture = ^NSString *{ return @"📦 +[NSBundle mainBundle]"; }; viewControllerFuture = ^UIViewController *{ NSBundle *mainBundle = [NSBundle mainBundle]; return [FLEXObjectExplorerFactory explorerViewControllerForObject:mainBundle]; }; break; case FLEXGlobalsRowApplication: titleFuture = ^NSString *{ return @"💾 +[UIApplication sharedApplication]"; }; viewControllerFuture = ^UIViewController *{ UIApplication *sharedApplication = [UIApplication sharedApplication]; return [FLEXObjectExplorerFactory explorerViewControllerForObject:sharedApplication]; }; break; case FLEXGlobalsRowKeyWindow: titleFuture = ^NSString *{ return @"🔑 -[UIApplication keyWindow]"; }; viewControllerFuture = ^UIViewController *{ return [FLEXObjectExplorerFactory explorerViewControllerForObject:s_applicationWindow]; }; break; case FLEXGlobalsRowMainScreen: titleFuture = ^NSString *{ return @"💻 +[UIScreen mainScreen]"; }; viewControllerFuture = ^UIViewController *{ UIScreen *mainScreen = [UIScreen mainScreen]; return [FLEXObjectExplorerFactory explorerViewControllerForObject:mainScreen]; }; break; case FLEXGlobalsRowCurrentDevice: titleFuture = ^NSString *{ return @"📱 +[UIDevice currentDevice]"; }; viewControllerFuture = ^UIViewController *{ UIDevice *currentDevice = [UIDevice currentDevice]; return [FLEXObjectExplorerFactory explorerViewControllerForObject:currentDevice]; }; break; case FLEXGlobalsCookies: titleFuture = ^NSString *{ return @"🍪 Cookies"; }; viewControllerFuture = ^UIViewController *{ return [[FLEXCookiesTableViewController alloc] init]; }; break; case FLEXGlobalsRowFileBrowser: titleFuture = ^NSString *{ return @"📁 File Browser"; }; viewControllerFuture = ^UIViewController *{ return [[FLEXFileBrowserTableViewController alloc] init]; }; break; case FLEXGlobalsRowSystemLog: titleFuture = ^{ return @"⚠️ System Log"; }; viewControllerFuture = ^{ return [[FLEXSystemLogTableViewController alloc] init]; }; break; case FLEXGlobalsRowNetworkHistory: titleFuture = ^{ return @"📡 Network History"; }; viewControllerFuture = ^{ return [[FLEXNetworkHistoryTableViewController alloc] init]; }; break; case FLEXGlobalsRowCount: break; } NSAssert(viewControllerFuture || rowAction, @"The switch-case above must assign one of these"); if (viewControllerFuture) { [defaultGlobalEntries addObject:[FLEXGlobalsTableViewControllerEntry entryWithNameFuture:titleFuture viewControllerFuture:viewControllerFuture]]; } else { [defaultGlobalEntries addObject:[FLEXGlobalsTableViewControllerEntry entryWithNameFuture:titleFuture action:rowAction]]; } } return defaultGlobalEntries; } - (id)initWithStyle:(UITableViewStyle)style { self = [super initWithStyle:style]; if (self) { self.title = @"💪 FLEX"; _entries = [[[self class] defaultGlobalEntries] arrayByAddingObjectsFromArray:[FLEXManager sharedManager].userGlobalEntries]; } return self; } - (void)deselectSelectedRow { NSIndexPath *selected = self.tableView.indexPathForSelectedRow; [self.tableView deselectRowAtIndexPath:selected animated:YES]; } #pragma mark - Public + (void)setApplicationWindow:(UIWindow *)applicationWindow { s_applicationWindow = applicationWindow; } #pragma mark - UIViewController - (void)viewDidLoad { [super viewDidLoad]; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(donePressed:)]; } #pragma mark - Misc - (void)donePressed:(id)sender { [self.delegate globalsViewControllerDidFinish:self]; } - (void)tryExploreAddress:(NSString *)addressString safely:(BOOL)safely { NSScanner *scanner = [NSScanner scannerWithString:addressString]; unsigned long long hexValue = 0; BOOL didParseAddress = [scanner scanHexLongLong:&hexValue]; const void *pointerValue = (void *)hexValue; NSString *error = nil; if (didParseAddress) { if (safely && ![FLEXRuntimeUtility pointerIsValidObjcObject:pointerValue]) { error = @"The given address is unlikely to be a valid object."; } } else { error = @"Malformed address. Make sure it's not too long and starts with '0x'."; } if (!error) { id object = (__bridge id)pointerValue; FLEXObjectExplorerViewController *explorer = [FLEXObjectExplorerFactory explorerViewControllerForObject:object]; [self.navigationController pushViewController:explorer animated:YES]; } else { [FLEXUtility alert:@"Uh-oh" message:error from:self]; [self deselectSelectedRow]; } } #pragma mark - Table Data Helpers - (FLEXGlobalsTableViewControllerEntry *)globalEntryAtIndexPath:(NSIndexPath *)indexPath { return self.entries[indexPath.row]; } - (NSString *)titleForRowAtIndexPath:(NSIndexPath *)indexPath { FLEXGlobalsTableViewControllerEntry *entry = [self globalEntryAtIndexPath:indexPath]; return entry.entryNameFuture(); } #pragma mark - Table View Data Source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.entries count]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; cell.textLabel.font = [FLEXUtility defaultFontOfSize:14.0]; } cell.textLabel.text = [self titleForRowAtIndexPath:indexPath]; return cell; } #pragma mark - Table View Delegate - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { FLEXGlobalsTableViewControllerEntry *entry = [self globalEntryAtIndexPath:indexPath]; if (entry.viewControllerFuture) { [self.navigationController pushViewController:entry.viewControllerFuture() animated:YES]; } else { entry.rowAction(self); } } @end