Skip to content

Commit

Permalink
Emit executionContextCreated notification from hcdp (#1378)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #1378

Without emitting the `Runtime.executionContextCreated` notification,
Chrome DevTools' Console tab doesn't work.

Adding code to intercept `Runtime.enable` and emit execution context
information. Also added handler for `console.log`. This way we can
exercise the ConsoleMessage-related code paths without using RN.

Reviewed By: mattbfb

Differential Revision: D56337202

fbshipit-source-id: 7230f73f2fdaa3e8a4db321e8e89b064f9f43269
  • Loading branch information
dannysu authored and facebook-github-bot committed Apr 22, 2024
1 parent fb98d3c commit 6285c8b
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 8 deletions.
10 changes: 7 additions & 3 deletions API/hermes/cdp/ConsoleMessage.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,17 @@ struct ConsoleMessage {
double timestamp;
ConsoleAPIType type;
std::vector<jsi::Value> args;
debugger::StackTrace stackTrace{};
debugger::StackTrace stackTrace;

ConsoleMessage(
double timestamp,
ConsoleAPIType type,
std::vector<jsi::Value> args)
: timestamp(timestamp), type(type), args(std::move(args)) {}
std::vector<jsi::Value> args,
debugger::StackTrace stackTrace = {})
: timestamp(timestamp),
type(type),
args(std::move(args)),
stackTrace(stackTrace) {}
};

