Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/mono.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'sdks/wasm/BrowserDebugProxy/DevToolsProxy.cs')
-rw-r--r--sdks/wasm/BrowserDebugProxy/DevToolsProxy.cs706
1 files changed, 375 insertions, 331 deletions
diff --git a/sdks/wasm/BrowserDebugProxy/DevToolsProxy.cs b/sdks/wasm/BrowserDebugProxy/DevToolsProxy.cs
index 5eac86d124a..93d18438d32 100644
--- a/sdks/wasm/BrowserDebugProxy/DevToolsProxy.cs
+++ b/sdks/wasm/BrowserDebugProxy/DevToolsProxy.cs
@@ -1,337 +1,381 @@
-using System;
-using System.Linq;
-using System.Threading.Tasks;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
-using System.Net.WebSockets;
-using System.Threading;
+using System;
+using System.Collections.Generic;
using System.IO;
+using System.Linq;
+using System.Net.WebSockets;
using System.Text;
-using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
-namespace WebAssembly.Net.Debugging {
-
- class DevToolsQueue {
- Task current_send;
- List<byte []> pending;
-
- public WebSocket Ws { get; private set; }
- public Task CurrentSend { get { return current_send; } }
- public DevToolsQueue (WebSocket sock)
- {
- this.Ws = sock;
- pending = new List<byte []> ();
- }
-
- public Task Send (byte [] bytes, CancellationToken token)
- {
- pending.Add (bytes);
- if (pending.Count == 1) {
- if (current_send != null)
- throw new Exception ("current_send MUST BE NULL IF THERE'S no pending send");
- //logger.LogTrace ("sending {0} bytes", bytes.Length);
- current_send = Ws.SendAsync (new ArraySegment<byte> (bytes), WebSocketMessageType.Text, true, token);
- return current_send;
- }
- return null;
- }
-
- public Task Pump (CancellationToken token)
- {
- current_send = null;
- pending.RemoveAt (0);
-
- if (pending.Count > 0) {
- if (current_send != null)
- throw new Exception ("current_send MUST BE NULL IF THERE'S no pending send");
-
- current_send = Ws.SendAsync (new ArraySegment<byte> (pending [0]), WebSocketMessageType.Text, true, token);
- return current_send;
- }
- return null;
- }
- }
-
- internal class DevToolsProxy {
- TaskCompletionSource<bool> side_exception = new TaskCompletionSource<bool> ();
- TaskCompletionSource<bool> client_initiated_close = new TaskCompletionSource<bool> ();
- Dictionary<MessageId, TaskCompletionSource<Result>> pending_cmds = new Dictionary<MessageId, TaskCompletionSource<Result>> ();
- ClientWebSocket browser;
- WebSocket ide;
- int next_cmd_id;
- List<Task> pending_ops = new List<Task> ();
- List<DevToolsQueue> queues = new List<DevToolsQueue> ();
-
- protected readonly ILogger logger;
-
- public DevToolsProxy (ILoggerFactory loggerFactory)
- {
- logger = loggerFactory.CreateLogger<DevToolsProxy>();
- }
-
- protected virtual Task<bool> AcceptEvent (SessionId sessionId, string method, JObject args, CancellationToken token)
- {
- return Task.FromResult (false);
- }
-
- protected virtual Task<bool> AcceptCommand (MessageId id, string method, JObject args, CancellationToken token)
- {
- return Task.FromResult (false);
- }
-
- async Task<string> ReadOne (WebSocket socket, CancellationToken token)
- {
- byte [] buff = new byte [4000];
- var mem = new MemoryStream ();
- while (true) {
-
- if (socket.State != WebSocketState.Open) {
- Log ("error", $"DevToolsProxy: Socket is no longer open.");
- client_initiated_close.TrySetResult (true);
- return null;
- }
-
- var result = await socket.ReceiveAsync (new ArraySegment<byte> (buff), token);
- if (result.MessageType == WebSocketMessageType.Close) {
- client_initiated_close.TrySetResult (true);
- return null;
- }
-
- mem.Write (buff, 0, result.Count);
-
- if (result.EndOfMessage)
- return Encoding.UTF8.GetString (mem.GetBuffer (), 0, (int)mem.Length);
- }
- }
-
- DevToolsQueue GetQueueForSocket (WebSocket ws)
- {
- return queues.FirstOrDefault (q => q.Ws == ws);
- }
-
- DevToolsQueue GetQueueForTask (Task task)
- {
- return queues.FirstOrDefault (q => q.CurrentSend == task);
- }
-
- void Send (WebSocket to, JObject o, CancellationToken token)
- {
- var sender = browser == to ? "Send-browser" : "Send-ide";
-
- var method = o ["method"]?.ToString ();
- //if (method != "Debugger.scriptParsed" && method != "Runtime.consoleAPICalled")
- Log ("protocol", $"{sender}: " + JsonConvert.SerializeObject (o));
- var bytes = Encoding.UTF8.GetBytes (o.ToString ());
-
- var queue = GetQueueForSocket (to);
-
- var task = queue.Send (bytes, token);
- if (task != null)
- pending_ops.Add (task);
- }
-
- async Task OnEvent (SessionId sessionId, string method, JObject args, CancellationToken token)
- {
- try {
- if (!await AcceptEvent (sessionId, method, args, token)) {
- //logger.LogDebug ("proxy browser: {0}::{1}",method, args);
- SendEventInternal (sessionId, method, args, token);
- }
- } catch (Exception e) {
- side_exception.TrySetException (e);
- }
- }
-
- async Task OnCommand (MessageId id, string method, JObject args, CancellationToken token)
- {
- try {
- if (!await AcceptCommand (id, method, args, token)) {
- var res = await SendCommandInternal (id, method, args, token);
- SendResponseInternal (id, res, token);
- }
- } catch (Exception e) {
- side_exception.TrySetException (e);
- }
- }
-
- void OnResponse (MessageId id, Result result)
- {
- //logger.LogTrace ("got id {0} res {1}", id, result);
- // Fixme
- if (pending_cmds.Remove (id, out var task)) {
- task.SetResult (result);
- return;
- }
- logger.LogError ("Cannot respond to command: {id} with result: {result} - command is not pending", id, result);
- }
-
- void ProcessBrowserMessage (string msg, CancellationToken token)
- {
- var res = JObject.Parse (msg);
-
- var method = res ["method"]?.ToString ();
- //if (method != "Debugger.scriptParsed" && method != "Runtime.consoleAPICalled")
- Log ("protocol", $"browser: {msg}");
-
- if (res ["id"] == null)
- pending_ops.Add (OnEvent (new SessionId (res ["sessionId"]?.Value<string> ()), res ["method"].Value<string> (), res ["params"] as JObject, token));
- else
- OnResponse (new MessageId (res ["sessionId"]?.Value<string> (), res ["id"].Value<int> ()), Result.FromJson (res));
- }
-
- void ProcessIdeMessage (string msg, CancellationToken token)
- {
- Log ("protocol", $"ide: {msg}");
- if (!string.IsNullOrEmpty (msg)) {
- var res = JObject.Parse (msg);
- pending_ops.Add (OnCommand (
- new MessageId (res ["sessionId"]?.Value<string> (), res ["id"].Value<int> ()),
- res ["method"].Value<string> (),
- res ["params"] as JObject, token));
- }
- }
-
- internal async Task<Result> SendCommand (SessionId id, string method, JObject args, CancellationToken token) {
- //Log ("verbose", $"sending command {method}: {args}");
- return await SendCommandInternal (id, method, args, token);
- }
-
- Task<Result> SendCommandInternal (SessionId sessionId, string method, JObject args, CancellationToken token)
- {
- int id = Interlocked.Increment (ref next_cmd_id);
-
- var o = JObject.FromObject (new {
- id,
- method,
- @params = args
- });
- if (sessionId.sessionId != null)
- o["sessionId"] = sessionId.sessionId;
- var tcs = new TaskCompletionSource<Result> ();
-
- var msgId = new MessageId (sessionId.sessionId, id);
- //Log ("verbose", $"add cmd id {sessionId}-{id}");
- pending_cmds[msgId] = tcs;
-
- Send (this.browser, o, token);
- return tcs.Task;
- }
-
- public void SendEvent (SessionId sessionId, string method, JObject args, CancellationToken token)
- {
- //Log ("verbose", $"sending event {method}: {args}");
- SendEventInternal (sessionId, method, args, token);
- }
-
- void SendEventInternal (SessionId sessionId, string method, JObject args, CancellationToken token)
- {
- var o = JObject.FromObject (new {
- method,
- @params = args
- });
- if (sessionId.sessionId != null)
- o["sessionId"] = sessionId.sessionId;
-
- Send (this.ide, o, token);
- }
-
- internal void SendResponse (MessageId id, Result result, CancellationToken token)
- {
- SendResponseInternal (id, result, token);
- }
-
- void SendResponseInternal (MessageId id, Result result, CancellationToken token)
- {
- JObject o = result.ToJObject (id);
- if (result.IsErr)
- logger.LogError ($"sending error response for id: {id} -> {result}");
-
- Send (this.ide, o, token);
- }
-
- // , HttpContext context)
- public async Task Run (Uri browserUri, WebSocket ideSocket)
- {
- Log ("info", $"DevToolsProxy: Starting on {browserUri}");
- using (this.ide = ideSocket) {
- Log ("verbose", $"DevToolsProxy: IDE waiting for connection on {browserUri}");
- queues.Add (new DevToolsQueue (this.ide));
- using (this.browser = new ClientWebSocket ()) {
- this.browser.Options.KeepAliveInterval = Timeout.InfiniteTimeSpan;
- await this.browser.ConnectAsync (browserUri, CancellationToken.None);
- queues.Add (new DevToolsQueue (this.browser));
-
- Log ("verbose", $"DevToolsProxy: Client connected on {browserUri}");
- var x = new CancellationTokenSource ();
-
- pending_ops.Add (ReadOne (browser, x.Token));
- pending_ops.Add (ReadOne (ide, x.Token));
- pending_ops.Add (side_exception.Task);
- pending_ops.Add (client_initiated_close.Task);
-
- try {
- while (!x.IsCancellationRequested) {
- var task = await Task.WhenAny (pending_ops.ToArray ());
- //logger.LogTrace ("pump {0} {1}", task, pending_ops.IndexOf (task));
- if (task == pending_ops [0]) {
- var msg = ((Task<string>)task).Result;
- if (msg != null) {
- pending_ops [0] = ReadOne (browser, x.Token); //queue next read
- ProcessBrowserMessage (msg, x.Token);
- }
- } else if (task == pending_ops [1]) {
- var msg = ((Task<string>)task).Result;
- if (msg != null) {
- pending_ops [1] = ReadOne (ide, x.Token); //queue next read
- ProcessIdeMessage (msg, x.Token);
- }
- } else if (task == pending_ops [2]) {
- var res = ((Task<bool>)task).Result;
- throw new Exception ("side task must always complete with an exception, what's going on???");
- } else if (task == pending_ops [3]) {
- var res = ((Task<bool>)task).Result;
- Log ("verbose", $"DevToolsProxy: Client initiated close from {browserUri}");
- x.Cancel ();
- } else {
- //must be a background task
- pending_ops.Remove (task);
- var queue = GetQueueForTask (task);
- if (queue != null) {
- var tsk = queue.Pump (x.Token);
- if (tsk != null)
- pending_ops.Add (tsk);
- }
- }
- }
- } catch (Exception e) {
- Log ("error", $"DevToolsProxy::Run: Exception {e}");
- //throw;
- } finally {
- if (!x.IsCancellationRequested)
- x.Cancel ();
- }
- }
- }
- }
-
- protected void Log (string priority, string msg)
- {
- switch (priority) {
- case "protocol":
- logger.LogTrace (msg);
- break;
- case "verbose":
- logger.LogDebug (msg);
- break;
- case "info":
- case "warning":
- case "error":
- default:
- logger.LogDebug (msg);
- break;
- }
- }
- }
+namespace Microsoft.WebAssembly.Diagnostics
+{
+
+ class DevToolsQueue
+ {
+ Task current_send;
+ List<byte[]> pending;
+
+ public WebSocket Ws { get; private set; }
+ public Task CurrentSend { get { return current_send; } }
+ public DevToolsQueue(WebSocket sock)
+ {
+ this.Ws = sock;
+ pending = new List<byte[]>();
+ }
+
+ public Task Send(byte[] bytes, CancellationToken token)
+ {
+ pending.Add(bytes);
+ if (pending.Count == 1)
+ {
+ if (current_send != null)
+ throw new Exception("current_send MUST BE NULL IF THERE'S no pending send");
+ //logger.LogTrace ("sending {0} bytes", bytes.Length);
+ current_send = Ws.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, token);
+ return current_send;
+ }
+ return null;
+ }
+
+ public Task Pump(CancellationToken token)
+ {
+ current_send = null;
+ pending.RemoveAt(0);
+
+ if (pending.Count > 0)
+ {
+ if (current_send != null)
+ throw new Exception("current_send MUST BE NULL IF THERE'S no pending send");
+
+ current_send = Ws.SendAsync(new ArraySegment<byte>(pending[0]), WebSocketMessageType.Text, true, token);
+ return current_send;
+ }
+ return null;
+ }
+ }
+
+ internal class DevToolsProxy
+ {
+ TaskCompletionSource<bool> side_exception = new TaskCompletionSource<bool>();
+ TaskCompletionSource<bool> client_initiated_close = new TaskCompletionSource<bool>();
+ Dictionary<MessageId, TaskCompletionSource<Result>> pending_cmds = new Dictionary<MessageId, TaskCompletionSource<Result>>();
+ ClientWebSocket browser;
+ WebSocket ide;
+ int next_cmd_id;
+ List<Task> pending_ops = new List<Task>();
+ List<DevToolsQueue> queues = new List<DevToolsQueue>();
+
+ protected readonly ILogger logger;
+
+ public DevToolsProxy(ILoggerFactory loggerFactory)
+ {
+ logger = loggerFactory.CreateLogger<DevToolsProxy>();
+ }
+
+ protected virtual Task<bool> AcceptEvent(SessionId sessionId, string method, JObject args, CancellationToken token)
+ {
+ return Task.FromResult(false);
+ }
+
+ protected virtual Task<bool> AcceptCommand(MessageId id, string method, JObject args, CancellationToken token)
+ {
+ return Task.FromResult(false);
+ }
+
+ async Task<string> ReadOne(WebSocket socket, CancellationToken token)
+ {
+ byte[] buff = new byte[4000];
+ var mem = new MemoryStream();
+ while (true)
+ {
+
+ if (socket.State != WebSocketState.Open)
+ {
+ Log("error", $"DevToolsProxy: Socket is no longer open.");
+ client_initiated_close.TrySetResult(true);
+ return null;
+ }
+
+ var result = await socket.ReceiveAsync(new ArraySegment<byte>(buff), token);
+ if (result.MessageType == WebSocketMessageType.Close)
+ {
+ client_initiated_close.TrySetResult(true);
+ return null;
+ }
+
+ mem.Write(buff, 0, result.Count);
+
+ if (result.EndOfMessage)
+ return Encoding.UTF8.GetString(mem.GetBuffer(), 0, (int)mem.Length);
+ }
+ }
+
+ DevToolsQueue GetQueueForSocket(WebSocket ws)
+ {
+ return queues.FirstOrDefault(q => q.Ws == ws);
+ }
+
+ DevToolsQueue GetQueueForTask(Task task)
+ {
+ return queues.FirstOrDefault(q => q.CurrentSend == task);
+ }
+
+ void Send(WebSocket to, JObject o, CancellationToken token)
+ {
+ var sender = browser == to ? "Send-browser" : "Send-ide";
+
+ var method = o["method"]?.ToString();
+ //if (method != "Debugger.scriptParsed" && method != "Runtime.consoleAPICalled")
+ Log("protocol", $"{sender}: " + JsonConvert.SerializeObject(o));
+ var bytes = Encoding.UTF8.GetBytes(o.ToString());
+
+ var queue = GetQueueForSocket(to);
+
+ var task = queue.Send(bytes, token);
+ if (task != null)
+ pending_ops.Add(task);
+ }
+
+ async Task OnEvent(SessionId sessionId, string method, JObject args, CancellationToken token)
+ {
+ try
+ {
+ if (!await AcceptEvent(sessionId, method, args, token))
+ {
+ //logger.LogDebug ("proxy browser: {0}::{1}",method, args);
+ SendEventInternal(sessionId, method, args, token);
+ }
+ }
+ catch (Exception e)
+ {
+ side_exception.TrySetException(e);
+ }
+ }
+
+ async Task OnCommand(MessageId id, string method, JObject args, CancellationToken token)
+ {
+ try
+ {
+ if (!await AcceptCommand(id, method, args, token))
+ {
+ var res = await SendCommandInternal(id, method, args, token);
+ SendResponseInternal(id, res, token);
+ }
+ }
+ catch (Exception e)
+ {
+ side_exception.TrySetException(e);
+ }
+ }
+
+ void OnResponse(MessageId id, Result result)
+ {
+ //logger.LogTrace ("got id {0} res {1}", id, result);
+ // Fixme
+ if (pending_cmds.Remove(id, out var task))
+ {
+ task.SetResult(result);
+ return;
+ }
+ logger.LogError("Cannot respond to command: {id} with result: {result} - command is not pending", id, result);
+ }
+
+ void ProcessBrowserMessage(string msg, CancellationToken token)
+ {
+ var res = JObject.Parse(msg);
+
+ var method = res["method"]?.ToString();
+ //if (method != "Debugger.scriptParsed" && method != "Runtime.consoleAPICalled")
+ Log("protocol", $"browser: {msg}");
+
+ if (res["id"] == null)
+ pending_ops.Add(OnEvent(new SessionId(res["sessionId"]?.Value<string>()), res["method"].Value<string>(), res["params"] as JObject, token));
+ else
+ OnResponse(new MessageId(res["sessionId"]?.Value<string>(), res["id"].Value<int>()), Result.FromJson(res));
+ }
+
+ void ProcessIdeMessage(string msg, CancellationToken token)
+ {
+ Log("protocol", $"ide: {msg}");
+ if (!string.IsNullOrEmpty(msg))
+ {
+ var res = JObject.Parse(msg);
+ pending_ops.Add(OnCommand(
+ new MessageId(res["sessionId"]?.Value<string>(), res["id"].Value<int>()),
+ res["method"].Value<string>(),
+ res["params"] as JObject, token));
+ }
+ }
+
+ internal async Task<Result> SendCommand(SessionId id, string method, JObject args, CancellationToken token)
+ {
+ //Log ("verbose", $"sending command {method}: {args}");
+ return await SendCommandInternal(id, method, args, token);
+ }
+
+ Task<Result> SendCommandInternal(SessionId sessionId, string method, JObject args, CancellationToken token)
+ {
+ int id = Interlocked.Increment(ref next_cmd_id);
+
+ var o = JObject.FromObject(new
+ {
+ id,
+ method,
+ @params = args
+ });
+ if (sessionId.sessionId != null)
+ o["sessionId"] = sessionId.sessionId;
+ var tcs = new TaskCompletionSource<Result>();
+
+ var msgId = new MessageId(sessionId.sessionId, id);
+ //Log ("verbose", $"add cmd id {sessionId}-{id}");
+ pending_cmds[msgId] = tcs;
+
+ Send(this.browser, o, token);
+ return tcs.Task;
+ }
+
+ public void SendEvent(SessionId sessionId, string method, JObject args, CancellationToken token)
+ {
+ //Log ("verbose", $"sending event {method}: {args}");
+ SendEventInternal(sessionId, method, args, token);
+ }
+
+ void SendEventInternal(SessionId sessionId, string method, JObject args, CancellationToken token)
+ {
+ var o = JObject.FromObject(new
+ {
+ method,
+ @params = args
+ });
+ if (sessionId.sessionId != null)
+ o["sessionId"] = sessionId.sessionId;
+
+ Send(this.ide, o, token);
+ }
+
+ internal void SendResponse(MessageId id, Result result, CancellationToken token)
+ {
+ SendResponseInternal(id, result, token);
+ }
+
+ void SendResponseInternal(MessageId id, Result result, CancellationToken token)
+ {
+ JObject o = result.ToJObject(id);
+ if (result.IsErr)
+ logger.LogError($"sending error response for id: {id} -> {result}");
+
+ Send(this.ide, o, token);
+ }
+
+ // , HttpContext context)
+ public async Task Run(Uri browserUri, WebSocket ideSocket)
+ {
+ Log("info", $"DevToolsProxy: Starting on {browserUri}");
+ using (this.ide = ideSocket)
+ {
+ Log("verbose", $"DevToolsProxy: IDE waiting for connection on {browserUri}");
+ queues.Add(new DevToolsQueue(this.ide));
+ using (this.browser = new ClientWebSocket())
+ {
+ this.browser.Options.KeepAliveInterval = Timeout.InfiniteTimeSpan;
+ await this.browser.ConnectAsync(browserUri, CancellationToken.None);
+ queues.Add(new DevToolsQueue(this.browser));
+
+ Log("verbose", $"DevToolsProxy: Client connected on {browserUri}");
+ var x = new CancellationTokenSource();
+
+ pending_ops.Add(ReadOne(browser, x.Token));
+ pending_ops.Add(ReadOne(ide, x.Token));
+ pending_ops.Add(side_exception.Task);
+ pending_ops.Add(client_initiated_close.Task);
+
+ try
+ {
+ while (!x.IsCancellationRequested)
+ {
+ var task = await Task.WhenAny(pending_ops.ToArray());
+ //logger.LogTrace ("pump {0} {1}", task, pending_ops.IndexOf (task));
+ if (task == pending_ops[0])
+ {
+ var msg = ((Task<string>)task).Result;
+ if (msg != null)
+ {
+ pending_ops[0] = ReadOne(browser, x.Token); //queue next read
+ ProcessBrowserMessage(msg, x.Token);
+ }
+ }
+ else if (task == pending_ops[1])
+ {
+ var msg = ((Task<string>)task).Result;
+ if (msg != null)
+ {
+ pending_ops[1] = ReadOne(ide, x.Token); //queue next read
+ ProcessIdeMessage(msg, x.Token);
+ }
+ }
+ else if (task == pending_ops[2])
+ {
+ var res = ((Task<bool>)task).Result;
+ throw new Exception("side task must always complete with an exception, what's going on???");
+ }
+ else if (task == pending_ops[3])
+ {
+ var res = ((Task<bool>)task).Result;
+ Log("verbose", $"DevToolsProxy: Client initiated close from {browserUri}");
+ x.Cancel();
+ }
+ else
+ {
+ //must be a background task
+ pending_ops.Remove(task);
+ var queue = GetQueueForTask(task);
+ if (queue != null)
+ {
+ var tsk = queue.Pump(x.Token);
+ if (tsk != null)
+ pending_ops.Add(tsk);
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ Log("error", $"DevToolsProxy::Run: Exception {e}");
+ //throw;
+ }
+ finally
+ {
+ if (!x.IsCancellationRequested)
+ x.Cancel();
+ }
+ }
+ }
+ }
+
+ protected void Log(string priority, string msg)
+ {
+ switch (priority)
+ {
+ case "protocol":
+ logger.LogTrace(msg);
+ break;
+ case "verbose":
+ logger.LogDebug(msg);
+ break;
+ case "info":
+ case "warning":
+ case "error":
+ default:
+ logger.LogDebug(msg);
+ break;
+ }
+ }
+ }
}