[dart2js] Add missing connectionState and onConnectionStateChange to RTCPeerConnection.

Closes https://github.com/dart-lang/sdk/issues/48735

Change-Id: I04f263afa4dee1b868ad254cb54ad7c398435e03
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/240240
Reviewed-by: Riley Porter <rileyporter@google.com>
Commit-Queue: Srujan Gaddam <srujzs@google.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7cfff32..445b83b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -51,6 +51,9 @@
   return their correct return types now as well - `PerformanceEntry?` and
   `PerformanceMeasure?`, respectively.
 
+- Add `connectionState` attribute and `connectionstatechange` listener to
+  RTCPeerConnection.
+
 #### `dart:indexed_db`
 
 - `IdbFactory.supportsDatabaseNames` has been deprecated. It will always return
diff --git a/sdk/lib/html/dart2js/html_dart2js.dart b/sdk/lib/html/dart2js/html_dart2js.dart
index 8ad4a99..671f68a 100644
--- a/sdk/lib/html/dart2js/html_dart2js.dart
+++ b/sdk/lib/html/dart2js/html_dart2js.dart
@@ -26985,6 +26985,15 @@
       const EventStreamProvider<MediaStreamEvent>('addstream');
 
   /**
+   * Static factory designed to expose `connectionstatechange` events to event
+   * handlers that are not necessarily instances of [RtcPeerConnection].
+   *
+   * See [EventStreamProvider] for usage information.
+   */
+  static const EventStreamProvider<Event> connectionStateChangeEvent =
+      const EventStreamProvider<Event>('connectionstatechange');
+
+  /**
    * Static factory designed to expose `datachannel` events to event
    * handlers that are not necessarily instances of [RtcPeerConnection].
    *
@@ -27048,6 +27057,8 @@
   static const EventStreamProvider<RtcTrackEvent> trackEvent =
       const EventStreamProvider<RtcTrackEvent>('track');
 
+  String? get connectionState native;
+
   String? get iceConnectionState native;
 
   String? get iceGatheringState native;
@@ -27162,6 +27173,10 @@
   /// Stream of `addstream` events handled by this [RtcPeerConnection].
   Stream<MediaStreamEvent> get onAddStream => addStreamEvent.forTarget(this);
 
+  /// Stream of `connectionstatechange` events handled by this [RtcPeerConnection].
+  Stream<Event> get onConnectionStateChange =>
+      connectionStateChangeEvent.forTarget(this);
+
   /// Stream of `datachannel` events handled by this [RtcPeerConnection].
   Stream<RtcDataChannelEvent> get onDataChannel =>
       dataChannelEvent.forTarget(this);
diff --git a/tests/lib/html/rtc_test.dart b/tests/lib/html/rtc_test.dart
index 6b3c3581..88dc43f 100644
--- a/tests/lib/html/rtc_test.dart
+++ b/tests/lib/html/rtc_test.dart
@@ -41,6 +41,20 @@
             new RtcSessionDescription({'sdp': 'foo', 'type': 'offer'});
         expect(description is RtcSessionDescription, isTrue);
       });
+
+      test('connection state', () {
+        var pc = new RtcPeerConnection({
+          'iceServers': [
+            {'url': 'stun:216.93.246.18:3478'}
+          ]
+        });
+        // `connectionState` is unsupported on some browsers, so we check
+        // whether the attribute exists first.
+        if (pc.connectionState != null) {
+          expect(pc.connectionState, equals('new'));
+        }
+        pc.onConnectionStateChange.listen((event) {});
+      });
     }
   });
 }
diff --git a/tests/lib_2/html/rtc_test.dart b/tests/lib_2/html/rtc_test.dart
index 1e68cbe..7deffb9 100644
--- a/tests/lib_2/html/rtc_test.dart
+++ b/tests/lib_2/html/rtc_test.dart
@@ -43,6 +43,20 @@
             new RtcSessionDescription({'sdp': 'foo', 'type': 'offer'});
         expect(description is RtcSessionDescription, isTrue);
       });
+
+      test('connection state', () {
+        var pc = new RtcPeerConnection({
+          'iceServers': [
+            {'url': 'stun:216.93.246.18:3478'}
+          ]
+        });
+        // `connectionState` is unsupported on some browsers, so we check
+        // whether the attribute exists first.
+        if (pc.connectionState != null) {
+          expect(pc.connectionState, equals('new'));
+        }
+        pc.onConnectionStateChange.listen((event) {});
+      });
     }
   });
 }
diff --git a/tools/dom/docs.json b/tools/dom/docs.json
index 2dfd29c..51334c0 100644
--- a/tools/dom/docs.json
+++ b/tools/dom/docs.json
@@ -2834,9 +2834,20 @@
           "   * See [EventStreamProvider] for usage information.",
           "   */"
         ],
