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