1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "mozilla/Preferences.h"
7 #include "mozilla/TextEvents.h"
8 #include "mozilla/TextEventDispatcher.h"
9 #include "nsIDocShell.h"
11 #include "nsIPresShell.h"
12 #include "nsIWidget.h"
13 #include "nsPIDOMWindow.h"
19 /******************************************************************************
21 *****************************************************************************/
23 bool TextEventDispatcher::sDispatchKeyEventsDuringComposition
= false;
25 TextEventDispatcher::TextEventDispatcher(nsIWidget
* aWidget
)
27 , mDispatchingEvent(0)
31 MOZ_RELEASE_ASSERT(mWidget
, "aWidget must not be nullptr");
33 static bool sInitialized
= false;
35 Preferences::AddBoolVarCache(
36 &sDispatchKeyEventsDuringComposition
,
37 "dom.keyboardevent.dispatch_during_composition",
44 TextEventDispatcher::BeginInputTransaction(
45 TextEventDispatcherListener
* aListener
)
47 return BeginInputTransactionInternal(aListener
, false);
51 TextEventDispatcher::BeginInputTransactionForTests(
52 TextEventDispatcherListener
* aListener
)
54 return BeginInputTransactionInternal(aListener
, true);
58 TextEventDispatcher::BeginInputTransactionInternal(
59 TextEventDispatcherListener
* aListener
,
62 if (NS_WARN_IF(!aListener
)) {
63 return NS_ERROR_INVALID_ARG
;
65 nsCOMPtr
<TextEventDispatcherListener
> listener
= do_QueryReferent(mListener
);
67 if (listener
== aListener
&& mForTests
== aForTests
) {
70 // If this has composition or is dispatching an event, any other listener
71 // can steal ownership. Especially, if the latter case is allowed,
72 // nobody cannot begin input transaction with this if a modal dialog is
73 // opened during dispatching an event.
74 if (IsComposing() || IsDispatchingEvent()) {
75 return NS_ERROR_ALREADY_INITIALIZED
;
78 mListener
= do_GetWeakReference(aListener
);
79 mForTests
= aForTests
;
80 if (listener
&& listener
!= aListener
) {
81 listener
->OnRemovedFrom(this);
87 TextEventDispatcher::OnDestroyWidget()
90 mPendingComposition
.Clear();
91 nsCOMPtr
<TextEventDispatcherListener
> listener
= do_QueryReferent(mListener
);
94 listener
->OnRemovedFrom(this);
99 TextEventDispatcher::GetState() const
101 nsCOMPtr
<TextEventDispatcherListener
> listener
= do_QueryReferent(mListener
);
103 return NS_ERROR_NOT_INITIALIZED
;
105 if (!mWidget
|| mWidget
->Destroyed()) {
106 return NS_ERROR_NOT_AVAILABLE
;
112 TextEventDispatcher::InitEvent(WidgetGUIEvent
& aEvent
) const
114 aEvent
.time
= PR_IntervalNow();
115 aEvent
.refPoint
= LayoutDeviceIntPoint(0, 0);
116 aEvent
.mFlags
.mIsSynthesizedForTests
= mForTests
;
120 TextEventDispatcher::DispatchEvent(nsIWidget
* aWidget
,
121 WidgetGUIEvent
& aEvent
,
122 nsEventStatus
& aStatus
)
124 nsRefPtr
<TextEventDispatcher
> kungFuDeathGrip(this);
125 nsCOMPtr
<nsIWidget
> widget(aWidget
);
127 nsresult rv
= widget
->DispatchEvent(&aEvent
, aStatus
);
133 TextEventDispatcher::StartComposition(nsEventStatus
& aStatus
)
135 aStatus
= nsEventStatus_eIgnore
;
137 nsresult rv
= GetState();
138 if (NS_WARN_IF(NS_FAILED(rv
))) {
142 if (NS_WARN_IF(mIsComposing
)) {
143 return NS_ERROR_FAILURE
;
147 WidgetCompositionEvent
compositionStartEvent(true, NS_COMPOSITION_START
,
149 InitEvent(compositionStartEvent
);
150 rv
= DispatchEvent(mWidget
, compositionStartEvent
, aStatus
);
151 if (NS_WARN_IF(NS_FAILED(rv
))) {
159 TextEventDispatcher::StartCompositionAutomaticallyIfNecessary(
160 nsEventStatus
& aStatus
)
166 nsresult rv
= StartComposition(aStatus
);
167 if (NS_WARN_IF(NS_FAILED(rv
))) {
171 // If started composition has already been committed, we shouldn't dispatch
172 // the compositionchange event.
173 if (!IsComposing()) {
174 aStatus
= nsEventStatus_eConsumeNoDefault
;
178 // Note that the widget might be destroyed during a call of
179 // StartComposition(). In such case, we shouldn't keep dispatching next
183 MOZ_ASSERT(rv
!= NS_ERROR_NOT_INITIALIZED
,
184 "aDispatcher must still be initialized in this case");
185 aStatus
= nsEventStatus_eConsumeNoDefault
;
186 return NS_OK
; // Don't throw exception in this case
189 aStatus
= nsEventStatus_eIgnore
;
194 TextEventDispatcher::CommitComposition(nsEventStatus
& aStatus
,
195 const nsAString
* aCommitString
)
197 aStatus
= nsEventStatus_eIgnore
;
199 nsresult rv
= GetState();
200 if (NS_WARN_IF(NS_FAILED(rv
))) {
204 // When there is no composition, caller shouldn't try to commit composition
205 // with non-existing composition string nor commit composition with empty
207 if (NS_WARN_IF(!IsComposing() &&
208 (!aCommitString
|| aCommitString
->IsEmpty()))) {
209 return NS_ERROR_FAILURE
;
212 nsCOMPtr
<nsIWidget
> widget(mWidget
);
213 rv
= StartCompositionAutomaticallyIfNecessary(aStatus
);
214 if (NS_WARN_IF(NS_FAILED(rv
))) {
217 if (aStatus
== nsEventStatus_eConsumeNoDefault
) {
221 // End current composition and make this free for other IMEs.
222 mIsComposing
= false;
224 uint32_t message
= aCommitString
? NS_COMPOSITION_COMMIT
:
225 NS_COMPOSITION_COMMIT_AS_IS
;
226 WidgetCompositionEvent
compositionCommitEvent(true, message
, widget
);
227 InitEvent(compositionCommitEvent
);
228 if (message
== NS_COMPOSITION_COMMIT
) {
229 compositionCommitEvent
.mData
= *aCommitString
;
231 rv
= DispatchEvent(widget
, compositionCommitEvent
, aStatus
);
232 if (NS_WARN_IF(NS_FAILED(rv
))) {
240 TextEventDispatcher::NotifyIME(const IMENotification
& aIMENotification
)
242 nsCOMPtr
<TextEventDispatcherListener
> listener
= do_QueryReferent(mListener
);
244 return NS_ERROR_NOT_IMPLEMENTED
;
246 nsresult rv
= listener
->NotifyIME(this, aIMENotification
);
247 // If the listener isn't available, it means that it cannot handle the
248 // notification or request for now. In this case, we should return
249 // NS_ERROR_NOT_IMPLEMENTED because it's not implemented at such moment.
250 if (rv
== NS_ERROR_NOT_AVAILABLE
) {
251 return NS_ERROR_NOT_IMPLEMENTED
;
257 TextEventDispatcher::DispatchKeyboardEvent(
259 const WidgetKeyboardEvent
& aKeyboardEvent
,
260 nsEventStatus
& aStatus
)
262 return DispatchKeyboardEventInternal(aMessage
, aKeyboardEvent
, aStatus
);
266 TextEventDispatcher::DispatchKeyboardEventInternal(
268 const WidgetKeyboardEvent
& aKeyboardEvent
,
269 nsEventStatus
& aStatus
,
270 uint32_t aIndexOfKeypress
)
272 MOZ_ASSERT(aMessage
== NS_KEY_DOWN
|| aMessage
== NS_KEY_UP
||
273 aMessage
== NS_KEY_PRESS
, "Invalid aMessage value");
274 nsresult rv
= GetState();
275 if (NS_WARN_IF(NS_FAILED(rv
))) {
279 // If the key shouldn't cause keypress events, don't this patch them.
280 if (aMessage
== NS_KEY_PRESS
&& !aKeyboardEvent
.ShouldCauseKeypressEvents()) {
284 // Basically, key events shouldn't be dispatched during composition.
286 // However, if we need to behave like other browsers, we need the keydown
287 // and keyup events. Note that this behavior is also allowed by D3E spec.
288 // FYI: keypress events must not be fired during composition.
289 if (!sDispatchKeyEventsDuringComposition
|| aMessage
== NS_KEY_PRESS
) {
292 // XXX If there was mOnlyContentDispatch for this case, it might be useful
293 // because our chrome doesn't assume that key events are fired during
297 WidgetKeyboardEvent
keyEvent(true, aMessage
, mWidget
);
299 keyEvent
.AssignKeyEventData(aKeyboardEvent
, false);
301 if (aStatus
== nsEventStatus_eConsumeNoDefault
) {
302 // If the key event should be dispatched as consumed event, marking it here.
303 // This is useful to prevent double action. E.g., when the key was already
304 // handled by system, our chrome shouldn't handle it.
305 keyEvent
.mFlags
.mDefaultPrevented
= true;
308 // Corrects each member for the specific key event type.
309 if (aMessage
== NS_KEY_DOWN
|| aMessage
== NS_KEY_UP
) {
310 MOZ_ASSERT(!aIndexOfKeypress
,
311 "aIndexOfKeypress must be 0 for either NS_KEY_DOWN or NS_KEY_UP");
312 // charCode of keydown and keyup should be 0.
313 keyEvent
.charCode
= 0;
314 } else if (keyEvent
.mKeyNameIndex
!= KEY_NAME_INDEX_USE_STRING
) {
315 MOZ_ASSERT(!aIndexOfKeypress
,
316 "aIndexOfKeypress must be 0 for NS_KEY_PRESS of non-printable key");
317 // If keypress event isn't caused by printable key, its charCode should
319 keyEvent
.charCode
= 0;
322 !aIndexOfKeypress
|| aIndexOfKeypress
< keyEvent
.mKeyValue
.Length(),
323 "aIndexOfKeypress must be 0 - mKeyValue.Length() - 1");
324 keyEvent
.keyCode
= 0;
326 keyEvent
.mKeyValue
.IsEmpty() ? 0 : keyEvent
.mKeyValue
[aIndexOfKeypress
];
327 keyEvent
.charCode
= static_cast<uint32_t>(ch
);
329 keyEvent
.mKeyValue
.Assign(ch
);
331 keyEvent
.mKeyValue
.Truncate();
334 if (aMessage
== NS_KEY_UP
) {
335 // mIsRepeat of keyup event must be false.
336 keyEvent
.mIsRepeat
= false;
338 // mIsComposing should be initialized later.
339 keyEvent
.mIsComposing
= false;
340 // XXX Currently, we don't support to dispatch key event with native key
341 // event information.
342 keyEvent
.mNativeKeyEvent
= nullptr;
343 // XXX Currently, we don't support to dispatch key events with data for
345 keyEvent
.mPluginEvent
.Clear();
346 // TODO: Manage mUniqueId here.
348 DispatchEvent(mWidget
, keyEvent
, aStatus
);
353 TextEventDispatcher::MaybeDispatchKeypressEvents(
354 const WidgetKeyboardEvent
& aKeyboardEvent
,
355 nsEventStatus
& aStatus
)
357 // If the key event was consumed, keypress event shouldn't be fired.
358 if (aStatus
== nsEventStatus_eConsumeNoDefault
) {
362 // If the key isn't a printable key or just inputting one character or
363 // no character, we should dispatch only one keypress. Otherwise, i.e.,
364 // if the key is a printable key and inputs multiple characters, keypress
365 // event should be dispatched the count of inputting characters times.
366 size_t keypressCount
=
367 aKeyboardEvent
.mKeyNameIndex
!= KEY_NAME_INDEX_USE_STRING
?
368 1 : std::max(static_cast<nsAString::size_type
>(1),
369 aKeyboardEvent
.mKeyValue
.Length());
370 bool isDispatched
= false;
371 bool consumed
= false;
372 for (size_t i
= 0; i
< keypressCount
; i
++) {
373 aStatus
= nsEventStatus_eIgnore
;
374 if (!DispatchKeyboardEventInternal(NS_KEY_PRESS
, aKeyboardEvent
,
376 // The widget must have been gone.
381 consumed
= (aStatus
== nsEventStatus_eConsumeNoDefault
);
385 // If one of the keypress event was consumed, return ConsumeNoDefault.
387 aStatus
= nsEventStatus_eConsumeNoDefault
;
393 /******************************************************************************
394 * TextEventDispatcher::PendingComposition
395 *****************************************************************************/
397 TextEventDispatcher::PendingComposition::PendingComposition()
403 TextEventDispatcher::PendingComposition::Clear()
407 mCaret
.mRangeType
= 0;
411 TextEventDispatcher::PendingComposition::EnsureClauseArray()
416 mClauses
= new TextRangeArray();
420 TextEventDispatcher::PendingComposition::SetString(const nsAString
& aString
)
427 TextEventDispatcher::PendingComposition::AppendClause(uint32_t aLength
,
430 if (NS_WARN_IF(!aLength
)) {
431 return NS_ERROR_INVALID_ARG
;
434 switch (aAttribute
) {
435 case NS_TEXTRANGE_RAWINPUT
:
436 case NS_TEXTRANGE_SELECTEDRAWTEXT
:
437 case NS_TEXTRANGE_CONVERTEDTEXT
:
438 case NS_TEXTRANGE_SELECTEDCONVERTEDTEXT
: {
441 textRange
.mStartOffset
=
442 mClauses
->IsEmpty() ? 0 : mClauses
->LastElement().mEndOffset
;
443 textRange
.mEndOffset
= textRange
.mStartOffset
+ aLength
;
444 textRange
.mRangeType
= aAttribute
;
445 mClauses
->AppendElement(textRange
);
449 return NS_ERROR_INVALID_ARG
;
454 TextEventDispatcher::PendingComposition::SetCaret(uint32_t aOffset
,
457 mCaret
.mStartOffset
= aOffset
;
458 mCaret
.mEndOffset
= mCaret
.mStartOffset
+ aLength
;
459 mCaret
.mRangeType
= NS_TEXTRANGE_CARETPOSITION
;
464 TextEventDispatcher::PendingComposition::Flush(TextEventDispatcher
* aDispatcher
,
465 nsEventStatus
& aStatus
)
467 aStatus
= nsEventStatus_eIgnore
;
469 nsresult rv
= aDispatcher
->GetState();
470 if (NS_WARN_IF(NS_FAILED(rv
))) {
474 if (mClauses
&& !mClauses
->IsEmpty() &&
475 mClauses
->LastElement().mEndOffset
!= mString
.Length()) {
476 NS_WARNING("Sum of length of the all clauses must be same as the string "
479 return NS_ERROR_ILLEGAL_VALUE
;
481 if (mCaret
.mRangeType
== NS_TEXTRANGE_CARETPOSITION
) {
482 if (mCaret
.mEndOffset
> mString
.Length()) {
483 NS_WARNING("Caret position is out of the composition string");
485 return NS_ERROR_ILLEGAL_VALUE
;
488 mClauses
->AppendElement(mCaret
);
491 nsRefPtr
<TextEventDispatcher
> kungFuDeathGrip(aDispatcher
);
492 nsCOMPtr
<nsIWidget
> widget(aDispatcher
->mWidget
);
493 WidgetCompositionEvent
compChangeEvent(true, NS_COMPOSITION_CHANGE
, widget
);
494 aDispatcher
->InitEvent(compChangeEvent
);
495 compChangeEvent
.mData
= mString
;
497 MOZ_ASSERT(!mClauses
->IsEmpty(),
498 "mClauses must be non-empty array when it's not nullptr");
499 compChangeEvent
.mRanges
= mClauses
;
502 // While this method dispatches a composition event, some other event handler
503 // cause more clauses to be added. So, we should clear pending composition
504 // before dispatching the event.
507 rv
= aDispatcher
->StartCompositionAutomaticallyIfNecessary(aStatus
);
508 if (NS_WARN_IF(NS_FAILED(rv
))) {
511 if (aStatus
== nsEventStatus_eConsumeNoDefault
) {
514 rv
= aDispatcher
->DispatchEvent(widget
, compChangeEvent
, aStatus
);
515 if (NS_WARN_IF(NS_FAILED(rv
))) {
522 } // namespace widget
523 } // namespace mozilla