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