From 27f504e9e3f829bcfb27f8b62216c870bd73ef6f Mon Sep 17 00:00:00 2001 From: Alexander Blom Date: Wed, 2 Nov 2016 12:18:15 -0700 Subject: [PATCH] Add Console agent Summary: Adds the Console agent which we hook from our console polyfill. It captures the stack and strips the frames of the polyfill. Reviewed By: davidaurelio Differential Revision: D4021502 fbshipit-source-id: 49cb700a139270485b7595e85e52d50c9a620db6 --- ReactCommon/inspector/ConsoleAgent.cpp | 152 ++++++++++++++++++ ReactCommon/inspector/ConsoleAgent.h | 39 +++++ ReactCommon/inspector/InspectorController.cpp | 5 +- ReactCommon/inspector/LegacyDebuggerAgent.cpp | 3 +- ReactCommon/inspector/LegacyDebuggerAgent.h | 3 +- .../src/Resolver/polyfills/console.js | 16 ++ 6 files changed, 213 insertions(+), 5 deletions(-) create mode 100644 ReactCommon/inspector/ConsoleAgent.cpp create mode 100644 ReactCommon/inspector/ConsoleAgent.h diff --git a/ReactCommon/inspector/ConsoleAgent.cpp b/ReactCommon/inspector/ConsoleAgent.cpp new file mode 100644 index 000000000..665a1df45 --- /dev/null +++ b/ReactCommon/inspector/ConsoleAgent.cpp @@ -0,0 +1,152 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include "ConsoleAgent.h" + +#include "Protocol.h" +#include "Util.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace facebook { +namespace react { + +namespace { + +static JSValueRef inspectorLog( + ConsoleAgent* agent, + JSContextRef ctx, + JSObjectRef thisObject, + size_t argumentCount, + const JSValueRef arguments[]) { + CHECK(argumentCount == 4) << "__inspectorLog takes 4 args"; + auto execState = toJS(ctx); + JSC::JSLockHolder lock(execState); + auto params = toJS(execState, arguments[2]); + agent->log( + execState, + Value(ctx, arguments[0]).toString().str(), + Value(ctx, arguments[1]).toString().str(), + JSC::asArray(params), + Value(ctx, arguments[3]).asUnsignedInteger()); + return JSValueMakeUndefined(ctx); +} + +size_t skipNativeCode(const Inspector::ScriptCallStack& callStack, size_t offset) { + for (; offset < callStack.size(); offset++) { + auto& frame = callStack.at(offset); + if (frame.sourceURL() != "[native code]") { + return offset; + } + } + + return callStack.size(); +} + +const Inspector::ScriptCallFrame* firstUserFrame(const Inspector::ScriptCallStack& callStack, size_t framesToSkip) { + // Skip out of native code + size_t offset = skipNativeCode(callStack, 0); + + // Skip frames of console polyfill + offset = skipNativeCode(callStack, offset + framesToSkip); + if (offset >= callStack.size()) { + return nullptr; + } + + if (callStack.at(offset).functionName() == "infoLog") { + offset += 1; + } + + if (offset >= callStack.size()) { + return nullptr; + } + + return &callStack.at(offset); +} + +} + +ConsoleAgent::ConsoleAgent(JSC::JSGlobalObject& globalObject, Inspector::InjectedScriptManager* injectedScriptManager) + : globalObject_(globalObject) + , injectedScriptManager_(injectedScriptManager) { + registerMethod("enable", [this](folly::dynamic) -> folly::dynamic { + using namespace std::placeholders; + enabled_ = true; + JSGlobalContextRef context = toGlobalRef(globalObject_.globalExec()); + installGlobalFunction(context, "__inspectorLog", std::bind(&inspectorLog, this, _1, _2, _3, _4)); + return nullptr; + }); + registerMethod("disable", [this](folly::dynamic) -> folly::dynamic { + JSGlobalContextRef context = toGlobalRef(globalObject_.globalExec()); + removeGlobal(context, "__inspectorLog"); + enabled_ = false; + return nullptr; + }); +} + +void ConsoleAgent::log(JSC::ExecState* execState, std::string message) { + log(execState, "log", std::move(message), nullptr, 0); +} + +void ConsoleAgent::log(JSC::ExecState* execState, std::string level, std::string message, JSC::JSArray* params, size_t framesToSkip) { + if (!enabled_) { + return; + } + + auto callStack = Inspector::createScriptCallStack(execState, Inspector::ScriptCallStack::maxCallStackSizeToCapture); + + auto logEntry = folly::dynamic::object + ("source", "console-api") + ("level", level) + ("text", std::move(message)) + ("timestamp", Timestamp::now()) + ("stackTrace", folly::parseJson(toStdString(callStack->buildInspectorArray()->toJSONString()))); + + if (params) { + logEntry("parameters", convertParams(execState, params)); + } + + if (auto frame = firstUserFrame(*callStack, framesToSkip)) { + logEntry + ("url", toStdString(frame->sourceURL())) + ("line", frame->lineNumber()) + ("column", frame->columnNumber()); + } + + sendEvent("messageAdded", folly::dynamic::object("message", std::move(logEntry))); +} + +folly::dynamic ConsoleAgent::convertParams(JSC::ExecState* execState, JSC::JSArray* params) { + auto injectedScript = injectedScriptManager_->injectedScriptFor(execState->lexicalGlobalObject()->globalExec()); + if (injectedScript.hasNoValue()) { + return nullptr; + } + + folly::dynamic remoteParams = folly::dynamic::array; + for (size_t i = 0, size = params->length(); i < size; i++) { + auto scriptValue = Deprecated::ScriptValue(execState->vm(), params->getIndex(execState, i)); + auto remoteValue = injectedScript.wrapObject(std::move(scriptValue), "console", true); + remoteParams.push_back(folly::parseJson(toStdString(remoteValue->toJSONString()))); + } + + return remoteParams; +} + +} +} diff --git a/ReactCommon/inspector/ConsoleAgent.h b/ReactCommon/inspector/ConsoleAgent.h new file mode 100644 index 000000000..ffa06ef6d --- /dev/null +++ b/ReactCommon/inspector/ConsoleAgent.h @@ -0,0 +1,39 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#pragma once + +#include "Agent.h" + +namespace JSC { +class JSGlobalObject; +class ExecState; +class JSArray; +} + +namespace Inspector { +class InjectedScriptManager; +} + +namespace facebook { +namespace react { + +class ConsoleAgent : public Agent { +public: + ConsoleAgent(JSC::JSGlobalObject& globalObject, Inspector::InjectedScriptManager* injectedScriptManager); + + void log(JSC::ExecState* execState, std::string message); + void log(JSC::ExecState* execState, std::string level, std::string message, JSC::JSArray* params, size_t framesToSkip); +private: + bool enabled_{false}; + JSC::JSGlobalObject& globalObject_; + Inspector::InjectedScriptManager* injectedScriptManager_; + + folly::dynamic convertParams(JSC::ExecState* execState, JSC::JSArray* params); + + std::string getDomain() override { + return "Console"; + } +}; + +} +} diff --git a/ReactCommon/inspector/InspectorController.cpp b/ReactCommon/inspector/InspectorController.cpp index c8e7bab2e..9a7a2e743 100644 --- a/ReactCommon/inspector/InspectorController.cpp +++ b/ReactCommon/inspector/InspectorController.cpp @@ -7,6 +7,7 @@ #include "LegacyInspectorEnvironment.h" #include "InspectorAgent.h" #include "PageAgent.h" +#include "ConsoleAgent.h" #include "LegacyAgents.h" #include @@ -142,8 +143,10 @@ InspectorController::InspectorController(JSC::JSGlobalObject& globalObject) dispatchers_.push_back(folly::make_unique()); dispatchers_.push_back(folly::make_unique()); - auto legacyAgents = folly::make_unique(globalObject, std::move(environment), nullptr); + auto consoleAgent = folly::make_unique(globalObject, environment->injectedScriptManager()); + auto legacyAgents = folly::make_unique(globalObject, std::move(environment), consoleAgent.get()); + dispatchers_.push_back(std::move(consoleAgent)); dispatchers_.push_back(std::move(legacyAgents)); } diff --git a/ReactCommon/inspector/LegacyDebuggerAgent.cpp b/ReactCommon/inspector/LegacyDebuggerAgent.cpp index 519f56bce..20b71a0da 100644 --- a/ReactCommon/inspector/LegacyDebuggerAgent.cpp +++ b/ReactCommon/inspector/LegacyDebuggerAgent.cpp @@ -37,8 +37,7 @@ InjectedScript LegacyDebuggerAgent::injectedScriptForEval(ErrorString* error, co } void LegacyDebuggerAgent::breakpointActionLog(JSC::ExecState* exec, const String& message) { - // TODO: Hook up - // consoleAgent_->log(exec, toStdString(message)); + consoleAgent_->log(exec, toStdString(message)); } } diff --git a/ReactCommon/inspector/LegacyDebuggerAgent.h b/ReactCommon/inspector/LegacyDebuggerAgent.h index e7a84fcc0..08bf34ce5 100644 --- a/ReactCommon/inspector/LegacyDebuggerAgent.h +++ b/ReactCommon/inspector/LegacyDebuggerAgent.h @@ -2,6 +2,7 @@ #pragma once +#include "ConsoleAgent.h" #include "LegacyScriptDebugServer.h" #include @@ -15,8 +16,6 @@ class JSGlobalObject; namespace facebook { namespace react { -class ConsoleAgent; - class LegacyDebuggerAgent : public Inspector::InspectorDebuggerAgent { public: LegacyDebuggerAgent(Inspector::InjectedScriptManager*, JSC::JSGlobalObject&, ConsoleAgent*); diff --git a/packager/react-packager/src/Resolver/polyfills/console.js b/packager/react-packager/src/Resolver/polyfills/console.js index ed603997c..e8d82e3fb 100644 --- a/packager/react-packager/src/Resolver/polyfills/console.js +++ b/packager/react-packager/src/Resolver/polyfills/console.js @@ -363,6 +363,15 @@ const LOG_LEVELS = { warn: 2, error: 3 }; +const INSPECTOR_LEVELS = []; +INSPECTOR_LEVELS[LOG_LEVELS.trace] = 'debug'; +INSPECTOR_LEVELS[LOG_LEVELS.info] = 'log'; +INSPECTOR_LEVELS[LOG_LEVELS.warn] = 'warning'; +INSPECTOR_LEVELS[LOG_LEVELS.error] = 'error'; + +// Strip the inner function in getNativeLogFunction(), if in dev also +// strip method printing to originalConsole. +const INSPECTOR_FRAMES_TO_SKIP = __DEV__ ? 2 : 1; function setupConsole(global) { if (!global.nativeLoggingHook) { @@ -387,6 +396,13 @@ function setupConsole(global) { // (Note: Logic duplicated in ExceptionsManager.js.) logLevel = LOG_LEVELS.warn; } + if (global.__inspectorLog) { + global.__inspectorLog( + INSPECTOR_LEVELS[logLevel], + str, + [].slice.call(arguments), + INSPECTOR_FRAMES_TO_SKIP); + } global.nativeLoggingHook(str, logLevel); }; }