[package:js] Allow external extension members for Native classes

Change-Id: I5baf81c3c2b6b3a112327e1aea084a93677bd9b5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/208380
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Riley Porter <rileyporter@google.com>
diff --git a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
index 2ccb299..01dd119 100644
--- a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
@@ -6237,14 +6237,14 @@
         message: r"""This is the enclosing class.""");
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
-const Code<Null> codeJsInteropExternalExtensionMemberNotOnJSClass =
-    messageJsInteropExternalExtensionMemberNotOnJSClass;
+const Code<Null> codeJsInteropExternalExtensionMemberOnTypeInvalid =
+    messageJsInteropExternalExtensionMemberOnTypeInvalid;
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
-const MessageCode messageJsInteropExternalExtensionMemberNotOnJSClass =
-    const MessageCode("JsInteropExternalExtensionMemberNotOnJSClass",
+const MessageCode messageJsInteropExternalExtensionMemberOnTypeInvalid =
+    const MessageCode("JsInteropExternalExtensionMemberOnTypeInvalid",
         message:
-            r"""JS interop class required for 'external' extension members.""",
+            r"""JS interop or Native class required for 'external' extension members.""",
         tip:
             r"""Try adding a JS interop annotation to the on type class of the extension.""");
 
diff --git a/pkg/_js_interop_checks/lib/js_interop_checks.dart b/pkg/_js_interop_checks/lib/js_interop_checks.dart
index 7567930..975fca7 100644
--- a/pkg/_js_interop_checks/lib/js_interop_checks.dart
+++ b/pkg/_js_interop_checks/lib/js_interop_checks.dart
@@ -12,7 +12,7 @@
         messageJsInteropAnonymousFactoryPositionalParameters,
         messageJsInteropEnclosingClassJSAnnotation,
         messageJsInteropEnclosingClassJSAnnotationContext,
-        messageJsInteropExternalExtensionMemberNotOnJSClass,
+        messageJsInteropExternalExtensionMemberOnTypeInvalid,
         messageJsInteropExternalMemberNotJSAnnotated,
         messageJsInteropIndexNotSupported,
         messageJsInteropNamedParameters,
@@ -282,13 +282,14 @@
   /// [member] is `external` and not an allowed `external` usage.
   void _checkDisallowedExternal(Member member) {
     if (member.isExternal) {
-      // TODO(rileyporter): Allow extension members on some Native classes.
       if (member.isExtensionMember) {
-        _diagnosticsReporter.report(
-            messageJsInteropExternalExtensionMemberNotOnJSClass,
-            member.fileOffset,
-            member.name.text.length,
-            member.fileUri);
+        if (!_isNativeExtensionMember(member)) {
+          _diagnosticsReporter.report(
+              messageJsInteropExternalExtensionMemberOnTypeInvalid,
+              member.fileOffset,
+              member.name.text.length,
+              member.fileUri);
+        }
       } else if (!hasJSInteropAnnotation(member) &&
           !_isAllowedExternalUsage(member)) {
         // Member could be JS annotated and not considered a JS interop member
@@ -338,6 +339,18 @@
   /// Returns whether given extension [member] is in an extension that is on a
   /// JS interop class.
   bool _isJSExtensionMember(Member member) {
+    return _checkExtensionMember(member, hasJSInteropAnnotation);
+  }
+
+  /// Returns whether given extension [member] is in an extension on a Native
+  /// class.
+  bool _isNativeExtensionMember(Member member) {
+    return _checkExtensionMember(member, _nativeClasses.containsValue);
+  }
+
+  /// Returns whether given extension [member] is on a class that passses the
+  /// given [validateExtensionClass].
+  bool _checkExtensionMember(Member member, Function validateExtensionClass) {
     assert(member.isExtensionMember);
     if (_libraryExtensionsIndex == null) {
       _libraryExtensionsIndex = {};
@@ -347,6 +360,6 @@
     }
 
     var onType = _libraryExtensionsIndex![member.reference]!.onType;
-    return onType is InterfaceType && hasJSInteropAnnotation(onType.classNode);
+    return onType is InterfaceType && validateExtensionClass(onType.classNode);
   }
 }
diff --git a/pkg/front_end/messages.status b/pkg/front_end/messages.status
index ba2bc55..8eb1f8d 100644
--- a/pkg/front_end/messages.status
+++ b/pkg/front_end/messages.status
@@ -514,8 +514,8 @@
 JsInteropDartClassExtendsJSClass/example: Fail # Web compiler specific
 JsInteropEnclosingClassJSAnnotation/analyzerCode: Fail # Web compiler specific
 JsInteropEnclosingClassJSAnnotation/example: Fail # Web compiler specific
-JsInteropExternalExtensionMemberNotOnJSClass/analyzerCode: Fail # Web compiler specific
-JsInteropExternalExtensionMemberNotOnJSClass/example: Fail # Web compiler specific
+JsInteropExternalExtensionMemberOnTypeInvalid/analyzerCode: Fail # Web compiler specific
+JsInteropExternalExtensionMemberOnTypeInvalid/example: Fail # Web compiler specific
 JsInteropExternalMemberNotJSAnnotated/analyzerCode: Fail # Web compiler specific
 JsInteropExternalMemberNotJSAnnotated/example: Fail # Web compiler specific
 JsInteropIndexNotSupported/analyzerCode: Fail # Web compiler specific
diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml
index 0873cba..e94eac1 100644
--- a/pkg/front_end/messages.yaml
+++ b/pkg/front_end/messages.yaml
@@ -4914,8 +4914,8 @@
   template: "This is the enclosing class."
   severity: CONTEXT
 
-JsInteropExternalExtensionMemberNotOnJSClass:
-  template: "JS interop class required for 'external' extension members."
+JsInteropExternalExtensionMemberOnTypeInvalid:
+  template: "JS interop or Native class required for 'external' extension members."
   tip: "Try adding a JS interop annotation to the on type class of the extension."
 
 JsInteropExternalMemberNotJSAnnotated:
diff --git a/tests/lib/js/external_nonjs_static_test.dart b/tests/lib/js/external_nonjs_static_test.dart
index 913d202..87c4615 100644
--- a/tests/lib/js/external_nonjs_static_test.dart
+++ b/tests/lib/js/external_nonjs_static_test.dart
@@ -7,6 +7,7 @@
 
 library external_nonjs_static_test;
 
+import 'dart:html';
 import 'package:js/js.dart';
 
 external var topLevelField;
@@ -101,43 +102,43 @@
 extension ExtensionNonJS on NonJSClass {
   external var field;
   //           ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
   external final finalField;
   //             ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
   external static var staticField;
   //                  ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
   external static final staticFinalField;
   //                    ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
 
   external get getter;
   //           ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
   external set setter(_);
   //           ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
 
   external static get staticGetter;
   //                  ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
   external static set staticSetter(_);
   //                  ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
 
   external method();
   //       ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
   external static staticMethod();
   //              ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
   external optionalParameterMethod([int? a, int b = 0]);
   //       ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
   external overridenMethod();
   //       ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
 
   nonExternalMethod() => 1;
   static nonExternalStaticMethod() => 2;
@@ -150,9 +151,29 @@
 extension ExtensionGenericNonJS<T> on GenericNonJSClass<T> {
   external T method();
   //         ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
 }
 
 class GenericNonJSClass<T> {}
 
+extension ExtensionNative on HtmlElement {
+  external var field;
+  external final finalField;
+  external static var staticField;
+  external static final staticFinalField;
+
+  external get getter;
+  external set setter(_);
+
+  external static get staticGetter;
+  external static set staticSetter(_);
+
+  external method();
+  external static staticMethod();
+  external optionalParameterMethod([int? a, int b = 0]);
+
+  nonExternalMethod() => 1;
+  static nonExternalStaticMethod() => 2;
+}
+
 main() {}
diff --git a/tests/lib/js/external_static_test.dart b/tests/lib/js/external_static_test.dart
index 0a1c8db..a455de0 100644
--- a/tests/lib/js/external_static_test.dart
+++ b/tests/lib/js/external_static_test.dart
@@ -8,6 +8,7 @@
 @JS()
 library external_static_test;
 
+import 'dart:html';
 import 'package:js/js.dart';
 
 // external top level members ok in @JS() library.
@@ -89,52 +90,52 @@
 extension ExtensionNonJS on NonJSClass {
   external var field;
   //           ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
   external final finalField;
   //             ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
   external static var staticField;
   //                  ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
   external static final staticFinalField;
   //                    ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
 
   external get getter;
   //           ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
   external set setter(_);
   //           ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
 
   external static get staticGetter;
   //                  ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
   external static set staticSetter(_);
   //                  ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
 
   external method();
   //       ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
   external static staticMethod();
   //              ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
   external optionalParameterMethod([int? a, int b = 0]);
   //       ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
   external overridenMethod();
   //       ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
 
   @JS('fieldAnnotation')
   external var annotatedField;
   //           ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
   @JS('memberAnnotation')
   external annotatedMethod();
   //       ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
 
   nonExternalMethod() => 1;
   static nonExternalStaticMethod() => 2;
@@ -147,7 +148,7 @@
 extension ExtensionGenericNonJS<T> on GenericNonJSClass<T> {
   external T method();
   //         ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
 }
 
 class GenericNonJSClass<T> {}
@@ -229,4 +230,24 @@
 @JS()
 class _privateJSClass {}
 
+extension ExtensionNative on HtmlElement {
+  external var field;
+  external final finalField;
+  external static var staticField;
+  external static final staticFinalField;
+
+  external get getter;
+  external set setter(_);
+
+  external static get staticGetter;
+  external static set staticSetter(_);
+
+  external method();
+  external static staticMethod();
+  external optionalParameterMethod([int? a, int b = 0]);
+
+  nonExternalMethod() => 1;
+  static nonExternalStaticMethod() => 2;
+}
+
 main() {}
diff --git a/tests/lib_2/js/external_nonjs_static_test.dart b/tests/lib_2/js/external_nonjs_static_test.dart
index 1de7e61..d0fa7df 100644
--- a/tests/lib_2/js/external_nonjs_static_test.dart
+++ b/tests/lib_2/js/external_nonjs_static_test.dart
@@ -9,6 +9,7 @@
 
 library external_nonjs_static_test;
 
+import 'dart:html';
 import 'package:js/js.dart';
 
 external get topLevelGetter;
@@ -75,27 +76,30 @@
 extension ExtensionNonJS on NonJSClass {
   external get getter;
   //           ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
   external set setter(_);
   //           ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
 
   external static get staticGetter;
   //                  ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
   external static set staticSetter(_);
   //                  ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
 
   external method();
   //       ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
   external static staticMethod();
   //              ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
+  external optionalParameterMethod([int a, int b = 0]);
+  //       ^
+  // [web] JS interop or Native class required for 'external' extension members.
   external overridenMethod();
   //       ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
 
   nonExternalMethod() => 1;
   static nonExternalStaticMethod() => 2;
@@ -108,9 +112,24 @@
 extension ExtensionGenericNonJS<T> on GenericNonJSClass<T> {
   external T method();
   //         ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
 }
 
 class GenericNonJSClass<T> {}
 
+extension ExtensionNative on HtmlElement {
+  external get getter;
+  external set setter(_);
+
+  external static get staticGetter;
+  external static set staticSetter(_);
+
+  external method();
+  external static staticMethod();
+  external optionalParameterMethod([int a, int b = 0]);
+
+  nonExternalMethod() => 1;
+  static nonExternalStaticMethod() => 2;
+}
+
 main() {}
diff --git a/tests/lib_2/js/external_static_test.dart b/tests/lib_2/js/external_static_test.dart
index 3f4c60b7..8084a72 100644
--- a/tests/lib_2/js/external_static_test.dart
+++ b/tests/lib_2/js/external_static_test.dart
@@ -10,6 +10,7 @@
 @JS()
 library external_static_test;
 
+import 'dart:html';
 import 'package:js/js.dart';
 
 // external top level members ok in @JS() library.
@@ -69,32 +70,35 @@
 extension ExtensionNonJS on NonJSClass {
   external get getter;
   //           ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
   external set setter(_);
   //           ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
 
   external static get staticGetter;
   //                  ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
   external static set staticSetter(_);
   //                  ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
 
   external method();
   //       ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
   external static staticMethod();
   //              ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
+  external optionalParameterMethod([int a, int b = 0]);
+  //       ^
+  // [web] JS interop or Native class required for 'external' extension members.
   external overridenMethod();
   //       ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
 
   @JS('memberAnnotation')
   external annotatedMethod();
   //       ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
 
   nonExternalMethod() => 1;
   static nonExternalStaticMethod() => 2;
@@ -107,7 +111,7 @@
 extension ExtensionGenericNonJS<T> on GenericNonJSClass<T> {
   external T method();
   //         ^
-  // [web] JS interop class required for 'external' extension members.
+  // [web] JS interop or Native class required for 'external' extension members.
 }
 
 class GenericNonJSClass<T> {}
@@ -121,6 +125,7 @@
 
   external method();
   external static staticMethod();
+  external optionalParameterMethod([int a, int b = 0]);
 
   @JS('memberAnnotation')
   external annotatedMethod();
@@ -176,4 +181,19 @@
 @JS()
 class _privateJSClass {}
 
+extension ExtensionNative on HtmlElement {
+  external get getter;
+  external set setter(_);
+
+  external static get staticGetter;
+  external static set staticSetter(_);
+
+  external method();
+  external static staticMethod();
+  external optionalParameterMethod([int a, int b = 0]);
+
+  nonExternalMethod() => 1;
+  static nonExternalStaticMethod() => 2;
+}
+
 main() {}