react-native-macos/ReactCommon/hermes/inspector/Inspector.h

307 строки
10 KiB
C++

// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <memory>
#include <queue>
#include <unordered_map>
#include <folly/Executor.h>
#include <folly/Unit.h>
#include <folly/futures/Future.h>
#include <hermes/DebuggerAPI.h>
#include <hermes/hermes.h>
#include <hermes/inspector/AsyncPauseState.h>
#include <hermes/inspector/RuntimeAdapter.h>
namespace facebook {
namespace hermes {
namespace inspector {
class Inspector;
class InspectorState;
/**
* ScriptInfo contains info about loaded scripts.
*/
struct ScriptInfo {
uint32_t fileId{};
std::string fileName;
std::string sourceMappingUrl;
};
struct ConsoleMessageInfo {
std::string source;
std::string level;
std::string url;
int line;
int column;
jsi::Array args;
ConsoleMessageInfo(std::string level, jsi::Array args)
: source("console-api"),
level(level),
url(""),
line(-1),
column(-1),
args(std::move(args)) {}
};
/**
* InspectorObserver notifies the observer of events that occur in the VM.
*/
class InspectorObserver {
public:
virtual ~InspectorObserver() = default;
/// onContextCreated fires when the VM is created.
virtual void onContextCreated(Inspector &inspector) = 0;
/// onBreakpointResolve fires when a lazy breakpoint is resolved.
virtual void onBreakpointResolved(
Inspector &inspector,
const facebook::hermes::debugger::BreakpointInfo &info) = 0;
/// onPause fires when VM transitions from running to paused state. This is
/// called directly on the JS thread while the VM is paused, so the receiver
/// can call debugger::ProgramState methods safely.
virtual void onPause(
Inspector &inspector,
const facebook::hermes::debugger::ProgramState &state) = 0;
/// onResume fires when VM transitions from paused to running state.
virtual void onResume(Inspector &inspector) = 0;
/// onScriptParsed fires when after the VM parses a script.
virtual void onScriptParsed(Inspector &inspector, const ScriptInfo &info) = 0;
// onMessageAdded fires when new console message is added.
virtual void onMessageAdded(
Inspector &inspector,
const ConsoleMessageInfo &info) = 0;
};
/**
* Inspector implements a future-based interface over the low-level Hermes
* debugging API.
*/
class Inspector : public facebook::hermes::debugger::EventObserver,
public std::enable_shared_from_this<Inspector> {
public:
/**
* Inspector's constructor should be used to install the inspector on the
* provided runtime before any JS executes in the runtime.
*/
Inspector(
std::shared_ptr<RuntimeAdapter> adapter,
InspectorObserver &observer,
bool pauseOnFirstStatement);
~Inspector();
/**
* disable turns off the inspector. All of the subsequent methods will not do
* anything unless the inspector is enabled.
*/
folly::Future<folly::Unit> disable();
/**
* enable turns on the inspector. All of the subsequent methods will not do
* anything unless the inspector is enabled. The returned future succeeds when
* the debugger is enabled, or fails with AlreadyEnabledException if the
* debugger was already enabled.
*/
folly::Future<folly::Unit> enable();
/**
* installs console log handler. Ideally this should be done inside
* constructor, but because it uses shared_from_this we can't do this
* in constructor.
*/
void installLogHandler();
/**
* executeIfEnabled executes the provided callback *on the JS thread with the
* inspector lock held*. Execution can be implicitly requested while running.
* The inspector lock:
*
* 1) Protects VM state transitions. This means that the VM is guaranteed to
* stay in the paused or running state for the duration of the callback.
* 2) Protects InspectorObserver callbacks. This means that if some shared
* data is accessed only in InspectorObserver and executeIfEnabled
* callbacks, it does not need to be locked, since it's already protected
* by the inspector lock.
*
* The returned future resolves to true in the VM can be paused, or
* fails with IllegalStateException otherwise. The description is only used
* to populate the IllegalStateException with more useful info on failure.
*/
folly::Future<folly::Unit> executeIfEnabled(
const std::string &description,
folly::Function<void(const facebook::hermes::debugger::ProgramState &)>
func);
/**
* setBreakpoint can be called at any time after the debugger is enabled to
* set a breakpoint in the VM. The future is fulfilled with the resolved
* breakpoint info.
*
* Resolving a breakpoint takes an indeterminate amount of time since Hermes
* only resolves breakpoints when the debugger is able to actively pause JS
* execution.
*/
folly::Future<facebook::hermes::debugger::BreakpointInfo> setBreakpoint(
facebook::hermes::debugger::SourceLocation loc,
folly::Optional<std::string> condition = folly::none);
folly::Future<folly::Unit> removeBreakpoint(
facebook::hermes::debugger::BreakpointID loc);
/**
* logs console message.
*/
folly::Future<folly::Unit> logMessage(ConsoleMessageInfo info);
/**
* resume and step methods are only valid when the VM is currently paused. The
* returned future suceeds when the VM resumes execution, or fails with an
* InvalidStateException otherwise.
*/
folly::Future<folly::Unit> resume();
folly::Future<folly::Unit> stepIn();
folly::Future<folly::Unit> stepOver();
folly::Future<folly::Unit> stepOut();
/**
* pause can be issued at any time while the inspector is enabled. It requests
* the VM to asynchronously break execution. The returned future suceeds if
* the VM can be paused in this state and fails with InvalidStateException if
* otherwise.
*/
folly::Future<folly::Unit> pause();
/**
* evaluate runs JavaScript code within the context of a call frame. The
* returned promise is fulfilled with an eval result if it's possible to
* evaluate code in the current state or fails with InvalidStateException
* otherwise.
*/
folly::Future<facebook::hermes::debugger::EvalResult> evaluate(
uint32_t frameIndex,
const std::string &src,
folly::Function<void(const facebook::hermes::debugger::EvalResult &)>
resultTransformer);
folly::Future<folly::Unit> setPauseOnExceptions(
const facebook::hermes::debugger::PauseOnThrowMode &mode);
/**
* didPause implements the pause callback from Hermes. This callback arrives
* on the JS thread.
*/
facebook::hermes::debugger::Command didPause(
facebook::hermes::debugger::Debugger &debugger) override;
/**
* breakpointResolved implements the breakpointResolved callback from Hermes.
*/
void breakpointResolved(
facebook::hermes::debugger::Debugger &debugger,
facebook::hermes::debugger::BreakpointID breakpointId) override;
private:
friend class InspectorState;
void triggerAsyncPause(bool andTickle);
void notifyContextCreated();
ScriptInfo getScriptInfoFromTopCallFrame();
void addCurrentScriptToLoadedScripts();
void removeAllBreakpoints();
void resetScriptsLoaded();
void notifyScriptsLoaded();
folly::Future<folly::Unit> setPendingCommand(debugger::Command command);
void transition(std::unique_ptr<InspectorState> nextState);
// All methods that end with OnExecutor run on executor_.
void disableOnExecutor(std::shared_ptr<folly::Promise<folly::Unit>> promise);
void enableOnExecutor(std::shared_ptr<folly::Promise<folly::Unit>> promise);
void executeIfEnabledOnExecutor(
const std::string &description,
folly::Function<void(const facebook::hermes::debugger::ProgramState &)>
func,
std::shared_ptr<folly::Promise<folly::Unit>> promise);
void setBreakpointOnExecutor(
debugger::SourceLocation loc,
folly::Optional<std::string> condition,
std::shared_ptr<
folly::Promise<facebook::hermes::debugger::BreakpointInfo>> promise);
void removeBreakpointOnExecutor(
debugger::BreakpointID breakpointId,
std::shared_ptr<folly::Promise<folly::Unit>> promise);
void logOnExecutor(
ConsoleMessageInfo info,
std::shared_ptr<folly::Promise<folly::Unit>> promise);
void setPendingCommandOnExecutor(
facebook::hermes::debugger::Command command,
std::shared_ptr<folly::Promise<folly::Unit>> promise);
void pauseOnExecutor(std::shared_ptr<folly::Promise<folly::Unit>> promise);
void evaluateOnExecutor(
uint32_t frameIndex,
const std::string &src,
std::shared_ptr<folly::Promise<facebook::hermes::debugger::EvalResult>>
promise,
folly::Function<void(const facebook::hermes::debugger::EvalResult &)>
resultTransformer);
void setPauseOnExceptionsOnExecutor(
const facebook::hermes::debugger::PauseOnThrowMode &mode,
std::shared_ptr<folly::Promise<folly::Unit>> promise);
void installConsoleFunction(
jsi::Object &console,
const std::string &name,
const std::string &chromeType);
std::shared_ptr<RuntimeAdapter> adapter_;
facebook::hermes::debugger::Debugger &debugger_;
InspectorObserver &observer_;
// All client methods (e.g. enable, setBreakpoint, resume, etc.) are executed
// on executor_ to prevent deadlocking on mutex_. See the implementation for
// more comments on the threading invariants used in this class.
std::unique_ptr<folly::Executor> executor_;
// All of the following member variables are guarded by mutex_.
std::mutex mutex_;
std::unique_ptr<InspectorState> state_;
// See the InspectorState::Running implementation for an explanation for why
// this state is here rather than in the Running class.
AsyncPauseState pendingPauseState_ = AsyncPauseState::None;
// All scripts loaded in to the VM, along with whether we've notified the
// client about the script yet.
struct LoadedScriptInfo {
ScriptInfo info;
bool notifiedClient;
};
std::unordered_map<int, LoadedScriptInfo> loadedScripts_;
};
} // namespace inspector
} // namespace hermes
} // namespace facebook