Bug 1342927 - Fire a MozTabChildNotReady event on a frameloader if force-painting a tab without a TabChild. r?billm draft
authorMike Conley <mconley@mozilla.com>
Tue, 28 Feb 2017 17:22:02 -0500
changeset 493710 69647e68a4d1ce0c6afd734822df0b8905a3b286
parent 493537 eb23648534779c110f3a1f2baae1849ae4a9c570
child 493711 5271f4ecec4ad889051007272541318e91db0186
push id47824
push usermconley@mozilla.com
push dateSun, 05 Mar 2017 18:35:35 +0000
reviewersbillm
bugs1342927
milestone54.0a1
Bug 1342927 - Fire a MozTabChildNotReady event on a frameloader if force-painting a tab without a TabChild. r?billm MozReview-Commit-ID: D8vgvQ3MLJN
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
dom/ipc/PProcessHangMonitor.ipdl
dom/ipc/ProcessHangMonitor.cpp
dom/ipc/TabParent.cpp
dom/ipc/TabParent.h
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -4245,16 +4245,33 @@ ContentParent::RecvDeallocateTabId(const
 mozilla::ipc::IPCResult
 ContentParent::RecvNotifyTabDestroying(const TabId& aTabId,
                                        const ContentParentId& aCpId)
 {
   NotifyTabDestroying(aTabId, aCpId);
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult
+ContentParent::RecvTabChildNotReady(const TabId& aTabId)
+{
+  ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
+  RefPtr<TabParent> tp =
+    cpm->GetTopLevelTabParentByProcessAndTabId(this->ChildID(), aTabId);
+
+  if (!tp) {
+    NS_WARNING("Couldn't find TabParent for TabChildNotReady message.");
+    return IPC_OK();
+  }
+
+  tp->DispatchTabChildNotReadyEvent();
+
+  return IPC_OK();
+}
+
 nsTArray<TabContext>
 ContentParent::GetManagedTabContext()
 {
   return Move(ContentProcessManager::GetSingleton()->
           GetTabContextByContentProcess(this->ChildID()));
 }
 
 mozilla::docshell::POfflineCacheUpdateParent*
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -486,16 +486,18 @@ public:
 
   virtual mozilla::ipc::IPCResult RecvDeallocateTabId(const TabId& aTabId,
                                                       const ContentParentId& aCpId,
                                                       const bool& aMarkedDestroying) override;
 
   virtual mozilla::ipc::IPCResult RecvNotifyTabDestroying(const TabId& aTabId,
                                                           const ContentParentId& aCpId) override;
 
+  virtual mozilla::ipc::IPCResult RecvTabChildNotReady(const TabId& aTabId) override;
+
   nsTArray<TabContext> GetManagedTabContext();
 
   virtual POfflineCacheUpdateParent*
   AllocPOfflineCacheUpdateParent(const URIParams& aManifestURI,
                                  const URIParams& aDocumentURI,
                                  const PrincipalInfo& aLoadingPrincipalInfo,
                                  const bool& aStickDocument) override;
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -1017,16 +1017,19 @@ parent:
                           ContentParentId cpId,
                           bool aMarkedDestroying);
 
     /**
      * Tell the chrome process there is a destruction of PBrowser(Tab)
      */
     async NotifyTabDestroying(TabId tabId,
                               ContentParentId cpId);
+
+    async TabChildNotReady(TabId tabId);
+
     /**
      * Starts an offline application cache update.
      * @param manifestURI
      *   URI of the manifest to fetch, the application cache group ID
      * @param documentURI
      *   URI of the document that referred the manifest
      * @param loadingPrincipal
      *   Principal of the document that referred the manifest
--- a/dom/ipc/PProcessHangMonitor.ipdl
+++ b/dom/ipc/PProcessHangMonitor.ipdl
@@ -29,16 +29,17 @@ union HangData
   PluginHangData;
 };
 
 protocol PProcessHangMonitor
 {
 parent:
   async HangEvidence(HangData data);
   async ClearHang();
+  async Ready();
 
 child:
   async TerminateScript();
 
   async BeginStartingDebugger();
   async EndStartingDebugger();
 
   async ForcePaint(TabId tabId, uint64_t aLayerObserverEpoch);
--- a/dom/ipc/ProcessHangMonitor.cpp
+++ b/dom/ipc/ProcessHangMonitor.cpp
@@ -7,21 +7,23 @@
 #include "mozilla/ProcessHangMonitor.h"
 #include "mozilla/ProcessHangMonitorIPC.h"
 
 #include "jsapi.h"
 #include "js/GCAPI.h"
 
 #include "mozilla/Atomics.h"
 #include "mozilla/BackgroundHangMonitor.h"
+#include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/TabParent.h"
+#include "mozilla/ipc/TaskFactory.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/plugins/PluginBridge.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Unused.h"
 
 #include "nsIFrameLoader.h"
 #include "nsIHangReport.h"
 #include "nsITabParent.h"
@@ -189,16 +191,18 @@ public:
     mDumpId = aDumpId;
   }
 
   void ClearHang() {
     mHangData = HangData();
     mDumpId.Truncate();
   }
 
+  void DispatchTabChildNotReady(TabId aTabId);
+
 private:
   ~HangMonitoredProcess() = default;
 
   // Everything here is main thread-only.
   HangMonitorParent* mActor;
   ContentParent* mContentParent;
   HangData mHangData;
   nsAutoString mDumpId;
@@ -208,16 +212,17 @@ class HangMonitorParent
   : public PProcessHangMonitorParent
 {
 public:
   explicit HangMonitorParent(ProcessHangMonitor* aMonitor);
   ~HangMonitorParent() override;
 
   void Bind(Endpoint<PProcessHangMonitorParent>&& aEndpoint);
 
+  mozilla::ipc::IPCResult RecvReady() override;
   mozilla::ipc::IPCResult RecvHangEvidence(const HangData& aHangData) override;
   mozilla::ipc::IPCResult RecvClearHang() override;
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
   void SetProcess(HangMonitoredProcess* aProcess) { mProcess = aProcess; }
 
   void Shutdown();
@@ -236,36 +241,45 @@ public:
    */
   void UpdateMinidump(uint32_t aPluginId, const nsString& aDumpId);
 
   MessageLoop* MonitorLoop() { return mHangMonitor->MonitorLoop(); }
 
 private:
   bool TakeBrowserMinidump(const PluginHangData& aPhd, nsString& aCrashId);
 
+  void DispatchTabChildNotReady(TabId aTabId);
+
   void ForcePaintOnThread(TabId aTabId, uint64_t aLayerObserverEpoch);
 
   void ShutdownOnThread();
 
   const RefPtr<ProcessHangMonitor> mHangMonitor;
 
   // This field is read-only after construction.
   bool mReportHangs;
 
+  // This field is only accessed on the hang thread. Inits to
+  // false, and will flip to true once the HangMonitorChild is
+  // constructed in the child process, and sends a message saying
+  // so.
+  bool mReady;
+
   // This field is only accessed on the hang thread.
   bool mIPCOpen;
 
   Monitor mMonitor;
 
   // Must be accessed with mMonitor held.
   RefPtr<HangMonitoredProcess> mProcess;
   bool mShutdownDone;
   // Map from plugin ID to crash dump ID. Protected by mBrowserCrashDumpHashLock.
   nsDataHashtable<nsUint32HashKey, nsString> mBrowserCrashDumpIds;
   Mutex mBrowserCrashDumpHashLock;
+  mozilla::ipc::TaskFactory<HangMonitorParent> mMainThreadTaskFactory;
 };
 
 } // namespace
 
 /* HangMonitorChild implementation */
 
 HangMonitorChild::HangMonitorChild(ProcessHangMonitor* aMonitor)
  : mHangMonitor(aMonitor),
