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 "mozilla/Assertions.h" // for MOZ_ASSERT, etc.
10 #include "mozilla/AutoRestore.h"
11 #include "mozilla/ContentEvents.h" // for InternalFocusEvent
12 #include "mozilla/EditorBase.h" // for EditorBase, etc.
13 #include "mozilla/EventListenerManager.h" // for EventListenerManager
14 #include "mozilla/EventStateManager.h" // for EventStateManager
15 #include "mozilla/HTMLEditor.h" // for HTMLEditor
16 #include "mozilla/IMEStateManager.h" // for IMEStateManager
17 #include "mozilla/Preferences.h" // for Preferences
18 #include "mozilla/PresShell.h" // for PresShell
19 #include "mozilla/TextEditor.h" // for TextEditor
20 #include "mozilla/TextEvents.h" // for WidgetCompositionEvent
21 #include "mozilla/dom/Element.h" // for Element
22 #include "mozilla/dom/Event.h" // for Event
23 #include "mozilla/dom/EventTarget.h" // for EventTarget
24 #include "mozilla/dom/MouseEvent.h" // for MouseEvent
25 #include "mozilla/dom/Selection.h"
26 #include "nsAString.h"
27 #include "nsCaret.h" // for nsCaret
28 #include "nsDebug.h" // for NS_WARNING, etc.
29 #include "nsFocusManager.h" // for nsFocusManager
30 #include "nsGkAtoms.h" // for nsGkAtoms, nsGkAtoms::input
31 #include "nsIContent.h" // for nsIContent
32 #include "nsIController.h" // for nsIController
34 #include "mozilla/dom/DOMStringList.h"
35 #include "mozilla/dom/DataTransfer.h"
36 #include "mozilla/dom/DragEvent.h"
37 #include "mozilla/dom/Document.h" // for Document
38 #include "nsIFormControl.h" // for nsIFormControl, etc.
39 #include "nsINode.h" // for nsINode, ::NODE_IS_EDITABLE, etc.
40 #include "nsIWidget.h" // for nsIWidget
41 #include "nsLiteralString.h" // for NS_LITERAL_STRING
42 #include "nsPIWindowRoot.h" // for nsPIWindowRoot
43 #include "nsPrintfCString.h" // for nsPrintfCString
45 #include "nsServiceManagerUtils.h" // for do_GetService
46 #include "nsString.h" // for nsAutoString
47 #include "nsQueryObject.h" // for do_QueryObject
48 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
49 # include "nsContentUtils.h" // for nsContentUtils, etc.
50 # include "nsIBidiKeyboard.h" // for nsIBidiKeyboard
53 #include "mozilla/dom/BrowserParent.h"
61 MOZ_CAN_RUN_SCRIPT
static void DoCommandCallback(Command aCommand
,
63 Document
* doc
= static_cast<Document
*>(aData
);
64 nsPIDOMWindowOuter
* win
= doc
->GetWindow();
68 nsCOMPtr
<nsPIWindowRoot
> root
= win
->GetTopWindowRoot();
73 const char* commandStr
= WidgetKeyboardEvent::GetCommandStr(aCommand
);
75 nsCOMPtr
<nsIController
> controller
;
76 root
->GetControllerForCommand(commandStr
, false /* for any window */,
77 getter_AddRefs(controller
));
83 if (NS_WARN_IF(NS_FAILED(
84 controller
->IsCommandEnabled(commandStr
, &commandEnabled
)))) {
88 controller
->DoCommand(commandStr
);
92 EditorEventListener::EditorEventListener()
93 : mEditorBase(nullptr),
95 mInTransaction(false),
96 mMouseDownOrUpConsumedByIME(false)
97 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
99 mHaveBidiKeyboards(false),
100 mShouldSwitchTextDirection(false),
106 EditorEventListener::~EditorEventListener() {
108 NS_WARNING("We've not been uninstalled yet");
113 nsresult
EditorEventListener::Connect(EditorBase
* aEditorBase
) {
114 if (NS_WARN_IF(!aEditorBase
)) {
115 return NS_ERROR_INVALID_ARG
;
118 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
119 nsIBidiKeyboard
* bidiKeyboard
= nsContentUtils::GetBidiKeyboard();
121 bool haveBidiKeyboards
= false;
122 bidiKeyboard
->GetHaveBidiKeyboards(&haveBidiKeyboards
);
123 mHaveBidiKeyboards
= haveBidiKeyboards
;
127 mEditorBase
= aEditorBase
;
129 nsresult rv
= InstallToEditor();
131 NS_WARNING("EditorEventListener::InstallToEditor() failed");
137 nsresult
EditorEventListener::InstallToEditor() {
138 MOZ_ASSERT(mEditorBase
, "The caller must set mEditorBase");
140 EventTarget
* eventTarget
= mEditorBase
->GetDOMEventTarget();
141 if (NS_WARN_IF(!eventTarget
)) {
142 return NS_ERROR_FAILURE
;
145 // register the event listeners with the listener manager
146 EventListenerManager
* eventListenerManager
=
147 eventTarget
->GetOrCreateListenerManager();
148 if (NS_WARN_IF(!eventListenerManager
)) {
149 return NS_ERROR_FAILURE
;
152 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
153 eventListenerManager
->AddEventListenerByType(
154 this, u
"keydown"_ns
, TrustedEventsAtSystemGroupBubble());
155 eventListenerManager
->AddEventListenerByType(
156 this, u
"keyup"_ns
, TrustedEventsAtSystemGroupBubble());
158 eventListenerManager
->AddEventListenerByType(
159 this, u
"keypress"_ns
, TrustedEventsAtSystemGroupBubble());
160 eventListenerManager
->AddEventListenerByType(
161 this, u
"dragenter"_ns
, TrustedEventsAtSystemGroupBubble());
162 eventListenerManager
->AddEventListenerByType(
163 this, u
"dragover"_ns
, TrustedEventsAtSystemGroupBubble());
164 eventListenerManager
->AddEventListenerByType(
165 this, u
"dragexit"_ns
, TrustedEventsAtSystemGroupBubble());
166 eventListenerManager
->AddEventListenerByType(
167 this, u
"drop"_ns
, TrustedEventsAtSystemGroupBubble());
168 // XXX We should add the mouse event listeners as system event group.
169 // E.g., web applications cannot prevent middle mouse paste by
170 // preventDefault() of click event at bubble phase.
171 // However, if we do so, all click handlers in any frames and frontend
172 // code need to check if it's editable. It makes easier create new bugs.
173 eventListenerManager
->AddEventListenerByType(this, u
"mousedown"_ns
,
174 TrustedEventsAtCapture());
175 eventListenerManager
->AddEventListenerByType(this, u
"mouseup"_ns
,
176 TrustedEventsAtCapture());
177 eventListenerManager
->AddEventListenerByType(this, u
"click"_ns
,
178 TrustedEventsAtCapture());
179 eventListenerManager
->AddEventListenerByType(
180 this, u
"auxclick"_ns
, TrustedEventsAtSystemGroupCapture());
181 // Focus event doesn't bubble so adding the listener to capturing phase as
182 // system event group.
183 eventListenerManager
->AddEventListenerByType(
184 this, u
"blur"_ns
, TrustedEventsAtSystemGroupCapture());
185 eventListenerManager
->AddEventListenerByType(
186 this, u
"focus"_ns
, TrustedEventsAtSystemGroupCapture());
187 eventListenerManager
->AddEventListenerByType(
188 this, u
"text"_ns
, TrustedEventsAtSystemGroupBubble());
189 eventListenerManager
->AddEventListenerByType(
190 this, u
"compositionstart"_ns
, TrustedEventsAtSystemGroupBubble());
191 eventListenerManager
->AddEventListenerByType(
192 this, u
"compositionend"_ns
, TrustedEventsAtSystemGroupBubble());
197 void EditorEventListener::Disconnect() {
198 if (DetachedFromEditor()) {
201 UninstallFromEditor();
203 nsFocusManager
* fm
= nsFocusManager::GetFocusManager();
205 nsIContent
* focusedContent
= fm
->GetFocusedElement();
206 mozilla::dom::Element
* root
= mEditorBase
->GetRoot();
207 if (focusedContent
&& root
&&
208 focusedContent
->IsInclusiveDescendantOf(root
)) {
209 // Reset the Selection ancestor limiter and SelectionController state
210 // that EditorBase::InitializeSelection set up.
211 DebugOnly
<nsresult
> rvIgnored
= mEditorBase
->FinalizeSelection();
212 NS_WARNING_ASSERTION(
213 NS_SUCCEEDED(rvIgnored
),
214 "EditorBase::FinalizeSelection() failed, but ignored");
218 mEditorBase
= nullptr;
221 void EditorEventListener::UninstallFromEditor() {
222 CleanupDragDropCaret();
224 EventTarget
* eventTarget
= mEditorBase
->GetDOMEventTarget();
225 if (NS_WARN_IF(!eventTarget
)) {
229 EventListenerManager
* eventListenerManager
=
230 eventTarget
->GetOrCreateListenerManager();
231 if (NS_WARN_IF(!eventListenerManager
)) {
235 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
236 eventListenerManager
->RemoveEventListenerByType(
237 this, u
"keydown"_ns
, TrustedEventsAtSystemGroupBubble());
238 eventListenerManager
->RemoveEventListenerByType(
239 this, u
"keyup"_ns
, TrustedEventsAtSystemGroupBubble());
241 eventListenerManager
->RemoveEventListenerByType(
242 this, u
"keypress"_ns
, TrustedEventsAtSystemGroupBubble());
243 eventListenerManager
->RemoveEventListenerByType(
244 this, u
"dragenter"_ns
, TrustedEventsAtSystemGroupBubble());
245 eventListenerManager
->RemoveEventListenerByType(
246 this, u
"dragover"_ns
, TrustedEventsAtSystemGroupBubble());
247 eventListenerManager
->RemoveEventListenerByType(
248 this, u
"dragexit"_ns
, TrustedEventsAtSystemGroupBubble());
249 eventListenerManager
->RemoveEventListenerByType(
250 this, u
"drop"_ns
, TrustedEventsAtSystemGroupBubble());
251 eventListenerManager
->RemoveEventListenerByType(this, u
"mousedown"_ns
,
252 TrustedEventsAtCapture());
253 eventListenerManager
->RemoveEventListenerByType(this, u
"mouseup"_ns
,
254 TrustedEventsAtCapture());
255 eventListenerManager
->RemoveEventListenerByType(this, u
"click"_ns
,
256 TrustedEventsAtCapture());
257 eventListenerManager
->RemoveEventListenerByType(
258 this, u
"auxclick"_ns
, TrustedEventsAtSystemGroupCapture());
259 eventListenerManager
->RemoveEventListenerByType(
260 this, u
"blur"_ns
, TrustedEventsAtSystemGroupCapture());
261 eventListenerManager
->RemoveEventListenerByType(
262 this, u
"focus"_ns
, TrustedEventsAtSystemGroupCapture());
263 eventListenerManager
->RemoveEventListenerByType(
264 this, u
"text"_ns
, TrustedEventsAtSystemGroupBubble());
265 eventListenerManager
->RemoveEventListenerByType(
266 this, u
"compositionstart"_ns
, TrustedEventsAtSystemGroupBubble());
267 eventListenerManager
->RemoveEventListenerByType(
268 this, u
"compositionend"_ns
, TrustedEventsAtSystemGroupBubble());
271 PresShell
* EditorEventListener::GetPresShell() const {
272 MOZ_ASSERT(!DetachedFromEditor());
273 return mEditorBase
->GetPresShell();
276 nsPresContext
* EditorEventListener::GetPresContext() const {
277 PresShell
* presShell
= GetPresShell();
278 return presShell
? presShell
->GetPresContext() : nullptr;
281 nsIContent
* EditorEventListener::GetFocusedRootContent() {
282 MOZ_ASSERT(!DetachedFromEditor());
283 nsCOMPtr
<nsIContent
> focusedContent
= mEditorBase
->GetFocusedContent();
284 if (!focusedContent
) {
288 Document
* composedDoc
= focusedContent
->GetComposedDoc();
289 if (NS_WARN_IF(!composedDoc
)) {
293 if (composedDoc
->HasFlag(NODE_IS_EDITABLE
)) {
297 return focusedContent
;
300 bool EditorEventListener::EditorHasFocus() {
301 MOZ_ASSERT(!DetachedFromEditor());
302 nsCOMPtr
<nsIContent
> focusedContent
= mEditorBase
->GetFocusedContent();
303 if (!focusedContent
) {
306 return !!focusedContent
->GetComposedDoc();
309 NS_IMPL_ISUPPORTS(EditorEventListener
, nsIDOMEventListener
)
311 bool EditorEventListener::DetachedFromEditor() const { return !mEditorBase
; }
313 bool EditorEventListener::DetachedFromEditorOrDefaultPrevented(
314 WidgetEvent
* aWidgetEvent
) const {
315 return NS_WARN_IF(!aWidgetEvent
) || DetachedFromEditor() ||
316 aWidgetEvent
->DefaultPrevented();
319 bool EditorEventListener::EnsureCommitComposition() {
320 MOZ_ASSERT(!DetachedFromEditor());
321 RefPtr
<EditorBase
> editorBase(mEditorBase
);
322 editorBase
->CommitComposition();
323 return !DetachedFromEditor();
326 NS_IMETHODIMP
EditorEventListener::HandleEvent(Event
* aEvent
) {
327 // Let's handle each event with the message of the internal event of the
328 // coming event. If the DOM event was created with improper interface,
329 // e.g., keydown event is created with |new MouseEvent("keydown", {});|,
330 // its message is always 0. Therefore, we can ban such strange event easy.
331 // However, we need to handle strange "focus" and "blur" event. See the
332 // following code of this switch statement.
333 // NOTE: Each event handler may require specific event interface. Before
334 // calling it, this queries the specific interface. If it would fail,
335 // each event handler would just ignore the event. So, in this method,
336 // you don't need to check if the QI succeeded before each call.
337 WidgetEvent
* internalEvent
= aEvent
->WidgetEventPtr();
338 switch (internalEvent
->mMessage
) {
341 // aEvent should be grabbed by the caller since this is
342 // nsIDOMEventListener method. However, our clang plugin cannot check it
343 // if we use Event::As*Event(). So, we need to grab it by ourselves.
344 RefPtr
<DragEvent
> dragEvent
= aEvent
->AsDragEvent();
345 nsresult rv
= DragEnter(dragEvent
);
346 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
347 "EditorEventListener::DragEnter() failed");
353 RefPtr
<DragEvent
> dragEvent
= aEvent
->AsDragEvent();
354 nsresult rv
= DragOverOrDrop(dragEvent
);
355 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
356 "EditorEventListener::DragOverOrDrop() failed");
361 RefPtr
<DragEvent
> dragEvent
= aEvent
->AsDragEvent();
362 nsresult rv
= DragExit(dragEvent
);
363 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
364 "EditorEventListener::DragExit() failed");
367 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
370 nsresult rv
= KeyDown(internalEvent
->AsKeyboardEvent());
371 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
372 "EditorEventListener::KeyDown() failed");
377 nsresult rv
= KeyUp(internalEvent
->AsKeyboardEvent());
378 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
379 "EditorEventListener::KeyUp() failed");
382 #endif // #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
385 nsresult rv
= KeyPress(internalEvent
->AsKeyboardEvent());
386 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
387 "EditorEventListener::KeyPress() failed");
392 // EditorEventListener may receive (1) all mousedown, mouseup and click
393 // events, (2) only mousedown event or (3) only mouseup event.
394 // mMouseDownOrUpConsumedByIME is used only for ignoring click event if
395 // preceding mousedown and/or mouseup event is consumed by IME.
396 // Therefore, even if case #2 or case #3 occurs,
397 // mMouseDownOrUpConsumedByIME is true here. Therefore, we should always
398 // overwrite it here.
399 mMouseDownOrUpConsumedByIME
=
400 NotifyIMEOfMouseButtonEvent(internalEvent
->AsMouseEvent());
401 if (mMouseDownOrUpConsumedByIME
) {
404 RefPtr
<MouseEvent
> mouseEvent
= aEvent
->AsMouseEvent();
405 if (NS_WARN_IF(!mouseEvent
)) {
408 nsresult rv
= MouseDown(mouseEvent
);
409 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
410 "EditorEventListener::MouseDown() failed");
415 // See above comment in the eMouseDown case, first.
416 // This code assumes that case #1 is occuring. However, if case #3 may
417 // occurs after case #2 and the mousedown is consumed,
418 // mMouseDownOrUpConsumedByIME is true even though EditorEventListener
419 // has not received the preceding mousedown event of this mouseup event.
420 // So, mMouseDownOrUpConsumedByIME may be invalid here. However,
421 // this is not a matter because mMouseDownOrUpConsumedByIME is referred
422 // only by eMouseClick case but click event is fired only in case #1.
423 // So, before a click event is fired, mMouseDownOrUpConsumedByIME is
424 // always initialized in the eMouseDown case if it's referred.
425 if (NotifyIMEOfMouseButtonEvent(internalEvent
->AsMouseEvent())) {
426 mMouseDownOrUpConsumedByIME
= true;
428 if (mMouseDownOrUpConsumedByIME
) {
431 RefPtr
<MouseEvent
> mouseEvent
= aEvent
->AsMouseEvent();
432 if (NS_WARN_IF(!mouseEvent
)) {
435 nsresult rv
= MouseUp(mouseEvent
);
436 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
437 "EditorEventListener::MouseUp() failed");
442 WidgetMouseEvent
* widgetMouseEvent
= internalEvent
->AsMouseEvent();
443 // Don't handle non-primary click events
444 if (widgetMouseEvent
->mButton
!= MouseButton::ePrimary
) {
450 case eMouseAuxClick
: {
451 WidgetMouseEvent
* widgetMouseEvent
= internalEvent
->AsMouseEvent();
452 if (NS_WARN_IF(!widgetMouseEvent
)) {
455 // If the preceding mousedown event or mouseup event was consumed,
456 // editor shouldn't handle this click event.
457 if (mMouseDownOrUpConsumedByIME
) {
458 mMouseDownOrUpConsumedByIME
= false;
459 widgetMouseEvent
->PreventDefault();
462 nsresult rv
= MouseClick(widgetMouseEvent
);
463 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
464 "EditorEventListener::MouseClick() failed");
469 nsresult rv
= Focus(internalEvent
->AsFocusEvent());
470 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
471 "EditorEventListener::Focus() failed");
476 nsresult rv
= Blur(internalEvent
->AsFocusEvent());
477 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
478 "EditorEventListener::Blur() failed");
482 case eCompositionChange
: {
484 HandleChangeComposition(internalEvent
->AsCompositionEvent());
485 NS_WARNING_ASSERTION(
487 "EditorEventListener::HandleChangeComposition() failed");
491 case eCompositionStart
: {
492 nsresult rv
= HandleStartComposition(internalEvent
->AsCompositionEvent());
493 NS_WARNING_ASSERTION(
495 "EditorEventListener::HandleStartComposition() failed");
499 case eCompositionEnd
: {
500 HandleEndComposition(internalEvent
->AsCompositionEvent());
508 nsAutoString eventType
;
509 aEvent
->GetType(eventType
);
510 nsPrintfCString
assertMessage(
511 "Editor doesn't handle \"%s\" event "
512 "because its internal event doesn't have proper message",
513 NS_ConvertUTF16toUTF8(eventType
).get());
514 NS_ASSERTION(false, assertMessage
.get());
520 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
522 // This function is borrowed from Chromium's ImeInput::IsCtrlShiftPressed
523 bool IsCtrlShiftPressed(const WidgetKeyboardEvent
* aKeyboardEvent
,
525 MOZ_ASSERT(aKeyboardEvent
);
526 // To check if a user is pressing only a control key and a right-shift key
527 // (or a left-shift key), we use the steps below:
528 // 1. Check if a user is pressing a control key and a right-shift key (or
529 // a left-shift key).
530 // 2. If the condition 1 is true, we should check if there are any other
531 // keys pressed at the same time.
532 // To ignore the keys checked in 1, we set their status to 0 before
533 // checking the key status.
535 if (!aKeyboardEvent
->IsControl()) {
539 switch (aKeyboardEvent
->mLocation
) {
540 case eKeyLocationRight
:
543 case eKeyLocationLeft
:
550 // Scan the key status to find pressed keys. We should abandon changing the
551 // text direction when there are other pressed keys.
552 if (aKeyboardEvent
->IsAlt() || aKeyboardEvent
->IsOS()) {
559 // This logic is mostly borrowed from Chromium's
560 // RenderWidgetHostViewWin::OnKeyEvent.
562 nsresult
EditorEventListener::KeyUp(const WidgetKeyboardEvent
* aKeyboardEvent
) {
563 if (NS_WARN_IF(!aKeyboardEvent
) || DetachedFromEditor()) {
567 if (!mHaveBidiKeyboards
) {
571 // XXX Why doesn't this method check if it's consumed?
572 RefPtr
<EditorBase
> editorBase(mEditorBase
);
573 if ((aKeyboardEvent
->mKeyCode
== NS_VK_SHIFT
||
574 aKeyboardEvent
->mKeyCode
== NS_VK_CONTROL
) &&
575 mShouldSwitchTextDirection
&& editorBase
->IsPlaintextEditor()) {
576 editorBase
->SwitchTextDirectionTo(mSwitchToRTL
577 ? EditorBase::TextDirection::eRTL
578 : EditorBase::TextDirection::eLTR
);
579 mShouldSwitchTextDirection
= false;
584 nsresult
EditorEventListener::KeyDown(
585 const WidgetKeyboardEvent
* aKeyboardEvent
) {
586 if (NS_WARN_IF(!aKeyboardEvent
) || DetachedFromEditor()) {
590 if (!mHaveBidiKeyboards
) {
594 // XXX Why isn't this method check if it's consumed?
595 if (aKeyboardEvent
->mKeyCode
== NS_VK_SHIFT
) {
597 if (IsCtrlShiftPressed(aKeyboardEvent
, switchToRTL
)) {
598 mShouldSwitchTextDirection
= true;
599 mSwitchToRTL
= switchToRTL
;
601 } else if (aKeyboardEvent
->mKeyCode
!= NS_VK_CONTROL
) {
602 // In case the user presses any other key besides Ctrl and Shift
603 mShouldSwitchTextDirection
= false;
608 #endif // #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
610 nsresult
EditorEventListener::KeyPress(WidgetKeyboardEvent
* aKeyboardEvent
) {
611 if (NS_WARN_IF(!aKeyboardEvent
)) {
615 RefPtr
<EditorBase
> editorBase(mEditorBase
);
616 if (!editorBase
->IsAcceptableInputEvent(aKeyboardEvent
) ||
617 DetachedFromEditorOrDefaultPrevented(aKeyboardEvent
)) {
621 nsresult rv
= editorBase
->HandleKeyPressEvent(aKeyboardEvent
);
623 NS_WARNING("EditorBase::HandleKeyPressEvent() failed");
626 if (DetachedFromEditorOrDefaultPrevented(aKeyboardEvent
)) {
630 if (!ShouldHandleNativeKeyBindings(aKeyboardEvent
)) {
634 // Now, ask the native key bindings to handle the event.
635 nsIWidget
* widget
= aKeyboardEvent
->mWidget
;
636 // If the event is created by chrome script, the widget is always nullptr.
638 nsPresContext
* presContext
= GetPresContext();
639 if (NS_WARN_IF(!presContext
)) {
642 widget
= presContext
->GetNearestWidget();
643 if (NS_WARN_IF(!widget
)) {
648 RefPtr
<Document
> doc
= editorBase
->GetDocument();
650 // WidgetKeyboardEvent::ExecuteEditCommands() requires non-nullptr mWidget.
651 // If the event is created by chrome script, it is nullptr but we need to
652 // execute native key bindings. Therefore, we need to set widget to
653 // WidgetEvent::mWidget temporarily.
654 AutoRestore
<nsCOMPtr
<nsIWidget
>> saveWidget(aKeyboardEvent
->mWidget
);
655 aKeyboardEvent
->mWidget
= widget
;
656 if (aKeyboardEvent
->ExecuteEditCommands(
657 nsIWidget::NativeKeyBindingsForRichTextEditor
, DoCommandCallback
,
659 aKeyboardEvent
->PreventDefault();
664 nsresult
EditorEventListener::MouseClick(WidgetMouseEvent
* aMouseClickEvent
) {
665 if (NS_WARN_IF(!aMouseClickEvent
) || DetachedFromEditor()) {
668 // nothing to do if editor isn't editable or clicked on out of the editor.
669 RefPtr
<TextEditor
> textEditor
= mEditorBase
->AsTextEditor();
670 if (textEditor
->IsReadonly() ||
671 !textEditor
->IsAcceptableInputEvent(aMouseClickEvent
)) {
675 // Notifies clicking on editor to IMEStateManager even when the event was
677 if (EditorHasFocus()) {
678 RefPtr
<nsPresContext
> presContext
= GetPresContext();
680 IMEStateManager::OnClickInEditor(presContext
, GetFocusedRootContent(),
682 if (DetachedFromEditor()) {
688 if (DetachedFromEditorOrDefaultPrevented(aMouseClickEvent
)) {
689 // We're done if 'preventdefault' is true (see for example bug 70698).
693 // If we got a mouse down inside the editing area, we should force the
694 // IME to commit before we change the cursor position.
695 if (!EnsureCommitComposition()) {
699 // XXX The following code is hack for our buggy "click" and "auxclick"
700 // implementation. "auxclick" event was added recently, however,
701 // any non-primary button click event handlers in our UI still keep
702 // listening to "click" events. Additionally, "auxclick" event is
703 // fired after "click" events and even if we do this in the system event
704 // group, middle click opens new tab before us. Therefore, we need to
705 // handle middle click at capturing phase of the default group even
706 // though this makes web apps cannot prevent middle click paste with
707 // calling preventDefault() of "click" nor "auxclick".
709 if (aMouseClickEvent
->mButton
!= MouseButton::eMiddle
||
710 !WidgetMouseEvent::IsMiddleClickPasteEnabled()) {
714 RefPtr
<PresShell
> presShell
= GetPresShell();
715 if (NS_WARN_IF(!presShell
)) {
718 nsPresContext
* presContext
= GetPresContext();
719 if (NS_WARN_IF(!presContext
)) {
722 MOZ_ASSERT(!aMouseClickEvent
->DefaultPrevented());
723 nsEventStatus status
= nsEventStatus_eIgnore
;
724 RefPtr
<EventStateManager
> esm
= presContext
->EventStateManager();
725 DebugOnly
<nsresult
> rvIgnored
= esm
->HandleMiddleClickPaste(
726 presShell
, aMouseClickEvent
, &status
, textEditor
);
727 NS_WARNING_ASSERTION(
728 NS_SUCCEEDED(rvIgnored
),
729 "EventStateManager::HandleMiddleClickPaste() failed, but ignored");
730 if (status
== nsEventStatus_eConsumeNoDefault
) {
731 // We no longer need to StopImmediatePropagation here since
732 // ClickHandlerChild.jsm checks for and ignores editables, so won't
733 // re-handle the event
734 aMouseClickEvent
->PreventDefault();
739 bool EditorEventListener::NotifyIMEOfMouseButtonEvent(
740 WidgetMouseEvent
* aMouseEvent
) {
741 MOZ_ASSERT(aMouseEvent
);
743 if (!EditorHasFocus()) {
747 RefPtr
<nsPresContext
> presContext
= GetPresContext();
748 if (NS_WARN_IF(!presContext
)) {
751 nsCOMPtr
<nsIContent
> focusedRootContent
= GetFocusedRootContent();
752 return IMEStateManager::OnMouseButtonEventInEditor(
753 presContext
, focusedRootContent
, aMouseEvent
);
756 nsresult
EditorEventListener::MouseDown(MouseEvent
* aMouseEvent
) {
757 // FYI: We don't need to check if it's already consumed here because
758 // we need to commit composition at mouse button operation.
759 // FYI: This may be called by HTMLEditorEventListener::MouseDown() even
760 // when the event is not acceptable for committing composition.
761 if (DetachedFromEditor()) {
764 Unused
<< EnsureCommitComposition();
769 * Drag event implementation
772 nsresult
EditorEventListener::DragEnter(DragEvent
* aDragEvent
) {
773 if (NS_WARN_IF(!aDragEvent
) || DetachedFromEditor()) {
777 RefPtr
<PresShell
> presShell
= GetPresShell();
778 if (NS_WARN_IF(!presShell
)) {
783 mCaret
= new nsCaret();
784 DebugOnly
<nsresult
> rvIgnored
= mCaret
->Init(presShell
);
785 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
786 "nsCaret::Init() failed, but ignored");
787 mCaret
->SetCaretReadOnly(true);
788 // This is to avoid the requirement that the Selection is Collapsed which
789 // it can't be when dragging a selection in the same shell.
790 // See nsCaret::IsVisible().
791 mCaret
->SetVisibilityDuringSelection(true);
794 presShell
->SetCaret(mCaret
);
796 nsresult rv
= DragOverOrDrop(aDragEvent
);
797 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
798 "EditorEventListener::DragOverOrDrop() failed");
802 void EditorEventListener::RefuseToDropAndHideCaret(DragEvent
* aDragEvent
) {
803 MOZ_ASSERT(aDragEvent
->WidgetEventPtr()->mFlags
.mInSystemGroup
);
805 aDragEvent
->PreventDefault();
806 aDragEvent
->StopImmediatePropagation();
807 DataTransfer
* dataTransfer
= aDragEvent
->GetDataTransfer();
809 dataTransfer
->SetDropEffectInt(nsIDragService::DRAGDROP_ACTION_NONE
);
812 mCaret
->SetVisible(false);
816 nsresult
EditorEventListener::DragOverOrDrop(DragEvent
* aDragEvent
) {
817 MOZ_ASSERT(aDragEvent
);
818 MOZ_ASSERT(aDragEvent
->WidgetEventPtr()->mMessage
== eDrop
||
819 aDragEvent
->WidgetEventPtr()->mMessage
== eDragOver
||
820 aDragEvent
->WidgetEventPtr()->mMessage
== eDragEnter
);
822 if (aDragEvent
->WidgetEventPtr()->mMessage
== eDrop
) {
823 CleanupDragDropCaret();
827 if (DetachedFromEditorOrDefaultPrevented(aDragEvent
->WidgetEventPtr())) {
831 int32_t dropOffset
= -1;
832 nsCOMPtr
<nsIContent
> dropParentContent
=
833 aDragEvent
->GetRangeParentContentAndOffset(&dropOffset
);
834 if (NS_WARN_IF(!dropParentContent
)) {
835 return NS_ERROR_FAILURE
;
837 if (DetachedFromEditor()) {
838 RefuseToDropAndHideCaret(aDragEvent
);
843 !dropParentContent
->IsEditable() || mEditorBase
->IsReadonly();
845 // First of all, hide caret if we won't insert the drop data into the editor
847 if (mCaret
&& (IsFileControlTextBox() || notEditable
)) {
848 mCaret
->SetVisible(false);
851 // If we're a native anonymous <input> element in <input type="file">,
852 // we don't need to handle the drop.
853 if (IsFileControlTextBox()) {
857 // If the drop target isn't ediable, the drop should be handled by the
860 // If we're a text control element which is readonly or disabled,
861 // we should refuse to drop.
862 if (!mEditorBase
->AsHTMLEditor()) {
863 RefuseToDropAndHideCaret(aDragEvent
);
866 // Otherwise, we shouldn't handle the drop.
870 // If the drag event does not have any data which we can handle, we should
871 // refuse to drop even if some parents can handle it because user may be
872 // trying to drop it on us, not our parent. For example, users don't want
873 // to drop HTML data to parent contenteditable element if they drop it on
874 // a child <input> element.
875 if (!DragEventHasSupportingData(aDragEvent
)) {
876 RefuseToDropAndHideCaret(aDragEvent
);
880 // If we don't accept the data drop at the point, for example, while dragging
881 // selection, it's not allowed dropping on its selection ranges. In this
882 // case, any parents shouldn't handle the drop instead of us, for example,
883 // dropping text shouldn't be treated as URL and load new page.
884 if (!CanInsertAtDropPosition(aDragEvent
)) {
885 RefuseToDropAndHideCaret(aDragEvent
);
889 aDragEvent
->PreventDefault();
890 aDragEvent
->StopImmediatePropagation();
892 if (aDragEvent
->WidgetEventPtr()->mMessage
== eDrop
) {
893 RefPtr
<TextEditor
> textEditor
= mEditorBase
->AsTextEditor();
894 nsresult rv
= textEditor
->OnDrop(aDragEvent
);
895 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "TextEditor::OnDrop() failed");
899 MOZ_ASSERT(aDragEvent
->WidgetEventPtr()->mMessage
== eDragOver
||
900 aDragEvent
->WidgetEventPtr()->mMessage
== eDragEnter
);
902 // If we handle the dragged item, we need to adjust drop effect here
903 // because once DataTransfer is retrieved, DragEvent has initialized it
904 // with nsContentUtils::SetDataTransferInEvent() but it does not check
905 // whether the content is movable or not.
906 DataTransfer
* dataTransfer
= aDragEvent
->GetDataTransfer();
908 dataTransfer
->DropEffectInt() == nsIDragService::DRAGDROP_ACTION_MOVE
) {
909 nsCOMPtr
<nsINode
> dragSource
= dataTransfer
->GetMozSourceNode();
910 if (dragSource
&& !dragSource
->IsEditable()) {
911 // In this case, we shouldn't allow "move" because the drag source
913 dataTransfer
->SetDropEffectInt(
914 nsContentUtils::FilterDropEffect(nsIDragService::DRAGDROP_ACTION_COPY
,
915 dataTransfer
->EffectAllowedInt()));
923 mCaret
->SetVisible(true);
924 mCaret
->SetCaretPosition(dropParentContent
, dropOffset
);
929 void EditorEventListener::CleanupDragDropCaret() {
934 mCaret
->SetVisible(false); // hide it, so that it turns off its timer
936 RefPtr
<PresShell
> presShell
= GetPresShell();
938 presShell
->RestoreCaret();
945 nsresult
EditorEventListener::DragExit(DragEvent
* aDragEvent
) {
946 // XXX If aDragEvent was created by chrome script, its defaultPrevented
947 // may be true, though. We shouldn't handle such event but we don't
948 // have a way to distinguish if coming event is created by chrome script.
949 NS_WARNING_ASSERTION(!aDragEvent
->WidgetEventPtr()->DefaultPrevented(),
950 "eDragExit shouldn't be cancelable");
951 if (NS_WARN_IF(!aDragEvent
) || DetachedFromEditor()) {
955 CleanupDragDropCaret();
960 bool EditorEventListener::DragEventHasSupportingData(
961 DragEvent
* aDragEvent
) const {
963 !DetachedFromEditorOrDefaultPrevented(aDragEvent
->WidgetEventPtr()));
964 MOZ_ASSERT(aDragEvent
->GetDataTransfer());
966 // Plaintext editors only support dropping text. Otherwise, HTML and files
967 // can be dropped as well.
968 DataTransfer
* dataTransfer
= aDragEvent
->GetDataTransfer();
970 NS_WARNING("No data transfer returned");
973 return dataTransfer
->HasType(NS_LITERAL_STRING_FROM_CSTRING(kTextMime
)) ||
974 dataTransfer
->HasType(
975 NS_LITERAL_STRING_FROM_CSTRING(kMozTextInternal
)) ||
976 (!mEditorBase
->IsPlaintextEditor() &&
977 (dataTransfer
->HasType(NS_LITERAL_STRING_FROM_CSTRING(kHTMLMime
)) ||
978 dataTransfer
->HasType(NS_LITERAL_STRING_FROM_CSTRING(kFileMime
))));
981 bool EditorEventListener::CanInsertAtDropPosition(DragEvent
* aDragEvent
) {
983 !DetachedFromEditorOrDefaultPrevented(aDragEvent
->WidgetEventPtr()));
984 MOZ_ASSERT(!mEditorBase
->IsReadonly());
985 MOZ_ASSERT(DragEventHasSupportingData(aDragEvent
));
987 DataTransfer
* dataTransfer
= aDragEvent
->GetDataTransfer();
988 if (NS_WARN_IF(!dataTransfer
)) {
992 // If there is no source node, this is probably an external drag and the
993 // drop is allowed. The later checks rely on checking if the drag target
994 // is the same as the drag source.
995 nsCOMPtr
<nsINode
> sourceNode
= dataTransfer
->GetMozSourceNode();
1000 // There is a source node, so compare the source documents and this document.
1001 // Disallow drops on the same document.
1003 RefPtr
<Document
> targetDocument
= mEditorBase
->GetDocument();
1004 if (NS_WARN_IF(!targetDocument
)) {
1008 RefPtr
<Document
> sourceDocument
= sourceNode
->OwnerDoc();
1010 // If the source and the dest are not same document, allow to drop it always.
1011 if (targetDocument
!= sourceDocument
) {
1015 // If the source node is a remote browser, treat this as coming from a
1016 // different document and allow the drop.
1017 if (BrowserParent::GetFrom(nsIContent::FromNode(sourceNode
))) {
1021 RefPtr
<Selection
> selection
= mEditorBase
->GetSelection();
1026 // If selection is collapsed, allow to drop it always.
1027 if (selection
->IsCollapsed()) {
1031 int32_t dropOffset
= -1;
1032 nsCOMPtr
<nsIContent
> dropParentContent
=
1033 aDragEvent
->GetRangeParentContentAndOffset(&dropOffset
);
1034 if (!dropParentContent
|| NS_WARN_IF(DetachedFromEditor())) {
1038 return !EditorUtils::IsPointInSelection(*selection
, *dropParentContent
,
1042 nsresult
EditorEventListener::HandleStartComposition(
1043 WidgetCompositionEvent
* aCompositionStartEvent
) {
1044 if (NS_WARN_IF(!aCompositionStartEvent
)) {
1045 return NS_ERROR_FAILURE
;
1047 if (DetachedFromEditor()) {
1050 RefPtr
<TextEditor
> textEditor
= mEditorBase
->AsTextEditor();
1051 if (!textEditor
->IsAcceptableInputEvent(aCompositionStartEvent
)) {
1054 // Although, "compositionstart" should be cancelable, but currently,
1055 // eCompositionStart event coming from widget is not cancelable.
1056 MOZ_ASSERT(!aCompositionStartEvent
->DefaultPrevented(),
1057 "eCompositionStart shouldn't be cancelable");
1058 nsresult rv
= textEditor
->OnCompositionStart(*aCompositionStartEvent
);
1059 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1060 "TextEditor::OnCompositionStart() failed");
1064 nsresult
EditorEventListener::HandleChangeComposition(
1065 WidgetCompositionEvent
* aCompositionChangeEvent
) {
1066 if (NS_WARN_IF(!aCompositionChangeEvent
)) {
1067 return NS_ERROR_FAILURE
;
1069 MOZ_ASSERT(!aCompositionChangeEvent
->DefaultPrevented(),
1070 "eCompositionChange event shouldn't be cancelable");
1071 if (DetachedFromEditor()) {
1074 RefPtr
<TextEditor
> textEditor
= mEditorBase
->AsTextEditor();
1075 if (!textEditor
->IsAcceptableInputEvent(aCompositionChangeEvent
)) {
1079 // if we are readonly, then do nothing.
1080 if (textEditor
->IsReadonly()) {
1084 nsresult rv
= textEditor
->OnCompositionChange(*aCompositionChangeEvent
);
1085 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1086 "TextEditor::OnCompositionChange() failed");
1090 void EditorEventListener::HandleEndComposition(
1091 WidgetCompositionEvent
* aCompositionEndEvent
) {
1092 if (NS_WARN_IF(!aCompositionEndEvent
) || DetachedFromEditor()) {
1095 RefPtr
<TextEditor
> textEditor
= mEditorBase
->AsTextEditor();
1096 if (!textEditor
->IsAcceptableInputEvent(aCompositionEndEvent
)) {
1099 MOZ_ASSERT(!aCompositionEndEvent
->DefaultPrevented(),
1100 "eCompositionEnd shouldn't be cancelable");
1102 textEditor
->OnCompositionEnd(*aCompositionEndEvent
);
1105 nsresult
EditorEventListener::Focus(InternalFocusEvent
* aFocusEvent
) {
1106 if (NS_WARN_IF(!aFocusEvent
) || DetachedFromEditor()) {
1110 RefPtr
<EditorBase
> editorBase(mEditorBase
);
1112 // Spell check a textarea the first time that it is focused.
1113 SpellCheckIfNeeded();
1114 if (DetachedFromEditor()) {
1118 EventTarget
* target
= aFocusEvent
->GetOriginalDOMEventTarget();
1119 nsCOMPtr
<nsINode
> eventTargetNode
= do_QueryInterface(target
);
1120 if (NS_WARN_IF(!eventTargetNode
)) {
1121 return NS_ERROR_UNEXPECTED
;
1124 // If the target is a document node but it's not editable, we should ignore
1125 // it because actual focused element's event is going to come.
1126 if (eventTargetNode
->IsDocument() &&
1127 !eventTargetNode
->HasFlag(NODE_IS_EDITABLE
)) {
1131 if (eventTargetNode
->IsContent()) {
1132 nsIContent
* content
=
1133 eventTargetNode
->AsContent()->FindFirstNonChromeOnlyAccessContent();
1134 // XXX If the focus event target is a form control in contenteditable
1135 // element, perhaps, the parent HTML editor should do nothing by this
1136 // handler. However, FindSelectionRoot() returns the root element of the
1137 // contenteditable editor. So, the editableRoot value is invalid for
1138 // the plain text editor, and it will be set to the wrong limiter of
1139 // the selection. However, fortunately, actual bugs are not found yet.
1140 nsCOMPtr
<nsIContent
> editableRoot
= editorBase
->FindSelectionRoot(content
);
1142 // make sure that the element is really focused in case an earlier
1143 // listener in the chain changed the focus.
1145 nsFocusManager
* focusManager
= nsFocusManager::GetFocusManager();
1146 if (NS_WARN_IF(!focusManager
)) {
1150 nsIContent
* focusedContent
= focusManager
->GetFocusedElement();
1151 if (!focusedContent
) {
1155 nsCOMPtr
<nsIContent
> originalTargetAsContent
=
1156 do_QueryInterface(aFocusEvent
->GetOriginalDOMEventTarget());
1158 if (!SameCOMIdentity(
1159 focusedContent
->FindFirstNonChromeOnlyAccessContent(),
1160 originalTargetAsContent
->FindFirstNonChromeOnlyAccessContent())) {
1166 editorBase
->OnFocus(*eventTargetNode
);
1167 if (DetachedFromEditorOrDefaultPrevented(aFocusEvent
)) {
1171 RefPtr
<nsPresContext
> presContext
= GetPresContext();
1172 if (NS_WARN_IF(!presContext
)) {
1175 nsCOMPtr
<nsIContent
> focusedContent
= editorBase
->GetFocusedContentForIME();
1176 IMEStateManager::OnFocusInEditor(presContext
, focusedContent
, *editorBase
);
1181 nsresult
EditorEventListener::Blur(InternalFocusEvent
* aBlurEvent
) {
1182 if (NS_WARN_IF(!aBlurEvent
) || DetachedFromEditor()) {
1186 // check if something else is focused. If another element is focused, then
1187 // we should not change the selection.
1188 nsFocusManager
* focusManager
= nsFocusManager::GetFocusManager();
1189 if (NS_WARN_IF(!focusManager
)) {
1193 Element
* focusedElement
= focusManager
->GetFocusedElement();
1194 if (!focusedElement
) {
1195 RefPtr
<EditorBase
> editorBase(mEditorBase
);
1196 DebugOnly
<nsresult
> rvIgnored
= editorBase
->FinalizeSelection();
1197 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
1198 "EditorBase::FinalizeSelection() failed, but ignored");
1203 void EditorEventListener::SpellCheckIfNeeded() {
1204 MOZ_ASSERT(!DetachedFromEditor());
1206 // If the spell check skip flag is still enabled from creation time,
1207 // disable it because focused editors are allowed to spell check.
1208 RefPtr
<EditorBase
> editorBase(mEditorBase
);
1209 if (!editorBase
->ShouldSkipSpellCheck()) {
1212 DebugOnly
<nsresult
> rvIgnored
=
1213 editorBase
->RemoveFlags(nsIEditor::eEditorSkipSpellCheck
);
1214 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
1215 "EditorBase::RemoveFlags() failed, but ignored");
1218 bool EditorEventListener::IsFileControlTextBox() {
1219 MOZ_ASSERT(!DetachedFromEditor());
1221 RefPtr
<EditorBase
> editorBase(mEditorBase
);
1222 Element
* rootElement
= editorBase
->GetRoot();
1223 if (!rootElement
|| !rootElement
->ChromeOnlyAccess()) {
1226 nsIContent
* parent
= rootElement
->FindFirstNonChromeOnlyAccessContent();
1227 if (!parent
|| !parent
->IsHTMLElement(nsGkAtoms::input
)) {
1230 nsCOMPtr
<nsIFormControl
> formControl
= do_QueryInterface(parent
);
1231 return formControl
->ControlType() == NS_FORM_INPUT_FILE
;
1234 bool EditorEventListener::ShouldHandleNativeKeyBindings(
1235 WidgetKeyboardEvent
* aKeyboardEvent
) {
1236 MOZ_ASSERT(!DetachedFromEditor());
1238 // Only return true if the target of the event is a desendant of the active
1239 // editing host in order to match the similar decision made in
1240 // nsXBLWindowKeyHandler.
1241 // Note that IsAcceptableInputEvent doesn't check for the active editing
1242 // host for keyboard events, otherwise this check would have been
1243 // unnecessary. IsAcceptableInputEvent currently makes a similar check for
1246 nsCOMPtr
<nsIContent
> targetContent
=
1247 do_QueryInterface(aKeyboardEvent
->GetDOMEventTarget());
1248 if (NS_WARN_IF(!targetContent
)) {
1252 RefPtr
<EditorBase
> editorBase(mEditorBase
);
1253 HTMLEditor
* htmlEditor
= editorBase
->AsHTMLEditor();
1258 RefPtr
<Document
> doc
= editorBase
->GetDocument();
1259 if (doc
->HasFlag(NODE_IS_EDITABLE
)) {
1260 // Don't need to perform any checks in designMode documents.
1264 nsIContent
* editingHost
= htmlEditor
->GetActiveEditingHost();
1269 return targetContent
->IsInclusiveDescendantOf(editingHost
);
1272 } // namespace mozilla