[dart2wasm,standalone] Migrate everything except String

This migrates remaining dart2wasm patches to avoid js-interop outside
of passing strings.

Change-Id: I92a9bb6cb97305a51858901d27966c3bd0af8fc6
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/491540
Reviewed-by: Martin Kustermann <kustermann@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
diff --git a/pkg/dart2wasm/test/ir_tests/standalone.wat b/pkg/dart2wasm/test/ir_tests/standalone.wat
index 2a64c90..534736e 100644
--- a/pkg/dart2wasm/test/ir_tests/standalone.wat
+++ b/pkg/dart2wasm/test/ir_tests/standalone.wat
@@ -1,9 +1,9 @@
 (module $module0
-  (type $JSExternWrapper (sub $Object (struct
+  (type $#Top (struct
+    (field $field0 i32)))
+  (type $JSExternWrapper (sub $#Top (struct
     (field $field0 i32)
     (field $_externRef externref))))
-  (type $Object (struct
-    (field $field0 i32)))
   (global $"\"Hello world\"" (mut (ref null $JSExternWrapper))
     (ref.null none))
 )
\ No newline at end of file
diff --git a/pkg/test_runner/lib/src/browser.dart b/pkg/test_runner/lib/src/browser.dart
index 6854ca8..7f38a98 100644
--- a/pkg/test_runner/lib/src/browser.dart
+++ b/pkg/test_runner/lib/src/browser.dart
@@ -339,6 +339,7 @@
 (module
   (type $i8array (array (mut i8)))
   (type $i16array (array (mut i16)))
+  (type $externarray (array (mut externref)))
   (import "wasm:js-string" "fromCharCodeArray"
     (func $fromCharCodeArray (param (ref null $i16array) i32 i32) (result (ref extern)))
   )
@@ -398,10 +399,15 @@
       )
     )
   )
+
+  (func (export "emptyExternRefArray")
+    (result (ref $externarray))
+    (array.new_default $externarray (i32.const 0))
+  )
 )
  */
 const _wasmStandaloneArrayHelper =
-    'AGFzbQEAAAABIgVedwFeeAFgA2MAf38BZG9gA2QBf38BZG9gA2QAf38BZG8CJAEOd2FzbTpqcy1zdHJpbmcRZnJvbUNoYXJDb2RlQXJyYXkAAgMDAgMEBzICFHN0cmluZ0Zyb21Bc2NpaUJ5dGVzAAEXc3RyaW5nRnJvbUNoYXJDb2RlQXJyYXkAAgpTAkMCAX8BZAAgAiEDQQAgAvsGACEEAkADQCADQX9qIQMgAyABSA0BIAQgAyAAIAP7DQH7DgAMAAsACyAEQQAgBPsPEAALDQAgACABIAEgAmoQAAs=';
+    'AGFzbQEAAAABKgdedwFeeAFebwFgA2MAf38BZG9gA2QBf38BZG9gA2QAf38BZG9gAAFkAgIkAQ53YXNtOmpzLXN0cmluZxFmcm9tQ2hhckNvZGVBcnJheQADAwQDBAUGB0gDFHN0cmluZ0Zyb21Bc2NpaUJ5dGVzAAEXc3RyaW5nRnJvbUNoYXJDb2RlQXJyYXkAAhNlbXB0eUV4dGVyblJlZkFycmF5AAMKWwNDAgF/AWQAIAIhA0EAIAL7BgAhBAJAA0AgA0F/aiEDIAMgAUgNASAEIAMgACAD+w0B+w4ADAALAAsgBEEAIAT7DxAACw0AIAAgASABIAJqEAALBwBBAPsHAgs=';
 
 String dart2wasmHtml(
   String title,
@@ -487,6 +493,7 @@
         }
       },
       tryParseResultGetDouble: ({result}) => result,
+      doubleParseInfallible: (str) => +str,
       i64ToString: (source, radix) => source.toString(radix),
       f64ToExponential: (source) => source.toExponential(),
       f64ToExponentialWithFractionDigits: (source, digits) => {
@@ -583,9 +590,47 @@
         // local time and UTC.
         return -date.getTimezoneOffset() * 60;
       },
+      mathPow: Math.pow,
+      mathAtan2: Math.atan2,
+      mathSin: Math.sin,
+      mathCos: Math.cos,
+      mathTan: Math.tan,
+      mathAcos: Math.acos,
+      mathAsin: Math.asin,
+      mathAtan: Math.atan,
+      mathExp: Math.exp,
+      mathLog: Math.log,
+      randomInt: () => {
+        const low = (Math.random() * 4294967295.0) | 0;
+        const high = (Math.random() * 4294967295.0) | 0;
+
+        return (BigInt(high) << 32n) | BigInt(low);
+      },
+      randomIntSecure: () => {
+        const typedArray = new BigUint64Array(1);
+        crypto.getRandomValues(typedArray);
+        return typedArray[0];
+      },
+      print: (line) => {
+        if (typeof dartPrint == "function") {
+          dartPrint(line);
+        } else {
+          console.log(line);
+        }
+      },
+      jsonEncodeString: JSON.stringify,
+      debugger: () => {
+        debugger;
+      },
+      inspect: (ref) => {},
+      timelineStreamEnabled: () => false,
+      reportTaskEvent: (taskId, flowId, type, name, jsonArgs) => {},
     };
 """;
   final additionalImports = standalone ? '{ dart: dartEmbedder }' : '{}';
+  final mainInvocation = standalone
+      ? r'appInstance.instantiatedModule.exports.$invokeMain(helperInstance.exports.emptyExternRefArray())'
+      : 'appInstance.invokeMain();';
 
   return """
 <!DOCTYPE html>
