Skip to content

Commit

Permalink
Bug 1126089 - Allow messages to be sent after frame script unload event.
Browse files Browse the repository at this point in the history
  • Loading branch information
rmottola committed Jul 5, 2019
1 parent b1e7429 commit 167ebbd
Show file tree
Hide file tree
Showing 19 changed files with 583 additions and 182 deletions.
6 changes: 3 additions & 3 deletions b2g/components/Frames.jsm
Expand Up @@ -25,7 +25,7 @@ const Observer = {
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;
Expand All @@ -40,7 +40,7 @@ const Observer = {
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();
},
Expand All @@ -61,7 +61,7 @@ const Observer = {
break;

// Every time an iframe is destroyed, its message manager also is
case 'message-manager-disconnect':
case 'message-manager-close':
this.onMessageManagerDestroyed(subject);
break;
}
Expand Down
28 changes: 7 additions & 21 deletions dom/base/nsDocument.cpp
Expand Up @@ -7311,14 +7311,14 @@ nsDocument::InitializeFrameLoader(nsFrameLoader* aLoader)
}

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);
Expand All @@ -7343,7 +7343,7 @@ nsDocument::MaybeInitializeFinalizeFrameLoaders()
if (!nsContentUtils::IsSafeToRunScript()) {
if (!mInDestructor && !mFrameLoaderRunner &&
(mInitializableFrameLoaders.Length() ||
mFinalizableFrameLoaders.Length())) {
mFrameLoaderFinalizers.Length())) {
mFrameLoaderRunner =
NS_NewRunnableMethod(this, &nsDocument::MaybeInitializeFinalizeFrameLoaders);
nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
Expand All @@ -7362,12 +7362,12 @@ nsDocument::MaybeInitializeFinalizeFrameLoaders()
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();
}
}
}
Expand All @@ -7384,20 +7384,6 @@ nsDocument::TryCancelFrameLoaderInitialization(nsIDocShell* aShell)
}
}

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,
Expand Down
5 changes: 2 additions & 3 deletions dom/base/nsDocument.h
Expand Up @@ -1034,9 +1034,8 @@ class nsDocument : public nsIDocument,
virtual void FlushSkinBindings() override;

virtual nsresult InitializeFrameLoader(nsFrameLoader* aLoader) override;
virtual nsresult FinalizeFrameLoader(nsFrameLoader* aLoader) override;
virtual nsresult FinalizeFrameLoader(nsFrameLoader* aLoader, nsIRunnable* aFinalizer) override;
virtual void TryCancelFrameLoaderInitialization(nsIDocShell* aShell) override;
virtual bool FrameLoaderScheduledToBeFinalized(nsIDocShell* aShell) override;
virtual nsIDocument*
RequestExternalResource(nsIURI* aURI,
nsINode* aRequestingNode,
Expand Down Expand Up @@ -1765,7 +1764,7 @@ class nsDocument : public nsIDocument,
nsString mLastStyleSheetSet;

nsTArray<nsRefPtr<nsFrameLoader> > mInitializableFrameLoaders;
nsTArray<nsRefPtr<nsFrameLoader> > mFinalizableFrameLoaders;
nsTArray<nsCOMPtr<nsIRunnable> > mFrameLoaderFinalizers;
nsRefPtr<nsRunnableMethod<nsDocument> > mFrameLoaderRunner;

nsRevocableEventPtr<nsRunnableMethod<nsDocument, void, false> >
Expand Down
208 changes: 153 additions & 55 deletions dom/base/nsFrameLoader.cpp
Expand Up @@ -107,25 +107,6 @@ 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
Expand Down Expand Up @@ -181,7 +162,6 @@ nsFrameLoader::nsFrameLoader(Element* aOwner, bool aNetworkCreated)

nsFrameLoader::~nsFrameLoader()
{
mNeedsAsyncDestroy = true;
if (mMessageManager) {
mMessageManager->Disconnect();
}
Expand Down Expand Up @@ -523,16 +503,6 @@ nsFrameLoader::GetDocShell(nsIDocShell **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)
Expand Down Expand Up @@ -1358,29 +1328,59 @@ nsFrameLoader::SwapWithOtherLoader(nsFrameLoader* aOther,
return NS_OK;
}

void
nsFrameLoader::DestroyChild()
NS_IMETHODIMP
nsFrameLoader::Destroy()
{
if (mRemoteBrowser) {
mRemoteBrowser->SetOwnerElement(nullptr);
mRemoteBrowser->Destroy();
mRemoteBrowser = nullptr;
}
StartDestroy();
return NS_OK;
}

NS_IMETHODIMP
nsFrameLoader::Destroy()
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() 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;
Expand All @@ -1392,7 +1392,6 @@ nsFrameLoader::Destroy()

SetOwnerContent(nullptr);
}
DestroyChild();

// Seems like this is a dynamic frame removal.
if (dynamicSubframeRemoval) {
Expand All @@ -1412,7 +1411,7 @@ nsFrameLoader::Destroy()
}
}
}

// Let our window know that we are gone
if (mDocShell) {
nsCOMPtr<nsPIDOMWindow> win_private(mDocShell->GetWindow());
Expand All @@ -1421,21 +1420,120 @@ nsFrameLoader::Destroy()
}
}

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);
nsCOMPtr<nsIRunnable> destroyRunnable = new nsFrameLoaderDestroyRunnable(this);
if (mNeedsAsyncDestroy || !doc ||
NS_FAILED(doc->FinalizeFrameLoader(this, destroyRunnable))) {
NS_DispatchToCurrentThread(destroyRunnable);
}
}

// Let go of our docshell now that the async destroyer holds on to
// the docshell.
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;
}

mDocShell = nullptr;
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();
}

// NOTE: 'this' may very well be gone by now.
// Fire the "unload" event if we're in-process.
if (mChildMessageManager) {
static_cast<nsInProcessTabChildGlobal*>(mChildMessageManager.get())->FireUnloadEvent();
}

return NS_OK;
// 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
Expand Down

0 comments on commit 167ebbd

Please sign in to comment.