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

Side by Side Diff: runtime/observatory/lib/src/service/object.dart

Issue 979823003: Major rework of vm service events. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 5 years, 9 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 part of service; 5 part of service;
6 6
7 /// A [ServiceObject] represents a persistent object within the vm. 7 /// A [ServiceObject] represents a persistent object within the vm.
8 abstract class ServiceObject extends Observable { 8 abstract class ServiceObject extends Observable {
9 static int LexicalSortName(ServiceObject o1, ServiceObject o2) { 9 static int LexicalSortName(ServiceObject o1, ServiceObject o2) {
10 return o1.name.compareTo(o2.name); 10 return o1.name.compareTo(o2.name);
(...skipping 346 matching lines...) Expand 10 before | Expand all | Expand 10 after
357 } 357 }
358 358
359 final StreamController<ServiceException> exceptions = 359 final StreamController<ServiceException> exceptions =
360 new StreamController.broadcast(); 360 new StreamController.broadcast();
361 final StreamController<ServiceError> errors = 361 final StreamController<ServiceError> errors =
362 new StreamController.broadcast(); 362 new StreamController.broadcast();
363 final StreamController<ServiceEvent> events = 363 final StreamController<ServiceEvent> events =
364 new StreamController.broadcast(); 364 new StreamController.broadcast();
365 365
366 bool _isIsolateLifecycleEvent(String eventType) { 366 bool _isIsolateLifecycleEvent(String eventType) {
367 return _isIsolateShutdownEvent(eventType) || 367 return _isIsolateExitEvent(eventType) ||
368 _isIsolateCreatedEvent(eventType); 368 _isIsolateStartEvent(eventType);
369 } 369 }
370 370
371 bool _isIsolateShutdownEvent(String eventType) { 371 bool _isIsolateExitEvent(String eventType) {
372 return (eventType == 'IsolateShutdown'); 372 return (eventType == 'IsolateExit');
373 } 373 }
374 374
375 bool _isIsolateCreatedEvent(String eventType) { 375 bool _isIsolateStartEvent(String eventType) {
376 return (eventType == 'IsolateCreated'); 376 return (eventType == 'IsolateStart');
377 } 377 }
378 378
379 void postServiceEvent(String response, ByteData data) { 379 void postServiceEvent(String response, ByteData data) {
380 var map; 380 var map;
381 try { 381 try {
382 map = _parseJSON(response); 382 map = _parseJSON(response);
383 assert(!map.containsKey('_data')); 383 assert(!map.containsKey('_data'));
384 if (data != null) { 384 if (data != null) {
385 map['_data'] = data; 385 map['_data'] = data;
386 } 386 }
387 } catch (e, st) { 387 } catch (e, st) {
388 Logger.root.severe('Ignoring malformed event response: ${response}'); 388 Logger.root.severe('Ignoring malformed event response: ${response}');
389 return; 389 return;
390 } 390 }
391 if (map['type'] != 'ServiceEvent') { 391 if (map['type'] != 'ServiceEvent') {
392 Logger.root.severe( 392 Logger.root.severe(
393 "Expected 'ServiceEvent' but found '${map['type']}'"); 393 "Expected 'ServiceEvent' but found '${map['type']}'");
394 return; 394 return;
395 } 395 }
396 396
397 var eventType = map['eventType']; 397 var eventType = map['eventType'];
398 398
399 if (_isIsolateLifecycleEvent(eventType)) { 399 if (_isIsolateLifecycleEvent(eventType)) {
400 String isolateId = map['isolate']['id']; 400 String isolateId = map['isolate']['id'];
401 var event; 401 var event;
402 if (_isIsolateCreatedEvent(eventType)) { 402 if (_isIsolateStartEvent(eventType)) {
403 _onIsolateCreated(map['isolate']); 403 _onIsolateStart(map['isolate']);
404 // By constructing the event *after* adding the isolate to the 404 // By constructing the event *after* adding the isolate to the
405 // isolate cache, the call to getFromMap will use the cached Isolate. 405 // isolate cache, the call to getFromMap will use the cached Isolate.
406 event = new ServiceObject._fromMap(this, map); 406 event = new ServiceObject._fromMap(this, map);
407 } else { 407 } else {
408 assert(_isIsolateShutdownEvent(eventType)); 408 assert(_isIsolateExitEvent(eventType));
409 // By constructing the event *before* removing the isolate from the 409 // By constructing the event *before* removing the isolate from the
410 // isolate cache, the call to getFromMap will use the cached Isolate. 410 // isolate cache, the call to getFromMap will use the cached Isolate.
411 event = new ServiceObject._fromMap(this, map); 411 event = new ServiceObject._fromMap(this, map);
412 _onIsolateShutdown(isolateId); 412 _onIsolateExit(isolateId);
413 } 413 }
414 assert(event != null); 414 assert(event != null);
415 events.add(event); 415 events.add(event);
416 return; 416 return;
417 } 417 }
418 418
419 // Extract the owning isolate from the event itself. 419 // Extract the owning isolate from the event itself.
420 String owningIsolateId = map['isolate']['id']; 420 String owningIsolateId = map['isolate']['id'];
421 getIsolate(owningIsolateId).then((owningIsolate) { 421 getIsolate(owningIsolateId).then((owningIsolate) {
422 if (owningIsolate == null) { 422 if (owningIsolate == null) {
423 // TODO(koda): Do we care about GC events in VM isolate? 423 // TODO(koda): Do we care about GC events in VM isolate?
424 Logger.root.severe('Ignoring event with unknown isolate id: ' 424 Logger.root.severe('Ignoring event with unknown isolate id: '
425 '$owningIsolateId'); 425 '$owningIsolateId');
426 return; 426 return;
427 } 427 }
428 var event = new ServiceObject._fromMap(owningIsolate, map); 428 var event = new ServiceObject._fromMap(owningIsolate, map);
429 owningIsolate._onEvent(event);
429 events.add(event); 430 events.add(event);
430 }); 431 });
431 } 432 }
432 433
433 Isolate _onIsolateCreated(Map isolateMap) { 434 Isolate _onIsolateStart(Map isolateMap) {
434 var isolateId = isolateMap['id']; 435 var isolateId = isolateMap['id'];
435 assert(!_isolateCache.containsKey(isolateId)); 436 assert(!_isolateCache.containsKey(isolateId));
436 Isolate isolate = new ServiceObject._fromMap(this, isolateMap); 437 Isolate isolate = new ServiceObject._fromMap(this, isolateMap);
437 _isolateCache[isolateId] = isolate; 438 _isolateCache[isolateId] = isolate;
438 notifyPropertyChange(#isolates, true, false); 439 notifyPropertyChange(#isolates, true, false);
439 // Eagerly load the isolate. 440 // Eagerly load the isolate.
440 isolate.load().catchError((e) { 441 isolate.load().catchError((e) {
441 Logger.root.info('Eagerly loading an isolate failed: $e'); 442 Logger.root.info('Eagerly loading an isolate failed: $e');
442 }); 443 });
443 return isolate; 444 return isolate;
444 } 445 }
445 446
446 void _onIsolateShutdown(String isolateId) { 447 void _onIsolateExit(String isolateId) {
447 assert(_isolateCache.containsKey(isolateId)); 448 assert(_isolateCache.containsKey(isolateId));
448 _isolateCache.remove(isolateId); 449 _isolateCache.remove(isolateId);
449 notifyPropertyChange(#isolates, true, false); 450 notifyPropertyChange(#isolates, true, false);
450 } 451 }
451 452
452 void _updateIsolatesFromList(List isolateList) { 453 void _updateIsolatesFromList(List isolateList) {
453 var shutdownIsolates = <String>[]; 454 var shutdownIsolates = <String>[];
454 var createdIsolates = <Map>[]; 455 var createdIsolates = <Map>[];
455 var isolateStillExists = <String, bool>{}; 456 var isolateStillExists = <String, bool>{};
456 457
(...skipping 14 matching lines...) Expand all
471 472
472 // Find shutdown isolates. 473 // Find shutdown isolates.
473 isolateStillExists.forEach((isolateId, exists) { 474 isolateStillExists.forEach((isolateId, exists) {
474 if (!exists) { 475 if (!exists) {
475 shutdownIsolates.add(isolateId); 476 shutdownIsolates.add(isolateId);
476 } 477 }
477 }); 478 });
478 479
479 // Process shutdown. 480 // Process shutdown.
480 for (var isolateId in shutdownIsolates) { 481 for (var isolateId in shutdownIsolates) {
481 _onIsolateShutdown(isolateId); 482 _onIsolateExit(isolateId);
482 } 483 }
483 484
484 // Process creation. 485 // Process creation.
485 for (var isolateMap in createdIsolates) { 486 for (var isolateMap in createdIsolates) {
486 _onIsolateCreated(isolateMap); 487 _onIsolateStart(isolateMap);
487 } 488 }
488 } 489 }
489 490
490 static final String _isolateIdPrefix = 'isolates/'; 491 static final String _isolateIdPrefix = 'isolates/';
491 492
492 ServiceObject getFromMap(ObservableMap map) { 493 ServiceObject getFromMap(ObservableMap map) {
493 if (map == null) { 494 if (map == null) {
494 return null; 495 return null;
495 } 496 }
496 String id = map['id']; 497 String id = map['id'];
497 if (!id.startsWith(_isolateIdPrefix)) { 498 if (!id.startsWith(_isolateIdPrefix)) {
498 // Currently the VM only supports upgrading Isolate ServiceObjects. 499 // Currently the VM only supports upgrading Isolate ServiceObjects.
499 throw new UnimplementedError(); 500 throw new UnimplementedError();
500 } 501 }
501 502
502 // Check cache. 503 // Check cache.
503 var isolate = _isolateCache[id]; 504 var isolate = _isolateCache[id];
504 if (isolate == null) { 505 if (isolate == null) {
505 // We should never see an unknown isolate here. 506 // We should never see an unknown isolate here.
506 throw new UnimplementedError(); 507 throw new UnimplementedError();
507 } 508 }
509 var mapIsRef = _hasRef(map['type']);
510 if (!mapIsRef) {
511 isolate.update(map);
512 }
508 return isolate; 513 return isolate;
509 } 514 }
510 515
511 // Note that this function does not reload the isolate if it found 516 // Note that this function does not reload the isolate if it found
512 // in the cache. 517 // in the cache.
513 Future<ServiceObject> getIsolate(String isolateId) { 518 Future<ServiceObject> getIsolate(String isolateId) {
514 if (!loaded) { 519 if (!loaded) {
515 // Trigger a VM load, then get the isolate. 520 // Trigger a VM load, then get the isolate.
516 return load().then((_) => getIsolate(isolateId)).catchError(_ignoreError); 521 return load().then((_) => getIsolate(isolateId)).catchError(_ignoreError);
517 } 522 }
(...skipping 249 matching lines...) Expand 10 before | Expand all | Expand 10 after
767 772
768 } 773 }
769 774
770 /// State for a running isolate. 775 /// State for a running isolate.
771 class Isolate extends ServiceObjectOwner with Coverage { 776 class Isolate extends ServiceObjectOwner with Coverage {
772 @reflectable VM get vm => owner; 777 @reflectable VM get vm => owner;
773 @reflectable Isolate get isolate => this; 778 @reflectable Isolate get isolate => this;
774 @observable ObservableMap counters = new ObservableMap(); 779 @observable ObservableMap counters = new ObservableMap();
775 780
776 @observable ServiceEvent pauseEvent = null; 781 @observable ServiceEvent pauseEvent = null;
777 bool get _isPaused => pauseEvent != null;
778 782
783 void _updateRunState() {
784 topFrame = (pauseEvent != null ? pauseEvent.topFrame : null);
785 paused = pauseEvent != null && pauseEvent.eventType != 'Resume';
786 running = (!paused && topFrame != null);
787 idle = (!paused && topFrame == null);
788 notifyPropertyChange(#topFrame, 0, 1);
789 notifyPropertyChange(#paused, 0, 1);
790 notifyPropertyChange(#running, 0, 1);
791 notifyPropertyChange(#idle, 0, 1);
792 }
793
794 @observable bool paused = false;
779 @observable bool running = false; 795 @observable bool running = false;
780 @observable bool idle = false; 796 @observable bool idle = false;
781 @observable bool loading = true; 797 @observable bool loading = true;
798
782 @observable bool ioEnabled = false; 799 @observable bool ioEnabled = false;
783 800
784 Map<String,ServiceObject> _cache = new Map<String,ServiceObject>(); 801 Map<String,ServiceObject> _cache = new Map<String,ServiceObject>();
785 final TagProfile tagProfile = new TagProfile(20); 802 final TagProfile tagProfile = new TagProfile(20);
786 803
787 Isolate._empty(ServiceObjectOwner owner) : super._empty(owner) { 804 Isolate._empty(ServiceObjectOwner owner) : super._empty(owner) {
788 assert(owner is VM); 805 assert(owner is VM);
789 } 806 }
790 807
791 void resetCachedProfileData() { 808 void resetCachedProfileData() {
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after
837 return new Future.value(objectClass); 854 return new Future.value(objectClass);
838 } 855 }
839 856
840 ServiceObject getFromMap(ObservableMap map) { 857 ServiceObject getFromMap(ObservableMap map) {
841 if (map == null) { 858 if (map == null) {
842 return null; 859 return null;
843 } 860 }
844 String mapId = map['id']; 861 String mapId = map['id'];
845 var obj = (mapId != null) ? _cache[mapId] : null; 862 var obj = (mapId != null) ? _cache[mapId] : null;
846 if (obj != null) { 863 if (obj != null) {
847 // Consider calling update when map is not a reference. 864 var mapIsRef = _hasRef(map['type']);
865 if (!mapIsRef) {
866 obj.update(map);
867 }
848 return obj; 868 return obj;
849 } 869 }
850 // Build the object from the map directly. 870 // Build the object from the map directly.
851 obj = new ServiceObject._fromMap(this, map); 871 obj = new ServiceObject._fromMap(this, map);
852 if ((obj != null) && obj.canCache) { 872 if ((obj != null) && obj.canCache) {
853 _cache[mapId] = obj; 873 _cache[mapId] = obj;
854 } 874 }
855 return obj; 875 return obj;
856 } 876 }
857 877
858 Future<ObservableMap> invokeRpcNoUpgrade(String method, Map params) { 878 Future<ObservableMap> invokeRpcNoUpgrade(String method, Map params) {
859 params['isolateId'] = id; 879 params['isolateId'] = id;
860 return vm.invokeRpcNoUpgrade(method, params); 880 return vm.invokeRpcNoUpgrade(method, params);
861 } 881 }
862 882
863 Future<ServiceObject> invokeRpc(String method, Map params) { 883 Future<ServiceObject> invokeRpc(String method, Map params) {
864 return invokeRpcNoUpgrade(method, params).then((ObservableMap response) { 884 return invokeRpcNoUpgrade(method, params).then((ObservableMap response) {
865 var obj = new ServiceObject._fromMap(this, response); 885 return getFromMap(response);
866 if ((obj != null) && obj.canCache) {
867 String objId = obj.id;
868 _cache.putIfAbsent(objId, () => obj);
869 }
870 return obj;
871 }); 886 });
872 } 887 }
873 888
874 Future<ServiceObject> getObject(String objectId) { 889 Future<ServiceObject> getObject(String objectId) {
875 assert(objectId != null && objectId != ''); 890 assert(objectId != null && objectId != '');
876 var obj = _cache[objectId]; 891 var obj = _cache[objectId];
877 if (obj != null) { 892 if (obj != null) {
878 return obj.reload(); 893 return obj.reload();
879 } 894 }
880 Map params = { 895 Map params = {
(...skipping 24 matching lines...) Expand all
905 920
906 final HeapSpace newSpace = new HeapSpace(); 921 final HeapSpace newSpace = new HeapSpace();
907 final HeapSpace oldSpace = new HeapSpace(); 922 final HeapSpace oldSpace = new HeapSpace();
908 923
909 @observable String fileAndLine; 924 @observable String fileAndLine;
910 925
911 @observable DartError error; 926 @observable DartError error;
912 @observable HeapSnapshot latestSnapshot; 927 @observable HeapSnapshot latestSnapshot;
913 Completer<HeapSnapshot> _snapshotFetch; 928 Completer<HeapSnapshot> _snapshotFetch;
914 929
915 void loadHeapSnapshot(ServiceEvent event) { 930 void _loadHeapSnapshot(ServiceEvent event) {
916 latestSnapshot = new HeapSnapshot(this, event.data); 931 latestSnapshot = new HeapSnapshot(this, event.data);
917 _snapshotFetch.complete(latestSnapshot); 932 _snapshotFetch.complete(latestSnapshot);
918 } 933 }
919 934
920 Future<HeapSnapshot> fetchHeapSnapshot() { 935 Future<HeapSnapshot> fetchHeapSnapshot() {
921 if (_snapshotFetch == null || _snapshotFetch.isCompleted) { 936 if (_snapshotFetch == null || _snapshotFetch.isCompleted) {
922 _snapshotFetch = new Completer<HeapSnapshot>(); 937 _snapshotFetch = new Completer<HeapSnapshot>();
923 isolate.invokeRpcNoUpgrade('requestHeapSnapshot', {}); 938 isolate.invokeRpcNoUpgrade('requestHeapSnapshot', {});
924 } 939 }
925 return _snapshotFetch.future; 940 return _snapshotFetch.future;
926 } 941 }
927 942
928 void updateHeapsFromMap(ObservableMap map) { 943 void updateHeapsFromMap(ObservableMap map) {
929 newSpace.update(map['new']); 944 newSpace.update(map['new']);
930 oldSpace.update(map['old']); 945 oldSpace.update(map['old']);
931 } 946 }
932 947
933 void _update(ObservableMap map, bool mapIsRef) { 948 void _update(ObservableMap map, bool mapIsRef) {
934 mainPort = map['mainPort']; 949 mainPort = map['mainPort'];
935 name = map['name']; 950 name = map['name'];
936 vmName = map['name']; 951 vmName = map['name'];
937 if (mapIsRef) { 952 if (mapIsRef) {
938 return; 953 return;
939 } 954 }
940 _loaded = true; 955 _loaded = true;
941 loading = false; 956 loading = false;
942 957
943 reloadBreakpoints();
944 _upgradeCollection(map, isolate); 958 _upgradeCollection(map, isolate);
945 if (map['rootLib'] == null || 959 if (map['rootLib'] == null ||
946 map['timers'] == null || 960 map['timers'] == null ||
947 map['heaps'] == null) { 961 map['heaps'] == null) {
948 Logger.root.severe("Malformed 'Isolate' response: $map"); 962 Logger.root.severe("Malformed 'Isolate' response: $map");
949 return; 963 return;
950 } 964 }
951 rootLib = map['rootLib']; 965 rootLib = map['rootLib'];
952 if (map['entry'] != null) { 966 if (map['entry'] != null) {
953 entry = map['entry']; 967 entry = map['entry'];
954 } 968 }
955 if (map['topFrame'] != null) {
956 topFrame = map['topFrame'];
957 } else {
958 topFrame = null ;
959 }
960 969
961 var countersMap = map['tagCounters']; 970 var countersMap = map['tagCounters'];
962 if (countersMap != null) { 971 if (countersMap != null) {
963 var names = countersMap['names']; 972 var names = countersMap['names'];
964 var counts = countersMap['counters']; 973 var counts = countersMap['counters'];
965 assert(names.length == counts.length); 974 assert(names.length == counts.length);
966 var sum = 0; 975 var sum = 0;
967 for (var i = 0; i < counts.length; i++) { 976 for (var i = 0; i < counts.length; i++) {
968 sum += counts[i]; 977 sum += counts[i];
969 } 978 }
(...skipping 17 matching lines...) Expand all
987 timers['total'] = timerMap['time_total_runtime']; 996 timers['total'] = timerMap['time_total_runtime'];
988 timers['compile'] = timerMap['time_compilation']; 997 timers['compile'] = timerMap['time_compilation'];
989 timers['gc'] = 0.0; // TODO(turnidge): Export this from VM. 998 timers['gc'] = 0.0; // TODO(turnidge): Export this from VM.
990 timers['init'] = (timerMap['time_script_loading'] + 999 timers['init'] = (timerMap['time_script_loading'] +
991 timerMap['time_creating_snapshot'] + 1000 timerMap['time_creating_snapshot'] +
992 timerMap['time_isolate_initialization'] + 1001 timerMap['time_isolate_initialization'] +
993 timerMap['time_bootstrap']); 1002 timerMap['time_bootstrap']);
994 timers['dart'] = timerMap['time_dart_execution']; 1003 timers['dart'] = timerMap['time_dart_execution'];
995 1004
996 updateHeapsFromMap(map['heaps']); 1005 updateHeapsFromMap(map['heaps']);
1006 _updateBreakpoints(map['breakpoints']);
997 1007
998 List features = map['features']; 1008 List features = map['features'];
999 if (features != null) { 1009 if (features != null) {
1000 for (var feature in features) { 1010 for (var feature in features) {
1001 if (feature == 'io') { 1011 if (feature == 'io') {
1002 ioEnabled = true; 1012 ioEnabled = true;
1003 } 1013 }
1004 } 1014 }
1005 } 1015 }
1006 // Isolate status
1007 pauseEvent = map['pauseEvent']; 1016 pauseEvent = map['pauseEvent'];
1008 running = (!_isPaused && map['topFrame'] != null); 1017 _updateRunState();
1009 idle = (!_isPaused && map['topFrame'] == null);
1010 error = map['error']; 1018 error = map['error'];
1011 1019
1012 libraries.clear(); 1020 libraries.clear();
1013 libraries.addAll(map['libraries']); 1021 libraries.addAll(map['libraries']);
1014 libraries.sort(ServiceObject.LexicalSortName); 1022 libraries.sort(ServiceObject.LexicalSortName);
1015 } 1023 }
1016 1024
1017 Future<TagProfile> updateTagProfile() { 1025 Future<TagProfile> updateTagProfile() {
1018 return isolate.invokeRpcNoUpgrade('getTagProfile', {}).then( 1026 return isolate.invokeRpcNoUpgrade('getTagProfile', {}).then(
1019 (ObservableMap map) { 1027 (ObservableMap map) {
1020 var seconds = new DateTime.now().millisecondsSinceEpoch / 1000.0; 1028 var seconds = new DateTime.now().millisecondsSinceEpoch / 1000.0;
1021 tagProfile._processTagProfile(seconds, map); 1029 tagProfile._processTagProfile(seconds, map);
1022 return tagProfile; 1030 return tagProfile;
1023 }); 1031 });
1024 } 1032 }
1025 1033
1026 ObservableList<Breakpoint> breakpoints = new ObservableList(); 1034 ObservableMap<int, Breakpoint> breakpoints = new ObservableMap();
1035
1036 void _updateBreakpoints(List newBpts) {
1037 // Build a map of new breakpoints.
1038 var newBptMap = {};
1039 newBpts.forEach((bpt) => (newBptMap[bpt.number] = bpt));
1040
1041 // Remove any old breakpoints which no longer exist.
1042 List toRemove = [];
1043 breakpoints.forEach((key, _) {
1044 if (!newBptMap.containsKey(key)) {
1045 toRemove.add(key);
1046 }
1047 });
1048 toRemove.forEach((key) => breakpoints.remove(key));
1049
1050 // Add all new breakpoints.
1051 breakpoints.addAll(newBptMap);
1052 }
1053
1054 void _addBreakpoint(Breakpoint bpt) {
1055 breakpoints[bpt.number] = bpt;
1056 }
1027 1057
1028 void _removeBreakpoint(Breakpoint bpt) { 1058 void _removeBreakpoint(Breakpoint bpt) {
1029 var script = bpt.script; 1059 breakpoints.remove(bpt.number);
1030 var tokenPos = bpt.tokenPos; 1060 bpt.remove();
1031 assert(tokenPos != null); 1061 }
1032 if (script.loaded) { 1062
1033 var line = script.tokenToLine(tokenPos); 1063 void _onEvent(ServiceEvent event) {
1034 assert(line != null); 1064 switch(event.eventType) {
1035 if (script.lines[line - 1] != null) { 1065 case 'IsolateStart':
1036 assert(script.lines[line - 1].bpt == bpt); 1066 case 'IsolateExit':
1037 script.lines[line - 1].bpt = null; 1067 Logger.root.severe(
1038 } 1068 'Isolate lifecycle event should not be delivered to isolate');
Cutch 2015/03/05 16:30:57 Throw here.
turnidge 2015/03/05 18:42:24 Turned this into an assert.
1069 break;
1070
1071 case 'BreakpointAdded':
1072 _addBreakpoint(event.breakpoint);
1073 break;
1074
1075 case 'BreakpointResolved':
1076 // Update occurs as side-effect of caching.
1077 break;
1078
1079 case 'BreakpointRemoved':
1080 _removeBreakpoint(event.breakpoint);
1081 break;
1082
1083 case 'PauseStart':
1084 case 'PauseExit':
1085 case 'PauseBreakpoint':
1086 case 'PauseInterrupted':
1087 case 'PauseException':
1088 case 'Resume':
1089 pauseEvent = event;
1090 _updateRunState();
1091 break;
1092
1093 case '_Graph':
1094 _loadHeapSnapshot(event);
1095 break;
1096
1097 case 'GC':
1098 // Ignore GC events for now.
1099 break;
1100
1101 default:
1102 // Log unrecognized events.
1103 Logger.root.severe('Unrecognized event: $event');
1104 break;
1039 } 1105 }
1040 } 1106 }
1041 1107
1042 void _addBreakpoint(Breakpoint bpt) {
1043 var script = bpt.script;
1044 var tokenPos = bpt.tokenPos;
1045 assert(tokenPos != null);
1046 if (script.loaded) {
1047 var line = script.tokenToLine(tokenPos);
1048 assert(line != null);
1049 assert(script.lines[line - 1].bpt == null);
1050 script.lines[line - 1].bpt = bpt;
1051 } else {
1052 // Load the script and then plop in the breakpoint.
1053 script.load().then((_) {
1054 _addBreakpoint(bpt);
1055 });
1056 }
1057 }
1058
1059 void _updateBreakpoints(ServiceMap newBreakpoints) {
1060 // Remove all of the old breakpoints from the Script lines.
1061 if (breakpoints != null) {
1062 for (var bpt in breakpoints) {
1063 _removeBreakpoint(bpt);
1064 }
1065 }
1066 // Add all of the new breakpoints to the Script lines.
1067 for (var bpt in newBreakpoints['breakpoints']) {
1068 _addBreakpoint(bpt);
1069 }
1070 breakpoints.clear();
1071 breakpoints.addAll(newBreakpoints['breakpoints']);
1072
1073 // Sort the breakpoints by breakpointNumber.
1074 breakpoints.sort((a, b) => (a.number - b.number));
1075 }
1076
1077 Future<ServiceObject> _inProgressReloadBpts;
1078
1079 Future reloadBreakpoints() {
1080 // TODO(turnidge): Can reusing the Future here ever cause us to
1081 // get stale breakpoints?
1082 if (_inProgressReloadBpts == null) {
1083 _inProgressReloadBpts =
1084 invokeRpc('getBreakpoints', {}).then((newBpts) {
1085 _updateBreakpoints(newBpts);
1086 }).whenComplete(() {
1087 _inProgressReloadBpts = null;
1088 });
1089 }
1090 return _inProgressReloadBpts;
1091 }
1092
1093 Future<ServiceObject> addBreakpoint(Script script, int line) { 1108 Future<ServiceObject> addBreakpoint(Script script, int line) {
1094 // TODO(turnidge): Pass line as an int instead of a string. 1109 // TODO(turnidge): Pass line as an int instead of a string.
1095 Map params = { 1110 Map params = {
1096 'scriptId': script.id, 1111 'scriptId': script.id,
1097 'line': '$line', 1112 'line': '$line',
1098 }; 1113 };
1099 return invokeRpc('addBreakpoint', params).then((result) { 1114 return invokeRpc('addBreakpoint', params).then((result) {
1100 if (result is DartError) { 1115 if (result is DartError) {
1101 return result; 1116 return result;
1102 } 1117 }
1103 Breakpoint bpt = result; 1118 Breakpoint bpt = result;
1104 if (bpt.resolved && 1119 if (bpt.resolved &&
1105 script.loaded && 1120 script.loaded &&
1106 script.tokenToLine(result.tokenPos) != line) { 1121 script.tokenToLine(result.tokenPos) != line) {
1107 // Unable to set a breakpoint at desired line. 1122 // Unable to set a breakpoint at desired line.
1108 script.lines[line - 1].possibleBpt = false; 1123 script.lines[line - 1].possibleBpt = false;
1109 } 1124 }
1110 // TODO(turnidge): Instead of reloading all of the breakpoints, 1125 return result;
1111 // rely on events to update the breakpoint list.
1112 return reloadBreakpoints().then((_) {
1113 return result;
1114 });
1115 }); 1126 });
1116 } 1127 }
1117 1128
1118 Future<ServiceObject> addBreakpointAtEntry(ServiceFunction function) { 1129 Future<ServiceObject> addBreakpointAtEntry(ServiceFunction function) {
1119 return invokeRpc('addBreakpointAtEntry', 1130 return invokeRpc('addBreakpointAtEntry',
1120 { 'functionId': function.id }).then((result) { 1131 { 'functionId': function.id });
1121 // TODO(turnidge): Instead of reloading all of the breakpoints,
1122 // rely on events to update the breakpoint list.
1123 return reloadBreakpoints().then((_) {
1124 return result;
1125 });
1126 });
1127 } 1132 }
1128 1133
1129 Future removeBreakpoint(Breakpoint bpt) { 1134 Future removeBreakpoint(Breakpoint bpt) {
1130 return invokeRpc('removeBreakpoint', 1135 return invokeRpc('removeBreakpoint',
1131 { 'breakpointId': bpt.id }).then((result) { 1136 { 'breakpointId': bpt.id });
1132 if (result is DartError) {
1133 // TODO(turnidge): Handle this more gracefully.
1134 Logger.root.severe(result.message);
1135 return result;
1136 }
1137 if (pauseEvent != null &&
1138 pauseEvent.breakpoint != null &&
1139 (pauseEvent.breakpoint.id == bpt.id)) {
1140 return isolate.reload();
1141 } else {
1142 return reloadBreakpoints();
1143 }
1144 });
1145 } 1137 }
1146 1138
1147 // TODO(turnidge): If the user invokes pause (or other rpcs) twice, 1139 // TODO(turnidge): If the user invokes pause (or other rpcs) twice,
1148 // they could get a race. Consider returning an "in progress" 1140 // they could get a race. Consider returning an "in progress"
1149 // future to avoid this. 1141 // future to avoid this.
1150 Future pause() { 1142 Future pause() {
1151 return invokeRpc('pause', {}).then((result) { 1143 return invokeRpc('pause', {}).then((result) {
1152 if (result is DartError) { 1144 if (result is DartError) {
1153 // TODO(turnidge): Handle this more gracefully. 1145 // TODO(turnidge): Handle this more gracefully.
1154 Logger.root.severe(result.message); 1146 Logger.root.severe(result.message);
(...skipping 157 matching lines...) Expand 10 before | Expand all | Expand 10 after
1312 bool get canCache { 1304 bool get canCache {
1313 return (_type == 'Class' || 1305 return (_type == 'Class' ||
1314 _type == 'Function' || 1306 _type == 'Function' ||
1315 _type == 'Field') && 1307 _type == 'Field') &&
1316 !_id.startsWith(objectIdRingPrefix); 1308 !_id.startsWith(objectIdRingPrefix);
1317 } 1309 }
1318 bool get immutable => false; 1310 bool get immutable => false;
1319 1311
1320 ServiceMap._empty(ServiceObjectOwner owner) : super._empty(owner); 1312 ServiceMap._empty(ServiceObjectOwner owner) : super._empty(owner);
1321 1313
1322 void _upgradeValues() {
1323 assert(owner != null);
1324 _upgradeCollection(_map, owner);
1325 }
1326
1327 void _update(ObservableMap map, bool mapIsRef) { 1314 void _update(ObservableMap map, bool mapIsRef) {
1328 _loaded = !mapIsRef; 1315 _loaded = !mapIsRef;
1329 1316
1317 _upgradeCollection(map, owner);
1330 // TODO(turnidge): Currently _map.clear() prevents us from 1318 // TODO(turnidge): Currently _map.clear() prevents us from
1331 // upgrading an already upgraded submap. Is clearing really the 1319 // upgrading an already upgraded submap. Is clearing really the
1332 // right thing to do here? 1320 // right thing to do here?
1333 _map.clear(); 1321 _map.clear();
1334 _map.addAll(map); 1322 _map.addAll(map);
1335 1323
1336 name = _map['name']; 1324 name = _map['name'];
1337 vmName = (_map.containsKey('vmName') ? _map['vmName'] : name); 1325 vmName = (_map.containsKey('vmName') ? _map['vmName'] : name);
1338 _upgradeValues();
1339 } 1326 }
1340 1327
1341 // Forward Map interface calls. 1328 // Forward Map interface calls.
1342 void addAll(Map other) => _map.addAll(other); 1329 void addAll(Map other) => _map.addAll(other);
1343 void clear() => _map.clear(); 1330 void clear() => _map.clear();
1344 bool containsValue(v) => _map.containsValue(v); 1331 bool containsValue(v) => _map.containsValue(v);
1345 bool containsKey(k) => _map.containsKey(k); 1332 bool containsKey(k) => _map.containsKey(k);
1346 void forEach(Function f) => _map.forEach(f); 1333 void forEach(Function f) => _map.forEach(f);
1347 putIfAbsent(key, Function ifAbsent) => _map.putIfAbsent(key, ifAbsent); 1334 putIfAbsent(key, Function ifAbsent) => _map.putIfAbsent(key, ifAbsent);
1348 void remove(key) => _map.remove(key); 1335 void remove(key) => _map.remove(key);
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after
1433 /// A [ServiceEvent] is an asynchronous event notification from the vm. 1420 /// A [ServiceEvent] is an asynchronous event notification from the vm.
1434 class ServiceEvent extends ServiceObject { 1421 class ServiceEvent extends ServiceObject {
1435 ServiceEvent._empty(ServiceObjectOwner owner) : super._empty(owner); 1422 ServiceEvent._empty(ServiceObjectOwner owner) : super._empty(owner);
1436 1423
1437 ServiceEvent.vmDisconencted() : super._empty(null) { 1424 ServiceEvent.vmDisconencted() : super._empty(null) {
1438 eventType = 'VMDisconnected'; 1425 eventType = 'VMDisconnected';
1439 } 1426 }
1440 1427
1441 @observable String eventType; 1428 @observable String eventType;
1442 @observable Breakpoint breakpoint; 1429 @observable Breakpoint breakpoint;
1430 @observable ServiceMap topFrame;
1443 @observable ServiceMap exception; 1431 @observable ServiceMap exception;
1444 @observable ByteData data; 1432 @observable ByteData data;
1445 @observable int count; 1433 @observable int count;
1446 1434
1447 void _update(ObservableMap map, bool mapIsRef) { 1435 void _update(ObservableMap map, bool mapIsRef) {
1448 _loaded = true; 1436 _loaded = true;
1449 _upgradeCollection(map, owner); 1437 _upgradeCollection(map, owner);
1450 eventType = map['eventType']; 1438 eventType = map['eventType'];
1451 name = 'ServiceEvent $eventType'; 1439 name = 'ServiceEvent $eventType';
1452 vmName = name; 1440 vmName = name;
1453 if (map['breakpoint'] != null) { 1441 if (map['breakpoint'] != null) {
1454 breakpoint = map['breakpoint']; 1442 breakpoint = map['breakpoint'];
1455 } 1443 }
1444 if (map['topFrame'] != null) {
1445 topFrame = map['topFrame'];
1446 }
1456 if (map['exception'] != null) { 1447 if (map['exception'] != null) {
1457 exception = map['exception']; 1448 exception = map['exception'];
1458 } 1449 }
1459 if (map['_data'] != null) { 1450 if (map['_data'] != null) {
1460 data = map['_data']; 1451 data = map['_data'];
1461 } 1452 }
1462 if (map['count'] != null) { 1453 if (map['count'] != null) {
1463 count = map['count']; 1454 count = map['count'];
1464 } 1455 }
1465 } 1456 }
1466 1457
1467 String toString() { 1458 String toString() {
1468 return 'ServiceEvent of type $eventType with ' 1459 if (data == null) {
1469 '${data == null ? 0 : data.lengthInBytes} bytes of binary data'; 1460 return "ServiceEvent(owner='${owner.id}', type='${eventType}')";
1461 } else {
1462 return "ServiceEvent(owner='${owner.id}', type='${eventType}', "
1463 "data.lengthInBytes=${data.lengthInBytes})";
1464 }
1470 } 1465 }
1471 } 1466 }
1472 1467
1473 class Breakpoint extends ServiceObject { 1468 class Breakpoint extends ServiceObject {
1474 Breakpoint._empty(ServiceObjectOwner owner) : super._empty(owner); 1469 Breakpoint._empty(ServiceObjectOwner owner) : super._empty(owner);
1475 1470
1476 // TODO(turnidge): Add state to track if a breakpoint has been 1471 // TODO(turnidge): Add state to track if a breakpoint has been
1477 // removed from the program. Remove from the cache when deleted. 1472 // removed from the program. Remove from the cache when deleted.
1478 bool get canCache => true; 1473 bool get canCache => true;
1479 bool get immutable => false; 1474 bool get immutable => false;
1480 1475
1481 // A unique integer identifier for this breakpoint. 1476 // A unique integer identifier for this breakpoint.
1482 @observable int number; 1477 @observable int number;
1483 1478
1484 // Source location information. 1479 // Source location information.
1485 @observable Script script; 1480 @observable Script script;
1486 @observable int tokenPos; 1481 @observable int tokenPos;
1487 1482
1488 // The breakpoint has been assigned to a final source location. 1483 // The breakpoint has been assigned to a final source location.
1489 @observable bool resolved; 1484 @observable bool resolved;
1490 1485
1491 // The breakpoint is active.
1492 @observable bool enabled;
1493
1494 void _update(ObservableMap map, bool mapIsRef) { 1486 void _update(ObservableMap map, bool mapIsRef) {
1495 _loaded = true; 1487 _loaded = true;
1496 _upgradeCollection(map, owner); 1488 _upgradeCollection(map, owner);
1497 1489
1490 var newNumber = map['breakpointNumber'];
1491 var newScript = map['location']['script'];
1492 var newTokenPos = map['location']['tokenPos'];
1493
1494 // number and script never change.
1495 assert(number == null || number == newNumber);
Cutch 2015/03/05 16:30:57 Wrap comparisons in ( ) here and elsewhere. e.g.
turnidge 2015/03/05 18:43:14 Done.
1496 assert(script == null || script == newScript);
1497
1498 number = map['breakpointNumber']; 1498 number = map['breakpointNumber'];
1499 script = map['location']['script']; 1499 script = map['location']['script'];
1500 tokenPos = map['location']['tokenPos']; 1500 resolved = map['resolved'];
1501 bool tokenPosChanged = tokenPos != newTokenPos;
1501 1502
1502 resolved = map['resolved']; 1503 if (script.loaded &&
1503 enabled = map['enabled']; 1504 tokenPos != null &&
1505 tokenPosChanged) {
1506 // The breakpoint has moved. Remove it and add it later.
1507 script._removeBreakpoint(this);
1508 }
1509
1510 tokenPos = newTokenPos;
1511 if (script.loaded && tokenPosChanged) {
1512 script._addBreakpoint(this);
1513 }
1514 }
1515
1516 void remove() {
1517 // Remove any references to this breakpoint. It has been removed.
1518 script._removeBreakpoint(this);
1519 if (isolate.pauseEvent != null &&
1520 isolate.pauseEvent.breakpoint != null &&
1521 isolate.pauseEvent.breakpoint.id == id) {
1522 isolate.pauseEvent.breakpoint = null;
1523 }
1504 } 1524 }
1505 1525
1506 String toString() { 1526 String toString() {
1507 if (number != null) { 1527 if (number != null) {
1508 return 'Breakpoint ${number} at ${script.name}(token:${tokenPos})'; 1528 return 'Breakpoint ${number} at ${script.name}(token:${tokenPos})';
1509 } else { 1529 } else {
1510 return 'Uninitialized breakpoint'; 1530 return 'Uninitialized breakpoint';
1511 } 1531 }
1512 } 1532 }
1513 } 1533 }
(...skipping 448 matching lines...) Expand 10 before | Expand all | Expand 10 after
1962 1982
1963 String toString() => 'Field(${owner.name}.$name)'; 1983 String toString() => 'Field(${owner.name}.$name)';
1964 } 1984 }
1965 1985
1966 1986
1967 class ScriptLine extends Observable { 1987 class ScriptLine extends Observable {
1968 final Script script; 1988 final Script script;
1969 final int line; 1989 final int line;
1970 final String text; 1990 final String text;
1971 @observable int hits; 1991 @observable int hits;
1972 @observable Breakpoint bpt;
1973 @observable bool possibleBpt = true; 1992 @observable bool possibleBpt = true;
1993 @observable bool breakpointResolved = false;
1994 @observable Set<Breakpoint> breakpoints;
1974 1995
1975 bool get isBlank { 1996 bool get isBlank {
1976 // Compute isBlank on demand. 1997 // Compute isBlank on demand.
1977 if (_isBlank == null) { 1998 if (_isBlank == null) {
1978 _isBlank = text.trim().isEmpty; 1999 _isBlank = text.trim().isEmpty;
1979 } 2000 }
1980 return _isBlank; 2001 return _isBlank;
1981 } 2002 }
1982 bool _isBlank; 2003 bool _isBlank;
1983 2004
(...skipping 24 matching lines...) Expand all
2008 if (!_isTrivialToken(token)) { 2029 if (!_isTrivialToken(token)) {
2009 return false; 2030 return false;
2010 } 2031 }
2011 } 2032 }
2012 } 2033 }
2013 return true; 2034 return true;
2014 } 2035 }
2015 2036
2016 ScriptLine(this.script, this.line, this.text) { 2037 ScriptLine(this.script, this.line, this.text) {
2017 possibleBpt = !_isTrivialLine(text); 2038 possibleBpt = !_isTrivialLine(text);
2039 }
2018 2040
2019 // TODO(turnidge): This is not so efficient. Consider improving. 2041 void addBreakpoint(Breakpoint bpt) {
2020 for (var bpt in this.script.isolate.breakpoints) { 2042 if (breakpoints == null) {
2021 if (bpt.script == this.script && 2043 breakpoints = new Set<Breakpoint>();
2022 bpt.script.tokenToLine(bpt.tokenPos) == line) { 2044 }
2023 this.bpt = bpt; 2045 breakpoints.add(bpt);
2024 } 2046 breakpointResolved = breakpointResolved || bpt.resolved;
2047 }
2048
2049 void removeBreakpoint(Breakpoint bpt) {
2050 assert(breakpoints != null && breakpoints.contains(bpt));
2051 breakpoints.remove(bpt);
2052 if (breakpoints.isEmpty) {
2053 breakpoints = null;
2054 breakpointResolved = false;
2025 } 2055 }
2026 } 2056 }
2027 } 2057 }
2028 2058
2029 class Script extends ServiceObject with Coverage { 2059 class Script extends ServiceObject with Coverage {
2030 final lines = new ObservableList<ScriptLine>(); 2060 final lines = new ObservableList<ScriptLine>();
2031 final _hits = new Map<int, int>(); 2061 final _hits = new Map<int, int>();
2032 @observable String kind; 2062 @observable String kind;
2033 @observable int firstTokenPos; 2063 @observable int firstTokenPos;
2034 @observable int lastTokenPos; 2064 @observable int lastTokenPos;
(...skipping 22 matching lines...) Expand all
2057 void _update(ObservableMap map, bool mapIsRef) { 2087 void _update(ObservableMap map, bool mapIsRef) {
2058 _upgradeCollection(map, isolate); 2088 _upgradeCollection(map, isolate);
2059 kind = map['kind']; 2089 kind = map['kind'];
2060 _url = map['name']; 2090 _url = map['name'];
2061 _shortUrl = _url.substring(_url.lastIndexOf('/') + 1); 2091 _shortUrl = _url.substring(_url.lastIndexOf('/') + 1);
2062 name = _shortUrl; 2092 name = _shortUrl;
2063 vmName = _url; 2093 vmName = _url;
2064 if (mapIsRef) { 2094 if (mapIsRef) {
2065 return; 2095 return;
2066 } 2096 }
2097 _parseTokenPosTable(map['tokenPosTable']);
2067 _processSource(map['source']); 2098 _processSource(map['source']);
2068 _parseTokenPosTable(map['tokenPosTable']);
2069 owningLibrary = map['owningLibrary']; 2099 owningLibrary = map['owningLibrary'];
2070 } 2100 }
2071 2101
2072 void _parseTokenPosTable(List<List<int>> table) { 2102 void _parseTokenPosTable(List<List<int>> table) {
2073 if (table == null) { 2103 if (table == null) {
2074 return; 2104 return;
2075 } 2105 }
2076 _tokenToLine.clear(); 2106 _tokenToLine.clear();
2077 _tokenToCol.clear(); 2107 _tokenToCol.clear();
2078 firstTokenPos = null; 2108 firstTokenPos = null;
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after
2138 if (sourceLines.length == 0) { 2168 if (sourceLines.length == 0) {
2139 return; 2169 return;
2140 } 2170 }
2141 // We have the source to the script. This is now loaded. 2171 // We have the source to the script. This is now loaded.
2142 _loaded = true; 2172 _loaded = true;
2143 lines.clear(); 2173 lines.clear();
2144 Logger.root.info('Adding ${sourceLines.length} source lines for ${_url}'); 2174 Logger.root.info('Adding ${sourceLines.length} source lines for ${_url}');
2145 for (var i = 0; i < sourceLines.length; i++) { 2175 for (var i = 0; i < sourceLines.length; i++) {
2146 lines.add(new ScriptLine(this, i + 1, sourceLines[i])); 2176 lines.add(new ScriptLine(this, i + 1, sourceLines[i]));
2147 } 2177 }
2178 for (var bpt in isolate.breakpoints.values) {
2179 if (bpt.script == this) {
2180 _addBreakpoint(bpt);
2181 }
2182 }
2183
2148 _applyHitsToLines(); 2184 _applyHitsToLines();
2149 // Notify any Observers that this Script's state has changed. 2185 // Notify any Observers that this Script's state has changed.
2150 notifyChange(null); 2186 notifyChange(null);
2151 } 2187 }
2152 2188
2153 void _applyHitsToLines() { 2189 void _applyHitsToLines() {
2154 for (var line in lines) { 2190 for (var line in lines) {
2155 var hits = _hits[line.line]; 2191 var hits = _hits[line.line];
2156 line.hits = hits; 2192 line.hits = hits;
2157 } 2193 }
2158 } 2194 }
2195
2196 void _addBreakpoint(Breakpoint bpt) {
2197 var line = tokenToLine(bpt.tokenPos);
2198 getLine(line).addBreakpoint(bpt);
2199 }
2200
2201 void _removeBreakpoint(Breakpoint bpt) {
2202 var line = tokenToLine(bpt.tokenPos);
2203 if (line != null) {
2204 getLine(line).removeBreakpoint(bpt);
2205 }
2206 }
2159 } 2207 }
2160 2208
2161 class PcDescriptor extends Observable { 2209 class PcDescriptor extends Observable {
2162 final int pcOffset; 2210 final int pcOffset;
2163 @reflectable final int deoptId; 2211 @reflectable final int deoptId;
2164 @reflectable final int tokenPos; 2212 @reflectable final int tokenPos;
2165 @reflectable final int tryIndex; 2213 @reflectable final int tryIndex;
2166 @reflectable final String kind; 2214 @reflectable final String kind;
2167 @observable Script script; 2215 @observable Script script;
2168 @observable String formattedLine; 2216 @observable String formattedLine;
(...skipping 663 matching lines...) Expand 10 before | Expand all | Expand 10 after
2832 var v = list[i]; 2880 var v = list[i];
2833 if ((v is ObservableMap) && _isServiceMap(v)) { 2881 if ((v is ObservableMap) && _isServiceMap(v)) {
2834 list[i] = owner.getFromMap(v); 2882 list[i] = owner.getFromMap(v);
2835 } else if (v is ObservableList) { 2883 } else if (v is ObservableList) {
2836 _upgradeObservableList(v, owner); 2884 _upgradeObservableList(v, owner);
2837 } else if (v is ObservableMap) { 2885 } else if (v is ObservableMap) {
2838 _upgradeObservableMap(v, owner); 2886 _upgradeObservableMap(v, owner);
2839 } 2887 }
2840 } 2888 }
2841 } 2889 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698