Skip to content

Commit

Permalink
Dispatch console messages to agents
Browse files Browse the repository at this point in the history
Summary:
Add a dispatcher that keeps track subscribers
and sends any console messages to each.

Make the `RuntimeDomainAgent` subscribe
and send console messages over CDP.

Reviewed By: dannysu

Differential Revision: D53946409

fbshipit-source-id: 3a4eae7e0b8a8597920e0724754bc9a0cd765af9
  • Loading branch information
Matt Blagden authored and facebook-github-bot committed Feb 26, 2024
1 parent 094abb9 commit 865857b
Show file tree
Hide file tree
Showing 7 changed files with 213 additions and 7 deletions.
8 changes: 7 additions & 1 deletion API/hermes/cdp/CDPAgent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ class CDPAgentImpl {
int32_t executionContextID_;
HermesRuntime &runtime_;
debugger::AsyncDebuggerAPI &asyncDebuggerAPI_;
ConsoleMessageDispatcher &consoleMessageDispatcher_;

/// Callback function for sending CDP response back. Same as the one in
/// CDPAgentImpl.
Expand Down Expand Up @@ -166,6 +167,7 @@ CDPAgentImpl::DomainAgents::DomainAgents(
: executionContextID_(executionContextID),
runtime_(cdpDebugAPI.runtime()),
asyncDebuggerAPI_(cdpDebugAPI.asyncDebuggerAPI()),
consoleMessageDispatcher_(cdpDebugAPI.consoleMessageDispatcher_),
messageCallback_(std::move(messageCallback)),
objTable_(std::make_shared<RemoteObjectsTable>()) {}

Expand All @@ -177,7 +179,11 @@ void CDPAgentImpl::DomainAgents::initialize() {
messageCallback_,
objTable_);
runtimeAgent_ = std::make_unique<RuntimeDomainAgent>(
executionContextID_, runtime_, messageCallback_, objTable_);
executionContextID_,
runtime_,
messageCallback_,
objTable_,
consoleMessageDispatcher_);
profilerAgent_ = std::make_unique<ProfilerDomainAgent>(
executionContextID_, runtime_, messageCallback_, objTable_);
}
Expand Down
10 changes: 9 additions & 1 deletion API/hermes/cdp/CDPDebugAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,21 @@ std::unique_ptr<CDPDebugAPI> CDPDebugAPI::create(
new CDPDebugAPI(runtime, maxCachedMessages));
}

CDPDebugAPI::~CDPDebugAPI() = default;
CDPDebugAPI::~CDPDebugAPI() {
// Destroy async debugger API first, as it flushes the queue of pending
// tasks, which may reference other objects.
asyncDebuggerAPI_.reset();
};

CDPDebugAPI::CDPDebugAPI(HermesRuntime &runtime, size_t maxCachedMessages)
: runtime_(runtime),
asyncDebuggerAPI_(debugger::AsyncDebuggerAPI::create(runtime)),
consoleMessageStorage_(maxCachedMessages) {}

void CDPDebugAPI::addConsoleMessage(ConsoleMessage message) {
consoleMessageDispatcher_.deliverMessage(message);
}

} // namespace cdp
} // namespace hermes
} // namespace facebook
3 changes: 3 additions & 0 deletions API/hermes/cdp/CDPDebugAPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ class HERMES_EXPORT CDPDebugAPI {
return *asyncDebuggerAPI_;
}

void addConsoleMessage(ConsoleMessage message);

private:
/// Allow CDPAgentImpl (but not integrators) to access
/// consoleMessageStorage_.
Expand All @@ -50,6 +52,7 @@ class HERMES_EXPORT CDPDebugAPI {
HermesRuntime &runtime_;
std::unique_ptr<debugger::AsyncDebuggerAPI> asyncDebuggerAPI_;
ConsoleMessageStorage consoleMessageStorage_;
ConsoleMessageDispatcher consoleMessageDispatcher_;
};

} // namespace cdp
Expand Down
47 changes: 47 additions & 0 deletions API/hermes/cdp/ConsoleMessage.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#define HERMES_CDP_CDPCONSOLEMESSAGESTORAGE_H

#include <queue>
#include <unordered_map>

#include <jsi/jsi.h>

Expand Down Expand Up @@ -75,6 +76,52 @@ class ConsoleMessageStorage {
std::deque<ConsoleMessage> consoleMessageCache_{};
};

class CDPAgent;

/// Token that identifies a specific subscription to console messages.
using ConsoleMessageRegistration = uint32_t;

