diff --git a/ReactCommon/cxxreact/BUCK b/ReactCommon/cxxreact/BUCK index 44f751d6dd8..efcf46c40ad 100644 --- a/ReactCommon/cxxreact/BUCK +++ b/ReactCommon/cxxreact/BUCK @@ -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 ) diff --git a/ReactCommon/cxxreact/JSCExecutor.cpp b/ReactCommon/cxxreact/JSCExecutor.cpp index 9918a0778c9..b49e9d8ee43 100644 --- a/ReactCommon/cxxreact/JSCExecutor.cpp +++ b/ReactCommon/cxxreact/JSCExecutor.cpp @@ -19,6 +19,10 @@ #include #include +#ifdef WITH_INSPECTOR +#include +#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; } diff --git a/ReactCommon/inspector/Agent.cpp b/ReactCommon/inspector/Agent.cpp new file mode 100644 index 00000000000..1a3720cd6a9 --- /dev/null +++ b/ReactCommon/inspector/Agent.cpp @@ -0,0 +1,50 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include "Agent.h" + +#include "Error.h" +#include "Protocol.h" + +#include + +namespace facebook { +namespace react { + +void Agent::onConnect(std::shared_ptr 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))); +} + +} +} diff --git a/ReactCommon/inspector/Agent.h b/ReactCommon/inspector/Agent.h new file mode 100644 index 00000000000..598f0f7dbcb --- /dev/null +++ b/ReactCommon/inspector/Agent.h @@ -0,0 +1,30 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#pragma once + +#include "Dispatcher.h" + +#include + +namespace facebook { +namespace react { + +class Agent : public Dispatcher { +public: + void onConnect(std::shared_ptr channel) override; + void onDisconnect() override; +protected: + using Method = std::function; + 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_; + std::unordered_map methods_; +}; + +} +} diff --git a/ReactCommon/inspector/Android.mk b/ReactCommon/inspector/Android.mk new file mode 100644 index 00000000000..bd13be66e81 --- /dev/null +++ b/ReactCommon/inspector/Android.mk @@ -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) diff --git a/ReactCommon/inspector/BUCK b/ReactCommon/inspector/BUCK new file mode 100644 index 00000000000..e5844b775d7 --- /dev/null +++ b/ReactCommon/inspector/BUCK @@ -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', + ], +) diff --git a/ReactCommon/inspector/Dispatcher.cpp b/ReactCommon/inspector/Dispatcher.cpp new file mode 100644 index 00000000000..54bbd03f2da --- /dev/null +++ b/ReactCommon/inspector/Dispatcher.cpp @@ -0,0 +1,3 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include "Dispatcher.h" diff --git a/ReactCommon/inspector/Dispatcher.h b/ReactCommon/inspector/Dispatcher.h new file mode 100644 index 00000000000..327942198cd --- /dev/null +++ b/ReactCommon/inspector/Dispatcher.h @@ -0,0 +1,33 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#pragma once + +#include + +#include +// Both double-conversions and WTF define the ASSERT macro +#undef ASSERT + +namespace facebook { +namespace react { + +class Channel { +public: + using MessageHandler = std::function; + + 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) = 0; + virtual void onDisconnect() = 0; +}; + +} +} diff --git a/ReactCommon/inspector/Error.cpp b/ReactCommon/inspector/Error.cpp new file mode 100644 index 00000000000..5bb780f4471 --- /dev/null +++ b/ReactCommon/inspector/Error.cpp @@ -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()); +} + +} +} diff --git a/ReactCommon/inspector/Error.h b/ReactCommon/inspector/Error.h new file mode 100644 index 00000000000..f373035a321 --- /dev/null +++ b/ReactCommon/inspector/Error.h @@ -0,0 +1,34 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#pragma once + +#include "Protocol.h" + +#include +#include + +#include + +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_; +}; + +} +} diff --git a/ReactCommon/inspector/Inspector.cpp b/ReactCommon/inspector/Inspector.cpp new file mode 100644 index 00000000000..68233b15e71 --- /dev/null +++ b/ReactCommon/inspector/Inspector.cpp @@ -0,0 +1,162 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include "Inspector.h" + +#include "InspectorController.h" + +#include + +#include +#include +#include + +#include + +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 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 controller) +: name(name) +, controller(std::move(controller)) {} + +Inspector::PageHolder::~PageHolder() = default; + +Inspector& Inspector::instance() { + static Inspector inspector; + return inspector; +} + +std::vector Inspector::getPages() const { + std::lock_guard lock(registrationMutex_); + std::vector 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 lock(registrationMutex_); + auto controller = folly::make_unique(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 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::connect(int pageId, std::unique_ptr remote) { + std::lock_guard lock(registrationMutex_); + return folly::make_unique(pages_.at(pageId).connect(std::move(remote))); +} + +void Inspector::disconnect(int pageId) { + std::lock_guard lock(registrationMutex_); + pages_.at(pageId).controller->onDisconnect(); +} + +std::shared_ptr Inspector::PageHolder::connect(std::unique_ptr remote) { + if (connection_) { + throw std::runtime_error("Already connected"); + } + connection_ = std::make_shared(*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) + : 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 lock(remoteMutex_); + if (!remoteConnection_) { + return; + } + + remoteConnection_->onMessage(std::move(message)); +} + +void Inspector::DuplexConnection::sendToLocal(std::string message) { + std::lock_guard lock(localMutex_); + if (!remoteConnection_) { + return; + } + + page_.controller->onMessage(std::move(message)); +} + +void Inspector::DuplexConnection::terminate(bool local) { + std::lock_guard lockLocal(localMutex_); + { + // Temp lock here so we can still send going away message + std::lock_guard lockRemote(remoteMutex_); + if (!remoteConnection_) { + // Already disconnected + return; + } + } + + if (local) { + page_.controller->onGoingAway(); + } + std::lock_guard lockRemote(remoteMutex_); + + auto remoteConnection = std::move(remoteConnection_); + if (local) { + remoteConnection->onDisconnect(); + } + page_.controller->onDisconnect(); + // This kills us + page_.connection_.reset(); +} + +} +} diff --git a/ReactCommon/inspector/Inspector.h b/ReactCommon/inspector/Inspector.h new file mode 100644 index 00000000000..5d103acaeea --- /dev/null +++ b/ReactCommon/inspector/Inspector.h @@ -0,0 +1,90 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#pragma once + +#include +#include +#include +#include +#include + +#include +#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); + private: + std::shared_ptr duplexConnection_; + }; + + static Inspector& instance(); + + void registerGlobalContext(std::string title, JSGlobalContextRef ctx); + void unregisterGlobalContext(JSGlobalContextRef ctx); + + std::vector getPages() const; + std::unique_ptr connect(int pageId, std::unique_ptr remote); +private: + struct PageHolder; + + class DuplexConnection { + public: + DuplexConnection(PageHolder& page, std::unique_ptr remoteConnection); + ~DuplexConnection(); + + void sendToRemote(std::string message); + void sendToLocal(std::string message); + void terminate(bool local); + private: + PageHolder& page_; + std::unique_ptr remoteConnection_; + std::mutex localMutex_; + std::mutex remoteMutex_; + }; + + struct PageHolder { + PageHolder(std::string name, std::unique_ptr controller); + ~PageHolder(); + + std::shared_ptr connect(std::unique_ptr remote); + + const std::string name; + std::unique_ptr controller; + std::shared_ptr connection_; + }; + + Inspector() {}; + void disconnect(int pageId); + + int numPages_ = 0; + std::unordered_map pages_; + mutable std::mutex registrationMutex_; +}; + +} +} diff --git a/ReactCommon/inspector/InspectorAgent.cpp b/ReactCommon/inspector/InspectorAgent.cpp new file mode 100644 index 00000000000..d94176059ae --- /dev/null +++ b/ReactCommon/inspector/InspectorAgent.cpp @@ -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")); +} + +} +} diff --git a/ReactCommon/inspector/InspectorAgent.h b/ReactCommon/inspector/InspectorAgent.h new file mode 100644 index 00000000000..569e73f79d5 --- /dev/null +++ b/ReactCommon/inspector/InspectorAgent.h @@ -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"; + } +}; + +} +} diff --git a/ReactCommon/inspector/InspectorController.cpp b/ReactCommon/inspector/InspectorController.cpp new file mode 100644 index 00000000000..d590f8a7c97 --- /dev/null +++ b/ReactCommon/inspector/InspectorController.cpp @@ -0,0 +1,178 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include "InspectorController.h" + +#include "Error.h" +#include "Agent.h" +#include "InspectorAgent.h" + +#include +#include +#include + +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& getDomains() { + return domains_; + } +private: + std::unordered_map 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("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) override { + Agent::onConnect(channel); + channel_ = std::static_pointer_cast(channel); + } +private: + std::shared_ptr channel_; + + std::string getDomain() override { + return "Schema"; + } +}; + +InspectorController::InspectorController(JSC::JSGlobalObject& globalObject) + : globalObject_(globalObject) { + auto inspectorAgent = folly::make_unique(); + inspectorAgent_ = inspectorAgent.get(); + dispatchers_.push_back(std::move(inspectorAgent)); + dispatchers_.push_back(folly::make_unique()); +} + +InspectorController::~InspectorController() { + CHECK(!channel_) << "Wasn't disconnected"; +} + +void InspectorController::onConnect(Receiver receiver) { + CHECK(!channel_) << "Already connected"; + + channel_ = std::make_shared(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(); +} + +} +} diff --git a/ReactCommon/inspector/InspectorController.h b/ReactCommon/inspector/InspectorController.h new file mode 100644 index 00000000000..2281e992958 --- /dev/null +++ b/ReactCommon/inspector/InspectorController.h @@ -0,0 +1,42 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#pragma once + +#include "Dispatcher.h" + +#include +#include +#include + +namespace JSC { +class JSGlobalObject; +} + +namespace facebook { +namespace react { + +class ConcreteChannel; +class InspectorAgent; + +using Receiver = std::function; + +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 channel_; + std::vector> dispatchers_; + InspectorAgent* inspectorAgent_; +}; + +} +} diff --git a/ReactCommon/inspector/Protocol.cpp b/ReactCommon/inspector/Protocol.cpp new file mode 100644 index 00000000000..eb7428ede49 --- /dev/null +++ b/ReactCommon/inspector/Protocol.cpp @@ -0,0 +1,86 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include "Protocol.h" + +#include "Error.h" + +#include +#include +#include + +#include + +namespace facebook { +namespace react { + +namespace { + +folly::dynamic getCallId(const folly::Optional& 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(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>; + auto epoch = std::chrono::duration_cast( + 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(code_); + return folly::toJson( + folly::dynamic::object("id", getCallId(callId_)) + ("error", folly::dynamic::object("code", errorCode)("message", message_)) + ); +} + + +} +} diff --git a/ReactCommon/inspector/Protocol.h b/ReactCommon/inspector/Protocol.h new file mode 100644 index 00000000000..3b36ae7977d --- /dev/null +++ b/ReactCommon/inspector/Protocol.h @@ -0,0 +1,77 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#pragma once + +#include + +#include +#include + +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 callId_; + ErrorCode code_; + std::string message_; +}; + +} +} diff --git a/ReactCommon/inspector/Util.cpp b/ReactCommon/inspector/Util.cpp new file mode 100644 index 00000000000..23c9ee6e156 --- /dev/null +++ b/ReactCommon/inspector/Util.cpp @@ -0,0 +1,9 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include "Util.h" + +namespace facebook { +namespace react { + +} +} diff --git a/ReactCommon/inspector/Util.h b/ReactCommon/inspector/Util.h new file mode 100644 index 00000000000..fb858bf30c6 --- /dev/null +++ b/ReactCommon/inspector/Util.h @@ -0,0 +1,45 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include + +namespace facebook { +namespace react { + +inline std::string toStdString(const WTF::String& str) { + return std::string(str.utf8().data()); +} + +template +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; + MapType map_; +}; + +} +} diff --git a/ReactCommon/jschelpers/JSCHelpers.cpp b/ReactCommon/jschelpers/JSCHelpers.cpp index 39b944d9be5..aa97dcb157e 100644 --- a/ReactCommon/jschelpers/JSCHelpers.cpp +++ b/ReactCommon/jschelpers/JSCHelpers.cpp @@ -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) { diff --git a/ReactCommon/jschelpers/JSCHelpers.h b/ReactCommon/jschelpers/JSCHelpers.h index 5819f2e465c..8932f017216 100644 --- a/ReactCommon/jschelpers/JSCHelpers.h +++ b/ReactCommon/jschelpers/JSCHelpers.h @@ -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);