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