@@ -635,7 +680,7 @@
         Promise.all(modules.map((m) => fetch(m).then((b) => handleWasmBytes(m, b)))),
     });
     dartMainRunner(() => {
-      appInstance.invokeMain();
+      $mainInvocation
     });
   }
 
diff --git a/sdk/lib/_internal/wasm/lib/convert_js_patch.dart b/sdk/lib/_internal/wasm/lib/convert_js_patch.dart
new file mode 100644
index 0000000..4366fac
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/convert_js_patch.dart
@@ -0,0 +1,55 @@
+// Copyright (c) 2026, 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:_error_utils";
+import "dart:_internal" show patch, unsafeCast;
+import "dart:_js_string_convert";
+import "dart:_js_types";
+import "dart:_js_helper" show JS, jsStringFromDartString, JSExternWrapperExt;
+import "dart:_object_helper";
+import "dart:_string";
+import "dart:_typed_data";
+import "dart:_wasm";
+
+@patch
+class _Utf8Decoder {
+  @patch
+  String? _convertSingleFastPath(List<int> codeUnits, int start, int end) {
+    if (codeUnits is JSUint8ArrayImpl) {
+      return decodeUtf8JS(codeUnits, start, end, allowMalformed);
+    }
+  }
+}
+
+@patch
+class _StringParser {
+  @patch
+  static WasmArray<WasmI16> stringToCharCodeArray(String string, int end) {
+    final externRef = jsStringFromDartString(string).wrappedExternRef;
+    final array = WasmArray<WasmI16>(end);
+    if (string.length == end) {
+      jsStringIntoCharCodeArray(externRef, array, 0.toWasmI32());
+    } else {
+      for (int i = 0; i < end; ++i) array.write(i, jsCharCodeAt(externRef, i));
+    }
+
+    return array;
+  }
+}
+
+// Assumes the given [string] is a valid float, so it can rely on the implicit
+// string to number conversion in JS using `+<string-of-number>`.
+@patch
+double _parseValidFloat(String string) =>
+    JS<double>('(s) => +s', jsStringFromDartString(string).wrappedExternRef);
+
+@patch
+String _stringFromCharCodeArray(WasmArray<WasmI16> array, int start, int end) {
+  return JSStringImpl.fromCharCodeArray(array, start, end);
+}
+
+@patch
+String _stringFromAsciiBytes(WasmArray<WasmI8> source, int start, int end) {
+  return JSStringImpl.fromAsciiBytes(source, start, end);
+}
diff --git a/sdk/lib/_internal/wasm/lib/convert_patch.dart b/sdk/lib/_internal/wasm/lib/convert_patch.dart
index 475e738..07fb129 100644
--- a/sdk/lib/_internal/wasm/lib/convert_patch.dart
+++ b/sdk/lib/_internal/wasm/lib/convert_patch.dart
@@ -7,14 +7,10 @@
 import "dart:_error_utils";
 import "dart:_internal"
     show patch, POWERS_OF_TEN, unsafeCast, pushWasmArray, popWasmArray;
-import "dart:_js_string_convert";
-import "dart:_js_types";
-import "dart:_js_helper" show JS, jsStringFromDartString, JSExternWrapperExt;
 import "dart:_list"
     show GrowableList, WasmListBaseUnsafeExtensions, WasmListBase;
 import "dart:_string";
 import "dart:_string_helper";
-import "dart:_object_helper";
 import "dart:_typed_data";
 import "dart:_object_helper";
 import "dart:_wasm";
@@ -26,7 +22,7 @@
   Object? Function(Object? key, Object? value)? reviver,
 ) {
   final listener = _JsonListener(reviver);
-  final parser = _JSStringImplParser(listener);
+  final parser = _StringParser(listener);
   parser.setNewChunk(unsafeCast<JSStringImpl>(source), source.length);
   parser.parse(0);
   parser.close();
@@ -291,13 +287,15 @@
     this.array = newArray;
   }
 
-  String getString() => JSStringImpl.fromAsciiBytes(array, 0, length);
+  String getString() {
+    return _stringFromAsciiBytes(array, 0, length);
+  }
 
   // TODO(lrn): See if parsing of numbers can be abstracted to something
   // not only working on strings, but also on char-code lists, without losing
   // performance.
   num parseNum() => num.parse(getString());
-  double parseDouble() => _jsParseValidFloat(getString());
+  double parseDouble() => _parseValidFloat(getString());
 }
 
 /**
@@ -696,9 +694,7 @@
    * built exactly during parsing.
    */
   double parseDouble(int start, int end) {
-    return _jsParseValidFloat(
-      getString(start, end, 0x7f, _emptyCodeUnitsCache),
-    );
+    return _parseValidFloat(getString(start, end, 0x7f, _emptyCodeUnitsCache));
   }
 
   /**
@@ -1659,34 +1655,32 @@
 }
 
 /**
- * Chunked JSON parser that parses [JSStringImpl] chunks.
+ * Chunked JSON parser that parses [String] chunks.
  */
