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:
Alexander Blom 2016-11-02 12:18:16 -07:00 committed by Facebook Github Bot
parent 27f504e9e3
commit 6a1783210b
5 changed files with 244 additions and 0 deletions

View File

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

View File

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

View File

@ -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());

View File

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

View File

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