Chain.forTrace() trims the innermost trace's VM chain

This was already being trimmed for all other stack traces, but the
innermost one requires different logic, so we missed it.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 916448c..b0c972a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,11 @@
   synchronously within `Chain.capture()`. This matches the existing behavior
   outside `Chain.capture()`.
 
+* `Chain.forTrace()` now trims the VM's stack chains for the innermost stack
+  trace within `Chain.capture()` (unless it's called synchronously, as above).
+  This avoids duplicated frames and makes the format of the innermost traces
+  consistent with the other traces in the chain.
+
 ## 1.8.2
 
 * Update to use strong-mode clean Zone API.
diff --git a/lib/src/stack_zone_specification.dart b/lib/src/stack_zone_specification.dart
index 2db98b7..cd5d068 100644
--- a/lib/src/stack_zone_specification.dart
+++ b/lib/src/stack_zone_specification.dart
@@ -90,13 +90,20 @@
     trace ??= StackTrace.current;
 
     var previous = _chains[trace] ?? _currentNode;
-    if (previous != null) return new _Node(trace, previous).toChain();
+    if (previous == null) {
+      // If there's no [_currentNode], we're running synchronously beneath
+      // [Chain.capture] and we should fall back to the VM's stack chaining. We
+      // can't use [Chain.from] here because it'll just call [chainFor] again.
+      if (trace is Trace) return new Chain([trace]);
+      return new LazyChain(() => new Chain.parse(trace.toString()));
+    } else {
+      if (trace is! Trace) {
+        var original = trace;
+        trace = new LazyTrace(() => new Trace.parse(_trimVMChain(original)));
+      }
 
-    // If there's no [_currentNode], we're running synchronously beneath
-    // [Chain.capture] and we should fall back to the VM's stack chaining. We
-    // can't use [Chain.from] here because it'll just call [chainFor] again.
-    if (trace is Trace) return new Chain([trace]);
-    return new LazyChain(() => new Chain.parse(trace.toString()));
+      return new _Node(trace, previous).toChain();
+    }
   }
 
   /// Tracks the current stack chain so it can be set to [_currentChain] when
@@ -203,6 +210,29 @@
       _currentNode = previousNode;
     }
   }
+
+  /// Like [new Trace.current], but if the current stack trace has VM chaining
+  /// enabled, this only returns the innermost sub-trace.
+  Trace _currentTrace([int level]) {
+    level ??= 0;
+    var stackTrace = StackTrace.current;
+    return new LazyTrace(() {
+      var text = _trimVMChain(stackTrace);
+      var trace = new Trace.parse(text);
+      // 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.
+      return new Trace(trace.frames.skip(level + (inJS ? 2 : 1)),
+          original: text);
+    });
+  }
+
+  /// Removes the VM's stack chains from the native [trace], since we're
+  /// generating our own and we don't want duplicate frames.
+  String _trimVMChain(StackTrace trace) {
+    var text = trace.toString();
+    var index = text.indexOf(vmChainGap);
+    return index == -1 ? text : text.substring(0, index);
+  }
 }
 
 /// A linked list node representing a single entry in a stack chain.
@@ -213,8 +243,7 @@
   /// The previous node in the chain.
   final _Node previous;
 
-  _Node(StackTrace trace, [this.previous])
-      : trace = trace == null ? _currentTrace() : new Trace.from(trace);
+  _Node(StackTrace trace, [this.previous]) : trace = new Trace.from(trace);
 
   /// Converts this to a [Chain].
   Chain toChain() {
@@ -227,22 +256,3 @@
     return new Chain(nodes);
   }
 }
-
-/// Like [new Trace.current], but if the current stack trace has VM chaining
-/// enabled, this only returns the innermost sub-trace.
-Trace _currentTrace([int level]) {
-  level ??= 0;
-  var stackTrace = StackTrace.current;
-  return new LazyTrace(() {
-    // Ignore the VM's stack chains when we generate our own. Otherwise we'll
-    // end up with duplicate frames all over the place.
-    var text = stackTrace.toString();
-    var index = text.indexOf(vmChainGap);
-    if (index != -1) text = text.substring(0, index);
-
-    var trace = new Trace.parse(text);
-    // 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.
-    return new Trace(trace.frames.skip(level + (inJS ? 2 : 1)), original: text);
-  });
-}
diff --git a/pubspec.yaml b/pubspec.yaml
index 79cf0b0..65d3ef6 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -7,7 +7,7 @@
 #
 # When the major version is upgraded, you *must* update that version constraint
 # in pub to stay in sync with this.
-version: 1.8.3-dev
+version: 1.8.3
 author: "Dart Team <misc@dartlang.org>"
 homepage: https://github.com/dart-lang/stack_trace
 description: A package for manipulating stack traces and printing them readably.
diff --git a/test/chain/vm_test.dart b/test/chain/vm_test.dart
index 6f793c4..a47cbf7 100644
--- a/test/chain/vm_test.dart
+++ b/test/chain/vm_test.dart
@@ -468,8 +468,11 @@
       });
 
       expect(chain.traces, hasLength(2));
+
+      // Assert that we've trimmed the VM's stack chains here to avoid
+      // duplication.
       expect(chain.traces.first.toString(),
-          equals(new Trace.from(trace).toString()));
+          equals(new Chain.parse(trace.toString()).traces.first.toString()));
       expect(
           chain.traces.last.frames, contains(frameMember(startsWith('main'))));
     });