Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(55)

Side by Side Diff: ios/chrome/browser/memory/memory_debugger.mm

Issue 1057933002: [iOS] Upstream //ios/chrome/browser/memory (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@upstreamNet
Patch Set: Remove extra bracket Created 5 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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
OLDNEW
« no previous file with comments | « ios/chrome/browser/memory/memory_debugger.h ('k') | ios/chrome/browser/memory/memory_debugger_manager.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698