summaryrefslogtreecommitdiffstats
path: root/editor/libeditor/EditorEventListener.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'editor/libeditor/EditorEventListener.cpp')
-rw-r--r--editor/libeditor/EditorEventListener.cpp1237
1 files changed, 1237 insertions, 0 deletions
diff --git a/editor/libeditor/EditorEventListener.cpp b/editor/libeditor/EditorEventListener.cpp
new file mode 100644
index 0000000000..299057d17f
--- /dev/null
+++ b/editor/libeditor/EditorEventListener.cpp
@@ -0,0 +1,1237 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 sw=2 et tw=78: */
+/* 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 "EditorEventListener.h"
+
+#include "EditorBase.h" // for EditorBase, etc.
+#include "EditorUtils.h" // for EditorUtils
+#include "HTMLEditor.h" // for HTMLEditor
+#include "TextEditor.h" // for TextEditor
+
+#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc.
+#include "mozilla/AutoRestore.h"
+#include "mozilla/ContentEvents.h" // for InternalFocusEvent
+#include "mozilla/EventListenerManager.h" // for EventListenerManager
+#include "mozilla/EventStateManager.h" // for EventStateManager
+#include "mozilla/IMEStateManager.h" // for IMEStateManager
+#include "mozilla/LookAndFeel.h" // for LookAndFeel
+#include "mozilla/NativeKeyBindingsType.h" // for NativeKeyBindingsType
+#include "mozilla/Preferences.h" // for Preferences
+#include "mozilla/PresShell.h" // for PresShell
+#include "mozilla/TextEvents.h" // for WidgetCompositionEvent
+#include "mozilla/dom/DataTransfer.h"
+#include "mozilla/dom/Document.h" // for Document
+#include "mozilla/dom/DOMStringList.h"
+#include "mozilla/dom/DragEvent.h"
+#include "mozilla/dom/Element.h" // for Element
+#include "mozilla/dom/Event.h" // for Event
+#include "mozilla/dom/EventTarget.h" // for EventTarget
+#include "mozilla/dom/HTMLTextAreaElement.h"
+#include "mozilla/dom/MouseEvent.h" // for MouseEvent
+#include "mozilla/dom/Selection.h"
+
+#include "nsAString.h"
+#include "nsCaret.h" // for nsCaret
+#include "nsDebug.h" // for NS_WARNING, etc.
+#include "nsFocusManager.h" // for nsFocusManager
+#include "nsGkAtoms.h" // for nsGkAtoms, nsGkAtoms::input
+#include "nsIContent.h" // for nsIContent
+#include "nsIContentInlines.h" // for nsINode::IsInDesignMode()
+#include "nsIController.h" // for nsIController
+#include "nsID.h"
+#include "nsIFormControl.h" // for nsIFormControl, etc.
+#include "nsINode.h" // for nsINode, etc.
+#include "nsIWidget.h" // for nsIWidget
+#include "nsLiteralString.h" // for NS_LITERAL_STRING
+#include "nsPIWindowRoot.h" // for nsPIWindowRoot
+#include "nsPrintfCString.h" // for nsPrintfCString
+#include "nsRange.h"
+#include "nsServiceManagerUtils.h" // for do_GetService
+#include "nsString.h" // for nsAutoString
+#include "nsQueryObject.h" // for do_QueryObject
+#ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
+# include "nsContentUtils.h" // for nsContentUtils, etc.
+# include "nsIBidiKeyboard.h" // for nsIBidiKeyboard
+#endif
+
+#include "mozilla/dom/BrowserParent.h"
+
+class nsPresContext;
+
+namespace mozilla {
+
+using namespace dom;
+
+MOZ_CAN_RUN_SCRIPT static void DoCommandCallback(Command aCommand,
+ void* aData) {
+ Document* doc = static_cast<Document*>(aData);
+ nsPIDOMWindowOuter* win = doc->GetWindow();
+ if (!win) {
+ return;
+ }
+ nsCOMPtr<nsPIWindowRoot> root = win->GetTopWindowRoot();
+ if (!root) {
+ return;
+ }
+
+ const char* commandStr = WidgetKeyboardEvent::GetCommandStr(aCommand);
+
+ nsCOMPtr<nsIController> controller;
+ root->GetControllerForCommand(commandStr, false /* for any window */,
+ getter_AddRefs(controller));
+ if (!controller) {
+ return;
+ }
+
+ bool commandEnabled;
+ if (NS_WARN_IF(NS_FAILED(
+ controller->IsCommandEnabled(commandStr, &commandEnabled)))) {
+ return;
+ }
+ if (commandEnabled) {
+ controller->DoCommand(commandStr);
+ }
+}
+
+EditorEventListener::EditorEventListener()
+ : mEditorBase(nullptr),
+ mCommitText(false),
+ mInTransaction(false),
+ mMouseDownOrUpConsumedByIME(false)
+#ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
+ ,
+ mHaveBidiKeyboards(false),
+ mShouldSwitchTextDirection(false),
+ mSwitchToRTL(false)
+#endif
+{
+}
+
+EditorEventListener::~EditorEventListener() {
+ if (mEditorBase) {
+ NS_WARNING("We've not been uninstalled yet");
+ Disconnect();
+ }
+}
+
+nsresult EditorEventListener::Connect(EditorBase* aEditorBase) {
+ if (NS_WARN_IF(!aEditorBase)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+#ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
+ nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard();
+ if (bidiKeyboard) {
+ bool haveBidiKeyboards = false;
+ bidiKeyboard->GetHaveBidiKeyboards(&haveBidiKeyboards);
+ mHaveBidiKeyboards = haveBidiKeyboards;
+ }
+#endif
+
+ mEditorBase = aEditorBase;
+
+ nsresult rv = InstallToEditor();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("EditorEventListener::InstallToEditor() failed");
+ Disconnect();
+ }
+ return rv;
+}
+
+nsresult EditorEventListener::InstallToEditor() {
+ MOZ_ASSERT(mEditorBase, "The caller must set mEditorBase");
+
+ EventTarget* eventTarget = mEditorBase->GetDOMEventTarget();
+ if (NS_WARN_IF(!eventTarget)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // register the event listeners with the listener manager
+ EventListenerManager* eventListenerManager =
+ eventTarget->GetOrCreateListenerManager();
+ if (NS_WARN_IF(!eventListenerManager)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // For non-html editor, ie.TextEditor, we want to preserve
+ // the event handling order to ensure listeners that are
+ // added to <input> and <texarea> still working as expected.
+ EventListenerFlags flags = mEditorBase->IsHTMLEditor()
+ ? TrustedEventsAtSystemGroupCapture()
+ : TrustedEventsAtSystemGroupBubble();
+#ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
+ eventListenerManager->AddEventListenerByType(this, u"keydown"_ns, flags);
+ eventListenerManager->AddEventListenerByType(this, u"keyup"_ns, flags);
+#endif
+
+ eventListenerManager->AddEventListenerByType(this, u"keypress"_ns, flags);
+ eventListenerManager->AddEventListenerByType(this, u"dragover"_ns, flags);
+ eventListenerManager->AddEventListenerByType(this, u"dragleave"_ns, flags);
+ eventListenerManager->AddEventListenerByType(this, u"drop"_ns, flags);
+ // XXX We should add the mouse event listeners as system event group.
+ // E.g., web applications cannot prevent middle mouse paste by
+ // preventDefault() of click event at bubble phase.
+ // However, if we do so, all click handlers in any frames and frontend
+ // code need to check if it's editable. It makes easier create new bugs.
+ eventListenerManager->AddEventListenerByType(this, u"mousedown"_ns,
+ TrustedEventsAtCapture());
+ eventListenerManager->AddEventListenerByType(this, u"mouseup"_ns,
+ TrustedEventsAtCapture());
+ eventListenerManager->AddEventListenerByType(this, u"click"_ns,
+ TrustedEventsAtCapture());
+ eventListenerManager->AddEventListenerByType(
+ this, u"auxclick"_ns, TrustedEventsAtSystemGroupCapture());
+ // Focus event doesn't bubble so adding the listener to capturing phase as
+ // system event group.
+ eventListenerManager->AddEventListenerByType(
+ this, u"blur"_ns, TrustedEventsAtSystemGroupCapture());
+ eventListenerManager->AddEventListenerByType(
+ this, u"focus"_ns, TrustedEventsAtSystemGroupCapture());
+ eventListenerManager->AddEventListenerByType(
+ this, u"text"_ns, TrustedEventsAtSystemGroupBubble());
+ eventListenerManager->AddEventListenerByType(
+ this, u"compositionstart"_ns, TrustedEventsAtSystemGroupBubble());
+ eventListenerManager->AddEventListenerByType(
+ this, u"compositionend"_ns, TrustedEventsAtSystemGroupBubble());
+
+ return NS_OK;
+}
+
+void EditorEventListener::Disconnect() {
+ if (DetachedFromEditor()) {
+ return;
+ }
+ UninstallFromEditor();
+
+ const OwningNonNull<EditorBase> editorBase = *mEditorBase;
+ mEditorBase = nullptr;
+
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm) {
+ nsIContent* focusedContent = fm->GetFocusedElement();
+ mozilla::dom::Element* root = editorBase->GetRoot();
+ if (focusedContent && root &&
+ focusedContent->IsInclusiveDescendantOf(root)) {
+ // Reset the Selection ancestor limiter and SelectionController state
+ // that EditorBase::InitializeSelection set up.
+ DebugOnly<nsresult> rvIgnored = editorBase->FinalizeSelection();
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rvIgnored),
+ "EditorBase::FinalizeSelection() failed, but ignored");
+ }
+ }
+}
+
+void EditorEventListener::UninstallFromEditor() {
+ CleanupDragDropCaret();
+
+ EventTarget* eventTarget = mEditorBase->GetDOMEventTarget();
+ if (NS_WARN_IF(!eventTarget)) {
+ return;
+ }
+
+ EventListenerManager* eventListenerManager =
+ eventTarget->GetOrCreateListenerManager();
+ if (NS_WARN_IF(!eventListenerManager)) {
+ return;
+ }
+
+ EventListenerFlags flags = mEditorBase->IsHTMLEditor()
+ ? TrustedEventsAtSystemGroupCapture()
+ : TrustedEventsAtSystemGroupBubble();
+#ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
+ eventListenerManager->RemoveEventListenerByType(this, u"keydown"_ns, flags);
+ eventListenerManager->RemoveEventListenerByType(this, u"keyup"_ns, flags);
+#endif
+ eventListenerManager->RemoveEventListenerByType(this, u"keypress"_ns, flags);
+ eventListenerManager->RemoveEventListenerByType(this, u"dragover"_ns, flags);
+ eventListenerManager->RemoveEventListenerByType(this, u"dragleave"_ns, flags);
+ eventListenerManager->RemoveEventListenerByType(this, u"drop"_ns, flags);
+ eventListenerManager->RemoveEventListenerByType(this, u"mousedown"_ns,
+ TrustedEventsAtCapture());
+ eventListenerManager->RemoveEventListenerByType(this, u"mouseup"_ns,
+ TrustedEventsAtCapture());
+ eventListenerManager->RemoveEventListenerByType(this, u"click"_ns,
+ TrustedEventsAtCapture());
+ eventListenerManager->RemoveEventListenerByType(
+ this, u"auxclick"_ns, TrustedEventsAtSystemGroupCapture());
+ eventListenerManager->RemoveEventListenerByType(
+ this, u"blur"_ns, TrustedEventsAtSystemGroupCapture());
+ eventListenerManager->RemoveEventListenerByType(
+ this, u"focus"_ns, TrustedEventsAtSystemGroupCapture());
+ eventListenerManager->RemoveEventListenerByType(
+ this, u"text"_ns, TrustedEventsAtSystemGroupBubble());
+ eventListenerManager->RemoveEventListenerByType(
+ this, u"compositionstart"_ns, TrustedEventsAtSystemGroupBubble());
+ eventListenerManager->RemoveEventListenerByType(
+ this, u"compositionend"_ns, TrustedEventsAtSystemGroupBubble());
+}
+
+PresShell* EditorEventListener::GetPresShell() const {
+ MOZ_ASSERT(!DetachedFromEditor());
+ return mEditorBase->GetPresShell();
+}
+
+nsPresContext* EditorEventListener::GetPresContext() const {
+ PresShell* presShell = GetPresShell();
+ return presShell ? presShell->GetPresContext() : nullptr;
+}
+
+bool EditorEventListener::EditorHasFocus() {
+ MOZ_ASSERT(!DetachedFromEditor());
+ const Element* focusedElement = mEditorBase->GetFocusedElement();
+ return focusedElement && focusedElement->IsInComposedDoc();
+}
+
+NS_IMPL_ISUPPORTS(EditorEventListener, nsIDOMEventListener)
+
+bool EditorEventListener::DetachedFromEditor() const { return !mEditorBase; }
+
+bool EditorEventListener::DetachedFromEditorOrDefaultPrevented(
+ WidgetEvent* aWidgetEvent) const {
+ return NS_WARN_IF(!aWidgetEvent) || DetachedFromEditor() ||
+ aWidgetEvent->DefaultPrevented();
+}
+
+bool EditorEventListener::EnsureCommitComposition() {
+ MOZ_ASSERT(!DetachedFromEditor());
+ RefPtr<EditorBase> editorBase(mEditorBase);
+ editorBase->CommitComposition();
+ return !DetachedFromEditor();
+}
+
+NS_IMETHODIMP EditorEventListener::HandleEvent(Event* aEvent) {
+ // Let's handle each event with the message of the internal event of the
+ // coming event. If the DOM event was created with improper interface,
+ // e.g., keydown event is created with |new MouseEvent("keydown", {});|,
+ // its message is always 0. Therefore, we can ban such strange event easy.
+ // However, we need to handle strange "focus" and "blur" event. See the
+ // following code of this switch statement.
+ // NOTE: Each event handler may require specific event interface. Before
+ // calling it, this queries the specific interface. If it would fail,
+ // each event handler would just ignore the event. So, in this method,
+ // you don't need to check if the QI succeeded before each call.
+ WidgetEvent* internalEvent = aEvent->WidgetEventPtr();
+
+ if (DetachedFromEditor()) {
+ return NS_OK;
+ }
+
+ // For nested documents with multiple HTMLEditor registered on different
+ // nsWindowRoot, make sure the HTMLEditor for the original event target
+ // handles the events.
+ if (mEditorBase->IsHTMLEditor()) {
+ nsCOMPtr<nsINode> originalEventTargetNode =
+ nsINode::FromEventTargetOrNull(aEvent->GetOriginalTarget());
+
+ if (originalEventTargetNode &&
+ mEditorBase != originalEventTargetNode->OwnerDoc()->GetHTMLEditor()) {
+ return NS_OK;
+ }
+ if (!originalEventTargetNode && internalEvent->mMessage == eFocus &&
+ aEvent->GetCurrentTarget()->IsRootWindow()) {
+ return NS_OK;
+ }
+ }
+
+ switch (internalEvent->mMessage) {
+ // dragover and drop
+ case eDragOver:
+ case eDrop: {
+ // The editor which is registered on nsWindowRoot shouldn't handle
+ // drop events when it can be handled by Input or TextArea element on
+ // the chain.
+ if (aEvent->GetCurrentTarget()->IsRootWindow() &&
+ TextControlElement::FromEventTargetOrNull(
+ internalEvent->GetDOMEventTarget())) {
+ return NS_OK;
+ }
+ // aEvent should be grabbed by the caller since this is
+ // nsIDOMEventListener method. However, our clang plugin cannot check it
+ // if we use Event::As*Event(). So, we need to grab it by ourselves.
+ RefPtr<DragEvent> dragEvent = aEvent->AsDragEvent();
+ nsresult rv = DragOverOrDrop(dragEvent);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "EditorEventListener::DragOverOrDrop() failed");
+ return rv;
+ }
+ // DragLeave
+ case eDragLeave: {
+ RefPtr<DragEvent> dragEvent = aEvent->AsDragEvent();
+ nsresult rv = DragLeave(dragEvent);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "EditorEventListener::DragLeave() failed");
+ return rv;
+ }
+#ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
+ // keydown
+ case eKeyDown: {
+ nsresult rv = KeyDown(internalEvent->AsKeyboardEvent());
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "EditorEventListener::KeyDown() failed");
+ return rv;
+ }
+ // keyup
+ case eKeyUp: {
+ nsresult rv = KeyUp(internalEvent->AsKeyboardEvent());
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "EditorEventListener::KeyUp() failed");
+ return rv;
+ }
+#endif // #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
+ // keypress
+ case eKeyPress: {
+ nsresult rv = KeyPress(internalEvent->AsKeyboardEvent());
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "EditorEventListener::KeyPress() failed");
+ return rv;
+ }
+ // mousedown
+ case eMouseDown: {
+ // EditorEventListener may receive (1) all mousedown, mouseup and click
+ // events, (2) only mousedown event or (3) only mouseup event.
+ // mMouseDownOrUpConsumedByIME is used only for ignoring click event if
+ // preceding mousedown and/or mouseup event is consumed by IME.
+ // Therefore, even if case #2 or case #3 occurs,
+ // mMouseDownOrUpConsumedByIME is true here. Therefore, we should always
+ // overwrite it here.
+ mMouseDownOrUpConsumedByIME =
+ NotifyIMEOfMouseButtonEvent(internalEvent->AsMouseEvent());
+ if (mMouseDownOrUpConsumedByIME) {
+ return NS_OK;
+ }
+ RefPtr<MouseEvent> mouseEvent = aEvent->AsMouseEvent();
+ if (NS_WARN_IF(!mouseEvent)) {
+ return NS_OK;
+ }
+ nsresult rv = MouseDown(mouseEvent);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "EditorEventListener::MouseDown() failed");
+ return rv;
+ }
+ // mouseup
+ case eMouseUp: {
+ // See above comment in the eMouseDown case, first.
+ // This code assumes that case #1 is occuring. However, if case #3 may
+ // occurs after case #2 and the mousedown is consumed,
+ // mMouseDownOrUpConsumedByIME is true even though EditorEventListener
+ // has not received the preceding mousedown event of this mouseup event.
+ // So, mMouseDownOrUpConsumedByIME may be invalid here. However,
+ // this is not a matter because mMouseDownOrUpConsumedByIME is referred
+ // only by eMouseClick case but click event is fired only in case #1.
+ // So, before a click event is fired, mMouseDownOrUpConsumedByIME is
+ // always initialized in the eMouseDown case if it's referred.
+ if (NotifyIMEOfMouseButtonEvent(internalEvent->AsMouseEvent())) {
+ mMouseDownOrUpConsumedByIME = true;
+ }
+ if (mMouseDownOrUpConsumedByIME) {
+ return NS_OK;
+ }
+ RefPtr<MouseEvent> mouseEvent = aEvent->AsMouseEvent();
+ if (NS_WARN_IF(!mouseEvent)) {
+ return NS_OK;
+ }
+ nsresult rv = MouseUp(mouseEvent);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "EditorEventListener::MouseUp() failed");
+ return rv;
+ }
+ // click
+ case eMouseClick: {
+ WidgetMouseEvent* widgetMouseEvent = internalEvent->AsMouseEvent();
+ // Don't handle non-primary click events
+ if (widgetMouseEvent->mButton != MouseButton::ePrimary) {
+ return NS_OK;
+ }
+ [[fallthrough]];
+ }
+ // auxclick
+ case eMouseAuxClick: {
+ WidgetMouseEvent* widgetMouseEvent = internalEvent->AsMouseEvent();
+ if (NS_WARN_IF(!widgetMouseEvent)) {
+ return NS_OK;
+ }
+ // If the preceding mousedown event or mouseup event was consumed,
+ // editor shouldn't handle this click event.
+ if (mMouseDownOrUpConsumedByIME) {
+ mMouseDownOrUpConsumedByIME = false;
+ widgetMouseEvent->PreventDefault();
+ return NS_OK;
+ }
+ nsresult rv = MouseClick(widgetMouseEvent);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "EditorEventListener::MouseClick() failed");
+ return rv;
+ }
+ // focus
+ case eFocus: {
+ const InternalFocusEvent* focusEvent = internalEvent->AsFocusEvent();
+ if (NS_WARN_IF(!focusEvent)) {
+ return NS_ERROR_FAILURE;
+ }
+ nsresult rv = Focus(*focusEvent);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "EditorEventListener::Focus() failed");
+ return rv;
+ }
+ // blur
+ case eBlur: {
+ const InternalFocusEvent* blurEvent = internalEvent->AsFocusEvent();
+ if (NS_WARN_IF(!blurEvent)) {
+ return NS_ERROR_FAILURE;
+ }
+ nsresult rv = Blur(*blurEvent);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "EditorEventListener::Blur() failed");
+ return rv;
+ }
+ // text
+ case eCompositionChange: {
+ nsresult rv =
+ HandleChangeComposition(internalEvent->AsCompositionEvent());
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "EditorEventListener::HandleChangeComposition() failed");
+ return rv;
+ }
+ // compositionstart
+ case eCompositionStart: {
+ nsresult rv = HandleStartComposition(internalEvent->AsCompositionEvent());
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "EditorEventListener::HandleStartComposition() failed");
+ return rv;
+ }
+ // compositionend
+ case eCompositionEnd: {
+ HandleEndComposition(internalEvent->AsCompositionEvent());
+ return NS_OK;
+ }
+ default:
+ break;
+ }
+
+#ifdef DEBUG
+ nsAutoString eventType;
+ aEvent->GetType(eventType);
+ nsPrintfCString assertMessage(
+ "Editor doesn't handle \"%s\" event "
+ "because its internal event doesn't have proper message",
+ NS_ConvertUTF16toUTF8(eventType).get());
+ NS_ASSERTION(false, assertMessage.get());
+#endif
+
+ return NS_OK;
+}
+
+#ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
+
+// This function is borrowed from Chromium's ImeInput::IsCtrlShiftPressed
+bool IsCtrlShiftPressed(const WidgetKeyboardEvent* aKeyboardEvent,
+ bool& isRTL) {
+ MOZ_ASSERT(aKeyboardEvent);
+ // To check if a user is pressing only a control key and a right-shift key
+ // (or a left-shift key), we use the steps below:
+ // 1. Check if a user is pressing a control key and a right-shift key (or
+ // a left-shift key).
+ // 2. If the condition 1 is true, we should check if there are any other
+ // keys pressed at the same time.
+ // To ignore the keys checked in 1, we set their status to 0 before
+ // checking the key status.
+
+ if (!aKeyboardEvent->IsControl()) {
+ return false;
+ }
+
+ switch (aKeyboardEvent->mLocation) {
+ case eKeyLocationRight:
+ isRTL = true;
+ break;
+ case eKeyLocationLeft:
+ isRTL = false;
+ break;
+ default:
+ return false;
+ }
+
+ // Scan the key status to find pressed keys. We should abandon changing the
+ // text direction when there are other pressed keys.
+ return !aKeyboardEvent->IsAlt() && !aKeyboardEvent->IsMeta();
+}
+
+// This logic is mostly borrowed from Chromium's
+// RenderWidgetHostViewWin::OnKeyEvent.
+
+nsresult EditorEventListener::KeyUp(const WidgetKeyboardEvent* aKeyboardEvent) {
+ if (NS_WARN_IF(!aKeyboardEvent) || DetachedFromEditor()) {
+ return NS_OK;
+ }
+
+ if (!mHaveBidiKeyboards) {
+ return NS_OK;
+ }
+
+ // XXX Why doesn't this method check if it's consumed?
+ RefPtr<EditorBase> editorBase(mEditorBase);
+ if ((aKeyboardEvent->mKeyCode == NS_VK_SHIFT ||
+ aKeyboardEvent->mKeyCode == NS_VK_CONTROL) &&
+ mShouldSwitchTextDirection &&
+ (editorBase->IsTextEditor() ||
+ editorBase->AsHTMLEditor()->IsPlaintextMailComposer())) {
+ editorBase->SwitchTextDirectionTo(mSwitchToRTL
+ ? EditorBase::TextDirection::eRTL
+ : EditorBase::TextDirection::eLTR);
+ mShouldSwitchTextDirection = false;
+ }
+ return NS_OK;
+}
+
+nsresult EditorEventListener::KeyDown(
+ const WidgetKeyboardEvent* aKeyboardEvent) {
+ if (NS_WARN_IF(!aKeyboardEvent) || DetachedFromEditor()) {
+ return NS_OK;
+ }
+
+ if (!mHaveBidiKeyboards) {
+ return NS_OK;
+ }
+
+ // XXX Why isn't this method check if it's consumed?
+ if (aKeyboardEvent->mKeyCode == NS_VK_SHIFT) {
+ bool switchToRTL;
+ if (IsCtrlShiftPressed(aKeyboardEvent, switchToRTL)) {
+ mShouldSwitchTextDirection = true;
+ mSwitchToRTL = switchToRTL;
+ }
+ } else if (aKeyboardEvent->mKeyCode != NS_VK_CONTROL) {
+ // In case the user presses any other key besides Ctrl and Shift
+ mShouldSwitchTextDirection = false;
+ }
+ return NS_OK;
+}
+
+#endif // #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
+
+nsresult EditorEventListener::KeyPress(WidgetKeyboardEvent* aKeyboardEvent) {
+ if (NS_WARN_IF(!aKeyboardEvent)) {
+ return NS_OK;
+ }
+
+ RefPtr<EditorBase> editorBase(mEditorBase);
+ if (!editorBase->IsAcceptableInputEvent(aKeyboardEvent) ||
+ DetachedFromEditorOrDefaultPrevented(aKeyboardEvent)) {
+ return NS_OK;
+ }
+
+ // The exposed root of our editor may have been hidden or destroyed by a
+ // preceding event listener. However, the destruction has not occurred yet if
+ // pending notifications have not been flushed yet. Therefore, before
+ // handling user input, we need to get the latest state and if it's now
+ // destroyed with the flushing, we should just ignore this event instead of
+ // returning error since this is just a event listener.
+ RefPtr<Document> document = editorBase->GetDocument();
+ if (!document) {
+ return NS_OK;
+ }
+ document->FlushPendingNotifications(FlushType::Layout);
+ if (editorBase->Destroyed() || DetachedFromEditor()) {
+ return NS_OK;
+ }
+
+ nsresult rv = editorBase->HandleKeyPressEvent(aKeyboardEvent);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("EditorBase::HandleKeyPressEvent() failed");
+ return rv;
+ }
+
+ auto GetWidget = [&]() -> nsIWidget* {
+ if (aKeyboardEvent->mWidget) {
+ return aKeyboardEvent->mWidget;
+ }
+ // If the event is created by chrome script, the widget is always nullptr.
+ return IMEStateManager::GetWidgetForTextInputHandling();
+ };
+
+ if (DetachedFromEditor()) {
+ return NS_OK;
+ }
+
+ if (LookAndFeel::GetInt(LookAndFeel::IntID::HideCursorWhileTyping)) {
+ if (nsPresContext* pc = GetPresContext()) {
+ if (nsIWidget* widget = GetWidget()) {
+ pc->EventStateManager()->StartHidingCursorWhileTyping(widget);
+ }
+ }
+ }
+
+ if (aKeyboardEvent->DefaultPrevented()) {
+ return NS_OK;
+ }
+
+ if (!ShouldHandleNativeKeyBindings(aKeyboardEvent)) {
+ return NS_OK;
+ }
+
+ // Now, ask the native key bindings to handle the event.
+
+ nsIWidget* widget = GetWidget();
+ if (NS_WARN_IF(!widget)) {
+ return NS_OK;
+ }
+
+ RefPtr<Document> doc = editorBase->GetDocument();
+
+ // WidgetKeyboardEvent::ExecuteEditCommands() requires non-nullptr mWidget.
+ // If the event is created by chrome script, it is nullptr but we need to
+ // execute native key bindings. Therefore, we need to set widget to
+ // WidgetEvent::mWidget temporarily.
+ AutoRestore<nsCOMPtr<nsIWidget>> saveWidget(aKeyboardEvent->mWidget);
+ aKeyboardEvent->mWidget = widget;
+ if (aKeyboardEvent->ExecuteEditCommands(NativeKeyBindingsType::RichTextEditor,
+ DoCommandCallback, doc)) {
+ aKeyboardEvent->PreventDefault();
+ }
+ return NS_OK;
+}
+
+nsresult EditorEventListener::MouseClick(WidgetMouseEvent* aMouseClickEvent) {
+ if (NS_WARN_IF(!aMouseClickEvent) || DetachedFromEditor()) {
+ return NS_OK;
+ }
+ // nothing to do if editor isn't editable or clicked on out of the editor.
+ OwningNonNull<EditorBase> editorBase = *mEditorBase;
+ if (editorBase->IsReadonly() ||
+ !editorBase->IsAcceptableInputEvent(aMouseClickEvent)) {
+ return NS_OK;
+ }
+
+ // Notifies clicking on editor to IMEStateManager even when the event was
+ // consumed.
+ if (EditorHasFocus()) {
+ if (RefPtr<nsPresContext> presContext = GetPresContext()) {
+ RefPtr<Element> focusedElement = mEditorBase->GetFocusedElement();
+ IMEStateManager::OnClickInEditor(*presContext, focusedElement,
+ *aMouseClickEvent);
+ if (DetachedFromEditor()) {
+ return NS_OK;
+ }
+ }
+ }
+
+ if (DetachedFromEditorOrDefaultPrevented(aMouseClickEvent)) {
+ // We're done if 'preventdefault' is true (see for example bug 70698).
+ return NS_OK;
+ }
+
+ // If we got a mouse down inside the editing area, we should force the
+ // IME to commit before we change the cursor position.
+ if (!EnsureCommitComposition()) {
+ return NS_OK;
+ }
+
+ // XXX The following code is hack for our buggy "click" and "auxclick"
+ // implementation. "auxclick" event was added recently, however,
+ // any non-primary button click event handlers in our UI still keep
+ // listening to "click" events. Additionally, "auxclick" event is
+ // fired after "click" events and even if we do this in the system event
+ // group, middle click opens new tab before us. Therefore, we need to
+ // handle middle click at capturing phase of the default group even
+ // though this makes web apps cannot prevent middle click paste with
+ // calling preventDefault() of "click" nor "auxclick".
+
+ if (aMouseClickEvent->mButton != MouseButton::eMiddle ||
+ !WidgetMouseEvent::IsMiddleClickPasteEnabled()) {
+ return NS_OK;
+ }
+
+ RefPtr<PresShell> presShell = GetPresShell();
+ if (NS_WARN_IF(!presShell)) {
+ return NS_OK;
+ }
+ nsPresContext* presContext = GetPresContext();
+ if (NS_WARN_IF(!presContext)) {
+ return NS_OK;
+ }
+ MOZ_ASSERT(!aMouseClickEvent->DefaultPrevented());
+ nsEventStatus status = nsEventStatus_eIgnore;
+ RefPtr<EventStateManager> esm = presContext->EventStateManager();
+ DebugOnly<nsresult> rvIgnored = esm->HandleMiddleClickPaste(
+ presShell, aMouseClickEvent, &status, editorBase);
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rvIgnored),
+ "EventStateManager::HandleMiddleClickPaste() failed, but ignored");
+ if (status == nsEventStatus_eConsumeNoDefault) {
+ // We no longer need to StopImmediatePropagation here since
+ // ClickHandlerChild.sys.mjs checks for and ignores editables, so won't
+ // re-handle the event
+ aMouseClickEvent->PreventDefault();
+ }
+ return NS_OK;
+}
+
+bool EditorEventListener::NotifyIMEOfMouseButtonEvent(
+ WidgetMouseEvent* aMouseEvent) {
+ MOZ_ASSERT(aMouseEvent);
+
+ if (!EditorHasFocus()) {
+ return false;
+ }
+
+ RefPtr<nsPresContext> presContext = GetPresContext();
+ if (NS_WARN_IF(!presContext)) {
+ return false;
+ }
+ RefPtr<Element> focusedElement = mEditorBase->GetFocusedElement();
+ return IMEStateManager::OnMouseButtonEventInEditor(
+ *presContext, focusedElement, *aMouseEvent);
+}
+
+nsresult EditorEventListener::MouseDown(MouseEvent* aMouseEvent) {
+ // FYI: We don't need to check if it's already consumed here because
+ // we need to commit composition at mouse button operation.
+ // FYI: This may be called by HTMLEditorEventListener::MouseDown() even
+ // when the event is not acceptable for committing composition.
+ if (DetachedFromEditor()) {
+ return NS_OK;
+ }
+ Unused << EnsureCommitComposition();
+ return NS_OK;
+}
+
+/**
+ * Drag event implementation
+ */
+
+void EditorEventListener::RefuseToDropAndHideCaret(DragEvent* aDragEvent) {
+ MOZ_ASSERT(aDragEvent->WidgetEventPtr()->mFlags.mInSystemGroup);
+
+ aDragEvent->PreventDefault();
+ aDragEvent->StopImmediatePropagation();
+ DataTransfer* dataTransfer = aDragEvent->GetDataTransfer();
+ if (dataTransfer) {
+ dataTransfer->SetDropEffectInt(nsIDragService::DRAGDROP_ACTION_NONE);
+ }
+ if (mCaret) {
+ mCaret->SetVisible(false);
+ }
+}
+
+nsresult EditorEventListener::DragOverOrDrop(DragEvent* aDragEvent) {
+ MOZ_ASSERT(aDragEvent);
+ MOZ_ASSERT(aDragEvent->WidgetEventPtr()->mMessage == eDrop ||
+ aDragEvent->WidgetEventPtr()->mMessage == eDragOver);
+
+ if (aDragEvent->WidgetEventPtr()->mMessage == eDrop) {
+ CleanupDragDropCaret();
+ MOZ_ASSERT(!mCaret);
+ } else {
+ InitializeDragDropCaret();
+ MOZ_ASSERT(mCaret);
+ }
+
+ if (DetachedFromEditorOrDefaultPrevented(aDragEvent->WidgetEventPtr())) {
+ return NS_OK;
+ }
+
+ int32_t dropOffset = -1;
+ nsCOMPtr<nsIContent> dropParentContent =
+ aDragEvent->GetRangeParentContentAndOffset(&dropOffset);
+ if (NS_WARN_IF(!dropParentContent) || NS_WARN_IF(dropOffset < 0)) {
+ return NS_ERROR_FAILURE;
+ }
+ if (DetachedFromEditor()) {
+ RefuseToDropAndHideCaret(aDragEvent);
+ return NS_OK;
+ }
+
+ bool notEditable =
+ !dropParentContent->IsEditable() || mEditorBase->IsReadonly();
+
+ // First of all, hide caret if we won't insert the drop data into the editor
+ // obviously.
+ if (mCaret && (IsFileControlTextBox() || notEditable)) {
+ mCaret->SetVisible(false);
+ }
+
+ // If we're a native anonymous <input> element in <input type="file">,
+ // we don't need to handle the drop.
+ if (IsFileControlTextBox()) {
+ return NS_OK;
+ }
+
+ // If the drop target isn't ediable, the drop should be handled by the
+ // element.
+ if (notEditable) {
+ // If we're a text control element which is readonly or disabled,
+ // we should refuse to drop.
+ if (mEditorBase->IsTextEditor()) {
+ RefuseToDropAndHideCaret(aDragEvent);
+ return NS_OK;
+ }
+ // Otherwise, we shouldn't handle the drop.
+ return NS_OK;
+ }
+
+ // If the drag event does not have any data which we can handle, we should
+ // refuse to drop even if some parents can handle it because user may be
+ // trying to drop it on us, not our parent. For example, users don't want
+ // to drop HTML data to parent contenteditable element if they drop it on
+ // a child <input> element.
+ if (!DragEventHasSupportingData(aDragEvent)) {
+ RefuseToDropAndHideCaret(aDragEvent);
+ return NS_OK;
+ }
+
+ // If we don't accept the data drop at the point, for example, while dragging
+ // selection, it's not allowed dropping on its selection ranges. In this
+ // case, any parents shouldn't handle the drop instead of us, for example,
+ // dropping text shouldn't be treated as URL and load new page.
+ if (!CanInsertAtDropPosition(aDragEvent)) {
+ RefuseToDropAndHideCaret(aDragEvent);
+ return NS_OK;
+ }
+
+ WidgetDragEvent* asWidgetEvent = aDragEvent->WidgetEventPtr()->AsDragEvent();
+ AutoRestore<bool> inHTMLEditorEventListener(
+ asWidgetEvent->mInHTMLEditorEventListener);
+ if (mEditorBase->IsHTMLEditor()) {
+ asWidgetEvent->mInHTMLEditorEventListener = true;
+ }
+ aDragEvent->PreventDefault();
+
+ aDragEvent->StopImmediatePropagation();
+
+ if (asWidgetEvent->mMessage == eDrop) {
+ RefPtr<EditorBase> editorBase = mEditorBase;
+ nsresult rv = editorBase->HandleDropEvent(aDragEvent);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "EditorBase::HandleDropEvent() failed");
+ return rv;
+ }
+
+ MOZ_ASSERT(asWidgetEvent->mMessage == eDragOver);
+
+ // If we handle the dragged item, we need to adjust drop effect here
+ // because once DataTransfer is retrieved, DragEvent has initialized it
+ // with nsContentUtils::SetDataTransferInEvent() but it does not check
+ // whether the content is movable or not.
+ DataTransfer* dataTransfer = aDragEvent->GetDataTransfer();
+ if (dataTransfer &&
+ dataTransfer->DropEffectInt() == nsIDragService::DRAGDROP_ACTION_MOVE) {
+ nsCOMPtr<nsINode> dragSource = dataTransfer->GetMozSourceNode();
+ if (dragSource && !dragSource->IsEditable()) {
+ // In this case, we shouldn't allow "move" because the drag source
+ // isn't editable.
+ dataTransfer->SetDropEffectInt(
+ nsContentUtils::FilterDropEffect(nsIDragService::DRAGDROP_ACTION_COPY,
+ dataTransfer->EffectAllowedInt()));
+ }
+ }
+
+ if (!mCaret) {
+ return NS_OK;
+ }
+
+ mCaret->SetVisible(true);
+ mCaret->SetCaretPosition(dropParentContent, dropOffset);
+
+ return NS_OK;
+}
+
+void EditorEventListener::InitializeDragDropCaret() {
+ if (mCaret) {
+ return;
+ }
+
+ RefPtr<PresShell> presShell = GetPresShell();
+ if (NS_WARN_IF(!presShell)) {
+ return;
+ }
+
+ mCaret = new nsCaret();
+ DebugOnly<nsresult> rvIgnored = mCaret->Init(presShell);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+ "nsCaret::Init() failed, but ignored");
+ mCaret->SetCaretReadOnly(true);
+ // This is to avoid the requirement that the Selection is Collapsed which
+ // it can't be when dragging a selection in the same shell.
+ // See nsCaret::IsVisible().
+ mCaret->SetVisibilityDuringSelection(true);
+
+ presShell->SetCaret(mCaret);
+}
+
+void EditorEventListener::CleanupDragDropCaret() {
+ if (!mCaret) {
+ return;
+ }
+
+ mCaret->SetVisible(false); // hide it, so that it turns off its timer
+
+ RefPtr<PresShell> presShell = GetPresShell();
+ if (presShell) {
+ presShell->RestoreCaret();
+ }
+
+ mCaret->Terminate();
+ mCaret = nullptr;
+}
+
+nsresult EditorEventListener::DragLeave(DragEvent* aDragEvent) {
+ // XXX If aDragEvent was created by chrome script, its defaultPrevented
+ // may be true, though. We shouldn't handle such event but we don't
+ // have a way to distinguish if coming event is created by chrome script.
+ NS_WARNING_ASSERTION(!aDragEvent->WidgetEventPtr()->DefaultPrevented(),
+ "eDragLeave shouldn't be cancelable");
+ if (NS_WARN_IF(!aDragEvent) || DetachedFromEditor()) {
+ return NS_OK;
+ }
+
+ CleanupDragDropCaret();
+
+ return NS_OK;
+}
+
+bool EditorEventListener::DragEventHasSupportingData(
+ DragEvent* aDragEvent) const {
+ MOZ_ASSERT(
+ !DetachedFromEditorOrDefaultPrevented(aDragEvent->WidgetEventPtr()));
+ MOZ_ASSERT(aDragEvent->GetDataTransfer());
+
+ // Plaintext editors only support dropping text. Otherwise, HTML and files
+ // can be dropped as well.
+ DataTransfer* dataTransfer = aDragEvent->GetDataTransfer();
+ if (!dataTransfer) {
+ NS_WARNING("No data transfer returned");
+ return false;
+ }
+ return dataTransfer->HasType(NS_LITERAL_STRING_FROM_CSTRING(kTextMime)) ||
+ dataTransfer->HasType(
+ NS_LITERAL_STRING_FROM_CSTRING(kMozTextInternal)) ||
+ (mEditorBase->IsHTMLEditor() &&
+ !mEditorBase->AsHTMLEditor()->IsPlaintextMailComposer() &&
+ (dataTransfer->HasType(NS_LITERAL_STRING_FROM_CSTRING(kHTMLMime)) ||
+ dataTransfer->HasType(NS_LITERAL_STRING_FROM_CSTRING(kFileMime))));
+}
+
+bool EditorEventListener::CanInsertAtDropPosition(DragEvent* aDragEvent) {
+ MOZ_ASSERT(
+ !DetachedFromEditorOrDefaultPrevented(aDragEvent->WidgetEventPtr()));
+ MOZ_ASSERT(!mEditorBase->IsReadonly());
+ MOZ_ASSERT(DragEventHasSupportingData(aDragEvent));
+
+ DataTransfer* dataTransfer = aDragEvent->GetDataTransfer();
+ if (NS_WARN_IF(!dataTransfer)) {
+ return false;
+ }
+
+ // If there is no source node, this is probably an external drag and the
+ // drop is allowed. The later checks rely on checking if the drag target
+ // is the same as the drag source.
+ nsCOMPtr<nsINode> sourceNode = dataTransfer->GetMozSourceNode();
+ if (!sourceNode) {
+ return true;
+ }
+
+ // There is a source node, so compare the source documents and this document.
+ // Disallow drops on the same document.
+
+ RefPtr<Document> targetDocument = mEditorBase->GetDocument();
+ if (NS_WARN_IF(!targetDocument)) {
+ return false;
+ }
+
+ RefPtr<Document> sourceDocument = sourceNode->OwnerDoc();
+
+ // If the source and the dest are not same document, allow to drop it always.
+ if (targetDocument != sourceDocument) {
+ return true;
+ }
+
+ // If the source node is a remote browser, treat this as coming from a
+ // different document and allow the drop.
+ if (BrowserParent::GetFrom(nsIContent::FromNode(sourceNode))) {
+ return true;
+ }
+
+ RefPtr<Selection> selection = mEditorBase->GetSelection();
+ if (!selection) {
+ return false;
+ }
+
+ // If selection is collapsed, allow to drop it always.
+ if (selection->IsCollapsed()) {
+ return true;
+ }
+
+ int32_t dropOffset = -1;
+ nsCOMPtr<nsIContent> dropParentContent =
+ aDragEvent->GetRangeParentContentAndOffset(&dropOffset);
+ if (!dropParentContent || NS_WARN_IF(dropOffset < 0) ||
+ NS_WARN_IF(DetachedFromEditor())) {
+ return false;
+ }
+
+ return !nsContentUtils::IsPointInSelection(*selection, *dropParentContent,
+ dropOffset);
+}
+
+nsresult EditorEventListener::HandleStartComposition(
+ WidgetCompositionEvent* aCompositionStartEvent) {
+ if (NS_WARN_IF(!aCompositionStartEvent)) {
+ return NS_ERROR_FAILURE;
+ }
+ if (DetachedFromEditor()) {
+ return NS_OK;
+ }
+ RefPtr<EditorBase> editorBase(mEditorBase);
+ if (!editorBase->IsAcceptableInputEvent(aCompositionStartEvent)) {
+ return NS_OK;
+ }
+ // Although, "compositionstart" should be cancelable, but currently,
+ // eCompositionStart event coming from widget is not cancelable.
+ MOZ_ASSERT(!aCompositionStartEvent->DefaultPrevented(),
+ "eCompositionStart shouldn't be cancelable");
+ nsresult rv = editorBase->OnCompositionStart(*aCompositionStartEvent);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "EditorBase::OnCompositionStart() failed");
+ return rv;
+}
+
+nsresult EditorEventListener::HandleChangeComposition(
+ WidgetCompositionEvent* aCompositionChangeEvent) {
+ if (NS_WARN_IF(!aCompositionChangeEvent)) {
+ return NS_ERROR_FAILURE;
+ }
+ MOZ_ASSERT(!aCompositionChangeEvent->DefaultPrevented(),
+ "eCompositionChange event shouldn't be cancelable");
+ if (DetachedFromEditor()) {
+ return NS_OK;
+ }
+ RefPtr<EditorBase> editorBase(mEditorBase);
+ if (!editorBase->IsAcceptableInputEvent(aCompositionChangeEvent)) {
+ return NS_OK;
+ }
+
+ // if we are readonly, then do nothing.
+ if (editorBase->IsReadonly()) {
+ return NS_OK;
+ }
+
+ nsresult rv = editorBase->OnCompositionChange(*aCompositionChangeEvent);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "EditorBase::OnCompositionChange() failed");
+ return rv;
+}
+
+void EditorEventListener::HandleEndComposition(
+ WidgetCompositionEvent* aCompositionEndEvent) {
+ if (NS_WARN_IF(!aCompositionEndEvent) || DetachedFromEditor()) {
+ return;
+ }
+ RefPtr<EditorBase> editorBase(mEditorBase);
+ if (!editorBase->IsAcceptableInputEvent(aCompositionEndEvent)) {
+ return;
+ }
+ MOZ_ASSERT(!aCompositionEndEvent->DefaultPrevented(),
+ "eCompositionEnd shouldn't be cancelable");
+
+ editorBase->OnCompositionEnd(*aCompositionEndEvent);
+}
+
+nsresult EditorEventListener::Focus(const InternalFocusEvent& aFocusEvent) {
+ if (DetachedFromEditor()) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINode> originalEventTargetNode =
+ nsINode::FromEventTargetOrNull(aFocusEvent.GetOriginalDOMEventTarget());
+ if (NS_WARN_IF(!originalEventTargetNode)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // If the target is a document node but it's not editable, we should
+ // ignore it because actual focused element's event is going to come.
+ if (originalEventTargetNode->IsDocument()) {
+ if (!originalEventTargetNode->IsInDesignMode()) {
+ return NS_OK;
+ }
+ }
+ // We should not receive focus events whose target is not a content node
+ // unless the node is a document node.
+ else if (NS_WARN_IF(!originalEventTargetNode->IsContent())) {
+ return NS_OK;
+ }
+
+ const OwningNonNull<EditorBase> editorBase(*mEditorBase);
+ DebugOnly<nsresult> rvIgnored = editorBase->OnFocus(*originalEventTargetNode);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "EditorBase::OnFocus() failed");
+ return NS_OK; // Don't return error code to the event listener manager.
+}
+
+nsresult EditorEventListener::Blur(const InternalFocusEvent& aBlurEvent) {
+ if (DetachedFromEditor()) {
+ return NS_OK;
+ }
+
+ DebugOnly<nsresult> rvIgnored = mEditorBase->OnBlur(aBlurEvent.mTarget);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "EditorBase::OnBlur() failed");
+ return NS_OK; // Don't return error code to the event listener manager.
+}
+
+bool EditorEventListener::IsFileControlTextBox() {
+ MOZ_ASSERT(!DetachedFromEditor());
+
+ RefPtr<EditorBase> editorBase(mEditorBase);
+ Element* rootElement = editorBase->GetRoot();
+ if (!rootElement || !rootElement->ChromeOnlyAccess()) {
+ return false;
+ }
+ nsIContent* parent = rootElement->FindFirstNonChromeOnlyAccessContent();
+ if (!parent || !parent->IsHTMLElement(nsGkAtoms::input)) {
+ return false;
+ }
+ nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(parent);
+ return formControl->ControlType() == FormControlType::InputFile;
+}
+
+bool EditorEventListener::ShouldHandleNativeKeyBindings(
+ WidgetKeyboardEvent* aKeyboardEvent) {
+ MOZ_ASSERT(!DetachedFromEditor());
+
+ // Only return true if the target of the event is a desendant of the active
+ // editing host in order to match the similar decision made in
+ // nsXBLWindowKeyHandler.
+ // Note that IsAcceptableInputEvent doesn't check for the active editing
+ // host for keyboard events, otherwise this check would have been
+ // unnecessary. IsAcceptableInputEvent currently makes a similar check for
+ // mouse events.
+
+ nsCOMPtr<nsIContent> targetContent = nsIContent::FromEventTargetOrNull(
+ aKeyboardEvent->GetOriginalDOMEventTarget());
+ if (NS_WARN_IF(!targetContent)) {
+ return false;
+ }
+
+ RefPtr<HTMLEditor> htmlEditor = HTMLEditor::GetFrom(mEditorBase);
+ if (!htmlEditor) {
+ return false;
+ }
+
+ if (htmlEditor->IsInDesignMode()) {
+ // Don't need to perform any checks in designMode documents.
+ return true;
+ }
+
+ nsIContent* editingHost = htmlEditor->ComputeEditingHost();
+ if (!editingHost) {
+ return false;
+ }
+
+ return targetContent->IsInclusiveDescendantOf(editingHost);
+}
+
+} // namespace mozilla