-class _JSStringImplParser extends _ChunkedJsonParserState
-    with _ChunkedJsonParser<JSStringImpl> {
+class _StringParser extends _ChunkedJsonParserState
+    with _ChunkedJsonParser<String> {
   @pragma('wasm:initialize-at-startup')
   static WasmArray<WasmI16> _emptyChunk = WasmArray<WasmI16>(0);
 
-  JSStringImpl _chunkAsString = unsafeCast<JSStringImpl>('');
+  external static WasmArray<WasmI16> stringToCharCodeArray(
+    String string,
+    int end,
+  );
+
+  String _chunkAsString = '';
   WasmArray<WasmI16> _chunkAsArray = _emptyChunk;
   int chunkEnd = 0;
 
-  _JSStringImplParser(_JsonListener listener) : super(listener);
+  _StringParser(_JsonListener listener) : super(listener);
 
-  void setNewChunk(JSStringImpl string, int end) {
-    final externRef = string.wrappedExternRef;
-    final array = WasmArray<WasmI16>(end);
-    if (string.length == end) {
-      jsStringIntoCharCodeArray(externRef, array, 0.toWasmI32());
-    } else {
-      for (int i = 0; i < end; ++i) array.write(i, jsCharCodeAt(externRef, i));
-    }
+  void setNewChunk(String string, int end) {
     _chunkAsString = string;
-    _chunkAsArray = array;
+    _chunkAsArray = stringToCharCodeArray(string, end);
     chunkEnd = end;
   }
 
   @override
-  JSStringImpl get chunk => _chunkAsString;
+  String get chunk => _chunkAsString;
 
   @pragma('wasm:prefer-inline')
   bool get isUtf16Input => true;
@@ -1708,13 +1702,7 @@
   }
 
   String getJsonObjectKeyString(int start, int end, int bits, int stringHash) {
-    return _internJSString(
-      _chunkAsString,
-      _chunkAsArray,
-      stringHash,
-      start,
-      end,
-    );
+    return _internString(_chunkAsString, _chunkAsArray, stringHash, start, end);
   }
 
   void beginString() {
@@ -1757,14 +1745,12 @@
   }
 
   double parseDouble(int start, int end) {
-    return _jsParseValidFloat(
-      getString(start, end, 0x7f, _emptyCodeUnitsCache),
-    );
+    return _parseValidFloat(getString(start, end, 0x7f, _emptyCodeUnitsCache));
   }
 
   void close() {
     super.close();
-    _jsStringInternCache.fill(0, null, _jsStringInternCacheSize);
+    _stringInternCache.fill(0, null, _stringInternCacheSize);
   }
 }
 
@@ -1776,21 +1762,21 @@
 @pragma("wasm:initialize-at-startup")
 final _codeUnitsCache = WasmArray<WasmI16>(_codeUnitsCacheSize);
 
-const int _jsStringInternCacheSize = 512;
+const int _stringInternCacheSize = 512;
 @pragma("wasm:initialize-at-startup")
-final WasmArray<JSStringImpl?> _jsStringInternCache = WasmArray<JSStringImpl?>(
-  _jsStringInternCacheSize,
+final WasmArray<String?> _stringInternCache = WasmArray<String?>(
+  _stringInternCacheSize,
 );
-JSStringImpl _internJSString(
-  JSStringImpl source,
+String _internString(
+  String source,
   WasmArray<WasmI16> sourceAsArray,
   int stringHash,
   int start,
   int end,
 ) {
   final length = end - start;
-  final int index = stringHash & (_jsStringInternCacheSize - 1);
-  final existing = _jsStringInternCache[index];
+  final int index = stringHash & (_stringInternCacheSize - 1);
+  final existing = _stringInternCache[index];
 
   insert:
   {
@@ -1810,23 +1796,21 @@
     }
   }
 
-  final result = unsafeCast<JSStringImpl>(
-    source.substringUnchecked(start, end),
-  );
+  final result = source.substringUnchecked(start, end);
   setIdentityHashField(result, stringHash);
-  _jsStringInternCache[index] = result;
+  _stringInternCache[index] = result;
   return result;
 }
 
-JSStringImpl _internJSStringFromAsciiSlice(
+String _internJSStringFromAsciiSlice(
   U8List source,
   int stringHash,
   int start,
   int end,
 ) {
   final length = end - start;
-  final int index = stringHash & (_jsStringInternCacheSize - 1);
-  final existing = _jsStringInternCache[index];
+  final int index = stringHash & (_stringInternCacheSize - 1);
+  final existing = _stringInternCache[index];
 
   insert:
   {
@@ -1845,14 +1829,30 @@
     }
   }
 
-  final result = JSStringImpl.fromAsciiBytes(
+  final result = _stringFromAsciiList(source, start, end);
+  setIdentityHashField(result, stringHash);
+  _stringInternCache[index] = result;
+  return result;
+}
+
+external String _stringFromCharCodeArray(
+  WasmArray<WasmI16> array,
+  int start,
+  int end,
+);
+
+external String _stringFromAsciiBytes(
+  WasmArray<WasmI8> source,
+  int start,
+  int end,
+);
+
+String _stringFromAsciiList(U8List source, int start, int end) {
+  return _stringFromAsciiBytes(
     source.data,
     source.offsetInElements + start,
     source.offsetInElements + end,
   );
-  setIdentityHashField(result, stringHash);
-  _jsStringInternCache[index] = result;
-  return result;
 }
 
 @patch
@@ -1871,7 +1871,7 @@
 class _JsonStringDecoderSink extends StringConversionSinkBase {
   final _JsonListener _listener;
 
-  late final _JSStringImplParser _stringParser;
+  late final _StringParser _stringParser;
 
   final Object? Function(Object? key, Object? value)? _reviver;
 
@@ -1879,12 +1879,12 @@
 
   _JsonStringDecoderSink(this._reviver, this._sink)
     : _listener = _JsonListener(_reviver) {
-    _stringParser = _JSStringImplParser(_listener);
+    _stringParser = _StringParser(_listener);
   }
 
   void addSlice(String chunk, int start, int end, bool isLast) {
     final parser = _stringParser;
-    parser.setNewChunk(unsafeCast<JSStringImpl>(chunk), end);
+    parser.setNewChunk(chunk, end);
     parser.parse(start);
     if (isLast) parser.close();
   }
@@ -1989,7 +1989,7 @@
     addStringSliceToString(decoder.convertChunked(chunk, start, end));
   }
 
-  JSStringImpl _stringFromBytesOrCache(
+  String _stringFromBytesOrCache(
     int start,
     int end,
     WasmArray<WasmI16> codeUnitsCache,
@@ -2004,13 +2004,9 @@
           chunk.offsetInElements + end,
         ),
       );
-      return JSStringImpl.fromCharCodeArray(codeUnitsCache, 0, length);
+      return _stringFromCharCodeArray(codeUnitsCache, 0, length);
     }
-    return JSStringImpl.fromAsciiBytes(
-      chunk.data,
-      chunk.offsetInElements + start,
-      chunk.offsetInElements + end,
-    );
+    return _stringFromAsciiList(chunk, start, end);
   }
 
   void addStringSliceToString(String string) {
@@ -2040,12 +2036,8 @@
   }
 
   double parseDouble(int start, int end) {
-    final result = JSStringImpl.fromAsciiBytes(
-      chunk.data,
-      chunk.offsetInElements + start,
-      chunk.offsetInElements + end,
-    );
-    return _jsParseValidFloat(result);
+    final string = _stringFromAsciiList(chunk, start, end);
+    return _parseValidFloat(string);
   }
 }
 
