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