зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
f43f83b1a9
Коммит
b615527436
|
@ -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>
|
Загрузка…
Ссылка в новой задаче