1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #ifndef IMMHandler_h_
7 #define IMMHandler_h_
8 
9 #include "nscore.h"
10 #include <windows.h>
11 #include "nsCOMPtr.h"
12 #include "nsString.h"
13 #include "nsTArray.h"
14 #include "nsIWidget.h"
15 #include "mozilla/EventForwards.h"
16 #include "mozilla/TextEventDispatcher.h"
17 #include "nsRect.h"
18 #include "WritingModes.h"
19 #include "npapi.h"
20 
21 class nsWindow;
22 class nsWindowBase;
23 
24 namespace mozilla {
25 namespace widget {
26 
27 struct MSGResult;
28 
29 class IMEContext final
30 {
31 public:
IMEContext()32   IMEContext()
33     : mWnd(nullptr)
34     , mIMC(nullptr)
35   {
36   }
37 
38   explicit IMEContext(HWND aWnd);
39   explicit IMEContext(nsWindowBase* aWindowBase);
40 
~IMEContext()41   ~IMEContext()
42   {
43     Clear();
44   }
45 
get()46   HIMC get() const
47   {
48     return mIMC;
49   }
50 
51   void Init(HWND aWnd);
52   void Init(nsWindowBase* aWindowBase);
53   void Clear();
54 
IsValid()55   bool IsValid() const
56   {
57     return !!mIMC;
58   }
59 
SetOpenState(bool aOpen)60   void SetOpenState(bool aOpen) const
61   {
62     if (!mIMC) {
63       return;
64     }
65     ::ImmSetOpenStatus(mIMC, aOpen);
66   }
67 
GetOpenState()68   bool GetOpenState() const
69   {
70     if (!mIMC) {
71       return false;
72     }
73     return (::ImmGetOpenStatus(mIMC) != FALSE);
74   }
75 
AssociateDefaultContext()76   bool AssociateDefaultContext()
77   {
78     // We assume that there is only default IMC, no new IMC has been created.
79     if (mIMC) {
80       return false;
81     }
82     if (!::ImmAssociateContextEx(mWnd, nullptr, IACE_DEFAULT)) {
83       return false;
84     }
85     mIMC = ::ImmGetContext(mWnd);
86     return (mIMC != nullptr);
87   }
88 
Disassociate()89   bool Disassociate()
90   {
91     if (!mIMC) {
92       return false;
93     }
94     if (!::ImmAssociateContextEx(mWnd, nullptr, 0)) {
95       return false;
96     }
97     ::ImmReleaseContext(mWnd, mIMC);
98     mIMC = nullptr;
99     return true;
100   }
101 
102 protected:
IMEContext(const IMEContext & aOther)103   IMEContext(const IMEContext& aOther)
104   {
105     MOZ_CRASH("Don't copy IMEContext");
106   }
107 
108   HWND mWnd;
109   HIMC mIMC;
110 };
111 
112 class IMMHandler final
113 {
114 public:
115   static void Initialize();
116   static void Terminate();
117 
118   // If Process*() returns true, the caller shouldn't do anything anymore.
119   static bool ProcessMessage(nsWindow* aWindow, UINT msg,
120                              WPARAM& wParam, LPARAM& lParam,
121                              MSGResult& aResult);
IsComposing()122   static bool IsComposing()
123   {
124     return IsComposingOnOurEditor();
125   }
IsComposingOn(nsWindow * aWindow)126   static bool IsComposingOn(nsWindow* aWindow)
127   {
128     return IsComposing() && IsComposingWindow(aWindow);
129   }
130 
131 #ifdef DEBUG
132   /**
133    * IsIMEAvailable() returns TRUE when current keyboard layout has IME.
134    * Otherwise, FALSE.
135    */
IsIMEAvailable()136   static bool IsIMEAvailable() { return !!::ImmIsIME(::GetKeyboardLayout(0)); }
137 #endif
138 
139   // If aForce is TRUE, these methods doesn't check whether we have composition
140   // or not.  If you don't set it to TRUE, these method doesn't commit/cancel
141   // the composition on uexpected window.
142   static void CommitComposition(nsWindow* aWindow, bool aForce = false);
143   static void CancelComposition(nsWindow* aWindow, bool aForce = false);
144   static void OnFocusChange(bool aFocus, nsWindow* aWindow);
145   static void OnUpdateComposition(nsWindow* aWindow);
146   static void OnSelectionChange(nsWindow* aWindow,
147                                 const IMENotification& aIMENotification,
148                                 bool aIsIMMActive);
149 
150   static nsIMEUpdatePreference GetIMEUpdatePreference();
151 
152   // Returns NS_SUCCESS_EVENT_CONSUMED if the mouse button event is consumed by
153   // IME.  Otherwise, NS_OK.
154   static nsresult OnMouseButtonEvent(nsWindow* aWindow,
155                                      const IMENotification& aIMENotification);
156   static void SetCandidateWindow(nsWindow* aWindow, CANDIDATEFORM* aForm);
157   static void DefaultProcOfPluginEvent(nsWindow* aWindow,
158                                        const NPEvent* aEvent);
159 
160 protected:
161   static void EnsureHandlerInstance();
162 
163   static bool IsComposingOnOurEditor();
164   static bool IsComposingOnPlugin();
165   static bool IsComposingWindow(nsWindow* aWindow);
166 
167   static bool IsJapanist2003Active();
168   static bool IsGoogleJapaneseInputActive();
169 
170   static bool ShouldDrawCompositionStringOurselves();
171   static bool IsVerticalWritingSupported();
172   // aWindow can be nullptr if it's called without receiving WM_INPUTLANGCHANGE.
173   static void InitKeyboardLayout(nsWindow* aWindow, HKL aKeyboardLayout);
174   static UINT GetKeyboardCodePage();
175 
176   /**
177    * Checks whether the window is top level window of the composing window.
178    * In this method, the top level window means in all windows, not only in all
179    * OUR windows.  I.e., if the aWindow is embedded, this always returns FALSE.
180    */
181   static bool IsTopLevelWindowOfComposition(nsWindow* aWindow);
182 
183   static bool ProcessInputLangChangeMessage(nsWindow* aWindow,
184                                               WPARAM wParam,
185                                               LPARAM lParam,
186                                               MSGResult& aResult);
187   static bool ProcessMessageForPlugin(nsWindow* aWindow, UINT msg,
188                                         WPARAM &wParam, LPARAM &lParam,
189                                         bool &aRet, MSGResult& aResult);
190 
191   IMMHandler();
192   ~IMMHandler();
193 
194   // On*() methods return true if the caller of message handler shouldn't do
195   // anything anymore.  Otherwise, false.
196   static bool OnKeyDownEvent(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
197                              MSGResult& aResult);
198 
199   bool OnIMEStartComposition(nsWindow* aWindow, MSGResult& aResult);
200   void OnIMEStartCompositionOnPlugin(nsWindow* aWindow,
201                                      WPARAM wParam, LPARAM lParam);
202   bool OnIMEComposition(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
203                         MSGResult& aResult);
204   void OnIMECompositionOnPlugin(nsWindow* aWindow, WPARAM wParam,
205                                 LPARAM lParam);
206   bool OnIMEEndComposition(nsWindow* aWindow, MSGResult& aResult);
207   void OnIMEEndCompositionOnPlugin(nsWindow* aWindow, WPARAM wParam,
208                                    LPARAM lParam);
209   bool OnIMERequest(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
210                     MSGResult& aResult);
211   bool OnIMECharOnPlugin(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
212                          MSGResult& aResult);
213   bool OnChar(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
214               MSGResult& aResult);
215   bool OnCharOnPlugin(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
216                       MSGResult& aResult);
217   void OnInputLangChange(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
218                          MSGResult& aResult);
219 
220   // These message handlers don't use instance members, we should not create
221   // the instance by the messages.  So, they should be static.
222   static bool OnIMEChar(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
223                         MSGResult& aResult);
224   static bool OnIMESetContext(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
225                               MSGResult& aResult);
226   static bool OnIMESetContextOnPlugin(nsWindow* aWindow,
227                                       WPARAM wParam, LPARAM lParam,
228                                       MSGResult& aResult);
229   static bool OnIMECompositionFull(nsWindow* aWindow, MSGResult& aResult);
230   static bool OnIMENotify(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
231                           MSGResult& aResult);
232   static bool OnIMESelect(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
233                           MSGResult& aResult);
234 
235   // The result of Handle* method mean "Processed" when it's TRUE.
236   void HandleStartComposition(nsWindow* aWindow,
237                               const IMEContext& aContext);
238   bool HandleComposition(nsWindow* aWindow,
239                          const IMEContext& aContext,
240                          LPARAM lParam);
241   // If aCommitString is null, this commits composition with the latest
242   // dispatched data.  Otherwise, commits composition with the value.
243   void HandleEndComposition(nsWindow* aWindow,
244                             const nsAString* aCommitString = nullptr);
245   bool HandleReconvert(nsWindow* aWindow, LPARAM lParam, LRESULT *oResult);
246   bool HandleQueryCharPosition(nsWindow* aWindow, LPARAM lParam,
247                                  LRESULT *oResult);
248   bool HandleDocumentFeed(nsWindow* aWindow, LPARAM lParam, LRESULT *oResult);
249 
250   /**
251    *  When a window's IME context is activating but we have composition on
252    *  another window, we should commit our composition because IME context is
253    *  shared by all our windows (including plug-ins).
254    *  @param aWindow is a new activated window.
255    *  If aWindow is our composing window, this method does nothing.
256    *  Otherwise, this commits the composition on the previous window.
257    *  If this method did commit a composition, this returns TRUE.
258    */
259   bool CommitCompositionOnPreviousWindow(nsWindow* aWindow);
260 
261   /**
262    *  ResolveIMECaretPos
263    *  Convert the caret rect of a composition event to another widget's
264    *  coordinate system.
265    *
266    *  @param aReferenceWidget The origin widget of aCursorRect.
267    *                          Typically, this is mReferenceWidget of the
268    *                          composing events. If the aCursorRect is in screen
269    *                          coordinates, set nullptr.
270    *  @param aCursorRect      The cursor rect.
271    *  @param aNewOriginWidget aOutRect will be in this widget's coordinates. If
272    *                          this is nullptr, aOutRect will be in screen
273    *                          coordinates.
274    *  @param aOutRect         The converted cursor rect.
275    */
276   void ResolveIMECaretPos(nsIWidget* aReferenceWidget,
277                           mozilla::LayoutDeviceIntRect& aCursorRect,
278                           nsIWidget* aNewOriginWidget,
279                           mozilla::LayoutDeviceIntRect& aOutRect);
280 
281   bool ConvertToANSIString(const nsAFlatString& aStr,
282                              UINT aCodePage,
283                              nsACString& aANSIStr);
284 
285   bool SetIMERelatedWindowsPos(nsWindow* aWindow,
286                                const IMEContext& aContext);
287   void SetIMERelatedWindowsPosOnPlugin(nsWindow* aWindow,
288                                        const IMEContext& aContext);
289   /**
290    * GetCharacterRectOfSelectedTextAt() returns character rect of the offset
291    * from the selection start or the start of composition string if there is
292    * a composition.
293    *
294    * @param aWindow         The window which has focus.
295    * @param aOffset         Offset from the selection start or the start of
296    *                        composition string when there is a composition.
297    *                        This must be in the selection range or
298    *                        the composition string.
299    * @param aCharRect       The result.
300    * @param aWritingMode    The writing mode of current selection.  When this
301    *                        is nullptr, this assumes that the selection is in
302    *                        horizontal writing mode.
303    * @return                true if this succeeded to retrieve the rect.
304    *                        Otherwise, false.
305    */
306   bool GetCharacterRectOfSelectedTextAt(
307          nsWindow* aWindow,
308          uint32_t aOffset,
309          mozilla::LayoutDeviceIntRect& aCharRect,
310          mozilla::WritingMode* aWritingMode = nullptr);
311   /**
312    * GetCaretRect() returns caret rect at current selection start.
313    *
314    * @param aWindow         The window which has focus.
315    * @param aCaretRect      The result.
316    * @param aWritingMode    The writing mode of current selection.  When this
317    *                        is nullptr, this assumes that the selection is in
318    *                        horizontal writing mode.
319    * @return                true if this succeeded to retrieve the rect.
320    *                        Otherwise, false.
321    */
322   bool GetCaretRect(nsWindow* aWindow,
323                     mozilla::LayoutDeviceIntRect& aCaretRect,
324                     mozilla::WritingMode* aWritingMode = nullptr);
325   void GetCompositionString(const IMEContext& aContext,
326                             DWORD aIndex,
327                             nsAString& aCompositionString) const;
328 
329   /**
330    * AdjustCompositionFont() makes IME vertical writing mode if it's supported.
331    * If aForceUpdate is true, it will update composition font even if writing
332    * mode isn't being changed.
333    */
334   void AdjustCompositionFont(nsWindow* aWindow,
335                              const IMEContext& aContext,
336                              const mozilla::WritingMode& aWritingMode,
337                              bool aForceUpdate = false);
338 
339   /**
340    * MaybeAdjustCompositionFont() calls AdjustCompositionFont() when the
341    * locale of active IME is CJK.  Note that this creates an instance even
342    * when there is no composition but the locale is CJK.
343    */
344   static void MaybeAdjustCompositionFont(
345                 nsWindow* aWindow,
346                 const mozilla::WritingMode& aWritingMode,
347                 bool aForceUpdate = false);
348 
349   /**
350    *  Get the current target clause of composition string.
351    *  If there are one or more characters whose attribute is ATTR_TARGET_*,
352    *  this returns the first character's offset and its length.
353    *  Otherwise, e.g., the all characters are ATTR_INPUT, this returns
354    *  the composition string range because the all is the current target.
355    *
356    *  aLength can be null (default), but aOffset must not be null.
357    *
358    *  The aOffset value is offset in the contents.  So, when you need offset
359    *  in the composition string, you need to subtract mCompositionStart from it.
360    */
361   bool GetTargetClauseRange(uint32_t *aOffset, uint32_t *aLength = nullptr);
362 
363   /**
364    * DispatchEvent() dispatches aEvent if aWidget hasn't been destroyed yet.
365    */
366   static void DispatchEvent(nsWindow* aWindow, WidgetGUIEvent& aEvent);
367 
368   /**
369    * DispatchCompositionChangeEvent() dispatches eCompositionChange event
370    * with clause information (it'll be retrieved by CreateTextRangeArray()).
371    * I.e., this should be called only during composing.  If a composition is
372    * being committed, only HandleCompositionEnd() should be called.
373    *
374    * @param aWindow     The window which has the composition.
375    * @param aContext    Native IME context which has the composition.
376    */
377   void DispatchCompositionChangeEvent(nsWindow* aWindow,
378                                       const IMEContext& aContext);
379 
380   nsresult EnsureClauseArray(int32_t aCount);
381   nsresult EnsureAttributeArray(int32_t aCount);
382 
383   /**
384    * When WM_IME_CHAR is received and passed to DefWindowProc, we need to
385    * record the messages.  In other words, we should record the messages
386    * when we receive WM_IME_CHAR on windowless plug-in (if we have focus,
387    * we always eat them).  When focus is moved from a windowless plug-in to
388    * our window during composition, WM_IME_CHAR messages were received when
389    * the plug-in has focus.  However, WM_CHAR messages are received after the
390    * plug-in lost focus.  So, we need to ignore the WM_CHAR messages because
391    * they make unexpected text input events on us.
392    */
393   nsTArray<MSG> mPassedIMEChar;
394 
IsIMECharRecordsEmpty()395   bool IsIMECharRecordsEmpty()
396   {
397     return mPassedIMEChar.IsEmpty();
398   }
ResetIMECharRecords()399   void ResetIMECharRecords()
400   {
401     mPassedIMEChar.Clear();
402   }
DequeueIMECharRecords(WPARAM & wParam,LPARAM & lParam)403   void DequeueIMECharRecords(WPARAM &wParam, LPARAM &lParam)
404   {
405     MSG msg = mPassedIMEChar.ElementAt(0);
406     wParam = msg.wParam;
407     lParam = msg.lParam;
408     mPassedIMEChar.RemoveElementAt(0);
409   }
EnqueueIMECharRecords(WPARAM wParam,LPARAM lParam)410   void EnqueueIMECharRecords(WPARAM wParam, LPARAM lParam)
411   {
412     MSG msg;
413     msg.wParam = wParam;
414     msg.lParam = lParam;
415     mPassedIMEChar.AppendElement(msg);
416   }
417 
418   TextEventDispatcher* GetTextEventDispatcherFor(nsWindow* aWindow);
419 
420   nsWindow* mComposingWindow;
421   RefPtr<TextEventDispatcher> mDispatcher;
422   nsString  mCompositionString;
423   InfallibleTArray<uint32_t> mClauseArray;
424   InfallibleTArray<uint8_t> mAttributeArray;
425 
426   int32_t mCursorPosition;
427   uint32_t mCompositionStart;
428 
429   struct Selection
430   {
431     nsString mString;
432     uint32_t mOffset;
433     mozilla::WritingMode mWritingMode;
434     bool mIsValid;
435 
SelectionSelection436     Selection()
437       : mOffset(UINT32_MAX)
438       , mIsValid(false)
439     {
440     }
441 
ClearSelection442     void Clear()
443     {
444       mOffset = UINT32_MAX;
445       mIsValid = false;
446     }
LengthSelection447     uint32_t Length() const { return mString.Length(); }
CollapsedSelection448     bool Collapsed() const { return !Length(); }
449 
450     bool IsValid() const;
451     bool Update(const IMENotification& aIMENotification);
452     bool Init(nsWindow* aWindow);
453     bool EnsureValidSelection(nsWindow* aWindow);
454   private:
455     Selection(const Selection& aOther) = delete;
456     void operator =(const Selection& aOther) = delete;
457   };
458   // mSelection stores the latest selection data only when sHasFocus is true.
459   // Don't access mSelection directly.  You should use GetSelection() for
460   // getting proper state.
461   Selection mSelection;
462 
GetSelection()463   Selection& GetSelection()
464   {
465     // When IME has focus, mSelection is automatically updated by
466     // NOTIFY_IME_OF_SELECTION_CHANGE.
467     if (sHasFocus) {
468       return mSelection;
469     }
470     // Otherwise, i.e., While IME doesn't have focus, we cannot observe
471     // selection changes.  So, in such case, we need to query selection
472     // when it's necessary.
473     static Selection sTempSelection;
474     sTempSelection.Clear();
475     return sTempSelection;
476   }
477 
478   bool mIsComposing;
479   bool mIsComposingOnPlugin;
480   bool mNativeCaretIsCreated;
481 
482   static mozilla::WritingMode sWritingModeOfCompositionFont;
483   static nsString sIMEName;
484   static UINT sCodePage;
485   static DWORD sIMEProperty;
486   static DWORD sIMEUIProperty;
487   static bool sAssumeVerticalWritingModeNotSupported;
488   static bool sHasFocus;
489   static bool sNativeCaretIsCreatedForPlugin;
490 };
491 
492 } // namespace widget
493 } // namespace mozilla
494 
495 #endif // IMMHandler_h_
496