| Index: ios/chrome/app/safe_mode/safe_mode_view_controller.mm
|
| diff --git a/ios/chrome/app/safe_mode/safe_mode_view_controller.mm b/ios/chrome/app/safe_mode/safe_mode_view_controller.mm
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..fbd921c49e2c743ed3cfe2dd7b2fa3e18de9bb27
|
| --- /dev/null
|
| +++ b/ios/chrome/app/safe_mode/safe_mode_view_controller.mm
|
| @@ -0,0 +1,305 @@
|
| +// Copyright 2013 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#import "ios/chrome/app/safe_mode/safe_mode_view_controller.h"
|
| +
|
| +#import <QuartzCore/QuartzCore.h>
|
| +
|
| +#include "base/strings/sys_string_conversions.h"
|
| +#import "ios/chrome/app/safe_mode_crashing_modules_config.h"
|
| +#import "ios/chrome/app/safe_mode_util.h"
|
| +#include "ios/chrome/browser/crash_report/breakpad_helper.h"
|
| +#import "ios/chrome/browser/ui/fancy_ui/primary_action_button.h"
|
| +#include "ios/chrome/browser/ui/ui_util.h"
|
| +#import "ios/chrome/browser/ui/uikit_ui_util.h"
|
| +#include "ios/chrome/grit/ios_chromium_strings.h"
|
| +#import "ui/gfx/ios/NSString+CrStringDrawing.h"
|
| +
|
| +namespace {
|
| +const CGFloat kVerticalSpacing = 20;
|
| +const CGFloat kUploadProgressSpacing = 5;
|
| +const NSTimeInterval kUploadPumpInterval = 0.1;
|
| +const NSTimeInterval kUploadTotalTime = 5;
|
| +} // anonymous namespace
|
| +
|
| +@interface SafeModeViewController ()
|
| +// Returns |YES| if any third-party modifications are detected.
|
| ++ (BOOL)detectedThirdPartyMods;
|
| +// Returns |YES| if there are crash reports to upload.
|
| ++ (BOOL)hasReportToUpload;
|
| +// Returns a message explaining which, if any, 3rd party modules were detected
|
| +// that may cause Chrome to crash.
|
| +- (NSString*)startupCrashModuleText;
|
| +// Starts timer to update progress bar for crash report upload.
|
| +- (void)startUploadProgress;
|
| +// Updates progress bar for crash report upload.
|
| +- (void)pumpUploadProgress;
|
| +// Called when user taps on "Resume Chrome" button. Restores the default
|
| +// Breakpad configuration and notifies the delegate to attempt to start the
|
| +// browser.
|
| +- (void)startBrowserFromSafeMode;
|
| +@end
|
| +
|
| +@implementation SafeModeViewController
|
| +
|
| +- (id)initWithDelegate:(id<SafeModeViewControllerDelegate>)delegate {
|
| + self = [super init];
|
| + if (self) {
|
| + delegate_.reset(delegate);
|
| + }
|
| + return self;
|
| +}
|
| +
|
| ++ (BOOL)hasSuggestions {
|
| + if ([SafeModeViewController detectedThirdPartyMods])
|
| + return YES;
|
| + return [SafeModeViewController hasReportToUpload];
|
| +}
|
| +
|
| ++ (BOOL)detectedThirdPartyMods {
|
| + std::vector<std::string> thirdPartyMods = safe_mode_util::GetLoadedImages(
|
| + "/Library/MobileSubstrate/DynamicLibraries/");
|
| + return (thirdPartyMods.size() > 0);
|
| +}
|
| +
|
| ++ (BOOL)hasReportToUpload {
|
| + // If uploading is enabled and more than one report has stacked up, then we
|
| + // assume that the app may be in a state that is preventing crash report
|
| + // uploads before crashing again.
|
| + return breakpad_helper::IsUploadingEnabled() &&
|
| + breakpad_helper::GetCrashReportCount() > 1;
|
| +}
|
| +
|
| +// Return any jailbroken library that appears in SafeModeCrashingModulesConfig.
|
| +- (NSArray*)startupCrashModules {
|
| + std::vector<std::string> modules = safe_mode_util::GetLoadedImages(
|
| + "/Library/MobileSubstrate/DynamicLibraries/");
|
| + NSMutableArray* array = [NSMutableArray arrayWithCapacity:modules.size()];
|
| + SafeModeCrashingModulesConfig* config =
|
| + [SafeModeCrashingModulesConfig sharedInstance];
|
| + for (size_t i = 0; i < modules.size(); i++) {
|
| + NSString* path = base::SysUTF8ToNSString(modules[i]);
|
| + NSString* friendlyName = [config startupCrashModuleFriendlyName:path];
|
| + if (friendlyName != nil)
|
| + [array addObject:friendlyName];
|
| + }
|
| + return array;
|
| +}
|
| +
|
| +// Since we are still supporting iOS5, this is a helper for basic flow layout.
|
| +- (void)centerView:(UIView*)view afterView:(UIView*)afterView {
|
| + CGPoint center = [view center];
|
| + center.x = [innerView_ frame].size.width / 2;
|
| + [view setCenter:center];
|
| +
|
| + if (afterView) {
|
| + CGRect frame = view.frame;
|
| + frame.origin.y = CGRectGetMaxY(afterView.frame) + kVerticalSpacing;
|
| + view.frame = frame;
|
| + }
|
| +}
|
| +
|
| +- (NSString*)startupCrashModuleText {
|
| + NSArray* knownModules = [self startupCrashModules];
|
| + if ([knownModules count]) {
|
| + NSString* wrongText =
|
| + NSLocalizedString(@"IDS_IOS_SAFE_MODE_NAMED_TWEAKS_FOUND", @"");
|
| + NSMutableString* text = [NSMutableString stringWithString:wrongText];
|
| + [text appendString:@"\n"];
|
| + for (NSString* module in knownModules) {
|
| + [text appendFormat:@"\n %@", module];
|
| + }
|
| + return text;
|
| + } else if ([SafeModeViewController detectedThirdPartyMods]) {
|
| + return NSLocalizedString(@"IDS_IOS_SAFE_MODE_TWEAKS_FOUND", @"");
|
| + } else {
|
| + return NSLocalizedString(@"IDS_IOS_SAFE_MODE_UNKNOWN_CAUSE", @"");
|
| + }
|
| +}
|
| +
|
| +- (void)viewDidLoad {
|
| + [super viewDidLoad];
|
| +
|
| + // Width of the inner view on iPhone.
|
| + const CGFloat kIPhoneWidth = 250;
|
| + // Width of the inner view on iPad.
|
| + const CGFloat kIPadWidth = 350;
|
| + // Horizontal buffer.
|
| + const CGFloat kHorizontalSpacing = 20;
|
| +
|
| + self.view.autoresizesSubviews = YES;
|
| + CGRect mainBounds = [[UIScreen mainScreen] bounds];
|
| + // SafeModeViewController only supports portrait orientation (see
|
| + // implementation of supportedInterfaceOrientations: below) but if the app is
|
| + // launched from landscape mode (e.g. iPad or iPhone 6+) then the mainScreen's
|
| + // bounds will still be landscape at this point. Swap the height and width
|
| + // here so that the dimensions will be correct once the app rotates to
|
| + // portrait.
|
| + if (IsLandscape()) {
|
| + mainBounds.size = CGSizeMake(mainBounds.size.height, mainBounds.size.width);
|
| + }
|
| + base::scoped_nsobject<UIScrollView> scrollView(
|
| + [[UIScrollView alloc] initWithFrame:mainBounds]);
|
| + self.view = scrollView;
|
| + [self.view setBackgroundColor:[UIColor colorWithWhite:0.902 alpha:1.0]];
|
| + const CGFloat kIPadInset =
|
| + (mainBounds.size.width - kIPadWidth - kHorizontalSpacing) / 2;
|
| + const CGFloat widthInset = IsIPadIdiom() ? kIPadInset : kHorizontalSpacing;
|
| + innerView_.reset([[UIView alloc]
|
| + initWithFrame:CGRectInset(mainBounds, widthInset, kVerticalSpacing * 2)]);
|
| + [innerView_ setBackgroundColor:[UIColor whiteColor]];
|
| + [innerView_ layer].cornerRadius = 3;
|
| + [innerView_ layer].borderWidth = 1;
|
| + [innerView_ layer].borderColor =
|
| + [UIColor colorWithWhite:0.851 alpha:1.0].CGColor;
|
| + [innerView_ layer].masksToBounds = YES;
|
| + [scrollView addSubview:innerView_];
|
| +
|
| + UIImage* fatalImage = [UIImage imageNamed:@"fatal_error.png"];
|
| + base::scoped_nsobject<UIImageView> imageView(
|
| + [[UIImageView alloc] initWithImage:fatalImage]);
|
| + // Shift the image down a bit.
|
| + CGRect imageFrame = [imageView frame];
|
| + imageFrame.origin.y = kVerticalSpacing;
|
| + [imageView setFrame:imageFrame];
|
| + [self centerView:imageView afterView:nil];
|
| + [innerView_ addSubview:imageView];
|
| +
|
| + base::scoped_nsobject<UILabel> awSnap([[UILabel alloc] init]);
|
| + [awSnap setText:NSLocalizedString(@"IDS_IOS_SAFE_MODE_AW_SNAP", @"")];
|
| + [awSnap setBackgroundColor:[UIColor clearColor]];
|
| + [awSnap setTextColor:[UIColor blackColor]];
|
| + [awSnap setFont:[UIFont boldSystemFontOfSize:21]];
|
| + [awSnap sizeToFit];
|
| + [self centerView:awSnap afterView:imageView];
|
| + [innerView_ addSubview:awSnap];
|
| +
|
| + base::scoped_nsobject<UILabel> description([[UILabel alloc] init]);
|
| + [description setText:[self startupCrashModuleText]];
|
| + [description setBackgroundColor:[UIColor clearColor]];
|
| + [description setTextColor:[UIColor colorWithWhite:0.31 alpha:1.0]];
|
| + [description setTextAlignment:NSTextAlignmentCenter];
|
| + [description setNumberOfLines:0];
|
| + [description setLineBreakMode:NSLineBreakByWordWrapping];
|
| + CGRect frame = [description frame];
|
| + frame.size.width = IsIPadIdiom() ? kIPadWidth : kIPhoneWidth;
|
| + CGSize maxSize = CGSizeMake(frame.size.width, 999999.0f);
|
| + frame.size.height =
|
| + [[description text] cr_boundingSizeWithSize:maxSize
|
| + font:[description font]]
|
| + .height;
|
| + [description setFrame:frame];
|
| + [self centerView:description afterView:awSnap];
|
| + [innerView_ addSubview:description];
|
| +
|
| + startButton_.reset([[PrimaryActionButton alloc] init]);
|
| + NSString* startText =
|
| + NSLocalizedString(@"IDS_IOS_SAFE_MODE_RELOAD_CHROME", @"");
|
| + [startButton_ setTitle:startText forState:UIControlStateNormal];
|
| + [startButton_ titleLabel].textAlignment = NSTextAlignmentCenter;
|
| + [startButton_ titleLabel].lineBreakMode = NSLineBreakByWordWrapping;
|
| + frame = [startButton_ frame];
|
| + frame.size.width = IsIPadIdiom() ? kIPadWidth : kIPhoneWidth;
|
| + maxSize = CGSizeMake(frame.size.width, 999999.0f);
|
| + const CGFloat kButtonBuffer = kVerticalSpacing / 2;
|
| + CGSize startTextBoundingSize =
|
| + [startText cr_boundingSizeWithSize:maxSize
|
| + font:[startButton_ titleLabel].font];
|
| + frame.size.height = startTextBoundingSize.height + kButtonBuffer;
|
| + [startButton_ setFrame:frame];
|
| + [startButton_ addTarget:self
|
| + action:@selector(startBrowserFromSafeMode)
|
| + forControlEvents:UIControlEventTouchUpInside];
|
| + [self centerView:startButton_ afterView:description];
|
| + [innerView_ addSubview:startButton_];
|
| +
|
| + UIView* lastView = startButton_;
|
| + if ([SafeModeViewController hasReportToUpload]) {
|
| + breakpad_helper::StartUploadingReportsInRecoveryMode();
|
| +
|
| + // If there are no jailbreak modifications, then present the "Sending crash
|
| + // report..." UI.
|
| + if (![SafeModeViewController detectedThirdPartyMods]) {
|
| + [startButton_ setEnabled:NO];
|
| +
|
| + uploadDescription_.reset([[UILabel alloc] init]);
|
| + [uploadDescription_
|
| + setText:NSLocalizedString(@"IDS_IOS_SAFE_MODE_SENDING_CRASH_REPORT",
|
| + @"")];
|
| + [uploadDescription_ setBackgroundColor:[UIColor clearColor]];
|
| + [uploadDescription_ setFont:[UIFont systemFontOfSize:13]];
|
| + [uploadDescription_ setTextColor:[UIColor colorWithWhite:0.31 alpha:1.0]];
|
| + [uploadDescription_ sizeToFit];
|
| + [self centerView:uploadDescription_ afterView:startButton_];
|
| + [innerView_ addSubview:uploadDescription_];
|
| +
|
| + uploadProgress_.reset([[UIProgressView alloc]
|
| + initWithProgressViewStyle:UIProgressViewStyleDefault]);
|
| + [self centerView:uploadProgress_ afterView:nil];
|
| + frame = [uploadProgress_ frame];
|
| + frame.origin.y =
|
| + CGRectGetMaxY([uploadDescription_ frame]) + kUploadProgressSpacing;
|
| + [uploadProgress_ setFrame:frame];
|
| + [innerView_ addSubview:uploadProgress_];
|
| +
|
| + lastView = uploadProgress_;
|
| + [self startUploadProgress];
|
| + }
|
| + }
|
| +
|
| + CGSize scrollSize =
|
| + CGSizeMake(mainBounds.size.width,
|
| + CGRectGetMaxY([lastView frame]) + kVerticalSpacing);
|
| + frame = [innerView_ frame];
|
| + frame.size.height = scrollSize.height;
|
| + [innerView_ setFrame:frame];
|
| + scrollSize.height += frame.origin.y;
|
| + [scrollView setContentSize:scrollSize];
|
| +}
|
| +
|
| +- (NSUInteger)supportedInterfaceOrientations {
|
| + return UIInterfaceOrientationMaskPortrait;
|
| +}
|
| +
|
| +#pragma mark - Private
|
| +
|
| +- (void)startUploadProgress {
|
| + uploadStartTime_.reset([[NSDate date] retain]);
|
| + uploadTimer_.reset(
|
| + [[NSTimer scheduledTimerWithTimeInterval:kUploadPumpInterval
|
| + target:self
|
| + selector:@selector(pumpUploadProgress)
|
| + userInfo:nil
|
| + repeats:YES] retain]);
|
| +}
|
| +
|
| +- (void)pumpUploadProgress {
|
| + NSTimeInterval elapsed =
|
| + [[NSDate date] timeIntervalSinceDate:uploadStartTime_];
|
| + // Theoretically we could stop early when the value returned by
|
| + // ios_internal::breakpad::GetCrashReportCount() changes, but this is
|
| + // simpler. If we decide to look for a change in crash report count, then we
|
| + // also probably want to replace the UIProgressView with a
|
| + // UIActivityIndicatorView.
|
| + if (elapsed <= kUploadTotalTime) {
|
| + [uploadProgress_ setProgress:elapsed / kUploadTotalTime animated:YES];
|
| + } else {
|
| + [uploadTimer_ invalidate];
|
| +
|
| + [startButton_ setEnabled:YES];
|
| + [uploadDescription_
|
| + setText:NSLocalizedString(@"IDS_IOS_SAFE_MODE_CRASH_REPORT_SENT",
|
| + @"")];
|
| + [uploadDescription_ sizeToFit];
|
| + [self centerView:uploadDescription_ afterView:startButton_];
|
| + [uploadProgress_ setHidden:YES];
|
| + }
|
| +}
|
| +
|
| +- (void)startBrowserFromSafeMode {
|
| + breakpad_helper::RestoreDefaultConfiguration();
|
| + [delegate_ startBrowserFromSafeMode];
|
| +}
|
| +
|
| +@end
|
|
|