Fix typo in 9b54bd30006c008b4a951331b273613d5bac3abf
[pm.git] / widget / TextEventDispatcher.cpp
blobf217d5016669ce08420bc2384cddbbde4ac36690
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"
10 #include "nsIFrame.h"
11 #include "nsIPresShell.h"
12 #include "nsIWidget.h"
13 #include "nsPIDOMWindow.h"
14 #include "nsView.h"
16 namespace mozilla {
17 namespace widget {
19 /******************************************************************************
20 * TextEventDispatcher
21 *****************************************************************************/
23 bool TextEventDispatcher::sDispatchKeyEventsDuringComposition = false;
25 TextEventDispatcher::TextEventDispatcher(nsIWidget* aWidget)
26 : mWidget(aWidget)
27 , mDispatchingEvent(0)
28 , mForTests(false)
29 , mIsComposing(false)
31 MOZ_RELEASE_ASSERT(mWidget, "aWidget must not be nullptr");
33 static bool sInitialized = false;
34 if (!sInitialized) {
35 Preferences::AddBoolVarCache(
36 &sDispatchKeyEventsDuringComposition,
37 "dom.keyboardevent.dispatch_during_composition",
38 false);
39 sInitialized = true;
43 nsresult
44 TextEventDispatcher::BeginInputTransaction(
45 TextEventDispatcherListener* aListener)
47 return BeginInputTransactionInternal(aListener, false);
50 nsresult
51 TextEventDispatcher::BeginInputTransactionForTests(
52 TextEventDispatcherListener* aListener)
54 return BeginInputTransactionInternal(aListener, true);
57 nsresult
58 TextEventDispatcher::BeginInputTransactionInternal(
59 TextEventDispatcherListener* aListener,
60 bool aForTests)
62 if (NS_WARN_IF(!aListener)) {
63 return NS_ERROR_INVALID_ARG;
65 nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
66 if (listener) {
67 if (listener == aListener && mForTests == aForTests) {
68 return NS_OK;
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);
83 return NS_OK;
86 void
87 TextEventDispatcher::OnDestroyWidget()
89 mWidget = nullptr;
90 mPendingComposition.Clear();
91 nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
92 mListener = nullptr;
93 if (listener) {
94 listener->OnRemovedFrom(this);
98 nsresult
99 TextEventDispatcher::GetState() const
101 nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
102 if (!listener) {
103 return NS_ERROR_NOT_INITIALIZED;
105 if (!mWidget || mWidget->Destroyed()) {
106 return NS_ERROR_NOT_AVAILABLE;
108 return NS_OK;
111 void
112 TextEventDispatcher::InitEvent(WidgetGUIEvent& aEvent) const
114 aEvent.time = PR_IntervalNow();
115 aEvent.refPoint = LayoutDeviceIntPoint(0, 0);
116 aEvent.mFlags.mIsSynthesizedForTests = mForTests;
119 nsresult
120 TextEventDispatcher::DispatchEvent(nsIWidget* aWidget,
121 WidgetGUIEvent& aEvent,
122 nsEventStatus& aStatus)
124 nsRefPtr<TextEventDispatcher> kungFuDeathGrip(this);
125 nsCOMPtr<nsIWidget> widget(aWidget);
126 mDispatchingEvent++;
127 nsresult rv = widget->DispatchEvent(&aEvent, aStatus);
128 mDispatchingEvent--;
129 return rv;
132 nsresult
133 TextEventDispatcher::StartComposition(nsEventStatus& aStatus)
135 aStatus = nsEventStatus_eIgnore;
137 nsresult rv = GetState();
138 if (NS_WARN_IF(NS_FAILED(rv))) {
139 return rv;
142 if (NS_WARN_IF(mIsComposing)) {
143 return NS_ERROR_FAILURE;
146 mIsComposing = true;
147 WidgetCompositionEvent compositionStartEvent(true, NS_COMPOSITION_START,
148 mWidget);
149 InitEvent(compositionStartEvent);
150 rv = DispatchEvent(mWidget, compositionStartEvent, aStatus);
151 if (NS_WARN_IF(NS_FAILED(rv))) {
152 return rv;
155 return NS_OK;
158 nsresult
159 TextEventDispatcher::StartCompositionAutomaticallyIfNecessary(
160 nsEventStatus& aStatus)
162 if (IsComposing()) {
163 return NS_OK;
166 nsresult rv = StartComposition(aStatus);
167 if (NS_WARN_IF(NS_FAILED(rv))) {
168 return rv;
171 // If started composition has already been committed, we shouldn't dispatch
172 // the compositionchange event.
173 if (!IsComposing()) {
174 aStatus = nsEventStatus_eConsumeNoDefault;
175 return NS_OK;
178 // Note that the widget might be destroyed during a call of
179 // StartComposition(). In such case, we shouldn't keep dispatching next
180 // event.
181 rv = GetState();
182 if (NS_FAILED(rv)) {
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;
190 return NS_OK;
193 nsresult
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))) {
201 return 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
206 // string.
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))) {
215 return rv;
217 if (aStatus == nsEventStatus_eConsumeNoDefault) {
218 return NS_OK;
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))) {
233 return rv;
236 return NS_OK;
239 nsresult
240 TextEventDispatcher::NotifyIME(const IMENotification& aIMENotification)
242 nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
243 if (!listener) {
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;
253 return rv;
256 bool
257 TextEventDispatcher::DispatchKeyboardEvent(
258 uint32_t aMessage,
259 const WidgetKeyboardEvent& aKeyboardEvent,
260 nsEventStatus& aStatus)
262 return DispatchKeyboardEventInternal(aMessage, aKeyboardEvent, aStatus);
265 bool
266 TextEventDispatcher::DispatchKeyboardEventInternal(
267 uint32_t aMessage,
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))) {
276 return false;
279 // If the key shouldn't cause keypress events, don't this patch them.
280 if (aMessage == NS_KEY_PRESS && !aKeyboardEvent.ShouldCauseKeypressEvents()) {
281 return false;
284 // Basically, key events shouldn't be dispatched during composition.
285 if (IsComposing()) {
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) {
290 return false;
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
294 // composition.
297 WidgetKeyboardEvent keyEvent(true, aMessage, mWidget);
298 InitEvent(keyEvent);
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
318 // be 0.
319 keyEvent.charCode = 0;
320 } else {
321 MOZ_RELEASE_ASSERT(
322 !aIndexOfKeypress || aIndexOfKeypress < keyEvent.mKeyValue.Length(),
323 "aIndexOfKeypress must be 0 - mKeyValue.Length() - 1");
324 keyEvent.keyCode = 0;
325 wchar_t ch =
326 keyEvent.mKeyValue.IsEmpty() ? 0 : keyEvent.mKeyValue[aIndexOfKeypress];
327 keyEvent.charCode = static_cast<uint32_t>(ch);
328 if (ch) {
329 keyEvent.mKeyValue.Assign(ch);
330 } else {
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
344 // plugins.
345 keyEvent.mPluginEvent.Clear();
346 // TODO: Manage mUniqueId here.
348 DispatchEvent(mWidget, keyEvent, aStatus);
349 return true;
352 bool
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) {
359 return false;
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,
375 aStatus, i)) {
376 // The widget must have been gone.
377 break;
379 isDispatched = true;
380 if (!consumed) {
381 consumed = (aStatus == nsEventStatus_eConsumeNoDefault);
385 // If one of the keypress event was consumed, return ConsumeNoDefault.
386 if (consumed) {
387 aStatus = nsEventStatus_eConsumeNoDefault;
390 return isDispatched;
393 /******************************************************************************
394 * TextEventDispatcher::PendingComposition
395 *****************************************************************************/
397 TextEventDispatcher::PendingComposition::PendingComposition()
399 Clear();
402 void
403 TextEventDispatcher::PendingComposition::Clear()
405 mString.Truncate();
406 mClauses = nullptr;
407 mCaret.mRangeType = 0;
410 void
411 TextEventDispatcher::PendingComposition::EnsureClauseArray()
413 if (mClauses) {
414 return;
416 mClauses = new TextRangeArray();
419 nsresult
420 TextEventDispatcher::PendingComposition::SetString(const nsAString& aString)
422 mString = aString;
423 return NS_OK;
426 nsresult
427 TextEventDispatcher::PendingComposition::AppendClause(uint32_t aLength,
428 uint32_t aAttribute)
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: {
439 EnsureClauseArray();
440 TextRange textRange;
441 textRange.mStartOffset =
442 mClauses->IsEmpty() ? 0 : mClauses->LastElement().mEndOffset;
443 textRange.mEndOffset = textRange.mStartOffset + aLength;
444 textRange.mRangeType = aAttribute;
445 mClauses->AppendElement(textRange);
446 return NS_OK;
448 default:
449 return NS_ERROR_INVALID_ARG;
453 nsresult
454 TextEventDispatcher::PendingComposition::SetCaret(uint32_t aOffset,
455 uint32_t aLength)
457 mCaret.mStartOffset = aOffset;
458 mCaret.mEndOffset = mCaret.mStartOffset + aLength;
459 mCaret.mRangeType = NS_TEXTRANGE_CARETPOSITION;
460 return NS_OK;
463 nsresult
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))) {
471 return 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 "
477 "length");
478 Clear();
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");
484 Clear();
485 return NS_ERROR_ILLEGAL_VALUE;
487 EnsureClauseArray();
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;
496 if (mClauses) {
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.
505 Clear();
507 rv = aDispatcher->StartCompositionAutomaticallyIfNecessary(aStatus);
508 if (NS_WARN_IF(NS_FAILED(rv))) {
509 return rv;
511 if (aStatus == nsEventStatus_eConsumeNoDefault) {
512 return NS_OK;
514 rv = aDispatcher->DispatchEvent(widget, compChangeEvent, aStatus);
515 if (NS_WARN_IF(NS_FAILED(rv))) {
516 return rv;
519 return NS_OK;
522 } // namespace widget
523 } // namespace mozilla