@@ -2179,6 +2171,12 @@
     82, 82, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98,
   ]);
 
+  external String? _convertSingleFastPath(
+    List<int> codeUnits,
+    int start,
+    int end,
+  );
+
   /// Reset the decoder to a state where it is ready to decode a new string but
   /// will not skip a leading BOM. Used by the fused UTF-8 / JSON decoder.
   void reset() {
@@ -2214,14 +2212,9 @@
     );
     if (start == end) return "";
 
-    if (codeUnits is JSUint8ArrayImpl) {
-      JSStringImpl? decoded = decodeUtf8JS(
-        codeUnits,
-        start,
-        end,
-        allowMalformed,
-      );
-      if (decoded != null) return decoded;
+    if (_convertSingleFastPath(codeUnits, start, end) case final converted?) {
+      // Platform-specific fast path
+      return converted;
     }
 
     final WasmArray<WasmI8> bytes;
@@ -2581,7 +2574,7 @@
         return true;
       })(),
     );
-    return JSStringImpl.fromCharCodeArray(_characterArray, 0, size);
+    return _stringFromCharCodeArray(_characterArray, 0, size);
   }
 
   String decode8(WasmArray<WasmI8> bytes, int start, int end, int size) {
@@ -2628,7 +2621,7 @@
       _characterArray.write(j++, byte & 0xFF);
     }
     assert(j == size);
-    return JSStringImpl.fromCharCodeArray(_characterArray, 0, size);
+    return _stringFromCharCodeArray(_characterArray, 0, size);
   }
 
   // This table is the Wasm array version of `_Utf8Decoder.transitionTable`,
@@ -2727,7 +2720,7 @@
     _charOrIndex = char;
 
     assert(j == size);
-    return JSStringImpl.fromCharCodeArray(_characterArray, 0, size);
+    return _stringFromCharCodeArray(_characterArray, 0, size);
   }
 
   String _decodeGeneral(
@@ -2891,10 +2884,7 @@
   return bytes;
 }
 
