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

Side by Side Diff: runtime/observatory/lib/src/elements/debugger.dart

Issue 866663003: Add a working command line debugger to Observatory. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 5 years, 10 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 | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library debugger_page_element; 5 library debugger_page_element;
6 6
7 import 'dart:async';
7 import 'dart:html'; 8 import 'dart:html';
8 import 'observatory_element.dart'; 9 import 'observatory_element.dart';
10 import 'package:observatory/cli.dart';
9 import 'package:observatory/service.dart'; 11 import 'package:observatory/service.dart';
10 import 'package:polymer/polymer.dart'; 12 import 'package:polymer/polymer.dart';
11 13
14 abstract class DebuggerCommand extends Command {
15 Debugger debugger;
16
17 DebuggerCommand(this.debugger, name, children)
Cutch 2015/02/09 21:45:05 Shouldn't all of these DebuggerCommands exists som
turnidge 2015/02/09 21:53:08 Yes. I want to leave them here until the interfac
18 : super(name, children);
19 }
20
21 class HelpCommand extends DebuggerCommand {
22 HelpCommand(Debugger debugger) : super(debugger, 'help', []);
23
24 Future run(List<String> args) {
25 var con = debugger.console;
26 con.printLine('List of commands:');
27 con.newline();
28
29 // TODO(turnidge): Build a real help system.
30 List completions = debugger.cmd.completeCommand('');
31 completions = completions.map((s )=> s.trimRight()).toList();
32 completions.sort();
33 con.printLine(completions.toString());
34 con.newline();
35 con.printLine("Command prefixes are accepted (e.g. 'h' for 'help')");
36 con.printLine("Hit [TAB] to complete a command (try 'i[TAB][TAB]')");
37 con.printLine("Hit [ENTER] to repeat the last command");
38
39 return new Future.value(null);
40 }
41 }
42
43 class PauseCommand extends DebuggerCommand {
44 PauseCommand(Debugger debugger) : super(debugger, 'pause', []);
45
46 Future run(List<String> args) {
47 if (!debugger.isolatePaused()) {
Cutch 2015/02/09 21:45:06 There is a race here if the user requests an isola
turnidge 2015/02/09 21:53:08 Added a TODO on Isolate.pause.
48 return debugger.isolate.pause();
49 } else {
50 debugger.console.printLine('The program is already paused');
51 return new Future.value(null);
52 }
53 }
54 }
55
56 class ContinueCommand extends DebuggerCommand {
57 ContinueCommand(Debugger debugger) : super(debugger, 'continue', []);
58
59 Future run(List<String> args) {
60 if (debugger.isolatePaused()) {
61 return debugger.isolate.resume().then((_) {
62 debugger.warnOutOfDate();
63 });
64 } else {
65 debugger.console.printLine('The program must be paused');
66 return new Future.value(null);
67 }
68 }
69 }
70
71 class NextCommand extends DebuggerCommand {
72 NextCommand(Debugger debugger) : super(debugger, 'next', []);
73
74 Future run(List<String> args) {
75 if (debugger.isolatePaused()) {
76 var event = debugger.isolate.pauseEvent;
77 if (event.eventType == 'IsolateCreated') {
78 debugger.console.printLine("Type 'continue' to start the isolate");
79 return new Future.value(null);
80 }
81 if (event.eventType == 'IsolateShutdown') {
82 debugger.console.printLine("Type 'continue' to exit the isolate");
83 return new Future.value(null);
84 }
85 return debugger.isolate.stepOver();
86 } else {
87 debugger.console.printLine('The program is already running');
88 return new Future.value(null);
89 }
90 }
91 }
92
93 class StepCommand extends DebuggerCommand {
94 StepCommand(Debugger debugger) : super(debugger, 'step', []);
95
96 Future run(List<String> args) {
97 if (debugger.isolatePaused()) {
98 var event = debugger.isolate.pauseEvent;
99 if (event.eventType == 'IsolateCreated') {
100 debugger.console.printLine("Type 'continue' to start the isolate");
101 return new Future.value(null);
102 }
103 if (event.eventType == 'IsolateShutdown') {
104 debugger.console.printLine("Type 'continue' to exit the isolate");
105 return new Future.value(null);
106 }
107 return debugger.isolate.stepInto();
108 } else {
109 debugger.console.printLine('The program is already running');
110 return new Future.value(null);
111 }
112 }
113 }
114
115 class FinishCommand extends DebuggerCommand {
116 FinishCommand(Debugger debugger) : super(debugger, 'finish', []);
117
118 Future run(List<String> args) {
119 if (debugger.isolatePaused()) {
120 return debugger.isolate.stepOut();
121 } else {
122 debugger.console.printLine('The program is already running');
123 return new Future.value(null);
124 }
125 }
126 }
127
128 // TODO(turnidge): Add argument completion.
129 class DeleteCommand extends DebuggerCommand {
130 DeleteCommand(Debugger debugger) : super(debugger, 'delete', []);
131
132 Future run(List<String> args) {
133 if (args.length < 1) {
134 debugger.console.printLine('delete expects one or more arguments');
135 return new Future.value(null);
136 }
137 List toDelete = [];
138 for (var arg in args) {
139 int id = int.parse(arg);
140 var bpt = null;
141 for (var candidate in debugger.isolate.breakpoints) {
142 if (candidate['breakpointNumber'] == id) {
143 bpt = candidate;
144 break;
145 }
146 }
147 if (bpt == null) {
148 debugger.console.printLine("Invalid breakpoint id '${id}'");
149 return new Future.value(null);
150 }
151 toDelete.add(bpt);
152 }
153 List pending = [];
154 for (var bpt in toDelete) {
155 pending.add(debugger.isolate.removeBreakpoint(bpt).then((_) {
156 var id = bpt['breakpointNumber'];
157 debugger.console.printLine("Removed breakpoint $id");
158 }));
159 }
160 return Future.wait(pending);
161 }
162 }
163
164 class InfoBreakpointsCommand extends DebuggerCommand {
165 InfoBreakpointsCommand(Debugger debugger) : super(debugger, 'breakpoints', []) ;
Cutch 2015/02/09 21:45:05 80 column limit.
turnidge 2015/02/09 21:53:08 Done.
166
167 Future run(List<String> args) {
168 return debugger.isolate.reloadBreakpoints().then((_) {
169 if (debugger.isolate.breakpoints.isEmpty) {
170 debugger.console.printLine('No breakpoints');
171 }
172 for (var bpt in debugger.isolate.breakpoints) {
173 var bpId = bpt['breakpointNumber'];
174 var script = bpt['location']['script'];
175 var tokenPos = bpt['location']['tokenPos'];
176 var line = script.tokenToLine(tokenPos);
177 var col = script.tokenToCol(tokenPos);
178 debugger.console.printLine(
179 'Breakpoint ${bpId} at ${script.name}:${line}:${col}');
180 }
181 });
182 }
183 }
184
185 class InfoIsolatesCommand extends DebuggerCommand {
186 InfoIsolatesCommand(Debugger debugger) : super(debugger, 'isolates', []);
187
188 Future run(List<String> args) {
189 for (var isolate in debugger.isolate.vm.isolates) {
190 debugger.console.printLine(
191 "Isolate ${isolate.id} '${isolate.name}'");
192 }
193 return new Future.value(null);
194 }
195 }
196
197 class InfoCommand extends DebuggerCommand {
198 InfoCommand(Debugger debugger) : super(debugger, 'info', [
199 new InfoBreakpointsCommand(debugger),
200 new InfoIsolatesCommand(debugger),
201 ]);
202
203 Future run(List<String> args) {
204 debugger.console.printLine("Invalid info command");
205 return new Future.value(null);
206 }
207 }
208
209 class RefreshCoverageCommand extends DebuggerCommand {
210 RefreshCoverageCommand(Debugger debugger) : super(debugger, 'coverage', []);
211
212 Future run(List<String> args) {
213 Set<Script> scripts = debugger.stackElement.activeScripts();
214 List pending = [];
215 for (var script in scripts) {
216 pending.add(script.refreshCoverage().then((_) {
217 debugger.console.printLine('Refreshed coverage for ${script.name}');
218 }));
219 }
220 return Future.wait(pending);
221 }
222 }
223
224 class RefreshCommand extends DebuggerCommand {
225 RefreshCommand(Debugger debugger) : super(debugger, 'refresh', [
226 new RefreshCoverageCommand(debugger),
227 ]);
228
229 Future run(List<String> args) {
230 return debugger.refreshStack();
231 }
232 }
233
234 // Tracks the state for an isolate debugging session.
235 class Debugger {
236 RootCommand cmd;
237 DebuggerConsoleElement console;
238 DebuggerStackElement stackElement;
239 ServiceMap stack;
240
241 Debugger() {
242 cmd = new RootCommand([
243 new HelpCommand(this),
244 new PauseCommand(this),
245 new ContinueCommand(this),
246 new NextCommand(this),
247 new StepCommand(this),
248 new FinishCommand(this),
249 new DeleteCommand(this),
250 new InfoCommand(this),
251 new RefreshCommand(this),
252 ]);
253 }
254
255 void set isolate(Isolate iso) {
256 _isolate = iso;
257 if (_isolate != null) {
258 _isolate.reload().then((_) {
259 _isolate.vm.events.stream.listen(_onEvent);
260 _refreshStack(isolate.pauseEvent).then((_) {
261 reportStatus();
262 });
263 });
264 }
265 }
266 Isolate get isolate => _isolate;
267 Isolate _isolate;
268
269 void init() {
270 console.newline();
271 console.printBold("Type 'h' for help");
272 }
273
274 Future refreshStack() {
275 return _refreshStack(isolate.pauseEvent).then((_) {
276 reportStatus();
277 });
278 }
279
280 bool isolatePaused() {
281 // TODO(turnidge): Stop relying on the isolate to track the last
282 // pause event. Since we listen to events directly in the
283 // debugger, this could introduce a race.
284 return isolate.pauseEvent != null;
285 }
286
287 void warnOutOfDate() {
288 // Wait a bit, then tell the user that the stack may be out of date.
289 new Timer(const Duration(seconds:2), () {
290 if (!isolatePaused()) {
291 stackElement.isSampled = true;
292 }
293 });
294 }
295
296 Future<ServiceMap> _refreshStack(ServiceEvent pauseEvent) {
297 return isolate.getStack().then((result) {
298 stack = result;
299 // TODO(turnidge): Replace only the changed part of the stack to
300 // reduce flicker.
301 // stackElement.stack = stack;
302 stackElement.updateStack(stack, pauseEvent);
303 });
304 }
305
306 void reportStatus() {
307 if (_isolate.idle) {
308 console.printLine('Isolate is idle');
309 } else if (_isolate.running) {
310 console.printLine("Isolate is running (type 'pause' to interrupt)");
311 } else if (_isolate.pauseEvent != null) {
312 _reportPause(_isolate.pauseEvent);
313 } else {
314 console.printLine('Isolate is in unknown state');
315 }
316 }
317
318 void _reportPause(ServiceEvent event) {
319 if (event.eventType == 'IsolateCreated') {
320 console.printLine(
321 "Paused at isolate start (type 'continue' to start the isolate')");
322 } else if (event.eventType == 'IsolateShutdown') {
323 console.printLine(
324 "Paused at isolate exit (type 'continue' to exit the isolate')");
325 }
326 if (stack['frames'].length > 0) {
327 var frame = stack['frames'][0];
328 var script = frame['script'];
329 script.load().then((_) {
330 var line = script.tokenToLine(frame['tokenPos']);
331 var col = script.tokenToCol(frame['tokenPos']);
332 if (event.breakpoint != null) {
333 var bpId = event.breakpoint['breakpointNumber'];
334 console.printLine('Breakpoint ${bpId} at ${script.name}:${line}:${col} ');
335 } else if (event.exception != null) {
336 // TODO(turnidge): Test this.
337 console.printLine(
338 'Exception ${event.exception} at ${script.name}:${line}:${col}');
339 } else {
340 console.printLine('Paused at ${script.name}:${line}:${col}');
341 }
342 });
343 }
344 }
345
346 void _onEvent(ServiceEvent event) {
347 if (event.owner != isolate) {
348 return;
349 }
350 switch(event.eventType) {
351 case 'IsolateShutdown':
352 console.printLine('Isolate shutdown');
353 isolate = null;
354 break;
355
356 case 'BreakpointReached':
357 case 'IsolateInterrupted':
358 case 'ExceptionThrown':
359 _refreshStack(event).then((_) {
360 _reportPause(event);
361 });
362 break;
363
364 case 'IsolateResumed':
365 console.printLine('Continuing...');
366 break;
367
368 case '_Graph':
369 case 'BreakpointResolved':
370 case 'IsolateCreated':
371 case 'GC':
372 // Ignore these events for now.
373 break;
374
375 default:
376 console.printLine('Unrecognized event: $event');
377 break;
378 }
379 }
380
381 String complete(String line) {
382 List<String> completions = cmd.completeCommand(line);
383 if (completions.length == 0) {
384 // No completions. Leave the line alone.
385 return line;
386 } else if (completions.length == 1) {
387 // Unambiguous completion.
388 return completions[0];
389 } else {
390 // Ambigous completion.
391 completions = completions.map((s )=> s.trimRight()).toList();
392 completions.sort();
393 console.printBold(completions.toString());
394
395 // TODO(turnidge): Complete to common prefix of all completions.
396 return line;
397 }
398 }
399
400 // TODO(turnidge): Implement real command line history.
401 String lastCommand;
402 bool busy = false;
403
404 Future run(String command) {
405 assert(!busy);
406 busy = true;
407 if (command == '') {
408 command = lastCommand;
409 }
410 lastCommand = command;
411 console.printBold('\$ $command');
412 return cmd.runCommand(command).then((_) {
413 busy = false;
414 }).catchError((e) {
415 console.printLine('ERROR $e');
416 });
417 }
418 }
419
12 @CustomTag('debugger-page') 420 @CustomTag('debugger-page')
13 class DebuggerPageElement extends ObservatoryElement { 421 class DebuggerPageElement extends ObservatoryElement {
14 @published Isolate isolate; 422 @published Isolate isolate;
15 @published bool showConsole = false; 423
424 isolateChanged(oldValue) {
425 if (isolate != null) {
426 debugger.isolate = isolate;
427 }
428 }
429 Debugger debugger = new Debugger();
16 430
17 DebuggerPageElement.created() : super.created(); 431 DebuggerPageElement.created() : super.created();
18 432
19 @override 433 @override
20 void attached() { 434 void attached() {
21 super.attached(); 435 super.attached();
22 436
23 // TODO(turnidge): Get these values from the DOM. 437 var navbarDiv = $['navbarDiv'];
24 // TODO(turnidge): splitterHeight is 0 until I implement it. 438 var stackDiv = $['stackDiv'];
25 const int navbarHeight = 56; 439 var splitterDiv = $['splitterDiv'];
26 const int splitterHeight = 0; 440 var cmdDiv = $['commandDiv'];
27 const int cmdHeight = 22; 441 var consoleDiv = $['consoleDiv'];
28 442
29 var stack = $['stack']; 443 int navbarHeight = navbarDiv.clientHeight;
444 int splitterHeight = splitterDiv.clientHeight;
445 int cmdHeight = cmdDiv.clientHeight;
446
30 int windowHeight = window.innerHeight; 447 int windowHeight = window.innerHeight;
31 int available = windowHeight - (navbarHeight + splitterHeight); 448 int fixedHeight = navbarHeight + splitterHeight + cmdHeight;
32 int stackHeight = available ~/ 1.3; 449 int available = windowHeight - fixedHeight;
33 if (showConsole) { 450 int stackHeight = available ~/ 1.6;
34 stack.style.setProperty('height', '${stackHeight}px'); 451 stackDiv.style.setProperty('height', '${stackHeight}px');
35 } else { 452
36 stack.style.setProperty('height', '${available}px'); 453 // Wire the debugger object to the stack, console, and command line.
37 } 454 var stackElement = $['stackElement'];
455 debugger.stackElement = stackElement;
456 stackElement.debugger = debugger;
457 debugger.console = $['console'];
458 $['commandline'].debugger = debugger;
459 debugger.init();
38 } 460 }
461
39 } 462 }
40 463
41 @CustomTag('debugger-stack') 464 @CustomTag('debugger-stack')
42 class DebuggerStackElement extends ObservatoryElement { 465 class DebuggerStackElement extends ObservatoryElement {
43 @published Isolate isolate; 466 @published Isolate isolate;
44 @published ServiceMap stack; 467 @observable bool hasStack = false;
45 @published int activeFrame = 0; 468 @observable bool isSampled = false;
469 Debugger debugger = null;
46 470
47 isolateChanged(oldValue) { 471 _addFrame(List frameList, ObservableMap frameInfo, bool expand) {
48 isolate.getStack().then((result) { 472 DebuggerFrameElement frameElement = new Element.tag('debugger-frame');
49 stack = result; 473 frameElement.expand = expand;
50 }); 474 frameElement.frame = frameInfo;
475
476 var li = new LIElement();
477 li.classes.add('list-group-item');
478 li.children.insert(0, frameElement);
479
480 frameList.insert(0, li);
481 }
482
483 void updateStack(ServiceMap newStack, ServiceEvent pauseEvent) {
484 List frameElements = $['frameList'].children;
485 List newFrames = newStack['frames'];
486
487 // Remove any frames whose functions don't match, starting from
488 // bottom of stack.
489 int oldPos = frameElements.length - 1;
490 int newPos = newFrames.length - 1;
491 while (oldPos >= 0 && newPos >= 0) {
492 if (!frameElements[oldPos].children[0].matchFrame(newFrames[newPos])) {
493 // The rest of the frame elements no longer match. Remove them.
494 for (int i = 0; i <= oldPos; i++) {
495 // NOTE(turnidge): removeRange is missing, sadly.
496 frameElements.removeAt(0);
497 }
498 break;
499 }
500 oldPos--;
501 newPos--;
502 }
503
504 // Remove any extra frames.
505 if (frameElements.length > newFrames.length) {
506 // Remove old frames from the top of stack.
507 int removeCount = frameElements.length - newFrames.length;
508 for (int i = 0; i < removeCount; i++) {
509 frameElements.removeAt(0);
510 }
511 }
512
513 // Add any new frames.
514 int newCount = 0;
515 if (frameElements.length < newFrames.length) {
516 // Add new frames to the top of stack.
517 newCount = newFrames.length - frameElements.length;
518 for (int i = newCount-1; i >= 0; i--) {
519 _addFrame(frameElements, newFrames[i], i == 0);
520 }
521 }
522 assert(frameElements.length == newFrames.length);
523
524 if (frameElements.isNotEmpty) {
525 frameElements[0].children[0].expand = true;
526 for (int i = newCount; i < frameElements.length; i++) {
527 frameElements[i].children[0].updateFrame(newFrames[i]);
528 }
529 }
530
531 isSampled = pauseEvent == null;
532 hasStack = frameElements.isNotEmpty;
533 }
534
535 Set<Script> activeScripts() {
536 var s = new Set<Script>();
537 List frameElements = $['frameList'].children;
538 for (var frameElement in frameElements) {
539 s.add(frameElement.children[0].script);
540 }
541 return s;
542 }
543
544 doPauseIsolate(_) {
545 if (debugger != null) {
546 return debugger.isolate.pause();
547 } else {
548 return new Future.value(null);
549 }
550 }
551
552 doRefreshStack(_) {
553 if (debugger != null) {
554 return debugger.refreshStack();
555 } else {
556 return new Future.value(null);
557 }
51 } 558 }
52 559
53 DebuggerStackElement.created() : super.created(); 560 DebuggerStackElement.created() : super.created();
54 } 561 }
55 562
56 @CustomTag('debugger-frame') 563 @CustomTag('debugger-frame')
57 class DebuggerFrameElement extends ObservatoryElement { 564 class DebuggerFrameElement extends ObservatoryElement {
58 @published ObservableMap frame; 565 @published ObservableMap frame;
59 566
60 // When true, the frame will start out expanded. 567 // When true, the frame will start out expanded.
61 @published bool expand = false; 568 @published bool expand = false;
62 569
63 @observable String scriptHeight; 570 @observable String scriptHeight;
64 @observable bool expanded = false; 571 @observable bool expanded = false;
65 @observable bool busy = false; 572 @observable bool busy = false;
66 573
67 DebuggerFrameElement.created() : super.created(); 574 DebuggerFrameElement.created() : super.created();
68 575
576 bool matchFrame(ObservableMap newFrame) {
577 return newFrame['function'].id == frame['function'].id;
578 }
579
580 void updateFrame(ObservableMap newFrame) {
581 assert(matchFrame(newFrame));
582 frame['depth'] = newFrame['depth'];
583 frame['tokenPos'] = newFrame['tokenPos'];
584 frame['vars'] = newFrame['vars'];
585 }
586
587 Script get script => frame['script'];
588
69 @override 589 @override
70 void attached() { 590 void attached() {
71 super.attached(); 591 super.attached();
72 int windowHeight = window.innerHeight; 592 int windowHeight = window.innerHeight;
73 scriptHeight = '${windowHeight ~/ 1.6}px'; 593 scriptHeight = '${windowHeight ~/ 1.6}px';
74 } 594 }
75 595
76 void expandChanged(oldValue) { 596 void expandChanged(oldValue) {
77 if (expand != expanded) { 597 if (expand != expanded) {
78 toggleExpand(null, null, null); 598 toggleExpand(null, null, null);
(...skipping 16 matching lines...) Expand all
95 busy = false; 615 busy = false;
96 }); 616 });
97 } 617 }
98 } 618 }
99 619
100 @CustomTag('debugger-console') 620 @CustomTag('debugger-console')
101 class DebuggerConsoleElement extends ObservatoryElement { 621 class DebuggerConsoleElement extends ObservatoryElement {
102 @published Isolate isolate; 622 @published Isolate isolate;
103 623
104 DebuggerConsoleElement.created() : super.created(); 624 DebuggerConsoleElement.created() : super.created();
625
626 void printLine(String line) {
627 var div = new DivElement();
628 div.classes.add('normal');
629 div.appendText(line);
630 $['consoleText'].children.add(div);
631 div.scrollIntoView();
632 }
633
634 void printBold(String line) {
635 var div = new DivElement();
636 div.classes.add('bold');
637 div.appendText(line);
638 $['consoleText'].children.add(div);
639 div.scrollIntoView();
640 }
641
642 void newline() {
643 var br = new BRElement();
644 $['consoleText'].children.add(br);
645 br.scrollIntoView();
646 }
105 } 647 }
106 648
107 @CustomTag('debugger-input') 649 @CustomTag('debugger-input')
108 class DebuggerInputElement extends ObservatoryElement { 650 class DebuggerInputElement extends ObservatoryElement {
109 @published Isolate isolate; 651 @published Isolate isolate;
110 @published String text = ''; 652 @published String text = '';
653 @observable Debugger debugger;
111 654
112 @override 655 @override
113 void ready() { 656 void ready() {
114 super.ready(); 657 super.ready();
115 var textBox = $['textBox']; 658 var textBox = $['textBox'];
116 textBox.select(); 659 textBox.select();
117 textBox.onKeyDown.listen((KeyboardEvent e) { 660 textBox.onKeyDown.listen((KeyboardEvent e) {
118 switch (e.keyCode) { 661 switch (e.keyCode) {
119 case KeyCode.TAB: 662 case KeyCode.TAB:
120 e.preventDefault(); 663 e.preventDefault();
121 textBox.setRangeText('TAB'); 664 int cursorPos = textBox.selectionStart;
122 textBox.setSelectionRange(textBox.selectionStart + 3, 665 var completion = debugger.complete(text.substring(0, cursorPos));
123 textBox.selectionStart + 3); 666 text = completion + text.substring(cursorPos);
667 // TODO(turnidge): Move the cursor to the end of the
668 // completion, rather than the end of the string.
124 break; 669 break;
125 case KeyCode.ENTER: 670 case KeyCode.ENTER:
126 print('Debugger command (not implemented): $text'); 671 if (!debugger.busy) {
127 text = ''; 672 debugger.run(text);
673 text = '';
674 }
128 break; 675 break;
129 } 676 }
130 }); 677 });
131 } 678 }
132 679
133 DebuggerInputElement.created() : super.created(); 680 DebuggerInputElement.created() : super.created();
134 } 681 }
135 682
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698