You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
395 lines
16 KiB
395 lines
16 KiB
//
|
|
// 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<FLEXGlobalsTableViewControllerEntry *> *entries;
|
|
|
|
@end
|
|
|
|
@implementation FLEXGlobalsTableViewController
|
|
|
|
+ (NSArray<FLEXGlobalsTableViewControllerEntry *> *)defaultGlobalEntries
|
|
{
|
|
NSMutableArray<FLEXGlobalsTableViewControllerEntry *> *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 <UIApplicationDelegate> 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
|