| Index: packages/stack_trace/lib/src/chain.dart | 
| diff --git a/packages/stack_trace/lib/src/chain.dart b/packages/stack_trace/lib/src/chain.dart | 
| index 2e07d4bb9c776b81e50647d60a9e449cdbd89ad0..2d1349de2076f0b9dc685b9274dbe8bfc3935a66 100644 | 
| --- a/packages/stack_trace/lib/src/chain.dart | 
| +++ b/packages/stack_trace/lib/src/chain.dart | 
| @@ -2,20 +2,22 @@ | 
| // for details. All rights reserved. Use of this source code is governed by a | 
| // BSD-style license that can be found in the LICENSE file. | 
|  | 
| -library stack_trace.chain; | 
| - | 
| import 'dart:async'; | 
| -import 'dart:collection'; | 
| import 'dart:math' as math; | 
|  | 
| import 'frame.dart'; | 
| +import 'lazy_chain.dart'; | 
| import 'stack_zone_specification.dart'; | 
| import 'trace.dart'; | 
| import 'utils.dart'; | 
|  | 
| /// A function that handles errors in the zone wrapped by [Chain.capture]. | 
| +@Deprecated("Will be removed in stack_trace 2.0.0.") | 
| typedef void ChainHandler(error, Chain chain); | 
|  | 
| +/// An opaque key used to track the current [StackZoneSpecification]. | 
| +final _specKey = new Object(); | 
| + | 
| /// A chain of stack traces. | 
| /// | 
| /// A stack chain is a collection of one or more stack traces that collectively | 
| @@ -37,7 +39,6 @@ typedef void ChainHandler(error, Chain chain); | 
| ///             "$stackChain"); | 
| ///     }); | 
| class Chain implements StackTrace { | 
| - | 
| /// The stack traces that make up this chain. | 
| /// | 
| /// Like the frames in a stack trace, the traces are ordered from most local | 
| @@ -46,11 +47,16 @@ class Chain implements StackTrace { | 
| final List<Trace> traces; | 
|  | 
| /// The [StackZoneSpecification] for the current zone. | 
| -  static StackZoneSpecification get _currentSpec => | 
| -    Zone.current[#stack_trace.stack_zone.spec]; | 
| +  static StackZoneSpecification get _currentSpec => Zone.current[_specKey]; | 
|  | 
| -  /// Runs [callback] in a [Zone] in which the current stack chain is tracked | 
| -  /// and automatically associated with (most) errors. | 
| +  /// If [when] is `true`, runs [callback] in a [Zone] in which the current | 
| +  /// stack chain is tracked and automatically associated with (most) errors. | 
| +  /// | 
| +  /// If [when] is `false`, this does not track stack chains. Instead, it's | 
| +  /// identical to [runZoned], except that it wraps any errors in [new | 
| +  /// Chain.forTrace]—which will only wrap the trace unless there's a different | 
| +  /// [Chain.capture] active. This makes it easy for the caller to only capture | 
| +  /// stack chains in debug mode or during development. | 
| /// | 
| /// If [onError] is passed, any error in the zone that would otherwise go | 
| /// unhandled is passed to it, along with the [Chain] associated with that | 
| @@ -64,7 +70,23 @@ class Chain implements StackTrace { | 
| /// considered unhandled. | 
| /// | 
| /// If [callback] returns a value, it will be returned by [capture] as well. | 
| -  static capture(callback(), {ChainHandler onError}) { | 
| +  static T capture<T>(T callback(), | 
| +      {void onError(error, Chain chain), bool when: true}) { | 
| +    if (!when) { | 
| +      var newOnError; | 
| +      if (onError != null) { | 
| +        newOnError = (error, stackTrace) { | 
| +          onError( | 
| +              error, | 
| +              stackTrace == null | 
| +                  ? new Chain.current() | 
| +                  : new Chain.forTrace(stackTrace)); | 
| +        }; | 
| +      } | 
| + | 
| +      return runZoned(callback, onError: newOnError); | 
| +    } | 
| + | 
| var spec = new StackZoneSpecification(onError); | 
| return runZoned(() { | 
| try { | 
| @@ -73,9 +95,20 @@ class Chain implements StackTrace { | 
| // TODO(nweiz): Don't special-case this when issue 19566 is fixed. | 
| return Zone.current.handleUncaughtError(error, stackTrace); | 
| } | 
| -    }, zoneSpecification: spec.toSpec(), zoneValues: { | 
| -      #stack_trace.stack_zone.spec: spec | 
| -    }); | 
| +    }, | 
| +        zoneSpecification: spec.toSpec(), | 
| +        zoneValues: {_specKey: spec, StackZoneSpecification.disableKey: false}); | 
| +  } | 
| + | 
| +  /// If [when] is `true` and this is called within a [Chain.capture] zone, runs | 
| +  /// [callback] in a [Zone] in which chain capturing is disabled. | 
| +  /// | 
| +  /// If [callback] returns a value, it will be returned by [disable] as well. | 
| +  static/*=T*/ disable/*<T>*/(/*=T*/ callback(), {bool when: true}) { | 
| +    var zoneValues = | 
| +        when ? {_specKey: null, StackZoneSpecification.disableKey: true} : null; | 
| + | 
| +    return runZoned(callback, zoneValues: zoneValues); | 
| } | 
|  | 
| /// Returns [futureOrStream] unmodified. | 
| @@ -94,9 +127,18 @@ class Chain implements StackTrace { | 
| /// | 
| /// If this is called outside of a [capture] zone, it just returns a | 
| /// single-trace chain. | 
| -  factory Chain.current([int level=0]) { | 
| +  factory Chain.current([int level = 0]) { | 
| if (_currentSpec != null) return _currentSpec.currentChain(level + 1); | 
| -    return new Chain([new Trace.current(level + 1)]); | 
| + | 
| +    var chain = new Chain.forTrace(StackTrace.current); | 
| +    return new LazyChain(() { | 
| +      // JS includes a frame for the call to StackTrace.current, but the VM | 
| +      // doesn't, so we skip an extra frame in a JS context. | 
| +      var first = new Trace( | 
| +          chain.traces.first.frames.skip(level + (inJS ? 2 : 1)), | 
| +          original: chain.traces.first.original.toString()); | 
| +      return new Chain([first]..addAll(chain.traces.skip(1))); | 
| +    }); | 
| } | 
|  | 
| /// Returns the stack chain associated with [trace]. | 
| @@ -109,8 +151,8 @@ class Chain implements StackTrace { | 
| /// If [trace] is already a [Chain], it will be returned as-is. | 
| factory Chain.forTrace(StackTrace trace) { | 
| if (trace is Chain) return trace; | 
| -    if (_currentSpec == null) return new Chain([new Trace.from(trace)]); | 
| -    return _currentSpec.chainFor(trace); | 
| +    if (_currentSpec != null) return _currentSpec.chainFor(trace); | 
| +    return new LazyChain(() => new Chain.parse(trace.toString())); | 
| } | 
|  | 
| /// Parses a string representation of a stack chain. | 
| @@ -120,6 +162,10 @@ class Chain implements StackTrace { | 
| /// and returned as a single-trace chain. | 
| factory Chain.parse(String chain) { | 
| if (chain.isEmpty) return new Chain([]); | 
| +    if (chain.contains(vmChainGap)) { | 
| +      return new Chain( | 
| +          chain.split(vmChainGap).map((trace) => new Trace.parseVM(trace))); | 
| +    } | 
| if (!chain.contains(chainGap)) return new Chain([new Trace.parse(chain)]); | 
|  | 
| return new Chain( | 
| @@ -127,13 +173,19 @@ class Chain implements StackTrace { | 
| } | 
|  | 
| /// Returns a new [Chain] comprised of [traces]. | 
| -  Chain(Iterable<Trace> traces) | 
| -      : traces = new UnmodifiableListView<Trace>(traces.toList()); | 
| +  Chain(Iterable<Trace> traces) : traces = new List<Trace>.unmodifiable(traces); | 
|  | 
| /// Returns a terser version of [this]. | 
| /// | 
| /// This calls [Trace.terse] on every trace in [traces], and discards any | 
| /// trace that contain only internal frames. | 
| +  /// | 
| +  /// This won't do anything with a raw JavaScript trace, since there's no way | 
| +  /// to determine which frames come from which Dart libraries. However, the | 
| +  /// [`source_map_stack_trace`][source_map_stack_trace] package can be used to | 
| +  /// convert JavaScript traces into Dart-style traces. | 
| +  /// | 
| +  /// [source_map_stack_trace]: https://pub.dartlang.org/packages/source_map_stack_trace | 
| Chain get terse => foldFrames((_) => false, terse: true); | 
|  | 
| /// Returns a new [Chain] based on [this] where multiple stack frames matching | 
| @@ -150,11 +202,12 @@ class Chain implements StackTrace { | 
| /// library or from this package, and simplify core library frames as in | 
| /// [Trace.terse]. | 
| Chain foldFrames(bool predicate(Frame frame), {bool terse: false}) { | 
| -    var foldedTraces = traces.map( | 
| -        (trace) => trace.foldFrames(predicate, terse: terse)); | 
| +    var foldedTraces = | 
| +        traces.map((trace) => trace.foldFrames(predicate, terse: terse)); | 
| var nonEmptyTraces = foldedTraces.where((trace) { | 
| // Ignore traces that contain only folded frames. | 
| if (trace.frames.length > 1) return true; | 
| +      if (trace.frames.isEmpty) return false; | 
|  | 
| // In terse mode, the trace may have removed an outer folded frame, | 
| // leaving a single non-folded frame. We can detect a folded frame because | 
| @@ -176,12 +229,13 @@ class Chain implements StackTrace { | 
| /// | 
| /// The trace version of a chain is just the concatenation of all the traces | 
| /// in the chain. | 
| -  Trace toTrace() => new Trace(flatten(traces.map((trace) => trace.frames))); | 
| +  Trace toTrace() => new Trace(traces.expand((trace) => trace.frames)); | 
|  | 
| String toString() { | 
| // Figure out the longest path so we know how much to pad. | 
| var longest = traces.map((trace) { | 
| -      return trace.frames.map((frame) => frame.location.length) | 
| +      return trace.frames | 
| +          .map((frame) => frame.location.length) | 
| .fold(0, math.max); | 
| }).fold(0, math.max); | 
|  | 
| @@ -189,7 +243,7 @@ class Chain implements StackTrace { | 
| // padding is consistent across all traces. | 
| return traces.map((trace) { | 
| return trace.frames.map((frame) { | 
| -        return '${padRight(frame.location, longest)}  ${frame.member}\n'; | 
| +        return '${frame.location.padRight(longest)}  ${frame.member}\n'; | 
| }).join(); | 
| }).join(chainGap); | 
| } | 
|  |