OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #import "ios/chrome/browser/memory/memory_debugger.h" |
| 6 |
| 7 #include "base/ios/ios_util.h" |
| 8 #import "base/mac/scoped_nsobject.h" |
| 9 #import "base/memory/scoped_ptr.h" |
| 10 #import "ios/chrome/browser/memory/memory_metrics.h" |
| 11 #include "ios/chrome/browser/ui/ui_util.h" |
| 12 #import "ios/chrome/browser/ui/uikit_ui_util.h" |
| 13 |
| 14 namespace { |
| 15 // The number of bytes in a megabyte. |
| 16 const CGFloat kNumBytesInMB = 1024 * 1024; |
| 17 // The horizontal and vertical padding between subviews. |
| 18 const CGFloat kPadding = 10; |
| 19 } // namespace |
| 20 |
| 21 @implementation MemoryDebugger { |
| 22 // A timer to trigger refreshes. |
| 23 base::scoped_nsobject<NSTimer> _refreshTimer; |
| 24 |
| 25 // A timer to trigger continuous memory warnings. |
| 26 base::scoped_nsobject<NSTimer> _memoryWarningTimer; |
| 27 |
| 28 // The font to use. |
| 29 base::scoped_nsobject<UIFont> _font; |
| 30 |
| 31 // Labels for memory metrics. |
| 32 base::scoped_nsobject<UILabel> _physicalFreeMemoryLabel; |
| 33 base::scoped_nsobject<UILabel> _realMemoryUsedLabel; |
| 34 base::scoped_nsobject<UILabel> _xcodeGaugeLabel; |
| 35 base::scoped_nsobject<UILabel> _dirtyVirtualMemoryLabel; |
| 36 |
| 37 // Inputs for memory commands. |
| 38 base::scoped_nsobject<UITextField> _bloatField; |
| 39 base::scoped_nsobject<UITextField> _refreshField; |
| 40 base::scoped_nsobject<UITextField> _continuousMemoryWarningField; |
| 41 |
| 42 // A place to store the artifical memory bloat. |
| 43 scoped_ptr<uint8> _bloat; |
| 44 |
| 45 // Distance the view was pushed up to accomodate the keyboard. |
| 46 CGFloat _keyboardOffset; |
| 47 |
| 48 // The current orientation of the device. |
| 49 BOOL _currentOrientation; |
| 50 } |
| 51 |
| 52 - (instancetype)init { |
| 53 self = [super initWithFrame:CGRectZero]; |
| 54 if (self) { |
| 55 _font.reset([[UIFont systemFontOfSize:14] retain]); |
| 56 self.backgroundColor = [UIColor colorWithWhite:0.8f alpha:0.9f]; |
| 57 self.opaque = NO; |
| 58 |
| 59 [self addSubviews]; |
| 60 [self adjustForOrientation:nil]; |
| 61 [self sizeToFit]; |
| 62 [self registerForNotifications]; |
| 63 } |
| 64 return self; |
| 65 } |
| 66 |
| 67 // NSTimers create a retain cycle so they must be invalidated before this |
| 68 // instance can be deallocated. |
| 69 - (void)invalidateTimers { |
| 70 [_refreshTimer invalidate]; |
| 71 [_memoryWarningTimer invalidate]; |
| 72 } |
| 73 |
| 74 - (void)dealloc { |
| 75 [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| 76 [super dealloc]; |
| 77 } |
| 78 |
| 79 #pragma mark UIView methods |
| 80 |
| 81 - (CGSize)sizeThatFits:(CGSize)size { |
| 82 CGFloat width = 0; |
| 83 CGFloat height = 0; |
| 84 for (UIView* subview in self.subviews) { |
| 85 width = MAX(width, CGRectGetMaxX(subview.frame)); |
| 86 height = MAX(height, CGRectGetMaxY(subview.frame)); |
| 87 } |
| 88 return CGSizeMake(width + kPadding, height + kPadding); |
| 89 } |
| 90 |
| 91 #pragma mark initialization helpers |
| 92 |
| 93 - (void)addSubviews { |
| 94 // |index| is used to calculate the vertical position of each element in |
| 95 // the debugger view. |
| 96 NSUInteger index = 0; |
| 97 |
| 98 // Display some metrics. |
| 99 _physicalFreeMemoryLabel.reset([[UILabel alloc] initWithFrame:CGRectZero]); |
| 100 [self addMetricWithName:@"Physical Free" |
| 101 atIndex:index++ |
| 102 usingLabel:_physicalFreeMemoryLabel]; |
| 103 _realMemoryUsedLabel.reset([[UILabel alloc] initWithFrame:CGRectZero]); |
| 104 [self addMetricWithName:@"Real Memory Used" |
| 105 atIndex:index++ |
| 106 usingLabel:_realMemoryUsedLabel]; |
| 107 _xcodeGaugeLabel.reset([[UILabel alloc] initWithFrame:CGRectZero]); |
| 108 [self addMetricWithName:@"Xcode Gauge" |
| 109 atIndex:index++ |
| 110 usingLabel:_xcodeGaugeLabel]; |
| 111 _dirtyVirtualMemoryLabel.reset([[UILabel alloc] initWithFrame:CGRectZero]); |
| 112 [self addMetricWithName:@"Dirty VM" |
| 113 atIndex:index++ |
| 114 usingLabel:_dirtyVirtualMemoryLabel]; |
| 115 |
| 116 // Since _performMemoryWarning is a private API it can't be compiled into |
| 117 // official builds. |
| 118 // TODO(lliabraa): Figure out how to support memory warnings (or something |
| 119 // like them) in official builds. |
| 120 #if CHROMIUM_BUILD |
| 121 [self addButtonWithTitle:@"Trigger Memory Warning" |
| 122 target:[UIApplication sharedApplication] |
| 123 action:@selector(_performMemoryWarning) |
| 124 withOrigin:[self originForSubviewAtIndex:index++]]; |
| 125 #endif // CHROMIUM_BUILD |
| 126 |
| 127 // Display a text input to set the amount of artificial memory bloat and a |
| 128 // button to reset the bloat to zero. |
| 129 _bloatField.reset([[UITextField alloc] initWithFrame:CGRectZero]); |
| 130 [self addLabelWithText:@"Set bloat (MB)" |
| 131 input:_bloatField |
| 132 inputTarget:self |
| 133 inputAction:@selector(updateBloat) |
| 134 buttonWithTitle:@"Clear" |
| 135 buttonTarget:self |
| 136 buttonAction:@selector(clearBloat) |
| 137 atIndex:index++]; |
| 138 [_bloatField setText:@"0"]; |
| 139 [self updateBloat]; |
| 140 |
| 141 // Since _performMemoryWarning is a private API it can't be compiled into |
| 142 // official builds. |
| 143 // TODO(lliabraa): Figure out how to support memory warnings (or something |
| 144 // like them) in official builds. |
| 145 #if CHROMIUM_BUILD |
| 146 // Display a text input to control the rate of continuous memory warnings. |
| 147 _continuousMemoryWarningField.reset( |
| 148 [[UITextField alloc] initWithFrame:CGRectZero]); |
| 149 [self addLabelWithText:@"Set memory warning interval (secs)" |
| 150 input:_continuousMemoryWarningField |
| 151 inputTarget:self |
| 152 inputAction:@selector(updateMemoryWarningInterval) |
| 153 atIndex:index++]; |
| 154 [_continuousMemoryWarningField setText:@"0.0"]; |
| 155 #endif // CHROMIUM_BUILD |
| 156 |
| 157 // Display a text input to control the refresh rate of the memory debugger. |
| 158 _refreshField.reset([[UITextField alloc] initWithFrame:CGRectZero]); |
| 159 [self addLabelWithText:@"Set refresh interval (secs)" |
| 160 input:_refreshField |
| 161 inputTarget:self |
| 162 inputAction:@selector(updateRefreshInterval) |
| 163 atIndex:index++]; |
| 164 [_refreshField setText:@"0.5"]; |
| 165 [self updateRefreshInterval]; |
| 166 } |
| 167 |
| 168 - (void)registerForNotifications { |
| 169 // On iOS 7, the screen coordinate system is not dependent on orientation so |
| 170 // the debugger has to handle its own rotation. |
| 171 if (!base::ios::IsRunningOnIOS8OrLater()) { |
| 172 // Register to receive orientation notifications. |
| 173 [[NSNotificationCenter defaultCenter] |
| 174 addObserver:self |
| 175 selector:@selector(adjustForOrientation:) |
| 176 name:UIDeviceOrientationDidChangeNotification |
| 177 object:nil]; |
| 178 } |
| 179 |
| 180 // Register to receive memory warning. |
| 181 [[NSNotificationCenter defaultCenter] |
| 182 addObserver:self |
| 183 selector:@selector(lowMemoryWarningReceived:) |
| 184 name:UIApplicationDidReceiveMemoryWarningNotification |
| 185 object:nil]; |
| 186 |
| 187 // Register to receive keyboard will show notification. |
| 188 [[NSNotificationCenter defaultCenter] |
| 189 addObserver:self |
| 190 selector:@selector(keyboardWillShow:) |
| 191 name:UIKeyboardWillShowNotification |
| 192 object:nil]; |
| 193 |
| 194 // Register to receive keyboard will hide notification. |
| 195 [[NSNotificationCenter defaultCenter] |
| 196 addObserver:self |
| 197 selector:@selector(keyboardWillHide:) |
| 198 name:UIKeyboardWillHideNotification |
| 199 object:nil]; |
| 200 } |
| 201 |
| 202 // Adds subviews for the specified metric, the value of which will be displayed |
| 203 // in |label|. |
| 204 - (void)addMetricWithName:(NSString*)name |
| 205 atIndex:(NSUInteger)index |
| 206 usingLabel:(UILabel*)label { |
| 207 // The width of the view for the metric's name. |
| 208 const CGFloat kNameWidth = 150; |
| 209 // The width of the view for each metric. |
| 210 const CGFloat kMetricWidth = 100; |
| 211 CGPoint nameOrigin = [self originForSubviewAtIndex:index]; |
| 212 CGRect nameFrame = |
| 213 CGRectMake(nameOrigin.x, nameOrigin.y, kNameWidth, [_font lineHeight]); |
| 214 base::scoped_nsobject<UILabel> nameLabel( |
| 215 [[UILabel alloc] initWithFrame:nameFrame]); |
| 216 [nameLabel setText:[NSString stringWithFormat:@"%@: ", name]]; |
| 217 [nameLabel setFont:_font]; |
| 218 [self addSubview:nameLabel]; |
| 219 label.frame = CGRectMake(CGRectGetMaxX(nameFrame), nameFrame.origin.y, |
| 220 kMetricWidth, [_font lineHeight]); |
| 221 [label setFont:_font]; |
| 222 [label setTextAlignment:NSTextAlignmentRight]; |
| 223 [self addSubview:label]; |
| 224 } |
| 225 |
| 226 // Adds a subview for a button with the given title and target/action. |
| 227 - (void)addButtonWithTitle:(NSString*)title |
| 228 target:(id)target |
| 229 action:(SEL)action |
| 230 withOrigin:(CGPoint)origin { |
| 231 base::scoped_nsobject<UIButton> button( |
| 232 [[UIButton buttonWithType:UIButtonTypeSystem] retain]); |
| 233 [button setTitle:title forState:UIControlStateNormal]; |
| 234 [button titleLabel].font = _font; |
| 235 [[button titleLabel] setTextAlignment:NSTextAlignmentCenter]; |
| 236 [button sizeToFit]; |
| 237 [button setFrame:CGRectMake(origin.x, origin.y, [button frame].size.width, |
| 238 [_font lineHeight])]; |
| 239 [button addTarget:target |
| 240 action:action |
| 241 forControlEvents:UIControlEventTouchUpInside]; |
| 242 [self addSubview:button]; |
| 243 } |
| 244 |
| 245 // Adds subviews for a UI component with label and input text field. |
| 246 // |
| 247 // ------------------------- |
| 248 // | labelText | <input> | |
| 249 // ------------------------- |
| 250 // |
| 251 // The inputTarget/inputAction will be invoked when the user finishes editing |
| 252 // in |input|. |
| 253 - (void)addLabelWithText:(NSString*)labelText |
| 254 input:(UITextField*)input |
| 255 inputTarget:(id)inputTarget |
| 256 inputAction:(SEL)inputAction |
| 257 atIndex:(NSUInteger)index { |
| 258 [self addLabelWithText:labelText |
| 259 input:input |
| 260 inputTarget:inputTarget |
| 261 inputAction:inputAction |
| 262 buttonWithTitle:nil |
| 263 buttonTarget:nil |
| 264 buttonAction:nil |
| 265 atIndex:index]; |
| 266 } |
| 267 |
| 268 // Adds subviews for a UI component with label, input text field and button. |
| 269 // |
| 270 // ------------------------------------- |
| 271 // | labelText | <input> | <button> | |
| 272 // ------------------------------------- |
| 273 // |
| 274 // The inputTarget/inputAction will be invoked when the user finishes editing |
| 275 // in |input|. |
| 276 - (void)addLabelWithText:(NSString*)labelText |
| 277 input:(UITextField*)input |
| 278 inputTarget:(id)inputTarget |
| 279 inputAction:(SEL)inputAction |
| 280 buttonWithTitle:(NSString*)buttonTitle |
| 281 buttonTarget:(id)buttonTarget |
| 282 buttonAction:(SEL)buttonAction |
| 283 atIndex:(NSUInteger)index { |
| 284 base::scoped_nsobject<UILabel> label( |
| 285 [[UILabel alloc] initWithFrame:CGRectZero]); |
| 286 if (labelText) { |
| 287 [label setText:[NSString stringWithFormat:@"%@: ", labelText]]; |
| 288 } |
| 289 [label setFont:_font]; |
| 290 [label sizeToFit]; |
| 291 CGPoint labelOrigin = [self originForSubviewAtIndex:index]; |
| 292 [label setFrame:CGRectOffset([label frame], labelOrigin.x, labelOrigin.y)]; |
| 293 [self addSubview:label]; |
| 294 if (input) { |
| 295 // The width of the views for each input text field. |
| 296 const CGFloat kInputWidth = 50; |
| 297 input.frame = |
| 298 CGRectMake(CGRectGetMaxX([label frame]) + kPadding, |
| 299 [label frame].origin.y, kInputWidth, [_font lineHeight]); |
| 300 input.font = _font; |
| 301 input.backgroundColor = [UIColor whiteColor]; |
| 302 input.delegate = self; |
| 303 input.keyboardType = UIKeyboardTypeNumbersAndPunctuation; |
| 304 input.adjustsFontSizeToFitWidth = YES; |
| 305 input.textAlignment = NSTextAlignmentRight; |
| 306 [input addTarget:inputTarget |
| 307 action:inputAction |
| 308 forControlEvents:UIControlEventEditingDidEnd]; |
| 309 |
| 310 [self addSubview:input]; |
| 311 } |
| 312 |
| 313 if (buttonTitle) { |
| 314 const CGFloat kButtonXOffset = |
| 315 input ? CGRectGetMaxX(input.frame) : CGRectGetMaxX([label frame]); |
| 316 CGPoint origin = |
| 317 CGPointMake(kButtonXOffset + kPadding, [label frame].origin.y); |
| 318 [self addButtonWithTitle:buttonTitle |
| 319 target:buttonTarget |
| 320 action:buttonAction |
| 321 withOrigin:origin]; |
| 322 } |
| 323 } |
| 324 |
| 325 // Returns the CGPoint of the origin of the subview at |index|. |
| 326 - (CGPoint)originForSubviewAtIndex:(NSUInteger)index { |
| 327 return CGPointMake(kPadding, |
| 328 (index + 1) * kPadding + index * [_font lineHeight]); |
| 329 } |
| 330 |
| 331 #pragma mark Refresh callback |
| 332 |
| 333 // Updates content and ensures the view is visible. |
| 334 - (void)refresh:(NSTimer*)timer { |
| 335 [self.superview bringSubviewToFront:self]; |
| 336 [self updateMemoryInfo]; |
| 337 } |
| 338 |
| 339 #pragma mark Memory inspection |
| 340 |
| 341 // Updates the memory metrics shown. |
| 342 - (void)updateMemoryInfo { |
| 343 CGFloat value = memory_util::GetFreePhysicalBytes() / kNumBytesInMB; |
| 344 [_physicalFreeMemoryLabel |
| 345 setText:[NSString stringWithFormat:@"%.2f MB", value]]; |
| 346 value = memory_util::GetRealMemoryUsedInBytes() / kNumBytesInMB; |
| 347 [_realMemoryUsedLabel setText:[NSString stringWithFormat:@"%.2f MB", value]]; |
| 348 value = memory_util::GetInternalVMBytes() / kNumBytesInMB; |
| 349 [_xcodeGaugeLabel setText:[NSString stringWithFormat:@"%.2f MB", value]]; |
| 350 value = memory_util::GetDirtyVMBytes() / kNumBytesInMB; |
| 351 [_dirtyVirtualMemoryLabel |
| 352 setText:[NSString stringWithFormat:@"%.2f MB", value]]; |
| 353 } |
| 354 |
| 355 #pragma mark Memory Warning notification callback |
| 356 |
| 357 // Flashes the debugger to indicate memory warning. |
| 358 - (void)lowMemoryWarningReceived:(NSNotification*)notification { |
| 359 UIColor* originalColor = self.backgroundColor; |
| 360 self.backgroundColor = |
| 361 [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.9]; |
| 362 [UIView animateWithDuration:1.0 |
| 363 delay:0.0 |
| 364 options:UIViewAnimationOptionAllowUserInteraction |
| 365 animations:^{ |
| 366 self.backgroundColor = originalColor; |
| 367 } |
| 368 completion:nil]; |
| 369 } |
| 370 |
| 371 #pragma mark Rotation notification callback |
| 372 |
| 373 - (void)didMoveToSuperview { |
| 374 UIView* superview = [self superview]; |
| 375 if (superview) |
| 376 [self setCenter:[superview center]]; |
| 377 } |
| 378 |
| 379 - (void)adjustForOrientation:(NSNotification*)notification { |
| 380 if (base::ios::IsRunningOnIOS8OrLater()) { |
| 381 return; |
| 382 } |
| 383 UIInterfaceOrientation orientation = |
| 384 [[UIApplication sharedApplication] statusBarOrientation]; |
| 385 if (orientation == _currentOrientation) { |
| 386 return; |
| 387 } |
| 388 _currentOrientation = orientation; |
| 389 CGFloat angle; |
| 390 switch (orientation) { |
| 391 case UIInterfaceOrientationPortrait: |
| 392 angle = 0; |
| 393 break; |
| 394 case UIInterfaceOrientationPortraitUpsideDown: |
| 395 angle = M_PI; |
| 396 break; |
| 397 case UIInterfaceOrientationLandscapeLeft: |
| 398 angle = -M_PI_2; |
| 399 break; |
| 400 case UIInterfaceOrientationLandscapeRight: |
| 401 angle = M_PI_2; |
| 402 break; |
| 403 case UIInterfaceOrientationUnknown: |
| 404 default: |
| 405 angle = 0; |
| 406 } |
| 407 |
| 408 // Since the debugger view is in screen coordinates and handles its own |
| 409 // rotation via the |transform| property, the view's position after rotation |
| 410 // can be unexpected and partially off-screen. Centering the view before |
| 411 // rotating it ensures that the view remains within the bounds of the screen. |
| 412 if (self.superview) { |
| 413 self.center = self.superview.center; |
| 414 } |
| 415 self.transform = CGAffineTransformMakeRotation(angle); |
| 416 } |
| 417 |
| 418 #pragma mark Keyboard notification callbacks |
| 419 |
| 420 // Ensures the debugger is visible by shifting it up as the keyboard animates |
| 421 // in. |
| 422 - (void)keyboardWillShow:(NSNotification*)notification { |
| 423 NSDictionary* userInfo = [notification userInfo]; |
| 424 NSValue* keyboardFrameValue = |
| 425 [userInfo valueForKey:UIKeyboardFrameEndUserInfoKey]; |
| 426 CGFloat keyboardHeight = CurrentKeyboardHeight(keyboardFrameValue); |
| 427 |
| 428 // Get the coord of the bottom of the debugger's frame. This is orientation |
| 429 // dependent on iOS 7 because the debugger is in screen coords. |
| 430 CGFloat bottomOfFrame = CGRectGetMaxY(self.frame); |
| 431 if (!base::ios::IsRunningOnIOS8OrLater() && IsLandscape()) |
| 432 bottomOfFrame = CGRectGetMaxX(self.frame); |
| 433 |
| 434 // Shift the debugger up by the "height" of the keyboard, but since the |
| 435 // keyboard rect is in screen coords, use the orientation to find the height. |
| 436 CGFloat distanceFromBottom = CurrentScreenHeight() - bottomOfFrame; |
| 437 _keyboardOffset = -1 * fmax(0.0f, keyboardHeight - distanceFromBottom); |
| 438 [self animateForKeyboardNotification:notification |
| 439 withOffset:CGPointMake(0, _keyboardOffset)]; |
| 440 } |
| 441 |
| 442 // Shifts the debugger back down when the keyboard is hidden. |
| 443 - (void)keyboardWillHide:(NSNotification*)notification { |
| 444 [self animateForKeyboardNotification:notification |
| 445 withOffset:CGPointMake(0, -_keyboardOffset)]; |
| 446 } |
| 447 |
| 448 - (void)animateForKeyboardNotification:(NSNotification*)notification |
| 449 withOffset:(CGPoint)offset { |
| 450 // Account for orientation. |
| 451 offset = CGPointApplyAffineTransform(offset, self.transform); |
| 452 // Normally this would use an animation block, but there is no API to |
| 453 // convert the UIKeyboardAnimationCurveUserInfoKey's value from a |
| 454 // UIViewAnimationCurve to a UIViewAnimationOption. Awesome! |
| 455 NSDictionary* userInfo = [notification userInfo]; |
| 456 [UIView beginAnimations:nil context:nullptr]; |
| 457 [UIView setAnimationDuration: |
| 458 [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]]; |
| 459 NSInteger animationCurveKeyValue = |
| 460 [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]; |
| 461 UIViewAnimationCurve animationCurve = |
| 462 (UIViewAnimationCurve)animationCurveKeyValue; |
| 463 [UIView setAnimationCurve:animationCurve]; |
| 464 [UIView setAnimationBeginsFromCurrentState:YES]; |
| 465 self.frame = CGRectOffset(self.frame, offset.x, offset.y); |
| 466 [UIView commitAnimations]; |
| 467 } |
| 468 |
| 469 #pragma mark Artificial memory bloat methods |
| 470 |
| 471 - (void)updateBloat { |
| 472 double bloatSizeMB; |
| 473 NSScanner* scanner = [NSScanner scannerWithString:[_bloatField text]]; |
| 474 if (![scanner scanDouble:&bloatSizeMB] || bloatSizeMB < 0.0) { |
| 475 bloatSizeMB = 0; |
| 476 NSString* errorMessage = |
| 477 [NSString stringWithFormat:@"Invalid value \"%@\" for bloat size.\n" |
| 478 @"Must be a positive number.\n" |
| 479 @"Resetting to %.1f MB", |
| 480 [_bloatField text], bloatSizeMB]; |
| 481 [self alert:errorMessage]; |
| 482 [_bloatField setText:[NSString stringWithFormat:@"%.1f", bloatSizeMB]]; |
| 483 } |
| 484 const CGFloat kBloatSizeBytes = ceil(bloatSizeMB * kNumBytesInMB); |
| 485 const uint64 kNumberOfBytes = static_cast<uint64>(kBloatSizeBytes); |
| 486 _bloat.reset(kNumberOfBytes ? new uint8[kNumberOfBytes] : nullptr); |
| 487 if (_bloat) { |
| 488 memset(_bloat.get(), -1, kNumberOfBytes); // Occupy memory. |
| 489 } else { |
| 490 if (kNumberOfBytes) { |
| 491 [self alert:@"Could not allocate memory."]; |
| 492 } |
| 493 } |
| 494 } |
| 495 |
| 496 - (void)clearBloat { |
| 497 [_bloatField setText:@"0"]; |
| 498 [_bloatField resignFirstResponder]; |
| 499 [self updateBloat]; |
| 500 } |
| 501 |
| 502 #pragma mark Refresh interval methods |
| 503 |
| 504 - (void)updateRefreshInterval { |
| 505 double refreshTimerValue; |
| 506 NSScanner* scanner = [NSScanner scannerWithString:[_refreshField text]]; |
| 507 if (![scanner scanDouble:&refreshTimerValue] || refreshTimerValue < 0.0) { |
| 508 refreshTimerValue = 0.5; |
| 509 NSString* errorMessage = [NSString |
| 510 stringWithFormat:@"Invalid value \"%@\" for refresh interval.\n" |
| 511 @"Must be a positive number.\n" @"Resetting to %.1f", |
| 512 [_refreshField text], refreshTimerValue]; |
| 513 [self alert:errorMessage]; |
| 514 [_refreshField |
| 515 setText:[NSString stringWithFormat:@"%.1f", refreshTimerValue]]; |
| 516 return; |
| 517 } |
| 518 [_refreshTimer invalidate]; |
| 519 _refreshTimer.reset( |
| 520 [[NSTimer scheduledTimerWithTimeInterval:refreshTimerValue |
| 521 target:self |
| 522 selector:@selector(refresh:) |
| 523 userInfo:nil |
| 524 repeats:YES] retain]); |
| 525 } |
| 526 |
| 527 #pragma mark Memory warning interval methods |
| 528 |
| 529 // Since _performMemoryWarning is a private API it can't be compiled into |
| 530 // official builds. |
| 531 // TODO(lliabraa): Figure out how to support memory warnings (or something |
| 532 // like them) in official builds. |
| 533 #if CHROMIUM_BUILD |
| 534 - (void)updateMemoryWarningInterval { |
| 535 [_memoryWarningTimer invalidate]; |
| 536 double timerValue; |
| 537 NSString* text = [_continuousMemoryWarningField text]; |
| 538 NSScanner* scanner = [NSScanner scannerWithString:text]; |
| 539 BOOL valueFound = [scanner scanDouble:&timerValue]; |
| 540 // If the text field is empty or contains 0, return early to turn off |
| 541 // continuous memory warnings. |
| 542 if (![text length] || timerValue == 0.0) { |
| 543 return; |
| 544 } |
| 545 // If no value could be parsed or a non-positive value was found, throw up an |
| 546 // error message and return early to turn off continuous memory warnings. |
| 547 if (!valueFound || timerValue <= 0.0) { |
| 548 NSString* errorMessage = [NSString |
| 549 stringWithFormat:@"Invalid value \"%@\" for memory warning interval.\n" |
| 550 @"Must be a positive number.\n" |
| 551 @"Turning off continuous memory warnings", |
| 552 text]; |
| 553 [self alert:errorMessage]; |
| 554 [_continuousMemoryWarningField setText:@""]; |
| 555 return; |
| 556 } |
| 557 // If a valid value was found have the timer start triggering continuous |
| 558 // memory warnings. |
| 559 _memoryWarningTimer.reset( |
| 560 [[NSTimer scheduledTimerWithTimeInterval:timerValue |
| 561 target:[UIApplication sharedApplication] |
| 562 selector:@selector(_performMemoryWarning) |
| 563 userInfo:nil |
| 564 repeats:YES] retain]); |
| 565 } |
| 566 #endif // CHROMIUM_BUILD |
| 567 |
| 568 #pragma mark UITextViewDelegate methods |
| 569 |
| 570 // Dismisses the keyboard if the user hits return. |
| 571 - (BOOL)textFieldShouldReturn:(UITextField*)textField { |
| 572 [textField resignFirstResponder]; |
| 573 return YES; |
| 574 } |
| 575 |
| 576 #pragma mark UIResponder methods |
| 577 |
| 578 // Allows the debugger to be dragged around the screen. |
| 579 - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event { |
| 580 UITouch* touch = [touches anyObject]; |
| 581 CGPoint start = [touch previousLocationInView:self]; |
| 582 CGPoint end = [touch locationInView:self]; |
| 583 CGPoint offset = CGPointMake(end.x - start.x, end.y - start.y); |
| 584 offset = CGPointApplyAffineTransform(offset, self.transform); |
| 585 self.frame = CGRectOffset(self.frame, offset.x, offset.y); |
| 586 } |
| 587 |
| 588 #pragma mark Error handling |
| 589 |
| 590 // Shows an alert with the given |errorMessage|. |
| 591 - (void)alert:(NSString*)errorMessage { |
| 592 UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Error" |
| 593 message:errorMessage |
| 594 delegate:self |
| 595 cancelButtonTitle:@"OK" |
| 596 otherButtonTitles:nil, nil]; |
| 597 [alert show]; |
| 598 } |
| 599 |
| 600 @end |
OLD | NEW |