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