Skip to content

Commit

Permalink
Bug 1588259: Part 1 - Suspend windows when spinning event loop for wi…
Browse files Browse the repository at this point in the history
…ndow.open. r=smaug

This doesn't solve all problems with potential reentrancy during window.open
nested event loops, but it does improve the situation somewhat. Since any
window in the same BrowsingContextGroup can target any window in the same
group, we need to suspend all windows in the group, not just the root of the
new window's parent. We also need to make sure we suspend all in-process
windows, even if we have out-of-process frames somewhere in the parent chain.

This patch takes care of suspending timeouts and input event handling in all
of these cases. It doesn't block all potential paths for running code in the
suspended windows, though, so the next patch explicitly prevents the
problematic reentrancy.

Differential Revision: https://phabricator.services.mozilla.com/D57666

--HG--
extra : moz-landing-system : lando
  • Loading branch information
kmaglione committed Dec 19, 2019
1 parent d98bab4 commit 29c31af
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 19 deletions.
43 changes: 42 additions & 1 deletion dom/base/nsContentUtils.cpp
Expand Up @@ -20,7 +20,7 @@
#include "imgRequestProxy.h"
#include "jsapi.h"
#include "jsfriendapi.h"
#include "js/Array.h" // JS::NewArrayObject
#include "js/Array.h" // JS::NewArrayObject
#include "js/ArrayBuffer.h" // JS::{GetArrayBufferData,IsArrayBufferObject,NewArrayBuffer}
#include "js/JSON.h"
#include "js/RegExp.h" // JS::ExecuteRegExpNoStatics, JS::NewUCRegExpObject, JS::RegExpFlags
Expand Down Expand Up @@ -533,6 +533,47 @@ class SameOriginCheckerImpl final : public nsIChannelEventSink,

} // namespace

AutoSuppressEventHandlingAndSuspend::AutoSuppressEventHandlingAndSuspend(
BrowsingContextGroup* aGroup) {
for (const auto& bc : aGroup->Toplevels()) {
SuppressBrowsingContext(bc);
}
}

void AutoSuppressEventHandlingAndSuspend::SuppressBrowsingContext(
BrowsingContext* aBC) {
if (nsCOMPtr<nsPIDOMWindowOuter> win = aBC->GetDOMWindow()) {
if (RefPtr<Document> doc = win->GetExtantDoc()) {
mDocuments.AppendElement(doc);
mWindows.AppendElement(win->GetCurrentInnerWindow());
// Note: Document::SuppressEventHandling will also automatically suppress
// event handling for any in-process sub-documents. However, since we need
// to deal with cases where remote BrowsingContexts may be interleaved
// with in-process ones, we still need to walk the entire tree ourselves.
// This may be slightly redundant in some cases, but since event handling
// suppressions maintain a count of current blockers, it does not cause
// any problems.
doc->SuppressEventHandling();
win->GetCurrentInnerWindow()->Suspend();
}
}

BrowsingContext::Children children;
aBC->GetChildren(children);
for (const auto& bc : children) {
SuppressBrowsingContext(bc);
}
}

AutoSuppressEventHandlingAndSuspend::~AutoSuppressEventHandlingAndSuspend() {
for (const auto& win : mWindows) {
win->Resume();
}
for (const auto& doc : mDocuments) {
doc->UnsuppressEventHandlingAndFireEvents(true);
}
}

/**
* This class is used to determine whether or not the user is currently
* interacting with the browser. It listens to observer events to toggle the
Expand Down
22 changes: 22 additions & 0 deletions dom/base/nsContentUtils.h
Expand Up @@ -133,6 +133,8 @@ class TextEditor;
enum class StorageAccess;

namespace dom {
class BrowsingContext;
class BrowsingContextGroup;
class ContentFrameMessageManager;
struct CustomElementDefinition;
class DataTransfer;
Expand Down Expand Up @@ -3415,6 +3417,26 @@ class MOZ_STACK_CLASS nsAutoScriptBlockerSuppressNodeRemoved
namespace mozilla {
namespace dom {

/**
* Suppresses event handling and suspends the active inner window for all
* in-process documents in a BrowsingContextGroup. This should be used while
* spinning the event loop for a synchronous operation (like `window.open()`)
* which affects operations in any other window in the same BrowsingContext
* group.
*/

class MOZ_RAII AutoSuppressEventHandlingAndSuspend {
public:
explicit AutoSuppressEventHandlingAndSuspend(BrowsingContextGroup* aGroup);
~AutoSuppressEventHandlingAndSuspend();

private:
void SuppressBrowsingContext(BrowsingContext* aBC);

AutoTArray<RefPtr<Document>, 16> mDocuments;
AutoTArray<nsCOMPtr<nsPIDOMWindowInner>, 16> mWindows;
};

class TreeOrderComparator {
public:
bool Equals(nsINode* aElem1, nsINode* aElem2) const {
Expand Down
26 changes: 8 additions & 18 deletions dom/ipc/ContentChild.cpp
Expand Up @@ -1059,15 +1059,6 @@ nsresult ContentChild::ProvideWindowCommon(
return NS_ERROR_ABORT;
}

nsCOMPtr<nsPIDOMWindowInner> parentTopInnerWindow;
if (aParent) {
nsCOMPtr<nsPIDOMWindowOuter> parentTopWindow =
nsPIDOMWindowOuter::From(aParent)->GetInProcessTop();
if (parentTopWindow) {
parentTopInnerWindow = parentTopWindow->GetCurrentInnerWindow();
}
}

// Set to true when we're ready to return from this function.
bool ready = false;

Expand Down Expand Up @@ -1236,12 +1227,15 @@ nsresult ContentChild::ProvideWindowCommon(
}
});

// Suspend our window if we have one to make sure we don't re-enter it.
if (parentTopInnerWindow) {
parentTopInnerWindow->Suspend();
}

{
// Suppress event handling for all contexts in our BrowsingContextGroup so
// that event handlers cannot target our new window while it's still being
// opened. Note that pending events that were suppressed while our blocker
// was active will be dispatched asynchronously from a runnable dispatched
// to the main event loop after this function returns, not immediately when
// we leave this scope.
AutoSuppressEventHandlingAndSuspend seh(browsingContext->Group());

AutoNoJSAPI nojsapi;

// Spin the event loop until we get a response. Callers of this function
Expand All @@ -1254,10 +1248,6 @@ nsresult ContentChild::ProvideWindowCommon(
"loop without ready being true.");
}

if (parentTopInnerWindow) {
parentTopInnerWindow->Resume();
}

// =====================
// End Nested Event Loop
// =====================
Expand Down

0 comments on commit 29c31af

Please sign in to comment.