+        "connectionstatechangeEvent": [
+          "/**",
+          "   * Static factory designed to expose `connectionstatechange` events to event",
+          "   * handlers that are not necessarily instances of [RtcPeerConnection].",
+          "   *",
+          "   * See [EventStreamProvider] for usage information.",
+          "   */"
+        ],
         "onaddstream": [
           "/// Stream of `addstream` events handled by this [RtcPeerConnection]."
         ],
+        "onconnectionstatechange": [
+          "/// Stream of `connectionstatechange` events handled by this [RtcPeerConnection]."
+        ],
         "ondatachannel": [
           "/// Stream of `datachannel` events handled by this [RtcPeerConnection]."
         ],
diff --git a/tools/dom/dom.json b/tools/dom/dom.json
index ebd5826..295e137 100644
--- a/tools/dom/dom.json
+++ b/tools/dom/dom.json
@@ -16409,6 +16409,9 @@
         "support_level": "untriaged"
       },
       "close": {},
+      "connectionState": {
+        "support_level": "untriaged"
+      },
       "createAnswer": {},
       "createDTMFSender": {},
       "createDataChannel": {},
@@ -16437,6 +16440,9 @@
       "iceGatheringState": {},
       "localDescription": {},
       "onaddstream": {},
+      "onconnectionstatechange": {
+        "support_level": "untriaged"
+      },
       "ondatachannel": {},
       "onicecandidate": {},
       "oniceconnectionstatechange": {},
diff --git a/tools/dom/idl/dart/dart.idl b/tools/dom/idl/dart/dart.idl
index f877c25..0021d66 100644
--- a/tools/dom/idl/dart/dart.idl
+++ b/tools/dom/idl/dart/dart.idl
@@ -138,6 +138,8 @@
   [DartSuppress] Promise<void> getStats(RTCStatsCallback successCallback, optional MediaStreamTrack? selector);
   [DartSuppress] Promise<void> setLocalDescription(RTCSessionDescriptionInit description, VoidCallback successCallback, [Default=Undefined] optional RTCPeerConnectionErrorCallback failureCallback);
   [DartSuppress] Promise<void> setRemoteDescription(RTCSessionDescriptionInit description, VoidCallback successCallback, [Default=Undefined] optional RTCPeerConnectionErrorCallback failureCallback);
+  [Custom] readonly attribute DOMString connectionState;
+  [Custom] attribute EventHandler onconnectionstatechange;
 };
 
 // See implementation in tools/dom/templates for canvas and offscreenCanvas.
diff --git a/tools/dom/scripts/htmleventgenerator.py b/tools/dom/scripts/htmleventgenerator.py
index d614e88..0e47653 100644
--- a/tools/dom/scripts/htmleventgenerator.py
+++ b/tools/dom/scripts/htmleventgenerator.py
@@ -202,6 +202,8 @@
         'RTCDataChannel.open': ('open', 'Event'),
         'RTCPeerConnection.addstream': ('addStream', 'MediaStreamEvent'),
         'RTCPeerConnection.datachannel': ('dataChannel', 'RtcDataChannelEvent'),
+        'RTCPeerConnection.connectionstatechange':
+        ('connectionStateChange', 'Event'),
         'RTCPeerConnection.icecandidate':
         ('iceCandidate', 'RtcPeerConnectionIceEvent'),
         'RTCPeerConnection.iceconnectionstatechange':