Index: ios/chrome/browser/memory/memory_debugger.mm |
diff --git a/ios/chrome/browser/memory/memory_debugger.mm b/ios/chrome/browser/memory/memory_debugger.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..22bf88181d6da8b8b9fe6f3cfa2475bb19adcb51 |
--- /dev/null |
+++ b/ios/chrome/browser/memory/memory_debugger.mm |
@@ -0,0 +1,600 @@ |
+// Copyright 2014 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/browser/memory/memory_debugger.h" |
+ |
+#include "base/ios/ios_util.h" |
+#import "base/mac/scoped_nsobject.h" |
+#import "base/memory/scoped_ptr.h" |
+#import "ios/chrome/browser/memory/memory_metrics.h" |
+#include "ios/chrome/browser/ui/ui_util.h" |
+#import "ios/chrome/browser/ui/uikit_ui_util.h" |
+ |
+namespace { |
+// The number of bytes in a megabyte. |
+const CGFloat kNumBytesInMB = 1024 * 1024; |
+// The horizontal and vertical padding between subviews. |
+const CGFloat kPadding = 10; |
+} // namespace |
+ |
+@implementation MemoryDebugger { |
+ // A timer to trigger refreshes. |
+ base::scoped_nsobject<NSTimer> _refreshTimer; |
+ |
+ // A timer to trigger continuous memory warnings. |
+ base::scoped_nsobject<NSTimer> _memoryWarningTimer; |
+ |
+ // The font to use. |
+ base::scoped_nsobject<UIFont> _font; |
+ |
+ // Labels for memory metrics. |
+ base::scoped_nsobject<UILabel> _physicalFreeMemoryLabel; |
+ base::scoped_nsobject<UILabel> _realMemoryUsedLabel; |
+ base::scoped_nsobject<UILabel> _xcodeGaugeLabel; |
+ base::scoped_nsobject<UILabel> _dirtyVirtualMemoryLabel; |
+ |
+ // Inputs for memory commands. |
+ base::scoped_nsobject<UITextField> _bloatField; |
+ base::scoped_nsobject<UITextField> _refreshField; |
+ base::scoped_nsobject<UITextField> _continuousMemoryWarningField; |
+ |
+ // A place to store the artifical memory bloat. |
+ scoped_ptr<uint8> _bloat; |
+ |
+ // Distance the view was pushed up to accomodate the keyboard. |
+ CGFloat _keyboardOffset; |
+ |
+ // The current orientation of the device. |
+ BOOL _currentOrientation; |
+} |
+ |
+- (instancetype)init { |
+ self = [super initWithFrame:CGRectZero]; |
+ if (self) { |
+ _font.reset([[UIFont systemFontOfSize:14] retain]); |
+ self.backgroundColor = [UIColor colorWithWhite:0.8f alpha:0.9f]; |
+ self.opaque = NO; |
+ |
+ [self addSubviews]; |
+ [self adjustForOrientation:nil]; |
+ [self sizeToFit]; |
+ [self registerForNotifications]; |
+ } |
+ return self; |
+} |
+ |
+// NSTimers create a retain cycle so they must be invalidated before this |
+// instance can be deallocated. |
+- (void)invalidateTimers { |
+ [_refreshTimer invalidate]; |
+ [_memoryWarningTimer invalidate]; |
+} |
+ |
+- (void)dealloc { |
+ [[NSNotificationCenter defaultCenter] removeObserver:self]; |
+ [super dealloc]; |
+} |
+ |
+#pragma mark UIView methods |
+ |
+- (CGSize)sizeThatFits:(CGSize)size { |
+ CGFloat width = 0; |
+ CGFloat height = 0; |
+ for (UIView* subview in self.subviews) { |
+ width = MAX(width, CGRectGetMaxX(subview.frame)); |
+ height = MAX(height, CGRectGetMaxY(subview.frame)); |
+ } |
+ return CGSizeMake(width + kPadding, height + kPadding); |
+} |
+ |
+#pragma mark initialization helpers |
+ |
+- (void)addSubviews { |
+ // |index| is used to calculate the vertical position of each element in |
+ // the debugger view. |
+ NSUInteger index = 0; |
+ |
+ // Display some metrics. |
+ _physicalFreeMemoryLabel.reset([[UILabel alloc] initWithFrame:CGRectZero]); |
+ [self addMetricWithName:@"Physical Free" |
+ atIndex:index++ |
+ usingLabel:_physicalFreeMemoryLabel]; |
+ _realMemoryUsedLabel.reset([[UILabel alloc] initWithFrame:CGRectZero]); |
+ [self addMetricWithName:@"Real Memory Used" |
+ atIndex:index++ |
+ usingLabel:_realMemoryUsedLabel]; |
+ _xcodeGaugeLabel.reset([[UILabel alloc] initWithFrame:CGRectZero]); |
+ [self addMetricWithName:@"Xcode Gauge" |
+ atIndex:index++ |
+ usingLabel:_xcodeGaugeLabel]; |
+ _dirtyVirtualMemoryLabel.reset([[UILabel alloc] initWithFrame:CGRectZero]); |
+ [self addMetricWithName:@"Dirty VM" |
+ atIndex:index++ |
+ usingLabel:_dirtyVirtualMemoryLabel]; |
+ |
+// Since _performMemoryWarning is a private API it can't be compiled into |
+// official builds. |
+// TODO(lliabraa): Figure out how to support memory warnings (or something |
+// like them) in official builds. |
+#if CHROMIUM_BUILD |
+ [self addButtonWithTitle:@"Trigger Memory Warning" |
+ target:[UIApplication sharedApplication] |
+ action:@selector(_performMemoryWarning) |
+ withOrigin:[self originForSubviewAtIndex:index++]]; |
+#endif // CHROMIUM_BUILD |
+ |
+ // Display a text input to set the amount of artificial memory bloat and a |
+ // button to reset the bloat to zero. |
+ _bloatField.reset([[UITextField alloc] initWithFrame:CGRectZero]); |
+ [self addLabelWithText:@"Set bloat (MB)" |
+ input:_bloatField |
+ inputTarget:self |
+ inputAction:@selector(updateBloat) |
+ buttonWithTitle:@"Clear" |
+ buttonTarget:self |
+ buttonAction:@selector(clearBloat) |
+ atIndex:index++]; |
+ [_bloatField setText:@"0"]; |
+ [self updateBloat]; |
+ |
+// Since _performMemoryWarning is a private API it can't be compiled into |
+// official builds. |
+// TODO(lliabraa): Figure out how to support memory warnings (or something |
+// like them) in official builds. |
+#if CHROMIUM_BUILD |
+ // Display a text input to control the rate of continuous memory warnings. |
+ _continuousMemoryWarningField.reset( |
+ [[UITextField alloc] initWithFrame:CGRectZero]); |
+ [self addLabelWithText:@"Set memory warning interval (secs)" |
+ input:_continuousMemoryWarningField |
+ inputTarget:self |
+ inputAction:@selector(updateMemoryWarningInterval) |
+ atIndex:index++]; |
+ [_continuousMemoryWarningField setText:@"0.0"]; |
+#endif // CHROMIUM_BUILD |
+ |
+ // Display a text input to control the refresh rate of the memory debugger. |
+ _refreshField.reset([[UITextField alloc] initWithFrame:CGRectZero]); |
+ [self addLabelWithText:@"Set refresh interval (secs)" |
+ input:_refreshField |
+ inputTarget:self |
+ inputAction:@selector(updateRefreshInterval) |
+ atIndex:index++]; |
+ [_refreshField setText:@"0.5"]; |
+ [self updateRefreshInterval]; |
+} |
+ |
+- (void)registerForNotifications { |
+ // On iOS 7, the screen coordinate system is not dependent on orientation so |
+ // the debugger has to handle its own rotation. |
+ if (!base::ios::IsRunningOnIOS8OrLater()) { |
+ // Register to receive orientation notifications. |
+ [[NSNotificationCenter defaultCenter] |
+ addObserver:self |
+ selector:@selector(adjustForOrientation:) |
+ name:UIDeviceOrientationDidChangeNotification |
+ object:nil]; |
+ } |
+ |
+ // Register to receive memory warning. |
+ [[NSNotificationCenter defaultCenter] |
+ addObserver:self |
+ selector:@selector(lowMemoryWarningReceived:) |
+ name:UIApplicationDidReceiveMemoryWarningNotification |
+ object:nil]; |
+ |
+ // Register to receive keyboard will show notification. |
+ [[NSNotificationCenter defaultCenter] |
+ addObserver:self |
+ selector:@selector(keyboardWillShow:) |
+ name:UIKeyboardWillShowNotification |
+ object:nil]; |
+ |
+ // Register to receive keyboard will hide notification. |
+ [[NSNotificationCenter defaultCenter] |
+ addObserver:self |
+ selector:@selector(keyboardWillHide:) |
+ name:UIKeyboardWillHideNotification |
+ object:nil]; |
+} |
+ |
+// Adds subviews for the specified metric, the value of which will be displayed |
+// in |label|. |
+- (void)addMetricWithName:(NSString*)name |
+ atIndex:(NSUInteger)index |
+ usingLabel:(UILabel*)label { |
+ // The width of the view for the metric's name. |
+ const CGFloat kNameWidth = 150; |
+ // The width of the view for each metric. |
+ const CGFloat kMetricWidth = 100; |
+ CGPoint nameOrigin = [self originForSubviewAtIndex:index]; |
+ CGRect nameFrame = |
+ CGRectMake(nameOrigin.x, nameOrigin.y, kNameWidth, [_font lineHeight]); |
+ base::scoped_nsobject<UILabel> nameLabel( |
+ [[UILabel alloc] initWithFrame:nameFrame]); |
+ [nameLabel setText:[NSString stringWithFormat:@"%@: ", name]]; |
+ [nameLabel setFont:_font]; |
+ [self addSubview:nameLabel]; |
+ label.frame = CGRectMake(CGRectGetMaxX(nameFrame), nameFrame.origin.y, |
+ kMetricWidth, [_font lineHeight]); |
+ [label setFont:_font]; |
+ [label setTextAlignment:NSTextAlignmentRight]; |
+ [self addSubview:label]; |
+} |
+ |
+// Adds a subview for a button with the given title and target/action. |
+- (void)addButtonWithTitle:(NSString*)title |
+ target:(id)target |
+ action:(SEL)action |
+ withOrigin:(CGPoint)origin { |
+ base::scoped_nsobject<UIButton> button( |
+ [[UIButton buttonWithType:UIButtonTypeSystem] retain]); |
+ [button setTitle:title forState:UIControlStateNormal]; |
+ [button titleLabel].font = _font; |
+ [[button titleLabel] setTextAlignment:NSTextAlignmentCenter]; |
+ [button sizeToFit]; |
+ [button setFrame:CGRectMake(origin.x, origin.y, [button frame].size.width, |
+ [_font lineHeight])]; |
+ [button addTarget:target |
+ action:action |
+ forControlEvents:UIControlEventTouchUpInside]; |
+ [self addSubview:button]; |
+} |
+ |
+// Adds subviews for a UI component with label and input text field. |
+// |
+// ------------------------- |
+// | labelText | <input> | |
+// ------------------------- |
+// |
+// The inputTarget/inputAction will be invoked when the user finishes editing |
+// in |input|. |
+- (void)addLabelWithText:(NSString*)labelText |
+ input:(UITextField*)input |
+ inputTarget:(id)inputTarget |
+ inputAction:(SEL)inputAction |
+ atIndex:(NSUInteger)index { |
+ [self addLabelWithText:labelText |
+ input:input |
+ inputTarget:inputTarget |
+ inputAction:inputAction |
+ buttonWithTitle:nil |
+ buttonTarget:nil |
+ buttonAction:nil |
+ atIndex:index]; |
+} |
+ |
+// Adds subviews for a UI component with label, input text field and button. |
+// |
+// ------------------------------------- |
+// | labelText | <input> | <button> | |
+// ------------------------------------- |
+// |
+// The inputTarget/inputAction will be invoked when the user finishes editing |
+// in |input|. |
+- (void)addLabelWithText:(NSString*)labelText |
+ input:(UITextField*)input |
+ inputTarget:(id)inputTarget |
+ inputAction:(SEL)inputAction |
+ buttonWithTitle:(NSString*)buttonTitle |
+ buttonTarget:(id)buttonTarget |
+ buttonAction:(SEL)buttonAction |
+ atIndex:(NSUInteger)index { |
+ base::scoped_nsobject<UILabel> label( |
+ [[UILabel alloc] initWithFrame:CGRectZero]); |
+ if (labelText) { |
+ [label setText:[NSString stringWithFormat:@"%@: ", labelText]]; |
+ } |
+ [label setFont:_font]; |
+ [label sizeToFit]; |
+ CGPoint labelOrigin = [self originForSubviewAtIndex:index]; |
+ [label setFrame:CGRectOffset([label frame], labelOrigin.x, labelOrigin.y)]; |
+ [self addSubview:label]; |
+ if (input) { |
+ // The width of the views for each input text field. |
+ const CGFloat kInputWidth = 50; |
+ input.frame = |
+ CGRectMake(CGRectGetMaxX([label frame]) + kPadding, |
+ [label frame].origin.y, kInputWidth, [_font lineHeight]); |
+ input.font = _font; |
+ input.backgroundColor = [UIColor whiteColor]; |
+ input.delegate = self; |
+ input.keyboardType = UIKeyboardTypeNumbersAndPunctuation; |
+ input.adjustsFontSizeToFitWidth = YES; |
+ input.textAlignment = NSTextAlignmentRight; |
+ [input addTarget:inputTarget |
+ action:inputAction |
+ forControlEvents:UIControlEventEditingDidEnd]; |
+ |
+ [self addSubview:input]; |
+ } |
+ |
+ if (buttonTitle) { |
+ const CGFloat kButtonXOffset = |
+ input ? CGRectGetMaxX(input.frame) : CGRectGetMaxX([label frame]); |
+ CGPoint origin = |
+ CGPointMake(kButtonXOffset + kPadding, [label frame].origin.y); |
+ [self addButtonWithTitle:buttonTitle |
+ target:buttonTarget |
+ action:buttonAction |
+ withOrigin:origin]; |
+ } |
+} |
+ |
+// Returns the CGPoint of the origin of the subview at |index|. |
+- (CGPoint)originForSubviewAtIndex:(NSUInteger)index { |
+ return CGPointMake(kPadding, |
+ (index + 1) * kPadding + index * [_font lineHeight]); |
+} |
+ |
+#pragma mark Refresh callback |
+ |
+// Updates content and ensures the view is visible. |
+- (void)refresh:(NSTimer*)timer { |
+ [self.superview bringSubviewToFront:self]; |
+ [self updateMemoryInfo]; |
+} |
+ |
+#pragma mark Memory inspection |
+ |
+// Updates the memory metrics shown. |
+- (void)updateMemoryInfo { |
+ CGFloat value = memory_util::GetFreePhysicalBytes() / kNumBytesInMB; |
+ [_physicalFreeMemoryLabel |
+ setText:[NSString stringWithFormat:@"%.2f MB", value]]; |
+ value = memory_util::GetRealMemoryUsedInBytes() / kNumBytesInMB; |
+ [_realMemoryUsedLabel setText:[NSString stringWithFormat:@"%.2f MB", value]]; |
+ value = memory_util::GetInternalVMBytes() / kNumBytesInMB; |
+ [_xcodeGaugeLabel setText:[NSString stringWithFormat:@"%.2f MB", value]]; |
+ value = memory_util::GetDirtyVMBytes() / kNumBytesInMB; |
+ [_dirtyVirtualMemoryLabel |
+ setText:[NSString stringWithFormat:@"%.2f MB", value]]; |
+} |
+ |
+#pragma mark Memory Warning notification callback |
+ |
+// Flashes the debugger to indicate memory warning. |
+- (void)lowMemoryWarningReceived:(NSNotification*)notification { |
+ UIColor* originalColor = self.backgroundColor; |
+ self.backgroundColor = |
+ [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.9]; |
+ [UIView animateWithDuration:1.0 |
+ delay:0.0 |
+ options:UIViewAnimationOptionAllowUserInteraction |
+ animations:^{ |
+ self.backgroundColor = originalColor; |
+ } |
+ completion:nil]; |
+} |
+ |
+#pragma mark Rotation notification callback |
+ |
+- (void)didMoveToSuperview { |
+ UIView* superview = [self superview]; |
+ if (superview) |
+ [self setCenter:[superview center]]; |
+} |
+ |
+- (void)adjustForOrientation:(NSNotification*)notification { |
+ if (base::ios::IsRunningOnIOS8OrLater()) { |
+ return; |
+ } |
+ UIInterfaceOrientation orientation = |
+ [[UIApplication sharedApplication] statusBarOrientation]; |
+ if (orientation == _currentOrientation) { |
+ return; |
+ } |
+ _currentOrientation = orientation; |
+ CGFloat angle; |
+ switch (orientation) { |
+ case UIInterfaceOrientationPortrait: |
+ angle = 0; |
+ break; |
+ case UIInterfaceOrientationPortraitUpsideDown: |
+ angle = M_PI; |
+ break; |
+ case UIInterfaceOrientationLandscapeLeft: |
+ angle = -M_PI_2; |
+ break; |
+ case UIInterfaceOrientationLandscapeRight: |
+ angle = M_PI_2; |
+ break; |
+ case UIInterfaceOrientationUnknown: |
+ default: |
+ angle = 0; |
+ } |
+ |
+ // Since the debugger view is in screen coordinates and handles its own |
+ // rotation via the |transform| property, the view's position after rotation |
+ // can be unexpected and partially off-screen. Centering the view before |
+ // rotating it ensures that the view remains within the bounds of the screen. |
+ if (self.superview) { |
+ self.center = self.superview.center; |
+ } |
+ self.transform = CGAffineTransformMakeRotation(angle); |
+} |
+ |
+#pragma mark Keyboard notification callbacks |
+ |
+// Ensures the debugger is visible by shifting it up as the keyboard animates |
+// in. |
+- (void)keyboardWillShow:(NSNotification*)notification { |
+ NSDictionary* userInfo = [notification userInfo]; |
+ NSValue* keyboardFrameValue = |
+ [userInfo valueForKey:UIKeyboardFrameEndUserInfoKey]; |
+ CGFloat keyboardHeight = CurrentKeyboardHeight(keyboardFrameValue); |
+ |
+ // Get the coord of the bottom of the debugger's frame. This is orientation |
+ // dependent on iOS 7 because the debugger is in screen coords. |
+ CGFloat bottomOfFrame = CGRectGetMaxY(self.frame); |
+ if (!base::ios::IsRunningOnIOS8OrLater() && IsLandscape()) |
+ bottomOfFrame = CGRectGetMaxX(self.frame); |
+ |
+ // Shift the debugger up by the "height" of the keyboard, but since the |
+ // keyboard rect is in screen coords, use the orientation to find the height. |
+ CGFloat distanceFromBottom = CurrentScreenHeight() - bottomOfFrame; |
+ _keyboardOffset = -1 * fmax(0.0f, keyboardHeight - distanceFromBottom); |
+ [self animateForKeyboardNotification:notification |
+ withOffset:CGPointMake(0, _keyboardOffset)]; |
+} |
+ |
+// Shifts the debugger back down when the keyboard is hidden. |
+- (void)keyboardWillHide:(NSNotification*)notification { |
+ [self animateForKeyboardNotification:notification |
+ withOffset:CGPointMake(0, -_keyboardOffset)]; |
+} |
+ |
+- (void)animateForKeyboardNotification:(NSNotification*)notification |
+ withOffset:(CGPoint)offset { |
+ // Account for orientation. |
+ offset = CGPointApplyAffineTransform(offset, self.transform); |
+ // Normally this would use an animation block, but there is no API to |
+ // convert the UIKeyboardAnimationCurveUserInfoKey's value from a |
+ // UIViewAnimationCurve to a UIViewAnimationOption. Awesome! |
+ NSDictionary* userInfo = [notification userInfo]; |
+ [UIView beginAnimations:nil context:nullptr]; |
+ [UIView setAnimationDuration: |
+ [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]]; |
+ NSInteger animationCurveKeyValue = |
+ [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]; |
+ UIViewAnimationCurve animationCurve = |
+ (UIViewAnimationCurve)animationCurveKeyValue; |
+ [UIView setAnimationCurve:animationCurve]; |
+ [UIView setAnimationBeginsFromCurrentState:YES]; |
+ self.frame = CGRectOffset(self.frame, offset.x, offset.y); |
+ [UIView commitAnimations]; |
+} |
+ |
+#pragma mark Artificial memory bloat methods |
+ |
+- (void)updateBloat { |
+ double bloatSizeMB; |
+ NSScanner* scanner = [NSScanner scannerWithString:[_bloatField text]]; |
+ if (![scanner scanDouble:&bloatSizeMB] || bloatSizeMB < 0.0) { |
+ bloatSizeMB = 0; |
+ NSString* errorMessage = |
+ [NSString stringWithFormat:@"Invalid value \"%@\" for bloat size.\n" |
+ @"Must be a positive number.\n" |
+ @"Resetting to %.1f MB", |
+ [_bloatField text], bloatSizeMB]; |
+ [self alert:errorMessage]; |
+ [_bloatField setText:[NSString stringWithFormat:@"%.1f", bloatSizeMB]]; |
+ } |
+ const CGFloat kBloatSizeBytes = ceil(bloatSizeMB * kNumBytesInMB); |
+ const uint64 kNumberOfBytes = static_cast<uint64>(kBloatSizeBytes); |
+ _bloat.reset(kNumberOfBytes ? new uint8[kNumberOfBytes] : nullptr); |
+ if (_bloat) { |
+ memset(_bloat.get(), -1, kNumberOfBytes); // Occupy memory. |
+ } else { |
+ if (kNumberOfBytes) { |
+ [self alert:@"Could not allocate memory."]; |
+ } |
+ } |
+} |
+ |
+- (void)clearBloat { |
+ [_bloatField setText:@"0"]; |
+ [_bloatField resignFirstResponder]; |
+ [self updateBloat]; |
+} |
+ |
+#pragma mark Refresh interval methods |
+ |
+- (void)updateRefreshInterval { |
+ double refreshTimerValue; |
+ NSScanner* scanner = [NSScanner scannerWithString:[_refreshField text]]; |
+ if (![scanner scanDouble:&refreshTimerValue] || refreshTimerValue < 0.0) { |
+ refreshTimerValue = 0.5; |
+ NSString* errorMessage = [NSString |
+ stringWithFormat:@"Invalid value \"%@\" for refresh interval.\n" |
+ @"Must be a positive number.\n" @"Resetting to %.1f", |
+ [_refreshField text], refreshTimerValue]; |
+ [self alert:errorMessage]; |
+ [_refreshField |
+ setText:[NSString stringWithFormat:@"%.1f", refreshTimerValue]]; |
+ return; |
+ } |
+ [_refreshTimer invalidate]; |
+ _refreshTimer.reset( |
+ [[NSTimer scheduledTimerWithTimeInterval:refreshTimerValue |
+ target:self |
+ selector:@selector(refresh:) |
+ userInfo:nil |
+ repeats:YES] retain]); |
+} |
+ |
+#pragma mark Memory warning interval methods |
+ |
+// Since _performMemoryWarning is a private API it can't be compiled into |
+// official builds. |
+// TODO(lliabraa): Figure out how to support memory warnings (or something |
+// like them) in official builds. |
+#if CHROMIUM_BUILD |
+- (void)updateMemoryWarningInterval { |
+ [_memoryWarningTimer invalidate]; |
+ double timerValue; |
+ NSString* text = [_continuousMemoryWarningField text]; |
+ NSScanner* scanner = [NSScanner scannerWithString:text]; |
+ BOOL valueFound = [scanner scanDouble:&timerValue]; |
+ // If the text field is empty or contains 0, return early to turn off |
+ // continuous memory warnings. |
+ if (![text length] || timerValue == 0.0) { |
+ return; |
+ } |
+ // If no value could be parsed or a non-positive value was found, throw up an |
+ // error message and return early to turn off continuous memory warnings. |
+ if (!valueFound || timerValue <= 0.0) { |
+ NSString* errorMessage = [NSString |
+ stringWithFormat:@"Invalid value \"%@\" for memory warning interval.\n" |
+ @"Must be a positive number.\n" |
+ @"Turning off continuous memory warnings", |
+ text]; |
+ [self alert:errorMessage]; |
+ [_continuousMemoryWarningField setText:@""]; |
+ return; |
+ } |
+ // If a valid value was found have the timer start triggering continuous |
+ // memory warnings. |
+ _memoryWarningTimer.reset( |
+ [[NSTimer scheduledTimerWithTimeInterval:timerValue |
+ target:[UIApplication sharedApplication] |
+ selector:@selector(_performMemoryWarning) |
+ userInfo:nil |
+ repeats:YES] retain]); |
+} |
+#endif // CHROMIUM_BUILD |
+ |
+#pragma mark UITextViewDelegate methods |
+ |
+// Dismisses the keyboard if the user hits return. |
+- (BOOL)textFieldShouldReturn:(UITextField*)textField { |
+ [textField resignFirstResponder]; |
+ return YES; |
+} |
+ |
+#pragma mark UIResponder methods |
+ |
+// Allows the debugger to be dragged around the screen. |
+- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event { |
+ UITouch* touch = [touches anyObject]; |
+ CGPoint start = [touch previousLocationInView:self]; |
+ CGPoint end = [touch locationInView:self]; |
+ CGPoint offset = CGPointMake(end.x - start.x, end.y - start.y); |
+ offset = CGPointApplyAffineTransform(offset, self.transform); |
+ self.frame = CGRectOffset(self.frame, offset.x, offset.y); |
+} |
+ |
+#pragma mark Error handling |
+ |
+// Shows an alert with the given |errorMessage|. |
+- (void)alert:(NSString*)errorMessage { |
+ UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Error" |
+ message:errorMessage |
+ delegate:self |
+ cancelButtonTitle:@"OK" |
+ otherButtonTitles:nil, nil]; |
+ [alert show]; |
+} |
+ |
+@end |