Bug 1588259: Part 1 - Suspend windows when spinning event loop for window.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
This commit is contained in:
Kris Maglione 2019-12-19 19:53:14 +00:00
Родитель d98bab47bb
Коммит 29c31af3ee
3 изменённых файлов: 72 добавлений и 19 удалений

Просмотреть файл

@ -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
@ -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

Просмотреть файл

@ -133,6 +133,8 @@ class TextEditor;
enum class StorageAccess;
namespace dom {
class BrowsingContext;
class BrowsingContextGroup;
class ContentFrameMessageManager;
struct CustomElementDefinition;
class DataTransfer;
@ -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 {

Просмотреть файл

@ -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;
@ -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
@ -1254,10 +1248,6 @@ nsresult ContentChild::ProvideWindowCommon(
"loop without ready being true.");
}
if (parentTopInnerWindow) {
parentTopInnerWindow->Resume();
}
// =====================
// End Nested Event Loop
// =====================