Skip to content
Snippets Groups Projects
Verified Commit 9ea4b1c5 authored by Björn Eßwein's avatar Björn Eßwein
Browse files

Reject the promise returned by SendCommand if the browser returned an error.

parent 32bbc9be
No related branches found
No related tags found
No related merge requests found
......@@ -254,10 +254,8 @@ namespace bessw.Unity.WebView.ChromeDevTools
public void Close()
{
Debug.Log($"BrowserTab close called for: '{pageTarget.Url}'");
_ = devtools.SendCommandAsync(new closeTarget(pageTarget.Id), delegate
{
devtools.Dispose();
});
_ = devtools.SendCommandAsync(new closeTarget(pageTarget.Id))
.ContinueWith(_ => devtools.Dispose());
}
}
}
......@@ -7,7 +7,9 @@ using Newtonsoft.Json.Linq;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEditor.PackageManager;
using UnityEngine;
namespace bessw.Unity.WebView.ChromeDevTools
......@@ -21,7 +23,7 @@ namespace bessw.Unity.WebView.ChromeDevTools
private IDevtoolsConnection devtools;
private ConcurrentDictionary<long, ResponseTypeAndCallback> commandResponseDict = new ConcurrentDictionary<long, ResponseTypeAndCallback>();
private ConcurrentDictionary<long, ResponseTypeAndCallback<IDevtoolsResponse>> commandResponseDict = new();
// devtools events
public event Action<screencastFrameEvent> onScreencastFrame;
......@@ -102,18 +104,23 @@ namespace bessw.Unity.WebView.ChromeDevTools
{
// get the callback and the type for this response
ResponseTypeAndCallback responseTypeAndCallback;
if (!commandResponseDict.TryRemove(response.Id, out responseTypeAndCallback))
if (!commandResponseDict.TryRemove(response.Id, out ResponseTypeAndCallback<IDevtoolsResponse> responseTypeAndCallback))
{
Debug.Log($"There is no command waiting for the response '{response.Id}'");
return;
}
if (response.IsError)
{
responseTypeAndCallback.Task.SetException(new DevtoolsCommandException(response.Error));
} else
{
// deserialize the result
IDevtoolsResponse commandResponse = (IDevtoolsResponse) response.Result.ToObject(responseTypeAndCallback.responseType, Browser.devtoolsSerializer);
IDevtoolsResponse commandResponse = (IDevtoolsResponse)response.Result.ToObject(responseTypeAndCallback.ResponseType, Browser.devtoolsSerializer);
// pass the response to the callback
responseTypeAndCallback.callback(commandResponse);
responseTypeAndCallback.Task.SetResult(commandResponse);
}
}
/// <summary>
......@@ -192,16 +199,24 @@ namespace bessw.Unity.WebView.ChromeDevTools
/// Coroutine wrapper for SendCommandAsync
/// json serializes and sends a command over the devtools websocket
/// </summary>
/// <typeparam name="T">IDevtoolsCommand</typeparam>
/// <typeparam name="C">IDevtoolsCommand</typeparam>
/// <param name="command"></param>
/// <returns></returns>
public IEnumerator SendCommand<T>(T command, Action<IDevtoolsResponse> callback) where T : IDevtoolsCommandWithResponse
public IEnumerator SendCommand<C>(C command, Action<IDevtoolsResponse> callback) where C : IDevtoolsCommandWithResponse
{
var sendTask = SendCommandAsync(command, callback);
var sendTask = SendCommandAsync(command)
.ContinueWith(_ => callback);
// wait until the command has been send
yield return new WaitUntil(() => sendTask.IsCompleted);
}
/// <summary>
/// json serializes and sends a command over the devtools websocket
/// </summary>
/// <typeparam name="C">IDevtoolsCommand</typeparam>
/// <returns>Task that resoves then the command is send</returns>
public Task<DevtoolsResponse> SendCommandAsync<C>(C command) where C : IDevtoolsCommand => SendCommandAsync<C, DevtoolsResponse>(command);
/// <summary>
/// json serializes and sends a command over the devtools websocket
/// </summary>
......@@ -209,7 +224,7 @@ namespace bessw.Unity.WebView.ChromeDevTools
/// <typeparam name="R">IDevtoolsResponse</typeparam>
/// <param name="command"></param>
/// <returns>Task that resoves with the response</returns>
public Task<R> SendCommandAsync<C, R>(C command) where C : IDevtoolsCommandWithResponse where R : class, IDevtoolsResponse
public Task<R> SendCommandAsync<C, R>(C command) where C : IDevtoolsCommand where R : class, IDevtoolsResponse
{
// apply the message wrapper
var wrappedCommand = new DevtoolsCommandWrapper<C>(command)
......@@ -217,10 +232,10 @@ namespace bessw.Unity.WebView.ChromeDevTools
Id = ++LAST_COMMAND_ID
};
TaskCompletionSource<R> tcs = new TaskCompletionSource<R>();
TaskCompletionSource<IDevtoolsResponse> response_tcs = new();
// register the response callback for this command id
if (!commandResponseDict.TryAdd(wrappedCommand.Id, new ResponseTypeAndCallback(typeof(R), (res) => tcs.SetResult(res as R))))
if (!commandResponseDict.TryAdd(wrappedCommand.Id, new ResponseTypeAndCallback<IDevtoolsResponse>(typeof(R), response_tcs)))
{
throw new InvalidOperationException($"could not add response callback for command '{wrappedCommand.Id}' to commandResponseDict");
}
......@@ -229,64 +244,8 @@ namespace bessw.Unity.WebView.ChromeDevTools
var json = JsonConvert.SerializeObject(wrappedCommand, Browser.devtoolsSerializerSettings);
Debug.Log($"ws send: '{json}'");
devtools.SendCommandAsync(json);
return tcs.Task;
}
/// <summary>
/// json serializes and sends a command over the devtools websocket
/// </summary>
/// <typeparam name="T">IDevtoolsCommand</typeparam>
/// <param name="command"></param>
/// <returns>Task that resoves then the command is send</returns>
public async Task SendCommandAsync<T>(T command, Action<IDevtoolsResponse> callback) where T : IDevtoolsCommandWithResponse
{
// apply the message wrapper
var wrappedCommand = new DevtoolsCommandWrapper<T>(command)
{
Id = ++LAST_COMMAND_ID
};
// get the response type from the commands attribute
CommandResponseAttribute cra = (CommandResponseAttribute)Attribute.GetCustomAttribute(
command.GetType(),
typeof(CommandResponseAttribute)
);
if( cra == null )
{
throw new NotImplementedException("command has no respnse attribute");
}
// TODO: handle command without response
Type responseType = cra.responseType;
// register the response callback for this command id
if (!commandResponseDict.TryAdd(wrappedCommand.Id, new ResponseTypeAndCallback(responseType, callback)))
{
throw new InvalidOperationException($"could not add response callback for command '{wrappedCommand.Id}' to commandResponseDict");
}
// json serialize the command and send it
var json = JsonConvert.SerializeObject(wrappedCommand, Browser.devtoolsSerializerSettings);
Debug.Log($"ws send: '{json}'");
await devtools.SendCommandAsync(json);
}
/// <summary>
/// json serializes and sends a command over the devtools websocket
/// </summary>
/// <typeparam name="T">IDevtoolsCommand</typeparam>
/// <returns>Task that resoves then the command is send</returns>
public async Task SendCommandAsync<T>(T command) where T : IDevtoolsCommand
{
// apply the message wrapper
var wrappedCommand = new DevtoolsCommandWrapper<T>(command)
{
Id = ++LAST_COMMAND_ID
};
// json serialize the command and send it
var json = JsonConvert.SerializeObject(wrappedCommand, Browser.devtoolsSerializerSettings);
Debug.Log($"ws send: '{json}'");
await devtools.SendCommandAsync(json);
return response_tcs.Task.ContinueWith(task => (R) task.Result);
}
public void Dispose()
......@@ -298,25 +257,34 @@ namespace bessw.Unity.WebView.ChromeDevTools
/////////////////////////////
// Utility classes:
/// <summary>
/// Container type to store a response type together with its callback.
/// This is used in DevtoolsProtocolHandler to parse the response to the correct C# data type and call the callback with it as parameter.
/// </summary>
record ResponseTypeAndCallback
record ResponseTypeAndCallback<RESPONSE_TYPE> where RESPONSE_TYPE : class, IDevtoolsResponse
{
public ResponseTypeAndCallback(Type responseType, Action<IDevtoolsResponse> callback)
public ResponseTypeAndCallback(Type responseType, TaskCompletionSource<RESPONSE_TYPE> task)
{
this.responseType = responseType;
this.callback = callback;
this.ResponseType = responseType;
this.Task = task;
}
public Type responseType { get; }
public Action<IDevtoolsResponse> callback { get; }
public Type ResponseType { get; }
public TaskCompletionSource<RESPONSE_TYPE> Task { get; }
}
public class UnexpectedMessageException : DevtoolsConnectionException
{
public UnexpectedMessageException(string message) : base($"Unexpected message {message}") { }
}
public class DevtoolsCommandException : DevtoolsConnectionException
{
public readonly long Code;
public DevtoolsCommandException(DevtoolsErrorResponse error) : base(error.Message)
{
Code = error.Code;
}
}
}
\ No newline at end of file
......@@ -7,13 +7,6 @@ namespace bessw.Unity.WebView.ChromeDevTools.Protocol
public string Method { get; set; }
public JObject Params { get; set; }
}
///
/// Every devtools command response has the same id and a method as the corresponding command
///
public class DevtoolsEventWrapper<T> : DevtoolsEventWrapper where T : IDevtoolsEvent
{
public new T Params { get; set; }
}
public interface IDevtoolsEvent { }
}
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
#nullable enable annotations
namespace bessw.Unity.WebView.ChromeDevTools.Protocol
{
public class DevtoolsResponseWrapper
{
public long Id { get; set; }
public JObject Result { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public JObject? Result { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public DevtoolsErrorResponse? Error { get; set; }
[JsonIgnore]
public bool IsError => Error is not null;
}
///
/// Every devtools command response has the same id and a method as the corresponding command
///
public class DevtoolsResponseWrapper<T> : DevtoolsResponseWrapper where T : IDevtoolsResponse
public class DevtoolsErrorResponse
{
public new T Result { get; set; }
public long Code { get; set; }
public string Message { get; set; }
}
public interface IDevtoolsResponse { }
public class DevtoolsResponse : IDevtoolsResponse { }
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment