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
This commit is contained in:
parent
9bfd95c8aa
commit
156e5d9837
|
@ -28,6 +28,7 @@ if THIS_IS_FBANDROID:
|
|||
],
|
||||
deps = [
|
||||
'//xplat/folly:molly',
|
||||
react_native_xplat_target('inspector:inspector'),
|
||||
])
|
||||
|
||||
cxx_library(
|
||||
|
@ -39,6 +40,7 @@ if THIS_IS_FBANDROID:
|
|||
'-DWITH_JSC_MEMORY_PRESSURE=1',
|
||||
'-DWITH_REACT_INTERNAL_SETTINGS=1',
|
||||
'-DWITH_FB_MEMORY_PROFILING=1',
|
||||
'-DWITH_INSPECTOR=1',
|
||||
],
|
||||
deps = JSC_DEPS
|
||||
)
|
||||
|
|
|
@ -19,6 +19,10 @@
|
|||
#include <jschelpers/JSCHelpers.h>
|
||||
#include <jschelpers/Value.h>
|
||||
|
||||
#ifdef WITH_INSPECTOR
|
||||
#include <inspector/Inspector.h>
|
||||
#endif
|
||||
|
||||
#include "Platform.h"
|
||||
#include "SystraceSection.h"
|
||||
#include "JSCNativeModules.h"
|
||||
|
@ -229,6 +233,10 @@ void JSCExecutor::initOnJSVMThread() throw(JSException) {
|
|||
// Add a pointer to ourselves so we can retrieve it later in our hooks
|
||||
JSObjectSetPrivate(JSContextGetGlobalObject(m_context), this);
|
||||
|
||||
#ifdef WITH_INSPECTOR
|
||||
Inspector::instance().registerGlobalContext("main", m_context);
|
||||
#endif
|
||||
|
||||
installNativeHook<&JSCExecutor::nativeFlushQueueImmediate>("nativeFlushQueueImmediate");
|
||||
installNativeHook<&JSCExecutor::nativeCallSyncHook>("nativeCallSyncHook");
|
||||
|
||||
|
@ -283,6 +291,10 @@ void JSCExecutor::terminateOnJSVMThread() {
|
|||
|
||||
m_nativeModules.reset();
|
||||
|
||||
#ifdef WITH_INSPECTOR
|
||||
Inspector::instance().unregisterGlobalContext(m_context);
|
||||
#endif
|
||||
|
||||
JSGlobalContextRelease(m_context);
|
||||
m_context = nullptr;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#include "Agent.h"
|
||||
|
||||
#include "Error.h"
|
||||
#include "Protocol.h"
|
||||
|
||||
#include <folly/json.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
void Agent::onConnect(std::shared_ptr<Channel> channel) {
|
||||
channel_ = std::move(channel);
|
||||
|
||||
channel_->registerDomain(getDomain(), [this](std::string, int callId, const std::string& method, folly::dynamic args) {
|
||||
auto result = handle(method, std::move(args));
|
||||
if (result.isNull()) {
|
||||
result = folly::dynamic::object;
|
||||
}
|
||||
auto message = folly::dynamic::object("id", callId)("result", std::move(result));
|
||||
channel_->sendMessage(folly::toJson(std::move(message)));
|
||||
});
|
||||
}
|
||||
|
||||
void Agent::onDisconnect() {
|
||||
channel_.reset();
|
||||
}
|
||||
|
||||
folly::dynamic Agent::handle(const std::string& method, folly::dynamic args) {
|
||||
try {
|
||||
return methods_.at(method)(std::move(args));
|
||||
} catch (const std::out_of_range& e) {
|
||||
throw InspectorException(ErrorCode::MethodNotFound, "Unknown method: '" + method + "'");
|
||||
}
|
||||
}
|
||||
|
||||
void Agent::registerMethod(std::string name, Method method) {
|
||||
methods_.emplace(std::move(name), std::move(method));
|
||||
}
|
||||
|
||||
void Agent::sendEvent(std::string name, folly::dynamic params) {
|
||||
if (!channel_) {
|
||||
return;
|
||||
}
|
||||
channel_->sendMessage(Event(getDomain(), std::move(name), std::move(params)));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Dispatcher.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
class Agent : public Dispatcher {
|
||||
public:
|
||||
void onConnect(std::shared_ptr<Channel> channel) override;
|
||||
void onDisconnect() override;
|
||||
protected:
|
||||
using Method = std::function<folly::dynamic(folly::dynamic)>;
|
||||
void registerMethod(std::string name, Method method);
|
||||
void sendEvent(std::string name, folly::dynamic params = nullptr);
|
||||
|
||||
virtual std::string getDomain() = 0;
|
||||
private:
|
||||
folly::dynamic handle(const std::string& method, folly::dynamic args);
|
||||
|
||||
std::shared_ptr<Channel> channel_;
|
||||
std::unordered_map<std::string, Method> methods_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_MODULE := inspector
|
||||
|
||||
LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp)
|
||||
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/..
|
||||
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)
|
||||
|
||||
LOCAL_CFLAGS := \
|
||||
-DLOG_TAG=\"ReactNative\"
|
||||
|
||||
LOCAL_CFLAGS += -Wall -Werror -fexceptions -frtti
|
||||
CXX11_FLAGS := -std=c++11
|
||||
LOCAL_CFLAGS += $(CXX11_FLAGS)
|
||||
LOCAL_EXPORT_CPPFLAGS := $(CXX11_FLAGS)
|
||||
|
||||
LOCAL_STATIC_LIBRARIES := jschelpers
|
||||
LOCAL_SHARED_LIBRARIES := libfolly_json libjsc libglog
|
||||
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
$(call import-module,folly)
|
||||
$(call import-module,jsc)
|
||||
$(call import-module,glog)
|
||||
$(call import-module,jschelpers)
|
|
@ -0,0 +1,54 @@
|
|||
EXPORTED_HEADERS = [
|
||||
'Inspector.h',
|
||||
]
|
||||
|
||||
def library(**kwargs):
|
||||
if THIS_IS_FBANDROID:
|
||||
include_defs('//ReactAndroid/DEFS')
|
||||
cxx_library(
|
||||
force_static = True,
|
||||
# We depend on JSC, support the same platforms
|
||||
supported_platforms_regex = '^android-(armv7|x86)$',
|
||||
deps = JSC_DEPS + [
|
||||
'//xplat/folly:molly',
|
||||
react_native_xplat_target('jschelpers:jschelpers'),
|
||||
],
|
||||
**kwargs
|
||||
)
|
||||
elif THIS_IS_FBOBJC:
|
||||
pass
|
||||
# TODO: Currently untested on iOS, but might require linking against a
|
||||
# custom version to ensure binary stability?
|
||||
# ios_library(
|
||||
# inherited_buck_flags = STATIC_LIBRARY_IOS_FLAGS,
|
||||
# frameworks = [
|
||||
# '$SDKROOT/System/Library/Frameworks/JavaScriptCore.framework',
|
||||
# ],
|
||||
# deps = [
|
||||
# '//xplat/folly:molly',
|
||||
# react_native_xplat_target('jschelpers:jschelpers'),
|
||||
# ],
|
||||
# **kwargs
|
||||
# )
|
||||
else:
|
||||
raise Error('Unknown repo')
|
||||
|
||||
library(
|
||||
name = 'inspector',
|
||||
preprocessor_flags = [
|
||||
'-DENABLE_INSPECTOR=1',
|
||||
],
|
||||
compiler_flags = [
|
||||
'-Wall',
|
||||
'-fexceptions',
|
||||
'-fvisibility=hidden',
|
||||
'-std=gnu++1y',
|
||||
],
|
||||
exported_headers = EXPORTED_HEADERS,
|
||||
headers = glob(['*.h'], excludes=EXPORTED_HEADERS),
|
||||
header_namespace = 'inspector',
|
||||
srcs = glob(['*.cpp']),
|
||||
visibility = [
|
||||
'PUBLIC',
|
||||
],
|
||||
)
|
|
@ -0,0 +1,3 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#include "Dispatcher.h"
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <folly/dynamic.h>
|
||||
// Both double-conversions and WTF define the ASSERT macro
|
||||
#undef ASSERT
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
class Channel {
|
||||
public:
|
||||
using MessageHandler = std::function<void(std::string message, int callId, const std::string& methodName, folly::dynamic args)>;
|
||||
|
||||
virtual ~Channel() = default;
|
||||
|
||||
virtual void sendMessage(std::string message) = 0;
|
||||
virtual void registerDomain(std::string domain, MessageHandler handler) = 0;
|
||||
};
|
||||
|
||||
class Dispatcher {
|
||||
public:
|
||||
virtual ~Dispatcher() {}
|
||||
|
||||
virtual void onConnect(std::shared_ptr<Channel> channel) = 0;
|
||||
virtual void onDisconnect() = 0;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#include "Error.h"
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
InspectorException::InspectorException(int callId, ErrorCode code, std::string message)
|
||||
: error_(callId, code, std::move(message)) {}
|
||||
|
||||
InspectorException::InspectorException(ErrorCode code, std::string message)
|
||||
: error_(code, std::move(message)) {}
|
||||
|
||||
InspectorException InspectorException::withCallId(int callId) const {
|
||||
return InspectorException(callId, error_.code(), error_.message());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Protocol.h"
|
||||
|
||||
#include <string>
|
||||
#include <exception>
|
||||
|
||||
#include <folly/Optional.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
class InspectorException : public std::exception {
|
||||
public:
|
||||
InspectorException(int callId, ErrorCode code, std::string message);
|
||||
explicit InspectorException(ErrorCode code, std::string message);
|
||||
|
||||
const char* what() const throw() override {
|
||||
return error_.message().c_str();
|
||||
}
|
||||
|
||||
const Error& error() const {
|
||||
return error_;
|
||||
}
|
||||
|
||||
InspectorException withCallId(int callId) const;
|
||||
private:
|
||||
Error error_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#include "Inspector.h"
|
||||
|
||||
#include "InspectorController.h"
|
||||
|
||||
#include <JavaScriptCore/config.h>
|
||||
|
||||
#include <JavaScriptCore/APICast.h>
|
||||
#include <JavaScriptCore/JSGlobalObject.h>
|
||||
#include <JavaScriptCore/JSLock.h>
|
||||
|
||||
#include <folly/Memory.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
namespace {
|
||||
|
||||
JSC::JSGlobalObject& getGlobalObject(JSContextRef ctx) {
|
||||
JSC::ExecState* exec = toJS(ctx);
|
||||
JSC::JSLockHolder locker(exec);
|
||||
|
||||
JSC::JSGlobalObject* globalObject = exec->vmEntryGlobalObject();
|
||||
return *globalObject;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Inspector::LocalConnection::LocalConnection(std::shared_ptr<Inspector::DuplexConnection> duplexConnection)
|
||||
: duplexConnection_(std::move(duplexConnection)) {}
|
||||
|
||||
void Inspector::LocalConnection::sendMessage(std::string message) {
|
||||
duplexConnection_->sendToLocal(std::move(message));
|
||||
}
|
||||
|
||||
void Inspector::LocalConnection::disconnect() {
|
||||
duplexConnection_->terminate(false);
|
||||
}
|
||||
|
||||
Inspector::PageHolder::PageHolder(std::string name, std::unique_ptr<InspectorController> controller)
|
||||
: name(name)
|
||||
, controller(std::move(controller)) {}
|
||||
|
||||
Inspector::PageHolder::~PageHolder() = default;
|
||||
|
||||
Inspector& Inspector::instance() {
|
||||
static Inspector inspector;
|
||||
return inspector;
|
||||
}
|
||||
|
||||
std::vector<Inspector::Page> Inspector::getPages() const {
|
||||
std::lock_guard<std::mutex> lock(registrationMutex_);
|
||||
std::vector<Page> pages;
|
||||
pages.reserve(pages_.size());
|
||||
for (auto& entry : pages_) {
|
||||
pages.emplace_back(Page{entry.first, entry.second.name});
|
||||
}
|
||||
return pages;
|
||||
}
|
||||
|
||||
void Inspector::registerGlobalContext(std::string title, JSGlobalContextRef ctx) {
|
||||
std::lock_guard<std::mutex> lock(registrationMutex_);
|
||||
auto controller = folly::make_unique<InspectorController>(getGlobalObject(ctx));
|
||||
auto pageId = numPages_++;
|
||||
pages_.emplace(
|
||||
std::piecewise_construct,
|
||||
std::forward_as_tuple(pageId),
|
||||
std::forward_as_tuple(std::move(title), std::move(controller)));
|
||||
}
|
||||
|
||||
void Inspector::unregisterGlobalContext(JSGlobalContextRef ctx) {
|
||||
std::lock_guard<std::mutex> lock(registrationMutex_);
|
||||
auto& globalObject = getGlobalObject(ctx);
|
||||
for (auto it = pages_.begin(); it != pages_.end(); it++) {
|
||||
auto& page = it->second;
|
||||
if (page.controller->getGlobalObject().globalExec() == globalObject.globalExec()) {
|
||||
if (page.connection_) {
|
||||
page.connection_->terminate(true);
|
||||
}
|
||||
pages_.erase(it);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Inspector::LocalConnection> Inspector::connect(int pageId, std::unique_ptr<RemoteConnection> remote) {
|
||||
std::lock_guard<std::mutex> lock(registrationMutex_);
|
||||
return folly::make_unique<LocalConnection>(pages_.at(pageId).connect(std::move(remote)));
|
||||
}
|
||||
|
||||
void Inspector::disconnect(int pageId) {
|
||||
std::lock_guard<std::mutex> lock(registrationMutex_);
|
||||
pages_.at(pageId).controller->onDisconnect();
|
||||
}
|
||||
|
||||
std::shared_ptr<Inspector::DuplexConnection> Inspector::PageHolder::connect(std::unique_ptr<RemoteConnection> remote) {
|
||||
if (connection_) {
|
||||
throw std::runtime_error("Already connected");
|
||||
}
|
||||
connection_ = std::make_shared<DuplexConnection>(*this, std::move(remote));
|
||||
controller->onConnect([connection = connection_](std::string message) {
|
||||
connection->sendToRemote(std::move(message));
|
||||
});
|
||||
return connection_;
|
||||
}
|
||||
|
||||
Inspector::DuplexConnection::DuplexConnection(PageHolder& page, std::unique_ptr<RemoteConnection> remoteConnection)
|
||||
: page_(page)
|
||||
, remoteConnection_(std::move(remoteConnection)) {}
|
||||
|
||||
Inspector::DuplexConnection::~DuplexConnection() {
|
||||
if (remoteConnection_) {
|
||||
LOG(FATAL) << "DuplexConnection wasn't terminated before destruction";
|
||||
}
|
||||
}
|
||||
|
||||
void Inspector::DuplexConnection::sendToRemote(std::string message) {
|
||||
std::lock_guard<std::mutex> lock(remoteMutex_);
|
||||
if (!remoteConnection_) {
|
||||
return;
|
||||
}
|
||||
|
||||
remoteConnection_->onMessage(std::move(message));
|
||||
}
|
||||
|
||||
void Inspector::DuplexConnection::sendToLocal(std::string message) {
|
||||
std::lock_guard<std::mutex> lock(localMutex_);
|
||||
if (!remoteConnection_) {
|
||||
return;
|
||||
}
|
||||
|
||||
page_.controller->onMessage(std::move(message));
|
||||
}
|
||||
|
||||
void Inspector::DuplexConnection::terminate(bool local) {
|
||||
std::lock_guard<std::mutex> lockLocal(localMutex_);
|
||||
{
|
||||
// Temp lock here so we can still send going away message
|
||||
std::lock_guard<std::mutex> lockRemote(remoteMutex_);
|
||||
if (!remoteConnection_) {
|
||||
// Already disconnected
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (local) {
|
||||
page_.controller->onGoingAway();
|
||||
}
|
||||
std::lock_guard<std::mutex> lockRemote(remoteMutex_);
|
||||
|
||||
auto remoteConnection = std::move(remoteConnection_);
|
||||
if (local) {
|
||||
remoteConnection->onDisconnect();
|
||||
}
|
||||
page_.controller->onDisconnect();
|
||||
// This kills us
|
||||
page_.connection_.reset();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <mutex>
|
||||
|
||||
#include <JavaScriptCore/JSBase.h>
|
||||
#undef WTF_EXPORT_PRIVATE
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
class InspectorController;
|
||||
class Sender;
|
||||
|
||||
class Inspector {
|
||||
private:
|
||||
class DuplexConnection;
|
||||
public:
|
||||
struct Page {
|
||||
const int id;
|
||||
const std::string title;
|
||||
};
|
||||
|
||||
struct RemoteConnection {
|
||||
virtual ~RemoteConnection() = default;
|
||||
virtual void onMessage(std::string message) = 0;
|
||||
virtual void onDisconnect() = 0;
|
||||
};
|
||||
|
||||
class LocalConnection {
|
||||
public:
|
||||
void sendMessage(std::string message);
|
||||
void disconnect();
|
||||
|
||||
LocalConnection(std::shared_ptr<DuplexConnection> duplexConnection);
|
||||
private:
|
||||
std::shared_ptr<DuplexConnection> duplexConnection_;
|
||||
};
|
||||
|
||||
static Inspector& instance();
|
||||
|
||||
void registerGlobalContext(std::string title, JSGlobalContextRef ctx);
|
||||
void unregisterGlobalContext(JSGlobalContextRef ctx);
|
||||
|
||||
std::vector<Page> getPages() const;
|
||||
std::unique_ptr<LocalConnection> connect(int pageId, std::unique_ptr<RemoteConnection> remote);
|
||||
private:
|
||||
struct PageHolder;
|
||||
|
||||
class DuplexConnection {
|
||||
public:
|
||||
DuplexConnection(PageHolder& page, std::unique_ptr<RemoteConnection> remoteConnection);
|
||||
~DuplexConnection();
|
||||
|
||||
void sendToRemote(std::string message);
|
||||
void sendToLocal(std::string message);
|
||||
void terminate(bool local);
|
||||
private:
|
||||
PageHolder& page_;
|
||||
std::unique_ptr<RemoteConnection> remoteConnection_;
|
||||
std::mutex localMutex_;
|
||||
std::mutex remoteMutex_;
|
||||
};
|
||||
|
||||
struct PageHolder {
|
||||
PageHolder(std::string name, std::unique_ptr<InspectorController> controller);
|
||||
~PageHolder();
|
||||
|
||||
std::shared_ptr<DuplexConnection> connect(std::unique_ptr<RemoteConnection> remote);
|
||||
|
||||
const std::string name;
|
||||
std::unique_ptr<InspectorController> controller;
|
||||
std::shared_ptr<DuplexConnection> connection_;
|
||||
};
|
||||
|
||||
Inspector() {};
|
||||
void disconnect(int pageId);
|
||||
|
||||
int numPages_ = 0;
|
||||
std::unordered_map<int, PageHolder> pages_;
|
||||
mutable std::mutex registrationMutex_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#include "InspectorAgent.h"
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
InspectorAgent::InspectorAgent() {
|
||||
auto emptyMethod = [](folly::dynamic) -> folly::dynamic {
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
registerMethod("enable", emptyMethod);
|
||||
registerMethod("disable", emptyMethod);
|
||||
}
|
||||
|
||||
void InspectorAgent::detach() {
|
||||
sendEvent("detached", folly::dynamic::object("reason", "target_closed"));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Agent.h"
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
class InspectorAgent : public Agent {
|
||||
public:
|
||||
InspectorAgent();
|
||||
|
||||
void detach();
|
||||
private:
|
||||
std::string getDomain() override {
|
||||
return "Inspector";
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
// 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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Dispatcher.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace JSC {
|
||||
class JSGlobalObject;
|
||||
}
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
class ConcreteChannel;
|
||||
class InspectorAgent;
|
||||
|
||||
using Receiver = std::function<void(std::string)>;
|
||||
|
||||
class InspectorController {
|
||||
public:
|
||||
InspectorController(JSC::JSGlobalObject& globalObject);
|
||||
~InspectorController();
|
||||
|
||||
JSC::JSGlobalObject& getGlobalObject() const { return globalObject_; }
|
||||
|
||||
void onConnect(Receiver receiver);
|
||||
void onMessage(std::string message);
|
||||
void onGoingAway();
|
||||
void onDisconnect();
|
||||
private:
|
||||
JSC::JSGlobalObject& globalObject_;
|
||||
std::shared_ptr<ConcreteChannel> channel_;
|
||||
std::vector<std::unique_ptr<Dispatcher>> dispatchers_;
|
||||
InspectorAgent* inspectorAgent_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#include "Protocol.h"
|
||||
|
||||
#include "Error.h"
|
||||
|
||||
#include <folly/dynamic.h>
|
||||
#include <folly/json.h>
|
||||
#include <folly/Conv.h>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
namespace {
|
||||
|
||||
folly::dynamic getCallId(const folly::Optional<int>& callId) {
|
||||
if (callId.hasValue()) {
|
||||
return callId.value();
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Method Method::parse(const std::string& formatted) {
|
||||
auto splitPos = formatted.find_first_of('.');
|
||||
if (splitPos == std::string::npos) {
|
||||
throw InspectorException(ErrorCode::InvalidRequest, "Invalid method format");
|
||||
}
|
||||
return Method(formatted.substr(0, splitPos), formatted.substr(splitPos + 1));
|
||||
}
|
||||
|
||||
Method::Method(std::string domain, std::string name)
|
||||
: domain_(std::move(domain))
|
||||
, name_(std::move(name)) {}
|
||||
|
||||
std::string Method::formatted() const {
|
||||
return folly::to<std::string>(domain_, '.', name_);
|
||||
}
|
||||
|
||||
Event::Event(std::string domain, std::string method, folly::dynamic params)
|
||||
: method_(std::move(domain), std::move(method))
|
||||
, params_(std::move(params)) {}
|
||||
|
||||
Event::operator std::string() const {
|
||||
auto event = folly::dynamic::object("method", method_.formatted());
|
||||
if (!params_.isNull()) {
|
||||
event("params", params_);
|
||||
}
|
||||
|
||||
return folly::toJson(std::move(event));
|
||||
}
|
||||
|
||||
namespace Timestamp {
|
||||
double now() {
|
||||
using duration = std::chrono::duration<double, std::ratio<1>>;
|
||||
auto epoch = std::chrono::duration_cast<duration>(
|
||||
std::chrono::system_clock::now().time_since_epoch()
|
||||
);
|
||||
return epoch.count();
|
||||
}
|
||||
}
|
||||
|
||||
Error::Error(int callId, ErrorCode code, std::string message)
|
||||
: callId_(callId)
|
||||
, code_(code)
|
||||
, message_(std::move(message)) {}
|
||||
|
||||
Error::Error(ErrorCode code, std::string message)
|
||||
: code_(code)
|
||||
, message_(std::move(message)) {}
|
||||
|
||||
Error::operator std::string() const {
|
||||
auto errorCode = static_cast<int>(code_);
|
||||
return folly::toJson(
|
||||
folly::dynamic::object("id", getCallId(callId_))
|
||||
("error", folly::dynamic::object("code", errorCode)("message", message_))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <folly/dynamic.h>
|
||||
#include <folly/Optional.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
class Method {
|
||||
public:
|
||||
static Method parse(const std::string& formatted);
|
||||
|
||||
Method(std::string domain, std::string name);
|
||||
|
||||
const std::string& domain() const {
|
||||
return domain_;
|
||||
}
|
||||
|
||||
const std::string& name() const {
|
||||
return name_;
|
||||
}
|
||||
|
||||
std::string formatted() const;
|
||||
private:
|
||||
std::string domain_;
|
||||
std::string name_;
|
||||
};
|
||||
|
||||
class Event {
|
||||
public:
|
||||
explicit Event(std::string domain, std::string method, folly::dynamic params);
|
||||
|
||||
operator std::string() const;
|
||||
private:
|
||||
Method method_;
|
||||
folly::dynamic params_;
|
||||
};
|
||||
|
||||
namespace Timestamp {
|
||||
double now();
|
||||
}
|
||||
|
||||
enum class ErrorCode {
|
||||
ParseError = -32700,
|
||||
InvalidRequest = -32600,
|
||||
MethodNotFound = -32601,
|
||||
InvalidParams = -32602,
|
||||
InternalError = -32603,
|
||||
ServerError = -32000,
|
||||
};
|
||||
|
||||
class Error {
|
||||
public:
|
||||
Error(int callId, ErrorCode code, std::string message);
|
||||
Error(ErrorCode code, std::string message);
|
||||
|
||||
const std::string& message() const {
|
||||
return message_;
|
||||
}
|
||||
|
||||
ErrorCode code() const {
|
||||
return code_;
|
||||
}
|
||||
|
||||
operator std::string() const;
|
||||
private:
|
||||
folly::Optional<int> callId_;
|
||||
ErrorCode code_;
|
||||
std::string message_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#include "Util.h"
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <JavaScriptCore/config.h>
|
||||
#include <wtf/text/WTFString.h>
|
||||
#include <wtf/text/CString.h>
|
||||
#include <JavaScriptCore/JSContextRef.h>
|
||||
#include <JavaScriptCore/JSObjectRef.h>
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
inline std::string toStdString(const WTF::String& str) {
|
||||
return std::string(str.utf8().data());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
class AgentMap {
|
||||
public:
|
||||
void add(JSGlobalContextRef ctx, T* agent) {
|
||||
map_[ctx] = agent;
|
||||
}
|
||||
|
||||
void remove(T* agent) {
|
||||
auto it = std::find_if(
|
||||
map_.begin(),
|
||||
map_.end(),
|
||||
[agent](const typename MapType::value_type& entry) { return entry.second == agent; });
|
||||
map_.erase(it);
|
||||
}
|
||||
|
||||
T* get(JSGlobalContextRef ctx) {
|
||||
return map_.at(ctx);
|
||||
}
|
||||
private:
|
||||
using MapType = std::unordered_map<JSGlobalContextRef, T*>;
|
||||
MapType map_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -108,6 +108,13 @@ void installGlobalProxy(
|
|||
JSClassRelease(proxyClass);
|
||||
}
|
||||
|
||||
void removeGlobal(JSGlobalContextRef ctx, const char* name) {
|
||||
JSStringRef jsName = JSStringCreateWithUTF8CString(name);
|
||||
JSObjectRef globalObject = JSContextGetGlobalObject(ctx);
|
||||
JSObjectSetProperty(ctx, globalObject, jsName, nullptr, 0, nullptr);
|
||||
JSStringRelease(jsName);
|
||||
}
|
||||
|
||||
JSValueRef makeJSCException(
|
||||
JSContextRef ctx,
|
||||
const char* exception_text) {
|
||||
|
|
|
@ -60,6 +60,8 @@ void installGlobalProxy(
|
|||
const char* name,
|
||||
JSObjectGetPropertyCallback callback);
|
||||
|
||||
void removeGlobal(JSGlobalContextRef ctx, const char* name);
|
||||
|
||||
JSValueRef makeJSCException(
|
||||
JSContextRef ctx,
|
||||
const char* exception_text);
|
||||
|
|
Loading…
Reference in New Issue