internal class MonoConstants
{
public const string RUNTIME_IS_READY = "mono_wasm_runtime_ready";
+ public const string EVENT_RAISED = "mono_wasm_debug_event_raised:aef14bca-5519-4dfe-b35a-f867abc123ae";
}
class Frame
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Mono.Cecil.Cil;
using Mono.Cecil.Pdb;
}
await RuntimeReady(sessionId, token);
}
+ else if (a?[0]?["value"]?.ToString() == MonoConstants.EVENT_RAISED)
+ {
+ if (a.Type != JTokenType.Array)
+ {
+ logger.LogDebug("Invalid event raised args, expected an array: {a}");
+ }
+ else
+ {
+ if (JObjectTryParse(a?[2]?["value"]?.Value<string>(), out JObject raiseArgs) &&
+ JObjectTryParse(a?[1]?["value"]?.Value<string>(), out JObject eventArgs))
+ {
+ await OnJSEventRaised(sessionId, eventArgs, token);
+ if (raiseArgs?["trace"]?.Value<bool>() == true) {
+ // Let the message show up on the console
+ return false;
+ }
+ }
+ }
+
+ // Don't log this message in the console
+ return true;
+ }
}
break;
}
return true;
}
+ async Task<bool> OnJSEventRaised(SessionId sessionId, JObject eventArgs, CancellationToken token)
+ {
+ string eventName = eventArgs?["eventName"]?.Value<string>();
+ if (string.IsNullOrEmpty(eventName))
+ {
+ logger.LogDebug($"Missing name for raised js event: {eventArgs}");
+ return false;
+ }
+
+ logger.LogDebug($"OnJsEventRaised: args: {eventArgs}");
+
+ switch (eventName)
+ {
+ default:
+ {
+ logger.LogDebug($"Unknown js event name: {eventName} with args {eventArgs}");
+ return await Task.FromResult(false);
+ }
+ }
+ }
+
async Task<bool> OnEvaluateOnCallFrame(MessageId msg_id, int scope_id, string expression, CancellationToken token)
{
try
sessions.Remove(sessionId);
}
}
+
+ bool JObjectTryParse(string str, out JObject obj, bool log_exception = true)
+ {
+ obj = null;
+ if (string.IsNullOrEmpty(str))
+ return false;
+
+ try
+ {
+ obj = JObject.Parse(str);
+ return true;
+ }
+ catch (JsonReaderException jre)
+ {
+ if (log_exception)
+ logger.LogDebug($"Could not parse {str}. Failed with {jre}");
+ return false;
+ }
+ }
}
}
Assert.False(res.IsOk);
});
}
+
+ [Fact]
+ public async Task BadRaiseDebugEventsTest()
+ {
+ var insp = new Inspector();
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+
+ var bad_expressions = new[]
+ {
+ "MONO.mono_wasm_raise_debug_event('')",
+ "MONO.mono_wasm_raise_debug_event(undefined)",
+ "MONO.mono_wasm_raise_debug_event({})",
+
+ "MONO.mono_wasm_raise_debug_event({eventName:'foo'}, '')",
+ "MONO.mono_wasm_raise_debug_event({eventName:'foo'}, 12)"
+ };
+
+ foreach (var expression in bad_expressions)
+ {
+ var res = await ctx.cli.SendCommand($"Runtime.evaluate",
+ JObject.FromObject(new
+ {
+ expression,
+ returnByValue = true
+ }), ctx.token);
+ Assert.False(res.IsOk, $"Expected to fail for {expression}");
+ }
+ });
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ [InlineData(null)]
+ public async Task RaiseDebugEventTraceTest(bool? trace)
+ {
+ var insp = new Inspector();
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+
+ var tcs = new TaskCompletionSource<bool>();
+ insp.On("Runtime.consoleAPICalled", async (args, token) => {
+ if (args?["type"]?.Value<string>() == "debug" &&
+ args?["args"]?.Type == JTokenType.Array &&
+ args?["args"]?[0]?["value"]?.Value<string>()?.StartsWith("mono_wasm_debug_event_raised:") == true)
+ {
+ tcs.SetResult(true);
+ }
+
+ await Task.CompletedTask;
+ });
+
+ var trace_str = trace.HasValue ? $"trace: {trace.ToString().ToLower()}" : String.Empty;
+ var expression = $"MONO.mono_wasm_raise_debug_event({{ eventName:'qwe' }}, {{ {trace_str} }})";
+ var res = await ctx.cli.SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression }), ctx.token);
+ Assert.True(res.IsOk, $"Expected to pass for {expression}");
+
+ var t = await Task.WhenAny(tcs.Task, Task.Delay(2000));
+
+ if (trace == true)
+ Assert.True(tcs.Task == t, "Timed out waiting for the event to be logged");
+ else
+ Assert.False(tcs.Task == t, "Event should not have been logged");
+ });
+ }
}
}
* @type {number} - address in wasm memory
*/
+/**
+ * @typedef Event
+ * @type {object}
+ * @property {string} eventName - name of the event being raised
+ * @property {object} eventArgs - arguments for the event itself
+ */
+
var MonoSupportLib = {
$MONO__postset: 'MONO.export_functions (Module);',
$MONO: {
data = data.slice(length);
}
return true;
- }
+ },
+
+ /**
+ * Raises an event for the debug proxy
+ *
+ * @param {Event} event - event to be raised
+ * @param {object} args - arguments for raising this event, eg. `{trace: true}`
+ */
+ mono_wasm_raise_debug_event: function(event, args={}) {
+ if (typeof event !== 'object')
+ throw new Error(`event must be an object, but got ${JSON.stringify(event)}`);
+
+ if (event.eventName === undefined)
+ throw new Error(`event.eventName is a required parameter, in event: ${JSON.stringify(event)}`);
+
+ if (typeof args !== 'object')
+ throw new Error(`args must be an object, but got ${JSON.stringify(args)}`);
+
+ console.debug('mono_wasm_debug_event_raised:aef14bca-5519-4dfe-b35a-f867abc123ae', JSON.stringify(event), JSON.stringify(args));
+ },
},
mono_wasm_add_typed_value: function (type, str_value, value) {