protected override async Task <bool> AcceptEvent(SessionId sessionId, string method, JObject args, CancellationToken token) { switch (method) { case "Runtime.consoleAPICalled": { var type = args["type"]?.ToString(); if (type == "debug") { var a = args["args"]; if (a?[0]?["value"]?.ToString() == MonoConstants.RUNTIME_IS_READY && a?[1]?["value"]?.ToString() == "fe00e07a-5519-4dfe-b35a-f867dbaf2e28") { if (a.Count() > 2) { try { // The optional 3rd argument is the stringified assembly // list so that we don't have to make more round trips var context = GetContext(sessionId); var loaded = a?[2]?["value"]?.ToString(); if (loaded != null) { context.LoadedFiles = JToken.Parse(loaded).ToObject <string[]>(); } } catch (InvalidCastException ice) { Log("verbose", ice.ToString()); } } await RuntimeReady(sessionId, token); } } break; } case "Runtime.executionContextCreated": { SendEvent(sessionId, method, args, token); var ctx = args?["context"]; var aux_data = ctx?["auxData"] as JObject; var id = ctx["id"].Value <int>(); if (aux_data != null) { var is_default = aux_data["isDefault"]?.Value <bool>(); if (is_default == true) { await OnDefaultContext(sessionId, new ExecutionContext { Id = id, AuxData = aux_data }, token); } } return(true); } case "Debugger.paused": { //TODO figure out how to stich out more frames and, in particular what happens when real wasm is on the stack var top_func = args?["callFrames"]?[0]?["functionName"]?.Value <string>(); if (top_func == "mono_wasm_fire_bp" || top_func == "_mono_wasm_fire_bp" || top_func == "_mono_wasm_fire_exception") { return(await OnPause(sessionId, args, token)); } break; } case "Debugger.breakpointResolved": { break; } case "Debugger.scriptParsed": { var url = args?["url"]?.Value <string>() ?? ""; switch (url) { case var _ when url == "": case var _ when url.StartsWith("wasm://", StringComparison.Ordinal): { Log("verbose", $"ignoring wasm: Debugger.scriptParsed {url}"); return(true); } } Log("verbose", $"proxying Debugger.scriptParsed ({sessionId.sessionId}) {url} {args}"); break; } case "Target.attachedToTarget": { if (args["targetInfo"]["type"]?.ToString() == "page") { await DeleteWebDriver(new SessionId(args["sessionId"]?.ToString()), token); } break; } } return(false); }
//static int frame_id=0; async Task <bool> OnPause(SessionId sessionId, JObject args, CancellationToken token) { //FIXME we should send release objects every now and then? Or intercept those we inject and deal in the runtime var res = await SendMonoCommand(sessionId, MonoCommands.GetCallStack(), token); var orig_callframes = args?["callFrames"]?.Values <JObject>(); var context = GetContext(sessionId); JObject data = null; var reason = "other";//other means breakpoint if (res.IsErr) { //Give up and send the original call stack return(false); } //step one, figure out where did we hit var res_value = res.Value?["result"]?["value"]; if (res_value == null || res_value is JValue) { //Give up and send the original call stack return(false); } Log("verbose", $"call stack (err is {res.Error} value is:\n{res.Value}"); var bp_id = res_value?["breakpoint_id"]?.Value <int>(); Log("verbose", $"We just hit bp {bp_id}"); if (!bp_id.HasValue) { //Give up and send the original call stack return(false); } var bp = context.BreakpointRequests.Values.SelectMany(v => v.Locations).FirstOrDefault(b => b.RemoteId == bp_id.Value); var callFrames = new List <object>(); foreach (var frame in orig_callframes) { var function_name = frame["functionName"]?.Value <string>(); var url = frame["url"]?.Value <string>(); if ("mono_wasm_fire_bp" == function_name || "_mono_wasm_fire_bp" == function_name || "_mono_wasm_fire_exception" == function_name) { if ("_mono_wasm_fire_exception" == function_name) { var exception_obj_id = await SendMonoCommand(sessionId, MonoCommands.GetExceptionObject(), token); var res_val = exception_obj_id.Value?["result"]?["value"]; var exception_dotnet_obj_id = new DotnetObjectId("object", res_val?["exception_id"]?.Value <string>()); data = JObject.FromObject(new { type = "object", subtype = "error", className = res_val?["class_name"]?.Value <string>(), uncaught = res_val?["uncaught"]?.Value <bool>(), description = res_val?["message"]?.Value <string>() + "\n", objectId = exception_dotnet_obj_id.ToString() }); reason = "exception"; } var frames = new List <Frame>(); int frame_id = 0; var the_mono_frames = res.Value?["result"]?["value"]?["frames"]?.Values <JObject>(); foreach (var mono_frame in the_mono_frames) { ++frame_id; var il_pos = mono_frame["il_pos"].Value <int>(); var method_token = mono_frame["method_token"].Value <uint>(); var assembly_name = mono_frame["assembly_name"].Value <string>(); // This can be different than `method.Name`, like in case of generic methods var method_name = mono_frame["method_name"]?.Value <string>(); var store = await LoadStore(sessionId, token); var asm = store.GetAssemblyByName(assembly_name); if (asm == null) { Log("info", $"Unable to find assembly: {assembly_name}"); continue; } var method = asm.GetMethodByToken(method_token); if (method == null) { Log("info", $"Unable to find il offset: {il_pos} in method token: {method_token} assembly name: {assembly_name}"); continue; } var location = method?.GetLocationByIl(il_pos); // When hitting a breakpoint on the "IncrementCount" method in the standard // Blazor project template, one of the stack frames is inside mscorlib.dll // and we get location==null for it. It will trigger a NullReferenceException // if we don't skip over that stack frame. if (location == null) { continue; } Log("info", $"frame il offset: {il_pos} method token: {method_token} assembly name: {assembly_name}"); Log("info", $"\tmethod {method_name} location: {location}"); frames.Add(new Frame(method, location, frame_id - 1)); callFrames.Add(new { functionName = method_name, callFrameId = $"dotnet:scope:{frame_id - 1}", functionLocation = method.StartLocation.AsLocation(), location = location.AsLocation(), url = store.ToUrl(location), scopeChain = new[] { new { type = "local", @object = new { @type = "object", className = "Object", description = "Object", objectId = $"dotnet:scope:{frame_id-1}", }, name = method_name, startLocation = method.StartLocation.AsLocation(), endLocation = method.EndLocation.AsLocation(), } } }); context.CallStack = frames; } } else if (!(function_name.StartsWith("wasm-function", StringComparison.Ordinal) || url.StartsWith("wasm://wasm/", StringComparison.Ordinal))) { callFrames.Add(frame); } } var bp_list = new string[bp == null ? 0 : 1]; if (bp != null) { bp_list[0] = bp.StackId; } var o = JObject.FromObject(new { callFrames, reason, data, hitBreakpoints = bp_list, }); SendEvent(sessionId, "Debugger.paused", o, token); return(true); }
internal Task <Result> SendMonoCommand(SessionId id, MonoCommands cmd, CancellationToken token) => SendCommand(id, "Runtime.evaluate", JObject.FromObject(cmd), token);
protected virtual Task <bool> AcceptEvent(SessionId sessionId, string method, JObject args, CancellationToken token) { return(Task.FromResult(false)); }
public void SendEvent(SessionId sessionId, string method, JObject args, CancellationToken token) { //Log ("verbose", $"sending event {method}: {args}"); SendEventInternal(sessionId, method, args, 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)); }
public virtual Task SendEvent(SessionId sessionId, string method, JObject args, CancellationToken token) { // logger.LogTrace($"sending event {method}: {args}"); return(SendEventInternal(sessionId, method, args, token)); }