From 76703c906efd2cfb958a5ee46193138fcd004781 Mon Sep 17 00:00:00 2001 From: Ian Moody Date: Thu, 18 Apr 2019 12:57:36 +0000 Subject: [PATCH] Bug 1379466 - Make editor listen for auxclick mouse events. r=smaug,masayuki Editable elements will no longer get click events for non-primary mouse buttons since they are being unshipped from the web in favour of auxclick events. Listen for auxclick as well so middle-click paste still works. Don't stop propagation after middle-click paste, instead ignore clicks on contenteditable elements in ClickHandlerChild. Update test_middle_click_paste.html for the new behaviour. Also remove the mNoContentDispatch overrides in HTMLInputElement and HTMLTextAreaElement that were needed for middle-pasting. Differential Revision: https://phabricator.services.mozilla.com/D26792 --- browser/actors/ClickHandlerChild.jsm | 10 ++++ dom/events/EventStateManager.cpp | 2 +- dom/events/test/test_clickevent_on_input.html | 9 +-- dom/html/HTMLInputElement.cpp | 28 ++------- dom/html/HTMLTextAreaElement.cpp | 18 ------ editor/libeditor/EditorEventListener.cpp | 19 +++++- editor/libeditor/tests/test_bug674770-1.html | 2 +- .../tests/test_middle_click_paste.html | 58 +++++++++---------- widget/tests/test_assign_event_data.html | 3 +- 9 files changed, 66 insertions(+), 83 deletions(-) diff --git a/browser/actors/ClickHandlerChild.jsm b/browser/actors/ClickHandlerChild.jsm index 12467200c372..039149c9bb98 100644 --- a/browser/actors/ClickHandlerChild.jsm +++ b/browser/actors/ClickHandlerChild.jsm @@ -23,6 +23,16 @@ class ClickHandlerChild extends ActorChild { (event.type == "click" && event.button == 1)) { return; } + // Don't do anything on editable things, we shouldn't open links in + // contenteditables, and editor needs to possibly handle middlemouse paste + let composedTarget = event.composedTarget; + if (composedTarget.isContentEditable || + (composedTarget.ownerDocument && + composedTarget.ownerDocument.designMode == "on") || + ChromeUtils.getClassName(composedTarget) == "HTMLInputElement" || + ChromeUtils.getClassName(composedTarget) == "HTMLTextAreaElement") { + return; + } let originalTarget = event.originalTarget; let ownerDoc = originalTarget.ownerDocument; diff --git a/dom/events/EventStateManager.cpp b/dom/events/EventStateManager.cpp index d1872d074022..4006cdca0682 100644 --- a/dom/events/EventStateManager.cpp +++ b/dom/events/EventStateManager.cpp @@ -5017,7 +5017,7 @@ nsresult EventStateManager::HandleMiddleClickPaste( nsEventStatus* aStatus, TextEditor* aTextEditor) { MOZ_ASSERT(aPresShell); MOZ_ASSERT(aMouseEvent); - MOZ_ASSERT((aMouseEvent->mMessage == eMouseClick && + MOZ_ASSERT((aMouseEvent->mMessage == eMouseAuxClick && aMouseEvent->button == WidgetMouseEventBase::eMiddleButton) || EventCausesClickEvents(*aMouseEvent)); MOZ_ASSERT(aStatus); diff --git a/dom/events/test/test_clickevent_on_input.html b/dom/events/test/test_clickevent_on_input.html index 2753429d33ea..6f180d447bf7 100644 --- a/dom/events/test/test_clickevent_on_input.html +++ b/dom/events/test/test_clickevent_on_input.html @@ -60,14 +60,11 @@ function isEnabledAccessibleCaret() function doTest(aButton) { - // NOTE #1: Right click causes a context menu to popup, then, the click event - // isn't generated. - // NOTE #2: If middle click causes text to be pasted, then, the click event - // isn't generated. - // NOTE #3: If touch caret is enabled, touch caret would ovelap input element, + // NOTE #1: Non-primary buttons don't generate 'click' events + // NOTE #2: If touch caret is enabled, touch caret would ovelap input element, // then, the click event isn't generated. if (aButton != 2 && - (aButton != 1 || !isEnabledMiddleClickPaste()) && + aButton != 1 && (aButton != 0 || !isEnabledAccessibleCaret())) { gClickCount = 0; // click on border of input diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp index aa3530126393..9fd76bbe4342 100644 --- a/dom/html/HTMLInputElement.cpp +++ b/dom/html/HTMLInputElement.cpp @@ -127,16 +127,15 @@ namespace dom { // First bits are needed for the control type. #define NS_OUTER_ACTIVATE_EVENT (1 << 9) #define NS_ORIGINAL_CHECKED_VALUE (1 << 10) -#define NS_NO_CONTENT_DISPATCH (1 << 11) +// (1 << 11 is unused) #define NS_ORIGINAL_INDETERMINATE_VALUE (1 << 12) #define NS_PRE_HANDLE_BLUR_EVENT (1 << 13) #define NS_PRE_HANDLE_INPUT_EVENT (1 << 14) #define NS_IN_SUBMIT_CLICK (1 << 15) -#define NS_CONTROL_TYPE(bits) \ - ((bits) & ~(NS_OUTER_ACTIVATE_EVENT | NS_ORIGINAL_CHECKED_VALUE | \ - NS_NO_CONTENT_DISPATCH | NS_ORIGINAL_INDETERMINATE_VALUE | \ - NS_PRE_HANDLE_BLUR_EVENT | NS_PRE_HANDLE_INPUT_EVENT | \ - NS_IN_SUBMIT_CLICK)) +#define NS_CONTROL_TYPE(bits) \ + ((bits) & ~(NS_OUTER_ACTIVATE_EVENT | NS_ORIGINAL_CHECKED_VALUE | \ + NS_ORIGINAL_INDETERMINATE_VALUE | NS_PRE_HANDLE_BLUR_EVENT | \ + NS_PRE_HANDLE_INPUT_EVENT | NS_IN_SUBMIT_CLICK)) // whether textfields should be selected once focused: // -1: no, 1: yes, 0: uninitialized @@ -3202,19 +3201,6 @@ void HTMLInputElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) { aVisitor.mItemFlags |= NS_ORIGINAL_CHECKED_VALUE; } - // If mNoContentDispatch is true we will not allow content to handle - // this event. But to allow middle mouse button paste to work we must allow - // middle clicks to go to text fields anyway. - if (aVisitor.mEvent->mFlags.mNoContentDispatch) { - aVisitor.mItemFlags |= NS_NO_CONTENT_DISPATCH; - } - if (IsSingleLineTextControl(false) && - aVisitor.mEvent->mMessage == eMouseClick && - aVisitor.mEvent->AsMouseEvent()->button == - WidgetMouseEvent::eMiddleButton) { - aVisitor.mEvent->mFlags.mNoContentDispatch = false; - } - // We must cache type because mType may change during JS event (bug 2369) aVisitor.mItemFlags |= mType; @@ -3705,7 +3691,6 @@ nsresult HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor) { bool outerActivateEvent = !!(aVisitor.mItemFlags & NS_OUTER_ACTIVATE_EVENT); bool originalCheckedValue = !!(aVisitor.mItemFlags & NS_ORIGINAL_CHECKED_VALUE); - bool noContentDispatch = !!(aVisitor.mItemFlags & NS_NO_CONTENT_DISPATCH); uint8_t oldType = NS_CONTROL_TYPE(aVisitor.mItemFlags); // Ideally we would make the default action for click and space just dispatch @@ -3756,9 +3741,6 @@ nsresult HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor) { } } - // Reset the flag for other content besides this text field - aVisitor.mEvent->mFlags.mNoContentDispatch = noContentDispatch; - // now check to see if the event was "cancelled" if (mCheckedIsToggled && outerActivateEvent) { if (aVisitor.mEventStatus == nsEventStatus_eConsumeNoDefault) { diff --git a/dom/html/HTMLTextAreaElement.cpp b/dom/html/HTMLTextAreaElement.cpp index e8bb824e160f..0d1ba35c8b59 100644 --- a/dom/html/HTMLTextAreaElement.cpp +++ b/dom/html/HTMLTextAreaElement.cpp @@ -43,8 +43,6 @@ #include "nsBaseCommandController.h" #include "nsXULControllers.h" -#define NS_NO_CONTENT_DISPATCH (1 << 0) - NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(TextArea) namespace mozilla { @@ -452,18 +450,6 @@ void HTMLTextAreaElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) { mHandlingSelect = true; } - // If noContentDispatch is true we will not allow content to handle - // this event. But to allow middle mouse button paste to work we must allow - // middle clicks to go to text fields anyway. - if (aVisitor.mEvent->mFlags.mNoContentDispatch) { - aVisitor.mItemFlags |= NS_NO_CONTENT_DISPATCH; - } - if (aVisitor.mEvent->mMessage == eMouseClick && - aVisitor.mEvent->AsMouseEvent()->button == - WidgetMouseEvent::eMiddleButton) { - aVisitor.mEvent->mFlags.mNoContentDispatch = false; - } - if (aVisitor.mEvent->mMessage == eBlur) { // Set mWantsPreHandleEvent and fire change event in PreHandleEvent to // prevent it breaks event target chain creation. @@ -520,10 +506,6 @@ nsresult HTMLTextAreaElement::PostHandleEvent(EventChainPostVisitor& aVisitor) { UpdateState(true); } - // Reset the flag for other content besides this text field - aVisitor.mEvent->mFlags.mNoContentDispatch = - ((aVisitor.mItemFlags & NS_NO_CONTENT_DISPATCH) != 0); - return NS_OK; } diff --git a/editor/libeditor/EditorEventListener.cpp b/editor/libeditor/EditorEventListener.cpp index 020da0073113..bbb63ca9af32 100644 --- a/editor/libeditor/EditorEventListener.cpp +++ b/editor/libeditor/EditorEventListener.cpp @@ -170,6 +170,8 @@ nsresult EditorEventListener::InstallToEditor() { TrustedEventsAtCapture()); elmP->AddEventListenerByType(this, NS_LITERAL_STRING("click"), TrustedEventsAtCapture()); + elmP->AddEventListenerByType(this, NS_LITERAL_STRING("auxclick"), + TrustedEventsAtSystemGroupCapture()); // Focus event doesn't bubble so adding the listener to capturing phase as // system event group. elmP->AddEventListenerByType(this, NS_LITERAL_STRING("blur"), @@ -242,6 +244,8 @@ void EditorEventListener::UninstallFromEditor() { TrustedEventsAtCapture()); elmP->RemoveEventListenerByType(this, NS_LITERAL_STRING("click"), TrustedEventsAtCapture()); + elmP->RemoveEventListenerByType(this, NS_LITERAL_STRING("auxclick"), + TrustedEventsAtSystemGroupCapture()); elmP->RemoveEventListenerByType(this, NS_LITERAL_STRING("blur"), TrustedEventsAtSystemGroupCapture()); elmP->RemoveEventListenerByType(this, NS_LITERAL_STRING("focus"), @@ -397,6 +401,15 @@ EditorEventListener::HandleEvent(Event* aEvent) { } // click case eMouseClick: { + WidgetMouseEvent* widgetMouseEvent = internalEvent->AsMouseEvent(); + // Don't handle non-primary click events + if (widgetMouseEvent->button != WidgetMouseEventBase::eLeftButton) { + return NS_OK; + } + MOZ_FALLTHROUGH; + } + // auxclick + case eMouseAuxClick: { WidgetMouseEvent* widgetMouseEvent = internalEvent->AsMouseEvent(); if (NS_WARN_IF(!widgetMouseEvent)) { return NS_OK; @@ -650,9 +663,9 @@ nsresult EditorEventListener::MouseClick(WidgetMouseEvent* aMouseClickEvent) { NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to paste for the middle button click"); if (status == nsEventStatus_eConsumeNoDefault) { - // Prevent the event from propagating up to be possibly handled - // again by the containing window: - aMouseClickEvent->StopImmediatePropagation(); + // We no longer need to StopImmediatePropagation here since + // ClickHandlerChild.jsm checks for and ignores editables, so won't + // re-handle the event aMouseClickEvent->PreventDefault(); } return NS_OK; diff --git a/editor/libeditor/tests/test_bug674770-1.html b/editor/libeditor/tests/test_bug674770-1.html index 2bac31e1f9c5..0b6089e0ef1c 100644 --- a/editor/libeditor/tests/test_bug674770-1.html +++ b/editor/libeditor/tests/test_bug674770-1.html @@ -55,7 +55,7 @@ function startTests() { SimpleTest.executeSoon(runNextTest); }, false); - SpecialPowers.addSystemEventListener(window, "click", function(aEvent) { + SpecialPowers.addSystemEventListener(window, "auxclick", function(aEvent) { // When the click event should cause default action, e.g., opening the link, // the event shouldn't have been consumed except the link handler. // However, in e10s mode, it's not consumed during propagating the event but diff --git a/editor/libeditor/tests/test_middle_click_paste.html b/editor/libeditor/tests/test_middle_click_paste.html index 10bdd51e26db..9f761497590b 100644 --- a/editor/libeditor/tests/test_middle_click_paste.html +++ b/editor/libeditor/tests/test_middle_click_paste.html @@ -164,12 +164,12 @@ async function doTextareaTests(aTextarea) { document.body.addEventListener("click", (event) => { event.preventDefault(); }, {capture: true, once: true}); inputEvents = []; synthesizeMouseAtCenter(aTextarea, {button: 1}); - is(aTextarea.value, "", - "If 'click' event is consumed at capturing phase of the , paste should be canceled"); - is(pasteEventCount, 0, - "If 'click' event is consumed at capturing phase of the , 'paste' event should not be fired"); - is(inputEvents.length, 0, - 'No "input" event should be fired when the "click" event is canceled'); + is(aTextarea.value, "abc", + "If 'click' event is consumed at capturing phase of the , paste should not be canceled"); + is(pasteEventCount, 1, + "If 'click' event is consumed at capturing phase of the , 'paste' event should still be fired"); + is(inputEvents.length, 1, + '"input" event should be fired when the "click" event is canceled'); aTextarea.value = ""; await copyPlaintext("abc"); @@ -194,9 +194,9 @@ async function doTextareaTests(aTextarea) { inputEvents = []; synthesizeMouseAtCenter(aTextarea, {button: 1}); is(aTextarea.value, "abc", - "Even if 'click' event handler is added to the