Bumping manifests a=b2g-bump
[gecko.git] / editor / libeditor / nsEditorEventListener.cpp
blob7a81a89b92fe526d870b7419097ed5b3c577cfe1
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
23 #include "nsID.h"
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
58 #endif
60 class nsPresContext;
62 using namespace mozilla;
63 using namespace mozilla::dom;
65 static void
66 DoCommandCallback(Command aCommand, void* aData)
68 nsIDocument* doc = static_cast<nsIDocument*>(aData);
69 nsPIDOMWindow* win = doc->GetWindow();
70 if (!win) {
71 return;
73 nsCOMPtr<nsPIWindowRoot> root = win->GetTopWindowRoot();
74 if (!root) {
75 return;
78 const char* commandStr = WidgetKeyboardEvent::GetCommandStr(aCommand);
80 nsCOMPtr<nsIController> controller;
81 root->GetControllerForCommand(commandStr, getter_AddRefs(controller));
82 if (!controller) {
83 return;
86 bool commandEnabled;
87 nsresult rv = controller->IsCommandEnabled(commandStr, &commandEnabled);
88 NS_ENSURE_SUCCESS_VOID(rv);
89 if (commandEnabled) {
90 controller->DoCommand(commandStr);
94 nsEditorEventListener::nsEditorEventListener()
95 : mEditor(nullptr)
96 , mCommitText(false)
97 , mInTransaction(false)
98 , mMouseDownOrUpConsumedByIME(false)
99 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
100 , mHaveBidiKeyboards(false)
101 , mShouldSwitchTextDirection(false)
102 , mSwitchToRTL(false)
103 #endif
107 nsEditorEventListener::~nsEditorEventListener()
109 if (mEditor) {
110 NS_WARNING("We're not uninstalled");
111 Disconnect();
115 nsresult
116 nsEditorEventListener::Connect(nsEditor* aEditor)
118 NS_ENSURE_ARG(aEditor);
120 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
121 nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard();
122 if (bidiKeyboard) {
123 bool haveBidiKeyboards = false;
124 bidiKeyboard->GetHaveBidiKeyboards(&haveBidiKeyboards);
125 mHaveBidiKeyboards = haveBidiKeyboards;
127 #endif
129 mEditor = aEditor;
131 nsresult rv = InstallToEditor();
132 if (NS_FAILED(rv)) {
133 Disconnect();
135 return rv;
138 nsresult
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());
157 #endif
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());
205 return NS_OK;
208 void
209 nsEditorEventListener::Disconnect()
211 if (!mEditor) {
212 return;
214 UninstallFromEditor();
216 nsIFocusManager* fm = nsFocusManager::GetFocusManager();
217 if (fm) {
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();
230 mEditor = nullptr;
233 void
234 nsEditorEventListener::UninstallFromEditor()
236 nsCOMPtr<EventTarget> piTarget = mEditor->GetDOMEventTarget();
237 if (!piTarget) {
238 return;
241 EventListenerManager* elmP = piTarget->GetOrCreateListenerManager();
242 if (!elmP) {
243 return;
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());
253 #endif
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();
303 nsPresContext*
304 nsEditorEventListener::GetPresContext()
306 nsCOMPtr<nsIPresShell> presShell = GetPresShell();
307 return presShell ? presShell->GetPresContext() : nullptr;
310 nsIContent*
311 nsEditorEventListener::GetFocusedRootContent()
313 NS_ENSURE_TRUE(mEditor, nullptr);
315 nsCOMPtr<nsIContent> focusedContent = mEditor->GetFocusedContent();
316 if (!focusedContent) {
317 return nullptr;
320 nsIDocument* composedDoc = focusedContent->GetComposedDoc();
321 NS_ENSURE_TRUE(composedDoc, nullptr);
323 return composedDoc->HasFlag(NODE_IS_EDITABLE) ? nullptr : focusedContent;
326 bool
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) {
333 return false;
335 nsIDocument* composedDoc = focusedContent->GetComposedDoc();
336 return !!composedDoc;
340 * nsISupports implementation
343 NS_IMPL_ISUPPORTS(nsEditorEventListener, nsIDOMEventListener)
346 * nsIDOMEventListener implementation
349 NS_IMETHODIMP
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) {
369 // dragenter
370 case NS_DRAGDROP_ENTER: {
371 nsCOMPtr<nsIDOMDragEvent> dragEvent = do_QueryInterface(aEvent);
372 return DragEnter(dragEvent);
374 // dragover
375 case NS_DRAGDROP_OVER_SYNTH: {
376 nsCOMPtr<nsIDOMDragEvent> dragEvent = do_QueryInterface(aEvent);
377 return DragOver(dragEvent);
379 // dragexit
380 case NS_DRAGDROP_EXIT_SYNTH: {
381 nsCOMPtr<nsIDOMDragEvent> dragEvent = do_QueryInterface(aEvent);
382 return DragExit(dragEvent);
384 // drop
385 case NS_DRAGDROP_DROP: {
386 nsCOMPtr<nsIDOMDragEvent> dragEvent = do_QueryInterface(aEvent);
387 return Drop(dragEvent);
389 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
390 // keydown
391 case NS_KEY_DOWN: {
392 nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent);
393 return KeyDown(keyEvent);
395 // keyup
396 case NS_KEY_UP: {
397 nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent);
398 return KeyUp(keyEvent);
400 #endif // #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
401 // keypress
402 case NS_KEY_PRESS: {
403 nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent);
404 return KeyPress(keyEvent);
406 // mousedown
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);
420 // mouseup
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);
439 // click
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();
448 return NS_OK;
450 return MouseClick(mouseEvent);
452 // focus
453 case NS_FOCUS_CONTENT:
454 return Focus(aEvent);
455 // blur
456 case NS_BLUR_CONTENT:
457 return Blur(aEvent);
458 // text
459 case NS_TEXT_TEXT:
460 return HandleText(aEvent);
461 // compositionstart
462 case NS_COMPOSITION_START:
463 return HandleStartComposition(aEvent);
464 // compositionend
465 case NS_COMPOSITION_END:
466 HandleEndComposition(aEvent);
467 return NS_OK;
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")) {
478 return Blur(aEvent);
480 #ifdef DEBUG
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());
485 #endif
487 return NS_OK;
490 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
491 #include <windows.h>
492 // Undo the windows.h damage
493 #undef GetMessage
494 #undef CreateEvent
495 #undef GetClassName
496 #undef GetBinaryType
497 #undef RemoveDirectory
498 #undef SetProp
500 namespace {
502 // This function is borrowed from Chromium's ImeInput::IsCtrlShiftPressed
503 bool IsCtrlShiftPressed(bool& isRTL)
505 BYTE keystate[256];
506 if (!::GetKeyboardState(keystate)) {
507 return false;
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) {
520 return false;
523 if (keystate[VK_RSHIFT] & kKeyDownMask) {
524 keystate[VK_RSHIFT] = 0;
525 isRTL = true;
526 } else if (keystate[VK_LSHIFT] & kKeyDownMask) {
527 keystate[VK_LSHIFT] = 0;
528 isRTL = false;
529 } else {
530 return false;
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) {
545 return false;
548 return true;
553 // This logic is mostly borrowed from Chromium's
554 // RenderWidgetHostViewWin::OnKeyEvent.
556 nsresult
557 nsEditorEventListener::KeyUp(nsIDOMKeyEvent* aKeyEvent)
559 NS_ENSURE_TRUE(aKeyEvent, NS_OK);
561 if (!mHaveBidiKeyboards) {
562 return NS_OK;
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;
575 return NS_OK;
578 nsresult
579 nsEditorEventListener::KeyDown(nsIDOMKeyEvent* aKeyEvent)
581 NS_ENSURE_TRUE(aKeyEvent, NS_OK);
583 if (!mHaveBidiKeyboards) {
584 return NS_OK;
587 uint32_t keyCode = 0;
588 aKeyEvent->GetKeyCode(&keyCode);
589 if (keyCode == nsIDOMKeyEvent::DOM_VK_SHIFT) {
590 bool switchToRTL;
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;
599 return NS_OK;
601 #endif
603 nsresult
604 nsEditorEventListener::KeyPress(nsIDOMKeyEvent* aKeyEvent)
606 NS_ENSURE_TRUE(aKeyEvent, NS_OK);
608 if (!mEditor->IsAcceptableInputEvent(aKeyEvent)) {
609 return NS_OK;
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
616 // below.
618 bool defaultPrevented;
619 aKeyEvent->GetDefaultPrevented(&defaultPrevented);
620 if (defaultPrevented) {
621 return NS_OK;
624 nsresult rv = mEditor->HandleKeyPressEvent(aKeyEvent);
625 NS_ENSURE_SUCCESS(rv, rv);
627 aKeyEvent->GetDefaultPrevented(&defaultPrevented);
628 if (defaultPrevented) {
629 return NS_OK;
632 if (!ShouldHandleNativeKeyBindings(aKeyEvent)) {
633 return NS_OK;
636 // Now, ask the native key bindings to handle the event.
637 WidgetKeyboardEvent* keyEvent =
638 aKeyEvent->GetInternalNSEvent()->AsKeyboardEvent();
639 MOZ_ASSERT(keyEvent,
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.
643 if (!widget) {
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);
654 if (handled) {
655 aKeyEvent->PreventDefault();
657 return NS_OK;
660 nsresult
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)) {
666 return NS_OK;
669 // Notifies clicking on editor to IMEStateManager even when the event was
670 // consumed.
671 if (EditorHasFocus()) {
672 nsPresContext* presContext = GetPresContext();
673 if (presContext) {
674 IMEStateManager::OnClickInEditor(presContext, GetFocusedRootContent(),
675 aMouseEvent);
679 bool preventDefault;
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).
683 return rv;
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();
690 int16_t button = -1;
691 aMouseEvent->GetButton(&button);
692 if (button == 1) {
693 return HandleMiddleClickPaste(aMouseEvent);
695 return NS_OK;
698 nsresult
699 nsEditorEventListener::HandleMiddleClickPaste(nsIDOMMouseEvent* aMouseEvent)
701 if (!Preferences::GetBool("middlemouse.paste", false)) {
702 // Middle click paste isn't enabled.
703 return NS_OK;
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;
711 int32_t offset = 0;
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;
727 if (ctrlKey) {
728 mailEditor = do_QueryObject(mEditor);
731 nsresult rv;
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;
743 if (mailEditor) {
744 mailEditor->PasteAsQuotation(clipboard);
745 } else {
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
755 return NS_OK;
758 bool
759 nsEditorEventListener::NotifyIMEOfMouseButtonEvent(
760 nsIDOMMouseEvent* aMouseEvent)
762 if (!EditorHasFocus()) {
763 return false;
766 bool defaultPrevented;
767 nsresult rv = aMouseEvent->GetDefaultPrevented(&defaultPrevented);
768 NS_ENSURE_SUCCESS(rv, false);
769 if (defaultPrevented) {
770 return false;
772 nsPresContext* presContext = GetPresContext();
773 NS_ENSURE_TRUE(presContext, false);
774 return IMEStateManager::OnMouseButtonEventInEditor(presContext,
775 GetFocusedRootContent(),
776 aMouseEvent);
779 nsresult
780 nsEditorEventListener::MouseDown(nsIDOMMouseEvent* aMouseEvent)
782 mEditor->ForceCompositionEnd();
783 return NS_OK;
786 nsresult
787 nsEditorEventListener::HandleText(nsIDOMEvent* aTextEvent)
789 if (!mEditor->IsAcceptableInputEvent(aTextEvent)) {
790 return NS_OK;
793 // if we are readonly or disabled, then do nothing.
794 if (mEditor->IsReadonly() || mEditor->IsDisabled()) {
795 return NS_OK;
798 return mEditor->UpdateIMEComposition(aTextEvent);
802 * Drag event implementation
805 nsresult
806 nsEditorEventListener::DragEnter(nsIDOMDragEvent* aDragEvent)
808 NS_ENSURE_TRUE(aDragEvent, NS_OK);
810 nsCOMPtr<nsIPresShell> presShell = GetPresShell();
811 NS_ENSURE_TRUE(presShell, NS_OK);
813 if (!mCaret) {
814 mCaret = new nsCaret();
815 mCaret->Init(presShell);
816 mCaret->SetCaretReadOnly(true);
819 presShell->SetCaret(mCaret);
821 return DragOver(aDragEvent);
824 nsresult
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) {
833 return NS_OK;
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
843 if (!mCaret) {
844 return NS_OK;
847 int32_t offset = 0;
848 nsresult rv = aDragEvent->GetRangeOffset(&offset);
849 NS_ENSURE_SUCCESS(rv, rv);
851 mCaret->SetVisible(true);
852 mCaret->SetCaretPosition(parent, offset);
854 return NS_OK;
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();
863 if (mCaret) {
864 mCaret->SetVisible(false);
866 return NS_OK;
869 void
870 nsEditorEventListener::CleanupDragDropCaret()
872 if (!mCaret) {
873 return;
876 mCaret->SetVisible(false); // hide it, so that it turns off its timer
878 nsCOMPtr<nsIPresShell> presShell = GetPresShell();
879 if (presShell) {
880 nsCOMPtr<nsISelectionController> selCon(do_QueryInterface(presShell));
881 if (selCon) {
882 selCon->SetCaretEnabled(false);
884 presShell->RestoreCaret();
887 mCaret->Terminate();
888 mCaret = nullptr;
891 nsresult
892 nsEditorEventListener::DragExit(nsIDOMDragEvent* aDragEvent)
894 NS_ENSURE_TRUE(aDragEvent, NS_OK);
896 CleanupDragDropCaret();
898 return NS_OK;
901 nsresult
902 nsEditorEventListener::Drop(nsIDOMDragEvent* aDragEvent)
904 NS_ENSURE_TRUE(aDragEvent, NS_OK);
906 CleanupDragDropCaret();
908 bool defaultPrevented;
909 aDragEvent->GetDefaultPrevented(&defaultPrevented);
910 if (defaultPrevented) {
911 return NS_OK;
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();
929 return NS_OK;
932 aDragEvent->StopPropagation();
933 aDragEvent->PreventDefault();
934 return mEditor->InsertFromDrop(aDragEvent);
937 bool
938 nsEditorEventListener::CanDrop(nsIDOMDragEvent* aEvent)
940 // if the target doc is read-only, we can't drop
941 if (mEditor->IsReadonly() || mEditor->IsDisabled()) {
942 return false;
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))))) {
959 return false;
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));
967 if (!sourceNode) {
968 return true;
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) {
983 return true;
986 nsCOMPtr<nsISelection> selection;
987 rv = mEditor->GetSelection(getter_AddRefs(selection));
988 if (NS_FAILED(rv) || !selection) {
989 return false;
992 // If selection is collapsed, allow to drop it always.
993 if (selection->Collapsed()) {
994 return true;
997 nsCOMPtr<nsIDOMNode> parent;
998 rv = aEvent->GetRangeParent(getter_AddRefs(parent));
999 if (NS_FAILED(rv) || !parent) {
1000 return false;
1003 int32_t offset = 0;
1004 rv = aEvent->GetRangeOffset(&offset);
1005 NS_ENSURE_SUCCESS(rv, false);
1007 int32_t rangeCount;
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
1016 continue;
1019 bool inRange = true;
1020 range->IsPointInRange(parent, offset, &inRange);
1021 if (inRange) {
1022 // Okay, now you can bail, we are over the orginal selection
1023 return false;
1026 return true;
1029 nsresult
1030 nsEditorEventListener::HandleStartComposition(nsIDOMEvent* aCompositionEvent)
1032 if (!mEditor->IsAcceptableInputEvent(aCompositionEvent)) {
1033 return NS_OK;
1035 WidgetCompositionEvent* compositionStart =
1036 aCompositionEvent->GetInternalNSEvent()->AsCompositionEvent();
1037 return mEditor->BeginIMEComposition(compositionStart);
1040 void
1041 nsEditorEventListener::HandleEndComposition(nsIDOMEvent* aCompositionEvent)
1043 if (!mEditor->IsAcceptableInputEvent(aCompositionEvent)) {
1044 return;
1047 mEditor->EndIMEComposition();
1050 nsresult
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()) {
1057 return NS_OK;
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)) {
1072 return NS_OK;
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.
1086 if (editableRoot) {
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)) {
1093 return NS_OK;
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);
1105 return NS_OK;
1108 nsresult
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));
1120 if (!element) {
1121 mEditor->FinalizeSelection();
1123 return NS_OK;
1126 void
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(&currentFlags);
1133 if(currentFlags & nsIPlaintextEditor::eEditorSkipSpellCheck) {
1134 currentFlags ^= nsIPlaintextEditor::eEditorSkipSpellCheck;
1135 mEditor->SetFlags(currentFlags);
1139 bool
1140 nsEditorEventListener::IsFileControlTextBox()
1142 dom::Element* root = mEditor->GetRoot();
1143 if (!root || !root->ChromeOnlyAccess()) {
1144 return false;
1146 nsIContent* parent = root->FindFirstNonChromeOnlyAccessContent();
1147 if (!parent || !parent->IsHTML(nsGkAtoms::input)) {
1148 return false;
1150 nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(parent);
1151 return formControl->GetType() == NS_FORM_INPUT_FILE;
1154 bool
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
1163 // mouse events.
1165 nsCOMPtr<nsIDOMEventTarget> target;
1166 aKeyEvent->GetTarget(getter_AddRefs(target));
1167 nsCOMPtr<nsIContent> targetContent = do_QueryInterface(target);
1168 if (!targetContent) {
1169 return false;
1172 nsCOMPtr<nsIHTMLEditor> htmlEditor =
1173 do_QueryInterface(static_cast<nsIEditor*>(mEditor));
1174 if (!htmlEditor) {
1175 return false;
1178 nsCOMPtr<nsIDocument> doc = mEditor->GetDocument();
1179 if (doc->HasFlag(NODE_IS_EDITABLE)) {
1180 // Don't need to perform any checks in designMode documents.
1181 return true;
1184 nsIContent* editingHost = htmlEditor->GetActiveEditingHost();
1185 if (!editingHost) {
1186 return false;
1189 return nsContentUtils::ContentIsDescendantOf(targetContent, editingHost);