/src/mozilla-central/dom/base/TextInputProcessor.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
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 | | |
7 | | #include "gfxPrefs.h" |
8 | | #include "mozilla/dom/Event.h" |
9 | | #include "mozilla/EventForwards.h" |
10 | | #include "mozilla/TextEventDispatcher.h" |
11 | | #include "mozilla/TextEvents.h" |
12 | | #include "mozilla/TextInputProcessor.h" |
13 | | #include "mozilla/widget/IMEData.h" |
14 | | #include "mozilla/dom/KeyboardEvent.h" |
15 | | #include "nsContentUtils.h" |
16 | | #include "nsIDocShell.h" |
17 | | #include "nsIWidget.h" |
18 | | #include "nsPIDOMWindow.h" |
19 | | #include "nsPresContext.h" |
20 | | |
21 | | using mozilla::dom::Event; |
22 | | using mozilla::dom::KeyboardEvent; |
23 | | using namespace mozilla::widget; |
24 | | |
25 | | namespace mozilla { |
26 | | |
27 | | /****************************************************************************** |
28 | | * TextInputProcessorNotification |
29 | | ******************************************************************************/ |
30 | | |
31 | | class TextInputProcessorNotification final : |
32 | | public nsITextInputProcessorNotification |
33 | | { |
34 | | typedef IMENotification::SelectionChangeData SelectionChangeData; |
35 | | typedef IMENotification::SelectionChangeDataBase SelectionChangeDataBase; |
36 | | typedef IMENotification::TextChangeData TextChangeData; |
37 | | typedef IMENotification::TextChangeDataBase TextChangeDataBase; |
38 | | |
39 | | public: |
40 | | explicit TextInputProcessorNotification(const char* aType) |
41 | | : mType(aType) |
42 | | , mTextChangeData() |
43 | 0 | { |
44 | 0 | } |
45 | | |
46 | | explicit TextInputProcessorNotification( |
47 | | const TextChangeDataBase& aTextChangeData) |
48 | | : mType("notify-text-change") |
49 | | , mTextChangeData(aTextChangeData) |
50 | 0 | { |
51 | 0 | } |
52 | | |
53 | | explicit TextInputProcessorNotification( |
54 | | const SelectionChangeDataBase& aSelectionChangeData) |
55 | | : mType("notify-selection-change") |
56 | | , mSelectionChangeData(aSelectionChangeData) |
57 | 0 | { |
58 | 0 | // SelectionChangeDataBase::mString still refers nsString instance owned |
59 | 0 | // by aSelectionChangeData. So, this needs to copy the instance. |
60 | 0 | nsString* string = new nsString(aSelectionChangeData.String()); |
61 | 0 | mSelectionChangeData.mString = string; |
62 | 0 | } |
63 | | |
64 | | NS_DECL_ISUPPORTS |
65 | | |
66 | | NS_IMETHOD GetType(nsACString& aType) final |
67 | 0 | { |
68 | 0 | aType = mType; |
69 | 0 | return NS_OK; |
70 | 0 | } |
71 | | |
72 | | // "notify-text-change" and "notify-selection-change" |
73 | | NS_IMETHOD GetOffset(uint32_t* aOffset) final |
74 | 0 | { |
75 | 0 | if (NS_WARN_IF(!aOffset)) { |
76 | 0 | return NS_ERROR_INVALID_ARG; |
77 | 0 | } |
78 | 0 | if (IsSelectionChange()) { |
79 | 0 | *aOffset = mSelectionChangeData.mOffset; |
80 | 0 | return NS_OK; |
81 | 0 | } |
82 | 0 | if (IsTextChange()) { |
83 | 0 | *aOffset = mTextChangeData.mStartOffset; |
84 | 0 | return NS_OK; |
85 | 0 | } |
86 | 0 | return NS_ERROR_NOT_AVAILABLE; |
87 | 0 | } |
88 | | |
89 | | // "notify-selection-change" |
90 | | NS_IMETHOD GetText(nsAString& aText) final |
91 | 0 | { |
92 | 0 | if (IsSelectionChange()) { |
93 | 0 | aText = mSelectionChangeData.String(); |
94 | 0 | return NS_OK; |
95 | 0 | } |
96 | 0 | return NS_ERROR_NOT_AVAILABLE; |
97 | 0 | } |
98 | | |
99 | | NS_IMETHOD GetCollapsed(bool* aCollapsed) final |
100 | 0 | { |
101 | 0 | if (NS_WARN_IF(!aCollapsed)) { |
102 | 0 | return NS_ERROR_INVALID_ARG; |
103 | 0 | } |
104 | 0 | if (IsSelectionChange()) { |
105 | 0 | *aCollapsed = mSelectionChangeData.IsCollapsed(); |
106 | 0 | return NS_OK; |
107 | 0 | } |
108 | 0 | return NS_ERROR_NOT_AVAILABLE; |
109 | 0 | } |
110 | | |
111 | | NS_IMETHOD GetLength(uint32_t* aLength) final |
112 | 0 | { |
113 | 0 | if (NS_WARN_IF(!aLength)) { |
114 | 0 | return NS_ERROR_INVALID_ARG; |
115 | 0 | } |
116 | 0 | if (IsSelectionChange()) { |
117 | 0 | *aLength = mSelectionChangeData.Length(); |
118 | 0 | return NS_OK; |
119 | 0 | } |
120 | 0 | return NS_ERROR_NOT_AVAILABLE; |
121 | 0 | } |
122 | | |
123 | | NS_IMETHOD GetReversed(bool* aReversed) final |
124 | 0 | { |
125 | 0 | if (NS_WARN_IF(!aReversed)) { |
126 | 0 | return NS_ERROR_INVALID_ARG; |
127 | 0 | } |
128 | 0 | if (IsSelectionChange()) { |
129 | 0 | *aReversed = mSelectionChangeData.mReversed; |
130 | 0 | return NS_OK; |
131 | 0 | } |
132 | 0 | return NS_ERROR_NOT_AVAILABLE; |
133 | 0 | } |
134 | | |
135 | | NS_IMETHOD GetWritingMode(nsACString& aWritingMode) final |
136 | 0 | { |
137 | 0 | if (IsSelectionChange()) { |
138 | 0 | WritingMode writingMode = mSelectionChangeData.GetWritingMode(); |
139 | 0 | if (!writingMode.IsVertical()) { |
140 | 0 | aWritingMode.AssignLiteral("horizontal-tb"); |
141 | 0 | } else if (writingMode.IsVerticalLR()) { |
142 | 0 | aWritingMode.AssignLiteral("vertical-lr"); |
143 | 0 | } else { |
144 | 0 | aWritingMode.AssignLiteral("vertical-rl"); |
145 | 0 | } |
146 | 0 | return NS_OK; |
147 | 0 | } |
148 | 0 | return NS_ERROR_NOT_AVAILABLE; |
149 | 0 | } |
150 | | |
151 | | NS_IMETHOD GetCausedByComposition(bool* aCausedByComposition) final |
152 | 0 | { |
153 | 0 | if (NS_WARN_IF(!aCausedByComposition)) { |
154 | 0 | return NS_ERROR_INVALID_ARG; |
155 | 0 | } |
156 | 0 | if (IsSelectionChange()) { |
157 | 0 | *aCausedByComposition = mSelectionChangeData.mCausedByComposition; |
158 | 0 | return NS_OK; |
159 | 0 | } |
160 | 0 | return NS_ERROR_NOT_AVAILABLE; |
161 | 0 | } |
162 | | |
163 | | NS_IMETHOD GetCausedBySelectionEvent( |
164 | | bool* aCausedBySelectionEvent) final |
165 | 0 | { |
166 | 0 | if (NS_WARN_IF(!aCausedBySelectionEvent)) { |
167 | 0 | return NS_ERROR_INVALID_ARG; |
168 | 0 | } |
169 | 0 | if (IsSelectionChange()) { |
170 | 0 | *aCausedBySelectionEvent = mSelectionChangeData.mCausedBySelectionEvent; |
171 | 0 | return NS_OK; |
172 | 0 | } |
173 | 0 | return NS_ERROR_NOT_AVAILABLE; |
174 | 0 | } |
175 | | |
176 | | NS_IMETHOD GetOccurredDuringComposition( |
177 | | bool* aOccurredDuringComposition) final |
178 | 0 | { |
179 | 0 | if (NS_WARN_IF(!aOccurredDuringComposition)) { |
180 | 0 | return NS_ERROR_INVALID_ARG; |
181 | 0 | } |
182 | 0 | if (IsSelectionChange()) { |
183 | 0 | *aOccurredDuringComposition = |
184 | 0 | mSelectionChangeData.mOccurredDuringComposition; |
185 | 0 | return NS_OK; |
186 | 0 | } |
187 | 0 | return NS_ERROR_NOT_AVAILABLE; |
188 | 0 | } |
189 | | |
190 | | // "notify-text-change" |
191 | | NS_IMETHOD GetRemovedLength(uint32_t* aLength) final |
192 | 0 | { |
193 | 0 | if (NS_WARN_IF(!aLength)) { |
194 | 0 | return NS_ERROR_INVALID_ARG; |
195 | 0 | } |
196 | 0 | if (IsTextChange()) { |
197 | 0 | *aLength = mTextChangeData.OldLength(); |
198 | 0 | return NS_OK; |
199 | 0 | } |
200 | 0 | return NS_ERROR_NOT_AVAILABLE; |
201 | 0 | } |
202 | | |
203 | | NS_IMETHOD GetAddedLength(uint32_t* aLength) final |
204 | 0 | { |
205 | 0 | if (NS_WARN_IF(!aLength)) { |
206 | 0 | return NS_ERROR_INVALID_ARG; |
207 | 0 | } |
208 | 0 | if (IsTextChange()) { |
209 | 0 | *aLength = mTextChangeData.NewLength(); |
210 | 0 | return NS_OK; |
211 | 0 | } |
212 | 0 | return NS_ERROR_NOT_AVAILABLE; |
213 | 0 | } |
214 | | |
215 | | NS_IMETHOD GetCausedOnlyByComposition( |
216 | | bool* aCausedOnlyByComposition) final |
217 | 0 | { |
218 | 0 | if (NS_WARN_IF(!aCausedOnlyByComposition)) { |
219 | 0 | return NS_ERROR_INVALID_ARG; |
220 | 0 | } |
221 | 0 | if (IsTextChange()) { |
222 | 0 | *aCausedOnlyByComposition = mTextChangeData.mCausedOnlyByComposition; |
223 | 0 | return NS_OK; |
224 | 0 | } |
225 | 0 | return NS_ERROR_NOT_AVAILABLE; |
226 | 0 | } |
227 | | |
228 | | NS_IMETHOD GetIncludingChangesDuringComposition( |
229 | | bool* aIncludingChangesDuringComposition) final |
230 | 0 | { |
231 | 0 | if (NS_WARN_IF(!aIncludingChangesDuringComposition)) { |
232 | 0 | return NS_ERROR_INVALID_ARG; |
233 | 0 | } |
234 | 0 | if (IsTextChange()) { |
235 | 0 | *aIncludingChangesDuringComposition = |
236 | 0 | mTextChangeData.mIncludingChangesDuringComposition; |
237 | 0 | return NS_OK; |
238 | 0 | } |
239 | 0 | return NS_ERROR_NOT_AVAILABLE; |
240 | 0 | } |
241 | | |
242 | | NS_IMETHOD GetIncludingChangesWithoutComposition( |
243 | | bool* aIncludingChangesWithoutComposition) final |
244 | 0 | { |
245 | 0 | if (NS_WARN_IF(!aIncludingChangesWithoutComposition)) { |
246 | 0 | return NS_ERROR_INVALID_ARG; |
247 | 0 | } |
248 | 0 | if (IsTextChange()) { |
249 | 0 | *aIncludingChangesWithoutComposition = |
250 | 0 | mTextChangeData.mIncludingChangesWithoutComposition; |
251 | 0 | return NS_OK; |
252 | 0 | } |
253 | 0 | return NS_ERROR_NOT_AVAILABLE; |
254 | 0 | } |
255 | | |
256 | | protected: |
257 | | virtual ~TextInputProcessorNotification() |
258 | 0 | { |
259 | 0 | if (IsSelectionChange()) { |
260 | 0 | delete mSelectionChangeData.mString; |
261 | 0 | mSelectionChangeData.mString = nullptr; |
262 | 0 | } |
263 | 0 | } |
264 | | |
265 | | bool IsTextChange() const |
266 | 0 | { |
267 | 0 | return mType.EqualsLiteral("notify-text-change"); |
268 | 0 | } |
269 | | |
270 | | bool IsSelectionChange() const |
271 | 0 | { |
272 | 0 | return mType.EqualsLiteral("notify-selection-change"); |
273 | 0 | } |
274 | | |
275 | | private: |
276 | | nsAutoCString mType; |
277 | | union |
278 | | { |
279 | | TextChangeDataBase mTextChangeData; |
280 | | SelectionChangeDataBase mSelectionChangeData; |
281 | | }; |
282 | | |
283 | | TextInputProcessorNotification() |
284 | | : mTextChangeData() |
285 | 0 | { |
286 | 0 | } |
287 | | }; |
288 | | |
289 | | NS_IMPL_ISUPPORTS(TextInputProcessorNotification, |
290 | | nsITextInputProcessorNotification) |
291 | | |
292 | | /****************************************************************************** |
293 | | * TextInputProcessor |
294 | | ******************************************************************************/ |
295 | | |
296 | | NS_IMPL_ISUPPORTS(TextInputProcessor, |
297 | | nsITextInputProcessor, |
298 | | TextEventDispatcherListener, |
299 | | nsISupportsWeakReference) |
300 | | |
301 | | TextInputProcessor::TextInputProcessor() |
302 | | : mDispatcher(nullptr) |
303 | | , mForTests(false) |
304 | 0 | { |
305 | 0 | } |
306 | | |
307 | | TextInputProcessor::~TextInputProcessor() |
308 | 0 | { |
309 | 0 | if (mDispatcher && mDispatcher->IsComposing()) { |
310 | 0 | // If this is composing and not canceling the composition, nobody can steal |
311 | 0 | // the rights of TextEventDispatcher from this instance. Therefore, this |
312 | 0 | // needs to cancel the composition here. |
313 | 0 | if (NS_SUCCEEDED(IsValidStateForComposition())) { |
314 | 0 | RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher); |
315 | 0 | nsEventStatus status = nsEventStatus_eIgnore; |
316 | 0 | kungFuDeathGrip->CommitComposition(status, &EmptyString()); |
317 | 0 | } |
318 | 0 | } |
319 | 0 | } |
320 | | |
321 | | bool |
322 | | TextInputProcessor::IsComposing() const |
323 | 0 | { |
324 | 0 | return mDispatcher && mDispatcher->IsComposing(); |
325 | 0 | } |
326 | | |
327 | | NS_IMETHODIMP |
328 | | TextInputProcessor::GetHasComposition(bool* aHasComposition) |
329 | 0 | { |
330 | 0 | MOZ_RELEASE_ASSERT(aHasComposition, "aHasComposition must not be nullptr"); |
331 | 0 | MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); |
332 | 0 | *aHasComposition = IsComposing(); |
333 | 0 | return NS_OK; |
334 | 0 | } |
335 | | |
336 | | NS_IMETHODIMP |
337 | | TextInputProcessor::BeginInputTransaction( |
338 | | mozIDOMWindow* aWindow, |
339 | | nsITextInputProcessorCallback* aCallback, |
340 | | bool* aSucceeded) |
341 | 0 | { |
342 | 0 | MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr"); |
343 | 0 | MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); |
344 | 0 | if (NS_WARN_IF(!aCallback)) { |
345 | 0 | *aSucceeded = false; |
346 | 0 | return NS_ERROR_INVALID_ARG; |
347 | 0 | } |
348 | 0 | return BeginInputTransactionInternal(aWindow, aCallback, false, *aSucceeded); |
349 | 0 | } |
350 | | |
351 | | NS_IMETHODIMP |
352 | | TextInputProcessor::BeginInputTransactionForTests( |
353 | | mozIDOMWindow* aWindow, |
354 | | nsITextInputProcessorCallback* aCallback, |
355 | | uint8_t aOptionalArgc, |
356 | | bool* aSucceeded) |
357 | 0 | { |
358 | 0 | MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr"); |
359 | 0 | MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); |
360 | 0 | nsITextInputProcessorCallback* callback = |
361 | 0 | aOptionalArgc >= 1 ? aCallback : nullptr; |
362 | 0 | return BeginInputTransactionInternal(aWindow, callback, true, *aSucceeded); |
363 | 0 | } |
364 | | |
365 | | nsresult |
366 | | TextInputProcessor::BeginInputTransactionInternal( |
367 | | mozIDOMWindow* aWindow, |
368 | | nsITextInputProcessorCallback* aCallback, |
369 | | bool aForTests, |
370 | | bool& aSucceeded) |
371 | 0 | { |
372 | 0 | aSucceeded = false; |
373 | 0 | if (NS_WARN_IF(!aWindow)) { |
374 | 0 | return NS_ERROR_INVALID_ARG; |
375 | 0 | } |
376 | 0 | nsCOMPtr<nsPIDOMWindowInner> pWindow = nsPIDOMWindowInner::From(aWindow); |
377 | 0 | if (NS_WARN_IF(!pWindow)) { |
378 | 0 | return NS_ERROR_INVALID_ARG; |
379 | 0 | } |
380 | 0 | nsCOMPtr<nsIDocShell> docShell(pWindow->GetDocShell()); |
381 | 0 | if (NS_WARN_IF(!docShell)) { |
382 | 0 | return NS_ERROR_FAILURE; |
383 | 0 | } |
384 | 0 | RefPtr<nsPresContext> presContext; |
385 | 0 | nsresult rv = docShell->GetPresContext(getter_AddRefs(presContext)); |
386 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
387 | 0 | return rv; |
388 | 0 | } |
389 | 0 | if (NS_WARN_IF(!presContext)) { |
390 | 0 | return NS_ERROR_FAILURE; |
391 | 0 | } |
392 | 0 | nsCOMPtr<nsIWidget> widget = presContext->GetRootWidget(); |
393 | 0 | if (NS_WARN_IF(!widget)) { |
394 | 0 | return NS_ERROR_FAILURE; |
395 | 0 | } |
396 | 0 | |
397 | 0 | RefPtr<TextEventDispatcher> dispatcher = widget->GetTextEventDispatcher(); |
398 | 0 | MOZ_RELEASE_ASSERT(dispatcher, "TextEventDispatcher must not be null"); |
399 | 0 |
|
400 | 0 | // If the instance was initialized and is being initialized for same |
401 | 0 | // dispatcher and same purpose, we don't need to initialize the dispatcher |
402 | 0 | // again. |
403 | 0 | if (mDispatcher && dispatcher == mDispatcher && aCallback == mCallback && |
404 | 0 | aForTests == mForTests) { |
405 | 0 | aSucceeded = true; |
406 | 0 | return NS_OK; |
407 | 0 | } |
408 | 0 | |
409 | 0 | // If this instance is composing or dispatching an event, don't allow to |
410 | 0 | // initialize again. Especially, if we allow to begin input transaction with |
411 | 0 | // another TextEventDispatcher during dispatching an event, it may cause that |
412 | 0 | // nobody cannot begin input transaction with it if the last event causes |
413 | 0 | // opening modal dialog. |
414 | 0 | if (mDispatcher && |
415 | 0 | (mDispatcher->IsComposing() || mDispatcher->IsDispatchingEvent())) { |
416 | 0 | return NS_ERROR_ALREADY_INITIALIZED; |
417 | 0 | } |
418 | 0 | |
419 | 0 | // And also if another instance is composing with the new dispatcher or |
420 | 0 | // dispatching an event, it'll fail to steal its ownership. Then, we should |
421 | 0 | // not throw an exception, just return false. |
422 | 0 | if (dispatcher->IsComposing() || dispatcher->IsDispatchingEvent()) { |
423 | 0 | return NS_OK; |
424 | 0 | } |
425 | 0 | |
426 | 0 | // This instance has finished preparing to link to the dispatcher. Therefore, |
427 | 0 | // let's forget the old dispatcher and purpose. |
428 | 0 | if (mDispatcher) { |
429 | 0 | mDispatcher->EndInputTransaction(this); |
430 | 0 | if (NS_WARN_IF(mDispatcher)) { |
431 | 0 | // Forcibly initialize the members if we failed to end the input |
432 | 0 | // transaction. |
433 | 0 | UnlinkFromTextEventDispatcher(); |
434 | 0 | } |
435 | 0 | } |
436 | 0 |
|
437 | 0 | if (aForTests) { |
438 | 0 | bool isAPZAware = gfxPrefs::TestEventsAsyncEnabled(); |
439 | 0 | rv = dispatcher->BeginTestInputTransaction(this, isAPZAware); |
440 | 0 | } else { |
441 | 0 | rv = dispatcher->BeginInputTransaction(this); |
442 | 0 | } |
443 | 0 |
|
444 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
445 | 0 | return rv; |
446 | 0 | } |
447 | 0 | |
448 | 0 | mDispatcher = dispatcher; |
449 | 0 | mCallback = aCallback; |
450 | 0 | mForTests = aForTests; |
451 | 0 | aSucceeded = true; |
452 | 0 | return NS_OK; |
453 | 0 | } |
454 | | |
455 | | void |
456 | | TextInputProcessor::UnlinkFromTextEventDispatcher() |
457 | 0 | { |
458 | 0 | mDispatcher = nullptr; |
459 | 0 | mForTests = false; |
460 | 0 | if (mCallback) { |
461 | 0 | nsCOMPtr<nsITextInputProcessorCallback> callback(mCallback); |
462 | 0 | mCallback = nullptr; |
463 | 0 |
|
464 | 0 | RefPtr<TextInputProcessorNotification> notification = |
465 | 0 | new TextInputProcessorNotification("notify-end-input-transaction"); |
466 | 0 | bool result = false; |
467 | 0 | callback->OnNotify(this, notification, &result); |
468 | 0 | } |
469 | 0 | } |
470 | | |
471 | | nsresult |
472 | | TextInputProcessor::IsValidStateForComposition() |
473 | 0 | { |
474 | 0 | if (NS_WARN_IF(!mDispatcher)) { |
475 | 0 | return NS_ERROR_NOT_INITIALIZED; |
476 | 0 | } |
477 | 0 | |
478 | 0 | nsresult rv = mDispatcher->GetState(); |
479 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
480 | 0 | return rv; |
481 | 0 | } |
482 | 0 | |
483 | 0 | return NS_OK; |
484 | 0 | } |
485 | | |
486 | | bool |
487 | | TextInputProcessor::IsValidEventTypeForComposition( |
488 | | const WidgetKeyboardEvent& aKeyboardEvent) const |
489 | 0 | { |
490 | 0 | // The key event type of composition methods must be "", "keydown" or "keyup". |
491 | 0 | if (aKeyboardEvent.mMessage == eKeyDown || |
492 | 0 | aKeyboardEvent.mMessage == eKeyUp) { |
493 | 0 | return true; |
494 | 0 | } |
495 | 0 | if (aKeyboardEvent.mMessage == eUnidentifiedEvent && |
496 | 0 | aKeyboardEvent.mSpecifiedEventType && |
497 | 0 | nsDependentAtomString( |
498 | 0 | aKeyboardEvent.mSpecifiedEventType).EqualsLiteral("on")) { |
499 | 0 | return true; |
500 | 0 | } |
501 | 0 | return false; |
502 | 0 | } |
503 | | |
504 | | TextInputProcessor::EventDispatcherResult |
505 | | TextInputProcessor::MaybeDispatchKeydownForComposition( |
506 | | const WidgetKeyboardEvent* aKeyboardEvent, |
507 | | uint32_t aKeyFlags) |
508 | 0 | { |
509 | 0 | EventDispatcherResult result; |
510 | 0 |
|
511 | 0 | result.mResult = IsValidStateForComposition(); |
512 | 0 | if (NS_WARN_IF(NS_FAILED(result.mResult))) { |
513 | 0 | result.mCanContinue = false; |
514 | 0 | return result; |
515 | 0 | } |
516 | 0 | |
517 | 0 | if (!aKeyboardEvent) { |
518 | 0 | return result; |
519 | 0 | } |
520 | 0 | |
521 | 0 | // If the mMessage is eKeyUp, the caller doesn't want TIP to dispatch |
522 | 0 | // eKeyDown event. |
523 | 0 | if (aKeyboardEvent->mMessage == eKeyUp) { |
524 | 0 | return result; |
525 | 0 | } |
526 | 0 | |
527 | 0 | // Modifier keys are not allowed because managing modifier state in this |
528 | 0 | // method makes this messy. |
529 | 0 | if (NS_WARN_IF(aKeyboardEvent->IsModifierKeyEvent())) { |
530 | 0 | result.mResult = NS_ERROR_INVALID_ARG; |
531 | 0 | result.mCanContinue = false; |
532 | 0 | return result; |
533 | 0 | } |
534 | 0 | |
535 | 0 | uint32_t consumedFlags = 0; |
536 | 0 |
|
537 | 0 | result.mResult = KeydownInternal(*aKeyboardEvent, aKeyFlags, false, |
538 | 0 | consumedFlags); |
539 | 0 | result.mDoDefault = !consumedFlags; |
540 | 0 | if (NS_WARN_IF(NS_FAILED(result.mResult))) { |
541 | 0 | result.mCanContinue = false; |
542 | 0 | return result; |
543 | 0 | } |
544 | 0 | |
545 | 0 | result.mCanContinue = NS_SUCCEEDED(IsValidStateForComposition()); |
546 | 0 | return result; |
547 | 0 | } |
548 | | |
549 | | TextInputProcessor::EventDispatcherResult |
550 | | TextInputProcessor::MaybeDispatchKeyupForComposition( |
551 | | const WidgetKeyboardEvent* aKeyboardEvent, |
552 | | uint32_t aKeyFlags) |
553 | 0 | { |
554 | 0 | EventDispatcherResult result; |
555 | 0 |
|
556 | 0 | if (!aKeyboardEvent) { |
557 | 0 | return result; |
558 | 0 | } |
559 | 0 | |
560 | 0 | // If the mMessage is eKeyDown, the caller doesn't want TIP to dispatch |
561 | 0 | // eKeyUp event. |
562 | 0 | if (aKeyboardEvent->mMessage == eKeyDown) { |
563 | 0 | return result; |
564 | 0 | } |
565 | 0 | |
566 | 0 | // If the widget has been destroyed, we can do nothing here. |
567 | 0 | result.mResult = IsValidStateForComposition(); |
568 | 0 | if (NS_FAILED(result.mResult)) { |
569 | 0 | result.mCanContinue = false; |
570 | 0 | return result; |
571 | 0 | } |
572 | 0 | |
573 | 0 | result.mResult = KeyupInternal(*aKeyboardEvent, aKeyFlags, result.mDoDefault); |
574 | 0 | if (NS_WARN_IF(NS_FAILED(result.mResult))) { |
575 | 0 | result.mCanContinue = false; |
576 | 0 | return result; |
577 | 0 | } |
578 | 0 | |
579 | 0 | result.mCanContinue = NS_SUCCEEDED(IsValidStateForComposition()); |
580 | 0 | return result; |
581 | 0 | } |
582 | | |
583 | | nsresult |
584 | | TextInputProcessor::PrepareKeyboardEventForComposition( |
585 | | KeyboardEvent* aDOMKeyEvent, |
586 | | uint32_t& aKeyFlags, |
587 | | uint8_t aOptionalArgc, |
588 | | WidgetKeyboardEvent*& aKeyboardEvent) |
589 | 0 | { |
590 | 0 | aKeyboardEvent = nullptr; |
591 | 0 |
|
592 | 0 | aKeyboardEvent = |
593 | 0 | aOptionalArgc && aDOMKeyEvent ? |
594 | 0 | aDOMKeyEvent->WidgetEventPtr()->AsKeyboardEvent() : nullptr; |
595 | 0 | if (!aKeyboardEvent || aOptionalArgc < 2) { |
596 | 0 | aKeyFlags = 0; |
597 | 0 | } |
598 | 0 |
|
599 | 0 | if (!aKeyboardEvent) { |
600 | 0 | return NS_OK; |
601 | 0 | } |
602 | 0 | |
603 | 0 | if (NS_WARN_IF(!IsValidEventTypeForComposition(*aKeyboardEvent))) { |
604 | 0 | return NS_ERROR_INVALID_ARG; |
605 | 0 | } |
606 | 0 | |
607 | 0 | return NS_OK; |
608 | 0 | } |
609 | | |
610 | | NS_IMETHODIMP |
611 | | TextInputProcessor::StartComposition(Event* aDOMKeyEvent, |
612 | | uint32_t aKeyFlags, |
613 | | uint8_t aOptionalArgc, |
614 | | bool* aSucceeded) |
615 | 0 | { |
616 | 0 | MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr"); |
617 | 0 | MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); |
618 | 0 | *aSucceeded = false; |
619 | 0 |
|
620 | 0 | RefPtr<KeyboardEvent> keyEvent; |
621 | 0 | if (aDOMKeyEvent) { |
622 | 0 | keyEvent = aDOMKeyEvent->AsKeyboardEvent(); |
623 | 0 | if (NS_WARN_IF(!keyEvent)) { |
624 | 0 | return NS_ERROR_INVALID_ARG; |
625 | 0 | } |
626 | 0 | } |
627 | 0 | |
628 | 0 | RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher); |
629 | 0 |
|
630 | 0 | WidgetKeyboardEvent* keyboardEvent; |
631 | 0 | nsresult rv = |
632 | 0 | PrepareKeyboardEventForComposition(keyEvent, aKeyFlags, aOptionalArgc, |
633 | 0 | keyboardEvent); |
634 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
635 | 0 | return rv; |
636 | 0 | } |
637 | 0 | |
638 | 0 | EventDispatcherResult dispatcherResult = |
639 | 0 | MaybeDispatchKeydownForComposition(keyboardEvent, aKeyFlags); |
640 | 0 | if (NS_WARN_IF(NS_FAILED(dispatcherResult.mResult)) || |
641 | 0 | !dispatcherResult.mCanContinue) { |
642 | 0 | return dispatcherResult.mResult; |
643 | 0 | } |
644 | 0 | |
645 | 0 | if (dispatcherResult.mDoDefault) { |
646 | 0 | nsEventStatus status = nsEventStatus_eIgnore; |
647 | 0 | rv = kungFuDeathGrip->StartComposition(status); |
648 | 0 | *aSucceeded = status != nsEventStatus_eConsumeNoDefault && |
649 | 0 | kungFuDeathGrip && kungFuDeathGrip->IsComposing(); |
650 | 0 | } |
651 | 0 |
|
652 | 0 | MaybeDispatchKeyupForComposition(keyboardEvent, aKeyFlags); |
653 | 0 |
|
654 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
655 | 0 | return rv; |
656 | 0 | } |
657 | 0 | return NS_OK; |
658 | 0 | } |
659 | | |
660 | | NS_IMETHODIMP |
661 | | TextInputProcessor::SetPendingCompositionString(const nsAString& aString) |
662 | 0 | { |
663 | 0 | MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); |
664 | 0 | RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher); |
665 | 0 | nsresult rv = IsValidStateForComposition(); |
666 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
667 | 0 | return rv; |
668 | 0 | } |
669 | 0 | return kungFuDeathGrip->SetPendingCompositionString(aString); |
670 | 0 | } |
671 | | |
672 | | NS_IMETHODIMP |
673 | | TextInputProcessor::AppendClauseToPendingComposition(uint32_t aLength, |
674 | | uint32_t aAttribute) |
675 | 0 | { |
676 | 0 | MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); |
677 | 0 | RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher); |
678 | 0 | TextRangeType textRangeType; |
679 | 0 | switch (aAttribute) { |
680 | 0 | case ATTR_RAW_CLAUSE: |
681 | 0 | case ATTR_SELECTED_RAW_CLAUSE: |
682 | 0 | case ATTR_CONVERTED_CLAUSE: |
683 | 0 | case ATTR_SELECTED_CLAUSE: |
684 | 0 | textRangeType = ToTextRangeType(aAttribute); |
685 | 0 | break; |
686 | 0 | default: |
687 | 0 | return NS_ERROR_INVALID_ARG; |
688 | 0 | } |
689 | 0 | nsresult rv = IsValidStateForComposition(); |
690 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
691 | 0 | return rv; |
692 | 0 | } |
693 | 0 | return kungFuDeathGrip->AppendClauseToPendingComposition(aLength, textRangeType); |
694 | 0 | } |
695 | | |
696 | | NS_IMETHODIMP |
697 | | TextInputProcessor::SetCaretInPendingComposition(uint32_t aOffset) |
698 | 0 | { |
699 | 0 | MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); |
700 | 0 | RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher); |
701 | 0 | nsresult rv = IsValidStateForComposition(); |
702 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
703 | 0 | return rv; |
704 | 0 | } |
705 | 0 | return kungFuDeathGrip->SetCaretInPendingComposition(aOffset, 0); |
706 | 0 | } |
707 | | |
708 | | NS_IMETHODIMP |
709 | | TextInputProcessor::FlushPendingComposition(Event* aDOMKeyEvent, |
710 | | uint32_t aKeyFlags, |
711 | | uint8_t aOptionalArgc, |
712 | | bool* aSucceeded) |
713 | 0 | { |
714 | 0 | MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr"); |
715 | 0 | MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); |
716 | 0 |
|
717 | 0 | // Even if this doesn't flush pending composition actually, we need to reset |
718 | 0 | // pending composition for starting next composition with new user input. |
719 | 0 | AutoPendingCompositionResetter resetter(this); |
720 | 0 |
|
721 | 0 | *aSucceeded = false; |
722 | 0 | RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher); |
723 | 0 | bool wasComposing = IsComposing(); |
724 | 0 |
|
725 | 0 | RefPtr<KeyboardEvent> keyEvent; |
726 | 0 | if (aDOMKeyEvent) { |
727 | 0 | keyEvent = aDOMKeyEvent->AsKeyboardEvent(); |
728 | 0 | if (NS_WARN_IF(!keyEvent)) { |
729 | 0 | return NS_ERROR_INVALID_ARG; |
730 | 0 | } |
731 | 0 | } |
732 | 0 | |
733 | 0 | WidgetKeyboardEvent* keyboardEvent; |
734 | 0 | nsresult rv = |
735 | 0 | PrepareKeyboardEventForComposition(keyEvent, aKeyFlags, aOptionalArgc, |
736 | 0 | keyboardEvent); |
737 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
738 | 0 | return rv; |
739 | 0 | } |
740 | 0 | |
741 | 0 | EventDispatcherResult dispatcherResult = |
742 | 0 | MaybeDispatchKeydownForComposition(keyboardEvent, aKeyFlags); |
743 | 0 | if (NS_WARN_IF(NS_FAILED(dispatcherResult.mResult)) || |
744 | 0 | !dispatcherResult.mCanContinue) { |
745 | 0 | return dispatcherResult.mResult; |
746 | 0 | } |
747 | 0 | |
748 | 0 | // Even if the preceding keydown event was consumed, if the composition |
749 | 0 | // was already started, we shouldn't prevent the change of composition. |
750 | 0 | if (dispatcherResult.mDoDefault || wasComposing) { |
751 | 0 | // Preceding keydown event may cause destroying the widget. |
752 | 0 | if (NS_FAILED(IsValidStateForComposition())) { |
753 | 0 | return NS_OK; |
754 | 0 | } |
755 | 0 | nsEventStatus status = nsEventStatus_eIgnore; |
756 | 0 | rv = kungFuDeathGrip->FlushPendingComposition(status); |
757 | 0 | *aSucceeded = status != nsEventStatus_eConsumeNoDefault; |
758 | 0 | } |
759 | 0 |
|
760 | 0 | MaybeDispatchKeyupForComposition(keyboardEvent, aKeyFlags); |
761 | 0 |
|
762 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
763 | 0 | return rv; |
764 | 0 | } |
765 | 0 | return NS_OK; |
766 | 0 | } |
767 | | |
768 | | NS_IMETHODIMP |
769 | | TextInputProcessor::CommitComposition(Event* aDOMKeyEvent, |
770 | | uint32_t aKeyFlags, |
771 | | uint8_t aOptionalArgc) |
772 | 0 | { |
773 | 0 | MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); |
774 | 0 |
|
775 | 0 | RefPtr<KeyboardEvent> keyEvent; |
776 | 0 | if (aDOMKeyEvent) { |
777 | 0 | keyEvent = aDOMKeyEvent->AsKeyboardEvent(); |
778 | 0 | if (NS_WARN_IF(!keyEvent)) { |
779 | 0 | return NS_ERROR_INVALID_ARG; |
780 | 0 | } |
781 | 0 | } |
782 | 0 | |
783 | 0 | WidgetKeyboardEvent* keyboardEvent; |
784 | 0 | nsresult rv = |
785 | 0 | PrepareKeyboardEventForComposition(keyEvent, aKeyFlags, aOptionalArgc, |
786 | 0 | keyboardEvent); |
787 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
788 | 0 | return rv; |
789 | 0 | } |
790 | 0 | |
791 | 0 | return CommitCompositionInternal(keyboardEvent, aKeyFlags); |
792 | 0 | } |
793 | | |
794 | | NS_IMETHODIMP |
795 | | TextInputProcessor::CommitCompositionWith(const nsAString& aCommitString, |
796 | | Event* aDOMKeyEvent, |
797 | | uint32_t aKeyFlags, |
798 | | uint8_t aOptionalArgc, |
799 | | bool* aSucceeded) |
800 | 0 | { |
801 | 0 | MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr"); |
802 | 0 | MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); |
803 | 0 |
|
804 | 0 | RefPtr<KeyboardEvent> keyEvent; |
805 | 0 | if (aDOMKeyEvent) { |
806 | 0 | keyEvent = aDOMKeyEvent->AsKeyboardEvent(); |
807 | 0 | if (NS_WARN_IF(!keyEvent)) { |
808 | 0 | return NS_ERROR_INVALID_ARG; |
809 | 0 | } |
810 | 0 | } |
811 | 0 | |
812 | 0 | WidgetKeyboardEvent* keyboardEvent; |
813 | 0 | nsresult rv = |
814 | 0 | PrepareKeyboardEventForComposition(keyEvent, aKeyFlags, aOptionalArgc, |
815 | 0 | keyboardEvent); |
816 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
817 | 0 | return rv; |
818 | 0 | } |
819 | 0 | |
820 | 0 | return CommitCompositionInternal(keyboardEvent, aKeyFlags, |
821 | 0 | &aCommitString, aSucceeded); |
822 | 0 | } |
823 | | |
824 | | nsresult |
825 | | TextInputProcessor::CommitCompositionInternal( |
826 | | const WidgetKeyboardEvent* aKeyboardEvent, |
827 | | uint32_t aKeyFlags, |
828 | | const nsAString* aCommitString, |
829 | | bool* aSucceeded) |
830 | 0 | { |
831 | 0 | if (aSucceeded) { |
832 | 0 | *aSucceeded = false; |
833 | 0 | } |
834 | 0 | RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher); |
835 | 0 | bool wasComposing = IsComposing(); |
836 | 0 |
|
837 | 0 | EventDispatcherResult dispatcherResult = |
838 | 0 | MaybeDispatchKeydownForComposition(aKeyboardEvent, aKeyFlags); |
839 | 0 | if (NS_WARN_IF(NS_FAILED(dispatcherResult.mResult)) || |
840 | 0 | !dispatcherResult.mCanContinue) { |
841 | 0 | return dispatcherResult.mResult; |
842 | 0 | } |
843 | 0 | |
844 | 0 | // Even if the preceding keydown event was consumed, if the composition |
845 | 0 | // was already started, we shouldn't prevent the commit of composition. |
846 | 0 | nsresult rv = NS_OK; |
847 | 0 | if (dispatcherResult.mDoDefault || wasComposing) { |
848 | 0 | // Preceding keydown event may cause destroying the widget. |
849 | 0 | if (NS_FAILED(IsValidStateForComposition())) { |
850 | 0 | return NS_OK; |
851 | 0 | } |
852 | 0 | nsEventStatus status = nsEventStatus_eIgnore; |
853 | 0 | rv = kungFuDeathGrip->CommitComposition(status, aCommitString); |
854 | 0 | if (aSucceeded) { |
855 | 0 | *aSucceeded = status != nsEventStatus_eConsumeNoDefault; |
856 | 0 | } |
857 | 0 | } |
858 | 0 |
|
859 | 0 | MaybeDispatchKeyupForComposition(aKeyboardEvent, aKeyFlags); |
860 | 0 |
|
861 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
862 | 0 | return rv; |
863 | 0 | } |
864 | 0 | return NS_OK; |
865 | 0 | } |
866 | | |
867 | | NS_IMETHODIMP |
868 | | TextInputProcessor::CancelComposition(Event* aDOMKeyEvent, |
869 | | uint32_t aKeyFlags, |
870 | | uint8_t aOptionalArgc) |
871 | 0 | { |
872 | 0 | MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); |
873 | 0 |
|
874 | 0 | RefPtr<KeyboardEvent> keyEvent; |
875 | 0 | if (aDOMKeyEvent) { |
876 | 0 | keyEvent = aDOMKeyEvent->AsKeyboardEvent(); |
877 | 0 | if (NS_WARN_IF(!keyEvent)) { |
878 | 0 | return NS_ERROR_INVALID_ARG; |
879 | 0 | } |
880 | 0 | } |
881 | 0 | |
882 | 0 | WidgetKeyboardEvent* keyboardEvent; |
883 | 0 | nsresult rv = |
884 | 0 | PrepareKeyboardEventForComposition(keyEvent, aKeyFlags, aOptionalArgc, |
885 | 0 | keyboardEvent); |
886 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
887 | 0 | return rv; |
888 | 0 | } |
889 | 0 | |
890 | 0 | return CancelCompositionInternal(keyboardEvent, aKeyFlags); |
891 | 0 | } |
892 | | |
893 | | nsresult |
894 | | TextInputProcessor::CancelCompositionInternal( |
895 | | const WidgetKeyboardEvent* aKeyboardEvent, |
896 | | uint32_t aKeyFlags) |
897 | 0 | { |
898 | 0 | RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher); |
899 | 0 |
|
900 | 0 | EventDispatcherResult dispatcherResult = |
901 | 0 | MaybeDispatchKeydownForComposition(aKeyboardEvent, aKeyFlags); |
902 | 0 | if (NS_WARN_IF(NS_FAILED(dispatcherResult.mResult)) || |
903 | 0 | !dispatcherResult.mCanContinue) { |
904 | 0 | return dispatcherResult.mResult; |
905 | 0 | } |
906 | 0 | |
907 | 0 | nsEventStatus status = nsEventStatus_eIgnore; |
908 | 0 | nsresult rv = kungFuDeathGrip->CommitComposition(status, &EmptyString()); |
909 | 0 |
|
910 | 0 | MaybeDispatchKeyupForComposition(aKeyboardEvent, aKeyFlags); |
911 | 0 |
|
912 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
913 | 0 | return rv; |
914 | 0 | } |
915 | 0 | return NS_OK; |
916 | 0 | } |
917 | | |
918 | | NS_IMETHODIMP |
919 | | TextInputProcessor::NotifyIME(TextEventDispatcher* aTextEventDispatcher, |
920 | | const IMENotification& aNotification) |
921 | 0 | { |
922 | 0 | // If This is called while this is being initialized, ignore the call. |
923 | 0 | // In such case, this method should return NS_ERROR_NOT_IMPLEMENTED because |
924 | 0 | // we can say, TextInputProcessor doesn't implement any handlers of the |
925 | 0 | // requests and notifications. |
926 | 0 | if (!mDispatcher) { |
927 | 0 | return NS_ERROR_NOT_IMPLEMENTED; |
928 | 0 | } |
929 | 0 | MOZ_ASSERT(aTextEventDispatcher == mDispatcher, |
930 | 0 | "Wrong TextEventDispatcher notifies this"); |
931 | 0 | NS_ASSERTION(mForTests || mCallback, |
932 | 0 | "mCallback can be null only when IME is initialized for tests"); |
933 | 0 | if (mCallback) { |
934 | 0 | RefPtr<TextInputProcessorNotification> notification; |
935 | 0 | switch (aNotification.mMessage) { |
936 | 0 | case REQUEST_TO_COMMIT_COMPOSITION: { |
937 | 0 | NS_ASSERTION(aTextEventDispatcher->IsComposing(), |
938 | 0 | "Why is this requested without composition?"); |
939 | 0 | notification = new TextInputProcessorNotification("request-to-commit"); |
940 | 0 | break; |
941 | 0 | } |
942 | 0 | case REQUEST_TO_CANCEL_COMPOSITION: { |
943 | 0 | NS_ASSERTION(aTextEventDispatcher->IsComposing(), |
944 | 0 | "Why is this requested without composition?"); |
945 | 0 | notification = new TextInputProcessorNotification("request-to-cancel"); |
946 | 0 | break; |
947 | 0 | } |
948 | 0 | case NOTIFY_IME_OF_FOCUS: |
949 | 0 | notification = new TextInputProcessorNotification("notify-focus"); |
950 | 0 | break; |
951 | 0 | case NOTIFY_IME_OF_BLUR: |
952 | 0 | notification = new TextInputProcessorNotification("notify-blur"); |
953 | 0 | break; |
954 | 0 | case NOTIFY_IME_OF_TEXT_CHANGE: |
955 | 0 | notification = new TextInputProcessorNotification( |
956 | 0 | aNotification.mTextChangeData); |
957 | 0 | break; |
958 | 0 | case NOTIFY_IME_OF_SELECTION_CHANGE: |
959 | 0 | notification = new TextInputProcessorNotification( |
960 | 0 | aNotification.mSelectionChangeData); |
961 | 0 | break; |
962 | 0 | case NOTIFY_IME_OF_POSITION_CHANGE: |
963 | 0 | notification = new TextInputProcessorNotification( |
964 | 0 | "notify-position-change"); |
965 | 0 | break; |
966 | 0 | default: |
967 | 0 | return NS_ERROR_NOT_IMPLEMENTED; |
968 | 0 | } |
969 | 0 | MOZ_RELEASE_ASSERT(notification); |
970 | 0 | bool result = false; |
971 | 0 | nsresult rv = mCallback->OnNotify(this, notification, &result); |
972 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
973 | 0 | return rv; |
974 | 0 | } |
975 | 0 | return result ? NS_OK : NS_ERROR_FAILURE; |
976 | 0 | } |
977 | 0 |
|
978 | 0 | switch (aNotification.mMessage) { |
979 | 0 | case REQUEST_TO_COMMIT_COMPOSITION: { |
980 | 0 | NS_ASSERTION(aTextEventDispatcher->IsComposing(), |
981 | 0 | "Why is this requested without composition?"); |
982 | 0 | CommitCompositionInternal(); |
983 | 0 | return NS_OK; |
984 | 0 | } |
985 | 0 | case REQUEST_TO_CANCEL_COMPOSITION: { |
986 | 0 | NS_ASSERTION(aTextEventDispatcher->IsComposing(), |
987 | 0 | "Why is this requested without composition?"); |
988 | 0 | CancelCompositionInternal(); |
989 | 0 | return NS_OK; |
990 | 0 | } |
991 | 0 | default: |
992 | 0 | return NS_ERROR_NOT_IMPLEMENTED; |
993 | 0 | } |
994 | 0 | } |
995 | | |
996 | | NS_IMETHODIMP_(IMENotificationRequests) |
997 | | TextInputProcessor::GetIMENotificationRequests() |
998 | 0 | { |
999 | 0 | // TextInputProcessor should support all change notifications. |
1000 | 0 | return IMENotificationRequests( |
1001 | 0 | IMENotificationRequests::NOTIFY_TEXT_CHANGE | |
1002 | 0 | IMENotificationRequests::NOTIFY_POSITION_CHANGE); |
1003 | 0 | } |
1004 | | |
1005 | | NS_IMETHODIMP_(void) |
1006 | | TextInputProcessor::OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) |
1007 | 0 | { |
1008 | 0 | // If This is called while this is being initialized, ignore the call. |
1009 | 0 | if (!mDispatcher) { |
1010 | 0 | return; |
1011 | 0 | } |
1012 | 0 | MOZ_ASSERT(aTextEventDispatcher == mDispatcher, |
1013 | 0 | "Wrong TextEventDispatcher notifies this"); |
1014 | 0 | UnlinkFromTextEventDispatcher(); |
1015 | 0 | } |
1016 | | |
1017 | | NS_IMETHODIMP_(void) |
1018 | | TextInputProcessor::WillDispatchKeyboardEvent( |
1019 | | TextEventDispatcher* aTextEventDispatcher, |
1020 | | WidgetKeyboardEvent& aKeyboardEvent, |
1021 | | uint32_t aIndexOfKeypress, |
1022 | | void* aData) |
1023 | 0 | { |
1024 | 0 | // TextInputProcessor doesn't set alternative char code nor modify charCode |
1025 | 0 | // even when Ctrl key is pressed. |
1026 | 0 | } |
1027 | | |
1028 | | nsresult |
1029 | | TextInputProcessor::PrepareKeyboardEventToDispatch( |
1030 | | WidgetKeyboardEvent& aKeyboardEvent, |
1031 | | uint32_t aKeyFlags) |
1032 | 0 | { |
1033 | 0 | if (NS_WARN_IF(aKeyboardEvent.mCodeNameIndex == CODE_NAME_INDEX_USE_STRING)) { |
1034 | 0 | return NS_ERROR_INVALID_ARG; |
1035 | 0 | } |
1036 | 0 | if ((aKeyFlags & KEY_NON_PRINTABLE_KEY) && |
1037 | 0 | NS_WARN_IF(aKeyboardEvent.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING)) { |
1038 | 0 | return NS_ERROR_INVALID_ARG; |
1039 | 0 | } |
1040 | 0 | if ((aKeyFlags & KEY_FORCE_PRINTABLE_KEY) && |
1041 | 0 | aKeyboardEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING) { |
1042 | 0 | aKeyboardEvent.GetDOMKeyName(aKeyboardEvent.mKeyValue); |
1043 | 0 | aKeyboardEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING; |
1044 | 0 | } |
1045 | 0 | if (aKeyFlags & KEY_KEEP_KEY_LOCATION_STANDARD) { |
1046 | 0 | // If .location is initialized with specific value, using |
1047 | 0 | // KEY_KEEP_KEY_LOCATION_STANDARD must be a bug of the caller. |
1048 | 0 | // Let's throw an exception for notifying the developer of this bug. |
1049 | 0 | if (NS_WARN_IF(aKeyboardEvent.mLocation)) { |
1050 | 0 | return NS_ERROR_INVALID_ARG; |
1051 | 0 | } |
1052 | 0 | } else if (!aKeyboardEvent.mLocation) { |
1053 | 0 | // If KeyboardEvent.mLocation is 0, it may be uninitialized. If so, we |
1054 | 0 | // should compute proper mLocation value from its .code value. |
1055 | 0 | aKeyboardEvent.mLocation = |
1056 | 0 | WidgetKeyboardEvent::ComputeLocationFromCodeValue( |
1057 | 0 | aKeyboardEvent.mCodeNameIndex); |
1058 | 0 | } |
1059 | 0 |
|
1060 | 0 | if (aKeyFlags & KEY_KEEP_KEYCODE_ZERO) { |
1061 | 0 | // If .keyCode is initialized with specific value, using |
1062 | 0 | // KEY_KEEP_KEYCODE_ZERO must be a bug of the caller. Let's throw an |
1063 | 0 | // exception for notifying the developer of such bug. |
1064 | 0 | if (NS_WARN_IF(aKeyboardEvent.mKeyCode)) { |
1065 | 0 | return NS_ERROR_INVALID_ARG; |
1066 | 0 | } |
1067 | 0 | } else if (!aKeyboardEvent.mKeyCode && |
1068 | 0 | aKeyboardEvent.mKeyNameIndex > KEY_NAME_INDEX_Unidentified && |
1069 | 0 | aKeyboardEvent.mKeyNameIndex < KEY_NAME_INDEX_USE_STRING) { |
1070 | 0 | // If KeyboardEvent.keyCode is 0, it may be uninitialized. If so, we may |
1071 | 0 | // be able to decide a good .keyCode value if the .key value is a |
1072 | 0 | // non-printable key. |
1073 | 0 | aKeyboardEvent.mKeyCode = |
1074 | 0 | WidgetKeyboardEvent::ComputeKeyCodeFromKeyNameIndex( |
1075 | 0 | aKeyboardEvent.mKeyNameIndex); |
1076 | 0 | } |
1077 | 0 |
|
1078 | 0 | aKeyboardEvent.mIsSynthesizedByTIP = (mForTests)? false : true; |
1079 | 0 |
|
1080 | 0 | return NS_OK; |
1081 | 0 | } |
1082 | | |
1083 | | NS_IMETHODIMP |
1084 | | TextInputProcessor::Keydown(Event* aDOMKeyEvent, |
1085 | | uint32_t aKeyFlags, |
1086 | | uint8_t aOptionalArgc, |
1087 | | uint32_t* aConsumedFlags) |
1088 | 0 | { |
1089 | 0 | MOZ_RELEASE_ASSERT(aConsumedFlags, "aConsumedFlags must not be nullptr"); |
1090 | 0 | MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); |
1091 | 0 | if (!aOptionalArgc) { |
1092 | 0 | aKeyFlags = 0; |
1093 | 0 | } |
1094 | 0 | if (NS_WARN_IF(!aDOMKeyEvent)) { |
1095 | 0 | return NS_ERROR_INVALID_ARG; |
1096 | 0 | } |
1097 | 0 | WidgetKeyboardEvent* originalKeyEvent = |
1098 | 0 | aDOMKeyEvent->WidgetEventPtr()->AsKeyboardEvent(); |
1099 | 0 | if (NS_WARN_IF(!originalKeyEvent)) { |
1100 | 0 | return NS_ERROR_INVALID_ARG; |
1101 | 0 | } |
1102 | 0 | return KeydownInternal(*originalKeyEvent, aKeyFlags, true, *aConsumedFlags); |
1103 | 0 | } |
1104 | | |
1105 | | nsresult |
1106 | | TextInputProcessor::KeydownInternal(const WidgetKeyboardEvent& aKeyboardEvent, |
1107 | | uint32_t aKeyFlags, |
1108 | | bool aAllowToDispatchKeypress, |
1109 | | uint32_t& aConsumedFlags) |
1110 | 0 | { |
1111 | 0 | aConsumedFlags = KEYEVENT_NOT_CONSUMED; |
1112 | 0 |
|
1113 | 0 | // We shouldn't modify the internal WidgetKeyboardEvent. |
1114 | 0 | WidgetKeyboardEvent keyEvent(aKeyboardEvent); |
1115 | 0 | nsresult rv = PrepareKeyboardEventToDispatch(keyEvent, aKeyFlags); |
1116 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1117 | 0 | return rv; |
1118 | 0 | } |
1119 | 0 | |
1120 | 0 | aConsumedFlags = (aKeyFlags & KEY_DEFAULT_PREVENTED) ? KEYDOWN_IS_CONSUMED : |
1121 | 0 | KEYEVENT_NOT_CONSUMED; |
1122 | 0 |
|
1123 | 0 | if (WidgetKeyboardEvent::GetModifierForKeyName(keyEvent.mKeyNameIndex)) { |
1124 | 0 | ModifierKeyData modifierKeyData(keyEvent); |
1125 | 0 | if (WidgetKeyboardEvent::IsLockableModifier(keyEvent.mKeyNameIndex)) { |
1126 | 0 | // If the modifier key is lockable modifier key such as CapsLock, |
1127 | 0 | // let's toggle modifier key state at keydown. |
1128 | 0 | ToggleModifierKey(modifierKeyData); |
1129 | 0 | } else { |
1130 | 0 | // Activate modifier flag before dispatching keydown event (i.e., keydown |
1131 | 0 | // event should indicate the releasing modifier is active. |
1132 | 0 | ActivateModifierKey(modifierKeyData); |
1133 | 0 | } |
1134 | 0 | if (aKeyFlags & KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT) { |
1135 | 0 | return NS_OK; |
1136 | 0 | } |
1137 | 0 | } else if (NS_WARN_IF(aKeyFlags & KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT)) { |
1138 | 0 | return NS_ERROR_INVALID_ARG; |
1139 | 0 | } |
1140 | 0 | keyEvent.mModifiers = GetActiveModifiers(); |
1141 | 0 |
|
1142 | 0 | if (!aAllowToDispatchKeypress && |
1143 | 0 | !(aKeyFlags & KEY_DONT_MARK_KEYDOWN_AS_PROCESSED)) { |
1144 | 0 | keyEvent.mKeyCode = NS_VK_PROCESSKEY; |
1145 | 0 | keyEvent.mKeyNameIndex = KEY_NAME_INDEX_Process; |
1146 | 0 | } |
1147 | 0 |
|
1148 | 0 | RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher); |
1149 | 0 | rv = IsValidStateForComposition(); |
1150 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1151 | 0 | return rv; |
1152 | 0 | } |
1153 | 0 | |
1154 | 0 | nsEventStatus status = aConsumedFlags ? nsEventStatus_eConsumeNoDefault : |
1155 | 0 | nsEventStatus_eIgnore; |
1156 | 0 | if (!kungFuDeathGrip->DispatchKeyboardEvent(eKeyDown, keyEvent, status)) { |
1157 | 0 | // If keydown event isn't dispatched, we don't need to dispatch keypress |
1158 | 0 | // events. |
1159 | 0 | return NS_OK; |
1160 | 0 | } |
1161 | 0 | |
1162 | 0 | aConsumedFlags |= |
1163 | 0 | (status == nsEventStatus_eConsumeNoDefault) ? KEYDOWN_IS_CONSUMED : |
1164 | 0 | KEYEVENT_NOT_CONSUMED; |
1165 | 0 |
|
1166 | 0 | if (aAllowToDispatchKeypress && |
1167 | 0 | kungFuDeathGrip->MaybeDispatchKeypressEvents(keyEvent, status)) { |
1168 | 0 | aConsumedFlags |= |
1169 | 0 | (status == nsEventStatus_eConsumeNoDefault) ? KEYPRESS_IS_CONSUMED : |
1170 | 0 | KEYEVENT_NOT_CONSUMED; |
1171 | 0 | } |
1172 | 0 |
|
1173 | 0 | return NS_OK; |
1174 | 0 | } |
1175 | | |
1176 | | NS_IMETHODIMP |
1177 | | TextInputProcessor::Keyup(Event* aDOMKeyEvent, |
1178 | | uint32_t aKeyFlags, |
1179 | | uint8_t aOptionalArgc, |
1180 | | bool* aDoDefault) |
1181 | 0 | { |
1182 | 0 | MOZ_RELEASE_ASSERT(aDoDefault, "aDoDefault must not be nullptr"); |
1183 | 0 | MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); |
1184 | 0 | if (!aOptionalArgc) { |
1185 | 0 | aKeyFlags = 0; |
1186 | 0 | } |
1187 | 0 | if (NS_WARN_IF(!aDOMKeyEvent)) { |
1188 | 0 | return NS_ERROR_INVALID_ARG; |
1189 | 0 | } |
1190 | 0 | WidgetKeyboardEvent* originalKeyEvent = |
1191 | 0 | aDOMKeyEvent->WidgetEventPtr()->AsKeyboardEvent(); |
1192 | 0 | if (NS_WARN_IF(!originalKeyEvent)) { |
1193 | 0 | return NS_ERROR_INVALID_ARG; |
1194 | 0 | } |
1195 | 0 | return KeyupInternal(*originalKeyEvent, aKeyFlags, *aDoDefault); |
1196 | 0 | } |
1197 | | |
1198 | | nsresult |
1199 | | TextInputProcessor::KeyupInternal(const WidgetKeyboardEvent& aKeyboardEvent, |
1200 | | uint32_t aKeyFlags, |
1201 | | bool& aDoDefault) |
1202 | 0 | { |
1203 | 0 | aDoDefault = false; |
1204 | 0 |
|
1205 | 0 | // We shouldn't modify the internal WidgetKeyboardEvent. |
1206 | 0 | WidgetKeyboardEvent keyEvent(aKeyboardEvent); |
1207 | 0 | nsresult rv = PrepareKeyboardEventToDispatch(keyEvent, aKeyFlags); |
1208 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1209 | 0 | return rv; |
1210 | 0 | } |
1211 | 0 | |
1212 | 0 | aDoDefault = !(aKeyFlags & KEY_DEFAULT_PREVENTED); |
1213 | 0 |
|
1214 | 0 | if (WidgetKeyboardEvent::GetModifierForKeyName(keyEvent.mKeyNameIndex)) { |
1215 | 0 | if (!WidgetKeyboardEvent::IsLockableModifier(keyEvent.mKeyNameIndex)) { |
1216 | 0 | // Inactivate modifier flag before dispatching keyup event (i.e., keyup |
1217 | 0 | // event shouldn't indicate the releasing modifier is active. |
1218 | 0 | InactivateModifierKey(ModifierKeyData(keyEvent)); |
1219 | 0 | } |
1220 | 0 | if (aKeyFlags & KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT) { |
1221 | 0 | return NS_OK; |
1222 | 0 | } |
1223 | 0 | } else if (NS_WARN_IF(aKeyFlags & KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT)) { |
1224 | 0 | return NS_ERROR_INVALID_ARG; |
1225 | 0 | } |
1226 | 0 | keyEvent.mModifiers = GetActiveModifiers(); |
1227 | 0 |
|
1228 | 0 | if (aKeyFlags & KEY_MARK_KEYUP_AS_PROCESSED) { |
1229 | 0 | keyEvent.mKeyCode = NS_VK_PROCESSKEY; |
1230 | 0 | keyEvent.mKeyNameIndex = KEY_NAME_INDEX_Process; |
1231 | 0 | } |
1232 | 0 |
|
1233 | 0 | RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher); |
1234 | 0 | rv = IsValidStateForComposition(); |
1235 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1236 | 0 | return rv; |
1237 | 0 | } |
1238 | 0 | |
1239 | 0 | nsEventStatus status = aDoDefault ? nsEventStatus_eIgnore : |
1240 | 0 | nsEventStatus_eConsumeNoDefault; |
1241 | 0 | kungFuDeathGrip->DispatchKeyboardEvent(eKeyUp, keyEvent, status); |
1242 | 0 | aDoDefault = (status != nsEventStatus_eConsumeNoDefault); |
1243 | 0 | return NS_OK; |
1244 | 0 | } |
1245 | | |
1246 | | NS_IMETHODIMP |
1247 | | TextInputProcessor::GetModifierState(const nsAString& aModifierKeyName, |
1248 | | bool* aActive) |
1249 | 0 | { |
1250 | 0 | MOZ_RELEASE_ASSERT(aActive, "aActive must not be null"); |
1251 | 0 | MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); |
1252 | 0 | if (!mModifierKeyDataArray) { |
1253 | 0 | *aActive = false; |
1254 | 0 | return NS_OK; |
1255 | 0 | } |
1256 | 0 | Modifiers activeModifiers = mModifierKeyDataArray->GetActiveModifiers(); |
1257 | 0 | Modifiers modifier = WidgetInputEvent::GetModifier(aModifierKeyName); |
1258 | 0 | *aActive = ((activeModifiers & modifier) != 0); |
1259 | 0 | return NS_OK; |
1260 | 0 | } |
1261 | | |
1262 | | NS_IMETHODIMP |
1263 | | TextInputProcessor::ShareModifierStateOf(nsITextInputProcessor* aOther) |
1264 | 0 | { |
1265 | 0 | MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); |
1266 | 0 | if (!aOther) { |
1267 | 0 | mModifierKeyDataArray = nullptr; |
1268 | 0 | return NS_OK; |
1269 | 0 | } |
1270 | 0 | TextInputProcessor* other = static_cast<TextInputProcessor*>(aOther); |
1271 | 0 | if (!other->mModifierKeyDataArray) { |
1272 | 0 | other->mModifierKeyDataArray = new ModifierKeyDataArray(); |
1273 | 0 | } |
1274 | 0 | mModifierKeyDataArray = other->mModifierKeyDataArray; |
1275 | 0 | return NS_OK; |
1276 | 0 | } |
1277 | | |
1278 | | /****************************************************************************** |
1279 | | * TextInputProcessor::AutoPendingCompositionResetter |
1280 | | ******************************************************************************/ |
1281 | | TextInputProcessor::AutoPendingCompositionResetter:: |
1282 | | AutoPendingCompositionResetter(TextInputProcessor* aTIP) |
1283 | | : mTIP(aTIP) |
1284 | 0 | { |
1285 | 0 | MOZ_RELEASE_ASSERT(mTIP.get(), "mTIP must not be null"); |
1286 | 0 | } |
1287 | | |
1288 | | TextInputProcessor::AutoPendingCompositionResetter:: |
1289 | | ~AutoPendingCompositionResetter() |
1290 | 0 | { |
1291 | 0 | if (mTIP->mDispatcher) { |
1292 | 0 | mTIP->mDispatcher->ClearPendingComposition(); |
1293 | 0 | } |
1294 | 0 | } |
1295 | | |
1296 | | /****************************************************************************** |
1297 | | * TextInputProcessor::ModifierKeyData |
1298 | | ******************************************************************************/ |
1299 | | TextInputProcessor::ModifierKeyData::ModifierKeyData( |
1300 | | const WidgetKeyboardEvent& aKeyboardEvent) |
1301 | | : mKeyNameIndex(aKeyboardEvent.mKeyNameIndex) |
1302 | | , mCodeNameIndex(aKeyboardEvent.mCodeNameIndex) |
1303 | 0 | { |
1304 | 0 | mModifier = WidgetKeyboardEvent::GetModifierForKeyName(mKeyNameIndex); |
1305 | 0 | MOZ_ASSERT(mModifier, "mKeyNameIndex must be a modifier key name"); |
1306 | 0 | } |
1307 | | |
1308 | | /****************************************************************************** |
1309 | | * TextInputProcessor::ModifierKeyDataArray |
1310 | | ******************************************************************************/ |
1311 | | Modifiers |
1312 | | TextInputProcessor::ModifierKeyDataArray::GetActiveModifiers() const |
1313 | 0 | { |
1314 | 0 | Modifiers result = MODIFIER_NONE; |
1315 | 0 | for (uint32_t i = 0; i < Length(); i++) { |
1316 | 0 | result |= ElementAt(i).mModifier; |
1317 | 0 | } |
1318 | 0 | return result; |
1319 | 0 | } |
1320 | | |
1321 | | void |
1322 | | TextInputProcessor::ModifierKeyDataArray::ActivateModifierKey( |
1323 | | const TextInputProcessor::ModifierKeyData& aModifierKeyData) |
1324 | 0 | { |
1325 | 0 | if (Contains(aModifierKeyData)) { |
1326 | 0 | return; |
1327 | 0 | } |
1328 | 0 | AppendElement(aModifierKeyData); |
1329 | 0 | } |
1330 | | |
1331 | | void |
1332 | | TextInputProcessor::ModifierKeyDataArray::InactivateModifierKey( |
1333 | | const TextInputProcessor::ModifierKeyData& aModifierKeyData) |
1334 | 0 | { |
1335 | 0 | RemoveElement(aModifierKeyData); |
1336 | 0 | } |
1337 | | |
1338 | | void |
1339 | | TextInputProcessor::ModifierKeyDataArray::ToggleModifierKey( |
1340 | | const TextInputProcessor::ModifierKeyData& aModifierKeyData) |
1341 | 0 | { |
1342 | 0 | auto index = IndexOf(aModifierKeyData); |
1343 | 0 | if (index == NoIndex) { |
1344 | 0 | AppendElement(aModifierKeyData); |
1345 | 0 | return; |
1346 | 0 | } |
1347 | 0 | RemoveElementAt(index); |
1348 | 0 | } |
1349 | | |
1350 | | } // namespace mozilla |