Version 2.12.0-33.0.dev

Merge commit '546fdf55bccaa02105c3cb0645f2325f5145f5ef' into 'dev'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 23f93f2..139cd68 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -56,8 +56,9 @@
 
 #### Linter
 
-Updated the Linter to `0.1.124`, which includes:
+Updated the Linter to `0.1.125`, which includes:
 
+* (Internal): test updates to the new `PhysicalResourceProvider` API.
 * Fixed false positives in `prefer_constructors_over_static_methods`.
 * Updates to `package_names` to allow leading underscores.
 * Fixed NPEs in `unnecessary_null_checks`.
diff --git a/DEPS b/DEPS
index a93ac4f..bb39333 100644
--- a/DEPS
+++ b/DEPS
@@ -117,7 +117,7 @@
   "intl_tag": "0.17.0-nullsafety",
   "jinja2_rev": "2222b31554f03e62600cd7e383376a7c187967a1",
   "json_rpc_2_rev": "b8dfe403fd8528fd14399dee3a6527b55802dd4d",
-  "linter_tag": "0.1.124",
+  "linter_tag": "0.1.125",
   "logging_rev": "e2f633b543ef89c54688554b15ca3d7e425b86a2",
   "markupsafe_rev": "8f45f5cfa0009d2a70589bcda0349b8cb2b72783",
   "markdown_rev": "6f89681d59541ddb1cf3a58efbdaa2304ffc3f51",
@@ -148,7 +148,7 @@
   "source_span_rev": "49ff31eabebed0da0ae6634124f8ba5c6fbf57f1",
   "sse_tag": "e5cf68975e8e87171a3dc297577aa073454a91dc",
   "stack_trace_tag": "6788afc61875079b71b3d1c3e65aeaa6a25cbc2f",
-  "stagehand_tag": "v3.3.9",
+  "stagehand_tag": "v3.3.11",
   "stream_channel_tag": "d7251e61253ec389ee6e045ee1042311bced8f1d",
   "string_scanner_rev": "1b63e6e5db5933d7be0a45da6e1129fe00262734",
   "sync_http_rev": "a85d7ec764ea485cbbc49f3f3e7f1b43f87a1c74",
diff --git a/pkg/analysis_server/lib/src/lsp/client_configuration.dart b/pkg/analysis_server/lib/src/lsp/client_configuration.dart
index 55bfaf5..8af5a85 100644
--- a/pkg/analysis_server/lib/src/lsp/client_configuration.dart
+++ b/pkg/analysis_server/lib/src/lsp/client_configuration.dart
@@ -20,6 +20,7 @@
     }
   }
 
+  bool get completeFunctionCalls => _settings['completeFunctionCalls'] ?? false;
   bool get enableSdkFormatter => _settings['enableSdkFormatter'] ?? true;
   int get lineLength => _settings['lineLength'];
 
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
index 430489a..e9d0fcd 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
@@ -239,6 +239,8 @@
                 // https://github.com/microsoft/vscode-languageserver-node/issues/673
                 includeCommitCharacters:
                     server.clientConfiguration.previewCommitCharacters,
+                completeFunctionCalls:
+                    server.clientConfiguration.completeFunctionCalls,
               ),
             )
             .toList();
@@ -372,6 +374,7 @@
           // not assume that the Dart ones would be correct for all of their
           // completions.
           includeCommitCharacters: false,
+          completeFunctionCalls: false,
         ),
       );
     });
diff --git a/pkg/analysis_server/lib/src/lsp/mapping.dart b/pkg/analysis_server/lib/src/lsp/mapping.dart
index 039b503..e425b2d 100644
--- a/pkg/analysis_server/lib/src/lsp/mapping.dart
+++ b/pkg/analysis_server/lib/src/lsp/mapping.dart
@@ -62,13 +62,14 @@
           _asMarkup(preferredFormats, content));
 }
 
