Files
react-native/ReactCommon/inspector/InspectorController.cpp
Alexander Blom 156e5d9837 Add on-device JSC inspector
Summary:
Introduces the inspector library supporting the Chrome Debugging Protocol for JavaScriptCore. Eventually this will mean that it is possible to attach
the Chrome inspector directly to the JSC instance running on the device. This library doesn't define the actual transport but leaves that up to the platform
layer.

The main entry point (and the only exported header) is `Inspector.h`.

This diff only introduces the basics supporting the `Schema` and `Inspector` domains meaning it doesn't have any features yet. These will come in following
diffs.

Reviewed By: michalgr

Differential Revision: D4021490

fbshipit-source-id: 517fd9033051c11ba97d312b16382445ae85d3f3
2016-11-02 12:29:14 -07:00

179 lines
4.7 KiB
C++

// Copyright 2004-present Facebook. All Rights Reserved.
#include "InspectorController.h"
#include "Error.h"
#include "Agent.h"
#include "InspectorAgent.h"
#include <folly/Memory.h>
#include <folly/Conv.h>
#include <folly/json.h>
namespace facebook {
namespace react {
class ConcreteChannel : public Channel {
public:
ConcreteChannel(Receiver receiver)
: receiver_(std::move(receiver)) {}
void sendMessage(std::string message) override {
receiver_(std::move(message));
}
void registerDomain(std::string domain, MessageHandler handler) override {
domains_.emplace(std::move(domain), std::move(handler));
}
std::unordered_map<std::string, MessageHandler>& getDomains() {
return domains_;
}
private:
std::unordered_map<std::string, MessageHandler> domains_;
Receiver receiver_;
};
class MessageRouter {
public:
MessageRouter(ConcreteChannel* channel)
: channel_(channel) {
CHECK(channel_) << "Channel is null";
}
/*
* Messages are in JSON, formatted like:
* {
* "id": 1,
* "method": "Debugger.removeBreakpoint",
* "params": { "removeBreakpoint": "xyz" }
* }
*/
void route(std::string message) {
try {
auto json = parseJson(message);
auto callId = getCallId(json);
receive(callId, std::move(message), std::move(json));
} catch (const InspectorException& e) {
channel_->sendMessage(e.error());
}
}
private:
void receive(int callId, std::string message, folly::dynamic json) {
try {
auto method = Method::parse(json["method"].asString());
auto& handler = getHandler(method.domain());
handler(std::move(message), callId, method.name(), std::move(json["params"]));
} catch (const InspectorException& e) {
throw e.withCallId(callId);
} catch (const std::exception& e) {
LOG(ERROR) << "Dispatcher failed: " << e.what();
throw InspectorException(callId, ErrorCode::ServerError, "Internal error");
} catch (...) {
throw InspectorException(callId, ErrorCode::ServerError, "Internal error");
}
}
folly::dynamic parseJson(const std::string& message) {
try {
return folly::parseJson(message);
} catch (const std::runtime_error& e) {
throw InspectorException(ErrorCode::ParseError, "Message must be in JSON format");
}
}
int getCallId(folly::dynamic& json) {
auto& id = json["id"];
if (!id.isInt()) {
throw InspectorException(ErrorCode::InvalidRequest, "The type of 'id' property must be number");
} else {
return id.asInt();
}
}
Channel::MessageHandler& getHandler(const std::string& domain) {
try {
auto& domains = channel_->getDomains();
return domains.at(domain);
} catch (const std::out_of_range& e) {
throw InspectorException(ErrorCode::MethodNotFound, folly::to<std::string>("Unknown domain: '", domain, "'"));
}
}
ConcreteChannel* channel_;
};
class SchemaAgent : public Agent {
public:
SchemaAgent() {
registerMethod("getDomains", [this](folly::dynamic) -> folly::dynamic {
CHECK(channel_) << "Channel is null";
folly::dynamic names = folly::dynamic::array;
auto& domains = channel_->getDomains();
for (auto& entry : domains) {
// TODO(blom): Actually get version?
names.push_back(folly::dynamic::object("name", entry.first)("version", "1.0"));
}
return names;
});
}
void onConnect(std::shared_ptr<Channel> channel) override {
Agent::onConnect(channel);
channel_ = std::static_pointer_cast<ConcreteChannel>(channel);
}
private:
std::shared_ptr<ConcreteChannel> channel_;
std::string getDomain() override {
return "Schema";
}
};
InspectorController::InspectorController(JSC::JSGlobalObject& globalObject)
: globalObject_(globalObject) {
auto inspectorAgent = folly::make_unique<InspectorAgent>();
inspectorAgent_ = inspectorAgent.get();
dispatchers_.push_back(std::move(inspectorAgent));
dispatchers_.push_back(folly::make_unique<SchemaAgent>());
}
InspectorController::~InspectorController() {
CHECK(!channel_) << "Wasn't disconnected";
}
void InspectorController::onConnect(Receiver receiver) {
CHECK(!channel_) << "Already connected";
channel_ = std::make_shared<ConcreteChannel>(std::move(receiver));
for (auto& dispatcher : dispatchers_) {
dispatcher->onConnect(channel_);
}
}
void InspectorController::onMessage(std::string message) {
CHECK(channel_) << "Not connected";
MessageRouter(channel_.get()).route(message);
}
void InspectorController::onGoingAway() {
CHECK(channel_) << "Not connected";
inspectorAgent_->detach();
}
void InspectorController::onDisconnect() {
CHECK(channel_) << "Not connected";
for (auto& dispatcher : dispatchers_) {
dispatcher->onDisconnect();
}
channel_.reset();
}
}
}