2018-07-22 14:58:24 +03:00
|
|
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
2018-11-30 18:39:55 +03:00
|
|
|
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
2018-07-22 14:58:24 +03:00
|
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
|
|
|
|
#include "ChildInternal.h"
|
|
|
|
|
|
|
|
#include "ProcessRecordReplay.h"
|
|
|
|
|
|
|
|
namespace mozilla {
|
|
|
|
namespace recordreplay {
|
|
|
|
namespace navigation {
|
|
|
|
|
|
|
|
typedef js::BreakpointPosition BreakpointPosition;
|
|
|
|
typedef js::ExecutionPoint ExecutionPoint;
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Navigation State
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
// The navigation state of a recording/replaying process describes where the
|
|
|
|
// process currently is and what it is doing in order to respond to messages
|
|
|
|
// from the middleman process.
|
|
|
|
//
|
|
|
|
// At all times, the navigation state will be in exactly one of the following
|
|
|
|
// phases:
|
|
|
|
//
|
2018-08-18 18:39:48 +03:00
|
|
|
// - Paused: The process is paused somewhere.
|
2018-07-22 14:58:24 +03:00
|
|
|
// - Forward: The process is running forward and scanning for breakpoint hits.
|
|
|
|
// - ReachBreakpoint: The process is running forward from a checkpoint to a
|
|
|
|
// particular execution point before the next checkpoint.
|
|
|
|
// - FindLastHit: The process is running forward and keeping track of the last
|
|
|
|
// point a breakpoint was hit within an execution region.
|
|
|
|
//
|
|
|
|
// This file manages data associated with each of these phases and the
|
|
|
|
// transitions that occur between them as the process executes or new
|
|
|
|
// messages are received from the middleman.
|
|
|
|
|
|
|
|
typedef AllocPolicy<MemoryKind::Navigation> UntrackedAllocPolicy;
|
|
|
|
|
|
|
|
// Abstract class for where we are at in the navigation state machine.
|
|
|
|
// Each subclass has a single instance contained in NavigationState (see below)
|
|
|
|
// and it and all its data are allocated using untracked memory that is not
|
|
|
|
// affected by restoring earlier checkpoints.
|
|
|
|
class NavigationPhase {
|
|
|
|
// All virtual members should only be accessed through NavigationState.
|
|
|
|
friend class NavigationState;
|
|
|
|
|
|
|
|
private:
|
|
|
|
MOZ_NORETURN void Unsupported(const char* aOperation) {
|
|
|
|
nsAutoCString str;
|
|
|
|
ToString(str);
|
|
|
|
|
|
|
|
Print("Operation %s not supported: %s\n", aOperation, str.get());
|
|
|
|
MOZ_CRASH("Unsupported navigation operation");
|
|
|
|
}
|
|
|
|
|
|
|
|
public:
|
|
|
|
virtual void ToString(nsAutoCString& aStr) = 0;
|
|
|
|
|
|
|
|
// The process has just reached or rewound to a checkpoint.
|
|
|
|
virtual void AfterCheckpoint(const CheckpointId& aCheckpoint) {
|
|
|
|
Unsupported("AfterCheckpoint");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Called when some position with an installed handler has been reached.
|
|
|
|
virtual void PositionHit(const ExecutionPoint& aPoint) {
|
|
|
|
Unsupported("PositionHit");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Called after receiving a resume command from the middleman.
|
|
|
|
virtual void Resume(bool aForward) { Unsupported("Resume"); }
|
|
|
|
|
|
|
|
// Called after the middleman tells us to rewind to a specific checkpoint.
|
|
|
|
virtual void RestoreCheckpoint(size_t aCheckpoint) {
|
|
|
|
Unsupported("RestoreCheckpoint");
|
|
|
|
}
|
|
|
|
|
2018-08-03 02:27:39 +03:00
|
|
|
// Called after the middleman tells us to run forward to a specific point.
|
|
|
|
virtual void RunToPoint(const ExecutionPoint& aTarget) {
|
|
|
|
Unsupported("RunToPoint");
|
|
|
|
}
|
|
|
|
|
2018-07-22 14:58:24 +03:00
|
|
|
// Process an incoming debugger request from the middleman.
|
|
|
|
virtual void HandleDebuggerRequest(js::CharBuffer* aRequestBuffer) {
|
|
|
|
Unsupported("HandleDebuggerRequest");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Called when a debugger request wants to try an operation that may
|
|
|
|
// trigger an unhandled divergence from the recording.
|
|
|
|
virtual bool MaybeDivergeFromRecording() {
|
|
|
|
Unsupported("MaybeDivergeFromRecording");
|
|
|
|
}
|
|
|
|
|
2018-11-06 20:37:22 +03:00
|
|
|
// Get the current execution point.
|
|
|
|
virtual ExecutionPoint CurrentExecutionPoint() {
|
|
|
|
Unsupported("CurrentExecutionPoint");
|
2018-07-22 14:58:24 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Called when execution reaches the endpoint of the recording.
|
|
|
|
virtual void HitRecordingEndpoint(const ExecutionPoint& aPoint) {
|
|
|
|
Unsupported("HitRecordingEndpoint");
|
|
|
|
}
|
2019-01-31 04:28:06 +03:00
|
|
|
|
|
|
|
// Called when a paint has occurred for the last normal checkpoint.
|
|
|
|
virtual bool ShouldSendPaintMessage() {
|
|
|
|
Unsupported("ShouldSendPaintMessage");
|
|
|
|
}
|
2018-07-22 14:58:24 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
// Information about a debugger request sent by the middleman.
|
|
|
|
struct RequestInfo {
|
|
|
|
// JSON contents for the request and response.
|
|
|
|
InfallibleVector<char16_t, 0, UntrackedAllocPolicy> mRequestBuffer;
|
|
|
|
InfallibleVector<char16_t, 0, UntrackedAllocPolicy> mResponseBuffer;
|
|
|
|
|
|
|
|
// Whether processing this request triggered an unhandled divergence.
|
|
|
|
bool mUnhandledDivergence;
|
|
|
|
|
|
|
|
RequestInfo() : mUnhandledDivergence(false) {}
|
|
|
|
|
|
|
|
RequestInfo(const RequestInfo& o)
|
|
|
|
: mUnhandledDivergence(o.mUnhandledDivergence) {
|
|
|
|
mRequestBuffer.append(o.mRequestBuffer.begin(), o.mRequestBuffer.length());
|
|
|
|
mResponseBuffer.append(o.mResponseBuffer.begin(),
|
|
|
|
o.mResponseBuffer.length());
|
|
|
|
}
|
|
|
|
};
|
|
|
|
typedef InfallibleVector<RequestInfo, 4, UntrackedAllocPolicy>
|
|
|
|
UntrackedRequestVector;
|
|
|
|
|
2018-08-18 18:39:48 +03:00
|
|
|
// Phase when the replaying process is paused.
|
|
|
|
class PausedPhase final : public NavigationPhase {
|
|
|
|
// Location of the pause.
|
2018-07-22 14:58:24 +03:00
|
|
|
ExecutionPoint mPoint;
|
|
|
|
|
2018-08-18 18:39:48 +03:00
|
|
|
// Whether we are paused at the end of the recording.
|
|
|
|
bool mRecordingEndpoint;
|
|
|
|
|
2018-07-22 14:58:24 +03:00
|
|
|
// All debugger requests we have seen while paused here.
|
|
|
|
UntrackedRequestVector mRequests;
|
|
|
|
|
|
|
|
// Index of the request currently being processed. Normally this is the
|
|
|
|
// last entry in |mRequests|, though may be earlier if we are recovering
|
|
|
|
// from an unhandled divergence.
|
|
|
|
size_t mRequestIndex;
|
|
|
|
|
2018-08-18 18:39:48 +03:00
|
|
|
// Whether we have saved a temporary checkpoint.
|
|
|
|
bool mSavedTemporaryCheckpoint;
|
|
|
|
|
|
|
|
// Whether we had to restore a checkpoint to deal with an unhandled
|
|
|
|
// recording divergence, and haven't finished rehandling old requests.
|
|
|
|
bool mRecoveringFromDivergence;
|
|
|
|
|
2018-07-22 14:58:24 +03:00
|
|
|
// Set when we were told to resume forward and need to clean up our state.
|
|
|
|
bool mResumeForward;
|
|
|
|
|
|
|
|
public:
|
2018-08-18 18:39:48 +03:00
|
|
|
void Enter(const ExecutionPoint& aPoint, bool aRewind = false,
|
|
|
|
bool aRecordingEndpoint = false);
|
2018-07-22 14:58:24 +03:00
|
|
|
|
|
|
|
void ToString(nsAutoCString& aStr) override {
|
2018-08-18 18:39:48 +03:00
|
|
|
aStr.AppendPrintf("Paused RecoveringFromDivergence %d",
|
|
|
|
mRecoveringFromDivergence);
|
2018-07-22 14:58:24 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void AfterCheckpoint(const CheckpointId& aCheckpoint) override;
|
|
|
|
void PositionHit(const ExecutionPoint& aPoint) override;
|
|
|
|
void Resume(bool aForward) override;
|
|
|
|
void RestoreCheckpoint(size_t aCheckpoint) override;
|
2018-08-18 18:39:48 +03:00
|
|
|
void RunToPoint(const ExecutionPoint& aTarget) override;
|
2018-07-22 14:58:24 +03:00
|
|
|
void HandleDebuggerRequest(js::CharBuffer* aRequestBuffer) override;
|
|
|
|
bool MaybeDivergeFromRecording() override;
|
2018-11-06 20:37:22 +03:00
|
|
|
ExecutionPoint CurrentExecutionPoint() override;
|
2018-07-22 14:58:24 +03:00
|
|
|
|
2018-08-18 18:39:48 +03:00
|
|
|
bool EnsureTemporaryCheckpoint();
|
2018-07-22 14:58:24 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
// Phase when execution is proceeding forwards in search of breakpoint hits.
|
|
|
|
class ForwardPhase final : public NavigationPhase {
|
|
|
|
// Some execution point in the recent past. There are no checkpoints or
|
|
|
|
// breakpoint hits between this point and the current point of execution.
|
|
|
|
ExecutionPoint mPoint;
|
|
|
|
|
|
|
|
public:
|
|
|
|
void Enter(const ExecutionPoint& aPoint);
|
|
|
|
|
|
|
|
void ToString(nsAutoCString& aStr) override { aStr.AppendPrintf("Forward"); }
|
|
|
|
|
|
|
|
void AfterCheckpoint(const CheckpointId& aCheckpoint) override;
|
|
|
|
void PositionHit(const ExecutionPoint& aPoint) override;
|
|
|
|
void HitRecordingEndpoint(const ExecutionPoint& aPoint) override;
|
2019-01-31 04:28:06 +03:00
|
|
|
bool ShouldSendPaintMessage() override;
|
2018-07-22 14:58:24 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
// Phase when the replaying process is running forward from a checkpoint to a
|
|
|
|
// breakpoint at a particular execution point.
|
|
|
|
class ReachBreakpointPhase final : public NavigationPhase {
|
|
|
|
private:
|
|
|
|
// Where to start running from.
|
|
|
|
CheckpointId mStart;
|
|
|
|
|
|
|
|
// The point we are running to.
|
|
|
|
ExecutionPoint mPoint;
|
|
|
|
|
|
|
|
// Point at which to decide whether to save a temporary checkpoint.
|
|
|
|
Maybe<ExecutionPoint> mTemporaryCheckpoint;
|
|
|
|
|
|
|
|
// Whether we have saved a temporary checkpoint at the specified point.
|
|
|
|
bool mSavedTemporaryCheckpoint;
|
|
|
|
|
|
|
|
// The time at which we started running forward from the initial
|
|
|
|
// checkpoint, in microseconds.
|
|
|
|
double mStartTime;
|
|
|
|
|
|
|
|
public:
|
|
|
|
void Enter(const CheckpointId& aStart, bool aRewind,
|
|
|
|
const ExecutionPoint& aPoint,
|
|
|
|
const Maybe<ExecutionPoint>& aTemporaryCheckpoint);
|
|
|
|
|
|
|
|
void ToString(nsAutoCString& aStr) override {
|
|
|
|
aStr.AppendPrintf("ReachBreakpoint: ");
|
2018-12-28 02:27:58 +03:00
|
|
|
mPoint.ToString(aStr);
|
2018-07-22 14:58:24 +03:00
|
|
|
if (mTemporaryCheckpoint.isSome()) {
|
|
|
|
aStr.AppendPrintf(" TemporaryCheckpoint: ");
|
2018-12-28 02:27:58 +03:00
|
|
|
mTemporaryCheckpoint.ref().ToString(aStr);
|
2018-07-22 14:58:24 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AfterCheckpoint(const CheckpointId& aCheckpoint) override;
|
|
|
|
void PositionHit(const ExecutionPoint& aPoint) override;
|
2019-01-31 04:28:06 +03:00
|
|
|
bool ShouldSendPaintMessage() override;
|
2018-07-22 14:58:24 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
// Phase when the replaying process is searching forward from a checkpoint to
|
|
|
|
// find the last point a breakpoint is hit before reaching an execution point.
|
|
|
|
class FindLastHitPhase final : public NavigationPhase {
|
|
|
|
// Where we started searching from.
|
|
|
|
CheckpointId mStart;
|
|
|
|
|
2019-01-31 04:28:06 +03:00
|
|
|
// Endpoint of the search.
|
|
|
|
ExecutionPoint mEnd;
|
2018-07-22 14:58:24 +03:00
|
|
|
|
2018-11-20 03:09:33 +03:00
|
|
|
// Whether the endpoint itself is considered to be part of the search space.
|
|
|
|
bool mIncludeEnd;
|
|
|
|
|
2018-07-22 14:58:24 +03:00
|
|
|
// Counter that increases as we run forward, for ordering hits.
|
|
|
|
size_t mCounter;
|
|
|
|
|
|
|
|
// All positions we are interested in hits for, including all breakpoint
|
|
|
|
// positions (and possibly other positions).
|
|
|
|
struct TrackedPosition {
|
|
|
|
BreakpointPosition mPosition;
|
|
|
|
|
|
|
|
// The last time this was hit so far, or invalid.
|
|
|
|
ExecutionPoint mLastHit;
|
|
|
|
|
|
|
|
// The value of the counter when the last hit occurred.
|
|
|
|
size_t mLastHitCount;
|
|
|
|
|
|
|
|
explicit TrackedPosition(const BreakpointPosition& aPosition)
|
|
|
|
: mPosition(aPosition), mLastHitCount(0) {}
|
|
|
|
};
|
|
|
|
InfallibleVector<TrackedPosition, 4, UntrackedAllocPolicy> mTrackedPositions;
|
|
|
|
|
|
|
|
const TrackedPosition& FindTrackedPosition(const BreakpointPosition& aPos);
|
2018-11-20 03:09:33 +03:00
|
|
|
void CheckForRegionEnd(const ExecutionPoint& aPoint);
|
2018-07-22 14:58:24 +03:00
|
|
|
void OnRegionEnd();
|
|
|
|
|
|
|
|
public:
|
|
|
|
// Note: this always rewinds.
|
2019-01-31 04:28:06 +03:00
|
|
|
void Enter(const CheckpointId& aStart, const ExecutionPoint& aEnd,
|
2018-11-20 03:09:33 +03:00
|
|
|
bool aIncludeEnd);
|
2018-07-22 14:58:24 +03:00
|
|
|
|
|
|
|
void ToString(nsAutoCString& aStr) override {
|
2019-02-15 11:15:57 +03:00
|
|
|
aStr.AppendPrintf("FindLastHit #%zu:%zu", mStart.mNormal,
|
|
|
|
mStart.mTemporary);
|
2019-01-31 04:28:06 +03:00
|
|
|
mEnd.ToString(aStr);
|
2018-07-22 14:58:24 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void AfterCheckpoint(const CheckpointId& aCheckpoint) override;
|
|
|
|
void PositionHit(const ExecutionPoint& aPoint) override;
|
|
|
|
void HitRecordingEndpoint(const ExecutionPoint& aPoint) override;
|
2019-01-31 04:28:06 +03:00
|
|
|
bool ShouldSendPaintMessage() override;
|
2018-07-22 14:58:24 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
// Structure which manages state about the breakpoints in existence and about
|
|
|
|
// how the process is being navigated through. This is allocated in untracked
|
|
|
|
// memory and its contents will not change when restoring an earlier
|
|
|
|
// checkpoint.
|
|
|
|
class NavigationState {
|
|
|
|
// When replaying, the last known recording endpoint. There may be other,
|
|
|
|
// later endpoints we haven't been informed about.
|
|
|
|
ExecutionPoint mRecordingEndpoint;
|
|
|
|
size_t mRecordingEndpointIndex;
|
|
|
|
|
|
|
|
// The last checkpoint we ran forward or rewound to.
|
|
|
|
CheckpointId mLastCheckpoint;
|
|
|
|
|
|
|
|
// The locations of all temporary checkpoints we have saved. Temporary
|
|
|
|
// checkpoints are taken immediately prior to reaching these points.
|
|
|
|
InfallibleVector<ExecutionPoint, 0, UntrackedAllocPolicy>
|
|
|
|
mTemporaryCheckpoints;
|
|
|
|
|
|
|
|
public:
|
2018-11-15 04:56:49 +03:00
|
|
|
// All the currently installed breakpoints.
|
2018-07-22 14:58:24 +03:00
|
|
|
InfallibleVector<BreakpointPosition, 4, UntrackedAllocPolicy> mBreakpoints;
|
|
|
|
|
|
|
|
CheckpointId LastCheckpoint() { return mLastCheckpoint; }
|
|
|
|
|
|
|
|
// The current phase of the process.
|
|
|
|
NavigationPhase* mPhase;
|
|
|
|
|
|
|
|
void SetPhase(NavigationPhase* phase) {
|
|
|
|
mPhase = phase;
|
|
|
|
|
|
|
|
if (SpewEnabled()) {
|
|
|
|
nsAutoCString str;
|
|
|
|
mPhase->ToString(str);
|
|
|
|
|
|
|
|
PrintSpew("SetNavigationPhase %s\n", str.get());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-18 18:39:48 +03:00
|
|
|
PausedPhase mPausedPhase;
|
2018-07-22 14:58:24 +03:00
|
|
|
ForwardPhase mForwardPhase;
|
|
|
|
ReachBreakpointPhase mReachBreakpointPhase;
|
|
|
|
FindLastHitPhase mFindLastHitPhase;
|
|
|
|
|
|
|
|
// For testing, specify that temporary checkpoints should be taken regardless
|
|
|
|
// of how much time has elapsed.
|
|
|
|
bool mAlwaysSaveTemporaryCheckpoints;
|
|
|
|
|
2018-11-06 20:37:41 +03:00
|
|
|
// Progress counts for all checkpoints that have been encountered.
|
|
|
|
InfallibleVector<ProgressCounter, 0, UntrackedAllocPolicy>
|
|
|
|
mCheckpointProgress;
|
2018-08-03 02:26:25 +03:00
|
|
|
|
2018-07-22 14:58:24 +03:00
|
|
|
// Note: NavigationState is initially zeroed.
|
|
|
|
NavigationState() : mPhase(&mForwardPhase) {
|
|
|
|
if (IsReplaying()) {
|
|
|
|
// The recording must include everything up to the first
|
|
|
|
// checkpoint. After that point we will ask the record/replay
|
|
|
|
// system to notify us about any further endpoints.
|
2018-11-06 20:37:41 +03:00
|
|
|
mRecordingEndpoint = ExecutionPoint(CheckpointId::First, 0);
|
2018-07-22 14:58:24 +03:00
|
|
|
}
|
2018-11-06 20:37:41 +03:00
|
|
|
mCheckpointProgress.append(0);
|
2018-07-22 14:58:24 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void AfterCheckpoint(const CheckpointId& aCheckpoint) {
|
|
|
|
mLastCheckpoint = aCheckpoint;
|
|
|
|
|
|
|
|
// Forget any temporary checkpoints we just rewound past, or made
|
|
|
|
// obsolete by reaching the next normal checkpoint.
|
|
|
|
while (mTemporaryCheckpoints.length() > aCheckpoint.mTemporary) {
|
|
|
|
mTemporaryCheckpoints.popBack();
|
|
|
|
}
|
|
|
|
|
2018-11-06 20:37:41 +03:00
|
|
|
// Update the progress counter for each normal checkpoint.
|
|
|
|
if (!aCheckpoint.mTemporary) {
|
|
|
|
ProgressCounter progress = *ExecutionProgressCounter();
|
|
|
|
if (aCheckpoint.mNormal < mCheckpointProgress.length()) {
|
|
|
|
MOZ_RELEASE_ASSERT(progress ==
|
|
|
|
mCheckpointProgress[aCheckpoint.mNormal]);
|
|
|
|
} else {
|
|
|
|
MOZ_RELEASE_ASSERT(aCheckpoint.mNormal == mCheckpointProgress.length());
|
|
|
|
mCheckpointProgress.append(progress);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-22 14:58:24 +03:00
|
|
|
mPhase->AfterCheckpoint(aCheckpoint);
|
|
|
|
|
|
|
|
// Make sure we don't run past the end of the recording.
|
|
|
|
if (!aCheckpoint.mTemporary) {
|
2018-11-06 20:37:41 +03:00
|
|
|
CheckForRecordingEndpoint(CheckpointExecutionPoint(aCheckpoint.mNormal));
|
2018-07-22 14:58:24 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
MOZ_RELEASE_ASSERT(IsRecording() ||
|
|
|
|
aCheckpoint.mNormal <= mRecordingEndpoint.mCheckpoint);
|
2018-08-18 18:39:48 +03:00
|
|
|
if (aCheckpoint.mNormal == mRecordingEndpoint.mCheckpoint &&
|
|
|
|
mRecordingEndpoint.HasPosition()) {
|
2018-07-22 14:58:24 +03:00
|
|
|
js::EnsurePositionHandler(mRecordingEndpoint.mPosition);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void PositionHit(const ExecutionPoint& aPoint) {
|
|
|
|
mPhase->PositionHit(aPoint);
|
|
|
|
CheckForRecordingEndpoint(aPoint);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Resume(bool aForward) { mPhase->Resume(aForward); }
|
|
|
|
|
|
|
|
void RestoreCheckpoint(size_t aCheckpoint) {
|
|
|
|
mPhase->RestoreCheckpoint(aCheckpoint);
|
|
|
|
}
|
|
|
|
|
2018-08-03 02:27:39 +03:00
|
|
|
void RunToPoint(const ExecutionPoint& aTarget) {
|
|
|
|
mPhase->RunToPoint(aTarget);
|
|
|
|
}
|
|
|
|
|
2018-07-22 14:58:24 +03:00
|
|
|
void HandleDebuggerRequest(js::CharBuffer* aRequestBuffer) {
|
|
|
|
mPhase->HandleDebuggerRequest(aRequestBuffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MaybeDivergeFromRecording() {
|
|
|
|
return mPhase->MaybeDivergeFromRecording();
|
|
|
|
}
|
|
|
|
|
2018-11-06 20:37:22 +03:00
|
|
|
ExecutionPoint CurrentExecutionPoint() {
|
|
|
|
return mPhase->CurrentExecutionPoint();
|
2018-07-22 14:58:24 +03:00
|
|
|
}
|
|
|
|
|
2019-02-15 11:15:57 +03:00
|
|
|
bool ShouldSendPaintMessage() { return mPhase->ShouldSendPaintMessage(); }
|
2019-01-31 04:28:06 +03:00
|
|
|
|
2018-07-22 14:58:24 +03:00
|
|
|
void SetRecordingEndpoint(size_t aIndex, const ExecutionPoint& aEndpoint) {
|
|
|
|
// Ignore endpoints older than the last one we know about.
|
|
|
|
if (aIndex <= mRecordingEndpointIndex) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
MOZ_RELEASE_ASSERT(mRecordingEndpoint.mCheckpoint <= aEndpoint.mCheckpoint);
|
|
|
|
mRecordingEndpointIndex = aIndex;
|
|
|
|
mRecordingEndpoint = aEndpoint;
|
|
|
|
if (aEndpoint.HasPosition()) {
|
|
|
|
js::EnsurePositionHandler(aEndpoint.mPosition);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CheckForRecordingEndpoint(const ExecutionPoint& aPoint) {
|
|
|
|
while (aPoint == mRecordingEndpoint) {
|
|
|
|
// The recording ended after the checkpoint, but maybe there is
|
|
|
|
// another, later endpoint now. This may call back into
|
|
|
|
// setRecordingEndpoint and notify us there is more recording data
|
|
|
|
// available.
|
|
|
|
if (!recordreplay::HitRecordingEndpoint()) {
|
|
|
|
mPhase->HitRecordingEndpoint(mRecordingEndpoint);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-06 20:37:22 +03:00
|
|
|
ExecutionPoint LastRecordingEndpoint() {
|
|
|
|
// Get the last recording endpoint in the recording file.
|
|
|
|
while (recordreplay::HitRecordingEndpoint()) {
|
|
|
|
}
|
|
|
|
return mRecordingEndpoint;
|
|
|
|
}
|
|
|
|
|
2018-07-22 14:58:24 +03:00
|
|
|
bool SaveTemporaryCheckpoint(const ExecutionPoint& aPoint) {
|
|
|
|
MOZ_RELEASE_ASSERT(aPoint.mCheckpoint == mLastCheckpoint.mNormal);
|
|
|
|
mTemporaryCheckpoints.append(aPoint);
|
|
|
|
return NewCheckpoint(/* aTemporary = */ true);
|
|
|
|
}
|
|
|
|
|
|
|
|
ExecutionPoint LastTemporaryCheckpointLocation() {
|
|
|
|
MOZ_RELEASE_ASSERT(!mTemporaryCheckpoints.empty());
|
|
|
|
return mTemporaryCheckpoints.back();
|
|
|
|
}
|
2018-11-06 20:37:41 +03:00
|
|
|
|
|
|
|
ExecutionPoint CheckpointExecutionPoint(size_t aCheckpoint) {
|
|
|
|
MOZ_RELEASE_ASSERT(aCheckpoint < mCheckpointProgress.length());
|
|
|
|
return ExecutionPoint(aCheckpoint, mCheckpointProgress[aCheckpoint]);
|
|
|
|
}
|
2018-07-22 14:58:24 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
static NavigationState* gNavigation;
|
|
|
|
|
2019-01-31 04:28:06 +03:00
|
|
|
// When searching backwards in the execution space, we need to ignore any
|
|
|
|
// temporary checkpoints associated with old normal checkpoints. We don't
|
|
|
|
// remember what execution points these old temporary checkpoints are
|
|
|
|
// associated with.
|
2019-02-15 11:15:57 +03:00
|
|
|
static CheckpointId SkipUnknownTemporaryCheckpoints(
|
|
|
|
const CheckpointId& aCheckpoint) {
|
2019-01-31 04:28:06 +03:00
|
|
|
CheckpointId rval = aCheckpoint;
|
|
|
|
while (rval.mTemporary &&
|
|
|
|
rval.mNormal != gNavigation->LastCheckpoint().mNormal) {
|
|
|
|
rval = GetLastSavedCheckpointPriorTo(rval);
|
|
|
|
}
|
|
|
|
return rval;
|
|
|
|
}
|
|
|
|
|
2018-07-22 14:58:24 +03:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
2018-08-18 18:39:48 +03:00
|
|
|
// Paused Phase
|
2018-07-22 14:58:24 +03:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
static bool ThisProcessCanRewind() { return HasSavedCheckpoint(); }
|
|
|
|
|
2018-11-15 04:56:49 +03:00
|
|
|
void PausedPhase::Enter(const ExecutionPoint& aPoint, bool aRewind,
|
|
|
|
bool aRecordingEndpoint) {
|
2018-07-22 14:58:24 +03:00
|
|
|
mPoint = aPoint;
|
2018-08-18 18:39:48 +03:00
|
|
|
mRecordingEndpoint = aRecordingEndpoint;
|
2018-07-22 14:58:24 +03:00
|
|
|
mRequests.clear();
|
|
|
|
mRequestIndex = 0;
|
2018-08-18 18:39:48 +03:00
|
|
|
mSavedTemporaryCheckpoint = false;
|
|
|
|
mRecoveringFromDivergence = false;
|
2018-07-22 14:58:24 +03:00
|
|
|
mResumeForward = false;
|
|
|
|
|
|
|
|
gNavigation->SetPhase(this);
|
|
|
|
|
2018-08-18 18:39:48 +03:00
|
|
|
if (aRewind) {
|
|
|
|
MOZ_RELEASE_ASSERT(!aPoint.HasPosition());
|
|
|
|
RestoreCheckpointAndResume(CheckpointId(aPoint.mCheckpoint));
|
|
|
|
Unreachable();
|
|
|
|
}
|
2018-07-22 14:58:24 +03:00
|
|
|
|
2018-12-28 02:24:55 +03:00
|
|
|
child::HitExecutionPoint(aPoint, aRecordingEndpoint);
|
2018-07-22 14:58:24 +03:00
|
|
|
}
|
|
|
|
|
2018-08-18 18:39:48 +03:00
|
|
|
void PausedPhase::AfterCheckpoint(const CheckpointId& aCheckpoint) {
|
|
|
|
MOZ_RELEASE_ASSERT(!mRecoveringFromDivergence);
|
|
|
|
if (!aCheckpoint.mTemporary) {
|
|
|
|
// We just rewound here, and are now where we should pause.
|
2018-11-06 20:37:41 +03:00
|
|
|
MOZ_RELEASE_ASSERT(
|
|
|
|
mPoint == gNavigation->CheckpointExecutionPoint(aCheckpoint.mNormal));
|
2018-12-28 02:24:55 +03:00
|
|
|
child::HitExecutionPoint(mPoint, mRecordingEndpoint);
|
2018-08-18 18:39:48 +03:00
|
|
|
} else {
|
|
|
|
// We just saved or restored the temporary checkpoint taken while
|
|
|
|
// processing debugger requests here.
|
|
|
|
MOZ_RELEASE_ASSERT(ThisProcessCanRewind());
|
|
|
|
MOZ_RELEASE_ASSERT(mSavedTemporaryCheckpoint);
|
|
|
|
}
|
2018-07-22 14:58:24 +03:00
|
|
|
}
|
|
|
|
|
2018-08-18 18:39:48 +03:00
|
|
|
void PausedPhase::PositionHit(const ExecutionPoint& aPoint) {
|
2018-07-22 14:58:24 +03:00
|
|
|
// Ignore positions hit while paused (we're probably doing an eval).
|
|
|
|
}
|
|
|
|
|
2018-08-18 18:39:48 +03:00
|
|
|
void PausedPhase::Resume(bool aForward) {
|
2018-07-22 14:58:24 +03:00
|
|
|
MOZ_RELEASE_ASSERT(!mRecoveringFromDivergence);
|
2018-08-18 18:39:48 +03:00
|
|
|
MOZ_RELEASE_ASSERT(!mResumeForward);
|
2018-07-22 14:58:24 +03:00
|
|
|
|
|
|
|
if (aForward) {
|
2018-08-18 18:39:48 +03:00
|
|
|
// If we have saved any temporary checkpoint, we performed an operation
|
|
|
|
// that may have side effects. Clear these unwanted changes by restoring
|
|
|
|
// the temporary checkpoint we saved earlier.
|
|
|
|
if (mSavedTemporaryCheckpoint) {
|
2018-07-22 14:58:24 +03:00
|
|
|
mResumeForward = true;
|
2018-08-18 18:39:48 +03:00
|
|
|
RestoreCheckpointAndResume(gNavigation->LastCheckpoint());
|
2018-07-22 14:58:24 +03:00
|
|
|
Unreachable();
|
|
|
|
}
|
|
|
|
|
|
|
|
js::ClearPausedState();
|
|
|
|
|
|
|
|
// Run forward from the current execution point.
|
|
|
|
gNavigation->mForwardPhase.Enter(mPoint);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-01-31 04:28:06 +03:00
|
|
|
// Search backwards in the execution space, from the last saved checkpoint to
|
|
|
|
// where we are paused.
|
|
|
|
CheckpointId start = GetLastSavedCheckpoint();
|
2018-08-18 18:39:48 +03:00
|
|
|
|
2019-01-31 04:28:06 +03:00
|
|
|
// Skip over any temporary checkpoint we saved.
|
|
|
|
if (mSavedTemporaryCheckpoint) {
|
|
|
|
start = GetLastSavedCheckpointPriorTo(start);
|
|
|
|
}
|
2018-08-18 18:39:48 +03:00
|
|
|
|
2019-01-31 04:28:06 +03:00
|
|
|
// Skip to the previous saved checkpoint if we are paused at a checkpoint.
|
|
|
|
if (!mPoint.HasPosition() && start == CheckpointId(mPoint.mCheckpoint)) {
|
|
|
|
start = GetLastSavedCheckpointPriorTo(start);
|
2018-08-18 18:39:48 +03:00
|
|
|
}
|
2019-01-31 04:28:06 +03:00
|
|
|
|
|
|
|
start = SkipUnknownTemporaryCheckpoints(start);
|
2019-02-15 11:15:57 +03:00
|
|
|
gNavigation->mFindLastHitPhase.Enter(start, mPoint,
|
|
|
|
/* aIncludeEnd = */ false);
|
2018-07-22 14:58:24 +03:00
|
|
|
Unreachable();
|
|
|
|
}
|
|
|
|
|
2018-08-18 18:39:48 +03:00
|
|
|
void PausedPhase::RestoreCheckpoint(size_t aCheckpoint) {
|
2018-11-06 20:37:41 +03:00
|
|
|
ExecutionPoint target = gNavigation->CheckpointExecutionPoint(aCheckpoint);
|
2018-08-18 18:39:48 +03:00
|
|
|
bool rewind = target != mPoint;
|
2018-11-15 04:56:49 +03:00
|
|
|
Enter(target, rewind, /* aRecordingEndpoint = */ false);
|
2018-07-22 14:58:24 +03:00
|
|
|
}
|
|
|
|
|
2018-08-18 18:39:48 +03:00
|
|
|
void PausedPhase::RunToPoint(const ExecutionPoint& aTarget) {
|
|
|
|
// This may only be used when we are paused at a normal checkpoint.
|
|
|
|
MOZ_RELEASE_ASSERT(!mPoint.HasPosition());
|
|
|
|
|
|
|
|
ResumeExecution();
|
2018-11-30 04:09:33 +03:00
|
|
|
|
|
|
|
// If we saved a temporary checkpoint, we need to rewind to erase any side
|
|
|
|
// effects that have happened, as when resuming forward.
|
2018-08-18 18:39:48 +03:00
|
|
|
gNavigation->mReachBreakpointPhase.Enter(
|
2018-11-30 04:09:33 +03:00
|
|
|
gNavigation->LastCheckpoint(), /* aRewind = */ mSavedTemporaryCheckpoint,
|
|
|
|
aTarget, /* aTemporaryCheckpoint = */ Nothing());
|
2018-08-18 18:39:48 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void PausedPhase::HandleDebuggerRequest(js::CharBuffer* aRequestBuffer) {
|
2018-07-22 14:58:24 +03:00
|
|
|
MOZ_RELEASE_ASSERT(!mRecoveringFromDivergence);
|
2018-08-18 18:39:48 +03:00
|
|
|
MOZ_RELEASE_ASSERT(!mResumeForward);
|
2018-07-22 14:58:24 +03:00
|
|
|
|
|
|
|
mRequests.emplaceBack();
|
2018-08-18 18:39:48 +03:00
|
|
|
size_t index = mRequests.length() - 1;
|
|
|
|
mRequests[index].mRequestBuffer.append(aRequestBuffer->begin(),
|
|
|
|
aRequestBuffer->length());
|
2018-07-22 14:58:24 +03:00
|
|
|
|
2018-08-18 18:39:48 +03:00
|
|
|
mRequestIndex = index;
|
2018-07-22 14:58:24 +03:00
|
|
|
|
|
|
|
js::CharBuffer responseBuffer;
|
|
|
|
js::ProcessRequest(aRequestBuffer->begin(), aRequestBuffer->length(),
|
|
|
|
&responseBuffer);
|
|
|
|
|
|
|
|
delete aRequestBuffer;
|
|
|
|
|
2018-08-18 18:39:48 +03:00
|
|
|
if (gNavigation->mPhase != this) {
|
|
|
|
// We saved a temporary checkpoint by calling MaybeDivergeFromRecording
|
|
|
|
// within ProcessRequest, then restored it while scanning backwards.
|
|
|
|
ResumeExecution();
|
|
|
|
return;
|
|
|
|
}
|
2018-07-22 14:58:24 +03:00
|
|
|
|
2018-08-18 18:39:48 +03:00
|
|
|
if (!mResumeForward && !mRecoveringFromDivergence) {
|
|
|
|
// We processed this request normally. Remember the response and send it to
|
|
|
|
// the middleman process.
|
|
|
|
MOZ_RELEASE_ASSERT(index == mRequestIndex);
|
|
|
|
mRequests[index].mResponseBuffer.append(responseBuffer.begin(),
|
|
|
|
responseBuffer.length());
|
|
|
|
child::RespondToRequest(responseBuffer);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mResumeForward) {
|
|
|
|
// We rewound to erase side effects from the temporary checkpoint we saved
|
|
|
|
// under ProcessRequest. Just start running forward.
|
|
|
|
MOZ_RELEASE_ASSERT(!mRecoveringFromDivergence);
|
|
|
|
gNavigation->mForwardPhase.Enter(mPoint);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We rewound after having an unhandled recording divergence while processing
|
|
|
|
// mRequests[index] or some later request. We need to redo all requests up to
|
|
|
|
// the last request we received.
|
2018-07-22 14:58:24 +03:00
|
|
|
|
2018-08-18 18:39:48 +03:00
|
|
|
// Remember that the last request triggered an unhandled divergence.
|
2018-07-22 14:58:24 +03:00
|
|
|
MOZ_RELEASE_ASSERT(!mRequests.back().mUnhandledDivergence);
|
|
|
|
mRequests.back().mUnhandledDivergence = true;
|
|
|
|
|
2018-08-18 18:39:48 +03:00
|
|
|
for (size_t i = index; i < mRequests.length(); i++) {
|
2018-07-22 14:58:24 +03:00
|
|
|
RequestInfo& info = mRequests[i];
|
|
|
|
mRequestIndex = i;
|
|
|
|
|
2018-08-18 18:39:48 +03:00
|
|
|
if (i == index) {
|
|
|
|
// We just performed this request, and responseBuffer has the right
|
|
|
|
// contents.
|
|
|
|
} else {
|
|
|
|
responseBuffer.clear();
|
|
|
|
js::ProcessRequest(info.mRequestBuffer.begin(),
|
|
|
|
info.mRequestBuffer.length(), &responseBuffer);
|
|
|
|
}
|
2018-07-22 14:58:24 +03:00
|
|
|
|
|
|
|
if (i < mRequests.length() - 1) {
|
|
|
|
// This is an old request, and we don't need to send another
|
|
|
|
// response to it. Make sure the response we just generated matched
|
|
|
|
// the earlier one we sent, though.
|
|
|
|
MOZ_RELEASE_ASSERT(responseBuffer.length() ==
|
|
|
|
info.mResponseBuffer.length());
|
|
|
|
MOZ_RELEASE_ASSERT(
|
|
|
|
memcmp(responseBuffer.begin(), info.mResponseBuffer.begin(),
|
|
|
|
responseBuffer.length() * sizeof(char16_t)) == 0);
|
|
|
|
} else {
|
|
|
|
// This is the current request we need to respond to.
|
|
|
|
MOZ_RELEASE_ASSERT(info.mResponseBuffer.empty());
|
|
|
|
info.mResponseBuffer.append(responseBuffer.begin(),
|
|
|
|
responseBuffer.length());
|
|
|
|
child::RespondToRequest(responseBuffer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// We've finished recovering, and can now process new incoming requests.
|
|
|
|
mRecoveringFromDivergence = false;
|
|
|
|
}
|
|
|
|
|
2018-08-18 18:39:48 +03:00
|
|
|
bool PausedPhase::MaybeDivergeFromRecording() {
|
2018-07-22 14:58:24 +03:00
|
|
|
if (!ThisProcessCanRewind()) {
|
|
|
|
// Recording divergence is not supported if we can't rewind. We can't
|
|
|
|
// simply allow execution to proceed from here as if we were not
|
|
|
|
// diverged, since any events or other activity that show up afterwards
|
|
|
|
// will not be reflected in the recording.
|
|
|
|
return false;
|
|
|
|
}
|
2018-08-18 18:39:48 +03:00
|
|
|
|
2018-10-17 18:59:32 +03:00
|
|
|
size_t index = mRequestIndex;
|
|
|
|
|
2018-08-18 18:39:48 +03:00
|
|
|
if (!EnsureTemporaryCheckpoint()) {
|
|
|
|
// One of the premature exit cases was hit in EnsureTemporaryCheckpoint.
|
|
|
|
// Don't allow any operations that can diverge from the recording.
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-10-17 18:59:32 +03:00
|
|
|
if (mRequests[index].mUnhandledDivergence) {
|
2018-08-18 18:39:48 +03:00
|
|
|
// We tried to process this request before and had an unhandled divergence.
|
|
|
|
// Disallow the request handler from doing anything that might diverge from
|
|
|
|
// the recording.
|
2018-07-22 14:58:24 +03:00
|
|
|
return false;
|
|
|
|
}
|
2018-08-18 18:39:48 +03:00
|
|
|
|
2018-07-22 14:58:24 +03:00
|
|
|
DivergeFromRecording();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-08-18 18:39:48 +03:00
|
|
|
bool PausedPhase::EnsureTemporaryCheckpoint() {
|
|
|
|
if (mSavedTemporaryCheckpoint) {
|
|
|
|
return true;
|
2018-07-22 14:58:24 +03:00
|
|
|
}
|
|
|
|
|
2018-08-18 18:39:48 +03:00
|
|
|
// We need to save a temporary checkpoint that we can restore if we hit
|
|
|
|
// a recording divergence.
|
|
|
|
mSavedTemporaryCheckpoint = true;
|
2018-07-22 14:58:24 +03:00
|
|
|
|
2018-08-18 18:39:48 +03:00
|
|
|
size_t index = mRequestIndex;
|
|
|
|
if (gNavigation->SaveTemporaryCheckpoint(mPoint)) {
|
|
|
|
// We just saved the temporary checkpoint.
|
|
|
|
return true;
|
|
|
|
}
|
2018-07-22 14:58:24 +03:00
|
|
|
|
2018-08-18 18:39:48 +03:00
|
|
|
// We just rewound here.
|
|
|
|
if (gNavigation->mPhase != this) {
|
|
|
|
// We are no longer paused at this point. We should be searching
|
|
|
|
// backwards in the region after this temporary checkpoint was taken.
|
|
|
|
// Return false to ensure we don't perform any side effects before
|
|
|
|
// resuming forward.
|
|
|
|
return false;
|
2018-07-22 14:58:24 +03:00
|
|
|
}
|
|
|
|
|
2018-08-18 18:39:48 +03:00
|
|
|
// We are still paused at this point. Either we had an unhandled
|
|
|
|
// recording divergence, or we intentionally rewound to erase side
|
|
|
|
// effects that occurred while paused here.
|
|
|
|
MOZ_RELEASE_ASSERT(!mRecoveringFromDivergence);
|
2018-08-03 02:27:39 +03:00
|
|
|
|
2018-08-18 18:39:48 +03:00
|
|
|
if (mResumeForward) {
|
|
|
|
// We can't diverge from the recording before resuming forward execution.
|
|
|
|
return false;
|
|
|
|
}
|
2018-07-22 14:58:24 +03:00
|
|
|
|
2018-08-18 18:39:48 +03:00
|
|
|
mRecoveringFromDivergence = true;
|
2018-07-22 14:58:24 +03:00
|
|
|
|
2018-08-18 18:39:48 +03:00
|
|
|
if (index == mRequestIndex) {
|
|
|
|
// We had an unhandled divergence for the same request where we
|
|
|
|
// created the temporary checkpoint. mUnhandledDivergence hasn't been
|
|
|
|
// set yet, but return now to avoid triggering the same divergence
|
|
|
|
// and rewinding again.
|
|
|
|
return false;
|
|
|
|
}
|
2018-07-22 14:58:24 +03:00
|
|
|
|
2018-08-18 18:39:48 +03:00
|
|
|
// Allow the caller to check mUnhandledDivergence.
|
|
|
|
return true;
|
2018-07-22 14:58:24 +03:00
|
|
|
}
|
|
|
|
|
2018-11-06 20:37:22 +03:00
|
|
|
ExecutionPoint PausedPhase::CurrentExecutionPoint() { return mPoint; }
|
2018-07-22 14:58:24 +03:00
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// ForwardPhase
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
void ForwardPhase::Enter(const ExecutionPoint& aPoint) {
|
|
|
|
mPoint = aPoint;
|
|
|
|
|
|
|
|
gNavigation->SetPhase(this);
|
|
|
|
|
|
|
|
// Install handlers for all breakpoints.
|
|
|
|
for (const BreakpointPosition& breakpoint : gNavigation->mBreakpoints) {
|
2018-11-15 04:56:49 +03:00
|
|
|
js::EnsurePositionHandler(breakpoint);
|
2018-07-22 14:58:24 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
ResumeExecution();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ForwardPhase::AfterCheckpoint(const CheckpointId& aCheckpoint) {
|
|
|
|
MOZ_RELEASE_ASSERT(!aCheckpoint.mTemporary &&
|
|
|
|
aCheckpoint.mNormal == mPoint.mCheckpoint + 1);
|
2018-11-06 20:37:41 +03:00
|
|
|
gNavigation->mPausedPhase.Enter(
|
|
|
|
gNavigation->CheckpointExecutionPoint(aCheckpoint.mNormal));
|
2018-07-22 14:58:24 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void ForwardPhase::PositionHit(const ExecutionPoint& aPoint) {
|
2018-11-15 04:56:49 +03:00
|
|
|
bool hitBreakpoint = false;
|
|
|
|
for (const BreakpointPosition& breakpoint : gNavigation->mBreakpoints) {
|
|
|
|
if (breakpoint.Subsumes(aPoint.mPosition)) {
|
|
|
|
hitBreakpoint = true;
|
|
|
|
}
|
|
|
|
}
|
2018-07-22 14:58:24 +03:00
|
|
|
|
2018-11-15 04:56:49 +03:00
|
|
|
if (hitBreakpoint) {
|
|
|
|
gNavigation->mPausedPhase.Enter(aPoint);
|
2018-07-22 14:58:24 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ForwardPhase::HitRecordingEndpoint(const ExecutionPoint& aPoint) {
|
2018-11-15 04:56:49 +03:00
|
|
|
gNavigation->mPausedPhase.Enter(aPoint, /* aRewind = */ false,
|
|
|
|
/* aRecordingEndpoint = */ true);
|
2018-07-22 14:58:24 +03:00
|
|
|
}
|
|
|
|
|
2019-02-15 11:15:57 +03:00
|
|
|
bool ForwardPhase::ShouldSendPaintMessage() { return true; }
|
2019-01-31 04:28:06 +03:00
|
|
|
|
2018-07-22 14:58:24 +03:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// ReachBreakpointPhase
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
void ReachBreakpointPhase::Enter(
|
|
|
|
const CheckpointId& aStart, bool aRewind, const ExecutionPoint& aPoint,
|
|
|
|
const Maybe<ExecutionPoint>& aTemporaryCheckpoint) {
|
|
|
|
MOZ_RELEASE_ASSERT(aPoint.HasPosition());
|
|
|
|
MOZ_RELEASE_ASSERT(aTemporaryCheckpoint.isNothing() ||
|
|
|
|
(aTemporaryCheckpoint.ref().HasPosition() &&
|
|
|
|
aTemporaryCheckpoint.ref() != aPoint));
|
|
|
|
mStart = aStart;
|
|
|
|
mPoint = aPoint;
|
|
|
|
mTemporaryCheckpoint = aTemporaryCheckpoint;
|
|
|
|
mSavedTemporaryCheckpoint = false;
|
|
|
|
|
|
|
|
gNavigation->SetPhase(this);
|
|
|
|
|
2018-08-03 02:27:39 +03:00
|
|
|
if (aRewind) {
|
|
|
|
RestoreCheckpointAndResume(aStart);
|
|
|
|
Unreachable();
|
|
|
|
} else {
|
|
|
|
AfterCheckpoint(aStart);
|
|
|
|
}
|
2018-07-22 14:58:24 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void ReachBreakpointPhase::AfterCheckpoint(const CheckpointId& aCheckpoint) {
|
2019-01-31 04:28:06 +03:00
|
|
|
// We can't run past our target point.
|
|
|
|
MOZ_RELEASE_ASSERT(aCheckpoint.mNormal <= mPoint.mCheckpoint);
|
2018-07-22 14:58:24 +03:00
|
|
|
|
2019-01-31 04:28:06 +03:00
|
|
|
if (aCheckpoint == mStart) {
|
2018-07-22 14:58:24 +03:00
|
|
|
// Remember the time we started running forwards from the initial
|
|
|
|
// checkpoint.
|
|
|
|
mStartTime = CurrentTime();
|
|
|
|
}
|
|
|
|
|
|
|
|
js::EnsurePositionHandler(mPoint.mPosition);
|
2019-01-31 04:28:06 +03:00
|
|
|
|
|
|
|
if (mTemporaryCheckpoint.isSome()) {
|
|
|
|
js::EnsurePositionHandler(mTemporaryCheckpoint.ref().mPosition);
|
|
|
|
}
|
2018-07-22 14:58:24 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// The number of milliseconds to elapse during a ReachBreakpoint search before
|
|
|
|
// we will save a temporary checkpoint.
|
|
|
|
static const double kTemporaryCheckpointThresholdMs = 10;
|
|
|
|
|
|
|
|
void AlwaysSaveTemporaryCheckpoints() {
|
|
|
|
gNavigation->mAlwaysSaveTemporaryCheckpoints = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ReachBreakpointPhase::PositionHit(const ExecutionPoint& aPoint) {
|
|
|
|
if (mTemporaryCheckpoint.isSome() && mTemporaryCheckpoint.ref() == aPoint) {
|
|
|
|
// We've reached the point at which we have the option of saving a
|
|
|
|
// temporary checkpoint.
|
|
|
|
double elapsedMs = (CurrentTime() - mStartTime) / 1000.0;
|
|
|
|
if (elapsedMs >= kTemporaryCheckpointThresholdMs ||
|
|
|
|
gNavigation->mAlwaysSaveTemporaryCheckpoints) {
|
|
|
|
MOZ_RELEASE_ASSERT(!mSavedTemporaryCheckpoint);
|
|
|
|
mSavedTemporaryCheckpoint = true;
|
|
|
|
|
|
|
|
if (!gNavigation->SaveTemporaryCheckpoint(aPoint)) {
|
|
|
|
// We just restored the checkpoint, and could be in any phase.
|
|
|
|
gNavigation->PositionHit(aPoint);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mPoint == aPoint) {
|
2018-11-15 04:56:49 +03:00
|
|
|
gNavigation->mPausedPhase.Enter(aPoint);
|
2018-07-22 14:58:24 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-15 11:15:57 +03:00
|
|
|
bool ReachBreakpointPhase::ShouldSendPaintMessage() {
|
2019-01-31 04:28:06 +03:00
|
|
|
// We don't need to send paint messages when reaching the breakpoint, as we
|
|
|
|
// will be pausing at the breakpoint and doing a repaint of the state there.
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-07-22 14:58:24 +03:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// FindLastHitPhase
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
2018-11-20 03:09:33 +03:00
|
|
|
void FindLastHitPhase::Enter(const CheckpointId& aStart,
|
2019-01-31 04:28:06 +03:00
|
|
|
const ExecutionPoint& aEnd, bool aIncludeEnd) {
|
2018-07-22 14:58:24 +03:00
|
|
|
mStart = aStart;
|
|
|
|
mEnd = aEnd;
|
2018-11-20 03:09:33 +03:00
|
|
|
mIncludeEnd = aIncludeEnd;
|
2018-07-22 14:58:24 +03:00
|
|
|
mCounter = 0;
|
|
|
|
mTrackedPositions.clear();
|
|
|
|
|
|
|
|
gNavigation->SetPhase(this);
|
|
|
|
|
|
|
|
// All breakpoints are tracked positions.
|
|
|
|
for (const BreakpointPosition& breakpoint : gNavigation->mBreakpoints) {
|
|
|
|
if (breakpoint.IsValid()) {
|
|
|
|
mTrackedPositions.emplaceBack(breakpoint);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Entry points to scripts containing breakpoints are tracked positions.
|
|
|
|
for (const BreakpointPosition& breakpoint : gNavigation->mBreakpoints) {
|
|
|
|
Maybe<BreakpointPosition> entry = GetEntryPosition(breakpoint);
|
|
|
|
if (entry.isSome()) {
|
|
|
|
mTrackedPositions.emplaceBack(entry.ref());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
RestoreCheckpointAndResume(mStart);
|
|
|
|
Unreachable();
|
|
|
|
}
|
|
|
|
|
|
|
|
void FindLastHitPhase::AfterCheckpoint(const CheckpointId& aCheckpoint) {
|
2019-01-31 04:28:06 +03:00
|
|
|
// We can't run past our endpoint.
|
|
|
|
MOZ_RELEASE_ASSERT(aCheckpoint.mNormal <= mEnd.mCheckpoint);
|
|
|
|
|
|
|
|
if (!mEnd.HasPosition() && mEnd.mCheckpoint == aCheckpoint.mNormal) {
|
|
|
|
MOZ_RELEASE_ASSERT(!aCheckpoint.mTemporary);
|
2018-07-22 14:58:24 +03:00
|
|
|
OnRegionEnd();
|
|
|
|
Unreachable();
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const TrackedPosition& tracked : mTrackedPositions) {
|
|
|
|
js::EnsurePositionHandler(tracked.mPosition);
|
|
|
|
}
|
|
|
|
|
2019-01-31 04:28:06 +03:00
|
|
|
if (mEnd.HasPosition()) {
|
|
|
|
js::EnsurePositionHandler(mEnd.mPosition);
|
2018-07-22 14:58:24 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FindLastHitPhase::PositionHit(const ExecutionPoint& aPoint) {
|
2018-11-20 03:09:33 +03:00
|
|
|
if (!mIncludeEnd) {
|
|
|
|
CheckForRegionEnd(aPoint);
|
2018-07-22 14:58:24 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
++mCounter;
|
|
|
|
|
|
|
|
for (TrackedPosition& tracked : mTrackedPositions) {
|
|
|
|
if (tracked.mPosition.Subsumes(aPoint.mPosition)) {
|
|
|
|
tracked.mLastHit = aPoint;
|
|
|
|
tracked.mLastHitCount = mCounter;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2018-11-20 03:09:33 +03:00
|
|
|
|
|
|
|
if (mIncludeEnd) {
|
|
|
|
CheckForRegionEnd(aPoint);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FindLastHitPhase::CheckForRegionEnd(const ExecutionPoint& aPoint) {
|
2019-01-31 04:28:06 +03:00
|
|
|
if (mEnd == aPoint) {
|
2018-11-20 03:09:33 +03:00
|
|
|
OnRegionEnd();
|
|
|
|
Unreachable();
|
|
|
|
}
|
2018-07-22 14:58:24 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void FindLastHitPhase::HitRecordingEndpoint(const ExecutionPoint& aPoint) {
|
|
|
|
OnRegionEnd();
|
|
|
|
Unreachable();
|
|
|
|
}
|
|
|
|
|
2019-02-15 11:15:57 +03:00
|
|
|
bool FindLastHitPhase::ShouldSendPaintMessage() {
|
2019-01-31 04:28:06 +03:00
|
|
|
// If the region we're searching contains multiple normal checkpoints, we
|
|
|
|
// only want to send paint messages for the first one. We won't pause at the
|
|
|
|
// later checkpoints, and sending paint messages for them will clobber the
|
|
|
|
// one for the checkpoint we will end up pausing at.
|
|
|
|
return gNavigation->LastCheckpoint().mNormal == mStart.mNormal;
|
|
|
|
}
|
|
|
|
|
2018-07-22 14:58:24 +03:00
|
|
|
const FindLastHitPhase::TrackedPosition& FindLastHitPhase::FindTrackedPosition(
|
|
|
|
const BreakpointPosition& aPos) {
|
|
|
|
for (const TrackedPosition& tracked : mTrackedPositions) {
|
|
|
|
if (tracked.mPosition == aPos) {
|
|
|
|
return tracked;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
MOZ_CRASH("Could not find tracked position");
|
|
|
|
}
|
|
|
|
|
|
|
|
void FindLastHitPhase::OnRegionEnd() {
|
|
|
|
// Find the point of the last hit which coincides with a breakpoint.
|
|
|
|
Maybe<TrackedPosition> lastBreakpoint;
|
|
|
|
for (const BreakpointPosition& breakpoint : gNavigation->mBreakpoints) {
|
|
|
|
const TrackedPosition& tracked = FindTrackedPosition(breakpoint);
|
|
|
|
if (tracked.mLastHit.HasPosition() &&
|
|
|
|
(lastBreakpoint.isNothing() ||
|
|
|
|
lastBreakpoint.ref().mLastHitCount < tracked.mLastHitCount)) {
|
|
|
|
lastBreakpoint = Some(tracked);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lastBreakpoint.isNothing()) {
|
|
|
|
// No breakpoints were encountered in the search space.
|
|
|
|
if (mStart.mTemporary) {
|
|
|
|
// We started searching forwards from a temporary checkpoint.
|
|
|
|
// Continue searching backwards without notifying the middleman.
|
2019-01-31 04:28:06 +03:00
|
|
|
CheckpointId start = GetLastSavedCheckpointPriorTo(mStart);
|
|
|
|
start = SkipUnknownTemporaryCheckpoints(start);
|
2018-07-22 14:58:24 +03:00
|
|
|
ExecutionPoint end = gNavigation->LastTemporaryCheckpointLocation();
|
2019-01-31 04:28:06 +03:00
|
|
|
if (end.HasPosition() || end.mCheckpoint != start.mNormal) {
|
2018-11-20 03:09:33 +03:00
|
|
|
// The temporary checkpoint comes immediately after its associated
|
|
|
|
// execution point. As we search backwards we need to look for hits at
|
|
|
|
// that execution point itself.
|
2019-01-31 04:28:06 +03:00
|
|
|
gNavigation->mFindLastHitPhase.Enter(start, end,
|
2018-11-20 03:09:33 +03:00
|
|
|
/* aIncludeEnd = */ true);
|
2018-08-18 18:39:48 +03:00
|
|
|
Unreachable();
|
|
|
|
} else {
|
|
|
|
// The last temporary checkpoint may be at the same execution point as
|
|
|
|
// the last normal checkpoint, if it was created while handling
|
|
|
|
// debugger requests there.
|
|
|
|
}
|
2018-07-22 14:58:24 +03:00
|
|
|
}
|
2018-08-18 18:39:48 +03:00
|
|
|
|
|
|
|
// Rewind to the last normal checkpoint and pause.
|
2018-11-06 20:37:41 +03:00
|
|
|
gNavigation->mPausedPhase.Enter(
|
|
|
|
gNavigation->CheckpointExecutionPoint(mStart.mNormal),
|
2018-11-15 04:56:49 +03:00
|
|
|
/* aRewind = */ true);
|
2018-08-18 18:39:48 +03:00
|
|
|
Unreachable();
|
2018-07-22 14:58:24 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// When running backwards, we don't want to place temporary checkpoints at
|
|
|
|
// the breakpoint where we are going to stop at. If the user continues
|
|
|
|
// rewinding then we will just have to discard the checkpoint and waste the
|
|
|
|
// work we did in saving it.
|
|
|
|
//
|
|
|
|
// Instead, try to place a temporary checkpoint at the last time the
|
|
|
|
// breakpoint's script was entered. This optimizes for the case of stepping
|
|
|
|
// around within a frame.
|
|
|
|
Maybe<BreakpointPosition> baseEntry =
|
|
|
|
GetEntryPosition(lastBreakpoint.ref().mPosition);
|
|
|
|
if (baseEntry.isSome()) {
|
|
|
|
const TrackedPosition& tracked = FindTrackedPosition(baseEntry.ref());
|
|
|
|
if (tracked.mLastHit.HasPosition() &&
|
|
|
|
tracked.mLastHitCount < lastBreakpoint.ref().mLastHitCount) {
|
2018-08-03 02:27:39 +03:00
|
|
|
gNavigation->mReachBreakpointPhase.Enter(mStart, /* aRewind = */ true,
|
|
|
|
lastBreakpoint.ref().mLastHit,
|
2018-07-22 14:58:24 +03:00
|
|
|
Some(tracked.mLastHit));
|
|
|
|
Unreachable();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// There was no suitable place for a temporary checkpoint, so rewind to the
|
|
|
|
// last checkpoint and play forward to the last breakpoint hit we found.
|
2018-08-03 02:27:39 +03:00
|
|
|
gNavigation->mReachBreakpointPhase.Enter(
|
|
|
|
mStart, /* aRewind = */ true, lastBreakpoint.ref().mLastHit, Nothing());
|
2018-07-22 14:58:24 +03:00
|
|
|
Unreachable();
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Hooks
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
bool IsInitialized() { return !!gNavigation; }
|
|
|
|
|
|
|
|
void BeforeCheckpoint() {
|
|
|
|
if (!IsInitialized()) {
|
|
|
|
void* navigationMem =
|
|
|
|
AllocateMemory(sizeof(NavigationState), MemoryKind::Navigation);
|
|
|
|
gNavigation = new (navigationMem) NavigationState();
|
|
|
|
|
|
|
|
js::SetupDevtoolsSandbox();
|
2018-11-06 20:37:41 +03:00
|
|
|
|
|
|
|
// Set the progress counter to zero before the first checkpoint. Execution
|
|
|
|
// that occurred before this checkpoint cannot be rewound to.
|
|
|
|
*ExecutionProgressCounter() = 0;
|
2018-07-22 14:58:24 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
AutoDisallowThreadEvents disallow;
|
|
|
|
|
|
|
|
// Reset the debugger to a consistent state before each checkpoint.
|
|
|
|
js::ClearPositionHandlers();
|
|
|
|
}
|
|
|
|
|
|
|
|
void AfterCheckpoint(const CheckpointId& aCheckpoint) {
|
|
|
|
AutoDisallowThreadEvents disallow;
|
|
|
|
|
|
|
|
MOZ_RELEASE_ASSERT(IsRecordingOrReplaying());
|
|
|
|
gNavigation->AfterCheckpoint(aCheckpoint);
|
|
|
|
}
|
|
|
|
|
2018-10-17 19:32:13 +03:00
|
|
|
size_t LastNormalCheckpoint() { return gNavigation->LastCheckpoint().mNormal; }
|
|
|
|
|
2018-07-22 14:58:24 +03:00
|
|
|
void DebuggerRequest(js::CharBuffer* aRequestBuffer) {
|
|
|
|
gNavigation->HandleDebuggerRequest(aRequestBuffer);
|
|
|
|
}
|
|
|
|
|
2018-11-15 04:56:49 +03:00
|
|
|
void AddBreakpoint(const BreakpointPosition& aPosition) {
|
|
|
|
gNavigation->mBreakpoints.append(aPosition);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ClearBreakpoints() {
|
|
|
|
if (gNavigation) {
|
|
|
|
gNavigation->mBreakpoints.clear();
|
|
|
|
}
|
2018-07-22 14:58:24 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void Resume(bool aForward) {
|
|
|
|
// For the primordial resume sent at startup, the navigation state will not
|
|
|
|
// have been initialized yet.
|
|
|
|
if (!gNavigation) {
|
|
|
|
ResumeExecution();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
gNavigation->Resume(aForward);
|
|
|
|
}
|
|
|
|
|
|
|
|
void RestoreCheckpoint(size_t aId) { gNavigation->RestoreCheckpoint(aId); }
|
|
|
|
|
2018-08-03 02:27:39 +03:00
|
|
|
void RunToPoint(const ExecutionPoint& aTarget) {
|
|
|
|
gNavigation->RunToPoint(aTarget);
|
|
|
|
}
|
|
|
|
|
2018-07-22 14:58:24 +03:00
|
|
|
ExecutionPoint GetRecordingEndpoint() {
|
2018-11-06 20:37:22 +03:00
|
|
|
if (IsRecording()) {
|
|
|
|
return gNavigation->CurrentExecutionPoint();
|
|
|
|
} else {
|
|
|
|
return gNavigation->LastRecordingEndpoint();
|
|
|
|
}
|
2018-07-22 14:58:24 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void SetRecordingEndpoint(size_t aIndex, const ExecutionPoint& aEndpoint) {
|
|
|
|
MOZ_RELEASE_ASSERT(IsReplaying());
|
|
|
|
gNavigation->SetRecordingEndpoint(aIndex, aEndpoint);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ProgressCounter gProgressCounter;
|
|
|
|
|
|
|
|
extern "C" {
|
|
|
|
|
|
|
|
MOZ_EXPORT ProgressCounter* RecordReplayInterface_ExecutionProgressCounter() {
|
|
|
|
return &gProgressCounter;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // extern "C"
|
|
|
|
|
2018-11-06 20:37:22 +03:00
|
|
|
ExecutionPoint CurrentExecutionPoint(
|
|
|
|
const Maybe<BreakpointPosition>& aPosition) {
|
|
|
|
if (aPosition.isSome()) {
|
|
|
|
return ExecutionPoint(gNavigation->LastCheckpoint().mNormal,
|
|
|
|
gProgressCounter, aPosition.ref());
|
|
|
|
}
|
|
|
|
return gNavigation->CurrentExecutionPoint();
|
2018-07-22 14:58:24 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void PositionHit(const BreakpointPosition& position) {
|
|
|
|
AutoDisallowThreadEvents disallow;
|
2018-11-06 20:37:22 +03:00
|
|
|
gNavigation->PositionHit(CurrentExecutionPoint(Some(position)));
|
2018-08-03 02:26:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
extern "C" {
|
|
|
|
|
|
|
|
MOZ_EXPORT ProgressCounter RecordReplayInterface_NewTimeWarpTarget() {
|
2018-10-17 18:33:00 +03:00
|
|
|
if (AreThreadEventsDisallowed()) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-08-03 02:26:25 +03:00
|
|
|
// NewTimeWarpTarget() must be called at consistent points between recording
|
|
|
|
// and replaying.
|
2018-10-17 18:33:00 +03:00
|
|
|
RecordReplayAssert("NewTimeWarpTarget");
|
2018-08-03 02:26:25 +03:00
|
|
|
|
|
|
|
if (!gNavigation) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Advance the progress counter for each time warp target. This can be called
|
|
|
|
// at any place and any number of times where recorded events are allowed.
|
|
|
|
ProgressCounter progress = ++gProgressCounter;
|
|
|
|
|
|
|
|
PositionHit(BreakpointPosition(BreakpointPosition::WarpTarget));
|
|
|
|
return progress;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // extern "C"
|
|
|
|
|
|
|
|
ExecutionPoint TimeWarpTargetExecutionPoint(ProgressCounter aTarget) {
|
2018-11-06 20:37:41 +03:00
|
|
|
// To construct an ExecutionPoint, we need the most recent checkpoint prior
|
|
|
|
// to aTarget. We could do a binary search here, but this code is cold and a
|
|
|
|
// linear search is more straightforwardly correct.
|
|
|
|
size_t checkpoint;
|
|
|
|
for (checkpoint = gNavigation->mCheckpointProgress.length() - 1;
|
|
|
|
checkpoint >= CheckpointId::First; checkpoint--) {
|
|
|
|
if (gNavigation->mCheckpointProgress[checkpoint] < aTarget) {
|
2018-08-03 02:26:25 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2018-11-06 20:37:41 +03:00
|
|
|
MOZ_RELEASE_ASSERT(checkpoint >= CheckpointId::First);
|
2018-08-03 02:26:25 +03:00
|
|
|
|
2018-11-06 20:37:41 +03:00
|
|
|
return ExecutionPoint(checkpoint, aTarget,
|
2018-08-03 02:26:25 +03:00
|
|
|
BreakpointPosition(BreakpointPosition::WarpTarget));
|
2018-07-22 14:58:24 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
bool MaybeDivergeFromRecording() {
|
|
|
|
return gNavigation->MaybeDivergeFromRecording();
|
|
|
|
}
|
|
|
|
|
2019-02-15 11:15:57 +03:00
|
|
|
bool ShouldSendPaintMessage() { return gNavigation->ShouldSendPaintMessage(); }
|
2019-01-31 04:28:06 +03:00
|
|
|
|
2018-07-22 14:58:24 +03:00
|
|
|
} // namespace navigation
|
|
|
|
} // namespace recordreplay
|
|
|
|
} // namespace mozilla
|