1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=4 sw=2 et tw=78: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "EditorEventListener.h"
9 #include "EditorBase.h" // for EditorBase, etc.
10 #include "EditorUtils.h" // for EditorUtils
11 #include "HTMLEditor.h" // for HTMLEditor
12 #include "TextEditor.h" // for TextEditor
14 #include "mozilla/Assertions.h" // for MOZ_ASSERT, etc.
15 #include "mozilla/AutoRestore.h"
16 #include "mozilla/ContentEvents.h" // for InternalFocusEvent
17 #include "mozilla/EventListenerManager.h" // for EventListenerManager
18 #include "mozilla/EventStateManager.h" // for EventStateManager
19 #include "mozilla/IMEStateManager.h" // for IMEStateManager
20 #include "mozilla/LookAndFeel.h" // for LookAndFeel
21 #include "mozilla/NativeKeyBindingsType.h" // for NativeKeyBindingsType
22 #include "mozilla/Preferences.h" // for Preferences
23 #include "mozilla/PresShell.h" // for PresShell
24 #include "mozilla/TextEvents.h" // for WidgetCompositionEvent
25 #include "mozilla/dom/DataTransfer.h"
26 #include "mozilla/dom/Document.h" // for Document
27 #include "mozilla/dom/DOMStringList.h"
28 #include "mozilla/dom/DragEvent.h"
29 #include "mozilla/dom/Element.h" // for Element
30 #include "mozilla/dom/Event.h" // for Event
31 #include "mozilla/dom/EventTarget.h" // for EventTarget
32 #include "mozilla/dom/HTMLTextAreaElement.h"
33 #include "mozilla/dom/MouseEvent.h" // for MouseEvent
34 #include "mozilla/dom/Selection.h"
36 #include "nsAString.h"
37 #include "nsCaret.h" // for nsCaret
38 #include "nsDebug.h" // for NS_WARNING, etc.
39 #include "nsFocusManager.h" // for nsFocusManager
40 #include "nsGkAtoms.h" // for nsGkAtoms, nsGkAtoms::input
41 #include "nsIContent.h" // for nsIContent
42 #include "nsIContentInlines.h" // for nsINode::IsInDesignMode()
43 #include "nsIController.h" // for nsIController
45 #include "nsIFormControl.h" // for nsIFormControl, etc.
46 #include "nsINode.h" // for nsINode, etc.
47 #include "nsIWidget.h" // for nsIWidget
48 #include "nsLiteralString.h" // for NS_LITERAL_STRING
49 #include "nsPIWindowRoot.h" // for nsPIWindowRoot
50 #include "nsPrintfCString.h" // for nsPrintfCString
52 #include "nsServiceManagerUtils.h" // for do_GetService
53 #include "nsString.h" // for nsAutoString
54 #include "nsQueryObject.h" // for do_QueryObject
55 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
56 # include "nsContentUtils.h" // for nsContentUtils, etc.
57 # include "nsIBidiKeyboard.h" // for nsIBidiKeyboard
60 #include "mozilla/dom/BrowserParent.h"
68 MOZ_CAN_RUN_SCRIPT
static void DoCommandCallback(Command aCommand
,
70 Document
* doc
= static_cast<Document
*>(aData
);
71 nsPIDOMWindowOuter
* win
= doc
->GetWindow();
75 nsCOMPtr
<nsPIWindowRoot
> root
= win
->GetTopWindowRoot();
80 const char* commandStr
= WidgetKeyboardEvent::GetCommandStr(aCommand
);
82 nsCOMPtr
<nsIController
> controller
;
83 root
->GetControllerForCommand(commandStr
, false /* for any window */,
84 getter_AddRefs(controller
));
90 if (NS_WARN_IF(NS_FAILED(
91 controller
->IsCommandEnabled(commandStr
, &commandEnabled
)))) {
95 controller
->DoCommand(commandStr
);
99 EditorEventListener::EditorEventListener()
100 : mEditorBase(nullptr),
102 mInTransaction(false),
103 mMouseDownOrUpConsumedByIME(false)
104 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
106 mHaveBidiKeyboards(false),
107 mShouldSwitchTextDirection(false),
113 EditorEventListener::~EditorEventListener() {
115 NS_WARNING("We've not been uninstalled yet");
120 nsresult
EditorEventListener::Connect(EditorBase
* aEditorBase
) {
121 if (NS_WARN_IF(!aEditorBase
)) {
122 return NS_ERROR_INVALID_ARG
;
125 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
126 nsIBidiKeyboard
* bidiKeyboard
= nsContentUtils::GetBidiKeyboard();
128 bool haveBidiKeyboards
= false;
129 bidiKeyboard
->GetHaveBidiKeyboards(&haveBidiKeyboards
);
130 mHaveBidiKeyboards
= haveBidiKeyboards
;
134 mEditorBase
= aEditorBase
;
136 nsresult rv
= InstallToEditor();
138 NS_WARNING("EditorEventListener::InstallToEditor() failed");
144 nsresult
EditorEventListener::InstallToEditor() {
145 MOZ_ASSERT(mEditorBase
, "The caller must set mEditorBase");
147 EventTarget
* eventTarget
= mEditorBase
->GetDOMEventTarget();
148 if (NS_WARN_IF(!eventTarget
)) {
149 return NS_ERROR_FAILURE
;
152 // register the event listeners with the listener manager
153 EventListenerManager
* eventListenerManager
=
154 eventTarget
->GetOrCreateListenerManager();
155 if (NS_WARN_IF(!eventListenerManager
)) {
156 return NS_ERROR_FAILURE
;
159 // For non-html editor, ie.TextEditor, we want to preserve
160 // the event handling order to ensure listeners that are
161 // added to <input> and <texarea> still working as expected.
162 EventListenerFlags flags
= mEditorBase
->IsHTMLEditor()
163 ? TrustedEventsAtSystemGroupCapture()
164 : TrustedEventsAtSystemGroupBubble();
165 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
166 eventListenerManager
->AddEventListenerByType(this, u
"keydown"_ns
, flags
);
167 eventListenerManager
->AddEventListenerByType(this, u
"keyup"_ns
, flags
);
170 eventListenerManager
->AddEventListenerByType(this, u
"keypress"_ns
, flags
);
171 eventListenerManager
->AddEventListenerByType(this, u
"dragover"_ns
, flags
);
172 eventListenerManager
->AddEventListenerByType(this, u
"dragleave"_ns
, flags
);
173 eventListenerManager
->AddEventListenerByType(this, u
"drop"_ns
, flags
);
174 // XXX We should add the mouse event listeners as system event group.
175 // E.g., web applications cannot prevent middle mouse paste by
176 // preventDefault() of click event at bubble phase.
177 // However, if we do so, all click handlers in any frames and frontend
178 // code need to check if it's editable. It makes easier create new bugs.
179 eventListenerManager
->AddEventListenerByType(this, u
"mousedown"_ns
,
180 TrustedEventsAtCapture());
181 eventListenerManager
->AddEventListenerByType(this, u
"mouseup"_ns
,
182 TrustedEventsAtCapture());
183 eventListenerManager
->AddEventListenerByType(this, u
"click"_ns
,
184 TrustedEventsAtCapture());
185 eventListenerManager
->AddEventListenerByType(
186 this, u
"auxclick"_ns
, TrustedEventsAtSystemGroupCapture());
187 // Focus event doesn't bubble so adding the listener to capturing phase as
188 // system event group.
189 eventListenerManager
->AddEventListenerByType(
190 this, u
"blur"_ns
, TrustedEventsAtSystemGroupCapture());
191 eventListenerManager
->AddEventListenerByType(
192 this, u
"focus"_ns
, TrustedEventsAtSystemGroupCapture());
193 eventListenerManager
->AddEventListenerByType(
194 this, u
"text"_ns
, TrustedEventsAtSystemGroupBubble());
195 eventListenerManager
->AddEventListenerByType(
196 this, u
"compositionstart"_ns
, TrustedEventsAtSystemGroupBubble());
197 eventListenerManager
->AddEventListenerByType(
198 this, u
"compositionend"_ns
, TrustedEventsAtSystemGroupBubble());
203 void EditorEventListener::Disconnect() {
204 if (DetachedFromEditor()) {
207 UninstallFromEditor();
209 const OwningNonNull
<EditorBase
> editorBase
= *mEditorBase
;
210 mEditorBase
= nullptr;
212 nsFocusManager
* fm
= nsFocusManager::GetFocusManager();
214 nsIContent
* focusedContent
= fm
->GetFocusedElement();
215 mozilla::dom::Element
* root
= editorBase
->GetRoot();
216 if (focusedContent
&& root
&&
217 focusedContent
->IsInclusiveDescendantOf(root
)) {
218 // Reset the Selection ancestor limiter and SelectionController state
219 // that EditorBase::InitializeSelection set up.
220 DebugOnly
<nsresult
> rvIgnored
= editorBase
->FinalizeSelection();
221 NS_WARNING_ASSERTION(
222 NS_SUCCEEDED(rvIgnored
),
223 "EditorBase::FinalizeSelection() failed, but ignored");
228 void EditorEventListener::UninstallFromEditor() {
229 CleanupDragDropCaret();
231 EventTarget
* eventTarget
= mEditorBase
->GetDOMEventTarget();
232 if (NS_WARN_IF(!eventTarget
)) {
236 EventListenerManager
* eventListenerManager
=
237 eventTarget
->GetOrCreateListenerManager();
238 if (NS_WARN_IF(!eventListenerManager
)) {
242 EventListenerFlags flags
= mEditorBase
->IsHTMLEditor()
243 ? TrustedEventsAtSystemGroupCapture()
244 : TrustedEventsAtSystemGroupBubble();
245 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
246 eventListenerManager
->RemoveEventListenerByType(this, u
"keydown"_ns
, flags
);
247 eventListenerManager
->RemoveEventListenerByType(this, u
"keyup"_ns
, flags
);
249 eventListenerManager
->RemoveEventListenerByType(this, u
"keypress"_ns
, flags
);
250 eventListenerManager
->RemoveEventListenerByType(this, u
"dragover"_ns
, flags
);
251 eventListenerManager
->RemoveEventListenerByType(this, u
"dragleave"_ns
, flags
);
252 eventListenerManager
->RemoveEventListenerByType(this, u
"drop"_ns
, flags
);
253 eventListenerManager
->RemoveEventListenerByType(this, u
"mousedown"_ns
,
254 TrustedEventsAtCapture());
255 eventListenerManager
->RemoveEventListenerByType(this, u
"mouseup"_ns
,
256 TrustedEventsAtCapture());
257 eventListenerManager
->RemoveEventListenerByType(this, u
"click"_ns
,
258 TrustedEventsAtCapture());
259 eventListenerManager
->RemoveEventListenerByType(
260 this, u
"auxclick"_ns
, TrustedEventsAtSystemGroupCapture());
261 eventListenerManager
->RemoveEventListenerByType(
262 this, u
"blur"_ns
, TrustedEventsAtSystemGroupCapture());
263 eventListenerManager
->RemoveEventListenerByType(
264 this, u
"focus"_ns
, TrustedEventsAtSystemGroupCapture());
265 eventListenerManager
->RemoveEventListenerByType(
266 this, u
"text"_ns
, TrustedEventsAtSystemGroupBubble());
267 eventListenerManager
->RemoveEventListenerByType(
268 this, u
"compositionstart"_ns
, TrustedEventsAtSystemGroupBubble());
269 eventListenerManager
->RemoveEventListenerByType(
270 this, u
"compositionend"_ns
, TrustedEventsAtSystemGroupBubble());
273 PresShell
* EditorEventListener::GetPresShell() const {
274 MOZ_ASSERT(!DetachedFromEditor());
275 return mEditorBase
->GetPresShell();
278 nsPresContext
* EditorEventListener::GetPresContext() const {
279 PresShell
* presShell
= GetPresShell();
280 return presShell
? presShell
->GetPresContext() : nullptr;
283 bool EditorEventListener::EditorHasFocus() {
284 MOZ_ASSERT(!DetachedFromEditor());
285 const Element
* focusedElement
= mEditorBase
->GetFocusedElement();
286 return focusedElement
&& focusedElement
->IsInComposedDoc();
289 NS_IMPL_ISUPPORTS(EditorEventListener
, nsIDOMEventListener
)
291 bool EditorEventListener::DetachedFromEditor() const { return !mEditorBase
; }
293 bool EditorEventListener::DetachedFromEditorOrDefaultPrevented(
294 WidgetEvent
* aWidgetEvent
) const {
295 return NS_WARN_IF(!aWidgetEvent
) || DetachedFromEditor() ||
296 aWidgetEvent
->DefaultPrevented();
299 bool EditorEventListener::EnsureCommitComposition() {
300 MOZ_ASSERT(!DetachedFromEditor());
301 RefPtr
<EditorBase
> editorBase(mEditorBase
);
302 editorBase
->CommitComposition();
303 return !DetachedFromEditor();
306 NS_IMETHODIMP
EditorEventListener::HandleEvent(Event
* aEvent
) {
307 // Let's handle each event with the message of the internal event of the
308 // coming event. If the DOM event was created with improper interface,
309 // e.g., keydown event is created with |new MouseEvent("keydown", {});|,
310 // its message is always 0. Therefore, we can ban such strange event easy.
311 // However, we need to handle strange "focus" and "blur" event. See the
312 // following code of this switch statement.
313 // NOTE: Each event handler may require specific event interface. Before
314 // calling it, this queries the specific interface. If it would fail,
315 // each event handler would just ignore the event. So, in this method,
316 // you don't need to check if the QI succeeded before each call.
317 WidgetEvent
* internalEvent
= aEvent
->WidgetEventPtr();
319 if (DetachedFromEditor()) {
323 // For nested documents with multiple HTMLEditor registered on different
324 // nsWindowRoot, make sure the HTMLEditor for the original event target
325 // handles the events.
326 if (mEditorBase
->IsHTMLEditor()) {
327 nsCOMPtr
<nsINode
> originalEventTargetNode
=
328 nsINode::FromEventTargetOrNull(aEvent
->GetOriginalTarget());
330 if (originalEventTargetNode
&&
331 mEditorBase
!= originalEventTargetNode
->OwnerDoc()->GetHTMLEditor()) {
334 if (!originalEventTargetNode
&& internalEvent
->mMessage
== eFocus
&&
335 aEvent
->GetCurrentTarget()->IsRootWindow()) {
340 switch (internalEvent
->mMessage
) {
344 // The editor which is registered on nsWindowRoot shouldn't handle
345 // drop events when it can be handled by Input or TextArea element on
347 if (aEvent
->GetCurrentTarget()->IsRootWindow() &&
348 TextControlElement::FromEventTargetOrNull(
349 internalEvent
->GetDOMEventTarget())) {
352 // aEvent should be grabbed by the caller since this is
353 // nsIDOMEventListener method. However, our clang plugin cannot check it
354 // if we use Event::As*Event(). So, we need to grab it by ourselves.
355 RefPtr
<DragEvent
> dragEvent
= aEvent
->AsDragEvent();
356 nsresult rv
= DragOverOrDrop(dragEvent
);
357 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
358 "EditorEventListener::DragOverOrDrop() failed");
363 RefPtr
<DragEvent
> dragEvent
= aEvent
->AsDragEvent();
364 nsresult rv
= DragLeave(dragEvent
);
365 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
366 "EditorEventListener::DragLeave() failed");
369 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
372 nsresult rv
= KeyDown(internalEvent
->AsKeyboardEvent());
373 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
374 "EditorEventListener::KeyDown() failed");
379 nsresult rv
= KeyUp(internalEvent
->AsKeyboardEvent());
380 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
381 "EditorEventListener::KeyUp() failed");
384 #endif // #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
387 nsresult rv
= KeyPress(internalEvent
->AsKeyboardEvent());
388 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
389 "EditorEventListener::KeyPress() failed");
394 // EditorEventListener may receive (1) all mousedown, mouseup and click
395 // events, (2) only mousedown event or (3) only mouseup event.
396 // mMouseDownOrUpConsumedByIME is used only for ignoring click event if
397 // preceding mousedown and/or mouseup event is consumed by IME.
398 // Therefore, even if case #2 or case #3 occurs,
399 // mMouseDownOrUpConsumedByIME is true here. Therefore, we should always
400 // overwrite it here.
401 mMouseDownOrUpConsumedByIME
=
402 NotifyIMEOfMouseButtonEvent(internalEvent
->AsMouseEvent());
403 if (mMouseDownOrUpConsumedByIME
) {
406 RefPtr
<MouseEvent
> mouseEvent
= aEvent
->AsMouseEvent();
407 if (NS_WARN_IF(!mouseEvent
)) {
410 nsresult rv
= MouseDown(mouseEvent
);
411 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
412 "EditorEventListener::MouseDown() failed");
417 // See above comment in the eMouseDown case, first.
418 // This code assumes that case #1 is occuring. However, if case #3 may
419 // occurs after case #2 and the mousedown is consumed,
420 // mMouseDownOrUpConsumedByIME is true even though EditorEventListener
421 // has not received the preceding mousedown event of this mouseup event.
422 // So, mMouseDownOrUpConsumedByIME may be invalid here. However,
423 // this is not a matter because mMouseDownOrUpConsumedByIME is referred
424 // only by eMouseClick case but click event is fired only in case #1.
425 // So, before a click event is fired, mMouseDownOrUpConsumedByIME is
426 // always initialized in the eMouseDown case if it's referred.
427 if (NotifyIMEOfMouseButtonEvent(internalEvent
->AsMouseEvent())) {
428 mMouseDownOrUpConsumedByIME
= true;
430 if (mMouseDownOrUpConsumedByIME
) {
433 RefPtr
<MouseEvent
> mouseEvent
= aEvent
->AsMouseEvent();
434 if (NS_WARN_IF(!mouseEvent
)) {
437 nsresult rv
= MouseUp(mouseEvent
);
438 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
439 "EditorEventListener::MouseUp() failed");
444 WidgetMouseEvent
* widgetMouseEvent
= internalEvent
->AsMouseEvent();
445 // Don't handle non-primary click events
446 if (widgetMouseEvent
->mButton
!= MouseButton::ePrimary
) {
452 case eMouseAuxClick
: {
453 WidgetMouseEvent
* widgetMouseEvent
= internalEvent
->AsMouseEvent();
454 if (NS_WARN_IF(!widgetMouseEvent
)) {
457 // If the preceding mousedown event or mouseup event was consumed,
458 // editor shouldn't handle this click event.
459 if (mMouseDownOrUpConsumedByIME
) {
460 mMouseDownOrUpConsumedByIME
= false;
461 widgetMouseEvent
->PreventDefault();
464 nsresult rv
= MouseClick(widgetMouseEvent
);
465 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
466 "EditorEventListener::MouseClick() failed");
471 const InternalFocusEvent
* focusEvent
= internalEvent
->AsFocusEvent();
472 if (NS_WARN_IF(!focusEvent
)) {
473 return NS_ERROR_FAILURE
;
475 nsresult rv
= Focus(*focusEvent
);
476 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
477 "EditorEventListener::Focus() failed");
482 const InternalFocusEvent
* blurEvent
= internalEvent
->AsFocusEvent();
483 if (NS_WARN_IF(!blurEvent
)) {
484 return NS_ERROR_FAILURE
;
486 nsresult rv
= Blur(*blurEvent
);
487 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
488 "EditorEventListener::Blur() failed");
492 case eCompositionChange
: {
494 HandleChangeComposition(internalEvent
->AsCompositionEvent());
495 NS_WARNING_ASSERTION(
497 "EditorEventListener::HandleChangeComposition() failed");
501 case eCompositionStart
: {
502 nsresult rv
= HandleStartComposition(internalEvent
->AsCompositionEvent());
503 NS_WARNING_ASSERTION(
505 "EditorEventListener::HandleStartComposition() failed");
509 case eCompositionEnd
: {
510 HandleEndComposition(internalEvent
->AsCompositionEvent());
518 nsAutoString eventType
;
519 aEvent
->GetType(eventType
);
520 nsPrintfCString
assertMessage(
521 "Editor doesn't handle \"%s\" event "
522 "because its internal event doesn't have proper message",
523 NS_ConvertUTF16toUTF8(eventType
).get());
524 NS_ASSERTION(false, assertMessage
.get());
530 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
532 // This function is borrowed from Chromium's ImeInput::IsCtrlShiftPressed
533 bool IsCtrlShiftPressed(const WidgetKeyboardEvent
* aKeyboardEvent
,
535 MOZ_ASSERT(aKeyboardEvent
);
536 // To check if a user is pressing only a control key and a right-shift key
537 // (or a left-shift key), we use the steps below:
538 // 1. Check if a user is pressing a control key and a right-shift key (or
539 // a left-shift key).
540 // 2. If the condition 1 is true, we should check if there are any other
541 // keys pressed at the same time.
542 // To ignore the keys checked in 1, we set their status to 0 before
543 // checking the key status.
545 if (!aKeyboardEvent
->IsControl()) {
549 switch (aKeyboardEvent
->mLocation
) {
550 case eKeyLocationRight
:
553 case eKeyLocationLeft
:
560 // Scan the key status to find pressed keys. We should abandon changing the
561 // text direction when there are other pressed keys.
562 return !aKeyboardEvent
->IsAlt() && !aKeyboardEvent
->IsMeta();
565 // This logic is mostly borrowed from Chromium's
566 // RenderWidgetHostViewWin::OnKeyEvent.
568 nsresult
EditorEventListener::KeyUp(const WidgetKeyboardEvent
* aKeyboardEvent
) {
569 if (NS_WARN_IF(!aKeyboardEvent
) || DetachedFromEditor()) {
573 if (!mHaveBidiKeyboards
) {
577 // XXX Why doesn't this method check if it's consumed?
578 RefPtr
<EditorBase
> editorBase(mEditorBase
);
579 if ((aKeyboardEvent
->mKeyCode
== NS_VK_SHIFT
||
580 aKeyboardEvent
->mKeyCode
== NS_VK_CONTROL
) &&
581 mShouldSwitchTextDirection
&&
582 (editorBase
->IsTextEditor() ||
583 editorBase
->AsHTMLEditor()->IsPlaintextMailComposer())) {
584 editorBase
->SwitchTextDirectionTo(mSwitchToRTL
585 ? EditorBase::TextDirection::eRTL
586 : EditorBase::TextDirection::eLTR
);
587 mShouldSwitchTextDirection
= false;
592 nsresult
EditorEventListener::KeyDown(
593 const WidgetKeyboardEvent
* aKeyboardEvent
) {
594 if (NS_WARN_IF(!aKeyboardEvent
) || DetachedFromEditor()) {
598 if (!mHaveBidiKeyboards
) {
602 // XXX Why isn't this method check if it's consumed?
603 if (aKeyboardEvent
->mKeyCode
== NS_VK_SHIFT
) {
605 if (IsCtrlShiftPressed(aKeyboardEvent
, switchToRTL
)) {
606 mShouldSwitchTextDirection
= true;
607 mSwitchToRTL
= switchToRTL
;
609 } else if (aKeyboardEvent
->mKeyCode
!= NS_VK_CONTROL
) {
610 // In case the user presses any other key besides Ctrl and Shift
611 mShouldSwitchTextDirection
= false;
616 #endif // #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
618 nsresult
EditorEventListener::KeyPress(WidgetKeyboardEvent
* aKeyboardEvent
) {
619 if (NS_WARN_IF(!aKeyboardEvent
)) {
623 RefPtr
<EditorBase
> editorBase(mEditorBase
);
624 if (!editorBase
->IsAcceptableInputEvent(aKeyboardEvent
) ||
625 DetachedFromEditorOrDefaultPrevented(aKeyboardEvent
)) {
629 // The exposed root of our editor may have been hidden or destroyed by a
630 // preceding event listener. However, the destruction has not occurred yet if
631 // pending notifications have not been flushed yet. Therefore, before
632 // handling user input, we need to get the latest state and if it's now
633 // destroyed with the flushing, we should just ignore this event instead of
634 // returning error since this is just a event listener.
635 RefPtr
<Document
> document
= editorBase
->GetDocument();
639 document
->FlushPendingNotifications(FlushType::Layout
);
640 if (editorBase
->Destroyed() || DetachedFromEditor()) {
644 nsresult rv
= editorBase
->HandleKeyPressEvent(aKeyboardEvent
);
646 NS_WARNING("EditorBase::HandleKeyPressEvent() failed");
650 auto GetWidget
= [&]() -> nsIWidget
* {
651 if (aKeyboardEvent
->mWidget
) {
652 return aKeyboardEvent
->mWidget
;
654 // If the event is created by chrome script, the widget is always nullptr.
655 return IMEStateManager::GetWidgetForTextInputHandling();
658 if (DetachedFromEditor()) {
662 if (LookAndFeel::GetInt(LookAndFeel::IntID::HideCursorWhileTyping
)) {
663 if (nsPresContext
* pc
= GetPresContext()) {
664 if (nsIWidget
* widget
= GetWidget()) {
665 pc
->EventStateManager()->StartHidingCursorWhileTyping(widget
);
670 if (aKeyboardEvent
->DefaultPrevented()) {
674 if (!ShouldHandleNativeKeyBindings(aKeyboardEvent
)) {
678 // Now, ask the native key bindings to handle the event.
680 nsIWidget
* widget
= GetWidget();
681 if (NS_WARN_IF(!widget
)) {
685 RefPtr
<Document
> doc
= editorBase
->GetDocument();
687 // WidgetKeyboardEvent::ExecuteEditCommands() requires non-nullptr mWidget.
688 // If the event is created by chrome script, it is nullptr but we need to
689 // execute native key bindings. Therefore, we need to set widget to
690 // WidgetEvent::mWidget temporarily.
691 AutoRestore
<nsCOMPtr
<nsIWidget
>> saveWidget(aKeyboardEvent
->mWidget
);
692 aKeyboardEvent
->mWidget
= widget
;
693 if (aKeyboardEvent
->ExecuteEditCommands(NativeKeyBindingsType::RichTextEditor
,
694 DoCommandCallback
, doc
)) {
695 aKeyboardEvent
->PreventDefault();
700 nsresult
EditorEventListener::MouseClick(WidgetMouseEvent
* aMouseClickEvent
) {
701 if (NS_WARN_IF(!aMouseClickEvent
) || DetachedFromEditor()) {
704 // nothing to do if editor isn't editable or clicked on out of the editor.
705 OwningNonNull
<EditorBase
> editorBase
= *mEditorBase
;
706 if (editorBase
->IsReadonly() ||
707 !editorBase
->IsAcceptableInputEvent(aMouseClickEvent
)) {
711 // Notifies clicking on editor to IMEStateManager even when the event was
713 if (EditorHasFocus()) {
714 if (RefPtr
<nsPresContext
> presContext
= GetPresContext()) {
715 RefPtr
<Element
> focusedElement
= mEditorBase
->GetFocusedElement();
716 IMEStateManager::OnClickInEditor(*presContext
, focusedElement
,
718 if (DetachedFromEditor()) {
724 if (DetachedFromEditorOrDefaultPrevented(aMouseClickEvent
)) {
725 // We're done if 'preventdefault' is true (see for example bug 70698).
729 // If we got a mouse down inside the editing area, we should force the
730 // IME to commit before we change the cursor position.
731 if (!EnsureCommitComposition()) {
735 // XXX The following code is hack for our buggy "click" and "auxclick"
736 // implementation. "auxclick" event was added recently, however,
737 // any non-primary button click event handlers in our UI still keep
738 // listening to "click" events. Additionally, "auxclick" event is
739 // fired after "click" events and even if we do this in the system event
740 // group, middle click opens new tab before us. Therefore, we need to
741 // handle middle click at capturing phase of the default group even
742 // though this makes web apps cannot prevent middle click paste with
743 // calling preventDefault() of "click" nor "auxclick".
745 if (aMouseClickEvent
->mButton
!= MouseButton::eMiddle
||
746 !WidgetMouseEvent::IsMiddleClickPasteEnabled()) {
750 RefPtr
<PresShell
> presShell
= GetPresShell();
751 if (NS_WARN_IF(!presShell
)) {
754 nsPresContext
* presContext
= GetPresContext();
755 if (NS_WARN_IF(!presContext
)) {
758 MOZ_ASSERT(!aMouseClickEvent
->DefaultPrevented());
759 nsEventStatus status
= nsEventStatus_eIgnore
;
760 RefPtr
<EventStateManager
> esm
= presContext
->EventStateManager();
761 DebugOnly
<nsresult
> rvIgnored
= esm
->HandleMiddleClickPaste(
762 presShell
, aMouseClickEvent
, &status
, editorBase
);
763 NS_WARNING_ASSERTION(
764 NS_SUCCEEDED(rvIgnored
),
765 "EventStateManager::HandleMiddleClickPaste() failed, but ignored");
766 if (status
== nsEventStatus_eConsumeNoDefault
) {
767 // We no longer need to StopImmediatePropagation here since
768 // ClickHandlerChild.sys.mjs checks for and ignores editables, so won't
769 // re-handle the event
770 aMouseClickEvent
->PreventDefault();
775 bool EditorEventListener::NotifyIMEOfMouseButtonEvent(
776 WidgetMouseEvent
* aMouseEvent
) {
777 MOZ_ASSERT(aMouseEvent
);
779 if (!EditorHasFocus()) {
783 RefPtr
<nsPresContext
> presContext
= GetPresContext();
784 if (NS_WARN_IF(!presContext
)) {
787 RefPtr
<Element
> focusedElement
= mEditorBase
->GetFocusedElement();
788 return IMEStateManager::OnMouseButtonEventInEditor(
789 *presContext
, focusedElement
, *aMouseEvent
);
792 nsresult
EditorEventListener::MouseDown(MouseEvent
* aMouseEvent
) {
793 // FYI: We don't need to check if it's already consumed here because
794 // we need to commit composition at mouse button operation.
795 // FYI: This may be called by HTMLEditorEventListener::MouseDown() even
796 // when the event is not acceptable for committing composition.
797 if (DetachedFromEditor()) {
800 Unused
<< EnsureCommitComposition();
805 * Drag event implementation
808 void EditorEventListener::RefuseToDropAndHideCaret(DragEvent
* aDragEvent
) {
809 MOZ_ASSERT(aDragEvent
->WidgetEventPtr()->mFlags
.mInSystemGroup
);
811 aDragEvent
->PreventDefault();
812 aDragEvent
->StopImmediatePropagation();
813 DataTransfer
* dataTransfer
= aDragEvent
->GetDataTransfer();
815 dataTransfer
->SetDropEffectInt(nsIDragService::DRAGDROP_ACTION_NONE
);
818 mCaret
->SetVisible(false);
822 nsresult
EditorEventListener::DragOverOrDrop(DragEvent
* aDragEvent
) {
823 MOZ_ASSERT(aDragEvent
);
824 MOZ_ASSERT(aDragEvent
->WidgetEventPtr()->mMessage
== eDrop
||
825 aDragEvent
->WidgetEventPtr()->mMessage
== eDragOver
);
827 if (aDragEvent
->WidgetEventPtr()->mMessage
== eDrop
) {
828 CleanupDragDropCaret();
831 InitializeDragDropCaret();
835 if (DetachedFromEditorOrDefaultPrevented(aDragEvent
->WidgetEventPtr())) {
839 int32_t dropOffset
= -1;
840 nsCOMPtr
<nsIContent
> dropParentContent
=
841 aDragEvent
->GetRangeParentContentAndOffset(&dropOffset
);
842 if (NS_WARN_IF(!dropParentContent
) || NS_WARN_IF(dropOffset
< 0)) {
843 return NS_ERROR_FAILURE
;
845 if (DetachedFromEditor()) {
846 RefuseToDropAndHideCaret(aDragEvent
);
851 !dropParentContent
->IsEditable() || mEditorBase
->IsReadonly();
853 // First of all, hide caret if we won't insert the drop data into the editor
855 if (mCaret
&& (IsFileControlTextBox() || notEditable
)) {
856 mCaret
->SetVisible(false);
859 // If we're a native anonymous <input> element in <input type="file">,
860 // we don't need to handle the drop.
861 if (IsFileControlTextBox()) {
865 // If the drop target isn't ediable, the drop should be handled by the
868 // If we're a text control element which is readonly or disabled,
869 // we should refuse to drop.
870 if (mEditorBase
->IsTextEditor()) {
871 RefuseToDropAndHideCaret(aDragEvent
);
874 // Otherwise, we shouldn't handle the drop.
878 // If the drag event does not have any data which we can handle, we should
879 // refuse to drop even if some parents can handle it because user may be
880 // trying to drop it on us, not our parent. For example, users don't want
881 // to drop HTML data to parent contenteditable element if they drop it on
882 // a child <input> element.
883 if (!DragEventHasSupportingData(aDragEvent
)) {
884 RefuseToDropAndHideCaret(aDragEvent
);
888 // If we don't accept the data drop at the point, for example, while dragging
889 // selection, it's not allowed dropping on its selection ranges. In this
890 // case, any parents shouldn't handle the drop instead of us, for example,
891 // dropping text shouldn't be treated as URL and load new page.
892 if (!CanInsertAtDropPosition(aDragEvent
)) {
893 RefuseToDropAndHideCaret(aDragEvent
);
897 WidgetDragEvent
* asWidgetEvent
= aDragEvent
->WidgetEventPtr()->AsDragEvent();
898 AutoRestore
<bool> inHTMLEditorEventListener(
899 asWidgetEvent
->mInHTMLEditorEventListener
);
900 if (mEditorBase
->IsHTMLEditor()) {
901 asWidgetEvent
->mInHTMLEditorEventListener
= true;
903 aDragEvent
->PreventDefault();
905 aDragEvent
->StopImmediatePropagation();
907 if (asWidgetEvent
->mMessage
== eDrop
) {
908 RefPtr
<EditorBase
> editorBase
= mEditorBase
;
909 nsresult rv
= editorBase
->HandleDropEvent(aDragEvent
);
910 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
911 "EditorBase::HandleDropEvent() failed");
915 MOZ_ASSERT(asWidgetEvent
->mMessage
== eDragOver
);
917 // If we handle the dragged item, we need to adjust drop effect here
918 // because once DataTransfer is retrieved, DragEvent has initialized it
919 // with nsContentUtils::SetDataTransferInEvent() but it does not check
920 // whether the content is movable or not.
921 DataTransfer
* dataTransfer
= aDragEvent
->GetDataTransfer();
923 dataTransfer
->DropEffectInt() == nsIDragService::DRAGDROP_ACTION_MOVE
) {
924 nsCOMPtr
<nsINode
> dragSource
= dataTransfer
->GetMozSourceNode();
925 if (dragSource
&& !dragSource
->IsEditable()) {
926 // In this case, we shouldn't allow "move" because the drag source
928 dataTransfer
->SetDropEffectInt(
929 nsContentUtils::FilterDropEffect(nsIDragService::DRAGDROP_ACTION_COPY
,
930 dataTransfer
->EffectAllowedInt()));
938 mCaret
->SetVisible(true);
939 mCaret
->SetCaretPosition(dropParentContent
, dropOffset
);
944 void EditorEventListener::InitializeDragDropCaret() {
949 RefPtr
<PresShell
> presShell
= GetPresShell();
950 if (NS_WARN_IF(!presShell
)) {
954 mCaret
= new nsCaret();
955 DebugOnly
<nsresult
> rvIgnored
= mCaret
->Init(presShell
);
956 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
957 "nsCaret::Init() failed, but ignored");
958 mCaret
->SetCaretReadOnly(true);
959 // This is to avoid the requirement that the Selection is Collapsed which
960 // it can't be when dragging a selection in the same shell.
961 // See nsCaret::IsVisible().
962 mCaret
->SetVisibilityDuringSelection(true);
964 presShell
->SetCaret(mCaret
);
967 void EditorEventListener::CleanupDragDropCaret() {
972 mCaret
->SetVisible(false); // hide it, so that it turns off its timer
974 RefPtr
<PresShell
> presShell
= GetPresShell();
976 presShell
->RestoreCaret();
983 nsresult
EditorEventListener::DragLeave(DragEvent
* aDragEvent
) {
984 // XXX If aDragEvent was created by chrome script, its defaultPrevented
985 // may be true, though. We shouldn't handle such event but we don't
986 // have a way to distinguish if coming event is created by chrome script.
987 NS_WARNING_ASSERTION(!aDragEvent
->WidgetEventPtr()->DefaultPrevented(),
988 "eDragLeave shouldn't be cancelable");
989 if (NS_WARN_IF(!aDragEvent
) || DetachedFromEditor()) {
993 CleanupDragDropCaret();
998 bool EditorEventListener::DragEventHasSupportingData(
999 DragEvent
* aDragEvent
) const {
1001 !DetachedFromEditorOrDefaultPrevented(aDragEvent
->WidgetEventPtr()));
1002 MOZ_ASSERT(aDragEvent
->GetDataTransfer());
1004 // Plaintext editors only support dropping text. Otherwise, HTML and files
1005 // can be dropped as well.
1006 DataTransfer
* dataTransfer
= aDragEvent
->GetDataTransfer();
1007 if (!dataTransfer
) {
1008 NS_WARNING("No data transfer returned");
1011 return dataTransfer
->HasType(NS_LITERAL_STRING_FROM_CSTRING(kTextMime
)) ||
1012 dataTransfer
->HasType(
1013 NS_LITERAL_STRING_FROM_CSTRING(kMozTextInternal
)) ||
1014 (mEditorBase
->IsHTMLEditor() &&
1015 !mEditorBase
->AsHTMLEditor()->IsPlaintextMailComposer() &&
1016 (dataTransfer
->HasType(NS_LITERAL_STRING_FROM_CSTRING(kHTMLMime
)) ||
1017 dataTransfer
->HasType(NS_LITERAL_STRING_FROM_CSTRING(kFileMime
))));
1020 bool EditorEventListener::CanInsertAtDropPosition(DragEvent
* aDragEvent
) {
1022 !DetachedFromEditorOrDefaultPrevented(aDragEvent
->WidgetEventPtr()));
1023 MOZ_ASSERT(!mEditorBase
->IsReadonly());
1024 MOZ_ASSERT(DragEventHasSupportingData(aDragEvent
));
1026 DataTransfer
* dataTransfer
= aDragEvent
->GetDataTransfer();
1027 if (NS_WARN_IF(!dataTransfer
)) {
1031 // If there is no source node, this is probably an external drag and the
1032 // drop is allowed. The later checks rely on checking if the drag target
1033 // is the same as the drag source.
1034 nsCOMPtr
<nsINode
> sourceNode
= dataTransfer
->GetMozSourceNode();
1039 // There is a source node, so compare the source documents and this document.
1040 // Disallow drops on the same document.
1042 RefPtr
<Document
> targetDocument
= mEditorBase
->GetDocument();
1043 if (NS_WARN_IF(!targetDocument
)) {
1047 RefPtr
<Document
> sourceDocument
= sourceNode
->OwnerDoc();
1049 // If the source and the dest are not same document, allow to drop it always.
1050 if (targetDocument
!= sourceDocument
) {
1054 // If the source node is a remote browser, treat this as coming from a
1055 // different document and allow the drop.
1056 if (BrowserParent::GetFrom(nsIContent::FromNode(sourceNode
))) {
1060 RefPtr
<Selection
> selection
= mEditorBase
->GetSelection();
1065 // If selection is collapsed, allow to drop it always.
1066 if (selection
->IsCollapsed()) {
1070 int32_t dropOffset
= -1;
1071 nsCOMPtr
<nsIContent
> dropParentContent
=
1072 aDragEvent
->GetRangeParentContentAndOffset(&dropOffset
);
1073 if (!dropParentContent
|| NS_WARN_IF(dropOffset
< 0) ||
1074 NS_WARN_IF(DetachedFromEditor())) {
1078 return !nsContentUtils::IsPointInSelection(*selection
, *dropParentContent
,
1082 nsresult
EditorEventListener::HandleStartComposition(
1083 WidgetCompositionEvent
* aCompositionStartEvent
) {
1084 if (NS_WARN_IF(!aCompositionStartEvent
)) {
1085 return NS_ERROR_FAILURE
;
1087 if (DetachedFromEditor()) {
1090 RefPtr
<EditorBase
> editorBase(mEditorBase
);
1091 if (!editorBase
->IsAcceptableInputEvent(aCompositionStartEvent
)) {
1094 // Although, "compositionstart" should be cancelable, but currently,
1095 // eCompositionStart event coming from widget is not cancelable.
1096 MOZ_ASSERT(!aCompositionStartEvent
->DefaultPrevented(),
1097 "eCompositionStart shouldn't be cancelable");
1098 nsresult rv
= editorBase
->OnCompositionStart(*aCompositionStartEvent
);
1099 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1100 "EditorBase::OnCompositionStart() failed");
1104 nsresult
EditorEventListener::HandleChangeComposition(
1105 WidgetCompositionEvent
* aCompositionChangeEvent
) {
1106 if (NS_WARN_IF(!aCompositionChangeEvent
)) {
1107 return NS_ERROR_FAILURE
;
1109 MOZ_ASSERT(!aCompositionChangeEvent
->DefaultPrevented(),
1110 "eCompositionChange event shouldn't be cancelable");
1111 if (DetachedFromEditor()) {
1114 RefPtr
<EditorBase
> editorBase(mEditorBase
);
1115 if (!editorBase
->IsAcceptableInputEvent(aCompositionChangeEvent
)) {
1119 // if we are readonly, then do nothing.
1120 if (editorBase
->IsReadonly()) {
1124 nsresult rv
= editorBase
->OnCompositionChange(*aCompositionChangeEvent
);
1125 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1126 "EditorBase::OnCompositionChange() failed");
1130 void EditorEventListener::HandleEndComposition(
1131 WidgetCompositionEvent
* aCompositionEndEvent
) {
1132 if (NS_WARN_IF(!aCompositionEndEvent
) || DetachedFromEditor()) {
1135 RefPtr
<EditorBase
> editorBase(mEditorBase
);
1136 if (!editorBase
->IsAcceptableInputEvent(aCompositionEndEvent
)) {
1139 MOZ_ASSERT(!aCompositionEndEvent
->DefaultPrevented(),
1140 "eCompositionEnd shouldn't be cancelable");
1142 editorBase
->OnCompositionEnd(*aCompositionEndEvent
);
1145 nsresult
EditorEventListener::Focus(const InternalFocusEvent
& aFocusEvent
) {
1146 if (DetachedFromEditor()) {
1150 nsCOMPtr
<nsINode
> originalEventTargetNode
=
1151 nsINode::FromEventTargetOrNull(aFocusEvent
.GetOriginalDOMEventTarget());
1152 if (NS_WARN_IF(!originalEventTargetNode
)) {
1153 return NS_ERROR_UNEXPECTED
;
1156 // If the target is a document node but it's not editable, we should
1157 // ignore it because actual focused element's event is going to come.
1158 if (originalEventTargetNode
->IsDocument()) {
1159 if (!originalEventTargetNode
->IsInDesignMode()) {
1163 // We should not receive focus events whose target is not a content node
1164 // unless the node is a document node.
1165 else if (NS_WARN_IF(!originalEventTargetNode
->IsContent())) {
1169 const OwningNonNull
<EditorBase
> editorBase(*mEditorBase
);
1170 DebugOnly
<nsresult
> rvIgnored
= editorBase
->OnFocus(*originalEventTargetNode
);
1171 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
), "EditorBase::OnFocus() failed");
1172 return NS_OK
; // Don't return error code to the event listener manager.
1175 nsresult
EditorEventListener::Blur(const InternalFocusEvent
& aBlurEvent
) {
1176 if (DetachedFromEditor()) {
1180 DebugOnly
<nsresult
> rvIgnored
= mEditorBase
->OnBlur(aBlurEvent
.mTarget
);
1181 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
), "EditorBase::OnBlur() failed");
1182 return NS_OK
; // Don't return error code to the event listener manager.
1185 bool EditorEventListener::IsFileControlTextBox() {
1186 MOZ_ASSERT(!DetachedFromEditor());
1188 RefPtr
<EditorBase
> editorBase(mEditorBase
);
1189 Element
* rootElement
= editorBase
->GetRoot();
1190 if (!rootElement
|| !rootElement
->ChromeOnlyAccess()) {
1193 nsIContent
* parent
= rootElement
->FindFirstNonChromeOnlyAccessContent();
1194 if (!parent
|| !parent
->IsHTMLElement(nsGkAtoms::input
)) {
1197 nsCOMPtr
<nsIFormControl
> formControl
= do_QueryInterface(parent
);
1198 return formControl
->ControlType() == FormControlType::InputFile
;
1201 bool EditorEventListener::ShouldHandleNativeKeyBindings(
1202 WidgetKeyboardEvent
* aKeyboardEvent
) {
1203 MOZ_ASSERT(!DetachedFromEditor());
1205 // Only return true if the target of the event is a desendant of the active
1206 // editing host in order to match the similar decision made in
1207 // nsXBLWindowKeyHandler.
1208 // Note that IsAcceptableInputEvent doesn't check for the active editing
1209 // host for keyboard events, otherwise this check would have been
1210 // unnecessary. IsAcceptableInputEvent currently makes a similar check for
1213 nsCOMPtr
<nsIContent
> targetContent
= nsIContent::FromEventTargetOrNull(
1214 aKeyboardEvent
->GetOriginalDOMEventTarget());
1215 if (NS_WARN_IF(!targetContent
)) {
1219 RefPtr
<HTMLEditor
> htmlEditor
= HTMLEditor::GetFrom(mEditorBase
);
1224 if (htmlEditor
->IsInDesignMode()) {
1225 // Don't need to perform any checks in designMode documents.
1229 nsIContent
* editingHost
= htmlEditor
->ComputeEditingHost();
1234 return targetContent
->IsInclusiveDescendantOf(editingHost
);
1237 } // namespace mozilla