class ConsoleMessageStorage {
Expand Down
20 changes: 19 additions & 1 deletion tools/hcdp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,32 @@
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

set(NO_EH_RTTI_SOURCES
JSONHelpers.cpp
../../API/hermes/cdp/JSONValueInterfaces.cpp
../../API/hermes/cdp/MessageInterfaces.cpp
../../API/hermes/cdp/MessageTypes.cpp
)

if (NOT HERMES_ENABLE_EH_RTTI)
if (GCC_COMPATIBLE)
set_property(SOURCE ${NO_EH_RTTI_SOURCES} APPEND_STRING
PROPERTY COMPILE_FLAGS "-fno-exceptions -fno-rtti")
elseif (MSVC)
set_property(SOURCE ${NO_EH_RTTI_SOURCES} APPEND_STRING
PROPERTY COMPILE_FLAGS "/EHs-c- /GR-")
endif ()
endif ()

set(HERMES_ENABLE_EH_RTTI ON)
add_hermes_tool(hcdp
hcdp.cpp
IPC.cpp
${NO_EH_RTTI_SOURCES}
${ALL_HEADER_FILES}
)

target_link_libraries(hcdp hermesSerialExecutor libhermes)
target_link_libraries(hcdp hermesSerialExecutor libhermes hermesParser)

install(TARGETS hcdp
RUNTIME DESTINATION bin
Expand Down
40 changes: 40 additions & 0 deletions tools/hcdp/JSONHelpers.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#include "JSONHelpers.h"

#include <hermes/Parser/JSONParser.h>
#include <hermes/cdp/JSONValueInterfaces.h>
#include <hermes/cdp/MessageTypesInlines.h>

namespace hermes {

using namespace ::hermes::parser;
using namespace ::facebook::hermes::cdp;
using namespace ::facebook::hermes::cdp::message;

std::optional<long long> getResponseId(const std::string &message) {
JSLexer::Allocator allocator;
JSONFactory factory(allocator);
std::optional<JSONObject *> obj = parseStrAsJsonObj(message, factory);
if (!obj.has_value()) {
return std::nullopt;
}

JSONValue *v = obj.value()->get("id");
if (v == nullptr) {
return std::nullopt;
}

std::unique_ptr<long long> id = valueFromJson<long long>(v);
if (id == nullptr) {
return std::nullopt;
}
return *id;
}

} // namespace hermes
22 changes: 22 additions & 0 deletions tools/hcdp/JSONHelpers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#ifndef HERMES_TOOLS_HCDP_JSONHELPERS_H
#define HERMES_TOOLS_HCDP_JSONHELPERS_H

#include <optional>
#include <string>

namespace hermes {

/// Parses a JSON string and extract the message ID out from it.
/// \param message JSON string
std::optional<long long> getResponseId(const std::string &message);

} // namespace hermes

#endif // HERMES_TOOLS_HCDP_JSONHELPERS_H
103 changes: 99 additions & 4 deletions tools/hcdp/hcdp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,32 @@ int main(void) {
#include <filesystem>
#include <fstream>
#include <iostream>
#include <sstream>

#include <hermes/Parser/JSONParser.h>
#include <hermes/SerialExecutor/SerialExecutor.h>
#include <hermes/Support/JSONEmitter.h>
#include <hermes/cdp/CDPAgent.h>
#include <hermes/cdp/CDPDebugAPI.h>
#include <hermes/cdp/MessageTypes.h>
#include <hermes/hermes.h>

#include "IPC.h"
#include "JSONHelpers.h"

namespace hermes {

namespace fbhermes = ::facebook::hermes;
namespace cdp = fbhermes::cdp;
namespace message = fbhermes::cdp::message;
using namespace facebook::hermes;

constexpr uint32_t kExecutionContextId = 1;
using namespace facebook;
using namespace ::hermes::parser;

namespace {

static uint32_t nextExecutionContextId = 1;

/// Manages a runtime executing a script on a thread.
class RuntimeInstance {
public:
Expand All @@ -49,6 +58,40 @@ class RuntimeInstance {
.build())) {
cdpDebugAPI_ = cdp::CDPDebugAPI::create(*runtime_);

// Install console.log handler
HermesRuntime &hermesRt = *runtime_.get();
auto console = jsi::Object(hermesRt);
auto nameID = jsi::PropNameID::forUtf8(hermesRt, "log");
console.setProperty(
hermesRt,
nameID,
jsi::Function::createFromHostFunction(
hermesRt,
nameID,
1,
[cdpDebugAPI = cdpDebugAPI_.get(), &hermesRt](
jsi::Runtime &runtime,
const jsi::Value &,
const jsi::Value *argsPtr,
size_t count) {
std::vector<jsi::Value> args;
for (size_t i = 0; i < count; ++i) {
args.emplace_back(runtime, argsPtr[i]);
}
const auto now = std::chrono::system_clock::now();
double timestamp =
std::chrono::duration_cast<std::chrono::seconds>(
now.time_since_epoch())
.count();
cdpDebugAPI->addConsoleMessage(
{timestamp,
cdp::ConsoleAPIType::kLog,
std::move(args),
hermesRt.getDebugger().captureStackTrace()});
return jsi::Value::undefined();
}));
runtime_->global().setProperty(hermesRt, "console", console);

executor_->add([this,
source = std::move(scriptSource),
url = std::move(scriptUrl)]() mutable {
Expand Down Expand Up @@ -86,6 +129,29 @@ class RuntimeInstance {
std::unique_ptr<fbhermes::HermesRuntime> runtime_;
std::unique_ptr<cdp::CDPDebugAPI> cdpDebugAPI_;
};

/// Thread-safe class for storing and later retrieving a message ID
class SynchronizedMessageId {
public:
void setId(long long id) {
std::lock_guard<std::mutex> lock(mutex_);
id_ = id;
}

void clearId() {
std::lock_guard<std::mutex> lock(mutex_);
id_ = std::nullopt;
}

std::optional<long long> getId() {
std::lock_guard<std::mutex> lock(mutex_);
return id_;
}

private:
std::mutex mutex_{};
std::optional<long long> id_{};
};
} // namespace

/// Read a script from the specified path.
Expand All @@ -106,6 +172,11 @@ static void debugScript(std::string scriptSource, std::string scriptUrl) {
std::make_unique<RuntimeInstance>(
std::move(scriptSource), std::move(scriptUrl));

uint32_t executionContextId = nextExecutionContextId++;

std::shared_ptr<SynchronizedMessageId> runtimeEnableMessageId =
std::make_shared<SynchronizedMessageId>();

// Process IPC messages
std::unordered_map<ClientID, std::unique_ptr<cdp::CDPAgent>> agents;
while (std::optional<IPCCommand> ipc = receiveIPC()) {
Expand All @@ -116,7 +187,7 @@ static void debugScript(std::string scriptSource, std::string scriptUrl) {
agents.emplace(
clientID,
cdp::CDPAgent::create(
kExecutionContextId,
executionContextId,
runtimeInstance->cdpDebugAPI(),
[&runtimeInstance](
std::function<void(fbhermes::HermesRuntime &)> task) {
Expand All @@ -125,7 +196,24 @@ static void debugScript(std::string scriptSource, std::string scriptUrl) {
runtimeInstance->addTask(
[&runtime, task = std::move(task)]() { task(runtime); });
},
[clientID](const std::string &message) {
[clientID, executionContextId, runtimeEnableMessageId](
const std::string &message) {
// Emit executionContextCreated notification, which is
// required for the Console tab to work.
if (runtimeEnableMessageId->getId().has_value()) {
auto id = getResponseId(message);
if (id.has_value() &&
id.value() == runtimeEnableMessageId->getId().value()) {
message::runtime::ExecutionContextCreatedNotification
note;
message::runtime::ExecutionContextDescription desc;
desc.id = executionContextId;
desc.name = "main";
note.context = std::move(desc);
sendIPC(kMessageIPCType, clientID, note.toJsonStr());
runtimeEnableMessageId->clearId();
}
}
// Forward message to the client, via an IPC message.
sendIPC(kMessageIPCType, clientID, message);
}));
Expand All @@ -139,6 +227,13 @@ static void debugScript(std::string scriptSource, std::string scriptUrl) {
// or one that never existed.
throw std::runtime_error("No such agent");
}
std::unique_ptr<message::Request> command =
message::Request::fromJson(ipc.value().message);
// Save Runtime.enable's message ID so that we could intercept the
// response and emit executionContextCreated notification.
if (command->method == "Runtime.enable") {
runtimeEnableMessageId->setId(command->id);
}
agent->second->handleCommand(ipc.value().message);
} break;

Expand Down

0 comments on commit 6285c8b

Please sign in to comment.