From b5b7f6b7283cfaafd6567f8188b38fe055d78e7b Mon Sep 17 00:00:00 2001 From: Nika Layzell Date: Thu, 6 Jun 2019 14:57:17 +0000 Subject: [PATCH] Bug 1451455 - Fire the 'load' event on out-of-process iframes, r=rhunt This is done by sending a message over PBrowser and PBrowserBridge when the event would fire to fire it in the correct process. Differential Revision: https://phabricator.services.mozilla.com/D33083 --HG-- extra : moz-landing-system : lando --- dom/base/nsGlobalWindowInner.cpp | 70 ++++++++++++++++++------- dom/base/nsGlobalWindowInner.h | 3 ++ dom/base/nsPIDOMWindow.h | 7 +++ dom/base/nsPIDOMWindowInlines.h | 4 ++ dom/html/test/test_ignoreuserfocus.html | 6 ++- dom/ipc/BrowserBridgeChild.cpp | 17 ++++++ dom/ipc/BrowserBridgeChild.h | 2 + dom/ipc/BrowserParent.cpp | 11 ++++ dom/ipc/BrowserParent.h | 2 + dom/ipc/PBrowser.ipdl | 5 ++ dom/ipc/PBrowserBridge.ipdl | 5 ++ 11 files changed, 112 insertions(+), 20 deletions(-) diff --git a/dom/base/nsGlobalWindowInner.cpp b/dom/base/nsGlobalWindowInner.cpp index 9ea84587c1a7..cf98202ac357 100644 --- a/dom/base/nsGlobalWindowInner.cpp +++ b/dom/base/nsGlobalWindowInner.cpp @@ -261,6 +261,7 @@ #include "mozilla/dom/ClientState.h" #include "mozilla/dom/WindowGlobalChild.h" +#include "mozilla/dom/BrowserChild.h" #include "mozilla/net/CookieSettings.h" @@ -1917,6 +1918,54 @@ bool nsGlobalWindowInner::DialogsAreBeingAbused() { return false; } +void nsGlobalWindowInner::FireFrameLoadEvent(bool aIsTrusted) { + // If we're not in a content frame, or are at a BrowsingContext tree boundary, + // such as the content-chrome boundary, don't fire the "load" event. + if (!GetBrowsingContext()->GetParent() || + !GetBrowsingContext()->IsContent()) { + return; + } + + // If embedder is same-process, fire the event on our embedder element. + // + // XXX: Bug 1440212 is looking into potentially changing this behaviour to act + // more like the remote case when in-process. + RefPtr element = GetBrowsingContext()->GetEmbedderElement(); + if (element) { + nsEventStatus status = nsEventStatus_eIgnore; + WidgetEvent event(aIsTrusted, eLoad); + event.mFlags.mBubbles = false; + event.mFlags.mCancelable = false; + + // Most of the time we could get a pres context to pass in here, + // but not always (i.e. if this window is not shown there won't + // be a pres context available). Since we're not firing a GUI + // event we don't need a pres context anyway so we just pass + // null as the pres context all the time here. + EventDispatcher::Dispatch(element, nullptr, &event, nullptr, &status); + return; + } + + // We don't have an in-process embedder. Try to get our `BrowserChild` actor + // to send a message to that embedder. We want to double-check that our outer + // window is actually the one at the root of this browserChild though, just in + // case. + RefPtr browserChild = + BrowserChild::GetFrom(static_cast(this)); + if (browserChild) { + // Double-check that our outer window is actually at the root of this + // `BrowserChild`, in case we're in an odd maybe-unhosted situation like a + // print preview dialog. + nsCOMPtr rootOuter = + do_GetInterface(browserChild->WebNavigation()); + if (!rootOuter || rootOuter != GetOuterWindow()) { + return; + } + + mozilla::Unused << browserChild->SendFireFrameLoadEvent(aIsTrusted); + } +} + nsresult nsGlobalWindowInner::PostHandleEvent(EventChainPostVisitor& aVisitor) { // Return early if there is nothing to do. switch (aVisitor.mEvent->mMessage) { @@ -1977,25 +2026,7 @@ nsresult nsGlobalWindowInner::PostHandleEvent(EventChainPostVisitor& aVisitor) { mTimeoutManager->OnDocumentLoaded(); - nsCOMPtr element = GetOuterWindow()->GetFrameElementInternal(); - nsIDocShell* docShell = GetDocShell(); - if (element && GetParentInternal() && docShell && - docShell->ItemType() != nsIDocShellTreeItem::typeChrome) { - // If we're not in chrome, or at a chrome boundary, fire the - // onload event for the frame element. - - nsEventStatus status = nsEventStatus_eIgnore; - WidgetEvent event(aVisitor.mEvent->IsTrusted(), eLoad); - event.mFlags.mBubbles = false; - event.mFlags.mCancelable = false; - - // Most of the time we could get a pres context to pass in here, - // but not always (i.e. if this window is not shown there won't - // be a pres context available). Since we're not firing a GUI - // event we don't need a pres context anyway so we just pass - // null as the pres context all the time here. - EventDispatcher::Dispatch(element, nullptr, &event, nullptr, &status); - } + FireFrameLoadEvent(aVisitor.mEvent->IsTrusted()); if (mVREventObserver) { mVREventObserver->NotifyAfterLoad(); @@ -7083,6 +7114,7 @@ nsPIDOMWindowInner::nsPIDOMWindowInner(nsPIDOMWindowOuter* aOuterWindow) mNumOfOpenWebSockets(0), mEvent(nullptr) { MOZ_ASSERT(aOuterWindow); + mBrowsingContext = aOuterWindow->GetBrowsingContext(); } void nsPIDOMWindowInner::RegisterReportingObserver(ReportingObserver* aObserver, diff --git a/dom/base/nsGlobalWindowInner.h b/dom/base/nsGlobalWindowInner.h index 57f384d96e95..d5b44672c785 100644 --- a/dom/base/nsGlobalWindowInner.h +++ b/dom/base/nsGlobalWindowInner.h @@ -1197,6 +1197,9 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget, // activation flag. bool ShouldResetBrowsingContextUserGestureActivation(); + // Try to fire the "load" event on our content embedder if we're an iframe. + void FireFrameLoadEvent(bool aIsTrusted); + public: // Dispatch a runnable related to the global. virtual nsresult Dispatch(mozilla::TaskCategory aCategory, diff --git a/dom/base/nsPIDOMWindow.h b/dom/base/nsPIDOMWindow.h index 3663cd862233..1bc71eacd1cd 100644 --- a/dom/base/nsPIDOMWindow.h +++ b/dom/base/nsPIDOMWindow.h @@ -388,6 +388,11 @@ class nsPIDOMWindowInner : public mozIDOMWindow { */ inline nsIDocShell* GetDocShell() const; + /** + * Get the browsing context in this window. + */ + inline mozilla::dom::BrowsingContext* GetBrowsingContext() const; + /** * Call this to indicate that some node (this window, its document, * or content in that document) has a paint event listener. @@ -627,6 +632,8 @@ class nsPIDOMWindowInner : public mozIDOMWindow { RefPtr mTabGroup; + RefPtr mBrowsingContext; + // A unique (as long as our 64-bit counter doesn't roll over) id for // this window. uint64_t mWindowID; diff --git a/dom/base/nsPIDOMWindowInlines.h b/dom/base/nsPIDOMWindowInlines.h index a40cd75bd682..e98d71e74599 100644 --- a/dom/base/nsPIDOMWindowInlines.h +++ b/dom/base/nsPIDOMWindowInlines.h @@ -72,6 +72,10 @@ mozilla::dom::BrowsingContext* nsPIDOMWindowOuter::GetBrowsingContext() const { return mBrowsingContext; } +mozilla::dom::BrowsingContext* nsPIDOMWindowInner::GetBrowsingContext() const { + return mBrowsingContext; +} + mozilla::dom::Element* nsPIDOMWindowOuter::GetFocusedElement() const { return mInnerWindow ? mInnerWindow->GetFocusedElement() : nullptr; } diff --git a/dom/html/test/test_ignoreuserfocus.html b/dom/html/test/test_ignoreuserfocus.html index 691a558dc1b1..cec6163688ba 100644 --- a/dom/html/test/test_ignoreuserfocus.html +++ b/dom/html/test/test_ignoreuserfocus.html @@ -44,10 +44,14 @@ iframe.setAttribute("ignoreuserfocus", "true"); iframe.setAttribute("height", "300px"); - iframe.addEventListener('load', function (e) { + iframe.addEventListener('mozbrowserloadend', function (e) { // Get privileged iframe because mozbrowser iframe is not same origin // with the parent. We need to access its content through the wrapper. var privilegedIframe = SpecialPowers.wrap(iframe); + if (!privilegedIframe.contentWindow.location.href.endsWith("file_ignoreuserfocus.html")) { + return; + } + privilegedIframe.contentWindow.addEventListener("MozAfterPaint", function(e) { privilegedIframe.contentWindow.addEventListener("focus", function(e) { diff --git a/dom/ipc/BrowserBridgeChild.cpp b/dom/ipc/BrowserBridgeChild.cpp index 54eca7ff6814..b727899b8f34 100644 --- a/dom/ipc/BrowserBridgeChild.cpp +++ b/dom/ipc/BrowserBridgeChild.cpp @@ -140,6 +140,23 @@ BrowserBridgeChild::RecvSetEmbeddedDocAccessibleCOMProxy( return IPC_OK(); } +mozilla::ipc::IPCResult BrowserBridgeChild::RecvFireFrameLoadEvent( + bool aIsTrusted) { + RefPtr owner = mFrameLoader->GetOwnerContent(); + if (!owner) { + return IPC_OK(); + } + + // 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); + + return IPC_OK(); +} + void BrowserBridgeChild::ActorDestroy(ActorDestroyReason aWhy) { mIPCOpen = false; } diff --git a/dom/ipc/BrowserBridgeChild.h b/dom/ipc/BrowserBridgeChild.h index 9ad3fbcf339c..6ea26540268c 100644 --- a/dom/ipc/BrowserBridgeChild.h +++ b/dom/ipc/BrowserBridgeChild.h @@ -83,6 +83,8 @@ class BrowserBridgeChild : public PBrowserBridgeChild { mozilla::ipc::IPCResult RecvSetEmbeddedDocAccessibleCOMProxy( const IDispatchHolder& aCOMProxy); + mozilla::ipc::IPCResult RecvFireFrameLoadEvent(bool aIsTrusted); + void ActorDestroy(ActorDestroyReason aWhy) override; private: diff --git a/dom/ipc/BrowserParent.cpp b/dom/ipc/BrowserParent.cpp index 9ffceb47e9d3..a1dacd9e95a5 100644 --- a/dom/ipc/BrowserParent.cpp +++ b/dom/ipc/BrowserParent.cpp @@ -3699,6 +3699,17 @@ mozilla::ipc::IPCResult BrowserParent::RecvGetSystemFont(nsCString* aFontName) { return IPC_OK(); } +mozilla::ipc::IPCResult BrowserParent::RecvFireFrameLoadEvent(bool aIsTrusted) { + BrowserBridgeParent* bridge = GetBrowserBridgeParent(); + if (!bridge) { + NS_WARNING("Received `load` event on unbridged BrowserParent!"); + return IPC_OK(); + } + + Unused << bridge->SendFireFrameLoadEvent(aIsTrusted); + return IPC_OK(); +} + NS_IMETHODIMP FakeChannel::OnAuthAvailable(nsISupports* aContext, nsIAuthInformation* aAuthInfo) { diff --git a/dom/ipc/BrowserParent.h b/dom/ipc/BrowserParent.h index 0c7804fffd50..5c8495be8d4b 100644 --- a/dom/ipc/BrowserParent.h +++ b/dom/ipc/BrowserParent.h @@ -765,6 +765,8 @@ class BrowserParent final : public PBrowserParent, mozilla::ipc::IPCResult RecvQueryVisitedState( InfallibleTArray&& aURIs); + mozilla::ipc::IPCResult RecvFireFrameLoadEvent(bool aIsTrusted); + private: void SuppressDisplayport(bool aEnabled); diff --git a/dom/ipc/PBrowser.ipdl b/dom/ipc/PBrowser.ipdl index 5f059f95aaae..da2fce200db2 100644 --- a/dom/ipc/PBrowser.ipdl +++ b/dom/ipc/PBrowser.ipdl @@ -663,6 +663,11 @@ parent: sync SetPrefersReducedMotionOverrideForTest(bool aValue); sync ResetPrefersReducedMotionOverrideForTest(); + /** + * Fire a `load` event on this PBrowser's embedding frame element. + */ + async FireFrameLoadEvent(bool aIsTrusted); + child: /** * Notify the remote browser that it has been Show()n on this diff --git a/dom/ipc/PBrowserBridge.ipdl b/dom/ipc/PBrowserBridge.ipdl index cb777d9c7aa7..3e2384b16559 100644 --- a/dom/ipc/PBrowserBridge.ipdl +++ b/dom/ipc/PBrowserBridge.ipdl @@ -51,6 +51,11 @@ child: */ async SetEmbeddedDocAccessibleCOMProxy(IDispatchHolder aCOMProxy); + /** + * Fire a `load` event on this PBrowserBridge's embedding frame element. + */ + async FireFrameLoadEvent(bool aIsTrusted); + parent: // Destroy the remote web browser due to the nsFrameLoader going away. async __delete__();