diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /editor/libeditor/HTMLEditorEventListener.cpp | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | editor/libeditor/HTMLEditorEventListener.cpp | 442 |
1 files changed, 442 insertions, 0 deletions
diff --git a/editor/libeditor/HTMLEditorEventListener.cpp b/editor/libeditor/HTMLEditorEventListener.cpp new file mode 100644 index 0000000000..a90d6c7a7d --- /dev/null +++ b/editor/libeditor/HTMLEditorEventListener.cpp @@ -0,0 +1,442 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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 "HTMLEditorEventListener.h" + +#include "HTMLEditUtils.h" +#include "mozilla/HTMLEditor.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/EventTarget.h" +#include "mozilla/dom/MouseEvent.h" +#include "mozilla/dom/Selection.h" +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsError.h" +#include "nsISupportsImpl.h" +#include "nsLiteralString.h" +#include "nsQueryObject.h" +#include "nsRange.h" + +namespace mozilla { + +using namespace dom; + +nsresult HTMLEditorEventListener::Connect(EditorBase* aEditorBase) { + // Guarantee that mEditorBase is always HTMLEditor. + HTMLEditor* htmlEditor = HTMLEditor::GetFrom(aEditorBase); + if (NS_WARN_IF(!htmlEditor)) { + return NS_ERROR_INVALID_ARG; + } + nsresult rv = EditorEventListener::Connect(htmlEditor); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "EditorEventListener::Connect() failed"); + return rv; +} + +void HTMLEditorEventListener::Disconnect() { + if (DetachedFromEditor()) { + EditorEventListener::Disconnect(); + } + + if (mListeningToMouseMoveEventForResizers) { + DebugOnly<nsresult> rvIgnored = ListenToMouseMoveEventForResizers(false); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rvIgnored), + "HTMLEditorEventListener::ListenToMouseMoveEventForResizers() failed, " + "but ignored"); + } + if (mListeningToMouseMoveEventForGrabber) { + DebugOnly<nsresult> rvIgnored = ListenToMouseMoveEventForGrabber(false); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rvIgnored), + "HTMLEditorEventListener::ListenToMouseMoveEventForGrabber() failed, " + "but ignored"); + } + if (mListeningToResizeEvent) { + DebugOnly<nsresult> rvIgnored = ListenToWindowResizeEvent(false); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rvIgnored), + "HTMLEditorEventListener::ListenToWindowResizeEvent() failed, " + "but ignored"); + } + + EditorEventListener::Disconnect(); +} + +NS_IMETHODIMP HTMLEditorEventListener::HandleEvent(Event* aEvent) { + switch (aEvent->WidgetEventPtr()->mMessage) { + case eMouseMove: { + if (DetachedFromEditor()) { + return NS_OK; + } + + RefPtr<MouseEvent> mouseMoveEvent = aEvent->AsMouseEvent(); + if (NS_WARN_IF(!aEvent->WidgetEventPtr())) { + return NS_ERROR_FAILURE; + } + + RefPtr<HTMLEditor> htmlEditor = mEditorBase->AsHTMLEditor(); + DebugOnly<nsresult> rvIgnored = + htmlEditor->UpdateResizerOrGrabberPositionTo(CSSIntPoint( + mouseMoveEvent->ClientX(), mouseMoveEvent->ClientY())); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rvIgnored), + "HTMLEditor::UpdateResizerOrGrabberPositionTo() failed, but ignored"); + return NS_OK; + } + case eResize: { + if (DetachedFromEditor()) { + return NS_OK; + } + + RefPtr<HTMLEditor> htmlEditor = mEditorBase->AsHTMLEditor(); + nsresult rv = htmlEditor->RefreshResizers(); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "HTMLEditor::RefreshResizers() failed"); + return rv; + } + default: { + nsresult rv = EditorEventListener::HandleEvent(aEvent); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "EditorEventListener::HandleEvent() failed"); + return rv; + } + } +} + +nsresult HTMLEditorEventListener::ListenToMouseMoveEventForResizersOrGrabber( + bool aListen, bool aForGrabber) { + MOZ_ASSERT(aForGrabber ? mListeningToMouseMoveEventForGrabber != aListen + : mListeningToMouseMoveEventForResizers != aListen); + + if (NS_WARN_IF(DetachedFromEditor())) { + return aListen ? NS_ERROR_FAILURE : NS_OK; + } + + if (aListen) { + if (aForGrabber && mListeningToMouseMoveEventForResizers) { + // We've already added mousemove event listener for resizers. + mListeningToMouseMoveEventForGrabber = true; + return NS_OK; + } + if (!aForGrabber && mListeningToMouseMoveEventForGrabber) { + // We've already added mousemove event listener for grabber. + mListeningToMouseMoveEventForResizers = true; + return NS_OK; + } + } else { + if (aForGrabber && mListeningToMouseMoveEventForResizers) { + // We need to keep listening to mousemove event listener for resizers. + mListeningToMouseMoveEventForGrabber = false; + return NS_OK; + } + if (!aForGrabber && mListeningToMouseMoveEventForGrabber) { + // We need to keep listening to mousemove event listener for grabber. + mListeningToMouseMoveEventForResizers = false; + return NS_OK; + } + } + + EventTarget* eventTarget = mEditorBase->AsHTMLEditor()->GetDOMEventTarget(); + if (NS_WARN_IF(!eventTarget)) { + return NS_ERROR_FAILURE; + } + + // Listen to mousemove events in the system group since web apps may stop + // propagation before we receive the events. + EventListenerManager* eventListenerManager = + eventTarget->GetOrCreateListenerManager(); + if (NS_WARN_IF(!eventListenerManager)) { + return NS_ERROR_FAILURE; + } + + if (aListen) { + eventListenerManager->AddEventListenerByType( + this, u"mousemove"_ns, TrustedEventsAtSystemGroupBubble()); + if (aForGrabber) { + mListeningToMouseMoveEventForGrabber = true; + } else { + mListeningToMouseMoveEventForResizers = true; + } + return NS_OK; + } + + eventListenerManager->RemoveEventListenerByType( + this, u"mousemove"_ns, TrustedEventsAtSystemGroupBubble()); + if (aForGrabber) { + mListeningToMouseMoveEventForGrabber = false; + } else { + mListeningToMouseMoveEventForResizers = false; + } + return NS_OK; +} + +nsresult HTMLEditorEventListener::ListenToWindowResizeEvent(bool aListen) { + if (mListeningToResizeEvent == aListen) { + return NS_OK; + } + + if (DetachedFromEditor()) { + return aListen ? NS_ERROR_FAILURE : NS_OK; + } + + Document* document = mEditorBase->AsHTMLEditor()->GetDocument(); + if (NS_WARN_IF(!document)) { + return NS_ERROR_FAILURE; + } + + // Document::GetWindow() may return nullptr when HTMLEditor is destroyed + // while the document is being unloaded. If we cannot retrieve window as + // expected, let's ignore it. + nsPIDOMWindowOuter* window = document->GetWindow(); + if (!window) { + NS_WARNING_ASSERTION(!aListen, + "There should be window when adding event listener"); + return NS_ERROR_FAILURE; + } + + nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(window); + if (NS_WARN_IF(!eventTarget)) { + return NS_ERROR_FAILURE; + } + + // Listen to resize events in the system group since web apps may stop + // propagation before we receive the events. + EventListenerManager* eventListenerManager = + eventTarget->GetOrCreateListenerManager(); + if (NS_WARN_IF(!eventListenerManager)) { + return NS_ERROR_FAILURE; + } + + if (aListen) { + eventListenerManager->AddEventListenerByType( + this, u"resize"_ns, TrustedEventsAtSystemGroupBubble()); + mListeningToResizeEvent = true; + return NS_OK; + } + + eventListenerManager->RemoveEventListenerByType( + this, u"resize"_ns, TrustedEventsAtSystemGroupBubble()); + mListeningToResizeEvent = false; + return NS_OK; +} + +nsresult HTMLEditorEventListener::MouseUp(MouseEvent* aMouseEvent) { + MOZ_ASSERT(aMouseEvent); + MOZ_ASSERT(aMouseEvent->IsTrusted()); + + if (DetachedFromEditor()) { + return NS_OK; + } + + // FYI: We need to notify HTML editor of mouseup even if it's consumed + // because HTML editor always needs to release grabbing resizer. + RefPtr<HTMLEditor> htmlEditor = mEditorBase->AsHTMLEditor(); + htmlEditor->PreHandleMouseUp(*aMouseEvent); + + if (NS_WARN_IF(!aMouseEvent->GetTarget())) { + return NS_ERROR_FAILURE; + } + // FYI: The event target must be an element node for conforming to the + // UI Events, but it may not be not an element node if it occurs + // on native anonymous node like a resizer. + + DebugOnly<nsresult> rvIgnored = htmlEditor->StopDraggingResizerOrGrabberAt( + CSSIntPoint(aMouseEvent->ClientX(), aMouseEvent->ClientY())); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rvIgnored), + "HTMLEditor::StopDraggingResizerOrGrabberAt() failed, but ignored"); + + nsresult rv = EditorEventListener::MouseUp(aMouseEvent); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "EditorEventListener::MouseUp() failed"); + return rv; +} + +static bool IsAcceptableMouseEvent(const HTMLEditor& aHTMLEditor, + MouseEvent* aMouseEvent) { + // Contenteditable should disregard mousedowns outside it. + // IsAcceptableInputEvent() checks it for a mouse event. + WidgetMouseEvent* mousedownEvent = + aMouseEvent->WidgetEventPtr()->AsMouseEvent(); + MOZ_ASSERT(mousedownEvent); + return aHTMLEditor.IsAcceptableInputEvent(mousedownEvent); +} + +nsresult HTMLEditorEventListener::HandlePrimaryMouseButtonDown( + HTMLEditor& aHTMLEditor, MouseEvent& aMouseEvent) { + RefPtr<EventTarget> eventTarget = aMouseEvent.GetExplicitOriginalTarget(); + if (NS_WARN_IF(!eventTarget)) { + return NS_ERROR_FAILURE; + } + nsIContent* eventTargetContent = nsIContent::FromEventTarget(eventTarget); + if (!eventTargetContent) { + return NS_OK; + } + + RefPtr<Element> toSelect; + bool isElement = eventTargetContent->IsElement(); + int32_t clickCount = aMouseEvent.Detail(); + switch (clickCount) { + case 1: + if (isElement) { + OwningNonNull<Element> element(*eventTargetContent->AsElement()); + DebugOnly<nsresult> rvIgnored = + aHTMLEditor.StartToDragResizerOrHandleDragGestureOnGrabber( + aMouseEvent, element); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rvIgnored), + "HTMLEditor::StartToDragResizerOrHandleDragGestureOnGrabber() " + "failed, but ignored"); + } + break; + case 2: + if (isElement) { + toSelect = eventTargetContent->AsElement(); + } + break; + case 3: + // Triple click selects `<a href>` instead of its container paragraph + // as users may want to modify the anchor element. + if (!isElement) { + toSelect = aHTMLEditor.GetInclusiveAncestorByTagName( + *nsGkAtoms::href, *eventTargetContent); + } + break; + } + if (toSelect) { + DebugOnly<nsresult> rvIgnored = aHTMLEditor.SelectElement(toSelect); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), + "HTMLEditor::SelectElement() failed, but ignored"); + aMouseEvent.PreventDefault(); + } + return NS_OK; +} + +nsresult HTMLEditorEventListener::HandleSecondaryMouseButtonDown( + HTMLEditor& aHTMLEditor, MouseEvent& aMouseEvent) { + RefPtr<Selection> selection = aHTMLEditor.GetSelection(); + if (NS_WARN_IF(!selection)) { + return NS_OK; + } + + int32_t offset = -1; + nsCOMPtr<nsIContent> parentContent = + aMouseEvent.GetRangeParentContentAndOffset(&offset); + if (NS_WARN_IF(!parentContent) || NS_WARN_IF(offset < 0)) { + return NS_ERROR_FAILURE; + } + + if (EditorUtils::IsPointInSelection(*selection, *parentContent, + AssertedCast<uint32_t>(offset))) { + return NS_OK; + } + + RefPtr<EventTarget> eventTarget = aMouseEvent.GetExplicitOriginalTarget(); + if (NS_WARN_IF(!eventTarget)) { + return NS_ERROR_FAILURE; + } + + Element* eventTargetElement = Element::FromEventTarget(eventTarget); + + // Select entire element clicked on if NOT within an existing selection + // and not the entire body, or table-related elements + if (HTMLEditUtils::IsImage(eventTargetElement)) { + // MOZ_KnownLive(eventTargetElement): Guaranteed by eventTarget. + DebugOnly<nsresult> rvIgnored = + aHTMLEditor.SelectElement(MOZ_KnownLive(eventTargetElement)); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), + "HTMLEditor::SelectElement() failed, but ignored"); + } else { + DebugOnly<nsresult> rvIgnored = selection->CollapseInLimiter( + parentContent, AssertedCast<uint32_t>(offset)); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), + "Selection::CollapseInLimiter() failed, but ignored"); + } + + // HACK !!! Context click places the caret but the context menu consumes + // the event; so we need to check resizing state ourselves + DebugOnly<nsresult> rvIgnored = + aHTMLEditor.CheckSelectionStateForAnonymousButtons(); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), + "HTMLEditor::CheckSelectionStateForAnonymousButtons() " + "failed, but ignored"); + + return NS_OK; +} + +nsresult HTMLEditorEventListener::MouseDown(MouseEvent* aMouseEvent) { + MOZ_ASSERT(aMouseEvent); + MOZ_ASSERT(aMouseEvent->IsTrusted()); + + if (NS_WARN_IF(!aMouseEvent) || DetachedFromEditor()) { + return NS_OK; + } + + // Even if it's not acceptable mousedown event (i.e., when mousedown + // event is fired outside of the active editing host), we need to commit + // composition because it will be change the selection to the clicked + // point. Then, we won't be able to commit the composition. + if (!EnsureCommitComposition()) { + return NS_OK; + } + + RefPtr<HTMLEditor> htmlEditor = mEditorBase->AsHTMLEditor(); + htmlEditor->PreHandleMouseDown(*aMouseEvent); + + if (!IsAcceptableMouseEvent(*htmlEditor, aMouseEvent)) { + return EditorEventListener::MouseDown(aMouseEvent); + } + + if (aMouseEvent->Button() == MouseButton::ePrimary) { + nsresult rv = HandlePrimaryMouseButtonDown(*htmlEditor, *aMouseEvent); + if (NS_FAILED(rv)) { + return rv; + } + } else if (aMouseEvent->Button() == MouseButton::eSecondary) { + nsresult rv = HandleSecondaryMouseButtonDown(*htmlEditor, *aMouseEvent); + if (NS_FAILED(rv)) { + return rv; + } + } + + nsresult rv = EditorEventListener::MouseDown(aMouseEvent); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "EditorEventListener::MouseDown() failed"); + return rv; +} + +nsresult HTMLEditorEventListener::MouseClick( + WidgetMouseEvent* aMouseClickEvent) { + if (NS_WARN_IF(DetachedFromEditor())) { + return NS_OK; + } + + RefPtr<Element> element = + Element::FromEventTargetOrNull(aMouseClickEvent->GetDOMEventTarget()); + if (NS_WARN_IF(!element)) { + return NS_ERROR_FAILURE; + } + + RefPtr<HTMLEditor> htmlEditor = mEditorBase->AsHTMLEditor(); + DebugOnly<nsresult> rvIgnored = + htmlEditor->DoInlineTableEditingAction(*element); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rvIgnored), + "HTMLEditor::DoInlineTableEditingAction() failed, but ignored"); + // DoInlineTableEditingAction might cause reframe + // Editor is destroyed. + if (NS_WARN_IF(htmlEditor->Destroyed())) { + return NS_OK; + } + + nsresult rv = EditorEventListener::MouseClick(aMouseClickEvent); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "EditorEventListener::MouseClick() failed"); + return rv; +} + +} // namespace mozilla |