-/// Builds an LSP snippet string that uses a $1 tabstop to set the selected text
-/// after insertion.
-String buildSnippetStringWithSelection(
+/// Builds an LSP snippet string with supplied ranges as tabstops.
+String buildSnippetStringWithTabStops(
   String text,
-  int selectionOffset,
-  int selectionLength,
+  List<int> offsetLengthPairs,
 ) {
+  text ??= '';
+  offsetLengthPairs ??= const [];
+
   String escape(String input) => input.replaceAllMapped(
         RegExp(r'[$}\\]'), // Replace any of $ } \
         (c) => '\\${c[0]}', // Prefix with a backslash
@@ -77,17 +78,29 @@
   // https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/#snippet-syntax
   //
   // $1, $2, etc. are used for tab stops and ${1:foo} inserts a placeholder of foo.
-  // Since we only need to support a single tab stop, our string is constructed of three parts:
-  // - Anything before the selection
-  // - The selection (which may or may not include text, depending on selectionLength)
-  // - Anything after the selection
-  final prefix = escape(text.substring(0, selectionOffset));
-  final selectionText = escape(
-      text.substring(selectionOffset, selectionOffset + selectionLength));
-  final selection = '\${1:$selectionText}';
-  final suffix = escape(text.substring(selectionOffset + selectionLength));
 
-  return '$prefix$selection$suffix';
+  final output = [];
+  var offset = 0;
+  var tabStopNumber = 1;
+  for (var i = 0; i < offsetLengthPairs.length; i += 2) {
+    final pairOffset = offsetLengthPairs[i];
+    final pairLength = offsetLengthPairs[i + 1];
+
+    // Add any text that came before this tabstop to the result.
+    output.add(escape(text.substring(offset, pairOffset)));
+
+    // Add this tabstop
+    final tabStopText =
+        escape(text.substring(pairOffset, pairOffset + pairLength));
+    output.add('\${${tabStopNumber++}:$tabStopText}');
+
+    offset = pairOffset + pairLength;
+  }
+
+  // Add any remaining text that was after the last tabstop.
+  output.add(escape(text.substring(offset)));
+
+  return output.join('');
 }
 
 /// Note: This code will fetch the version of each document being modified so
@@ -769,6 +782,7 @@
   int replacementOffset,
   int replacementLength, {
   @required bool includeCommitCharacters,
+  @required bool completeFunctionCalls,
 }) {
   // Build display labels and text to insert. insertText and filterText may
   // differ from label (for ex. if the label includes things like (…)). If
@@ -782,14 +796,24 @@
     label = label.substring(0, label.length - 1);
   }
 
-  if (suggestion.displayText == null) {
-    switch (suggestion.element?.kind) {
-      case server.ElementKind.CONSTRUCTOR:
-      case server.ElementKind.FUNCTION:
-      case server.ElementKind.METHOD:
-        label += suggestion.parameterNames?.isNotEmpty ?? false ? '(…)' : '()';
-        break;
-    }
+  // isCallable is used to suffix the label with parens so it's clear the item
+  // is callable.
+  //
+  // isInvocation means the location at which it's used is an invocation (and
+  // therefore it is appropriate to include the parens/parameters in the
+  // inserted text).
+  //
+  // In the case of show combinators, the parens will still be shown to indicate
+  // functions but they should not be included in the completions.
+  final elementKind = suggestion.element?.kind;
+  final isCallable = elementKind == server.ElementKind.CONSTRUCTOR ||
+      elementKind == server.ElementKind.FUNCTION ||
+      elementKind == server.ElementKind.METHOD;
+  final isInvocation =
+      suggestion.kind == server.CompletionSuggestionKind.INVOCATION;
+
+  if (suggestion.displayText == null && isCallable) {
+    label += suggestion.parameterNames?.isNotEmpty ?? false ? '(…)' : '()';
   }
 
   final supportsDeprecatedFlag =
@@ -809,13 +833,33 @@
           supportedCompletionItemKinds, suggestion.kind, label);
 
   var insertTextFormat = lsp.InsertTextFormat.PlainText;
-  if (supportsSnippets && suggestion.selectionOffset != 0) {
-    insertTextFormat = lsp.InsertTextFormat.Snippet;
-    insertText = buildSnippetStringWithSelection(
-      suggestion.completion,
-      suggestion.selectionOffset,
-      suggestion.selectionLength,
-    );
+
+  // If the client supports snippets, we can support completeFunctionCalls or
+  // setting a selection.
+  if (supportsSnippets) {
+    // completeFunctionCalls should only work if commit characters are disabled
+    // otherwise the editor may insert parens that we're also inserting.
+    if (!includeCommitCharacters &&
+        completeFunctionCalls &&
+        isCallable &&
+        isInvocation) {
+      insertTextFormat = lsp.InsertTextFormat.Snippet;
+      final hasRequiredParameters =
+          (suggestion.defaultArgumentListTextRanges?.length ?? 0) > 0;
+      final functionCallSuffix = hasRequiredParameters
+          ? buildSnippetStringWithTabStops(
+              suggestion.defaultArgumentListString,
+              suggestion.defaultArgumentListTextRanges,
+            )
+          : '\${1:}'; // No required params still gets a tabstop in the parens.
+      insertText += '($functionCallSuffix)';
+    } else if (suggestion.selectionOffset != 0) {
+      insertTextFormat = lsp.InsertTextFormat.Snippet;
+      insertText = buildSnippetStringWithTabStops(
+        suggestion.completion,
+        [suggestion.selectionOffset, suggestion.selectionLength],
+      );
+    }
   }
 
   // Because we potentially send thousands of these items, we should minimise
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/replace_with_var.dart b/pkg/analysis_server/lib/src/services/correction/dart/replace_with_var.dart
index cd226a8..8ac0b18 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/replace_with_var.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/replace_with_var.dart
@@ -42,6 +42,9 @@
       String typeArgumentsText;
       int typeArgumentsOffset;
       if (type is NamedType && type.typeArguments != null) {
+        if (initializer is CascadeExpression) {
+          initializer = (initializer as CascadeExpression).target;
+        }
         if (initializer is TypedLiteral) {
           if (initializer.typeArguments == null) {
             typeArgumentsText = utils.getNodeText(type.typeArguments);
diff --git a/pkg/analysis_server/test/lsp/completion_test.dart b/pkg/analysis_server/test/lsp/completion_test.dart
index cdecc6f..94ff72b 100644
--- a/pkg/analysis_server/test/lsp/completion_test.dart
+++ b/pkg/analysis_server/test/lsp/completion_test.dart
@@ -15,6 +15,7 @@
 void main() {
   defineReflectiveSuite(() {
     defineReflectiveTests(CompletionTest);
+    defineReflectiveTests(CompletionTestWithNullSafetyTest);
   });
 }
 
@@ -89,6 +90,93 @@
     expect(options.allCommitCharacters, equals(dartCompletionCommitCharacters));
   }
 
+  Future<void> test_completeFunctionCalls() async {
+    final content = '''
+    void myFunction(String a, int b, {String c}) {}
+
+    main() {
+      [[myFu^]]
+    }
+    ''';
+
+    await provideConfig(
+      () => initialize(
+        textDocumentCapabilities: withCompletionItemSnippetSupport(
+            emptyTextDocumentClientCapabilities),
+        workspaceCapabilities:
+            withConfigurationSupport(emptyWorkspaceClientCapabilities),
+      ),
+      {'completeFunctionCalls': true},
+    );
+    await openFile(mainFileUri, withoutMarkers(content));
+    final res = await getCompletion(mainFileUri, positionFromMarker(content));
+    final item = res.singleWhere((c) => c.label == 'myFunction(…)');
+    // Ensure the snippet comes through in the expected format with the expected
+    // placeholders.
+    expect(item.insertTextFormat, equals(InsertTextFormat.Snippet));
+    expect(item.insertText, equals(r'myFunction(${1:a}, ${2:b})'));
+    expect(item.textEdit.newText, equals(item.insertText));
+    expect(
+      item.textEdit.range,
+      equals(rangeFromMarkers(content)),
+    );
+  }
+
+  Future<void> test_completeFunctionCalls_noRequiredParameters() async {
+    final content = '''
+    void myFunction({int a}) {}
+
+    main() {
+      [[myFu^]]
+    }
+    ''';
+
+    await provideConfig(
+      () => initialize(
+        textDocumentCapabilities: withCompletionItemSnippetSupport(
+            emptyTextDocumentClientCapabilities),
+        workspaceCapabilities:
+            withConfigurationSupport(emptyWorkspaceClientCapabilities),
+      ),
+      {'completeFunctionCalls': true},
+    );
+    await openFile(mainFileUri, withoutMarkers(content));
+    final res = await getCompletion(mainFileUri, positionFromMarker(content));
+    final item = res.singleWhere((c) => c.label == 'myFunction(…)');
+    // With no required params, there should still be parens and a tabstop inside.
+    expect(item.insertTextFormat, equals(InsertTextFormat.Snippet));
+    expect(item.insertText, equals(r'myFunction(${1:})'));
+    expect(item.textEdit.newText, equals(item.insertText));
+    expect(
+      item.textEdit.range,
+      equals(rangeFromMarkers(content)),
+    );
+  }
+
+  Future<void> test_completeFunctionCalls_show() async {
+    final content = '''
+    import 'dart:math' show mi^
+    ''';
+
+    await provideConfig(
+      () => initialize(
+        textDocumentCapabilities: withCompletionItemSnippetSupport(
+            emptyTextDocumentClientCapabilities),
+        workspaceCapabilities:
+            withConfigurationSupport(emptyWorkspaceClientCapabilities),
+      ),
+      {'completeFunctionCalls': true},
+    );
+    await openFile(mainFileUri, withoutMarkers(content));
+    final res = await getCompletion(mainFileUri, positionFromMarker(content));
+    final item = res.singleWhere((c) => c.label == 'min(…)');
+    // Ensure the snippet does not include the parens/args as it doesn't
+    // make sense in the show clause. There will still be a trailing tabstop.
+    expect(item.insertTextFormat, equals(InsertTextFormat.Snippet));
+    expect(item.insertText, equals(r'min${1:}'));
+    expect(item.textEdit.newText, equals(item.insertText));
+  }
+
   Future<void> test_completionKinds_default() async {
     newFile(join(projectFolderPath, 'file.dart'));
     newFolder(join(projectFolderPath, 'folder'));
@@ -1237,3 +1325,44 @@
     expect(updated, contains('a.abcdefghij'));
   }
 }
+
+@reflectiveTest
+class CompletionTestWithNullSafetyTest extends AbstractLspAnalysisServerTest {
+  @override
+  String get testPackageLanguageVersion => latestLanguageVersion;
+
+  @failingTest
+  Future<void> test_completeFunctionCalls_requiredNamed() async {
+    // TODO(dantup): Find out how we can tell this parameter is required
+    // (in the completion mapping).
+    final content = '''
+    void myFunction(String a, int b, {required String c, String d = ''}) {}
+
+    main() {
+      [[myFu^]]
+    }
+    ''';
+
+    await provideConfig(
+      () => initialize(
+        textDocumentCapabilities: withCompletionItemSnippetSupport(
+            emptyTextDocumentClientCapabilities),
+        workspaceCapabilities:
+            withConfigurationSupport(emptyWorkspaceClientCapabilities),
+      ),
+      {'completeFunctionCalls': true},
+    );
+    await openFile(mainFileUri, withoutMarkers(content));
+    final res = await getCompletion(mainFileUri, positionFromMarker(content));
+    final item = res.singleWhere((c) => c.label == 'myFunction(…)');
+    // Ensure the snippet comes through in the expected format with the expected
+    // placeholders.
+    expect(item.insertTextFormat, equals(InsertTextFormat.Snippet));
+    expect(item.insertText, equals(r'myFunction(${1:a}, ${2:b}, ${2:c})'));
+    expect(item.textEdit.newText, equals(item.insertText));
+    expect(
+      item.textEdit.range,
+      equals(rangeFromMarkers(content)),
+    );
+  }
+}
diff --git a/pkg/analysis_server/test/lsp/mapping_test.dart b/pkg/analysis_server/test/lsp/mapping_test.dart
index 2ae7ac1..72bf3f6 100644
--- a/pkg/analysis_server/test/lsp/mapping_test.dart
+++ b/pkg/analysis_server/test/lsp/mapping_test.dart
@@ -74,18 +74,35 @@
     expect(result, isNull);
   }
 
-  Future<void> test_selectionsInSnippets_empty() async {
-    var result = lsp.buildSnippetStringWithSelection('teststring', 4, 0);
-    expect(result, equals(r'test${1:}string'));
+  Future<void> test_tabStopsInSnippets_contains() async {
+    var result = lsp.buildSnippetStringWithTabStops('a, b, c', [3, 1]);
+    expect(result, equals(r'a, ${1:b}, c'));
   }
 
-  Future<void> test_selectionsInSnippets_escaping() async {
-    var result = lsp.buildSnippetStringWithSelection(r'te$tstri}ng', 4, 3);
-    expect(result, equals(r'te\$t${1:str}i\}ng'));
+  Future<void> test_tabStopsInSnippets_empty() async {
+    var result = lsp.buildSnippetStringWithTabStops('a, b', []);
+    expect(result, equals(r'a, b'));
   }
 
-  Future<void> test_selectionsInSnippets_selection() async {
-    var result = lsp.buildSnippetStringWithSelection('teststring', 4, 3);
-    expect(result, equals(r'test${1:str}ing'));
+  Future<void> test_tabStopsInSnippets_endsWith() async {
+    var result = lsp.buildSnippetStringWithTabStops('a, b', [3, 1]);
+    expect(result, equals(r'a, ${1:b}'));
+  }
+
+  Future<void> test_tabStopsInSnippets_escape() async {
+    var result = lsp.buildSnippetStringWithTabStops(
+        r'te$tstri}ng, te$tstri}ng, te$tstri}ng', [13, 11]);
+    expect(result, equals(r'te\$tstri\}ng, ${1:te\$tstri\}ng}, te\$tstri\}ng'));
+  }
+
+  Future<void> test_tabStopsInSnippets_multiple() async {
+    var result =
+        lsp.buildSnippetStringWithTabStops('a, b, c', [0, 1, 3, 1, 6, 1]);
+    expect(result, equals(r'${1:a}, ${2:b}, ${3:c}'));
+  }
+
+  Future<void> test_tabStopsInSnippets_startsWith() async {
+    var result = lsp.buildSnippetStringWithTabStops('a, b', [0, 1]);
+    expect(result, equals(r'${1:a}, b'));
   }
 }
diff --git a/pkg/analysis_server/test/src/services/correction/fix/replace_with_var_test.dart b/pkg/analysis_server/test/src/services/correction/fix/replace_with_var_test.dart
index d069876..422034e 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/replace_with_var_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/replace_with_var_test.dart
@@ -74,6 +74,21 @@
 ''');
   }
 
+  Future<void> test_generic_instanceCreation_cascade() async {
+    await resolveTestCode('''
+Set f() {
+  Set<String> s = Set<String>()..addAll([]);
+  return s;
+}
+''');
+    await assertHasFix('''
+Set f() {
+  var s = Set<String>()..addAll([]);
+  return s;
+}
+''');
+  }
+
   Future<void> test_generic_instanceCreation_withArguments() async {
     await resolveTestCode('''
 C<int> f() {
@@ -193,6 +208,21 @@
     await assertNoFix();
   }
 
+  Future<void> test_generic_setLiteral_cascade() async {
+    await resolveTestCode('''
+Set f() {
+  Set<String> s = {}..addAll([]);
+  return s;
+}
+''');
+    await assertHasFix('''
+Set f() {
+  var s = <String>{}..addAll([]);
+  return s;
+}
+''');
+  }
+
   Future<void> test_generic_setLiteral_const() async {
     await resolveTestCode('''
 String f() {
diff --git a/pkg/analysis_server/tool/lsp_spec/README.md b/pkg/analysis_server/tool/lsp_spec/README.md
index 051abb3..e95c70a 100644
--- a/pkg/analysis_server/tool/lsp_spec/README.md
+++ b/pkg/analysis_server/tool/lsp_spec/README.md
@@ -31,6 +31,7 @@
 
 - `dart.enableSdkFormatter`: When set to `false`, prevents registration (or unregisters) the SDK formatter. When set to `true` or not supplied, will register/reregister the SDK formatter.
 - `dart.lineLength`: The number of characters the formatter should wrap code at. If unspecified, code will be wrapped at `80` characters.
+- `dart.completeFunctionCalls`: Completes functions/methods with their required parameters.
 
 ## Method Status
 
diff --git a/pkg/dartdev/pubspec.yaml b/pkg/dartdev/pubspec.yaml
index e5b9295..aea30a3 100644
--- a/pkg/dartdev/pubspec.yaml
+++ b/pkg/dartdev/pubspec.yaml
@@ -23,7 +23,7 @@
   pedantic: ^1.9.0
   pub:
     path: ../../third_party/pkg/pub
-  stagehand: 3.3.7
+  stagehand: any
   telemetry:
     path: ../telemetry
   usage: ^3.4.0
diff --git a/pkg/nnbd_migration/lib/src/messages.dart b/pkg/nnbd_migration/lib/src/messages.dart
index f4796c1..3f9b048 100644
--- a/pkg/nnbd_migration/lib/src/messages.dart
+++ b/pkg/nnbd_migration/lib/src/messages.dart
@@ -34,7 +34,7 @@
 
 Please upgrade the packages containing these libraries to null safe versions
 before continuing.  To see what null safe package versions are available, run
-the following command: `dart pub outdated --mode=null-safety --prereleases`.
+the following command: `dart pub outdated --mode=null-safety`.
 
 To skip this check and try to migrate anyway, re-run with the flag
 `$_skipImportCheckFlag`.
diff --git a/runtime/lib/isolate.cc b/runtime/lib/isolate.cc
index 1ff4a38..94de7aa 100644
--- a/runtime/lib/isolate.cc
+++ b/runtime/lib/isolate.cc
@@ -76,6 +76,15 @@
   return Integer::New(id);
 }
 
+DEFINE_NATIVE_ENTRY(RawReceivePortImpl_setActive, 0, 2) {
+  GET_NON_NULL_NATIVE_ARGUMENT(ReceivePort, port, arguments->NativeArgAt(0));
+  GET_NON_NULL_NATIVE_ARGUMENT(Bool, active, arguments->NativeArgAt(1));
+  Dart_Port id = port.Id();
+  PortMap::SetPortState(
+      id, active.value() ? PortMap::kLivePort : PortMap::kInactivePort);
+  return Object::null();
+}
+
 DEFINE_NATIVE_ENTRY(SendPortImpl_get_id, 0, 1) {
   GET_NON_NULL_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0));
   return Integer::New(port.Id());
diff --git a/runtime/observatory/tests/service/validate_timer_port_behavior_test.dart b/runtime/observatory/tests/service/validate_timer_port_behavior_test.dart
new file mode 100644
index 0000000..ae40cb2
--- /dev/null
+++ b/runtime/observatory/tests/service/validate_timer_port_behavior_test.dart
@@ -0,0 +1,74 @@
+// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+import 'dart:async';
+import 'dart:developer';
+import 'dart:isolate' hide Isolate;
+import 'package:observatory/service_io.dart';
+import 'package:test/test.dart';
+
+import 'service_test_common.dart';
+import 'test_helper.dart';
+
+void warmup() {
+  Timer timer = Timer(const Duration(days: 30), () => null);
+  debugger();
+  timer.cancel();
+  debugger();
+  timer = Timer(const Duration(days: 30), () => null);
+  debugger();
+  timer.cancel();
+}
+
+late Set<int> originalPortIds;
+late int timerPortId;
+
+final tests = <IsolateTest>[
+  hasPausedAtStart,
+  (Isolate isolate) async {
+    final originalPorts =
+        (await isolate.invokeRpcNoUpgrade('getPorts', {}))['ports'];
+    originalPortIds = {
+      for (int i = 0; i < originalPorts.length; ++i) originalPorts[i]['portId'],
+    };
+  },
+  resumeIsolate,
+  hasStoppedAtBreakpoint,
+  (Isolate isolate) async {
+    // Determine the ID of the timer port.
+    final ports = (await isolate.invokeRpcNoUpgrade('getPorts', {}))['ports'];
+    timerPortId = ports
+        .firstWhere((p) => !originalPortIds.contains(p['portId']))['portId'];
+  },
+  resumeIsolate,
+  hasStoppedAtBreakpoint,
+  (Isolate isolate) async {
+    // After cancelling the timer, there should be no active timers left.
+    // The timer port should be inactive and not reported.
+    final ports = (await isolate.invokeRpcNoUpgrade('getPorts', {}))['ports'];
+    for (final port in ports) {
+      if (port['portId'] == timerPortId) {
+        fail('Timer port should no longer be active');
+      }
+    }
+  },
+  resumeIsolate,
+  hasStoppedAtBreakpoint,
+  (Isolate isolate) async {
+    // After setting a new timer, the timer port should be active and have the same
+    // port ID as before as the original port is still being used.
+    final ports = (await isolate.invokeRpcNoUpgrade('getPorts', {}))['ports'];
+    bool foundTimerPort = false;
+    for (final port in ports) {
+      if (port['portId'] == timerPortId) {
+        foundTimerPort = true;
+        break;
+      }
+    }
+    expect(foundTimerPort, true);
+  },
+];
+
+main(args) async => runIsolateTests(args, tests,
+    pause_on_start: true, testeeConcurrent: warmup);
diff --git a/runtime/observatory_2/tests/service_2/validate_timer_port_behavior_test.dart b/runtime/observatory_2/tests/service_2/validate_timer_port_behavior_test.dart
new file mode 100644
index 0000000..fd91428
--- /dev/null
+++ b/runtime/observatory_2/tests/service_2/validate_timer_port_behavior_test.dart
@@ -0,0 +1,74 @@
+// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+import 'dart:async';
+import 'dart:developer';
+import 'dart:isolate' hide Isolate;
+import 'package:observatory_2/service_io.dart';
+import 'package:test/test.dart';
+
+import 'service_test_common.dart';
+import 'test_helper.dart';
+
+void warmup() {
+  Timer timer = Timer(const Duration(days: 30), () => null);
+  debugger();
+  timer.cancel();
+  debugger();
+  timer = Timer(const Duration(days: 30), () => null);
+  debugger();
+  timer.cancel();
+}
+
+Set<int> originalPortIds;
+int timerPortId;
+
+final tests = <IsolateTest>[
+  hasPausedAtStart,
+  (Isolate isolate) async {
+    final originalPorts =
+        (await isolate.invokeRpcNoUpgrade('getPorts', {}))['ports'];
+    originalPortIds = {
+      for (int i = 0; i < originalPorts.length; ++i) originalPorts[i]['portId'],
+    };
+  },
+  resumeIsolate,
+  hasStoppedAtBreakpoint,
+  (Isolate isolate) async {
+    // Determine the ID of the timer port.
+    final ports = (await isolate.invokeRpcNoUpgrade('getPorts', {}))['ports'];
+    timerPortId = ports
+        .firstWhere((p) => !originalPortIds.contains(p['portId']))['portId'];
+  },
+  resumeIsolate,
+  hasStoppedAtBreakpoint,
+  (Isolate isolate) async {
+    // After cancelling the timer, there should be no active timers left.
+    // The timer port should be inactive and not reported.
+    final ports = (await isolate.invokeRpcNoUpgrade('getPorts', {}))['ports'];
+    for (final port in ports) {
+      if (port['portId'] == timerPortId) {
+        fail('Timer port should no longer be active');
+      }
+    }
+  },
+  resumeIsolate,
+  hasStoppedAtBreakpoint,
+  (Isolate isolate) async {
+    // After setting a new timer, the timer port should be active and have the same
+    // port ID as before as the original port is still being used.
+    final ports = (await isolate.invokeRpcNoUpgrade('getPorts', {}))['ports'];
+    bool foundTimerPort = false;
+    for (final port in ports) {
+      if (port['portId'] == timerPortId) {
+        foundTimerPort = true;
+        break;
+      }
+    }
+    expect(foundTimerPort, true);
+  },
+];
+
+main(args) async => runIsolateTests(args, tests,
+    pause_on_start: true, testeeConcurrent: warmup);
diff --git a/runtime/vm/bootstrap_natives.h b/runtime/vm/bootstrap_natives.h
index 8b15f7b..c8ac080 100644
--- a/runtime/vm/bootstrap_natives.h
+++ b/runtime/vm/bootstrap_natives.h
@@ -58,6 +58,7 @@
   V(RawReceivePortImpl_get_id, 1)                                              \
   V(RawReceivePortImpl_get_sendport, 1)                                        \
   V(RawReceivePortImpl_closeInternal, 1)                                       \
+  V(RawReceivePortImpl_setActive, 2)                                           \
   V(SendPortImpl_get_id, 1)                                                    \
   V(SendPortImpl_get_hashcode, 1)                                              \
   V(SendPortImpl_sendInternal_, 2)                                             \
diff --git a/runtime/vm/heap/scavenger.cc b/runtime/vm/heap/scavenger.cc
index 0de6a9f..897bf85 100644
--- a/runtime/vm/heap/scavenger.cc
+++ b/runtime/vm/heap/scavenger.cc
@@ -762,6 +762,7 @@
 Scavenger::~Scavenger() {
   ASSERT(!scavenging_);
   delete to_;
+  ASSERT(blocks_ == nullptr);
 }
 
 intptr_t Scavenger::NewSizeInWords(intptr_t old_size_in_words) const {
@@ -1024,9 +1025,8 @@
   // Grab the deduplication sets out of the isolate's consolidated store buffer.
   StoreBuffer* store_buffer = heap_->isolate_group()->store_buffer();
   StoreBufferBlock* pending = blocks_;
-  blocks_ = nullptr;
   intptr_t total_count = 0;
-  while (pending != NULL) {
+  while (pending != nullptr) {
     StoreBufferBlock* next = pending->next();
     // Generated code appends to store buffers; tell MemorySanitizer.
     MSAN_UNPOISON(pending, sizeof(*pending));
@@ -1045,10 +1045,10 @@
     pending->Reset();
     // Return the emptied block for recycling (no need to check threshold).
     store_buffer->PushBlock(pending, StoreBuffer::kIgnoreThreshold);
-    pending = next;
+    blocks_ = pending = next;
   }
   // Done iterating through old objects remembered in the store buffers.
-  visitor->VisitingOldObject(NULL);
+  visitor->VisitingOldObject(nullptr);
 
   heap_->RecordData(kStoreBufferEntries, total_count);
   heap_->RecordData(kDataUnused1, 0);
@@ -1645,9 +1645,23 @@
   to_ = *from;
   *from = temp;
 
+  // Release any remaining part of the promotion worklist that wasn't completed.
   promotion_stack_.Reset();
 
-  // This also rebuilds the remembered set.
+  // Release any remaining part of the rememebred set that wasn't completed.
+  StoreBuffer* store_buffer = heap_->isolate_group()->store_buffer();
+  StoreBufferBlock* pending = blocks_;
+  while (pending != nullptr) {
+    StoreBufferBlock* next = pending->next();
+    pending->Reset();
+    // Return the emptied block for recycling (no need to check threshold).
+    store_buffer->PushBlock(pending, StoreBuffer::kIgnoreThreshold);
+    pending = next;
+  }
+  blocks_ = nullptr;
+
+  // Reverse the partial forwarding from the aborted scavenge. This also
+  // rebuilds the remembered set.
   Become::FollowForwardingPointers(thread);
 
   // Don't scavenge again until the next old-space GC has occurred. Prevents
diff --git a/runtime/vm/heap/scavenger.h b/runtime/vm/heap/scavenger.h
index 9f84dcd..5e7486b 100644
--- a/runtime/vm/heap/scavenger.h
+++ b/runtime/vm/heap/scavenger.h
@@ -429,7 +429,7 @@
   bool scavenging_;
   bool early_tenure_ = false;
   RelaxedAtomic<intptr_t> root_slices_started_;
-  StoreBufferBlock* blocks_;
+  StoreBufferBlock* blocks_ = nullptr;
 
   int64_t gc_time_micros_;
   intptr_t collections_;
diff --git a/runtime/vm/isolate_reload.cc b/runtime/vm/isolate_reload.cc
index f9e26a0..4df27eb 100644
--- a/runtime/vm/isolate_reload.cc
+++ b/runtime/vm/isolate_reload.cc
@@ -1927,7 +1927,7 @@
     visitor->VisitPointers(class_table, saved_num_cids_);
   }
   ClassPtr* saved_tlc_class_table =
-      saved_class_table_.load(std::memory_order_relaxed);
+      saved_tlc_class_table_.load(std::memory_order_relaxed);
   if (saved_tlc_class_table != NULL) {
     auto class_table =
         reinterpret_cast<ObjectPtr*>(&(saved_tlc_class_table[0]));
diff --git a/runtime/vm/port.cc b/runtime/vm/port.cc
index 9d5c331..c7312de 100644
--- a/runtime/vm/port.cc
+++ b/runtime/vm/port.cc
@@ -29,6 +29,8 @@
       return "live";
     case kControlPort:
       return "control";
+    case kInactivePort:
+      return "inactive";
     default:
       UNREACHABLE();
       return "UNKNOWN";
@@ -72,10 +74,11 @@
 
   Entry& entry = *it;
   PortState old_state = entry.state;
-  ASSERT(old_state == kNewPort);
   entry.state = state;
   if (state == kLivePort) {
     entry.handler->increment_live_ports();
+  } else if (state == kInactivePort && old_state == kLivePort) {
+    entry.handler->decrement_live_ports();
   }
   if (FLAG_trace_isolates) {
     OS::PrintErr(
@@ -211,6 +214,18 @@
   return handler->IsCurrentIsolate();
 }
 
+bool PortMap::IsLivePort(Dart_Port id) {
+  MutexLocker ml(mutex_);
+  auto it = ports_->TryLookup(id);
+  if (it == ports_->end()) {
+    // Port does not exist.
+    return false;
+  }
+
+  PortState state = (*it).state;
+  return (state == kLivePort || state == kControlPort);
+}
+
 Isolate* PortMap::GetIsolate(Dart_Port id) {
   MutexLocker ml(mutex_);
   auto it = ports_->TryLookup(id);
diff --git a/runtime/vm/port.h b/runtime/vm/port.h
index 0297f1d..1405362 100644
--- a/runtime/vm/port.h
+++ b/runtime/vm/port.h
@@ -28,6 +28,8 @@
     kNewPort = 0,      // a newly allocated port
     kLivePort = 1,     // a regular port (has a ReceivePort)
     kControlPort = 2,  // a special control port (has a ReceivePort)
+    kInactivePort =
+        3,  // an inactive port (has a ReceivePort) not considered live.
   };
 
   // Allocate a port for the provided handler and return its VM-global id.
@@ -55,6 +57,9 @@
   // Returns whether a port is local to the current isolate.
   static bool IsLocalPort(Dart_Port id);
 
+  // Returns whether a port is live (e.g., is not new or inactive).
+  static bool IsLivePort(Dart_Port id);
+
   // Returns the owning Isolate for port 'id'.
   static Isolate* GetIsolate(Dart_Port id);
 
@@ -84,9 +89,6 @@
   // Allocate a new unique port.
   static Dart_Port AllocatePort();
 
-  static bool IsActivePort(Dart_Port id);
-  static bool IsLivePort(Dart_Port id);
-
   // Lock protecting access to the port map.
   static Mutex* mutex_;
 
diff --git a/runtime/vm/port_test.cc b/runtime/vm/port_test.cc
index 2fb63d7..afcbb7b 100644
--- a/runtime/vm/port_test.cc
+++ b/runtime/vm/port_test.cc
@@ -106,6 +106,11 @@
   EXPECT(PortMapTestPeer::IsActivePort(port));
   EXPECT(PortMapTestPeer::IsLivePort(port));
 
+  // Inactive port.
+  PortMap::SetPortState(port, PortMap::kInactivePort);
+  EXPECT(PortMapTestPeer::IsActivePort(port));
+  EXPECT(!PortMapTestPeer::IsLivePort(port));
+
   PortMap::ClosePort(port);
   EXPECT(!PortMapTestPeer::IsActivePort(port));
   EXPECT(!PortMapTestPeer::IsLivePort(port));
diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc
index b207114..fee9fbb 100644
--- a/runtime/vm/service.cc
+++ b/runtime/vm/service.cc
@@ -3178,11 +3178,14 @@
   JSONObject jsobj(js);
   jsobj.AddProperty("type", "PortList");
   {
-    Instance& port = Instance::Handle(zone.GetZone());
+    ReceivePort& port = ReceivePort::Handle(zone.GetZone());
     JSONArray arr(&jsobj, "ports");
     for (int i = 0; i < ports.Length(); ++i) {
       port ^= ports.At(i);
-      arr.AddValue(port);
+      // Don't report inactive ports.
+      if (PortMap::IsLivePort(port.Id())) {
+        arr.AddValue(port);
+      }
     }
   }
   return true;
diff --git a/sdk/lib/_internal/vm/lib/isolate_patch.dart b/sdk/lib/_internal/vm/lib/isolate_patch.dart
index 97e90ce..187efdb 100644
--- a/sdk/lib/_internal/vm/lib/isolate_patch.dart
+++ b/sdk/lib/_internal/vm/lib/isolate_patch.dart
@@ -188,6 +188,12 @@
   // Call into the VM to close the VM maintained mappings.
   _closeInternal() native "RawReceivePortImpl_closeInternal";
 
+  // Set this port as active or inactive in the VM. If inactive, this port
+  // will not be considered live even if it hasn't been explicitly closed.
+  // TODO(bkonyi): determine if we want to expose this as an option through
+  // RawReceivePort.
+  _setActive(bool active) native "RawReceivePortImpl_setActive";
+
   void set handler(Function? value) {
     final id = this._get_id();
     if (!_portMap.containsKey(id)) {
diff --git a/sdk/lib/_internal/vm/lib/timer_impl.dart b/sdk/lib/_internal/vm/lib/timer_impl.dart
index db626c2..a348d19 100644
--- a/sdk/lib/_internal/vm/lib/timer_impl.dart
+++ b/sdk/lib/_internal/vm/lib/timer_impl.dart
@@ -138,6 +138,7 @@
 
   static RawReceivePort? _receivePort;
   static SendPort? _sendPort;
+  static bool _receivePortActive = false;
   static int _scheduledWakeupTime = 0;
 
   static bool _handlingCallbacks = false;
@@ -262,12 +263,10 @@
   // Enqueue one message for each zero timer. To be able to distinguish from
   // EventHandler messages we send a _ZERO_EVENT instead of a _TIMEOUT_EVENT.
   static void _notifyZeroHandler() {
-    var port = _sendPort;
-    if (port == null) {
-      port = _createTimerHandler();
-      _sendPort = port;
+    if (!_receivePortActive) {
+      _createTimerHandler();
     }
-    port.send(_ZERO_EVENT);
+    _sendPort!.send(_ZERO_EVENT);
   }
 
   // Handle the notification of a zero timer. Make sure to also execute non-zero
@@ -314,7 +313,6 @@
       _cancelWakeup();
       return;
     }
-
     // Only send a message if the requested wakeup time differs from the
     // already scheduled wakeup time.
     var wakeupTime = _heap.first._wakeupTime;
@@ -433,12 +431,10 @@
 
   // Tell the event handler to wake this isolate at a specific time.
   static void _scheduleWakeup(int wakeupTime) {
-    var port = _sendPort;
-    if (port == null) {
-      port = _createTimerHandler();
-      _sendPort = port;
+    if (!_receivePortActive) {
+      _createTimerHandler();
     }
-    VMLibraryHooks.eventHandlerSendData(null, port, wakeupTime);
+    VMLibraryHooks.eventHandlerSendData(null, _sendPort, wakeupTime);
     _scheduledWakeupTime = wakeupTime;
   }
 
@@ -452,22 +448,23 @@
 
   // Create a receive port and register a message handler for the timer
   // events.
-  static SendPort _createTimerHandler() {
-    assert(_receivePort == null);
-    assert(_sendPort == null);
-    final port = new RawReceivePort(_handleMessage);
-    final sendPort = port.sendPort;
-    _receivePort = port;
-    _sendPort = sendPort;
-    _scheduledWakeupTime = 0;
-    return sendPort;
+  static void _createTimerHandler() {
+    if (_receivePort == null) {
+      assert(_receivePort == null);
+      assert(_sendPort == null);
+      _receivePort = RawReceivePort(_handleMessage);
+      _sendPort = _receivePort!.sendPort;
+      _scheduledWakeupTime = 0;
+    } else {
+      (_receivePort as _RawReceivePortImpl)._setActive(true);
+    }
+    _receivePortActive = true;
   }
 
   static void _shutdownTimerHandler() {
-    _sendPort = null;
     _scheduledWakeupTime = 0;
-    _receivePort!.close();
-    _receivePort = null;
+    (_receivePort as _RawReceivePortImpl)._setActive(false);
+    _receivePortActive = false;
   }
 
   // The Timer factory registered with the dart:async library by the embedder.
diff --git a/sdk/lib/html/doc/WORKAROUNDS.md b/sdk/lib/html/doc/WORKAROUNDS.md
new file mode 100644
index 0000000..d3d8456
--- /dev/null
+++ b/sdk/lib/html/doc/WORKAROUNDS.md
@@ -0,0 +1,128 @@
+Dart web platform libraries e.g. `dart:html` is partially hand-written and
+partially generated, with the code generation using the Chrome IDL as the source
+of truth for many browser interfaces. This introduces a dependency on the
+version of the IDL and doesn’t always match up with other browser interfaces.
+
+Currently, we do not intend on updating our scripts to use a newer version of
+the IDL, so APIs and classes in these libraries may be inaccurate.
+
+In order to work around this, we ask users to leverage JS interop. Longer term,
+we intend to revamp our web library offerings to be more robust and reliable.
+
+The following are workarounds to common issues you might see with using the web
+platform libraries.
+
+## Common Issues
+
+### Missing/broken APIs
+
+As mentioned above, there exists stale interfaces. While some of these may be
+fixed in the source code, many might not.
+
+In order to circumvent this, you can use the `js_util` library, like
+`getProperty`, `setProperty`, `callMethod`, and `callConstructor`.
+
+Let’s look at an example. `FileReader` is a `dart:html` interface that is
+missing the API `readAsBinaryString` ([#42834][]). We can work around this by
+doing something like the following:
+
+```
+import 'dart:html';
+import 'dart:js_util' as js_util;
+
+import 'package:async_helper/async_minitest.dart';
+import 'package:expect/expect.dart';
+
+void main() async {
+  var reader = new FileReader();
+  reader.onLoad.listen(expectAsync((event) {
+    String result = reader.result as String;
+    Expect.equals(result, '00000000');
+  }));
+  js_util.callMethod(reader, 'readAsBinaryString', [new Blob(['00000000'])]);
+  // We can manipulate properties as well.
+  js_util.setProperty(reader, 'foo', 'bar'); // reader.foo is now ‘bar’
+  Expect.equals(js_util.getProperty(reader, 'foo'), 'bar');
+}
+```
+
+In the case where the API is missing a constructor, we can use
+`callConstructor`. For example, instead of using the factory constructor for
+`KeyboardEvent`, we can do the following:
+
+```
+import 'dart:html';
+import 'dart:js_util' as js_util;
+
+import 'package:expect/expect.dart';
+
+void main() {
+  List<dynamic> eventArgs = <dynamic>[
+    'KeyboardEvent',
+    <String, dynamic>{'key': 'A'}
+  ];
+  KeyboardEvent event = js_util.callConstructor(
+      js_util.getProperty(window, 'KeyboardEvent'), js_util.jsify(eventArgs));
+  Expect.equals(event.key, 'A');
+}
+```
+
+### Private/unimplemented native types
+
+There are several native interfaces that are suppressed e.g.
+`USBDevice` ([#42200][]) due to historical reasons. These native interfaces are
+marked with `@Native`, are private, and have no attributes associated with them.
+Therefore, unlike other `@Native` objects, we can’t access any of the APIs or
+attributes associated with this interface. We can use the `js_util` library
+again to circumvent this issue. For example, we can manipulate a
+`_SubtleCrypto` object:
+
+```
+import 'dart:html';
+import 'dart:js_util' as js_util;
+import 'dart:typed_data';
+
+import 'package:js/js.dart';
+
+@JS()
+external Crypto get crypto;
+
+void main() async {
+  var subtle = crypto.subtle!;
+  var array = Uint8List(16);
+  var promise = js_util.promiseToFuture<ByteBuffer>(js_util
+      .callMethod(subtle, 'digest', ['SHA-256', array])); // SubtleCrypto.digest
+  var digest = await promise;
+}
+```
+
+What you shouldn’t do is attempt to cast these native objects using your own JS
+interop types, e.g.
+
+```
+import 'dart:html';
+
+import 'package:js/js.dart';
+
+@JS()
+external Crypto get crypto;
+
+@JS()
+class SubtleCrypto {}
+
+void main() {
+  SubtleCrypto subtle = crypto.subtle! as SubtleCrypto;
+}
+```
+
+With the above, you’ll see a type error:
+
+`Uncaught TypeError: Instance of 'SubtleCrypto': type 'Interceptor' is not a subtype of type 'SubtleCrypto'`
+
+This is because the types in the `@Native` annotation are reserved and the above
+leads to namespace conflicts between the `@Native` type and the user JS interop
+type in the compiler. These `@Native` types inherit the `Interceptor` class,
+which is why you see the message above.
+
+[#42834]: https://github.com/dart-lang/sdk/issues/42834
+[#42200]: https://github.com/dart-lang/sdk/issues/42200
diff --git a/tools/VERSION b/tools/VERSION
index 31011dd..648028b 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 12
 PATCH 0
-PRERELEASE 32
+PRERELEASE 33
 PRERELEASE_PATCH 0
\ No newline at end of file