From cf1d141d21be7fa97ec14308bbebbcdab4f52162 Mon Sep 17 00:00:00 2001 From: Mike Conley Date: Thu, 13 Apr 2017 17:54:07 -0400 Subject: [PATCH] Bug 1336763 - Add a hasBeforeUnload attribute to nsITabParent. r=Ehsan This will return true if any of the frames loaded in the associated TabChild have set at least one onbeforeunload event handler. If those handlers are all removed, or all of the documents with onbeforeunload event handlers are unloaded, this becomes false again. Note that subframes that are sandboxed without the allow-modals permission will not affect the hasBeforeUnload attribute, since those iframes should never cause the beforeunload confirmation dialog to display. MozReview-Commit-ID: 8b0gBYWwMDn --HG-- extra : rebase_source : 69f3b692d6e73f6277e6982aad02bcd1ebdd8acf --- dom/base/nsGlobalWindow.cpp | 40 +++++++++++++++++++++++++++- dom/base/nsGlobalWindow.h | 6 +++++ dom/interfaces/base/nsITabChild.idl | 3 +++ dom/interfaces/base/nsITabParent.idl | 6 +++++ dom/ipc/PBrowser.ipdl | 2 ++ dom/ipc/TabChild.cpp | 23 ++++++++++++++++ dom/ipc/TabChild.h | 1 + dom/ipc/TabParent.cpp | 15 +++++++++++ dom/ipc/TabParent.h | 7 +++++ 9 files changed, 102 insertions(+), 1 deletion(-) diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index fee80f059d70..523e86aa49ff 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -1570,7 +1570,8 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalWindow *aOuterWindow) mIsValidatingTabGroup(false), #endif mCanSkipCCGeneration(0), - mAutoActivateVRDisplayID(0) + mAutoActivateVRDisplayID(0), + mBeforeUnloadListenerCount(0) { AssertIsOnMainThread(); @@ -1607,6 +1608,13 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalWindow *aOuterWindow) MOZ_ASSERT(IsFrozen()); } + if (XRE_IsContentProcess()) { + nsCOMPtr docShell = GetDocShell(); + if (docShell) { + mTabChild = docShell->GetTabChild(); + } + } + // We could have failed the first time through trying // to create the entropy collector, so we should // try to get one until we succeed. @@ -2122,6 +2130,12 @@ nsGlobalWindow::FreeInnerObjects() DisableVRUpdates(); mHasVREvents = false; mVRDisplays.Clear(); + + if (mTabChild) { + while (mBeforeUnloadListenerCount-- > 0) { + mTabChild->BeforeUnloadRemoved(); + } + } } //***************************************************************************** @@ -2261,6 +2275,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindow) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuspendedDoc) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIndexedDB) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentPrincipal) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTabChild) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDoc) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleService) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWakeLock) @@ -2344,6 +2359,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindow) NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuspendedDoc) NS_IMPL_CYCLE_COLLECTION_UNLINK(mIndexedDB) NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentPrincipal) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mTabChild) NS_IMPL_CYCLE_COLLECTION_UNLINK(mDoc) NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleService) NS_IMPL_CYCLE_COLLECTION_UNLINK(mWakeLock) @@ -13411,6 +13427,15 @@ nsGlobalWindow::EventListenerAdded(nsIAtom* aType) NotifyVREventListenerAdded(); } + if (aType == nsGkAtoms::onbeforeunload && + mTabChild && + (!mDoc || !(mDoc->GetSandboxFlags() & SANDBOXED_MODALS))) { + MOZ_ASSERT(IsInnerWindow()); + mBeforeUnloadListenerCount++; + MOZ_ASSERT(mBeforeUnloadListenerCount > 0); + mTabChild->BeforeUnloadAdded(); + } + // We need to initialize localStorage in order to receive notifications. if (aType == nsGkAtoms::onstorage) { ErrorResult rv; @@ -13419,6 +13444,19 @@ nsGlobalWindow::EventListenerAdded(nsIAtom* aType) } } +void +nsGlobalWindow::EventListenerRemoved(nsIAtom* aType) +{ + if (aType == nsGkAtoms::onbeforeunload && + mTabChild && + (!mDoc || !(mDoc->GetSandboxFlags() & SANDBOXED_MODALS))) { + MOZ_ASSERT(IsInnerWindow()); + mBeforeUnloadListenerCount--; + MOZ_ASSERT(mBeforeUnloadListenerCount >= 0); + mTabChild->BeforeUnloadRemoved(); + } +} + void nsGlobalWindow::NotifyVREventListenerAdded() { diff --git a/dom/base/nsGlobalWindow.h b/dom/base/nsGlobalWindow.h index 27c8d40aaa3c..b9369fec81cc 100644 --- a/dom/base/nsGlobalWindow.h +++ b/dom/base/nsGlobalWindow.h @@ -87,6 +87,7 @@ class nsIControllers; class nsIJSID; class nsIScriptContext; class nsIScriptTimeoutHandler; +class nsITabChild; class nsITimeoutHandler; class nsIWebBrowserChrome; class mozIDOMWindowProxy; @@ -459,6 +460,8 @@ public: using EventTarget::EventListenerAdded; virtual void EventListenerAdded(nsIAtom* aType) override; + using EventTarget::EventListenerRemoved; + virtual void EventListenerRemoved(nsIAtom* aType) override; // nsIInterfaceRequestor NS_DECL_NSIINTERFACEREQUESTOR @@ -1952,6 +1955,8 @@ protected: // These member variables are used on both inner and the outer windows. nsCOMPtr mDocumentPrincipal; + // mTabChild is only ever populated in the content process. + nsCOMPtr mTabChild; uint32_t mSuspendDepth; uint32_t mFreezeDepth; @@ -2040,6 +2045,7 @@ protected: // after loading. The value is the ID of the VRDisplay that content should // begin presentation on. uint32_t mAutoActivateVRDisplayID; // Outer windows only + int64_t mBeforeUnloadListenerCount; // Inner windows only #ifdef ENABLE_INTL_API RefPtr mIntlUtils; diff --git a/dom/interfaces/base/nsITabChild.idl b/dom/interfaces/base/nsITabChild.idl index cf3d0bfd64a9..76bdedc4d05d 100644 --- a/dom/interfaces/base/nsITabChild.idl +++ b/dom/interfaces/base/nsITabChild.idl @@ -34,5 +34,8 @@ interface nsITabChild : nsISupports [array, size_is(linksCount)] in nsIDroppedLinkItem links); readonly attribute uint64_t tabId; + + [noscript, notxpcom] void beforeUnloadAdded(); + [noscript, notxpcom] void beforeUnloadRemoved(); }; diff --git a/dom/interfaces/base/nsITabParent.idl b/dom/interfaces/base/nsITabParent.idl index 5109673d3369..4eee98d0a35e 100644 --- a/dom/interfaces/base/nsITabParent.idl +++ b/dom/interfaces/base/nsITabParent.idl @@ -69,4 +69,10 @@ interface nsITabParent : nsISupports * permissions required to load a document with the given principal. */ void transmitPermissionsForPrincipal(in nsIPrincipal aPrincipal); + + /** + * True if any of the frames loaded in the TabChild have registered + * an onbeforeunload event handler. + */ + readonly attribute boolean hasBeforeUnload; }; diff --git a/dom/ipc/PBrowser.ipdl b/dom/ipc/PBrowser.ipdl index a7b096d16b8d..1352377e438f 100644 --- a/dom/ipc/PBrowser.ipdl +++ b/dom/ipc/PBrowser.ipdl @@ -555,6 +555,8 @@ parent: async AccessKeyNotHandled(WidgetKeyboardEvent event); + async SetHasBeforeUnload(bool aHasBeforeUnload); + child: async NativeSynthesisResponse(uint64_t aObserverId, nsCString aResponse); diff --git a/dom/ipc/TabChild.cpp b/dom/ipc/TabChild.cpp index 538880452f1f..564d80ad4b53 100644 --- a/dom/ipc/TabChild.cpp +++ b/dom/ipc/TabChild.cpp @@ -379,6 +379,7 @@ TabChild::TabChild(nsIContentChild* aManager, , mChromeFlags(aChromeFlags) , mActiveSuppressDisplayport(0) , mLayersId(0) + , mBeforeUnloadListeners(0) , mLayersConnected(true) , mDidFakeShow(false) , mNotified(false) @@ -3315,6 +3316,28 @@ TabChild::ForcePaint(uint64_t aLayerObserverEpoch) RecvSetDocShellIsActive(true, false, aLayerObserverEpoch); } +void +TabChild::BeforeUnloadAdded() +{ + if (mBeforeUnloadListeners == 0) { + SendSetHasBeforeUnload(true); + } + + mBeforeUnloadListeners++; + MOZ_ASSERT(mBeforeUnloadListeners >= 0); +} + +void +TabChild::BeforeUnloadRemoved() +{ + mBeforeUnloadListeners--; + MOZ_ASSERT(mBeforeUnloadListeners >= 0); + + if (mBeforeUnloadListeners == 0) { + SendSetHasBeforeUnload(false); + } +} + already_AddRefed TabChild::GetRelatedSHistory() { diff --git a/dom/ipc/TabChild.h b/dom/ipc/TabChild.h index 54be8255ec49..266513131878 100644 --- a/dom/ipc/TabChild.h +++ b/dom/ipc/TabChild.h @@ -780,6 +780,7 @@ private: uint32_t mChromeFlags; int32_t mActiveSuppressDisplayport; uint64_t mLayersId; + int64_t mBeforeUnloadListeners; CSSRect mUnscaledOuterRect; nscolor mLastBackgroundColor; bool mLayersConnected; diff --git a/dom/ipc/TabParent.cpp b/dom/ipc/TabParent.cpp index d0a20bcd0d55..955b1ca01355 100644 --- a/dom/ipc/TabParent.cpp +++ b/dom/ipc/TabParent.cpp @@ -168,6 +168,7 @@ TabParent::TabParent(nsIContentParent* aManager, , mLayerTreeEpoch(0) , mPreserveLayers(false) , mHasPresented(false) + , mHasBeforeUnload(false) { MOZ_ASSERT(aManager); } @@ -2025,6 +2026,13 @@ TabParent::RecvAccessKeyNotHandled(const WidgetKeyboardEvent& aEvent) return IPC_OK(); } +mozilla::ipc::IPCResult +TabParent::RecvSetHasBeforeUnload(const bool& aHasBeforeUnload) +{ + mHasBeforeUnload = aHasBeforeUnload; + return IPC_OK(); +} + bool TabParent::HandleQueryContentEvent(WidgetQueryContentEvent& aEvent) { @@ -2797,6 +2805,13 @@ TabParent::TransmitPermissionsForPrincipal(nsIPrincipal* aPrincipal) ->TransmitPermissionsForPrincipal(aPrincipal); } +NS_IMETHODIMP +TabParent::GetHasBeforeUnload(bool* aResult) +{ + *aResult = mHasBeforeUnload; + return NS_OK; +} + class LayerTreeUpdateRunnable final : public mozilla::Runnable { diff --git a/dom/ipc/TabParent.h b/dom/ipc/TabParent.h index c3b82953e38a..7716ff407acb 100644 --- a/dom/ipc/TabParent.h +++ b/dom/ipc/TabParent.h @@ -170,6 +170,9 @@ public: virtual mozilla::ipc::IPCResult RecvAccessKeyNotHandled(const WidgetKeyboardEvent& aEvent) override; + virtual mozilla::ipc::IPCResult + RecvSetHasBeforeUnload(const bool& aHasBeforeUnload) override; + virtual mozilla::ipc::IPCResult RecvBrowserFrameOpenWindow(PBrowserParent* aOpener, PRenderFrameParent* aRenderFrame, const nsString& aURL, @@ -762,6 +765,10 @@ private: // at least once. bool mHasPresented; + // True if at least one window hosted in the TabChild has added a + // beforeunload event listener. + bool mHasBeforeUnload; + public: static TabParent* GetTabParentFromLayersId(uint64_t aLayersId); };