Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /* vim: set ts=4 et sw=4 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 "mozilla/Logging.h"
8 : #include "prtime.h"
9 :
10 : #include "IMContextWrapper.h"
11 : #include "nsGtkKeyUtils.h"
12 : #include "nsWindow.h"
13 : #include "mozilla/AutoRestore.h"
14 : #include "mozilla/Likely.h"
15 : #include "mozilla/MiscEvents.h"
16 : #include "mozilla/Preferences.h"
17 : #include "mozilla/TextEventDispatcher.h"
18 : #include "mozilla/TextEvents.h"
19 : #include "WritingModes.h"
20 :
21 : namespace mozilla {
22 : namespace widget {
23 :
24 : LazyLogModule gGtkIMLog("nsGtkIMModuleWidgets");
25 :
26 : static inline const char*
27 0 : ToChar(bool aBool)
28 : {
29 0 : return aBool ? "true" : "false";
30 : }
31 :
32 : static const char*
33 0 : GetEnabledStateName(uint32_t aState)
34 : {
35 0 : switch (aState) {
36 : case IMEState::DISABLED:
37 0 : return "DISABLED";
38 : case IMEState::ENABLED:
39 0 : return "ENABLED";
40 : case IMEState::PASSWORD:
41 0 : return "PASSWORD";
42 : case IMEState::PLUGIN:
43 0 : return "PLUG_IN";
44 : default:
45 0 : return "UNKNOWN ENABLED STATUS!!";
46 : }
47 : }
48 :
49 : static const char*
50 0 : GetEventType(GdkEventKey* aKeyEvent)
51 : {
52 0 : switch (aKeyEvent->type) {
53 : case GDK_KEY_PRESS:
54 0 : return "GDK_KEY_PRESS";
55 : case GDK_KEY_RELEASE:
56 0 : return "GDK_KEY_RELEASE";
57 : default:
58 0 : return "Unknown";
59 : }
60 : }
61 :
62 : class GetWritingModeName : public nsAutoCString
63 : {
64 : public:
65 0 : explicit GetWritingModeName(const WritingMode& aWritingMode)
66 0 : {
67 0 : if (!aWritingMode.IsVertical()) {
68 0 : AssignLiteral("Horizontal");
69 0 : return;
70 : }
71 0 : if (aWritingMode.IsVerticalLR()) {
72 0 : AssignLiteral("Vertical (LTR)");
73 0 : return;
74 : }
75 0 : AssignLiteral("Vertical (RTL)");
76 : }
77 0 : virtual ~GetWritingModeName() {}
78 : };
79 :
80 : class GetTextRangeStyleText final : public nsAutoCString
81 : {
82 : public:
83 0 : explicit GetTextRangeStyleText(const TextRangeStyle& aStyle)
84 0 : {
85 0 : if (!aStyle.IsDefined()) {
86 0 : AssignLiteral("{ IsDefined()=false }");
87 0 : return;
88 : }
89 :
90 0 : if (aStyle.IsLineStyleDefined()) {
91 0 : AppendLiteral("{ mLineStyle=");
92 0 : AppendLineStyle(aStyle.mLineStyle);
93 0 : if (aStyle.IsUnderlineColorDefined()) {
94 0 : AppendLiteral(", mUnderlineColor=");
95 0 : AppendColor(aStyle.mUnderlineColor);
96 : } else {
97 0 : AppendLiteral(", IsUnderlineColorDefined=false");
98 : }
99 : } else {
100 0 : AppendLiteral("{ IsLineStyleDefined()=false");
101 : }
102 :
103 0 : if (aStyle.IsForegroundColorDefined()) {
104 0 : AppendLiteral(", mForegroundColor=");
105 0 : AppendColor(aStyle.mForegroundColor);
106 : } else {
107 0 : AppendLiteral(", IsForegroundColorDefined()=false");
108 : }
109 :
110 0 : if (aStyle.IsBackgroundColorDefined()) {
111 0 : AppendLiteral(", mBackgroundColor=");
112 0 : AppendColor(aStyle.mBackgroundColor);
113 : } else {
114 0 : AppendLiteral(", IsBackgroundColorDefined()=false");
115 : }
116 :
117 0 : AppendLiteral(" }");
118 : }
119 0 : void AppendLineStyle(uint8_t aLineStyle)
120 : {
121 0 : switch (aLineStyle) {
122 : case TextRangeStyle::LINESTYLE_NONE:
123 0 : AppendLiteral("LINESTYLE_NONE");
124 0 : break;
125 : case TextRangeStyle::LINESTYLE_SOLID:
126 0 : AppendLiteral("LINESTYLE_SOLID");
127 0 : break;
128 : case TextRangeStyle::LINESTYLE_DOTTED:
129 0 : AppendLiteral("LINESTYLE_DOTTED");
130 0 : break;
131 : case TextRangeStyle::LINESTYLE_DASHED:
132 0 : AppendLiteral("LINESTYLE_DASHED");
133 0 : break;
134 : case TextRangeStyle::LINESTYLE_DOUBLE:
135 0 : AppendLiteral("LINESTYLE_DOUBLE");
136 0 : break;
137 : case TextRangeStyle::LINESTYLE_WAVY:
138 0 : AppendLiteral("LINESTYLE_WAVY");
139 0 : break;
140 : default:
141 0 : AppendPrintf("Invalid(0x%02X)", aLineStyle);
142 0 : break;
143 : }
144 0 : }
145 0 : void AppendColor(nscolor aColor)
146 : {
147 0 : AppendPrintf("{ R=0x%02X, G=0x%02X, B=0x%02X, A=0x%02X }",
148 0 : NS_GET_R(aColor), NS_GET_G(aColor), NS_GET_B(aColor),
149 0 : NS_GET_A(aColor));
150 0 : }
151 0 : virtual ~GetTextRangeStyleText() {};
152 : };
153 :
154 : const static bool kUseSimpleContextDefault = MOZ_WIDGET_GTK == 2;
155 :
156 : /******************************************************************************
157 : * IMContextWrapper
158 : ******************************************************************************/
159 :
160 : IMContextWrapper* IMContextWrapper::sLastFocusedContext = nullptr;
161 : bool IMContextWrapper::sUseSimpleContext;
162 :
163 2 : NS_IMPL_ISUPPORTS(IMContextWrapper,
164 : TextEventDispatcherListener,
165 : nsISupportsWeakReference)
166 :
167 2 : IMContextWrapper::IMContextWrapper(nsWindow* aOwnerWindow)
168 : : mOwnerWindow(aOwnerWindow)
169 : , mLastFocusedWindow(nullptr)
170 : , mContext(nullptr)
171 : , mSimpleContext(nullptr)
172 : , mDummyContext(nullptr)
173 : , mComposingContext(nullptr)
174 : , mCompositionStart(UINT32_MAX)
175 : , mProcessingKeyEvent(nullptr)
176 : , mCompositionState(eCompositionState_NotComposing)
177 : , mIsIMFocused(false)
178 : , mIsDeletingSurrounding(false)
179 : , mLayoutChanged(false)
180 : , mSetCursorPositionOnKeyEvent(true)
181 : , mPendingResettingIMContext(false)
182 2 : , mRetrieveSurroundingSignalReceived(false)
183 : {
184 : static bool sFirstInstance = true;
185 2 : if (sFirstInstance) {
186 1 : sFirstInstance = false;
187 1 : sUseSimpleContext =
188 1 : Preferences::GetBool(
189 : "intl.ime.use_simple_context_on_password_field",
190 : kUseSimpleContextDefault);
191 : }
192 2 : Init();
193 2 : }
194 :
195 : void
196 2 : IMContextWrapper::Init()
197 : {
198 2 : MOZ_LOG(gGtkIMLog, LogLevel::Info,
199 : ("0x%p Init(), mOwnerWindow=0x%p",
200 : this, mOwnerWindow));
201 :
202 2 : MozContainer* container = mOwnerWindow->GetMozContainer();
203 2 : NS_PRECONDITION(container, "container is null");
204 2 : GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(container));
205 :
206 : // NOTE: gtk_im_*_new() abort (kill) the whole process when it fails.
207 : // So, we don't need to check the result.
208 :
209 : // Normal context.
210 2 : mContext = gtk_im_multicontext_new();
211 2 : gtk_im_context_set_client_window(mContext, gdkWindow);
212 2 : g_signal_connect(mContext, "preedit_changed",
213 2 : G_CALLBACK(IMContextWrapper::OnChangeCompositionCallback), this);
214 2 : g_signal_connect(mContext, "retrieve_surrounding",
215 2 : G_CALLBACK(IMContextWrapper::OnRetrieveSurroundingCallback), this);
216 2 : g_signal_connect(mContext, "delete_surrounding",
217 2 : G_CALLBACK(IMContextWrapper::OnDeleteSurroundingCallback), this);
218 2 : g_signal_connect(mContext, "commit",
219 2 : G_CALLBACK(IMContextWrapper::OnCommitCompositionCallback), this);
220 2 : g_signal_connect(mContext, "preedit_start",
221 2 : G_CALLBACK(IMContextWrapper::OnStartCompositionCallback), this);
222 2 : g_signal_connect(mContext, "preedit_end",
223 2 : G_CALLBACK(IMContextWrapper::OnEndCompositionCallback), this);
224 :
225 : // Simple context
226 2 : if (sUseSimpleContext) {
227 0 : mSimpleContext = gtk_im_context_simple_new();
228 0 : gtk_im_context_set_client_window(mSimpleContext, gdkWindow);
229 0 : g_signal_connect(mSimpleContext, "preedit_changed",
230 : G_CALLBACK(&IMContextWrapper::OnChangeCompositionCallback),
231 0 : this);
232 0 : g_signal_connect(mSimpleContext, "retrieve_surrounding",
233 : G_CALLBACK(&IMContextWrapper::OnRetrieveSurroundingCallback),
234 0 : this);
235 0 : g_signal_connect(mSimpleContext, "delete_surrounding",
236 : G_CALLBACK(&IMContextWrapper::OnDeleteSurroundingCallback),
237 0 : this);
238 0 : g_signal_connect(mSimpleContext, "commit",
239 : G_CALLBACK(&IMContextWrapper::OnCommitCompositionCallback),
240 0 : this);
241 0 : g_signal_connect(mSimpleContext, "preedit_start",
242 : G_CALLBACK(IMContextWrapper::OnStartCompositionCallback),
243 0 : this);
244 0 : g_signal_connect(mSimpleContext, "preedit_end",
245 : G_CALLBACK(IMContextWrapper::OnEndCompositionCallback),
246 0 : this);
247 : }
248 :
249 : // Dummy context
250 2 : mDummyContext = gtk_im_multicontext_new();
251 2 : gtk_im_context_set_client_window(mDummyContext, gdkWindow);
252 2 : }
253 :
254 0 : IMContextWrapper::~IMContextWrapper()
255 : {
256 0 : if (this == sLastFocusedContext) {
257 0 : sLastFocusedContext = nullptr;
258 : }
259 0 : MOZ_LOG(gGtkIMLog, LogLevel::Info,
260 : ("0x%p ~IMContextWrapper()", this));
261 0 : }
262 :
263 : NS_IMETHODIMP
264 0 : IMContextWrapper::NotifyIME(TextEventDispatcher* aTextEventDispatcher,
265 : const IMENotification& aNotification)
266 : {
267 0 : switch (aNotification.mMessage) {
268 : case REQUEST_TO_COMMIT_COMPOSITION:
269 : case REQUEST_TO_CANCEL_COMPOSITION: {
270 : nsWindow* window =
271 0 : static_cast<nsWindow*>(aTextEventDispatcher->GetWidget());
272 0 : return EndIMEComposition(window);
273 : }
274 : case NOTIFY_IME_OF_FOCUS:
275 0 : OnFocusChangeInGecko(true);
276 0 : return NS_OK;
277 : case NOTIFY_IME_OF_BLUR:
278 0 : OnFocusChangeInGecko(false);
279 0 : return NS_OK;
280 : case NOTIFY_IME_OF_POSITION_CHANGE:
281 0 : OnLayoutChange();
282 0 : return NS_OK;
283 : case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
284 0 : OnUpdateComposition();
285 0 : return NS_OK;
286 : case NOTIFY_IME_OF_SELECTION_CHANGE: {
287 : nsWindow* window =
288 0 : static_cast<nsWindow*>(aTextEventDispatcher->GetWidget());
289 0 : OnSelectionChange(window, aNotification);
290 0 : return NS_OK;
291 : }
292 : default:
293 0 : return NS_ERROR_NOT_IMPLEMENTED;
294 : }
295 : }
296 :
297 : NS_IMETHODIMP_(void)
298 0 : IMContextWrapper::OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher)
299 : {
300 : // XXX When input transaction is being stolen by add-on, what should we do?
301 0 : }
302 :
303 : NS_IMETHODIMP_(void)
304 0 : IMContextWrapper::WillDispatchKeyboardEvent(
305 : TextEventDispatcher* aTextEventDispatcher,
306 : WidgetKeyboardEvent& aKeyboardEvent,
307 : uint32_t aIndexOfKeypress,
308 : void* aData)
309 : {
310 : KeymapWrapper::WillDispatchKeyboardEvent(aKeyboardEvent,
311 0 : static_cast<GdkEventKey*>(aData));
312 0 : }
313 :
314 : TextEventDispatcher*
315 0 : IMContextWrapper::GetTextEventDispatcher()
316 : {
317 0 : if (NS_WARN_IF(!mLastFocusedWindow)) {
318 0 : return nullptr;
319 : }
320 : TextEventDispatcher* dispatcher =
321 0 : mLastFocusedWindow->GetTextEventDispatcher();
322 : // nsIWidget::GetTextEventDispatcher() shouldn't return nullptr.
323 0 : MOZ_RELEASE_ASSERT(dispatcher);
324 0 : return dispatcher;
325 : }
326 :
327 : NS_IMETHODIMP_(IMENotificationRequests)
328 0 : IMContextWrapper::GetIMENotificationRequests()
329 : {
330 : // While a plugin has focus, IMContextWrapper doesn't need any
331 : // notifications.
332 0 : if (mInputContext.mIMEState.mEnabled == IMEState::PLUGIN) {
333 0 : return IMENotificationRequests();
334 : }
335 :
336 : IMENotificationRequests::Notifications notifications =
337 0 : IMENotificationRequests::NOTIFY_NOTHING;
338 : // If it's not enabled, we don't need position change notification.
339 0 : if (IsEnabled()) {
340 0 : notifications |= IMENotificationRequests::NOTIFY_POSITION_CHANGE;
341 : }
342 0 : return IMENotificationRequests(notifications);
343 : }
344 :
345 : void
346 0 : IMContextWrapper::OnDestroyWindow(nsWindow* aWindow)
347 : {
348 0 : MOZ_LOG(gGtkIMLog, LogLevel::Info,
349 : ("0x%p OnDestroyWindow(aWindow=0x%p), mLastFocusedWindow=0x%p, "
350 : "mOwnerWindow=0x%p, mLastFocusedModule=0x%p",
351 : this, aWindow, mLastFocusedWindow, mOwnerWindow, sLastFocusedContext));
352 :
353 0 : NS_PRECONDITION(aWindow, "aWindow must not be null");
354 :
355 0 : if (mLastFocusedWindow == aWindow) {
356 0 : EndIMEComposition(aWindow);
357 0 : if (mIsIMFocused) {
358 0 : Blur();
359 : }
360 0 : mLastFocusedWindow = nullptr;
361 : }
362 :
363 0 : if (mOwnerWindow != aWindow) {
364 0 : return;
365 : }
366 :
367 0 : if (sLastFocusedContext == this) {
368 0 : sLastFocusedContext = nullptr;
369 : }
370 :
371 : /**
372 : * NOTE:
373 : * The given window is the owner of this, so, we must release the
374 : * contexts now. But that might be referred from other nsWindows
375 : * (they are children of this. But we don't know why there are the
376 : * cases). So, we need to clear the pointers that refers to contexts
377 : * and this if the other referrers are still alive. See bug 349727.
378 : */
379 0 : if (mContext) {
380 0 : PrepareToDestroyContext(mContext);
381 0 : gtk_im_context_set_client_window(mContext, nullptr);
382 0 : g_object_unref(mContext);
383 0 : mContext = nullptr;
384 : }
385 :
386 0 : if (mSimpleContext) {
387 0 : gtk_im_context_set_client_window(mSimpleContext, nullptr);
388 0 : g_object_unref(mSimpleContext);
389 0 : mSimpleContext = nullptr;
390 : }
391 :
392 0 : if (mDummyContext) {
393 : // mContext and mDummyContext have the same slaveType and signal_data
394 : // so no need for another workaround_gtk_im_display_closed.
395 0 : gtk_im_context_set_client_window(mDummyContext, nullptr);
396 0 : g_object_unref(mDummyContext);
397 0 : mDummyContext = nullptr;
398 : }
399 :
400 0 : if (NS_WARN_IF(mComposingContext)) {
401 0 : g_object_unref(mComposingContext);
402 0 : mComposingContext = nullptr;
403 : }
404 :
405 0 : mOwnerWindow = nullptr;
406 0 : mLastFocusedWindow = nullptr;
407 0 : mInputContext.mIMEState.mEnabled = IMEState::DISABLED;
408 :
409 0 : MOZ_LOG(gGtkIMLog, LogLevel::Debug,
410 : ("0x%p OnDestroyWindow(), succeeded, Completely destroyed",
411 : this));
412 : }
413 :
414 : // Work around gtk bug http://bugzilla.gnome.org/show_bug.cgi?id=483223:
415 : // (and the similar issue of GTK+ IIIM)
416 : // The GTK+ XIM and IIIM modules register handlers for the "closed" signal
417 : // on the display, but:
418 : // * The signal handlers are not disconnected when the module is unloaded.
419 : //
420 : // The GTK+ XIM module has another problem:
421 : // * When the signal handler is run (with the module loaded) it tries
422 : // XFree (and fails) on a pointer that did not come from Xmalloc.
423 : //
424 : // To prevent these modules from being unloaded, use static variables to
425 : // hold ref of GtkIMContext class.
426 : // For GTK+ XIM module, to prevent the signal handler from being run,
427 : // find the signal handlers and remove them.
428 : //
429 : // GtkIMContextXIMs share XOpenIM connections and display closed signal
430 : // handlers (where possible).
431 :
432 : void
433 0 : IMContextWrapper::PrepareToDestroyContext(GtkIMContext* aContext)
434 : {
435 : #if (MOZ_WIDGET_GTK == 2)
436 : GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT(aContext);
437 : GtkIMContext *slave = multicontext->slave;
438 : #else
439 0 : GtkIMContext *slave = nullptr; //TODO GTK3
440 : #endif
441 0 : if (!slave) {
442 0 : return;
443 : }
444 :
445 0 : GType slaveType = G_TYPE_FROM_INSTANCE(slave);
446 0 : const gchar *im_type_name = g_type_name(slaveType);
447 0 : if (strcmp(im_type_name, "GtkIMContextIIIM") == 0) {
448 : // Add a reference to prevent the IIIM module from being unloaded
449 : static gpointer gtk_iiim_context_class =
450 0 : g_type_class_ref(slaveType);
451 : // Mute unused variable warning:
452 : (void)gtk_iiim_context_class;
453 : }
454 : }
455 :
456 : void
457 0 : IMContextWrapper::OnFocusWindow(nsWindow* aWindow)
458 : {
459 0 : if (MOZ_UNLIKELY(IsDestroyed())) {
460 0 : return;
461 : }
462 :
463 0 : MOZ_LOG(gGtkIMLog, LogLevel::Info,
464 : ("0x%p OnFocusWindow(aWindow=0x%p), mLastFocusedWindow=0x%p",
465 : this, aWindow, mLastFocusedWindow));
466 0 : mLastFocusedWindow = aWindow;
467 0 : Focus();
468 : }
469 :
470 : void
471 0 : IMContextWrapper::OnBlurWindow(nsWindow* aWindow)
472 : {
473 0 : if (MOZ_UNLIKELY(IsDestroyed())) {
474 0 : return;
475 : }
476 :
477 0 : MOZ_LOG(gGtkIMLog, LogLevel::Info,
478 : ("0x%p OnBlurWindow(aWindow=0x%p), mLastFocusedWindow=0x%p, "
479 : "mIsIMFocused=%s",
480 : this, aWindow, mLastFocusedWindow, ToChar(mIsIMFocused)));
481 :
482 0 : if (!mIsIMFocused || mLastFocusedWindow != aWindow) {
483 0 : return;
484 : }
485 :
486 0 : Blur();
487 : }
488 :
489 : bool
490 0 : IMContextWrapper::OnKeyEvent(nsWindow* aCaller,
491 : GdkEventKey* aEvent,
492 : bool aKeyDownEventWasSent /* = false */)
493 : {
494 0 : NS_PRECONDITION(aEvent, "aEvent must be non-null");
495 :
496 0 : if (!mInputContext.mIMEState.MaybeEditable() ||
497 0 : MOZ_UNLIKELY(IsDestroyed())) {
498 0 : return false;
499 : }
500 :
501 0 : MOZ_LOG(gGtkIMLog, LogLevel::Info,
502 : ("0x%p OnKeyEvent(aCaller=0x%p, aKeyDownEventWasSent=%s), "
503 : "mCompositionState=%s, current context=0x%p, active context=0x%p, "
504 : "aEvent(0x%p): { type=%s, keyval=%s, unicode=0x%X }",
505 : this, aCaller, ToChar(aKeyDownEventWasSent),
506 : GetCompositionStateName(), GetCurrentContext(), GetActiveContext(),
507 : aEvent, GetEventType(aEvent), gdk_keyval_name(aEvent->keyval),
508 : gdk_keyval_to_unicode(aEvent->keyval)));
509 :
510 0 : if (aCaller != mLastFocusedWindow) {
511 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
512 : ("0x%p OnKeyEvent(), FAILED, the caller isn't focused "
513 : "window, mLastFocusedWindow=0x%p",
514 : this, mLastFocusedWindow));
515 0 : return false;
516 : }
517 :
518 : // Even if old IM context has composition, key event should be sent to
519 : // current context since the user expects so.
520 0 : GtkIMContext* currentContext = GetCurrentContext();
521 0 : if (MOZ_UNLIKELY(!currentContext)) {
522 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
523 : ("0x%p OnKeyEvent(), FAILED, there are no context",
524 : this));
525 0 : return false;
526 : }
527 :
528 0 : if (mSetCursorPositionOnKeyEvent) {
529 0 : SetCursorPosition(currentContext);
530 0 : mSetCursorPositionOnKeyEvent = false;
531 : }
532 :
533 0 : mKeyDownEventWasSent = aKeyDownEventWasSent;
534 0 : mFilterKeyEvent = true;
535 0 : mProcessingKeyEvent = aEvent;
536 : gboolean isFiltered =
537 0 : gtk_im_context_filter_keypress(currentContext, aEvent);
538 0 : mProcessingKeyEvent = nullptr;
539 :
540 : // We filter the key event if the event was not committed (because
541 : // it's probably part of a composition) or if the key event was
542 : // committed _and_ changed. This way we still let key press
543 : // events go through as simple key press events instead of
544 : // composed characters.
545 0 : bool filterThisEvent = isFiltered && mFilterKeyEvent;
546 :
547 0 : if (IsComposingOnCurrentContext() && !isFiltered) {
548 0 : if (aEvent->type == GDK_KEY_PRESS) {
549 0 : if (!mDispatchedCompositionString.IsEmpty()) {
550 : // If there is composition string, we shouldn't dispatch
551 : // any keydown events during composition.
552 0 : filterThisEvent = true;
553 : } else {
554 : // A Hangul input engine for SCIM doesn't emit preedit_end
555 : // signal even when composition string becomes empty. On the
556 : // other hand, we should allow to make composition with empty
557 : // string for other languages because there *might* be such
558 : // IM. For compromising this issue, we should dispatch
559 : // compositionend event, however, we don't need to reset IM
560 : // actually.
561 0 : DispatchCompositionCommitEvent(currentContext, &EmptyString());
562 0 : filterThisEvent = false;
563 : }
564 : } else {
565 : // Key release event may not be consumed by IM, however, we
566 : // shouldn't dispatch any keyup event during composition.
567 0 : filterThisEvent = true;
568 : }
569 : }
570 :
571 0 : MOZ_LOG(gGtkIMLog, LogLevel::Debug,
572 : ("0x%p OnKeyEvent(), succeeded, filterThisEvent=%s "
573 : "(isFiltered=%s, mFilterKeyEvent=%s), mCompositionState=%s",
574 : this, ToChar(filterThisEvent), ToChar(isFiltered),
575 : ToChar(mFilterKeyEvent), GetCompositionStateName()));
576 :
577 0 : return filterThisEvent;
578 : }
579 :
580 : void
581 0 : IMContextWrapper::OnFocusChangeInGecko(bool aFocus)
582 : {
583 0 : MOZ_LOG(gGtkIMLog, LogLevel::Info,
584 : ("0x%p OnFocusChangeInGecko(aFocus=%s), "
585 : "mCompositionState=%s, mIsIMFocused=%s",
586 : this, ToChar(aFocus), GetCompositionStateName(),
587 : ToChar(mIsIMFocused)));
588 :
589 : // We shouldn't carry over the removed string to another editor.
590 0 : mSelectedStringRemovedByComposition.Truncate();
591 0 : mSelection.Clear();
592 0 : }
593 :
594 : void
595 0 : IMContextWrapper::ResetIME()
596 : {
597 0 : MOZ_LOG(gGtkIMLog, LogLevel::Info,
598 : ("0x%p ResetIME(), mCompositionState=%s, mIsIMFocused=%s",
599 : this, GetCompositionStateName(), ToChar(mIsIMFocused)));
600 :
601 0 : GtkIMContext* activeContext = GetActiveContext();
602 0 : if (MOZ_UNLIKELY(!activeContext)) {
603 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
604 : ("0x%p ResetIME(), FAILED, there are no context",
605 : this));
606 0 : return;
607 : }
608 :
609 0 : RefPtr<IMContextWrapper> kungFuDeathGrip(this);
610 0 : RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
611 :
612 0 : mPendingResettingIMContext = false;
613 0 : gtk_im_context_reset(activeContext);
614 :
615 : // The last focused window might have been destroyed by a DOM event handler
616 : // which was called by us during a call of gtk_im_context_reset().
617 0 : if (!lastFocusedWindow ||
618 0 : NS_WARN_IF(lastFocusedWindow != mLastFocusedWindow) ||
619 0 : lastFocusedWindow->Destroyed()) {
620 0 : return;
621 : }
622 :
623 0 : nsAutoString compositionString;
624 0 : GetCompositionString(activeContext, compositionString);
625 :
626 0 : MOZ_LOG(gGtkIMLog, LogLevel::Debug,
627 : ("0x%p ResetIME() called gtk_im_context_reset(), "
628 : "activeContext=0x%p, mCompositionState=%s, compositionString=%s, "
629 : "mIsIMFocused=%s",
630 : this, activeContext, GetCompositionStateName(),
631 : NS_ConvertUTF16toUTF8(compositionString).get(),
632 : ToChar(mIsIMFocused)));
633 :
634 : // XXX IIIMF (ATOK X3 which is one of the Language Engine of it is still
635 : // used in Japan!) sends only "preedit_changed" signal with empty
636 : // composition string synchronously. Therefore, if composition string
637 : // is now empty string, we should assume that the IME won't send
638 : // "commit" signal.
639 0 : if (IsComposing() && compositionString.IsEmpty()) {
640 : // WARNING: The widget might have been gone after this.
641 0 : DispatchCompositionCommitEvent(activeContext, &EmptyString());
642 : }
643 : }
644 :
645 : nsresult
646 0 : IMContextWrapper::EndIMEComposition(nsWindow* aCaller)
647 : {
648 0 : if (MOZ_UNLIKELY(IsDestroyed())) {
649 0 : return NS_OK;
650 : }
651 :
652 0 : MOZ_LOG(gGtkIMLog, LogLevel::Info,
653 : ("0x%p EndIMEComposition(aCaller=0x%p), "
654 : "mCompositionState=%s",
655 : this, aCaller, GetCompositionStateName()));
656 :
657 0 : if (aCaller != mLastFocusedWindow) {
658 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
659 : ("0x%p EndIMEComposition(), FAILED, the caller isn't "
660 : "focused window, mLastFocusedWindow=0x%p",
661 : this, mLastFocusedWindow));
662 0 : return NS_OK;
663 : }
664 :
665 0 : if (!IsComposing()) {
666 0 : return NS_OK;
667 : }
668 :
669 : // Currently, GTK has API neither to commit nor to cancel composition
670 : // forcibly. Therefore, TextComposition will recompute commit string for
671 : // the request even if native IME will cause unexpected commit string.
672 : // So, we don't need to emulate commit or cancel composition with
673 : // proper composition events.
674 : // XXX ResetIME() might not enough for finishing compositoin on some
675 : // environments. We should emulate focus change too because some IMEs
676 : // may commit or cancel composition at blur.
677 0 : ResetIME();
678 :
679 0 : return NS_OK;
680 : }
681 :
682 : void
683 0 : IMContextWrapper::OnLayoutChange()
684 : {
685 0 : if (MOZ_UNLIKELY(IsDestroyed())) {
686 0 : return;
687 : }
688 :
689 0 : if (IsComposing()) {
690 0 : SetCursorPosition(GetActiveContext());
691 : } else {
692 : // If not composing, candidate window position is updated before key
693 : // down
694 0 : mSetCursorPositionOnKeyEvent = true;
695 : }
696 0 : mLayoutChanged = true;
697 : }
698 :
699 : void
700 0 : IMContextWrapper::OnUpdateComposition()
701 : {
702 0 : if (MOZ_UNLIKELY(IsDestroyed())) {
703 0 : return;
704 : }
705 :
706 0 : if (!IsComposing()) {
707 : // Composition has been committed. So we need update selection for
708 : // caret later
709 0 : mSelection.Clear();
710 0 : EnsureToCacheSelection();
711 0 : mSetCursorPositionOnKeyEvent = true;
712 : }
713 :
714 : // If we've already set candidate window position, we don't need to update
715 : // the position with update composition notification.
716 0 : if (!mLayoutChanged) {
717 0 : SetCursorPosition(GetActiveContext());
718 : }
719 : }
720 :
721 : void
722 4 : IMContextWrapper::SetInputContext(nsWindow* aCaller,
723 : const InputContext* aContext,
724 : const InputContextAction* aAction)
725 : {
726 4 : if (MOZ_UNLIKELY(IsDestroyed())) {
727 0 : return;
728 : }
729 :
730 4 : MOZ_LOG(gGtkIMLog, LogLevel::Info,
731 : ("0x%p SetInputContext(aCaller=0x%p, aContext={ mIMEState={ "
732 : "mEnabled=%s }, mHTMLInputType=%s })",
733 : this, aCaller, GetEnabledStateName(aContext->mIMEState.mEnabled),
734 : NS_ConvertUTF16toUTF8(aContext->mHTMLInputType).get()));
735 :
736 4 : if (aCaller != mLastFocusedWindow) {
737 4 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
738 : ("0x%p SetInputContext(), FAILED, "
739 : "the caller isn't focused window, mLastFocusedWindow=0x%p",
740 : this, mLastFocusedWindow));
741 4 : return;
742 : }
743 :
744 0 : if (!mContext) {
745 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
746 : ("0x%p SetInputContext(), FAILED, "
747 : "there are no context",
748 : this));
749 0 : return;
750 : }
751 :
752 :
753 0 : if (sLastFocusedContext != this) {
754 0 : mInputContext = *aContext;
755 0 : MOZ_LOG(gGtkIMLog, LogLevel::Debug,
756 : ("0x%p SetInputContext(), succeeded, "
757 : "but we're not active",
758 : this));
759 0 : return;
760 : }
761 :
762 : bool changingEnabledState =
763 0 : aContext->mIMEState.mEnabled != mInputContext.mIMEState.mEnabled ||
764 0 : aContext->mHTMLInputType != mInputContext.mHTMLInputType;
765 :
766 : // Release current IME focus if IME is enabled.
767 0 : if (changingEnabledState && mInputContext.mIMEState.MaybeEditable()) {
768 0 : EndIMEComposition(mLastFocusedWindow);
769 0 : Blur();
770 : }
771 :
772 0 : mInputContext = *aContext;
773 :
774 0 : if (changingEnabledState) {
775 : #if (MOZ_WIDGET_GTK == 3)
776 0 : static bool sInputPurposeSupported = !gtk_check_version(3, 6, 0);
777 0 : if (sInputPurposeSupported && mInputContext.mIMEState.MaybeEditable()) {
778 0 : GtkIMContext* currentContext = GetCurrentContext();
779 0 : if (currentContext) {
780 0 : GtkInputPurpose purpose = GTK_INPUT_PURPOSE_FREE_FORM;
781 0 : const nsString& inputType = mInputContext.mHTMLInputType;
782 : // Password case has difficult issue. Desktop IMEs disable
783 : // composition if input-purpose is password.
784 : // For disabling IME on |ime-mode: disabled;|, we need to check
785 : // mEnabled value instead of inputType value. This hack also
786 : // enables composition on
787 : // <input type="password" style="ime-mode: enabled;">.
788 : // This is right behavior of ime-mode on desktop.
789 : //
790 : // On the other hand, IME for tablet devices may provide a
791 : // specific software keyboard for password field. If so,
792 : // the behavior might look strange on both:
793 : // <input type="text" style="ime-mode: disabled;">
794 : // <input type="password" style="ime-mode: enabled;">
795 : //
796 : // Temporarily, we should focus on desktop environment for now.
797 : // I.e., let's ignore tablet devices for now. When somebody
798 : // reports actual trouble on tablet devices, we should try to
799 : // look for a way to solve actual problem.
800 0 : if (mInputContext.mIMEState.mEnabled == IMEState::PASSWORD) {
801 0 : purpose = GTK_INPUT_PURPOSE_PASSWORD;
802 0 : } else if (inputType.EqualsLiteral("email")) {
803 0 : purpose = GTK_INPUT_PURPOSE_EMAIL;
804 0 : } else if (inputType.EqualsLiteral("url")) {
805 0 : purpose = GTK_INPUT_PURPOSE_URL;
806 0 : } else if (inputType.EqualsLiteral("tel")) {
807 0 : purpose = GTK_INPUT_PURPOSE_PHONE;
808 0 : } else if (inputType.EqualsLiteral("number")) {
809 0 : purpose = GTK_INPUT_PURPOSE_NUMBER;
810 : }
811 :
812 0 : g_object_set(currentContext, "input-purpose", purpose, nullptr);
813 : }
814 : }
815 : #endif // #if (MOZ_WIDGET_GTK == 3)
816 :
817 : // Even when aState is not enabled state, we need to set IME focus.
818 : // Because some IMs are updating the status bar of them at this time.
819 : // Be aware, don't use aWindow here because this method shouldn't move
820 : // focus actually.
821 0 : Focus();
822 :
823 : // XXX Should we call Blur() when it's not editable? E.g., it might be
824 : // better to close VKB automatically.
825 : }
826 : }
827 :
828 : InputContext
829 1 : IMContextWrapper::GetInputContext()
830 : {
831 1 : mInputContext.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
832 1 : return mInputContext;
833 : }
834 :
835 : GtkIMContext*
836 0 : IMContextWrapper::GetCurrentContext() const
837 : {
838 0 : if (IsEnabled()) {
839 0 : return mContext;
840 : }
841 0 : if (mInputContext.mIMEState.mEnabled == IMEState::PASSWORD) {
842 0 : return mSimpleContext;
843 : }
844 0 : return mDummyContext;
845 : }
846 :
847 : bool
848 0 : IMContextWrapper::IsValidContext(GtkIMContext* aContext) const
849 : {
850 0 : if (!aContext) {
851 0 : return false;
852 : }
853 0 : return aContext == mContext ||
854 0 : aContext == mSimpleContext ||
855 0 : aContext == mDummyContext;
856 : }
857 :
858 : bool
859 0 : IMContextWrapper::IsEnabled() const
860 : {
861 0 : return mInputContext.mIMEState.mEnabled == IMEState::ENABLED ||
862 0 : mInputContext.mIMEState.mEnabled == IMEState::PLUGIN ||
863 0 : (!sUseSimpleContext &&
864 0 : mInputContext.mIMEState.mEnabled == IMEState::PASSWORD);
865 : }
866 :
867 : void
868 0 : IMContextWrapper::Focus()
869 : {
870 0 : MOZ_LOG(gGtkIMLog, LogLevel::Info,
871 : ("0x%p Focus(), sLastFocusedContext=0x%p",
872 : this, sLastFocusedContext));
873 :
874 0 : if (mIsIMFocused) {
875 0 : NS_ASSERTION(sLastFocusedContext == this,
876 : "We're not active, but the IM was focused?");
877 0 : return;
878 : }
879 :
880 0 : GtkIMContext* currentContext = GetCurrentContext();
881 0 : if (!currentContext) {
882 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
883 : ("0x%p Focus(), FAILED, there are no context",
884 : this));
885 0 : return;
886 : }
887 :
888 0 : if (sLastFocusedContext && sLastFocusedContext != this) {
889 0 : sLastFocusedContext->Blur();
890 : }
891 :
892 0 : sLastFocusedContext = this;
893 :
894 0 : gtk_im_context_focus_in(currentContext);
895 0 : mIsIMFocused = true;
896 0 : mSetCursorPositionOnKeyEvent = true;
897 :
898 0 : if (!IsEnabled()) {
899 : // We should release IME focus for uim and scim.
900 : // These IMs are using snooper that is released at losing focus.
901 0 : Blur();
902 : }
903 : }
904 :
905 : void
906 0 : IMContextWrapper::Blur()
907 : {
908 0 : MOZ_LOG(gGtkIMLog, LogLevel::Info,
909 : ("0x%p Blur(), mIsIMFocused=%s",
910 : this, ToChar(mIsIMFocused)));
911 :
912 0 : if (!mIsIMFocused) {
913 0 : return;
914 : }
915 :
916 0 : GtkIMContext* currentContext = GetCurrentContext();
917 0 : if (!currentContext) {
918 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
919 : ("0x%p Blur(), FAILED, there are no context",
920 : this));
921 0 : return;
922 : }
923 :
924 0 : gtk_im_context_focus_out(currentContext);
925 0 : mIsIMFocused = false;
926 : }
927 :
928 : void
929 0 : IMContextWrapper::OnSelectionChange(nsWindow* aCaller,
930 : const IMENotification& aIMENotification)
931 : {
932 0 : mSelection.Assign(aIMENotification);
933 : bool retrievedSurroundingSignalReceived =
934 0 : mRetrieveSurroundingSignalReceived;
935 0 : mRetrieveSurroundingSignalReceived = false;
936 :
937 0 : if (MOZ_UNLIKELY(IsDestroyed())) {
938 0 : return;
939 : }
940 :
941 : const IMENotification::SelectionChangeDataBase& selectionChangeData =
942 0 : aIMENotification.mSelectionChangeData;
943 :
944 0 : MOZ_LOG(gGtkIMLog, LogLevel::Info,
945 : ("0x%p OnSelectionChange(aCaller=0x%p, aIMENotification={ "
946 : "mSelectionChangeData={ mOffset=%u, Length()=%u, mReversed=%s, "
947 : "mWritingMode=%s, mCausedByComposition=%s, "
948 : "mCausedBySelectionEvent=%s, mOccurredDuringComposition=%s "
949 : "} }), mCompositionState=%s, mIsDeletingSurrounding=%s, "
950 : "mRetrieveSurroundingSignalReceived=%s",
951 : this, aCaller, selectionChangeData.mOffset,
952 : selectionChangeData.Length(),
953 : ToChar(selectionChangeData.mReversed),
954 : GetWritingModeName(selectionChangeData.GetWritingMode()).get(),
955 : ToChar(selectionChangeData.mCausedByComposition),
956 : ToChar(selectionChangeData.mCausedBySelectionEvent),
957 : ToChar(selectionChangeData.mOccurredDuringComposition),
958 : GetCompositionStateName(), ToChar(mIsDeletingSurrounding),
959 : ToChar(retrievedSurroundingSignalReceived)));
960 :
961 0 : if (aCaller != mLastFocusedWindow) {
962 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
963 : ("0x%p OnSelectionChange(), FAILED, "
964 : "the caller isn't focused window, mLastFocusedWindow=0x%p",
965 : this, mLastFocusedWindow));
966 0 : return;
967 : }
968 :
969 0 : if (!IsComposing()) {
970 : // Now we have no composition (mostly situation on calling this method)
971 : // If we have it, it will set by
972 : // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED.
973 0 : mSetCursorPositionOnKeyEvent = true;
974 : }
975 :
976 : // The focused editor might have placeholder text with normal text node.
977 : // In such case, the text node must be removed from a compositionstart
978 : // event handler. So, we're dispatching eCompositionStart,
979 : // we should ignore selection change notification.
980 0 : if (mCompositionState == eCompositionState_CompositionStartDispatched) {
981 0 : if (NS_WARN_IF(!mSelection.IsValid())) {
982 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
983 : ("0x%p OnSelectionChange(), FAILED, "
984 : "new offset is too large, cannot keep composing",
985 : this));
986 : } else {
987 : // Modify the selection start offset with new offset.
988 0 : mCompositionStart = mSelection.mOffset;
989 : // XXX We should modify mSelectedStringRemovedByComposition?
990 : // But how?
991 0 : MOZ_LOG(gGtkIMLog, LogLevel::Debug,
992 : ("0x%p OnSelectionChange(), ignored, mCompositionStart "
993 : "is updated to %u, the selection change doesn't cause "
994 : "resetting IM context",
995 : this, mCompositionStart));
996 : // And don't reset the IM context.
997 0 : return;
998 : }
999 : // Otherwise, reset the IM context due to impossible to keep composing.
1000 : }
1001 :
1002 : // If the selection change is caused by deleting surrounding text,
1003 : // we shouldn't need to notify IME of selection change.
1004 0 : if (mIsDeletingSurrounding) {
1005 0 : return;
1006 : }
1007 :
1008 : bool occurredBeforeComposition =
1009 0 : IsComposing() && !selectionChangeData.mOccurredDuringComposition &&
1010 0 : !selectionChangeData.mCausedByComposition;
1011 0 : if (occurredBeforeComposition) {
1012 0 : mPendingResettingIMContext = true;
1013 : }
1014 :
1015 : // When the selection change is caused by dispatching composition event,
1016 : // selection set event and/or occurred before starting current composition,
1017 : // we shouldn't notify IME of that and commit existing composition.
1018 0 : if (!selectionChangeData.mCausedByComposition &&
1019 0 : !selectionChangeData.mCausedBySelectionEvent &&
1020 0 : !occurredBeforeComposition) {
1021 : // Hack for ibus-pinyin. ibus-pinyin will synthesize a set of
1022 : // composition which commits with empty string after calling
1023 : // gtk_im_context_reset(). Therefore, selecting text causes
1024 : // unexpectedly removing it. For preventing it but not breaking the
1025 : // other IMEs which use surrounding text, we should call it only when
1026 : // surrounding text has been retrieved after last selection range was
1027 : // set. If it's not retrieved, that means that current IME doesn't
1028 : // have any content cache, so, it must not need the notification of
1029 : // selection change.
1030 0 : if (IsComposing() || retrievedSurroundingSignalReceived) {
1031 0 : ResetIME();
1032 : }
1033 : }
1034 : }
1035 :
1036 : /* static */
1037 : void
1038 0 : IMContextWrapper::OnStartCompositionCallback(GtkIMContext* aContext,
1039 : IMContextWrapper* aModule)
1040 : {
1041 0 : aModule->OnStartCompositionNative(aContext);
1042 0 : }
1043 :
1044 : void
1045 0 : IMContextWrapper::OnStartCompositionNative(GtkIMContext* aContext)
1046 : {
1047 0 : MOZ_LOG(gGtkIMLog, LogLevel::Info,
1048 : ("0x%p OnStartCompositionNative(aContext=0x%p), "
1049 : "current context=0x%p",
1050 : this, aContext, GetCurrentContext()));
1051 :
1052 : // See bug 472635, we should do nothing if IM context doesn't match.
1053 0 : if (GetCurrentContext() != aContext) {
1054 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1055 : ("0x%p OnStartCompositionNative(), FAILED, "
1056 : "given context doesn't match",
1057 : this));
1058 0 : return;
1059 : }
1060 :
1061 0 : mComposingContext = static_cast<GtkIMContext*>(g_object_ref(aContext));
1062 :
1063 0 : if (!DispatchCompositionStart(aContext)) {
1064 0 : return;
1065 : }
1066 0 : mCompositionTargetRange.mOffset = mCompositionStart;
1067 0 : mCompositionTargetRange.mLength = 0;
1068 : }
1069 :
1070 : /* static */
1071 : void
1072 0 : IMContextWrapper::OnEndCompositionCallback(GtkIMContext* aContext,
1073 : IMContextWrapper* aModule)
1074 : {
1075 0 : aModule->OnEndCompositionNative(aContext);
1076 0 : }
1077 :
1078 : void
1079 0 : IMContextWrapper::OnEndCompositionNative(GtkIMContext* aContext)
1080 : {
1081 0 : MOZ_LOG(gGtkIMLog, LogLevel::Info,
1082 : ("0x%p OnEndCompositionNative(aContext=0x%p)",
1083 : this, aContext));
1084 :
1085 : // See bug 472635, we should do nothing if IM context doesn't match.
1086 : // Note that if this is called after focus move, the context may different
1087 : // from any our owning context.
1088 0 : if (!IsValidContext(aContext)) {
1089 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1090 : ("0x%p OnEndCompositionNative(), FAILED, "
1091 : "given context doesn't match with any context",
1092 : this));
1093 0 : return;
1094 : }
1095 :
1096 0 : g_object_unref(mComposingContext);
1097 0 : mComposingContext = nullptr;
1098 :
1099 : // If we already handled the commit event, we should do nothing here.
1100 0 : if (IsComposing()) {
1101 0 : if (!DispatchCompositionCommitEvent(aContext)) {
1102 : // If the widget is destroyed, we should do nothing anymore.
1103 0 : return;
1104 : }
1105 : }
1106 :
1107 0 : if (mPendingResettingIMContext) {
1108 0 : ResetIME();
1109 : }
1110 : }
1111 :
1112 : /* static */
1113 : void
1114 0 : IMContextWrapper::OnChangeCompositionCallback(GtkIMContext* aContext,
1115 : IMContextWrapper* aModule)
1116 : {
1117 0 : aModule->OnChangeCompositionNative(aContext);
1118 0 : }
1119 :
1120 : void
1121 0 : IMContextWrapper::OnChangeCompositionNative(GtkIMContext* aContext)
1122 : {
1123 0 : MOZ_LOG(gGtkIMLog, LogLevel::Info,
1124 : ("0x%p OnChangeCompositionNative(aContext=0x%p)",
1125 : this, aContext));
1126 :
1127 : // See bug 472635, we should do nothing if IM context doesn't match.
1128 : // Note that if this is called after focus move, the context may different
1129 : // from any our owning context.
1130 0 : if (!IsValidContext(aContext)) {
1131 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1132 : ("0x%p OnChangeCompositionNative(), FAILED, "
1133 : "given context doesn't match with any context",
1134 : this));
1135 0 : return;
1136 : }
1137 :
1138 0 : nsAutoString compositionString;
1139 0 : GetCompositionString(aContext, compositionString);
1140 0 : if (!IsComposing() && compositionString.IsEmpty()) {
1141 0 : mDispatchedCompositionString.Truncate();
1142 0 : return; // Don't start the composition with empty string.
1143 : }
1144 :
1145 : // Be aware, widget can be gone
1146 0 : DispatchCompositionChangeEvent(aContext, compositionString);
1147 : }
1148 :
1149 : /* static */
1150 : gboolean
1151 0 : IMContextWrapper::OnRetrieveSurroundingCallback(GtkIMContext* aContext,
1152 : IMContextWrapper* aModule)
1153 : {
1154 0 : return aModule->OnRetrieveSurroundingNative(aContext);
1155 : }
1156 :
1157 : gboolean
1158 0 : IMContextWrapper::OnRetrieveSurroundingNative(GtkIMContext* aContext)
1159 : {
1160 0 : MOZ_LOG(gGtkIMLog, LogLevel::Info,
1161 : ("0x%p OnRetrieveSurroundingNative(aContext=0x%p), "
1162 : "current context=0x%p",
1163 : this, aContext, GetCurrentContext()));
1164 :
1165 : // See bug 472635, we should do nothing if IM context doesn't match.
1166 0 : if (GetCurrentContext() != aContext) {
1167 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1168 : ("0x%p OnRetrieveSurroundingNative(), FAILED, "
1169 : "given context doesn't match",
1170 : this));
1171 0 : return FALSE;
1172 : }
1173 :
1174 0 : nsAutoString uniStr;
1175 : uint32_t cursorPos;
1176 0 : if (NS_FAILED(GetCurrentParagraph(uniStr, cursorPos))) {
1177 0 : return FALSE;
1178 : }
1179 :
1180 0 : NS_ConvertUTF16toUTF8 utf8Str(nsDependentSubstring(uniStr, 0, cursorPos));
1181 0 : uint32_t cursorPosInUTF8 = utf8Str.Length();
1182 0 : AppendUTF16toUTF8(nsDependentSubstring(uniStr, cursorPos), utf8Str);
1183 0 : gtk_im_context_set_surrounding(aContext, utf8Str.get(), utf8Str.Length(),
1184 0 : cursorPosInUTF8);
1185 0 : mRetrieveSurroundingSignalReceived = true;
1186 0 : return TRUE;
1187 : }
1188 :
1189 : /* static */
1190 : gboolean
1191 0 : IMContextWrapper::OnDeleteSurroundingCallback(GtkIMContext* aContext,
1192 : gint aOffset,
1193 : gint aNChars,
1194 : IMContextWrapper* aModule)
1195 : {
1196 0 : return aModule->OnDeleteSurroundingNative(aContext, aOffset, aNChars);
1197 : }
1198 :
1199 : gboolean
1200 0 : IMContextWrapper::OnDeleteSurroundingNative(GtkIMContext* aContext,
1201 : gint aOffset,
1202 : gint aNChars)
1203 : {
1204 0 : MOZ_LOG(gGtkIMLog, LogLevel::Info,
1205 : ("0x%p OnDeleteSurroundingNative(aContext=0x%p, aOffset=%d, "
1206 : "aNChar=%d), current context=0x%p",
1207 : this, aContext, aOffset, aNChars, GetCurrentContext()));
1208 :
1209 : // See bug 472635, we should do nothing if IM context doesn't match.
1210 0 : if (GetCurrentContext() != aContext) {
1211 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1212 : ("0x%p OnDeleteSurroundingNative(), FAILED, "
1213 : "given context doesn't match",
1214 : this));
1215 0 : return FALSE;
1216 : }
1217 :
1218 0 : AutoRestore<bool> saveDeletingSurrounding(mIsDeletingSurrounding);
1219 0 : mIsDeletingSurrounding = true;
1220 0 : if (NS_SUCCEEDED(DeleteText(aContext, aOffset, (uint32_t)aNChars))) {
1221 0 : return TRUE;
1222 : }
1223 :
1224 : // failed
1225 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1226 : ("0x%p OnDeleteSurroundingNative(), FAILED, "
1227 : "cannot delete text",
1228 : this));
1229 0 : return FALSE;
1230 : }
1231 :
1232 : /* static */
1233 : void
1234 0 : IMContextWrapper::OnCommitCompositionCallback(GtkIMContext* aContext,
1235 : const gchar* aString,
1236 : IMContextWrapper* aModule)
1237 : {
1238 0 : aModule->OnCommitCompositionNative(aContext, aString);
1239 0 : }
1240 :
1241 : void
1242 0 : IMContextWrapper::OnCommitCompositionNative(GtkIMContext* aContext,
1243 : const gchar* aUTF8Char)
1244 : {
1245 0 : const gchar emptyStr = 0;
1246 0 : const gchar *commitString = aUTF8Char ? aUTF8Char : &emptyStr;
1247 :
1248 0 : MOZ_LOG(gGtkIMLog, LogLevel::Info,
1249 : ("0x%p OnCommitCompositionNative(aContext=0x%p), "
1250 : "current context=0x%p, active context=0x%p, commitString=\"%s\", "
1251 : "mProcessingKeyEvent=0x%p, IsComposingOn(aContext)=%s",
1252 : this, aContext, GetCurrentContext(), GetActiveContext(), commitString,
1253 : mProcessingKeyEvent, ToChar(IsComposingOn(aContext))));
1254 :
1255 : // See bug 472635, we should do nothing if IM context doesn't match.
1256 0 : if (!IsValidContext(aContext)) {
1257 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1258 : ("0x%p OnCommitCompositionNative(), FAILED, "
1259 : "given context doesn't match",
1260 : this));
1261 0 : return;
1262 : }
1263 :
1264 : // If we are not in composition and committing with empty string,
1265 : // we need to do nothing because if we continued to handle this
1266 : // signal, we would dispatch compositionstart, text, compositionend
1267 : // events with empty string. Of course, they are unnecessary events
1268 : // for Web applications and our editor.
1269 0 : if (!IsComposingOn(aContext) && !commitString[0]) {
1270 0 : return;
1271 : }
1272 :
1273 : // If IME doesn't change their keyevent that generated this commit,
1274 : // don't send it through XIM - just send it as a normal key press
1275 : // event.
1276 : // NOTE: While a key event is being handled, this might be caused on
1277 : // current context. Otherwise, this may be caused on active context.
1278 0 : if (!IsComposingOn(aContext) && mProcessingKeyEvent &&
1279 0 : aContext == GetCurrentContext()) {
1280 : char keyval_utf8[8]; /* should have at least 6 bytes of space */
1281 : gint keyval_utf8_len;
1282 : guint32 keyval_unicode;
1283 :
1284 0 : keyval_unicode = gdk_keyval_to_unicode(mProcessingKeyEvent->keyval);
1285 0 : keyval_utf8_len = g_unichar_to_utf8(keyval_unicode, keyval_utf8);
1286 0 : keyval_utf8[keyval_utf8_len] = '\0';
1287 :
1288 0 : if (!strcmp(commitString, keyval_utf8)) {
1289 0 : MOZ_LOG(gGtkIMLog, LogLevel::Info,
1290 : ("0x%p OnCommitCompositionNative(), "
1291 : "we'll send normal key event",
1292 : this));
1293 0 : mFilterKeyEvent = false;
1294 0 : return;
1295 : }
1296 : }
1297 :
1298 0 : NS_ConvertUTF8toUTF16 str(commitString);
1299 : // Be aware, widget can be gone
1300 0 : DispatchCompositionCommitEvent(aContext, &str);
1301 : }
1302 :
1303 : void
1304 0 : IMContextWrapper::GetCompositionString(GtkIMContext* aContext,
1305 : nsAString& aCompositionString)
1306 : {
1307 : gchar *preedit_string;
1308 : gint cursor_pos;
1309 : PangoAttrList *feedback_list;
1310 : gtk_im_context_get_preedit_string(aContext, &preedit_string,
1311 0 : &feedback_list, &cursor_pos);
1312 0 : if (preedit_string && *preedit_string) {
1313 0 : CopyUTF8toUTF16(preedit_string, aCompositionString);
1314 : } else {
1315 0 : aCompositionString.Truncate();
1316 : }
1317 :
1318 0 : MOZ_LOG(gGtkIMLog, LogLevel::Info,
1319 : ("0x%p GetCompositionString(aContext=0x%p), "
1320 : "aCompositionString=\"%s\"",
1321 : this, aContext, preedit_string));
1322 :
1323 0 : pango_attr_list_unref(feedback_list);
1324 0 : g_free(preedit_string);
1325 0 : }
1326 :
1327 : bool
1328 0 : IMContextWrapper::DispatchCompositionStart(GtkIMContext* aContext)
1329 : {
1330 0 : MOZ_LOG(gGtkIMLog, LogLevel::Info,
1331 : ("0x%p DispatchCompositionStart(aContext=0x%p)",
1332 : this, aContext));
1333 :
1334 0 : if (IsComposing()) {
1335 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1336 : ("0x%p DispatchCompositionStart(), FAILED, "
1337 : "we're already in composition",
1338 : this));
1339 0 : return true;
1340 : }
1341 :
1342 0 : if (!mLastFocusedWindow) {
1343 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1344 : ("0x%p DispatchCompositionStart(), FAILED, "
1345 : "there are no focused window in this module",
1346 : this));
1347 0 : return false;
1348 : }
1349 :
1350 0 : if (NS_WARN_IF(!EnsureToCacheSelection())) {
1351 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1352 : ("0x%p DispatchCompositionStart(), FAILED, "
1353 : "cannot query the selection offset",
1354 : this));
1355 0 : return false;
1356 : }
1357 :
1358 : // Keep the last focused window alive
1359 0 : RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
1360 :
1361 : // XXX The composition start point might be changed by composition events
1362 : // even though we strongly hope it doesn't happen.
1363 : // Every composition event should have the start offset for the result
1364 : // because it may high cost if we query the offset every time.
1365 0 : mCompositionStart = mSelection.mOffset;
1366 0 : mDispatchedCompositionString.Truncate();
1367 :
1368 0 : if (mProcessingKeyEvent && !mKeyDownEventWasSent &&
1369 0 : mProcessingKeyEvent->type == GDK_KEY_PRESS) {
1370 : // If this composition is started by a native keydown event, we need to
1371 : // dispatch our keydown event here (before composition start).
1372 : bool isCancelled;
1373 0 : mLastFocusedWindow->DispatchKeyDownEvent(mProcessingKeyEvent,
1374 0 : &isCancelled);
1375 0 : MOZ_LOG(gGtkIMLog, LogLevel::Debug,
1376 : ("0x%p DispatchCompositionStart(), FAILED, keydown event "
1377 : "is dispatched",
1378 : this));
1379 0 : if (lastFocusedWindow->IsDestroyed() ||
1380 0 : lastFocusedWindow != mLastFocusedWindow) {
1381 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1382 : ("0x%p DispatchCompositionStart(), FAILED, the focused "
1383 : "widget was destroyed/changed by keydown event",
1384 : this));
1385 0 : return false;
1386 : }
1387 : }
1388 :
1389 0 : RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher();
1390 0 : nsresult rv = dispatcher->BeginNativeInputTransaction();
1391 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
1392 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1393 : ("0x%p DispatchCompositionStart(), FAILED, "
1394 : "due to BeginNativeInputTransaction() failure",
1395 : this));
1396 0 : return false;
1397 : }
1398 :
1399 0 : MOZ_LOG(gGtkIMLog, LogLevel::Debug,
1400 : ("0x%p DispatchCompositionStart(), dispatching "
1401 : "compositionstart... (mCompositionStart=%u)",
1402 : this, mCompositionStart));
1403 0 : mCompositionState = eCompositionState_CompositionStartDispatched;
1404 : nsEventStatus status;
1405 0 : dispatcher->StartComposition(status);
1406 0 : if (lastFocusedWindow->IsDestroyed() ||
1407 0 : lastFocusedWindow != mLastFocusedWindow) {
1408 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1409 : ("0x%p DispatchCompositionStart(), FAILED, the focused "
1410 : "widget was destroyed/changed by compositionstart event",
1411 : this));
1412 0 : return false;
1413 : }
1414 :
1415 0 : return true;
1416 : }
1417 :
1418 : bool
1419 0 : IMContextWrapper::DispatchCompositionChangeEvent(
1420 : GtkIMContext* aContext,
1421 : const nsAString& aCompositionString)
1422 : {
1423 0 : MOZ_LOG(gGtkIMLog, LogLevel::Info,
1424 : ("0x%p DispatchCompositionChangeEvent(aContext=0x%p)",
1425 : this, aContext));
1426 :
1427 0 : if (!mLastFocusedWindow) {
1428 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1429 : ("0x%p DispatchCompositionChangeEvent(), FAILED, "
1430 : "there are no focused window in this module",
1431 : this));
1432 0 : return false;
1433 : }
1434 :
1435 0 : if (!IsComposing()) {
1436 0 : MOZ_LOG(gGtkIMLog, LogLevel::Debug,
1437 : ("0x%p DispatchCompositionChangeEvent(), the composition "
1438 : "wasn't started, force starting...",
1439 : this));
1440 0 : if (!DispatchCompositionStart(aContext)) {
1441 0 : return false;
1442 : }
1443 : }
1444 :
1445 0 : RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher();
1446 0 : nsresult rv = dispatcher->BeginNativeInputTransaction();
1447 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
1448 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1449 : ("0x%p DispatchCompositionChangeEvent(), FAILED, "
1450 : "due to BeginNativeInputTransaction() failure",
1451 : this));
1452 0 : return false;
1453 : }
1454 :
1455 : // Store the selected string which will be removed by following
1456 : // compositionchange event.
1457 0 : if (mCompositionState == eCompositionState_CompositionStartDispatched) {
1458 0 : if (NS_WARN_IF(!EnsureToCacheSelection(
1459 : &mSelectedStringRemovedByComposition))) {
1460 : // XXX How should we behave in this case??
1461 : } else {
1462 : // XXX We should assume, for now, any web applications don't change
1463 : // selection at handling this compositionchange event.
1464 0 : mCompositionStart = mSelection.mOffset;
1465 : }
1466 : }
1467 :
1468 : RefPtr<TextRangeArray> rangeArray =
1469 0 : CreateTextRangeArray(aContext, aCompositionString);
1470 :
1471 0 : rv = dispatcher->SetPendingComposition(aCompositionString, rangeArray);
1472 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
1473 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1474 : ("0x%p DispatchCompositionChangeEvent(), FAILED, "
1475 : "due to SetPendingComposition() failure",
1476 : this));
1477 0 : return false;
1478 : }
1479 :
1480 0 : mCompositionState = eCompositionState_CompositionChangeEventDispatched;
1481 :
1482 : // We cannot call SetCursorPosition for e10s-aware.
1483 : // DispatchEvent is async on e10s, so composition rect isn't updated now
1484 : // on tab parent.
1485 0 : mDispatchedCompositionString = aCompositionString;
1486 0 : mLayoutChanged = false;
1487 0 : mCompositionTargetRange.mOffset =
1488 0 : mCompositionStart + rangeArray->TargetClauseOffset();
1489 0 : mCompositionTargetRange.mLength = rangeArray->TargetClauseLength();
1490 :
1491 0 : RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
1492 : nsEventStatus status;
1493 0 : rv = dispatcher->FlushPendingComposition(status);
1494 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
1495 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1496 : ("0x%p DispatchCompositionChangeEvent(), FAILED, "
1497 : "due to FlushPendingComposition() failure",
1498 : this));
1499 0 : return false;
1500 : }
1501 :
1502 0 : if (lastFocusedWindow->IsDestroyed() ||
1503 0 : lastFocusedWindow != mLastFocusedWindow) {
1504 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1505 : ("0x%p DispatchCompositionChangeEvent(), FAILED, the "
1506 : "focused widget was destroyed/changed by "
1507 : "compositionchange event",
1508 : this));
1509 0 : return false;
1510 : }
1511 0 : return true;
1512 : }
1513 :
1514 : bool
1515 0 : IMContextWrapper::DispatchCompositionCommitEvent(
1516 : GtkIMContext* aContext,
1517 : const nsAString* aCommitString)
1518 : {
1519 0 : MOZ_LOG(gGtkIMLog, LogLevel::Info,
1520 : ("0x%p DispatchCompositionCommitEvent(aContext=0x%p, "
1521 : "aCommitString=0x%p, (\"%s\"))",
1522 : this, aContext, aCommitString,
1523 : aCommitString ? NS_ConvertUTF16toUTF8(*aCommitString).get() : ""));
1524 :
1525 0 : if (!mLastFocusedWindow) {
1526 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1527 : ("0x%p DispatchCompositionCommitEvent(), FAILED, "
1528 : "there are no focused window in this module",
1529 : this));
1530 0 : return false;
1531 : }
1532 :
1533 0 : if (!IsComposing()) {
1534 0 : if (!aCommitString || aCommitString->IsEmpty()) {
1535 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1536 : ("0x%p DispatchCompositionCommitEvent(), FAILED, "
1537 : "there is no composition and empty commit string",
1538 : this));
1539 0 : return true;
1540 : }
1541 0 : MOZ_LOG(gGtkIMLog, LogLevel::Debug,
1542 : ("0x%p DispatchCompositionCommitEvent(), "
1543 : "the composition wasn't started, force starting...",
1544 : this));
1545 0 : if (!DispatchCompositionStart(aContext)) {
1546 0 : return false;
1547 : }
1548 : }
1549 :
1550 0 : RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher();
1551 0 : nsresult rv = dispatcher->BeginNativeInputTransaction();
1552 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
1553 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1554 : ("0x%p DispatchCompositionCommitEvent(), FAILED, "
1555 : "due to BeginNativeInputTransaction() failure",
1556 : this));
1557 0 : return false;
1558 : }
1559 :
1560 0 : RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
1561 :
1562 : // Emulate selection until receiving actual selection range.
1563 0 : mSelection.CollapseTo(
1564 0 : mCompositionStart +
1565 0 : (aCommitString ? aCommitString->Length() :
1566 0 : mDispatchedCompositionString.Length()),
1567 0 : mSelection.mWritingMode);
1568 :
1569 0 : mCompositionState = eCompositionState_NotComposing;
1570 0 : mCompositionStart = UINT32_MAX;
1571 0 : mCompositionTargetRange.Clear();
1572 0 : mDispatchedCompositionString.Truncate();
1573 0 : mSelectedStringRemovedByComposition.Truncate();
1574 :
1575 : nsEventStatus status;
1576 0 : rv = dispatcher->CommitComposition(status, aCommitString);
1577 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
1578 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1579 : ("0x%p DispatchCompositionChangeEvent(), FAILED, "
1580 : "due to CommitComposition() failure",
1581 : this));
1582 0 : return false;
1583 : }
1584 :
1585 0 : if (lastFocusedWindow->IsDestroyed() ||
1586 0 : lastFocusedWindow != mLastFocusedWindow) {
1587 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1588 : ("0x%p DispatchCompositionCommitEvent(), FAILED, "
1589 : "the focused widget was destroyed/changed by "
1590 : "compositioncommit event",
1591 : this));
1592 0 : return false;
1593 : }
1594 :
1595 0 : return true;
1596 : }
1597 :
1598 : already_AddRefed<TextRangeArray>
1599 0 : IMContextWrapper::CreateTextRangeArray(GtkIMContext* aContext,
1600 : const nsAString& aCompositionString)
1601 : {
1602 0 : MOZ_LOG(gGtkIMLog, LogLevel::Info,
1603 : ("0x%p CreateTextRangeArray(aContext=0x%p, "
1604 : "aCompositionString=\"%s\" (Length()=%u))",
1605 : this, aContext, NS_ConvertUTF16toUTF8(aCompositionString).get(),
1606 : aCompositionString.Length()));
1607 :
1608 0 : RefPtr<TextRangeArray> textRangeArray = new TextRangeArray();
1609 :
1610 : gchar *preedit_string;
1611 : gint cursor_pos_in_chars;
1612 : PangoAttrList *feedback_list;
1613 : gtk_im_context_get_preedit_string(aContext, &preedit_string,
1614 0 : &feedback_list, &cursor_pos_in_chars);
1615 0 : if (!preedit_string || !*preedit_string) {
1616 0 : if (!aCompositionString.IsEmpty()) {
1617 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1618 : ("0x%p CreateTextRangeArray(), FAILED, due to "
1619 : "preedit_string is null",
1620 : this));
1621 : }
1622 0 : pango_attr_list_unref(feedback_list);
1623 0 : g_free(preedit_string);
1624 0 : return textRangeArray.forget();
1625 : }
1626 :
1627 : // Convert caret offset from offset in characters to offset in UTF-16
1628 : // string. If we couldn't proper offset in UTF-16 string, we should
1629 : // assume that the caret is at the end of the composition string.
1630 0 : uint32_t caretOffsetInUTF16 = aCompositionString.Length();
1631 0 : if (NS_WARN_IF(cursor_pos_in_chars < 0)) {
1632 : // Note that this case is undocumented. We should assume that the
1633 : // caret is at the end of the composition string.
1634 0 : } else if (cursor_pos_in_chars == 0) {
1635 0 : caretOffsetInUTF16 = 0;
1636 : } else {
1637 : gchar* charAfterCaret =
1638 0 : g_utf8_offset_to_pointer(preedit_string, cursor_pos_in_chars);
1639 0 : if (NS_WARN_IF(!charAfterCaret)) {
1640 0 : MOZ_LOG(gGtkIMLog, LogLevel::Warning,
1641 : ("0x%p CreateTextRangeArray(), failed to get UTF-8 "
1642 : "string before the caret (cursor_pos_in_chars=%d)",
1643 : this, cursor_pos_in_chars));
1644 : } else {
1645 0 : glong caretOffset = 0;
1646 : gunichar2* utf16StrBeforeCaret =
1647 0 : g_utf8_to_utf16(preedit_string, charAfterCaret - preedit_string,
1648 0 : nullptr, &caretOffset, nullptr);
1649 0 : if (NS_WARN_IF(!utf16StrBeforeCaret) ||
1650 0 : NS_WARN_IF(caretOffset < 0)) {
1651 0 : MOZ_LOG(gGtkIMLog, LogLevel::Warning,
1652 : ("0x%p CreateTextRangeArray(), WARNING, failed to "
1653 : "convert to UTF-16 string before the caret "
1654 : "(cursor_pos_in_chars=%d, caretOffset=%ld)",
1655 : this, cursor_pos_in_chars, caretOffset));
1656 : } else {
1657 0 : caretOffsetInUTF16 = static_cast<uint32_t>(caretOffset);
1658 0 : uint32_t compositionStringLength = aCompositionString.Length();
1659 0 : if (NS_WARN_IF(caretOffsetInUTF16 > compositionStringLength)) {
1660 0 : MOZ_LOG(gGtkIMLog, LogLevel::Warning,
1661 : ("0x%p CreateTextRangeArray(), WARNING, "
1662 : "caretOffsetInUTF16=%u is larger than "
1663 : "compositionStringLength=%u",
1664 : this, caretOffsetInUTF16, compositionStringLength));
1665 0 : caretOffsetInUTF16 = compositionStringLength;
1666 : }
1667 : }
1668 0 : if (utf16StrBeforeCaret) {
1669 0 : g_free(utf16StrBeforeCaret);
1670 : }
1671 : }
1672 : }
1673 :
1674 : PangoAttrIterator* iter;
1675 0 : iter = pango_attr_list_get_iterator(feedback_list);
1676 0 : if (!iter) {
1677 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1678 : ("0x%p CreateTextRangeArray(), FAILED, iterator couldn't "
1679 : "be allocated",
1680 : this));
1681 0 : pango_attr_list_unref(feedback_list);
1682 0 : g_free(preedit_string);
1683 0 : return textRangeArray.forget();
1684 : }
1685 :
1686 0 : uint32_t minOffsetOfClauses = aCompositionString.Length();
1687 0 : do {
1688 0 : TextRange range;
1689 0 : if (!SetTextRange(iter, preedit_string, caretOffsetInUTF16, range)) {
1690 0 : continue;
1691 : }
1692 0 : MOZ_ASSERT(range.Length());
1693 0 : minOffsetOfClauses = std::min(minOffsetOfClauses, range.mStartOffset);
1694 0 : textRangeArray->AppendElement(range);
1695 0 : } while (pango_attr_iterator_next(iter));
1696 :
1697 : // If the IME doesn't define clause from the start of the composition,
1698 : // we should insert dummy clause information since TextRangeArray assumes
1699 : // that there must be a clause whose start is 0 when there is one or
1700 : // more clauses.
1701 0 : if (minOffsetOfClauses) {
1702 0 : TextRange dummyClause;
1703 0 : dummyClause.mStartOffset = 0;
1704 0 : dummyClause.mEndOffset = minOffsetOfClauses;
1705 0 : dummyClause.mRangeType = TextRangeType::eRawClause;
1706 0 : textRangeArray->InsertElementAt(0, dummyClause);
1707 0 : MOZ_LOG(gGtkIMLog, LogLevel::Warning,
1708 : ("0x%p CreateTextRangeArray(), inserting a dummy clause "
1709 : "at the beginning of the composition string mStartOffset=%u, "
1710 : "mEndOffset=%u, mRangeType=%s",
1711 : this, dummyClause.mStartOffset, dummyClause.mEndOffset,
1712 : ToChar(dummyClause.mRangeType)));
1713 : }
1714 :
1715 0 : TextRange range;
1716 0 : range.mStartOffset = range.mEndOffset = caretOffsetInUTF16;
1717 0 : range.mRangeType = TextRangeType::eCaret;
1718 0 : textRangeArray->AppendElement(range);
1719 0 : MOZ_LOG(gGtkIMLog, LogLevel::Debug,
1720 : ("0x%p CreateTextRangeArray(), mStartOffset=%u, "
1721 : "mEndOffset=%u, mRangeType=%s",
1722 : this, range.mStartOffset, range.mEndOffset,
1723 : ToChar(range.mRangeType)));
1724 :
1725 0 : pango_attr_iterator_destroy(iter);
1726 0 : pango_attr_list_unref(feedback_list);
1727 0 : g_free(preedit_string);
1728 :
1729 0 : return textRangeArray.forget();
1730 : }
1731 :
1732 : /* static */
1733 : nscolor
1734 0 : IMContextWrapper::ToNscolor(PangoAttrColor* aPangoAttrColor)
1735 : {
1736 0 : PangoColor& pangoColor = aPangoAttrColor->color;
1737 0 : uint8_t r = pangoColor.red / 0x100;
1738 0 : uint8_t g = pangoColor.green / 0x100;
1739 0 : uint8_t b = pangoColor.blue / 0x100;
1740 0 : return NS_RGB(r, g, b);
1741 : }
1742 :
1743 : bool
1744 0 : IMContextWrapper::SetTextRange(PangoAttrIterator* aPangoAttrIter,
1745 : const gchar* aUTF8CompositionString,
1746 : uint32_t aUTF16CaretOffset,
1747 : TextRange& aTextRange) const
1748 : {
1749 : // Set the range offsets in UTF-16 string.
1750 : gint utf8ClauseStart, utf8ClauseEnd;
1751 0 : pango_attr_iterator_range(aPangoAttrIter, &utf8ClauseStart, &utf8ClauseEnd);
1752 0 : if (utf8ClauseStart == utf8ClauseEnd) {
1753 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1754 : ("0x%p SetTextRange(), FAILED, due to collapsed range",
1755 : this));
1756 0 : return false;
1757 : }
1758 :
1759 0 : if (!utf8ClauseStart) {
1760 0 : aTextRange.mStartOffset = 0;
1761 : } else {
1762 : glong utf16PreviousClausesLength;
1763 : gunichar2* utf16PreviousClausesString =
1764 0 : g_utf8_to_utf16(aUTF8CompositionString, utf8ClauseStart, nullptr,
1765 0 : &utf16PreviousClausesLength, nullptr);
1766 :
1767 0 : if (NS_WARN_IF(!utf16PreviousClausesString)) {
1768 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1769 : ("0x%p SetTextRange(), FAILED, due to g_utf8_to_utf16() "
1770 : "failure (retrieving previous string of current clause)",
1771 : this));
1772 0 : return false;
1773 : }
1774 :
1775 0 : aTextRange.mStartOffset = utf16PreviousClausesLength;
1776 0 : g_free(utf16PreviousClausesString);
1777 : }
1778 :
1779 : glong utf16CurrentClauseLength;
1780 : gunichar2* utf16CurrentClauseString =
1781 0 : g_utf8_to_utf16(aUTF8CompositionString + utf8ClauseStart,
1782 0 : utf8ClauseEnd - utf8ClauseStart,
1783 0 : nullptr, &utf16CurrentClauseLength, nullptr);
1784 :
1785 0 : if (NS_WARN_IF(!utf16CurrentClauseString)) {
1786 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1787 : ("0x%p SetTextRange(), FAILED, due to g_utf8_to_utf16() "
1788 : "failure (retrieving current clause)",
1789 : this));
1790 0 : return false;
1791 : }
1792 :
1793 : // iBus Chewing IME tells us that there is an empty clause at the end of
1794 : // the composition string but we should ignore it since our code doesn't
1795 : // assume that there is an empty clause.
1796 0 : if (!utf16CurrentClauseLength) {
1797 0 : MOZ_LOG(gGtkIMLog, LogLevel::Warning,
1798 : ("0x%p SetTextRange(), FAILED, due to current clause length "
1799 : "is 0",
1800 : this));
1801 0 : return false;
1802 : }
1803 :
1804 0 : aTextRange.mEndOffset = aTextRange.mStartOffset + utf16CurrentClauseLength;
1805 0 : g_free(utf16CurrentClauseString);
1806 0 : utf16CurrentClauseString = nullptr;
1807 :
1808 : // Set styles
1809 0 : TextRangeStyle& style = aTextRange.mRangeStyle;
1810 :
1811 : // Underline
1812 : PangoAttrInt* attrUnderline =
1813 : reinterpret_cast<PangoAttrInt*>(
1814 0 : pango_attr_iterator_get(aPangoAttrIter, PANGO_ATTR_UNDERLINE));
1815 0 : if (attrUnderline) {
1816 0 : switch (attrUnderline->value) {
1817 : case PANGO_UNDERLINE_NONE:
1818 0 : style.mLineStyle = TextRangeStyle::LINESTYLE_NONE;
1819 0 : break;
1820 : case PANGO_UNDERLINE_DOUBLE:
1821 0 : style.mLineStyle = TextRangeStyle::LINESTYLE_DOUBLE;
1822 0 : break;
1823 : case PANGO_UNDERLINE_ERROR:
1824 0 : style.mLineStyle = TextRangeStyle::LINESTYLE_WAVY;
1825 0 : break;
1826 : case PANGO_UNDERLINE_SINGLE:
1827 : case PANGO_UNDERLINE_LOW:
1828 0 : style.mLineStyle = TextRangeStyle::LINESTYLE_SOLID;
1829 0 : break;
1830 : default:
1831 0 : MOZ_LOG(gGtkIMLog, LogLevel::Warning,
1832 : ("0x%p SetTextRange(), retrieved unknown underline "
1833 : "style: %d",
1834 : this, attrUnderline->value));
1835 0 : style.mLineStyle = TextRangeStyle::LINESTYLE_SOLID;
1836 0 : break;
1837 : }
1838 0 : style.mDefinedStyles |= TextRangeStyle::DEFINED_LINESTYLE;
1839 :
1840 : // Underline color
1841 : PangoAttrColor* attrUnderlineColor =
1842 : reinterpret_cast<PangoAttrColor*>(
1843 : pango_attr_iterator_get(aPangoAttrIter,
1844 0 : PANGO_ATTR_UNDERLINE_COLOR));
1845 0 : if (attrUnderlineColor) {
1846 0 : style.mUnderlineColor = ToNscolor(attrUnderlineColor);
1847 0 : style.mDefinedStyles |= TextRangeStyle::DEFINED_UNDERLINE_COLOR;
1848 : }
1849 : } else {
1850 0 : style.mLineStyle = TextRangeStyle::LINESTYLE_NONE;
1851 0 : style.mDefinedStyles |= TextRangeStyle::DEFINED_LINESTYLE;
1852 : }
1853 :
1854 : // Don't set colors if they are not specified. They should be computed by
1855 : // textframe if only one of the colors are specified.
1856 :
1857 : // Foreground color (text color)
1858 : PangoAttrColor* attrForeground =
1859 : reinterpret_cast<PangoAttrColor*>(
1860 0 : pango_attr_iterator_get(aPangoAttrIter, PANGO_ATTR_FOREGROUND));
1861 0 : if (attrForeground) {
1862 0 : style.mForegroundColor = ToNscolor(attrForeground);
1863 0 : style.mDefinedStyles |= TextRangeStyle::DEFINED_FOREGROUND_COLOR;
1864 : }
1865 :
1866 : // Background color
1867 : PangoAttrColor* attrBackground =
1868 : reinterpret_cast<PangoAttrColor*>(
1869 0 : pango_attr_iterator_get(aPangoAttrIter, PANGO_ATTR_BACKGROUND));
1870 0 : if (attrBackground) {
1871 0 : style.mBackgroundColor = ToNscolor(attrBackground);
1872 0 : style.mDefinedStyles |= TextRangeStyle::DEFINED_BACKGROUND_COLOR;
1873 : }
1874 :
1875 : /**
1876 : * We need to judge the meaning of the clause for a11y. Before we support
1877 : * IME specific composition string style, we used following rules:
1878 : *
1879 : * 1: If attrUnderline and attrForground are specified, we assumed the
1880 : * clause is TextRangeType::eSelectedClause.
1881 : * 2: If only attrUnderline is specified, we assumed the clause is
1882 : * TextRangeType::eConvertedClause.
1883 : * 3: If only attrForground is specified, we assumed the clause is
1884 : * TextRangeType::eSelectedRawClause.
1885 : * 4: If neither attrUnderline nor attrForeground is specified, we assumed
1886 : * the clause is TextRangeType::eRawClause.
1887 : *
1888 : * However, this rules are odd since there can be two or more selected
1889 : * clauses. Additionally, our old rules caused that IME developers/users
1890 : * cannot specify composition string style as they want.
1891 : *
1892 : * So, we shouldn't guess the meaning from its visual style.
1893 : */
1894 :
1895 0 : if (!attrUnderline && !attrForeground && !attrBackground) {
1896 0 : MOZ_LOG(gGtkIMLog, LogLevel::Warning,
1897 : ("0x%p SetTextRange(), FAILED, due to no attr, "
1898 : "aTextRange= { mStartOffset=%u, mEndOffset=%u }",
1899 : this, aTextRange.mStartOffset, aTextRange.mEndOffset));
1900 0 : return false;
1901 : }
1902 :
1903 : // If the range covers whole of composition string and the caret is at
1904 : // the end of the composition string, the range is probably not converted.
1905 0 : if (!utf8ClauseStart &&
1906 0 : utf8ClauseEnd == static_cast<gint>(strlen(aUTF8CompositionString)) &&
1907 0 : aTextRange.mEndOffset == aUTF16CaretOffset) {
1908 0 : aTextRange.mRangeType = TextRangeType::eRawClause;
1909 : }
1910 : // Typically, the caret is set at the start of the selected clause.
1911 : // So, if the caret is in the clause, we can assume that the clause is
1912 : // selected.
1913 0 : else if (aTextRange.mStartOffset <= aUTF16CaretOffset &&
1914 0 : aTextRange.mEndOffset > aUTF16CaretOffset) {
1915 0 : aTextRange.mRangeType = TextRangeType::eSelectedClause;
1916 : }
1917 : // Otherwise, we should assume that the clause is converted but not
1918 : // selected.
1919 : else {
1920 0 : aTextRange.mRangeType = TextRangeType::eConvertedClause;
1921 : }
1922 :
1923 0 : MOZ_LOG(gGtkIMLog, LogLevel::Debug,
1924 : ("0x%p SetTextRange(), succeeded, aTextRange= { "
1925 : "mStartOffset=%u, mEndOffset=%u, mRangeType=%s, mRangeStyle=%s }",
1926 : this, aTextRange.mStartOffset, aTextRange.mEndOffset,
1927 : ToChar(aTextRange.mRangeType),
1928 : GetTextRangeStyleText(aTextRange.mRangeStyle).get()));
1929 :
1930 0 : return true;
1931 : }
1932 :
1933 : void
1934 0 : IMContextWrapper::SetCursorPosition(GtkIMContext* aContext)
1935 : {
1936 0 : MOZ_LOG(gGtkIMLog, LogLevel::Info,
1937 : ("0x%p SetCursorPosition(aContext=0x%p), "
1938 : "mCompositionTargetRange={ mOffset=%u, mLength=%u }"
1939 : "mSelection={ mOffset=%u, Length()=%u, mWritingMode=%s }",
1940 : this, aContext, mCompositionTargetRange.mOffset,
1941 : mCompositionTargetRange.mLength,
1942 : mSelection.mOffset, mSelection.Length(),
1943 : GetWritingModeName(mSelection.mWritingMode).get()));
1944 :
1945 0 : bool useCaret = false;
1946 0 : if (!mCompositionTargetRange.IsValid()) {
1947 0 : if (!mSelection.IsValid()) {
1948 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1949 : ("0x%p SetCursorPosition(), FAILED, "
1950 : "mCompositionTargetRange and mSelection are invalid",
1951 : this));
1952 0 : return;
1953 : }
1954 0 : useCaret = true;
1955 : }
1956 :
1957 0 : if (!mLastFocusedWindow) {
1958 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1959 : ("0x%p SetCursorPosition(), FAILED, due to no focused "
1960 : "window",
1961 : this));
1962 0 : return;
1963 : }
1964 :
1965 0 : if (MOZ_UNLIKELY(!aContext)) {
1966 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1967 : ("0x%p SetCursorPosition(), FAILED, due to no context",
1968 : this));
1969 0 : return;
1970 : }
1971 :
1972 : WidgetQueryContentEvent charRect(true,
1973 : useCaret ? eQueryCaretRect :
1974 : eQueryTextRect,
1975 0 : mLastFocusedWindow);
1976 0 : if (useCaret) {
1977 0 : charRect.InitForQueryCaretRect(mSelection.mOffset);
1978 : } else {
1979 0 : if (mSelection.mWritingMode.IsVertical()) {
1980 : // For preventing the candidate window to overlap the target
1981 : // clause, we should set fake (typically, very tall) caret rect.
1982 0 : uint32_t length = mCompositionTargetRange.mLength ?
1983 0 : mCompositionTargetRange.mLength : 1;
1984 0 : charRect.InitForQueryTextRect(mCompositionTargetRange.mOffset,
1985 0 : length);
1986 : } else {
1987 0 : charRect.InitForQueryTextRect(mCompositionTargetRange.mOffset, 1);
1988 : }
1989 : }
1990 0 : InitEvent(charRect);
1991 : nsEventStatus status;
1992 0 : mLastFocusedWindow->DispatchEvent(&charRect, status);
1993 0 : if (!charRect.mSucceeded) {
1994 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
1995 : ("0x%p SetCursorPosition(), FAILED, %s was failed",
1996 : this, useCaret ? "eQueryCaretRect" : "eQueryTextRect"));
1997 0 : return;
1998 : }
1999 :
2000 : nsWindow* rootWindow =
2001 0 : static_cast<nsWindow*>(mLastFocusedWindow->GetTopLevelWidget());
2002 :
2003 : // Get the position of the rootWindow in screen.
2004 0 : LayoutDeviceIntPoint root = rootWindow->WidgetToScreenOffset();
2005 :
2006 : // Get the position of IM context owner window in screen.
2007 0 : LayoutDeviceIntPoint owner = mOwnerWindow->WidgetToScreenOffset();
2008 :
2009 : // Compute the caret position in the IM owner window.
2010 0 : LayoutDeviceIntRect rect = charRect.mReply.mRect + root - owner;
2011 0 : rect.width = 0;
2012 0 : GdkRectangle area = rootWindow->DevicePixelsToGdkRectRoundOut(rect);
2013 :
2014 0 : gtk_im_context_set_cursor_location(aContext, &area);
2015 : }
2016 :
2017 : nsresult
2018 0 : IMContextWrapper::GetCurrentParagraph(nsAString& aText,
2019 : uint32_t& aCursorPos)
2020 : {
2021 0 : MOZ_LOG(gGtkIMLog, LogLevel::Info,
2022 : ("0x%p GetCurrentParagraph(), mCompositionState=%s",
2023 : this, GetCompositionStateName()));
2024 :
2025 0 : if (!mLastFocusedWindow) {
2026 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
2027 : ("0x%p GetCurrentParagraph(), FAILED, there are no "
2028 : "focused window in this module",
2029 : this));
2030 0 : return NS_ERROR_NULL_POINTER;
2031 : }
2032 :
2033 : nsEventStatus status;
2034 :
2035 0 : uint32_t selOffset = mCompositionStart;
2036 0 : uint32_t selLength = mSelectedStringRemovedByComposition.Length();
2037 :
2038 : // If focused editor doesn't have composition string, we should use
2039 : // current selection.
2040 0 : if (!EditorHasCompositionString()) {
2041 : // Query cursor position & selection
2042 0 : if (NS_WARN_IF(!EnsureToCacheSelection())) {
2043 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
2044 : ("0x%p GetCurrentParagraph(), FAILED, due to no "
2045 : "valid selection information",
2046 : this));
2047 0 : return NS_ERROR_FAILURE;
2048 : }
2049 :
2050 0 : selOffset = mSelection.mOffset;
2051 0 : selLength = mSelection.Length();
2052 : }
2053 :
2054 0 : MOZ_LOG(gGtkIMLog, LogLevel::Debug,
2055 : ("0x%p GetCurrentParagraph(), selOffset=%u, selLength=%u",
2056 : this, selOffset, selLength));
2057 :
2058 : // XXX nsString::Find and nsString::RFind take int32_t for offset, so,
2059 : // we cannot support this request when the current offset is larger
2060 : // than INT32_MAX.
2061 0 : if (selOffset > INT32_MAX || selLength > INT32_MAX ||
2062 0 : selOffset + selLength > INT32_MAX) {
2063 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
2064 : ("0x%p GetCurrentParagraph(), FAILED, The selection is "
2065 : "out of range",
2066 : this));
2067 0 : return NS_ERROR_FAILURE;
2068 : }
2069 :
2070 : // Get all text contents of the focused editor
2071 : WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
2072 0 : mLastFocusedWindow);
2073 0 : queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
2074 0 : mLastFocusedWindow->DispatchEvent(&queryTextContentEvent, status);
2075 0 : NS_ENSURE_TRUE(queryTextContentEvent.mSucceeded, NS_ERROR_FAILURE);
2076 :
2077 0 : nsAutoString textContent(queryTextContentEvent.mReply.mString);
2078 0 : if (selOffset + selLength > textContent.Length()) {
2079 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
2080 : ("0x%p GetCurrentParagraph(), FAILED, The selection is "
2081 : "invalid, textContent.Length()=%u",
2082 : this, textContent.Length()));
2083 0 : return NS_ERROR_FAILURE;
2084 : }
2085 :
2086 : // Remove composing string and restore the selected string because
2087 : // GtkEntry doesn't remove selected string until committing, however,
2088 : // our editor does it. We should emulate the behavior for IME.
2089 0 : if (EditorHasCompositionString() &&
2090 0 : mDispatchedCompositionString != mSelectedStringRemovedByComposition) {
2091 0 : textContent.Replace(mCompositionStart,
2092 : mDispatchedCompositionString.Length(),
2093 0 : mSelectedStringRemovedByComposition);
2094 : }
2095 :
2096 : // Get only the focused paragraph, by looking for newlines
2097 0 : int32_t parStart = (selOffset == 0) ? 0 :
2098 0 : textContent.RFind("\n", false, selOffset - 1, -1) + 1;
2099 0 : int32_t parEnd = textContent.Find("\n", false, selOffset + selLength, -1);
2100 0 : if (parEnd < 0) {
2101 0 : parEnd = textContent.Length();
2102 : }
2103 0 : aText = nsDependentSubstring(textContent, parStart, parEnd - parStart);
2104 0 : aCursorPos = selOffset - uint32_t(parStart);
2105 :
2106 0 : MOZ_LOG(gGtkIMLog, LogLevel::Debug,
2107 : ("0x%p GetCurrentParagraph(), succeeded, aText=%s, "
2108 : "aText.Length()=%u, aCursorPos=%u",
2109 : this, NS_ConvertUTF16toUTF8(aText).get(),
2110 : aText.Length(), aCursorPos));
2111 :
2112 0 : return NS_OK;
2113 : }
2114 :
2115 : nsresult
2116 0 : IMContextWrapper::DeleteText(GtkIMContext* aContext,
2117 : int32_t aOffset,
2118 : uint32_t aNChars)
2119 : {
2120 0 : MOZ_LOG(gGtkIMLog, LogLevel::Info,
2121 : ("0x%p DeleteText(aContext=0x%p, aOffset=%d, aNChars=%u), "
2122 : "mCompositionState=%s",
2123 : this, aContext, aOffset, aNChars, GetCompositionStateName()));
2124 :
2125 0 : if (!mLastFocusedWindow) {
2126 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
2127 : ("0x%p DeleteText(), FAILED, there are no focused window "
2128 : "in this module",
2129 : this));
2130 0 : return NS_ERROR_NULL_POINTER;
2131 : }
2132 :
2133 0 : if (!aNChars) {
2134 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
2135 : ("0x%p DeleteText(), FAILED, aNChars must not be zero",
2136 : this));
2137 0 : return NS_ERROR_INVALID_ARG;
2138 : }
2139 :
2140 0 : RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
2141 : nsEventStatus status;
2142 :
2143 : // First, we should cancel current composition because editor cannot
2144 : // handle changing selection and deleting text.
2145 : uint32_t selOffset;
2146 0 : bool wasComposing = IsComposing();
2147 0 : bool editorHadCompositionString = EditorHasCompositionString();
2148 0 : if (wasComposing) {
2149 0 : selOffset = mCompositionStart;
2150 0 : if (!DispatchCompositionCommitEvent(aContext,
2151 : &mSelectedStringRemovedByComposition)) {
2152 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
2153 : ("0x%p DeleteText(), FAILED, quitting from DeletText",
2154 : this));
2155 0 : return NS_ERROR_FAILURE;
2156 : }
2157 : } else {
2158 0 : if (NS_WARN_IF(!EnsureToCacheSelection())) {
2159 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
2160 : ("0x%p DeleteText(), FAILED, due to no valid selection "
2161 : "information",
2162 : this));
2163 0 : return NS_ERROR_FAILURE;
2164 : }
2165 0 : selOffset = mSelection.mOffset;
2166 : }
2167 :
2168 : // Get all text contents of the focused editor
2169 : WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
2170 0 : mLastFocusedWindow);
2171 0 : queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
2172 0 : mLastFocusedWindow->DispatchEvent(&queryTextContentEvent, status);
2173 0 : NS_ENSURE_TRUE(queryTextContentEvent.mSucceeded, NS_ERROR_FAILURE);
2174 0 : if (queryTextContentEvent.mReply.mString.IsEmpty()) {
2175 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
2176 : ("0x%p DeleteText(), FAILED, there is no contents",
2177 : this));
2178 0 : return NS_ERROR_FAILURE;
2179 : }
2180 :
2181 : NS_ConvertUTF16toUTF8 utf8Str(
2182 0 : nsDependentSubstring(queryTextContentEvent.mReply.mString,
2183 0 : 0, selOffset));
2184 : glong offsetInUTF8Characters =
2185 0 : g_utf8_strlen(utf8Str.get(), utf8Str.Length()) + aOffset;
2186 0 : if (offsetInUTF8Characters < 0) {
2187 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
2188 : ("0x%p DeleteText(), FAILED, aOffset is too small for "
2189 : "current cursor pos (computed offset: %ld)",
2190 : this, offsetInUTF8Characters));
2191 0 : return NS_ERROR_FAILURE;
2192 : }
2193 :
2194 : AppendUTF16toUTF8(
2195 0 : nsDependentSubstring(queryTextContentEvent.mReply.mString, selOffset),
2196 0 : utf8Str);
2197 : glong countOfCharactersInUTF8 =
2198 0 : g_utf8_strlen(utf8Str.get(), utf8Str.Length());
2199 : glong endInUTF8Characters =
2200 0 : offsetInUTF8Characters + aNChars;
2201 0 : if (countOfCharactersInUTF8 < endInUTF8Characters) {
2202 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
2203 : ("0x%p DeleteText(), FAILED, aNChars is too large for "
2204 : "current contents (content length: %ld, computed end offset: %ld)",
2205 : this, countOfCharactersInUTF8, endInUTF8Characters));
2206 0 : return NS_ERROR_FAILURE;
2207 : }
2208 :
2209 : gchar* charAtOffset =
2210 0 : g_utf8_offset_to_pointer(utf8Str.get(), offsetInUTF8Characters);
2211 : gchar* charAtEnd =
2212 0 : g_utf8_offset_to_pointer(utf8Str.get(), endInUTF8Characters);
2213 :
2214 : // Set selection to delete
2215 : WidgetSelectionEvent selectionEvent(true, eSetSelection,
2216 0 : mLastFocusedWindow);
2217 :
2218 : nsDependentCSubstring utf8StrBeforeOffset(utf8Str, 0,
2219 0 : charAtOffset - utf8Str.get());
2220 0 : selectionEvent.mOffset =
2221 0 : NS_ConvertUTF8toUTF16(utf8StrBeforeOffset).Length();
2222 :
2223 : nsDependentCSubstring utf8DeletingStr(utf8Str,
2224 : utf8StrBeforeOffset.Length(),
2225 0 : charAtEnd - charAtOffset);
2226 0 : selectionEvent.mLength =
2227 0 : NS_ConvertUTF8toUTF16(utf8DeletingStr).Length();
2228 :
2229 0 : selectionEvent.mReversed = false;
2230 0 : selectionEvent.mExpandToClusterBoundary = false;
2231 0 : lastFocusedWindow->DispatchEvent(&selectionEvent, status);
2232 :
2233 0 : if (!selectionEvent.mSucceeded ||
2234 0 : lastFocusedWindow != mLastFocusedWindow ||
2235 0 : lastFocusedWindow->Destroyed()) {
2236 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
2237 : ("0x%p DeleteText(), FAILED, setting selection caused "
2238 : "focus change or window destroyed",
2239 : this));
2240 0 : return NS_ERROR_FAILURE;
2241 : }
2242 :
2243 : // Delete the selection
2244 : WidgetContentCommandEvent contentCommandEvent(true, eContentCommandDelete,
2245 0 : mLastFocusedWindow);
2246 0 : mLastFocusedWindow->DispatchEvent(&contentCommandEvent, status);
2247 :
2248 0 : if (!contentCommandEvent.mSucceeded ||
2249 0 : lastFocusedWindow != mLastFocusedWindow ||
2250 0 : lastFocusedWindow->Destroyed()) {
2251 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
2252 : ("0x%p DeleteText(), FAILED, deleting the selection caused "
2253 : "focus change or window destroyed",
2254 : this));
2255 0 : return NS_ERROR_FAILURE;
2256 : }
2257 :
2258 0 : if (!wasComposing) {
2259 0 : return NS_OK;
2260 : }
2261 :
2262 : // Restore the composition at new caret position.
2263 0 : if (!DispatchCompositionStart(aContext)) {
2264 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
2265 : ("0x%p DeleteText(), FAILED, resterting composition start",
2266 : this));
2267 0 : return NS_ERROR_FAILURE;
2268 : }
2269 :
2270 0 : if (!editorHadCompositionString) {
2271 0 : return NS_OK;
2272 : }
2273 :
2274 0 : nsAutoString compositionString;
2275 0 : GetCompositionString(aContext, compositionString);
2276 0 : if (!DispatchCompositionChangeEvent(aContext, compositionString)) {
2277 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
2278 : ("0x%p DeleteText(), FAILED, restoring composition string",
2279 : this));
2280 0 : return NS_ERROR_FAILURE;
2281 : }
2282 :
2283 0 : return NS_OK;
2284 : }
2285 :
2286 : void
2287 0 : IMContextWrapper::InitEvent(WidgetGUIEvent& aEvent)
2288 : {
2289 0 : aEvent.mTime = PR_Now() / 1000;
2290 0 : }
2291 :
2292 : bool
2293 0 : IMContextWrapper::EnsureToCacheSelection(nsAString* aSelectedString)
2294 : {
2295 0 : if (aSelectedString) {
2296 0 : aSelectedString->Truncate();
2297 : }
2298 :
2299 0 : if (mSelection.IsValid()) {
2300 0 : if (aSelectedString) {
2301 0 : *aSelectedString = mSelection.mString;
2302 : }
2303 0 : return true;
2304 : }
2305 :
2306 0 : if (NS_WARN_IF(!mLastFocusedWindow)) {
2307 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
2308 : ("0x%p EnsureToCacheSelection(), FAILED, due to "
2309 : "no focused window",
2310 : this));
2311 0 : return false;
2312 : }
2313 :
2314 : nsEventStatus status;
2315 : WidgetQueryContentEvent selection(true, eQuerySelectedText,
2316 0 : mLastFocusedWindow);
2317 0 : InitEvent(selection);
2318 0 : mLastFocusedWindow->DispatchEvent(&selection, status);
2319 0 : if (NS_WARN_IF(!selection.mSucceeded)) {
2320 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
2321 : ("0x%p EnsureToCacheSelection(), FAILED, due to "
2322 : "failure of query selection event",
2323 : this));
2324 0 : return false;
2325 : }
2326 :
2327 0 : mSelection.Assign(selection);
2328 0 : if (!mSelection.IsValid()) {
2329 0 : MOZ_LOG(gGtkIMLog, LogLevel::Error,
2330 : ("0x%p EnsureToCacheSelection(), FAILED, due to "
2331 : "failure of query selection event (invalid result)",
2332 : this));
2333 0 : return false;
2334 : }
2335 :
2336 0 : if (!mSelection.Collapsed() && aSelectedString) {
2337 0 : aSelectedString->Assign(selection.mReply.mString);
2338 : }
2339 :
2340 0 : MOZ_LOG(gGtkIMLog, LogLevel::Debug,
2341 : ("0x%p EnsureToCacheSelection(), Succeeded, mSelection="
2342 : "{ mOffset=%u, Length()=%u, mWritingMode=%s }",
2343 : this, mSelection.mOffset, mSelection.Length(),
2344 : GetWritingModeName(mSelection.mWritingMode).get()));
2345 0 : return true;
2346 : }
2347 :
2348 : /******************************************************************************
2349 : * IMContextWrapper::Selection
2350 : ******************************************************************************/
2351 :
2352 : void
2353 0 : IMContextWrapper::Selection::Assign(const IMENotification& aIMENotification)
2354 : {
2355 0 : MOZ_ASSERT(aIMENotification.mMessage == NOTIFY_IME_OF_SELECTION_CHANGE);
2356 0 : mString = aIMENotification.mSelectionChangeData.String();
2357 0 : mOffset = aIMENotification.mSelectionChangeData.mOffset;
2358 0 : mWritingMode = aIMENotification.mSelectionChangeData.GetWritingMode();
2359 0 : }
2360 :
2361 : void
2362 0 : IMContextWrapper::Selection::Assign(const WidgetQueryContentEvent& aEvent)
2363 : {
2364 0 : MOZ_ASSERT(aEvent.mMessage == eQuerySelectedText);
2365 0 : MOZ_ASSERT(aEvent.mSucceeded);
2366 0 : mString = aEvent.mReply.mString.Length();
2367 0 : mOffset = aEvent.mReply.mOffset;
2368 0 : mWritingMode = aEvent.GetWritingMode();
2369 0 : }
2370 :
2371 : } // namespace widget
2372 : } // namespace mozilla
|