mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-17 15:25:52 +00:00
Bug 1665550 - part 4: Make EventStateManager
update mGestureDownFrameOwner
when anonymous nodes in <input>
or <textarea>
are replaced r=smaug
`EventStateManager` gives up to track gesture to start a drag if mouse down content which is stored in `mGestureDownFrameOwner` gets lost its primary frame. When user tries to start to drag selected text in `<input>` or `<textarea>` element, mouse down content is an anonymous node in `TextControlElement`. So, if a reflow occurs after `mousedown` event, the anonymous `<div>` element is replaced with new one and `EventStateManager` gives up to track it. Therefore, this patch makes `EventStateManager` do similar things as `nsBaseDragService`. When `nsTextControlFrame` notifies of remove/add the anonymous nodes, `EventStateManager` tries to keep tracking gesture with a new anonymous node. Differential Revision: https://phabricator.services.mozilla.com/D119488
This commit is contained in:
parent
a171e12db6
commit
04b41051dd
@ -4,12 +4,13 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "EventStateManager.h"
|
||||
|
||||
#include "mozilla/AsyncEventDispatcher.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/EditorBase.h"
|
||||
#include "mozilla/EventDispatcher.h"
|
||||
#include "mozilla/EventForwards.h"
|
||||
#include "mozilla/EventStateManager.h"
|
||||
#include "mozilla/EventStates.h"
|
||||
#include "mozilla/HTMLEditor.h"
|
||||
#include "mozilla/IMEStateManager.h"
|
||||
@ -21,6 +22,7 @@
|
||||
#include "mozilla/ScopeExit.h"
|
||||
#include "mozilla/ScrollTypes.h"
|
||||
#include "mozilla/TextComposition.h"
|
||||
#include "mozilla/TextControlElement.h"
|
||||
#include "mozilla/TextEditor.h"
|
||||
#include "mozilla/TextEvents.h"
|
||||
#include "mozilla/TouchEvents.h"
|
||||
@ -254,6 +256,7 @@ EventStateManager::EventStateManager()
|
||||
mRClickCount(0),
|
||||
mShouldAlwaysUseLineDeltas(false),
|
||||
mShouldAlwaysUseLineDeltasInitialized(false),
|
||||
mGestureDownInTextControl(false),
|
||||
mInTouchDrag(false),
|
||||
m_haveShutdown(false) {
|
||||
if (sESMInstanceCount == 0) {
|
||||
@ -1880,6 +1883,10 @@ void EventStateManager::BeginTrackingRemoteDragGesture(
|
||||
nsIContent* aContent, RemoteDragStartData* aDragStartData) {
|
||||
mGestureDownContent = aContent;
|
||||
mGestureDownFrameOwner = aContent;
|
||||
mGestureDownInTextControl =
|
||||
aContent && aContent->IsInNativeAnonymousSubtree() &&
|
||||
TextControlElement::FromNodeOrNull(
|
||||
aContent->GetClosestNativeAnonymousSubtreeRootParent());
|
||||
mGestureDownDragStartData = aDragStartData;
|
||||
}
|
||||
|
||||
@ -1892,6 +1899,7 @@ void EventStateManager::BeginTrackingRemoteDragGesture(
|
||||
void EventStateManager::StopTrackingDragGesture(bool aClearInChildProcesses) {
|
||||
mGestureDownContent = nullptr;
|
||||
mGestureDownFrameOwner = nullptr;
|
||||
mGestureDownInTextControl = false;
|
||||
mGestureDownDragStartData = nullptr;
|
||||
|
||||
// If a content process starts a drag but the mouse is released before the
|
||||
@ -5788,6 +5796,38 @@ void EventStateManager::ContentRemoved(Document* aDocument,
|
||||
}
|
||||
}
|
||||
|
||||
void EventStateManager::TextControlRootWillBeRemoved(
|
||||
TextControlElement& aTextControlElement) {
|
||||
if (!mGestureDownInTextControl || !mGestureDownFrameOwner ||
|
||||
!mGestureDownFrameOwner->IsInNativeAnonymousSubtree()) {
|
||||
return;
|
||||
}
|
||||
// If we track gesture to start drag in aTextControlElement, we should keep
|
||||
// tracking it with aTextContrlElement itself for now because this may be
|
||||
// caused by reframing aTextControlElement which may not be intended by the
|
||||
// user.
|
||||
if (&aTextControlElement ==
|
||||
mGestureDownFrameOwner->GetClosestNativeAnonymousSubtreeRootParent()) {
|
||||
mGestureDownFrameOwner = &aTextControlElement;
|
||||
}
|
||||
}
|
||||
|
||||
void EventStateManager::TextControlRootAdded(
|
||||
Element& aAnonymousDivElement, TextControlElement& aTextControlElement) {
|
||||
if (!mGestureDownInTextControl ||
|
||||
mGestureDownFrameOwner != &aTextControlElement) {
|
||||
return;
|
||||
}
|
||||
// If we track gesture to start drag in aTextControlElement, but the frame
|
||||
// owner is the text control element itself, the anonymous nodes in it are
|
||||
// recreated by a reframe. If so, we should keep tracking it with the
|
||||
// recreated native anonymous node.
|
||||
mGestureDownFrameOwner =
|
||||
aAnonymousDivElement.GetFirstChild()
|
||||
? aAnonymousDivElement.GetFirstChild()
|
||||
: static_cast<nsIContent*>(&aAnonymousDivElement);
|
||||
}
|
||||
|
||||
bool EventStateManager::EventStatusOK(WidgetGUIEvent* aEvent) {
|
||||
return !(aEvent->mMessage == eMouseDown &&
|
||||
aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary &&
|
||||
|
@ -41,6 +41,7 @@ class EnterLeaveDispatcher;
|
||||
class EventStates;
|
||||
class IMEContentObserver;
|
||||
class ScrollbarsForWheel;
|
||||
class TextControlElement;
|
||||
class WheelTransaction;
|
||||
|
||||
namespace dom {
|
||||
@ -159,6 +160,19 @@ class EventStateManager : public nsSupportsWeakReference, public nsIObserver {
|
||||
void NativeAnonymousContentRemoved(nsIContent* aAnonContent);
|
||||
void ContentRemoved(dom::Document* aDocument, nsIContent* aContent);
|
||||
|
||||
/**
|
||||
* Called when a native anonymous <div> element which is root element of
|
||||
* text editor will be removed.
|
||||
*/
|
||||
void TextControlRootWillBeRemoved(TextControlElement& aTextControlElement);
|
||||
|
||||
/**
|
||||
* Called when a native anonymous <div> element which is root element of
|
||||
* text editor is created.
|
||||
*/
|
||||
void TextControlRootAdded(dom::Element& aAnonymousDivElement,
|
||||
TextControlElement& aTextControlElement);
|
||||
|
||||
bool EventStatusOK(WidgetGUIEvent* aEvent);
|
||||
|
||||
/**
|
||||
@ -1177,6 +1191,8 @@ class EventStateManager : public nsSupportsWeakReference, public nsIObserver {
|
||||
bool mShouldAlwaysUseLineDeltas : 1;
|
||||
bool mShouldAlwaysUseLineDeltasInitialized : 1;
|
||||
|
||||
bool mGestureDownInTextControl : 1;
|
||||
|
||||
bool mInTouchDrag;
|
||||
|
||||
bool m_haveShutdown;
|
||||
|
@ -3183,6 +3183,106 @@ async function doTest() {
|
||||
document.removeEventListener("drop", onDrop);
|
||||
})();
|
||||
|
||||
// -------- Test dragging text from an <input> and reframing the <input> element before dragstart.
|
||||
await (async function test_dragging_from_input_element_and_reframing_input_element_before_dragstart() {
|
||||
const description = "dragging part of text in <input> element and reframing the <input> element before dragstart";
|
||||
container.innerHTML = '<input value="Drag Me">';
|
||||
const input = document.querySelector("div#container > input");
|
||||
document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
|
||||
input.setSelectionRange(1, 4);
|
||||
beforeinputEvents = [];
|
||||
inputEvents = [];
|
||||
dragEvents = [];
|
||||
const onMouseMove = aEvent => {
|
||||
input.style.display = "none";
|
||||
document.documentElement.scrollTop;
|
||||
input.style.display = "";
|
||||
document.documentElement.scrollTop;
|
||||
};
|
||||
const onMouseDown = aEvent => {
|
||||
document.addEventListener("mousemove", onMouseMove, {once: true});
|
||||
}
|
||||
const onDrop = aEvent => {
|
||||
dragEvents.push(aEvent);
|
||||
comparePlainText(aEvent.dataTransfer.getData("text/plain"),
|
||||
input.value.substring(1, 4),
|
||||
`${description}: dataTransfer should have selected text as "text/plain"`);
|
||||
is(aEvent.dataTransfer.getData("text/html"), "",
|
||||
`${description}: dataTransfer should not have data as "text/html"`);
|
||||
};
|
||||
document.addEventListener("mousedown", onMouseDown, {once: true});
|
||||
document.addEventListener("drop", onDrop);
|
||||
if (
|
||||
await trySynthesizePlainDragAndDrop(
|
||||
description,
|
||||
{
|
||||
srcSelection: SpecialPowers.wrap(input).editor.selection,
|
||||
destElement: dropZone,
|
||||
}
|
||||
)
|
||||
) {
|
||||
is(beforeinputEvents.length, 0,
|
||||
`${description}: No "beforeinput" event should be fired when dragging <input> value to non-editable drop zone`);
|
||||
is(inputEvents.length, 0,
|
||||
`${description}: No "input" event should be fired when dragging <input> value to non-editable drop zone`);
|
||||
is(dragEvents.length, 1,
|
||||
`${description}: only one "drop" event should be fired`);
|
||||
}
|
||||
document.removeEventListener("mousedown", onMouseDown);
|
||||
document.removeEventListener("mousemove", onMouseMove);
|
||||
document.removeEventListener("drop", onDrop);
|
||||
})();
|
||||
|
||||
// -------- Test dragging text from an <textarea> and reframing the <textarea> element before dragstart.
|
||||
await (async function test_dragging_from_textarea_element_and_reframing_textarea_element_before_dragstart() {
|
||||
const description = "dragging part of text in <textarea> element and reframing the <textarea> element before dragstart";
|
||||
container.innerHTML = "<textarea>Some Text To Drag</textarea>";
|
||||
const textarea = document.querySelector("div#container > textarea");
|
||||
document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
|
||||
textarea.setSelectionRange(1, 7);
|
||||
beforeinputEvents = [];
|
||||
inputEvents = [];
|
||||
dragEvents = [];
|
||||
const onMouseMove = aEvent => {
|
||||
textarea.style.display = "none";
|
||||
document.documentElement.scrollTop;
|
||||
textarea.style.display = "";
|
||||
document.documentElement.scrollTop;
|
||||
};
|
||||
const onMouseDown = aEvent => {
|
||||
document.addEventListener("mousemove", onMouseMove, {once: true});
|
||||
}
|
||||
const onDrop = aEvent => {
|
||||
dragEvents.push(aEvent);
|
||||
comparePlainText(aEvent.dataTransfer.getData("text/plain"),
|
||||
textarea.value.substring(1, 7),
|
||||
`${description}: dataTransfer should have selected text as "text/plain"`);
|
||||
is(aEvent.dataTransfer.getData("text/html"), "",
|
||||
`${description}: dataTransfer should not have data as "text/html"`);
|
||||
};
|
||||
document.addEventListener("mousedown", onMouseDown, {once: true});
|
||||
document.addEventListener("drop", onDrop);
|
||||
if (
|
||||
await trySynthesizePlainDragAndDrop(
|
||||
description,
|
||||
{
|
||||
srcSelection: SpecialPowers.wrap(textarea).editor.selection,
|
||||
destElement: dropZone,
|
||||
}
|
||||
)
|
||||
) {
|
||||
is(beforeinputEvents.length, 0,
|
||||
`${description}: No "beforeinput" event should be fired when dragging <textarea> value to non-editable drop zone`);
|
||||
is(inputEvents.length, 0,
|
||||
`${description}: No "input" event should be fired when dragging <textarea> value to non-editable drop zone`);
|
||||
is(dragEvents.length, 1,
|
||||
`${description}: only one "drop" event should be fired`);
|
||||
}
|
||||
document.removeEventListener("mousedown", onMouseDown);
|
||||
document.removeEventListener("mousemove", onMouseMove);
|
||||
document.removeEventListener("drop", onDrop);
|
||||
})();
|
||||
|
||||
document.removeEventListener("beforeinput", onBeforeinput);
|
||||
document.removeEventListener("input", onInput);
|
||||
SimpleTest.finish();
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include "nsILayoutHistoryState.h"
|
||||
|
||||
#include "nsFocusManager.h"
|
||||
#include "mozilla/EventStateManager.h"
|
||||
#include "mozilla/PresShell.h"
|
||||
#include "mozilla/PresState.h"
|
||||
#include "mozilla/TextEditor.h"
|
||||
@ -166,6 +167,13 @@ void nsTextControlFrame::DestroyFrom(nsIFrame* aDestructRoot,
|
||||
}
|
||||
}
|
||||
}
|
||||
// Otherwise, EventStateManager may track gesture to start drag with native
|
||||
// anonymous nodes in the text control element.
|
||||
else if (textControlElement->GetPresContext(Element::eForComposedDoc)) {
|
||||
textControlElement->GetPresContext(Element::eForComposedDoc)
|
||||
->EventStateManager()
|
||||
->TextControlRootWillBeRemoved(*textControlElement);
|
||||
}
|
||||
|
||||
// If we're a subclass like nsNumberControlFrame, then it owns the root of the
|
||||
// anonymous subtree where mRootNode is.
|
||||
@ -1350,6 +1358,20 @@ nsTextControlFrame::EditorInitializer::Run() {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Otherwise, EventStateManager may be tracking gesture to start a drag.
|
||||
else if (TextControlElement* textControlElement =
|
||||
TextControlElement::FromNode(mFrame->GetContent())) {
|
||||
if (nsPresContext* presContext =
|
||||
textControlElement->GetPresContext(Element::eForComposedDoc)) {
|
||||
if (TextEditor* textEditor =
|
||||
textControlElement->GetTextEditorWithoutCreation()) {
|
||||
if (Element* anonymousDivElement = textEditor->GetRoot()) {
|
||||
presContext->EventStateManager()->TextControlRootAdded(
|
||||
*anonymousDivElement, *textControlElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mFrame->FinishedInitializer();
|
||||
return NS_OK;
|
||||
|
@ -3095,15 +3095,45 @@ async function synthesizePlainDragAndDrop(aParams) {
|
||||
|
||||
await new Promise(r => setTimeout(r, 0));
|
||||
|
||||
synthesizeMouse(
|
||||
srcElement,
|
||||
srcX,
|
||||
srcY,
|
||||
{ type: "mousedown", id },
|
||||
srcWindow
|
||||
);
|
||||
if (logFunc) {
|
||||
logFunc(`mousedown at ${srcX}, ${srcY}`);
|
||||
let mouseDownEvent;
|
||||
function onMouseDown(aEvent) {
|
||||
mouseDownEvent = aEvent;
|
||||
if (logFunc) {
|
||||
logFunc(`"${aEvent.type}" event is fired`);
|
||||
}
|
||||
if (
|
||||
!srcElement.contains(
|
||||
_EU_maybeUnwrap(_EU_maybeWrap(aEvent).composedTarget)
|
||||
)
|
||||
) {
|
||||
// If srcX and srcY does not point in one of rects in srcElement,
|
||||
// "mousedown" target is not in srcElement. Such case must not
|
||||
// be expected by this API users so that we should throw an exception
|
||||
// for making debugging easier.
|
||||
throw new Error(
|
||||
'event target of "mousedown" is not srcElement nor its descendant'
|
||||
);
|
||||
}
|
||||
}
|
||||
try {
|
||||
srcWindow.addEventListener("mousedown", onMouseDown, { capture: true });
|
||||
synthesizeMouse(
|
||||
srcElement,
|
||||
srcX,
|
||||
srcY,
|
||||
{ type: "mousedown", id },
|
||||
srcWindow
|
||||
);
|
||||
if (logFunc) {
|
||||
logFunc(`mousedown at ${srcX}, ${srcY}`);
|
||||
}
|
||||
if (!mouseDownEvent) {
|
||||
throw new Error('"mousedown" event is not fired');
|
||||
}
|
||||
} finally {
|
||||
srcWindow.removeEventListener("mousedown", onMouseDown, {
|
||||
capture: true,
|
||||
});
|
||||
}
|
||||
|
||||
let dragStartEvent;
|
||||
@ -3120,7 +3150,7 @@ async function synthesizePlainDragAndDrop(aParams) {
|
||||
// If srcX and srcY does not point in one of rects in srcElement,
|
||||
// "dragstart" target is not in srcElement. Such case must not
|
||||
// be expected by this API users so that we should throw an exception
|
||||
// for making debug easier.
|
||||
// for making debugging easier.
|
||||
throw new Error(
|
||||
'event target of "dragstart" is not srcElement nor its descendant'
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user