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 veritical position of each element in | |
sdefresne
2015/04/03 08:21:49
nit: s/veritical/vertical/
| |
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 // stable builds. | |
sdefresne
2015/04/03 08:21:48
nit: s/stable/official/
| |
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 // stable builds. | |
sdefresne
2015/04/03 08:21:48
nit: s/stable/official/
| |
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 // TODO(lliabraa): Correctly handle rotation (crbug/368263). | |
sdefresne
2015/04/03 08:21:48
nit: Remove this comment as the bug has been marke
| |
413 if (self.superview) { | |
414 self.center = self.superview.center; | |
415 } | |
416 self.transform = CGAffineTransformMakeRotation(angle); | |
417 } | |
418 | |
419 #pragma mark Keyboard notification callbacks | |
420 | |
421 // Ensures the debugger is visible by shifting it up as the keyboard animates | |
422 // in. | |
423 - (void)keyboardWillShow:(NSNotification*)notification { | |
424 NSDictionary* userInfo = [notification userInfo]; | |
425 NSValue* keyboardFrameValue = | |
426 [userInfo valueForKey:UIKeyboardFrameEndUserInfoKey]; | |
427 CGFloat keyboardHeight = CurrentKeyboardHeight(keyboardFrameValue); | |
428 | |
429 // Get the coord of the bottom of the debugger's frame. This is orientation | |
430 // dependent on iOS 7 because the debugger is in screen coords. | |
431 CGFloat bottomOfFrame = CGRectGetMaxY(self.frame); | |
432 if (!base::ios::IsRunningOnIOS8OrLater() && IsLandscape()) | |
433 bottomOfFrame = CGRectGetMaxX(self.frame); | |
434 | |
435 // Shift the debugger up by the "height" of the keyboard, but since the | |
436 // keyboard rect is in screen coords, use the orientation to find the height. | |
437 CGFloat distanceFromBottom = CurrentScreenHeight() - bottomOfFrame; | |
438 _keyboardOffset = -1 * fmax(0.0f, keyboardHeight - distanceFromBottom); | |
439 [self animateForKeyboardNotification:notification | |
440 withOffset:CGPointMake(0, _keyboardOffset)]; | |
441 } | |
442 | |
443 // Shifts the debugger back down when the keyboard is hidden. | |
444 - (void)keyboardWillHide:(NSNotification*)notification { | |
445 [self animateForKeyboardNotification:notification | |
446 withOffset:CGPointMake(0, -_keyboardOffset)]; | |
447 } | |
448 | |
449 - (void)animateForKeyboardNotification:(NSNotification*)notification | |
450 withOffset:(CGPoint)offset { | |
451 // Account for orientation. | |
452 offset = CGPointApplyAffineTransform(offset, self.transform); | |
453 // Normally this would use an animation block, but there is no API to | |
454 // convert the UIKeyboardAnimationCurveUserInfoKey's value from a | |
455 // UIViewAnimationCurve to a UIViewAnimationOption. Awesome! | |
456 NSDictionary* userInfo = [notification userInfo]; | |
457 [UIView beginAnimations:nil context:NULL]; | |
458 [UIView setAnimationDuration: | |
459 [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]]; | |
460 NSInteger animationCurveKeyValue = | |
461 [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]; | |
462 UIViewAnimationCurve animationCurve = | |
463 (UIViewAnimationCurve)animationCurveKeyValue; | |
464 [UIView setAnimationCurve:animationCurve]; | |
465 [UIView setAnimationBeginsFromCurrentState:YES]; | |
466 self.frame = CGRectOffset(self.frame, offset.x, offset.y); | |
467 [UIView commitAnimations]; | |
468 } | |
469 | |
470 #pragma mark Artificial memory bloat methods | |
471 | |
472 - (void)updateBloat { | |
473 double bloatSizeMB; | |
474 NSScanner* scanner = [NSScanner scannerWithString:[_bloatField text]]; | |
475 if (![scanner scanDouble:&bloatSizeMB] || bloatSizeMB < 0.0) { | |
476 bloatSizeMB = 0; | |
477 NSString* errorMessage = | |
478 [NSString stringWithFormat:@"Invalid value \"%@\" for bloat size.\n" | |
479 @"Must be a positive number.\n" | |
480 @"Resetting to %.1f MB", | |
481 [_bloatField text], bloatSizeMB]; | |
482 [self alert:errorMessage]; | |
483 [_bloatField setText:[NSString stringWithFormat:@"%.1f", bloatSizeMB]]; | |
484 } | |
485 const CGFloat kBloatSizeBytes = ceil(bloatSizeMB * kNumBytesInMB); | |
486 const uint64 kNumberOfBytes = static_cast<uint64>(kBloatSizeBytes); | |
487 _bloat.reset(kNumberOfBytes ? (uint8*)malloc(kNumberOfBytes) : NULL); | |
sdefresne
2015/04/03 08:21:49
style: C-style cast are forbidden
Moreover _bloat
| |
488 if (_bloat) { | |
489 memset(_bloat.get(), -1, kNumberOfBytes); // Occupy memory. | |
490 } else { | |
491 if (kNumberOfBytes) { | |
492 [self alert:@"Could not allocate memory."]; | |
493 } | |
494 } | |
495 } | |
496 | |
497 - (void)clearBloat { | |
498 [_bloatField setText:@"0"]; | |
499 [_bloatField resignFirstResponder]; | |
500 [self updateBloat]; | |
501 } | |
502 | |
503 #pragma mark Refresh interval methods | |
504 | |
505 - (void)updateRefreshInterval { | |
506 double refreshTimerValue; | |
507 NSScanner* scanner = [NSScanner scannerWithString:[_refreshField text]]; | |
508 if (![scanner scanDouble:&refreshTimerValue] || refreshTimerValue < 0.0) { | |
509 refreshTimerValue = 0.5; | |
510 NSString* errorMessage = [NSString | |
511 stringWithFormat:@"Invalid value \"%@\" for refresh interval.\n" | |
512 @"Must be a positive number.\n" @"Resetting to %.1f", | |
513 [_refreshField text], refreshTimerValue]; | |
514 [self alert:errorMessage]; | |
515 [_refreshField | |
516 setText:[NSString stringWithFormat:@"%.1f", refreshTimerValue]]; | |
517 return; | |
518 } | |
519 [_refreshTimer invalidate]; | |
520 _refreshTimer.reset( | |
521 [[NSTimer scheduledTimerWithTimeInterval:refreshTimerValue | |
522 target:self | |
523 selector:@selector(refresh:) | |
524 userInfo:nil | |
525 repeats:YES] retain]); | |
526 } | |
527 | |
528 #pragma mark Memory warning interval methods | |
529 | |
530 // Since _performMemoryWarning is a private API it can't be compiled into | |
531 // stable builds. | |
sdefresne
2015/04/03 08:21:48
nit: s/stable/official/
| |
532 // TODO(lliabraa): Figure out how to support memory warnings (or something | |
533 // like them) in official builds. | |
534 #if CHROMIUM_BUILD | |
535 - (void)updateMemoryWarningInterval { | |
536 [_memoryWarningTimer invalidate]; | |
537 double timerValue; | |
538 NSString* text = [_continuousMemoryWarningField text]; | |
539 NSScanner* scanner = [NSScanner scannerWithString:text]; | |
540 BOOL valueFound = [scanner scanDouble:&timerValue]; | |
541 // If the text field is empty or contains 0, return early to turn off | |
542 // continuous memory warnings. | |
543 if (![text length] || timerValue == 0.0) { | |
544 return; | |
545 } | |
546 // If no value could be parsed or a non-positive value was found, throw up an | |
547 // error message and return early to turn off continuous memory warnings. | |
548 if (!valueFound || timerValue <= 0.0) { | |
549 NSString* errorMessage = [NSString | |
550 stringWithFormat:@"Invalid value \"%@\" for memory warning interval.\n" | |
551 @"Must be a positive number.\n" | |
552 @"Turning off continuous memory warnings", | |
553 text]; | |
554 [self alert:errorMessage]; | |
555 [_continuousMemoryWarningField setText:@""]; | |
556 return; | |
557 } | |
558 // If a valid value was found have the timer start triggering continuous | |
559 // memory warnings. | |
560 _memoryWarningTimer.reset( | |
561 [[NSTimer scheduledTimerWithTimeInterval:timerValue | |
562 target:[UIApplication sharedApplication] | |
563 selector:@selector(_performMemoryWarning) | |
564 userInfo:nil | |
565 repeats:YES] retain]); | |
566 } | |
567 #endif // CHROMIUM_BUILD | |
568 | |
569 #pragma mark UITextViewDelegate methods | |
570 | |
571 // Dismisses the keyboard if the user hits return. | |
572 - (BOOL)textFieldShouldReturn:(UITextField*)textField { | |
573 [textField resignFirstResponder]; | |
574 return YES; | |
575 } | |
576 | |
577 #pragma mark UIResponder methods | |
578 | |
579 // Allows the debugger to be dragged around the screen. | |
580 - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event { | |
581 UITouch* touch = [touches anyObject]; | |
582 CGPoint start = [touch previousLocationInView:self]; | |
583 CGPoint end = [touch locationInView:self]; | |
584 CGPoint offset = CGPointMake(end.x - start.x, end.y - start.y); | |
585 offset = CGPointApplyAffineTransform(offset, self.transform); | |
586 self.frame = CGRectOffset(self.frame, offset.x, offset.y); | |
587 } | |
588 | |
589 #pragma mark Error handling | |
590 | |
591 // Shows an alert with the given |errorMessage|. | |
592 - (void)alert:(NSString*)errorMessage { | |
593 UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Error" | |
594 message:errorMessage | |
595 delegate:self | |
596 cancelButtonTitle:@"OK" | |
597 otherButtonTitles:nil, nil]; | |
598 [alert show]; | |
599 } | |
600 | |
601 @end | |
OLD | NEW |