Backed out changeset d407a28318e6 (bug 1609815) for causing windows ming bustages CLOSED TREE

--HG--
extra : histedit_source : b2c748e31e0f6ba8fcf9960a336e0bbd361b07e6
This commit is contained in:
Ciure Andrei 2020-02-27 07:05:19 +02:00
parent 236d721acb
commit 00dd87f6f4
276 changed files with 19236 additions and 403 deletions

View file

@ -116,10 +116,15 @@ Accessible::Accessible(nsIContent* aContent, DocAccessible* aDoc)
mHideEventTarget(false) { mHideEventTarget(false) {
mBits.groupInfo = nullptr; mBits.groupInfo = nullptr;
mInt.mIndexOfEmbeddedChild = -1; mInt.mIndexOfEmbeddedChild = -1;
// Assign an ID to this Accessible for use in UniqueID().
recordreplay::RegisterThing(this);
} }
Accessible::~Accessible() { Accessible::~Accessible() {
NS_ASSERTION(!mDoc, "LastRelease was never called!?!"); NS_ASSERTION(!mDoc, "LastRelease was never called!?!");
recordreplay::UnregisterThing(this);
} }
ENameValueFlag Accessible::Name(nsString& aName) const { ENameValueFlag Accessible::Name(nsString& aName) const {

View file

@ -172,7 +172,15 @@ class Accessible : public nsISupports {
/** /**
* Return the unique identifier of the accessible. * Return the unique identifier of the accessible.
*/ */
void* UniqueID() { return static_cast<void*>(this); } void* UniqueID() {
// When recording or replaying, use an ID which will be consistent when
// recording/replaying (pointer values are not consistent), so that IPC
// messages from the parent process can be handled when replaying.
if (recordreplay::IsRecordingOrReplaying()) {
return reinterpret_cast<void*>(recordreplay::ThingIndex(this));
}
return static_cast<void*>(this);
}
/** /**
* Return language associated with the accessible. * Return language associated with the accessible.

View file

@ -125,7 +125,7 @@ async function testToggleToolboxButtons() {
]; ];
// Filter out all the buttons which are not supported on the current target. // Filter out all the buttons which are not supported on the current target.
// (DevTools Fission Preferences etc...) // (WebReplay, DevTools Fission Preferences etc...)
const target = await TargetFactory.forTab(gBrowser.selectedTab); const target = await TargetFactory.forTab(gBrowser.selectedTab);
const toolbarButtons = toolbox.toolbarButtons.filter(tool => const toolbarButtons = toolbox.toolbarButtons.filter(tool =>
tool.isTargetSupported(target) tool.isTargetSupported(target)

View file

@ -20,6 +20,7 @@ const {
getCSSStyleRules, getCSSStyleRules,
} = require("devtools/shared/inspector/css-logic"); } = require("devtools/shared/inspector/css-logic");
const InspectorUtils = require("InspectorUtils"); const InspectorUtils = require("InspectorUtils");
const Debugger = require("Debugger");
// Set up a dummy environment so that EventUtils works. We need to be careful to // Set up a dummy environment so that EventUtils works. We need to be careful to
// pass a window object into each EventUtils method we call rather than having // pass a window object into each EventUtils method we call rather than having
@ -501,6 +502,13 @@ var TestActor = (exports.TestActor = protocol.ActorClassWithSpec(testSpec, {
* Get the window which mouse events on node should be delivered to. * Get the window which mouse events on node should be delivered to.
*/ */
windowForMouseEvent: function(node) { windowForMouseEvent: function(node) {
// When replaying, the node is a proxy for an element in the replaying
// process. Use the window which the server is running against, which is
// able to receive events. We can't use isReplaying here because this actor
// is loaded into its own sandbox.
if (Debugger.recordReplayProcessKind() == "Middleman") {
return this.targetActor.window;
}
return node.ownerDocument.defaultView; return node.ownerDocument.defaultView;
}, },

View file

@ -15,7 +15,7 @@ namespace mozilla {
NS_IMPL_ISUPPORTS(TimelineConsumers, nsIObserver); NS_IMPL_ISUPPORTS(TimelineConsumers, nsIObserver);
StaticMutex TimelineConsumers::sMutex; StaticMutexNotRecorded TimelineConsumers::sMutex;
// Manually manage this singleton's lifetime and destroy it before shutdown. // Manually manage this singleton's lifetime and destroy it before shutdown.
// This avoids the leakchecker detecting false-positive memory leaks when // This avoids the leakchecker detecting false-positive memory leaks when

View file

@ -122,7 +122,7 @@ class TimelineConsumers : public nsIObserver {
LinkedList<MarkersStorage> mMarkersStores; LinkedList<MarkersStorage> mMarkersStores;
// Protects this class's data structures. // Protects this class's data structures.
static StaticMutex sMutex; static StaticMutexNotRecorded sMutex;
}; };
} // namespace mozilla } // namespace mozilla

View file

@ -107,6 +107,11 @@ TabGroup* TabGroup::GetFromWindow(mozIDOMWindowProxy* aWindow) {
TabGroup* TabGroup::GetFromActor(BrowserChild* aBrowserChild) { TabGroup* TabGroup::GetFromActor(BrowserChild* aBrowserChild) {
MOZ_RELEASE_ASSERT(NS_IsMainThread()); MOZ_RELEASE_ASSERT(NS_IsMainThread());
// Middleman processes do not assign event targets to their tab children.
if (recordreplay::IsMiddleman()) {
return GetChromeTabGroup();
}
nsCOMPtr<nsIEventTarget> target = nsCOMPtr<nsIEventTarget> target =
aBrowserChild->Manager()->GetEventTargetFor(aBrowserChild); aBrowserChild->Manager()->GetEventTargetFor(aBrowserChild);
if (!target) { if (!target) {

View file

@ -249,6 +249,7 @@
#include "mozilla/HangAnnotations.h" #include "mozilla/HangAnnotations.h"
#include "mozilla/Encoding.h" #include "mozilla/Encoding.h"
#include "nsXULElement.h" #include "nsXULElement.h"
#include "mozilla/RecordReplay.h"
#include "nsThreadManager.h" #include "nsThreadManager.h"
#include "nsIBidiKeyboard.h" #include "nsIBidiKeyboard.h"
#include "ReferrerInfo.h" #include "ReferrerInfo.h"
@ -9904,6 +9905,12 @@ uint64_t nsContentUtils::GenerateProcessSpecificId(uint64_t aId) {
MOZ_RELEASE_ASSERT(id < (uint64_t(1) << kIdBits)); MOZ_RELEASE_ASSERT(id < (uint64_t(1) << kIdBits));
uint64_t bits = id & ((uint64_t(1) << kIdBits) - 1); uint64_t bits = id & ((uint64_t(1) << kIdBits) - 1);
// Set the high bit for middleman processes so it doesn't conflict with the
// content process's generated IDs.
if (recordreplay::IsMiddleman()) {
bits |= uint64_t(1) << (kIdBits - 1);
}
return (processBits << kIdBits) | bits; return (processBits << kIdBits) | bits;
} }

View file

@ -52,6 +52,7 @@
#include "mozilla/dom/ipc/StructuredCloneData.h" #include "mozilla/dom/ipc/StructuredCloneData.h"
#include "mozilla/dom/DOMStringList.h" #include "mozilla/dom/DOMStringList.h"
#include "mozilla/jsipc/CrossProcessObjectWrappers.h" #include "mozilla/jsipc/CrossProcessObjectWrappers.h"
#include "mozilla/recordreplay/ParentIPC.h"
#include "nsPrintfCString.h" #include "nsPrintfCString.h"
#include "nsXULAppAPI.h" #include "nsXULAppAPI.h"
#include "nsQueryObject.h" #include "nsQueryObject.h"
@ -593,11 +594,35 @@ class MMListenerRemover {
RefPtr<nsFrameMessageManager> mMM; RefPtr<nsFrameMessageManager> mMM;
}; };
// When recording or replaying, return whether a message should be received in
// the middleman process instead of the recording/replaying process.
static bool DirectMessageToMiddleman(const nsAString& aMessage) {
// Middleman processes run developer tools server code and need to receive
// debugger related messages. The session store flush message needs to be
// received in order to cleanly shutdown the process.
return (StringBeginsWith(aMessage, NS_LITERAL_STRING("debug:")) &&
recordreplay::parent::DebuggerRunsInMiddleman()) ||
aMessage.EqualsLiteral("SessionStore:flush");
}
void nsFrameMessageManager::ReceiveMessage( void nsFrameMessageManager::ReceiveMessage(
nsISupports* aTarget, nsFrameLoader* aTargetFrameLoader, bool aTargetClosed, nsISupports* aTarget, nsFrameLoader* aTargetFrameLoader, bool aTargetClosed,
const nsAString& aMessage, bool aIsSync, StructuredCloneData* aCloneData, const nsAString& aMessage, bool aIsSync, StructuredCloneData* aCloneData,
mozilla::jsipc::CpowHolder* aCpows, nsIPrincipal* aPrincipal, mozilla::jsipc::CpowHolder* aCpows, nsIPrincipal* aPrincipal,
nsTArray<StructuredCloneData>* aRetVal, ErrorResult& aError) { nsTArray<StructuredCloneData>* aRetVal, ErrorResult& aError) {
// If we are recording or replaying, we will end up here in both the
// middleman process and the recording/replaying process. Ignore the message
// in one of the processes, so that it is only received in one place.
if (recordreplay::IsRecordingOrReplaying()) {
if (DirectMessageToMiddleman(aMessage)) {
return;
}
} else if (recordreplay::IsMiddleman()) {
if (!DirectMessageToMiddleman(aMessage)) {
return;
}
}
MOZ_ASSERT(aTarget); MOZ_ASSERT(aTarget);
nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners = nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners =

View file

@ -530,7 +530,8 @@ class ScriptErrorEvent : public Runnable {
JS::Rooted<JSObject*> stackGlobal(rootingCx); JS::Rooted<JSObject*> stackGlobal(rootingCx);
xpc::FindExceptionStackForConsoleReport(win, mError, mErrorStack, &stack, xpc::FindExceptionStackForConsoleReport(win, mError, mErrorStack, &stack,
&stackGlobal); &stackGlobal);
mReport->LogToConsoleWithStack(stack, stackGlobal); mReport->LogToConsoleWithStack(stack, stackGlobal,
JS::ExceptionTimeWarpTarget(mError));
} }
return NS_OK; return NS_OK;

View file

@ -38,6 +38,11 @@ void nsWrapperCache::SetWrapperJSObject(JSObject* aWrapper) {
if (aWrapper && !JS::ObjectIsTenured(aWrapper)) { if (aWrapper && !JS::ObjectIsTenured(aWrapper)) {
CycleCollectedJSRuntime::Get()->NurseryWrapperAdded(this); CycleCollectedJSRuntime::Get()->NurseryWrapperAdded(this);
} }
// Never collect the wrapper object while recording or replaying, to avoid
// non-deterministic behaviors if the cache is emptied and then refilled at
// a different point when replaying.
recordreplay::HoldJSObject(aWrapper);
} }
void nsWrapperCache::ReleaseWrapper(void* aScriptObjectHolder) { void nsWrapperCache::ReleaseWrapper(void* aScriptObjectHolder) {

View file

@ -4052,6 +4052,13 @@ void DeprecationWarning(JSContext* aCx, JSObject* aObject,
void DeprecationWarning(const GlobalObject& aGlobal, void DeprecationWarning(const GlobalObject& aGlobal,
Document::DeprecatedOperations aOperation) { Document::DeprecatedOperations aOperation) {
if (NS_IsMainThread()) { if (NS_IsMainThread()) {
// After diverging from the recording, a replaying process is not able to
// report warnings and will be forced to rewind. Avoid reporting warnings
// in this case so that the debugger can access deprecated properties.
if (recordreplay::HasDivergedFromRecording()) {
return;
}
nsCOMPtr<nsPIDOMWindowInner> window = nsCOMPtr<nsPIDOMWindowInner> window =
do_QueryInterface(aGlobal.GetAsSupports()); do_QueryInterface(aGlobal.GetAsSupports());
if (window && window->GetExtantDoc()) { if (window && window->GetExtantDoc()) {

View file

@ -2653,6 +2653,11 @@ class MOZ_STACK_CLASS BindingJSObjectCreator {
void InitializationSucceeded() { void InitializationSucceeded() {
T* pointer; T* pointer;
mNative.forget(&pointer); mNative.forget(&pointer);
// Never collect binding objects while recording or replaying, to avoid
// non-deterministically releasing references during finalization.
recordreplay::HoldJSObject(mReflector);
mReflector = nullptr; mReflector = nullptr;
} }

View file

@ -114,6 +114,12 @@ interface nsIScriptError : nsIConsoleMessage
readonly attribute nsIArray notes; readonly attribute nsIArray notes;
/**
* If we are recording or replaying, this value may identify the point
* where the error was generated, otherwise zero.
*/
attribute unsigned long long timeWarpTarget;
/** /**
* If the ScriptError is a CSS parser error, this value will contain the * If the ScriptError is a CSS parser error, this value will contain the
* CSS selectors of the CSS ruleset where the error occured. * CSS selectors of the CSS ruleset where the error occured.

View file

@ -41,6 +41,7 @@ nsScriptErrorBase::nsScriptErrorBase()
mOuterWindowID(0), mOuterWindowID(0),
mInnerWindowID(0), mInnerWindowID(0),
mTimeStamp(0), mTimeStamp(0),
mTimeWarpTarget(0),
mInitializedOnMainThread(false), mInitializedOnMainThread(false),
mIsFromPrivateWindow(false), mIsFromPrivateWindow(false),
mIsFromChromeContext(false) {} mIsFromChromeContext(false) {}
@ -385,6 +386,18 @@ nsScriptErrorBase::GetIsFromPrivateWindow(bool* aIsFromPrivateWindow) {
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP
nsScriptErrorBase::SetTimeWarpTarget(uint64_t aTarget) {
mTimeWarpTarget = aTarget;
return NS_OK;
}
NS_IMETHODIMP
nsScriptErrorBase::GetTimeWarpTarget(uint64_t* aTarget) {
*aTarget = mTimeWarpTarget;
return NS_OK;
}
NS_IMETHODIMP NS_IMETHODIMP
nsScriptErrorBase::GetIsFromChromeContext(bool* aIsFromChromeContext) { nsScriptErrorBase::GetIsFromChromeContext(bool* aIsFromChromeContext) {
NS_WARNING_ASSERTION(NS_IsMainThread() || mInitializedOnMainThread, NS_WARNING_ASSERTION(NS_IsMainThread() || mInitializedOnMainThread,

View file

@ -83,6 +83,7 @@ class nsScriptErrorBase : public nsIScriptError {
uint64_t mOuterWindowID; uint64_t mOuterWindowID;
uint64_t mInnerWindowID; uint64_t mInnerWindowID;
int64_t mTimeStamp; int64_t mTimeStamp;
uint64_t mTimeWarpTarget;
// mInitializedOnMainThread, mIsFromPrivateWindow and mIsFromChromeContext are // mInitializedOnMainThread, mIsFromPrivateWindow and mIsFromChromeContext are
// set on the main thread from InitializeOnMainThread(). // set on the main thread from InitializeOnMainThread().
mozilla::Atomic<bool> mInitializedOnMainThread; mozilla::Atomic<bool> mInitializedOnMainThread;

View file

@ -321,6 +321,17 @@ bool WebGLContext::CreateAndInitGL(
return false; return false;
} }
// WebGL can't be used when recording/replaying.
if (recordreplay::IsRecordingOrReplaying()) {
FailureReason reason;
reason.info =
"Can't use WebGL when recording or replaying "
"(https://bugzil.la/1506467).";
out_failReasons->push_back(reason);
GenerateWarning("%s", reason.info.BeginReading());
return false;
}
// WebGL2 is separately blocked: // WebGL2 is separately blocked:
if (IsWebGL2() && !forceEnabled) { if (IsWebGL2() && !forceEnabled) {
const nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo(); const nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo();

View file

@ -1179,6 +1179,13 @@ HTMLMediaElement::MediaLoadListener::OnStartRequest(nsIRequest* aRequest) {
return NS_BINDING_ABORTED; return NS_BINDING_ABORTED;
} }
// Media element playback is not currently supported when recording or
// replaying. See bug 1304146.
if (recordreplay::IsRecordingOrReplaying()) {
mElement->ReportLoadError("Media elements not available when recording");
return NS_ERROR_NOT_AVAILABLE;
}
// The element is only needed until we've had a chance to call // The element is only needed until we've had a chance to call
// InitializeDecoderForChannel. So make sure mElement is cleared here. // InitializeDecoderForChannel. So make sure mElement is cleared here.
RefPtr<HTMLMediaElement> element; RefPtr<HTMLMediaElement> element;

View file

@ -9,7 +9,6 @@
#include <stdint.h> #include <stdint.h>
#include "mozilla/Decimal.h" #include "mozilla/Decimal.h"
#include "mozilla/Maybe.h"
#include "mozilla/UniquePtr.h" #include "mozilla/UniquePtr.h"
#include "nsIConstraintValidation.h" #include "nsIConstraintValidation.h"
#include "nsString.h" #include "nsString.h"

View file

@ -95,6 +95,12 @@ interface nsIRemoteTab : nsISupports
*/ */
void stopApzAutoscroll(in nsViewID aScrollId, in uint32_t aPresShellId); void stopApzAutoscroll(in nsViewID aScrollId, in uint32_t aPresShellId);
/**
* Save a recording of the associated content process' behavior to the
* specified filename. Returns whether the process is being recorded.
*/
bool saveRecording(in AString aFileName);
cenum NavigationType : 8 { cenum NavigationType : 8 {
NAVIGATE_BACK = 0, NAVIGATE_BACK = 0,
NAVIGATE_FORWARD = 1, NAVIGATE_FORWARD = 1,

View file

@ -86,6 +86,7 @@
#include "mozilla/layers/ShadowLayers.h" #include "mozilla/layers/ShadowLayers.h"
#include "mozilla/layers/WebRenderLayerManager.h" #include "mozilla/layers/WebRenderLayerManager.h"
#include "mozilla/plugins/PPluginWidgetChild.h" #include "mozilla/plugins/PPluginWidgetChild.h"
#include "mozilla/recordreplay/ParentIPC.h"
#include "nsBrowserStatusFilter.h" #include "nsBrowserStatusFilter.h"
#include "nsColorPickerProxy.h" #include "nsColorPickerProxy.h"
#include "nsCommandParams.h" #include "nsCommandParams.h"
@ -290,7 +291,11 @@ class BrowserChild::DelayedDeleteRunnable final : public Runnable,
} }
// Check in case ActorDestroy was called after RecvDestroy message. // Check in case ActorDestroy was called after RecvDestroy message.
if (mBrowserChild->IPCOpen()) { // Middleman processes with their own recording child process avoid
// sending a delete message, so that the parent process does not
// receive two deletes for the same actor.
if (mBrowserChild->IPCOpen() &&
!recordreplay::parent::IsMiddlemanWithRecordingChild()) {
Unused << PBrowserChild::Send__delete__(mBrowserChild); Unused << PBrowserChild::Send__delete__(mBrowserChild);
} }
@ -584,6 +589,11 @@ nsresult BrowserChild::Init(mozIDOMWindowProxy* aParent,
mIPCOpen = true; mIPCOpen = true;
// Recording/replaying processes use their own compositor.
if (recordreplay::IsRecordingOrReplaying()) {
mPuppetWidget->CreateCompositor();
}
#if !defined(MOZ_WIDGET_ANDROID) && !defined(MOZ_THUNDERBIRD) && \ #if !defined(MOZ_WIDGET_ANDROID) && !defined(MOZ_THUNDERBIRD) && \
!defined(MOZ_SUITE) !defined(MOZ_SUITE)
mSessionStoreListener = new TabListener(docShell, nullptr); mSessionStoreListener = new TabListener(docShell, nullptr);
@ -1175,6 +1185,13 @@ mozilla::ipc::IPCResult BrowserChild::RecvShow(
return IPC_FAIL_NO_REASON(this); return IPC_FAIL_NO_REASON(this);
} }
// We have now done enough initialization for the record/replay system to
// create checkpoints. Create a checkpoint now, in case this process never
// paints later on (the usual place where checkpoints occur).
if (recordreplay::IsRecordingOrReplaying()) {
recordreplay::child::CreateCheckpoint();
}
UpdateVisibility(); UpdateVisibility();
return IPC_OK(); return IPC_OK();
@ -1222,7 +1239,9 @@ mozilla::ipc::IPCResult BrowserChild::RecvCompositorOptionsChanged(
mozilla::ipc::IPCResult BrowserChild::RecvUpdateDimensions( mozilla::ipc::IPCResult BrowserChild::RecvUpdateDimensions(
const DimensionInfo& aDimensionInfo) { const DimensionInfo& aDimensionInfo) {
if (mLayersConnected.isNothing()) { // When recording/replaying we need to make sure the dimensions are up to
// date on the compositor used in this process.
if (mLayersConnected.isNothing() && !recordreplay::IsRecordingOrReplaying()) {
return IPC_OK(); return IPC_OK();
} }
@ -2217,6 +2236,20 @@ mozilla::ipc::IPCResult BrowserChild::RecvActivateFrameEvent(
return IPC_OK(); return IPC_OK();
} }
// Return whether a remote script should be loaded in middleman processes in
// addition to any child recording process they have.
static bool LoadScriptInMiddleman(const nsString& aURL) {
return // Middleman processes run devtools server side scripts.
(StringBeginsWith(aURL, NS_LITERAL_STRING("resource://devtools/")) &&
recordreplay::parent::DebuggerRunsInMiddleman())
// This script includes event listeners needed to propagate document
// title changes.
|| aURL.EqualsLiteral("chrome://global/content/browser-child.js")
// This script is needed to respond to session store requests from the
// UI process.
|| aURL.EqualsLiteral("chrome://browser/content/content-sessionStore.js");
}
mozilla::ipc::IPCResult BrowserChild::RecvLoadRemoteScript( mozilla::ipc::IPCResult BrowserChild::RecvLoadRemoteScript(
const nsString& aURL, const bool& aRunInGlobalScope) { const nsString& aURL, const bool& aRunInGlobalScope) {
if (!InitBrowserChildMessageManager()) if (!InitBrowserChildMessageManager())
@ -2231,6 +2264,11 @@ mozilla::ipc::IPCResult BrowserChild::RecvLoadRemoteScript(
return IPC_OK(); return IPC_OK();
} }
// Make sure we only load whitelisted scripts in middleman processes.
if (recordreplay::IsMiddleman() && !LoadScriptInMiddleman(aURL)) {
return IPC_OK();
}
LoadScriptInternal(mm, aURL, !aRunInGlobalScope); LoadScriptInternal(mm, aURL, !aRunInGlobalScope);
return IPC_OK(); return IPC_OK();
} }
@ -2806,7 +2844,10 @@ void BrowserChild::InitAPZState() {
void BrowserChild::NotifyPainted() { void BrowserChild::NotifyPainted() {
if (!mNotified) { if (!mNotified) {
SendNotifyCompositorTransaction(); // Recording/replaying processes have a compositor but not a remote frame.
if (!recordreplay::IsRecordingOrReplaying()) {
SendNotifyCompositorTransaction();
}
mNotified = true; mNotified = true;
} }
} }

View file

@ -292,6 +292,20 @@ BrowserHost::StopApzAutoscroll(nsViewID aScrollId, uint32_t aPresShellId) {
return NS_OK; return NS_OK;
} }
/* bool saveRecording (in AString aFileName); */
NS_IMETHODIMP
BrowserHost::SaveRecording(const nsAString& aFileName, bool* _retval) {
if (!mRoot) {
return NS_OK;
}
nsCOMPtr<nsIFile> file;
nsresult rv = NS_NewLocalFile(aFileName, false, getter_AddRefs(file));
if (NS_FAILED(rv)) {
return rv;
}
return GetContentParent()->SaveRecording(file, _retval);
}
NS_IMETHODIMP NS_IMETHODIMP
BrowserHost::MaybeCancelContentJSExecutionFromScript( BrowserHost::MaybeCancelContentJSExecutionFromScript(
nsIRemoteTab::NavigationType aNavigationType, nsIRemoteTab::NavigationType aNavigationType,

View file

@ -223,7 +223,8 @@ BrowserParent::BrowserParent(ContentParent* aManager, const TabId& aTabId,
mHasPresented(false), mHasPresented(false),
mIsReadyToHandleInputEvents(false), mIsReadyToHandleInputEvents(false),
mIsMouseEnterIntoWidgetEventSuppressed(false), mIsMouseEnterIntoWidgetEventSuppressed(false),
mIsDestroyingForProcessSwitch(false) { mIsDestroyingForProcessSwitch(false),
mIsActiveRecordReplayTab(false) {
MOZ_ASSERT(aManager); MOZ_ASSERT(aManager);
// When the input event queue is disabled, we don't need to handle the case // When the input event queue is disabled, we don't need to handle the case
// that some input events are dispatched before PBrowserConstructor. // that some input events are dispatched before PBrowserConstructor.
@ -645,6 +646,8 @@ void BrowserParent::DestroyInternal() {
->ParentDestroy(); ->ParentDestroy();
} }
#endif #endif
SetIsActiveRecordReplayTab(false);
} }
void BrowserParent::Destroy() { void BrowserParent::Destroy() {
@ -3340,6 +3343,11 @@ void BrowserParent::SetDocShellIsActive(bool isActive) {
} }
} }
#endif #endif
// Keep track of how many active recording/replaying tabs there are.
if (Manager()->IsRecordingOrReplaying()) {
SetIsActiveRecordReplayTab(isActive);
}
} }
bool BrowserParent::GetHasPresented() { return mHasPresented; } bool BrowserParent::GetHasPresented() { return mHasPresented; }
@ -3921,6 +3929,16 @@ void BrowserParent::SetBrowserHost(BrowserHost* aBrowser) {
mBrowserHost = aBrowser; mBrowserHost = aBrowser;
} }
/* static */
size_t BrowserParent::gNumActiveRecordReplayTabs;
void BrowserParent::SetIsActiveRecordReplayTab(bool aIsActive) {
if (aIsActive != mIsActiveRecordReplayTab) {
gNumActiveRecordReplayTabs += aIsActive ? 1 : -1;
mIsActiveRecordReplayTab = aIsActive;
}
}
mozilla::ipc::IPCResult BrowserParent::RecvSetSystemFont( mozilla::ipc::IPCResult BrowserParent::RecvSetSystemFont(
const nsCString& aFontName) { const nsCString& aFontName) {
nsCOMPtr<nsIWidget> widget = GetWidget(); nsCOMPtr<nsIWidget> widget = GetWidget();

View file

@ -133,6 +133,10 @@ class BrowserParent final : public PBrowserParent,
static TabId GetTabIdFrom(nsIDocShell* docshell); static TabId GetTabIdFrom(nsIDocShell* docshell);
static bool AreRecordReplayTabsActive() {
return gNumActiveRecordReplayTabs != 0;
}
const TabId GetTabId() const { return mTabId; } const TabId GetTabId() const { return mTabId; }
ContentParent* Manager() const { return mManager; } ContentParent* Manager() const { return mManager; }
@ -989,6 +993,15 @@ class BrowserParent final : public PBrowserParent,
// BrowserParent with a new one connected to a different process, and we // BrowserParent with a new one connected to a different process, and we
// should ignore nsIWebProgressListener stop requests. // should ignore nsIWebProgressListener stop requests.
bool mIsDestroyingForProcessSwitch : 1; bool mIsDestroyingForProcessSwitch : 1;
// How many record/replay tabs have active docshells in this process.
static size_t gNumActiveRecordReplayTabs;
// Whether this tab is contributing to gNumActiveRecordReplayTabs.
bool mIsActiveRecordReplayTab : 1;
// Update whether this is an active record/replay tab.
void SetIsActiveRecordReplayTab(bool aIsActive);
}; };
struct MOZ_STACK_CLASS BrowserParent::AutoUseNewTab final { struct MOZ_STACK_CLASS BrowserParent::AutoUseNewTab final {

View file

@ -94,6 +94,7 @@
#include "mozilla/PerformanceUtils.h" #include "mozilla/PerformanceUtils.h"
#include "mozilla/plugins/PluginInstanceParent.h" #include "mozilla/plugins/PluginInstanceParent.h"
#include "mozilla/plugins/PluginModuleParent.h" #include "mozilla/plugins/PluginModuleParent.h"
#include "mozilla/recordreplay/ParentIPC.h"
#include "mozilla/widget/ScreenManager.h" #include "mozilla/widget/ScreenManager.h"
#include "mozilla/widget/WidgetMessageUtils.h" #include "mozilla/widget/WidgetMessageUtils.h"
#include "nsBaseDragService.h" #include "nsBaseDragService.h"
@ -687,6 +688,17 @@ bool ContentChild::Init(MessageLoop* aIOLoop, base::ProcessId aParentPid,
return false; return false;
} }
// Middleman processes use a special channel for forwarding messages to
// their own children.
if (recordreplay::IsMiddleman()) {
SetMiddlemanIPCChannel(recordreplay::parent::ChannelToUIProcess());
// Eagerly mark this child as connected, as using another IPC channel will
// cause that channel's protocol to be marked as connected instead and
// prevent this one from being able to send IPDL messages.
ActorConnected();
}
if (!Open(std::move(aChannel), aParentPid, aIOLoop)) { if (!Open(std::move(aChannel), aParentPid, aIOLoop)) {
return false; return false;
} }
@ -1367,8 +1379,12 @@ void ContentChild::InitXPCOM(
RecvBidiKeyboardNotify(aXPCOMInit.isLangRTL(), RecvBidiKeyboardNotify(aXPCOMInit.isLangRTL(),
aXPCOMInit.haveBidiKeyboards()); aXPCOMInit.haveBidiKeyboards());
// Create the CPOW manager as soon as possible. // Create the CPOW manager as soon as possible. Middleman processes don't use
SendPJavaScriptConstructor(); // CPOWs, because their recording child will also have a CPOW manager that
// communicates with the UI process.
if (!recordreplay::IsMiddleman()) {
SendPJavaScriptConstructor();
}
if (aXPCOMInit.domainPolicy().active()) { if (aXPCOMInit.domainPolicy().active()) {
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
@ -1686,7 +1702,8 @@ static bool StartMacOSContentSandbox() {
} }
// If the sandbox is already enabled, there's nothing more to do here. // If the sandbox is already enabled, there's nothing more to do here.
if (Preferences::GetBool("security.sandbox.content.mac.earlyinit")) { if (Preferences::GetBool("security.sandbox.content.mac.earlyinit") &&
!recordreplay::IsRecordingOrReplaying()) {
return true; return true;
} }
@ -1857,7 +1874,11 @@ static void FirstIdle(void) {
MOZ_ASSERT(gFirstIdleTask); MOZ_ASSERT(gFirstIdleTask);
gFirstIdleTask = nullptr; gFirstIdleTask = nullptr;
ContentChild::GetSingleton()->SendFirstIdle(); // When recording or replaying, the middleman process will send this message
// instead.
if (!recordreplay::IsRecordingOrReplaying()) {
ContentChild::GetSingleton()->SendFirstIdle();
}
} }
mozilla::jsipc::PJavaScriptChild* ContentChild::AllocPJavaScriptChild() { mozilla::jsipc::PJavaScriptChild* ContentChild::AllocPJavaScriptChild() {
@ -2078,6 +2099,9 @@ jsipc::CPOWManager* ContentChild::GetCPOWManager() {
LoneManagedOrNullAsserts(ManagedPJavaScriptChild())) { LoneManagedOrNullAsserts(ManagedPJavaScriptChild())) {
return CPOWManagerFor(c); return CPOWManagerFor(c);
} }
if (recordreplay::IsMiddleman()) {
return nullptr;
}
return CPOWManagerFor(SendPJavaScriptConstructor()); return CPOWManagerFor(SendPJavaScriptConstructor());
} }
@ -3618,6 +3642,12 @@ mozilla::ipc::IPCResult ContentChild::RecvAddDynamicScalars(
return IPC_OK(); return IPC_OK();
} }
mozilla::ipc::IPCResult ContentChild::RecvSaveRecording(
const FileDescriptor& aFile) {
recordreplay::parent::SaveRecording(aFile);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentChild::RecvCrossProcessRedirect( mozilla::ipc::IPCResult ContentChild::RecvCrossProcessRedirect(
RedirectToRealChannelArgs&& aArgs, RedirectToRealChannelArgs&& aArgs,
CrossProcessRedirectResolver&& aResolve) { CrossProcessRedirectResolver&& aResolve) {

View file

@ -151,6 +151,7 @@
#include "mozilla/net/PCookieServiceParent.h" #include "mozilla/net/PCookieServiceParent.h"
#include "mozilla/plugins/PluginBridge.h" #include "mozilla/plugins/PluginBridge.h"
#include "mozilla/psm/PSMContentListener.h" #include "mozilla/psm/PSMContentListener.h"
#include "mozilla/recordreplay/ParentIPC.h"
#include "mozilla/widget/ScreenManager.h" #include "mozilla/widget/ScreenManager.h"
#include "nsAnonymousTemporaryFile.h" #include "nsAnonymousTemporaryFile.h"
#include "nsAppRunner.h" #include "nsAppRunner.h"
@ -643,7 +644,9 @@ bool ContentParent::sEarlySandboxInit = false;
/*static*/ RefPtr<ContentParent::LaunchPromise> /*static*/ RefPtr<ContentParent::LaunchPromise>
ContentParent::PreallocateProcess() { ContentParent::PreallocateProcess() {
RefPtr<ContentParent> process = new ContentParent( RefPtr<ContentParent> process = new ContentParent(
/* aOpener = */ nullptr, NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE)); /* aOpener = */ nullptr, NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE),
eNotRecordingOrReplaying,
/* aRecordingFile = */ EmptyString());
return process->LaunchSubprocessAsync(PROCESS_PRIORITY_PREALLOC); return process->LaunchSubprocessAsync(PROCESS_PRIORITY_PREALLOC);
} }
@ -824,6 +827,44 @@ already_AddRefed<ContentParent> ContentParent::MinTabSelect(
return candidate.forget(); return candidate.forget();
} }
static bool CreateTemporaryRecordingFile(nsAString& aResult) {
static int sNumTemporaryRecordings;
nsCOMPtr<nsIFile> file;
return !NS_FAILED(
NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(file))) &&
!NS_FAILED(file->AppendNative(
nsPrintfCString("TempRecording.%d.%d", base::GetCurrentProcId(),
++sNumTemporaryRecordings))) &&
!NS_FAILED(file->GetPath(aResult));
}
/*static*/
Maybe<ContentParent::RecordReplayState> ContentParent::GetRecordReplayState(
Element* aFrameElement, nsAString& aRecordingFile) {
if (!aFrameElement) {
return Some(eNotRecordingOrReplaying);
}
aFrameElement->GetAttr(kNameSpaceID_None, nsGkAtoms::ReplayExecution,
aRecordingFile);
if (!aRecordingFile.IsEmpty()) {
return Some(eReplaying);
}
aFrameElement->GetAttr(kNameSpaceID_None, nsGkAtoms::RecordExecution,
aRecordingFile);
if (aRecordingFile.IsEmpty() &&
recordreplay::parent::SaveAllRecordingsDirectory()) {
aRecordingFile.AssignLiteral("*");
}
if (!aRecordingFile.IsEmpty()) {
if (aRecordingFile.EqualsLiteral("*") &&
!CreateTemporaryRecordingFile(aRecordingFile)) {
return Nothing();
}
return Some(eRecording);
}
return Some(eNotRecordingOrReplaying);
}
/*static*/ /*static*/
already_AddRefed<ContentParent> ContentParent::GetUsedBrowserProcess( already_AddRefed<ContentParent> ContentParent::GetUsedBrowserProcess(
ContentParent* aOpener, const nsAString& aRemoteType, ContentParent* aOpener, const nsAString& aRemoteType,
@ -1494,6 +1535,21 @@ void ContentParent::ShutDownProcess(ShutDownMethod aMethod) {
// other methods. We first call Shutdown() in the child. After the child is // other methods. We first call Shutdown() in the child. After the child is
// ready, it calls FinishShutdown() on us. Then we close the channel. // ready, it calls FinishShutdown() on us. Then we close the channel.
if (aMethod == SEND_SHUTDOWN_MESSAGE) { if (aMethod == SEND_SHUTDOWN_MESSAGE) {
if (const char* directory =
recordreplay::parent::SaveAllRecordingsDirectory()) {
// Save a recording for the child process before it shuts down.
static int sNumSavedRecordings;
nsCOMPtr<nsIFile> file;
if (!NS_FAILED(NS_NewNativeLocalFile(nsDependentCString(directory), false,
getter_AddRefs(file))) &&
!NS_FAILED(file->AppendNative(
nsPrintfCString("Recording.%d.%d", base::GetCurrentProcId(),
++sNumSavedRecordings)))) {
bool unused;
SaveRecording(file, &unused);
}
}
if (mIPCOpen && !mShutdownPending) { if (mIPCOpen && !mShutdownPending) {
// Stop sending input events with input priority when shutting down. // Stop sending input events with input priority when shutting down.
SetInputPriorityEventEnabled(false); SetInputPriorityEventEnabled(false);
@ -1754,6 +1810,14 @@ void ContentParent::ActorDestroy(ActorDestroyReason why) {
[subprocess = mSubprocess] { subprocess->Destroy(); })); [subprocess = mSubprocess] { subprocess->Destroy(); }));
mSubprocess = nullptr; mSubprocess = nullptr;
// Delete any remaining replaying children.
for (auto& replayingProcess : mReplayingChildren) {
if (replayingProcess) {
replayingProcess->Destroy();
replayingProcess = nullptr;
}
}
ContentProcessManager* cpm = ContentProcessManager::GetSingleton(); ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
cpm->RemoveContentProcess(this->ChildID()); cpm->RemoveContentProcess(this->ChildID());
@ -1823,6 +1887,12 @@ bool ContentParent::ShouldKeepProcessAlive() {
return false; return false;
} }
// Recording/replaying content parents cannot be reused and should not be
// kept alive.
if (this->IsRecordingOrReplaying()) {
return false;
}
auto contentParents = sBrowserContentParents->Get(mRemoteType); auto contentParents = sBrowserContentParents->Get(mRemoteType);
if (!contentParents) { if (!contentParents) {
return false; return false;
@ -1920,6 +1990,84 @@ void ContentParent::NotifyTabDestroyed(const TabId& aTabId,
} }
} }
mozilla::ipc::IPCResult ContentParent::RecvOpenRecordReplayChannel(
const uint32_t& aChannelId, FileDescriptor* aConnection) {
// We should only get this message from the child if it is recording or
// replaying.
if (!this->IsRecordingOrReplaying()) {
return IPC_FAIL_NO_REASON(this);
}
recordreplay::parent::OpenChannel(Pid(), aChannelId, aConnection);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvCreateReplayingProcess(
const uint32_t& aChannelId) {
// We should only get this message from the child if it is recording or
// replaying.
if (!this->IsRecordingOrReplaying()) {
return IPC_FAIL_NO_REASON(this);
}
if (recordreplay::parent::UseCloudForReplayingProcesses()) {
recordreplay::parent::CreateReplayingCloudProcess(Pid(), aChannelId);
return IPC_OK();
}
while (aChannelId >= mReplayingChildren.length()) {
if (!mReplayingChildren.append(nullptr)) {
return IPC_FAIL_NO_REASON(this);
}
}
if (mReplayingChildren[aChannelId]) {
return IPC_FAIL_NO_REASON(this);
}
std::vector<std::string> extraArgs;
recordreplay::parent::GetArgumentsForChildProcess(
Pid(), aChannelId, NS_ConvertUTF16toUTF8(mRecordingFile).get(),
/* aRecording = */ false, extraArgs);
GeckoChildProcessHost* child =
new GeckoChildProcessHost(GeckoProcessType_Content);
mReplayingChildren[aChannelId] = child;
if (!child->LaunchAndWaitForProcessHandle(extraArgs)) {
return IPC_FAIL_NO_REASON(this);
}
// Replaying processes can fork themselves, and we can get crashes for
// them that correspond with one of those forked processes. When the crash
// reporter tries to read exception time annotations for one of these crashes,
// it hangs because the original replaying process hasn't actually crashed.
// Workaround this by removing the file descriptor for exception time
// annotations in replaying processes, so that the crash reporter will not
// attempt to read them.
ProcessId pid = base::GetProcId(child->GetChildProcessHandle());
CrashReporter::DeregisterChildCrashAnnotationFileDescriptor(pid);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvGenerateReplayCrashReport(
const uint32_t& aChannelId) {
if (aChannelId >= mReplayingChildren.length()) {
return IPC_FAIL(this, "invalid channel ID");
}
GeckoChildProcessHost* child = mReplayingChildren[aChannelId];
if (!child) {
return IPC_FAIL(this, "invalid channel ID");
}
if (mCrashReporter) {
ProcessId pid = base::GetProcId(child->GetChildProcessHandle());
mCrashReporter->GenerateCrashReport(pid);
}
return IPC_OK();
}
jsipc::CPOWManager* ContentParent::GetCPOWManager() { jsipc::CPOWManager* ContentParent::GetCPOWManager() {
if (PJavaScriptParent* p = if (PJavaScriptParent* p =
LoneManagedOrNullAsserts(ManagedPJavaScriptParent())) { LoneManagedOrNullAsserts(ManagedPJavaScriptParent())) {
@ -2107,8 +2255,10 @@ bool ContentParent::BeginSubprocessLaunch(bool aIsSync,
} }
#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) #if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
// If we're launching a middleman process for a
// recording or replay, start the sandbox later.
bool sandboxEnabled = IsContentSandboxEnabled(); bool sandboxEnabled = IsContentSandboxEnabled();
if (sandboxEnabled && sEarlySandboxInit) { if (sandboxEnabled && sEarlySandboxInit && !IsRecordingOrReplaying()) {
AppendSandboxParams(extraArgs); AppendSandboxParams(extraArgs);
} }
if (sandboxEnabled) { if (sandboxEnabled) {
@ -2241,7 +2391,10 @@ RefPtr<ContentParent::LaunchPromise> ContentParent::LaunchSubprocessAsync(
} }
ContentParent::ContentParent(ContentParent* aOpener, ContentParent::ContentParent(ContentParent* aOpener,
const nsAString& aRemoteType, int32_t aJSPluginID) const nsAString& aRemoteType,
RecordReplayState aRecordReplayState,
const nsAString& aRecordingFile,
int32_t aJSPluginID)
: mSelfRef(nullptr), : mSelfRef(nullptr),
mSubprocess(nullptr), mSubprocess(nullptr),
mLaunchTS(TimeStamp::Now()), mLaunchTS(TimeStamp::Now()),
@ -2256,6 +2409,8 @@ ContentParent::ContentParent(ContentParent* aOpener,
mNumDestroyingTabs(0), mNumDestroyingTabs(0),
mLifecycleState(LifecycleState::LAUNCHING), mLifecycleState(LifecycleState::LAUNCHING),
mIsForBrowser(!mRemoteType.IsEmpty()), mIsForBrowser(!mRemoteType.IsEmpty()),
mRecordReplayState(aRecordReplayState),
mRecordingFile(aRecordingFile),
mCalledClose(false), mCalledClose(false),
mCalledKillHard(false), mCalledKillHard(false),
mCreatedPairedMinidumps(false), mCreatedPairedMinidumps(false),
@ -5761,6 +5916,30 @@ void ContentParent::DeallocPSHistoryParent(PSHistoryParent* aActor) {
delete static_cast<SHistoryParent*>(aActor); delete static_cast<SHistoryParent*>(aActor);
} }
nsresult ContentParent::SaveRecording(nsIFile* aFile, bool* aRetval) {
if (mRecordReplayState != eRecording) {
*aRetval = false;
return NS_OK;
}
PRFileDesc* prfd;
nsresult rv = aFile->OpenNSPRFileDesc(
PR_WRONLY | PR_TRUNCATE | PR_CREATE_FILE, 0644, &prfd);
if (NS_FAILED(rv)) {
return rv;
}
FileDescriptor::PlatformHandleType handle =
FileDescriptor::PlatformHandleType(PR_FileDesc2NativeHandle(prfd));
Unused << SendSaveRecording(FileDescriptor(handle));
PR_Close(prfd);
*aRetval = true;
return NS_OK;
}
mozilla::ipc::IPCResult ContentParent::RecvMaybeReloadPlugins() { mozilla::ipc::IPCResult ContentParent::RecvMaybeReloadPlugins() {
RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst(); RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
pluginHost->ReloadPlugins(); pluginHost->ReloadPlugins();

View file

@ -314,6 +314,13 @@ class ContentParent final
// Let managees query if it is safe to send messages. // Let managees query if it is safe to send messages.
bool IsDestroyed() const { return !mIPCOpen; } bool IsDestroyed() const { return !mIPCOpen; }
mozilla::ipc::IPCResult RecvOpenRecordReplayChannel(
const uint32_t& channelId, FileDescriptor* connection);
mozilla::ipc::IPCResult RecvCreateReplayingProcess(
const uint32_t& aChannelId);
mozilla::ipc::IPCResult RecvGenerateReplayCrashReport(
const uint32_t& aChannelId);
mozilla::ipc::IPCResult RecvCreateGMPService(); mozilla::ipc::IPCResult RecvCreateGMPService();
mozilla::ipc::IPCResult RecvLoadPlugin( mozilla::ipc::IPCResult RecvLoadPlugin(
@ -738,13 +745,20 @@ class ContentParent final
nsIReferrerInfo* aReferrerInfo, bool aLoadUri, nsIReferrerInfo* aReferrerInfo, bool aLoadUri,
nsIContentSecurityPolicy* aCsp); nsIContentSecurityPolicy* aCsp);
enum RecordReplayState { eNotRecordingOrReplaying, eRecording, eReplaying };
explicit ContentParent(int32_t aPluginID) explicit ContentParent(int32_t aPluginID)
: ContentParent(nullptr, EmptyString(), aPluginID) {} : ContentParent(nullptr, EmptyString(), eNotRecordingOrReplaying,
ContentParent(ContentParent* aOpener, const nsAString& aRemoteType) EmptyString(), aPluginID) {}
: ContentParent(aOpener, aRemoteType, nsFakePluginTag::NOT_JSPLUGIN) {} ContentParent(ContentParent* aOpener, const nsAString& aRemoteType,
RecordReplayState aRecordReplayState = eNotRecordingOrReplaying,
const nsAString& aRecordingFile = EmptyString())
: ContentParent(aOpener, aRemoteType, aRecordReplayState, aRecordingFile,
nsFakePluginTag::NOT_JSPLUGIN) {}
ContentParent(ContentParent* aOpener, const nsAString& aRemoteType, ContentParent(ContentParent* aOpener, const nsAString& aRemoteType,
int32_t aPluginID); RecordReplayState aRecordReplayState,
const nsAString& aRecordingFile, int32_t aPluginID);
// Launch the subprocess and associated initialization. // Launch the subprocess and associated initialization.
// Returns false if the process fails to start. // Returns false if the process fails to start.
@ -1287,6 +1301,12 @@ class ContentParent final
const bool& aMinimizeMemoryUsage, const bool& aMinimizeMemoryUsage,
const Maybe<FileDescriptor>& aDMDFile) override; const Maybe<FileDescriptor>& aDMDFile) override;
nsresult SaveRecording(nsIFile* aFile, bool* aRetval);
bool IsRecordingOrReplaying() const {
return mRecordReplayState != eNotRecordingOrReplaying;
}
void OnBrowsingContextGroupSubscribe(BrowsingContextGroup* aGroup); void OnBrowsingContextGroupSubscribe(BrowsingContextGroup* aGroup);
void OnBrowsingContextGroupUnsubscribe(BrowsingContextGroup* aGroup); void OnBrowsingContextGroupUnsubscribe(BrowsingContextGroup* aGroup);
@ -1300,6 +1320,12 @@ class ContentParent final
static bool ShouldSyncPreference(const char16_t* aData); static bool ShouldSyncPreference(const char16_t* aData);
private: private:
// Determine the recording/replaying state for this frame.
// Return `Nothing` in case of error, typically if we need
// to create a temporary recording file but could not.
static Maybe<RecordReplayState> GetRecordReplayState(
Element* aFrameElement, nsAString& aRecordingFile);
// Return an existing ContentParent if possible. Otherwise, `nullptr`. // Return an existing ContentParent if possible. Otherwise, `nullptr`.
static already_AddRefed<ContentParent> GetUsedBrowserProcess( static already_AddRefed<ContentParent> GetUsedBrowserProcess(
ContentParent* aOpener, const nsAString& aRemoteType, ContentParent* aOpener, const nsAString& aRemoteType,
@ -1376,6 +1402,16 @@ class ContentParent final
bool mIsForBrowser; bool mIsForBrowser;
// Whether this process is recording or replaying its execution, and any
// associated recording file.
RecordReplayState mRecordReplayState;
nsString mRecordingFile;
// When recording or replaying, the child process is a middleman. This vector
// stores any replaying children we have spawned on behalf of that middleman,
// indexed by their record/replay channel ID.
Vector<mozilla::ipc::GeckoChildProcessHost*> mReplayingChildren;
// These variables track whether we've called Close() and KillHard() on our // These variables track whether we've called Close() and KillHard() on our
// channel. // channel.
bool mCalledClose; bool mCalledClose;

View file

@ -9,6 +9,7 @@
#include "ContentProcess.h" #include "ContentProcess.h"
#include "base/shared_memory.h" #include "base/shared_memory.h"
#include "mozilla/Preferences.h" #include "mozilla/Preferences.h"
#include "mozilla/recordreplay/ParentIPC.h"
#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) #if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
# include <stdlib.h> # include <stdlib.h>
@ -175,6 +176,12 @@ bool ContentProcess::Init(int aArgc, char* aArgv[]) {
return false; return false;
} }
if (recordreplay::IsMiddleman()) {
recordreplay::parent::InitializeMiddleman(aArgc, aArgv, ParentPid(),
deserializer.GetPrefsHandle(),
deserializer.GetPrefMapHandle());
}
mContent.Init(IOThreadChild::message_loop(), ParentPid(), *parentBuildID, mContent.Init(IOThreadChild::message_loop(), ParentPid(), *parentBuildID,
IOThreadChild::TakeChannel(), *childID, *isForBrowser); IOThreadChild::TakeChannel(), *childID, *isForBrowser);
@ -182,8 +189,11 @@ bool ContentProcess::Init(int aArgc, char* aArgv[]) {
#if (defined(XP_MACOSX)) && defined(MOZ_SANDBOX) #if (defined(XP_MACOSX)) && defined(MOZ_SANDBOX)
mContent.SetProfileDir(profileDir); mContent.SetProfileDir(profileDir);
# if defined(DEBUG) # if defined(DEBUG)
// For WebReplay middleman processes, the sandbox is
// started after receiving the SetProcessSandbox message.
if (IsContentSandboxEnabled() && if (IsContentSandboxEnabled() &&
Preferences::GetBool("security.sandbox.content.mac.earlyinit")) { Preferences::GetBool("security.sandbox.content.mac.earlyinit") &&
!recordreplay::IsMiddleman()) {
AssertMacSandboxEnabled(); AssertMacSandboxEnabled();
} }
# endif /* DEBUG */ # endif /* DEBUG */

View file

@ -831,6 +831,9 @@ child:
*/ */
async PClientOpenWindowOp(ClientOpenWindowArgs aArgs); async PClientOpenWindowOp(ClientOpenWindowArgs aArgs);
/* Save the execution up to the current point in a recording process. */
async SaveRecording(FileDescriptor file);
// This message is sent to content processes, and triggers the creation of a // This message is sent to content processes, and triggers the creation of a
// new HttpChannelChild that will be connected to the parent channel // new HttpChannelChild that will be connected to the parent channel
// represented by registrarId. // represented by registrarId.
@ -877,6 +880,11 @@ child:
parent: parent:
async InitBackground(Endpoint<PBackgroundParent> aEndpoint); async InitBackground(Endpoint<PBackgroundParent> aEndpoint);
sync OpenRecordReplayChannel(uint32_t channelId)
returns (FileDescriptor connection);
async CreateReplayingProcess(uint32_t channelId);
async GenerateReplayCrashReport(uint32_t channelId);
async CreateGMPService(); async CreateGMPService();
async InitStreamFilter(uint64_t channelId, nsString addonId) async InitStreamFilter(uint64_t channelId, nsString addonId)

View file

@ -140,7 +140,11 @@ class HangMonitorChild : public PProcessHangMonitorChild,
private: private:
void ShutdownOnThread(); void ShutdownOnThread();
static Atomic<HangMonitorChild*, SequentiallyConsistent> sInstance; // Ordering of this atomic is not preserved while recording/replaying, as it
// may be accessed during the JS interrupt callback.
static Atomic<HangMonitorChild*, SequentiallyConsistent,
recordreplay::Behavior::DontPreserve>
sInstance;
const RefPtr<ProcessHangMonitor> mHangMonitor; const RefPtr<ProcessHangMonitor> mHangMonitor;
Monitor mMonitor; Monitor mMonitor;
@ -173,7 +177,9 @@ class HangMonitorChild : public PProcessHangMonitorChild,
Atomic<bool> mPaintWhileInterruptingJSActive; Atomic<bool> mPaintWhileInterruptingJSActive;
}; };
Atomic<HangMonitorChild*, SequentiallyConsistent> HangMonitorChild::sInstance; Atomic<HangMonitorChild*, SequentiallyConsistent,
recordreplay::Behavior::DontPreserve>
HangMonitorChild::sInstance;
/* Parent process objects */ /* Parent process objects */
@ -304,7 +310,9 @@ class HangMonitorParent : public PProcessHangMonitorParent,
HangMonitorChild::HangMonitorChild(ProcessHangMonitor* aMonitor) HangMonitorChild::HangMonitorChild(ProcessHangMonitor* aMonitor)
: mHangMonitor(aMonitor), : mHangMonitor(aMonitor),
mMonitor("HangMonitorChild lock"), // Ordering of this atomic is not preserved while recording/replaying, as
// it may be accessed during the JS interrupt callback.
mMonitor("HangMonitorChild lock", recordreplay::Behavior::DontPreserve),
mSentReport(false), mSentReport(false),
mTerminateScript(false), mTerminateScript(false),
mTerminateGlobal(false), mTerminateGlobal(false),
@ -333,6 +341,13 @@ HangMonitorChild::~HangMonitorChild() {
bool HangMonitorChild::InterruptCallback() { bool HangMonitorChild::InterruptCallback() {
MOZ_RELEASE_ASSERT(NS_IsMainThread()); MOZ_RELEASE_ASSERT(NS_IsMainThread());
// The interrupt callback is triggered at non-deterministic points when
// recording/replaying, so don't perform any operations that can interact
// with the recording.
if (recordreplay::IsRecordingOrReplaying()) {
return true;
}
// Don't start painting if we're not in a good place to run script. We run // Don't start painting if we're not in a good place to run script. We run
// chrome script during layout and such, and it wouldn't be good to interrupt // chrome script during layout and such, and it wouldn't be good to interrupt
// painting code from there. // painting code from there.

View file

@ -433,6 +433,10 @@ cubeb* GetCubebContextUnlocked() {
PREF_CUBEB_FORCE_NULL_CONTEXT)); PREF_CUBEB_FORCE_NULL_CONTEXT));
return nullptr; return nullptr;
} }
if (recordreplay::IsRecordingOrReplaying()) {
// Media is not supported when recording or replaying. See bug 1304146.
return nullptr;
}
if (sCubebState != CubebState::Uninitialized) { if (sCubebState != CubebState::Uninitialized) {
// If we have already passed the initialization point (below), just return // If we have already passed the initialization point (below), just return
// the current context, which may be null (e.g., after error or shutdown.) // the current context, which may be null (e.g., after error or shutdown.)
@ -605,7 +609,7 @@ void InitLibrary() {
NS_NewRunnableFunction("CubebUtils::InitLibrary", &InitBrandName)); NS_NewRunnableFunction("CubebUtils::InitLibrary", &InitBrandName));
#endif #endif
#ifdef MOZ_CUBEB_REMOTING #ifdef MOZ_CUBEB_REMOTING
if (sCubebSandbox && XRE_IsContentProcess()) { if (sCubebSandbox && XRE_IsContentProcess() && !recordreplay::IsMiddleman()) {
InitAudioIPCConnection(); InitAudioIPCConnection();
} }
#endif #endif

View file

@ -335,6 +335,16 @@ MediaResult MediaFormatReader::DecoderFactory::DoCreateDecoder(Data& aData) {
} }
} }
// Media playback is not supported when recording or replaying. See bug
// 1304146.
if (recordreplay::IsRecordingOrReplaying()) {
return MediaResult(
NS_ERROR_DOM_MEDIA_FATAL_ERR,
nsPrintfCString("error creating %s decoder: "
"media playback is disabled while recording/replaying",
TrackTypeToStr(aData.mTrack)));
}
// result may not be updated by PDMFactory::CreateDecoder, as such it must be // result may not be updated by PDMFactory::CreateDecoder, as such it must be
// initialized to a fatal error by default. // initialized to a fatal error by default.
MediaResult result = MediaResult result =

View file

@ -295,8 +295,8 @@ class SourceListener : public SupportsWeakPtr<SourceListener> {
SourceListenerPromise; SourceListenerPromise;
MOZ_DECLARE_WEAKREFERENCE_TYPENAME(SourceListener) MOZ_DECLARE_WEAKREFERENCE_TYPENAME(SourceListener)
NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_MAIN_THREAD_DESTRUCTION( NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_MAIN_THREAD_DESTRUCTION_AND_RECORDING(
SourceListener) SourceListener, recordreplay::Behavior::Preserve)
SourceListener(); SourceListener();

View file

@ -253,6 +253,13 @@ JSObject* AudioContext::WrapObject(JSContext* aCx,
already_AddRefed<AudioContext> AudioContext::Constructor( already_AddRefed<AudioContext> AudioContext::Constructor(
const GlobalObject& aGlobal, const AudioContextOptions& aOptions, const GlobalObject& aGlobal, const AudioContextOptions& aOptions,
ErrorResult& aRv) { ErrorResult& aRv) {
// Audio playback is not yet supported when recording or replaying. See bug
// 1304147.
if (recordreplay::IsRecordingOrReplaying()) {
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
return nullptr;
}
nsCOMPtr<nsPIDOMWindowInner> window = nsCOMPtr<nsPIDOMWindowInner> window =
do_QueryInterface(aGlobal.GetAsSupports()); do_QueryInterface(aGlobal.GetAsSupports());
if (!window) { if (!window) {
@ -295,6 +302,13 @@ already_AddRefed<AudioContext> AudioContext::Constructor(
already_AddRefed<AudioContext> AudioContext::Constructor( already_AddRefed<AudioContext> AudioContext::Constructor(
const GlobalObject& aGlobal, uint32_t aNumberOfChannels, uint32_t aLength, const GlobalObject& aGlobal, uint32_t aNumberOfChannels, uint32_t aLength,
float aSampleRate, ErrorResult& aRv) { float aSampleRate, ErrorResult& aRv) {
// Audio playback is not yet supported when recording or replaying. See bug
// 1304147.
if (recordreplay::IsRecordingOrReplaying()) {
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
return nullptr;
}
nsCOMPtr<nsPIDOMWindowInner> window = nsCOMPtr<nsPIDOMWindowInner> window =
do_QueryInterface(aGlobal.GetAsSupports()); do_QueryInterface(aGlobal.GetAsSupports());
if (!window) { if (!window) {

View file

@ -690,6 +690,12 @@ nsresult nsPluginHost::InstantiatePluginInstance(
return NS_ERROR_FAILURE; return NS_ERROR_FAILURE;
} }
// Plugins are not supported when recording or replaying executions.
// See bug 1483232.
if (recordreplay::IsRecordingOrReplaying()) {
return NS_ERROR_FAILURE;
}
RefPtr<nsPluginInstanceOwner> instanceOwner = new nsPluginInstanceOwner(); RefPtr<nsPluginInstanceOwner> instanceOwner = new nsPluginInstanceOwner();
if (!instanceOwner) { if (!instanceOwner) {
return NS_ERROR_OUT_OF_MEMORY; return NS_ERROR_OUT_OF_MEMORY;

View file

@ -542,6 +542,13 @@ bool InterruptCallback(JSContext* aCx) {
WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx); WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
MOZ_ASSERT(worker); MOZ_ASSERT(worker);
// As with the main thread, the interrupt callback is triggered
// non-deterministically when recording/replaying, so return early to avoid
// performing any recorded events.
if (recordreplay::IsRecordingOrReplaying()) {
return true;
}
// Now is a good time to turn on profiling if it's pending. // Now is a good time to turn on profiling if it's pending.
PROFILER_JS_INTERRUPT_CALLBACK(); PROFILER_JS_INTERRUPT_CALLBACK();

View file

@ -11,7 +11,6 @@
#include <ostream> #include <ostream>
#include <sstream> #include <sstream>
#include <cstring> #include <cstring>
#include <functional>
#include <vector> #include <vector>
#include "RecordingTypes.h" #include "RecordingTypes.h"

View file

@ -1341,6 +1341,13 @@ APZEventResult APZCTreeManager::ReceiveInputEvent(InputData& aEvent) {
APZThreadUtils::AssertOnControllerThread(); APZThreadUtils::AssertOnControllerThread();
APZEventResult result; APZEventResult result;
// Ignore input events when there are active tabs that are recording or
// replaying. APZ does not work with the special layers constructed by
// the middleman processes being communicated with here.
if (dom::BrowserParent::AreRecordReplayTabsActive()) {
return result;
}
// Use a RAII class for updating the focus sequence number of this event // Use a RAII class for updating the focus sequence number of this event
AutoFocusSequenceNumberSetter focusSetter(mFocusState, aEvent); AutoFocusSequenceNumberSetter focusSetter(mFocusState, aEvent);

View file

@ -472,7 +472,7 @@ CompositorBridgeChild* ClientLayerManager::GetRemoteRenderer() {
} }
CompositorBridgeChild* ClientLayerManager::GetCompositorBridgeChild() { CompositorBridgeChild* ClientLayerManager::GetCompositorBridgeChild() {
if (!XRE_IsParentProcess()) { if (!XRE_IsParentProcess() && !recordreplay::IsRecordingOrReplaying()) {
return CompositorBridgeChild::Get(); return CompositorBridgeChild::Get();
} }
return GetRemoteRenderer(); return GetRemoteRenderer();
@ -495,6 +495,8 @@ void ClientLayerManager::DidComposite(TransactionId aTransactionId,
const TimeStamp& aCompositeStart, const TimeStamp& aCompositeStart,
const TimeStamp& aCompositeEnd) { const TimeStamp& aCompositeEnd) {
if (!mWidget) { if (!mWidget) {
// When recording/replaying this manager may have already been destroyed.
MOZ_ASSERT(recordreplay::IsRecordingOrReplaying());
return; return;
} }

View file

@ -360,7 +360,7 @@ CompositorBridgeParent::CompositorBridgeParent(
void CompositorBridgeParent::InitSameProcess(widget::CompositorWidget* aWidget, void CompositorBridgeParent::InitSameProcess(widget::CompositorWidget* aWidget,
const LayersId& aLayerTreeId) { const LayersId& aLayerTreeId) {
MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(XRE_IsParentProcess() || recordreplay::IsRecordingOrReplaying());
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
mWidget = aWidget; mWidget = aWidget;
@ -1021,7 +1021,11 @@ void CompositorBridgeParent::CompositeToTarget(VsyncId aId, DrawTarget* aTarget,
bool requestNextFrame = bool requestNextFrame =
mCompositionManager->TransformShadowTree(time, mVsyncRate); mCompositionManager->TransformShadowTree(time, mVsyncRate);
if (requestNextFrame) { // Don't eagerly schedule new compositions here when recording or replaying.
// Recording/replaying processes schedule composites at the top of the main
// thread's event loop rather than via PVsync, which can cause the composites
// scheduled here to pile up without any drawing actually happening.
if (requestNextFrame && !recordreplay::IsRecordingOrReplaying()) {
ScheduleComposition(); ScheduleComposition();
#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
// If we have visible windowed plugins then we need to wait for content (and // If we have visible windowed plugins then we need to wait for content (and

View file

@ -146,7 +146,7 @@ CompositorManagerChild::CreateWidgetCompositorBridge(
already_AddRefed<CompositorBridgeChild> already_AddRefed<CompositorBridgeChild>
CompositorManagerChild::CreateSameProcessWidgetCompositorBridge( CompositorManagerChild::CreateSameProcessWidgetCompositorBridge(
LayerManager* aLayerManager, uint32_t aNamespace) { LayerManager* aLayerManager, uint32_t aNamespace) {
MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(XRE_IsParentProcess() || recordreplay::IsRecordingOrReplaying());
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
if (NS_WARN_IF(!sInstance || !sInstance->CanSend())) { if (NS_WARN_IF(!sInstance || !sInstance->CanSend())) {
return nullptr; return nullptr;

View file

@ -29,7 +29,7 @@ StaticAutoPtr<nsTArray<CompositorManagerParent*>>
/* static */ /* static */
already_AddRefed<CompositorManagerParent> already_AddRefed<CompositorManagerParent>
CompositorManagerParent::CreateSameProcess() { CompositorManagerParent::CreateSameProcess() {
MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(XRE_IsParentProcess() || recordreplay::IsRecordingOrReplaying());
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
StaticMutexAutoLock lock(sMutex); StaticMutexAutoLock lock(sMutex);
@ -77,7 +77,7 @@ already_AddRefed<CompositorBridgeParent>
CompositorManagerParent::CreateSameProcessWidgetCompositorBridge( CompositorManagerParent::CreateSameProcessWidgetCompositorBridge(
CSSToLayoutDeviceScale aScale, const CompositorOptions& aOptions, CSSToLayoutDeviceScale aScale, const CompositorOptions& aOptions,
bool aUseExternalSurfaceSize, const gfx::IntSize& aSurfaceSize) { bool aUseExternalSurfaceSize, const gfx::IntSize& aSurfaceSize) {
MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(XRE_IsParentProcess() || recordreplay::IsRecordingOrReplaying());
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
// When we are in a combined UI / GPU process, InProcessCompositorSession // When we are in a combined UI / GPU process, InProcessCompositorSession

View file

@ -80,7 +80,8 @@ CompositorVsyncScheduler::CompositorVsyncScheduler(
// but is only accessed after on the compositor thread. // but is only accessed after on the compositor thread.
mAsapScheduling = mAsapScheduling =
StaticPrefs::layers_offmainthreadcomposition_frame_rate() == 0 || StaticPrefs::layers_offmainthreadcomposition_frame_rate() == 0 ||
gfxPlatform::IsInLayoutAsapMode(); gfxPlatform::IsInLayoutAsapMode() ||
recordreplay::IsRecordingOrReplaying();
} }
CompositorVsyncScheduler::~CompositorVsyncScheduler() { CompositorVsyncScheduler::~CompositorVsyncScheduler() {

View file

@ -147,6 +147,12 @@ mozilla::ipc::IPCResult LayerTransactionParent::RecvPaintTime(
mozilla::ipc::IPCResult LayerTransactionParent::RecvUpdate( mozilla::ipc::IPCResult LayerTransactionParent::RecvUpdate(
const TransactionInfo& aInfo) { const TransactionInfo& aInfo) {
auto guard = MakeScopeExit([&] {
if (recordreplay::IsRecordingOrReplaying()) {
recordreplay::child::NotifyPaintComplete();
}
});
AUTO_PROFILER_TRACING_MARKER("Paint", "LayerTransaction", GRAPHICS); AUTO_PROFILER_TRACING_MARKER("Paint", "LayerTransaction", GRAPHICS);
AUTO_PROFILER_LABEL("LayerTransactionParent::RecvUpdate", GRAPHICS); AUTO_PROFILER_LABEL("LayerTransactionParent::RecvUpdate", GRAPHICS);
PerfStats::AutoMetricRecording<PerfStats::Metric::LayerTransactions> PerfStats::AutoMetricRecording<PerfStats::Metric::LayerTransactions>
@ -492,6 +498,11 @@ mozilla::ipc::IPCResult LayerTransactionParent::RecvUpdate(
(TimeStamp::Now() - updateStart).ToMilliseconds()); (TimeStamp::Now() - updateStart).ToMilliseconds());
} }
// Compose after every update when recording/replaying.
if (recordreplay::IsRecordingOrReplaying()) {
mCompositorBridge->ForceComposeToTarget(nullptr);
}
return IPC_OK(); return IPC_OK();
} }

View file

@ -712,6 +712,10 @@ bool ShadowLayerForwarder::EndTransaction(
mShadowManager->SendRecordPaintTimes(mPaintTiming); mShadowManager->SendRecordPaintTimes(mPaintTiming);
} }
if (recordreplay::IsRecordingOrReplaying()) {
recordreplay::child::NotifyPaintStart();
}
*aSent = true; *aSent = true;
mIsFirstPaint = false; mIsFirstPaint = false;
mFocusTarget = FocusTarget(); mFocusTarget = FocusTarget();

View file

@ -48,44 +48,49 @@ struct DeleteOnMainThreadTask : public Runnable {
} // namespace layers } // namespace layers
} // namespace mozilla } // namespace mozilla
#define NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_MAIN_THREAD_DESTRUCTION( \ #define NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_MAIN_THREAD_DESTRUCTION_AND_RECORDING( \
_class) \ _class, _recording) \
public: \ public: \
NS_METHOD_(MozExternalRefCountType) AddRef(void) { \ NS_METHOD_(MozExternalRefCountType) AddRef(void) { \
MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \ MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \
MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \ MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \
nsrefcnt count = ++mRefCnt; \ nsrefcnt count = ++mRefCnt; \
NS_LOG_ADDREF(this, count, #_class, sizeof(*this)); \ NS_LOG_ADDREF(this, count, #_class, sizeof(*this)); \
return (nsrefcnt)count; \ return (nsrefcnt)count; \
} \ } \
void DeleteToBeCalledOnMainThread() { \ void DeleteToBeCalledOnMainThread() { \
MOZ_ASSERT(NS_IsMainThread()); \ MOZ_ASSERT(NS_IsMainThread()); \
NS_LOG_RELEASE(this, 0, #_class); \ NS_LOG_RELEASE(this, 0, #_class); \
delete this; \ delete this; \
} \ } \
NS_METHOD_(MozExternalRefCountType) Release(void) { \ NS_METHOD_(MozExternalRefCountType) Release(void) { \
MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \
nsrefcnt count = --mRefCnt; \ nsrefcnt count = --mRefCnt; \
if (count == 0) { \ if (count == 0) { \
if (NS_IsMainThread()) { \ if (NS_IsMainThread()) { \
DeleteToBeCalledOnMainThread(); \ DeleteToBeCalledOnMainThread(); \
} else { \ } else { \
NS_DispatchToMainThread( \ NS_DispatchToMainThread( \
new mozilla::layers::DeleteOnMainThreadTask<_class>(this)); \ new mozilla::layers::DeleteOnMainThreadTask<_class>(this)); \
} \ } \
} else { \ } else { \
NS_LOG_RELEASE(this, count, #_class); \ NS_LOG_RELEASE(this, count, #_class); \
} \ } \
return count; \ return count; \
} \ } \
\ \
protected: \ protected: \
::mozilla::ThreadSafeAutoRefCnt mRefCnt; \ ::mozilla::ThreadSafeAutoRefCntWithRecording<_recording> mRefCnt; \
\ \
private: \ private: \
::mozilla::layers::HelperForMainThreadDestruction \ ::mozilla::layers::HelperForMainThreadDestruction \
mHelperForMainThreadDestruction; \ mHelperForMainThreadDestruction; \
\ \
public: public:
#define NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_MAIN_THREAD_DESTRUCTION( \
_class) \
NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_MAIN_THREAD_DESTRUCTION_AND_RECORDING( \
_class, recordreplay::Behavior::DontPreserve)
#endif #endif

View file

@ -891,7 +891,7 @@ void gfxPlatform::Init() {
gfxConfig::Init(); gfxConfig::Init();
if (XRE_IsParentProcess()) { if (XRE_IsParentProcess() || recordreplay::IsRecordingOrReplaying()) {
GPUProcessManager::Initialize(); GPUProcessManager::Initialize();
RDDProcessManager::Initialize(); RDDProcessManager::Initialize();
@ -1392,12 +1392,12 @@ void gfxPlatform::InitLayersIPC() {
sLayersIPCIsUp = true; sLayersIPCIsUp = true;
if (XRE_IsContentProcess()) { if (XRE_IsContentProcess()) {
if (gfxVars::UseOMTP()) { if (gfxVars::UseOMTP() && !recordreplay::IsRecordingOrReplaying()) {
layers::PaintThread::Start(); layers::PaintThread::Start();
} }
} }
if (XRE_IsParentProcess()) { if (XRE_IsParentProcess() || recordreplay::IsRecordingOrReplaying()) {
if (!gfxConfig::IsEnabled(Feature::GPU_PROCESS) && UseWebRender()) { if (!gfxConfig::IsEnabled(Feature::GPU_PROCESS) && UseWebRender()) {
wr::RenderThread::Start(); wr::RenderThread::Start();
image::ImageMemoryReporter::InitForWebRender(); image::ImageMemoryReporter::InitForWebRender();
@ -1422,7 +1422,7 @@ void gfxPlatform::ShutdownLayersIPC() {
layers::ImageBridgeChild::ShutDown(); layers::ImageBridgeChild::ShutDown();
} }
if (gfxVars::UseOMTP()) { if (gfxVars::UseOMTP() && !recordreplay::IsRecordingOrReplaying()) {
layers::PaintThread::Shutdown(); layers::PaintThread::Shutdown();
} }
} else if (XRE_IsParentProcess()) { } else if (XRE_IsParentProcess()) {
@ -2707,6 +2707,11 @@ void gfxPlatform::InitCompositorAccelerationPrefs() {
FeatureStatus::Blocked, "Acceleration blocked by headless mode", FeatureStatus::Blocked, "Acceleration blocked by headless mode",
NS_LITERAL_CSTRING("FEATURE_FAILURE_COMP_HEADLESSMODE")); NS_LITERAL_CSTRING("FEATURE_FAILURE_COMP_HEADLESSMODE"));
} }
if (recordreplay::IsRecordingOrReplaying()) {
feature.ForceDisable(
FeatureStatus::Blocked, "Acceleration blocked by recording/replaying",
NS_LITERAL_CSTRING("FEATURE_FAILURE_COMP_RECORDREPLAY"));
}
} }
/*static*/ /*static*/
@ -2854,6 +2859,12 @@ void gfxPlatform::InitWebRenderConfig() {
// crash reports. // crash reports.
ScopedGfxFeatureReporter reporter("WR", prefEnabled || envvarEnabled); ScopedGfxFeatureReporter reporter("WR", prefEnabled || envvarEnabled);
if (!XRE_IsParentProcess()) { if (!XRE_IsParentProcess()) {
// Force-disable WebRender in recording/replaying child processes, which
// have their own compositor.
if (recordreplay::IsRecordingOrReplaying()) {
gfxVars::SetUseWebRender(false);
}
// The parent process runs through all the real decision-making code // The parent process runs through all the real decision-making code
// later in this function. For other processes we still want to report // later in this function. For other processes we still want to report
// the state of the feature for crash reports. // the state of the feature for crash reports.
@ -3250,7 +3261,8 @@ bool gfxPlatform::IsInLayoutAsapMode() {
/* static */ /* static */
bool gfxPlatform::ForceSoftwareVsync() { bool gfxPlatform::ForceSoftwareVsync() {
return StaticPrefs::layout_frame_rate() > 0; return StaticPrefs::layout_frame_rate() > 0 ||
recordreplay::IsRecordingOrReplaying();
} }
/* static */ /* static */
@ -3267,7 +3279,7 @@ int gfxPlatform::GetDefaultFrameRate() { return 60; }
/* static */ /* static */
void gfxPlatform::ReInitFrameRate() { void gfxPlatform::ReInitFrameRate() {
if (XRE_IsParentProcess()) { if (XRE_IsParentProcess() || recordreplay::IsRecordingOrReplaying()) {
RefPtr<VsyncSource> oldSource = gPlatform->mVsyncSource; RefPtr<VsyncSource> oldSource = gPlatform->mVsyncSource;
// Start a new one: // Start a new one:

View file

@ -632,7 +632,8 @@ class gfxPlatform : public mozilla::layers::MemoryPressureListener {
*/ */
virtual mozilla::gfx::VsyncSource* GetHardwareVsync() { virtual mozilla::gfx::VsyncSource* GetHardwareVsync() {
MOZ_ASSERT(mVsyncSource != nullptr); MOZ_ASSERT(mVsyncSource != nullptr);
MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(XRE_IsParentProcess() ||
mozilla::recordreplay::IsRecordingOrReplaying());
return mVsyncSource; return mVsyncSource;
} }

View file

@ -91,7 +91,12 @@ bool LockScreenOrientation(const hal::ScreenOrientation& aOrientation) {
return allowed; return allowed;
} }
void UnlockScreenOrientation() { Hal()->SendUnlockScreenOrientation(); } void UnlockScreenOrientation() {
// Don't send this message from both the middleman and recording processes.
if (!recordreplay::IsMiddleman()) {
Hal()->SendUnlockScreenOrientation();
}
}
void EnableSensorNotifications(SensorType aSensor) { void EnableSensorNotifications(SensorType aSensor) {
Hal()->SendEnableSensorNotifications(aSensor); Hal()->SendEnableSensorNotifications(aSensor);

View file

@ -105,7 +105,7 @@ class IDecoderFrameRecycler {
class Decoder { class Decoder {
public: public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Decoder) NS_INLINE_DECL_THREADSAFE_REFCOUNTING_RECORDED(Decoder)
explicit Decoder(RasterImage* aImage); explicit Decoder(RasterImage* aImage);

View file

@ -67,7 +67,7 @@ class IDecodingTask : public IResumable {
*/ */
class MetadataDecodingTask final : public IDecodingTask { class MetadataDecodingTask final : public IDecodingTask {
public: public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MetadataDecodingTask, override) NS_INLINE_DECL_THREADSAFE_REFCOUNTING_RECORDED(MetadataDecodingTask, override)
explicit MetadataDecodingTask(NotNull<Decoder*> aDecoder); explicit MetadataDecodingTask(NotNull<Decoder*> aDecoder);
@ -97,7 +97,8 @@ class MetadataDecodingTask final : public IDecodingTask {
*/ */
class AnonymousDecodingTask final : public IDecodingTask { class AnonymousDecodingTask final : public IDecodingTask {
public: public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AnonymousDecodingTask, override) NS_INLINE_DECL_THREADSAFE_REFCOUNTING_RECORDED(AnonymousDecodingTask,
override)
explicit AnonymousDecodingTask(NotNull<Decoder*> aDecoder, bool aResumable); explicit AnonymousDecodingTask(NotNull<Decoder*> aDecoder, bool aResumable);

View file

@ -104,7 +104,8 @@ class CostEntry {
bool operator<(const CostEntry& aOther) const { bool operator<(const CostEntry& aOther) const {
return mCost < aOther.mCost || return mCost < aOther.mCost ||
(mCost == aOther.mCost && mSurface < aOther.mSurface); (mCost == aOther.mCost &&
recordreplay::RecordReplayValue(mSurface < aOther.mSurface));
} }
private: private:

View file

@ -16,8 +16,13 @@ FileDescriptorSet::FileDescriptorSet() : consumed_descriptor_highwater_(0) {}
FileDescriptorSet::~FileDescriptorSet() { FileDescriptorSet::~FileDescriptorSet() {
if (consumed_descriptor_highwater_ == descriptors_.size()) return; if (consumed_descriptor_highwater_ == descriptors_.size()) return;
CHROMIUM_LOG(WARNING) // Middleman processes copy FileDescriptorSets before forwarding them to
<< "FileDescriptorSet destroyed with unconsumed descriptors"; // recording children, and destroying sets without using their descriptors is
// expected.
if (!mozilla::recordreplay::IsMiddleman()) {
CHROMIUM_LOG(WARNING)
<< "FileDescriptorSet destroyed with unconsumed descriptors";
}
// We close all the descriptors where the close flag is set. If this // We close all the descriptors where the close flag is set. If this
// message should have been transmitted, then closing those with close // message should have been transmitted, then closing those with close

View file

@ -32,6 +32,11 @@ class CrashReporterClient {
// crash reporter needs metadata), the shmem should be parsed. // crash reporter needs metadata), the shmem should be parsed.
template <typename T> template <typename T>
static void InitSingleton(T* aToplevelProtocol) { static void InitSingleton(T* aToplevelProtocol) {
// The crash reporter is not enabled in recording/replaying processes.
if (recordreplay::IsRecordingOrReplaying()) {
return;
}
Shmem shmem; Shmem shmem;
if (!AllocShmem(aToplevelProtocol, &shmem)) { if (!AllocShmem(aToplevelProtocol, &shmem)) {
MOZ_DIAGNOSTIC_ASSERT(false, "failed to allocate crash reporter shmem"); MOZ_DIAGNOSTIC_ASSERT(false, "failed to allocate crash reporter shmem");

View file

@ -7,6 +7,7 @@
#include "CrashReporterHost.h" #include "CrashReporterHost.h"
#include "CrashReporterMetadataShmem.h" #include "CrashReporterMetadataShmem.h"
#include "mozilla/dom/Promise.h" #include "mozilla/dom/Promise.h"
#include "mozilla/recordreplay/ParentIPC.h"
#include "mozilla/Sprintf.h" #include "mozilla/Sprintf.h"
#include "mozilla/SyncRunnable.h" #include "mozilla/SyncRunnable.h"
#include "mozilla/Telemetry.h" #include "mozilla/Telemetry.h"
@ -100,6 +101,11 @@ bool CrashReporterHost::AdoptMinidump(nsIFile* aFile,
} }
int32_t CrashReporterHost::GetCrashType() { int32_t CrashReporterHost::GetCrashType() {
if (mExtraAnnotations[CrashReporter::Annotation::RecordReplayHang]
.EqualsLiteral("1")) {
return nsICrashService::CRASH_TYPE_HANG;
}
if (mExtraAnnotations[CrashReporter::Annotation::PluginHang].EqualsLiteral( if (mExtraAnnotations[CrashReporter::Annotation::PluginHang].EqualsLiteral(
"1")) { "1")) {
return nsICrashService::CRASH_TYPE_HANG; return nsICrashService::CRASH_TYPE_HANG;

View file

@ -42,6 +42,7 @@
#include "mozilla/Logging.h" #include "mozilla/Logging.h"
#include "mozilla/Maybe.h" #include "mozilla/Maybe.h"
#include "mozilla/Omnijar.h" #include "mozilla/Omnijar.h"
#include "mozilla/RecordReplay.h"
#include "mozilla/RDDProcessHost.h" #include "mozilla/RDDProcessHost.h"
#include "mozilla/Scoped.h" #include "mozilla/Scoped.h"
#include "mozilla/Services.h" #include "mozilla/Services.h"
@ -142,9 +143,14 @@ class BaseProcessLauncher {
SprintfLiteral(mPidString, "%d", base::GetCurrentProcId()); SprintfLiteral(mPidString, "%d", base::GetCurrentProcId());
// Compute the serial event target we'll use for launching. // Compute the serial event target we'll use for launching.
nsCOMPtr<nsIEventTarget> threadOrPool = GetIPCLauncher(); if (mozilla::recordreplay::IsMiddleman()) {
mLaunchThread = new TaskQueue(threadOrPool.forget()); // During Web Replay, the middleman process launches the actual content
// processes, and doesn't initialize enough of XPCOM to use thread pools.
mLaunchThread = IOThread();
} else {
nsCOMPtr<nsIEventTarget> threadOrPool = GetIPCLauncher();
mLaunchThread = new TaskQueue(threadOrPool.forget());
}
if (ShouldHaveDirectoryService()) { if (ShouldHaveDirectoryService()) {
// "Current process directory" means the app dir, not the current // "Current process directory" means the app dir, not the current
// working dir or similar. // working dir or similar.

View file

@ -561,6 +561,18 @@ static void TryRegisterStrongMemoryReporter() {
Atomic<size_t> MessageChannel::gUnresolvedResponses; Atomic<size_t> MessageChannel::gUnresolvedResponses;
// Channels in record/replay middleman processes can forward messages that
// originated in a child recording process. Middleman processes are given
// a large negative sequence number so that sequence numbers on their messages
// can be distinguished from those on recording process messages.
static const int32_t MiddlemanStartSeqno = -(1 << 30);
/* static */
bool MessageChannel::MessageOriginatesFromMiddleman(const Message& aMessage) {
MOZ_ASSERT(recordreplay::IsMiddleman());
return aMessage.seqno() < MiddlemanStartSeqno;
}
MessageChannel::MessageChannel(const char* aName, IToplevelProtocol* aListener) MessageChannel::MessageChannel(const char* aName, IToplevelProtocol* aListener)
: mName(aName), : mName(aName),
mListener(aListener), mListener(aListener),
@ -610,6 +622,10 @@ MessageChannel::MessageChannel(const char* aName, IToplevelProtocol* aListener)
TryRegisterStrongMemoryReporter<PendingResponseReporter>(); TryRegisterStrongMemoryReporter<PendingResponseReporter>();
TryRegisterStrongMemoryReporter<ChannelCountReporter>(); TryRegisterStrongMemoryReporter<ChannelCountReporter>();
if (recordreplay::IsMiddleman()) {
mNextSeqno = MiddlemanStartSeqno;
}
} }
MessageChannel::~MessageChannel() { MessageChannel::~MessageChannel() {
@ -2039,6 +2055,15 @@ void MessageChannel::MessageTask::Clear() {
NS_IMETHODIMP NS_IMETHODIMP
MessageChannel::MessageTask::GetPriority(uint32_t* aPriority) { MessageChannel::MessageTask::GetPriority(uint32_t* aPriority) {
if (recordreplay::IsRecordingOrReplaying()) {
// Ignore message priorities in recording/replaying processes. Incoming
// messages were sorted in the middleman process according to their
// priority before being forwarded here, and reordering them again in this
// process can cause problems such as dispatching messages for an actor
// before the constructor for that actor.
*aPriority = PRIORITY_NORMAL;
return NS_OK;
}
switch (mMessage.priority()) { switch (mMessage.priority()) {
case Message::NORMAL_PRIORITY: case Message::NORMAL_PRIORITY:
*aPriority = PRIORITY_NORMAL; *aPriority = PRIORITY_NORMAL;
@ -2140,8 +2165,10 @@ void MessageChannel::DispatchSyncMessage(ActorLifecycleProxy* aProxy,
int nestedLevel = aMsg.nested_level(); int nestedLevel = aMsg.nested_level();
MOZ_RELEASE_ASSERT(nestedLevel == IPC::Message::NOT_NESTED || MOZ_RELEASE_ASSERT(
NS_IsMainThread()); nestedLevel == IPC::Message::NOT_NESTED || NS_IsMainThread() ||
// Middleman processes forward sync messages on a non-main thread.
recordreplay::IsMiddleman());
#ifdef MOZ_TASK_TRACER #ifdef MOZ_TASK_TRACER
AutoScopedLabel autolabel("sync message %s", aMsg.name()); AutoScopedLabel autolabel("sync message %s", aMsg.name());
#endif #endif

View file

@ -322,6 +322,10 @@ class MessageChannel : HasResultCodes, MessageLoop::DestructionObserver {
*/ */
bool IsCrossProcess() const { return mIsCrossProcess; } bool IsCrossProcess() const { return mIsCrossProcess; }
// Return whether a message definitely originated from a middleman process,
// due to its sequence number.
static bool MessageOriginatesFromMiddleman(const Message& aMessage);
#ifdef OS_WIN #ifdef OS_WIN
struct MOZ_STACK_CLASS SyncStackFrame { struct MOZ_STACK_CLASS SyncStackFrame {
SyncStackFrame(MessageChannel* channel, bool interrupt); SyncStackFrame(MessageChannel* channel, bool interrupt);

View file

@ -17,6 +17,8 @@
#include "mozilla/ipc/MessageChannel.h" #include "mozilla/ipc/MessageChannel.h"
#include "mozilla/ipc/Transport.h" #include "mozilla/ipc/Transport.h"
#include "mozilla/recordreplay/ChildIPC.h"
#include "mozilla/recordreplay/ParentIPC.h"
#include "mozilla/StaticMutex.h" #include "mozilla/StaticMutex.h"
#include "mozilla/SystemGroup.h" #include "mozilla/SystemGroup.h"
#include "mozilla/Unused.h" #include "mozilla/Unused.h"
@ -575,6 +577,7 @@ IToplevelProtocol::IToplevelProtocol(const char* aName, ProtocolId aProtoId,
mOtherPid(mozilla::ipc::kInvalidProcessId), mOtherPid(mozilla::ipc::kInvalidProcessId),
mLastLocalId(0), mLastLocalId(0),
mEventTargetMutex("ProtocolEventTargetMutex"), mEventTargetMutex("ProtocolEventTargetMutex"),
mMiddlemanChannelOverride(nullptr),
mChannel(aName, this) { mChannel(aName, this) {
mToplevel = this; mToplevel = this;
} }
@ -586,7 +589,15 @@ base::ProcessId IToplevelProtocol::OtherPid() const {
} }
void IToplevelProtocol::SetOtherProcessId(base::ProcessId aOtherPid) { void IToplevelProtocol::SetOtherProcessId(base::ProcessId aOtherPid) {
mOtherPid = aOtherPid; // When recording an execution, all communication we do is forwarded from
// the middleman to the parent process, so use its pid instead of the
// middleman's pid.
if (recordreplay::IsRecordingOrReplaying() &&
aOtherPid == recordreplay::child::MiddlemanProcessId()) {
mOtherPid = recordreplay::child::ParentProcessId();
} else {
mOtherPid = aOtherPid;
}
} }
bool IToplevelProtocol::Open(UniquePtr<Transport> aTransport, bool IToplevelProtocol::Open(UniquePtr<Transport> aTransport,
@ -628,8 +639,12 @@ bool IToplevelProtocol::IsOnCxxStack() const {
int32_t IToplevelProtocol::NextId() { int32_t IToplevelProtocol::NextId() {
// Genreate the next ID to use for a shared memory or protocol. Parent and // Genreate the next ID to use for a shared memory or protocol. Parent and
// Child sides of the protocol use different pools. // Child sides of the protocol use different pools, and actors created in the
// middleman need to use a distinct pool as well.
int32_t tag = 0; int32_t tag = 0;
if (recordreplay::IsMiddleman()) {
tag |= 1 << 0;
}
if (GetSide() == ParentSide) { if (GetSide() == ParentSide) {
tag |= 1 << 1; tag |= 1 << 1;
} }

View file

@ -24,6 +24,7 @@
#include "mozilla/ipc/Shmem.h" #include "mozilla/ipc/Shmem.h"
#include "mozilla/ipc/Transport.h" #include "mozilla/ipc/Transport.h"
#include "mozilla/ipc/MessageLink.h" #include "mozilla/ipc/MessageLink.h"
#include "mozilla/recordreplay/ChildIPC.h"
#include "mozilla/LinkedList.h" #include "mozilla/LinkedList.h"
#include "mozilla/Maybe.h" #include "mozilla/Maybe.h"
#include "mozilla/MozPromise.h" #include "mozilla/MozPromise.h"
@ -405,8 +406,18 @@ class IToplevelProtocol : public IProtocol {
bool IsTrackingSharedMemory(Shmem::SharedMemory* aSegment); bool IsTrackingSharedMemory(Shmem::SharedMemory* aSegment);
bool DestroySharedMemory(Shmem& aShmem); bool DestroySharedMemory(Shmem& aShmem);
MessageChannel* GetIPCChannel() { return &mChannel; } MessageChannel* GetIPCChannel() {
const MessageChannel* GetIPCChannel() const { return &mChannel; } if (mMiddlemanChannelOverride) {
return mMiddlemanChannelOverride;
}
return &mChannel;
}
const MessageChannel* GetIPCChannel() const {
if (mMiddlemanChannelOverride) {
return mMiddlemanChannelOverride;
}
return &mChannel;
}
// NOTE: The target actor's Manager must already be set. // NOTE: The target actor's Manager must already be set.
void SetEventTargetForActorInternal(IProtocol* aActor, void SetEventTargetForActorInternal(IProtocol* aActor,
@ -509,6 +520,13 @@ class IToplevelProtocol : public IProtocol {
already_AddRefed<nsIEventTarget> GetMessageEventTarget(const Message& aMsg); already_AddRefed<nsIEventTarget> GetMessageEventTarget(const Message& aMsg);
void SetMiddlemanIPCChannel(MessageChannel* aChannel) {
// Middleman processes sometimes need to change the channel used by a
// protocol.
MOZ_RELEASE_ASSERT(recordreplay::IsMiddleman());
mMiddlemanChannelOverride = aChannel;
}
protected: protected:
// Override this method in top-level protocols to change the event target // Override this method in top-level protocols to change the event target
// for a new actor (and its sub-actors). // for a new actor (and its sub-actors).
@ -543,6 +561,12 @@ class IToplevelProtocol : public IProtocol {
Mutex mEventTargetMutex; Mutex mEventTargetMutex;
IDMap<nsCOMPtr<nsIEventTarget>> mEventTargetMap; IDMap<nsCOMPtr<nsIEventTarget>> mEventTargetMap;
// In the middleman process for recordreplay, we override the channel which
// should be used by an actor. Due to this, we need to hold a separate pointer
// here which can be used to specify that we shouldn't send messages to our
// mChannel actor member. FIXME: This should probably be removed.
MessageChannel* mMiddlemanChannelOverride;
MessageChannel mChannel; MessageChannel mChannel;
}; };
@ -722,7 +746,16 @@ class Endpoint {
// be used to send and receive messages. The endpoint becomes invalid. // be used to send and receive messages. The endpoint becomes invalid.
bool Bind(PFooSide* aActor) { bool Bind(PFooSide* aActor) {
MOZ_RELEASE_ASSERT(mValid); MOZ_RELEASE_ASSERT(mValid);
MOZ_RELEASE_ASSERT(mMyPid == base::GetCurrentProcId()); if (mMyPid != base::GetCurrentProcId()) {
// These pids must match, unless we are recording or replaying, in
// which case the parent process will have supplied the pid for the
// middleman process instead. Fix this here. If we're replaying
// we'll see the pid of the middleman used while recording.
MOZ_RELEASE_ASSERT(recordreplay::IsRecordingOrReplaying());
MOZ_RELEASE_ASSERT(recordreplay::IsReplaying() ||
mMyPid == recordreplay::child::MiddlemanProcessId());
mMyPid = base::GetCurrentProcId();
}
UniquePtr<Transport> transport = UniquePtr<Transport> transport =
mozilla::ipc::OpenDescriptor(mTransport, mMode); mozilla::ipc::OpenDescriptor(mTransport, mMode);

View file

@ -977,6 +977,8 @@ description = test only
description = test only description = test only
[PContent::SyncMessage] [PContent::SyncMessage]
description = JS MessageManager implementation description = JS MessageManager implementation
[PContent::OpenRecordReplayChannel]
description = bug 1475898 this could be async
[PContent::LoadPlugin] [PContent::LoadPlugin]
description = Legacy NPAPI IPC description = Legacy NPAPI IPC
[PContent::ConnectPluginBridge] [PContent::ConnectPluginBridge]

View file

@ -13,7 +13,8 @@
interface IJSDebugger : nsISupports interface IJSDebugger : nsISupports
{ {
/** /**
* Define the global Debugger constructor on a given global. * Define the global Debugger and RecordReplayControl constructors on a
* given global.
*/ */
[implicit_jscontext] [implicit_jscontext]
void addClass(in jsval global); void addClass(in jsval global);

View file

@ -50,6 +50,21 @@ JSDebugger::AddClass(JS::Handle<JS::Value> global, JSContext* cx) {
return NS_ERROR_FAILURE; return NS_ERROR_FAILURE;
} }
if (recordreplay::IsRecordingOrReplaying() || recordreplay::IsMiddleman()) {
if (!recordreplay::DefineRecordReplayControlObject(cx, obj)) {
return NS_ERROR_FAILURE;
}
} else {
// Define an empty RecordReplayControl object, to avoid reference errors in
// scripts that run in normal processes. DefineRecordReplayControlObject
// can't be called in normal processes.
JS::RootedObject staticObject(cx, JS_NewObject(cx, nullptr));
if (!staticObject ||
!JS_DefineProperty(cx, obj, "RecordReplayControl", staticObject, 0)) {
return NS_ERROR_FAILURE;
}
}
return NS_OK; return NS_OK;
} }

View file

@ -598,7 +598,13 @@ CrossProcessCpowHolder::~CrossProcessCpowHolder() {
// the corresponding part of the CPOW in the other process // the corresponding part of the CPOW in the other process
// will eventually be collected. The scope for this object // will eventually be collected. The scope for this object
// doesn't really matter, because it immediately becomes // doesn't really matter, because it immediately becomes
// garbage. // garbage. Ignore this for middleman processes used when
// recording or replaying, as they do not have a CPOW manager
// and the message will also be received in the recording
// process.
if (recordreplay::IsMiddleman()) {
return;
}
AutoJSAPI jsapi; AutoJSAPI jsapi;
if (!jsapi.Init(xpc::PrivilegedJunkScope())) { if (!jsapi.Init(xpc::PrivilegedJunkScope())) {
return; return;
@ -622,6 +628,9 @@ bool CrossProcessCpowHolder::ToObject(JSContext* cx,
bool JavaScriptShared::Unwrap(JSContext* cx, const nsTArray<CpowEntry>& aCpows, bool JavaScriptShared::Unwrap(JSContext* cx, const nsTArray<CpowEntry>& aCpows,
JS::MutableHandleObject objp) { JS::MutableHandleObject objp) {
// Middleman processes never operate on CPOWs.
MOZ_ASSERT(!recordreplay::IsMiddleman());
objp.set(nullptr); objp.set(nullptr);
if (!aCpows.Length()) { if (!aCpows.Length()) {

View file

@ -9,7 +9,6 @@
#include "mozilla/Range.h" #include "mozilla/Range.h"
#include "mozilla/Span.h" #include "mozilla/Span.h"
#include "mozilla/Utf8.h"
#include "js/TypeDecls.h" #include "js/TypeDecls.h"
#include "js/Utility.h" #include "js/Utility.h"

View file

@ -20,7 +20,9 @@ struct JSStructuredCloneWriter;
struct JSPrincipals { struct JSPrincipals {
/* Don't call "destroy"; use reference counting macros below. */ /* Don't call "destroy"; use reference counting macros below. */
mozilla::Atomic<int32_t, mozilla::SequentiallyConsistent> refcount; mozilla::Atomic<int32_t, mozilla::SequentiallyConsistent,
mozilla::recordreplay::Behavior::DontPreserve>
refcount;
#ifdef JS_DEBUG #ifdef JS_DEBUG
/* A helper to facilitate principals debugging. */ /* A helper to facilitate principals debugging. */

View file

@ -123,30 +123,42 @@ class ProfilingStackFrame {
// Descriptive label for this stack frame. Must be a static string! Can be // Descriptive label for this stack frame. Must be a static string! Can be
// an empty string, but not a null pointer. // an empty string, but not a null pointer.
mozilla::Atomic<const char*, mozilla::ReleaseAcquire> label_; mozilla::Atomic<const char*, mozilla::ReleaseAcquire,
mozilla::recordreplay::Behavior::DontPreserve>
label_;
// An additional descriptive string of this frame which is combined with // An additional descriptive string of this frame which is combined with
// |label_| in profiler output. Need not be (and usually isn't) static. Can // |label_| in profiler output. Need not be (and usually isn't) static. Can
// be null. // be null.
mozilla::Atomic<const char*, mozilla::ReleaseAcquire> dynamicString_; mozilla::Atomic<const char*, mozilla::ReleaseAcquire,
mozilla::recordreplay::Behavior::DontPreserve>
dynamicString_;
// Stack pointer for non-JS stack frames, the script pointer otherwise. // Stack pointer for non-JS stack frames, the script pointer otherwise.
mozilla::Atomic<void*, mozilla::ReleaseAcquire> spOrScript; mozilla::Atomic<void*, mozilla::ReleaseAcquire,
mozilla::recordreplay::Behavior::DontPreserve>
spOrScript;
// The bytecode offset for JS stack frames. // The bytecode offset for JS stack frames.
// Must not be used on non-JS frames; it'll contain either the default 0, // Must not be used on non-JS frames; it'll contain either the default 0,
// or a leftover value from a previous JS stack frame that was using this // or a leftover value from a previous JS stack frame that was using this
// ProfilingStackFrame object. // ProfilingStackFrame object.
mozilla::Atomic<int32_t, mozilla::ReleaseAcquire> pcOffsetIfJS_; mozilla::Atomic<int32_t, mozilla::ReleaseAcquire,
mozilla::recordreplay::Behavior::DontPreserve>
pcOffsetIfJS_;
// ID of the JS Realm for JS stack frames. // ID of the JS Realm for JS stack frames.
// Must not be used on non-JS frames; it'll contain either the default 0, // Must not be used on non-JS frames; it'll contain either the default 0,
// or a leftover value from a previous JS stack frame that was using this // or a leftover value from a previous JS stack frame that was using this
// ProfilingStackFrame object. // ProfilingStackFrame object.
mozilla::Atomic<uint64_t, mozilla::ReleaseAcquire> realmID_; mozilla::Atomic<uint64_t, mozilla::ReleaseAcquire,
mozilla::recordreplay::Behavior::DontPreserve>
realmID_;
// Bits 0...8 hold the Flags. Bits 9...31 hold the category pair. // Bits 0...8 hold the Flags. Bits 9...31 hold the category pair.
mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> flagsAndCategoryPair_; mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire,
mozilla::recordreplay::Behavior::DontPreserve>
flagsAndCategoryPair_;
static int32_t pcToOffset(JSScript* aScript, jsbytecode* aPc); static int32_t pcToOffset(JSScript* aScript, jsbytecode* aPc);
@ -481,7 +493,8 @@ class JS_FRIEND_API ProfilingStack final {
// written from the current thread. // written from the current thread.
// //
// This is effectively a unique pointer. // This is effectively a unique pointer.
mozilla::Atomic<js::ProfilingStackFrame*, mozilla::SequentiallyConsistent> mozilla::Atomic<js::ProfilingStackFrame*, mozilla::SequentiallyConsistent,
mozilla::recordreplay::Behavior::DontPreserve>
frames{nullptr}; frames{nullptr};
// This may exceed the capacity, so instead use the stackSize() method to // This may exceed the capacity, so instead use the stackSize() method to
@ -495,7 +508,9 @@ class JS_FRIEND_API ProfilingStack final {
// This is an atomic variable that uses ReleaseAcquire memory ordering. // This is an atomic variable that uses ReleaseAcquire memory ordering.
// See the "Concurrency considerations" paragraph at the top of this file // See the "Concurrency considerations" paragraph at the top of this file
// for more details. // for more details.
mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> stackPointer; mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire,
mozilla::recordreplay::Behavior::DontPreserve>
stackPointer;
}; };
namespace js { namespace js {

View file

@ -764,7 +764,8 @@ void js::FutexThread::lock() {
lock->lock(); lock->lock();
} }
/* static */ mozilla::Atomic<js::Mutex*, mozilla::SequentiallyConsistent> /* static */ mozilla::Atomic<js::Mutex*, mozilla::SequentiallyConsistent,
mozilla::recordreplay::Behavior::DontPreserve>
FutexThread::lock_; FutexThread::lock_;
/* static */ /* static */

View file

@ -129,7 +129,9 @@ class FutexThread {
// Shared futex lock for all runtimes. We can perhaps do better, // Shared futex lock for all runtimes. We can perhaps do better,
// but any lock will need to be per-domain (consider SharedWorker) // but any lock will need to be per-domain (consider SharedWorker)
// or coarser. // or coarser.
static mozilla::Atomic<js::Mutex*, mozilla::SequentiallyConsistent> lock_; static mozilla::Atomic<js::Mutex*, mozilla::SequentiallyConsistent,
mozilla::recordreplay::Behavior::DontPreserve>
lock_;
// A flag that controls whether waiting is allowed. // A flag that controls whether waiting is allowed.
ThreadData<bool> canWait_; ThreadData<bool> canWait_;

View file

@ -390,6 +390,12 @@ static MOZ_ALWAYS_INLINE bool ShouldCaptureDebugInfo(JSContext* cx) {
} }
static mozilla::Maybe<mozilla::TimeStamp> MaybeNow() { static mozilla::Maybe<mozilla::TimeStamp> MaybeNow() {
// ShouldCaptureDebugInfo() may return inconsistent values when recording
// or replaying, so in places where we might need the current time for
// promise debug info we always capture the current time.
if (mozilla::recordreplay::IsRecordingOrReplaying()) {
return mozilla::Some(mozilla::TimeStamp::Now());
}
return mozilla::Nothing(); return mozilla::Nothing();
} }
@ -479,6 +485,7 @@ class PromiseDebugInfo : public NativeObject {
if (!ShouldCaptureDebugInfo(cx)) { if (!ShouldCaptureDebugInfo(cx)) {
return; return;
} }
mozilla::recordreplay::AutoDisallowThreadEvents disallow;
// If async stacks weren't enabled and the Promise's global wasn't a // If async stacks weren't enabled and the Promise's global wasn't a
// debuggee when the Promise was created, we won't have a debugInfo // debuggee when the Promise was created, we won't have a debugInfo
@ -2227,6 +2234,7 @@ CreatePromiseObjectInternal(JSContext* cx, HandleObject proto /* = nullptr */,
if (MOZ_LIKELY(!ShouldCaptureDebugInfo(cx))) { if (MOZ_LIKELY(!ShouldCaptureDebugInfo(cx))) {
return promise; return promise;
} }
mozilla::recordreplay::AutoDisallowThreadEvents disallow;
// Store an allocation stack so we can later figure out what the // Store an allocation stack so we can later figure out what the
// control flow was for some unexpected results. Frightfully expensive, // control flow was for some unexpected results. Frightfully expensive,
@ -5760,6 +5768,15 @@ JS::AutoDebuggerJobQueueInterruption::~AutoDebuggerJobQueueInterruption() {
} }
bool JS::AutoDebuggerJobQueueInterruption::init(JSContext* cx) { bool JS::AutoDebuggerJobQueueInterruption::init(JSContext* cx) {
// When recording or replaying this class doesn't do anything. Callbacks on
// the job queue can interact with the recording, and this class can be used
// at different places between recording and replay. Because nested event
// loops aren't pushed in debugger callbacks when recording/replaying, the
// job queue does not need to be saved during debugger callbacks.
if (mozilla::recordreplay::IsRecordingOrReplaying()) {
return true;
}
MOZ_ASSERT(cx->jobQueue); MOZ_ASSERT(cx->jobQueue);
this->cx = cx; this->cx = cx;
saved = cx->jobQueue->saveJobQueue(cx); saved = cx->jobQueue->saveJobQueue(cx);
@ -5767,8 +5784,10 @@ bool JS::AutoDebuggerJobQueueInterruption::init(JSContext* cx) {
} }
void JS::AutoDebuggerJobQueueInterruption::runJobs() { void JS::AutoDebuggerJobQueueInterruption::runJobs() {
JS::AutoSaveExceptionState ases(cx); if (!mozilla::recordreplay::IsRecordingOrReplaying()) {
cx->jobQueue->runJobs(cx); JS::AutoSaveExceptionState ases(cx);
cx->jobQueue->runJobs(cx);
}
} }
const JSJitInfo promise_then_info = { const JSJitInfo promise_then_info = {

View file

@ -12,6 +12,7 @@
#include "mozilla/GuardObjects.h" // for MOZ_GUARD_OBJECT_NOTIFIER_PARAM #include "mozilla/GuardObjects.h" // for MOZ_GUARD_OBJECT_NOTIFIER_PARAM
#include "mozilla/HashTable.h" // for HashSet<>::Range, HashMapEntry #include "mozilla/HashTable.h" // for HashSet<>::Range, HashMapEntry
#include "mozilla/Maybe.h" // for Maybe, Nothing, Some #include "mozilla/Maybe.h" // for Maybe, Nothing, Some
#include "mozilla/RecordReplay.h" // for IsMiddleman
#include "mozilla/ScopeExit.h" // for MakeScopeExit, ScopeExit #include "mozilla/ScopeExit.h" // for MakeScopeExit, ScopeExit
#include "mozilla/ThreadLocal.h" // for ThreadLocal #include "mozilla/ThreadLocal.h" // for ThreadLocal
#include "mozilla/TimeStamp.h" // for TimeStamp, TimeDuration #include "mozilla/TimeStamp.h" // for TimeStamp, TimeDuration
@ -5891,6 +5892,29 @@ bool Debugger::isCompilableUnit(JSContext* cx, unsigned argc, Value* vp) {
return true; return true;
} }
/* static */
bool Debugger::recordReplayProcessKind(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (mozilla::recordreplay::IsMiddleman()) {
JSString* str = JS_NewStringCopyZ(cx, "Middleman");
if (!str) {
return false;
}
args.rval().setString(str);
} else if (mozilla::recordreplay::IsRecordingOrReplaying()) {
JSString* str = JS_NewStringCopyZ(cx, "RecordingReplaying");
if (!str) {
return false;
}
args.rval().setString(str);
} else {
args.rval().setUndefined();
}
return true;
}
bool Debugger::CallData::adoptDebuggeeValue() { bool Debugger::CallData::adoptDebuggeeValue() {
if (!args.requireAtLeast(cx, "Debugger.adoptDebuggeeValue", 1)) { if (!args.requireAtLeast(cx, "Debugger.adoptDebuggeeValue", 1)) {
return false; return false;
@ -6019,7 +6043,9 @@ const JSFunctionSpec Debugger::methods[] = {
JS_FS_END}; JS_FS_END};
const JSFunctionSpec Debugger::static_methods[]{ const JSFunctionSpec Debugger::static_methods[]{
JS_FN("isCompilableUnit", Debugger::isCompilableUnit, 1, 0), JS_FS_END}; JS_FN("isCompilableUnit", Debugger::isCompilableUnit, 1, 0),
JS_FN("recordReplayProcessKind", Debugger::recordReplayProcessKind, 0, 0),
JS_FS_END};
DebuggerScript* Debugger::newDebuggerScript( DebuggerScript* Debugger::newDebuggerScript(
JSContext* cx, Handle<DebuggerScriptReferent> referent) { JSContext* cx, Handle<DebuggerScriptReferent> referent) {

View file

@ -517,6 +517,12 @@ The functions described below are not called with a `this` value.
compilation - accumulate lines in a buffer until isCompilableUnit is true, compilation - accumulate lines in a buffer until isCompilableUnit is true,
then pass it to the compiler. then pass it to the compiler.
### `recordReplayProcessKind()`
: Return the kind of record/replay firefox process that is currently
running: the string "RecordingReplaying" if this is a recording or
replaying process, the string "Middleman" if this is a middleman
process, or undefined for normal firefox content or UI processes.
[add]: #adddebuggee-global [add]: #adddebuggee-global
[source]: Debugger.Source.md [source]: Debugger.Source.md
[script]: Debugger.Script.md [script]: Debugger.Script.md

View file

@ -35,7 +35,8 @@ namespace js {
// Memory protection occurs at non-deterministic points when // Memory protection occurs at non-deterministic points when
// recording/replaying. // recording/replaying.
static mozilla::Atomic<bool, mozilla::SequentiallyConsistent> static mozilla::Atomic<bool, mozilla::SequentiallyConsistent,
mozilla::recordreplay::Behavior::DontPreserve>
sProtectedRegionsInit(false); sProtectedRegionsInit(false);
/* /*

View file

@ -395,6 +395,12 @@ bool frontend::SourceAwareCompiler<Unit>::createSourceAndParser(
if (!compilationInfo.assignSource(sourceBuffer_)) { if (!compilationInfo.assignSource(sourceBuffer_)) {
return false; return false;
} }
// Note the contents of any compiled scripts when recording/replaying.
if (mozilla::recordreplay::IsRecordingOrReplaying()) {
mozilla::recordreplay::NoteContentParse(
this, compilationInfo.options.filename(), "application/javascript",
sourceBuffer_.units(), sourceBuffer_.length());
}
if (CanLazilyParse(compilationInfo)) { if (CanLazilyParse(compilationInfo)) {
syntaxParser.emplace(compilationInfo.cx, compilationInfo.options, syntaxParser.emplace(compilationInfo.cx, compilationInfo.options,

View file

@ -15,9 +15,8 @@
#include "mozilla/Span.h" // mozilla::Span #include "mozilla/Span.h" // mozilla::Span
#include "mozilla/Vector.h" // mozilla::Vector #include "mozilla/Vector.h" // mozilla::Vector
#include <functional> // std::function #include <stddef.h> // ptrdiff_t
#include <stddef.h> // ptrdiff_t #include <stdint.h> // uint16_t, uint32_t
#include <stdint.h> // uint16_t, uint32_t
#include "jsapi.h" // CompletionKind #include "jsapi.h" // CompletionKind

View file

@ -262,7 +262,8 @@ class ArenaLists {
}; };
using ConcurrentUseState = using ConcurrentUseState =
mozilla::Atomic<ConcurrentUse, mozilla::SequentiallyConsistent>; mozilla::Atomic<ConcurrentUse, mozilla::SequentiallyConsistent,
mozilla::recordreplay::Behavior::DontPreserve>;
// Whether this structure can be accessed by other threads. // Whether this structure can be accessed by other threads.
UnprotectedData<AllAllocKindArray<ConcurrentUseState>> concurrentUseState_; UnprotectedData<AllAllocKindArray<ConcurrentUseState>> concurrentUseState_;

View file

@ -34,7 +34,9 @@ class AtomMarkingRuntime {
public: public:
// The extent of all allocated and free words in atom mark bitmaps. // The extent of all allocated and free words in atom mark bitmaps.
// This monotonically increases and may be read from without locking. // This monotonically increases and may be read from without locking.
mozilla::Atomic<size_t, mozilla::SequentiallyConsistent> allocatedWords; mozilla::Atomic<size_t, mozilla::SequentiallyConsistent,
mozilla::recordreplay::Behavior::DontPreserve>
allocatedWords;
AtomMarkingRuntime() : allocatedWords(0) {} AtomMarkingRuntime() : allocatedWords(0) {}

View file

@ -1705,7 +1705,8 @@ bool GCRuntime::shouldCompact() {
bool GCRuntime::isCompactingGCEnabled() const { bool GCRuntime::isCompactingGCEnabled() const {
return compactingEnabled && return compactingEnabled &&
rt->mainContextFromOwnThread()->compactingDisabledCount == 0; rt->mainContextFromOwnThread()->compactingDisabledCount == 0 &&
!mozilla::recordreplay::IsRecordingOrReplaying();
} }
AutoDisableCompactingGC::AutoDisableCompactingGC(JSContext* cx) : cx(cx) { AutoDisableCompactingGC::AutoDisableCompactingGC(JSContext* cx) : cx(cx) {
@ -2916,6 +2917,42 @@ void Nursery::requestMinorGC(JS::GCReason reason) const {
runtime()->mainContextFromOwnThread()->requestInterrupt(InterruptReason::GC); runtime()->mainContextFromOwnThread()->requestInterrupt(InterruptReason::GC);
} }
// Return false if a pending GC may not occur because we are recording or
// replaying. GCs must occur at the same points when replaying as they did
// while recording, so any trigger reasons whose behavior is non-deterministic
// between recording and replaying are excluded here.
//
// Non-deterministic behaviors here are very narrow: the amount of malloc'ed
// memory or memory used by GC things may vary between recording or replaying,
// but other behaviors that would normally be non-deterministic (timers and so
// forth) are captured in the recording and replayed exactly.
static bool RecordReplayCheckCanGC(JS::GCReason reason) {
if (!mozilla::recordreplay::IsRecordingOrReplaying()) {
return true;
}
switch (reason) {
case JS::GCReason::EAGER_ALLOC_TRIGGER:
case JS::GCReason::LAST_DITCH:
case JS::GCReason::TOO_MUCH_MALLOC:
case JS::GCReason::ALLOC_TRIGGER:
case JS::GCReason::DELAYED_ATOMS_GC:
case JS::GCReason::TOO_MUCH_WASM_MEMORY:
case JS::GCReason::TOO_MUCH_JIT_CODE:
case JS::GCReason::INCREMENTAL_ALLOC_TRIGGER:
return false;
default:
break;
}
// If the above filter misses a non-deterministically triggered GC, this
// assertion will fail.
mozilla::recordreplay::RecordReplayAssert("RecordReplayCheckCanGC %d",
(int)reason);
return true;
}
bool GCRuntime::triggerGC(JS::GCReason reason) { bool GCRuntime::triggerGC(JS::GCReason reason) {
/* /*
* Don't trigger GCs if this is being called off the main thread from * Don't trigger GCs if this is being called off the main thread from
@ -2930,6 +2967,11 @@ bool GCRuntime::triggerGC(JS::GCReason reason) {
return false; return false;
} }
// GCs can only be triggered in certain ways when recording/replaying.
if (!RecordReplayCheckCanGC(reason)) {
return false;
}
JS::PrepareForFullGC(rt->mainContextFromOwnThread()); JS::PrepareForFullGC(rt->mainContextFromOwnThread());
requestMajorGC(reason); requestMajorGC(reason);
return true; return true;
@ -3070,6 +3112,11 @@ bool GCRuntime::triggerZoneGC(Zone* zone, JS::GCReason reason, size_t used,
return false; return false;
} }
// GCs can only be triggered in certain ways when recording/replaying.
if (!RecordReplayCheckCanGC(reason)) {
return false;
}
#ifdef JS_GC_ZEAL #ifdef JS_GC_ZEAL
if (hasZealMode(ZealMode::Alloc)) { if (hasZealMode(ZealMode::Alloc)) {
MOZ_RELEASE_ASSERT(triggerGC(reason)); MOZ_RELEASE_ASSERT(triggerGC(reason));
@ -5414,6 +5461,10 @@ IncrementalProgress GCRuntime::markUntilBudgetExhausted(
SliceBudget& sliceBudget) { SliceBudget& sliceBudget) {
// Run a marking slice and return whether the stack is now empty. // Run a marking slice and return whether the stack is now empty.
// Marked GC things may vary between recording and replaying, so marking
// and sweeping should not perform any recorded events.
mozilla::recordreplay::AutoDisallowThreadEvents disallow;
#ifdef DEBUG #ifdef DEBUG
AutoSetThreadIsMarking threadIsMarking; AutoSetThreadIsMarking threadIsMarking;
#endif // DEBUG #endif // DEBUG
@ -6036,6 +6087,10 @@ bool GCRuntime::initSweepActions() {
} }
IncrementalProgress GCRuntime::performSweepActions(SliceBudget& budget) { IncrementalProgress GCRuntime::performSweepActions(SliceBudget& budget) {
// Marked GC things may vary between recording and replaying, so sweep
// actions should not perform any recorded events.
mozilla::recordreplay::AutoDisallowThreadEvents disallow;
gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::SWEEP); gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::SWEEP);
JSFreeOp fop(rt); JSFreeOp fop(rt);
@ -7244,6 +7299,11 @@ SliceBudget GCRuntime::defaultBudget(JS::GCReason reason, int64_t millis) {
} }
void GCRuntime::gc(JSGCInvocationKind gckind, JS::GCReason reason) { void GCRuntime::gc(JSGCInvocationKind gckind, JS::GCReason reason) {
// Watch out for calls to gc() that don't go through triggerGC().
if (!RecordReplayCheckCanGC(reason)) {
return;
}
collect(true, SliceBudget::unlimited(), mozilla::Some(gckind), reason); collect(true, SliceBudget::unlimited(), mozilla::Some(gckind), reason);
} }
@ -8216,7 +8276,8 @@ JS_PUBLIC_API void JS::DisableIncrementalGC(JSContext* cx) {
} }
JS_PUBLIC_API bool JS::IsIncrementalGCEnabled(JSContext* cx) { JS_PUBLIC_API bool JS::IsIncrementalGCEnabled(JSContext* cx) {
return cx->runtime()->gc.isIncrementalGCEnabled(); return cx->runtime()->gc.isIncrementalGCEnabled() &&
!mozilla::recordreplay::IsRecordingOrReplaying();
} }
JS_PUBLIC_API bool JS::IsIncrementalGCInProgress(JSContext* cx) { JS_PUBLIC_API bool JS::IsIncrementalGCInProgress(JSContext* cx) {

View file

@ -70,7 +70,9 @@ class GCParallelTask : public mozilla::LinkedListElement<GCParallelTask>,
protected: protected:
// A flag to signal a request for early completion of the off-thread task. // A flag to signal a request for early completion of the off-thread task.
mozilla::Atomic<bool, mozilla::MemoryOrdering::ReleaseAcquire> cancel_; mozilla::Atomic<bool, mozilla::MemoryOrdering::ReleaseAcquire,
mozilla::recordreplay::Behavior::DontPreserve>
cancel_;
public: public:
explicit GCParallelTask(gc::GCRuntime* gc) explicit GCParallelTask(gc::GCRuntime* gc)

View file

@ -795,7 +795,9 @@ class GCRuntime {
private: private:
// Any activity affecting the heap. // Any activity affecting the heap.
mozilla::Atomic<JS::HeapState, mozilla::SequentiallyConsistent> heapState_; mozilla::Atomic<JS::HeapState, mozilla::SequentiallyConsistent,
mozilla::recordreplay::Behavior::DontPreserve>
heapState_;
friend class AutoHeapSession; friend class AutoHeapSession;
friend class JS::AutoEnterCycleCollection; friend class JS::AutoEnterCycleCollection;
@ -841,12 +843,16 @@ class GCRuntime {
MainThreadData<RootedValueMap> rootsHash; MainThreadData<RootedValueMap> rootsHash;
// An incrementing id used to assign unique ids to cells that require one. // An incrementing id used to assign unique ids to cells that require one.
mozilla::Atomic<uint64_t, mozilla::ReleaseAcquire> nextCellUniqueId_; mozilla::Atomic<uint64_t, mozilla::ReleaseAcquire,
mozilla::recordreplay::Behavior::DontPreserve>
nextCellUniqueId_;
/* /*
* Number of the committed arenas in all GC chunks including empty chunks. * Number of the committed arenas in all GC chunks including empty chunks.
*/ */
mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> numArenasFreeCommitted; mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire,
mozilla::recordreplay::Behavior::DontPreserve>
numArenasFreeCommitted;
MainThreadData<VerifyPreTracer*> verifyPreData; MainThreadData<VerifyPreTracer*> verifyPreData;
private: private:
@ -860,7 +866,9 @@ class GCRuntime {
*/ */
MainThreadData<JSGCMode> mode; MainThreadData<JSGCMode> mode;
mozilla::Atomic<size_t, mozilla::ReleaseAcquire> numActiveZoneIters; mozilla::Atomic<size_t, mozilla::ReleaseAcquire,
mozilla::recordreplay::Behavior::DontPreserve>
numActiveZoneIters;
/* /*
* The self hosting zone is collected once after initialization. We don't * The self hosting zone is collected once after initialization. We don't
@ -898,7 +906,9 @@ class GCRuntime {
*/ */
UnprotectedData<bool> grayBitsValid; UnprotectedData<bool> grayBitsValid;
mozilla::Atomic<JS::GCReason, mozilla::Relaxed> majorGCTriggerReason; mozilla::Atomic<JS::GCReason, mozilla::Relaxed,
mozilla::recordreplay::Behavior::DontPreserve>
majorGCTriggerReason;
private: private:
/* Perform full GC if rt->keepAtoms() becomes false. */ /* Perform full GC if rt->keepAtoms() becomes false. */

View file

@ -3896,6 +3896,10 @@ bool js::gc::UnmarkGrayGCThingUnchecked(JSRuntime* rt, JS::GCCellPtr thing) {
MOZ_ASSERT(thing); MOZ_ASSERT(thing);
MOZ_ASSERT(thing.asCell()->isMarkedGray()); MOZ_ASSERT(thing.asCell()->isMarkedGray());
// Gray cell unmarking can occur at different points between recording and
// replay, so disallow recorded events from occurring in the tracer.
mozilla::recordreplay::AutoDisallowThreadEvents d;
AutoGeckoProfilerEntry profilingStackFrame( AutoGeckoProfilerEntry profilingStackFrame(
TlsContext.get(), "UnmarkGrayGCThing", JS::ProfilingCategoryPair::GCCC); TlsContext.get(), "UnmarkGrayGCThing", JS::ProfilingCategoryPair::GCCC);

View file

@ -62,9 +62,13 @@ static size_t virtualMemoryLimit = size_t(-1);
* VirtualAlloc always hands out regions of memory in increasing order. * VirtualAlloc always hands out regions of memory in increasing order.
*/ */
#if defined(XP_DARWIN) #if defined(XP_DARWIN)
static mozilla::Atomic<int, mozilla::Relaxed> growthDirection(1); static mozilla::Atomic<int, mozilla::Relaxed,
mozilla::recordreplay::Behavior::DontPreserve>
growthDirection(1);
#elif defined(XP_UNIX) #elif defined(XP_UNIX)
static mozilla::Atomic<int, mozilla::Relaxed> growthDirection(0); static mozilla::Atomic<int, mozilla::Relaxed,
mozilla::recordreplay::Behavior::DontPreserve>
growthDirection(0);
#endif #endif
/* /*
@ -264,6 +268,7 @@ static inline uint64_t GetNumberInRange(uint64_t minNum, uint64_t maxNum) {
do { do {
mozilla::Maybe<uint64_t> result; mozilla::Maybe<uint64_t> result;
do { do {
mozilla::recordreplay::AutoPassThroughThreadEvents pt;
result = mozilla::RandomUint64(); result = mozilla::RandomUint64();
} while (!result); } while (!result);
rndNum = result.value() / binSize; rndNum = result.value() / binSize;
@ -830,6 +835,9 @@ bool MarkPagesInUseHard(void* region, size_t length) {
} }
size_t GetPageFaultCount() { size_t GetPageFaultCount() {
if (mozilla::recordreplay::IsRecordingOrReplaying()) {
return 0;
}
#ifdef XP_WIN #ifdef XP_WIN
PROCESS_MEMORY_COUNTERS pmc; PROCESS_MEMORY_COUNTERS pmc;
if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc)) == 0) { if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc)) == 0) {

View file

@ -245,6 +245,12 @@ js::Nursery::Nursery(GCRuntime* gc)
} }
bool js::Nursery::init(AutoLockGCBgAlloc& lock) { bool js::Nursery::init(AutoLockGCBgAlloc& lock) {
// The nursery is permanently disabled when recording or replaying. Nursery
// collections may occur at non-deterministic points in execution.
if (mozilla::recordreplay::IsRecordingOrReplaying()) {
return true;
}
capacity_ = roundSize(tunables().gcMinNurseryBytes()); capacity_ = roundSize(tunables().gcMinNurseryBytes());
if (!allocateNextChunk(0, lock)) { if (!allocateNextChunk(0, lock)) {
capacity_ = 0; capacity_ = 0;
@ -293,7 +299,7 @@ js::Nursery::~Nursery() { disable(); }
void js::Nursery::enable() { void js::Nursery::enable() {
MOZ_ASSERT(isEmpty()); MOZ_ASSERT(isEmpty());
MOZ_ASSERT(!gc->isVerifyPreBarriersEnabled()); MOZ_ASSERT(!gc->isVerifyPreBarriersEnabled());
if (isEnabled()) { if (isEnabled() || mozilla::recordreplay::IsRecordingOrReplaying()) {
return; return;
} }
@ -958,6 +964,8 @@ void js::Nursery::collect(JS::GCReason reason) {
JSRuntime* rt = runtime(); JSRuntime* rt = runtime();
MOZ_ASSERT(!rt->mainContextFromOwnThread()->suppressGC); MOZ_ASSERT(!rt->mainContextFromOwnThread()->suppressGC);
mozilla::recordreplay::AutoDisallowThreadEvents disallow;
if (!isEnabled() || isEmpty()) { if (!isEnabled() || isEmpty()) {
// Our barriers are not always exact, and there may be entries in the // Our barriers are not always exact, and there may be entries in the
// storebuffer even when the nursery is disabled or empty. It's not safe // storebuffer even when the nursery is disabled or empty. It's not safe

View file

@ -680,7 +680,9 @@ struct TriggerResult {
size_t thresholdBytes; size_t thresholdBytes;
}; };
using AtomicByteCount = mozilla::Atomic<size_t, mozilla::ReleaseAcquire>; using AtomicByteCount =
mozilla::Atomic<size_t, mozilla::ReleaseAcquire,
mozilla::recordreplay::Behavior::DontPreserve>;
/* /*
* Tracks the size of allocated data. This is used for both GC and malloc data. * Tracks the size of allocated data. This is used for both GC and malloc data.

View file

@ -349,8 +349,10 @@ struct Statistics {
PhaseTimeTable parallelTimes; PhaseTimeTable parallelTimes;
/* Number of events of this type for this GC. */ /* Number of events of this type for this GC. */
EnumeratedArray<Count, COUNT_LIMIT, EnumeratedArray<
mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire>> Count, COUNT_LIMIT,
mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire,
mozilla::recordreplay::Behavior::DontPreserve>>
counts; counts;
/* Other GC statistics. */ /* Other GC statistics. */

View file

@ -180,7 +180,8 @@ class Zone : public js::ZoneAllocator, public js::gc::GraphNodeBase<JS::Zone> {
js::WriteOnceData<bool> isSystemZone_; js::WriteOnceData<bool> isSystemZone_;
enum class HelperThreadUse : uint32_t { None, Pending, Active }; enum class HelperThreadUse : uint32_t { None, Pending, Active };
mozilla::Atomic<HelperThreadUse, mozilla::SequentiallyConsistent> mozilla::Atomic<HelperThreadUse, mozilla::SequentiallyConsistent,
mozilla::recordreplay::Behavior::DontPreserve>
helperThreadUse_; helperThreadUse_;
// The helper thread context with exclusive access to this zone, if // The helper thread context with exclusive access to this zone, if
@ -230,7 +231,9 @@ class Zone : public js::ZoneAllocator, public js::gc::GraphNodeBase<JS::Zone> {
js::ZoneOrGCTaskData<js::gc::UniqueIdMap> uniqueIds_; js::ZoneOrGCTaskData<js::gc::UniqueIdMap> uniqueIds_;
// Number of allocations since the most recent minor GC for this thread. // Number of allocations since the most recent minor GC for this thread.
mozilla::Atomic<uint32_t, mozilla::Relaxed> tenuredAllocsSinceMinorGC_; mozilla::Atomic<uint32_t, mozilla::Relaxed,
mozilla::recordreplay::Behavior::DontPreserve>
tenuredAllocsSinceMinorGC_;
// Live weakmaps in this zone. // Live weakmaps in this zone.
js::ZoneOrGCTaskData<mozilla::LinkedList<js::WeakMapBase>> gcWeakMapList_; js::ZoneOrGCTaskData<mozilla::LinkedList<js::WeakMapBase>> gcWeakMapList_;

View file

@ -481,6 +481,9 @@ static jsbytecode* GetResumePC(JSScript* script, jsbytecode* pc,
break; break;
} }
} }
if (skippedLoopHead && script->trackRecordReplayProgress()) {
mozilla::recordreplay::AdvanceExecutionProgressCounter();
}
return pc; return pc;
} }

View file

@ -2248,7 +2248,23 @@ bool BaselineCodeGen<Handler>::emit_LoopHead() {
if (!emitWarmUpCounterIncrement()) { if (!emitWarmUpCounterIncrement()) {
return false; return false;
} }
return true; return emitIncExecutionProgressCounter(R0.scratchReg());
}
template <typename Handler>
bool BaselineCodeGen<Handler>::emitIncExecutionProgressCounter(
Register scratch) {
if (!mozilla::recordreplay::IsRecordingOrReplaying()) {
return true;
}
auto incCounter = [this]() {
masm.inc64(
AbsoluteAddress(mozilla::recordreplay::ExecutionProgressCounter()));
return true;
};
return emitTestScriptFlag(JSScript::MutableFlags::TrackRecordReplayProgress,
true, incCounter, scratch);
} }
template <typename Handler> template <typename Handler>
@ -6718,6 +6734,10 @@ bool BaselineCodeGen<Handler>::emitPrologue() {
// chain is in R1. For function scripts, the env chain is in the callee. // chain is in R1. For function scripts, the env chain is in the callee.
emitInitFrameFields(R1.scratchReg()); emitInitFrameFields(R1.scratchReg());
if (!emitIncExecutionProgressCounter(R2.scratchReg())) {
return false;
}
// When compiling with Debugger instrumentation, set the debuggeeness of // When compiling with Debugger instrumentation, set the debuggeeness of
// the frame before any operation that can call into the VM. // the frame before any operation that can call into the VM.
if (!emitIsDebuggeeCheck()) { if (!emitIsDebuggeeCheck()) {

View file

@ -174,6 +174,8 @@ class BaselineCodeGen {
Register scratch); Register scratch);
void emitJumpToInterpretOpLabel(); void emitJumpToInterpretOpLabel();
MOZ_MUST_USE bool emitIncExecutionProgressCounter(Register scratch);
MOZ_MUST_USE bool emitCheckThis(ValueOperand val, bool reinit = false); MOZ_MUST_USE bool emitCheckThis(ValueOperand val, bool reinit = false);
void emitLoadReturnValue(ValueOperand val); void emitLoadReturnValue(ValueOperand val);
void emitPushNonArrowFunctionNewTarget(); void emitPushNonArrowFunctionNewTarget();

View file

@ -4801,6 +4801,11 @@ void CacheIRCompiler::emitPostBarrierShared(Register obj,
Register maybeIndex) { Register maybeIndex) {
JitSpew(JitSpew_Codegen, "%s", __FUNCTION__); JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
// Generational GC is disabled for WebReplay.
if (mozilla::recordreplay::IsRecordingOrReplaying()) {
return;
}
if (val.constant()) { if (val.constant()) {
MOZ_ASSERT_IF(val.value().isGCThing(), MOZ_ASSERT_IF(val.value().isGCThing(),
!IsInsideNursery(val.value().toGCThing())); !IsInsideNursery(val.value().toGCThing()));

View file

@ -13020,6 +13020,11 @@ void CodeGenerator::visitInterruptCheck(LInterruptCheck* lir) {
OutOfLineCode* ool = OutOfLineCode* ool =
oolCallVM<Fn, InterruptCheck>(lir, ArgList(), StoreNothing()); oolCallVM<Fn, InterruptCheck>(lir, ArgList(), StoreNothing());
if (lir->mir()->trackRecordReplayProgress()) {
masm.inc64(
AbsoluteAddress(mozilla::recordreplay::ExecutionProgressCounter()));
}
const void* interruptAddr = gen->runtime->addressOfInterruptBits(); const void* interruptAddr = gen->runtime->addressOfInterruptBits();
masm.branch32(Assembler::NotEqual, AbsoluteAddress(interruptAddr), Imm32(0), masm.branch32(Assembler::NotEqual, AbsoluteAddress(interruptAddr), Imm32(0),
ool->entry()); ool->entry());

View file

@ -170,6 +170,7 @@ class CompileInfo {
hadOverflowBailout_(script->hadOverflowBailout()), hadOverflowBailout_(script->hadOverflowBailout()),
hadFrequentBailouts_(script->hadFrequentBailouts()), hadFrequentBailouts_(script->hadFrequentBailouts()),
mayReadFrameArgsDirectly_(script->mayReadFrameArgsDirectly()), mayReadFrameArgsDirectly_(script->mayReadFrameArgsDirectly()),
trackRecordReplayProgress_(script->trackRecordReplayProgress()),
inlineScriptTree_(inlineScriptTree) { inlineScriptTree_(inlineScriptTree) {
MOZ_ASSERT_IF(osrPc, JSOp(*osrPc) == JSOp::LoopHead); MOZ_ASSERT_IF(osrPc, JSOp(*osrPc) == JSOp::LoopHead);
@ -230,6 +231,7 @@ class CompileInfo {
hadOverflowBailout_(false), hadOverflowBailout_(false),
hadFrequentBailouts_(false), hadFrequentBailouts_(false),
mayReadFrameArgsDirectly_(false), mayReadFrameArgsDirectly_(false),
trackRecordReplayProgress_(false),
inlineScriptTree_(nullptr), inlineScriptTree_(nullptr),
needsBodyEnvironmentObject_(false), needsBodyEnvironmentObject_(false),
funNeedsSomeEnvironmentObject_(false) { funNeedsSomeEnvironmentObject_(false) {
@ -480,6 +482,7 @@ class CompileInfo {
bool hadOverflowBailout() const { return hadOverflowBailout_; } bool hadOverflowBailout() const { return hadOverflowBailout_; }
bool hadFrequentBailouts() const { return hadFrequentBailouts_; } bool hadFrequentBailouts() const { return hadFrequentBailouts_; }
bool mayReadFrameArgsDirectly() const { return mayReadFrameArgsDirectly_; } bool mayReadFrameArgsDirectly() const { return mayReadFrameArgsDirectly_; }
bool trackRecordReplayProgress() const { return trackRecordReplayProgress_; }
private: private:
unsigned nimplicit_; unsigned nimplicit_;
@ -504,6 +507,7 @@ class CompileInfo {
bool hadFrequentBailouts_; bool hadFrequentBailouts_;
bool mayReadFrameArgsDirectly_; bool mayReadFrameArgsDirectly_;
bool trackRecordReplayProgress_;
InlineScriptTree* inlineScriptTree_; InlineScriptTree* inlineScriptTree_;

View file

@ -1115,6 +1115,14 @@ AbortReasonOr<Ok> IonBuilder::buildInline(IonBuilder* callerBuilder,
insertRecompileCheck(pc); insertRecompileCheck(pc);
// Insert an interrupt check when recording or replaying, which will bump
// the record/replay system's progress counter.
if (script()->trackRecordReplayProgress()) {
MInterruptCheck* check = MInterruptCheck::New(alloc());
check->setTrackRecordReplayProgress();
current->add(check);
}
// Initialize the env chain now that all resume points operands are // Initialize the env chain now that all resume points operands are
// initialized. // initialized.
MOZ_TRY(initEnvironmentChain(callInfo.fun())); MOZ_TRY(initEnvironmentChain(callInfo.fun()));
@ -1813,6 +1821,14 @@ AbortReasonOr<Ok> IonBuilder::emitLoopHeadInstructions(jsbytecode* pc) {
current->add(check); current->add(check);
insertRecompileCheck(pc); insertRecompileCheck(pc);
if (script()->trackRecordReplayProgress()) {
check->setTrackRecordReplayProgress();
// When recording/replaying, MInterruptCheck is effectful and should
// not reexecute after bailing out.
MOZ_TRY(resumeAfter(check));
}
return Ok(); return Ok();
} }
@ -6148,6 +6164,14 @@ AbortReasonOr<bool> IonBuilder::testShouldDOMCall(TypeSet* inTypes,
return false; return false;
} }
// Some DOM optimizations cause execution to skip over recorded events such
// as wrapper cache accesses, e.g. through GVN or loop hoisting of the
// expression which performs the event. Disable DOM optimizations when
// recording or replaying to avoid this problem.
if (mozilla::recordreplay::IsRecordingOrReplaying()) {
return false;
}
// If all the DOM objects flowing through are legal with this // If all the DOM objects flowing through are legal with this
// property, we can bake in a call to the bottom half of the DOM // property, we can bake in a call to the bottom half of the DOM
// accessor // accessor
@ -6483,10 +6507,13 @@ AbortReasonOr<Ok> IonBuilder::jsop_eval(uint32_t argc) {
// Try to pattern match 'eval(v + "()")'. In this case v is likely a // Try to pattern match 'eval(v + "()")'. In this case v is likely a
// name on the env chain and the eval is performing a call on that // name on the env chain and the eval is performing a call on that
// value. Use an env chain lookup rather than a full eval. // value. Use an env chain lookup rather than a full eval. Avoid this
// optimization if we're tracking script progress, as this will not
// execute the script and give an inconsistent progress count.
if (string->isConcat() && if (string->isConcat() &&
string->getOperand(1)->type() == MIRType::String && string->getOperand(1)->type() == MIRType::String &&
string->getOperand(1)->maybeConstantValue()) { string->getOperand(1)->maybeConstantValue() &&
!script()->trackRecordReplayProgress()) {
JSAtom* atom = JSAtom* atom =
&string->getOperand(1)->maybeConstantValue()->toString()->asAtom(); &string->getOperand(1)->maybeConstantValue()->toString()->asAtom();
@ -8232,6 +8259,10 @@ AbortReasonOr<Ok> IonBuilder::loadStaticSlot(JSObject* staticObject,
// Whether a write of the given value may need a post-write barrier for GC // Whether a write of the given value may need a post-write barrier for GC
// purposes. // purposes.
bool IonBuilder::needsPostBarrier(MDefinition* value) { bool IonBuilder::needsPostBarrier(MDefinition* value) {
// Generational GC is disabled for WebReplay.
if (mozilla::recordreplay::IsRecordingOrReplaying()) {
return false;
}
CompileZone* zone = realm->zone(); CompileZone* zone = realm->zone();
if (value->mightBeType(MIRType::Object)) { if (value->mightBeType(MIRType::Object)) {
return true; return true;

View file

@ -227,7 +227,8 @@ class JitRuntime {
// Number of Ion compilations which were finished off thread and are // Number of Ion compilations which were finished off thread and are
// waiting to be lazily linked. This is only set while holding the helper // waiting to be lazily linked. This is only set while holding the helper
// thread state lock, but may be read from at other times. // thread state lock, but may be read from at other times.
typedef mozilla::Atomic<size_t, mozilla::SequentiallyConsistent> typedef mozilla::Atomic<size_t, mozilla::SequentiallyConsistent,
mozilla::recordreplay::Behavior::DontPreserve>
NumFinishedOffThreadTasksType; NumFinishedOffThreadTasksType;
NumFinishedOffThreadTasksType numFinishedOffThreadTasks_; NumFinishedOffThreadTasksType numFinishedOffThreadTasks_;

View file

@ -181,7 +181,9 @@ class alignas(uintptr_t) JitScript final {
// Number of times the script has been called or has had backedges taken. // Number of times the script has been called or has had backedges taken.
// Reset if the script's JIT code is forcibly discarded. See also the // Reset if the script's JIT code is forcibly discarded. See also the
// ScriptWarmUpData class. // ScriptWarmUpData class.
mozilla::Atomic<uint32_t, mozilla::Relaxed> warmUpCount_ = {}; mozilla::Atomic<uint32_t, mozilla::Relaxed,
mozilla::recordreplay::Behavior::DontPreserve>
warmUpCount_ = {};
// Offset of the StackTypeSet array. // Offset of the StackTypeSet array.
uint32_t typeSetOffset_ = 0; uint32_t typeSetOffset_ = 0;

View file

@ -6294,13 +6294,21 @@ class MCheckOverRecursed : public MNullaryInstruction {
// Check whether we need to fire the interrupt handler. // Check whether we need to fire the interrupt handler.
class MInterruptCheck : public MNullaryInstruction { class MInterruptCheck : public MNullaryInstruction {
MInterruptCheck() : MNullaryInstruction(classOpcode) { setGuard(); } bool trackRecordReplayProgress_;
MInterruptCheck()
: MNullaryInstruction(classOpcode), trackRecordReplayProgress_(false) {
setGuard();
}
public: public:
INSTRUCTION_HEADER(InterruptCheck) INSTRUCTION_HEADER(InterruptCheck)
TRIVIAL_NEW_WRAPPERS TRIVIAL_NEW_WRAPPERS
AliasSet getAliasSet() const override { return AliasSet::None(); } AliasSet getAliasSet() const override { return AliasSet::None(); }
bool trackRecordReplayProgress() const { return trackRecordReplayProgress_; }
void setTrackRecordReplayProgress() { trackRecordReplayProgress_ = true; }
}; };
// Check whether we need to fire the interrupt handler (in wasm code). // Check whether we need to fire the interrupt handler (in wasm code).

View file

@ -133,7 +133,9 @@ class MIRGenerator final {
TempAllocator* alloc_; TempAllocator* alloc_;
MIRGraph* graph_; MIRGraph* graph_;
AbortReasonOr<Ok> offThreadStatus_; AbortReasonOr<Ok> offThreadStatus_;
mozilla::Atomic<bool, mozilla::Relaxed> cancelBuild_; mozilla::Atomic<bool, mozilla::Relaxed,
mozilla::recordreplay::Behavior::DontPreserve>
cancelBuild_;
uint32_t wasmMaxStackArgBytes_; uint32_t wasmMaxStackArgBytes_;
bool needsOverrecursedCheck_; bool needsOverrecursedCheck_;

View file

@ -510,7 +510,9 @@ class ProcessExecutableMemory {
// pagesAllocated_ is an Atomic so that bytesAllocated does not have to // pagesAllocated_ is an Atomic so that bytesAllocated does not have to
// take the lock. // take the lock.
mozilla::Atomic<size_t, mozilla::ReleaseAcquire> pagesAllocated_; mozilla::Atomic<size_t, mozilla::ReleaseAcquire,
mozilla::recordreplay::Behavior::DontPreserve>
pagesAllocated_;
// Page where we should try to allocate next. // Page where we should try to allocate next.
size_t cursor_; size_t cursor_;

View file

@ -116,6 +116,11 @@ bool CodeGeneratorShared::generatePrologue() {
masm.profilerEnterFrame(masm.getStackPointer(), CallTempReg0); masm.profilerEnterFrame(masm.getStackPointer(), CallTempReg0);
} }
if (gen->outerInfo().trackRecordReplayProgress()) {
masm.inc64(
AbsoluteAddress(mozilla::recordreplay::ExecutionProgressCounter()));
}
// Ensure that the Ion frame is properly aligned. // Ensure that the Ion frame is properly aligned.
masm.assertStackAlignment(JitStackAlignment, 0); masm.assertStackAlignment(JitStackAlignment, 0);

View file

@ -2701,6 +2701,14 @@ namespace JS {
*/ */
extern JS_PUBLIC_API JSObject* ExceptionStackOrNull(JS::HandleObject obj); extern JS_PUBLIC_API JSObject* ExceptionStackOrNull(JS::HandleObject obj);
/**
* If this process is recording or replaying and the given value is an
* exception object (or an unwrappable cross-compartment wrapper for one),
* return the point where this exception was thrown, for time warping later.
* Returns zero otherwise.
*/
extern JS_PUBLIC_API uint64_t ExceptionTimeWarpTarget(JS::HandleValue exn);
} /* namespace JS */ } /* namespace JS */
/** /**

Some files were not shown because too many files have changed in this diff Show more