Bug 1559841. Make the 'load' event wait for OOP-iframes to load. r=farre
authorJonathan Watt <jwatt@jwatt.org>
Sat, 24 Aug 2019 00:03:54 +0000
changeset 553435 20abb86e00492710a3774d5e5c08b549b5672980
parent 553434 0b04d121c56c021ba390946e3fa0d5c3c44ddfa9
child 553436 c5adf2e84f978973f8b9aacb15bdca91511d64df
push id2165
push userffxbld-merge
push dateMon, 14 Oct 2019 16:30:58 +0000
treeherdermozilla-release@0eae18af659f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfarre
bugs1559841
milestone70.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1559841. Make the 'load' event wait for OOP-iframes to load. r=farre Differential Revision: https://phabricator.services.mozilla.com/D41953
dom/ipc/BrowserBridgeChild.cpp
dom/ipc/BrowserBridgeChild.h
dom/ipc/ContentChild.cpp
uriloader/base/nsDocLoader.cpp
uriloader/base/nsDocLoader.h
--- a/dom/ipc/BrowserBridgeChild.cpp
+++ b/dom/ipc/BrowserBridgeChild.cpp
@@ -153,16 +153,18 @@ mozilla::ipc::IPCResult BrowserBridgeChi
 
   // Fire the `load` event on our embedder element.
   nsEventStatus status = nsEventStatus_eIgnore;
   WidgetEvent event(aIsTrusted, eLoad);
   event.mFlags.mBubbles = false;
   event.mFlags.mCancelable = false;
   EventDispatcher::Dispatch(owner, nullptr, &event, nullptr, &status);
 
+  UnblockOwnerDocsLoadEvent(owner->OwnerDoc());
+
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult BrowserBridgeChild::RecvScrollRectIntoView(
     const nsRect& aRect, const ScrollAxis& aVertical,
     const ScrollAxis& aHorizontal, const ScrollFlags& aScrollFlags,
     const int32_t& aAppUnitsPerDevPixel) {
   RefPtr<Element> owner = mFrameLoader->GetOwnerContent();
@@ -190,12 +192,26 @@ mozilla::ipc::IPCResult BrowserBridgeChi
   presShell->ScrollFrameRectIntoView(frame, rect, aVertical, aHorizontal,
                                      aScrollFlags);
 
   return IPC_OK();
 }
 
 void BrowserBridgeChild::ActorDestroy(ActorDestroyReason aWhy) {
   mIPCOpen = false;
+
+  // Ensure we unblock our document's 'load' event (in case the OOP-iframe has
+  // been removed before it finishes loading, or its subprocess crashed):
+  if (RefPtr<Element> owner = mFrameLoader->GetOwnerContent()) {
+    UnblockOwnerDocsLoadEvent(owner->OwnerDoc());
+  }
+}
+
+void BrowserBridgeChild::UnblockOwnerDocsLoadEvent(Document* aOwnerDoc) {
+  // XXX bug 1576296: Is it expected that we sometimes don't have a docShell?
+  if (!mHadInitialLoad && aOwnerDoc->GetDocShell()) {
+    mHadInitialLoad = true;
+    nsDocShell::Cast(aOwnerDoc->GetDocShell())->OOPChildLoadDone(this);
+  }
 }
 
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/ipc/BrowserBridgeChild.h
+++ b/dom/ipc/BrowserBridgeChild.h
@@ -15,16 +15,17 @@ namespace mozilla {
 
 namespace a11y {
 class RemoteIframeDocProxyAccessibleWrap;
 }
 
 namespace dom {
 class BrowsingContext;
 class ContentChild;
+class Document;
 
 /**
  * BrowserBridgeChild implements the child actor part of the PBrowserBridge
  * protocol. See PBrowserBridge for more information.
  */
 class BrowserBridgeChild : public PBrowserBridgeChild {
  public:
   typedef mozilla::layers::LayersId LayersId;
@@ -91,19 +92,22 @@ class BrowserBridgeChild : public PBrows
       const ScrollAxis& aHorizontal, const ScrollFlags& aScrollFlags,
       const int32_t& aAppUnitsPerDevPixel);
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
  private:
   ~BrowserBridgeChild();
 
+  void UnblockOwnerDocsLoadEvent(Document* aOwnerDoc);
+
   TabId mId;
   LayersId mLayersId;
   bool mIPCOpen;
+  bool mHadInitialLoad = false;
   RefPtr<nsFrameLoader> mFrameLoader;
   RefPtr<BrowsingContext> mBrowsingContext;
 #if defined(ACCESSIBILITY) && defined(XP_WIN)
   RefPtr<a11y::RemoteIframeDocProxyAccessibleWrap> mEmbeddedDocAccessible;
 #endif
 };
 
 }  // namespace dom
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -2089,16 +2089,20 @@ already_AddRefed<RemoteBrowser> ContentC
   }
   if (docShell->GetAffectPrivateSessionLifetime()) {
     chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_LIFETIME;
   }
 
   TabId tabId(nsContentUtils::GenerateTabId());
   RefPtr<BrowserBridgeChild> browserBridge =
       new BrowserBridgeChild(aFrameLoader, aBrowsingContext, tabId);