@@ -313,16 +327,21 @@ HangMonitorChild::InterruptCallback()
     mForcePaint = false;
   }
 
   if (forcePaint) {
     RefPtr<TabChild> tabChild = TabChild::FindTabChild(forcePaintTab);
     if (tabChild) {
       js::AutoAssertNoContentJS nojs(mContext);
       tabChild->ForcePaint(forcePaintEpoch);
+    } else {
+      auto cc = ContentChild::GetSingleton();
+      if (cc) {
+        cc->SendTabChildNotReady(forcePaintTab);
+      }
     }
   }
 }
 
 void
 HangMonitorChild::Shutdown()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
@@ -418,16 +437,18 @@ HangMonitorChild::Bind(Endpoint<PProcess
 {
   MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
 
   MOZ_ASSERT(!sInstance);
   sInstance = this;
 
   DebugOnly<bool> ok = aEndpoint.Bind(this);
   MOZ_ASSERT(ok);
+
+  Unused << SendReady();
 }
 
 void
 HangMonitorChild::NotifySlowScriptAsync(TabId aTabId,
                                         const nsCString& aFileName,
                                         unsigned aLineNo)
 {
   if (mIPCOpen) {
@@ -540,20 +561,22 @@ HangMonitorChild::ClearHangAsync()
     Unused << SendClearHang();
   }
 }
 
 /* HangMonitorParent implementation */
 
 HangMonitorParent::HangMonitorParent(ProcessHangMonitor* aMonitor)
  : mHangMonitor(aMonitor),
+   mReady(false),
    mIPCOpen(true),
    mMonitor("HangMonitorParent lock"),
    mShutdownDone(false),
-   mBrowserCrashDumpHashLock("mBrowserCrashDumpIds lock")
+   mBrowserCrashDumpHashLock("mBrowserCrashDumpIds lock"),
+   mMainThreadTaskFactory(this)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   mReportHangs = mozilla::Preferences::GetBool("dom.ipc.reportProcessHangs", false);
 }
 
 HangMonitorParent::~HangMonitorParent()
 {
 #ifdef MOZ_CRASHREPORTER
@@ -610,22 +633,47 @@ HangMonitorParent::ForcePaint(dom::TabPa
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   TabId id = aTab->GetTabId();
   MonitorLoop()->PostTask(NewNonOwningRunnableMethod<TabId, uint64_t>(
                             this, &HangMonitorParent::ForcePaintOnThread, id, aLayerObserverEpoch));
 }
 
 void
+HangMonitorParent::DispatchTabChildNotReady(TabId aTabId)
+{
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+  if (!mProcess) {
+    return;
+  }
+
+  mProcess->DispatchTabChildNotReady(aTabId);
+}
+
+void
 HangMonitorParent::ForcePaintOnThread(TabId aTabId, uint64_t aLayerObserverEpoch)
 {
   MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
 
   if (mIPCOpen) {
-    Unused << SendForcePaint(aTabId, aLayerObserverEpoch);
+    if (mReady) {
+      Unused << SendForcePaint(aTabId, aLayerObserverEpoch);
+    } else {
+      // We've never heard from the HangMonitorChild before, so
+      // it's either not finished setting up, or has only recently
+      // finished setting up. In either case, we're dealing with
+      // a new content process that probably hasn't had time to
+      // get the ContentChild, let alone the TabChild for aTabId,
+      // set up, and so attempting to force paint on the non-existant
+      // TabChild is not going to work. Instead, we tell the main
+      // thread that we're waiting on a TabChild to be created.
+      NS_DispatchToMainThread(
+        mMainThreadTaskFactory.NewRunnableMethod(
+          &HangMonitorParent::DispatchTabChildNotReady, aTabId));
+    }
   }
 }
 
 void
 HangMonitorParent::ActorDestroy(ActorDestroyReason aWhy)
 {
   MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
   mIPCOpen = false;
@@ -714,16 +762,24 @@ HangMonitorParent::TakeBrowserMinidump(c
     }
   }
 #endif // MOZ_CRASHREPORTER
 
   return false;
 }
 
 mozilla::ipc::IPCResult
