Merge mozilla-central to autoland on a CLOSED TREE
[gecko.git] / widget / windows / TSFTextStore.h
blob17358a488d6c50072ee5d351e72cedfdfb35f923
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/. */
6 #ifndef TSFTextStore_h_
7 #define TSFTextStore_h_
9 #include "nsCOMPtr.h"
10 #include "nsIWidget.h"
11 #include "nsString.h"
12 #include "nsWindow.h"
14 #include "WinUtils.h"
15 #include "WritingModes.h"
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/TextEvents.h"
23 #include "mozilla/TextRange.h"
24 #include "mozilla/WindowsVersion.h"
25 #include "mozilla/widget/IMEData.h"
27 #include <msctf.h>
28 #include <textstor.h>
30 // GUID_PROP_INPUTSCOPE is declared in inputscope.h using INIT_GUID.
31 // With initguid.h, we get its instance instead of extern declaration.
32 #ifdef INPUTSCOPE_INIT_GUID
33 # include <initguid.h>
34 #endif
35 #ifdef TEXTATTRS_INIT_GUID
36 # include <tsattrs.h>
37 #endif
38 #include <inputscope.h>
40 // TSF InputScope, for earlier SDK 8
41 #define IS_SEARCH static_cast<InputScope>(50)
43 struct ITfThreadMgr;
44 struct ITfDocumentMgr;
45 struct ITfDisplayAttributeMgr;
46 struct ITfCategoryMgr;
47 class nsWindow;
49 inline std::ostream& operator<<(std::ostream& aStream,
50 const TS_SELECTIONSTYLE& aSelectionStyle) {
51 const char* ase = "Unknown";
52 switch (aSelectionStyle.ase) {
53 case TS_AE_START:
54 ase = "TS_AE_START";
55 break;
56 case TS_AE_END:
57 ase = "TS_AE_END";
58 break;
59 case TS_AE_NONE:
60 ase = "TS_AE_NONE";
61 break;
63 aStream << "{ ase=" << ase << ", fInterimChar="
64 << (aSelectionStyle.fInterimChar ? "TRUE" : "FALSE") << " }";
65 return aStream;
68 inline std::ostream& operator<<(std::ostream& aStream,
69 const TS_SELECTION_ACP& aACP) {
70 aStream << "{ acpStart=" << aACP.acpStart << ", acpEnd=" << aACP.acpEnd
71 << ", style=" << mozilla::ToString(aACP.style).c_str() << " }";
72 return aStream;
75 namespace mozilla {
76 namespace widget {
78 class TSFStaticSink;
79 struct MSGResult;
82 * Text Services Framework text store
85 class TSFTextStore final : public ITextStoreACP,
86 public ITfContextOwnerCompositionSink,
87 public ITfMouseTrackerACP {
88 friend class TSFStaticSink;
90 private:
91 typedef IMENotification::SelectionChangeDataBase SelectionChangeDataBase;
92 typedef IMENotification::SelectionChangeData SelectionChangeData;
93 typedef IMENotification::TextChangeDataBase TextChangeDataBase;
94 typedef IMENotification::TextChangeData TextChangeData;
96 public: /*IUnknown*/
97 STDMETHODIMP QueryInterface(REFIID, void**);
99 NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(TSFTextStore)
101 public: /*ITextStoreACP*/
102 STDMETHODIMP AdviseSink(REFIID, IUnknown*, DWORD);
103 STDMETHODIMP UnadviseSink(IUnknown*);
104 STDMETHODIMP RequestLock(DWORD, HRESULT*);
105 STDMETHODIMP GetStatus(TS_STATUS*);
106 STDMETHODIMP QueryInsert(LONG, LONG, ULONG, LONG*, LONG*);
107 STDMETHODIMP GetSelection(ULONG, ULONG, TS_SELECTION_ACP*, ULONG*);
108 STDMETHODIMP SetSelection(ULONG, const TS_SELECTION_ACP*);
109 STDMETHODIMP GetText(LONG, LONG, WCHAR*, ULONG, ULONG*, TS_RUNINFO*, ULONG,
110 ULONG*, LONG*);
111 STDMETHODIMP SetText(DWORD, LONG, LONG, const WCHAR*, ULONG, TS_TEXTCHANGE*);
112 STDMETHODIMP GetFormattedText(LONG, LONG, IDataObject**);
113 STDMETHODIMP GetEmbedded(LONG, REFGUID, REFIID, IUnknown**);
114 STDMETHODIMP QueryInsertEmbedded(const GUID*, const FORMATETC*, BOOL*);
115 STDMETHODIMP InsertEmbedded(DWORD, LONG, LONG, IDataObject*, TS_TEXTCHANGE*);
116 STDMETHODIMP RequestSupportedAttrs(DWORD, ULONG, const TS_ATTRID*);
117 STDMETHODIMP RequestAttrsAtPosition(LONG, ULONG, const TS_ATTRID*, DWORD);
118 STDMETHODIMP RequestAttrsTransitioningAtPosition(LONG, ULONG,
119 const TS_ATTRID*, DWORD);
120 STDMETHODIMP FindNextAttrTransition(LONG, LONG, ULONG, const TS_ATTRID*,
121 DWORD, LONG*, BOOL*, LONG*);
122 STDMETHODIMP RetrieveRequestedAttrs(ULONG, TS_ATTRVAL*, ULONG*);
123 STDMETHODIMP GetEndACP(LONG*);
124 STDMETHODIMP GetActiveView(TsViewCookie*);
125 STDMETHODIMP GetACPFromPoint(TsViewCookie, const POINT*, DWORD, LONG*);
126 STDMETHODIMP GetTextExt(TsViewCookie, LONG, LONG, RECT*, BOOL*);
127 STDMETHODIMP GetScreenExt(TsViewCookie, RECT*);
128 STDMETHODIMP GetWnd(TsViewCookie, HWND*);
129 STDMETHODIMP InsertTextAtSelection(DWORD, const WCHAR*, ULONG, LONG*, LONG*,
130 TS_TEXTCHANGE*);
131 STDMETHODIMP InsertEmbeddedAtSelection(DWORD, IDataObject*, LONG*, LONG*,
132 TS_TEXTCHANGE*);
134 public: /*ITfContextOwnerCompositionSink*/
135 STDMETHODIMP OnStartComposition(ITfCompositionView*, BOOL*);
136 STDMETHODIMP OnUpdateComposition(ITfCompositionView*, ITfRange*);
137 STDMETHODIMP OnEndComposition(ITfCompositionView*);
139 public: /*ITfMouseTrackerACP*/
140 STDMETHODIMP AdviseMouseSink(ITfRangeACP*, ITfMouseSink*, DWORD*);
141 STDMETHODIMP UnadviseMouseSink(DWORD);
143 public:
144 static void Initialize(void);
145 static void Terminate(void);
147 static bool ProcessRawKeyMessage(const MSG& aMsg);
148 static void ProcessMessage(nsWindow* aWindow, UINT aMessage, WPARAM& aWParam,
149 LPARAM& aLParam, MSGResult& aResult);
151 static void SetIMEOpenState(bool);
152 static bool GetIMEOpenState(void);
154 static void CommitComposition(bool aDiscard) {
155 NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called");
156 if (!sEnabledTextStore) {
157 return;
159 RefPtr<TSFTextStore> textStore(sEnabledTextStore);
160 textStore->CommitCompositionInternal(aDiscard);
163 static void SetInputContext(nsWindow* aWidget, const InputContext& aContext,
164 const InputContextAction& aAction);
166 static nsresult OnFocusChange(bool aGotFocus, nsWindow* aFocusedWidget,
167 const InputContext& aContext);
168 static nsresult OnTextChange(const IMENotification& aIMENotification) {
169 NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called");
170 if (!sEnabledTextStore) {
171 return NS_OK;
173 RefPtr<TSFTextStore> textStore(sEnabledTextStore);
174 return textStore->OnTextChangeInternal(aIMENotification);
177 static nsresult OnSelectionChange(const IMENotification& aIMENotification) {
178 NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called");
179 if (!sEnabledTextStore) {
180 return NS_OK;
182 RefPtr<TSFTextStore> textStore(sEnabledTextStore);
183 return textStore->OnSelectionChangeInternal(aIMENotification);
186 static nsresult OnLayoutChange() {
187 NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called");
188 if (!sEnabledTextStore) {
189 return NS_OK;
191 RefPtr<TSFTextStore> textStore(sEnabledTextStore);
192 return textStore->OnLayoutChangeInternal();
195 static nsresult OnUpdateComposition() {
196 NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called");
197 if (!sEnabledTextStore) {
198 return NS_OK;
200 RefPtr<TSFTextStore> textStore(sEnabledTextStore);
201 return textStore->OnUpdateCompositionInternal();
204 static nsresult OnMouseButtonEvent(const IMENotification& aIMENotification) {
205 NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called");
206 if (!sEnabledTextStore) {
207 return NS_OK;
209 RefPtr<TSFTextStore> textStore(sEnabledTextStore);
210 return textStore->OnMouseButtonEventInternal(aIMENotification);
213 static IMENotificationRequests GetIMENotificationRequests();
215 // Returns the address of the pointer so that the TSF automatic test can
216 // replace the system object with a custom implementation for testing.
217 // XXX TSF doesn't work now. Should we remove it?
218 static void* GetNativeData(uint32_t aDataType) {
219 switch (aDataType) {
220 case NS_NATIVE_TSF_THREAD_MGR:
221 Initialize(); // Apply any previous changes
222 return static_cast<void*>(&sThreadMgr);
223 case NS_NATIVE_TSF_CATEGORY_MGR:
224 return static_cast<void*>(&sCategoryMgr);
225 case NS_NATIVE_TSF_DISPLAY_ATTR_MGR:
226 return static_cast<void*>(&sDisplayAttrMgr);
227 default:
228 return nullptr;
232 static void* GetThreadManager() { return static_cast<void*>(sThreadMgr); }
234 static bool ThinksHavingFocus() {
235 return (sEnabledTextStore && sEnabledTextStore->mContext);
238 static bool IsInTSFMode() { return sThreadMgr != nullptr; }
240 static bool IsComposing() {
241 return (sEnabledTextStore && sEnabledTextStore->mComposition.isSome());
244 static bool IsComposingOn(nsWindow* aWidget) {
245 return (IsComposing() && sEnabledTextStore->mWidget == aWidget);
248 static nsWindow* GetEnabledWindowBase() {
249 return sEnabledTextStore ? sEnabledTextStore->mWidget.get() : nullptr;
253 * Returns true if active keyboard layout is a legacy IMM-IME.
255 static bool IsIMM_IMEActive();
258 * Returns true if active TIP is MS-IME for Japanese.
260 static bool IsMSJapaneseIMEActive();
263 * Returns true if active TIP is Google Japanese Input.
264 * Note that if Google Japanese Input is installed as an IMM-IME,
265 * this return false even if Google Japanese Input is active.
266 * So, you may need to check IMMHandler::IsGoogleJapaneseInputActive() too.
268 static bool IsGoogleJapaneseInputActive();
271 * Returns true if active TIP is ATOK.
273 static bool IsATOKActive();
276 * Returns true if active TIP or IME is a black listed one and we should
277 * set input scope of URL bar to IS_DEFAULT rather than IS_URL.
279 static bool ShouldSetInputScopeOfURLBarToDefault();
282 * Returns true if TSF may crash if GetSelection() returns E_FAIL.
284 static bool DoNotReturnErrorFromGetSelection();
286 #ifdef DEBUG
287 // Returns true when keyboard layout has IME (TIP).
288 static bool CurrentKeyboardLayoutHasIME();
289 #endif // #ifdef DEBUG
291 protected:
292 TSFTextStore();
293 ~TSFTextStore();
295 static bool CreateAndSetFocus(nsWindow* aFocusedWidget,
296 const InputContext& aContext);
297 static void EnsureToDestroyAndReleaseEnabledTextStoreIf(
298 RefPtr<TSFTextStore>& aTextStore);
299 static void MarkContextAsKeyboardDisabled(ITfContext* aContext);
300 static void MarkContextAsEmpty(ITfContext* aContext);
302 bool Init(nsWindow* aWidget, const InputContext& aContext);
303 void Destroy();
304 void ReleaseTSFObjects();
306 bool IsReadLock(DWORD aLock) const {
307 return (TS_LF_READ == (aLock & TS_LF_READ));
309 bool IsReadWriteLock(DWORD aLock) const {
310 return (TS_LF_READWRITE == (aLock & TS_LF_READWRITE));
312 bool IsReadLocked() const { return IsReadLock(mLock); }
313 bool IsReadWriteLocked() const { return IsReadWriteLock(mLock); }
315 // This is called immediately after a call of OnLockGranted() of mSink.
316 // Note that mLock isn't cleared yet when this is called.
317 void DidLockGranted();
319 bool GetScreenExtInternal(RECT& aScreenExt);
320 // If aDispatchCompositionChangeEvent is true, this method will dispatch
321 // compositionchange event if this is called during IME composing.
322 // aDispatchCompositionChangeEvent should be true only when this is called
323 // from SetSelection. Because otherwise, the compositionchange event should
324 // not be sent from here.
325 HRESULT SetSelectionInternal(const TS_SELECTION_ACP*,
326 bool aDispatchCompositionChangeEvent = false);
327 bool InsertTextAtSelectionInternal(const nsAString& aInsertStr,
328 TS_TEXTCHANGE* aTextChange);
329 void CommitCompositionInternal(bool);
330 HRESULT GetDisplayAttribute(ITfProperty* aProperty, ITfRange* aRange,
331 TF_DISPLAYATTRIBUTE* aResult);
332 HRESULT RestartCompositionIfNecessary(ITfRange* pRangeNew = nullptr);
333 class Composition;
334 HRESULT RestartComposition(Composition& aCurrentComposition,
335 ITfCompositionView* aCompositionView,
336 ITfRange* aNewRange);
338 // Following methods record composing action(s) to mPendingActions.
339 // They will be flushed FlushPendingActions().
340 HRESULT RecordCompositionStartAction(ITfCompositionView* aCompositionView,
341 ITfRange* aRange,
342 bool aPreserveSelection);
343 HRESULT RecordCompositionStartAction(ITfCompositionView* aCompositionView,
344 LONG aStart, LONG aLength,
345 bool aPreserveSelection);
346 HRESULT RecordCompositionUpdateAction();
347 HRESULT RecordCompositionEndAction();
349 // DispatchEvent() dispatches the event and if it may not be handled
350 // synchronously, this makes the instance not notify TSF of pending
351 // notifications until next notification from content.
352 void DispatchEvent(WidgetGUIEvent& aEvent);
353 void OnLayoutInformationAvaliable();
355 // FlushPendingActions() performs pending actions recorded in mPendingActions
356 // and clear it.
357 void FlushPendingActions();
358 // MaybeFlushPendingNotifications() performs pending notifications to TSF.
359 void MaybeFlushPendingNotifications();
361 nsresult OnTextChangeInternal(const IMENotification& aIMENotification);
362 nsresult OnSelectionChangeInternal(const IMENotification& aIMENotification);
363 nsresult OnMouseButtonEventInternal(const IMENotification& aIMENotification);
364 nsresult OnLayoutChangeInternal();
365 nsresult OnUpdateCompositionInternal();
367 // mPendingSelectionChangeData stores selection change data until notifying
368 // TSF of selection change. If two or more selection changes occur, this
369 // stores the latest selection change data because only it is necessary.
370 Maybe<SelectionChangeData> mPendingSelectionChangeData;
372 // mPendingTextChangeData stores one or more text change data until notifying
373 // TSF of text change. If two or more text changes occur, this merges
374 // every text change data.
375 TextChangeData mPendingTextChangeData;
377 void NotifyTSFOfTextChange();
378 void NotifyTSFOfSelectionChange();
379 bool NotifyTSFOfLayoutChange();
380 void NotifyTSFOfLayoutChangeAgain();
382 HRESULT HandleRequestAttrs(DWORD aFlags, ULONG aFilterCount,
383 const TS_ATTRID* aFilterAttrs);
384 void SetInputScope(const nsString& aHTMLInputType,
385 const nsString& aHTMLInputMode);
387 // Creates native caret over our caret. This method only works on desktop
388 // application. Otherwise, this does nothing.
389 void CreateNativeCaret();
390 // Destroys native caret if there is.
391 void MaybeDestroyNativeCaret();
394 * MaybeHackNoErrorLayoutBugs() is a helper method of GetTextExt(). In
395 * strictly speaking, TSF is aware of asynchronous layout computation like us.
396 * However, Windows 10 version 1803 and older (including Windows 8.1 and
397 * older) Windows has a bug which is that the caller of GetTextExt() of TSF
398 * does not return TS_E_NOLAYOUT to TIP as is. Additionally, even after
399 * fixing this bug, some TIPs are not work well when we return TS_E_NOLAYOUT.
400 * For avoiding this issue, this method checks current Windows version and
401 * active TIP, and if in case we cannot return TS_E_NOLAYOUT, this modifies
402 * aACPStart and aACPEnd to making sure that they are in range of unmodified
403 * characters.
405 * @param aACPStart Initial value should be acpStart of GetTextExt().
406 * If this method returns true, this may be modified
407 * to be in range of unmodified characters.
408 * @param aACPEnd Initial value should be acpEnd of GetTextExt().
409 * If this method returns true, this may be modified
410 * to be in range of unmodified characters.
411 * And also this may become same as aACPStart.
412 * @return true if the caller shouldn't return TS_E_NOLAYOUT.
413 * In this case, this method modifies aACPStart and/or
414 * aASCPEnd to compute rectangle of unmodified characters.
415 * false if the caller can return TS_E_NOLAYOUT or
416 * we cannot have proper unmodified characters.
418 bool MaybeHackNoErrorLayoutBugs(LONG& aACPStart, LONG& aACPEnd);
420 // Holds the pointer to our current win32 widget
421 RefPtr<nsWindow> mWidget;
422 // mDispatcher is a helper class to dispatch composition events.
423 RefPtr<TextEventDispatcher> mDispatcher;
424 // Document manager for the currently focused editor
425 RefPtr<ITfDocumentMgr> mDocumentMgr;
426 // Edit cookie associated with the current editing context
427 DWORD mEditCookie;
428 // Editing context at the bottom of mDocumentMgr's context stack
429 RefPtr<ITfContext> mContext;
430 // Currently installed notification sink
431 RefPtr<ITextStoreACPSink> mSink;
432 // TS_AS_* mask of what events to notify
433 DWORD mSinkMask;
434 // 0 if not locked, otherwise TS_LF_* indicating the current lock
435 DWORD mLock;
436 // 0 if no lock is queued, otherwise TS_LF_* indicating the queue lock
437 DWORD mLockQueued;
439 uint32_t mHandlingKeyMessage;
440 void OnStartToHandleKeyMessage() {
441 // If we're starting to handle another key message during handling a
442 // key message, let's assume that the handling key message is handled by
443 // TIP and it sends another key message for hacking something.
444 // Let's try to dispatch a keyboard event now.
445 // FYI: All callers of this method grab this instance with local variable.
446 // So, even after calling MaybeDispatchKeyboardEventAsProcessedByIME(),
447 // we're safe to access any members.
448 if (!mDestroyed && sHandlingKeyMsg && !sIsKeyboardEventDispatched) {
449 MaybeDispatchKeyboardEventAsProcessedByIME();
451 ++mHandlingKeyMessage;
453 void OnEndHandlingKeyMessage(bool aIsProcessedByTSF) {
454 // If sHandlingKeyMsg has been handled by TSF or TIP and we're still
455 // alive, but we haven't dispatch keyboard event for it, let's fire it now.
456 // FYI: All callers of this method grab this instance with local variable.
457 // So, even after calling MaybeDispatchKeyboardEventAsProcessedByIME(),
458 // we're safe to access any members.
459 if (!mDestroyed && sHandlingKeyMsg && aIsProcessedByTSF &&
460 !sIsKeyboardEventDispatched) {
461 MaybeDispatchKeyboardEventAsProcessedByIME();
463 MOZ_ASSERT(mHandlingKeyMessage);
464 if (--mHandlingKeyMessage) {
465 return;
467 // If TSFTextStore instance is destroyed during handling key message(s),
468 // release all TSF objects when all nested key messages have been handled.
469 if (mDestroyed) {
470 ReleaseTSFObjects();
475 * MaybeDispatchKeyboardEventAsProcessedByIME() tries to dispatch eKeyDown
476 * event or eKeyUp event for sHandlingKeyMsg and marking the dispatching
477 * event as "processed by IME". Note that if the document is locked, this
478 * just adds a pending action into the queue and sets
479 * sIsKeyboardEventDispatched to true.
481 void MaybeDispatchKeyboardEventAsProcessedByIME();
484 * DispatchKeyboardEventAsProcessedByIME() dispatches an eKeyDown or
485 * eKeyUp event with NativeKey class and aMsg.
487 void DispatchKeyboardEventAsProcessedByIME(const MSG& aMsg);
489 // Composition class stores a copy of the active composition string. Only
490 // the data is updated during an InsertTextAtSelection call if we have a
491 // composition. The data acts as a buffer until OnUpdateComposition is
492 // called and the data is flushed to editor through eCompositionChange.
493 // This allows all changes to be updated in batches to avoid inconsistencies
494 // and artifacts.
495 class Composition final : public OffsetAndData<LONG> {
496 public:
497 explicit Composition(ITfCompositionView* aCompositionView,
498 LONG aCompositionStartOffset,
499 const nsAString& aCompositionString)
500 : OffsetAndData<LONG>(aCompositionStartOffset, aCompositionString),
501 mView(aCompositionView) {}
503 ITfCompositionView* GetView() const { return mView; }
505 friend std::ostream& operator<<(std::ostream& aStream,
506 const Composition& aComposition) {
507 aStream << "{ mView=0x" << aComposition.mView.get()
508 << ", OffsetAndData<LONG>="
509 << static_cast<const OffsetAndData<LONG>&>(aComposition) << " }";
510 return aStream;
513 private:
514 RefPtr<ITfCompositionView> const mView;
516 // While the document is locked, we cannot dispatch any events which cause
517 // DOM events since the DOM events' handlers may modify the locked document.
518 // However, even while the document is locked, TSF may queries us.
519 // For that, TSFTextStore modifies mComposition even while the document is
520 // locked. With mComposition, query methods can returns the text content
521 // information.
522 Maybe<Composition> mComposition;
525 * IsHandlingCompositionInParent() returns true if eCompositionStart is
526 * dispatched, but eCompositionCommit(AsIs) is not dispatched. This means
527 * that if composition is handled in a content process, this status indicates
528 * whether ContentCacheInParent has composition or not. On the other hand,
529 * if it's handled in the chrome process, this is exactly same as
530 * IsHandlingCompositionInContent().
532 bool IsHandlingCompositionInParent() const {
533 return mDispatcher && mDispatcher->IsComposing();
537 * IsHandlingCompositionInContent() returns true if there is a composition in
538 * the focused editor which may be in a content process.
540 bool IsHandlingCompositionInContent() const {
541 return mDispatcher && mDispatcher->IsHandlingComposition();
544 class Selection {
545 public:
546 static TS_SELECTION_ACP EmptyACP() {
547 return TS_SELECTION_ACP{
548 .acpStart = 0,
549 .acpEnd = 0,
550 .style = {.ase = TS_AE_NONE, .fInterimChar = FALSE}};
553 bool HasRange() const { return mACP.isSome(); }
554 const TS_SELECTION_ACP& ACPRef() const { return mACP.ref(); }
556 explicit Selection(const TS_SELECTION_ACP& aSelection) {
557 SetSelection(aSelection);
560 explicit Selection(uint32_t aOffsetToCollapse) {
561 Collapse(aOffsetToCollapse);
564 explicit Selection(const SelectionChangeDataBase& aSelectionChangeData) {
565 SetSelection(aSelectionChangeData);
568 explicit Selection(const WidgetQueryContentEvent& aQuerySelectionEvent) {
569 SetSelection(aQuerySelectionEvent);
572 Selection(uint32_t aStart, uint32_t aLength, bool aReversed,
573 const WritingMode& aWritingMode) {
574 SetSelection(aStart, aLength, aReversed, aWritingMode);
577 void SetSelection(const TS_SELECTION_ACP& aSelection) {
578 mACP = Some(aSelection);
579 // Selection end must be active in our editor.
580 if (mACP->style.ase != TS_AE_START) {
581 mACP->style.ase = TS_AE_END;
583 // We're not support interim char selection for now.
584 // XXX Probably, this is necessary for supporting South Asian languages.
585 mACP->style.fInterimChar = FALSE;
588 bool SetSelection(const SelectionChangeDataBase& aSelectionChangeData) {
589 MOZ_ASSERT(aSelectionChangeData.IsInitialized());
590 if (!aSelectionChangeData.HasRange()) {
591 if (mACP.isNothing()) {
592 return false;
594 mACP.reset();
595 // Let's keep the WritingMode because users don't want to change the UI
596 // of TIP temporarily since no selection case is created only by web
597 // apps, but they or TIP would restore selection at last point later.
598 return true;
600 return SetSelection(aSelectionChangeData.mOffset,
601 aSelectionChangeData.Length(),
602 aSelectionChangeData.mReversed,
603 aSelectionChangeData.GetWritingMode());
606 bool SetSelection(const WidgetQueryContentEvent& aQuerySelectionEvent) {
607 MOZ_ASSERT(aQuerySelectionEvent.mMessage == eQuerySelectedText);
608 MOZ_ASSERT(aQuerySelectionEvent.Succeeded());
609 if (aQuerySelectionEvent.DidNotFindSelection()) {
610 if (mACP.isNothing()) {
611 return false;
613 mACP.reset();
614 // Let's keep the WritingMode because users don't want to change the UI
615 // of TIP temporarily since no selection case is created only by web
616 // apps, but they or TIP would restore selection at last point later.
617 return true;
619 return SetSelection(aQuerySelectionEvent.mReply->StartOffset(),
620 aQuerySelectionEvent.mReply->DataLength(),
621 aQuerySelectionEvent.mReply->mReversed,
622 aQuerySelectionEvent.mReply->WritingModeRef());
625 bool SetSelection(uint32_t aStart, uint32_t aLength, bool aReversed,
626 const WritingMode& aWritingMode) {
627 const bool changed = mACP.isNothing() ||
628 mACP->acpStart != static_cast<LONG>(aStart) ||
629 mACP->acpEnd != static_cast<LONG>(aStart + aLength);
630 mACP = Some(
631 TS_SELECTION_ACP{.acpStart = static_cast<LONG>(aStart),
632 .acpEnd = static_cast<LONG>(aStart + aLength),
633 .style = {.ase = aReversed ? TS_AE_START : TS_AE_END,
634 .fInterimChar = FALSE}});
635 mWritingMode = aWritingMode;
637 return changed;
640 bool Collapsed() const {
641 return mACP.isNothing() || mACP->acpStart == mACP->acpEnd;
644 void Collapse(uint32_t aOffset) {
645 // XXX This does not update the selection's mWritingMode.
646 // If it is ever used to "collapse" to an entirely new location,
647 // we may need to fix that.
648 mACP = Some(
649 TS_SELECTION_ACP{.acpStart = static_cast<LONG>(aOffset),
650 .acpEnd = static_cast<LONG>(aOffset),
651 .style = {.ase = TS_AE_END, .fInterimChar = FALSE}});
654 LONG MinOffset() const {
655 MOZ_ASSERT(mACP.isSome());
656 LONG min = std::min(mACP->acpStart, mACP->acpEnd);
657 MOZ_ASSERT(min >= 0);
658 return min;
661 LONG MaxOffset() const {
662 MOZ_ASSERT(mACP.isSome());
663 LONG max = std::max(mACP->acpStart, mACP->acpEnd);
664 MOZ_ASSERT(max >= 0);
665 return max;
668 LONG StartOffset() const {
669 MOZ_ASSERT(mACP.isSome());
670 MOZ_ASSERT(mACP->acpStart >= 0);
671 return mACP->acpStart;
674 LONG EndOffset() const {
675 MOZ_ASSERT(mACP.isSome());
676 MOZ_ASSERT(mACP->acpEnd >= 0);
677 return mACP->acpEnd;
680 LONG Length() const {
681 MOZ_ASSERT_IF(mACP.isSome(), mACP->acpEnd >= mACP->acpStart);
682 return mACP.isSome() ? std::abs(mACP->acpEnd - mACP->acpStart) : 0;
685 bool IsReversed() const {
686 return mACP.isSome() && mACP->style.ase == TS_AE_START;
689 TsActiveSelEnd ActiveSelEnd() const {
690 return mACP.isSome() ? mACP->style.ase : TS_AE_NONE;
693 bool IsInterimChar() const {
694 return mACP.isSome() && mACP->style.fInterimChar != FALSE;
697 const WritingMode& WritingModeRef() const { return mWritingMode; }
699 bool EqualsExceptDirection(const TS_SELECTION_ACP& aACP) const {
700 if (mACP.isNothing()) {
701 return false;
703 if (mACP->style.ase == aACP.style.ase) {
704 return mACP->acpStart == aACP.acpStart && mACP->acpEnd == aACP.acpEnd;
706 return mACP->acpStart == aACP.acpEnd && mACP->acpEnd == aACP.acpStart;
709 bool EqualsExceptDirection(
710 const SelectionChangeDataBase& aChangedSelection) const {
711 MOZ_ASSERT(aChangedSelection.IsInitialized());
712 if (mACP.isNothing()) {
713 return aChangedSelection.HasRange();
715 return aChangedSelection.Length() == static_cast<uint32_t>(Length()) &&
716 aChangedSelection.mOffset == static_cast<uint32_t>(StartOffset());
719 friend std::ostream& operator<<(std::ostream& aStream,
720 const Selection& aSelection) {
721 aStream << "{ mACP=" << ToString(aSelection.mACP).c_str()
722 << ", mWritingMode=" << ToString(aSelection.mWritingMode).c_str()
723 << ", Collapsed()="
724 << (aSelection.Collapsed() ? "true" : "false")
725 << ", Length=" << aSelection.Length() << " }";
726 return aStream;
729 private:
730 Maybe<TS_SELECTION_ACP> mACP; // If Nothing, there is no selection
731 WritingMode mWritingMode;
733 // Don't access mSelection directly. Instead, Use SelectionForTSFRef().
734 // This is modified immediately when TSF requests to set selection and not
735 // updated by selection change in content until mContentForTSF is cleared.
736 Maybe<Selection> mSelectionForTSF;
739 * Get the selection expected by TSF. If mSelectionForTSF is already valid,
740 * this just return the reference to it. Otherwise, this initializes it
741 * with eQuerySelectedText. Please check if the result is valid before
742 * actually using it.
743 * Note that this is also called by ContentForTSF().
745 Maybe<Selection>& SelectionForTSF();
747 struct PendingAction final {
748 enum class Type : uint8_t {
749 eCompositionStart,
750 eCompositionUpdate,
751 eCompositionEnd,
752 eSetSelection,
753 eKeyboardEvent,
755 Type mType;
756 // For eCompositionStart, eCompositionEnd and eSetSelection
757 LONG mSelectionStart;
758 // For eCompositionStart and eSetSelection
759 LONG mSelectionLength;
760 // For eCompositionStart, eCompositionUpdate and eCompositionEnd
761 nsString mData;
762 // For eCompositionUpdate
763 RefPtr<TextRangeArray> mRanges;
764 // For eKeyboardEvent
765 MSG mKeyMsg;
766 // For eSetSelection
767 bool mSelectionReversed;
768 // For eCompositionUpdate
769 bool mIncomplete;
770 // For eCompositionStart
771 bool mAdjustSelection;
773 // Items of mPendingActions are appended when TSF tells us to need to dispatch
774 // DOM composition events. However, we cannot dispatch while the document is
775 // locked because it can cause modifying the locked document. So, the pending
776 // actions should be performed when document lock is unlocked.
777 nsTArray<PendingAction> mPendingActions;
779 PendingAction* LastOrNewPendingCompositionUpdate() {
780 if (!mPendingActions.IsEmpty()) {
781 PendingAction& lastAction = mPendingActions.LastElement();
782 if (lastAction.mType == PendingAction::Type::eCompositionUpdate) {
783 return &lastAction;
786 PendingAction* newAction = mPendingActions.AppendElement();
787 newAction->mType = PendingAction::Type::eCompositionUpdate;
788 newAction->mRanges = new TextRangeArray();
789 newAction->mIncomplete = true;
790 return newAction;
794 * IsLastPendingActionCompositionEndAt() checks whether the previous pending
795 * action is committing composition whose range starts from aStart and its
796 * length is aLength. In other words, this checks whether new composition
797 * which will replace same range as previous pending commit can be merged
798 * with the previous composition.
800 * @param aStart The inserted offset you expected.
801 * @param aLength The inserted text length you expected.
802 * @return true if the last pending action is
803 * eCompositionEnd and it inserted the text
804 * between aStart and aStart + aLength.
806 bool IsLastPendingActionCompositionEndAt(LONG aStart, LONG aLength) const {
807 if (mPendingActions.IsEmpty()) {
808 return false;
810 const PendingAction& pendingLastAction = mPendingActions.LastElement();
811 return pendingLastAction.mType == PendingAction::Type::eCompositionEnd &&
812 pendingLastAction.mSelectionStart == aStart &&
813 pendingLastAction.mData.Length() == static_cast<ULONG>(aLength);
816 bool IsPendingCompositionUpdateIncomplete() const {
817 if (mPendingActions.IsEmpty()) {
818 return false;
820 const PendingAction& lastAction = mPendingActions.LastElement();
821 return lastAction.mType == PendingAction::Type::eCompositionUpdate &&
822 lastAction.mIncomplete;
825 void CompleteLastActionIfStillIncomplete() {
826 if (!IsPendingCompositionUpdateIncomplete()) {
827 return;
829 RecordCompositionUpdateAction();
832 void RemoveLastCompositionUpdateActions() {
833 while (!mPendingActions.IsEmpty()) {
834 const PendingAction& lastAction = mPendingActions.LastElement();
835 if (lastAction.mType != PendingAction::Type::eCompositionUpdate) {
836 break;
838 mPendingActions.RemoveLastElement();
842 // When On*Composition() is called without document lock, we need to flush
843 // the recorded actions at quitting the method.
844 // AutoPendingActionAndContentFlusher class is usedful for it.
845 class MOZ_STACK_CLASS AutoPendingActionAndContentFlusher final {
846 public:
847 explicit AutoPendingActionAndContentFlusher(TSFTextStore* aTextStore)
848 : mTextStore(aTextStore) {
849 MOZ_ASSERT(!mTextStore->mIsRecordingActionsWithoutLock);
850 if (!mTextStore->IsReadWriteLocked()) {
851 mTextStore->mIsRecordingActionsWithoutLock = true;
855 ~AutoPendingActionAndContentFlusher() {
856 if (!mTextStore->mIsRecordingActionsWithoutLock) {
857 return;
859 mTextStore->FlushPendingActions();
860 mTextStore->mIsRecordingActionsWithoutLock = false;
863 private:
864 AutoPendingActionAndContentFlusher() {}
866 RefPtr<TSFTextStore> mTextStore;
869 class Content final {
870 public:
871 Content(TSFTextStore& aTSFTextStore, const nsAString& aText)
872 : mText(aText),
873 mLastComposition(aTSFTextStore.mComposition),
874 mComposition(aTSFTextStore.mComposition),
875 mSelection(aTSFTextStore.mSelectionForTSF) {}
877 void OnLayoutChanged() { mMinModifiedOffset.reset(); }
879 // OnCompositionEventsHandled() is called when all pending composition
880 // events are handled in the focused content which may be in a remote
881 // process.
882 void OnCompositionEventsHandled() { mLastComposition = mComposition; }
884 const nsDependentSubstring GetSelectedText() const;
885 const nsDependentSubstring GetSubstring(uint32_t aStart,
886 uint32_t aLength) const;
887 void ReplaceSelectedTextWith(const nsAString& aString);
888 void ReplaceTextWith(LONG aStart, LONG aLength, const nsAString& aString);
890 void StartComposition(ITfCompositionView* aCompositionView,
891 const PendingAction& aCompStart,
892 bool aPreserveSelection);
894 * RestoreCommittedComposition() restores the committed string as
895 * composing string. If InsertTextAtSelection() or something is called
896 * before a call of OnStartComposition() or previous composition is
897 * committed and new composition is restarted to clean up the commited
898 * string, there is a pending compositionend. In this case, we need to
899 * cancel the pending compositionend and continue the composition.
901 * @param aCompositionView The composition view.
902 * @param aCanceledCompositionEnd The pending compositionend which is
903 * canceled for restarting the composition.
905 void RestoreCommittedComposition(
906 ITfCompositionView* aCompositionView,
907 const PendingAction& aCanceledCompositionEnd);
908 void EndComposition(const PendingAction& aCompEnd);
910 const nsString& TextRef() const { return mText; }
911 const Maybe<OffsetAndData<LONG>>& LastComposition() const {
912 return mLastComposition;
914 const Maybe<uint32_t>& MinModifiedOffset() const {
915 return mMinModifiedOffset;
917 const Maybe<StartAndEndOffsets<LONG>>& LatestCompositionRange() const {
918 return mLatestCompositionRange;
921 // Returns true if layout of the character at the aOffset has not been
922 // calculated.
923 bool IsLayoutChangedAt(uint32_t aOffset) const {
924 return IsLayoutChanged() && (mMinModifiedOffset.value() <= aOffset);
926 // Returns true if layout of the content has been changed, i.e., the new
927 // layout has not been calculated.
928 bool IsLayoutChanged() const { return mMinModifiedOffset.isSome(); }
929 bool HasOrHadComposition() const {
930 return mLatestCompositionRange.isSome();
933 Maybe<TSFTextStore::Composition>& Composition() { return mComposition; }
934 Maybe<TSFTextStore::Selection>& Selection() { return mSelection; }
936 friend std::ostream& operator<<(std::ostream& aStream,
937 const Content& aContent) {
938 aStream << "{ mText="
939 << PrintStringDetail(aContent.mText,
940 PrintStringDetail::kMaxLengthForEditor)
941 .get()
942 << ", mLastComposition=" << aContent.mLastComposition
943 << ", mLatestCompositionRange="
944 << aContent.mLatestCompositionRange
945 << ", mMinModifiedOffset=" << aContent.mMinModifiedOffset << " }";
946 return aStream;
949 private:
950 nsString mText;
952 // mLastComposition may store the composition string and its start offset
953 // when the document is locked. This is necessary to compute
954 // mMinTextModifiedOffset.
955 Maybe<OffsetAndData<LONG>> mLastComposition;
957 Maybe<TSFTextStore::Composition>& mComposition;
958 Maybe<TSFTextStore::Selection>& mSelection;
960 // The latest composition's start and end offset.
961 Maybe<StartAndEndOffsets<LONG>> mLatestCompositionRange;
963 // The minimum offset of modified part of the text.
964 Maybe<uint32_t> mMinModifiedOffset;
966 // mContentForTSF is cache of content. The information is expected by TSF
967 // and TIP. Therefore, this is useful for answering the query from TSF or
968 // TIP.
969 // This is initialized by ContentForTSF() automatically (therefore, don't
970 // access this member directly except at calling Clear(), IsInitialized(),
971 // IsLayoutChangeAfter() or IsLayoutChanged()).
972 // This is cleared when:
973 // - When there is no composition, the document is unlocked.
974 // - When there is a composition, all dispatched events are handled by
975 // the focused editor which may be in a remote process.
976 // So, if two compositions are created very quickly, this cache may not be
977 // cleared between eCompositionCommit(AsIs) and eCompositionStart.
978 Maybe<Content> mContentForTSF;
980 Maybe<Content>& ContentForTSF();
982 class MOZ_STACK_CLASS AutoNotifyingTSFBatch final {
983 public:
984 explicit AutoNotifyingTSFBatch(TSFTextStore& aTextStore)
985 : mTextStore(aTextStore), mOldValue(aTextStore.mDeferNotifyingTSF) {
986 mTextStore.mDeferNotifyingTSF = true;
988 ~AutoNotifyingTSFBatch() {
989 mTextStore.mDeferNotifyingTSF = mOldValue;
990 mTextStore.MaybeFlushPendingNotifications();
993 private:
994 TSFTextStore& mTextStore;
995 bool mOldValue;
998 // CanAccessActualContentDirectly() returns true when TSF/TIP can access
999 // actual content directly. In other words, mContentForTSF and/or
1000 // mSelectionForTSF doesn't cache content or they matches with actual
1001 // contents due to no pending text/selection change notifications.
1002 bool CanAccessActualContentDirectly() const;
1004 // While mContentForTSF is valid, this returns the text stored by it.
1005 // Otherwise, return the current text content retrieved by eQueryTextContent.
1006 bool GetCurrentText(nsAString& aTextContent);
1008 class MouseTracker final {
1009 public:
1010 static const DWORD kInvalidCookie = static_cast<DWORD>(-1);
1012 MouseTracker();
1014 HRESULT Init(TSFTextStore* aTextStore);
1015 HRESULT AdviseSink(TSFTextStore* aTextStore, ITfRangeACP* aTextRange,
1016 ITfMouseSink* aMouseSink);
1017 void UnadviseSink();
1019 bool IsUsing() const { return mSink != nullptr; }
1020 DWORD Cookie() const { return mCookie; }
1021 bool OnMouseButtonEvent(ULONG aEdge, ULONG aQuadrant, DWORD aButtonStatus);
1022 const Maybe<StartAndEndOffsets<LONG>> Range() const { return mRange; }
1024 private:
1025 RefPtr<ITfMouseSink> mSink;
1026 Maybe<StartAndEndOffsets<LONG>> mRange;
1027 DWORD mCookie;
1029 // mMouseTrackers is an array to store each information of installed
1030 // ITfMouseSink instance.
1031 nsTArray<MouseTracker> mMouseTrackers;
1033 // The input scopes for this context, defaults to IS_DEFAULT.
1034 nsTArray<InputScope> mInputScopes;
1036 // The URL cache of the focused document.
1037 nsString mDocumentURL;
1039 // Support retrieving attributes.
1040 // TODO: We should support RightToLeft, perhaps.
1041 enum {
1042 // Used for result of GetRequestedAttrIndex()
1043 eNotSupported = -1,
1045 // Supported attributes
1046 eInputScope = 0,
1047 eDocumentURL,
1048 eTextVerticalWriting,
1049 eTextOrientation,
1051 // Count of the supported attributes
1052 NUM_OF_SUPPORTED_ATTRS
1054 bool mRequestedAttrs[NUM_OF_SUPPORTED_ATTRS] = {false};
1056 int32_t GetRequestedAttrIndex(const TS_ATTRID& aAttrID);
1057 TS_ATTRID GetAttrID(int32_t aIndex);
1059 bool mRequestedAttrValues = false;
1061 // If edit actions are being recorded without document lock, this is true.
1062 // Otherwise, false.
1063 bool mIsRecordingActionsWithoutLock = false;
1064 // If GetTextExt() or GetACPFromPoint() is called and the layout hasn't been
1065 // calculated yet, these methods return TS_E_NOLAYOUT. At that time,
1066 // mHasReturnedNoLayoutError is set to true.
1067 bool mHasReturnedNoLayoutError = false;
1068 // Before calling ITextStoreACPSink::OnLayoutChange() and
1069 // ITfContextOwnerServices::OnLayoutChange(), mWaitingQueryLayout is set to
1070 // true. This is set to false when GetTextExt() or GetACPFromPoint() is
1071 // called.
1072 bool mWaitingQueryLayout = false;
1073 // During the document is locked, we shouldn't destroy the instance.
1074 // If this is true, the instance will be destroyed after unlocked.
1075 bool mPendingDestroy = false;
1076 // If this is false, MaybeFlushPendingNotifications() will clear the
1077 // mContentForTSF.
1078 bool mDeferClearingContentForTSF = false;
1079 // While the instance is initializing content/selection cache, another
1080 // initialization shouldn't run recursively. Therefore, while the
1081 // initialization is running, this is set to true. Use AutoNotifyingTSFBatch
1082 // to set this.
1083 bool mDeferNotifyingTSF = false;
1084 // While the instance is dispatching events, the event may not be handled
1085 // synchronously when remote content has focus. In the case, we cannot
1086 // return the latest layout/content information to TSF/TIP until we get next
1087 // update notification from ContentCacheInParent. For preventing TSF/TIP
1088 // retrieves the latest content/layout information while it becomes available,
1089 // we should put off notifying TSF of any updates.
1090 bool mDeferNotifyingTSFUntilNextUpdate = false;
1091 // While the document is locked, committing composition always fails since
1092 // TSF needs another document lock for modifying the composition, selection
1093 // and etc. So, committing composition should be performed after the
1094 // document is unlocked.
1095 bool mDeferCommittingComposition = false;
1096 bool mDeferCancellingComposition = false;
1097 // Immediately after a call of Destroy(), mDestroyed becomes true. If this
1098 // is true, the instance shouldn't grant any requests from the TIP anymore.
1099 bool mDestroyed = false;
1100 // While the instance is being destroyed, this is set to true for avoiding
1101 // recursive Destroy() calls.
1102 bool mBeingDestroyed = false;
1103 // Whether we're in the private browsing mode.
1104 bool mInPrivateBrowsing = true;
1105 // Debug flag to check whether we're initializing mContentForTSF and
1106 // mSelectionForTSF.
1107 bool mIsInitializingContentForTSF = false;
1108 bool mIsInitializingSelectionForTSF = false;
1110 // TSF thread manager object for the current application
1111 static StaticRefPtr<ITfThreadMgr> sThreadMgr;
1112 static already_AddRefed<ITfThreadMgr> GetThreadMgr();
1113 // sMessagePump is QI'ed from sThreadMgr
1114 static StaticRefPtr<ITfMessagePump> sMessagePump;
1116 public:
1117 // Expose GetMessagePump() for WinUtils.
1118 static already_AddRefed<ITfMessagePump> GetMessagePump();
1120 private:
1121 // sKeystrokeMgr is QI'ed from sThreadMgr
1122 static StaticRefPtr<ITfKeystrokeMgr> sKeystrokeMgr;
1123 // TSF display attribute manager
1124 static StaticRefPtr<ITfDisplayAttributeMgr> sDisplayAttrMgr;
1125 static already_AddRefed<ITfDisplayAttributeMgr> GetDisplayAttributeMgr();
1126 // TSF category manager
1127 static StaticRefPtr<ITfCategoryMgr> sCategoryMgr;
1128 static already_AddRefed<ITfCategoryMgr> GetCategoryMgr();
1129 // Compartment for (Get|Set)IMEOpenState()
1130 static StaticRefPtr<ITfCompartment> sCompartmentForOpenClose;
1131 static already_AddRefed<ITfCompartment> GetCompartmentForOpenClose();
1133 // Current text store which is managing a keyboard enabled editor (i.e.,
1134 // editable editor). Currently only ONE TSFTextStore instance is ever used,
1135 // although Create is called when an editor is focused and Destroy called
1136 // when the focused editor is blurred.
1137 static StaticRefPtr<TSFTextStore> sEnabledTextStore;
1139 // For IME (keyboard) disabled state:
1140 static StaticRefPtr<ITfDocumentMgr> sDisabledDocumentMgr;
1141 static StaticRefPtr<ITfContext> sDisabledContext;
1143 static StaticRefPtr<ITfInputProcessorProfiles> sInputProcessorProfiles;
1144 static already_AddRefed<ITfInputProcessorProfiles>
1145 GetInputProcessorProfiles();
1147 // Handling key message.
1148 static const MSG* sHandlingKeyMsg;
1150 // TSF client ID for the current application
1151 static DWORD sClientId;
1153 // true if an eKeyDown or eKeyUp event for sHandlingKeyMsg has already
1154 // been dispatched.
1155 static bool sIsKeyboardEventDispatched;
1158 } // namespace widget
1159 } // namespace mozilla
1161 #endif // #ifndef TSFTextStore_h_