diff --git a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs
index 2c31b557cdcac..67509d17a362e 100644
--- a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs
+++ b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs
@@ -52,6 +52,11 @@ internal static partial class Runtime
[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern object TypedArrayCopyFrom(int jsObjHandle, int arrayPtr, int begin, int end, int bytesPerElement, out int exceptionalResult);
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ internal static extern string? AddEventListener(int jsObjHandle, string name, int weakDelegateHandle, int optionsObjHandle);
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ internal static extern string? RemoveEventListener(int jsObjHandle, string name, int weakDelegateHandle, bool capture);
+
// /
// / Execute the provided string in the JavaScript context
// /
diff --git a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserWebSocket.cs b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserWebSocket.cs
index b052020b11e0f..e46a6160ae105 100644
--- a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserWebSocket.cs
+++ b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserWebSocket.cs
@@ -167,13 +167,13 @@ internal async Task ConnectAsyncJavaScript(Uri uri, CancellationToken cancellati
_onError = errorEvt => errorEvt.Dispose();
// Attach the onError callback
- _innerWebSocket.SetObjectProperty("onerror", _onError);
+ _innerWebSocket.AddEventListener("error", _onError);
// Setup the onClose callback
_onClose = (closeEvent) => OnCloseCallback(closeEvent, cancellationToken);
// Attach the onClose callback
- _innerWebSocket.SetObjectProperty("onclose", _onClose);
+ _innerWebSocket.AddEventListener("close", _onClose);
// Setup the onOpen callback
_onOpen = (evt) =>
@@ -203,13 +203,13 @@ internal async Task ConnectAsyncJavaScript(Uri uri, CancellationToken cancellati
};
// Attach the onOpen callback
- _innerWebSocket.SetObjectProperty("onopen", _onOpen);
+ _innerWebSocket.AddEventListener("open", _onOpen);
// Setup the onMessage callback
_onMessage = (messageEvent) => OnMessageCallback(messageEvent);
// Attach the onMessage callaback
- _innerWebSocket.SetObjectProperty("onmessage", _onMessage);
+ _innerWebSocket.AddEventListener("message", _onMessage);
await _tcsConnect.Task.ConfigureAwait(continueOnCapturedContext: true);
}
catch (Exception wse)
@@ -298,7 +298,7 @@ private void OnMessageCallback(JSObject messageEvent)
}
}
};
- reader.Invoke("addEventListener", "loadend", loadend);
+ reader.AddEventListener("loadend", loadend);
reader.Invoke("readAsArrayBuffer", blobData);
}
break;
@@ -318,26 +318,10 @@ private void NativeCleanup()
{
// We need to clear the events on websocket as well or stray events
// are possible leading to crashes.
- if (_onClose != null)
- {
- _innerWebSocket?.SetObjectProperty("onclose", "");
- _onClose = null;
- }
- if (_onError != null)
- {
- _innerWebSocket?.SetObjectProperty("onerror", "");
- _onError = null;
- }
- if (_onOpen != null)
- {
- _innerWebSocket?.SetObjectProperty("onopen", "");
- _onOpen = null;
- }
- if (_onMessage != null)
- {
- _innerWebSocket?.SetObjectProperty("onmessage", "");
- _onMessage = null;
- }
+ _innerWebSocket?.RemoveEventListener("close", _onClose);
+ _innerWebSocket?.RemoveEventListener("error", _onError);
+ _innerWebSocket?.RemoveEventListener("open", _onOpen);
+ _innerWebSocket?.RemoveEventListener("message", _onMessage);
}
public override void Dispose()
diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.cs
index 73032ff7b3a07..85eb8285f3932 100644
--- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.cs
+++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.cs
@@ -77,6 +77,61 @@ public object Invoke(string method, params object?[] args)
return res;
}
+ public struct EventListenerOptions {
+ public bool Capture;
+ public bool Once;
+ public bool Passive;
+ public object? Signal;
+ }
+
+ public int AddEventListener(string name, Delegate listener, EventListenerOptions? options = null)
+ {
+ var optionsDict = options.HasValue
+ ? new JSObject()
+ : null;
+
+ try {
+ if (options?.Signal != null)
+ throw new NotImplementedException("EventListenerOptions.Signal");
+
+ var jsfunc = Runtime.GetJSOwnedObjectHandle(listener);
+ // int exception;
+ if (options.HasValue) {
+ // TODO: Optimize this
+ var _options = options.Value;
+ optionsDict?.SetObjectProperty("capture", _options.Capture, true, true);
+ optionsDict?.SetObjectProperty("once", _options.Once, true, true);
+ optionsDict?.SetObjectProperty("passive", _options.Passive, true, true);
+ }
+
+ // TODO: Pass options explicitly instead of using the object
+ // TODO: Handle errors
+ // We can't currently do this because adding any additional parameters or a return value causes
+ // a signature mismatch at runtime
+ var ret = Interop.Runtime.AddEventListener(JSHandle, name, jsfunc, optionsDict?.JSHandle ?? 0);
+ if (ret != null)
+ throw new JSException(ret);
+ return jsfunc;
+ } finally {
+ optionsDict?.Dispose();
+ }
+ }
+
+ public void RemoveEventListener(string name, Delegate? listener, EventListenerOptions? options = null)
+ {
+ if (listener == null)
+ return;
+ var jsfunc = Runtime.GetJSOwnedObjectHandle(listener);
+ RemoveEventListener(name, jsfunc, options);
+ }
+
+ public void RemoveEventListener(string name, int listenerHandle, EventListenerOptions? options = null)
+ {
+ var ret = Interop.Runtime.RemoveEventListener(JSHandle, name, listenerHandle, options?.Capture ?? false);
+ if (ret != null)
+ throw new JSException(ret);
+ }
+
///
/// Returns the named property from the object, or throws a JSException on error.
///
diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.cs
index 34467dbdb9a2e..d30808f44d17d 100644
--- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.cs
+++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.cs
@@ -218,6 +218,74 @@ public static int BindExistingObject(object rawObj, int jsId)
return jsObject.Int32Handle;
}
+ private static int NextJSOwnedObjectID = 1;
+ private static object JSOwnedObjectLock = new object();
+ private static Dictionary