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 TSFTextStore_h_
7 #define TSFTextStore_h_
8 
9 #include "nsCOMPtr.h"
10 #include "nsIWidget.h"
11 #include "nsString.h"
12 #include "nsWindowBase.h"
13 
14 #include "WinUtils.h"
15 #include "WritingModes.h"
16 
17 #include "mozilla/Attributes.h"
18 #include "mozilla/RefPtr.h"
19 #include "mozilla/StaticPtr.h"
20 #include "mozilla/TextEventDispatcher.h"
21 #include "mozilla/TextRange.h"
22 #include "mozilla/WindowsVersion.h"
23 
24 #include <msctf.h>
25 #include <textstor.h>
26 
27 // GUID_PROP_INPUTSCOPE is declared in inputscope.h using INIT_GUID.
28 // With initguid.h, we get its instance instead of extern declaration.
29 #ifdef INPUTSCOPE_INIT_GUID
30 #include <initguid.h>
31 #endif
32 #ifdef TEXTATTRS_INIT_GUID
33 #include <tsattrs.h>
34 #endif
35 #include <inputscope.h>
36 
37 // TSF InputScope, for earlier SDK 8
38 #define IS_SEARCH static_cast<InputScope>(50)
39 
40 struct ITfThreadMgr;
41 struct ITfDocumentMgr;
42 struct ITfDisplayAttributeMgr;
43 struct ITfCategoryMgr;
44 class nsWindow;
45 
46 namespace mozilla {
47 namespace widget {
48 
49 struct MSGResult;
50 
51 /*
52  * Text Services Framework text store
53  */
54 
55 class TSFTextStore final : public ITextStoreACP
56                          , public ITfContextOwnerCompositionSink
57                          , public ITfMouseTrackerACP
58 {
59 private:
60   typedef IMENotification::SelectionChangeDataBase SelectionChangeDataBase;
61   typedef IMENotification::SelectionChangeData SelectionChangeData;
62   typedef IMENotification::TextChangeDataBase TextChangeDataBase;
63   typedef IMENotification::TextChangeData TextChangeData;
64 
65 public: /*IUnknown*/
66   STDMETHODIMP          QueryInterface(REFIID, void**);
67 
68   NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(TSFTextStore)
69 
70 public: /*ITextStoreACP*/
71   STDMETHODIMP AdviseSink(REFIID, IUnknown*, DWORD);
72   STDMETHODIMP UnadviseSink(IUnknown*);
73   STDMETHODIMP RequestLock(DWORD, HRESULT*);
74   STDMETHODIMP GetStatus(TS_STATUS*);
75   STDMETHODIMP QueryInsert(LONG, LONG, ULONG, LONG*, LONG*);
76   STDMETHODIMP GetSelection(ULONG, ULONG, TS_SELECTION_ACP*, ULONG*);
77   STDMETHODIMP SetSelection(ULONG, const TS_SELECTION_ACP*);
78   STDMETHODIMP GetText(LONG, LONG, WCHAR*, ULONG, ULONG*, TS_RUNINFO*, ULONG,
79                        ULONG*, LONG*);
80   STDMETHODIMP SetText(DWORD, LONG, LONG, const WCHAR*, ULONG, TS_TEXTCHANGE*);
81   STDMETHODIMP GetFormattedText(LONG, LONG, IDataObject**);
82   STDMETHODIMP GetEmbedded(LONG, REFGUID, REFIID, IUnknown**);
83   STDMETHODIMP QueryInsertEmbedded(const GUID*, const FORMATETC*, BOOL*);
84   STDMETHODIMP InsertEmbedded(DWORD, LONG, LONG, IDataObject*, TS_TEXTCHANGE*);
85   STDMETHODIMP RequestSupportedAttrs(DWORD, ULONG, const TS_ATTRID*);
86   STDMETHODIMP RequestAttrsAtPosition(LONG, ULONG, const TS_ATTRID*, DWORD);
87   STDMETHODIMP RequestAttrsTransitioningAtPosition(LONG, ULONG,
88                                                    const TS_ATTRID*, DWORD);
89   STDMETHODIMP FindNextAttrTransition(LONG, LONG, ULONG, const TS_ATTRID*,
90                                       DWORD, LONG*, BOOL*, LONG*);
91   STDMETHODIMP RetrieveRequestedAttrs(ULONG, TS_ATTRVAL*, ULONG*);
92   STDMETHODIMP GetEndACP(LONG*);
93   STDMETHODIMP GetActiveView(TsViewCookie*);
94   STDMETHODIMP GetACPFromPoint(TsViewCookie, const POINT*, DWORD, LONG*);
95   STDMETHODIMP GetTextExt(TsViewCookie, LONG, LONG, RECT*, BOOL*);
96   STDMETHODIMP GetScreenExt(TsViewCookie, RECT*);
97   STDMETHODIMP GetWnd(TsViewCookie, HWND*);
98   STDMETHODIMP InsertTextAtSelection(DWORD, const WCHAR*, ULONG, LONG*, LONG*,
99                                      TS_TEXTCHANGE*);
100   STDMETHODIMP InsertEmbeddedAtSelection(DWORD, IDataObject*, LONG*, LONG*,
101                                          TS_TEXTCHANGE*);
102 
103 public: /*ITfContextOwnerCompositionSink*/
104   STDMETHODIMP OnStartComposition(ITfCompositionView*, BOOL*);
105   STDMETHODIMP OnUpdateComposition(ITfCompositionView*, ITfRange*);
106   STDMETHODIMP OnEndComposition(ITfCompositionView*);
107 
108 public: /*ITfMouseTrackerACP*/
109   STDMETHODIMP AdviseMouseSink(ITfRangeACP*, ITfMouseSink*, DWORD*);
110   STDMETHODIMP UnadviseMouseSink(DWORD);
111 
112 public:
113   static void     Initialize(void);
114   static void     Terminate(void);
115 
116   static bool     ProcessRawKeyMessage(const MSG& aMsg);
117   static void     ProcessMessage(nsWindowBase* aWindow, UINT aMessage,
118                                  WPARAM& aWParam, LPARAM& aLParam,
119                                  MSGResult& aResult);
120 
121   static void     SetIMEOpenState(bool);
122   static bool     GetIMEOpenState(void);
123 
CommitComposition(bool aDiscard)124   static void     CommitComposition(bool aDiscard)
125   {
126     NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called");
127     if (!sEnabledTextStore) {
128       return;
129     }
130     RefPtr<TSFTextStore> textStore(sEnabledTextStore);
131     textStore->CommitCompositionInternal(aDiscard);
132   }
133 
134   static void SetInputContext(nsWindowBase* aWidget,
135                               const InputContext& aContext,
136                               const InputContextAction& aAction);
137 
138   static nsresult OnFocusChange(bool aGotFocus,
139                                 nsWindowBase* aFocusedWidget,
140                                 const InputContext& aContext);
OnTextChange(const IMENotification & aIMENotification)141   static nsresult OnTextChange(const IMENotification& aIMENotification)
142   {
143     NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called");
144     if (!sEnabledTextStore) {
145       return NS_OK;
146     }
147     RefPtr<TSFTextStore> textStore(sEnabledTextStore);
148     return textStore->OnTextChangeInternal(aIMENotification);
149   }
150 
OnSelectionChange(const IMENotification & aIMENotification)151   static nsresult OnSelectionChange(const IMENotification& aIMENotification)
152   {
153     NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called");
154     if (!sEnabledTextStore) {
155       return NS_OK;
156     }
157     RefPtr<TSFTextStore> textStore(sEnabledTextStore);
158     return textStore->OnSelectionChangeInternal(aIMENotification);
159   }
160 
OnLayoutChange()161   static nsresult OnLayoutChange()
162   {
163     NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called");
164     if (!sEnabledTextStore) {
165       return NS_OK;
166     }
167     RefPtr<TSFTextStore> textStore(sEnabledTextStore);
168     return textStore->OnLayoutChangeInternal();
169   }
170 
OnUpdateComposition()171   static nsresult OnUpdateComposition()
172   {
173     NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called");
174     if (!sEnabledTextStore) {
175       return NS_OK;
176     }
177     RefPtr<TSFTextStore> textStore(sEnabledTextStore);
178     return textStore->OnUpdateCompositionInternal();
179   }
180 
OnMouseButtonEvent(const IMENotification & aIMENotification)181   static nsresult OnMouseButtonEvent(const IMENotification& aIMENotification)
182   {
183     NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called");
184     if (!sEnabledTextStore) {
185       return NS_OK;
186     }
187     RefPtr<TSFTextStore> textStore(sEnabledTextStore);
188     return textStore->OnMouseButtonEventInternal(aIMENotification);
189   }
190 
191   static nsIMEUpdatePreference GetIMEUpdatePreference();
192 
193   // Returns the address of the pointer so that the TSF automatic test can
194   // replace the system object with a custom implementation for testing.
195   // XXX TSF doesn't work now.  Should we remove it?
GetNativeData(uint32_t aDataType)196   static void* GetNativeData(uint32_t aDataType)
197   {
198     switch (aDataType) {
199       case NS_NATIVE_TSF_THREAD_MGR:
200         Initialize(); // Apply any previous changes
201         return static_cast<void*>(&sThreadMgr);
202       case NS_NATIVE_TSF_CATEGORY_MGR:
203         return static_cast<void*>(&sCategoryMgr);
204       case NS_NATIVE_TSF_DISPLAY_ATTR_MGR:
205         return static_cast<void*>(&sDisplayAttrMgr);
206       default:
207         return nullptr;
208     }
209   }
210 
GetMessagePump()211   static ITfMessagePump* GetMessagePump()
212   {
213     return sMessagePump;
214   }
215 
GetThreadManager()216   static void* GetThreadManager()
217   {
218     return static_cast<void*>(sThreadMgr);
219   }
220 
ThinksHavingFocus()221   static bool ThinksHavingFocus()
222   {
223     return (sEnabledTextStore && sEnabledTextStore->mContext);
224   }
225 
IsInTSFMode()226   static bool IsInTSFMode()
227   {
228     return sThreadMgr != nullptr;
229   }
230 
IsComposing()231   static bool IsComposing()
232   {
233     return (sEnabledTextStore && sEnabledTextStore->mComposition.IsComposing());
234   }
235 
IsComposingOn(nsWindowBase * aWidget)236   static bool IsComposingOn(nsWindowBase* aWidget)
237   {
238     return (IsComposing() && sEnabledTextStore->mWidget == aWidget);
239   }
240 
GetEnabledWindowBase()241   static nsWindowBase* GetEnabledWindowBase()
242   {
243     return sEnabledTextStore ? sEnabledTextStore->mWidget.get() : nullptr;
244   }
245 
246   /**
247    * Returns true if active keyboard layout is a legacy IMM-IME.
248    */
249   static bool IsIMM_IMEActive();
250 
251   /**
252    * Returns true if active TIP is MS-IME for Japanese.
253    */
254   static bool IsMSJapaneseIMEActive();
255 
256 #ifdef DEBUG
257   // Returns true when keyboard layout has IME (TIP).
258   static bool     CurrentKeyboardLayoutHasIME();
259 #endif // #ifdef DEBUG
260 
261 protected:
262   TSFTextStore();
263   ~TSFTextStore();
264 
265   static bool CreateAndSetFocus(nsWindowBase* aFocusedWidget,
266                                 const InputContext& aContext);
267   static void EnsureToDestroyAndReleaseEnabledTextStoreIf(
268                 RefPtr<TSFTextStore>& aTextStore);
269   static void MarkContextAsKeyboardDisabled(ITfContext* aContext);
270   static void MarkContextAsEmpty(ITfContext* aContext);
271 
272   bool     Init(nsWindowBase* aWidget, const InputContext& aContext);
273   void     Destroy();
274   void     ReleaseTSFObjects();
275 
IsReadLock(DWORD aLock)276   bool     IsReadLock(DWORD aLock) const
277   {
278     return (TS_LF_READ == (aLock & TS_LF_READ));
279   }
IsReadWriteLock(DWORD aLock)280   bool     IsReadWriteLock(DWORD aLock) const
281   {
282     return (TS_LF_READWRITE == (aLock & TS_LF_READWRITE));
283   }
IsReadLocked()284   bool     IsReadLocked() const { return IsReadLock(mLock); }
IsReadWriteLocked()285   bool     IsReadWriteLocked() const { return IsReadWriteLock(mLock); }
286 
287   // This is called immediately after a call of OnLockGranted() of mSink.
288   // Note that mLock isn't cleared yet when this is called.
289   void     DidLockGranted();
290 
291   bool     GetScreenExtInternal(RECT& aScreenExt);
292   // If aDispatchCompositionChangeEvent is true, this method will dispatch
293   // compositionchange event if this is called during IME composing.
294   // aDispatchCompositionChangeEvent should be true only when this is called
295   // from SetSelection.  Because otherwise, the compositionchange event should
296   // not be sent from here.
297   HRESULT  SetSelectionInternal(const TS_SELECTION_ACP*,
298                                 bool aDispatchCompositionChangeEvent = false);
299   bool     InsertTextAtSelectionInternal(const nsAString& aInsertStr,
300                                          TS_TEXTCHANGE* aTextChange);
301   void     CommitCompositionInternal(bool);
302   HRESULT  GetDisplayAttribute(ITfProperty* aProperty,
303                                ITfRange* aRange,
304                                TF_DISPLAYATTRIBUTE* aResult);
305   HRESULT  RestartCompositionIfNecessary(ITfRange* pRangeNew = nullptr);
306   HRESULT  RestartComposition(ITfCompositionView* aCompositionView,
307                               ITfRange* aNewRange);
308 
309   // Following methods record composing action(s) to mPendingActions.
310   // They will be flushed FlushPendingActions().
311   HRESULT  RecordCompositionStartAction(ITfCompositionView* aCompositionView,
312                                         ITfRange* aRange,
313                                         bool aPreserveSelection);
314   HRESULT  RecordCompositionStartAction(ITfCompositionView* aComposition,
315                                         LONG aStart,
316                                         LONG aLength,
317                                         bool aPreserveSelection);
318   HRESULT  RecordCompositionUpdateAction();
319   HRESULT  RecordCompositionEndAction();
320 
321   // DispatchEvent() dispatches the event and if it may not be handled
322   // synchronously, this makes the instance not notify TSF of pending
323   // notifications until next notification from content.
324   void     DispatchEvent(WidgetGUIEvent& aEvent);
325   void     OnLayoutInformationAvaliable();
326 
327   // FlushPendingActions() performs pending actions recorded in mPendingActions
328   // and clear it.
329   void     FlushPendingActions();
330   // MaybeFlushPendingNotifications() performs pending notifications to TSF.
331   void     MaybeFlushPendingNotifications();
332 
333   nsresult OnTextChangeInternal(const IMENotification& aIMENotification);
334   nsresult OnSelectionChangeInternal(const IMENotification& aIMENotification);
335   nsresult OnMouseButtonEventInternal(const IMENotification& aIMENotification);
336   nsresult OnLayoutChangeInternal();
337   nsresult OnUpdateCompositionInternal();
338 
339   // mPendingSelectionChangeData stores selection change data until notifying
340   // TSF of selection change.  If two or more selection changes occur, this
341   // stores the latest selection change data because only it is necessary.
342   SelectionChangeData mPendingSelectionChangeData;
343 
344   // mPendingTextChangeData stores one or more text change data until notifying
345   // TSF of text change.  If two or more text changes occur, this merges
346   // every text change data.
347   TextChangeData mPendingTextChangeData;
348 
349   void     NotifyTSFOfTextChange();
350   void     NotifyTSFOfSelectionChange();
351   bool     NotifyTSFOfLayoutChange();
352   void     NotifyTSFOfLayoutChangeAgain();
353 
354   HRESULT  HandleRequestAttrs(DWORD aFlags,
355                               ULONG aFilterCount,
356                               const TS_ATTRID* aFilterAttrs);
357   void     SetInputScope(const nsString& aHTMLInputType,
358                          const nsString& aHTMLInputInputmode);
359 
360   // Creates native caret over our caret.  This method only works on desktop
361   // application.  Otherwise, this does nothing.
362   void     CreateNativeCaret();
363   // Destroys native caret if there is.
364   void     MaybeDestroyNativeCaret();
365 
366   // Holds the pointer to our current win32 widget
367   RefPtr<nsWindowBase>       mWidget;
368   // mDispatcher is a helper class to dispatch composition events.
369   RefPtr<TextEventDispatcher> mDispatcher;
370   // Document manager for the currently focused editor
371   RefPtr<ITfDocumentMgr>     mDocumentMgr;
372   // Edit cookie associated with the current editing context
373   DWORD                        mEditCookie;
374   // Editing context at the bottom of mDocumentMgr's context stack
375   RefPtr<ITfContext>         mContext;
376   // Currently installed notification sink
377   RefPtr<ITextStoreACPSink>  mSink;
378   // TS_AS_* mask of what events to notify
379   DWORD                        mSinkMask;
380   // 0 if not locked, otherwise TS_LF_* indicating the current lock
381   DWORD                        mLock;
382   // 0 if no lock is queued, otherwise TS_LF_* indicating the queue lock
383   DWORD                        mLockQueued;
384 
385   uint32_t mHandlingKeyMessage;
OnStartToHandleKeyMessage()386   void OnStartToHandleKeyMessage() { ++mHandlingKeyMessage; }
OnEndHandlingKeyMessage()387   void OnEndHandlingKeyMessage()
388   {
389     MOZ_ASSERT(mHandlingKeyMessage);
390     if (--mHandlingKeyMessage) {
391       return;
392     }
393     // If TSFTextStore instance is destroyed during handling key message(s),
394     // release all TSF objects when all nested key messages have been handled.
395     if (mDestroyed) {
396       ReleaseTSFObjects();
397     }
398   }
399 
400   class Composition final
401   {
402   public:
403     // nullptr if no composition is active, otherwise the current composition
404     RefPtr<ITfCompositionView> mView;
405 
406     // Current copy of the active composition string. Only mString is
407     // changed during a InsertTextAtSelection call if we have a composition.
408     // mString acts as a buffer until OnUpdateComposition is called
409     // and mString is flushed to editor through eCompositionChange.
410     // This way all changes are updated in batches to avoid
411     // inconsistencies/artifacts.
412     nsString mString;
413 
414     // The start of the current active composition, in ACP offsets
415     LONG mStart;
416 
IsComposing()417     bool IsComposing() const
418     {
419       return (mView != nullptr);
420     }
421 
EndOffset()422     LONG EndOffset() const
423     {
424       return mStart + static_cast<LONG>(mString.Length());
425     }
426 
427     // Start() and End() updates the members for emulating the latest state.
428     // Unless flush the pending actions, this data never matches the actual
429     // content.
430     void Start(ITfCompositionView* aCompositionView,
431                LONG aCompositionStartOffset,
432                const nsAString& aCompositionString);
433     void End();
434   };
435   // While the document is locked, we cannot dispatch any events which cause
436   // DOM events since the DOM events' handlers may modify the locked document.
437   // However, even while the document is locked, TSF may queries us.
438   // For that, TSFTextStore modifies mComposition even while the document is
439   // locked.  With mComposition, query methods can returns the text content
440   // information.
441   Composition mComposition;
442 
443   /**
444    * IsComposingInContent() returns true if there is a composition in the
445    * focused editor and it's caused by native IME (either TIP of TSF or IME of
446    * IMM).  I.e., returns true between eCompositionStart and
447    * eCompositionCommit(AsIs).
448    */
449   bool IsComposingInContent() const;
450 
451   class Selection
452   {
453   public:
Selection()454     Selection() : mDirty(true) {}
455 
IsDirty()456     bool IsDirty() const { return mDirty; };
MarkDirty()457     void MarkDirty() { mDirty = true; }
458 
ACP()459     TS_SELECTION_ACP& ACP()
460     {
461       MOZ_ASSERT(!mDirty);
462       return mACP;
463     }
464 
SetSelection(const TS_SELECTION_ACP & aSelection)465     void SetSelection(const TS_SELECTION_ACP& aSelection)
466     {
467       mDirty = false;
468       mACP = aSelection;
469       // Selection end must be active in our editor.
470       if (mACP.style.ase != TS_AE_START) {
471         mACP.style.ase = TS_AE_END;
472       }
473       // We're not support interim char selection for now.
474       // XXX Probably, this is necessary for supporting South Asian languages.
475       mACP.style.fInterimChar = FALSE;
476     }
477 
SetSelection(uint32_t aStart,uint32_t aLength,bool aReversed,WritingMode aWritingMode)478     bool SetSelection(uint32_t aStart,
479                       uint32_t aLength,
480                       bool aReversed,
481                       WritingMode aWritingMode)
482     {
483       bool changed = mDirty ||
484                      mACP.acpStart != static_cast<LONG>(aStart) ||
485                      mACP.acpEnd != static_cast<LONG>(aStart + aLength);
486 
487       mDirty = false;
488       mACP.acpStart = static_cast<LONG>(aStart);
489       mACP.acpEnd = static_cast<LONG>(aStart + aLength);
490       mACP.style.ase = aReversed ? TS_AE_START : TS_AE_END;
491       mACP.style.fInterimChar = FALSE;
492       mWritingMode = aWritingMode;
493 
494       return changed;
495     }
496 
IsCollapsed()497     bool IsCollapsed() const
498     {
499       MOZ_ASSERT(!mDirty);
500       return (mACP.acpStart == mACP.acpEnd);
501     }
502 
CollapseAt(uint32_t aOffset)503     void CollapseAt(uint32_t aOffset)
504     {
505       // XXX This does not update the selection's mWritingMode.
506       // If it is ever used to "collapse" to an entirely new location,
507       // we may need to fix that.
508       mDirty = false;
509       mACP.acpStart = mACP.acpEnd = static_cast<LONG>(aOffset);
510       mACP.style.ase = TS_AE_END;
511       mACP.style.fInterimChar = FALSE;
512     }
513 
MinOffset()514     LONG MinOffset() const
515     {
516       MOZ_ASSERT(!mDirty);
517       LONG min = std::min(mACP.acpStart, mACP.acpEnd);
518       MOZ_ASSERT(min >= 0);
519       return min;
520     }
521 
MaxOffset()522     LONG MaxOffset() const
523     {
524       MOZ_ASSERT(!mDirty);
525       LONG max = std::max(mACP.acpStart, mACP.acpEnd);
526       MOZ_ASSERT(max >= 0);
527       return max;
528     }
529 
StartOffset()530     LONG StartOffset() const
531     {
532       MOZ_ASSERT(!mDirty);
533       MOZ_ASSERT(mACP.acpStart >= 0);
534       return mACP.acpStart;
535     }
536 
EndOffset()537     LONG EndOffset() const
538     {
539       MOZ_ASSERT(!mDirty);
540       MOZ_ASSERT(mACP.acpEnd >= 0);
541       return mACP.acpEnd;
542     }
543 
Length()544     LONG Length() const
545     {
546       MOZ_ASSERT(!mDirty);
547       MOZ_ASSERT(mACP.acpEnd >= mACP.acpStart);
548       return std::abs(mACP.acpEnd - mACP.acpStart);
549     }
550 
IsReversed()551     bool IsReversed() const
552     {
553       MOZ_ASSERT(!mDirty);
554       return (mACP.style.ase == TS_AE_START);
555     }
556 
ActiveSelEnd()557     TsActiveSelEnd ActiveSelEnd() const
558     {
559       MOZ_ASSERT(!mDirty);
560       return mACP.style.ase;
561     }
562 
IsInterimChar()563     bool IsInterimChar() const
564     {
565       MOZ_ASSERT(!mDirty);
566       return (mACP.style.fInterimChar != FALSE);
567     }
568 
GetWritingMode()569     WritingMode GetWritingMode() const
570     {
571       MOZ_ASSERT(!mDirty);
572       return mWritingMode;
573     }
574 
EqualsExceptDirection(const TS_SELECTION_ACP & aACP)575     bool EqualsExceptDirection(const TS_SELECTION_ACP& aACP) const
576     {
577       if (mACP.style.ase == aACP.style.ase) {
578         return mACP.acpStart == aACP.acpStart &&
579                mACP.acpEnd == aACP.acpEnd;
580       }
581       return mACP.acpStart == aACP.acpEnd &&
582              mACP.acpEnd == aACP.acpStart;
583     }
584 
EqualsExceptDirection(const SelectionChangeDataBase & aChangedSelection)585     bool EqualsExceptDirection(
586            const SelectionChangeDataBase& aChangedSelection) const
587     {
588       MOZ_ASSERT(!mDirty);
589       MOZ_ASSERT(aChangedSelection.IsValid());
590       return aChangedSelection.Length() == static_cast<uint32_t>(Length()) &&
591              aChangedSelection.mOffset == static_cast<uint32_t>(StartOffset());
592     }
593 
594   private:
595     TS_SELECTION_ACP mACP;
596     WritingMode mWritingMode;
597     bool mDirty;
598   };
599   // Don't access mSelection directly except at calling MarkDirty().
600   // Use SelectionForTSFRef() instead.  This is modified immediately when
601   // TSF requests to set selection and not updated by selection change in
602   // content until mContentForTSF is cleared.
603   Selection mSelectionForTSF;
604 
605   /**
606    * Get the selection expected by TSF.  If mSelectionForTSF is already valid,
607    * this just return the reference to it.  Otherwise, this initializes it
608    * with eQuerySelectedText.  Please check if the result is valid before
609    * actually using it.
610    * Note that this is also called by ContentForTSFRef().
611    */
612   Selection& SelectionForTSFRef();
613 
614   class MOZ_STACK_CLASS AutoSetTemporarySelection final
615   {
616   public:
AutoSetTemporarySelection(Selection & aSelection)617     AutoSetTemporarySelection(Selection& aSelection)
618       : mSelection(aSelection)
619     {
620       mDirty = mSelection.IsDirty();
621       if (mDirty) {
622         mSelection.CollapseAt(0);
623       }
624     }
625 
~AutoSetTemporarySelection()626     ~AutoSetTemporarySelection()
627     {
628       if (mDirty) {
629         mSelection.MarkDirty();
630       }
631     }
632 
633  private:
634     Selection& mSelection;
635     bool mDirty;
636   };
637 
638   struct PendingAction final
639   {
640     enum ActionType : uint8_t
641     {
642       COMPOSITION_START,
643       COMPOSITION_UPDATE,
644       COMPOSITION_END,
645       SET_SELECTION
646     };
647     ActionType mType;
648     // For compositionstart and selectionset
649     LONG mSelectionStart;
650     LONG mSelectionLength;
651     // For compositionstart, compositionupdate and compositionend
652     nsString mData;
653     // For compositionupdate
654     RefPtr<TextRangeArray> mRanges;
655     // For selectionset
656     bool mSelectionReversed;
657     // For compositionupdate
658     bool mIncomplete;
659     // For compositionstart
660     bool mAdjustSelection;
661   };
662   // Items of mPendingActions are appended when TSF tells us to need to dispatch
663   // DOM composition events.  However, we cannot dispatch while the document is
664   // locked because it can cause modifying the locked document.  So, the pending
665   // actions should be performed when document lock is unlocked.
666   nsTArray<PendingAction> mPendingActions;
667 
LastOrNewPendingCompositionUpdate()668   PendingAction* LastOrNewPendingCompositionUpdate()
669   {
670     if (!mPendingActions.IsEmpty()) {
671       PendingAction& lastAction = mPendingActions.LastElement();
672       if (lastAction.mType == PendingAction::COMPOSITION_UPDATE) {
673         return &lastAction;
674       }
675     }
676     PendingAction* newAction = mPendingActions.AppendElement();
677     newAction->mType = PendingAction::COMPOSITION_UPDATE;
678     newAction->mRanges = new TextRangeArray();
679     newAction->mIncomplete = true;
680     return newAction;
681   }
682 
683   /**
684    * WasTextInsertedWithoutCompositionAt() checks if text was inserted without
685    * composition immediately before (e.g., see InsertTextAtSelectionInternal()).
686    *
687    * @param aStart              The inserted offset you expected.
688    * @param aLength             The inserted text length you expected.
689    * @return                    true if the last pending actions are
690    *                            COMPOSITION_START and COMPOSITION_END and
691    *                            aStart and aLength match their information.
692    */
WasTextInsertedWithoutCompositionAt(LONG aStart,LONG aLength)693   bool WasTextInsertedWithoutCompositionAt(LONG aStart, LONG aLength) const
694   {
695     if (mPendingActions.Length() < 2) {
696       return false;
697     }
698     const PendingAction& pendingLastAction = mPendingActions.LastElement();
699     if (pendingLastAction.mType != PendingAction::COMPOSITION_END ||
700         pendingLastAction.mData.Length() != aLength) {
701       return false;
702     }
703     const PendingAction& pendingPreLastAction =
704       mPendingActions[mPendingActions.Length() - 2];
705     return pendingPreLastAction.mType == PendingAction::COMPOSITION_START &&
706            pendingPreLastAction.mSelectionStart == aStart;
707   }
708 
IsPendingCompositionUpdateIncomplete()709   bool IsPendingCompositionUpdateIncomplete() const
710   {
711     if (mPendingActions.IsEmpty()) {
712       return false;
713     }
714     const PendingAction& lastAction = mPendingActions.LastElement();
715     return lastAction.mType == PendingAction::COMPOSITION_UPDATE &&
716            lastAction.mIncomplete;
717   }
718 
CompleteLastActionIfStillIncomplete()719   void CompleteLastActionIfStillIncomplete()
720   {
721     if (!IsPendingCompositionUpdateIncomplete()) {
722       return;
723     }
724     RecordCompositionUpdateAction();
725   }
726 
727   // When On*Composition() is called without document lock, we need to flush
728   // the recorded actions at quitting the method.
729   // AutoPendingActionAndContentFlusher class is usedful for it.
730   class MOZ_STACK_CLASS AutoPendingActionAndContentFlusher final
731   {
732   public:
AutoPendingActionAndContentFlusher(TSFTextStore * aTextStore)733     AutoPendingActionAndContentFlusher(TSFTextStore* aTextStore)
734       : mTextStore(aTextStore)
735     {
736       MOZ_ASSERT(!mTextStore->mIsRecordingActionsWithoutLock);
737       if (!mTextStore->IsReadWriteLocked()) {
738         mTextStore->mIsRecordingActionsWithoutLock = true;
739       }
740     }
741 
~AutoPendingActionAndContentFlusher()742     ~AutoPendingActionAndContentFlusher()
743     {
744       if (!mTextStore->mIsRecordingActionsWithoutLock) {
745         return;
746       }
747       mTextStore->FlushPendingActions();
748       mTextStore->mIsRecordingActionsWithoutLock = false;
749     }
750 
751   private:
AutoPendingActionAndContentFlusher()752     AutoPendingActionAndContentFlusher() {}
753 
754     RefPtr<TSFTextStore> mTextStore;
755   };
756 
757   class Content final
758   {
759   public:
Content(TSFTextStore::Composition & aComposition,TSFTextStore::Selection & aSelection)760     Content(TSFTextStore::Composition& aComposition,
761             TSFTextStore::Selection& aSelection) :
762       mComposition(aComposition), mSelection(aSelection)
763     {
764       Clear();
765     }
766 
Clear()767     void Clear()
768     {
769       mText.Truncate();
770       mLastCompositionString.Truncate();
771       mInitialized = false;
772     }
773 
IsInitialized()774     bool IsInitialized() const { return mInitialized; }
775 
Init(const nsAString & aText)776     void Init(const nsAString& aText)
777     {
778       mText = aText;
779       if (mComposition.IsComposing()) {
780         mLastCompositionString = mComposition.mString;
781       } else {
782         mLastCompositionString.Truncate();
783       }
784       mMinTextModifiedOffset = NOT_MODIFIED;
785       mInitialized = true;
786     }
787 
OnLayoutChanged()788     void OnLayoutChanged()
789     {
790       mMinTextModifiedOffset = NOT_MODIFIED;
791     }
792 
793     const nsDependentSubstring GetSelectedText() const;
794     const nsDependentSubstring GetSubstring(uint32_t aStart,
795                                             uint32_t aLength) const;
796     void ReplaceSelectedTextWith(const nsAString& aString);
797     void ReplaceTextWith(LONG aStart, LONG aLength, const nsAString& aString);
798 
799     void StartComposition(ITfCompositionView* aCompositionView,
800                           const PendingAction& aCompStart,
801                           bool aPreserveSelection);
802     /**
803      * RestoreCommittedComposition() restores the committed string as
804      * composing string.  If InsertTextAtSelection() or something is called
805      * before a call of OnStartComposition(), there is a pending
806      * compositionstart and a pending compositionend.  In this case, we
807      * need to cancel the pending compositionend and continue the composition.
808      *
809      * @param aCompositionView          The composition view.
810      * @param aPendingCompositionStart  The pending compositionstart which
811      *                                  started the committed composition.
812      * @param aCanceledCompositionEnd   The pending compositionend which is
813      *                                  canceled for restarting the composition.
814      */
815     void RestoreCommittedComposition(
816                          ITfCompositionView* aCompositionView,
817                          const PendingAction& aPendingCompositionStart,
818                          const PendingAction& aCanceledCompositionEnd);
819     void EndComposition(const PendingAction& aCompEnd);
820 
Text()821     const nsString& Text() const
822     {
823       MOZ_ASSERT(mInitialized);
824       return mText;
825     }
LastCompositionString()826     const nsString& LastCompositionString() const
827     {
828       MOZ_ASSERT(mInitialized);
829       return mLastCompositionString;
830     }
MinTextModifiedOffset()831     uint32_t MinTextModifiedOffset() const
832     {
833       MOZ_ASSERT(mInitialized);
834       return mMinTextModifiedOffset;
835     }
836 
837     // Returns true if layout of the character at the aOffset has not been
838     // calculated.
IsLayoutChangedAt(uint32_t aOffset)839     bool IsLayoutChangedAt(uint32_t aOffset) const
840     {
841       return IsLayoutChanged() && (mMinTextModifiedOffset <= aOffset);
842     }
843     // Returns true if layout of the content has been changed, i.e., the new
844     // layout has not been calculated.
IsLayoutChanged()845     bool IsLayoutChanged() const
846     {
847       return mInitialized && (mMinTextModifiedOffset != NOT_MODIFIED);
848     }
849     // Returns minimum offset of modified text range.
MinOffsetOfLayoutChanged()850     uint32_t MinOffsetOfLayoutChanged() const
851     {
852       return mInitialized ? mMinTextModifiedOffset : NOT_MODIFIED;
853     }
854 
Composition()855     TSFTextStore::Composition& Composition() { return mComposition; }
Selection()856     TSFTextStore::Selection& Selection() { return mSelection; }
857 
858   private:
859     nsString mText;
860     // mLastCompositionString stores the composition string when the document
861     // is locked. This is necessary to compute mMinTextModifiedOffset.
862     nsString mLastCompositionString;
863     TSFTextStore::Composition& mComposition;
864     TSFTextStore::Selection& mSelection;
865 
866     // The minimum offset of modified part of the text.
867     enum : uint32_t
868     {
869       NOT_MODIFIED = UINT32_MAX
870     };
871     uint32_t mMinTextModifiedOffset;
872 
873     bool mInitialized;
874   };
875   // mContentForTSF is cache of content.  The information is expected by TSF
876   // and TIP.  Therefore, this is useful for answering the query from TSF or
877   // TIP.
878   // This is initialized by ContentForTSFRef() automatically (therefore, don't
879   // access this member directly except at calling Clear(), IsInitialized(),
880   // IsLayoutChangeAfter() or IsLayoutChanged()).
881   // This is cleared when:
882   //  - When there is no composition, the document is unlocked.
883   //  - When there is a composition, all dispatched events are handled by
884   //    the focused editor which may be in a remote process.
885   // So, if two compositions are created very quickly, this cache may not be
886   // cleared between eCompositionCommit(AsIs) and eCompositionStart.
887   Content mContentForTSF;
888 
889   Content& ContentForTSFRef();
890 
891   // CanAccessActualContentDirectly() returns true when TSF/TIP can access
892   // actual content directly.  In other words, mContentForTSF and/or
893   // mSelectionForTSF doesn't cache content or they matches with actual
894   // contents due to no pending text/selection change notifications.
895   bool CanAccessActualContentDirectly() const;
896 
897   // While mContentForTSF is valid, this returns the text stored by it.
898   // Otherwise, return the current text content retrieved by eQueryTextContent.
899   bool GetCurrentText(nsAString& aTextContent);
900 
901   class MouseTracker final
902   {
903   public:
904     static const DWORD kInvalidCookie = static_cast<DWORD>(-1);
905 
906     MouseTracker();
907 
908     HRESULT Init(TSFTextStore* aTextStore);
909     HRESULT AdviseSink(TSFTextStore* aTextStore,
910                        ITfRangeACP* aTextRange, ITfMouseSink* aMouseSink);
911     void UnadviseSink();
912 
IsUsing()913     bool IsUsing() const { return mSink != nullptr; }
InRange(uint32_t aOffset)914     bool InRange(uint32_t aOffset) const
915     {
916       if (NS_WARN_IF(mStart < 0) ||
917           NS_WARN_IF(mLength <= 0)) {
918         return false;
919       }
920       return aOffset >= static_cast<uint32_t>(mStart) &&
921              aOffset < static_cast<uint32_t>(mStart + mLength);
922     }
Cookie()923     DWORD Cookie() const { return mCookie; }
924     bool OnMouseButtonEvent(ULONG aEdge, ULONG aQuadrant, DWORD aButtonStatus);
RangeStart()925     LONG RangeStart() const { return mStart; }
926 
927   private:
928     RefPtr<ITfMouseSink> mSink;
929     LONG mStart;
930     LONG mLength;
931     DWORD mCookie;
932   };
933   // mMouseTrackers is an array to store each information of installed
934   // ITfMouseSink instance.
935   nsTArray<MouseTracker> mMouseTrackers;
936 
937   // The input scopes for this context, defaults to IS_DEFAULT.
938   nsTArray<InputScope>         mInputScopes;
939 
940   // Support retrieving attributes.
941   // TODO: We should support RightToLeft, perhaps.
942   enum
943   {
944     // Used for result of GetRequestedAttrIndex()
945     eNotSupported = -1,
946 
947     // Supported attributes
948     eInputScope = 0,
949     eTextVerticalWriting,
950     eTextOrientation,
951 
952     // Count of the supported attributes
953     NUM_OF_SUPPORTED_ATTRS
954   };
955   bool mRequestedAttrs[NUM_OF_SUPPORTED_ATTRS];
956 
957   int32_t GetRequestedAttrIndex(const TS_ATTRID& aAttrID);
958   TS_ATTRID GetAttrID(int32_t aIndex);
959 
960   bool mRequestedAttrValues;
961 
962   // If edit actions are being recorded without document lock, this is true.
963   // Otherwise, false.
964   bool                         mIsRecordingActionsWithoutLock;
965   // If GetTextExt() or GetACPFromPoint() is called and the layout hasn't been
966   // calculated yet, these methods return TS_E_NOLAYOUT.  At that time,
967   // mHasReturnedNoLayoutError is set to true.
968   bool                         mHasReturnedNoLayoutError;
969   // Before calling ITextStoreACPSink::OnLayoutChange() and
970   // ITfContextOwnerServices::OnLayoutChange(), mWaitingQueryLayout is set to
971   // true.  This is set to  false when GetTextExt() or GetACPFromPoint() is
972   // called.
973   bool                         mWaitingQueryLayout;
974   // During the documet is locked, we shouldn't destroy the instance.
975   // If this is true, the instance will be destroyed after unlocked.
976   bool                         mPendingDestroy;
977   // If this is false, MaybeFlushPendingNotifications() will clear the
978   // mContentForTSF.
979   bool                         mDeferClearingContentForTSF;
980   // While there is native caret, this is true.  Otherwise, false.
981   bool                         mNativeCaretIsCreated;
982   // While the instance is dispatching events, the event may not be handled
983   // synchronously in e10s mode.  So, in such case, in strictly speaking,
984   // we shouldn't query layout information.  However, TS_E_NOLAYOUT bugs of
985   // ITextStoreAPC::GetTextExt() blocks us to behave ideally.
986   // For preventing it to be called, we should put off notifying TSF of
987   // anything until layout information becomes available.
988   bool                         mDeferNotifyingTSF;
989   // While the document is locked, committing composition always fails since
990   // TSF needs another document lock for modifying the composition, selection
991   // and etc.  So, committing composition should be performed after the
992   // document is unlocked.
993   bool                         mDeferCommittingComposition;
994   bool                         mDeferCancellingComposition;
995   // Immediately after a call of Destroy(), mDestroyed becomes true.  If this
996   // is true, the instance shouldn't grant any requests from the TIP anymore.
997   bool                         mDestroyed;
998   // While the instance is being destroyed, this is set to true for avoiding
999   // recursive Destroy() calls.
1000   bool                         mBeingDestroyed;
1001 
1002 
1003   // TSF thread manager object for the current application
1004   static StaticRefPtr<ITfThreadMgr> sThreadMgr;
1005   // sMessagePump is QI'ed from sThreadMgr
1006   static StaticRefPtr<ITfMessagePump> sMessagePump;
1007   // sKeystrokeMgr is QI'ed from sThreadMgr
1008   static StaticRefPtr<ITfKeystrokeMgr> sKeystrokeMgr;
1009   // TSF display attribute manager
1010   static StaticRefPtr<ITfDisplayAttributeMgr> sDisplayAttrMgr;
1011   // TSF category manager
1012   static StaticRefPtr<ITfCategoryMgr> sCategoryMgr;
1013 
1014   // Current text store which is managing a keyboard enabled editor (i.e.,
1015   // editable editor).  Currently only ONE TSFTextStore instance is ever used,
1016   // although Create is called when an editor is focused and Destroy called
1017   // when the focused editor is blurred.
1018   static StaticRefPtr<TSFTextStore> sEnabledTextStore;
1019 
1020   // For IME (keyboard) disabled state:
1021   static StaticRefPtr<ITfDocumentMgr> sDisabledDocumentMgr;
1022   static StaticRefPtr<ITfContext> sDisabledContext;
1023 
1024   static StaticRefPtr<ITfInputProcessorProfiles> sInputProcessorProfiles;
1025 
1026   // TSF client ID for the current application
1027   static DWORD sClientId;
1028 
1029   // Enables/Disables hack for specific TIP.
1030   static bool sCreateNativeCaretForLegacyATOK;
1031   static bool sDoNotReturnNoLayoutErrorToATOKOfCompositionString;
1032   static bool sDoNotReturnNoLayoutErrorToMSSimplifiedTIP;
1033   static bool sDoNotReturnNoLayoutErrorToMSTraditionalTIP;
1034   static bool sDoNotReturnNoLayoutErrorToFreeChangJie;
1035   static bool sDoNotReturnNoLayoutErrorToEasyChangjei;
1036   static bool sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar;
1037   static bool sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret;
1038   static bool sHackQueryInsertForMSSimplifiedTIP;
1039   static bool sHackQueryInsertForMSTraditionalTIP;
1040 };
1041 
1042 } // namespace widget
1043 } // namespace mozilla
1044 
1045 #endif // #ifndef TSFTextStore_h_
1046