Add JS agent support
Summary: Adds the possibility to define agents in Javascript. Javascript agents are simple classes that extend `InspectorAgent` and pass down the given `EventSender` to the super constructor. The library will then call methods on the object for each received method call over the protocol. Reviewed By: davidaurelio Differential Revision: D4021508 fbshipit-source-id: bbe609e92ea726cbbbec833df81705ebd3346c77
This commit is contained in:
parent
27f504e9e3
commit
6a1783210b
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule InspectorAgent
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
export type EventSender = (name: string, params: Object) => void;
|
||||
|
||||
class InspectorAgent {
|
||||
_eventSender: EventSender;
|
||||
|
||||
constructor(eventSender: EventSender) {
|
||||
this._eventSender = eventSender;
|
||||
}
|
||||
|
||||
sendEvent(name: string, params: Object) {
|
||||
this._eventSender(name, params);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = InspectorAgent;
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule JSInspector
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
import type EventSender from 'InspectorAgent';
|
||||
|
||||
interface Agent {
|
||||
constructor(eventSender: EventSender): void
|
||||
}
|
||||
|
||||
// Flow doesn't support static declarations in interface
|
||||
type AgentClass = Class<Agent> & { DOMAIN: string };
|
||||
|
||||
declare function __registerInspectorAgent(type: AgentClass): void;
|
||||
declare function __inspectorTimestamp(): number;
|
||||
|
||||
const JSInspector = {
|
||||
registerAgent(type: AgentClass) {
|
||||
if (global.__registerInspectorAgent) {
|
||||
global.__registerInspectorAgent(type);
|
||||
}
|
||||
},
|
||||
getTimestamp(): number {
|
||||
return global.__inspectorTimestamp();
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = JSInspector;
|
|
@ -8,6 +8,7 @@
|
|||
#include "InspectorAgent.h"
|
||||
#include "PageAgent.h"
|
||||
#include "ConsoleAgent.h"
|
||||
#include "JSDispatcher.h"
|
||||
#include "LegacyAgents.h"
|
||||
|
||||
#include <folly/Memory.h>
|
||||
|
@ -142,6 +143,7 @@ InspectorController::InspectorController(JSC::JSGlobalObject& globalObject)
|
|||
dispatchers_.push_back(std::move(inspectorAgent));
|
||||
dispatchers_.push_back(folly::make_unique<SchemaAgent>());
|
||||
dispatchers_.push_back(folly::make_unique<PageAgent>());
|
||||
dispatchers_.push_back(folly::make_unique<JSDispatcher>(globalObject));
|
||||
|
||||
auto consoleAgent = folly::make_unique<ConsoleAgent>(globalObject, environment->injectedScriptManager());
|
||||
auto legacyAgents = folly::make_unique<LegacyAgents>(globalObject, std::move(environment), consoleAgent.get());
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#include "JSDispatcher.h"
|
||||
|
||||
#include "Protocol.h"
|
||||
#include "Util.h"
|
||||
|
||||
#include <jschelpers/JSCHelpers.h>
|
||||
|
||||
#include <JavaScriptCore/APICast.h>
|
||||
#include <JavaScriptCore/JSContextRef.h>
|
||||
#include <JavaScriptCore/JSObjectRef.h>
|
||||
#include <JavaScriptCore/JSStringRef.h>
|
||||
|
||||
#include <folly/json.h>
|
||||
|
||||
#include <exception>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
namespace {
|
||||
|
||||
static JSValueRef nativeRegisterAgent(
|
||||
JSDispatcher* agent,
|
||||
JSContextRef ctx,
|
||||
JSObjectRef thisObject,
|
||||
size_t argumentCount,
|
||||
const JSValueRef arguments[]) {
|
||||
CHECK(argumentCount == 1) << "__registerInspectorAgent takes 1 arg";
|
||||
auto execState = toJS(ctx);
|
||||
JSC::JSLockHolder lock(execState);
|
||||
auto globalContext = JSContextGetGlobalContext(ctx);
|
||||
agent->addAgent(
|
||||
execState,
|
||||
Value(globalContext, arguments[0]).asObject());
|
||||
return JSValueMakeUndefined(ctx);
|
||||
}
|
||||
|
||||
static JSValueRef nativeSendEvent(
|
||||
JSDispatcher* agent,
|
||||
const std::string& domain,
|
||||
JSContextRef ctx,
|
||||
JSObjectRef thisObject,
|
||||
size_t argumentCount,
|
||||
const JSValueRef arguments[]) {
|
||||
CHECK(argumentCount == 2) << "sendEvent takes 2 args";
|
||||
auto execState = toJS(ctx);
|
||||
JSC::JSLockHolder lock(execState);
|
||||
auto globalContext = JSContextGetGlobalContext(ctx);
|
||||
auto params = folly::parseJson(Value(globalContext, arguments[1]).toJSONString());
|
||||
agent->sendEvent(
|
||||
domain,
|
||||
Value(globalContext, arguments[0]).toString().str(),
|
||||
std::move(params));
|
||||
return JSValueMakeUndefined(ctx);
|
||||
}
|
||||
|
||||
static JSValueRef nativeInspectorTimestamp(
|
||||
JSContextRef ctx,
|
||||
JSObjectRef function,
|
||||
JSObjectRef thisObject,
|
||||
size_t argumentCount,
|
||||
const JSValueRef arguments[],
|
||||
JSValueRef *exception) {
|
||||
return JSValueMakeNumber(ctx, Timestamp::now());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
JSDispatcher::JSDispatcher(JSC::JSGlobalObject& globalObject) {
|
||||
using namespace std::placeholders;
|
||||
JSGlobalContextRef context = toGlobalRef(globalObject.globalExec());
|
||||
installGlobalFunction(context, "__registerInspectorAgent", std::bind(&nativeRegisterAgent, this, _1, _2, _3, _4));
|
||||
installGlobalFunction(context, "__inspectorTimestamp", &nativeInspectorTimestamp);
|
||||
}
|
||||
|
||||
void JSDispatcher::onConnect(std::shared_ptr<Channel> channel) {
|
||||
channel_ = std::move(channel);
|
||||
|
||||
for (auto& pair : agents_) {
|
||||
registerAgent(pair.first);
|
||||
}
|
||||
}
|
||||
|
||||
void JSDispatcher::onDisconnect() {
|
||||
channel_.reset();
|
||||
}
|
||||
|
||||
void JSDispatcher::addAgent(JSC::ExecState* execState, Object agentType) {
|
||||
auto context = agentType.context();
|
||||
auto domainObject = agentType.getProperty("DOMAIN");
|
||||
if (domainObject.isUndefined()) {
|
||||
throw std::invalid_argument("DOMAIN should be string");
|
||||
}
|
||||
auto domain = domainObject.toString().str();
|
||||
// Bind the domain to the send event function
|
||||
using namespace std::placeholders;
|
||||
Value sendEventFunction = Object(
|
||||
context,
|
||||
makeFunction(context, "sendEvent", std::bind(&nativeSendEvent, this, domain, _1, _2, _3, _4)));
|
||||
auto agent = agentType.callAsConstructor({ sendEventFunction });
|
||||
agent.makeProtected();
|
||||
|
||||
if (channel_) {
|
||||
registerAgent(domain);
|
||||
}
|
||||
agents_.emplace(std::move(domain), std::move(agent));
|
||||
}
|
||||
|
||||
void JSDispatcher::registerAgent(const std::string& name) {
|
||||
channel_->registerDomain(
|
||||
name,
|
||||
[this, name](std::string, int callId, const std::string& methodName, folly::dynamic args) {
|
||||
Object& agent = agents_.at(name);
|
||||
auto context = agent.context();
|
||||
JSC::JSLockHolder lock(toJS(context));
|
||||
// TODO(blom): Check undefined before asObject
|
||||
auto method = agent.getProperty(methodName.c_str()).asObject();
|
||||
if (args.isNull()) {
|
||||
args = folly::dynamic::object;
|
||||
}
|
||||
auto response = method.callAsFunction(agent, { Value::fromDynamic(context, args) });
|
||||
auto result = (response.isUndefined() || response.isNull()) ? folly::dynamic::object() : folly::parseJson(response.toJSONString());
|
||||
auto message = folly::dynamic::object("id", callId)("result", std::move(result));
|
||||
channel_->sendMessage(folly::toJson(std::move(message)));
|
||||
});
|
||||
}
|
||||
|
||||
void JSDispatcher::sendEvent(std::string domain, std::string name, folly::dynamic params) {
|
||||
channel_->sendMessage(Event(std::move(domain), std::move(name), std::move(params)));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Dispatcher.h"
|
||||
|
||||
#include <JavaScriptCore/config.h>
|
||||
#include <JavaScriptCore/JSValueRef.h>
|
||||
|
||||
#include <jschelpers/Value.h>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace JSC {
|
||||
class JSGlobalObject;
|
||||
class JSObject;
|
||||
class ExecState;
|
||||
class JSArray;
|
||||
}
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
class JSDispatcher : public Dispatcher {
|
||||
public:
|
||||
JSDispatcher(JSC::JSGlobalObject& globalObject);
|
||||
|
||||
void onConnect(std::shared_ptr<Channel> channel) override;
|
||||
void onDisconnect() override;
|
||||
|
||||
void addAgent(JSC::ExecState* execState, Object agentType);
|
||||
void registerAgent(const std::string& name);
|
||||
|
||||
void sendEvent(std::string domain, std::string name, folly::dynamic params);
|
||||
private:
|
||||
std::shared_ptr<Channel> channel_;
|
||||
|
||||
std::unordered_map<std::string, Object> agents_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue