☠☠ backed out by 76aab45b3300 ☠ ☠ | |
author | Bill McCloskey <bill.mccloskey@gmail.com> |
Thu, 26 Feb 2015 21:35:26 -0800 | |
changeset 233565 | f63a2cf3fa11b5ee08b84e5d24dcfab51f56c90c |
parent 233564 | 7d9a91ee3d484f651b31e3a2a4724e33836890aa |
child 233566 | 3fb5364095bfeaedb7c3689aa6c3b9b80e7045a4 |
push id | 28417 |
push user | ryanvm@gmail.com |
push date | Fri, 13 Mar 2015 19:52:44 +0000 |
treeherder | mozilla-central@977add19414a [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | smaug |
bugs | 1126089 |
milestone | 39.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
|
--- a/b2g/components/Frames.jsm +++ b/b2g/components/Frames.jsm @@ -20,32 +20,32 @@ const Observer = { _frames: new Map(), // Also save current number of iframes opened by app _apps: new Map(), start: function () { Services.obs.addObserver(this, 'remote-browser-shown', false); Services.obs.addObserver(this, 'inprocess-browser-shown', false); - Services.obs.addObserver(this, 'message-manager-disconnect', false); + Services.obs.addObserver(this, 'message-manager-close', false); SystemAppProxy.getFrames().forEach(frame => { let mm = frame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager; this._frames.set(mm, frame); let mozapp = frame.getAttribute('mozapp'); if (mozapp) { this._apps.set(mozapp, (this._apps.get(mozapp) || 0) + 1); } }); }, stop: function () { Services.obs.removeObserver(this, 'remote-browser-shown'); Services.obs.removeObserver(this, 'inprocess-browser-shown'); - Services.obs.removeObserver(this, 'message-manager-disconnect'); + Services.obs.removeObserver(this, 'message-manager-close'); this._frames.clear(); this._apps.clear(); }, observe: function (subject, topic, data) { switch(topic) { // Listen for frame creation in OOP (device) as well as in parent process (b2g desktop) @@ -56,17 +56,17 @@ const Observer = { // get a ref to the app <iframe> frameLoader.QueryInterface(Ci.nsIFrameLoader); let frame = frameLoader.ownerElement; let mm = frame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager; this.onMessageManagerCreated(mm, frame); break; // Every time an iframe is destroyed, its message manager also is - case 'message-manager-disconnect': + case 'message-manager-close': this.onMessageManagerDestroyed(subject); break; } }, onMessageManagerCreated: function (mm, frame) { this._frames.set(mm, frame);
--- a/browser/components/uitour/UITour.jsm +++ b/browser/components/uitour/UITour.jsm @@ -679,17 +679,17 @@ this.UITour = { } } if (!this.tourBrowsersByWindow.has(window)) { this.tourBrowsersByWindow.set(window, new Set()); } this.tourBrowsersByWindow.get(window).add(browser); - Services.obs.addObserver(this, "message-manager-disconnect", false); + Services.obs.addObserver(this, "message-manager-close", false); window.addEventListener("SSWindowClosing", this); return true; }, handleEvent: function(aEvent) { log.debug("handleEvent: type =", aEvent.type, "event =", aEvent); @@ -731,17 +731,17 @@ this.UITour = { } }, observe: function(aSubject, aTopic, aData) { log.debug("observe: aTopic =", aTopic); switch (aTopic) { // The browser message manager is disconnected when the <browser> is // destroyed and we want to teardown at that point. - case "message-manager-disconnect": { + case "message-manager-close": { let winEnum = Services.wm.getEnumerator("navigator:browser"); while (winEnum.hasMoreElements()) { let window = winEnum.getNext(); if (window.closed) continue; let tourBrowsers = this.tourBrowsersByWindow.get(window); if (!tourBrowsers)
--- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -7329,24 +7329,24 @@ nsDocument::InitializeFrameLoader(nsFram NS_NewRunnableMethod(this, &nsDocument::MaybeInitializeFinalizeFrameLoaders); NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY); nsContentUtils::AddScriptRunner(mFrameLoaderRunner); } return NS_OK; } nsresult -nsDocument::FinalizeFrameLoader(nsFrameLoader* aLoader) +nsDocument::FinalizeFrameLoader(nsFrameLoader* aLoader, nsIRunnable* aFinalizer) { mInitializableFrameLoaders.RemoveElement(aLoader); if (mInDestructor) { return NS_ERROR_FAILURE; } - mFinalizableFrameLoaders.AppendElement(aLoader); + mFrameLoaderFinalizers.AppendElement(aFinalizer); if (!mFrameLoaderRunner) { mFrameLoaderRunner = NS_NewRunnableMethod(this, &nsDocument::MaybeInitializeFinalizeFrameLoaders); NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY); nsContentUtils::AddScriptRunner(mFrameLoaderRunner); } return NS_OK; } @@ -7361,17 +7361,17 @@ nsDocument::MaybeInitializeFinalizeFrame return; } // We're not in an update, but it is not safe to run scripts, so // postpone frameloader initialization and finalization. if (!nsContentUtils::IsSafeToRunScript()) { if (!mInDestructor && !mFrameLoaderRunner && (mInitializableFrameLoaders.Length() || - mFinalizableFrameLoaders.Length())) { + mFrameLoaderFinalizers.Length())) { mFrameLoaderRunner = NS_NewRunnableMethod(this, &nsDocument::MaybeInitializeFinalizeFrameLoaders); nsContentUtils::AddScriptRunner(mFrameLoaderRunner); } return; } mFrameLoaderRunner = nullptr; @@ -7380,52 +7380,38 @@ nsDocument::MaybeInitializeFinalizeFrame // array. But be careful to keep the loader alive when starting the load! while (mInitializableFrameLoaders.Length()) { nsRefPtr<nsFrameLoader> loader = mInitializableFrameLoaders[0]; mInitializableFrameLoaders.RemoveElementAt(0); NS_ASSERTION(loader, "null frameloader in the array?"); loader->ReallyStartLoading(); } - uint32_t length = mFinalizableFrameLoaders.Length(); + uint32_t length = mFrameLoaderFinalizers.Length(); if (length > 0) { - nsTArray<nsRefPtr<nsFrameLoader> > loaders; - mFinalizableFrameLoaders.SwapElements(loaders); + nsTArray<nsCOMPtr<nsIRunnable> > finalizers; + mFrameLoaderFinalizers.SwapElements(finalizers); for (uint32_t i = 0; i < length; ++i) { - loaders[i]->Finalize(); + finalizers[i]->Run(); } } } void nsDocument::TryCancelFrameLoaderInitialization(nsIDocShell* aShell) { uint32_t length = mInitializableFrameLoaders.Length(); for (uint32_t i = 0; i < length; ++i) { if (mInitializableFrameLoaders[i]->GetExistingDocShell() == aShell) { mInitializableFrameLoaders.RemoveElementAt(i); return; } } } -bool -nsDocument::FrameLoaderScheduledToBeFinalized(nsIDocShell* aShell) -{ - if (aShell) { - uint32_t length = mFinalizableFrameLoaders.Length(); - for (uint32_t i = 0; i < length; ++i) { - if (mFinalizableFrameLoaders[i]->GetExistingDocShell() == aShell) { - return true; - } - } - } - return false; -} - nsIDocument* nsDocument::RequestExternalResource(nsIURI* aURI, nsINode* aRequestingNode, ExternalResourceLoad** aPendingLoad) { NS_PRECONDITION(aURI, "Must have a URI"); NS_PRECONDITION(aRequestingNode, "Must have a node"); if (mDisplayDocument) {
--- a/dom/base/nsDocument.h +++ b/dom/base/nsDocument.h @@ -1028,19 +1028,18 @@ public: float aBottomSize, float aLeftSize, bool aIgnoreRootScrollFrame, bool aFlushLayout, nsIDOMNodeList** aReturn) MOZ_OVERRIDE; virtual void FlushSkinBindings() MOZ_OVERRIDE; virtual nsresult InitializeFrameLoader(nsFrameLoader* aLoader) MOZ_OVERRIDE; - virtual nsresult FinalizeFrameLoader(nsFrameLoader* aLoader) MOZ_OVERRIDE; + virtual nsresult FinalizeFrameLoader(nsFrameLoader* aLoader, nsIRunnable* aFinalizer) MOZ_OVERRIDE; virtual void TryCancelFrameLoaderInitialization(nsIDocShell* aShell) MOZ_OVERRIDE; - virtual bool FrameLoaderScheduledToBeFinalized(nsIDocShell* aShell) MOZ_OVERRIDE; virtual nsIDocument* RequestExternalResource(nsIURI* aURI, nsINode* aRequestingNode, ExternalResourceLoad** aPendingLoad) MOZ_OVERRIDE; virtual void EnumerateExternalResources(nsSubDocEnumFunc aCallback, void* aData) MOZ_OVERRIDE; nsTArray<nsCString> mHostObjectURIs; @@ -1763,17 +1762,17 @@ private: // AddStyleRelevantLink. bool mStyledLinksCleared; #endif // Member to store out last-selected stylesheet set. nsString mLastStyleSheetSet; nsTArray<nsRefPtr<nsFrameLoader> > mInitializableFrameLoaders; - nsTArray<nsRefPtr<nsFrameLoader> > mFinalizableFrameLoaders; + nsTArray<nsCOMPtr<nsIRunnable> > mFrameLoaderFinalizers; nsRefPtr<nsRunnableMethod<nsDocument> > mFrameLoaderRunner; nsRevocableEventPtr<nsRunnableMethod<nsDocument, void, false> > mPendingTitleChangeEvent; nsExternalResourceMap mExternalResourceMap; // All images in process of being preloaded. This is a hashtable so
--- a/dom/base/nsFrameLoader.cpp +++ b/dom/base/nsFrameLoader.cpp @@ -102,35 +102,16 @@ using namespace mozilla; using namespace mozilla::hal; using namespace mozilla::dom; using namespace mozilla::dom::ipc; using namespace mozilla::layers; using namespace mozilla::layout; typedef FrameMetrics::ViewID ViewID; -class nsAsyncDocShellDestroyer : public nsRunnable -{ -public: - explicit nsAsyncDocShellDestroyer(nsIDocShell* aDocShell) - : mDocShell(aDocShell) - { - } - - NS_IMETHOD Run() - { - nsCOMPtr<nsIBaseWindow> base_win(do_QueryInterface(mDocShell)); - if (base_win) { - base_win->Destroy(); - } - return NS_OK; - } - nsRefPtr<nsIDocShell> mDocShell; -}; - // Bug 136580: Limit to the number of nested content frames that can have the // same URL. This is to stop content that is recursively loading // itself. Note that "#foo" on the end of URL doesn't affect // whether it's considered identical, but "?foo" or ";foo" are // considered and compared. // Bug 228829: Limit this to 1, like IE does. #define MAX_SAME_URL_CONTENT_FRAMES 1 @@ -176,17 +157,16 @@ nsFrameLoader::nsFrameLoader(Element* aO , mChildID(0) , mEventMode(EVENT_MODE_NORMAL_DISPATCH) { ResetPermissionManagerStatus(); } nsFrameLoader::~nsFrameLoader() { - mNeedsAsyncDestroy = true; if (mMessageManager) { mMessageManager->Disconnect(); } MOZ_RELEASE_ASSERT(mDestroyCalled); } nsFrameLoader* nsFrameLoader::Create(Element* aOwner, bool aNetworkCreated) @@ -518,26 +498,16 @@ nsFrameLoader::GetDocShell(nsIDocShell * } *aDocShell = mDocShell; NS_IF_ADDREF(*aDocShell); return rv; } -void -nsFrameLoader::Finalize() -{ - nsCOMPtr<nsIBaseWindow> base_win(do_QueryInterface(mDocShell)); - if (base_win) { - base_win->Destroy(); - } - mDocShell = nullptr; -} - static void FirePageHideEvent(nsIDocShellTreeItem* aItem, EventTarget* aChromeEventHandler) { nsCOMPtr<nsIDocument> doc = aItem->GetDocument(); NS_ASSERTION(doc, "What happened here?"); doc->OnPageHide(true, aChromeEventHandler); @@ -1353,51 +1323,80 @@ nsFrameLoader::SwapWithOtherLoader(nsFra FirePageShowEvent(ourDocshell, ourEventTarget, true); FirePageShowEvent(otherDocshell, otherEventTarget, true); mInSwap = aOther->mInSwap = false; return NS_OK; } -void -nsFrameLoader::DestroyChild() -{ - if (mRemoteBrowser) { - mRemoteBrowser->SetOwnerElement(nullptr); - mRemoteBrowser->Destroy(); - mRemoteBrowser = nullptr; - } -} - NS_IMETHODIMP nsFrameLoader::Destroy() { + StartDestroy(); + return NS_OK; +} + +class nsFrameLoaderDestroyRunnable : public nsRunnable +{ + enum DestroyPhase + { + // See the implementation of Run for an explanation of these phases. + eDestroyDocShell, + eWaitForUnloadMessage, + eDestroyComplete + }; + + nsRefPtr<nsFrameLoader> mFrameLoader; + DestroyPhase mPhase; + +public: + explicit nsFrameLoaderDestroyRunnable(nsFrameLoader* aFrameLoader) + : mFrameLoader(aFrameLoader), mPhase(eDestroyDocShell) {} + + NS_IMETHODIMP Run() MOZ_OVERRIDE; +}; + +void +nsFrameLoader::StartDestroy() +{ + // nsFrameLoader::StartDestroy is called just before the frameloader is + // detached from the <browser> element. Destruction continues in phases via + // the nsFrameLoaderDestroyRunnable. + if (mDestroyCalled) { - return NS_OK; + return; } mDestroyCalled = true; + // After this point, we return an error when trying to send a message using + // the message manager on the frame. if (mMessageManager) { - mMessageManager->Disconnect(); + mMessageManager->Close(); } - if (mChildMessageManager) { - static_cast<nsInProcessTabChildGlobal*>(mChildMessageManager.get())->Disconnect(); + + // Retain references to the <browser> element and the frameloader in case we + // receive any messages from the message manager on the frame. These + // references are dropped in DestroyComplete. + if (mChildMessageManager || mRemoteBrowser) { + mOwnerContentStrong = mOwnerContent; + if (mRemoteBrowser) { + mRemoteBrowser->CacheFrameLoader(this); + } } nsCOMPtr<nsIDocument> doc; bool dynamicSubframeRemoval = false; if (mOwnerContent) { doc = mOwnerContent->OwnerDoc(); dynamicSubframeRemoval = !mIsTopLevelContent && !doc->InUnlinkOrDeletion(); doc->SetSubDocumentFor(mOwnerContent, nullptr); SetOwnerContent(nullptr); } - DestroyChild(); // Seems like this is a dynamic frame removal. if (dynamicSubframeRemoval) { if (mDocShell) { mDocShell->RemoveFromSessionHistory(); } } @@ -1407,42 +1406,141 @@ nsFrameLoader::Destroy() nsCOMPtr<nsIDocShellTreeItem> parentItem; mDocShell->GetParent(getter_AddRefs(parentItem)); nsCOMPtr<nsIDocShellTreeOwner> owner = do_GetInterface(parentItem); if (owner) { owner->ContentShellRemoved(mDocShell); } } } - + // Let our window know that we are gone if (mDocShell) { nsCOMPtr<nsPIDOMWindow> win_private(mDocShell->GetWindow()); if (win_private) { win_private->SetFrameElementInternal(nullptr); } } - if ((mNeedsAsyncDestroy || !doc || - NS_FAILED(doc->FinalizeFrameLoader(this))) && mDocShell) { - nsCOMPtr<nsIRunnable> event = new nsAsyncDocShellDestroyer(mDocShell); - NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY); - NS_DispatchToCurrentThread(event); - - // Let go of our docshell now that the async destroyer holds on to - // the docshell. - - mDocShell = nullptr; + nsCOMPtr<nsIRunnable> destroyRunnable = new nsFrameLoaderDestroyRunnable(this); + if (mNeedsAsyncDestroy || !doc || + NS_FAILED(doc->FinalizeFrameLoader(this, destroyRunnable))) { + NS_DispatchToCurrentThread(destroyRunnable); } - - // NOTE: 'this' may very well be gone by now. +} + +nsresult +nsFrameLoaderDestroyRunnable::Run() +{ + switch (mPhase) { + case eDestroyDocShell: + mFrameLoader->DestroyDocShell(); + + // In the out-of-process case, TabParent will eventually call + // DestroyComplete once it receives a __delete__ message from the child. In + // the in-process case, we dispatch a series of runnables to ensure that + // DestroyComplete gets called at the right time. The frame loader is kept + // alive by mFrameLoader during this time. + if (mFrameLoader->mChildMessageManager) { + // When the docshell is destroyed, NotifyWindowIDDestroyed is called to + // asynchronously notify {outer,inner}-window-destroyed via a runnable. We + // don't want DestroyComplete to run until after those runnables have + // run. Since we're enqueueing ourselves after the window-destroyed + // runnables are enqueued, we're guaranteed to run after. + mPhase = eWaitForUnloadMessage; + NS_DispatchToCurrentThread(this); + } + break; + + case eWaitForUnloadMessage: + // The *-window-destroyed observers have finished running at this + // point. However, it's possible that a *-window-destroyed observer might + // have sent a message using the message manager. These messages might not + // have been processed yet. So we enqueue ourselves again to ensure that + // DestroyComplete runs after all messages sent by *-window-destroyed + // observers have been processed. + mPhase = eDestroyComplete; + NS_DispatchToCurrentThread(this); + break; + + case eDestroyComplete: + // Now that all messages sent by unload listeners and window destroyed + // observers have been processed, we disconnect the message manager and + // finish destruction. + mFrameLoader->DestroyComplete(); + break; + } return NS_OK; } +void +nsFrameLoader::DestroyDocShell() +{ + // This code runs after the frameloader has been detached from the <browser> + // element. We postpone this work because we may not be allowed to run + // script at that time. + + // Ask the TabChild to fire the frame script "unload" event, destroy its + // docshell, and finally destroy the PBrowser actor. This eventually leads to + // nsFrameLoader::DestroyComplete being called. + if (mRemoteBrowser) { + mRemoteBrowser->Destroy(); + } + + // Fire the "unload" event if we're in-process. + if (mChildMessageManager) { + static_cast<nsInProcessTabChildGlobal*>(mChildMessageManager.get())->FireUnloadEvent(); + } + + // Destroy the docshell. + nsCOMPtr<nsIBaseWindow> base_win(do_QueryInterface(mDocShell)); + if (base_win) { + base_win->Destroy(); + } + mDocShell = nullptr; + + if (mChildMessageManager) { + // Stop handling events in the in-process frame script. + static_cast<nsInProcessTabChildGlobal*>(mChildMessageManager.get())->DisconnectEventListeners(); + } +} + +void +nsFrameLoader::DestroyComplete() +{ + // We get here, as part of StartDestroy, after the docshell has been destroyed + // and all message manager messages sent during docshell destruction have been + // dispatched. We also get here if the child process crashes. In the latter + // case, StartDestroy might not have been called. + + // Drop the strong references created in StartDestroy. + if (mChildMessageManager || mRemoteBrowser) { + mOwnerContentStrong = nullptr; + if (mRemoteBrowser) { + mRemoteBrowser->CacheFrameLoader(nullptr); + } + } + + // Call TabParent::Destroy if we haven't already (in case of a crash). + if (mRemoteBrowser) { + mRemoteBrowser->SetOwnerElement(nullptr); + mRemoteBrowser->Destroy(); + mRemoteBrowser = nullptr; + } + + if (mMessageManager) { + mMessageManager->Disconnect(); + } + + if (mChildMessageManager) { + static_cast<nsInProcessTabChildGlobal*>(mChildMessageManager.get())->Disconnect(); + } +} + NS_IMETHODIMP nsFrameLoader::GetDepthTooGreat(bool* aDepthTooGreat) { *aDepthTooGreat = mDepthTooGreat; return NS_OK; } void
--- a/dom/base/nsFrameLoader.h +++ b/dom/base/nsFrameLoader.h @@ -73,17 +73,19 @@ public: bool aNetworkCreated); NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsFrameLoader, nsIFrameLoader) NS_DECL_NSIFRAMELOADER NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED nsresult CheckForRecursiveLoad(nsIURI* aURI); nsresult ReallyStartLoading(); - void Finalize(); + void StartDestroy(); + void DestroyDocShell(); + void DestroyComplete(); nsIDocShell* GetExistingDocShell() { return mDocShell; } mozilla::dom::EventTarget* GetTabChildGlobalAsEventTarget(); nsresult CreateStaticClone(nsIFrameLoader* aDest); /** * MessageManagerCallback methods that we override. */ virtual bool DoLoadMessageManagerScript(const nsAString& aURL, @@ -314,16 +316,21 @@ private: void ResetPermissionManagerStatus(); void InitializeBrowserAPI(); nsCOMPtr<nsIDocShell> mDocShell; nsCOMPtr<nsIURI> mURIToLoad; mozilla::dom::Element* mOwnerContent; // WEAK + // After the frameloader has been removed from the DOM but before all of the + // messages from the frame have been received, we keep a strong reference to + // our <browser> element. + nsRefPtr<mozilla::dom::Element> mOwnerContentStrong; + // Note: this variable must be modified only by ResetPermissionManagerStatus() uint32_t mAppIdSentToPermissionManager; public: // public because a callback needs these. nsRefPtr<nsFrameMessageManager> mMessageManager; nsCOMPtr<nsIInProcessContentFrameMessageManager> mChildMessageManager; private:
--- a/dom/base/nsFrameMessageManager.cpp +++ b/dom/base/nsFrameMessageManager.cpp @@ -296,17 +296,18 @@ SameProcessCpowHolder::ToObject(JSContex aObjp.set(mObj); return JS_WrapObject(aCx, aObjp); } // nsIMessageListenerManager NS_IMETHODIMP nsFrameMessageManager::AddMessageListener(const nsAString& aMessage, - nsIMessageListener* aListener) + nsIMessageListener* aListener, + bool aListenWhenClosed) { nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners = mListeners.Get(aMessage); if (!listeners) { listeners = new nsAutoTObserverArray<nsMessageListenerInfo, 1>(); mListeners.Put(aMessage, listeners); } else { uint32_t len = listeners->Length(); @@ -315,16 +316,17 @@ nsFrameMessageManager::AddMessageListene return NS_OK; } } } nsMessageListenerInfo* entry = listeners->AppendElement(); NS_ENSURE_TRUE(entry, NS_ERROR_OUT_OF_MEMORY); entry->mStrongListener = aListener; + entry->mListenWhenClosed = aListenWhenClosed; return NS_OK; } NS_IMETHODIMP nsFrameMessageManager::RemoveMessageListener(const nsAString& aMessage, nsIMessageListener* aListener) { nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners = @@ -402,16 +404,17 @@ nsFrameMessageManager::AddWeakMessageLis if (listeners->ElementAt(i).mWeakListener == weak) { return NS_OK; } } } nsMessageListenerInfo* entry = listeners->AppendElement(); entry->mWeakListener = weak; + entry->mListenWhenClosed = false; return NS_OK; } NS_IMETHODIMP nsFrameMessageManager::RemoveWeakMessageListener(const nsAString& aMessage, nsIMessageListener* aListener) { nsWeakPtr weak = do_GetWeakReference(aListener); @@ -979,16 +982,30 @@ nsresult nsFrameMessageManager::ReceiveMessage(nsISupports* aTarget, const nsAString& aMessage, bool aIsSync, const StructuredCloneData* aCloneData, mozilla::jsipc::CpowHolder* aCpows, nsIPrincipal* aPrincipal, InfallibleTArray<nsString>* aJSONRetVal) { + return ReceiveMessage(aTarget, mClosed, aMessage, aIsSync, + aCloneData, aCpows, aPrincipal, aJSONRetVal); +} + +nsresult +nsFrameMessageManager::ReceiveMessage(nsISupports* aTarget, + bool aTargetClosed, + const nsAString& aMessage, + bool aIsSync, + const StructuredCloneData* aCloneData, + mozilla::jsipc::CpowHolder* aCpows, + nsIPrincipal* aPrincipal, + InfallibleTArray<nsString>* aJSONRetVal) +{ nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners = mListeners.Get(aMessage); if (listeners) { MMListenerRemover lr(this); nsAutoTObserverArray<nsMessageListenerInfo, 1>::EndLimitedIterator iter(*listeners); @@ -999,16 +1016,20 @@ nsFrameMessageManager::ReceiveMessage(ns if (listener.mWeakListener) { weakListener = do_QueryReferent(listener.mWeakListener); if (!weakListener) { listeners->RemoveElement(listener); continue; } } + if (!listener.mListenWhenClosed && aTargetClosed) { + continue; + } + nsCOMPtr<nsIXPConnectWrappedJS> wrappedJS; if (weakListener) { wrappedJS = do_QueryInterface(weakListener); } else { wrappedJS = do_QueryInterface(listener.mStrongListener); } if (!wrappedJS) { @@ -1149,17 +1170,17 @@ nsFrameMessageManager::ReceiveMessage(ns continue; } aJSONRetVal->AppendElement(json); } } } } nsRefPtr<nsFrameMessageManager> kungfuDeathGrip = mParentManager; - return mParentManager ? mParentManager->ReceiveMessage(aTarget, aMessage, + return mParentManager ? mParentManager->ReceiveMessage(aTarget, aTargetClosed, aMessage, aIsSync, aCloneData, aCpows, aPrincipal, aJSONRetVal) : NS_OK; } void nsFrameMessageManager::AddChildManager(nsFrameMessageManager* aManager) { @@ -1230,32 +1251,48 @@ nsFrameMessageManager::RemoveFromParent( mParentManager->RemoveChildManager(this); } mParentManager = nullptr; mCallback = nullptr; mOwnedCallback = nullptr; } void +nsFrameMessageManager::Close() +{ + if (!mClosed) { + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->NotifyObservers(NS_ISUPPORTS_CAST(nsIContentFrameMessageManager*, this), + "message-manager-close", nullptr); + } + } + mClosed = true; + mCallback = nullptr; + mOwnedCallback = nullptr; +} + +void nsFrameMessageManager::Disconnect(bool aRemoveFromParent) { + // Notify message-manager-close if we haven't already. + Close(); + if (!mDisconnected) { nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); if (obs) { obs->NotifyObservers(NS_ISUPPORTS_CAST(nsIContentFrameMessageManager*, this), "message-manager-disconnect", nullptr); } } if (mParentManager && aRemoveFromParent) { mParentManager->RemoveChildManager(this); } mDisconnected = true; mParentManager = nullptr; - mCallback = nullptr; - mOwnedCallback = nullptr; if (!mHandlingMessage) { mListeners.Clear(); } } namespace { struct MessageManagerReferentCount
--- a/dom/base/nsFrameMessageManager.h +++ b/dom/base/nsFrameMessageManager.h @@ -128,16 +128,17 @@ struct nsMessageListenerInfo bool operator==(const nsMessageListenerInfo& aOther) const { return &aOther == this; } // Exactly one of mStrongListener and mWeakListener must be non-null. nsCOMPtr<nsIMessageListener> mStrongListener; nsWeakPtr mWeakListener; + bool mListenWhenClosed; }; class MOZ_STACK_CLASS SameProcessCpowHolder : public mozilla::jsipc::CpowHolder { public: SameProcessCpowHolder(JSRuntime *aRuntime, JS::Handle<JSObject*> aObj) : mObj(aRuntime, aObj) @@ -164,16 +165,17 @@ public: nsFrameMessageManager* aParentManager, /* mozilla::dom::ipc::MessageManagerFlags */ uint32_t aFlags) : mChrome(!!(aFlags & mozilla::dom::ipc::MM_CHROME)), mGlobal(!!(aFlags & mozilla::dom::ipc::MM_GLOBAL)), mIsProcessManager(!!(aFlags & mozilla::dom::ipc::MM_PROCESSMANAGER)), mIsBroadcaster(!!(aFlags & mozilla::dom::ipc::MM_BROADCASTER)), mOwnsCallback(!!(aFlags & mozilla::dom::ipc::MM_OWNSCALLBACK)), mHandlingMessage(false), + mClosed(false), mDisconnected(false), mCallback(aCallback), mParentManager(aParentManager) { NS_ASSERTION(mChrome || !aParentManager, "Should not set parent manager!"); NS_ASSERTION(!mIsBroadcaster || !mCallback, "Broadcasters cannot have callbacks!"); // This is a bit hackish. When parent manager is global, we want @@ -233,16 +235,17 @@ public: InfallibleTArray<nsString>* aJSONRetVal); void AddChildManager(nsFrameMessageManager* aManager); void RemoveChildManager(nsFrameMessageManager* aManager) { mChildManagers.RemoveObject(aManager); } void Disconnect(bool aRemoveFromParent = true); + void Close(); void InitWithCallback(mozilla::dom::ipc::MessageManagerCallback* aCallback); void SetCallback(mozilla::dom::ipc::MessageManagerCallback* aCallback); mozilla::dom::ipc::MessageManagerCallback* GetCallback() { return mCallback; } @@ -285,16 +288,21 @@ private: JS::Handle<JS::Value> aJSON, JS::Handle<JS::Value> aObjects, nsIPrincipal* aPrincipal, JSContext* aCx, uint8_t aArgc, JS::MutableHandle<JS::Value> aRetval, bool aIsSync); + nsresult ReceiveMessage(nsISupports* aTarget, bool aTargetClosed, const nsAString& aMessage, + bool aIsSync, const StructuredCloneData* aCloneData, + mozilla::jsipc::CpowHolder* aCpows, nsIPrincipal* aPrincipal, + InfallibleTArray<nsString>* aJSONRetVal); + NS_IMETHOD LoadScript(const nsAString& aURL, bool aAllowDelayedLoad, bool aRunInGlobalScope); NS_IMETHOD RemoveDelayedScript(const nsAString& aURL); NS_IMETHOD GetDelayedScripts(JSContext* aCx, JS::MutableHandle<JS::Value> aList); protected: friend class MMListenerRemover; @@ -304,16 +312,17 @@ protected: nsAutoTObserverArray<nsMessageListenerInfo, 1>> mListeners; nsCOMArray<nsIContentFrameMessageManager> mChildManagers; bool mChrome; // true if we're in the chrome process bool mGlobal; // true if we're the global frame message manager bool mIsProcessManager; // true if the message manager belongs to the process realm bool mIsBroadcaster; // true if the message manager is a broadcaster bool mOwnsCallback; bool mHandlingMessage; + bool mClosed; // true if we can no longer send messages bool mDisconnected; mozilla::dom::ipc::MessageManagerCallback* mCallback; nsAutoPtr<mozilla::dom::ipc::MessageManagerCallback> mOwnedCallback; nsFrameMessageManager* mParentManager; nsTArray<nsString> mPendingScripts; nsTArray<bool> mPendingScriptsGlobalStates; void LoadPendingScripts(nsFrameMessageManager* aManager,
--- a/dom/base/nsIDocument.h +++ b/dom/base/nsIDocument.h @@ -56,16 +56,17 @@ class nsIDOMNodeList; class nsIHTMLCollection; class nsILayoutHistoryState; class nsILoadContext; class nsIObjectLoadingContent; class nsIObserver; class nsIPresShell; class nsIPrincipal; class nsIRequest; +class nsIRunnable; class nsIStreamListener; class nsIStructuredCloneContainer; class nsIStyleRule; class nsIStyleSheet; class nsIURI; class nsIVariant; class nsLocation; class nsViewManager; @@ -1647,21 +1648,19 @@ public: already_AddRefed<nsIDocumentEncoder> GetCachedEncoder(); void SetCachedEncoder(already_AddRefed<nsIDocumentEncoder> aEncoder); // In case of failure, the document really can't initialize the frame loader. virtual nsresult InitializeFrameLoader(nsFrameLoader* aLoader) = 0; // In case of failure, the caller must handle the error, for example by // finalizing frame loader asynchronously. - virtual nsresult FinalizeFrameLoader(nsFrameLoader* aLoader) = 0; + virtual nsresult FinalizeFrameLoader(nsFrameLoader* aLoader, nsIRunnable* aFinalizer) = 0; // Removes the frame loader of aShell from the initialization list. virtual void TryCancelFrameLoaderInitialization(nsIDocShell* aShell) = 0; - // Returns true if the frame loader of aShell is in the finalization list. - virtual bool FrameLoaderScheduledToBeFinalized(nsIDocShell* aShell) = 0; /** * Check whether this document is a root document that is not an * external resource. */ bool IsRootDisplayDocument() const { return !mParentDocument && !mDisplayDocument;
--- a/dom/base/nsIMessageManager.idl +++ b/dom/base/nsIMessageManager.idl @@ -196,31 +196,38 @@ interface nsIMessageListener : nsISuppor * returned as JSON (will be changed to use structured clones). * When there are multiple listeners to sync messages, each * listener's return value is sent back as an array. |undefined| * return values show up as undefined values in the array. */ void receiveMessage(); }; -[scriptable, builtinclass, uuid(aae827bd-acf1-45fe-a556-ea545d4c0804)] +[scriptable, builtinclass, uuid(b949bfec-bb7d-47bc-b387-ac6a9b655072)] interface nsIMessageListenerManager : nsISupports { /** * Register |listener| to receive |messageName|. All listener * callbacks for a particular message are invoked when that message * is received. * * The message manager holds a strong ref to |listener|. * * If the same listener registers twice for the same message, the * second registration is ignored. + * + * Pass true for listenWhenClosed if you want to receive messages + * during the short period after a frame has been removed from the + * DOM and before its frame script has finished unloading. This + * parameter only has an effect for frame message managers in + * the main process. Default is false. */ void addMessageListener(in AString messageName, - in nsIMessageListener listener); + in nsIMessageListener listener, + [optional] in boolean listenWhenClosed); /** * Undo an |addMessageListener| call -- that is, calling this causes us to no * longer invoke |listener| when |messageName| is received. * * removeMessageListener does not remove a message listener added via * addWeakMessageListener; use removeWeakMessageListener for that. */ @@ -247,17 +254,17 @@ interface nsIMessageListenerManager : ns }; /** * Message "senders" have a single "other side" to which messages are * sent. For example, a child-process message manager will send * messages that are only delivered to its one parent-process message * manager. */ -[scriptable, builtinclass, uuid(d6b0d851-43e6-426d-9f13-054bc0198175)] +[scriptable, builtinclass, uuid(bb5d79e4-e73c-45e7-9651-4d718f4b994c)] interface nsIMessageSender : nsIMessageListenerManager { /** * Send |messageName| and |obj| to the "other side" of this message * manager. This invokes listeners who registered for * |messageName|. * * See nsIMessageListener::receiveMessage() for the format of the @@ -279,17 +286,17 @@ interface nsIMessageSender : nsIMessageL /** * Message "broadcasters" don't have a single "other side" that they * send messages to, but rather a set of subordinate message managers. * For example, broadcasting a message through a window message * manager will broadcast the message to all frame message managers * within its window. */ -[scriptable, builtinclass, uuid(d36346b9-5d3b-497d-9c28-ffbc3e4f6d0d)] +[scriptable, builtinclass, uuid(4d7d62ad-4725-4f39-86cf-8fb22bf9c1d8)] interface nsIMessageBroadcaster : nsIMessageListenerManager { /** * Like |sendAsyncMessage()|, but also broadcasts this message to * all "child" message managers of this message manager. See long * comment above for details. * * WARNING: broadcasting messages can be very expensive and leak @@ -306,17 +313,17 @@ interface nsIMessageBroadcaster : nsIMes readonly attribute unsigned long childCount; /** * Return a single subordinate message manager. */ nsIMessageListenerManager getChildAt(in unsigned long aIndex); }; -[scriptable, builtinclass, uuid(7fda0941-9dcc-448b-bd39-16373c5b4003)] +[scriptable, builtinclass, uuid(0e602c9e-1977-422a-a8e4-fe0d4a4f78d0)] interface nsISyncMessageSender : nsIMessageSender { /** * Like |sendAsyncMessage()|, except blocks the sender until all * listeners of the message have been invoked. Returns an array * containing return values from each listener invoked. */ [implicit_jscontext, optional_argc] @@ -336,17 +343,17 @@ interface nsISyncMessageSender : nsIMess */ [implicit_jscontext, optional_argc] jsval sendRpcMessage([optional] in AString messageName, [optional] in jsval obj, [optional] in jsval objects, [optional] in nsIPrincipal principal); }; -[scriptable, builtinclass, uuid(e04a7ade-c61a-46ec-9f13-efeabedd9d3d)] +[scriptable, builtinclass, uuid(13f3555f-769e-44ea-b607-5239230c3162)] interface nsIMessageManagerGlobal : nsISyncMessageSender { /** * Print a string to stdout. */ void dump(in DOMString aStr); /** @@ -371,23 +378,23 @@ interface nsIContentFrameMessageManager readonly attribute nsIDOMWindow content; /** * The top level docshell or null. */ readonly attribute nsIDocShell docShell; }; -[uuid(a2325927-9c0c-437d-9215-749c79235031)] +[uuid(a9e07e89-7125-48e3-bf73-2cbae7fc5b1c)] interface nsIInProcessContentFrameMessageManager : nsIContentFrameMessageManager { [notxpcom] nsIContent getOwnerContent(); }; -[scriptable, builtinclass, uuid(9ca95410-b253-11e4-ab27-0800200c9a66)] +[scriptable, builtinclass, uuid(d0c799a2-d5ff-4a75-acbb-b8c8347944a6)] interface nsIContentProcessMessageManager : nsIMessageManagerGlobal { }; [scriptable, builtinclass, uuid(6fb78110-45ae-11e3-8f96-0800200c9a66)] interface nsIFrameScriptLoader : nsISupports { /**
--- a/dom/base/nsInProcessTabChildGlobal.cpp +++ b/dom/base/nsInProcessTabChildGlobal.cpp @@ -96,16 +96,17 @@ nsInProcessTabChildGlobal::DoSendAsyncMe NS_DispatchToCurrentThread(ev); return true; } nsInProcessTabChildGlobal::nsInProcessTabChildGlobal(nsIDocShell* aShell, nsIContent* aOwner, nsFrameMessageManager* aChrome) : mDocShell(aShell), mInitialized(false), mLoadingScript(false), + mPreventEventsEscaping(false), mOwner(aOwner), mChromeMessageManager(aChrome) { SetIsNotDOMBinding(); mozilla::HoldJSObjects(this); // If owner corresponds to an <iframe mozbrowser> or <iframe mozapp>, we'll // have to tweak our PreHandleEvent implementation. nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwner); @@ -202,89 +203,100 @@ nsInProcessTabChildGlobal::GetContent(ns NS_IMETHODIMP nsInProcessTabChildGlobal::GetDocShell(nsIDocShell** aDocShell) { NS_IF_ADDREF(*aDocShell = mDocShell); return NS_OK; } void -nsInProcessTabChildGlobal::Disconnect() +nsInProcessTabChildGlobal::FireUnloadEvent() { - // Let the frame scripts know the child is being closed. We do any other - // cleanup after the event has been fired. See DelayedDisconnect - nsContentUtils::AddScriptRunner( - NS_NewRunnableMethod(this, &nsInProcessTabChildGlobal::DelayedDisconnect) - ); + // We're called from nsDocument::MaybeInitializeFinalizeFrameLoaders, so it + // should be safe to run script. + MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); + + // Don't let the unload event propagate to chrome event handlers. + mPreventEventsEscaping = true; + DOMEventTargetHelper::DispatchTrustedEvent(NS_LITERAL_STRING("unload")); + + // Allow events fired during docshell destruction (pagehide, unload) to + // propagate to the <browser> element since chrome code depends on this. + mPreventEventsEscaping = false; } void -nsInProcessTabChildGlobal::DelayedDisconnect() +nsInProcessTabChildGlobal::DisconnectEventListeners() { - // Don't let the event escape - mOwner = nullptr; - - // Fire the "unload" event - DOMEventTargetHelper::DispatchTrustedEvent(NS_LITERAL_STRING("unload")); - - // Continue with the Disconnect cleanup if (mDocShell) { nsCOMPtr<nsPIDOMWindow> win = mDocShell->GetWindow(); if (win) { MOZ_ASSERT(win->IsOuterWindow()); win->SetChromeEventHandler(win->GetChromeEventHandler()); } } + if (mListenerManager) { + mListenerManager->Disconnect(); + } + mDocShell = nullptr; +} + +void +nsInProcessTabChildGlobal::Disconnect() +{ mChromeMessageManager = nullptr; + mOwner = nullptr; if (mMessageManager) { static_cast<nsFrameMessageManager*>(mMessageManager.get())->Disconnect(); mMessageManager = nullptr; } - if (mListenerManager) { - mListenerManager->Disconnect(); - } } NS_IMETHODIMP_(nsIContent *) nsInProcessTabChildGlobal::GetOwnerContent() { return mOwner; } nsresult nsInProcessTabChildGlobal::PreHandleEvent(EventChainPreVisitor& aVisitor) { aVisitor.mCanHandle = true; - if (mIsBrowserOrAppFrame && - (!mOwner || !nsContentUtils::IsInChromeDocshell(mOwner->OwnerDoc()))) { - if (mOwner) { - nsPIDOMWindow* innerWindow = mOwner->OwnerDoc()->GetInnerWindow(); - if (innerWindow) { - aVisitor.mParentTarget = innerWindow->GetParentTarget(); - } - } - } else { - aVisitor.mParentTarget = mOwner; - } - #ifdef DEBUG if (mOwner) { nsCOMPtr<nsIFrameLoaderOwner> owner = do_QueryInterface(mOwner); nsRefPtr<nsFrameLoader> fl = owner->GetFrameLoader(); if (fl) { NS_ASSERTION(this == fl->GetTabChildGlobalAsEventTarget(), "Wrong event target!"); NS_ASSERTION(fl->mMessageManager == mChromeMessageManager, "Wrong message manager!"); } } #endif + if (mPreventEventsEscaping) { + aVisitor.mParentTarget = nullptr; + return NS_OK; + } + + if (mIsBrowserOrAppFrame && + (!mOwner || !nsContentUtils::IsInChromeDocshell(mOwner->OwnerDoc()))) { + if (mOwner) { + nsPIDOMWindow* innerWindow = mOwner->OwnerDoc()->GetInnerWindow(); + if (innerWindow) { + aVisitor.mParentTarget = innerWindow->GetParentTarget(); + } + } + } else { + aVisitor.mParentTarget = mOwner; + } + return NS_OK; } nsresult nsInProcessTabChildGlobal::InitTabChildGlobal() { // If you change this, please change GetCompartmentName() in XPCJSRuntime.cpp // accordingly.
--- a/dom/base/nsInProcessTabChildGlobal.h +++ b/dom/base/nsInProcessTabChildGlobal.h @@ -1,9 +1,9 @@ -/* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*- */ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8; -*- */ /* vim: set sw=4 ts=8 et 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/. */ #ifndef nsInProcessTabChildGlobal_h #define nsInProcessTabChildGlobal_h @@ -113,16 +113,18 @@ public: aWantsUntrusted, optional_argc); } using mozilla::DOMEventTargetHelper::AddEventListener; virtual JSContext* GetJSContextForEventHandlers() MOZ_OVERRIDE { return nsContentUtils::GetSafeJSContext(); } virtual nsIPrincipal* GetPrincipal() MOZ_OVERRIDE { return mPrincipal; } void LoadFrameScript(const nsAString& aURL, bool aRunInGlobalScope); + void FireUnloadEvent(); + void DisconnectEventListeners(); void Disconnect(); void SendMessageToParent(const nsString& aMessage, bool aSync, const nsString& aJSON, nsTArray<nsString>* aJSONRetVal); nsFrameMessageManager* GetInnerManager() { return static_cast<nsFrameMessageManager*>(mMessageManager.get()); } @@ -132,18 +134,16 @@ public: { return mChromeMessageManager; } void SetChromeMessageManager(nsFrameMessageManager* aParent) { mChromeMessageManager = aParent; } - void DelayedDisconnect(); - virtual JSObject* GetGlobalJSObject() MOZ_OVERRIDE { if (!mGlobal) { return nullptr; } return mGlobal->GetJSObject(); } virtual JSObject* WrapObject(JSContext* cx) MOZ_OVERRIDE @@ -159,15 +159,16 @@ protected: nsCOMPtr<nsIDocShell> mDocShell; bool mInitialized; bool mLoadingScript; // Is this the message manager for an in-process <iframe mozbrowser> or // <iframe mozapp>? This affects where events get sent, so it affects // PreHandleEvent. bool mIsBrowserOrAppFrame; + bool mPreventEventsEscaping; public: nsIContent* mOwner; nsFrameMessageManager* mChromeMessageManager; nsTArray<nsCOMPtr<nsIRunnable> > mASyncMessages; }; #endif
--- a/dom/base/test/browser.ini +++ b/dom/base/test/browser.ini @@ -1,14 +1,17 @@ [DEFAULT] +support-files = + file_messagemanager_unload.html [browser_bug593387.js] skip-if = e10s # Bug ?????? - test directly touches content (contentWindow.iframe.addEventListener) [browser_bug902350.js] skip-if = e10s # Bug ?????? - test e10s utils don't support load events from iframe etc, which this test relies on. [browser_messagemanager_loadprocessscript.js] [browser_pagehide_on_tab_close.js] skip-if = e10s # this tests non-e10s behavior. it's not expected to work in e10s. +[browser_messagemanager_unload.js] [browser_state_notifications.js] # skip-if = e10s # Bug ?????? - content-document-* notifications come while document's URI is still about:blank, but test expects real URL. skip-if = true # Intermittent failures - bug 987493. Restore the skip-if above once fixed [browser_bug1058164.js] skip-if = e10s # We need bug 918634 to land before this can be tested with e10s.
new file mode 100644 --- /dev/null +++ b/dom/base/test/browser_messagemanager_unload.js @@ -0,0 +1,98 @@ +function frameScript() +{ + Components.utils.import("resource://gre/modules/Services.jsm"); + + function eventHandler(e) { + if (!docShell) { + sendAsyncMessage("Test:Fail", "docShell is null"); + } + + sendAsyncMessage("Test:Event", [e.type, e.target === content.document, e.eventPhase]); + } + + let outerID = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor). + getInterface(Components.interfaces.nsIDOMWindowUtils).outerWindowID; + function onOuterWindowDestroyed(subject, topic, data) { + if (docShell) { + sendAsyncMessage("Test:Fail", "docShell is non-null"); + } + + let id = subject.QueryInterface(Components.interfaces.nsISupportsPRUint64).data; + sendAsyncMessage("Test:Event", ["outer-window-destroyed", id == outerID]); + if (id == outerID) { + Services.obs.removeObserver(onOuterWindowDestroyed, "outer-window-destroyed"); + } + } + + let url = "https://example.com/browser/dom/base/test/file_messagemanager_unload.html"; + + content.location = url; + addEventListener("load", (e) => { + if (e.target.location != url) { + return; + } + + addEventListener("unload", eventHandler, false); + addEventListener("unload", eventHandler, true); + addEventListener("pagehide", eventHandler, false); + addEventListener("pagehide", eventHandler, true); + Services.obs.addObserver(onOuterWindowDestroyed, "outer-window-destroyed", false); + + sendAsyncMessage("Test:Ready"); + }, true); +} + +const EXPECTED = [ + // Unload events on the TabChildGlobal. These come first so that the + // docshell is available. + ["unload", false, 2], + ["unload", false, 2], + + // pagehide and unload events for the top-level page. + ["pagehide", true, 1], + ["pagehide", true, 3], + ["unload", true, 1], + + // pagehide and unload events for the iframe. + ["pagehide", false, 1], + ["pagehide", false, 3], + ["unload", false, 1], + + // outer-window-destroyed for both pages. + ["outer-window-destroyed", false], + ["outer-window-destroyed", true], +]; + +function test() { + waitForExplicitFinish(); + + var newTab = gBrowser.addTab("about:blank"); + gBrowser.selectedTab = newTab; + + let browser = newTab.linkedBrowser; + + browser.messageManager.loadFrameScript("data:,(" + frameScript.toString() + ")()", false); + + browser.messageManager.addMessageListener("Test:Fail", (msg) => { + ok(false, msg.data); + }, true); + + let index = 0; + browser.messageManager.addMessageListener("Test:Event", (msg) => { + ok(msg.target === browser, "<browser> is correct"); + + info(JSON.stringify(msg.data)); + + is(JSON.stringify(msg.data), JSON.stringify(EXPECTED[index]), "results match"); + index++; + + if (index == EXPECTED.length) { + finish(); + } + }, true); + + browser.messageManager.addMessageListener("Test:Ready", () => { + info("Got ready message"); + gBrowser.removeCurrentTab(); + }); +}
new file mode 100644 --- /dev/null +++ b/dom/base/test/file_messagemanager_unload.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<html> + <body> + <iframe id="frame" src="about:robots"></iframe> + </body> +</html>
--- a/dom/ipc/TabChild.cpp +++ b/dom/ipc/TabChild.cpp @@ -2645,53 +2645,27 @@ TabChild::RecvAppOfflineStatus(const uin nsCOMPtr<nsIIOService> ioService = mozilla::services::GetIOService(); if (gIOService && ioService) { gIOService->SetAppOfflineInternal(aId, aOffline ? nsIAppOfflineInfo::OFFLINE : nsIAppOfflineInfo::ONLINE); } return true; } -class UnloadScriptEvent : public nsRunnable -{ -public: - UnloadScriptEvent(TabChild* aTabChild, TabChildGlobal* aTabChildGlobal) - : mTabChild(aTabChild), mTabChildGlobal(aTabChildGlobal) - { } - - NS_IMETHOD Run() - { - nsCOMPtr<nsIDOMEvent> event; - NS_NewDOMEvent(getter_AddRefs(event), mTabChildGlobal, nullptr, nullptr); - if (event) { - event->InitEvent(NS_LITERAL_STRING("unload"), false, false); - event->SetTrusted(true); - - bool dummy; - mTabChildGlobal->DispatchEvent(event, &dummy); - } - - return NS_OK; - } - - nsRefPtr<TabChild> mTabChild; - TabChildGlobal* mTabChildGlobal; -}; - bool TabChild::RecvDestroy() { MOZ_ASSERT(mDestroyed == false); mDestroyed = true; if (mTabChildGlobal) { - // Let the frame scripts know the child is being closed - nsContentUtils::AddScriptRunner( - new UnloadScriptEvent(this, mTabChildGlobal) - ); + // Message handlers are called from the event loop, so it better be safe to + // run script. + MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); + mTabChildGlobal->DispatchTrustedEvent(NS_LITERAL_STRING("unload")); } nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService(); observerService->RemoveObserver(this, BROWSER_ZOOM_TO_RECT); observerService->RemoveObserver(this, BEFORE_FIRST_PAINT);
--- a/dom/ipc/TabParent.cpp +++ b/dom/ipc/TabParent.cpp @@ -314,16 +314,22 @@ TabParent::RemoveTabParentFromTable(uint sLayerToTabParentTable->Remove(aLayersId); if (sLayerToTabParentTable->Count() == 0) { delete sLayerToTabParentTable; sLayerToTabParentTable = nullptr; } } void +TabParent::CacheFrameLoader(nsFrameLoader* aFrameLoader) +{ + mFrameLoader = aFrameLoader; +} + +void TabParent::SetOwnerElement(Element* aElement) { mFrameElement = aElement; TryCacheDPIAndScale(); } void TabParent::GetAppType(nsAString& aOut) @@ -402,41 +408,38 @@ void TabParent::ActorDestroy(ActorDestroyReason why) { if (sEventCapturer == this) { sEventCapturer = nullptr; } if (mIMETabParent == this) { mIMETabParent = nullptr; } - nsRefPtr<nsFrameLoader> frameLoader = GetFrameLoader(); + nsRefPtr<nsFrameLoader> frameLoader = GetFrameLoader(true); nsCOMPtr<nsIObserverService> os = services::GetObserverService(); - nsRefPtr<nsFrameMessageManager> fmm; if (frameLoader) { - fmm = frameLoader->GetFrameMessageManager(); nsCOMPtr<Element> frameElement(mFrameElement); ReceiveMessage(CHILD_PROCESS_SHUTDOWN_MESSAGE, false, nullptr, nullptr, nullptr); - frameLoader->DestroyChild(); + frameLoader->DestroyComplete(); if (why == AbnormalShutdown && os) { os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, frameLoader), "oop-frameloader-crashed", nullptr); nsContentUtils::DispatchTrustedEvent(frameElement->OwnerDoc(), frameElement, NS_LITERAL_STRING("oop-browser-crashed"), true, true); } + + mFrameLoader = nullptr; } if (os) { os->NotifyObservers(NS_ISUPPORTS_CAST(nsITabParent*, this), "ipc:browser-destroyed", nullptr); } - if (fmm) { - fmm->Disconnect(); - } } bool TabParent::RecvMoveFocus(const bool& aForward) { nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID); if (fm) { nsCOMPtr<nsIDOMElement> dummy; @@ -2280,17 +2283,17 @@ TabParent::RecvGetWidgetNativeData(Windo bool TabParent::ReceiveMessage(const nsString& aMessage, bool aSync, const StructuredCloneData* aCloneData, CpowHolder* aCpows, nsIPrincipal* aPrincipal, InfallibleTArray<nsString>* aJSONRetVal) { - nsRefPtr<nsFrameLoader> frameLoader = GetFrameLoader(); + nsRefPtr<nsFrameLoader> frameLoader = GetFrameLoader(true); if (frameLoader && frameLoader->GetFrameMessageManager()) { nsRefPtr<nsFrameMessageManager> manager = frameLoader->GetFrameMessageManager(); manager->ReceiveMessage(mFrameElement, aMessage, aSync, aCloneData, @@ -2398,18 +2401,26 @@ TabParent::AllowContentIME() nsCOMPtr<nsIContent> focusedContent = fm->GetFocusedContent(); if (focusedContent && focusedContent->IsEditable()) return false; return true; } already_AddRefed<nsFrameLoader> -TabParent::GetFrameLoader() const +TabParent::GetFrameLoader(bool aUseCachedFrameLoaderAfterDestroy) const { + if (mIsDestroyed && !aUseCachedFrameLoaderAfterDestroy) { + return nullptr; + } + + if (mFrameLoader) { + nsRefPtr<nsFrameLoader> fl = mFrameLoader; + return fl.forget(); + } nsCOMPtr<nsIFrameLoaderOwner> frameLoaderOwner = do_QueryInterface(mFrameElement); return frameLoaderOwner ? frameLoaderOwner->GetFrameLoader() : nullptr; } void TabParent::TryCacheDPIAndScale() { if (mDPI > 0) {
--- a/dom/ipc/TabParent.h +++ b/dom/ipc/TabParent.h @@ -75,16 +75,18 @@ public: TabParent(nsIContentParent* aManager, const TabId& aTabId, const TabContext& aContext, uint32_t aChromeFlags); Element* GetOwnerElement() const { return mFrameElement; } void SetOwnerElement(Element* aElement); + void CacheFrameLoader(nsFrameLoader* aFrameLoader); + /** * Get the mozapptype attribute from this TabParent's owner DOM element. */ void GetAppType(nsAString& aOut); /** * Returns true iff this TabParent's nsIFrameLoader is visible. * @@ -447,17 +449,17 @@ protected: ScreenIntSize mDimensions; ScreenOrientation mOrientation; float mDPI; CSSToLayoutDeviceScale mDefaultScale; bool mShown; bool mUpdatedDimensions; private: - already_AddRefed<nsFrameLoader> GetFrameLoader() const; + already_AddRefed<nsFrameLoader> GetFrameLoader(bool aUseCachedFrameLoaderAfterDestroy = false) const; layout::RenderFrameParent* GetRenderFrame(); nsRefPtr<nsIContentParent> mManager; void TryCacheDPIAndScale(); CSSPoint AdjustTapToChildWidget(const CSSPoint& aPoint); // Update state prior to routing an APZ-aware event to the child process. // |aOutTargetGuid| will contain the identifier @@ -489,16 +491,21 @@ private: uint32_t mChromeFlags; // When true, the TabParent is initialized without child side's request. // When false, the TabParent is initialized by window.open() from child side. bool mInitedByParent; nsCOMPtr<nsILoadContext> mLoadContext; + // We keep a strong reference to the frameloader after we've sent the + // Destroy message and before we've received __delete__. This allows us to + // dispatch message manager messages during this time. + nsRefPtr<nsFrameLoader> mFrameLoader; + TabId mTabId; // Helper class for RecvCreateWindow. struct AutoUseNewTab; // When loading a new tab or window via window.open, the child process sends // a new PBrowser to use. We store that tab in sNextTabParent and then // proceed through the browser's normal paths to create a new
--- a/testing/mochitest/browser-test.js +++ b/testing/mochitest/browser-test.js @@ -43,16 +43,52 @@ function b2gStart() { let homescreen = document.getElementById('systemapp'); var webNav = homescreen.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIWebNavigation); var url = "chrome://mochikit/content/harness.xul?manifestFile=tests.json"; webNav.loadURI(url, null, null, null, null); } +let TabDestroyObserver = { + outstanding: new Set(), + promiseResolver: null, + + init: function() { + Services.obs.addObserver(this, "message-manager-close", false); + Services.obs.addObserver(this, "message-manager-disconnect", false); + }, + + destroy: function() { + Services.obs.removeObserver(this, "message-manager-close"); + Services.obs.removeObserver(this, "message-manager-disconnect"); + }, + + observe: function(subject, topic, data) { + if (topic == "message-manager-close") { + this.outstanding.add(subject); + } else if (topic == "message-manager-disconnect") { + this.outstanding.delete(subject); + if (!this.outstanding.size && this.promiseResolver) { + this.promiseResolver(); + } + } + }, + + wait: function() { + if (!this.outstanding.size) { + return Promise.resolve(); + } + + return new Promise((resolve) => { + this.promiseResolver = resolve; + }); + }, +}; + function testInit() { gConfig = readConfig(); if (gConfig.testRoot == "browser" || gConfig.testRoot == "metro" || gConfig.testRoot == "webapprtChrome") { // Make sure to launch the test harness for the first opened window only var prefs = Services.prefs; if (prefs.prefHasUserValue("testing.browserTestHarness.running")) @@ -180,16 +216,18 @@ Tester.prototype = { get currentTest() { return this.tests[this.currentTestIndex]; }, get done() { return this.currentTestIndex == this.tests.length - 1; }, start: function Tester_start() { + TabDestroyObserver.init(); + //if testOnLoad was not called, then gConfig is not defined if (!gConfig) gConfig = readConfig(); if (gConfig.runUntilFailure) this.runUntilFailure = true; if (gConfig.repeat) @@ -269,16 +307,18 @@ Tester.prototype = { } } // Make sure the window is raised before each test. this.SimpleTest.waitForFocus(aCallback); }, finish: function Tester_finish(aSkipSummary) { + TabDestroyObserver.destroy(); + this.Promise.Debugging.flushUncaughtErrors(); var passCount = this.tests.reduce(function(a, f) a + f.passCount, 0); var failCount = this.tests.reduce(function(a, f) a + f.failCount, 0); var todoCount = this.tests.reduce(function(a, f) a + f.todoCount, 0); if (this.repeat > 0) { --this.repeat; @@ -578,16 +618,19 @@ Tester.prototype = { let {AsyncShutdown} = Cu.import("resource://gre/modules/AsyncShutdown.jsm", {}); let barrier = new AsyncShutdown.Barrier( "ShutdownLeaks: Wait for cleanup to be finished before checking for leaks"); Services.obs.notifyObservers({wrappedJSObject: barrier}, "shutdown-leaks-before-check", null); + barrier.client.addBlocker("ShutdownLeaks: Wait for tabs to finish closing", + TabDestroyObserver.wait()); + barrier.wait().then(() => { // Simulate memory pressure so that we're forced to free more resources // and thus get rid of more false leaks like already terminated workers. Services.obs.notifyObservers(null, "memory-pressure", "heap-minimize"); let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"] .getService(Ci.nsIMessageBroadcaster); ppmm.broadcastAsyncMessage("browser-test:collect-request");
--- a/toolkit/devtools/server/main.js +++ b/toolkit/devtools/server/main.js @@ -708,43 +708,43 @@ var DebuggerServer = { deferred.resolve(actor); }); aMm.sendAsyncMessage("DevTools:InitDebuggerServer", { prefix: prefix }); - function onDisconnect() { - Services.obs.removeObserver(onMessageManagerDisconnect, "message-manager-disconnect"); - events.off(aConnection, "closed", onDisconnect); + function onClose() { + Services.obs.removeObserver(onMessageManagerClose, "message-manager-close"); + events.off(aConnection, "closed", onClose); if (childTransport) { // If we have a child transport, the actor has already // been created. We need to stop using this message manager. childTransport.close(); childTransport = null; aConnection.cancelForwarding(prefix); // ... and notify the child process to clean the tab actors. try { aMm.sendAsyncMessage("debug:content-process-destroy"); } catch(e) {} } } - let onMessageManagerDisconnect = DevToolsUtils.makeInfallible(function (subject, topic, data) { + let onMessageManagerClose = DevToolsUtils.makeInfallible(function (subject, topic, data) { if (subject == aMm) { - onDisconnect(); + onClose(); aConnection.send({ from: actor.actor, type: "tabDetached" }); } }).bind(this); - Services.obs.addObserver(onMessageManagerDisconnect, - "message-manager-disconnect", false); + Services.obs.addObserver(onMessageManagerClose, + "message-manager-close", false); - events.on(aConnection, "closed", onDisconnect); + events.on(aConnection, "closed", onClose); return deferred.promise; }, /** * Check if the caller is running in a content child process. * * @return boolean @@ -877,19 +877,19 @@ var DebuggerServer = { netMonitor = new NetworkMonitorManager(aFrame, actor.actor); events.emit(DebuggerServer, "new-child-process", { mm: mm }); deferred.resolve(actor); }).bind(this); mm.addMessageListener("debug:actor", onActorCreated); - let onMessageManagerDisconnect = DevToolsUtils.makeInfallible(function (subject, topic, data) { + let onMessageManagerClose = DevToolsUtils.makeInfallible(function (subject, topic, data) { if (subject == mm) { - Services.obs.removeObserver(onMessageManagerDisconnect, topic); + Services.obs.removeObserver(onMessageManagerClose, topic); // provides hook to actor modules that need to exchange messages // between e10s parent and child processes this.emit("disconnected-from-child:" + childID, { mm: mm, childID: childID }); mm.removeMessageListener("debug:setup-in-parent", onSetupInParent); if (childTransport) { @@ -920,18 +920,18 @@ var DebuggerServer = { netMonitor = null; } if (aOnDisconnect) { aOnDisconnect(mm); } } }).bind(this); - Services.obs.addObserver(onMessageManagerDisconnect, - "message-manager-disconnect", false); + Services.obs.addObserver(onMessageManagerClose, + "message-manager-close", false); events.once(aConnection, "closed", () => { if (childTransport) { // When the client disconnects, we have to unplug the dedicated // ChildDebuggerTransport... childTransport.close(); childTransport = null; aConnection.cancelForwarding(prefix);