+HangMonitorParent::RecvReady()
+{
+  MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
+  mReady = true;
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
 HangMonitorParent::RecvHangEvidence(const HangData& aHangData)
 {
   // chrome process, background thread
   MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
 
   if (!mReportHangs) {
     return IPC_OK();
   }
@@ -1053,16 +1109,27 @@ HangMonitoredProcess::UserCanceled()
 
   if (mActor) {
     uint32_t id = mHangData.get_PluginHangData().pluginId();
     mActor->CleanupPluginHang(id, true);
   }
   return NS_OK;
 }
 
+void
+HangMonitoredProcess::DispatchTabChildNotReady(TabId aTabId)
+{
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+  if (!mContentParent) {
+    return;
+  }
+
+  Unused << mContentParent->RecvTabChildNotReady(aTabId);
+}
+
 static bool
 InterruptCallback(JSContext* cx)
 {
   if (HangMonitorChild* child = HangMonitorChild::Get()) {
     child->InterruptCallback();
   }
 
   return true;
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -3327,16 +3327,35 @@ TabParent::LiveResizeStarted()
 }
 
 void
 TabParent::LiveResizeStopped()
 {
   SuppressDisplayport(false);
 }
 
+void
+TabParent::DispatchTabChildNotReadyEvent()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsCOMPtr<mozilla::dom::EventTarget> target = do_QueryInterface(mFrameElement);
+  if (!target) {
+    NS_WARNING("Could not locate target for tab child not ready event.");
+    return;
+  }
+
+  RefPtr<Event> event = NS_NewDOMEvent(mFrameElement, nullptr, nullptr);
+  event->InitEvent(NS_LITERAL_STRING("MozTabChildNotReady"), true, false);
+  event->SetTrusted(true);
+  event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
+  bool dummy;
+  mFrameElement->DispatchEvent(event, &dummy);
+}
+
 NS_IMETHODIMP
 FakeChannel::OnAuthAvailable(nsISupports *aContext, nsIAuthInformation *aAuthInfo)
 {
   nsAuthInformationHolder* holder =
     static_cast<nsAuthInformationHolder*>(aAuthInfo);
 
   if (!net::gNeckoChild->SendOnAuthAvailable(mCallbackId,
                                              holder->User(),
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -594,16 +594,18 @@ public:
                           uint64_t* aLayersId);
 
   mozilla::ipc::IPCResult RecvEnsureLayersConnected() override;
 
   // LiveResizeListener implementation
   void LiveResizeStarted() override;
   void LiveResizeStopped() override;
 
+  void DispatchTabChildNotReadyEvent();
+
 protected:
   bool ReceiveMessage(const nsString& aMessage,
                       bool aSync,
                       ipc::StructuredCloneData* aData,
                       mozilla::jsipc::CpowHolder* aCpows,
                       nsIPrincipal* aPrincipal,
                       nsTArray<ipc::StructuredCloneData>* aJSONRetVal = nullptr);