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

Side by Side Diff: lib/watcher.dart

Issue 5885170347409408: Add location information to watchers to make them more debuggable. (Closed) Base URL: git@github.com:dart-lang/web-ui.git@master
Patch Set: Created 7 years, 5 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
« no previous file with comments | « lib/testing/render_test.dart ('k') | pubspec.yaml » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, 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 /** 5 /**
6 * A library to observe changes on Dart objects. 6 * A library to observe changes on Dart objects.
7 * 7 *
8 * Similar to the principle of watchers in AngularJS, this library provides the 8 * Similar to the principle of watchers in AngularJS, this library provides the
9 * mechanisms to observe and react to changes that happen in an application's 9 * mechanisms to observe and react to changes that happen in an application's
10 * data model. 10 * data model.
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
43 * A common design pattern for MVC applications is to call [dispatch] at the end 43 * A common design pattern for MVC applications is to call [dispatch] at the end
44 * of each event loop (e.g. after each UI event is fired). Our view library does 44 * of each event loop (e.g. after each UI event is fired). Our view library does
45 * this automatically. 45 * this automatically.
46 */ 46 */
47 library watcher; 47 library watcher;
48 48
49 import 'dart:async'; 49 import 'dart:async';
50 import 'dart:collection' hide LinkedList, LinkedListEntry; 50 import 'dart:collection' hide LinkedList, LinkedListEntry;
51 import 'observe.dart'; 51 import 'observe.dart';
52 import 'src/linked_list.dart'; 52 import 'src/linked_list.dart';
53 import 'package:logging/logging.dart';
53 54
54 /** 55 /**
55 * True to use the [observe] library instead of watchers. 56 * True to use the [observe] library instead of watchers.
56 * 57 *
57 * Observers require the [observable] annotation on objects and for collection 58 * Observers require the [observable] annotation on objects and for collection
58 * types to be observable, such as [ObservableList]. But in return they offer 59 * types to be observable, such as [ObservableList]. But in return they offer
59 * better performance and more precise change tracking. [dispatch] is not 60 * better performance and more precise change tracking. [dispatch] is not
60 * required with observers, and changes to observable objects are always 61 * required with observers, and changes to observable objects are always
61 * detected. 62 * detected.
62 * 63 *
63 * Currently this flag is experimental, but it may be the default in the future. 64 * Currently this flag is experimental, but it may be the default in the future.
64 */ 65 */
65 bool useObservers = false; 66 bool useObservers = false;
66 67
67 /** 68 /**
69 * True to enable more verbose debugging messages. This is useful for tracing
70 * down errors that produce loops in the watcher evaluation order, for example
71 * when you see an error message of the form "_Possible loop in watchers
72 * propagation, stopped dispatch_".
73 */
74 bool verboseDebugMessages = false;
75
76
77 /**
78 * Function used to record the current stack trace when [verboseDebugMessages]
79 * is true. This can be replaced if you prefer to format strack traces
80 * differently or filter frames of the stack traces. For
81 * example using <http://pub.dartlang.org/packages/stack_trace>, you can do:
82 *
83 * import 'package:watcher/watcher.dart';
84 * import 'package:stack_trace/stack_trace.dart';
85 * main() {
86 * verboseDebugMessages = true;
87 * readCurrentStackTrace = () {
88 * try { throw "" ; } catch (e, trace) {
89 * return new Trace.from(trace).terse.toString();
90 * }
91 * };
92 * ...
93 * }
94 */
95 Function readCurrentStackTrace = () {
96 try {
97 throw "";
98 } catch (e, trace) {
99 return trace.toString();
100 }
101 };
102
103 /**
104 * Log for messages produced at runtime by this library. Logging can be
105 * configured by accessing Logger.root from the logging library.
106 */
107 final Logger _logger = new Logger('watcher');
108
109 /**
68 * Watch for changes in [target]. The [callback] function will be called when 110 * Watch for changes in [target]. The [callback] function will be called when
69 * [dispatch] is called and the value represented by [target] had changed. The 111 * [dispatch] is called and the value represented by [target] had changed. The
70 * returned function can be used to unregister this watcher. 112 * returned function can be used to unregister this watcher.
71 * 113 *
72 * There are several values you can use for [target]: 114 * There are several values you can use for [target]:
73 * 115 *
74 * * A [Getter] function. 116 * * A [Getter] function.
75 * Use this to watch expressions as they change. For instance, to watch 117 * Use this to watch expressions as they change. For instance, to watch
76 * whether `a.b.c` changes, wrap it in a getter and call [watch] as follows: 118 * whether `a.b.c` changes, wrap it in a getter and call [watch] as follows:
77 * watch(() => a.b.c, ...) 119 * watch(() => a.b.c, ...)
78 * These targets are tracked to check for equality. If calling `target()` 120 * These targets are tracked to check for equality. If calling `target()`
79 * returns the same result, then the [callback] will not be invoked. In the 121 * returns the same result, then the [callback] will not be invoked. In the
80 * special case whe the getter returns a [List], we will treat the value in a 122 * special case whe the getter returns a [List], we will treat the value in a
81 * special way, similar to passing [List] directly as [target]. 123 * special way, similar to passing [List] directly as [target].
82 * **Important**: this library assumes that [Getter] is a read-only function 124 * **Important**: this library assumes that [Getter] is a read-only function
83 * and that it will consistently return the same value if called multiple 125 * and that it will consistently return the same value if called multiple
84 * times in a row. 126 * times in a row.
85 * 127 *
86 * * A [List]. 128 * * A [List].
87 * Use this to watch whether a list object changes. For instance, to detect 129 * Use this to watch whether a list object changes. For instance, to detect
88 * if an element is added or changed in a list can call [watch] as follows: 130 * if an element is added or changed in a list can call [watch] as follows:
89 * watch(list, ...) 131 * watch(list, ...)
90 * 132 *
91 * * A [Handle]. 133 * * A [Handle].
92 * This is syntactic sugar for using the getter portion of a [Handle]. 134 * This is syntactic sugar for using the getter portion of a [Handle].
93 * watch(handle, ...) // equivalent to `watch(handle._getter, ...)` 135 * watch(handle, ...) // equivalent to `watch(handle._getter, ...)`
94 */ 136 */
95 ChangeUnobserver watch(target, ChangeObserver callback, [String debugName]) { 137 ChangeUnobserver watch(target, ChangeObserver callback,
138 [String debugName, String location]) {
96 if (useObservers) return observe(target, callback); 139 if (useObservers) return observe(target, callback);
97 140
98 if (callback == null) return () {}; // no use in passing null as a callback. 141 if (callback == null) return () {}; // no use in passing null as a callback.
99 if (_watchers == null) _watchers = new LinkedList<_Watcher>(); 142 if (_watchers == null) _watchers = new LinkedList<_Watcher>();
143 debugName = debugName == null ? '<unnamed>' : debugName;
100 Function exp; 144 Function exp;
101 _WatcherType watcherType = _WatcherType.OTHER; 145 _WatcherType watcherType = _WatcherType.OTHER;
102 if (target is Handle) { 146 if (target is Handle) {
103 exp = (target as Handle)._getter; 147 exp = (target as Handle)._getter;
104 } else if (target is Function) { 148 } else if (target is Function) {
105 exp = target; 149 exp = target;
106 try { 150 try {
107 var val = target(); 151 var val = target();
108 if (val is List) { 152 if (val is List) {
109 watcherType = _WatcherType.LIST; 153 watcherType = _WatcherType.LIST;
110 } else if (val is Iterable) { 154 } else if (val is Iterable) {
111 watcherType = _WatcherType.LIST; 155 watcherType = _WatcherType.LIST;
112 exp = () => target().toList(); 156 exp = () => target().toList();
113 } else if ((val is LinkedHashMap) || (val is SplayTreeMap)) { 157 } else if ((val is LinkedHashMap) || (val is SplayTreeMap)) {
114 watcherType = _WatcherType.ORDERED_MAP; 158 watcherType = _WatcherType.ORDERED_MAP;
115 } else if (val is Map) { 159 } else if (val is Map) {
116 watcherType = _WatcherType.HASH_MAP; 160 watcherType = _WatcherType.HASH_MAP;
117 } 161 }
118 } catch (e, trace) { // in case target() throws some error 162 } catch (e, trace) { // in case target() throws some error
119 // TODO(sigmund): use logging instead of print when logger is in the SDK 163 _logger.warning('evaluating $debugName watcher threw error ($e, $trace)');
120 // and available via pub (see dartbug.com/4363)
121 print('error: evaluating ${debugName != null ? debugName : "<unnamed>"} '
122 'watcher threw error ($e, $trace)');
123 } 164 }
124 } else if (target is List) { 165 } else if (target is List) {
125 exp = () => target; 166 exp = () => target;
126 watcherType = _WatcherType.LIST; 167 watcherType = _WatcherType.LIST;
127 } else if (target is Iterable) { 168 } else if (target is Iterable) {
128 exp = () => target.toList(); 169 exp = () => target.toList();
129 watcherType = _WatcherType.LIST; 170 watcherType = _WatcherType.LIST;
130 } else if ((target is LinkedHashMap) || (target is SplayTreeMap)) { 171 } else if ((target is LinkedHashMap) || (target is SplayTreeMap)) {
131 exp = () => target; 172 exp = () => target;
132 watcherType = _WatcherType.ORDERED_MAP; 173 watcherType = _WatcherType.ORDERED_MAP;
133 } else if (target is Map) { 174 } else if (target is Map) {
134 exp = () => target; 175 exp = () => target;
135 watcherType = _WatcherType.HASH_MAP; 176 watcherType = _WatcherType.HASH_MAP;
136 } 177 }
137 178
138 var watcher = _createWatcher(watcherType, exp, callback, debugName); 179 if (verboseDebugMessages && location == null
180 && readCurrentStackTrace != null) {
181 location = readCurrentStackTrace();
182 }
183
184 var watcher = _createWatcher(watcherType, exp, callback, debugName, location);
139 var node = _watchers.add(watcher); 185 var node = _watchers.add(watcher);
140 return node.remove; 186 return node.remove;
141 } 187 }
142 188
143 /** 189 /**
144 * Creates a watcher for [exp] of [type] with [callback] function and 190 * Creates a watcher for [exp] of [type] with [callback] function and
145 * [debugName]. 191 * [debugName].
146 */ 192 */
147 _Watcher _createWatcher(_WatcherType type, Function exp, 193 _Watcher _createWatcher(_WatcherType type, Function exp,
148 ChangeObserver callback, String debugName) { 194 ChangeObserver callback, String debugName, String location) {
149 switch(type) { 195 switch(type) {
150 case _WatcherType.LIST: 196 case _WatcherType.LIST:
151 return new _ListWatcher(exp, callback, debugName); 197 return new _ListWatcher(exp, callback, debugName, location);
152 case _WatcherType.ORDERED_MAP: 198 case _WatcherType.ORDERED_MAP:
153 return new _OrderDependantMapWatcher(exp, callback, debugName); 199 return new _OrderDependantMapWatcher(exp, callback, debugName, location);
154 case _WatcherType.HASH_MAP: 200 case _WatcherType.HASH_MAP:
155 return new _HashMapWatcher(exp, callback, debugName); 201 return new _HashMapWatcher(exp, callback, debugName, location);
156 default: 202 default:
157 return new _Watcher(exp, callback, debugName); 203 return new _Watcher(exp, callback, debugName, location);
158 } 204 }
159 } 205 }
160 206
161 /** 207 /**
162 * Add a watcher for [exp] and immediatly invoke [callback]. The watch event 208 * Add a watcher for [exp] and immediatly invoke [callback]. The watch event
163 * passed to [callback] will have `null` as the old value, and the current 209 * passed to [callback] will have `null` as the old value, and the current
164 * evaluation of [exp] as the new value. 210 * evaluation of [exp] as the new value.
165 */ 211 */
166 ChangeUnobserver watchAndInvoke(exp, callback, [debugName]) { 212 ChangeUnobserver watchAndInvoke(exp, ChangeObserver callback,
167 var res = watch(exp, callback, debugName); 213 [String debugName, String location]) {
214 var res = watch(exp, callback, debugName, location);
168 // TODO(jmesserly): this should be "is Getter" once dart2js bug is fixed. 215 // TODO(jmesserly): this should be "is Getter" once dart2js bug is fixed.
169 216
170 var value = exp; 217 var value = exp;
171 if (value is Function) { 218 if (value is Function) {
172 value = value(); 219 value = value();
173 } 220 }
174 if (value is Iterable && value is! List) { 221 if (value is Iterable && value is! List) {
175 // TODO(jmesserly): we do this for compat with watch and observe, see the 222 // TODO(jmesserly): we do this for compat with watch and observe, see the
176 // respective methods. 223 // respective methods.
177 value = value.toList(); 224 value = value.toList();
178 } 225 }
179 callback(new ChangeNotification(null, value)); 226 callback(new ChangeNotification(null, value));
180 return res; 227 return res;
181 } 228 }
182 229
183 /** Internal set of active watchers. */ 230 /** Internal set of active watchers. */
184 LinkedList<_Watcher> _watchers; 231 LinkedList<_Watcher> _watchers;
185 232
186 /** 233 /**
187 * An internal representation of a watcher. Contains the expression it watches, 234 * An internal representation of a watcher. Contains the expression it watches,
188 * the last value seen for it, and a callback to invoke when a change is 235 * the last value seen for it, and a callback to invoke when a change is
189 * detected. 236 * detected.
190 */ 237 */
191 class _Watcher { 238 class _Watcher {
192 /** Name used to debug. */ 239 /** Name used to debug. */
193 final String debugName; 240 final String debugName;
194 241
242 /** Location where the watcher was first installed (for debugging). */
243 String location;
244
245 /** Unique id used for debugging purposes. */
246 final int _uniqueId;
247 static int _nextId = 0;
248
195 /** Function that retrieves the value being watched. */ 249 /** Function that retrieves the value being watched. */
196 final Getter _getter; 250 final Getter _getter;
197 251
198 /** Callback to invoke when the value changes. */ 252 /** Callback to invoke when the value changes. */
199 final ChangeObserver _callback; 253 final ChangeObserver _callback;
200 254
201 /** Last value observed on the matched expression. */ 255 /** Last value observed on the matched expression. */
202 var _lastValue; 256 var _lastValue;
203 257
204 _Watcher(this._getter, this._callback, this.debugName) { 258 _Watcher(this._getter, this._callback, this.debugName, this.location)
259 : _uniqueId = _nextId++ {
205 _lastValue = _getter(); 260 _lastValue = _getter();
206 } 261 }
207 262
208 String toString() => debugName == null ? '<unnamed>' : debugName; 263 String toString() => '$debugName (id: #$_uniqueId)';
209 264
210 /** Detect if any changes occurred and if so invoke [_callback]. */ 265 /** Detect if any changes occurred and if so invoke [_callback]. */
211 bool compareAndNotify() { 266 bool compareAndNotify() {
212 var currentValue = _safeRead(); 267 var currentValue = _safeRead();
213 if (_compare(currentValue)) { 268 if (_compare(currentValue)) {
214 var oldValue = _lastValue; 269 var oldValue = _lastValue;
215 _update(currentValue); 270 _update(currentValue);
216 _callback(new ChangeNotification(oldValue, currentValue)); 271 _callback(new ChangeNotification(oldValue, currentValue));
217 return true; 272 return true;
218 } 273 }
219 return false; 274 return false;
220 } 275 }
221 276
222 bool _compare(currentValue) => _lastValue != currentValue; 277 bool _compare(currentValue) => _lastValue != currentValue;
223 278
224 void _update(currentValue) { 279 void _update(currentValue) {
280 if (verboseDebugMessages) {
281 if (location != null) {
282 _logger.info('watcher updated: $this, defined at:\n$location');
283 location = null;
284 } else {
285 _logger.info('watcher updated: $this');
286 }
287 }
225 _lastValue = currentValue; 288 _lastValue = currentValue;
226 } 289 }
227 290
228 /** Read [_getter] but detect whether exceptions were thrown. */ 291 /** Read [_getter] but detect whether exceptions were thrown. */
229 _safeRead() { 292 _safeRead() {
230 try { 293 try {
231 return _getter(); 294 return _getter();
232 } catch (e, trace) { 295 } catch (e, trace) {
233 print('error: evaluating $this watcher threw an exception ($e, $trace)'); 296 _logger.warning('$this watcher threw an exception: $e, $trace');
234 } 297 }
235 return _lastValue; 298 return _lastValue;
236 } 299 }
237 } 300 }
238 301
239 /** Bound for the [dispatch] algorithm. */ 302 /** Bound for the [dispatch] algorithm. */
240 final int _maxIter = 10; 303 final int maxNumIterations = 10;
241 304
242 /** 305 /**
243 * Scan all registered watchers and invoke their callbacks if the watched value 306 * Scan all registered watchers and invoke their callbacks if the watched value
244 * has changed. Because we allow listeners to modify other watched expressions, 307 * has changed. Because we allow listeners to modify other watched expressions,
245 * [dispatch] will reiterate until no changes occur or until we reach a 308 * [dispatch] will reiterate until no changes occur or until we reach a
246 * particular limit (10) to ensure termination. 309 * particular limit (10) to ensure termination.
247 */ 310 */
248 void dispatch() { 311 void dispatch() {
249 if (_watchers == null) return; 312 if (_watchers == null) return;
250 bool dirty; 313 bool dirty;
251 int total = 0; 314 int total = 0;
252 do { 315 do {
253 dirty = false; 316 dirty = false;
254 for (var watcher in _watchers) { 317 for (var watcher in _watchers) {
255 // Get the next node just in case this node gets remove by the watcher 318 // Get the next node just in case this node gets remove by the watcher
256 if (watcher.compareAndNotify()) { 319 if (watcher.compareAndNotify()) {
257 dirty = true; 320 dirty = true;
258 } 321 }
259 } 322 }
260 } while (dirty && ++total < _maxIter); 323 } while (dirty && ++total < maxNumIterations);
261 if (total == _maxIter) { 324 if (total == maxNumIterations) {
262 print('Possible loop in watchers propagation, stopped dispatch.'); 325 _logger.warning('Possible loop in watchers propagation, stopped dispatch.');
263 } 326 }
264 } 327 }
265 328
266 /** 329 /**
267 * An indirect getter. Basically a simple closure that returns a value, which is 330 * An indirect getter. Basically a simple closure that returns a value, which is
268 * the most common argument in [watch]. 331 * the most common argument in [watch].
269 */ 332 */
270 typedef T Getter<T>(); 333 typedef T Getter<T>();
271 334
272 /** An indirect setter. */ 335 /** An indirect setter. */
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
314 } 377 }
315 } 378 }
316 } 379 }
317 380
318 /** 381 /**
319 * A watcher for list objects. It stores as the last value a shallow copy of the 382 * A watcher for list objects. It stores as the last value a shallow copy of the
320 * list as it was when we last detected any changes. 383 * list as it was when we last detected any changes.
321 */ 384 */
322 class _ListWatcher<T> extends _Watcher { 385 class _ListWatcher<T> extends _Watcher {
323 386
324 _ListWatcher(getter, ChangeObserver callback, String debugName) 387 _ListWatcher(getter, ChangeObserver callback, String debugName,
325 : super(getter, callback, debugName) { 388 String location)
389 : super(getter, callback, debugName, location) {
326 _update(_safeRead()); 390 _update(_safeRead());
327 } 391 }
328 392
329 bool _compare(List<T> currentValue) { 393 bool _compare(List<T> currentValue) {
330 return _iterablesNotEqual(_lastValue, currentValue); 394 return _iterablesNotEqual(_lastValue, currentValue);
331 } 395 }
332 396
333 void _update(currentValue) { 397 void _update(currentValue) {
334 _lastValue = new List<T>.from(currentValue); 398 _lastValue = new List<T>.from(currentValue);
335 } 399 }
336 } 400 }
337 401
338 /** 402 /**
339 * A watcher for hash map objects. It stores as the last value a shallow copy 403 * A watcher for hash map objects. It stores as the last value a shallow copy
340 * of the map as it was when we last detected any changes. Order for the map 404 * of the map as it was when we last detected any changes. Order for the map
341 * does not matter for equality. 405 * does not matter for equality.
342 */ 406 */
343 class _HashMapWatcher<K, V> extends _Watcher { 407 class _HashMapWatcher<K, V> extends _Watcher {
344 408
345 _HashMapWatcher(getter, ChangeObserver callback, String debugName) 409 _HashMapWatcher(getter, ChangeObserver callback, String debugName,
346 : super(getter, callback, debugName) { 410 String location)
411 : super(getter, callback, debugName, location) {
347 _update(_safeRead()); 412 _update(_safeRead());
348 } 413 }
349 414
350 bool _compare(Map<K, V> currentValue) { 415 bool _compare(Map<K, V> currentValue) {
351 Iterable<K> keys = _lastValue.keys; 416 Iterable<K> keys = _lastValue.keys;
352 if (keys.length != currentValue.keys.length) return true; 417 if (keys.length != currentValue.keys.length) return true;
353 418
354 Iterator<K> keyIterator = keys.iterator; 419 Iterator<K> keyIterator = keys.iterator;
355 while (keyIterator.moveNext()) { 420 while (keyIterator.moveNext()) {
356 K key = keyIterator.current; 421 K key = keyIterator.current;
357 if (!currentValue.containsKey(key)) return true; 422 if (!currentValue.containsKey(key)) return true;
358 if (_lastValue[key] != currentValue[key]) return true; 423 if (_lastValue[key] != currentValue[key]) return true;
359 } 424 }
360 return false; 425 return false;
361 } 426 }
362 427
363 void _update(currentValue) { 428 void _update(currentValue) {
364 _lastValue = new Map<K, V>.from(currentValue); 429 _lastValue = new Map<K, V>.from(currentValue);
365 } 430 }
366 } 431 }
367 432
368 /** 433 /**
369 * A watcher for maps where key order matters. It stores as the last value a 434 * A watcher for maps where key order matters. It stores as the last value a
370 * shallow copy of the map as it was when we last detected any changes. 435 * shallow copy of the map as it was when we last detected any changes.
371 */ 436 */
372 class _OrderDependantMapWatcher<K, V> extends _Watcher { 437 class _OrderDependantMapWatcher<K, V> extends _Watcher {
373 438
374 _OrderDependantMapWatcher(getter, ChangeObserver callback, String debugName) 439 _OrderDependantMapWatcher(getter, ChangeObserver callback, String debugName,
375 : super(getter, callback, debugName) { 440 String location)
441 : super(getter, callback, debugName, location) {
376 _update(_safeRead()); 442 _update(_safeRead());
377 } 443 }
378 444
379 bool _compare(Map<K, V> currentValue) { 445 bool _compare(Map<K, V> currentValue) {
380 return _iterablesNotEqual(currentValue.keys, _lastValue.keys) || 446 return _iterablesNotEqual(currentValue.keys, _lastValue.keys) ||
381 _iterablesNotEqual(currentValue.values, _lastValue.values); 447 _iterablesNotEqual(currentValue.values, _lastValue.values);
382 } 448 }
383 449
384 void _update(currentValue) { 450 void _update(currentValue) {
385 _lastValue = new LinkedHashMap.from(currentValue); 451 _lastValue = new LinkedHashMap.from(currentValue);
(...skipping 19 matching lines...) Expand all
405 class _WatcherType { 471 class _WatcherType {
406 final _value; 472 final _value;
407 const _WatcherType._internal(this._value); 473 const _WatcherType._internal(this._value);
408 toString() => 'Enum.$_value'; 474 toString() => 'Enum.$_value';
409 475
410 static const LIST = const _WatcherType._internal('LIST'); 476 static const LIST = const _WatcherType._internal('LIST');
411 static const HASH_MAP = const _WatcherType._internal('HASH_MAP'); 477 static const HASH_MAP = const _WatcherType._internal('HASH_MAP');
412 static const ORDERED_MAP = const _WatcherType._internal('ORDERED_MAP'); 478 static const ORDERED_MAP = const _WatcherType._internal('ORDERED_MAP');
413 static const OTHER = const _WatcherType._internal('OTHER'); 479 static const OTHER = const _WatcherType._internal('OTHER');
414 } 480 }
OLDNEW
« no previous file with comments | « lib/testing/render_test.dart ('k') | pubspec.yaml » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698