Bug 1730117 - Part 1: Make sync XHR suppress event handling for the nested in-process documents; r=smaug
Differential Revision: https://phabricator.services.mozilla.com/D125187
This commit is contained in:
@@ -15,6 +15,33 @@
|
|||||||
#include "nsTArray.h"
|
#include "nsTArray.h"
|
||||||
|
|
||||||
namespace mozilla::dom {
|
namespace mozilla::dom {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suppresses event handling and suspends for all in-process documents in a
|
||||||
|
* BrowsingContext subtree.
|
||||||
|
*/
|
||||||
|
class MOZ_RAII AutoSuppressEventHandling
|
||||||
|
: private AutoWalkBrowsingContextGroup {
|
||||||
|
public:
|
||||||
|
explicit AutoSuppressEventHandling(BrowsingContext* aContext) {
|
||||||
|
if (aContext) {
|
||||||
|
SuppressBrowsingContext(aContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit AutoSuppressEventHandling(BrowsingContextGroup* aGroup) {
|
||||||
|
if (aGroup) {
|
||||||
|
SuppressBrowsingContextGroup(aGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~AutoSuppressEventHandling();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void SuppressDocument(Document* aDocument) override;
|
||||||
|
void UnsuppressDocument(Document* aDocument) override;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Suppresses event handling and suspends the active inner window for all
|
* Suppresses event handling and suspends the active inner window for all
|
||||||
* in-process documents in a BrowsingContextGroup. This should be used while
|
* in-process documents in a BrowsingContextGroup. This should be used while
|
||||||
@@ -22,21 +49,16 @@ namespace mozilla::dom {
|
|||||||
* which affects operations in any other window in the same BrowsingContext
|
* which affects operations in any other window in the same BrowsingContext
|
||||||
* group.
|
* group.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class MOZ_RAII AutoSuppressEventHandlingAndSuspend
|
class MOZ_RAII AutoSuppressEventHandlingAndSuspend
|
||||||
: private AutoWalkBrowsingContextGroup {
|
: private AutoSuppressEventHandling {
|
||||||
public:
|
public:
|
||||||
explicit AutoSuppressEventHandlingAndSuspend(BrowsingContextGroup* aGroup) {
|
explicit AutoSuppressEventHandlingAndSuspend(BrowsingContextGroup* aGroup)
|
||||||
if (aGroup) {
|
: AutoSuppressEventHandling(aGroup) {}
|
||||||
SuppressBrowsingContextGroup(aGroup);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
~AutoSuppressEventHandlingAndSuspend();
|
~AutoSuppressEventHandlingAndSuspend();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void SuppressDocument(Document* aDocument) override;
|
void SuppressDocument(Document* aDocument) override;
|
||||||
void UnsuppressDocument(Document* aDocument) override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AutoTArray<nsCOMPtr<nsPIDOMWindowInner>, 16> mWindows;
|
AutoTArray<nsCOMPtr<nsPIDOMWindowInner>, 16> mWindows;
|
||||||
|
|||||||
@@ -673,7 +673,7 @@ class SameOriginCheckerImpl final : public nsIChannelEventSink,
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void AutoSuppressEventHandlingAndSuspend::SuppressDocument(Document* aDoc) {
|
void AutoSuppressEventHandling::SuppressDocument(Document* aDoc) {
|
||||||
// Note: Document::SuppressEventHandling will also automatically suppress
|
// Note: Document::SuppressEventHandling will also automatically suppress
|
||||||
// event handling for any in-process sub-documents. However, since we need
|
// event handling for any in-process sub-documents. However, since we need
|
||||||
// to deal with cases where remote BrowsingContexts may be interleaved
|
// to deal with cases where remote BrowsingContexts may be interleaved
|
||||||
@@ -682,21 +682,28 @@ void AutoSuppressEventHandlingAndSuspend::SuppressDocument(Document* aDoc) {
|
|||||||
// suppressions maintain a count of current blockers, it does not cause
|
// suppressions maintain a count of current blockers, it does not cause
|
||||||
// any problems.
|
// any problems.
|
||||||
aDoc->SuppressEventHandling();
|
aDoc->SuppressEventHandling();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoSuppressEventHandling::UnsuppressDocument(Document* aDoc) {
|
||||||
|
aDoc->UnsuppressEventHandlingAndFireEvents(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
AutoSuppressEventHandling::~AutoSuppressEventHandling() {
|
||||||
|
UnsuppressDocuments();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoSuppressEventHandlingAndSuspend::SuppressDocument(Document* aDoc) {
|
||||||
|
AutoSuppressEventHandling::SuppressDocument(aDoc);
|
||||||
if (nsCOMPtr<nsPIDOMWindowInner> win = aDoc->GetInnerWindow()) {
|
if (nsCOMPtr<nsPIDOMWindowInner> win = aDoc->GetInnerWindow()) {
|
||||||
win->Suspend();
|
win->Suspend();
|
||||||
mWindows.AppendElement(win);
|
mWindows.AppendElement(win);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AutoSuppressEventHandlingAndSuspend::UnsuppressDocument(Document* aDoc) {
|
|
||||||
aDoc->UnsuppressEventHandlingAndFireEvents(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
AutoSuppressEventHandlingAndSuspend::~AutoSuppressEventHandlingAndSuspend() {
|
AutoSuppressEventHandlingAndSuspend::~AutoSuppressEventHandlingAndSuspend() {
|
||||||
for (const auto& win : mWindows) {
|
for (const auto& win : mWindows) {
|
||||||
win->Resume();
|
win->Resume();
|
||||||
}
|
}
|
||||||
UnsuppressDocuments();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
16
dom/base/test/file_suppressed_events_inner.html
Normal file
16
dom/base/test/file_suppressed_events_inner.html
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Test event suppression</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>Inner</div>
|
||||||
|
<script type="application/javascript">
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
top.postMessage("ready", "*");
|
||||||
|
};
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
10
dom/base/test/file_suppressed_events_middle.html
Normal file
10
dom/base/test/file_suppressed_events_middle.html
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Test event suppression</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>Middle</div>
|
||||||
|
<iframe src="http://mochi.test:8888/tests/dom/base/test/file_suppressed_events_inner.html"></iframe>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
82
dom/base/test/file_suppressed_events_top_xhr.html
Normal file
82
dom/base/test/file_suppressed_events_top_xhr.html
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<!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 xhr = new XMLHttpRequest();
|
||||||
|
opener.info("xhr open");
|
||||||
|
xhr.open('GET', 'slow.sjs', false);
|
||||||
|
|
||||||
|
const TOTAL = 100;
|
||||||
|
for (let i = 0; i < TOTAL; i++) {
|
||||||
|
synthesizeMouseAtCenter(innerDiv, { type: "mousemove" }, innerWin);
|
||||||
|
}
|
||||||
|
xhr.send();
|
||||||
|
opener.info(`xhr done`);
|
||||||
|
|
||||||
|
// 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>
|
||||||
@@ -774,6 +774,13 @@ skip-if = debug == false
|
|||||||
[test_suppressed_events_and_scrolling.html]
|
[test_suppressed_events_and_scrolling.html]
|
||||||
support-files =
|
support-files =
|
||||||
file_suppressed_events_and_scrolling.html
|
file_suppressed_events_and_scrolling.html
|
||||||
|
[test_suppressed_events_nested_iframe.html]
|
||||||
|
skip-if = os == "android"
|
||||||
|
support-files =
|
||||||
|
file_suppressed_events_top_xhr.html
|
||||||
|
file_suppressed_events_middle.html
|
||||||
|
file_suppressed_events_inner.html
|
||||||
|
!/gfx/layers/apz/test/mochitest/apz_test_utils.js
|
||||||
[test_suppressed_microtasks.html]
|
[test_suppressed_microtasks.html]
|
||||||
skip-if = debug || asan || verify || toolkit == 'android' # The test needs to run reasonably fast.
|
skip-if = debug || asan || verify || toolkit == 'android' # The test needs to run reasonably fast.
|
||||||
[test_text_wholeText.html]
|
[test_text_wholeText.html]
|
||||||
|
|||||||
49
dom/base/test/test_suppressed_events_nested_iframe.html
Normal file
49
dom/base/test/test_suppressed_events_nested_iframe.html
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<!--
|
||||||
|
https://bugzilla.mozilla.org/show_bug.cgi?id=1730117
|
||||||
|
-->
|
||||||
|
<head>
|
||||||
|
<title>Test event suppression on nested iframe</title>
|
||||||
|
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1730117">Mozilla Bug 1730117</a>
|
||||||
|
<p id="display"></p>
|
||||||
|
<div id="content" style="display: none">
|
||||||
|
</div>
|
||||||
|
<pre id="test">
|
||||||
|
<script type="application/javascript">
|
||||||
|
|
||||||
|
function waitForMessage(aMsg) {
|
||||||
|
return new Promise((aResolve) => {
|
||||||
|
window.addEventListener("message", function handler(e) {
|
||||||
|
info(`receive: ${e.data}`);
|
||||||
|
if (e.data != aMsg) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.removeEventListener("message", handler);
|
||||||
|
aResolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Test for Bug 1730117 **/
|
||||||
|
|
||||||
|
add_task(async function test_sync_xhr() {
|
||||||
|
await SpecialPowers.pushPrefEnv({"set": [
|
||||||
|
["test.events.async.enabled", true],
|
||||||
|
["dom.events.coalesce.mousemove", false],
|
||||||
|
]});
|
||||||
|
|
||||||
|
let w = window.open("file_suppressed_events_top_xhr.html");
|
||||||
|
await waitForMessage("done");
|
||||||
|
w.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
#include "mozilla/BasePrincipal.h"
|
#include "mozilla/BasePrincipal.h"
|
||||||
#include "mozilla/CheckedInt.h"
|
#include "mozilla/CheckedInt.h"
|
||||||
#include "mozilla/Components.h"
|
#include "mozilla/Components.h"
|
||||||
|
#include "mozilla/dom/AutoSuppressEventHandlingAndSuspend.h"
|
||||||
#include "mozilla/dom/BlobBinding.h"
|
#include "mozilla/dom/BlobBinding.h"
|
||||||
#include "mozilla/dom/BlobURLProtocolHandler.h"
|
#include "mozilla/dom/BlobURLProtocolHandler.h"
|
||||||
#include "mozilla/dom/DocGroup.h"
|
#include "mozilla/dom/DocGroup.h"
|
||||||
@@ -2797,15 +2798,10 @@ void XMLHttpRequestMainThread::EnsureChannelContentType() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void XMLHttpRequestMainThread::UnsuppressEventHandlingAndResume() {
|
void XMLHttpRequestMainThread::ResumeTimeout() {
|
||||||
MOZ_ASSERT(NS_IsMainThread());
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
MOZ_ASSERT(mFlagSynchronous);
|
MOZ_ASSERT(mFlagSynchronous);
|
||||||
|
|
||||||
if (mSuspendedDoc) {
|
|
||||||
mSuspendedDoc->UnsuppressEventHandlingAndFireEvents(true);
|
|
||||||
mSuspendedDoc = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mResumeTimeoutRunnable) {
|
if (mResumeTimeoutRunnable) {
|
||||||
DispatchToMainThread(mResumeTimeoutRunnable.forget());
|
DispatchToMainThread(mResumeTimeoutRunnable.forget());
|
||||||
mResumeTimeoutRunnable = nullptr;
|
mResumeTimeoutRunnable = nullptr;
|
||||||
@@ -3028,7 +3024,15 @@ void XMLHttpRequestMainThread::SendInternal(const BodyExtractorBase* aBody,
|
|||||||
mFlagSend = true;
|
mFlagSend = true;
|
||||||
|
|
||||||
// If we're synchronous, spin an event loop here and wait
|
// If we're synchronous, spin an event loop here and wait
|
||||||
|
RefPtr<Document> suspendedDoc;
|
||||||
if (mFlagSynchronous) {
|
if (mFlagSynchronous) {
|
||||||
|
auto scopeExit = MakeScopeExit([&] {
|
||||||
|
CancelSyncTimeoutTimer();
|
||||||
|
ResumeTimeout();
|
||||||
|
ResumeEventDispatching();
|
||||||
|
});
|
||||||
|
Maybe<AutoSuppressEventHandling> autoSuppress;
|
||||||
|
|
||||||
mFlagSyncLooping = true;
|
mFlagSyncLooping = true;
|
||||||
|
|
||||||
if (GetOwner()) {
|
if (GetOwner()) {
|
||||||
@@ -3036,10 +3040,8 @@ void XMLHttpRequestMainThread::SendInternal(const BodyExtractorBase* aBody,
|
|||||||
GetOwner()->GetOuterWindow()->GetInProcessTop()) {
|
GetOwner()->GetOuterWindow()->GetInProcessTop()) {
|
||||||
if (nsCOMPtr<nsPIDOMWindowInner> topInner =
|
if (nsCOMPtr<nsPIDOMWindowInner> topInner =
|
||||||
topWindow->GetCurrentInnerWindow()) {
|
topWindow->GetCurrentInnerWindow()) {
|
||||||
mSuspendedDoc = topWindow->GetExtantDoc();
|
suspendedDoc = topWindow->GetExtantDoc();
|
||||||
if (mSuspendedDoc) {
|
autoSuppress.emplace(topWindow->GetBrowsingContext());
|
||||||
mSuspendedDoc->SuppressEventHandling();
|
|
||||||
}
|
|
||||||
topInner->Suspend();
|
topInner->Suspend();
|
||||||
mResumeTimeoutRunnable = new nsResumeTimeoutsEvent(topInner);
|
mResumeTimeoutRunnable = new nsResumeTimeoutsEvent(topInner);
|
||||||
}
|
}
|
||||||
@@ -3048,11 +3050,6 @@ void XMLHttpRequestMainThread::SendInternal(const BodyExtractorBase* aBody,
|
|||||||
|
|
||||||
SuspendEventDispatching();
|
SuspendEventDispatching();
|
||||||
StopProgressEventTimer();
|
StopProgressEventTimer();
|
||||||
auto scopeExit = MakeScopeExit([&] {
|
|
||||||
CancelSyncTimeoutTimer();
|
|
||||||
UnsuppressEventHandlingAndResume();
|
|
||||||
ResumeEventDispatching();
|
|
||||||
});
|
|
||||||
|
|
||||||
SyncTimeoutType syncTimeoutType = MaybeStartSyncTimeoutTimer();
|
SyncTimeoutType syncTimeoutType = MaybeStartSyncTimeoutTimer();
|
||||||
if (syncTimeoutType == eErrorOrExpired) {
|
if (syncTimeoutType == eErrorOrExpired) {
|
||||||
@@ -3061,7 +3058,7 @@ void XMLHttpRequestMainThread::SendInternal(const BodyExtractorBase* aBody,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
nsAutoSyncOperation sync(mSuspendedDoc,
|
nsAutoSyncOperation sync(suspendedDoc,
|
||||||
SyncOperationBehavior::eSuspendInput);
|
SyncOperationBehavior::eSuspendInput);
|
||||||
if (!SpinEventLoopUntil([&]() { return !mFlagSyncLooping; })) {
|
if (!SpinEventLoopUntil([&]() { return !mFlagSyncLooping; })) {
|
||||||
aRv.Throw(NS_ERROR_UNEXPECTED);
|
aRv.Throw(NS_ERROR_UNEXPECTED);
|
||||||
|
|||||||
@@ -304,7 +304,7 @@ class XMLHttpRequestMainThread final : public XMLHttpRequest,
|
|||||||
bool IsCrossSiteCORSRequest() const;
|
bool IsCrossSiteCORSRequest() const;
|
||||||
bool IsDeniedCrossSiteCORSRequest();
|
bool IsDeniedCrossSiteCORSRequest();
|
||||||
|
|
||||||
void UnsuppressEventHandlingAndResume();
|
void ResumeTimeout();
|
||||||
|
|
||||||
void MaybeLowerChannelPriority();
|
void MaybeLowerChannelPriority();
|
||||||
|
|
||||||
@@ -679,7 +679,6 @@ class XMLHttpRequestMainThread final : public XMLHttpRequest,
|
|||||||
void StartTimeoutTimer();
|
void StartTimeoutTimer();
|
||||||
void HandleTimeoutCallback();
|
void HandleTimeoutCallback();
|
||||||
|
|
||||||
RefPtr<Document> mSuspendedDoc;
|
|
||||||
nsCOMPtr<nsIRunnable> mResumeTimeoutRunnable;
|
nsCOMPtr<nsIRunnable> mResumeTimeoutRunnable;
|
||||||
|
|
||||||
nsCOMPtr<nsITimer> mSyncTimeoutTimer;
|
nsCOMPtr<nsITimer> mSyncTimeoutTimer;
|
||||||
|
|||||||
Reference in New Issue
Block a user