LCOV - code coverage report
Current view: top level - widget/gtk - IMContextWrapper.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 41 947 4.3 %
Date: 2017-07-14 16:53:18 Functions: 5 66 7.6 %
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.13