[dart2wasm, standalone] Migrate String

This migrates the `String` implementation from using JS interop to
explicit host imports for the standalone target.

This moves a few helper methods shared between the JS and standalone
targets to `dart:_string_helper`. This also moves the embedder regexp
implementation to `dart:_string` to be able to access internals in some
string methods (similar to how the JS implementation special-cases
`JSSyntaxRegExp`).

This removes the final real use of JS-interop in the standalone target.
So, we can:

 - Remove internal JS helper libraries from the target.
 - Skip JS-interop transformations in the compiler.
 - Stop emitting a helper module and support script.

Because `js_interop` is imported in `dart:_wasm`, we can't remove the
library entirely. This replaces it with a stub to avoid compilation
errors, a proper removal is tracked in dartbug.com/63166.

Change-Id: Ide495c210c3a272438deebf8fe4f3f44ba314ffa
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/501960
Reviewed-by: Martin Kustermann <kustermann@google.com>
Reviewed-by: Kevin Moore <kevmoo@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
This commit is contained in:
Simon Binder
2026-05-20 00:22:27 -07:00
committed by dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent 3de2ddee09
commit 58f9d88fb2
58 changed files with 1606 additions and 677 deletions
+12 -10
View File
@@ -324,20 +324,22 @@ class AsyncStateMachineCodeGenerator extends StateMachineCodeGenerator {
b.local_set(exceptionLocal);
callCompleteError();
// Handle JS exceptions.
b.catch_legacy(translator.getJsExceptionTag(b.moduleBuilder));
if (!translator.options.standalone) {
// Handle JS exceptions.
b.catch_legacy(translator.getJsExceptionTag(b.moduleBuilder));
final jsExceptionLocal = addLocal(w.RefType.extern(nullable: true));
b.local_tee(jsExceptionLocal);
final jsExceptionLocal = addLocal(w.RefType.extern(nullable: true));
b.local_tee(jsExceptionLocal);
call(translator.boxJsException.reference);
b.local_tee(exceptionLocal); // ref null #Top
call(translator.boxJsException.reference);
b.local_tee(exceptionLocal); // ref null #Top
b.local_get(jsExceptionLocal);
call(translator.jsExceptionStackTrace.reference);
b.local_set(stackTraceLocal);
b.local_get(jsExceptionLocal);
call(translator.jsExceptionStackTrace.reference);
b.local_set(stackTraceLocal);
callCompleteError();
callCompleteError();
}
b.end(); // try
+11 -3
View File
@@ -1129,9 +1129,11 @@ abstract class AstCodeGenerator
b.rethrow_(tryBlock);
// Handle JS exceptions.
b.catch_legacy(translator.getJsExceptionTag(b.moduleBuilder));
translateStatement(node.finalizer);
b.rethrow_(tryBlock);
if (!translator.options.standalone) {
b.catch_legacy(translator.getJsExceptionTag(b.moduleBuilder));
translateStatement(node.finalizer);
b.rethrow_(tryBlock);
}
b.end(); // tryBlock
@@ -6201,6 +6203,12 @@ class LambdaCallTarget extends CallTarget {
/// Note that the guard type can be nullable, but the value for the exception
/// needs to be non-null regardless of the guard type, as per Dart semantics.
bool guardCanMatchJSException(Translator translator, DartType guard) {
if (translator.options.standalone) {
// Standalone mode doesn't run in a JavaScript context and doesn't have JS
// exceptions.
return false;
}
return translator.typeEnvironment.isSubtypeOf(
InterfaceType(translator.jsValueClass, Nullability.nonNullable),
guard.extensionTypeErasure,
+28 -24
View File
@@ -425,11 +425,13 @@ Future<CompilationResult> _runTfaPhase(
);
}
js.performJSInteropTransformations(
component.libraries,
coreTypes,
classHierarchy,
);
if (!options.translatorOptions.standalone) {
js.performJSInteropTransformations(
component.libraries,
coreTypes,
classHierarchy,
);
}
final librariesToTransform = component.libraries;
final constantEvaluator = ConstantEvaluator(
@@ -635,25 +637,30 @@ Future<CompilationResult> _runCodegenPhase(
});
await Future.wait(writeFutures);
final jsRuntimeFinalizer = js.RuntimeFinalizer(
coreTypes,
translator.interopMemberNamer,
);
if (!options.translatorOptions.standalone) {
final jsRuntimeFinalizer = js.RuntimeFinalizer(
coreTypes,
translator.interopMemberNamer,
);
final jsRuntime = jsRuntimeFinalizer.generate(
moduleOutputData.mainModule.moduleImportName,
translator.functions.translatedProcedures,
translator.internalizedStringsForJSRuntime,
translator.options.requireJsStringBuiltin,
translator.options.enableDeferredLoading ||
translator.options.enableMultiModuleStressTestMode,
);
final jsRuntime = jsRuntimeFinalizer.generate(
moduleOutputData.mainModule.moduleImportName,
translator.functions.translatedProcedures,
translator.internalizedStringsForJSRuntime,
translator.options.requireJsStringBuiltin,
translator.options.enableDeferredLoading ||
translator.options.enableMultiModuleStressTestMode,
);
final supportJs = _generateSupportJs(options.translatorOptions);
final supportJs = _generateSupportJs(options.translatorOptions);
final deferredMapFile = options.deferredMapUri;
if (deferredMapFile != null) {
await writeDeferredMapFile(component, coreTypes, options, loadingMap);
final deferredMapFile = options.deferredMapUri;
if (deferredMapFile != null) {
await writeDeferredMapFile(component, coreTypes, options, loadingMap);
}
await ioManager.writeJsRuntime(jsRuntime);
await ioManager.writeSupportJs(supportJs);
}
final wasmOutputFilename = path.basename(options.outputFile);
@@ -666,9 +673,6 @@ Future<CompilationResult> _runCodegenPhase(
)
.toSet();
await ioManager.writeJsRuntime(jsRuntime);
await ioManager.writeSupportJs(supportJs);
if (options.recordedUsesFile != null) {
record_use.LoadingUnit loadingUnitForNode(TreeNode node) {
while (node is! NamedNode) {
+1 -1
View File
@@ -386,7 +386,7 @@ class ExceptionHandlerStack {
canHandleJSExceptions |= handler.canHandleJSExceptions;
}
if (canHandleJSExceptions) {
if (canHandleJSExceptions && !codeGen.translator.options.standalone) {
b.catch_legacy(codeGen.translator.getJsExceptionTag(b.moduleBuilder));
final jsExceptionLocal = codeGen.addLocal(
+2
View File
@@ -43,6 +43,8 @@ class ExceptionTags {
}
w.Tag _importJsExceptionTag() {
assert(!translator.options.standalone);
final w.FunctionType tagType = translator.typesBuilder.defineFunction(
const [w.RefType.extern(nullable: true)],
const [],
+44 -36
View File
@@ -159,8 +159,6 @@ class WasmTarget extends Target {
'dart:_compact_hash',
'dart:_http',
'dart:_internal',
'dart:_js_helper',
'dart:_js_types',
'dart:_list',
'dart:_string',
'dart:_wasm',
@@ -168,11 +166,16 @@ class WasmTarget extends Target {
'dart:developer',
'dart:ffi',
'dart:io',
'dart:js_interop',
'dart:js_interop_unsafe',
'dart:nativewrappers',
'dart:typed_data',
if (mode == .standalone) 'dart:_embedder',
if (mode == .standalone)
'dart:_embedder'
else ...[
'dart:_js_helper',
'dart:_js_types',
'dart:js_interop',
'dart:js_interop_unsafe',
],
];
@override
@@ -182,16 +185,19 @@ class WasmTarget extends Target {
'dart:_boxed_int',
'dart:_compact_hash',
'dart:_error_utils',
'dart:_js_helper',
'dart:_js_types',
'dart:_list',
'dart:_string',
'dart:_wasm',
'dart:collection',
'dart:js_interop',
'dart:js_interop_unsafe',
'dart:typed_data',
if (mode == .standalone) 'dart:_embedder',
if (mode == .standalone)
'dart:_embedder'
else ...[
'dart:_js_helper',
'dart:_js_types',
'dart:js_interop',
'dart:js_interop_unsafe',
],
];
@override
@@ -319,32 +325,34 @@ class WasmTarget extends Target {
}
}
Set<Library> transitiveImportingJSInterop = {
...jsInteropHelper.calculateTransitiveImportsOfJsInteropIfUsed(
component.libraries,
Uri.parse("dart:js_interop"),
),
...jsInteropHelper.calculateTransitiveImportsOfJsInteropIfUsed(
component.libraries,
Uri.parse("dart:convert"),
),
...jsInteropHelper.calculateTransitiveImportsOfJsInteropIfUsed(
component.libraries,
Uri.parse("dart:_string"),
),
};
if (transitiveImportingJSInterop.isEmpty) {
logger?.call("Skipped JS interop transformations");
} else {
_performJSInteropTransformations(
component,
coreTypes,
hierarchy,
transitiveImportingJSInterop,
diagnosticReporter,
referenceFromIndex,
);
logger?.call("Transformed JS interop classes");
if (mode != .standalone) {
Set<Library> transitiveImportingJSInterop = {
...jsInteropHelper.calculateTransitiveImportsOfJsInteropIfUsed(
component.libraries,
Uri.parse("dart:js_interop"),
),
...jsInteropHelper.calculateTransitiveImportsOfJsInteropIfUsed(
component.libraries,
Uri.parse("dart:convert"),
),
...jsInteropHelper.calculateTransitiveImportsOfJsInteropIfUsed(
component.libraries,
Uri.parse("dart:_string"),
),
};
if (transitiveImportingJSInterop.isEmpty) {
logger?.call("Skipped JS interop transformations");
} else {
_performJSInteropTransformations(
component,
coreTypes,
hierarchy,
transitiveImportingJSInterop,
diagnosticReporter,
referenceFromIndex,
);
logger?.call("Transformed JS interop classes");
}
}
// If we are compiling with a null environment, skip constant resolution
@@ -6,8 +6,8 @@ Part 0
- pkg/compiler/test/custom_split/data/fuse_with_and/lib4.dart prefix: b4
References
- dart:_boxed_int::BoxedInt::@methods::toRadixString
- dart:_string::@methods::dart:_string::_JS_Inline_22
- dart:_string::@methods::dart:_string::_JS_Inline_30
- dart:_string::@methods::dart:_string::_JS_Inline_173
- dart:_string::@methods::dart:_string::_JS_Inline_181
- dart:_string::@methods::dart:_string::_jsIdentical
- dart:_string::@methods::dart:_string::_jsStringToUpperCase
- dart:_string::@methods::jsStringFromCharCodeArray
@@ -6,8 +6,8 @@ Part 0
- pkg/compiler/test/custom_split/data/fuse_with_and/lib4.dart prefix: b4
References
- dart:_boxed_int::BoxedInt::@methods::toRadixString
- dart:_string::@methods::dart:_string::_JS_Inline_22
- dart:_string::@methods::dart:_string::_JS_Inline_30
- dart:_string::@methods::dart:_string::_JS_Inline_173
- dart:_string::@methods::dart:_string::_JS_Inline_181
- dart:_string::@methods::dart:_string::_jsIdentical
- dart:_string::@methods::dart:_string::_jsStringToUpperCase
- dart:_string::@methods::jsStringFromCharCodeArray
@@ -6,8 +6,8 @@ Part 0
- pkg/compiler/test/custom_split/data/fuse_with_or/lib4.dart prefix: b4
References
- dart:_boxed_int::BoxedInt::@methods::toRadixString
- dart:_string::@methods::dart:_string::_JS_Inline_22
- dart:_string::@methods::dart:_string::_JS_Inline_30
- dart:_string::@methods::dart:_string::_JS_Inline_173
- dart:_string::@methods::dart:_string::_JS_Inline_181
- dart:_string::@methods::dart:_string::_jsIdentical
- dart:_string::@methods::dart:_string::_jsStringToUpperCase
- dart:_string::@methods::jsStringFromCharCodeArray
@@ -6,8 +6,8 @@ Part 0
- pkg/compiler/test/custom_split/data/fuse_with_or/lib4.dart prefix: b4
References
- dart:_boxed_int::BoxedInt::@methods::toRadixString
- dart:_string::@methods::dart:_string::_JS_Inline_22
- dart:_string::@methods::dart:_string::_JS_Inline_30
- dart:_string::@methods::dart:_string::_JS_Inline_173
- dart:_string::@methods::dart:_string::_JS_Inline_181
- dart:_string::@methods::dart:_string::_jsIdentical
- dart:_string::@methods::dart:_string::_jsStringToUpperCase
- dart:_string::@methods::jsStringFromCharCodeArray
@@ -6,8 +6,8 @@ Part 0
- pkg/compiler/test/custom_split/data/just_fuse/lib4.dart prefix: b4
References
- dart:_boxed_int::BoxedInt::@methods::toRadixString
- dart:_string::@methods::dart:_string::_JS_Inline_22
- dart:_string::@methods::dart:_string::_JS_Inline_30
- dart:_string::@methods::dart:_string::_JS_Inline_173
- dart:_string::@methods::dart:_string::_JS_Inline_181
- dart:_string::@methods::dart:_string::_jsIdentical
- dart:_string::@methods::dart:_string::_jsStringToUpperCase
- dart:_string::@methods::jsStringFromCharCodeArray
@@ -6,8 +6,8 @@ Part 0
- pkg/compiler/test/custom_split/data/just_fuse/lib4.dart prefix: b4
References
- dart:_boxed_int::BoxedInt::@methods::toRadixString
- dart:_string::@methods::dart:_string::_JS_Inline_22
- dart:_string::@methods::dart:_string::_JS_Inline_30
- dart:_string::@methods::dart:_string::_JS_Inline_173
- dart:_string::@methods::dart:_string::_JS_Inline_181
- dart:_string::@methods::dart:_string::_jsIdentical
- dart:_string::@methods::dart:_string::_jsStringToUpperCase
- dart:_string::@methods::jsStringFromCharCodeArray
+4 -4
View File
@@ -116,7 +116,7 @@
local.get $var0
local.get $onlyUsedInSuper
local.set $var3
i32.const 63
i32.const 85
local.get $var3
struct.new $BoxedInt
call $"new _MixinApplication1&Base&SubMixin.named (initializer)"
@@ -248,7 +248,7 @@
global.get $"\", \""
local.get $onlyUsedInSubBody
local.set $var4
i32.const 63
i32.const 85
local.get $var4
struct.new $BoxedInt
array.new_fixed $Array<Object?> 6
@@ -273,7 +273,7 @@
local.get $var0
local.get $onlyUsedInSuper1
local.set $var3
i32.const 63
i32.const 85
local.get $var3
struct.new $BoxedInt
local.get $onlyUsedInSuper2
@@ -328,7 +328,7 @@
local.get $var0
local.get $onlyUsedInSuper1
local.set $var3
i32.const 63
i32.const 85
local.get $var3
struct.new $BoxedInt
call $"new _MixinApplication1&Base&SubMixin.sub2 (initializer)"
@@ -18,7 +18,7 @@
(global $MyConstClass (ref $MyConstClass)
(i32.const 108)
(i32.const 0)
(i32.const 103)
(i32.const 60)
(i32.const 0)
(global.get $.h1-nonshared-const)
(struct.new $JSExternWrapper)
@@ -17,7 +17,7 @@
(global $MyConstClass (ref $MyConstClass)
(i32.const 108)
(i32.const 0)
(i32.const 103)
(i32.const 60)
(i32.const 0)
(global.get $.h0-nonshared-const)
(struct.new $JSExternWrapper)
@@ -16,7 +16,7 @@
(global $MyConstClass (ref $MyConstClass)
(i32.const 108)
(i32.const 0)
(i32.const 103)
(i32.const 60)
(i32.const 0)
(global.get $.shared-const)
(struct.new $JSExternWrapper)
@@ -10,7 +10,7 @@
(global $".Foo called " (import "" "Foo called ") (ref extern))
(table $module0.cross-module-funcs-0 (import "module0" "cross-module-funcs-0") 4 funcref)
(global $"\"Foo called \"" (ref $JSExternWrapper)
(i32.const 102)
(i32.const 58)
(i32.const 0)
(global.get $".Foo called ")
(struct.new $JSExternWrapper))
@@ -38,7 +38,7 @@
(ref.null none))
(global $"\">(\"" (ref $JSExternWrapper) <...>)
(global $"\"globalH1Bar<\"" (ref $JSExternWrapper)
(i32.const 102)
(i32.const 58)
(i32.const 0)
(global.get $.globalH1Bar<)
(struct.new $JSExternWrapper))
@@ -144,7 +144,7 @@
struct.get $H1 $fun
local.tee $var0
struct.get $#Closure-0-1 $context
i32.const 64
i32.const 86
i64.const 1
struct.new $BoxedInt
local.get $var0
@@ -21,7 +21,7 @@
(table $module0.constant-table0 (import "module0" "constant-table0") 1 (ref null $_FunctionType))
(table $module0.cross-module-funcs-0 (import "module0" "cross-module-funcs-0") 11 funcref)
(global $"\"globalH0Foo\"" (ref $JSExternWrapper)
(i32.const 102)
(i32.const 58)
(i32.const 0)
(global.get $.globalH0Foo)
(struct.new $JSExternWrapper))
@@ -11,24 +11,24 @@
(global $1 (import "module0" "global2") (ref $BoxedInt))
(global $2 (import "module0" "global3") (ref $BoxedInt))
(table $module0.cross-module-funcs-0 (import "module0" "cross-module-funcs-0") 18 funcref)
(table $module0.dispatch0 (import "module0" "dispatch0") 649 funcref)
(table $module0.dispatch0 (import "module0" "dispatch0") 654 funcref)
(global $"\"Foo0.doitDispatch(\"" (ref $JSExternWrapper)
(i32.const 103)
(i32.const 60)
(i32.const 0)
(global.get $".Foo0.doitDispatch(")
(struct.new $JSExternWrapper))
(global $"\"Foo1.doitDevirt(\"" (ref $JSExternWrapper)
(i32.const 103)
(i32.const 60)
(i32.const 0)
(global.get $".Foo1.doitDevirt(")
(struct.new $JSExternWrapper))
(global $"\"Foo1.doitDispatch(\"" (ref $JSExternWrapper)
(i32.const 103)
(i32.const 60)
(i32.const 0)
(global.get $".Foo1.doitDispatch(")
(struct.new $JSExternWrapper))
(global $"\"FooBase(\"" (ref $JSExternWrapper)
(i32.const 103)
(i32.const 60)
(i32.const 0)
(global.get $".FooBase(")
(struct.new $JSExternWrapper))
@@ -71,7 +71,7 @@
global.get $1
local.get $var0
struct.get $Object $field0
i32.const 457
i32.const 365
i32.add
call_indirect $module0.dispatch0 (param (ref $Object) (ref null $#Top))
block $label2 (result (ref $Object))
@@ -22,44 +22,44 @@
(global $FooConst0 (import "module0" "global7") (ref $Object))
(global $fooGlobal0 (import "module0" "global16") (ref null $#Top))
(table $module0.cross-module-funcs-0 (import "module0" "cross-module-funcs-0") 45 funcref)
(table $module0.dispatch0 (import "module0" "dispatch0") 666 funcref)
(table $module0.dispatch0 (import "module0" "dispatch0") 670 funcref)
(global $"\"0\"" (ref $JSExternWrapper) <...>)
(global $"\"1\"" (ref $JSExternWrapper) <...>)
(global $"\"2\"" (ref $JSExternWrapper) <...>)
(global $"\"3\"" (ref $JSExternWrapper) <...>)
(global $"\"4\"" (ref $JSExternWrapper) <...>)
(global $"\"FooConst0(\"" (ref $JSExternWrapper)
(i32.const 103)
(i32.const 60)
(i32.const 0)
(global.get $".FooConst0(")
(struct.new $JSExternWrapper))
(global $"\"FooConst1(\"" (ref $JSExternWrapper)
(i32.const 103)
(i32.const 60)
(i32.const 0)
(global.get $".FooConst1(")
(struct.new $JSExternWrapper))
(global $"\"FooConst2(\"" (ref $JSExternWrapper)
(i32.const 103)
(i32.const 60)
(i32.const 0)
(global.get $".FooConst2(")
(struct.new $JSExternWrapper))
(global $"\"FooConst3(\"" (ref $JSExternWrapper)
(i32.const 103)
(i32.const 60)
(i32.const 0)
(global.get $".FooConst3(")
(struct.new $JSExternWrapper))
(global $"\"FooConst4(\"" (ref $JSExternWrapper)
(i32.const 103)
(i32.const 60)
(i32.const 0)
(global.get $".FooConst4(")
(struct.new $JSExternWrapper))
(global $"\"FooConst5(\"" (ref $JSExternWrapper)
(i32.const 103)
(i32.const 60)
(i32.const 0)
(global.get $".FooConst5(")
(struct.new $JSExternWrapper))
(global $"\"FooConstBase(\"" (ref $JSExternWrapper)
(i32.const 103)
(i32.const 60)
(i32.const 0)
(global.get $".FooConstBase(")
(struct.new $JSExternWrapper))
@@ -9,7 +9,7 @@
(type $_Type <...>)
(global $"\")\"_11" (import "$" "2") (ref $JSExternWrapper))
(global $_InterfaceType (import "$" "0") (ref $_InterfaceType))
(table $$.% (import "$" "%") 742 funcref)
(table $$.% (import "$" "%") 765 funcref)
(table $$.' (import "$" "'") 20 funcref)
(global $"\">.takeT(\"" (ref $JSExternWrapper) <...>)
(global $"\"Foo<\"" (ref $JSExternWrapper) <...>)
@@ -7,7 +7,7 @@
(type $WasmListBase <...>)
(type $_Type <...>)
(table $module0.cross-module-funcs-0 (import "module0" "cross-module-funcs-0") 12 funcref)
(table $module0.dispatch0 (import "module0" "dispatch0") 666 funcref)
(table $module0.dispatch0 (import "module0" "dispatch0") 670 funcref)
(elem $module0.cross-module-funcs-0
(set 0 (ref.func $"runTest <noInline>")))
(func $"runTest <noInline>"
+1 -1
View File
@@ -46,7 +46,7 @@
(table $dtable0 3 i31ref)
(table $dtable2 3 funcref)
(global $1 (ref $BoxedInt)
(i32.const 63)
(i32.const 85)
(i64.const 1)
(struct.new $BoxedInt))
(global $true (ref $BoxedBool)
@@ -5,13 +5,13 @@
(field $field0 i32)
(field $value i64))))
(global $1 (ref $BoxedInt)
(i32.const 59)
(i32.const 84)
(i64.const 1)
(struct.new $BoxedInt))
(func $"main <noInline>"
global.get $1
call $print
i32.const 59
i32.const 84
i64.const 2
struct.new $BoxedInt
call $print
+1 -1
View File
@@ -6,7 +6,7 @@
(field $_externRef externref))))
(global $".hello world" (import "" "hello world") (ref extern))
(global $"\"hello world\"" (ref $JSExternWrapper)
(i32.const 95)
(i32.const 56)
(global.get $".hello world")
(struct.new $JSExternWrapper))
(func $"main <noInline>"
@@ -11,7 +11,7 @@
(global $".hello world" (import "" "hello world") (ref extern))
(table $module0.cross-module-funcs-0 (import "module0" "cross-module-funcs-0") 2 funcref)
(global $"\"hello world\"" (ref $JSExternWrapper)
(i32.const 96)
(i32.const 57)
(i32.const 0)
(global.get $".hello world")
(struct.new $JSExternWrapper))
@@ -39,7 +39,7 @@
else
call $"ktrue implicit getter"
if (result (ref null $BoxedDouble))
i32.const 72
i32.const 92
call $"doubleValue implicit getter"
struct.new $BoxedDouble
else
+1 -1
View File
@@ -40,7 +40,7 @@
else
call $"ktrue implicit getter"
if (result (ref null $BoxedInt))
i32.const 62
i32.const 86
call $"intValue implicit getter"
struct.new $BoxedInt
else
+5 -5
View File
@@ -9,30 +9,30 @@
i32.const 1
memory.grow $foo.mem
drop
i32.const 67
i32.const 89
i32.const 0
f32.load align=4
f64.promote_f32
struct.new $BoxedDouble
call $print
i32.const 67
i32.const 89
i32.const 0
f32.load align=4
f64.promote_f32
struct.new $BoxedDouble
call $print
i32.const 67
i32.const 89
i32.const 0
f64.load align=8
struct.new $BoxedDouble
call $print
i32.const 67
i32.const 89
i32.const 1
f32.load align=4
f64.promote_f32
struct.new $BoxedDouble
call $print
i32.const 67
i32.const 89
i32.const 1
f32.load align=4
f64.promote_f32
@@ -21,7 +21,7 @@
global.get $"\")\""
call $JSStringImpl._interpolate3
drop
i32.const 59
i32.const 84
local.get $var0
struct.get $JSExternWrapper $_externRef
call $"wasm:js-string.length (import)"
+3 -3
View File
@@ -1,9 +1,9 @@
(module $module0
(type $#Top (struct
(field $field0 i32)))
(type $JSExternWrapper (sub $#Top (struct
(type $JSStringImpl (sub final $#Top (struct
(field $field0 i32)
(field $_externRef externref))))
(global $"\"Hello world\"" (mut (ref null $JSExternWrapper))
(field $_ref (ref extern)))))
(global $"\"Hello world\"" (mut (ref null $JSStringImpl))
(ref.null none))
)
+54 -32
View File
@@ -5,9 +5,9 @@
(type $JSExternWrapper <...>)
(type $JavaScriptStack <...>)
(tag $tag0 (param (ref $#Top) (ref $#Top)))
(global $"WasmArray<WasmI16>[748]" (ref $Array<WasmI16>) <...>)
(global $"WasmArray<WasmI16>[688]" (ref $Array<WasmI16>) <...>)
(global $"WasmArray<WasmI32>[225]" (ref $Array<WasmI32>) <...>)
(global $"WasmArray<WasmI32>[748]" (ref $Array<WasmI32>) <...>)
(global $"WasmArray<WasmI32>[688]" (ref $Array<WasmI32>) <...>)
(global $"\"Caught Error\"" (ref $JSExternWrapper) <...>)
(global $"\"Caught JSAny\"" (ref $JSExternWrapper) <...>)
(global $"\"Caught Object\"" (ref $JSExternWrapper) <...>)
@@ -32,7 +32,7 @@
local.get $var1
struct.get $#Top $field0
local.tee $var0
i32.const 76
i32.const 59
i32.eq
if (result i32)
i32.const 0
@@ -40,22 +40,22 @@
block $label3 (result i32)
i32.const -1
global.get $"WasmArray<WasmI32>[225]"
i32.const 76
i32.const 59
array.get $Array<WasmI32>
local.get $var0
i32.add
local.tee $var0
i32.const 748
i32.const 688
i32.ge_u
br_if $label3
drop
global.get $"WasmArray<WasmI32>[748]"
global.get $"WasmArray<WasmI32>[688]"
local.get $var0
array.get $Array<WasmI32>
i32.const 76
i32.const 59
i32.eq
if
global.get $"WasmArray<WasmI16>[748]"
global.get $"WasmArray<WasmI16>[688]"
local.get $var0
array.get_u $Array<WasmI16>
br $label3
@@ -128,30 +128,52 @@
local.set $var2
local.set $var1
block $label3 (result i32)
i32.const 1
local.get $var1
struct.get $#Top $field0
local.tee $var0
i32.const 40
i32.eq
br_if $label3
drop
i32.const 1
local.get $var0
i32.const 43
i32.sub
i32.const 12
i32.lt_u
br_if $label3
drop
i32.const 1
local.get $var0
i32.const 99
i32.sub
i32.const 2
i32.lt_u
br_if $label3
drop
block $label4
local.get $var1
struct.get $#Top $field0
local.tee $var0
i32.const 54
i32.le_u
if
local.get $var0
i32.const 40
i32.le_u
if
i32.const 1
local.get $var0
i32.const 40
i32.eq
br_if $label3
drop
br $label4
end
i32.const 1
local.get $var0
i32.const 43
i32.ge_u
br_if $label3
drop
br $label4
end
local.get $var0
i32.const 101
i32.le_u
if
i32.const 1
local.get $var0
i32.const 101
i32.eq
br_if $label3
drop
br $label4
end
i32.const 1
local.get $var0
i32.const 105
i32.eq
br_if $label3
drop
end $label4
i32.const 0
end $label3
br_if $label1
+101 -18
View File
@@ -343,6 +343,9 @@ and `base64 -w 0`:
(import "wasm:js-string" "fromCharCodeArray"
(func $fromCharCodeArray (param (ref null $i16array) i32 i32) (result (ref extern)))
)
(import "wasm:js-string" "intoCharCodeArray"
(func $intoCharCodeArray (param (ref null extern) (ref null $i16array) i32) (result i32))
)
(func (export "stringFromAsciiBytes")
(param $arr (ref $i8array))
@@ -357,21 +360,24 @@ and `base64 -w 0`:
(local.set $i (local.get $length))
(local.set $expanded (array.new $i16array (i32.const 0) (local.get $length)))
;; do { i--; expanded[i] = arr[i]; } while (i >= start);
;; do { i--; expanded[i] = arr[i + start]; } while (i >= 0);
(block $break
(loop $loop
(local.set $i (i32.add (local.get $i) (i32.const -1)))
(br_if $break
(i32.lt_s
(local.get $i)
(local.get $start)
(i32.const 0)
)
)
(array.set $i16array
(local.get $expanded)
(local.get $i)
(array.get_u $i8array (local.get $arr) (local.get $i))
(array.get_u $i8array
(local.get $arr)
(i32.add (local.get $i) (local.get $start))
)
)
br $loop
)
@@ -400,6 +406,18 @@ and `base64 -w 0`:
)
)
(func (export "stringToCharCodeArray")
(param $str externref)
(param $array (ref $i16array))
(param $start i32)
(drop (call $intoCharCodeArray
(local.get $str)
(local.get $array)
(local.get $start)
))
)
(func (export "emptyExternRefArray")
(result (ref $externarray))
(array.new_default $externarray (i32.const 0))
@@ -407,15 +425,13 @@ and `base64 -w 0`:
)
*/
const _wasmStandaloneArrayHelper =
'AGFzbQEAAAABKgdedwFeeAFebwFgA2MAf38BZG9gA2QBf38BZG9gA2QAf38BZG9gAAFkAgIkAQ53YXNtOmpzLXN0cmluZxFmcm9tQ2hhckNvZGVBcnJheQADAwQDBAUGB0gDFHN0cmluZ0Zyb21Bc2NpaUJ5dGVzAAEXc3RyaW5nRnJvbUNoYXJDb2RlQXJyYXkAAhNlbXB0eUV4dGVyblJlZkFycmF5AAMKWwNDAgF/AWQAIAIhA0EAIAL7BgAhBAJAA0AgA0F/aiEDIAMgAUgNASAEIAMgACAD+w0B+w4ADAALAAsgBEEAIAT7DxAACw0AIAAgASABIAJqEAALBwBBAPsHAgs=';
'AGFzbQEAAAABOQledwFeeAFebwFgA2MAf38BZG9gA29jAH8Bf2ADZAF/fwFkb2ADZAB/fwFkb2ADb2QAfwBgAAFkAgJHAg53YXNtOmpzLXN0cmluZxFmcm9tQ2hhckNvZGVBcnJheQADDndhc206anMtc3RyaW5nEWludG9DaGFyQ29kZUFycmF5AAQDBQQFBgcIB2AEFHN0cmluZ0Zyb21Bc2NpaUJ5dGVzAAIXc3RyaW5nRnJvbUNoYXJDb2RlQXJyYXkAAxVzdHJpbmdUb0NoYXJDb2RlQXJyYXkABBNlbXB0eUV4dGVyblJlZkFycmF5AAUKagRGAgF/AWQAIAIhA0EAIAL7BgAhBAJAA0AgA0F/aiEDIANBAEgNASAEIAMgACADIAFq+w0B+w4ADAALAAsgBEEAIAT7DxAACw0AIAAgASABIAJqEAALCwAgACABIAIQARoLBwBBAPsHAgs=';
String dart2wasmHtml(
String title,
String wasmPath,
String mjsPath,
String supportJsPath,
bool standalone,
) {
/*
*/
String dart2WasmStandaloneHtml(String title, String wasmPath) {
const standaloneEmbedder =
"""
const { instance: helperInstance } = await WebAssembly.instantiate(Uint8Array.fromBase64('$_wasmStandaloneArrayHelper'), {}, {
@@ -447,6 +463,27 @@ String dart2wasmHtml(
const str = helperInstance.exports.stringFromCharCodeArray(chars, start, length);
return str;
},
stringLength: (s) => s.length,
stringEquals: (a, b) => a === b,
stringCompare: (a, b) => a === b ? 0 : (a < b ? -1 : 1),
stringCodeUnitAt: (str, idx) => str.charCodeAt(idx),
stringIndexOfString: (a, b, start) => a.indexOf(b, start),
stringLastIndexOfString: (a, b, start) => a.lastIndexOf(b, start),
stringReplaceAllString: (str, needle, replace) => str.replaceAll(needle, () => replace),
stringReplaceAllRegExp: (str, regex, replace) => str.replaceAll(regex.regular, () => replace),
stringSubstring: (str, start, end) => str.substring(start, end),
stringToLowerCase: (str) => str.toLowerCase(),
stringToUpperCase: (str) => str.toUpperCase(),
stringConcat: (a, b) => a + b,
stringRepeat: (str, amount) => str.repeat(amount),
stringReplaceRange: (str, start, end, replacement) => {
const before = str.substring(0, start);
const after = str.substring(end);
return before + replacement + after;
},
stringToCodeUnits: (str, array, startIndex) => {
helperInstance.exports.stringToCharCodeArray(str, array, startIndex);
},
monotonicClockFrequency: () => 1_000_000,
monotonicClockTicks: () => BigInt(Math.round(performance.now() * 1000)),
weakRefCreate: (dartValue) => new WeakRef(dartValue),
@@ -627,14 +664,61 @@ String dart2wasmHtml(
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>
<html>
<head>
<meta charset="utf-8">
<meta name="dart.unittest" content="full-stack-traces">
<title> Test $title </title>
<link rel="preload" href="$wasmPath" as="fetch" crossorigin>
<style>
.unittest-table { font-family:monospace; border:1px; }
.unittest-pass { background: #6b3;}
.unittest-fail { background: #d55;}
.unittest-error { background: #a11;}
</style>
</head>
<body>
<h1> Running $title</h1>
<script type="text/javascript"
src="/root_dart/pkg/test_runner/lib/src/test_controller.js">
</script>
<script type="module">
$standaloneEmbedder
// Default stack trace limit in V8 is 10, which hides some of the stack frames
// we check in stack trace tests.
Error.stackTraceLimit = 20;
async function loadAndRun(wasmPath) {
const { instance } = await WebAssembly.instantiateStreaming(fetch(wasmPath), {dart: dartEmbedder});
window.loadData = async (relativeToWasmFileUri) => {
const path = '$wasmPath'.slice(0, wasmPath.lastIndexOf('/'));
const response = await fetch(`\${path}/\${relativeToWasmFileUri}`);
return response.arrayBuffer();
};
dartMainRunner(() => {
instance.exports.\$invokeMain(helperInstance.exports.emptyExternRefArray());
});
}
loadAndRun('$wasmPath');
</script>
</body>
</html>""";
}
String dart2wasmHtml(
String title,
String wasmPath,
String mjsPath,
String supportJsPath,
) {
return """
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8">
@@ -674,13 +758,12 @@ String dart2wasmHtml(
const response = await fetch(`\${path}/\${relativeToWasmFileUri}`);
return response.arrayBuffer();
};
${standalone ? standaloneEmbedder : ''}
const appInstance = await compiledApp.instantiate($additionalImports, {
const appInstance = await compiledApp.instantiate({}, {
loadDeferredModules: (modules, handleWasmBytes) =>
Promise.all(modules.map((m) => fetch(m).then((b) => handleWasmBytes(m, b)))),
});
dartMainRunner(() => {
$mainInvocation
appInstance.invokeMain();
});
}
+9 -7
View File
@@ -1078,13 +1078,15 @@ class StandardTestSuite extends TestSuite {
Path('$outputDir/$nameNoExt.support.js'),
);
content = dart2wasmHtml(
testFile.path.toNativePath(),
wasmPath,
mjsPath,
supportJsPath,
configuration.isDart2wasmStandalone,
);
final title = testFile.path.toNativePath();
content = configuration.isDart2wasmStandalone
? dart2WasmStandaloneHtml(title, wasmPath)
: dart2wasmHtml(
testFile.path.toNativePath(),
wasmPath,
mjsPath,
supportJsPath,
);
} else if (configuration.compiler == Compiler.ddc) {
var ddcConfig =
configuration.compilerConfiguration as DevCompilerConfiguration;
+4 -3
View File
@@ -2,8 +2,9 @@
// 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;
import "dart:_internal" show patch, unsafeCast;
import "dart:_string" show StringUncheckedOperations;
import "dart:_string_helper" show skipLeadingWhitespace, skipTrailingWhitespace;
import "dart:_wasm";
import "dart:_error_utils";
@@ -53,11 +54,11 @@ class int {
int? radix,
int? Function(String)? onError,
) {
int end = source.lastNonWhitespace() + 1;
int end = skipTrailingWhitespace(source, source.length);
if (end == 0) {
return _handleFormatError(onError, source, source.length, radix, null);
}
int start = source.firstNonWhitespace();
int start = skipLeadingWhitespace(source, 0);
int first = source.codeUnitAtUnchecked(start);
int sign = 1;
+6 -202
View File
@@ -308,61 +308,7 @@ final class JSStringImpl extends js.JSExternWrapper
String Function(Match)? onMatch,
String Function(String)? onNonMatch,
}) {
if (onMatch == null) onMatch = _matchString;
if (onNonMatch == null) onNonMatch = _stringIdentity;
if (from is String) {
final patternLength = from.length;
if (patternLength == 0) {
// Pattern is the empty string.
StringBuffer buffer = StringBuffer();
int i = 0;
buffer.write(onNonMatch(""));
final length = this.length;
while (i < length) {
buffer.write(onMatch(StringMatch(i, this, "")));
// Special case to avoid splitting a surrogate pair.
int code = codeUnitAt(i);
if ((code & ~0x3FF) == 0xD800 && length > i + 1) {
// Leading surrogate;
code = codeUnitAt(i + 1);
if ((code & ~0x3FF) == 0xDC00) {
// Matching trailing surrogate.
buffer.write(onNonMatch(substring(i, i + 2)));
i += 2;
continue;
}
}
buffer.write(onNonMatch(this[i]));
i++;
}
buffer.write(onMatch(StringMatch(i, this, "")));
buffer.write(onNonMatch(""));
return buffer.toString();
}
StringBuffer buffer = StringBuffer();
int startIndex = 0;
final length = this.length;
while (startIndex < length) {
int position = indexOf(from, startIndex);
if (position == -1) {
break;
}
buffer.write(onNonMatch(substring(startIndex, position)));
buffer.write(onMatch(StringMatch(position, this, from)));
startIndex = position + patternLength;
}
buffer.write(onNonMatch(substring(startIndex)));
return buffer.toString();
}
StringBuffer buffer = StringBuffer();
int startIndex = 0;
for (Match match in from.allMatches(this)) {
buffer.write(onNonMatch(substring(startIndex, match.start)));
buffer.write(onMatch(match));
startIndex = match.end;
}
buffer.write(onNonMatch(substring(startIndex)));
return buffer.toString();
return splitMapJoinImpl(this, from, onMatch, onNonMatch);
}
String _replaceRange(int start, int end, String replacement) {
@@ -435,32 +381,7 @@ final class JSStringImpl extends js.JSExternWrapper
(re as js.JSValue).toExternRef,
);
} else {
final result = <String>[];
// End of most recent match. That is, start of next part to add to result.
int start = 0;
// Length of most recent match.
// Set >0, so no match on the empty string causes the result to be [""].
int length = 1;
for (var match in pattern.allMatches(this)) {
int matchStart = match.start;
int matchEnd = match.end;
length = matchEnd - matchStart;
if (length == 0 && start == matchStart) {
// An empty match right after another match is ignored.
// This includes an empty match at the start of the string.
continue;
}
int end = matchStart;
result.add(substring(start, end));
start = matchEnd;
}
if (start < this.length || length > 0) {
// An empty match at the end of the string does not cause a "" at the
// end. A non-empty match ending at the end of the string does add a
// "".
result.add(substring(start));
}
return result;
return genericSplitImpl(this, pattern);
}
}
@@ -514,98 +435,6 @@ final class JSStringImpl extends js.JSExternWrapper
: JSStringImpl.fromRefUnchecked(upperCaseRef);
}
// Characters with Whitespace property (Unicode 6.3).
// 0009..000D ; White_Space # Cc <control-0009>..<control-000D>
// 0020 ; White_Space # Zs SPACE
// 0085 ; White_Space # Cc <control-0085>
// 00A0 ; White_Space # Zs NO-BREAK SPACE
// 1680 ; White_Space # Zs OGHAM SPACE MARK
// 2000..200A ; White_Space # Zs EN QUAD..HAIR SPACE
// 2028 ; White_Space # Zl LINE SEPARATOR
// 2029 ; White_Space # Zp PARAGRAPH SEPARATOR
// 202F ; White_Space # Zs NARROW NO-BREAK SPACE
// 205F ; White_Space # Zs MEDIUM MATHEMATICAL SPACE
// 3000 ; White_Space # Zs IDEOGRAPHIC SPACE
//
// BOM: 0xFEFF
static bool _isWhitespace(int codeUnit) {
// Most codeUnits should be less than 256. Special case with a smaller
// switch.
if (codeUnit < 256) {
switch (codeUnit) {
case 0x09:
case 0x0A:
case 0x0B:
case 0x0C:
case 0x0D:
case 0x20:
case 0x85:
case 0xA0:
return true;
default:
return false;
}
}
switch (codeUnit) {
case 0x1680:
case 0x2000:
case 0x2001:
case 0x2002:
case 0x2003:
case 0x2004:
case 0x2005:
case 0x2006:
case 0x2007:
case 0x2008:
case 0x2009:
case 0x200A:
case 0x2028:
case 0x2029:
case 0x202F:
case 0x205F:
case 0x3000:
case 0xFEFF:
return true;
default:
return false;
}
}
static const int spaceCodeUnit = 0x20;
static const int carriageReturnCodeUnit = 0x0D;
static const int nelCodeUnit = 0x85;
/// Finds the index of the first non-whitespace character, or the
/// end of the string. Start looking at position [index].
static int _skipLeadingWhitespace(JSStringImpl string, int index) {
final stringLength = string.length;
while (index < stringLength) {
int codeUnit = string._codeUnitAtUnchecked(index);
if (codeUnit != spaceCodeUnit &&
codeUnit != carriageReturnCodeUnit &&
!_isWhitespace(codeUnit)) {
break;
}
index++;
}
return index;
}
/// Finds the index after the last non-whitespace character, or 0.
/// Start looking at position [index - 1].
static int _skipTrailingWhitespace(JSStringImpl string, int index) {
while (index > 0) {
int codeUnit = string._codeUnitAtUnchecked(index - 1);
if (codeUnit != spaceCodeUnit &&
codeUnit != carriageReturnCodeUnit &&
!_isWhitespace(codeUnit)) {
break;
}
index--;
}
return index;
}
// dart2wasm can't use JavaScript trim directly, because JavaScript does not
// trim the NEXT LINE (NEL) character (0x85).
@override
@@ -625,7 +454,7 @@ final class JSStringImpl extends js.JSExternWrapper
final int firstCode = result._codeUnitAtUnchecked(0);
int startIndex = 0;
if (firstCode == nelCodeUnit) {
startIndex = _skipLeadingWhitespace(result, 1);
startIndex = skipLeadingWhitespace(result, 1);
if (startIndex == resultLength) return "";
}
@@ -635,7 +464,7 @@ final class JSStringImpl extends js.JSExternWrapper
// Therefore we don't need to verify that endIndex > startIndex.
final int lastCode = result.codeUnitAt(endIndex - 1);
if (lastCode == nelCodeUnit) {
endIndex = _skipTrailingWhitespace(result, endIndex - 1);
endIndex = skipTrailingWhitespace(result, endIndex - 1);
}
if (startIndex == 0 && endIndex == resultLength) {
@@ -664,7 +493,7 @@ final class JSStringImpl extends js.JSExternWrapper
// Check NEL.
int firstCode = result._codeUnitAtUnchecked(0);
if (firstCode == nelCodeUnit) {
startIndex = _skipLeadingWhitespace(result, 1);
startIndex = skipLeadingWhitespace(result, 1);
}
if (startIndex == 0) {
@@ -692,7 +521,7 @@ final class JSStringImpl extends js.JSExternWrapper
int endIndex = resultLength;
int lastCode = result.codeUnitAt(endIndex - 1);
if (lastCode == nelCodeUnit) {
endIndex = _skipTrailingWhitespace(result, endIndex - 1);
endIndex = skipTrailingWhitespace(result, endIndex - 1);
}
if (endIndex == resultLength) {
@@ -850,33 +679,8 @@ final class JSStringImpl extends js.JSExternWrapper
@override
String toString() => this;
int firstNonWhitespace() {
final length = this.length;
int first = 0;
for (; first < length; first++) {
if (!_isWhitespace(_codeUnitAtUnchecked(first))) {
break;
}
}
return first;
}
int lastNonWhitespace() {
int last = length - 1;
for (; last >= 0; last--) {
if (!_isWhitespace(_codeUnitAtUnchecked(last))) {
break;
}
}
return last;
}
}
String _matchString(Match match) => match[0]!;
String _stringIdentity(String string) => string;
// NOTE: The [replacement] does not need special escaping it will be used
// as-is (due to passing `() => r` to the JS `replaceAll()` call)
JSStringImpl _jsStringReplaceAll(
+193 -2
View File
@@ -2,10 +2,11 @@
// 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 dart._string_match;
library dart._string_helper;
import "dart:_error_utils";
import "dart:_internal" show IterableElementError;
import "dart:_internal" show IterableElementError, unsafeCast;
import 'dart:_string' show StringUncheckedOperations;
class StringMatch implements Match {
const StringMatch(this.start, this.input, this.pattern);
@@ -96,3 +97,193 @@ int stringFinalizeHash(int hash) {
hash &= 0x3FFFFFFF;
return hash == 0 ? 1 : hash;
}
String splitMapJoinImpl(
String source,
Pattern from,
String Function(Match)? onMatch,
String Function(String)? onNonMatch,
) {
if (onMatch == null) onMatch = _matchString;
if (onNonMatch == null) onNonMatch = _stringIdentity;
if (from is String) {
final patternLength = from.length;
if (patternLength == 0) {
// Pattern is the empty string.
StringBuffer buffer = StringBuffer();
int i = 0;
buffer.write(onNonMatch(""));
final length = source.length;
while (i < length) {
buffer.write(onMatch(StringMatch(i, source, "")));
// Special case to avoid splitting a surrogate pair.
int code = source.codeUnitAt(i);
if ((code & ~0x3FF) == 0xD800 && length > i + 1) {
// Leading surrogate;
code = source.codeUnitAt(i + 1);
if ((code & ~0x3FF) == 0xDC00) {
// Matching trailing surrogate.
buffer.write(onNonMatch(source.substring(i, i + 2)));
i += 2;
continue;
}
}
buffer.write(onNonMatch(source[i]));
i++;
}
buffer.write(onMatch(StringMatch(i, source, "")));
buffer.write(onNonMatch(""));
return buffer.toString();
}
StringBuffer buffer = StringBuffer();
int startIndex = 0;
final length = source.length;
while (startIndex < length) {
int position = source.indexOf(from, startIndex);
if (position == -1) {
break;
}
buffer.write(onNonMatch(source.substring(startIndex, position)));
buffer.write(onMatch(StringMatch(position, source, from)));
startIndex = position + patternLength;
}
buffer.write(onNonMatch(source.substring(startIndex)));
return buffer.toString();
}
StringBuffer buffer = StringBuffer();
int startIndex = 0;
for (Match match in from.allMatches(source)) {
buffer.write(onNonMatch(source.substring(startIndex, match.start)));
buffer.write(onMatch(match));
startIndex = match.end;
}
buffer.write(onNonMatch(source.substring(startIndex)));
return buffer.toString();
}
/// Implementation of [String.split] for patterns where no specialized JS
/// implementation exists.
List<String> genericSplitImpl(String source, Pattern pattern) {
final result = <String>[];
// End of most recent match. That is, start of next part to add to result.
int start = 0;
// Length of most recent match.
// Set >0, so no match on the empty string causes the result to be [""].
int length = 1;
for (var match in pattern.allMatches(source)) {
int matchStart = match.start;
int matchEnd = match.end;
length = matchEnd - matchStart;
if (length == 0 && start == matchStart) {
// An empty match right after another match is ignored.
// This includes an empty match at the start of the string.
continue;
}
int end = matchStart;
result.add(source.substring(start, end));
start = matchEnd;
}
if (start < source.length || length > 0) {
// An empty match at the end of the string does not cause a "" at the
// end. A non-empty match ending at the end of the string does add a
// "".
result.add(source.substring(start));
}
return result;
}
// Characters with Whitespace property (Unicode 6.3).
// 0009..000D ; White_Space # Cc <control-0009>..<control-000D>
// 0020 ; White_Space # Zs SPACE
// 0085 ; White_Space # Cc <control-0085>
// 00A0 ; White_Space # Zs NO-BREAK SPACE
// 1680 ; White_Space # Zs OGHAM SPACE MARK
// 2000..200A ; White_Space # Zs EN QUAD..HAIR SPACE
// 2028 ; White_Space # Zl LINE SEPARATOR
// 2029 ; White_Space # Zp PARAGRAPH SEPARATOR
// 202F ; White_Space # Zs NARROW NO-BREAK SPACE
// 205F ; White_Space # Zs MEDIUM MATHEMATICAL SPACE
// 3000 ; White_Space # Zs IDEOGRAPHIC SPACE
//
// BOM: 0xFEFF
bool isWhitespace(int codeUnit) {
// Most codeUnits should be less than 256. Special case with a smaller
// switch.
if (codeUnit < 256) {
switch (codeUnit) {
case 0x09:
case 0x0A:
case 0x0B:
case 0x0C:
case 0x0D:
case 0x20:
case 0x85:
case 0xA0:
return true;
default:
return false;
}
}
switch (codeUnit) {
case 0x1680:
case 0x2000:
case 0x2001:
case 0x2002:
case 0x2003:
case 0x2004:
case 0x2005:
case 0x2006:
case 0x2007:
case 0x2008:
case 0x2009:
case 0x200A:
case 0x2028:
case 0x2029:
case 0x202F:
case 0x205F:
case 0x3000:
case 0xFEFF:
return true;
default:
return false;
}
}
const int spaceCodeUnit = 0x20;
const int carriageReturnCodeUnit = 0x0D;
const int nelCodeUnit = 0x85;
/// Finds the index of the first non-whitespace character, or the
/// end of the string. Start looking at position [index].
int skipLeadingWhitespace(String string, int index) {
final stringLength = string.length;
while (index < stringLength) {
int codeUnit = string.codeUnitAtUnchecked(index);
if (codeUnit != spaceCodeUnit &&
codeUnit != carriageReturnCodeUnit &&
!isWhitespace(codeUnit)) {
break;
}
index++;
}
return index;
}
/// Finds the index after the last non-whitespace character, or 0.
/// Start looking at position [index - 1] to [from].
int skipTrailingWhitespace(String string, int index, [int from = 0]) {
while (index > from) {
int codeUnit = string.codeUnitAtUnchecked(index - 1);
if (codeUnit != spaceCodeUnit &&
codeUnit != carriageReturnCodeUnit &&
!isWhitespace(codeUnit)) {
break;
}
index--;
}
return index;
}
String _matchString(Match match) => match[0]!;
String _stringIdentity(String string) => string;
@@ -208,10 +208,3 @@ class String {
return JSStringImpl.fromCodePoint(charCode);
}
}
extension _StringExt on String {
int firstNonWhitespace() =>
unsafeCast<JSStringImpl>(this).firstNonWhitespace();
int lastNonWhitespace() => unsafeCast<JSStringImpl>(this).lastNonWhitespace();
}
@@ -120,10 +120,3 @@ class String {
);
}
}
extension _StringExt on String {
int firstNonWhitespace() =>
unsafeCast<JSStringImpl>(this).firstNonWhitespace();
int lastNonWhitespace() => unsafeCast<JSStringImpl>(this).lastNonWhitespace();
}
@@ -4,8 +4,8 @@
import "dart:_embedder" as embedder;
import "dart:_internal" show patch, unsafeCast;
import "dart:_js_helper" show jsStringFromDartString, JSExternWrapperExt;
import "dart:_string";
import "dart:_string_helper";
import "dart:_wasm";
@patch
@@ -21,12 +21,13 @@ class _Utf8Decoder {
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());
final externRef = unsafeCast<JSStringImpl>(string).wrappedExternRef;
embedder.stringToCodeUnits(externRef, array, 0.toWasmI32());
} else {
for (int i = 0; i < end; ++i) array.write(i, jsCharCodeAt(externRef, i));
for (int i = 0; i < end; ++i)
array.write(i, string.codeUnitAtUnchecked(i));
}
return array;
@@ -4,7 +4,7 @@
import 'dart:_embedder' as embedder;
import 'dart:_internal' show patch;
import 'dart:_js_helper' show jsStringFromDartString, JSExternWrapperExt;
import 'dart:_string' show embedderStringFromDartString;
import 'dart:_wasm';
import 'dart:async' show Zone;
import 'dart:isolate';
@@ -15,7 +15,7 @@ bool debugger({bool when = true, String? message}) {
embedder.debugger(
message == null
? WasmExternRef.nullRef
: jsStringFromDartString(message).wrappedExternRef,
: embedderStringFromDartString(message).wrappedExternRef,
);
}
@@ -193,7 +193,7 @@ void _reportTaskEvent(
WasmI32.fromInt(taskId),
WasmI32.fromInt(flowId),
WasmI32.fromInt(type),
jsStringFromDartString(name).wrappedExternRef,
jsStringFromDartString(argumentsAsJson).wrappedExternRef,
embedderStringFromDartString(name).wrappedExternRef,
embedderStringFromDartString(argumentsAsJson).wrappedExternRef,
);
}
@@ -4,7 +4,7 @@
import 'dart:_embedder';
import 'dart:_internal' show patch;
import 'dart:_js_helper';
import 'dart:_string';
import 'dart:_wasm';
@patch
@@ -21,7 +21,7 @@ class double {
@patch
static double? tryParse(String source) {
final parseResult = doubleTryParse(
jsStringFromDartString(source).wrappedExternRef,
embedderStringFromDartString(source).wrappedExternRef,
);
if (parseResult.isNull) {
return null;
@@ -74,6 +74,75 @@ external WasmExternRef stringFromAsciiBytes(
WasmI32 length,
);
@pragma("wasm:import", "dart.stringLength")
external WasmI32 stringLength(WasmExternRef? string);
@pragma("wasm:import", "dart.stringEquals")
external WasmI32 stringEquals(WasmExternRef? a, WasmExternRef? b);
@pragma("wasm:import", "dart.stringCompare")
external WasmI32 stringCompare(WasmExternRef? a, WasmExternRef? b);
@pragma("wasm:import", "dart.stringCodeUnitAt")
external WasmI32 stringCodeUnitAt(WasmExternRef? a, WasmI32 index);
@pragma("wasm:import", "dart.stringIndexOfString")
external WasmI32 stringIndexOfString(
WasmExternRef? a,
WasmExternRef? b,
WasmI32 start,
);
@pragma("wasm:import", "dart.stringLastIndexOfString")
external WasmI32 stringLastIndexOfString(
WasmExternRef? a,
WasmExternRef? b,
WasmI32 start,
);
/// Specialization for [String.replaceAll] where the pattern is an embedder-
/// managed string.
@pragma("wasm:import", "dart.stringReplaceAllString")
external WasmExternRef? stringReplaceAllString(
WasmExternRef? string,
WasmExternRef? needle,
WasmExternRef? replacement,
);
/// Specialization for [String.replaceAll] where the pattern is an embedder-
/// managed regular expression.
@pragma("wasm:import", "dart.stringReplaceAllRegExp")
external WasmExternRef? stringReplaceAllRegExp(
WasmExternRef? string,
WasmExternRef? needle,
WasmExternRef? replacement,
);
@pragma("wasm:import", "dart.stringSubstring")
external WasmExternRef? stringSubstring(
WasmExternRef? a,
WasmI32 start,
WasmI32 end,
);
@pragma("wasm:import", "dart.stringToLowerCase")
external WasmExternRef? stringToLowerCase(WasmExternRef? string);
@pragma("wasm:import", "dart.stringToUpperCase")
external WasmExternRef? stringToUpperCase(WasmExternRef? string);
@pragma("wasm:import", "dart.stringConcat")
external WasmExternRef? stringConcat(WasmExternRef? a, WasmExternRef? b);
@pragma("wasm:import", "dart.stringRepeat")
external WasmExternRef? stringRepeat(WasmExternRef? string, WasmI32 times);
@pragma("wasm:import", "dart.stringReplaceRange")
external WasmExternRef? stringReplaceRange(
WasmExternRef? string,
WasmI32 start,
WasmI32 end,
WasmExternRef? replacement,
);
/// Writes code units of [string] into [outArray], starting at array position
/// [startIndex].
@pragma("wasm:import", "dart.stringToCodeUnits")
external WasmVoid stringToCodeUnits(
WasmExternRef? string,
WasmArray<WasmI16> outArray,
WasmI32 startIndex,
);
/// Get the frequency of ticks reported by [monotonicClockTicks] in Hz.
///
/// Currently, the only supported values are 1kHz and 1MHz. Attempting to use
@@ -0,0 +1,532 @@
// 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:_error_utils';
import 'dart:_internal';
import 'dart:_object_helper';
import 'dart:_string_helper';
import 'dart:_wasm';
part 'regexp.dart';
abstract class StringUncheckedOperationsBase {
int _codeUnitAtUnchecked(int index);
String _substringUnchecked(int start, int end);
}
extension StringUncheckedOperations on String {
@pragma('wasm:prefer-inline')
int codeUnitAtUnchecked(int index) =>
unsafeCast<StringUncheckedOperationsBase>(
this,
)._codeUnitAtUnchecked(index);
@pragma('wasm:prefer-inline')
String substringUnchecked(int start, int end) =>
unsafeCast<StringUncheckedOperationsBase>(
this,
)._substringUnchecked(start, end);
}
/// A string managed by the WebAssembly embedder.
///
/// This is not necessarily a JavaScript string, but the `JSStringImpl` name is
/// referenced a lot in the compiler and since this class and it's counterpart
/// in the JS target have the same structure (wrapping an externref), adopting
/// the same name avoids conditional names in the compiler.
final class JSStringImpl implements String, StringUncheckedOperationsBase {
WasmExternRef? _ref;
JSStringImpl.fromRefUnchecked(this._ref);
WasmExternRef? get wrappedExternRef => _ref;
@override
@pragma("wasm:prefer-inline")
int get length => embedder.stringLength(_ref).toIntUnsigned();
@override
@pragma("wasm:prefer-inline")
bool get isEmpty => length == 0;
@override
@pragma("wasm:prefer-inline")
bool get isNotEmpty => !isEmpty;
@pragma("wasm:entry-point")
static String _interpolate(WasmArray<Object?> values) {
final valuesLength = values.length;
final result = StringBuffer();
for (int i = 0; i < valuesLength; i++) {
result.write(values[i].toString());
}
return result.toString();
}
@pragma("wasm:entry-point", "call")
static String _interpolate1(Object? value) {
return value is String ? value : value.toString();
}
@pragma("wasm:entry-point", "call")
static String _interpolate2(Object? value1, Object? value2) {
return (StringBuffer(
value1 is String ? value1 : value1.toString(),
)..write(value2 is String ? value2 : value2.toString())).toString();
}
@pragma("wasm:entry-point", "call")
static String _interpolate3(Object? value1, Object? value2, Object? value3) {
return (StringBuffer(value1 is String ? value1 : value1.toString())
..write(value2 is String ? value2 : value2.toString())
..write(value3 is String ? value3 : value3.toString()))
.toString();
}
@pragma("wasm:entry-point", "call")
static String _interpolate4(
Object? value1,
Object? value2,
Object? value3,
Object? value4,
) {
return (StringBuffer(value1 is String ? value1 : value1.toString())
..write(value2 is String ? value2 : value2.toString())
..write(value3 is String ? value3 : value3.toString())
..write(value4 is String ? value4 : value4.toString()))
.toString();
}
static JSStringImpl fromAsciiBytes(
WasmArray<WasmI8> source,
int start,
int end,
) {
final length = WasmI32.fromInt(end - start);
return JSStringImpl.fromRefUnchecked(
embedder.stringFromAsciiBytes(source, WasmI32.fromInt(start), length),
);
}
static JSStringImpl fromCharCodeArray(
WasmArray<WasmI16> source,
int start,
int end,
) {
return JSStringImpl.fromRefUnchecked(
embedder.stringFromCharCodeArray(
source,
WasmI32.fromInt(start),
WasmI32.fromInt(end - start),
),
);
}
@pragma("wasm:initialize-at-startup")
static final _stringFromCodePointBuffer = WasmArray<WasmI16>(2);
static JSStringImpl fromCharCode(int charCode) {
final array = _stringFromCodePointBuffer;
array.write(0, charCode);
return JSStringImpl.fromCharCodeArray(array, 0, 1);
}
static JSStringImpl fromCodePoint(int codePoint) {
final array = _stringFromCodePointBuffer;
if (codePoint <= 0xffff) {
array.write(0, codePoint);
return JSStringImpl.fromCharCodeArray(array, 0, 1);
}
final low = 0xDC00 | (codePoint & 0x3ff);
final high = 0xD7C0 + (codePoint >> 10);
array.write(0, high);
array.write(1, low);
return JSStringImpl.fromCharCodeArray(array, 0, 2);
}
@override
@pragma("wasm:prefer-inline")
int codeUnitAt(int index) {
final length = this.length;
IndexErrorUtils.checkIndex(index, length);
return _codeUnitAtUnchecked(index);
}
@override
@pragma("wasm:prefer-inline")
int _codeUnitAtUnchecked(int index) {
return embedder
.stringCodeUnitAt(wrappedExternRef, WasmI32.fromInt(index))
.toIntUnsigned();
}
@override
Iterable<Match> allMatches(String string, [int start = 0]) {
final stringLength = string.length;
RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(start, stringLength);
return StringAllMatchesIterable(string, this, start);
}
@override
Match? matchAsPrefix(String string, [int start = 0]) {
final stringLength = string.length;
RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(start, stringLength);
final length = this.length;
if (start + length > stringLength) return null;
// TODO(lrn): See if this can be optimized.
for (int i = 0; i < length; i++) {
if (string.codeUnitAt(start + i) != codeUnitAt(i)) {
return null;
}
}
return StringMatch(start, string, this);
}
@override
@pragma('wasm:pure-function')
String operator +(String other) {
return JSStringImpl.fromRefUnchecked(
embedder.stringConcat(
wrappedExternRef,
unsafeCast<JSStringImpl>(other).wrappedExternRef,
),
);
}
@override
bool endsWith(String other) {
final otherLength = other.length;
final length = this.length;
if (otherLength > length) return false;
return other == _substringUnchecked(length - otherLength, length);
}
@override
String replaceAll(Pattern from, String to) {
if (from is String) {
if (from.isEmpty) {
if (isEmpty) return to;
StringBuffer result = StringBuffer();
result.write(to);
final length = this.length;
for (int i = 0; i < length; i++) {
result.write(this[i]);
result.write(to);
}
return result.toString();
}
return JSStringImpl.fromRefUnchecked(
embedder.stringReplaceAllString(
wrappedExternRef,
unsafeCast<JSStringImpl>(from).wrappedExternRef,
unsafeCast<JSStringImpl>(to).wrappedExternRef,
),
);
} else if (from is EmbedderRegExp) {
return JSStringImpl.fromRefUnchecked(
embedder.stringReplaceAllRegExp(
wrappedExternRef,
from._regexp,
unsafeCast<JSStringImpl>(to).wrappedExternRef,
),
);
} else {
int startIndex = 0;
StringBuffer result = StringBuffer();
for (Match match in from.allMatches(this)) {
result.write(substring(startIndex, match.start));
result.write(to);
startIndex = match.end;
}
result.write(substring(startIndex));
return result.toString();
}
}
@override
String replaceAllMapped(Pattern from, String Function(Match) convert) {
return splitMapJoin(from, onMatch: convert);
}
@override
String splitMapJoin(
Pattern from, {
String Function(Match)? onMatch,
String Function(String)? onNonMatch,
}) {
return splitMapJoinImpl(this, from, onMatch, onNonMatch);
}
String _replaceRange(int start, int end, String replacement) {
return JSStringImpl.fromRefUnchecked(
embedder.stringReplaceRange(
wrappedExternRef,
WasmI32.fromInt(start),
WasmI32.fromInt(end),
unsafeCast<JSStringImpl>(replacement).wrappedExternRef,
),
);
}
@override
String replaceFirst(Pattern from, String to, [int startIndex = 0]) {
Iterator<Match> matches = from.allMatches(this, startIndex).iterator;
if (!matches.moveNext()) return this;
Match match = matches.current;
return replaceRange(match.start, match.end, to);
}
@override
String replaceFirstMapped(
Pattern from,
String replace(Match match), [
int startIndex = 0,
]) {
RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(startIndex, length);
final Iterator<Match> matches = from.allMatches(this, startIndex).iterator;
if (!matches.moveNext()) return this;
final Match match = matches.current;
return replaceRange(match.start, match.end, replace(match));
}
@override
List<String> split(Pattern pattern) {
return genericSplitImpl(this, pattern);
}
@override
String replaceRange(int start, int? end, String replacement) {
end ??= length;
RangeErrorUtils.checkValidRange(start, end, length);
return _replaceRange(start, end, replacement);
}
@override
bool startsWith(Pattern pattern, [int index = 0]) {
RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(index, length);
if (pattern is String) {
final patternLength = pattern.length;
final endIndex = index + patternLength;
if (endIndex > length) return false;
return pattern == substring(index, endIndex);
}
return pattern.matchAsPrefix(this, index) != null;
}
@override
String substring(int start, [int? end]) {
end ??= length;
RangeErrorUtils.checkValidRange(start, end, length);
if (start == end) return "";
return _substringUnchecked(start, end);
}
@override
@pragma('wasm:prefer-inline')
String _substringUnchecked(int start, int end) =>
JSStringImpl.fromRefUnchecked(
embedder.stringSubstring(
wrappedExternRef,
WasmI32.fromInt(start),
WasmI32.fromInt(end),
),
);
@override
String toLowerCase() {
final toLower = embedder.stringToLowerCase(wrappedExternRef);
if (embedder.stringEquals(toLower, wrappedExternRef).toBool()) {
return this;
} else {
return JSStringImpl.fromRefUnchecked(toLower);
}
}
@override
String toUpperCase() {
final toUpper = embedder.stringToUpperCase(wrappedExternRef);
if (embedder.stringEquals(toUpper, wrappedExternRef).toBool()) {
return this;
} else {
return JSStringImpl.fromRefUnchecked(toUpper);
}
}
String _trim(bool left, bool right) {
if (isEmpty) return this;
var start = 0, end = length;
if (left) start = skipLeadingWhitespace(this, 0);
if (right) end = skipTrailingWhitespace(this, length, start);
if (start >= end) return '';
if (start == 0 && end == length) return this;
return _substringUnchecked(start, end);
}
@override
String trim() {
return _trim(true, true);
}
@override
String trimLeft() {
return _trim(true, false);
}
@override
String trimRight() {
return _trim(false, true);
}
@override
String operator *(int times) {
if (0 >= times) return '';
if (times == 1 || length == 0) return this;
if (times & 0x7fffffff != times) {
throw Exception(
'The implementation cannot handle very large operands (was: $times).',
);
}
return JSStringImpl.fromRefUnchecked(
embedder.stringRepeat(wrappedExternRef, WasmI32.fromInt(times)),
);
}
@override
String padLeft(int width, [String padding = ' ']) {
int delta = width - length;
if (delta <= 0) return this;
return (padding * delta) + this;
}
@override
String padRight(int width, [String padding = ' ']) {
int delta = width - length;
if (delta <= 0) return this;
return this + (padding * delta);
}
@override
List<int> get codeUnits => CodeUnits(this);
@override
Runes get runes => Runes(this);
@override
int indexOf(Pattern pattern, [int start = 0]) {
final length = this.length;
RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(start, length);
if (pattern is JSStringImpl) {
return embedder
.stringIndexOfString(
wrappedExternRef,
pattern.wrappedExternRef,
WasmI32.fromInt(start),
)
.toIntSigned();
} else if (pattern is EmbedderRegExp) {
final match = pattern._search(this, start, false);
return match?.start ?? -1;
} else {
for (int i = start; i <= length; i++) {
if (pattern.matchAsPrefix(this, i) != null) return i;
}
return -1;
}
}
@override
int lastIndexOf(Pattern pattern, [int? start]) {
final length = this.length;
if (start == null) {
start = length;
} else {
RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(start, length);
}
if (pattern is JSStringImpl) {
if (start + pattern.length > length) {
start = length - pattern.length;
}
return embedder
.stringLastIndexOfString(
wrappedExternRef,
pattern.wrappedExternRef,
WasmI32.fromInt(start),
)
.toIntSigned();
}
for (int i = start; i >= 0; i--) {
if (pattern.matchAsPrefix(this, i) != null) return i;
}
return -1;
}
@override
bool contains(Pattern other, [int startIndex = 0]) {
final length = this.length;
RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(startIndex, length);
if (other is String || other is EmbedderRegExp) {
return indexOf(other, startIndex) >= 0;
} else {
return other.allMatches(substring(startIndex)).isNotEmpty;
}
}
@override
int get hashCode {
int hash = getIdentityHashField(this);
if (hash != 0) return hash;
hash = _computeHashCode();
setIdentityHashField(this, hash);
return hash;
}
/// This must be kept in sync with `StringBase.hashCode` in string_patch.dart.
int _computeHashCode() {
int hash = 0;
final length = this.length;
for (int i = 0; i < length; i++) {
hash = stringCombineHashes(hash, _codeUnitAtUnchecked(i));
}
return stringFinalizeHash(hash);
}
@override
@pragma("wasm:prefer-inline")
String operator [](int index) {
IndexErrorUtils.checkIndex(index, length);
return JSStringImpl.fromCharCode(_codeUnitAtUnchecked(index));
}
@override
@pragma('wasm:prefer-inline')
bool operator ==(Object other) =>
other is JSStringImpl && embedder.stringEquals(_ref, other._ref).toBool();
@override
@pragma('wasm:prefer-inline')
int compareTo(String other) => embedder
.stringCompare(
wrappedExternRef,
unsafeCast<JSStringImpl>(other).wrappedExternRef,
)
.toIntSigned();
@override
String toString() => this;
}
String _matchString(Match match) => match[0]!;
String _stringIdentity(String string) => string;
@patch
@pragma('wasm:prefer-inline')
JSStringImpl embedderStringFromDartString(String s) {
return unsafeCast<JSStringImpl>(s);
}
@@ -3,10 +3,11 @@
// 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:_string' show embedderStringFromDartString, JSStringImpl;
import 'dart:_wasm';
String jsonEncode(String object) => JSStringImpl.fromRef(
embedder.jsonEncodeString(jsStringFromDartString(object).wrappedExternRef),
String jsonEncode(String object) => JSStringImpl.fromRefUnchecked(
embedder.jsonEncodeString(
embedderStringFromDartString(object).wrappedExternRef,
),
);
@@ -0,0 +1,9 @@
// 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.
// dart:js_interop is not available on this target, but it's referenced in
// dart:_wasm.
// TODO(63166): Untangle these libraries.
typedef JSAny = Never;
@@ -3,8 +3,8 @@
// 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 embedderStringFromDartString;
@patch
void printToConsole(String line) =>
embedder.print(jsStringFromDartString(line).wrappedExternRef);
embedder.print(embedderStringFromDartString(line).wrappedExternRef);
@@ -0,0 +1,216 @@
// 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.
part of 'dart:_string';
final class EmbedderRegExp implements RegExp {
WasmExternRef? _regexp = WasmExternRef.nullRef;
@override
final String pattern;
@override
final bool isMultiLine;
@override
final bool isCaseSensitive;
@override
final bool isUnicode;
@override
final bool isDotAll;
EmbedderRegExp(
this.pattern,
this.isMultiLine,
this.isCaseSensitive,
this.isUnicode,
this.isDotAll,
) {
final compiled = embedder.regexpCreateOrFailWithString(
embedderStringFromDartString(pattern).wrappedExternRef,
WasmI32.fromBool(isMultiLine),
WasmI32.fromBool(isCaseSensitive),
WasmI32.fromBool(isUnicode),
WasmI32.fromBool(isDotAll),
);
if (!embedder.regexpIsRegexp(compiled).toBool()) {
// The returned value is the stringified JavaScript exception. Turn it
// into a Dart exception.
final errorMessage = JSStringImpl.fromRefUnchecked(compiled);
throw FormatException('Illegal RegExp pattern ($errorMessage)', pattern);
}
this._regexp = compiled;
}
@override
String toString() {
final buffer = StringBuffer('RegExp/');
buffer.write(pattern);
buffer.write('/');
if (isMultiLine) buffer.write('m');
if (!isCaseSensitive) buffer.write('i');
if (isUnicode) buffer.write('u');
if (isDotAll) buffer.write('s');
return buffer.toString();
}
@override
Iterable<RegExpMatch> allMatches(String input, [int start = 0]) {
RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(start, input.length);
return Iterable.withIterator(
() => _EmbedderMatchesIterator(this, input, start),
);
}
@override
RegExpMatch? firstMatch(String input) {
return _search(input, 0, false);
}
@override
bool hasMatch(String input) {
return firstMatch(input) != null;
}
@override
Match? matchAsPrefix(String string, [int start = 0]) {
return _search(string, start, true);
}
_EmbedderMatch? _search(String string, int start, bool exactStartIndex) {
RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(start, string.length);
final match = embedder.regexpMatch(
_regexp,
embedderStringFromDartString(string).wrappedExternRef,
WasmI32.fromInt(start),
WasmI32.fromBool(exactStartIndex),
);
if (match.isNull) {
return null;
}
return _EmbedderMatch(this, string).._match = match;
}
@override
String? stringMatch(String input) {
var match = firstMatch(input);
if (match != null) return match[0];
return null;
}
}
final class _EmbedderMatch implements RegExpMatch {
@override
final EmbedderRegExp pattern;
@override
final String input;
WasmExternRef? _match = WasmExternRef.nullRef;
_EmbedderMatch(this.pattern, this.input);
@override
String? operator [](int group) {
return this.group(group);
}
@override
int get start => embedder.regexpMatchGetStart(_match).toIntUnsigned();
@override
int get end => embedder.regexpMatchGetEnd(_match).toIntUnsigned();
@override
int get groupCount =>
embedder.regexpMatchGetGroupCount(_match).toIntUnsigned();
@override
String? group(int group) {
IndexErrorUtils.checkIndex(group, groupCount + 1);
final contents = embedder.regexpMatchGetGroup(
_match,
WasmI32.fromInt(group),
);
return contents.isNull ? null : JSStringImpl.fromRefUnchecked(contents);
}
@override
List<String?> groups(List<int> groupIndices) {
return [for (final index in groupIndices) group(index)];
}
@override
late final List<String> groupNames = List.generate(
embedder.regexpMatchGetNamedGroups(_match).toIntUnsigned(),
(i) {
return JSStringImpl.fromRefUnchecked(
embedder.regexpMatchGetGroupName(_match, WasmI32.fromInt(i)),
);
},
);
@override
String? namedGroup(String name) {
final groupIndex = groupNames.indexOf(name);
if (groupIndex < 0) {
throw ArgumentError.value(name, "name", "Not a capture group name");
}
final contents = embedder.regexpMatchGetGroupByName(
_match,
WasmI32.fromInt(groupIndex),
);
return contents.isNull ? null : JSStringImpl.fromRefUnchecked(contents);
}
}
class _EmbedderMatchesIterator implements Iterator<RegExpMatch> {
final EmbedderRegExp _regExp;
String? _string;
int _nextIndex;
RegExpMatch? _current;
_EmbedderMatchesIterator(this._regExp, this._string, this._nextIndex);
RegExpMatch get current => _current as RegExpMatch;
static bool _isLeadSurrogate(int c) {
return c >= 0xd800 && c <= 0xdbff;
}
static bool _isTrailSurrogate(int c) {
return c >= 0xdc00 && c <= 0xdfff;
}
bool moveNext() {
var string = _string;
if (string == null) return false;
if (_nextIndex <= string.length) {
final match = _regExp._search(_string!, _nextIndex, false);
if (match != null) {
_current = match;
int nextIndex = match.end;
if (match.start == nextIndex) {
// Zero-width match. Advance by one more, unless the regexp
// is in unicode mode and it would put us within a surrogate
// pair. In that case, advance past the code point as a whole.
if (_regExp.isUnicode &&
_nextIndex + 1 < string.length &&
_isLeadSurrogate(string.codeUnitAt(_nextIndex)) &&
_isTrailSurrogate(string.codeUnitAt(_nextIndex + 1))) {
nextIndex++;
}
nextIndex++;
}
_nextIndex = nextIndex;
return true;
}
}
_current = null;
_string = null; // Marks iteration as ended.
return false;
}
}
@@ -5,7 +5,6 @@
import 'dart:_embedder';
import 'dart:_error_utils';
import 'dart:_internal' show patch;
import 'dart:_js_helper';
import 'dart:_string';
import 'dart:_wasm';
@@ -19,220 +18,13 @@ class RegExp {
bool unicode = false,
bool dotAll = false,
}) {
return _EmbedderRegExp(source, multiLine, caseSensitive, unicode, dotAll);
return EmbedderRegExp(source, multiLine, caseSensitive, unicode, dotAll);
}
@patch
static String escape(String text) {
return JSStringImpl.fromRefUnchecked(
regexpEscape(jsStringFromDartString(text).wrappedExternRef),
regexpEscape(embedderStringFromDartString(text).wrappedExternRef),
);
}
}
final class _EmbedderRegExp implements RegExp {
WasmExternRef? _regexp = WasmExternRef.nullRef;
@override
final String pattern;
@override
final bool isMultiLine;
@override
final bool isCaseSensitive;
@override
final bool isUnicode;
@override
final bool isDotAll;
_EmbedderRegExp(
this.pattern,
this.isMultiLine,
this.isCaseSensitive,
this.isUnicode,
this.isDotAll,
) {
final compiled = regexpCreateOrFailWithString(
jsStringFromDartString(pattern).wrappedExternRef,
WasmI32.fromBool(isMultiLine),
WasmI32.fromBool(isCaseSensitive),
WasmI32.fromBool(isUnicode),
WasmI32.fromBool(isDotAll),
);
if (!regexpIsRegexp(compiled).toBool()) {
// The returned value is the stringified JavaScript exception. Turn it
// into a Dart exception.
final errorMessage = JSStringImpl.fromRefUnchecked(compiled);
throw FormatException('Illegal RegExp pattern ($errorMessage)', pattern);
}
this._regexp = compiled;
}
@override
String toString() {
final buffer = StringBuffer('RegExp/');
buffer.write(pattern);
buffer.write('/');
if (isMultiLine) buffer.write('m');
if (!isCaseSensitive) buffer.write('i');
if (isUnicode) buffer.write('u');
if (isDotAll) buffer.write('s');
return buffer.toString();
}
@override
Iterable<RegExpMatch> allMatches(String input, [int start = 0]) {
RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(start, input.length);
return Iterable.withIterator(
() => _EmbedderMatchesIterator(this, input, start),
);
}
@override
RegExpMatch? firstMatch(String input) {
return _search(input, 0, false);
}
@override
bool hasMatch(String input) {
return firstMatch(input) != null;
}
@override
Match? matchAsPrefix(String string, [int start = 0]) {
return _search(string, start, true);
}
_EmbedderMatch? _search(String string, int start, bool exactStartIndex) {
RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(start, string.length);
final match = regexpMatch(
_regexp,
jsStringFromDartString(string).wrappedExternRef,
WasmI32.fromInt(start),
WasmI32.fromBool(exactStartIndex),
);
if (match.isNull) {
return null;
}
return _EmbedderMatch(this, string).._match = match;
}
@override
String? stringMatch(String input) {
var match = firstMatch(input);
if (match != null) return match[0];
return null;
}
}
final class _EmbedderMatch implements RegExpMatch {
@override
final _EmbedderRegExp pattern;
@override
final String input;
WasmExternRef? _match = WasmExternRef.nullRef;
_EmbedderMatch(this.pattern, this.input);
@override
String? operator [](int group) {
return this.group(group);
}
@override
int get start => regexpMatchGetStart(_match).toIntUnsigned();
@override
int get end => regexpMatchGetEnd(_match).toIntUnsigned();
@override
int get groupCount => regexpMatchGetGroupCount(_match).toIntUnsigned();
@override
String? group(int group) {
IndexErrorUtils.checkIndex(group, groupCount + 1);
final contents = regexpMatchGetGroup(_match, WasmI32.fromInt(group));
return contents.isNull ? null : JSStringImpl.fromRefUnchecked(contents);
}
@override
List<String?> groups(List<int> groupIndices) {
return [for (final index in groupIndices) group(index)];
}
@override
late final List<String> groupNames = List.generate(
regexpMatchGetNamedGroups(_match).toIntUnsigned(),
(i) {
return JSStringImpl.fromRefUnchecked(
regexpMatchGetGroupName(_match, WasmI32.fromInt(i)),
);
},
);
@override
String? namedGroup(String name) {
final groupIndex = groupNames.indexOf(name);
if (groupIndex < 0) {
throw ArgumentError.value(name, "name", "Not a capture group name");
}
final contents = regexpMatchGetGroupByName(
_match,
WasmI32.fromInt(groupIndex),
);
return contents.isNull ? null : JSStringImpl.fromRefUnchecked(contents);
}
}
class _EmbedderMatchesIterator implements Iterator<RegExpMatch> {
final _EmbedderRegExp _regExp;
String? _string;
int _nextIndex;
RegExpMatch? _current;
_EmbedderMatchesIterator(this._regExp, this._string, this._nextIndex);
RegExpMatch get current => _current as RegExpMatch;
static bool _isLeadSurrogate(int c) {
return c >= 0xd800 && c <= 0xdbff;
}
static bool _isTrailSurrogate(int c) {
return c >= 0xdc00 && c <= 0xdfff;
}
bool moveNext() {
var string = _string;
if (string == null) return false;
if (_nextIndex <= string.length) {
final match = _regExp._search(_string!, _nextIndex, false);
if (match != null) {
_current = match;
int nextIndex = match.end;
if (match.start == nextIndex) {
// Zero-width match. Advance by one more, unless the regexp
// is in unicode mode and it would put us within a surrogate
// pair. In that case, advance past the code point as a whole.
if (_regExp.isUnicode &&
_nextIndex + 1 < string.length &&
_isLeadSurrogate(string.codeUnitAt(_nextIndex)) &&
_isTrailSurrogate(string.codeUnitAt(_nextIndex + 1))) {
nextIndex++;
}
nextIndex++;
}
_nextIndex = nextIndex;
return true;
}
}
_current = null;
_string = null; // Marks iteration as ended.
return false;
}
}
@@ -3,8 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:_embedder';
import 'dart:_error_utils' show RangeErrorUtils;
import 'dart:_internal' show patch;
import 'dart:_js_helper';
import 'dart:_string';
import 'dart:_wasm';
@@ -35,6 +35,7 @@ class StringBuffer {
@patch
void writeCharCode(int charCode) {
RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(charCode, 0x10ffff);
stringBufferWriteCharCode(_hostBuffer, WasmI32.fromInt(charCode));
}
@@ -75,7 +76,7 @@ class StringBuffer {
void _writeString(String str) {
stringBufferWriteString(
_hostBuffer,
jsStringFromDartString(str).wrappedExternRef,
embedderStringFromDartString(str).wrappedExternRef,
);
}
}
@@ -0,0 +1,164 @@
// 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:_boxed_int';
import 'dart:_embedder' show stringFromCharCodeArray;
import 'dart:_error_utils';
import 'dart:_internal' show EfficientLengthIterable, patch, unsafeCast;
import 'dart:_typed_data';
import 'dart:_list';
import 'dart:_string';
import 'dart:_wasm';
import 'dart:typed_data';
@pragma('wasm:initialize-at-startup')
const int _stringFromCharCodesSize = 512;
final _stringFromCharCodes = WasmArray<WasmI16>(_stringFromCharCodesSize);
@patch
class String {
@patch
factory String.fromCharCodes(
Iterable<int> charCodes, [
int start = 0,
int? end,
]) {
RangeError.checkNotNegative(start, "start");
if (end != null && end < start) {
throw RangeError.range(end, start, null, "end");
}
if (charCodes is U8List) {
return _fromU8ListCharCodes(charCodes, start, end);
}
if (charCodes is U16List) {
return _fromU16ListCharCodes(charCodes, start, end);
}
if (charCodes is WasmListBase) {
final result = _fromWasmListBaseCharCodes(
unsafeCast<WasmListBase<int>>(charCodes),
start,
end,
);
if (result != null) return result;
}
return _fromIterableCharCodes(charCodes, start, end);
}
static String _fromU8ListCharCodes(
U8List charCodes,
int start,
int? optionalEnd,
) {
final length = charCodes.length;
int end = optionalEnd != null
? (optionalEnd < length ? optionalEnd : length)
: length;
if (end <= start) return '';
return JSStringImpl.fromAsciiBytes(
charCodes.data,
charCodes.offsetInElements + start,
charCodes.offsetInElements + end,
);
}
static String _fromU16ListCharCodes(
U16List charCodes,
int start,
int? optionalEnd,
) {
final length = charCodes.length;
int end = optionalEnd != null
? (optionalEnd < length ? optionalEnd : length)
: length;
if (end <= start) return '';
final count = end - start;
final int offset = charCodes.offsetInElements;
start += offset;
end += offset;
final data = charCodes.data;
return JSStringImpl.fromCharCodeArray(data, start, end);
}
static String? _fromWasmListBaseCharCodes(
WasmListBase<int> charCodes,
int start,
int? optionalEnd,
) {
final length = charCodes.length;
final int end = optionalEnd != null
? (optionalEnd < length ? optionalEnd : length)
: length;
if (end <= start) return '';
final count = end - start;
final src = charCodes.data;
final dst = count < _stringFromCharCodesSize
? _stringFromCharCodes
: WasmArray<WasmI16>(count);
for (int i = 0; i < count; ++i) {
final charCode = unsafeCast<BoxedInt>(src[start + i]);
if (charCode.gtU(0xffff)) {
return null; // fall back to general case.
}
dst.write(i, charCode);
}
return JSStringImpl.fromCharCodeArray(dst, 0, count);
}
static String _fromIterableCharCodes(
Iterable<int> charCodes,
int start,
int? end,
) {
RangeError.checkNotNegative(start, "start");
if (end != null) {
if (end < start) {
throw RangeError.range(end, start, null, "end");
}
if (end == start) return "";
}
final length = charCodes.length;
// Skip until `start`.
final it = charCodes.iterator;
for (int i = 0; i < start; i++) {
it.moveNext();
}
// Convert to WasmArray for JSStringImpl.fromCharCodeArray.
final charCodesLength = (end ?? length) - start;
if (charCodesLength <= 0) return "";
final typedArrayLength = charCodesLength * 2;
final WasmArray<WasmI16> list = WasmArray(typedArrayLength);
int index = 0; // index in `list`.
end ??= start + charCodesLength;
for (int i = start; i < end; i++) {
if (!it.moveNext()) {
break;
}
final charCode = it.current;
if (charCode.leU(0xffff)) {
list.write(index++, charCode);
} else if (charCode.leU(0x10ffff)) {
list.write(index++, 0xd800 + ((((charCode - 0x10000) >> 10) & 0x3ff)));
list.write(index++, 0xdc00 + (charCode & 0x3ff));
} else {
throw RangeError.range(charCode, 0, 0x10ffff);
}
}
return JSStringImpl.fromCharCodeArray(list, 0, index);
}
@patch
@pragma("wasm:prefer-inline")
factory String.fromCharCode(int charCode) {
RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(charCode, 0x10ffff);
return JSStringImpl.fromCodePoint(charCode);
}
}
@@ -0,0 +1,19 @@
// 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;
import "dart:_wasm";
import "dart:js_interop";
@patch
extension WasmExternRefToJSAny on WasmExternRef {
@patch
JSAny get toJS => throw UnsupportedError(
'WasmExternRefToJSAny.toJS is unsupported on the standalone target.',
);
}
@patch
WasmExternRef? externRefForJSAny(JSAny object) => throw UnsupportedError(
'externRefForJSAny is unsupported on the standalone target.',
);
+21 -23
View File
@@ -295,7 +295,7 @@
"_internal/wasm_standalone/lib/regexp_patch.dart",
"_internal/wasm_standalone/lib/stack_trace_patch.dart",
"_internal/wasm_standalone/lib/string_buffer_patch.dart",
"_internal/wasm/lib/string_patch.dart",
"_internal/wasm_standalone/lib/string_patch.dart",
"_internal/wasm_standalone/lib/stopwatch_patch.dart",
"_internal/wasm/lib/sync_star_patch.dart",
"_internal/wasm_standalone/lib/uri_patch.dart",
@@ -334,7 +334,7 @@
]
},
"_string": {
"uri": "_internal/wasm/lib/js_string.dart"
"uri": "_internal/wasm_standalone/lib/embedder_string.dart"
},
"_typed_data": {
"uri": "_internal/wasm/lib/typed_data.dart",
@@ -342,12 +342,6 @@
"_internal/wasm_standalone/lib/typed_data_copy_from_js.dart"
]
},
"_js_helper": {
"uri": "_internal/wasm/lib/js_helper.dart",
"patches": [
"_internal/wasm/lib/js_helper_patch.dart"
]
},
"_internal": {
"uri": "internal/internal.dart",
"patches": [
@@ -361,7 +355,7 @@
},
"_wasm": {
"uri": "_wasm/wasm_types.dart",
"patches": "_internal/wasm/lib/wasm_types_patch.dart"
"patches": "_internal/wasm_standalone/lib/wasm_types_patch.dart"
},
"math": {
"uri": "math/math.dart",
@@ -369,6 +363,10 @@
"_internal/wasm/lib/math_patch.dart",
"_internal/wasm_standalone/lib/math_externs_patch.dart"
]
},
"js_interop": {
"uri": "_internal/wasm_standalone/lib/js_interop.dart",
"support_conditional_import": false
}
}
},
@@ -419,6 +417,20 @@
"_internal/wasm/lib/math_patch.dart",
"_internal/wasm/lib/math_externs_patch.dart"
]
},
"_js_string_convert": {
"uri": "_internal/wasm/lib/js_string_convert.dart"
},
"_js_types": {
"uri": "_internal/wasm/lib/js_types.dart"
},
"js_interop": {
"uri": "js_interop/js_interop.dart",
"patches": "_internal/wasm/lib/js_interop_patch.dart"
},
"js_interop_unsafe": {
"uri": "js_interop_unsafe/js_interop_unsafe.dart",
"patches": "_internal/wasm/lib/js_interop_unsafe_patch.dart"
}
}
},
@@ -448,12 +460,6 @@
"_internal/js_shared/lib/http_patch.dart"
]
},
"_js_string_convert": {
"uri": "_internal/wasm/lib/js_string_convert.dart"
},
"_js_types": {
"uri": "_internal/wasm/lib/js_types.dart"
},
"_list": {
"uri": "_internal/wasm/lib/list.dart"
},
@@ -497,14 +503,6 @@
"patches": [
"_internal/wasm/lib/isolate_patch.dart"
]
},
"js_interop": {
"uri": "js_interop/js_interop.dart",
"patches": "_internal/wasm/lib/js_interop_patch.dart"
},
"js_interop_unsafe": {
"uri": "js_interop_unsafe/js_interop_unsafe.dart",
"patches": "_internal/wasm/lib/js_interop_unsafe_patch.dart"
}
}
},
+18 -17
View File
@@ -253,7 +253,7 @@ wasm_standalone:
- _internal/wasm_standalone/lib/regexp_patch.dart
- _internal/wasm_standalone/lib/stack_trace_patch.dart
- _internal/wasm_standalone/lib/string_buffer_patch.dart
- _internal/wasm/lib/string_patch.dart # TODO(53884): Rewrite without JS interop in _internal/wasm_standalone
- _internal/wasm_standalone/lib/string_patch.dart
- _internal/wasm_standalone/lib/stopwatch_patch.dart
- _internal/wasm/lib/sync_star_patch.dart
- _internal/wasm_standalone/lib/uri_patch.dart
@@ -282,15 +282,11 @@ wasm_standalone:
patches:
- _internal/wasm_standalone/lib/boxed_double_patch.dart
_string:
uri: _internal/wasm/lib/js_string.dart # TODO(53884): Rewrite without JS interop in _internal/wasm_standalone
uri: _internal/wasm_standalone/lib/embedder_string.dart
_typed_data:
uri: _internal/wasm/lib/typed_data.dart
patches:
- _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:
- _internal/wasm/lib/js_helper_patch.dart
_internal:
uri: internal/internal.dart
patches:
@@ -302,12 +298,17 @@ wasm_standalone:
- _internal/vm_shared/lib/check_valid_weak_target_patch.dart
_wasm:
uri: _wasm/wasm_types.dart
patches: _internal/wasm/lib/wasm_types_patch.dart # TODO(53884): Remove once other wasm_standalone patches no longer reference this
patches: _internal/wasm_standalone/lib/wasm_types_patch.dart
math:
uri: math/math.dart
patches:
- _internal/wasm/lib/math_patch.dart
- _internal/wasm_standalone/lib/math_externs_patch.dart
js_interop:
# TODO(63166): Remove js_interop from wasm_standalone. It's fundamentally unsupported, but
# currently added as a stub because APIs in dart:_wasm reference it.
uri: _internal/wasm_standalone/lib/js_interop.dart
support_conditional_import: false
wasm_js_common:
include:
@@ -343,6 +344,16 @@ wasm_js_common:
patches:
- _internal/wasm/lib/math_patch.dart
- _internal/wasm/lib/math_externs_patch.dart
_js_string_convert:
uri: _internal/wasm/lib/js_string_convert.dart
_js_types:
uri: _internal/wasm/lib/js_types.dart
js_interop:
uri: js_interop/js_interop.dart
patches: _internal/wasm/lib/js_interop_patch.dart
js_interop_unsafe:
uri: js_interop_unsafe/js_interop_unsafe.dart
patches: _internal/wasm/lib/js_interop_unsafe_patch.dart
wasm_common:
libraries:
@@ -362,10 +373,6 @@ wasm_common:
uri: _http/http.dart
patches:
- _internal/js_shared/lib/http_patch.dart
_js_string_convert:
uri: _internal/wasm/lib/js_string_convert.dart
_js_types:
uri: _internal/wasm/lib/js_types.dart
_list:
uri: _internal/wasm/lib/list.dart
_object_helper:
@@ -398,12 +405,6 @@ wasm_common:
uri: isolate/isolate.dart
patches:
- "_internal/wasm/lib/isolate_patch.dart"
js_interop:
uri: js_interop/js_interop.dart
patches: _internal/wasm/lib/js_interop_patch.dart
js_interop_unsafe:
uri: js_interop_unsafe/js_interop_unsafe.dart
patches: _internal/wasm/lib/js_interop_unsafe_patch.dart
dart2js:
include:
+5
View File
@@ -49,3 +49,8 @@ LibTest/js_interop/importModule_A02_t01: Skip # https://github.com/dart-lang/sdk
LibTest/js_interop/importModule_A02_t02: Skip # https://github.com/dart-lang/sdk/issues/61204
LibTest/js_interop/importModule_A02_t03: Skip # https://github.com/dart-lang/sdk/issues/61204
LibTest/js_interop/interop_A08_t05: Skip # https://github.com/dart-lang/sdk/issues/61204
[ $compiler == dart2wasm && $dart2wasm_standalone ]
LanguageFeatures/Augmentations/js_interop/*: SkipByDesign # This is a generic WebAssembly target without JavaScript support
LibTest/js_interop/*: SkipByDesign # This is a generic WebAssembly target without JavaScript support
LibTest/js_interop_unsafe/*: SkipByDesign # This is a generic WebAssembly target without JavaScript support
+6 -2
View File
@@ -37,9 +37,13 @@ main() {
const bool.fromEnvironment("dart.library.web_gl"),
);
// All web backends support `dart:js_interop`
// All web backends support `dart:js_interop`. dart2wasm standalone is tested
// on the web but is not a web backend.
final isDart2WasmStandalone = const String.fromEnvironment(
"test_runner.configuration",
).contains('standalone');
Expect.equals(
isWebConfiguration,
isWebConfiguration && !isDart2WasmStandalone,
const bool.fromEnvironment("dart.library.js_interop"),
);
+4
View File
@@ -135,6 +135,10 @@ js_interop_unsafe/*: SkipByDesign # Only supported on web backends.
[ $compiler != dart2js && $compiler != ddc ]
web/*: SkipByDesign
[ $compiler == dart2wasm && $dart2wasm_standalone ]
js/*: SkipByDesign # This is a generic WebAssembly target without JavaScript support
js_interop_unsafe/*: SkipByDesign # This is a generic WebAssembly target without JavaScript support
[ $runtime == chrome && $system == macos ]
convert/streamed_conversion_utf8_encode_test: SkipSlow # Times out. Issue 22050
html/canvasrendering/arc_test: Skip # Issue 42048