Bug 1686037 - Part 2: Clean up pointer capture info when PresContext is destroyed; r=smaug

Found a possible leak from running layout/base/tests/test_bug993936.html after
enable implicit pointer capture for touch event. The test synthesize touchstart
and touchmove event, but no touchend, so we don't run the release steps and the
PointerCaptureInfo still hold a reference to Element which cause the leak.

This could also possible happens in real world, for example, user touch a page
with finger that triggers pointer capture, and then tab get closed before touch
is released.

Differential Revision: https://phabricator.services.mozilla.com/D102403
This commit is contained in:
Edgar Chen 2021-01-21 16:38:59 +00:00
Родитель f43f83b1a9
Коммит b615527436
5 изменённых файлов: 121 добавлений и 11 удалений

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

@ -3864,6 +3864,7 @@ void EventStateManager::NotifyDestroyPresContext(nsPresContext* aPresContext) {
SetContentState(nullptr, NS_EVENT_STATE_HOVER);
}
mPointersEnterLeaveHelper.Clear();
PointerEventHandler::NotifyDestroyPresContext(aPresContext);
}
void EventStateManager::SetPresContext(nsPresContext* aPresContext) {

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

@ -356,11 +356,18 @@ void PointerEventHandler::CheckPointerCaptureState(WidgetPointerEvent* aEvent) {
captureInfo->mPendingElement == captureInfo->mOverrideElement) {
return;
}
// cache captureInfo->mPendingElement since it may be changed in the pointer
// event listener
RefPtr<Element> pendingElement = captureInfo->mPendingElement.get();
if (captureInfo->mOverrideElement) {
RefPtr<Element> overrideElement = captureInfo->mOverrideElement;
RefPtr<Element> overrideElement = captureInfo->mOverrideElement;
RefPtr<Element> pendingElement = captureInfo->mPendingElement;
// Update captureInfo before dispatching event since sPointerCaptureList may
// be changed in the pointer event listener.
captureInfo->mOverrideElement = captureInfo->mPendingElement;
if (captureInfo->Empty()) {
sPointerCaptureList->Remove(aEvent->pointerId);
}
if (overrideElement) {
DispatchGotOrLostPointerCaptureEvent(/* aIsGotCapture */ false, aEvent,
overrideElement);
}
@ -368,11 +375,6 @@ void PointerEventHandler::CheckPointerCaptureState(WidgetPointerEvent* aEvent) {
DispatchGotOrLostPointerCaptureEvent(/* aIsGotCapture */ true, aEvent,
pendingElement);
}
captureInfo->mOverrideElement = std::move(pendingElement);
if (captureInfo->Empty()) {
sPointerCaptureList->Remove(aEvent->pointerId);
}
}
/* static */
@ -689,6 +691,29 @@ void PointerEventHandler::DispatchPointerFromMouseOrTouch(
}
}
/* static */
void PointerEventHandler::NotifyDestroyPresContext(
nsPresContext* aPresContext) {
// Clean up pointer capture info
for (auto iter = sPointerCaptureList->Iter(); !iter.Done(); iter.Next()) {
PointerCaptureInfo* data = iter.UserData();
MOZ_ASSERT(data, "how could we have a null PointerCaptureInfo here?");
if (data->mPendingElement &&
data->mPendingElement->GetPresContext(Element::eForComposedDoc) ==
aPresContext) {
data->mPendingElement = nullptr;
}
if (data->mOverrideElement &&
data->mOverrideElement->GetPresContext(Element::eForComposedDoc) ==
aPresContext) {
data->mOverrideElement = nullptr;
}
if (data->Empty()) {
iter.Remove();
}
}
}
/* static */
uint16_t PointerEventHandler::GetPointerType(uint32_t aPointerId) {
PointerInfo* pointerInfo = nullptr;
@ -713,7 +738,7 @@ void PointerEventHandler::DispatchGotOrLostPointerCaptureEvent(
Element* aCaptureTarget) {
Document* targetDoc = aCaptureTarget->OwnerDoc();
RefPtr<PresShell> presShell = targetDoc->GetPresShell();
if (NS_WARN_IF(!presShell)) {
if (NS_WARN_IF(!presShell || presShell->IsDestroying())) {
return;
}

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

@ -18,6 +18,7 @@
class nsIFrame;
class nsIContent;
class nsPresContext;
namespace mozilla {
@ -196,6 +197,8 @@ class PointerEventHandler final {
return sSpoofedPointerId.valueOr(0);
}
static void NotifyDestroyPresContext(nsPresContext* aPresContext);
private:
// Set pointer capture of the specified pointer by the element.
static void SetPointerCaptureById(uint32_t aPointerId,

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

@ -113,3 +113,4 @@ skip-if =
support-files =
file_pointercapture_xorigin_iframe.html
file_pointercapture_xorigin_iframe_pointerlock.html
[test_pointercapture_remove_iframe.html]

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

@ -0,0 +1,80 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1686037
-->
<head>
<title>Bug 1686037</title>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<style>
#target {
width: 100px;
height: 100px;
background-color: green;
}
iframe {
width: 400px;
height: 300px;
border: 1px solid blue;
}
</style>
</head>
<body>
<a target="_blank"href="https://bugzilla.mozilla.org/show_bug.cgi?id=1686037">Mozilla Bug 1686037</a>
<div id="target"></div>
<iframe srcdoc="<div style='width: 100px; height: 100px; background-color: blue;'></div>"></iframe>
<pre id="test">
<script type="text/javascript">
/**
* Test for Bug 1686037
*/
function waitForEvent(aTarget, aEventName, aCallback = null) {
return new Promise((aResolve) => {
aTarget.addEventListener(aEventName, async (e) => {
ok(true, `got ${e.type} event on ${e.target}, pointerid: ${e.pointerId}`);
if (aCallback) {
await aCallback(e);
}
aResolve();
}, { once: true });
});
}
function waitForPointerDownAndSetPointerCapture(aTarget) {
return waitForEvent(aTarget, "pointerdown", async (event) => {
return new Promise((aResolve) => {
aTarget.addEventListener("gotpointercapture", (e) => {
ok(true, `got ${e.type} event on ${e.target}, pointerid: ${e.pointerId}`);
aResolve();
}, { once: true });
aTarget.setPointerCapture(event.pointerId);
});
});
}
add_task(async function test_remove_iframe_after_pointer_capture() {
await SimpleTest.promiseFocus();
let iframe = document.querySelector("iframe");
let iframeWin = iframe.contentWindow;
let targetInIframe = iframe.contentDocument.querySelector("div");
let promise = Promise.all([
waitForPointerDownAndSetPointerCapture(targetInIframe),
waitForEvent(targetInIframe, "pointermove")
]);
synthesizeTouch(targetInIframe, 10, 10, { type: "touchstart", id: 10 }, iframeWin);
synthesizeTouch(targetInIframe, 10, 10, { type: "touchmove", id: 10 }, iframeWin);
await promise;
// Intentionally not synthesize touchend event to not trigger implicit releasing
// pointer capture. And iframe removal should trigger pointer capture clean up.
iframe.remove();
});
</script>
</pre>
</body>
</html>