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
This commit is contained in:
Alexander Blom
2016-11-02 12:18:13 -07:00
committed by Facebook Github Bot
parent 156e5d9837
commit 0ac7bf29af
15 changed files with 592 additions and 0 deletions

View File

@@ -4,7 +4,10 @@
#include "Error.h"
#include "Agent.h"
#include "LegacyInspectorEnvironment.h"
#include "InspectorAgent.h"
#include "PageAgent.h"
#include "LegacyAgents.h"
#include <folly/Memory.h>
#include <folly/Conv.h>
@@ -132,10 +135,16 @@ private:
InspectorController::InspectorController(JSC::JSGlobalObject& globalObject)
: globalObject_(globalObject) {
auto environment = folly::make_unique<LegacyInspectorEnvironment>();
auto inspectorAgent = folly::make_unique<InspectorAgent>();
inspectorAgent_ = inspectorAgent.get();
dispatchers_.push_back(std::move(inspectorAgent));
dispatchers_.push_back(folly::make_unique<SchemaAgent>());
dispatchers_.push_back(folly::make_unique<PageAgent>());
auto legacyAgents = folly::make_unique<LegacyAgents>(globalObject, std::move(environment), nullptr);
dispatchers_.push_back(std::move(legacyAgents));
}
InspectorController::~InspectorController() {

View File

@@ -0,0 +1,35 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "LegacyAgents.h"
#include "LegacyInspectorEnvironment.h"
#include "LegacyRuntimeAgent.h"
#include "LegacyDebuggerAgent.h"
#include <JavaScriptCore/config.h>
#include <JavaScriptCore/JSGlobalObject.h>
#include <folly/Memory.h>
namespace facebook {
namespace react {
using namespace Inspector;
LegacyAgents::LegacyAgents(
JSC::JSGlobalObject& globalObject,
std::unique_ptr<LegacyInspectorEnvironment> environment,
ConsoleAgent* consoleAgent)
: LegacyDispatcher(globalObject)
, environment_(std::move(environment)) {
auto injectedScriptManager = environment_->injectedScriptManager();
auto runtimeAgent = folly::make_unique<LegacyRuntimeAgent>(injectedScriptManager, globalObject);
auto debuggerAgent = folly::make_unique<LegacyDebuggerAgent>(injectedScriptManager, globalObject, consoleAgent);
runtimeAgent->setScriptDebugServer(&debuggerAgent->scriptDebugServer());
addAgent("Runtime", std::move(runtimeAgent));
addAgent("Debugger", std::move(debuggerAgent));
}
}
}

View File

@@ -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<LegacyInspectorEnvironment> environment,
ConsoleAgent* consoleAgent);
private:
std::unique_ptr<LegacyInspectorEnvironment> environment_;
ConsoleAgent* consoleAgent_;
};
}
}

View File

@@ -0,0 +1,45 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "LegacyDebuggerAgent.h"
#include "Util.h"
#include <JavaScriptCore/InjectedScript.h>
#include <JavaScriptCore/InjectedScriptManager.h>
#include <JavaScriptCore/JSGlobalObject.h>
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));
}
}
}

View File

@@ -0,0 +1,41 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include "LegacyScriptDebugServer.h"
#include <JavaScriptCore/config.h>
#include <JavaScriptCore/InspectorDebuggerAgent.h>
#include <JavaScriptCore/InspectorConsoleAgent.h>
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_;
};
}
}

View File

@@ -0,0 +1,54 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "LegacyDispatcher.h"
#include "Util.h"
#include <wtf/text/CString.h>
#include <JavaScriptCore/JSGlobalObject.h>
#include <JavaScriptCore/JSLock.h>
namespace facebook {
namespace react {
using namespace Inspector;
LegacyDispatcher::FrontendChannel::FrontendChannel(std::shared_ptr<Channel> 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<InspectorAgentBase> agent) {
domains_.emplace_back(std::move(domain));
agents_.append(std::move(agent));
}
void LegacyDispatcher::onConnect(std::shared_ptr<Channel> channel) {
// TODO: Should perhaps only create this once and then connect each time instead
frontendChannel_ = std::make_unique<FrontendChannel>(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);
}
}
}

