From 0ac7bf29af5dc1ca3072e1d43f6b0c19b610194d Mon Sep 17 00:00:00 2001 From: Alexander Blom Date: Wed, 2 Nov 2016 12:18:13 -0700 Subject: [PATCH] Add Page, Runtime, Debugger agents Summary: Runtime and Debugger agents are shipped with JSC so we reuse them. Messages are routed to them through the `LegacyDispatcher` which also handles translating their events. The Page agent emits the `Page.getResourceTree` method that the Chrome inspector expects. Reviewed By: michalgr Differential Revision: D4021499 fbshipit-source-id: a93d0add01cee732401f8e8db1d43205bfbd4cd4 --- ReactCommon/inspector/InspectorController.cpp | 9 +++ ReactCommon/inspector/LegacyAgents.cpp | 35 ++++++++++ ReactCommon/inspector/LegacyAgents.h | 29 +++++++++ ReactCommon/inspector/LegacyDebuggerAgent.cpp | 45 +++++++++++++ ReactCommon/inspector/LegacyDebuggerAgent.h | 41 ++++++++++++ ReactCommon/inspector/LegacyDispatcher.cpp | 54 ++++++++++++++++ ReactCommon/inspector/LegacyDispatcher.h | 48 ++++++++++++++ .../inspector/LegacyInspectorEnvironment.cpp | 34 ++++++++++ .../inspector/LegacyInspectorEnvironment.h | 35 ++++++++++ ReactCommon/inspector/LegacyRuntimeAgent.cpp | 63 ++++++++++++++++++ ReactCommon/inspector/LegacyRuntimeAgent.h | 39 +++++++++++ .../inspector/LegacyScriptDebugServer.cpp | 64 +++++++++++++++++++ .../inspector/LegacyScriptDebugServer.h | 45 +++++++++++++ ReactCommon/inspector/PageAgent.cpp | 31 +++++++++ ReactCommon/inspector/PageAgent.h | 20 ++++++ 15 files changed, 592 insertions(+) create mode 100644 ReactCommon/inspector/LegacyAgents.cpp create mode 100644 ReactCommon/inspector/LegacyAgents.h create mode 100644 ReactCommon/inspector/LegacyDebuggerAgent.cpp create mode 100644 ReactCommon/inspector/LegacyDebuggerAgent.h create mode 100644 ReactCommon/inspector/LegacyDispatcher.cpp create mode 100644 ReactCommon/inspector/LegacyDispatcher.h create mode 100644 ReactCommon/inspector/LegacyInspectorEnvironment.cpp create mode 100644 ReactCommon/inspector/LegacyInspectorEnvironment.h create mode 100644 ReactCommon/inspector/LegacyRuntimeAgent.cpp create mode 100644 ReactCommon/inspector/LegacyRuntimeAgent.h create mode 100644 ReactCommon/inspector/LegacyScriptDebugServer.cpp create mode 100644 ReactCommon/inspector/LegacyScriptDebugServer.h create mode 100644 ReactCommon/inspector/PageAgent.cpp create mode 100644 ReactCommon/inspector/PageAgent.h diff --git a/ReactCommon/inspector/InspectorController.cpp b/ReactCommon/inspector/InspectorController.cpp index d590f8a7c..c8e7bab2e 100644 --- a/ReactCommon/inspector/InspectorController.cpp +++ b/ReactCommon/inspector/InspectorController.cpp @@ -4,7 +4,10 @@ #include "Error.h" #include "Agent.h" +#include "LegacyInspectorEnvironment.h" #include "InspectorAgent.h" +#include "PageAgent.h" +#include "LegacyAgents.h" #include #include @@ -132,10 +135,16 @@ private: InspectorController::InspectorController(JSC::JSGlobalObject& globalObject) : globalObject_(globalObject) { + auto environment = folly::make_unique(); auto inspectorAgent = folly::make_unique(); inspectorAgent_ = inspectorAgent.get(); dispatchers_.push_back(std::move(inspectorAgent)); dispatchers_.push_back(folly::make_unique()); + dispatchers_.push_back(folly::make_unique()); + + auto legacyAgents = folly::make_unique(globalObject, std::move(environment), nullptr); + + dispatchers_.push_back(std::move(legacyAgents)); } InspectorController::~InspectorController() { diff --git a/ReactCommon/inspector/LegacyAgents.cpp b/ReactCommon/inspector/LegacyAgents.cpp new file mode 100644 index 000000000..2ccd2eb69 --- /dev/null +++ b/ReactCommon/inspector/LegacyAgents.cpp @@ -0,0 +1,35 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include "LegacyAgents.h" + +#include "LegacyInspectorEnvironment.h" +#include "LegacyRuntimeAgent.h" +#include "LegacyDebuggerAgent.h" + +#include +#include +#include + +namespace facebook { +namespace react { + +using namespace Inspector; + +LegacyAgents::LegacyAgents( + JSC::JSGlobalObject& globalObject, + std::unique_ptr environment, + ConsoleAgent* consoleAgent) + : LegacyDispatcher(globalObject) + , environment_(std::move(environment)) { + auto injectedScriptManager = environment_->injectedScriptManager(); + auto runtimeAgent = folly::make_unique(injectedScriptManager, globalObject); + auto debuggerAgent = folly::make_unique(injectedScriptManager, globalObject, consoleAgent); + + runtimeAgent->setScriptDebugServer(&debuggerAgent->scriptDebugServer()); + + addAgent("Runtime", std::move(runtimeAgent)); + addAgent("Debugger", std::move(debuggerAgent)); +} + +} +} diff --git a/ReactCommon/inspector/LegacyAgents.h b/ReactCommon/inspector/LegacyAgents.h new file mode 100644 index 000000000..be3b114e3 --- /dev/null +++ b/ReactCommon/inspector/LegacyAgents.h @@ -0,0 +1,29 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#pragma once + +#include "LegacyDispatcher.h" + +namespace JSC { +class JSGlobalObject; +} + +namespace facebook { +namespace react { + +class LegacyInspectorEnvironment; +class ConsoleAgent; + +class LegacyAgents : public LegacyDispatcher { +public: + LegacyAgents( + JSC::JSGlobalObject& globalObject, + std::unique_ptr environment, + ConsoleAgent* consoleAgent); +private: + std::unique_ptr environment_; + ConsoleAgent* consoleAgent_; +}; + +} +} diff --git a/ReactCommon/inspector/LegacyDebuggerAgent.cpp b/ReactCommon/inspector/LegacyDebuggerAgent.cpp new file mode 100644 index 000000000..519f56bce --- /dev/null +++ b/ReactCommon/inspector/LegacyDebuggerAgent.cpp @@ -0,0 +1,45 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include "LegacyDebuggerAgent.h" + +#include "Util.h" + +#include +#include +#include + +namespace facebook { +namespace react { + +using namespace Inspector; + +LegacyDebuggerAgent::LegacyDebuggerAgent(InjectedScriptManager* injectedScriptManager, JSC::JSGlobalObject& globalObject, ConsoleAgent* consoleAgent) + : InspectorDebuggerAgent(injectedScriptManager) + , scriptDebugServer_(globalObject) + , consoleAgent_(consoleAgent) {} + +void LegacyDebuggerAgent::startListeningScriptDebugServer() { + scriptDebugServer().addListener(this); +} + +void LegacyDebuggerAgent::stopListeningScriptDebugServer(bool isBeingDestroyed) { + scriptDebugServer().removeListener(this, isBeingDestroyed); +} + +InjectedScript LegacyDebuggerAgent::injectedScriptForEval(ErrorString* error, const int* executionContextId) { + if (executionContextId) { + *error = ASCIILiteral("Execution context id is not supported for JSContext inspection as there is only one execution context."); + return InjectedScript(); + } + + JSC::ExecState* exec = scriptDebugServer_.globalObject().globalExec(); + return injectedScriptManager()->injectedScriptFor(exec); +} + +void LegacyDebuggerAgent::breakpointActionLog(JSC::ExecState* exec, const String& message) { + // TODO: Hook up + // consoleAgent_->log(exec, toStdString(message)); +} + +} +} diff --git a/ReactCommon/inspector/LegacyDebuggerAgent.h b/ReactCommon/inspector/LegacyDebuggerAgent.h new file mode 100644 index 000000000..e7a84fcc0 --- /dev/null +++ b/ReactCommon/inspector/LegacyDebuggerAgent.h @@ -0,0 +1,41 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#pragma once + +#include "LegacyScriptDebugServer.h" + +#include +#include +#include + +namespace JSC { +class JSGlobalObject; +} + +namespace facebook { +namespace react { + +class ConsoleAgent; + +class LegacyDebuggerAgent : public Inspector::InspectorDebuggerAgent { +public: + LegacyDebuggerAgent(Inspector::InjectedScriptManager*, JSC::JSGlobalObject&, ConsoleAgent*); + + virtual LegacyScriptDebugServer& scriptDebugServer() override { return scriptDebugServer_; } + + virtual void startListeningScriptDebugServer() override; + virtual void stopListeningScriptDebugServer(bool isBeingDestroyed) override; + virtual Inspector::InjectedScript injectedScriptForEval(Inspector::ErrorString*, const int* executionContextId) override; + + virtual void breakpointActionLog(JSC::ExecState*, const String&) override; + + virtual void muteConsole() override { } + virtual void unmuteConsole() override { } + +private: + LegacyScriptDebugServer scriptDebugServer_; + ConsoleAgent* consoleAgent_; +}; + +} +} diff --git a/ReactCommon/inspector/LegacyDispatcher.cpp b/ReactCommon/inspector/LegacyDispatcher.cpp new file mode 100644 index 000000000..8fa7d7aeb --- /dev/null +++ b/ReactCommon/inspector/LegacyDispatcher.cpp @@ -0,0 +1,54 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include "LegacyDispatcher.h" + +#include "Util.h" + +#include +#include +#include + +namespace facebook { +namespace react { + +using namespace Inspector; + +LegacyDispatcher::FrontendChannel::FrontendChannel(std::shared_ptr channel) +: channel_(channel) {} + +bool LegacyDispatcher::FrontendChannel::sendMessageToFrontend(const WTF::String& message) { + channel_->sendMessage(toStdString(message)); + return true; +} + +LegacyDispatcher::LegacyDispatcher(JSC::JSGlobalObject& globalObject) + : globalObject_(globalObject) {} + +void LegacyDispatcher::addAgent(std::string domain, std::unique_ptr agent) { + domains_.emplace_back(std::move(domain)); + agents_.append(std::move(agent)); +} + +void LegacyDispatcher::onConnect(std::shared_ptr channel) { + // TODO: Should perhaps only create this once and then connect each time instead + frontendChannel_ = std::make_unique(channel); + dispatcher_.reset(InspectorBackendDispatcher::create(frontendChannel_.get()).leakRef()); + + auto messageHandler = [this](std::string message, int, const std::string&, folly::dynamic) { + JSC::JSLockHolder lock(globalObject_.globalExec()); + dispatcher_->dispatch(message.c_str()); + }; + for (auto& domain : domains_) { + channel->registerDomain(domain, messageHandler); + } + + agents_.didCreateFrontendAndBackend(frontendChannel_.get(), dispatcher_.get()); +} + +void LegacyDispatcher::onDisconnect() { + // TODO: Perhaps support InspectedTargetDestroyed + agents_.willDestroyFrontendAndBackend(InspectorDisconnectReason::InspectorDestroyed); +} + +} +} diff --git a/ReactCommon/inspector/LegacyDispatcher.h b/ReactCommon/inspector/LegacyDispatcher.h new file mode 100644 index 000000000..b10553445 --- /dev/null +++ b/ReactCommon/inspector/LegacyDispatcher.h @@ -0,0 +1,48 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#pragma once + +#include "Dispatcher.h" + +#include +#include +#include +#include +#include + +#include +#include + +namespace JSC { +class JSGlobalObject; +} + +namespace facebook { +namespace react { + +class LegacyDispatcher : public Dispatcher { +public: + LegacyDispatcher(JSC::JSGlobalObject& globalObject); + void addAgent(std::string domain, std::unique_ptr agent); + + void onConnect(std::shared_ptr channel) override; + void onDisconnect() override; +private: + class FrontendChannel : public Inspector::InspectorFrontendChannel { + public: + FrontendChannel(std::shared_ptr channel); + bool sendMessageToFrontend(const WTF::String& message) override; + private: + std::shared_ptr channel_; + }; + + JSC::JSGlobalObject& globalObject_; + std::vector domains_; + Inspector::InspectorAgentRegistry agents_; + + std::unique_ptr frontendChannel_; + std::unique_ptr dispatcher_; +}; + +} +} diff --git a/ReactCommon/inspector/LegacyInspectorEnvironment.cpp b/ReactCommon/inspector/LegacyInspectorEnvironment.cpp new file mode 100644 index 000000000..2a010d923 --- /dev/null +++ b/ReactCommon/inspector/LegacyInspectorEnvironment.cpp @@ -0,0 +1,34 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include "LegacyInspectorEnvironment.h" + +#include +#include +#include +#include +#include + +#include + +namespace facebook { +namespace react { + +using namespace Inspector; + +LegacyInspectorEnvironment::LegacyInspectorEnvironment() + : injectedScriptManager_(folly::make_unique(*this, InjectedScriptHost::create())) {} + +LegacyInspectorEnvironment::~LegacyInspectorEnvironment() { + injectedScriptManager_->disconnect(); +} + +InspectorFunctionCallHandler LegacyInspectorEnvironment::functionCallHandler() const { + return JSC::call; +} + +InspectorEvaluateHandler LegacyInspectorEnvironment::evaluateHandler() const { + return JSC::evaluate; +} + +} +} diff --git a/ReactCommon/inspector/LegacyInspectorEnvironment.h b/ReactCommon/inspector/LegacyInspectorEnvironment.h new file mode 100644 index 000000000..00ec90205 --- /dev/null +++ b/ReactCommon/inspector/LegacyInspectorEnvironment.h @@ -0,0 +1,35 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#pragma once + +#include +#include + +namespace Inspector { +class InjectedScriptManager; +} + +namespace facebook { +namespace react { + +class LegacyInspectorEnvironment : public Inspector::InspectorEnvironment { +public: + LegacyInspectorEnvironment(); + ~LegacyInspectorEnvironment(); + + Inspector::InjectedScriptManager* injectedScriptManager() const { + return injectedScriptManager_.get(); + } +private: + std::unique_ptr injectedScriptManager_; + + bool developerExtrasEnabled() const override { return true; } + bool canAccessInspectedScriptState(JSC::ExecState*) const override { return true; } + Inspector::InspectorFunctionCallHandler functionCallHandler() const override; + Inspector::InspectorEvaluateHandler evaluateHandler() const override; + void willCallInjectedScriptFunction(JSC::ExecState*, const WTF::String& scriptName, int scriptLine) override {}; + void didCallInjectedScriptFunction(JSC::ExecState*) override {} +}; + +} +} diff --git a/ReactCommon/inspector/LegacyRuntimeAgent.cpp b/ReactCommon/inspector/LegacyRuntimeAgent.cpp new file mode 100644 index 000000000..f08a9d178 --- /dev/null +++ b/ReactCommon/inspector/LegacyRuntimeAgent.cpp @@ -0,0 +1,63 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include "LegacyRuntimeAgent.h" + +#include +#include +#include + +namespace facebook { +namespace react { + +using namespace Inspector; + +LegacyRuntimeAgent::LegacyRuntimeAgent(InjectedScriptManager* injectedScriptManager, JSC::JSGlobalObject& globalObject) + : InspectorRuntimeAgent(injectedScriptManager) + , m_globalObject(globalObject) {} + +void LegacyRuntimeAgent::didCreateFrontendAndBackend(InspectorFrontendChannel* frontendChannel, InspectorBackendDispatcher* backendDispatcher) { + // m_frontendDispatcher = folly::make_unique(frontendChannel); + frontendChannel_ = frontendChannel; + m_backendDispatcher.reset(InspectorRuntimeBackendDispatcher::create(backendDispatcher, this).leakRef()); +} + +void LegacyRuntimeAgent::enable(ErrorString* error) { + InspectorRuntimeAgent::enable(error); + + auto contextObject = InspectorObject::create(); + contextObject->setNumber(ASCIILiteral("id"), 1); + contextObject->setBoolean(ASCIILiteral("isDefault"), true); + contextObject->setBoolean(ASCIILiteral("isPageContext"), true); + contextObject->setString(ASCIILiteral("origin"), ASCIILiteral("")); + contextObject->setString(ASCIILiteral("name"), ASCIILiteral("React Native")); + + auto jsonMessage = InspectorObject::create(); + jsonMessage->setString(ASCIILiteral("method"), ASCIILiteral("Runtime.executionContextCreated")); + auto paramsObject = InspectorObject::create(); + paramsObject->setValue(ASCIILiteral("context"), contextObject); + jsonMessage->setObject(ASCIILiteral("params"), paramsObject); + + frontendChannel_->sendMessageToFrontend(jsonMessage->toJSONString()); +} + +void LegacyRuntimeAgent::willDestroyFrontendAndBackend(InspectorDisconnectReason) { + frontendChannel_ = nullptr; + m_backendDispatcher = nullptr; +} + +JSC::VM& LegacyRuntimeAgent::globalVM() { + return m_globalObject.vm(); +} + +InjectedScript LegacyRuntimeAgent::injectedScriptForEval(ErrorString* error, const int* executionContextId) { + JSC::ExecState* scriptState = m_globalObject.globalExec(); + InjectedScript injectedScript = injectedScriptManager()->injectedScriptFor(scriptState); + if (injectedScript.hasNoValue()) { + *error = ASCIILiteral("Internal error: main world execution context not found."); + } + + return injectedScript; +} + +} +} diff --git a/ReactCommon/inspector/LegacyRuntimeAgent.h b/ReactCommon/inspector/LegacyRuntimeAgent.h new file mode 100644 index 000000000..e0b04aa74 --- /dev/null +++ b/ReactCommon/inspector/LegacyRuntimeAgent.h @@ -0,0 +1,39 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#pragma once + +#include + +#include +#include + +namespace JSC { +class JSGlobalObject; +} + +namespace facebook { +namespace react { + +class LegacyRuntimeAgent : public Inspector::InspectorRuntimeAgent { +public: + LegacyRuntimeAgent(Inspector::InjectedScriptManager*, JSC::JSGlobalObject&); + + void enable(Inspector::ErrorString* error) override; + + void didCreateFrontendAndBackend(Inspector::InspectorFrontendChannel*, Inspector::InspectorBackendDispatcher*) override; + void willDestroyFrontendAndBackend(Inspector::InspectorDisconnectReason) override; + + JSC::VM& globalVM() override; + Inspector::InjectedScript injectedScriptForEval(Inspector::ErrorString* error, const int* executionContextId) override; + + void muteConsole() override { } + void unmuteConsole() override { } +private: + Inspector::InspectorFrontendChannel* frontendChannel_; + // std::unique_ptr m_frontendDispatcher; + std::unique_ptr m_backendDispatcher; + JSC::JSGlobalObject& m_globalObject; +}; + +} +} diff --git a/ReactCommon/inspector/LegacyScriptDebugServer.cpp b/ReactCommon/inspector/LegacyScriptDebugServer.cpp new file mode 100644 index 000000000..3453e708d --- /dev/null +++ b/ReactCommon/inspector/LegacyScriptDebugServer.cpp @@ -0,0 +1,64 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include "LegacyScriptDebugServer.h" + +#include + +namespace facebook { +namespace react { + +using namespace Inspector; + +LegacyScriptDebugServer::LegacyScriptDebugServer(JSC::JSGlobalObject& globalObject) + : Inspector::ScriptDebugServer(false) + , globalObject_(globalObject) {} + +void LegacyScriptDebugServer::addListener(ScriptDebugListener* listener) +{ + if (!listener) { + return; + } + + bool wasEmpty = listeners_.isEmpty(); + listeners_.add(listener); + + // First listener. Attach the debugger to the JSGlobalObject. + if (wasEmpty) { + attach(&globalObject_); + recompileAllJSFunctions(); + } +} + +void LegacyScriptDebugServer::removeListener(ScriptDebugListener* listener, bool isBeingDestroyed) { + if (!listener) { + return; + } + + listeners_.remove(listener); + + // Last listener. Detach the debugger from the JSGlobalObject. + if (listeners_.isEmpty()) { + detach(&globalObject_, isBeingDestroyed ? Debugger::GlobalObjectIsDestructing : Debugger::TerminatingDebuggingSession); + if (!isBeingDestroyed) { + recompileAllJSFunctions(); + } + } +} + +void LegacyScriptDebugServer::recompileAllJSFunctions() { + JSC::Debugger::recompileAllJSFunctions(&globalObject_.vm()); +} + +void LegacyScriptDebugServer::runEventLoopWhilePaused() { + // Drop all locks so another thread can work in the VM while we are nested. + JSC::JSLock::DropAllLocks dropAllLocks(&globalObject_.vm()); + + // Spinning here is our best option, we could override the method + // notifyDoneProcessingDebuggerEvents but it's marked as final :( + while (!m_doneProcessingDebuggerEvents) { + usleep(10 * 1000); + } +} + +} +} diff --git a/ReactCommon/inspector/LegacyScriptDebugServer.h b/ReactCommon/inspector/LegacyScriptDebugServer.h new file mode 100644 index 000000000..fe8380451 --- /dev/null +++ b/ReactCommon/inspector/LegacyScriptDebugServer.h @@ -0,0 +1,45 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#pragma once + +#include +#include + +namespace JSC { +class JSGlobalObject; +} + +namespace facebook { +namespace react { + +class LegacyScriptDebugServer : public Inspector::ScriptDebugServer { +public: + LegacyScriptDebugServer(JSC::JSGlobalObject& object); + + void addListener(Inspector::ScriptDebugListener* listener); + void removeListener(Inspector::ScriptDebugListener* listener, bool isBeingDestroyed); + + JSC::JSGlobalObject& globalObject() const { return globalObject_; } + + void recompileAllJSFunctions() override; + +private: + ListenerSet* getListenersForGlobalObject(JSC::JSGlobalObject*) override { return &listeners_; } + void didPause(JSC::JSGlobalObject*) override { } + void didContinue(JSC::JSGlobalObject*) override { } + void runEventLoopWhilePaused() override; + bool isContentScript(JSC::ExecState*) const override { return false; } + + // NOTE: Currently all exceptions are reported at the API boundary through reportAPIException. + // Until a time comes where an exception can be caused outside of the API (e.g. setTimeout + // or some other async operation in a pure JSContext) we can ignore exceptions reported here. + // TODO: Should we actually ignore them? + void reportException(JSC::ExecState*, JSC::JSValue) const override { } + + ListenerSet listeners_; + JSC::JSGlobalObject& globalObject_; +}; + + +} +} diff --git a/ReactCommon/inspector/PageAgent.cpp b/ReactCommon/inspector/PageAgent.cpp new file mode 100644 index 000000000..02dbb5861 --- /dev/null +++ b/ReactCommon/inspector/PageAgent.cpp @@ -0,0 +1,31 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include "PageAgent.h" + +namespace facebook { +namespace react { + +PageAgent::PageAgent() { + auto emptyMethod = [](folly::dynamic args) -> folly::dynamic { + return nullptr; + }; + + registerMethod("enable", emptyMethod); + registerMethod("disable", emptyMethod); + registerMethod("getResourceTree", [](folly::dynamic args) -> folly::dynamic { + return folly::dynamic::object + ("frameTree", folly::dynamic::object + ("childFrames", folly::dynamic::array) + ("resources", folly::dynamic::array) + ("frame", folly::dynamic::object + ("id", "1") + ("loaderId", "1") + ("name", "main") + ("url", "") + ("securityOrigin", "") + ("mimeType", "application/octet-stream"))); + }); +} + +} +} diff --git a/ReactCommon/inspector/PageAgent.h b/ReactCommon/inspector/PageAgent.h new file mode 100644 index 000000000..c2f747eb7 --- /dev/null +++ b/ReactCommon/inspector/PageAgent.h @@ -0,0 +1,20 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#pragma once + +#include "Agent.h" + +namespace facebook { +namespace react { + +class PageAgent : public Agent { +public: + PageAgent(); +private: + std::string getDomain() override { + return "Page"; + } +}; + +} +}