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/. */
6 #include "mozilla/Assertions.h" // for MOZ_ASSERT, etc
7 #include "mozilla/EventListenerManager.h" // for EventListenerManager
8 #include "mozilla/IMEStateManager.h" // for IMEStateManager
9 #include "mozilla/Preferences.h" // for Preferences
10 #include "mozilla/TextEvents.h" // for WidgetCompositionEvent
11 #include "mozilla/dom/Element.h" // for Element
12 #include "mozilla/dom/EventTarget.h" // for EventTarget
13 #include "nsAString.h"
14 #include "nsCaret.h" // for nsCaret
15 #include "nsDebug.h" // for NS_ENSURE_TRUE, etc
16 #include "nsEditor.h" // for nsEditor, etc
17 #include "nsEditorEventListener.h"
18 #include "nsFocusManager.h" // for nsFocusManager
19 #include "nsGkAtoms.h" // for nsGkAtoms, nsGkAtoms::input
20 #include "nsIClipboard.h" // for nsIClipboard, etc
21 #include "nsIContent.h" // for nsIContent
22 #include "nsIController.h" // for nsIController
24 #include "mozilla/dom/DOMStringList.h"
25 #include "mozilla/dom/DataTransfer.h"
26 #include "nsIDOMDocument.h" // for nsIDOMDocument
27 #include "nsIDOMDragEvent.h" // for nsIDOMDragEvent
28 #include "nsIDOMElement.h" // for nsIDOMElement
29 #include "nsIDOMEvent.h" // for nsIDOMEvent
30 #include "nsIDOMEventTarget.h" // for nsIDOMEventTarget
31 #include "nsIDOMKeyEvent.h" // for nsIDOMKeyEvent
32 #include "nsIDOMMouseEvent.h" // for nsIDOMMouseEvent
33 #include "nsIDOMNode.h" // for nsIDOMNode
34 #include "nsIDOMRange.h" // for nsIDOMRange
35 #include "nsIDocument.h" // for nsIDocument
36 #include "nsIEditor.h" // for nsEditor::GetSelection, etc
37 #include "nsIEditorIMESupport.h"
38 #include "nsIEditorMailSupport.h" // for nsIEditorMailSupport
39 #include "nsIFocusManager.h" // for nsIFocusManager
40 #include "nsIFormControl.h" // for nsIFormControl, etc
41 #include "nsIHTMLEditor.h" // for nsIHTMLEditor
42 #include "nsINode.h" // for nsINode, ::NODE_IS_EDITABLE, etc
43 #include "nsIPlaintextEditor.h" // for nsIPlaintextEditor, etc
44 #include "nsIPresShell.h" // for nsIPresShell
45 #include "nsISelection.h" // for nsISelection
46 #include "nsISelectionController.h" // for nsISelectionController, etc
47 #include "nsISelectionPrivate.h" // for nsISelectionPrivate
48 #include "nsITransferable.h" // for kFileMime, kHTMLMime, etc
49 #include "nsIWidget.h" // for nsIWidget
50 #include "nsLiteralString.h" // for NS_LITERAL_STRING
51 #include "nsPIWindowRoot.h" // for nsPIWindowRoot
52 #include "nsPrintfCString.h" // for nsPrintfCString
53 #include "nsServiceManagerUtils.h" // for do_GetService
54 #include "nsString.h" // for nsAutoString
55 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
56 #include "nsContentUtils.h" // for nsContentUtils, etc
57 #include "nsIBidiKeyboard.h" // for nsIBidiKeyboard
62 using namespace mozilla
;
63 using namespace mozilla::dom
;
66 DoCommandCallback(Command aCommand
, void* aData
)
68 nsIDocument
* doc
= static_cast<nsIDocument
*>(aData
);
69 nsPIDOMWindow
* win
= doc
->GetWindow();
73 nsCOMPtr
<nsPIWindowRoot
> root
= win
->GetTopWindowRoot();
78 const char* commandStr
= WidgetKeyboardEvent::GetCommandStr(aCommand
);
80 nsCOMPtr
<nsIController
> controller
;
81 root
->GetControllerForCommand(commandStr
, getter_AddRefs(controller
));
87 nsresult rv
= controller
->IsCommandEnabled(commandStr
, &commandEnabled
);
88 NS_ENSURE_SUCCESS_VOID(rv
);
90 controller
->DoCommand(commandStr
);
94 nsEditorEventListener::nsEditorEventListener()
97 , mInTransaction(false)
98 , mMouseDownOrUpConsumedByIME(false)
99 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
100 , mHaveBidiKeyboards(false)
101 , mShouldSwitchTextDirection(false)
102 , mSwitchToRTL(false)
107 nsEditorEventListener::~nsEditorEventListener()
110 NS_WARNING("We're not uninstalled");
116 nsEditorEventListener::Connect(nsEditor
* aEditor
)
118 NS_ENSURE_ARG(aEditor
);
120 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
121 nsIBidiKeyboard
* bidiKeyboard
= nsContentUtils::GetBidiKeyboard();
123 bool haveBidiKeyboards
= false;
124 bidiKeyboard
->GetHaveBidiKeyboards(&haveBidiKeyboards
);
125 mHaveBidiKeyboards
= haveBidiKeyboards
;
131 nsresult rv
= InstallToEditor();
139 nsEditorEventListener::InstallToEditor()
141 NS_PRECONDITION(mEditor
, "The caller must set mEditor");
143 nsCOMPtr
<EventTarget
> piTarget
= mEditor
->GetDOMEventTarget();
144 NS_ENSURE_TRUE(piTarget
, NS_ERROR_FAILURE
);
146 // register the event listeners with the listener manager
147 EventListenerManager
* elmP
= piTarget
->GetOrCreateListenerManager();
148 NS_ENSURE_STATE(elmP
);
150 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
151 elmP
->AddEventListenerByType(this,
152 NS_LITERAL_STRING("keydown"),
153 TrustedEventsAtSystemGroupBubble());
154 elmP
->AddEventListenerByType(this,
155 NS_LITERAL_STRING("keyup"),
156 TrustedEventsAtSystemGroupBubble());
158 elmP
->AddEventListenerByType(this,
159 NS_LITERAL_STRING("keypress"),
160 TrustedEventsAtSystemGroupBubble());
161 elmP
->AddEventListenerByType(this,
162 NS_LITERAL_STRING("dragenter"),
163 TrustedEventsAtSystemGroupBubble());
164 elmP
->AddEventListenerByType(this,
165 NS_LITERAL_STRING("dragover"),
166 TrustedEventsAtSystemGroupBubble());
167 elmP
->AddEventListenerByType(this,
168 NS_LITERAL_STRING("dragexit"),
169 TrustedEventsAtSystemGroupBubble());
170 elmP
->AddEventListenerByType(this,
171 NS_LITERAL_STRING("drop"),
172 TrustedEventsAtSystemGroupBubble());
173 // XXX We should add the mouse event listeners as system event group.
174 // E.g., web applications cannot prevent middle mouse paste by
175 // preventDefault() of click event at bubble phase.
176 // However, if we do so, all click handlers in any frames and frontend
177 // code need to check if it's editable. It makes easier create new bugs.
178 elmP
->AddEventListenerByType(this,
179 NS_LITERAL_STRING("mousedown"),
180 TrustedEventsAtCapture());
181 elmP
->AddEventListenerByType(this,
182 NS_LITERAL_STRING("mouseup"),
183 TrustedEventsAtCapture());
184 elmP
->AddEventListenerByType(this,
185 NS_LITERAL_STRING("click"),
186 TrustedEventsAtCapture());
187 // Focus event doesn't bubble so adding the listener to capturing phase.
188 // Make sure this works after bug 235441 gets fixed.
189 elmP
->AddEventListenerByType(this,
190 NS_LITERAL_STRING("blur"),
191 TrustedEventsAtCapture());
192 elmP
->AddEventListenerByType(this,
193 NS_LITERAL_STRING("focus"),
194 TrustedEventsAtCapture());
195 elmP
->AddEventListenerByType(this,
196 NS_LITERAL_STRING("text"),
197 TrustedEventsAtSystemGroupBubble());
198 elmP
->AddEventListenerByType(this,
199 NS_LITERAL_STRING("compositionstart"),
200 TrustedEventsAtSystemGroupBubble());
201 elmP
->AddEventListenerByType(this,
202 NS_LITERAL_STRING("compositionend"),
203 TrustedEventsAtSystemGroupBubble());
209 nsEditorEventListener::Disconnect()
214 UninstallFromEditor();
216 nsIFocusManager
* fm
= nsFocusManager::GetFocusManager();
218 nsCOMPtr
<nsIDOMElement
> domFocus
;
219 fm
->GetFocusedElement(getter_AddRefs(domFocus
));
220 nsCOMPtr
<nsINode
> focusedElement
= do_QueryInterface(domFocus
);
221 mozilla::dom::Element
* root
= mEditor
->GetRoot();
222 if (focusedElement
&& root
&&
223 nsContentUtils::ContentIsDescendantOf(focusedElement
, root
)) {
224 // Reset the Selection ancestor limiter and SelectionController state
225 // that nsEditor::InitializeSelection set up.
226 mEditor
->FinalizeSelection();
234 nsEditorEventListener::UninstallFromEditor()
236 nsCOMPtr
<EventTarget
> piTarget
= mEditor
->GetDOMEventTarget();
241 EventListenerManager
* elmP
= piTarget
->GetOrCreateListenerManager();
246 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
247 elmP
->RemoveEventListenerByType(this,
248 NS_LITERAL_STRING("keydown"),
249 TrustedEventsAtSystemGroupBubble());
250 elmP
->RemoveEventListenerByType(this,
251 NS_LITERAL_STRING("keyup"),
252 TrustedEventsAtSystemGroupBubble());
254 elmP
->RemoveEventListenerByType(this,
255 NS_LITERAL_STRING("keypress"),
256 TrustedEventsAtSystemGroupBubble());
257 elmP
->RemoveEventListenerByType(this,
258 NS_LITERAL_STRING("dragenter"),
259 TrustedEventsAtSystemGroupBubble());
260 elmP
->RemoveEventListenerByType(this,
261 NS_LITERAL_STRING("dragover"),
262 TrustedEventsAtSystemGroupBubble());
263 elmP
->RemoveEventListenerByType(this,
264 NS_LITERAL_STRING("dragexit"),
265 TrustedEventsAtSystemGroupBubble());
266 elmP
->RemoveEventListenerByType(this,
267 NS_LITERAL_STRING("drop"),
268 TrustedEventsAtSystemGroupBubble());
269 elmP
->RemoveEventListenerByType(this,
270 NS_LITERAL_STRING("mousedown"),
271 TrustedEventsAtCapture());
272 elmP
->RemoveEventListenerByType(this,
273 NS_LITERAL_STRING("mouseup"),
274 TrustedEventsAtCapture());
275 elmP
->RemoveEventListenerByType(this,
276 NS_LITERAL_STRING("click"),
277 TrustedEventsAtCapture());
278 elmP
->RemoveEventListenerByType(this,
279 NS_LITERAL_STRING("blur"),
280 TrustedEventsAtCapture());
281 elmP
->RemoveEventListenerByType(this,
282 NS_LITERAL_STRING("focus"),
283 TrustedEventsAtCapture());
284 elmP
->RemoveEventListenerByType(this,
285 NS_LITERAL_STRING("text"),
286 TrustedEventsAtSystemGroupBubble());
287 elmP
->RemoveEventListenerByType(this,
288 NS_LITERAL_STRING("compositionstart"),
289 TrustedEventsAtSystemGroupBubble());
290 elmP
->RemoveEventListenerByType(this,
291 NS_LITERAL_STRING("compositionend"),
292 TrustedEventsAtSystemGroupBubble());
295 already_AddRefed
<nsIPresShell
>
296 nsEditorEventListener::GetPresShell()
298 NS_PRECONDITION(mEditor
,
299 "The caller must check whether this is connected to an editor");
300 return mEditor
->GetPresShell();
304 nsEditorEventListener::GetPresContext()
306 nsCOMPtr
<nsIPresShell
> presShell
= GetPresShell();
307 return presShell
? presShell
->GetPresContext() : nullptr;
311 nsEditorEventListener::GetFocusedRootContent()
313 NS_ENSURE_TRUE(mEditor
, nullptr);
315 nsCOMPtr
<nsIContent
> focusedContent
= mEditor
->GetFocusedContent();
316 if (!focusedContent
) {
320 nsIDocument
* composedDoc
= focusedContent
->GetComposedDoc();
321 NS_ENSURE_TRUE(composedDoc
, nullptr);
323 return composedDoc
->HasFlag(NODE_IS_EDITABLE
) ? nullptr : focusedContent
;
327 nsEditorEventListener::EditorHasFocus()
329 NS_PRECONDITION(mEditor
,
330 "The caller must check whether this is connected to an editor");
331 nsCOMPtr
<nsIContent
> focusedContent
= mEditor
->GetFocusedContent();
332 if (!focusedContent
) {
335 nsIDocument
* composedDoc
= focusedContent
->GetComposedDoc();
336 return !!composedDoc
;
340 * nsISupports implementation
343 NS_IMPL_ISUPPORTS(nsEditorEventListener
, nsIDOMEventListener
)
346 * nsIDOMEventListener implementation
350 nsEditorEventListener::HandleEvent(nsIDOMEvent
* aEvent
)
352 NS_ENSURE_TRUE(mEditor
, NS_ERROR_FAILURE
);
354 nsCOMPtr
<nsIEditor
> kungFuDeathGrip
= mEditor
;
356 WidgetEvent
* internalEvent
= aEvent
->GetInternalNSEvent();
358 // Let's handle each event with the message of the internal event of the
359 // coming event. If the DOM event was created with improper interface,
360 // e.g., keydown event is created with |new MouseEvent("keydown", {});|,
361 // its message is always 0. Therefore, we can ban such strange event easy.
362 // However, we need to handle strange "focus" and "blur" event. See the
363 // following code of this switch statement.
364 // NOTE: Each event handler may require specific event interface. Before
365 // calling it, this queries the specific interface. If it would fail,
366 // each event handler would just ignore the event. So, in this method,
367 // you don't need to check if the QI succeeded before each call.
368 switch (internalEvent
->message
) {
370 case NS_DRAGDROP_ENTER
: {
371 nsCOMPtr
<nsIDOMDragEvent
> dragEvent
= do_QueryInterface(aEvent
);
372 return DragEnter(dragEvent
);
375 case NS_DRAGDROP_OVER_SYNTH
: {
376 nsCOMPtr
<nsIDOMDragEvent
> dragEvent
= do_QueryInterface(aEvent
);
377 return DragOver(dragEvent
);
380 case NS_DRAGDROP_EXIT_SYNTH
: {
381 nsCOMPtr
<nsIDOMDragEvent
> dragEvent
= do_QueryInterface(aEvent
);
382 return DragExit(dragEvent
);
385 case NS_DRAGDROP_DROP
: {
386 nsCOMPtr
<nsIDOMDragEvent
> dragEvent
= do_QueryInterface(aEvent
);
387 return Drop(dragEvent
);
389 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
392 nsCOMPtr
<nsIDOMKeyEvent
> keyEvent
= do_QueryInterface(aEvent
);
393 return KeyDown(keyEvent
);
397 nsCOMPtr
<nsIDOMKeyEvent
> keyEvent
= do_QueryInterface(aEvent
);
398 return KeyUp(keyEvent
);
400 #endif // #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
403 nsCOMPtr
<nsIDOMKeyEvent
> keyEvent
= do_QueryInterface(aEvent
);
404 return KeyPress(keyEvent
);
407 case NS_MOUSE_BUTTON_DOWN
: {
408 nsCOMPtr
<nsIDOMMouseEvent
> mouseEvent
= do_QueryInterface(aEvent
);
409 NS_ENSURE_TRUE(mouseEvent
, NS_OK
);
410 // nsEditorEventListener may receive (1) all mousedown, mouseup and click
411 // events, (2) only mousedown event or (3) only mouseup event.
412 // mMouseDownOrUpConsumedByIME is used only for ignoring click event if
413 // preceding mousedown and/or mouseup event is consumed by IME.
414 // Therefore, even if case #2 or case #3 occurs,
415 // mMouseDownOrUpConsumedByIME is true here. Therefore, we should always
416 // overwrite it here.
417 mMouseDownOrUpConsumedByIME
= NotifyIMEOfMouseButtonEvent(mouseEvent
);
418 return mMouseDownOrUpConsumedByIME
? NS_OK
: MouseDown(mouseEvent
);
421 case NS_MOUSE_BUTTON_UP
: {
422 nsCOMPtr
<nsIDOMMouseEvent
> mouseEvent
= do_QueryInterface(aEvent
);
423 NS_ENSURE_TRUE(mouseEvent
, NS_OK
);
424 // See above comment in the NS_MOUSE_BUTTON_DOWN case, first.
425 // This code assumes that case #1 is occuring. However, if case #3 may
426 // occurs after case #2 and the mousedown is consumed,
427 // mMouseDownOrUpConsumedByIME is true even though nsEditorEventListener
428 // has not received the preceding mousedown event of this mouseup event.
429 // So, mMouseDownOrUpConsumedByIME may be invalid here. However,
430 // this is not a matter because mMouseDownOrUpConsumedByIME is referred
431 // only by NS_MOUSE_CLICK case but click event is fired only in case #1.
432 // So, before a click event is fired, mMouseDownOrUpConsumedByIME is
433 // always initialized in the NS_MOUSE_BUTTON_DOWN case if it's referred.
434 if (NotifyIMEOfMouseButtonEvent(mouseEvent
)) {
435 mMouseDownOrUpConsumedByIME
= true;
437 return mMouseDownOrUpConsumedByIME
? NS_OK
: MouseUp(mouseEvent
);
440 case NS_MOUSE_CLICK
: {
441 nsCOMPtr
<nsIDOMMouseEvent
> mouseEvent
= do_QueryInterface(aEvent
);
442 NS_ENSURE_TRUE(mouseEvent
, NS_OK
);
443 // If the preceding mousedown event or mouseup event was consumed,
444 // editor shouldn't handle this click event.
445 if (mMouseDownOrUpConsumedByIME
) {
446 mMouseDownOrUpConsumedByIME
= false;
447 mouseEvent
->PreventDefault();
450 return MouseClick(mouseEvent
);
453 case NS_FOCUS_CONTENT
:
454 return Focus(aEvent
);
456 case NS_BLUR_CONTENT
:
460 return HandleText(aEvent
);
462 case NS_COMPOSITION_START
:
463 return HandleStartComposition(aEvent
);
465 case NS_COMPOSITION_END
:
466 HandleEndComposition(aEvent
);
470 nsAutoString eventType
;
471 aEvent
->GetType(eventType
);
472 // We should accept "focus" and "blur" event even if it's synthesized with
473 // wrong interface for compatibility with older Gecko.
474 if (eventType
.EqualsLiteral("focus")) {
475 return Focus(aEvent
);
477 if (eventType
.EqualsLiteral("blur")) {
481 nsPrintfCString
assertMessage("Editor doesn't handle \"%s\" event "
482 "because its internal event doesn't have proper message",
483 NS_ConvertUTF16toUTF8(eventType
).get());
484 NS_ASSERTION(false, assertMessage
.get());
490 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
492 // Undo the windows.h damage
497 #undef RemoveDirectory
502 // This function is borrowed from Chromium's ImeInput::IsCtrlShiftPressed
503 bool IsCtrlShiftPressed(bool& isRTL
)
506 if (!::GetKeyboardState(keystate
)) {
510 // To check if a user is pressing only a control key and a right-shift key
511 // (or a left-shift key), we use the steps below:
512 // 1. Check if a user is pressing a control key and a right-shift key (or
513 // a left-shift key).
514 // 2. If the condition 1 is true, we should check if there are any other
515 // keys pressed at the same time.
516 // To ignore the keys checked in 1, we set their status to 0 before
517 // checking the key status.
518 const int kKeyDownMask
= 0x80;
519 if ((keystate
[VK_CONTROL
] & kKeyDownMask
) == 0) {
523 if (keystate
[VK_RSHIFT
] & kKeyDownMask
) {
524 keystate
[VK_RSHIFT
] = 0;
526 } else if (keystate
[VK_LSHIFT
] & kKeyDownMask
) {
527 keystate
[VK_LSHIFT
] = 0;
533 // Scan the key status to find pressed keys. We should abandon changing the
534 // text direction when there are other pressed keys.
535 // This code is executed only when a user is pressing a control key and a
536 // right-shift key (or a left-shift key), i.e. we should ignore the status of
537 // the keys: VK_SHIFT, VK_CONTROL, VK_RCONTROL, and VK_LCONTROL.
538 // So, we reset their status to 0 and ignore them.
539 keystate
[VK_SHIFT
] = 0;
540 keystate
[VK_CONTROL
] = 0;
541 keystate
[VK_RCONTROL
] = 0;
542 keystate
[VK_LCONTROL
] = 0;
543 for (int i
= 0; i
<= VK_PACKET
; ++i
) {
544 if (keystate
[i
] & kKeyDownMask
) {
553 // This logic is mostly borrowed from Chromium's
554 // RenderWidgetHostViewWin::OnKeyEvent.
557 nsEditorEventListener::KeyUp(nsIDOMKeyEvent
* aKeyEvent
)
559 NS_ENSURE_TRUE(aKeyEvent
, NS_OK
);
561 if (!mHaveBidiKeyboards
) {
565 uint32_t keyCode
= 0;
566 aKeyEvent
->GetKeyCode(&keyCode
);
567 if ((keyCode
== nsIDOMKeyEvent::DOM_VK_SHIFT
||
568 keyCode
== nsIDOMKeyEvent::DOM_VK_CONTROL
) &&
569 mShouldSwitchTextDirection
&& mEditor
->IsPlaintextEditor()) {
570 mEditor
->SwitchTextDirectionTo(mSwitchToRTL
?
571 nsIPlaintextEditor::eEditorRightToLeft
:
572 nsIPlaintextEditor::eEditorLeftToRight
);
573 mShouldSwitchTextDirection
= false;
579 nsEditorEventListener::KeyDown(nsIDOMKeyEvent
* aKeyEvent
)
581 NS_ENSURE_TRUE(aKeyEvent
, NS_OK
);
583 if (!mHaveBidiKeyboards
) {
587 uint32_t keyCode
= 0;
588 aKeyEvent
->GetKeyCode(&keyCode
);
589 if (keyCode
== nsIDOMKeyEvent::DOM_VK_SHIFT
) {
591 if (IsCtrlShiftPressed(switchToRTL
)) {
592 mShouldSwitchTextDirection
= true;
593 mSwitchToRTL
= switchToRTL
;
595 } else if (keyCode
!= nsIDOMKeyEvent::DOM_VK_CONTROL
) {
596 // In case the user presses any other key besides Ctrl and Shift
597 mShouldSwitchTextDirection
= false;
604 nsEditorEventListener::KeyPress(nsIDOMKeyEvent
* aKeyEvent
)
606 NS_ENSURE_TRUE(aKeyEvent
, NS_OK
);
608 if (!mEditor
->IsAcceptableInputEvent(aKeyEvent
)) {
612 // DOM event handling happens in two passes, the client pass and the system
613 // pass. We do all of our processing in the system pass, to allow client
614 // handlers the opportunity to cancel events and prevent typing in the editor.
615 // If the client pass cancelled the event, defaultPrevented will be true
618 bool defaultPrevented
;
619 aKeyEvent
->GetDefaultPrevented(&defaultPrevented
);
620 if (defaultPrevented
) {
624 nsresult rv
= mEditor
->HandleKeyPressEvent(aKeyEvent
);
625 NS_ENSURE_SUCCESS(rv
, rv
);
627 aKeyEvent
->GetDefaultPrevented(&defaultPrevented
);
628 if (defaultPrevented
) {
632 if (!ShouldHandleNativeKeyBindings(aKeyEvent
)) {
636 // Now, ask the native key bindings to handle the event.
637 WidgetKeyboardEvent
* keyEvent
=
638 aKeyEvent
->GetInternalNSEvent()->AsKeyboardEvent();
640 "DOM key event's internal event must be WidgetKeyboardEvent");
641 nsIWidget
* widget
= keyEvent
->widget
;
642 // If the event is created by chrome script, the widget is always nullptr.
644 nsCOMPtr
<nsIPresShell
> ps
= GetPresShell();
645 nsPresContext
* pc
= ps
? ps
->GetPresContext() : nullptr;
646 widget
= pc
? pc
->GetNearestWidget() : nullptr;
647 NS_ENSURE_TRUE(widget
, NS_OK
);
650 nsCOMPtr
<nsIDocument
> doc
= mEditor
->GetDocument();
651 bool handled
= widget
->ExecuteNativeKeyBinding(
652 nsIWidget::NativeKeyBindingsForRichTextEditor
,
653 *keyEvent
, DoCommandCallback
, doc
);
655 aKeyEvent
->PreventDefault();
661 nsEditorEventListener::MouseClick(nsIDOMMouseEvent
* aMouseEvent
)
663 // nothing to do if editor isn't editable or clicked on out of the editor.
664 if (mEditor
->IsReadonly() || mEditor
->IsDisabled() ||
665 !mEditor
->IsAcceptableInputEvent(aMouseEvent
)) {
669 // Notifies clicking on editor to IMEStateManager even when the event was
671 if (EditorHasFocus()) {
672 nsPresContext
* presContext
= GetPresContext();
674 IMEStateManager::OnClickInEditor(presContext
, GetFocusedRootContent(),
680 nsresult rv
= aMouseEvent
->GetDefaultPrevented(&preventDefault
);
681 if (NS_FAILED(rv
) || preventDefault
) {
682 // We're done if 'preventdefault' is true (see for example bug 70698).
686 // If we got a mouse down inside the editing area, we should force the
687 // IME to commit before we change the cursor position
688 mEditor
->ForceCompositionEnd();
691 aMouseEvent
->GetButton(&button
);
693 return HandleMiddleClickPaste(aMouseEvent
);
699 nsEditorEventListener::HandleMiddleClickPaste(nsIDOMMouseEvent
* aMouseEvent
)
701 if (!Preferences::GetBool("middlemouse.paste", false)) {
702 // Middle click paste isn't enabled.
706 // Set the selection to the point under the mouse cursor:
707 nsCOMPtr
<nsIDOMNode
> parent
;
708 if (NS_FAILED(aMouseEvent
->GetRangeParent(getter_AddRefs(parent
)))) {
709 return NS_ERROR_NULL_POINTER
;
712 if (NS_FAILED(aMouseEvent
->GetRangeOffset(&offset
))) {
713 return NS_ERROR_NULL_POINTER
;
716 nsCOMPtr
<nsISelection
> selection
;
717 if (NS_SUCCEEDED(mEditor
->GetSelection(getter_AddRefs(selection
)))) {
718 selection
->Collapse(parent
, offset
);
721 // If the ctrl key is pressed, we'll do paste as quotation.
722 // Would've used the alt key, but the kde wmgr treats alt-middle specially.
723 bool ctrlKey
= false;
724 aMouseEvent
->GetCtrlKey(&ctrlKey
);
726 nsCOMPtr
<nsIEditorMailSupport
> mailEditor
;
728 mailEditor
= do_QueryObject(mEditor
);
732 int32_t clipboard
= nsIClipboard::kGlobalClipboard
;
733 nsCOMPtr
<nsIClipboard
> clipboardService
=
734 do_GetService("@mozilla.org/widget/clipboard;1", &rv
);
735 if (NS_SUCCEEDED(rv
)) {
736 bool selectionSupported
;
737 rv
= clipboardService
->SupportsSelectionClipboard(&selectionSupported
);
738 if (NS_SUCCEEDED(rv
) && selectionSupported
) {
739 clipboard
= nsIClipboard::kSelectionClipboard
;
744 mailEditor
->PasteAsQuotation(clipboard
);
746 mEditor
->Paste(clipboard
);
749 // Prevent the event from propagating up to be possibly handled
750 // again by the containing window:
751 aMouseEvent
->StopPropagation();
752 aMouseEvent
->PreventDefault();
754 // We processed the event, whether drop/paste succeeded or not
759 nsEditorEventListener::NotifyIMEOfMouseButtonEvent(
760 nsIDOMMouseEvent
* aMouseEvent
)
762 if (!EditorHasFocus()) {
766 bool defaultPrevented
;
767 nsresult rv
= aMouseEvent
->GetDefaultPrevented(&defaultPrevented
);
768 NS_ENSURE_SUCCESS(rv
, false);
769 if (defaultPrevented
) {
772 nsPresContext
* presContext
= GetPresContext();
773 NS_ENSURE_TRUE(presContext
, false);
774 return IMEStateManager::OnMouseButtonEventInEditor(presContext
,
775 GetFocusedRootContent(),
780 nsEditorEventListener::MouseDown(nsIDOMMouseEvent
* aMouseEvent
)
782 mEditor
->ForceCompositionEnd();
787 nsEditorEventListener::HandleText(nsIDOMEvent
* aTextEvent
)
789 if (!mEditor
->IsAcceptableInputEvent(aTextEvent
)) {
793 // if we are readonly or disabled, then do nothing.
794 if (mEditor
->IsReadonly() || mEditor
->IsDisabled()) {
798 return mEditor
->UpdateIMEComposition(aTextEvent
);
802 * Drag event implementation
806 nsEditorEventListener::DragEnter(nsIDOMDragEvent
* aDragEvent
)
808 NS_ENSURE_TRUE(aDragEvent
, NS_OK
);
810 nsCOMPtr
<nsIPresShell
> presShell
= GetPresShell();
811 NS_ENSURE_TRUE(presShell
, NS_OK
);
814 mCaret
= new nsCaret();
815 mCaret
->Init(presShell
);
816 mCaret
->SetCaretReadOnly(true);
819 presShell
->SetCaret(mCaret
);
821 return DragOver(aDragEvent
);
825 nsEditorEventListener::DragOver(nsIDOMDragEvent
* aDragEvent
)
827 NS_ENSURE_TRUE(aDragEvent
, NS_OK
);
829 nsCOMPtr
<nsIDOMNode
> parent
;
830 bool defaultPrevented
;
831 aDragEvent
->GetDefaultPrevented(&defaultPrevented
);
832 if (defaultPrevented
) {
836 aDragEvent
->GetRangeParent(getter_AddRefs(parent
));
837 nsCOMPtr
<nsIContent
> dropParent
= do_QueryInterface(parent
);
838 NS_ENSURE_TRUE(dropParent
, NS_ERROR_FAILURE
);
840 if (dropParent
->IsEditable() && CanDrop(aDragEvent
)) {
841 aDragEvent
->PreventDefault(); // consumed
848 nsresult rv
= aDragEvent
->GetRangeOffset(&offset
);
849 NS_ENSURE_SUCCESS(rv
, rv
);
851 mCaret
->SetVisible(true);
852 mCaret
->SetCaretPosition(parent
, offset
);
857 if (!IsFileControlTextBox()) {
858 // This is needed when dropping on an input, to prevent the editor for
859 // the editable parent from receiving the event.
860 aDragEvent
->StopPropagation();
864 mCaret
->SetVisible(false);
870 nsEditorEventListener::CleanupDragDropCaret()
876 mCaret
->SetVisible(false); // hide it, so that it turns off its timer
878 nsCOMPtr
<nsIPresShell
> presShell
= GetPresShell();
880 nsCOMPtr
<nsISelectionController
> selCon(do_QueryInterface(presShell
));
882 selCon
->SetCaretEnabled(false);
884 presShell
->RestoreCaret();
892 nsEditorEventListener::DragExit(nsIDOMDragEvent
* aDragEvent
)
894 NS_ENSURE_TRUE(aDragEvent
, NS_OK
);
896 CleanupDragDropCaret();
902 nsEditorEventListener::Drop(nsIDOMDragEvent
* aDragEvent
)
904 NS_ENSURE_TRUE(aDragEvent
, NS_OK
);
906 CleanupDragDropCaret();
908 bool defaultPrevented
;
909 aDragEvent
->GetDefaultPrevented(&defaultPrevented
);
910 if (defaultPrevented
) {
914 nsCOMPtr
<nsIDOMNode
> parent
;
915 aDragEvent
->GetRangeParent(getter_AddRefs(parent
));
916 nsCOMPtr
<nsIContent
> dropParent
= do_QueryInterface(parent
);
917 NS_ENSURE_TRUE(dropParent
, NS_ERROR_FAILURE
);
919 if (!dropParent
->IsEditable() || !CanDrop(aDragEvent
)) {
920 // was it because we're read-only?
921 if ((mEditor
->IsReadonly() || mEditor
->IsDisabled()) &&
922 !IsFileControlTextBox()) {
923 // it was decided to "eat" the event as this is the "least surprise"
924 // since someone else handling it might be unintentional and the
925 // user could probably re-drag to be not over the disabled/readonly
926 // editfields if that is what is desired.
927 return aDragEvent
->StopPropagation();
932 aDragEvent
->StopPropagation();
933 aDragEvent
->PreventDefault();
934 return mEditor
->InsertFromDrop(aDragEvent
);
938 nsEditorEventListener::CanDrop(nsIDOMDragEvent
* aEvent
)
940 // if the target doc is read-only, we can't drop
941 if (mEditor
->IsReadonly() || mEditor
->IsDisabled()) {
945 nsCOMPtr
<nsIDOMDataTransfer
> domDataTransfer
;
946 aEvent
->GetDataTransfer(getter_AddRefs(domDataTransfer
));
947 nsCOMPtr
<DataTransfer
> dataTransfer
= do_QueryInterface(domDataTransfer
);
948 NS_ENSURE_TRUE(dataTransfer
, false);
950 nsRefPtr
<DOMStringList
> types
= dataTransfer
->Types();
952 // Plaintext editors only support dropping text. Otherwise, HTML and files
953 // can be dropped as well.
954 if (!types
->Contains(NS_LITERAL_STRING(kTextMime
)) &&
955 !types
->Contains(NS_LITERAL_STRING(kMozTextInternal
)) &&
956 (mEditor
->IsPlaintextEditor() ||
957 (!types
->Contains(NS_LITERAL_STRING(kHTMLMime
)) &&
958 !types
->Contains(NS_LITERAL_STRING(kFileMime
))))) {
962 // If there is no source node, this is probably an external drag and the
963 // drop is allowed. The later checks rely on checking if the drag target
964 // is the same as the drag source.
965 nsCOMPtr
<nsIDOMNode
> sourceNode
;
966 dataTransfer
->GetMozSourceNode(getter_AddRefs(sourceNode
));
971 // There is a source node, so compare the source documents and this document.
972 // Disallow drops on the same document.
974 nsCOMPtr
<nsIDOMDocument
> domdoc
= mEditor
->GetDOMDocument();
975 NS_ENSURE_TRUE(domdoc
, false);
977 nsCOMPtr
<nsIDOMDocument
> sourceDoc
;
978 nsresult rv
= sourceNode
->GetOwnerDocument(getter_AddRefs(sourceDoc
));
979 NS_ENSURE_SUCCESS(rv
, false);
981 // If the source and the dest are not same document, allow to drop it always.
982 if (domdoc
!= sourceDoc
) {
986 nsCOMPtr
<nsISelection
> selection
;
987 rv
= mEditor
->GetSelection(getter_AddRefs(selection
));
988 if (NS_FAILED(rv
) || !selection
) {
992 // If selection is collapsed, allow to drop it always.
993 if (selection
->Collapsed()) {
997 nsCOMPtr
<nsIDOMNode
> parent
;
998 rv
= aEvent
->GetRangeParent(getter_AddRefs(parent
));
999 if (NS_FAILED(rv
) || !parent
) {
1004 rv
= aEvent
->GetRangeOffset(&offset
);
1005 NS_ENSURE_SUCCESS(rv
, false);
1008 rv
= selection
->GetRangeCount(&rangeCount
);
1009 NS_ENSURE_SUCCESS(rv
, false);
1011 for (int32_t i
= 0; i
< rangeCount
; i
++) {
1012 nsCOMPtr
<nsIDOMRange
> range
;
1013 rv
= selection
->GetRangeAt(i
, getter_AddRefs(range
));
1014 if (NS_FAILED(rv
) || !range
) {
1015 // Don't bail yet, iterate through them all
1019 bool inRange
= true;
1020 range
->IsPointInRange(parent
, offset
, &inRange
);
1022 // Okay, now you can bail, we are over the orginal selection
1030 nsEditorEventListener::HandleStartComposition(nsIDOMEvent
* aCompositionEvent
)
1032 if (!mEditor
->IsAcceptableInputEvent(aCompositionEvent
)) {
1035 WidgetCompositionEvent
* compositionStart
=
1036 aCompositionEvent
->GetInternalNSEvent()->AsCompositionEvent();
1037 return mEditor
->BeginIMEComposition(compositionStart
);
1041 nsEditorEventListener::HandleEndComposition(nsIDOMEvent
* aCompositionEvent
)
1043 if (!mEditor
->IsAcceptableInputEvent(aCompositionEvent
)) {
1047 mEditor
->EndIMEComposition();
1051 nsEditorEventListener::Focus(nsIDOMEvent
* aEvent
)
1053 NS_ENSURE_TRUE(aEvent
, NS_OK
);
1055 // Don't turn on selection and caret when the editor is disabled.
1056 if (mEditor
->IsDisabled()) {
1060 // Spell check a textarea the first time that it is focused.
1061 SpellCheckIfNeeded();
1063 nsCOMPtr
<nsIDOMEventTarget
> target
;
1064 aEvent
->GetTarget(getter_AddRefs(target
));
1065 nsCOMPtr
<nsINode
> node
= do_QueryInterface(target
);
1066 NS_ENSURE_TRUE(node
, NS_ERROR_UNEXPECTED
);
1068 // If the target is a document node but it's not editable, we should ignore
1069 // it because actual focused element's event is going to come.
1070 if (node
->IsNodeOfType(nsINode::eDOCUMENT
) &&
1071 !node
->HasFlag(NODE_IS_EDITABLE
)) {
1075 if (node
->IsNodeOfType(nsINode::eCONTENT
)) {
1076 // XXX If the focus event target is a form control in contenteditable
1077 // element, perhaps, the parent HTML editor should do nothing by this
1078 // handler. However, FindSelectionRoot() returns the root element of the
1079 // contenteditable editor. So, the editableRoot value is invalid for
1080 // the plain text editor, and it will be set to the wrong limiter of
1081 // the selection. However, fortunately, actual bugs are not found yet.
1082 nsCOMPtr
<nsIContent
> editableRoot
= mEditor
->FindSelectionRoot(node
);
1084 // make sure that the element is really focused in case an earlier
1085 // listener in the chain changed the focus.
1087 nsIFocusManager
* fm
= nsFocusManager::GetFocusManager();
1088 NS_ENSURE_TRUE(fm
, NS_OK
);
1090 nsCOMPtr
<nsIDOMElement
> element
;
1091 fm
->GetFocusedElement(getter_AddRefs(element
));
1092 if (!SameCOMIdentity(element
, target
)) {
1098 mEditor
->OnFocus(target
);
1100 nsCOMPtr
<nsIPresShell
> ps
= GetPresShell();
1101 NS_ENSURE_TRUE(ps
, NS_OK
);
1102 nsCOMPtr
<nsIContent
> focusedContent
= mEditor
->GetFocusedContentForIME();
1103 IMEStateManager::OnFocusInEditor(ps
->GetPresContext(), focusedContent
);
1109 nsEditorEventListener::Blur(nsIDOMEvent
* aEvent
)
1111 NS_ENSURE_TRUE(aEvent
, NS_OK
);
1113 // check if something else is focused. If another element is focused, then
1114 // we should not change the selection.
1115 nsIFocusManager
* fm
= nsFocusManager::GetFocusManager();
1116 NS_ENSURE_TRUE(fm
, NS_OK
);
1118 nsCOMPtr
<nsIDOMElement
> element
;
1119 fm
->GetFocusedElement(getter_AddRefs(element
));
1121 mEditor
->FinalizeSelection();
1127 nsEditorEventListener::SpellCheckIfNeeded()
1129 // If the spell check skip flag is still enabled from creation time,
1130 // disable it because focused editors are allowed to spell check.
1131 uint32_t currentFlags
= 0;
1132 mEditor
->GetFlags(¤tFlags
);
1133 if(currentFlags
& nsIPlaintextEditor::eEditorSkipSpellCheck
) {
1134 currentFlags
^= nsIPlaintextEditor::eEditorSkipSpellCheck
;
1135 mEditor
->SetFlags(currentFlags
);
1140 nsEditorEventListener::IsFileControlTextBox()
1142 dom::Element
* root
= mEditor
->GetRoot();
1143 if (!root
|| !root
->ChromeOnlyAccess()) {
1146 nsIContent
* parent
= root
->FindFirstNonChromeOnlyAccessContent();
1147 if (!parent
|| !parent
->IsHTML(nsGkAtoms::input
)) {
1150 nsCOMPtr
<nsIFormControl
> formControl
= do_QueryInterface(parent
);
1151 return formControl
->GetType() == NS_FORM_INPUT_FILE
;
1155 nsEditorEventListener::ShouldHandleNativeKeyBindings(nsIDOMKeyEvent
* aKeyEvent
)
1157 // Only return true if the target of the event is a desendant of the active
1158 // editing host in order to match the similar decision made in
1159 // nsXBLWindowKeyHandler.
1160 // Note that IsAcceptableInputEvent doesn't check for the active editing
1161 // host for keyboard events, otherwise this check would have been
1162 // unnecessary. IsAcceptableInputEvent currently makes a similar check for
1165 nsCOMPtr
<nsIDOMEventTarget
> target
;
1166 aKeyEvent
->GetTarget(getter_AddRefs(target
));
1167 nsCOMPtr
<nsIContent
> targetContent
= do_QueryInterface(target
);
1168 if (!targetContent
) {
1172 nsCOMPtr
<nsIHTMLEditor
> htmlEditor
=
1173 do_QueryInterface(static_cast<nsIEditor
*>(mEditor
));
1178 nsCOMPtr
<nsIDocument
> doc
= mEditor
->GetDocument();
1179 if (doc
->HasFlag(NODE_IS_EDITABLE
)) {
1180 // Don't need to perform any checks in designMode documents.
1184 nsIContent
* editingHost
= htmlEditor
->GetActiveEditingHost();
1189 return nsContentUtils::ContentIsDescendantOf(targetContent
, editingHost
);