servo/support/hololens/ServoApp/Devtools/Client.cpp
2020-08-03 17:36:52 +02:00

288 lines
9.1 KiB
C++

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
#include "pch.h"
#include "strutils.h"
#include "Client.h"
using namespace winrt::Windows::Data::Json;
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Networking;
using namespace winrt::Windows::Storage::Streams;
namespace winrt::servo {
void DevtoolsClient::Stop() {
if (mReceiving && mReceiveOp.has_value() &&
mReceiveOp->Status() != AsyncStatus::Completed) {
mReceiveOp->Cancel();
}
}
void DevtoolsClient::Run() {
if (mReceiving) {
throw hresult_error(E_FAIL, L"Already running");
}
mReceiving = true;
auto socket = Sockets::StreamSocket();
auto hostname = HostName(mHostname);
auto connecting = socket.ConnectAsync(hostname, mPort);
connecting.Completed([=](const auto &, const auto &) {
mDataReader = DataReader(socket.InputStream());
mDataWriter = DataWriter(socket.OutputStream());
JsonObject out;
out.Insert(L"auth_token", JsonValue::CreateStringValue(mToken));
Send(out);
mReceiveOp = {Loop()};
mReceiveOp->Completed([=](const auto &, const auto &) {
mReceiveOp = {};
mDataReader->DetachStream();
mDataWriter->DetachStream();
mReceiving = false;
mDelegate.OnDevtoolsDetached();
});
});
}
void DevtoolsClient::Evaluate(hstring code) {
if (!code.empty() && mConsoleActor.has_value()) {
JsonObject out;
out.Insert(L"to", *mConsoleActor);
out.Insert(L"type", JsonValue::CreateStringValue(L"evaluateJSAsync"));
out.Insert(L"text", JsonValue::CreateStringValue(code));
Send(out);
}
}
IAsyncAction DevtoolsClient::Loop() {
auto cancellation = co_await winrt::get_cancellation_token();
cancellation.callback([=] {
if (mReaderOp.Status() != AsyncStatus::Completed) {
mReaderOp.Cancel();
}
});
while (!cancellation()) {
unsigned int len = 0;
while (!cancellation()) {
mReaderOp = mDataReader->LoadAsync(1);
co_await mReaderOp;
hstring c = mDataReader->ReadString(1);
if (c == L":")
break;
try {
unsigned int digit = std::stoi(c.c_str());
len = 10 * len + digit;
} catch (...) {
throw hresult_error(E_FAIL, L"Can't parse message header:" + c);
}
if (len >= 100000) {
throw hresult_error(E_FAIL, L"Message length too long");
}
}
if (cancellation()) {
break;
}
hstring request = L"";
mReaderOp = mDataReader->LoadAsync(len);
auto bytesLoaded = co_await mReaderOp;
request = request + mDataReader->ReadString(bytesLoaded);
JsonObject json;
if (!JsonObject::TryParse(request, json)) {
throw hresult_error(E_FAIL, L"Can't parse message: " + request);
}
HandleMessage(json);
}
}
void DevtoolsClient::HandleMessage(JsonObject obj) {
// Basic devtools protocol implementation:
// https://docs.firefox-dev.tools/backend/protocol.html
if (obj.HasKey(L"from") && obj.GetNamedString(L"from") == L"root") {
if (obj.HasKey(L"applicationType")) {
// First message. Ask for the current tab
JsonObject out;
out.Insert(L"to", JsonValue::CreateStringValue(L"root"));
out.Insert(L"type", JsonValue::CreateStringValue(L"getTab"));
Send(out);
return;
} else if (obj.HasKey(L"tab")) {
// Got the current tab.
auto tab = obj.GetNamedObject(L"tab");
JsonObject out;
out.Insert(L"to", tab.GetNamedValue(L"actor"));
out.Insert(L"type", JsonValue::CreateStringValue(L"getTarget"));
Send(out);
return;
}
} else if (obj.HasKey(L"resultID")) {
// evaluateJSAsync response.
if (obj.GetNamedString(L"type", L"") == L"evaluationResult") {
HandleEvaluationResult(obj);
}
return;
} else if (obj.HasKey(L"type")) { // Not from root
if (obj.GetNamedString(L"type") == L"pageError") {
// Got a page error
HandlePageError(obj.GetNamedObject(L"pageError"));
return;
} else if (obj.GetNamedString(L"type") == L"consoleAPICall") {
// console.* calls
auto message = obj.GetNamedObject(L"message");
HandleConsoleMessage(message);
return;
} else if (obj.GetNamedString(L"type") == L"tabAttached") {
// Ignore
return;
} else if (obj.GetNamedString(L"type") == L"networkEvent") {
// Ignore
return;
} else if (obj.GetNamedString(L"type") == L"tabNavigated") {
if (obj.HasKey(L"state") && obj.GetNamedString(L"state") == L"stop") {
mDelegate.ClearConsole();
}
return;
} else if (obj.GetNamedString(L"type") == L"networkEventUpdate") {
// FIXME: log if there is a non-200 HTTP response
return;
}
} else if (obj.HasKey(L"frame")) {
auto frame = obj.GetNamedObject(L"frame");
// Attach to tab, and ask for cached messaged
JsonObject msg1;
mConsoleActor = frame.GetNamedValue(L"consoleActor");
msg1.Insert(L"to", frame.GetNamedValue(L"actor"));
msg1.Insert(L"type", JsonValue::CreateStringValue(L"attach"));
Send(msg1);
JsonObject msg2;
msg2.Insert(L"to", *mConsoleActor);
msg2.Insert(L"type", JsonValue::CreateStringValue(L"getCachedMessages"));
JsonArray types;
types.Append(JsonValue::CreateStringValue(L"PageError"));
types.Append(JsonValue::CreateStringValue(L"ConsoleAPI"));
msg2.Insert(L"messageTypes", types);
Send(msg2);
return;
} else if (obj.HasKey(L"messages")) {
// Response to getCachedMessages
for (auto messageValue : obj.GetNamedArray(L"messages")) {
auto message = messageValue.GetObject();
if (message.GetNamedString(L"_type") == L"ConsoleAPI") {
HandleConsoleMessage(message);
} else if (message.GetNamedString(L"_type") == L"PageError") {
HandlePageError(message);
} else {
HandleNonHandledMessage(message);
}
}
return;
}
HandleNonHandledMessage(obj);
}
DevtoolsMessageLevel DevtoolsClient::ParseLevel(JsonObject message) {
if (message.GetNamedBoolean(L"error", false)) {
return DevtoolsMessageLevel::Error;
}
if (message.GetNamedBoolean(L"warning", false)) {
return DevtoolsMessageLevel::Warn;
}
if (message.GetNamedBoolean(L"exception", false)) {
return DevtoolsMessageLevel::Error;
}
auto level = message.GetNamedString(L"level", L"");
if (level == L"warn") {
return DevtoolsMessageLevel::Warn;
} else if (level == L"error") {
return DevtoolsMessageLevel::Error;
}
return DevtoolsMessageLevel::None;
}
hstring DevtoolsClient::ParseSource(JsonObject message) {
auto source = message.GetNamedString(L"filename", L"<>");
if (message.HasKey(L"lineNumber")) {
source = source + L":" + to_hstring(message.GetNamedNumber(L"lineNumber"));
}
if (message.HasKey(L"columnNumber")) {
source =
source + L":" + to_hstring(message.GetNamedNumber(L"columnNumber"));
}
return source;
}
void DevtoolsClient::HandlePageError(JsonObject message) {
auto source = ParseSource(message);
auto body = message.GetNamedString(L"errorMessage", L"");
auto level = ParseLevel(message);
mDelegate.OnDevtoolsMessage(level, source, body);
}
void DevtoolsClient::HandleEvaluationResult(JsonObject message) {
auto level = DevtoolsMessageLevel::None;
hstring body = L"";
if (message.HasKey(L"result")) {
auto value = message.GetNamedValue(L"result");
if (value.ValueType() == JsonValueType::Object) {
auto type = value.GetObject().GetNamedString(L"type");
if (type == L"undefined") {
body = L"undefined";
} else {
body = L"<object>";
}
} else {
body = value.Stringify();
}
} else if (message.GetNamedValue(L"exception").ValueType() !=
JsonValueType::Null) {
level = DevtoolsMessageLevel::Error;
body = message.GetNamedString(L"exceptionMessage", L"");
}
mDelegate.OnDevtoolsMessage(level, L"", body);
}
void DevtoolsClient::HandleConsoleMessage(JsonObject message) {
auto source = ParseSource(message);
auto level = ParseLevel(message);
hstring body = L"";
for (auto arg : message.GetNamedArray(L"arguments")) {
body = body + arg.Stringify();
}
mDelegate.OnDevtoolsMessage(level, source, body);
}
void DevtoolsClient::HandleNonHandledMessage(JsonObject message) {
auto level = DevtoolsMessageLevel::Warn;
auto body = L"Unhandled devtools message: " + message.Stringify();
mDelegate.OnDevtoolsMessage(level, L"", body);
}
void DevtoolsClient::SendPendingObjects() {
if (mPendingObjects.empty() || mSending) {
return;
}
mSending = true;
auto obj = mPendingObjects.front();
mPendingObjects.erase(mPendingObjects.begin());
hstring msg = obj.Stringify();
hstring size = to_hstring(msg.size());
hstring request = size + L":" + msg;
mDataWriter->WriteString(request);
mDataWriter->StoreAsync().Completed([=](const auto &, const auto &) {
mDataWriter->FlushAsync().Completed([=](const auto &, const auto &) {
mSending = false;
SendPendingObjects();
});
});
}
void DevtoolsClient::Send(JsonObject obj) {
mPendingObjects.push_back(obj);
SendPendingObjects();
}
} // namespace winrt::servo