Bug 1465287 Part 8 - Allow spawning recording/replaying child processes and saving recordings, r=jld,mrbkap.

--HG--
extra : rebase_source : 1da4b1a7e485cfdafb38318860546ce3d0552815
This commit is contained in:
Brian Hackett 2018-07-22 11:52:42 +00:00
Родитель d138090586
Коммит 3354a96d8d
12 изменённых файлов: 248 добавлений и 15 удалений

Просмотреть файл

@ -605,7 +605,8 @@ public:
// present.
if (!targetProcess) {
targetProcess =
ContentParent::GetNewOrUsedBrowserProcess(NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE),
ContentParent::GetNewOrUsedBrowserProcess(nullptr,
NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE),
ContentParent::GetInitialProcessPriority(nullptr),
nullptr);
}

Просмотреть файл

@ -113,4 +113,10 @@ interface nsITabParent : nsISupports
* autoscrolled.
*/
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);
};

Просмотреть файл

@ -3828,6 +3828,13 @@ ContentChild::RecvAddDynamicScalars(nsTArray<DynamicScalarDefinition>&& aDefs)
return IPC_OK();
}
mozilla::ipc::IPCResult
ContentChild::RecvSaveRecording(const FileDescriptor& aFile)
{
recordreplay::parent::SaveRecording(aFile);
return IPC_OK();
}
already_AddRefed<nsIEventTarget>
ContentChild::GetSpecificMessageEventTarget(const Message& aMsg)
{

Просмотреть файл

@ -736,6 +736,9 @@ public:
virtual bool
DeallocPClientOpenWindowOpChild(PClientOpenWindowOpChild* aActor) override;
mozilla::ipc::IPCResult
RecvSaveRecording(const FileDescriptor& aFile) override;
#ifdef NIGHTLY_BUILD
// Fetch the current number of pending input events.
//

Просмотреть файл

@ -117,6 +117,7 @@
#include "nsConsoleService.h"
#include "nsContentUtils.h"
#include "nsDebugImpl.h"
#include "nsDirectoryServiceDefs.h"
#include "nsEmbedCID.h"
#include "nsFrameLoader.h"
#include "nsFrameMessageManager.h"
@ -611,7 +612,9 @@ ContentParent::PreallocateProcess()
{
RefPtr<ContentParent> process =
new ContentParent(/* aOpener = */ nullptr,
NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE));
NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE),
eNotRecordingOrReplaying,
/* aRecordingFile = */ EmptyString());
PreallocatedProcessManager::AddBlocker(process);
@ -767,18 +770,55 @@ ContentParent::MinTabSelect(const nsTArray<ContentParent*>& aContentParents,
return candidate.forget();
}
static bool
CreateTemporaryRecordingFile(nsAString& aResult)
{
unsigned long elapsed = (TimeStamp::Now() - TimeStamp::ProcessCreation()).ToMilliseconds();
nsCOMPtr<nsIFile> file;
return !NS_FAILED(NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(file)))
&& !NS_FAILED(file->AppendNative(nsPrintfCString("Recording%lu", elapsed)))
&& !NS_FAILED(file->GetPath(aResult));
}
/*static*/ already_AddRefed<ContentParent>
ContentParent::GetNewOrUsedBrowserProcess(const nsAString& aRemoteType,
ContentParent::GetNewOrUsedBrowserProcess(Element* aFrameElement,
const nsAString& aRemoteType,
ProcessPriority aPriority,
ContentParent* aOpener,
bool aPreferUsed)
{
// Figure out if this process will be recording or replaying, and which file
// to use for the recording.
RecordReplayState recordReplayState = eNotRecordingOrReplaying;
nsAutoString recordingFile;
if (aFrameElement) {
aFrameElement->GetAttr(kNameSpaceID_None, nsGkAtoms::ReplayExecution, recordingFile);
if (!recordingFile.IsEmpty()) {
recordReplayState = eReplaying;
} else {
aFrameElement->GetAttr(kNameSpaceID_None, nsGkAtoms::RecordExecution, recordingFile);
if (recordingFile.IsEmpty() && recordreplay::parent::SaveAllRecordingsDirectory()) {
recordingFile.AssignLiteral("*");
}
if (!recordingFile.IsEmpty()) {
if (recordingFile.EqualsLiteral("*") && !CreateTemporaryRecordingFile(recordingFile)) {
return nullptr;
}
recordReplayState = eRecording;
}
}
}
nsTArray<ContentParent*>& contentParents = GetOrCreatePool(aRemoteType);
uint32_t maxContentParents = GetMaxProcessCount(aRemoteType);
if (aRemoteType.EqualsLiteral(LARGE_ALLOCATION_REMOTE_TYPE)) {
if (recordReplayState != eNotRecordingOrReplaying) {
// Fall through and always create a new process when recording or replaying.
} else if (aRemoteType.EqualsLiteral(LARGE_ALLOCATION_REMOTE_TYPE)) {
// We never want to re-use Large-Allocation processes.
if (contentParents.Length() >= maxContentParents) {
return GetNewOrUsedBrowserProcess(NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE),
return GetNewOrUsedBrowserProcess(aFrameElement,
NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE),
aPriority,
aOpener);
}
@ -836,7 +876,7 @@ ContentParent::GetNewOrUsedBrowserProcess(const nsAString& aRemoteType,
}
// Create a new process from scratch.
RefPtr<ContentParent> p = new ContentParent(aOpener, aRemoteType);
RefPtr<ContentParent> p = new ContentParent(aOpener, aRemoteType, recordReplayState, recordingFile);
// Until the new process is ready let's not allow to start up any preallocated processes.
PreallocatedProcessManager::AddBlocker(p);
@ -845,7 +885,10 @@ ContentParent::GetNewOrUsedBrowserProcess(const nsAString& aRemoteType,
return nullptr;
}
contentParents.AppendElement(p);
if (recordReplayState == eNotRecordingOrReplaying) {
contentParents.AppendElement(p);
}
p->mActivateTS = TimeStamp::Now();
return p.forget();
}
@ -942,7 +985,7 @@ ContentParent::RecvCreateChildProcess(const IPCTabContext& aContext,
aPriority);
}
else {
cp = GetNewOrUsedBrowserProcess(NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE),
cp = GetNewOrUsedBrowserProcess(nullptr, NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE),
aPriority, this);
}
@ -1163,7 +1206,7 @@ ContentParent::CreateBrowser(const TabContext& aContext,
initialPriority);
} else {
constructorSender =
GetNewOrUsedBrowserProcess(remoteType, initialPriority,
GetNewOrUsedBrowserProcess(aFrameElement, remoteType, initialPriority,
nullptr, isPreloadBrowser);
}
if (!constructorSender) {
@ -1405,6 +1448,18 @@ ContentParent::ShutDownProcess(ShutDownMethod aMethod)
// other methods. We first call Shutdown() in the child. After the child is
// ready, it calls FinishShutdown() on us. Then we close the channel.
if (aMethod == SEND_SHUTDOWN_MESSAGE) {
if (const char* directory = recordreplay::parent::SaveAllRecordingsDirectory()) {
// Save a recording for the child process before it shuts down.
unsigned long elapsed = (TimeStamp::Now() - TimeStamp::ProcessCreation()).ToMilliseconds();
nsCOMPtr<nsIFile> file;
if (!NS_FAILED(NS_NewNativeLocalFile(nsDependentCString(directory), false,
getter_AddRefs(file))) &&
!NS_FAILED(file->AppendNative(nsPrintfCString("Recording%lu", elapsed)))) {
bool unused;
SaveRecording(file, &unused);
}
}
if (mIPCOpen && !mShutdownPending) {
// Stop sending input events with input priority when shutting down.
SetInputPriorityEventEnabled(false);
@ -1794,6 +1849,14 @@ ContentParent::ActorDestroy(ActorDestroyReason why)
DelayedDeleteSubprocess, mSubprocess));
mSubprocess = nullptr;
// Delete any remaining replaying children.
for (auto& replayingProcess : mReplayingChildren) {
if (replayingProcess) {
DelayedDeleteSubprocess(replayingProcess);
replayingProcess = nullptr;
}
}
// IPDL rules require actors to live on past ActorDestroy, but it
// may be that the kungFuDeathGrip above is the last reference to
// |this|. If so, when we go out of scope here, we're deleted and
@ -1981,6 +2044,65 @@ 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 (!recordreplay::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 (!recordreplay::IsRecordingOrReplaying()) {
return IPC_FAIL_NO_REASON(this);
}
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);
mReplayingChildren[aChannelId] = new GeckoChildProcessHost(GeckoProcessType_Content);
if (!mReplayingChildren[aChannelId]->LaunchAndWaitForProcessHandle(extraArgs)) {
return IPC_FAIL_NO_REASON(this);
}
return IPC_OK();
}
mozilla::ipc::IPCResult
ContentParent::RecvTerminateReplayingProcess(const uint32_t& aChannelId)
{
// We should only get this message from the child if it is recording or replaying.
if (!recordreplay::IsRecordingOrReplaying()) {
return IPC_FAIL_NO_REASON(this);
}
if (aChannelId < mReplayingChildren.length() && mReplayingChildren[aChannelId]) {
DelayedDeleteSubprocess(mReplayingChildren[aChannelId]);
mReplayingChildren[aChannelId] = nullptr;
}
return IPC_OK();
}
jsipc::CPOWManager*
ContentParent::GetCPOWManager()
{
@ -2101,6 +2223,18 @@ ContentParent::LaunchSubprocess(ProcessPriority aInitialPriority /* = PROCESS_PR
extraArgs.push_back("-parentBuildID");
extraArgs.push_back(parentBuildID.get());
// Specify whether the process is recording or replaying an execution.
if (mRecordReplayState != eNotRecordingOrReplaying) {
nsPrintfCString buf("%d", mRecordReplayState == eRecording
? (int) recordreplay::ProcessKind::MiddlemanRecording
: (int) recordreplay::ProcessKind::MiddlemanReplaying);
extraArgs.push_back(recordreplay::gProcessKindOption);
extraArgs.push_back(buf.get());
extraArgs.push_back(recordreplay::gRecordingFileOption);
extraArgs.push_back(NS_ConvertUTF16toUTF8(mRecordingFile).get());
}
SetOtherProcessId(kInvalidProcessId, ProcessIdState::ePending);
#ifdef ASYNC_CONTENTPROC_LAUNCH
if (!mSubprocess->Launch(extraArgs)) {
@ -2154,6 +2288,8 @@ ContentParent::LaunchSubprocess(ProcessPriority aInitialPriority /* = PROCESS_PR
ContentParent::ContentParent(ContentParent* aOpener,
const nsAString& aRemoteType,
RecordReplayState aRecordReplayState,
const nsAString& aRecordingFile,
int32_t aJSPluginID)
: nsIContentParent()
, mSubprocess(nullptr)
@ -2168,6 +2304,8 @@ ContentParent::ContentParent(ContentParent* aOpener,
, mIsAvailable(true)
, mIsAlive(true)
, mIsForBrowser(!mRemoteType.IsEmpty())
, mRecordReplayState(aRecordReplayState)
, mRecordingFile(aRecordingFile)
, mCalledClose(false)
, mCalledKillHard(false)
, mCreatedPairedMinidumps(false)
@ -5767,6 +5905,31 @@ ContentParent::CanCommunicateWith(ContentParentId aOtherProcess)
return parentId == aOtherProcess;
}
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()
{

Просмотреть файл

@ -174,7 +174,8 @@ public:
* 3. normal iframe
*/
static already_AddRefed<ContentParent>
GetNewOrUsedBrowserProcess(const nsAString& aRemoteType,
GetNewOrUsedBrowserProcess(Element* aFrameElement,
const nsAString& aRemoteType,
hal::ProcessPriority aPriority =
hal::ProcessPriority::PROCESS_PRIORITY_FOREGROUND,
ContentParent* aOpener = nullptr,
@ -303,6 +304,11 @@ public:
virtual mozilla::ipc::IPCResult RecvBridgeToChildProcess(const ContentParentId& aCpId,
Endpoint<PContentBridgeParent>* aEndpoint) override;
virtual mozilla::ipc::IPCResult RecvOpenRecordReplayChannel(const uint32_t& channelId,
FileDescriptor* connection) override;
virtual mozilla::ipc::IPCResult RecvCreateReplayingProcess(const uint32_t& aChannelId) override;
virtual mozilla::ipc::IPCResult RecvTerminateReplayingProcess(const uint32_t& aChannelId) override;
virtual mozilla::ipc::IPCResult RecvCreateGMPService() override;
virtual mozilla::ipc::IPCResult RecvLoadPlugin(const uint32_t& aPluginId, nsresult* aRv,
@ -743,16 +749,28 @@ private:
FORWARD_SHMEM_ALLOCATOR_TO(PContentParent)
enum RecordReplayState
{
eNotRecordingOrReplaying,
eRecording,
eReplaying
};
explicit ContentParent(int32_t aPluginID)
: ContentParent(nullptr, EmptyString(), aPluginID)
: ContentParent(nullptr, EmptyString(), eNotRecordingOrReplaying, EmptyString(), aPluginID)
{}
ContentParent(ContentParent* aOpener,
const nsAString& aRemoteType)
: ContentParent(aOpener, aRemoteType, nsFakePluginTag::NOT_JSPLUGIN)
const nsAString& aRemoteType,
RecordReplayState aRecordReplayState = eNotRecordingOrReplaying,
const nsAString& aRecordingFile = EmptyString())
: ContentParent(aOpener, aRemoteType, aRecordReplayState, aRecordingFile,
nsFakePluginTag::NOT_JSPLUGIN)
{}
ContentParent(ContentParent* aOpener,
const nsAString& aRemoteType,
RecordReplayState aRecordReplayState,
const nsAString& aRecordingFile,
int32_t aPluginID);
// Launch the subprocess and associated initialization.
@ -1248,6 +1266,8 @@ public:
bool CanCommunicateWith(ContentParentId aOtherProcess);
nsresult SaveRecording(nsIFile* aFile, bool* aRetval);
private:
// If you add strong pointers to cycle collected objects here, be sure to
@ -1291,6 +1311,16 @@ private:
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
// channel.
bool mCalledClose;

Просмотреть файл

@ -726,6 +726,9 @@ child:
*/
async PClientOpenWindowOp(ClientOpenWindowArgs aArgs);
/* Save the execution up to the current point in a recording process. */
async SaveRecording(FileDescriptor file);
parent:
async InitBackground(Endpoint<PBackgroundParent> aEndpoint);
@ -737,6 +740,11 @@ parent:
sync BridgeToChildProcess(ContentParentId cpId)
returns (Endpoint<PContentBridgeParent> endpoint);
sync OpenRecordReplayChannel(uint32_t channelId)
returns (FileDescriptor connection);
async CreateReplayingProcess(uint32_t channelId);
async TerminateReplayingProcess(uint32_t channelId);
async CreateGMPService();
async InitStreamFilter(uint64_t channelId, nsString addonId)

Просмотреть файл

@ -2964,6 +2964,17 @@ TabParent::PreserveLayers(bool aPreserveLayers)
return NS_OK;
}
NS_IMETHODIMP
TabParent::SaveRecording(const nsAString& aFilename, bool* aRetval)
{
nsCOMPtr<nsIFile> file;
nsresult rv = NS_NewLocalFile(aFilename, false, getter_AddRefs(file));
if (NS_FAILED(rv)) {
return rv;
}
return Manager()->AsContentParent()->SaveRecording(file, aRetval);
}
NS_IMETHODIMP
TabParent::SuppressDisplayport(bool aEnabled)
{

Просмотреть файл

@ -849,6 +849,8 @@ description =
description =
[PContent::BridgeToChildProcess]
description =
[PContent::OpenRecordReplayChannel]
description = bug 1475898 this could be async
[PContent::LoadPlugin]
description =
[PContent::ConnectPluginBridge]

Просмотреть файл

@ -951,7 +951,7 @@ nsXULAppInfo::EnsureContentProcess()
return NS_ERROR_NOT_AVAILABLE;
RefPtr<ContentParent> unused = ContentParent::GetNewOrUsedBrowserProcess(
NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE));
nullptr, NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE));
return NS_OK;
}

Просмотреть файл

@ -982,7 +982,7 @@ TestShellParent* GetOrCreateTestShellParent()
// processes.
RefPtr<ContentParent> parent =
ContentParent::GetNewOrUsedBrowserProcess(
NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE));
nullptr, NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE));
parent.forget(&gContentParent);
} else if (!gContentParent->IsAlive()) {
return nullptr;

Просмотреть файл

@ -1982,6 +1982,8 @@ GK_ATOM(DisplayPortMargins, "_displayportmargins")
GK_ATOM(DisplayPortBase, "_displayportbase")
GK_ATOM(forcemessagemanager, "forcemessagemanager")
GK_ATOM(preloadedState, "preloadedState")
GK_ATOM(RecordExecution, "recordExecution")
GK_ATOM(ReplayExecution, "replayExecution")
// Names for system metrics
GK_ATOM(scrollbar_start_backward, "scrollbar-start-backward")