+  // XXX bug 1576296: Figure out why we intermittently we don't have a docShell
+  if (auto docShell = owner->OwnerDoc()->GetDocShell()) {
+    nsDocShell::Cast(docShell)->OOPChildLoadStarted(browserBridge);
+  }
   browserChild->SendPBrowserBridgeConstructor(
       browserBridge, PromiseFlatString(aContext.PresentationURL()), aRemoteType,
       aBrowsingContext, chromeFlags, tabId);
   browserBridge->mIPCOpen = true;
 
 #if defined(ACCESSIBILITY)
   a11y::DocAccessible* docAcc =
       a11y::GetExistingDocAccessible(owner->OwnerDoc());
--- a/uriloader/base/nsDocLoader.cpp
+++ b/uriloader/base/nsDocLoader.cpp
@@ -238,16 +238,17 @@ nsDocLoader::Stop(void) {
   // Stop call.
   mIsFlushingLayout = false;
 
   // Clear out mChildrenInOnload.  We want to make sure to fire our
   // onload at this point, and there's no issue with mChildrenInOnload
   // after this, since mDocumentRequest will be null after the
   // DocLoaderIsEmpty() call.
   mChildrenInOnload.Clear();
+  mOOPChildrenLoading.Clear();
 
   // Make sure to call DocLoaderIsEmpty now so that we reset mDocumentRequest,
   // etc, as needed.  We could be getting into here from a subframe onload, in
   // which case the call to DocLoaderIsEmpty() is coming but hasn't quite
   // happened yet, Canceling the loadgroup did nothing (because it was already
   // empty), and we're about to start a new load (which is what triggered this
   // Stop() call).
 
@@ -272,25 +273,27 @@ bool nsDocLoader::IsBusy() {
   //
   //   1. One of its children is in the middle of an onload handler.  Note that
   //      the handler may have already removed this child from mChildList!
   //   2. It is currently loading a document and either has parts of it still
   //      loading, or has a busy child docloader.
   //   3. It's currently flushing layout in DocLoaderIsEmpty().
   //
 
-  if (mChildrenInOnload.Count() || mIsFlushingLayout) {
+  if (!mChildrenInOnload.IsEmpty() || !mOOPChildrenLoading.IsEmpty() ||
+      mIsFlushingLayout) {
     return true;
   }
 
   /* Is this document loader busy? */
   if (!IsBlockingLoadEvent()) {
     return false;
   }
 
+  // Check if any in-process sub-document is awaiting its 'load' event:
   bool busy;
   rv = mLoadGroup->IsPending(&busy);
   if (NS_FAILED(rv)) {
     return false;
   }
   if (busy) {
     return true;
   }
--- a/uriloader/base/nsDocLoader.h
+++ b/uriloader/base/nsDocLoader.h
@@ -26,16 +26,22 @@
 #include "nsISupportsPriority.h"
 #include "nsCOMPtr.h"
 #include "PLDHashTable.h"
 #include "nsAutoPtr.h"
 #include "nsCycleCollectionParticipant.h"
 
 #include "mozilla/LinkedList.h"
 
+namespace mozilla {
+namespace dom {
+class BrowserBridgeChild;
+}  // namespace dom
+}  // namespace mozilla
+
 /****************************************************************************
  * nsDocLoader implementation...
  ****************************************************************************/
 
 #define NS_THIS_DOCLOADER_IMPL_CID                   \
   { /* b4ec8387-98aa-4c08-93b6-6d23069c06f2 */       \
     0xb4ec8387, 0x98aa, 0x4c08, {                    \
       0x93, 0xb6, 0x6d, 0x23, 0x06, 0x9c, 0x06, 0xf2 \
@@ -46,16 +52,18 @@ class nsDocLoader : public nsIDocumentLo
                     public nsIRequestObserver,
                     public nsSupportsWeakReference,
                     public nsIProgressEventSink,
                     public nsIWebProgress,
                     public nsIInterfaceRequestor,
                     public nsIChannelEventSink,
                     public nsISupportsPriority {
  public:
+  typedef mozilla::dom::BrowserBridgeChild BrowserBridgeChild;
+
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_THIS_DOCLOADER_IMPL_CID)
 
   nsDocLoader();
 
   virtual MOZ_MUST_USE nsresult Init();
 
   static already_AddRefed<nsDocLoader> GetAsDocLoader(nsISupports* aSupports);
   // Needed to deal with ambiguous inheritance from nsISupports...
@@ -130,16 +138,37 @@ class nsDocLoader : public nsIDocumentLo
   bool HasFakeOnLoadDispatched() { return mHasFakeOnLoadDispatched; };
 
   void ResetToFirstLoad() {
     mHasFakeOnLoadDispatched = false;
     mIsReadyToHandlePostMessage = false;
     mTreatAsBackgroundLoad = false;
   };
 
+  // Inform a parent docloader that a BrowserBridgeChild has been created for
+  // an OOP sub-document.
+  // (This is the OOP counterpart to ChildEnteringOnload below.)
+  void OOPChildLoadStarted(BrowserBridgeChild* aChild) {
+    MOZ_ASSERT(!mOOPChildrenLoading.Contains(aChild));
+    mOOPChildrenLoading.AppendElement(aChild);
+  }
+
+  // Inform a parent docloader that the BrowserBridgeChild for one of its
+  // OOP sub-documents is done calling its onload handler.
+  // (This is the OOP counterpart to ChildDoneWithOnload below.)
+  void OOPChildLoadDone(BrowserBridgeChild* aChild) {
+    // aChild may not be in the list if nsDocLoader::Stop was called.  We don't
+    // want to unblock 'load' for a different document than the one that was
+    // loading the OOP-iframe so we need to check if RemoveElement returns
+    // false.
+    if (mOOPChildrenLoading.RemoveElement(aChild)) {
+      DocLoaderIsEmpty(true);
+    }
+  }
+
  protected:
   virtual ~nsDocLoader();
 
   virtual MOZ_MUST_USE nsresult SetDocLoaderParent(nsDocLoader* aLoader);
 
   bool IsBusy();
 
   void SetBackgroundLoadIframe();
@@ -336,16 +365,20 @@ class nsDocLoader : public nsIDocumentLo
   static const PLDHashTableOps sRequestInfoHashOps;
 
   // A list of kids that are in the middle of their onload calls and will let
   // us know once they're done.  We don't want to fire onload for "normal"
   // DocLoaderIsEmpty calls (those coming from requests finishing in our
   // loadgroup) unless this is empty.
   nsCOMArray<nsIDocumentLoader> mChildrenInOnload;
 
+  // The OOP counterpart to mChildrenInOnload.
+  // Not holding strong refs here since we don't actually use the BBCs.
+  nsTArray<const BrowserBridgeChild*> mOOPChildrenLoading;
+
   int64_t GetMaxTotalProgress();
 
   nsresult AddRequestInfo(nsIRequest* aRequest);
   void RemoveRequestInfo(nsIRequest* aRequest);
   nsRequestInfo* GetRequestInfo(nsIRequest* aRequest) const;
   void ClearRequestInfoHash();
   int64_t CalculateMaxProgress();
   ///    void DumpChannelInfo(void);