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