[pkg:js] Lower external static @staticInterop class members
Lowers external static class members (fields, getters, setters, methods)
on a @staticInterop class to their respective js_util transformations.
Tests are added for a non-namespaced and a namespaced library.
Change-Id: Ife0b53c05310e924ebfbcf2059f6048ae8447c2f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/277047
Reviewed-by: Sigmund Cherem <sigmund@google.com>
diff --git a/pkg/_js_interop_checks/lib/src/transformations/js_util_optimizer.dart b/pkg/_js_interop_checks/lib/src/transformations/js_util_optimizer.dart
index bea8ac4..f5b6b44 100644
--- a/pkg/_js_interop_checks/lib/src/transformations/js_util_optimizer.dart
+++ b/pkg/_js_interop_checks/lib/src/transformations/js_util_optimizer.dart
@@ -7,7 +7,8 @@
import 'package:kernel/core_types.dart';
import 'package:kernel/type_environment.dart';
-import '../js_interop.dart' show getJSName, hasTrustTypesAnnotation;
+import '../js_interop.dart'
+ show getJSName, hasStaticInteropAnnotation, hasTrustTypesAnnotation;
/// Replaces js_util methods with inline calls to foreign_helper JS which
/// emits the code as a JavaScript code fragment.
@@ -20,6 +21,8 @@
final List<Procedure> _callConstructorUncheckedTargets;
final Procedure _getPropertyTarget;
final Procedure _getPropertyTrustTypeTarget;
+ final Procedure _globalThisTarget;
+ final InterfaceType _objectType;
final Procedure _setPropertyTarget;
final Procedure _setPropertyUncheckedTarget;
@@ -65,6 +68,9 @@
.getTopLevelProcedure('dart:js_util', 'getProperty'),
_getPropertyTrustTypeTarget = _coreTypes.index
.getTopLevelProcedure('dart:js_util', '_getPropertyTrustType'),
+ _globalThisTarget = _coreTypes.index
+ .getTopLevelProcedure('dart:js_util', 'get:globalThis'),
+ _objectType = hierarchy.coreTypes.objectNonNullableRawType,
_setPropertyTarget = _coreTypes.index
.getTopLevelProcedure('dart:js_util', 'setProperty'),
_setPropertyUncheckedTarget = _coreTypes.index
@@ -102,24 +108,53 @@
visitProcedure(Procedure node) {
_staticTypeContext.enterMember(node);
ReturnStatement? transformedBody;
- if (node.isExternal && node.isExtensionMember) {
- var index = _extensionMemberIndex ??=
- _createExtensionMembersIndex(node.enclosingLibrary);
- Reference reference = node.reference;
- var nodeDescriptor = index[reference]!;
- bool shouldTrustType = _shouldTrustType.contains(reference);
- if (!nodeDescriptor.isStatic) {
- if (nodeDescriptor.kind == ExtensionMemberKind.Getter) {
- transformedBody = _getExternalGetterBody(node, shouldTrustType);
- } else if (nodeDescriptor.kind == ExtensionMemberKind.Setter) {
- transformedBody = _getExternalSetterBody(node);
- } else if (nodeDescriptor.kind == ExtensionMemberKind.Method) {
- transformedBody = _getExternalMethodBody(node, shouldTrustType);
+ if (node.isExternal) {
+ if (node.isExtensionMember) {
+ var index = _extensionMemberIndex ??=
+ _createExtensionMembersIndex(node.enclosingLibrary);
+ Reference reference = node.reference;
+ var nodeDescriptor = index[reference]!;
+ bool shouldTrustType = _shouldTrustType.contains(reference);
+ if (!nodeDescriptor.isStatic) {
+ if (nodeDescriptor.kind == ExtensionMemberKind.Getter) {
+ transformedBody = _getExternalGetterBody(node, shouldTrustType);
+ } else if (nodeDescriptor.kind == ExtensionMemberKind.Setter) {
+ transformedBody = _getExternalSetterBody(node);
+ } else if (nodeDescriptor.kind == ExtensionMemberKind.Method) {
+ transformedBody = _getExternalMethodBody(node, shouldTrustType);
+ }
+ }
+ } else if (node.isStatic) {
+ // Fetch the dotted prefix of the member.
+ var libraryName = getJSName(node.enclosingLibrary);
+ var dottedPrefix = libraryName;
+ var enclosingClass = node.enclosingClass;
+ if (enclosingClass != null &&
+ hasStaticInteropAnnotation(enclosingClass)) {
+ var className = getJSName(enclosingClass);
+ if (className.isEmpty) className = enclosingClass.name;
+ if (dottedPrefix.isNotEmpty) {
+ dottedPrefix = '$dottedPrefix.$className';
+ } else {
+ dottedPrefix = className;
+ }
+ var shouldTrustType = hasTrustTypesAnnotation(enclosingClass);
+ var receiver = _getObjectOffGlobalThis(node, dottedPrefix.split('.'));
+ if (node.kind == ProcedureKind.Getter) {
+ transformedBody =
+ _getExternalGetterBody(node, shouldTrustType, receiver);
+ } else if (node.kind == ProcedureKind.Setter) {
+ transformedBody = _getExternalSetterBody(node, receiver);
+ } else if (node.kind == ProcedureKind.Method) {
+ transformedBody =
+ _getExternalMethodBody(node, shouldTrustType, receiver);
+ }
}
}
}
if (transformedBody != null) {
node.function.body = transformedBody;
+ transformedBody.parent = node.function;
node.isExternal = false;
} else {
node.transformChildren(this);
@@ -128,6 +163,23 @@
return node;
}
+ /// Given a list of strings, [selectors], recursively fetches the property
+ /// that corresponds to each string off of the `globalThis` object.
+ ///
+ /// Returns an expression that contains the nested property gets.
+ Expression _getObjectOffGlobalThis(Procedure node, List<String> selectors) {
+ Expression currentTarget = StaticGet(_globalThisTarget)
+ ..fileOffset = node.fileOffset;
+ for (String selector in selectors) {
+ currentTarget = StaticInvocation(
+ _getPropertyTrustTypeTarget,
+ Arguments([currentTarget, StringLiteral(selector)],
+ types: [_objectType]))
+ ..fileOffset = node.fileOffset;
+ }
+ return currentTarget;
+ }
+
/// Returns and initializes `_extensionMemberIndex` to an index of the member
/// reference to the member `ExtensionMemberDescriptor`, for all extension
/// members in the given [library].
@@ -151,17 +203,23 @@
/// Returns a new function body for the given [node] external getter.
///
/// The new function body will call the optimized version of
- /// `js_util.getProperty` for the given external getter.
- ReturnStatement _getExternalGetterBody(Procedure node, bool shouldTrustType) {
+ /// `js_util.getProperty` for the given external getter. If [shouldTrustType]
+ /// is true, we call a variant that does not check the return type. If
+ /// [receiver] is non-null, we use that instead of the first positional
+ /// parameter as the receiver for `js_util.getProperty`.
+ ReturnStatement _getExternalGetterBody(Procedure node, bool shouldTrustType,
+ [Expression? receiver]) {
var function = node.function;
- assert(function.positionalParameters.length == 1);
+ // Parameter `this` only exists for instance extension members.
+ assert(function.positionalParameters.length ==
+ (_isInstanceExtensionMember(node) ? 1 : 0));
Procedure target =
shouldTrustType ? _getPropertyTrustTypeTarget : _getPropertyTarget;
var getPropertyInvocation = StaticInvocation(
target,
Arguments([
- VariableGet(function.positionalParameters.first),
- StringLiteral(_getExtensionMemberName(node))
+ receiver ?? VariableGet(function.positionalParameters.first),
+ StringLiteral(_getMemberJSName(node))
], types: [
function.returnType
]))
@@ -172,16 +230,21 @@
/// Returns a new function body for the given [node] external setter.
///
/// The new function body will call the optimized version of
- /// `js_util.setProperty` for the given external setter.
- ReturnStatement _getExternalSetterBody(Procedure node) {
+ /// `js_util.setProperty` for the given external setter. If [receiver] is
+ /// non-null, we use that instead of the first positional parameter as the
+ /// receiver for `js_util.setProperty`.
+ ReturnStatement _getExternalSetterBody(Procedure node,
+ [Expression? receiver]) {
var function = node.function;
- assert(function.positionalParameters.length == 2);
+ // Parameter `this` only exists for instance extension members.
+ assert(function.positionalParameters.length ==
+ (_isInstanceExtensionMember(node) ? 2 : 1));
var value = function.positionalParameters.last;
var setPropertyInvocation = StaticInvocation(
_setPropertyTarget,
Arguments([
- VariableGet(function.positionalParameters.first),
- StringLiteral(_getExtensionMemberName(node)),
+ receiver ?? VariableGet(function.positionalParameters.first),
+ StringLiteral(_getMemberJSName(node)),
VariableGet(value)
], types: [
value.type
@@ -193,18 +256,26 @@
/// Returns a new function body for the given [node] external method.
///
/// The new function body will call the optimized version of
- /// `js_util.callMethod` for the given external method.
- ReturnStatement _getExternalMethodBody(Procedure node, bool shouldTrustType) {
+ /// `js_util.callMethod` for the given external method. If [shouldTrustType]
+ /// is true, we call a variant that does not check the return type. If
+ /// [receiver] is non-null, we use that instead of the first positional
+ /// parameter as the receiver for `js_util.callMethod`.
+ ReturnStatement _getExternalMethodBody(Procedure node, bool shouldTrustType,
+ [Expression? receiver]) {
var function = node.function;
Procedure target =
shouldTrustType ? _callMethodTrustTypeTarget : _callMethodTarget;
+ var positionalParameters = function.positionalParameters;
+ if (_isInstanceExtensionMember(node)) {
+ // Ignore `this` for instance extension members.
+ positionalParameters = positionalParameters.sublist(1);
+ }
var callMethodInvocation = StaticInvocation(
target,
Arguments([
- VariableGet(function.positionalParameters.first),
- StringLiteral(_getExtensionMemberName(node)),
- ListLiteral(function.positionalParameters
- .sublist(1)
+ receiver ?? VariableGet(function.positionalParameters.first),
+ StringLiteral(_getMemberJSName(node)),
+ ListLiteral(positionalParameters
.map<Expression>((argument) => VariableGet(argument))
.toList())
], types: [
@@ -215,17 +286,26 @@
shouldTrustType: shouldTrustType));
}
- /// Returns the extension member name.
+ /// Return whether [node] is an extension member that's declared as
+ /// non-`static`.
+ bool _isInstanceExtensionMember(Member node) =>
+ node.isExtensionMember &&
+ !_extensionMemberIndex![node.reference]!.isStatic;
+
+ /// Returns the underlying JS name.
///
/// Returns either the name from the `@JS` annotation if non-empty, or the
- /// declared name of the extension member. Does not return the CFE generated
- /// name for the top level member for this extension member.
- String _getExtensionMemberName(Procedure node) {
+ /// declared name of the member. In the case of an extension member, this
+ /// does not return the CFE generated name for the top level member.
+ String _getMemberJSName(Procedure node) {
var jsAnnotationName = getJSName(node);
if (jsAnnotationName.isNotEmpty) {
return jsAnnotationName;
}
- return _extensionMemberIndex![node.reference]!.name.text;
+ if (node.isExtensionMember) {
+ return _extensionMemberIndex![node.reference]!.name.text;
+ }
+ return node.name.text;
}
/// Replaces js_util method calls with optimization when possible.
@@ -286,12 +366,13 @@
node, targets, arguments.positional.sublist(0, 2));
}
- /// Lowers the given js_util `callConstructor` call to `_callConstructorUncheckedN`
- /// when the additional validation checks on the arguments can be elided.
+ /// Lowers the given js_util `callConstructor` call to
+ /// `_callConstructorUncheckedN` when the additional validation checks on the
+ /// arguments can be elided.
///
/// Calls will be lowered when using a List literal or constant list with 0-4
- /// elements for the `callConstructor` arguments, or the `List.empty()` factory.
- /// Removing the checks allows further inlining by the compilers.
+ /// elements for the `callConstructor` arguments, or the `List.empty()`
+ /// factory. Removing the checks allows further inlining by the compilers.
StaticInvocation _lowerCallConstructor(StaticInvocation node) {
Arguments arguments = node.arguments;
assert(arguments.positional.length == 2);
diff --git a/tests/lib/js/static_interop_test/external_static_member_lowerings_test.dart b/tests/lib/js/static_interop_test/external_static_member_lowerings_test.dart
new file mode 100644
index 0000000..0035255
--- /dev/null
+++ b/tests/lib/js/static_interop_test/external_static_member_lowerings_test.dart
@@ -0,0 +1,94 @@
+// Copyright (c) 2022, 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.
+
+@JS()
+library external_static_member_lowerings_test;
+
+import 'package:expect/minitest.dart';
+import 'package:js/js.dart';
+
+@JS()
+external dynamic eval(String code);
+
+@JS()
+@staticInterop
+class ExternalStatic {
+ external static String field;
+ @JS('field')
+ external static String renamedField;
+ external static final String finalField;
+
+ external static String get getSet;
+ external static set getSet(String val);
+ @JS('getSet')
+ external static String get renamedGetSet;
+ @JS('getSet')
+ external static set renamedGetSet(String val);
+
+ external static String method();
+ external static String differentArgsMethod(String a, [String b = '']);
+ @JS('method')
+ external static String renamedMethod();
+}
+
+@JS('ExternalStatic')
+@staticInterop
+@trustTypes
+class ExternalStaticTrustType {
+ external static double field;
+ external static double get getSet;
+ external static double method();
+}
+
+void main() {
+ eval('''
+ globalThis.ExternalStatic = function ExternalStatic() {}
+ globalThis.ExternalStatic.method = function() {
+ return 'method';
+ }
+ globalThis.ExternalStatic.differentArgsMethod = function(a, b) {
+ return a + b;
+ }
+ globalThis.ExternalStatic.field = 'field';
+ globalThis.ExternalStatic.finalField = 'finalField';
+ globalThis.ExternalStatic.getSet = 'getSet';
+ ''');
+ testClassStaticMembers();
+}
+
+void testClassStaticMembers() {
+ // Fields.
+ expect(ExternalStatic.field, 'field');
+ ExternalStatic.field = 'modified';
+ expect(ExternalStatic.field, 'modified');
+ expect(ExternalStatic.renamedField, 'modified');
+ ExternalStatic.renamedField = 'renamedField';
+ expect(ExternalStatic.renamedField, 'renamedField');
+ expect(ExternalStatic.finalField, 'finalField');
+
+ // Getters and setters.
+ expect(ExternalStatic.getSet, 'getSet');
+ ExternalStatic.getSet = 'modified';
+ expect(ExternalStatic.getSet, 'modified');
+ expect(ExternalStatic.renamedGetSet, 'modified');
+ ExternalStatic.renamedGetSet = 'renamedGetSet';
+ expect(ExternalStatic.renamedGetSet, 'renamedGetSet');
+
+ // Methods and tear-offs.
+ expect(ExternalStatic.method(), 'method');
+ expect((ExternalStatic.method)(), 'method');
+ expect(ExternalStatic.differentArgsMethod('method'), 'method');
+ expect((ExternalStatic.differentArgsMethod)('optional', 'method'),
+ 'optionalmethod');
+ expect(ExternalStatic.renamedMethod(), 'method');
+ expect((ExternalStatic.renamedMethod)(), 'method');
+
+ // Use wrong return type in conjunction with `@trustTypes`.
+ expect(ExternalStaticTrustType.field, 'renamedField');
+
+ expect(ExternalStaticTrustType.getSet, 'renamedGetSet');
+
+ expect(ExternalStaticTrustType.method(), 'method');
+ expect((ExternalStaticTrustType.method)(), 'method');
+}
diff --git a/tests/lib/js/static_interop_test/external_static_member_lowerings_with_namespaces_test.dart b/tests/lib/js/static_interop_test/external_static_member_lowerings_with_namespaces_test.dart
new file mode 100644
index 0000000..fd6eead
--- /dev/null
+++ b/tests/lib/js/static_interop_test/external_static_member_lowerings_with_namespaces_test.dart
@@ -0,0 +1,105 @@
+// Copyright (c) 2022, 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.
+
+// Like `external_static_member_lowerings_test.dart`, but uses the namespaces
+// in the `@JS` annotations instead.
+
+@JS('library1.library2')
+library external_static_member_lowerings_with_namespaces_test;
+
+import 'dart:js_util' as js_util;
+
+import 'package:expect/minitest.dart';
+import 'package:js/js.dart';
+
+@JS('library3.ExternalStatic')
+@staticInterop
+class ExternalStatic {
+ external static String field;
+ @JS('field')
+ external static String renamedField;
+ external static final String finalField;
+
+ external static String get getSet;
+ external static set getSet(String val);
+ @JS('getSet')
+ external static String get renamedGetSet;
+ @JS('getSet')
+ external static set renamedGetSet(String val);
+
+ external static String method();
+ external static String differentArgsMethod(String a, [String b = '']);
+ @JS('method')
+ external static String renamedMethod();
+}
+
+@JS('library3.ExternalStatic')
+@staticInterop
+@trustTypes
+class ExternalStaticTrustType {
+ external static double field;
+ external static double get getSet;
+ external static double method();
+}
+
+void main() {
+ // Use `callMethod` instead of top-level external to `eval` since the library
+ // is namespaced.
+ js_util.callMethod(js_util.globalThis, 'eval', [
+ '''
+ var library3 = {};
+ var library2 = {library3: library3};
+ var library1 = {library2: library2};
+ globalThis.library1 = library1;
+
+ library3.ExternalStatic = function ExternalStatic() {}
+ library3.ExternalStatic.method = function() {
+ return 'method';
+ }
+ library3.ExternalStatic.differentArgsMethod = function(a, b) {
+ return a + b;
+ }
+ library3.ExternalStatic.field = 'field';
+ library3.ExternalStatic.finalField = 'finalField';
+ library3.ExternalStatic.getSet = 'getSet';
+ '''
+ ]);
+ testClassStaticMembers();
+}
+
+void testClassStaticMembers() {
+ // Fields.
+ expect(ExternalStatic.field, 'field');
+ ExternalStatic.field = 'modified';
+ expect(ExternalStatic.field, 'modified');
+ expect(ExternalStatic.renamedField, 'modified');
+ ExternalStatic.renamedField = 'renamedField';
+ expect(ExternalStatic.renamedField, 'renamedField');
+ expect(ExternalStatic.finalField, 'finalField');
+
+ // Getters and setters.
+ expect(ExternalStatic.getSet, 'getSet');
+ ExternalStatic.getSet = 'modified';
+ expect(ExternalStatic.getSet, 'modified');
+ expect(ExternalStatic.renamedGetSet, 'modified');
+ ExternalStatic.renamedGetSet = 'renamedGetSet';
+ expect(ExternalStatic.renamedGetSet, 'renamedGetSet');
+
+ // Methods and tearoffs.
+ expect(ExternalStatic.method(), 'method');
+ expect((ExternalStatic.method)(), 'method');
+ expect(ExternalStatic.differentArgsMethod('method'), 'method');
+ expect((ExternalStatic.differentArgsMethod)('optional', 'method'),
+ 'optionalmethod');
+ expect(ExternalStatic.renamedMethod(), 'method');
+ expect((ExternalStatic.renamedMethod)(), 'method');
+
+ // Use wrong return type in conjunction with `@trustTypes`.
+ expect(ExternalStaticTrustType.field, 'renamedField');
+
+ expect(ExternalStaticTrustType.getSet, 'renamedGetSet');
+
+ expect(ExternalStaticTrustType.method(), 'method');
+ expect((ExternalStaticTrustType.method)(), 'method');
+}
diff --git a/tests/lib/lib.status b/tests/lib/lib.status
index 6f41bfa..cad10e6 100644
--- a/tests/lib/lib.status
+++ b/tests/lib/lib.status
@@ -7,6 +7,10 @@
[ $arch == simarm ]
convert/utf85_test: Skip # Pass, Slow Issue 12644.
+[ $compiler == dart2wasm ]
+js/static_interop_test/external_static_member_lowerings_test: SkipByDesign # Tests @trustTypes, which is unsupported on dart2wasm. TODO(srujzs): Refactor that code out.
+js/static_interop_test/external_static_member_lowerings_with_namespaces_test: SkipByDesign # Tests @trustTypes, which is unsupported on dart2wasm. TODO(srujzs): Refactor that code out.
+
[ $mode == product ]
developer/timeline_test: Skip # Not supported
isolate/issue_24243_parent_isolate_test: Skip # Requires checked mode
@@ -59,6 +63,8 @@
js/method_call_on_object_test: SkipByDesign # Issue 42085.
js/mock_test/*: SkipByDesign # Issue 42085.
js/parameters_test: SkipByDesign # Issue 42085.
+js/static_interop_test/external_static_member_lowerings_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
+js/static_interop_test/external_static_member_lowerings_with_namespaces_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
js/trust_types_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
[ $compiler != dart2js && $compiler != dartdevc && $compiler != dartdevk ]
diff --git a/tests/lib_2/js/static_interop_test/external_static_member_lowerings_test.dart b/tests/lib_2/js/static_interop_test/external_static_member_lowerings_test.dart
new file mode 100644
index 0000000..0035255
--- /dev/null
+++ b/tests/lib_2/js/static_interop_test/external_static_member_lowerings_test.dart
@@ -0,0 +1,94 @@
+// Copyright (c) 2022, 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.
+
+@JS()
+library external_static_member_lowerings_test;
+
+import 'package:expect/minitest.dart';
+import 'package:js/js.dart';
+
+@JS()
+external dynamic eval(String code);
+
+@JS()
+@staticInterop
+class ExternalStatic {
+ external static String field;
+ @JS('field')
+ external static String renamedField;
+ external static final String finalField;
+
+ external static String get getSet;
+ external static set getSet(String val);
+ @JS('getSet')
+ external static String get renamedGetSet;
+ @JS('getSet')
+ external static set renamedGetSet(String val);
+
+ external static String method();
+ external static String differentArgsMethod(String a, [String b = '']);
+ @JS('method')
+ external static String renamedMethod();
+}
+
+@JS('ExternalStatic')
+@staticInterop
+@trustTypes
+class ExternalStaticTrustType {
+ external static double field;
+ external static double get getSet;
+ external static double method();
+}
+
+void main() {
+ eval('''
+ globalThis.ExternalStatic = function ExternalStatic() {}
+ globalThis.ExternalStatic.method = function() {
+ return 'method';
+ }
+ globalThis.ExternalStatic.differentArgsMethod = function(a, b) {
+ return a + b;
+ }
+ globalThis.ExternalStatic.field = 'field';
+ globalThis.ExternalStatic.finalField = 'finalField';
+ globalThis.ExternalStatic.getSet = 'getSet';
+ ''');
+ testClassStaticMembers();
+}
+
+void testClassStaticMembers() {
+ // Fields.
+ expect(ExternalStatic.field, 'field');
+ ExternalStatic.field = 'modified';
+ expect(ExternalStatic.field, 'modified');
+ expect(ExternalStatic.renamedField, 'modified');
+ ExternalStatic.renamedField = 'renamedField';
+ expect(ExternalStatic.renamedField, 'renamedField');
+ expect(ExternalStatic.finalField, 'finalField');
+
+ // Getters and setters.
+ expect(ExternalStatic.getSet, 'getSet');
+ ExternalStatic.getSet = 'modified';
+ expect(ExternalStatic.getSet, 'modified');
+ expect(ExternalStatic.renamedGetSet, 'modified');
+ ExternalStatic.renamedGetSet = 'renamedGetSet';
+ expect(ExternalStatic.renamedGetSet, 'renamedGetSet');
+
+ // Methods and tear-offs.
+ expect(ExternalStatic.method(), 'method');
+ expect((ExternalStatic.method)(), 'method');
+ expect(ExternalStatic.differentArgsMethod('method'), 'method');
+ expect((ExternalStatic.differentArgsMethod)('optional', 'method'),
+ 'optionalmethod');
+ expect(ExternalStatic.renamedMethod(), 'method');
+ expect((ExternalStatic.renamedMethod)(), 'method');
+
+ // Use wrong return type in conjunction with `@trustTypes`.
+ expect(ExternalStaticTrustType.field, 'renamedField');
+
+ expect(ExternalStaticTrustType.getSet, 'renamedGetSet');
+
+ expect(ExternalStaticTrustType.method(), 'method');
+ expect((ExternalStaticTrustType.method)(), 'method');
+}
diff --git a/tests/lib_2/js/static_interop_test/external_static_member_lowerings_with_namespaces_test.dart b/tests/lib_2/js/static_interop_test/external_static_member_lowerings_with_namespaces_test.dart
new file mode 100644
index 0000000..fd6eead
--- /dev/null
+++ b/tests/lib_2/js/static_interop_test/external_static_member_lowerings_with_namespaces_test.dart
@@ -0,0 +1,105 @@
+// Copyright (c) 2022, 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.
+
+// Like `external_static_member_lowerings_test.dart`, but uses the namespaces
+// in the `@JS` annotations instead.
+
+@JS('library1.library2')
+library external_static_member_lowerings_with_namespaces_test;
+
+import 'dart:js_util' as js_util;
+
+import 'package:expect/minitest.dart';
+import 'package:js/js.dart';
+
+@JS('library3.ExternalStatic')
+@staticInterop
+class ExternalStatic {
+ external static String field;
+ @JS('field')
+ external static String renamedField;
+ external static final String finalField;
+
+ external static String get getSet;
+ external static set getSet(String val);
+ @JS('getSet')
+ external static String get renamedGetSet;
+ @JS('getSet')
+ external static set renamedGetSet(String val);
+
+ external static String method();
+ external static String differentArgsMethod(String a, [String b = '']);
+ @JS('method')
+ external static String renamedMethod();
+}
+
+@JS('library3.ExternalStatic')
+@staticInterop
+@trustTypes
+class ExternalStaticTrustType {
+ external static double field;
+ external static double get getSet;
+ external static double method();
+}
+
+void main() {
+ // Use `callMethod` instead of top-level external to `eval` since the library
+ // is namespaced.
+ js_util.callMethod(js_util.globalThis, 'eval', [
+ '''
+ var library3 = {};
+ var library2 = {library3: library3};
+ var library1 = {library2: library2};
+ globalThis.library1 = library1;
+
+ library3.ExternalStatic = function ExternalStatic() {}
+ library3.ExternalStatic.method = function() {
+ return 'method';
+ }
+ library3.ExternalStatic.differentArgsMethod = function(a, b) {
+ return a + b;
+ }
+ library3.ExternalStatic.field = 'field';
+ library3.ExternalStatic.finalField = 'finalField';
+ library3.ExternalStatic.getSet = 'getSet';
+ '''
+ ]);
+ testClassStaticMembers();
+}
+
+void testClassStaticMembers() {
+ // Fields.
+ expect(ExternalStatic.field, 'field');
+ ExternalStatic.field = 'modified';
+ expect(ExternalStatic.field, 'modified');
+ expect(ExternalStatic.renamedField, 'modified');
+ ExternalStatic.renamedField = 'renamedField';
+ expect(ExternalStatic.renamedField, 'renamedField');
+ expect(ExternalStatic.finalField, 'finalField');
+
+ // Getters and setters.
+ expect(ExternalStatic.getSet, 'getSet');
+ ExternalStatic.getSet = 'modified';
+ expect(ExternalStatic.getSet, 'modified');
+ expect(ExternalStatic.renamedGetSet, 'modified');
+ ExternalStatic.renamedGetSet = 'renamedGetSet';
+ expect(ExternalStatic.renamedGetSet, 'renamedGetSet');
+
+ // Methods and tearoffs.
+ expect(ExternalStatic.method(), 'method');
+ expect((ExternalStatic.method)(), 'method');
+ expect(ExternalStatic.differentArgsMethod('method'), 'method');
+ expect((ExternalStatic.differentArgsMethod)('optional', 'method'),
+ 'optionalmethod');
+ expect(ExternalStatic.renamedMethod(), 'method');
+ expect((ExternalStatic.renamedMethod)(), 'method');
+
+ // Use wrong return type in conjunction with `@trustTypes`.
+ expect(ExternalStaticTrustType.field, 'renamedField');
+
+ expect(ExternalStaticTrustType.getSet, 'renamedGetSet');
+
+ expect(ExternalStaticTrustType.method(), 'method');
+ expect((ExternalStaticTrustType.method)(), 'method');
+}
diff --git a/tests/lib_2/lib_2.status b/tests/lib_2/lib_2.status
index 2574799..b7350ac 100644
--- a/tests/lib_2/lib_2.status
+++ b/tests/lib_2/lib_2.status
@@ -7,6 +7,10 @@
[ $arch == simarm ]
convert/utf85_test: Skip # Pass, Slow Issue 12644.
+[ $compiler == dart2wasm ]
+js/static_interop_test/external_static_member_lowerings_test: SkipByDesign # Tests @trustTypes, which is unsupported on dart2wasm. TODO(srujzs): Refactor that code out.
+js/static_interop_test/external_static_member_lowerings_with_namespaces_test: SkipByDesign # Tests @trustTypes, which is unsupported on dart2wasm. TODO(srujzs): Refactor that code out.
+
[ $mode == product ]
developer/timeline_test: Skip # Not supported
isolate/issue_24243_parent_isolate_test: Skip # Requires checked mode
@@ -59,6 +63,8 @@
js/method_call_on_object_test: SkipByDesign # Issue 42085.
js/mock_test/*: SkipByDesign # Issue 42085.
js/parameters_test: SkipByDesign # Issue 42085.
+js/static_interop_test/external_static_member_lowerings_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
+js/static_interop_test/external_static_member_lowerings_with_namespaces_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
js/trust_types_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
[ $compiler != dart2js && $compiler != dartdevc && $compiler != dartdevk ]