/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/ContentParent.h" #include "mozilla/ipc/BackgroundParent.h" #include "mozilla/ipc/PBackgroundParent.h" #include "mozilla/unused.h" #include "nsThreadUtils.h" #include "NuwaParent.h" using namespace mozilla::ipc; using namespace mozilla::dom; using namespace IPC; namespace mozilla { namespace dom { /*static*/ NuwaParent* NuwaParent::Alloc() { RefPtr actor = new NuwaParent(); return actor.forget().take(); } /*static*/ bool NuwaParent::ActorConstructed(mozilla::dom::PNuwaParent *aActor) { NuwaParent* actor = static_cast(aActor); actor->ActorConstructed(); return true; } /*static*/ bool NuwaParent::Dealloc(mozilla::dom::PNuwaParent *aActor) { RefPtr actor = dont_AddRef(static_cast(aActor)); return true; } NuwaParent::NuwaParent() : mBlocked(false) , mMonitor("NuwaParent") , mClonedActor(nullptr) , mWorkerThread(do_GetCurrentThread()) , mNewProcessPid(0) { AssertIsOnBackgroundThread(); } NuwaParent::~NuwaParent() { // Both the worker thread and the main thread (ContentParent) hold a ref to // this. The instance may be destroyed on either thread. MOZ_ASSERT(!mContentParent); } inline void NuwaParent::AssertIsOnWorkerThread() { nsCOMPtr currentThread = do_GetCurrentThread(); MOZ_ASSERT(currentThread == mWorkerThread); } bool NuwaParent::ActorConstructed() { AssertIsOnWorkerThread(); MOZ_ASSERT(Manager()); MOZ_ASSERT(!mContentParent); mContentParent = BackgroundParent::GetContentParent(Manager()); if (!mContentParent) { return false; } // mContentParent is guaranteed to be alive. It's safe to set its backward ref // to this. mContentParent->SetNuwaParent(this); return true; } mozilla::ipc::IProtocol* NuwaParent::CloneProtocol(Channel* aChannel, ProtocolCloneContext* aCtx) { MOZ_ASSERT(NS_IsMainThread()); RefPtr self = this; MonitorAutoLock lock(mMonitor); // Alloc NuwaParent on the worker thread. nsCOMPtr runnable = NS_NewRunnableFunction([self] () -> void { MonitorAutoLock lock(self->mMonitor); // XXX Calling NuwaParent::Alloc() leads to a compilation error. Use // self->Alloc() as a workaround. self->mClonedActor = self->Alloc(); lock.Notify(); }); MOZ_ASSERT(runnable); MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mWorkerThread->Dispatch(runnable, NS_DISPATCH_NORMAL))); while (!mClonedActor) { lock.Wait(); } RefPtr actor = mClonedActor; mClonedActor = nullptr; // mManager of the cloned actor is assigned after returning from this method. // We can't call ActorConstructed() right after Alloc() in the above runnable. // To be safe we dispatch a runnable to the current thread to do it. runnable = NS_NewRunnableFunction([actor] () -> void { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr nested = NS_NewRunnableFunction([actor] () -> void { AssertIsOnBackgroundThread(); // Call NuwaParent::ActorConstructed() on the worker thread. actor->ActorConstructed(); // The actor can finally be deleted after fully constructed. mozilla::unused << actor->Send__delete__(actor); }); MOZ_ASSERT(nested); MOZ_ALWAYS_TRUE(NS_SUCCEEDED( actor->mWorkerThread->Dispatch(nested, NS_DISPATCH_NORMAL))); }); MOZ_ASSERT(runnable); MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable))); return actor; } void NuwaParent::ActorDestroy(ActorDestroyReason aWhy) { AssertIsOnWorkerThread(); RefPtr self = this; nsCOMPtr runnable = NS_NewRunnableFunction([self] () -> void { // These extra nsRefPtr serve as kungFuDeathGrip to keep both objects from // deletion in breaking the ref cycle. RefPtr contentParent = self->mContentParent; contentParent->SetNuwaParent(nullptr); // Need to clear the ref to ContentParent on the main thread. self->mContentParent = nullptr; }); MOZ_ASSERT(runnable); MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable))); } bool NuwaParent::RecvNotifyReady() { #ifdef MOZ_NUWA_PROCESS if (!mContentParent || !mContentParent->IsNuwaProcess()) { NS_ERROR("Received NotifyReady() message from a non-Nuwa process."); return false; } // Creating a NonOwningRunnableMethod here is safe because refcount changes of // mContentParent have to go the the main thread. The mContentParent will // be alive when the runnable runs. nsCOMPtr runnable = NS_NewNonOwningRunnableMethod(mContentParent.get(), &ContentParent::OnNuwaReady); MOZ_ASSERT(runnable); MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable))); return true; #else NS_ERROR("NuwaParent::RecvNotifyReady() not implemented!"); return false; #endif } bool NuwaParent::RecvAddNewProcess(const uint32_t& aPid, nsTArray&& aFds) { #ifdef MOZ_NUWA_PROCESS if (!mContentParent || !mContentParent->IsNuwaProcess()) { NS_ERROR("Received AddNewProcess() message from a non-Nuwa process."); return false; } mNewProcessPid = aPid; mNewProcessFds->SwapElements(aFds); MonitorAutoLock lock(mMonitor); if (mBlocked) { // Unblock ForkNewProcess(). mMonitor.Notify(); mBlocked = false; } else { nsCOMPtr runnable = NS_NewNonOwningRunnableMethodWithArgs< uint32_t, UniquePtr>&& >( mContentParent.get(), &ContentParent::OnNewProcessCreated, mNewProcessPid, Move(mNewProcessFds)); MOZ_ASSERT(runnable); MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable))); } return true; #else NS_ERROR("NuwaParent::RecvAddNewProcess() not implemented!"); return false; #endif } bool NuwaParent::ForkNewProcess(uint32_t& aPid, UniquePtr>&& aFds, bool aBlocking) { MOZ_ASSERT(mWorkerThread); MOZ_ASSERT(NS_IsMainThread()); mNewProcessFds = Move(aFds); RefPtr self = this; nsCOMPtr runnable = NS_NewRunnableFunction([self] () -> void { mozilla::unused << self->SendFork(); }); MOZ_ASSERT(runnable); MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mWorkerThread->Dispatch(runnable, NS_DISPATCH_NORMAL))); if (!aBlocking) { return false; } MonitorAutoLock lock(mMonitor); mBlocked = true; while (mBlocked) { // This will be notified in NuwaParent::RecvAddNewProcess(). lock.Wait(); } if (!mNewProcessPid) { return false; } aPid = mNewProcessPid; aFds = Move(mNewProcessFds); mNewProcessPid = 0; return true; } } // namespace dom } // namespace mozilla