/// Dispatcher to deliver console messages to all registered subscribers.
/// Everything in this class must be used exclusively from the runtime thread.
class ConsoleMessageDispatcher {
public:
ConsoleMessageDispatcher() {}
~ConsoleMessageDispatcher() {}

/// Register a subscriber and return a token that can be used to
/// unregister in the future. Must only be called from the runtime thread.
ConsoleMessageRegistration subscribe(
std::function<void(const ConsoleMessage &)> handler) {
auto token = ++tokenCounter_;
subscribers_[token] = handler;
return token;
}

/// Unregister a subscriber using the token returned from registration.
/// Must only be called from the runtime thread.
void unsubscribe(ConsoleMessageRegistration token) {
subscribers_.erase(token);
}

/// Deliver a new console message to each subscriber. Must only be called
/// from the runtime thread.
void deliverMessage(const ConsoleMessage &message) {
for (auto &pair : subscribers_) {
pair.second(message);
}
}

private:
/// Collection of subscribers, identified by registration token.
std::unordered_map<
ConsoleMessageRegistration,
std::function<void(const ConsoleMessage &)>>
subscribers_;

/// Counter to generate unique registration tokens.
ConsoleMessageRegistration tokenCounter_ = 0;
};

} // namespace cdp
} // namespace hermes
} // namespace facebook
Expand Down
73 changes: 70 additions & 3 deletions API/hermes/cdp/RuntimeDomainAgent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -255,15 +255,24 @@ RuntimeDomainAgent::RuntimeDomainAgent(
int32_t executionContextID,
HermesRuntime &runtime,
SynchronizedOutboundCallback messageCallback,
std::shared_ptr<RemoteObjectsTable> objTable)
std::shared_ptr<RemoteObjectsTable> objTable,
ConsoleMessageDispatcher &consoleMessageDispatcher)
: DomainAgent(
executionContextID,
std::move(messageCallback),
std::move(objTable)),
runtime_(runtime),
enabled_(false) {}
consoleMessageDispatcher_(consoleMessageDispatcher),
enabled_(false) {
consoleMessageRegistration_ = consoleMessageDispatcher_.subscribe(
[this](const ConsoleMessage &message) {
this->consoleAPICalled(message);
});
}

RuntimeDomainAgent::~RuntimeDomainAgent() {}
RuntimeDomainAgent::~RuntimeDomainAgent() {
consoleMessageDispatcher_.unsubscribe(consoleMessageRegistration_);
}

void RuntimeDomainAgent::enable(const m::runtime::EnableRequest &req) {
if (enabled_) {
Expand Down Expand Up @@ -630,6 +639,64 @@ RuntimeDomainAgent::makePropsFromValue(
return result;
}

static std::string consoleMessageTypeName(ConsoleAPIType type) {
switch (type) {
case ConsoleAPIType::kLog:
return "log";
case ConsoleAPIType::kDebug:
return "debug";
case ConsoleAPIType::kInfo:
return "info";
case ConsoleAPIType::kError:
return "error";
case ConsoleAPIType::kWarning:
return "warning";
case ConsoleAPIType::kDir:
return "dir";
case ConsoleAPIType::kDirXML:
return "dirxml";
case ConsoleAPIType::kTable:
return "table";
case ConsoleAPIType::kTrace:
return "trace";
case ConsoleAPIType::kStartGroup:
return "startGroup";
case ConsoleAPIType::kStartGroupCollapsed:
return "startGroupCollapsed";
case ConsoleAPIType::kEndGroup:
return "endGroup";
case ConsoleAPIType::kClear:
return "clear";
case ConsoleAPIType::kAssert:
return "assert";
case ConsoleAPIType::kTimeEnd:
return "timeEnd";
case ConsoleAPIType::kCount:
return "count";
default:
assert(false && "unknown console API type");
return "error";
}
}

void RuntimeDomainAgent::consoleAPICalled(const ConsoleMessage &message) {
if (!enabled_) {
return;
}

m::runtime::ConsoleAPICalledNotification note;
note.type = consoleMessageTypeName(message.type);
note.timestamp = message.timestamp;
note.executionContextId = executionContextID_;

for (auto &arg : message.args) {
note.args.push_back(m::runtime::makeRemoteObject(
runtime_, arg, *objTable_, "ConsoleObjectGroup", false, false));
}

sendNotificationToClient(note);
}

} // namespace cdp
} // namespace hermes
} // namespace facebook
12 changes: 10 additions & 2 deletions API/hermes/cdp/RuntimeDomainAgent.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#ifndef HERMES_CDP_RUNTIMEDOMAINAGENT_H
#define HERMES_CDP_RUNTIMEDOMAINAGENT_H

#include "CDPDebugAPI.h"
#include "DomainAgent.h"