View File

@@ -0,0 +1,48 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include "Dispatcher.h"
#include <JavaScriptCore/config.h>
#include <JavaScriptCore/InspectorAgentRegistry.h>
#include <JavaScriptCore/InspectorAgentBase.h>
#include <JavaScriptCore/InspectorFrontendChannel.h>
#include <JavaScriptCore/InspectorBackendDispatcher.h>
#include <memory>
#include <vector>
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<Inspector::InspectorAgentBase> agent);
void onConnect(std::shared_ptr<Channel> channel) override;
void onDisconnect() override;
private:
class FrontendChannel : public Inspector::InspectorFrontendChannel {
public:
FrontendChannel(std::shared_ptr<Channel> channel);
bool sendMessageToFrontend(const WTF::String& message) override;
private:
std::shared_ptr<Channel> channel_;
};
JSC::JSGlobalObject& globalObject_;
std::vector<std::string> domains_;
Inspector::InspectorAgentRegistry agents_;
std::unique_ptr<FrontendChannel> frontendChannel_;
std::unique_ptr<Inspector::InspectorBackendDispatcher> dispatcher_;
};
}
}

View File

@@ -0,0 +1,34 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "LegacyInspectorEnvironment.h"
#include <JavaScriptCore/config.h>
#include <JavaScriptCore/Completion.h>
#include <JavaScriptCore/JSGlobalObject.h>
#include <JavaScriptCore/InjectedScriptManager.h>
#include <JavaScriptCore/InjectedScriptHost.h>
#include <folly/Memory.h>
namespace facebook {
namespace react {
using namespace Inspector;
LegacyInspectorEnvironment::LegacyInspectorEnvironment()
: injectedScriptManager_(folly::make_unique<InjectedScriptManager>(*this, InjectedScriptHost::create())) {}
LegacyInspectorEnvironment::~LegacyInspectorEnvironment() {
injectedScriptManager_->disconnect();
}
InspectorFunctionCallHandler LegacyInspectorEnvironment::functionCallHandler() const {
return JSC::call;
}
InspectorEvaluateHandler LegacyInspectorEnvironment::evaluateHandler() const {
return JSC::evaluate;
}
}
}

View File

@@ -0,0 +1,35 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <JavaScriptCore/config.h>
#include <JavaScriptCore/InspectorEnvironment.h>
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<Inspector::InjectedScriptManager> 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 {}
};
}
}

View File

@@ -0,0 +1,63 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "LegacyRuntimeAgent.h"
#include <JavaScriptCore/InjectedScript.h>
#include <JavaScriptCore/InjectedScriptManager.h>
#include <JavaScriptCore/JSGlobalObject.h>
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<InspectorRuntimeFrontendDispatcher>(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;
}
}
}

View File

@@ -0,0 +1,39 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <memory>
#include <JavaScriptCore/config.h>
#include <JavaScriptCore/InspectorRuntimeAgent.h>
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<Inspector::InspectorRuntimeFrontendDispatcher> m_frontendDispatcher;
std::unique_ptr<Inspector::InspectorRuntimeBackendDispatcher> m_backendDispatcher;
JSC::JSGlobalObject& m_globalObject;
};
}
}

View File

@@ -0,0 +1,64 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "LegacyScriptDebugServer.h"
#include <JavaScriptCore/JSGlobalObject.h>
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);
}
}
}
}

View File

@@ -0,0 +1,45 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <JavaScriptCore/config.h>
#include <JavaScriptCore/ScriptDebugServer.h>
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_;
};
}
}

View File

@@ -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")));
});
}
}
}

View File

@@ -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";
}
};
}
}