Bug 1730117 - Part 2: Make EnterModalState suppress event handling for the nested in-process documents; r=smaug

This also makes nsIDOMWindowUtils::SuppressEventHandling work properly.

Differential Revision: https://phabricator.services.mozilla.com/D125615
This commit is contained in:
Edgar Chen 2021-09-22 14:50:56 +00:00
Родитель e016ae9d81
Коммит 3aeb8d0b4b
8 изменённых файлов: 248 добавлений и 25 удалений

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

@ -1635,13 +1635,13 @@ nsDOMWindowUtils::DisableNonTestMouseEvents(bool aDisable) {
NS_IMETHODIMP
nsDOMWindowUtils::SuppressEventHandling(bool aSuppress) {
nsCOMPtr<Document> doc = GetDocument();
NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
NS_ENSURE_STATE(window);
if (aSuppress) {
doc->SuppressEventHandling();
window->SuppressEventHandling();
} else {
doc->UnsuppressEventHandlingAndFireEvents(true);
window->UnsuppressEventHandling();
}
return NS_OK;

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

@ -1492,7 +1492,7 @@ void nsGlobalWindowOuter::ShutDown() {
void nsGlobalWindowOuter::DropOuterWindowDocs() {
MOZ_ASSERT_IF(mDoc, !mDoc->EventHandlingSuppressed());
mDoc = nullptr;
mSuspendedDoc = nullptr;
mSuspendedDocs.Clear();
}
void nsGlobalWindowOuter::CleanUp() {
@ -1597,7 +1597,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindowOuter)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mArguments)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocalStorage)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuspendedDoc)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuspendedDocs)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentPrincipal)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentStoragePrincipal)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentPartitionedPrincipal)
@ -1629,7 +1629,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindowOuter)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mArguments)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalStorage)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuspendedDoc)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuspendedDocs)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentPrincipal)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentStoragePrincipal)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentPartitionedPrincipal)
@ -2157,9 +2157,9 @@ nsresult nsGlobalWindowOuter::SetNewDocument(Document* aDocument,
mDelayedCloseForPrinting = false;
mDelayedPrintUntilAfterLoad = false;
// Take this opportunity to clear mSuspendedDoc. Our old inner window is now
// Take this opportunity to clear mSuspendedDocs. Our old inner window is now
// responsible for unsuspending it.
mSuspendedDoc = nullptr;
mSuspendedDocs.Clear();
#ifdef DEBUG
mLastOpenedURI = aDocument->GetDocumentURI();
@ -6356,6 +6356,44 @@ void nsGlobalWindowOuter::ReallyCloseWindow() {
CleanUp();
}
void nsGlobalWindowOuter::SuppressEventHandling() {
if (mSuppressEventHandlingDepth == 0) {
if (BrowsingContext* bc = GetBrowsingContext()) {
bc->PreOrderWalk([&](BrowsingContext* aBC) {
if (nsCOMPtr<nsPIDOMWindowOuter> win = aBC->GetDOMWindow()) {
if (RefPtr<Document> doc = win->GetExtantDoc()) {
mSuspendedDocs.AppendElement(doc);
// 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();
}
}
});
}
}
mSuppressEventHandlingDepth++;
}
void nsGlobalWindowOuter::UnsuppressEventHandling() {
MOZ_ASSERT(mSuppressEventHandlingDepth != 0);
mSuppressEventHandlingDepth--;
if (mSuppressEventHandlingDepth == 0 && mSuspendedDocs.Length()) {
RefPtr<Document> currentDoc = GetExtantDoc();
bool fireEvent = currentDoc == mSuspendedDocs[0];
nsTArray<RefPtr<Document>> suspendedDocs = std::move(mSuspendedDocs);
for (const auto& doc : suspendedDocs) {
doc->UnsuppressEventHandlingAndFireEvents(fireEvent);
}
}
}
nsGlobalWindowOuter* nsGlobalWindowOuter::EnterModalState() {
// GetInProcessScriptableTop, not GetInProcessTop, so that EnterModalState
// works properly with <iframe mozbrowser>.
@ -6406,12 +6444,7 @@ nsGlobalWindowOuter* nsGlobalWindowOuter::EnterModalState() {
}
if (topWin->mModalStateDepth == 0) {
NS_ASSERTION(!topWin->mSuspendedDoc, "Shouldn't have mSuspendedDoc here!");
topWin->mSuspendedDoc = topDoc;
if (topDoc) {
topDoc->SuppressEventHandling();
}
topWin->SuppressEventHandling();
if (nsGlobalWindowInner* inner = topWin->GetCurrentInnerWindowInternal()) {
inner->Suspend();
@ -6445,12 +6478,7 @@ void nsGlobalWindowOuter::LeaveModalState() {
inner->Resume();
}
if (mSuspendedDoc) {
nsCOMPtr<Document> currentDoc = GetExtantDoc();
mSuspendedDoc->UnsuppressEventHandlingAndFireEvents(currentDoc ==
mSuspendedDoc);
mSuspendedDoc = nullptr;
}
UnsuppressEventHandling();
}
// Remember the time of the last dialog quit.
@ -7711,6 +7739,7 @@ mozilla::dom::DocGroup* nsPIDOMWindowOuter::GetDocGroup() const {
nsPIDOMWindowOuter::nsPIDOMWindowOuter(uint64_t aWindowID)
: mFrameElement(nullptr),
mModalStateDepth(0),
mSuppressEventHandlingDepth(0),
mIsBackground(false),
mMediaSuspend(StaticPrefs::media_block_autoplay_until_in_foreground()
? nsISuspendedTypes::SUSPENDED_BLOCK

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

@ -328,6 +328,9 @@ class nsGlobalWindowOuter final : public mozilla::dom::EventTarget,
// Outer windows only.
virtual void EnsureSizeAndPositionUpToDate() override;
virtual void SuppressEventHandling() override;
virtual void UnsuppressEventHandling() override;
MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual nsGlobalWindowOuter* EnterModalState()
override;
virtual void LeaveModalState() override;
@ -1128,10 +1131,10 @@ class nsGlobalWindowOuter final : public mozilla::dom::EventTarget,
// It's useful when we get matched EnterModalState/LeaveModalState calls, in
// which case the outer window is responsible for unsuspending events on the
// document. If we don't (for example, if the outer window is closed before
// the LeaveModalState call), then the inner window whose mDoc is our
// mSuspendedDoc is responsible for unsuspending it.
RefPtr<Document> mSuspendedDoc;
// documents. If we don't (for example, if the outer window is closed before
// the LeaveModalState call), then the inner window whose mDoc is in our
// mSuspendedDocs is responsible for unsuspending.
nsTArray<RefPtr<Document>> mSuspendedDocs;
// This is the CC generation the last time we called CanSkip.
uint32_t mCanSkipCCGeneration;

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

@ -910,6 +910,13 @@ class nsPIDOMWindowOuter : public mozIDOMWindowProxy {
*/
virtual void EnsureSizeAndPositionUpToDate() = 0;
/**
* Suppresses/unsuppresses user initiated event handling in window's document
* and all in-process descendant documents.
*/
virtual void SuppressEventHandling() = 0;
virtual void UnsuppressEventHandling() = 0;
/**
* Callback for notifying a window about a modal dialog being
* opened/closed with the window as a parent.
@ -1124,6 +1131,8 @@ class nsPIDOMWindowOuter : public mozIDOMWindowProxy {
uint32_t mModalStateDepth;
uint32_t mSuppressEventHandlingDepth;
// Tracks whether our docshell is active. If it is, mIsBackground
// is false. Too bad we have so many different concepts of
// "active".

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

@ -0,0 +1,79 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test event suppression</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<script src="/tests/SimpleTest/paint_listener.js"></script>
<script src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<div>Top</div>
<script type="application/javascript">
function waitForMessage(aMsg, aCallback) {
window.addEventListener("message", function handler(e) {
if (e.data != aMsg) {
return;
}
info(`received: ${e.data}`);
window.removeEventListener("message", handler);
if (aCallback) {
aCallback(e);
}
});
}
function waitForClickEvent(aElement, aWindow) {
return new Promise((aResolve) => {
aElement.addEventListener("click", aResolve, { once: true });
synthesizeMouseAtCenter(aElement, { type: "mousedown" }, aWindow);
synthesizeMouseAtCenter(aElement, { type: "mouseup" }, aWindow);
});
}
waitForMessage("ready", async function(e) {
await waitUntilApzStable();
let innerWin = e.source;
let innerDiv = innerWin.document.querySelector("div");
let eventCount = 0;
innerDiv.addEventListener("mousemove", function() {
eventCount++;
});
// Test that event handling is suppressed.
let utils = SpecialPowers.getDOMWindowUtils(window);
utils.suppressEventHandling(true);
const TOTAL = 100;
for (let i = 0; i < TOTAL; i++) {
synthesizeMouseAtCenter(innerDiv, { type: "mousemove" }, innerWin);
}
utils.suppressEventHandling(false);
// Wait for click event to ensure we have received all mousemove events.
await waitForClickEvent(innerDiv, innerWin);
opener.info(`eventCount: ${eventCount}`);
opener.ok(eventCount < TOTAL, "event should be suspressed");
// Test that event handling is not suppressed.
eventCount = 0;
for (let i = 0; i < TOTAL; i++) {
synthesizeMouseAtCenter(innerDiv, { type: "mousemove" }, innerWin);
}
// Wait for click event to ensure we have received all mousemove events.
await waitForClickEvent(innerDiv, innerWin);
opener.info(`eventCount: ${eventCount}`);
opener.is(eventCount, TOTAL, "event should not be suspressed");
opener.postMessage("done", "*");
});
</script>
<iframe src="http://example.org/tests/dom/base/test/file_suppressed_events_middle.html"></iframe>
</body>
</html>

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

@ -0,0 +1,79 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test event suppression</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<script src="/tests/SimpleTest/paint_listener.js"></script>
<script src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<div>Top</div>
<script type="application/javascript">
function waitForMessage(aMsg, aCallback) {
window.addEventListener("message", function handler(e) {
if (e.data != aMsg) {
return;
}
info(`received: ${e.data}`);
window.removeEventListener("message", handler);
if (aCallback) {
aCallback(e);
}
});
}
function waitForClickEvent(aElement, aWindow) {
return new Promise((aResolve) => {
aElement.addEventListener("click", aResolve, { once: true });
synthesizeMouseAtCenter(aElement, { type: "mousedown" }, aWindow);
synthesizeMouseAtCenter(aElement, { type: "mouseup" }, aWindow);
});
}
waitForMessage("ready", async function(e) {
await waitUntilApzStable();
let innerWin = e.source;
let innerDiv = innerWin.document.querySelector("div");
let eventCount = 0;
innerDiv.addEventListener("mousemove", function() {
eventCount++;
});
// Test that event handling is suppressed.
let utils = SpecialPowers.getDOMWindowUtils(window);
utils.enterModalState();
const TOTAL = 100;
for (let i = 0; i < TOTAL; i++) {
synthesizeMouseAtCenter(innerDiv, { type: "mousemove" }, innerWin);
}
utils.leaveModalState();
// Wait for click event to ensure we have received all mousemove events.
await waitForClickEvent(innerDiv, innerWin);
opener.info(`eventCount: ${eventCount}`);
opener.ok(eventCount < TOTAL, "event should be suspressed");
// Test that event handling is not suppressed.
eventCount = 0;
for (let i = 0; i < TOTAL; i++) {
synthesizeMouseAtCenter(innerDiv, { type: "mousemove" }, innerWin);
}
// Wait for click event to ensure we have received all mousemove events.
await waitForClickEvent(innerDiv, innerWin);
opener.info(`eventCount: ${eventCount}`);
opener.is(eventCount, TOTAL, "event should not be suspressed");
opener.postMessage("done", "*");
});
</script>
<iframe src="http://example.org/tests/dom/base/test/file_suppressed_events_middle.html"></iframe>
</body>
</html>

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

@ -778,6 +778,8 @@ support-files =
skip-if = os == "android"
support-files =
file_suppressed_events_top_xhr.html
file_suppressed_events_top_modalstate.html
file_suppressed_events_top.html
file_suppressed_events_middle.html
file_suppressed_events_inner.html
!/gfx/layers/apz/test/mochitest/apz_test_utils.js

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

@ -43,6 +43,28 @@ add_task(async function test_sync_xhr() {
w.close();
});
add_task(async function test_modalstate() {
await SpecialPowers.pushPrefEnv({"set": [
["test.events.async.enabled", false],
["dom.events.coalesce.mousemove", false],
]});
let w = window.open("file_suppressed_events_top_modalstate.html");
await waitForMessage("done");
w.close();
});
add_task(async function test_suppress_event_handling() {
await SpecialPowers.pushPrefEnv({"set": [
["test.events.async.enabled", false],
["dom.events.coalesce.mousemove", false],
]});
let w = window.open("file_suppressed_events_top.html");
await waitForMessage("done");
w.close();
});
</script>
</pre>
</body>