namespace facebook {
Expand All @@ -24,9 +25,10 @@ class RuntimeDomainAgent : public DomainAgent {
public:
RuntimeDomainAgent(
int32_t executionContextID,
HermesRuntime &runtime_,
HermesRuntime &runtime,
SynchronizedOutboundCallback messageCallback,
std::shared_ptr<RemoteObjectsTable> objTable);
std::shared_ptr<RemoteObjectsTable> objTable,
ConsoleMessageDispatcher &consoleMessageDispatcher);
~RuntimeDomainAgent();

/// Handles Runtime.enable request
Expand All @@ -46,6 +48,8 @@ class RuntimeDomainAgent : public DomainAgent {
void evaluate(const m::runtime::EvaluateRequest &req);
/// Handles Runtime.callFunctionOn request
void callFunctionOn(const m::runtime::CallFunctionOnRequest &req);
/// Dispatches a Runtime.consoleAPICalled notification
void consoleAPICalled(const ConsoleMessage &message);

private:
bool checkRuntimeEnabled(const m::Request &req);
Expand All @@ -62,6 +66,7 @@ class RuntimeDomainAgent : public DomainAgent {
bool generatePreview);

HermesRuntime &runtime_;
ConsoleMessageDispatcher &consoleMessageDispatcher_;

/// Whether Runtime.enable was received and wasn't disabled by receiving
/// Runtime.disable
Expand All @@ -70,6 +75,9 @@ class RuntimeDomainAgent : public DomainAgent {
// preparedScripts_ stores user-entered scripts that have been prepared for
// execution, and may be invoked by a later command.
std::vector<std::shared_ptr<const jsi::PreparedJavaScript>> preparedScripts_;

/// Console message subscription token, used to unsubscribe during shutdown.
ConsoleMessageRegistration consoleMessageRegistration_;
};

} // namespace cdp
Expand Down
67 changes: 67 additions & 0 deletions unittests/API/CDPAgentTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2220,4 +2220,71 @@ TEST_F(CDPAgentTest, RuntimeCallFunctionOnExecutionContext) {
sendAndCheckResponse("Debugger.resume", msgId++);
}

TEST_F(CDPAgentTest, ConsoleLog) {
int msgId = 1;
constexpr double kTimestamp = 123.0;
const std::string kStringValue = "string value";

// Startup
sendAndCheckResponse("Runtime.enable", msgId++);

// Generate message
jsi::String arg0 = jsi::String::createFromAscii(*runtime_, kStringValue);

jsi::Object arg1 = jsi::Object(*runtime_);
arg1.setProperty(*runtime_, "number1", 1);
arg1.setProperty(*runtime_, "bool1", false);

jsi::Object arg2 = jsi::Object(*runtime_);
arg2.setProperty(*runtime_, "number2", 2);
arg2.setProperty(*runtime_, "bool2", true);

ConsoleMessage message(
kTimestamp, ConsoleAPIType::kWarning, std::vector<jsi::Value>());
message.args.reserve(3);
message.args.push_back(std::move(arg0));
message.args.push_back(std::move(arg1));
message.args.push_back(std::move(arg2));
cdpDebugAPI_->addConsoleMessage(std::move(message));

// Validate notification
auto note = expectNotification("Runtime.consoleAPICalled");

EXPECT_EQ(jsonScope_.getNumber(note, {"params", "timestamp"}), kTimestamp);
EXPECT_EQ(
jsonScope_.getNumber(note, {"params", "executionContextId"}),
kTestExecutionContextId);
EXPECT_EQ(jsonScope_.getString(note, {"params", "type"}), "warning");

EXPECT_EQ(jsonScope_.getArray(note, {"params", "args"})->size(), 3);

EXPECT_EQ(
jsonScope_.getString(note, {"params", "args", "0", "type"}), "string");
EXPECT_EQ(
jsonScope_.getString(note, {"params", "args", "0", "value"}),
kStringValue);

EXPECT_EQ(
jsonScope_.getString(note, {"params", "args", "1", "type"}), "object");
std::string object1ID =
jsonScope_.getString(note, {"params", "args", "1", "objectId"});
getAndEnsureProps(
msgId++,
object1ID,
{{"number1", PropInfo("number").setValue("1")},
{"bool1", PropInfo("boolean").setValue("false")},
{"__proto__", PropInfo("object")}});

EXPECT_EQ(
jsonScope_.getString(note, {"params", "args", "2", "type"}), "object");
std::string object2ID =
jsonScope_.getString(note, {"params", "args", "2", "objectId"});
getAndEnsureProps(
msgId++,
object2ID,
{{"number2", PropInfo("number").setValue("2")},
{"bool2", PropInfo("boolean").setValue("true")},
{"__proto__", PropInfo("object")}});
}

#endif // HERMES_ENABLE_DEBUGGER

0 comments on commit 865857b

Please sign in to comment.