-// Assumes the given [string] is a valid float, so it can rely on the implicit
-// string to number conversion in JS using `+<string-of-number>`.
-double _jsParseValidFloat(String string) =>
-    JS<double>('(s) => +s', jsStringFromDartString(string).wrappedExternRef);
+external double _parseValidFloat(String string);
 
 const ImmutableWasmArray<BoxedInt> _intBoxes256 = ImmutableWasmArray.literal([
   0, 1, 2, 3, 4, 5, 6, 7, //
diff --git a/sdk/lib/_internal/wasm_standalone/lib/convert_standalone_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/convert_standalone_patch.dart
new file mode 100644
index 0000000..26aca3f
--- /dev/null
+++ b/sdk/lib/_internal/wasm_standalone/lib/convert_standalone_patch.dart
@@ -0,0 +1,51 @@
+// Copyright (c) 2026, 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:_embedder" as embedder;
+import "dart:_internal" show patch, unsafeCast;
+import "dart:_js_helper" show jsStringFromDartString, JSExternWrapperExt;
+import "dart:_string";
+import "dart:_wasm";
+
+@patch
+class _Utf8Decoder {
+  @patch
+  String? _convertSingleFastPath(List<int> codeUnits, int start, int end) {
+    // Always decode utf-8 in Dart.
+    return null;
+  }
+}
+
+@patch
+class _StringParser {
+  @patch
+  static WasmArray<WasmI16> stringToCharCodeArray(String string, int end) {
+    final externRef = unsafeCast<JSStringImpl>(string).wrappedExternRef;
+    final array = WasmArray<WasmI16>(end);
+    if (string.length == end) {
+      jsStringIntoCharCodeArray(externRef, array, 0.toWasmI32());
+    } else {
+      for (int i = 0; i < end; ++i) array.write(i, jsCharCodeAt(externRef, i));
+    }
+
+    return array;
+  }
+}
+
+@patch
+double _parseValidFloat(String string) {
+  return embedder
+      .doubleParseInfallible(unsafeCast<JSStringImpl>(string).wrappedExternRef)
+      .toDouble();
+}
+
+@patch
+String _stringFromCharCodeArray(WasmArray<WasmI16> array, int start, int end) {
+  return JSStringImpl.fromCharCodeArray(array, start, end);
+}
+
+@patch
+String _stringFromAsciiBytes(WasmArray<WasmI8> source, int start, int end) {
+  return JSStringImpl.fromAsciiBytes(source, start, end);
+}
diff --git a/sdk/lib/_internal/wasm_standalone/lib/deferred_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/deferred_patch.dart
new file mode 100644
index 0000000..7fded7d
--- /dev/null
+++ b/sdk/lib/_internal/wasm_standalone/lib/deferred_patch.dart
@@ -0,0 +1,30 @@
+// Copyright (c) 2026, 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.
+
+final Set<int> _loaded = {};
+
+// The standalone target doesn't support loading deferred libraries, dart2wasm
+// emits an error when `--enable-deferred-loading` is enabled with this target.
+// These methods are still referenced in the compiler, but we can simply make
+// them do nothing.
+
+Future<void> loadLibraryFromLoadId(int loadId) {
+  _loaded.add(loadId);
+  return Future.value();
+}
+
+bool checkLibraryIsLoadedFromLoadId(int loadId) {
+  if (_loaded.contains(loadId)) {
+    return true;
+  }
+  throw DeferredLoadIdNotLoadedError();
+}
+
+class DeferredLoadIdNotLoadedError extends Error implements NoSuchMethodError {
+  DeferredLoadIdNotLoadedError();
+
+  String toString() {
+    return 'Deferred loading is not available with dart2wasm standalone';
+  }
+}
diff --git a/sdk/lib/_internal/wasm_standalone/lib/developer_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/developer_patch.dart
new file mode 100644
index 0000000..6498679
--- /dev/null
+++ b/sdk/lib/_internal/wasm_standalone/lib/developer_patch.dart
@@ -0,0 +1,199 @@
+// Copyright (c) 2026, 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:_embedder' as embedder;
+import 'dart:_internal' show patch;
+import 'dart:_js_helper' show jsStringFromDartString, JSExternWrapperExt;
+import 'dart:_wasm';
+import 'dart:async' show Zone;
+import 'dart:isolate';
+
+@patch
+bool debugger({bool when = true, String? message}) {
+  if (when) {
+    embedder.debugger(
+      message == null
+          ? WasmExternRef.nullRef
+          : jsStringFromDartString(message).wrappedExternRef,
+    );
+  }
+
+  return when;
+}
+
+@patch
+Object? inspect(Object? object) {
+  embedder.inspect(object == null ? null : WasmAnyRef.fromObject(object));
+  return object;
+}
+
+@patch
+void log(
+  String message, {
+  DateTime? time,
+  int? sequenceNumber,
+  int level = 0,
+  String name = '',
+  Zone? zone,
+  Object? error,
+  StackTrace? stackTrace,
+}) {
+  // TODO.
+}
+
+@patch
+int get reachabilityBarrier => 0;
+
+@patch
+abstract final class NativeRuntime {
+  @patch
+  static String? get buildId => null;
+
+  @patch
+  static void writeHeapSnapshotToFile(String filepath) =>
+      throw UnsupportedError(
+        "Generating heap snapshots is not supported for WebAssembly.",
+      );
+
+  @patch
+  static void streamTimelineTo(
+    TimelineRecorder recorder, {
+    String? path,
+    String streams = "Dart,GC,Compiler",
+    bool enableProfiler = false,
+    Duration samplingInterval = const Duration(microseconds: 1000),
+  }) => throw UnsupportedError(
+    "Streaming timelines is not supported for WebAssembly.",
+  );
+
+  @patch
+  static void stopStreamingTimeline() => throw UnsupportedError(
+    "Streaming timelines is not supported for WebAssembly.",
+  );
+}
+
+@patch
+bool get extensionStreamHasListener => false;
+
+final _extensions = <String, ServiceExtensionHandler>{};
+
+@patch
+void _postEvent(String eventKind, String eventData) {
+  // TODO.
+}
+
+@patch
+ServiceExtensionHandler? _lookupExtension(String method) {
+  return _extensions[method];
+}
+
+@patch
+_registerExtension(String method, ServiceExtensionHandler handler) {
+  _extensions[method] = handler;
+}
+
+@patch
+void _getServerInfo(SendPort sendPort) {
+  sendPort.send(null);
+}
+
+@patch
+void _webServerControl(SendPort sendPort, bool enable, bool? silenceOutput) {
+  sendPort.send(null);
+}
+
+@patch
+int _getServiceMajorVersion() {
+  return 0;
+}
+
+@patch
+int _getServiceMinorVersion() {
+  return 0;
+}
+
+@patch
+String? _getIsolateIdFromSendPort(SendPort sendPort) {
+  return null;
+}
+
+@patch
+String? _getObjectId(Object object) {
+  return null;
+}
+
+@patch
+class UserTag {
+  @patch
+  factory UserTag(String label) = _FakeUserTag;
+
+  @patch
+  static UserTag get defaultTag => _FakeUserTag._defaultTag;
+}
+
+final class _FakeUserTag implements UserTag {
+  static final _instances = <String, _FakeUserTag>{};
+
+  _FakeUserTag.real(this.label);
+
+  factory _FakeUserTag(String label) {
+    // Canonicalize by name.
+    var existingTag = _instances[label];
+    if (existingTag != null) {
+      return existingTag;
+    }
+    // Throw an exception if we've reached the maximum number of user tags.
+    if (_instances.length == UserTag.maxUserTags) {
+      throw UnsupportedError(
+        'UserTag instance limit (${UserTag.maxUserTags}) reached.',
+      );
+    }
+    return _instances[label] = _FakeUserTag.real(label);
+  }
+
+  final String label;
+
+  UserTag makeCurrent() {
+    var old = _currentTag;
+    _currentTag = this;
+    return old;
+  }
+
+  static final UserTag _defaultTag = _FakeUserTag('Default');
+}
+
+var _currentTag = _FakeUserTag._defaultTag;
+
+@patch
+UserTag getCurrentTag() => _currentTag;
+
+@patch
+bool _isDartStreamEnabled() => embedder.dartTimelineStreamEnabled().toBool();
+
+int _taskId = 1;
+
+@patch
+int _getNextTaskId() {
+  return _taskId++;
+}
+
+@patch
+int _getTraceClock() => embedder.monotonicClockTicks().toInt();
+
+@patch
+void _reportTaskEvent(
+  int taskId,
+  int flowId,
+  int type,
+  String name,
+  String argumentsAsJson,
+) {
+  embedder.reportTaskEvent(
+    WasmI32.fromInt(taskId),
+    WasmI32.fromInt(flowId),
+    WasmI32.fromInt(type),
+    jsStringFromDartString(name).wrappedExternRef,
+    jsStringFromDartString(argumentsAsJson).wrappedExternRef,
+  );
+}
diff --git a/sdk/lib/_internal/wasm_standalone/lib/embedder.dart b/sdk/lib/_internal/wasm_standalone/lib/embedder.dart
index b355af7..e9c2a9a 100644
--- a/sdk/lib/_internal/wasm_standalone/lib/embedder.dart
+++ b/sdk/lib/_internal/wasm_standalone/lib/embedder.dart
@@ -186,6 +186,10 @@
 @pragma("wasm:import", "dart.tryParseResultGetDouble")
 external WasmF64 tryParseResultGetDouble(WasmExternRef? parseResult);
 
+/// Parses a double from a string known to contain a a valid double.
+@pragma("wasm:import", "dart.doubleParseInfallible")
+external WasmF64 doubleParseInfallible(WasmExternRef? string);
+
 /// The implementation of [int.toRadixString].
 ///
 /// The SDK will only call this with radix values between 2 and 36 (inclusive).
@@ -333,3 +337,53 @@
 external WasmI32 timeZoneOffsetInSecondsForClampedSeconds(
   WasmI64 secondsSinceEpoch,
 );
+
+@pragma("wasm:import", "dart.mathPow")
+external WasmF64 mathPow(WasmF64 base, WasmF64 exponent);
+@pragma("wasm:import", "dart.mathAtan2")
+external WasmF64 mathAtan2(WasmF64 a, WasmF64 b);
+@pragma("wasm:import", "dart.mathSin")
+external WasmF64 mathSin(WasmF64 x);
+@pragma("wasm:import", "dart.mathCos")
+external WasmF64 mathCos(WasmF64 x);
+@pragma("wasm:import", "dart.mathTan")
+external WasmF64 mathTan(WasmF64 x);
+@pragma("wasm:import", "dart.mathAcos")
+external WasmF64 mathAcos(WasmF64 x);
+@pragma("wasm:import", "dart.mathAsin")
+external WasmF64 mathAsin(WasmF64 x);
+@pragma("wasm:import", "dart.mathAtan")
+external WasmF64 mathAtan(WasmF64 x);
+@pragma("wasm:import", "dart.mathExp")
+external WasmF64 mathExp(WasmF64 a);
+@pragma("wasm:import", "dart.mathLog")
+external WasmF64 mathLog(WasmF64 a);
+
+@pragma("wasm:import", "dart.randomInt")
+external WasmI64 randomInt();
+@pragma("wasm:import", "dart.randomIntSecure")
+external WasmI64 randomIntSecure();
+
+@pragma("wasm:import", "dart.print")
+external WasmVoid print(WasmExternRef? line);
+@pragma("wasm:import", "dart.jsonEncodeString")
+external WasmExternRef jsonEncodeString(WasmExternRef? line);
+
+@pragma("wasm:import", "dart.debugger")
+external WasmVoid debugger(WasmExternRef? message);
+
+/// Send a reference to [object] to any attached debuggers.
+@pragma("wasm:import", "dart.inspect")
+external WasmVoid inspect(WasmAnyRef? object);
+
+@pragma("wasm:import", "dart.timelineStreamEnabled")
+external WasmI32 dartTimelineStreamEnabled();
+
+@pragma("wasm:import", "dart.reportTaskEvent")
+external WasmI32 reportTaskEvent(
+  WasmI32 taskId,
+  WasmI32 flowId,
+  WasmI32 type,
+  WasmExternRef? name,
+  WasmExternRef? argumentsAsJson,
+);
diff --git a/sdk/lib/_internal/wasm_standalone/lib/internal_json_encode_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/internal_json_encode_patch.dart
new file mode 100644
index 0000000..9c9d308
--- /dev/null
+++ b/sdk/lib/_internal/wasm_standalone/lib/internal_json_encode_patch.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2026, 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:_embedder' as embedder;
+import "dart:_js_helper" show jsStringFromDartString, JSExternWrapperExt;
+import 'dart:_string' show JSStringImpl;
+import 'dart:_wasm';
+
+String jsonEncode(String object) => JSStringImpl.fromRef(
+  embedder.jsonEncodeString(jsStringFromDartString(object).wrappedExternRef),
+);
diff --git a/sdk/lib/_internal/wasm_standalone/lib/invoke_main_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/invoke_main_patch.dart
new file mode 100644
index 0000000..cefb7d4
--- /dev/null
+++ b/sdk/lib/_internal/wasm_standalone/lib/invoke_main_patch.dart
@@ -0,0 +1,49 @@
+// Copyright (c) 2026, 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:_string';
+import 'dart:_wasm';
+
+@pragma("wasm:prefer-inline")
+void _invokeMainArg0(List<String> args, Function() mainMethod) {
+  mainMethod();
+  return;
+}
+
+@pragma("wasm:prefer-inline")
+void _invokeMainArg1(List<String> args, Function(List<String>) mainMethod) {
+  mainMethod(args);
+  return;
+}
+
+@pragma("wasm:prefer-inline")
+void _invokeMainArg2(
+  List<String> args,
+  Function(List<String>, Null) mainMethod,
+) {
+  mainMethod(args, null);
+  return;
+}
+
+// Will be patched in `pkg/dart2wasm/lib/compile.dart` right before TFA.
+external void _invokeMainInternal(List<String> args);
+
+/// Used to invoke the `main` function from JS, printing any exceptions that
+/// escape.
+@pragma("wasm:export", "\$invokeMain")
+WasmVoid _invokeMain(WasmArray<WasmExternRef?> args) {
+  try {
+    final dartArgs = <String>[];
+    for (var i = 0; i < args.length; i++) {
+      dartArgs.add(JSStringImpl.fromRefUnchecked(args[i]));
+    }
+
+    _invokeMainInternal(dartArgs);
+    return WasmVoid();
+  } catch (e, s) {
+    print(e);
+    print(s);
+    rethrow;
+  }
+}
diff --git a/sdk/lib/_internal/wasm_standalone/lib/math_externs_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/math_externs_patch.dart
new file mode 100644
index 0000000..e962831
--- /dev/null
+++ b/sdk/lib/_internal/wasm_standalone/lib/math_externs_patch.dart
@@ -0,0 +1,102 @@
+// Copyright (c) 2026, 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:_error_utils";
+import 'dart:_embedder' as embedder;
+import "dart:_internal" show patch;
+import "dart:_wasm";
+
+@patch
+double atan2(num a, num b) => embedder
+    .mathAtan2(
+      WasmF64.fromDouble(a.toDouble()),
+      WasmF64.fromDouble(b.toDouble()),
+    )
+    .toDouble();
+@patch
+double sin(num radians) =>
+    embedder.mathSin(WasmF64.fromDouble(radians.toDouble())).toDouble();
+@patch
+double cos(num radians) =>
+    embedder.mathCos(WasmF64.fromDouble(radians.toDouble())).toDouble();
+@patch
+double tan(num radians) =>
+    embedder.mathTan(WasmF64.fromDouble(radians.toDouble())).toDouble();
+@patch
+double acos(num x) =>
+    embedder.mathAcos(WasmF64.fromDouble(x.toDouble())).toDouble();
+@patch
+double asin(num x) =>
+    embedder.mathAsin(WasmF64.fromDouble(x.toDouble())).toDouble();
+@patch
+double atan(num x) =>
+    embedder.mathAtan(WasmF64.fromDouble(x.toDouble())).toDouble();
+@patch
+double sqrt(num x) => x.toDouble().sqrt();
+@patch
+double exp(num x) =>
+    embedder.mathExp(WasmF64.fromDouble(x.toDouble())).toDouble();
+@patch
+double log(num x) =>
+    embedder.mathLog(WasmF64.fromDouble(x.toDouble())).toDouble();
+
+double _doublePow(double base, double exponent) => embedder
+    .mathPow(
+      WasmF64.fromDouble(base.toDouble()),
+      WasmF64.fromDouble(exponent.toDouble()),
+    )
+    .toDouble();
+
+@patch
+class _Random {
+  @patch
+  static int _initialSeed() => embedder.randomInt().toInt();
+}
+
+class _SecureRandom implements Random {
+  _SecureRandom() {
+    // Throw early in constructor if entropy source is not hooked up.
+    _getBytes(1);
+  }
+
+  // Return count bytes of entropy as an integer; count <= 8.
+  int _getBytes(int count) {
+    final random = embedder.randomIntSecure().toInt();
+    return random.toUnsigned(64);
+  }
+
+  int nextInt(int max) {
+    RangeErrorUtils.checkValueInInterval(
+      max,
+      1,
+      _POW2_32,
+      "max",
+      "Must be positive and <= 2^32",
+    );
+    final byteCount =
+        ((max - 1).bitLength + 7) >> 3; // Divide number of bits by 8, round up.
+    if (byteCount == 0) {
+      return 0; // Not random if max == 1.
+    }
+    var rnd;
+    var result;
+    do {
+      rnd = _getBytes(byteCount);
+      result = rnd % max;
+    } while ((rnd - result + max) > (1 << (byteCount << 3)));
+    return result;
+  }
+
+  double nextDouble() {
+    return (_getBytes(7) >> 3) / _POW2_53_D;
+  }
+
+  bool nextBool() {
+    return _getBytes(1).isEven;
+  }
+
+  // Constants used by the algorithm.
+  static const _POW2_32 = 1 << 32;
+  static const _POW2_53_D = 1.0 * (1 << 53);
+}
diff --git a/sdk/lib/_internal/wasm_standalone/lib/print_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/print_patch.dart
new file mode 100644
index 0000000..31a5d70
--- /dev/null
+++ b/sdk/lib/_internal/wasm_standalone/lib/print_patch.dart
@@ -0,0 +1,10 @@
+// Copyright (c) 2026, 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:_embedder' as embedder;
+import 'dart:_js_helper' show jsStringFromDartString, JSExternWrapperExt;
+
+@patch
+void printToConsole(String line) =>
+    embedder.print(jsStringFromDartString(line).wrappedExternRef);
diff --git a/sdk/lib/_internal/wasm_standalone/lib/typed_data_copy_from_js.dart b/sdk/lib/_internal/wasm_standalone/lib/typed_data_copy_from_js.dart
new file mode 100644
index 0000000..14d7f8c
--- /dev/null
+++ b/sdk/lib/_internal/wasm_standalone/lib/typed_data_copy_from_js.dart
@@ -0,0 +1,30 @@
+// Copyright (c) 2026, 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:_internal' show patch;
+
+// The standalone platform doesn't support interacting with JS typed array
+// objects.
+
+@patch
+bool tryCopyExternalIntTypedData(
+  Iterable<int> from,
+  _IntListMixin to,
+  int start,
+  int skipCount,
+  int count,
+) {
+  return false;
+}
+
+@patch
+bool tryCopyExternalFloatTypedData(
+  Iterable<double> from,
+  _DoubleListMixin to,
+  int start,
+  int skipCount,
+  int count,
+) {
+  return false;
+}
diff --git a/sdk/lib/libraries.json b/sdk/lib/libraries.json
index 9cd2f87..5b247d6 100644
--- a/sdk/lib/libraries.json
+++ b/sdk/lib/libraries.json
@@ -168,7 +168,8 @@
       "convert": {
         "uri": "convert/convert.dart",
         "patches": [
-          "_internal/wasm/lib/convert_patch.dart"
+          "_internal/wasm/lib/convert_patch.dart",
+          "_internal/wasm/lib/convert_js_patch.dart"
         ]
       },
       "typed_data": {
@@ -305,13 +306,14 @@
       "convert": {
         "uri": "convert/convert.dart",
         "patches": [
-          "_internal/wasm/lib/convert_patch.dart"
+          "_internal/wasm/lib/convert_patch.dart",
+          "_internal/wasm_standalone/lib/convert_standalone_patch.dart"
         ]
       },
       "developer": {
         "uri": "developer/developer.dart",
         "patches": [
-          "_internal/js_runtime/lib/developer_patch.dart"
+          "_internal/wasm_standalone/lib/developer_patch.dart"
         ]
       },
       "typed_data": {
@@ -337,7 +339,7 @@
       "_typed_data": {
         "uri": "_internal/wasm/lib/typed_data.dart",
         "patches": [
-          "_internal/wasm/lib/typed_data_copy_from_js.dart"
+          "_internal/wasm_standalone/lib/typed_data_copy_from_js.dart"
         ]
       },
       "_js_helper": {
@@ -350,10 +352,10 @@
         "uri": "internal/internal.dart",
         "patches": [
           "_internal/wasm/lib/internal_patch.dart",
-          "_internal/wasm/lib/deferred_patch.dart",
-          "_internal/wasm/lib/internal_json_encode_patch.dart",
-          "_internal/wasm/lib/invoke_main_patch.dart",
-          "_internal/wasm/lib/print_patch.dart",
+          "_internal/wasm_standalone/lib/deferred_patch.dart",
+          "_internal/wasm_standalone/lib/internal_json_encode_patch.dart",
+          "_internal/wasm_standalone/lib/invoke_main_patch.dart",
+          "_internal/wasm_standalone/lib/print_patch.dart",
           "_internal/vm_shared/lib/check_valid_weak_target_patch.dart"
         ]
       },
@@ -365,7 +367,7 @@
         "uri": "math/math.dart",
         "patches": [
           "_internal/wasm/lib/math_patch.dart",
-          "_internal/wasm/lib/math_externs_patch.dart"
+          "_internal/wasm_standalone/lib/math_externs_patch.dart"
         ]
       }
     }
diff --git a/sdk/lib/libraries.yaml b/sdk/lib/libraries.yaml
index 175a55b..e23e2f2 100644
--- a/sdk/lib/libraries.yaml
+++ b/sdk/lib/libraries.yaml
@@ -156,6 +156,7 @@
       uri: convert/convert.dart
       patches:
         - _internal/wasm/lib/convert_patch.dart
+        - _internal/wasm/lib/convert_js_patch.dart
     typed_data:
       uri: typed_data/typed_data.dart
       patches:
@@ -261,11 +262,12 @@
     convert:
       uri: convert/convert.dart
       patches:
-        - _internal/wasm/lib/convert_patch.dart # TODO(53884): Rewrite without JS interop in _internal/wasm_standalone
+        - _internal/wasm/lib/convert_patch.dart
+        - _internal/wasm_standalone/lib/convert_standalone_patch.dart
     developer:
       uri: developer/developer.dart
       patches:
-      - _internal/js_runtime/lib/developer_patch.dart # TODO(53884): Rewrite without JS interop in _internal/wasm_standalone
+      - _internal/wasm_standalone/lib/developer_patch.dart
     typed_data:
       uri: typed_data/typed_data.dart
       patches:
@@ -284,7 +286,7 @@
     _typed_data:
       uri: _internal/wasm/lib/typed_data.dart
       patches:
-        - _internal/wasm/lib/typed_data_copy_from_js.dart # TODO(53884): Rewrite without JS interop in _internal/wasm_standalone
+        - _internal/wasm_standalone/lib/typed_data_copy_from_js.dart
     _js_helper: # TODO(53884): Remove once other wasm_standalone patches no longer reference this
       uri: _internal/wasm/lib/js_helper.dart
       patches:
@@ -293,10 +295,10 @@
       uri: internal/internal.dart
       patches:
       - _internal/wasm/lib/internal_patch.dart
-      - _internal/wasm/lib/deferred_patch.dart # TODO(53884): Rewrite without JS interop in _internal/wasm_standalone
-      - _internal/wasm/lib/internal_json_encode_patch.dart # Needs to be migrated
-      - _internal/wasm/lib/invoke_main_patch.dart # TODO(53884): Rewrite without JS interop in _internal/wasm_standalone
-      - _internal/wasm/lib/print_patch.dart # TODO(53884): Rewrite without JS interop in _internal/wasm_standalone
+      - _internal/wasm_standalone/lib/deferred_patch.dart
+      - _internal/wasm_standalone/lib/internal_json_encode_patch.dart
+      - _internal/wasm_standalone/lib/invoke_main_patch.dart
+      - _internal/wasm_standalone/lib/print_patch.dart
       - _internal/vm_shared/lib/check_valid_weak_target_patch.dart
     _wasm:
       uri: _wasm/wasm_types.dart
@@ -305,7 +307,7 @@
       uri: math/math.dart
       patches:
         - _internal/wasm/lib/math_patch.dart
-        - _internal/wasm/lib/math_externs_patch.dart # TODO(53884): Rewrite without JS interop in _internal/wasm_standalone
+        - _internal/wasm_standalone/lib/math_externs_patch.dart
 
 wasm_js_common:
   include: