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 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is mozilla.org code.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * Netscape Communications Corporation.
20 : * Portions created by the Initial Developer are Copyright (C) 1998
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Pierre Phaneuf <pp@ludusdesign.com>
25 : * Masayuki Nakano <masayuki@d-toybox.com>
26 : *
27 : * Alternatively, the contents of this file may be used under the terms of
28 : * either of the GNU General Public License Version 2 or later (the "GPL"),
29 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 : * in which case the provisions of the GPL or the LGPL are applicable instead
31 : * of those above. If you wish to allow use of your version of this file only
32 : * under the terms of either the GPL or the LGPL, and not to allow others to
33 : * use your version of this file under the terms of the MPL, indicate your
34 : * decision by deleting the provisions above and replace them with the notice
35 : * and other provisions required by the GPL or the LGPL. If you do not delete
36 : * the provisions above, a recipient may use your version of this file under
37 : * the terms of any one of the MPL, the GPL or the LGPL.
38 : *
39 : * ***** END LICENSE BLOCK ***** */
40 : #include "nsEditorEventListener.h"
41 : #include "nsEditor.h"
42 :
43 : #include "nsIDOMDOMStringList.h"
44 : #include "nsIDOMEvent.h"
45 : #include "nsIDOMNSEvent.h"
46 : #include "nsIDOMDocument.h"
47 : #include "nsIDOMEventTarget.h"
48 : #include "nsIDocument.h"
49 : #include "nsIPresShell.h"
50 : #include "nsISelection.h"
51 : #include "nsISelectionController.h"
52 : #include "nsIDOMKeyEvent.h"
53 : #include "nsIDOMMouseEvent.h"
54 : #include "nsIPrivateTextEvent.h"
55 : #include "nsIEditorMailSupport.h"
56 : #include "nsFocusManager.h"
57 : #include "nsEventListenerManager.h"
58 : #include "nsIMEStateManager.h"
59 : #include "mozilla/Preferences.h"
60 :
61 : // Drag & Drop, Clipboard
62 : #include "nsIServiceManager.h"
63 : #include "nsIClipboard.h"
64 : #include "nsIContent.h"
65 : #include "nsISupportsPrimitives.h"
66 : #include "nsIDOMRange.h"
67 : #include "nsEditorUtils.h"
68 : #include "nsISelectionPrivate.h"
69 : #include "nsIDOMDragEvent.h"
70 : #include "nsIFocusManager.h"
71 : #include "nsIDOMWindow.h"
72 : #include "nsContentUtils.h"
73 : #include "nsIBidiKeyboard.h"
74 :
75 : using namespace mozilla;
76 :
77 : class nsAutoEditorKeypressOperation {
78 : public:
79 0 : nsAutoEditorKeypressOperation(nsEditor *aEditor, nsIDOMNSEvent *aEvent)
80 0 : : mEditor(aEditor) {
81 0 : mEditor->BeginKeypressHandling(aEvent);
82 0 : }
83 0 : ~nsAutoEditorKeypressOperation() {
84 0 : mEditor->EndKeypressHandling();
85 0 : }
86 :
87 : private:
88 : nsEditor *mEditor;
89 : };
90 :
91 0 : nsEditorEventListener::nsEditorEventListener() :
92 : mEditor(nsnull), mCommitText(false),
93 0 : mInTransaction(false)
94 : #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
95 : , mHaveBidiKeyboards(false)
96 : , mShouldSwitchTextDirection(false)
97 : , mSwitchToRTL(false)
98 : #endif
99 : {
100 0 : }
101 :
102 0 : nsEditorEventListener::~nsEditorEventListener()
103 : {
104 0 : if (mEditor) {
105 0 : NS_WARNING("We're not uninstalled");
106 0 : Disconnect();
107 : }
108 0 : }
109 :
110 : nsresult
111 0 : nsEditorEventListener::Connect(nsEditor* aEditor)
112 : {
113 0 : NS_ENSURE_ARG(aEditor);
114 :
115 : #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
116 : nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard();
117 : if (bidiKeyboard) {
118 : bool haveBidiKeyboards = false;
119 : bidiKeyboard->GetHaveBidiKeyboards(&haveBidiKeyboards);
120 : mHaveBidiKeyboards = haveBidiKeyboards;
121 : }
122 : #endif
123 :
124 0 : mEditor = aEditor;
125 :
126 0 : nsresult rv = InstallToEditor();
127 0 : if (NS_FAILED(rv)) {
128 0 : Disconnect();
129 : }
130 0 : return rv;
131 : }
132 :
133 : nsresult
134 0 : nsEditorEventListener::InstallToEditor()
135 : {
136 0 : NS_PRECONDITION(mEditor, "The caller must set mEditor");
137 :
138 0 : nsCOMPtr<nsIDOMEventTarget> piTarget = mEditor->GetDOMEventTarget();
139 0 : NS_ENSURE_TRUE(piTarget, NS_ERROR_FAILURE);
140 :
141 : // register the event listeners with the listener manager
142 0 : nsEventListenerManager* elmP = piTarget->GetListenerManager(true);
143 0 : NS_ENSURE_STATE(elmP);
144 :
145 : #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
146 : elmP->AddEventListenerByType(this,
147 : NS_LITERAL_STRING("keydown"),
148 : NS_EVENT_FLAG_BUBBLE |
149 : NS_EVENT_FLAG_SYSTEM_EVENT);
150 : elmP->AddEventListenerByType(this,
151 : NS_LITERAL_STRING("keyup"),
152 : NS_EVENT_FLAG_BUBBLE |
153 : NS_EVENT_FLAG_SYSTEM_EVENT);
154 : #endif
155 : elmP->AddEventListenerByType(this,
156 0 : NS_LITERAL_STRING("keypress"),
157 : NS_EVENT_FLAG_BUBBLE |
158 0 : NS_EVENT_FLAG_SYSTEM_EVENT);
159 : elmP->AddEventListenerByType(this,
160 0 : NS_LITERAL_STRING("dragenter"),
161 : NS_EVENT_FLAG_BUBBLE |
162 0 : NS_EVENT_FLAG_SYSTEM_EVENT);
163 : elmP->AddEventListenerByType(this,
164 0 : NS_LITERAL_STRING("dragover"),
165 : NS_EVENT_FLAG_BUBBLE |
166 0 : NS_EVENT_FLAG_SYSTEM_EVENT);
167 : elmP->AddEventListenerByType(this,
168 0 : NS_LITERAL_STRING("dragexit"),
169 : NS_EVENT_FLAG_BUBBLE |
170 0 : NS_EVENT_FLAG_SYSTEM_EVENT);
171 : elmP->AddEventListenerByType(this,
172 0 : NS_LITERAL_STRING("drop"),
173 : NS_EVENT_FLAG_BUBBLE |
174 0 : NS_EVENT_FLAG_SYSTEM_EVENT);
175 : // XXX We should add the mouse event listeners as system event group.
176 : // E.g., web applications cannot prevent middle mouse paste by
177 : // preventDefault() of click event at bubble phase.
178 : // However, if we do so, all click handlers in any frames and frontend
179 : // code need to check if it's editable. It makes easier create new bugs.
180 : elmP->AddEventListenerByType(this,
181 0 : NS_LITERAL_STRING("mousedown"),
182 0 : NS_EVENT_FLAG_CAPTURE);
183 : elmP->AddEventListenerByType(this,
184 0 : NS_LITERAL_STRING("mouseup"),
185 0 : NS_EVENT_FLAG_CAPTURE);
186 : elmP->AddEventListenerByType(this,
187 0 : NS_LITERAL_STRING("click"),
188 0 : NS_EVENT_FLAG_CAPTURE);
189 : // Focus event doesn't bubble so adding the listener to capturing phase.
190 : // Make sure this works after bug 235441 gets fixed.
191 : elmP->AddEventListenerByType(this,
192 0 : NS_LITERAL_STRING("blur"),
193 0 : NS_EVENT_FLAG_CAPTURE);
194 : elmP->AddEventListenerByType(this,
195 0 : NS_LITERAL_STRING("focus"),
196 0 : NS_EVENT_FLAG_CAPTURE);
197 : elmP->AddEventListenerByType(this,
198 0 : NS_LITERAL_STRING("text"),
199 : NS_EVENT_FLAG_BUBBLE |
200 0 : NS_EVENT_FLAG_SYSTEM_EVENT);
201 : elmP->AddEventListenerByType(this,
202 0 : NS_LITERAL_STRING("compositionstart"),
203 : NS_EVENT_FLAG_BUBBLE |
204 0 : NS_EVENT_FLAG_SYSTEM_EVENT);
205 : elmP->AddEventListenerByType(this,
206 0 : NS_LITERAL_STRING("compositionend"),
207 : NS_EVENT_FLAG_BUBBLE |
208 0 : NS_EVENT_FLAG_SYSTEM_EVENT);
209 :
210 0 : return NS_OK;
211 : }
212 :
213 : void
214 0 : nsEditorEventListener::Disconnect()
215 : {
216 0 : if (!mEditor) {
217 0 : return;
218 : }
219 0 : UninstallFromEditor();
220 0 : mEditor = nsnull;
221 : }
222 :
223 : void
224 0 : nsEditorEventListener::UninstallFromEditor()
225 : {
226 0 : nsCOMPtr<nsIDOMEventTarget> piTarget = mEditor->GetDOMEventTarget();
227 0 : if (!piTarget) {
228 : return;
229 : }
230 :
231 : nsEventListenerManager* elmP =
232 0 : piTarget->GetListenerManager(true);
233 0 : if (!elmP) {
234 : return;
235 : }
236 :
237 : #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
238 : elmP->RemoveEventListenerByType(this,
239 : NS_LITERAL_STRING("keydown"),
240 : NS_EVENT_FLAG_BUBBLE |
241 : NS_EVENT_FLAG_SYSTEM_EVENT);
242 : elmP->RemoveEventListenerByType(this,
243 : NS_LITERAL_STRING("keyup"),
244 : NS_EVENT_FLAG_BUBBLE |
245 : NS_EVENT_FLAG_SYSTEM_EVENT);
246 : #endif
247 : elmP->RemoveEventListenerByType(this,
248 0 : NS_LITERAL_STRING("keypress"),
249 : NS_EVENT_FLAG_BUBBLE |
250 0 : NS_EVENT_FLAG_SYSTEM_EVENT);
251 : elmP->RemoveEventListenerByType(this,
252 0 : NS_LITERAL_STRING("dragenter"),
253 : NS_EVENT_FLAG_BUBBLE |
254 0 : NS_EVENT_FLAG_SYSTEM_EVENT);
255 : elmP->RemoveEventListenerByType(this,
256 0 : NS_LITERAL_STRING("dragover"),
257 : NS_EVENT_FLAG_BUBBLE |
258 0 : NS_EVENT_FLAG_SYSTEM_EVENT);
259 : elmP->RemoveEventListenerByType(this,
260 0 : NS_LITERAL_STRING("dragexit"),
261 : NS_EVENT_FLAG_BUBBLE |
262 0 : NS_EVENT_FLAG_SYSTEM_EVENT);
263 : elmP->RemoveEventListenerByType(this,
264 0 : NS_LITERAL_STRING("drop"),
265 : NS_EVENT_FLAG_BUBBLE |
266 0 : NS_EVENT_FLAG_SYSTEM_EVENT);
267 : elmP->RemoveEventListenerByType(this,
268 0 : NS_LITERAL_STRING("mousedown"),
269 0 : NS_EVENT_FLAG_CAPTURE);
270 : elmP->RemoveEventListenerByType(this,
271 0 : NS_LITERAL_STRING("mouseup"),
272 0 : NS_EVENT_FLAG_CAPTURE);
273 : elmP->RemoveEventListenerByType(this,
274 0 : NS_LITERAL_STRING("click"),
275 0 : NS_EVENT_FLAG_CAPTURE);
276 : elmP->RemoveEventListenerByType(this,
277 0 : NS_LITERAL_STRING("blur"),
278 0 : NS_EVENT_FLAG_CAPTURE);
279 : elmP->RemoveEventListenerByType(this,
280 0 : NS_LITERAL_STRING("focus"),
281 0 : NS_EVENT_FLAG_CAPTURE);
282 : elmP->RemoveEventListenerByType(this,
283 0 : NS_LITERAL_STRING("text"),
284 : NS_EVENT_FLAG_BUBBLE |
285 0 : NS_EVENT_FLAG_SYSTEM_EVENT);
286 : elmP->RemoveEventListenerByType(this,
287 0 : NS_LITERAL_STRING("compositionstart"),
288 : NS_EVENT_FLAG_BUBBLE |
289 0 : NS_EVENT_FLAG_SYSTEM_EVENT);
290 : elmP->RemoveEventListenerByType(this,
291 0 : NS_LITERAL_STRING("compositionend"),
292 : NS_EVENT_FLAG_BUBBLE |
293 0 : NS_EVENT_FLAG_SYSTEM_EVENT);
294 : }
295 :
296 : already_AddRefed<nsIPresShell>
297 0 : nsEditorEventListener::GetPresShell()
298 : {
299 0 : NS_PRECONDITION(mEditor,
300 : "The caller must check whether this is connected to an editor");
301 0 : return mEditor->GetPresShell();
302 : }
303 :
304 : /**
305 : * nsISupports implementation
306 : */
307 :
308 0 : NS_IMPL_ISUPPORTS1(nsEditorEventListener, nsIDOMEventListener)
309 :
310 : /**
311 : * nsIDOMEventListener implementation
312 : */
313 :
314 : NS_IMETHODIMP
315 0 : nsEditorEventListener::HandleEvent(nsIDOMEvent* aEvent)
316 : {
317 0 : NS_ENSURE_TRUE(mEditor, NS_ERROR_NOT_AVAILABLE);
318 :
319 0 : nsAutoString eventType;
320 0 : aEvent->GetType(eventType);
321 :
322 0 : nsCOMPtr<nsIDOMDragEvent> dragEvent = do_QueryInterface(aEvent);
323 0 : if (dragEvent) {
324 0 : if (eventType.EqualsLiteral("dragenter"))
325 0 : return DragEnter(dragEvent);
326 0 : if (eventType.EqualsLiteral("dragover"))
327 0 : return DragOver(dragEvent);
328 0 : if (eventType.EqualsLiteral("dragexit"))
329 0 : return DragExit(dragEvent);
330 0 : if (eventType.EqualsLiteral("drop"))
331 0 : return Drop(dragEvent);
332 : }
333 :
334 : #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
335 : if (eventType.EqualsLiteral("keydown"))
336 : return KeyDown(aEvent);
337 : if (eventType.EqualsLiteral("keyup"))
338 : return KeyUp(aEvent);
339 : #endif
340 0 : if (eventType.EqualsLiteral("keypress"))
341 0 : return KeyPress(aEvent);
342 0 : if (eventType.EqualsLiteral("mousedown"))
343 0 : return MouseDown(aEvent);
344 0 : if (eventType.EqualsLiteral("mouseup"))
345 0 : return MouseUp(aEvent);
346 0 : if (eventType.EqualsLiteral("click"))
347 0 : return MouseClick(aEvent);
348 0 : if (eventType.EqualsLiteral("focus"))
349 0 : return Focus(aEvent);
350 0 : if (eventType.EqualsLiteral("blur"))
351 0 : return Blur(aEvent);
352 0 : if (eventType.EqualsLiteral("text"))
353 0 : return HandleText(aEvent);
354 0 : if (eventType.EqualsLiteral("compositionstart"))
355 0 : return HandleStartComposition(aEvent);
356 0 : if (eventType.EqualsLiteral("compositionend"))
357 0 : return HandleEndComposition(aEvent);
358 :
359 0 : return NS_OK;
360 : }
361 :
362 : #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
363 : #include <windows.h>
364 :
365 : namespace {
366 :
367 : // This function is borrowed from Chromium's ImeInput::IsCtrlShiftPressed
368 : bool IsCtrlShiftPressed(bool& isRTL)
369 : {
370 : BYTE keystate[256];
371 : if (!::GetKeyboardState(keystate)) {
372 : return false;
373 : }
374 :
375 : // To check if a user is pressing only a control key and a right-shift key
376 : // (or a left-shift key), we use the steps below:
377 : // 1. Check if a user is pressing a control key and a right-shift key (or
378 : // a left-shift key).
379 : // 2. If the condition 1 is true, we should check if there are any other
380 : // keys pressed at the same time.
381 : // To ignore the keys checked in 1, we set their status to 0 before
382 : // checking the key status.
383 : const int kKeyDownMask = 0x80;
384 : if ((keystate[VK_CONTROL] & kKeyDownMask) == 0)
385 : return false;
386 :
387 : if (keystate[VK_RSHIFT] & kKeyDownMask) {
388 : keystate[VK_RSHIFT] = 0;
389 : isRTL = true;
390 : } else if (keystate[VK_LSHIFT] & kKeyDownMask) {
391 : keystate[VK_LSHIFT] = 0;
392 : isRTL = false;
393 : } else {
394 : return false;
395 : }
396 :
397 : // Scan the key status to find pressed keys. We should abandon changing the
398 : // text direction when there are other pressed keys.
399 : // This code is executed only when a user is pressing a control key and a
400 : // right-shift key (or a left-shift key), i.e. we should ignore the status of
401 : // the keys: VK_SHIFT, VK_CONTROL, VK_RCONTROL, and VK_LCONTROL.
402 : // So, we reset their status to 0 and ignore them.
403 : keystate[VK_SHIFT] = 0;
404 : keystate[VK_CONTROL] = 0;
405 : keystate[VK_RCONTROL] = 0;
406 : keystate[VK_LCONTROL] = 0;
407 : for (int i = 0; i <= VK_PACKET; ++i) {
408 : if (keystate[i] & kKeyDownMask)
409 : return false;
410 : }
411 : return true;
412 : }
413 :
414 : }
415 :
416 : // This logic is mostly borrowed from Chromium's
417 : // RenderWidgetHostViewWin::OnKeyEvent.
418 :
419 : NS_IMETHODIMP
420 : nsEditorEventListener::KeyUp(nsIDOMEvent* aKeyEvent)
421 : {
422 : if (mHaveBidiKeyboards) {
423 : nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent);
424 : if (!keyEvent) {
425 : // non-key event passed to keyup. bad things.
426 : return NS_OK;
427 : }
428 :
429 : PRUint32 keyCode = 0;
430 : keyEvent->GetKeyCode(&keyCode);
431 : if (keyCode == nsIDOMKeyEvent::DOM_VK_SHIFT ||
432 : keyCode == nsIDOMKeyEvent::DOM_VK_CONTROL) {
433 : if (mShouldSwitchTextDirection && mEditor->IsPlaintextEditor()) {
434 : mEditor->SwitchTextDirectionTo(mSwitchToRTL ?
435 : nsIPlaintextEditor::eEditorRightToLeft :
436 : nsIPlaintextEditor::eEditorLeftToRight);
437 : mShouldSwitchTextDirection = false;
438 : }
439 : }
440 : }
441 :
442 : return NS_OK;
443 : }
444 :
445 : NS_IMETHODIMP
446 : nsEditorEventListener::KeyDown(nsIDOMEvent* aKeyEvent)
447 : {
448 : if (mHaveBidiKeyboards) {
449 : nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent);
450 : if (!keyEvent) {
451 : // non-key event passed to keydown. bad things.
452 : return NS_OK;
453 : }
454 :
455 : PRUint32 keyCode = 0;
456 : keyEvent->GetKeyCode(&keyCode);
457 : if (keyCode == nsIDOMKeyEvent::DOM_VK_SHIFT) {
458 : bool switchToRTL;
459 : if (IsCtrlShiftPressed(switchToRTL)) {
460 : mShouldSwitchTextDirection = true;
461 : mSwitchToRTL = switchToRTL;
462 : }
463 : } else if (keyCode != nsIDOMKeyEvent::DOM_VK_CONTROL) {
464 : // In case the user presses any other key besides Ctrl and Shift
465 : mShouldSwitchTextDirection = false;
466 : }
467 : }
468 :
469 : return NS_OK;
470 : }
471 : #endif
472 :
473 : NS_IMETHODIMP
474 0 : nsEditorEventListener::KeyPress(nsIDOMEvent* aKeyEvent)
475 : {
476 0 : NS_ENSURE_TRUE(mEditor, NS_ERROR_NOT_AVAILABLE);
477 :
478 0 : if (!mEditor->IsAcceptableInputEvent(aKeyEvent)) {
479 0 : return NS_OK;
480 : }
481 :
482 : // Transfer the event's trusted-ness to our editor
483 0 : nsCOMPtr<nsIDOMNSEvent> NSEvent = do_QueryInterface(aKeyEvent);
484 0 : nsAutoEditorKeypressOperation operation(mEditor, NSEvent);
485 :
486 : // DOM event handling happens in two passes, the client pass and the system
487 : // pass. We do all of our processing in the system pass, to allow client
488 : // handlers the opportunity to cancel events and prevent typing in the editor.
489 : // If the client pass cancelled the event, defaultPrevented will be true
490 : // below.
491 :
492 : bool defaultPrevented;
493 0 : aKeyEvent->GetDefaultPrevented(&defaultPrevented);
494 0 : if (defaultPrevented) {
495 0 : return NS_OK;
496 : }
497 :
498 0 : nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent);
499 0 : if (!keyEvent) {
500 : //non-key event passed to keypress. bad things.
501 0 : return NS_OK;
502 : }
503 :
504 0 : return mEditor->HandleKeyPressEvent(keyEvent);
505 : }
506 :
507 : NS_IMETHODIMP
508 0 : nsEditorEventListener::MouseClick(nsIDOMEvent* aMouseEvent)
509 : {
510 0 : NS_ENSURE_TRUE(mEditor, NS_ERROR_NOT_AVAILABLE);
511 :
512 0 : nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
513 0 : NS_ENSURE_TRUE(mouseEvent, NS_OK);
514 :
515 : // nothing to do if editor isn't editable or clicked on out of the editor.
516 0 : if (mEditor->IsReadonly() || mEditor->IsDisabled() ||
517 0 : !mEditor->IsAcceptableInputEvent(aMouseEvent)) {
518 0 : return NS_OK;
519 : }
520 :
521 : // Notifies clicking on editor to IMEStateManager even when the event was
522 : // consumed.
523 0 : nsCOMPtr<nsIContent> focusedContent = mEditor->GetFocusedContent();
524 0 : if (focusedContent) {
525 0 : nsIDocument* currentDoc = focusedContent->GetCurrentDoc();
526 0 : nsCOMPtr<nsIPresShell> presShell = GetPresShell();
527 : nsPresContext* presContext =
528 0 : presShell ? presShell->GetPresContext() : nsnull;
529 0 : if (presContext && currentDoc) {
530 : nsIMEStateManager::OnClickInEditor(presContext,
531 0 : currentDoc->HasFlag(NODE_IS_EDITABLE) ? nsnull : focusedContent,
532 0 : mouseEvent);
533 : }
534 : }
535 :
536 : bool preventDefault;
537 0 : nsresult rv = aMouseEvent->GetDefaultPrevented(&preventDefault);
538 0 : if (NS_FAILED(rv) || preventDefault) {
539 : // We're done if 'preventdefault' is true (see for example bug 70698).
540 0 : return rv;
541 : }
542 :
543 : // If we got a mouse down inside the editing area, we should force the
544 : // IME to commit before we change the cursor position
545 0 : mEditor->ForceCompositionEnd();
546 :
547 0 : PRUint16 button = (PRUint16)-1;
548 0 : mouseEvent->GetButton(&button);
549 : // middle-mouse click (paste);
550 0 : if (button == 1)
551 : {
552 0 : if (Preferences::GetBool("middlemouse.paste", false))
553 : {
554 : // Set the selection to the point under the mouse cursor:
555 0 : nsCOMPtr<nsIDOMNode> parent;
556 0 : if (NS_FAILED(mouseEvent->GetRangeParent(getter_AddRefs(parent))))
557 0 : return NS_ERROR_NULL_POINTER;
558 0 : PRInt32 offset = 0;
559 0 : if (NS_FAILED(mouseEvent->GetRangeOffset(&offset)))
560 0 : return NS_ERROR_NULL_POINTER;
561 :
562 0 : nsCOMPtr<nsISelection> selection;
563 0 : if (NS_SUCCEEDED(mEditor->GetSelection(getter_AddRefs(selection))))
564 0 : (void)selection->Collapse(parent, offset);
565 :
566 : // If the ctrl key is pressed, we'll do paste as quotation.
567 : // Would've used the alt key, but the kde wmgr treats alt-middle specially.
568 0 : bool ctrlKey = false;
569 0 : mouseEvent->GetCtrlKey(&ctrlKey);
570 :
571 0 : nsCOMPtr<nsIEditorMailSupport> mailEditor;
572 0 : if (ctrlKey)
573 0 : mailEditor = do_QueryObject(mEditor);
574 :
575 0 : PRInt32 clipboard = nsIClipboard::kGlobalClipboard;
576 : nsCOMPtr<nsIClipboard> clipboardService =
577 0 : do_GetService("@mozilla.org/widget/clipboard;1", &rv);
578 0 : if (NS_SUCCEEDED(rv)) {
579 : bool selectionSupported;
580 0 : rv = clipboardService->SupportsSelectionClipboard(&selectionSupported);
581 0 : if (NS_SUCCEEDED(rv) && selectionSupported) {
582 0 : clipboard = nsIClipboard::kSelectionClipboard;
583 : }
584 : }
585 :
586 0 : if (mailEditor)
587 0 : mailEditor->PasteAsQuotation(clipboard);
588 : else
589 0 : mEditor->Paste(clipboard);
590 :
591 : // Prevent the event from propagating up to be possibly handled
592 : // again by the containing window:
593 0 : mouseEvent->StopPropagation();
594 0 : mouseEvent->PreventDefault();
595 :
596 : // We processed the event, whether drop/paste succeeded or not
597 0 : return NS_OK;
598 : }
599 : }
600 0 : return NS_OK;
601 : }
602 :
603 : NS_IMETHODIMP
604 0 : nsEditorEventListener::MouseDown(nsIDOMEvent* aMouseEvent)
605 : {
606 0 : NS_ENSURE_TRUE(mEditor, NS_ERROR_NOT_AVAILABLE);
607 0 : mEditor->ForceCompositionEnd();
608 0 : return NS_OK;
609 : }
610 :
611 : NS_IMETHODIMP
612 0 : nsEditorEventListener::HandleText(nsIDOMEvent* aTextEvent)
613 : {
614 0 : NS_ENSURE_TRUE(mEditor, NS_ERROR_NOT_AVAILABLE);
615 :
616 0 : if (!mEditor->IsAcceptableInputEvent(aTextEvent)) {
617 0 : return NS_OK;
618 : }
619 :
620 0 : nsCOMPtr<nsIPrivateTextEvent> textEvent = do_QueryInterface(aTextEvent);
621 0 : if (!textEvent) {
622 : //non-ui event passed in. bad things.
623 0 : return NS_OK;
624 : }
625 :
626 0 : nsAutoString composedText;
627 0 : nsCOMPtr<nsIPrivateTextRangeList> textRangeList;
628 :
629 0 : textEvent->GetText(composedText);
630 0 : textRangeList = textEvent->GetInputRange();
631 :
632 : // if we are readonly or disabled, then do nothing.
633 0 : if (mEditor->IsReadonly() || mEditor->IsDisabled()) {
634 0 : return NS_OK;
635 : }
636 :
637 : // Transfer the event's trusted-ness to our editor
638 0 : nsCOMPtr<nsIDOMNSEvent> NSEvent = do_QueryInterface(aTextEvent);
639 0 : nsAutoEditorKeypressOperation operation(mEditor, NSEvent);
640 :
641 0 : return mEditor->UpdateIMEComposition(composedText, textRangeList);
642 : }
643 :
644 : /**
645 : * Drag event implementation
646 : */
647 :
648 : nsresult
649 0 : nsEditorEventListener::DragEnter(nsIDOMDragEvent* aDragEvent)
650 : {
651 0 : nsCOMPtr<nsIPresShell> presShell = GetPresShell();
652 0 : NS_ENSURE_TRUE(presShell, NS_OK);
653 :
654 0 : if (!mCaret) {
655 0 : mCaret = new nsCaret();
656 0 : mCaret->Init(presShell);
657 0 : mCaret->SetCaretReadOnly(true);
658 : }
659 :
660 0 : presShell->SetCaret(mCaret);
661 :
662 0 : return DragOver(aDragEvent);
663 : }
664 :
665 : nsresult
666 0 : nsEditorEventListener::DragOver(nsIDOMDragEvent* aDragEvent)
667 : {
668 0 : nsCOMPtr<nsIDOMNode> parent;
669 : bool defaultPrevented;
670 0 : aDragEvent->GetDefaultPrevented(&defaultPrevented);
671 0 : if (defaultPrevented) {
672 0 : return NS_OK;
673 : }
674 :
675 0 : aDragEvent->GetRangeParent(getter_AddRefs(parent));
676 0 : nsCOMPtr<nsIContent> dropParent = do_QueryInterface(parent);
677 0 : NS_ENSURE_TRUE(dropParent, NS_ERROR_FAILURE);
678 :
679 0 : if (!dropParent->IsEditable()) {
680 0 : return NS_OK;
681 : }
682 :
683 0 : if (CanDrop(aDragEvent)) {
684 0 : aDragEvent->PreventDefault(); // consumed
685 :
686 0 : if (mCaret) {
687 0 : PRInt32 offset = 0;
688 0 : nsresult rv = aDragEvent->GetRangeOffset(&offset);
689 0 : NS_ENSURE_SUCCESS(rv, rv);
690 :
691 : // to avoid flicker, we could track the node and offset to see if we moved
692 0 : if (mCaret)
693 0 : mCaret->EraseCaret();
694 :
695 : //mCaret->SetCaretVisible(true); // make sure it's visible
696 0 : mCaret->DrawAtPosition(parent, offset);
697 : }
698 : }
699 : else
700 : {
701 : // This is needed when dropping on an input, to prevent the editor for
702 : // the editable parent from receiving the event.
703 0 : aDragEvent->StopPropagation();
704 :
705 0 : if (mCaret)
706 : {
707 0 : mCaret->EraseCaret();
708 : }
709 : }
710 :
711 0 : return NS_OK;
712 : }
713 :
714 : void
715 0 : nsEditorEventListener::CleanupDragDropCaret()
716 : {
717 0 : if (mCaret)
718 : {
719 0 : mCaret->EraseCaret();
720 0 : mCaret->SetCaretVisible(false); // hide it, so that it turns off its timer
721 :
722 0 : nsCOMPtr<nsIPresShell> presShell = GetPresShell();
723 0 : if (presShell)
724 : {
725 0 : presShell->RestoreCaret();
726 : }
727 :
728 0 : mCaret->Terminate();
729 0 : mCaret = nsnull;
730 : }
731 0 : }
732 :
733 : nsresult
734 0 : nsEditorEventListener::DragExit(nsIDOMDragEvent* aDragEvent)
735 : {
736 0 : CleanupDragDropCaret();
737 :
738 0 : return NS_OK;
739 : }
740 :
741 : nsresult
742 0 : nsEditorEventListener::Drop(nsIDOMDragEvent* aMouseEvent)
743 : {
744 0 : CleanupDragDropCaret();
745 :
746 : bool defaultPrevented;
747 0 : aMouseEvent->GetDefaultPrevented(&defaultPrevented);
748 0 : if (defaultPrevented) {
749 0 : return NS_OK;
750 : }
751 :
752 0 : nsCOMPtr<nsIDOMNode> parent;
753 0 : aMouseEvent->GetRangeParent(getter_AddRefs(parent));
754 0 : nsCOMPtr<nsIContent> dropParent = do_QueryInterface(parent);
755 0 : NS_ENSURE_TRUE(dropParent, NS_ERROR_FAILURE);
756 :
757 0 : if (!dropParent->IsEditable()) {
758 0 : return NS_OK;
759 : }
760 :
761 0 : if (!CanDrop(aMouseEvent)) {
762 : // was it because we're read-only?
763 0 : if (mEditor->IsReadonly() || mEditor->IsDisabled())
764 : {
765 : // it was decided to "eat" the event as this is the "least surprise"
766 : // since someone else handling it might be unintentional and the
767 : // user could probably re-drag to be not over the disabled/readonly
768 : // editfields if that is what is desired.
769 0 : return aMouseEvent->StopPropagation();
770 : }
771 0 : return NS_OK;
772 : }
773 :
774 0 : aMouseEvent->StopPropagation();
775 0 : aMouseEvent->PreventDefault();
776 0 : return mEditor->InsertFromDrop(aMouseEvent);
777 : }
778 :
779 : bool
780 0 : nsEditorEventListener::CanDrop(nsIDOMDragEvent* aEvent)
781 : {
782 : // if the target doc is read-only, we can't drop
783 0 : if (mEditor->IsReadonly() || mEditor->IsDisabled()) {
784 0 : return false;
785 : }
786 :
787 0 : nsCOMPtr<nsIDOMDataTransfer> dataTransfer;
788 0 : aEvent->GetDataTransfer(getter_AddRefs(dataTransfer));
789 0 : NS_ENSURE_TRUE(dataTransfer, false);
790 :
791 0 : nsCOMPtr<nsIDOMDOMStringList> types;
792 0 : dataTransfer->GetTypes(getter_AddRefs(types));
793 0 : NS_ENSURE_TRUE(types, false);
794 :
795 : // Plaintext editors only support dropping text. Otherwise, HTML and files
796 : // can be dropped as well.
797 : bool typeSupported;
798 0 : types->Contains(NS_LITERAL_STRING(kTextMime), &typeSupported);
799 0 : if (!typeSupported) {
800 0 : types->Contains(NS_LITERAL_STRING(kMozTextInternal), &typeSupported);
801 0 : if (!typeSupported && !mEditor->IsPlaintextEditor()) {
802 0 : types->Contains(NS_LITERAL_STRING(kHTMLMime), &typeSupported);
803 0 : if (!typeSupported) {
804 0 : types->Contains(NS_LITERAL_STRING(kFileMime), &typeSupported);
805 : }
806 : }
807 : }
808 :
809 0 : NS_ENSURE_TRUE(typeSupported, false);
810 :
811 : // If there is no source node, this is probably an external drag and the
812 : // drop is allowed. The later checks rely on checking if the drag target
813 : // is the same as the drag source.
814 0 : nsCOMPtr<nsIDOMNode> sourceNode;
815 0 : dataTransfer->GetMozSourceNode(getter_AddRefs(sourceNode));
816 0 : if (!sourceNode)
817 0 : return true;
818 :
819 : // There is a source node, so compare the source documents and this document.
820 : // Disallow drops on the same document.
821 :
822 0 : nsCOMPtr<nsIDOMDocument> domdoc;
823 0 : nsresult rv = mEditor->GetDocument(getter_AddRefs(domdoc));
824 0 : NS_ENSURE_SUCCESS(rv, false);
825 :
826 0 : nsCOMPtr<nsIDOMDocument> sourceDoc;
827 0 : rv = sourceNode->GetOwnerDocument(getter_AddRefs(sourceDoc));
828 0 : NS_ENSURE_SUCCESS(rv, false);
829 0 : if (domdoc == sourceDoc) // source and dest are the same document; disallow drops within the selection
830 : {
831 0 : nsCOMPtr<nsISelection> selection;
832 0 : rv = mEditor->GetSelection(getter_AddRefs(selection));
833 0 : if (NS_FAILED(rv) || !selection)
834 0 : return false;
835 :
836 : bool isCollapsed;
837 0 : rv = selection->GetIsCollapsed(&isCollapsed);
838 0 : NS_ENSURE_SUCCESS(rv, false);
839 :
840 : // Don't bother if collapsed - can always drop
841 0 : if (!isCollapsed)
842 : {
843 0 : nsCOMPtr<nsIDOMNode> parent;
844 0 : rv = aEvent->GetRangeParent(getter_AddRefs(parent));
845 0 : if (NS_FAILED(rv) || !parent) return false;
846 :
847 0 : PRInt32 offset = 0;
848 0 : rv = aEvent->GetRangeOffset(&offset);
849 0 : NS_ENSURE_SUCCESS(rv, false);
850 :
851 : PRInt32 rangeCount;
852 0 : rv = selection->GetRangeCount(&rangeCount);
853 0 : NS_ENSURE_SUCCESS(rv, false);
854 :
855 0 : for (PRInt32 i = 0; i < rangeCount; i++)
856 : {
857 0 : nsCOMPtr<nsIDOMRange> range;
858 0 : rv = selection->GetRangeAt(i, getter_AddRefs(range));
859 0 : if (NS_FAILED(rv) || !range)
860 0 : continue; //don't bail yet, iterate through them all
861 :
862 0 : bool inRange = true;
863 0 : (void)range->IsPointInRange(parent, offset, &inRange);
864 0 : if (inRange)
865 0 : return false; //okay, now you can bail, we are over the orginal selection
866 : }
867 : }
868 : }
869 :
870 0 : return true;
871 : }
872 :
873 : NS_IMETHODIMP
874 0 : nsEditorEventListener::HandleStartComposition(nsIDOMEvent* aCompositionEvent)
875 : {
876 0 : NS_ENSURE_TRUE(mEditor, NS_ERROR_NOT_AVAILABLE);
877 0 : if (!mEditor->IsAcceptableInputEvent(aCompositionEvent)) {
878 0 : return NS_OK;
879 : }
880 0 : return mEditor->BeginIMEComposition();
881 : }
882 :
883 : NS_IMETHODIMP
884 0 : nsEditorEventListener::HandleEndComposition(nsIDOMEvent* aCompositionEvent)
885 : {
886 0 : NS_ENSURE_TRUE(mEditor, NS_ERROR_NOT_AVAILABLE);
887 0 : if (!mEditor->IsAcceptableInputEvent(aCompositionEvent)) {
888 0 : return NS_OK;
889 : }
890 :
891 : // Transfer the event's trusted-ness to our editor
892 0 : nsCOMPtr<nsIDOMNSEvent> NSEvent = do_QueryInterface(aCompositionEvent);
893 0 : nsAutoEditorKeypressOperation operation(mEditor, NSEvent);
894 :
895 0 : return mEditor->EndIMEComposition();
896 : }
897 :
898 : NS_IMETHODIMP
899 0 : nsEditorEventListener::Focus(nsIDOMEvent* aEvent)
900 : {
901 0 : NS_ENSURE_TRUE(mEditor, NS_ERROR_NOT_AVAILABLE);
902 0 : NS_ENSURE_ARG(aEvent);
903 :
904 : // Don't turn on selection and caret when the editor is disabled.
905 0 : if (mEditor->IsDisabled()) {
906 0 : return NS_OK;
907 : }
908 :
909 : // Spell check a textarea the first time that it is focused.
910 0 : SpellCheckIfNeeded();
911 :
912 0 : nsCOMPtr<nsIDOMEventTarget> target;
913 0 : aEvent->GetTarget(getter_AddRefs(target));
914 0 : nsCOMPtr<nsINode> node = do_QueryInterface(target);
915 0 : NS_ENSURE_TRUE(node, NS_ERROR_UNEXPECTED);
916 :
917 : // If the traget is a document node but it's not editable, we should ignore
918 : // it because actual focused element's event is going to come.
919 0 : if (node->IsNodeOfType(nsINode::eDOCUMENT) &&
920 0 : !node->HasFlag(NODE_IS_EDITABLE)) {
921 0 : return NS_OK;
922 : }
923 :
924 0 : if (node->IsNodeOfType(nsINode::eCONTENT)) {
925 : // XXX If the focus event target is a form control in contenteditable
926 : // element, perhaps, the parent HTML editor should do nothing by this
927 : // handler. However, FindSelectionRoot() returns the root element of the
928 : // contenteditable editor. So, the editableRoot value is invalid for
929 : // the plain text editor, and it will be set to the wrong limiter of
930 : // the selection. However, fortunately, actual bugs are not found yet.
931 0 : nsCOMPtr<nsIContent> editableRoot = mEditor->FindSelectionRoot(node);
932 :
933 : // make sure that the element is really focused in case an earlier
934 : // listener in the chain changed the focus.
935 0 : if (editableRoot) {
936 0 : nsIFocusManager* fm = nsFocusManager::GetFocusManager();
937 0 : NS_ENSURE_TRUE(fm, NS_OK);
938 :
939 0 : nsCOMPtr<nsIDOMElement> element;
940 0 : fm->GetFocusedElement(getter_AddRefs(element));
941 0 : if (!SameCOMIdentity(element, target))
942 0 : return NS_OK;
943 : }
944 : }
945 :
946 0 : mEditor->OnFocus(target);
947 0 : return NS_OK;
948 : }
949 :
950 : NS_IMETHODIMP
951 0 : nsEditorEventListener::Blur(nsIDOMEvent* aEvent)
952 : {
953 0 : NS_ENSURE_TRUE(mEditor, NS_ERROR_NOT_AVAILABLE);
954 0 : NS_ENSURE_ARG(aEvent);
955 :
956 : // check if something else is focused. If another element is focused, then
957 : // we should not change the selection.
958 0 : nsIFocusManager* fm = nsFocusManager::GetFocusManager();
959 0 : NS_ENSURE_TRUE(fm, NS_OK);
960 :
961 0 : nsCOMPtr<nsIDOMElement> element;
962 0 : fm->GetFocusedElement(getter_AddRefs(element));
963 0 : if (element)
964 0 : return NS_OK;
965 :
966 : // turn off selection and caret
967 0 : nsCOMPtr<nsISelectionController>selCon;
968 0 : mEditor->GetSelectionController(getter_AddRefs(selCon));
969 0 : if (selCon)
970 : {
971 0 : nsCOMPtr<nsISelection> selection;
972 0 : selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
973 0 : getter_AddRefs(selection));
974 :
975 : nsCOMPtr<nsISelectionPrivate> selectionPrivate =
976 0 : do_QueryInterface(selection);
977 0 : if (selectionPrivate) {
978 0 : selectionPrivate->SetAncestorLimiter(nsnull);
979 : }
980 :
981 0 : nsCOMPtr<nsIPresShell> presShell = GetPresShell();
982 0 : if (presShell) {
983 0 : nsRefPtr<nsCaret> caret = presShell->GetCaret();
984 0 : if (caret) {
985 0 : caret->SetIgnoreUserModify(true);
986 : }
987 : }
988 :
989 0 : selCon->SetCaretEnabled(false);
990 :
991 0 : if(mEditor->IsFormWidget() || mEditor->IsPasswordEditor() ||
992 0 : mEditor->IsReadonly() || mEditor->IsDisabled() ||
993 0 : mEditor->IsInputFiltered())
994 : {
995 0 : selCon->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN);//hide but do NOT turn off
996 : }
997 : else
998 : {
999 0 : selCon->SetDisplaySelection(nsISelectionController::SELECTION_DISABLED);
1000 : }
1001 :
1002 0 : selCon->RepaintSelection(nsISelectionController::SELECTION_NORMAL);
1003 : }
1004 :
1005 0 : return NS_OK;
1006 : }
1007 :
1008 : void
1009 0 : nsEditorEventListener::SpellCheckIfNeeded() {
1010 : // If the spell check skip flag is still enabled from creation time,
1011 : // disable it because focused editors are allowed to spell check.
1012 0 : PRUint32 currentFlags = 0;
1013 0 : mEditor->GetFlags(¤tFlags);
1014 0 : if(currentFlags & nsIPlaintextEditor::eEditorSkipSpellCheck)
1015 : {
1016 0 : currentFlags ^= nsIPlaintextEditor::eEditorSkipSpellCheck;
1017 0 : mEditor->SetFlags(currentFlags);
1018 : }
1019 0 : }
1020 :
|