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 bool aInPrivateBrowsing); 353 354 // Creates native caret over our caret. This method only works on desktop 355 // application. Otherwise, this does nothing. 356 void CreateNativeCaret(); 357 // Destroys native caret if there is. 358 void MaybeDestroyNativeCaret(); 359 360 /** 361 * MaybeHackNoErrorLayoutBugs() is a helper method of GetTextExt(). In 362 * strictly speaking, TSF is aware of asynchronous layout computation like us. 363 * However, Windows 10 version 1803 and older (including Windows 8.1 and 364 * older) Windows has a bug which is that the caller of GetTextExt() of TSF 365 * does not return TS_E_NOLAYOUT to TIP as is. Additionally, even after 366 * fixing this bug, some TIPs are not work well when we return TS_E_NOLAYOUT. 367 * For avoiding this issue, this method checks current Windows version and 368 * active TIP, and if in case we cannot return TS_E_NOLAYOUT, this modifies 369 * aACPStart and aACPEnd to making sure that they are in range of unmodified 370 * characters. 371 * 372 * @param aACPStart Initial value should be acpStart of GetTextExt(). 373 * If this method returns true, this may be modified 374 * to be in range of unmodified characters. 375 * @param aACPEnd Initial value should be acpEnd of GetTextExt(). 376 * If this method returns true, this may be modified 377 * to be in range of unmodified characters. 378 * And also this may become same as aACPStart. 379 * @return true if the caller shouldn't return TS_E_NOLAYOUT. 380 * In this case, this method modifies aACPStart and/or 381 * aASCPEnd to compute rectangle of unmodified characters. 382 * false if the caller can return TS_E_NOLAYOUT or 383 * we cannot have proper unmodified characters. 384 */ 385 bool MaybeHackNoErrorLayoutBugs(LONG& aACPStart, LONG& aACPEnd); 386 387 // Holds the pointer to our current win32 widget 388 RefPtr<nsWindowBase> mWidget; 389 // mDispatcher is a helper class to dispatch composition events. 390 RefPtr<TextEventDispatcher> mDispatcher; 391 // Document manager for the currently focused editor 392 RefPtr<ITfDocumentMgr> mDocumentMgr; 393 // Edit cookie associated with the current editing context 394 DWORD mEditCookie; 395 // Editing context at the bottom of mDocumentMgr's context stack 396 RefPtr<ITfContext> mContext; 397 // Currently installed notification sink 398 RefPtr<ITextStoreACPSink> mSink; 399 // TS_AS_* mask of what events to notify 400 DWORD mSinkMask; 401 // 0 if not locked, otherwise TS_LF_* indicating the current lock 402 DWORD mLock; 403 // 0 if no lock is queued, otherwise TS_LF_* indicating the queue lock 404 DWORD mLockQueued; 405 406 uint32_t mHandlingKeyMessage; OnStartToHandleKeyMessage()407 void OnStartToHandleKeyMessage() { 408 // If we're starting to handle another key message during handling a 409 // key message, let's assume that the handling key message is handled by 410 // TIP and it sends another key message for hacking something. 411 // Let's try to dispatch a keyboard event now. 412 // FYI: All callers of this method grab this instance with local variable. 413 // So, even after calling MaybeDispatchKeyboardEventAsProcessedByIME(), 414 // we're safe to access any members. 415 if (!mDestroyed && sHandlingKeyMsg && !sIsKeyboardEventDispatched) { 416 MaybeDispatchKeyboardEventAsProcessedByIME(); 417 } 418 ++mHandlingKeyMessage; 419 } OnEndHandlingKeyMessage(bool aIsProcessedByTSF)420 void OnEndHandlingKeyMessage(bool aIsProcessedByTSF) { 421 // If sHandlingKeyMsg has been handled by TSF or TIP and we're still 422 // alive, but we haven't dispatch keyboard event for it, let's fire it now. 423 // FYI: All callers of this method grab this instance with local variable. 424 // So, even after calling MaybeDispatchKeyboardEventAsProcessedByIME(), 425 // we're safe to access any members. 426 if (!mDestroyed && sHandlingKeyMsg && aIsProcessedByTSF && 427 !sIsKeyboardEventDispatched) { 428 MaybeDispatchKeyboardEventAsProcessedByIME(); 429 } 430 MOZ_ASSERT(mHandlingKeyMessage); 431 if (--mHandlingKeyMessage) { 432 return; 433 } 434 // If TSFTextStore instance is destroyed during handling key message(s), 435 // release all TSF objects when all nested key messages have been handled. 436 if (mDestroyed) { 437 ReleaseTSFObjects(); 438 } 439 } 440 441 /** 442 * MaybeDispatchKeyboardEventAsProcessedByIME() tries to dispatch eKeyDown 443 * event or eKeyUp event for sHandlingKeyMsg and marking the dispatching 444 * event as "processed by IME". Note that if the document is locked, this 445 * just adds a pending action into the queue and sets 446 * sIsKeyboardEventDispatched to true. 447 */ 448 void MaybeDispatchKeyboardEventAsProcessedByIME(); 449 450 /** 451 * DispatchKeyboardEventAsProcessedByIME() dispatches an eKeyDown or 452 * eKeyUp event with NativeKey class and aMsg. 453 */ 454 void DispatchKeyboardEventAsProcessedByIME(const MSG& aMsg); 455 456 class Composition final { 457 public: 458 // nullptr if no composition is active, otherwise the current composition 459 RefPtr<ITfCompositionView> mView; 460 461 // Current copy of the active composition string. Only mString is 462 // changed during a InsertTextAtSelection call if we have a composition. 463 // mString acts as a buffer until OnUpdateComposition is called 464 // and mString is flushed to editor through eCompositionChange. 465 // This way all changes are updated in batches to avoid 466 // inconsistencies/artifacts. 467 nsString mString; 468 469 // The start of the current active composition, in ACP offsets 470 LONG mStart; 471 IsComposing()472 bool IsComposing() const { return (mView != nullptr); } 473 EndOffset()474 LONG EndOffset() const { 475 return mStart + static_cast<LONG>(mString.Length()); 476 } 477 478 // Start() and End() updates the members for emulating the latest state. 479 // Unless flush the pending actions, this data never matches the actual 480 // content. 481 void Start(ITfCompositionView* aCompositionView, 482 LONG aCompositionStartOffset, 483 const nsAString& aCompositionString); 484 void End(); 485 }; 486 // While the document is locked, we cannot dispatch any events which cause 487 // DOM events since the DOM events' handlers may modify the locked document. 488 // However, even while the document is locked, TSF may queries us. 489 // For that, TSFTextStore modifies mComposition even while the document is 490 // locked. With mComposition, query methods can returns the text content 491 // information. 492 Composition mComposition; 493 494 /** 495 * IsHandlingComposition() returns true if there is a composition in the 496 * focused editor. 497 */ IsHandlingComposition()498 bool IsHandlingComposition() const { 499 return mDispatcher && mDispatcher->IsHandlingComposition(); 500 } 501 502 class Selection { 503 public: Selection()504 Selection() : mDirty(true) {} 505 IsDirty()506 bool IsDirty() const { return mDirty; }; MarkDirty()507 void MarkDirty() { mDirty = true; } 508 ACP()509 TS_SELECTION_ACP& ACP() { 510 MOZ_ASSERT(!mDirty); 511 return mACP; 512 } 513 SetSelection(const TS_SELECTION_ACP & aSelection)514 void SetSelection(const TS_SELECTION_ACP& aSelection) { 515 mDirty = false; 516 mACP = aSelection; 517 // Selection end must be active in our editor. 518 if (mACP.style.ase != TS_AE_START) { 519 mACP.style.ase = TS_AE_END; 520 } 521 // We're not support interim char selection for now. 522 // XXX Probably, this is necessary for supporting South Asian languages. 523 mACP.style.fInterimChar = FALSE; 524 } 525 SetSelection(uint32_t aStart,uint32_t aLength,bool aReversed,WritingMode aWritingMode)526 bool SetSelection(uint32_t aStart, uint32_t aLength, bool aReversed, 527 WritingMode aWritingMode) { 528 bool changed = mDirty || mACP.acpStart != static_cast<LONG>(aStart) || 529 mACP.acpEnd != static_cast<LONG>(aStart + aLength); 530 531 mDirty = false; 532 mACP.acpStart = static_cast<LONG>(aStart); 533 mACP.acpEnd = static_cast<LONG>(aStart + aLength); 534 mACP.style.ase = aReversed ? TS_AE_START : TS_AE_END; 535 mACP.style.fInterimChar = FALSE; 536 mWritingMode = aWritingMode; 537 538 return changed; 539 } 540 IsCollapsed()541 bool IsCollapsed() const { 542 MOZ_ASSERT(!mDirty); 543 return (mACP.acpStart == mACP.acpEnd); 544 } 545 CollapseAt(uint32_t aOffset)546 void CollapseAt(uint32_t aOffset) { 547 // XXX This does not update the selection's mWritingMode. 548 // If it is ever used to "collapse" to an entirely new location, 549 // we may need to fix that. 550 mDirty = false; 551 mACP.acpStart = mACP.acpEnd = static_cast<LONG>(aOffset); 552 mACP.style.ase = TS_AE_END; 553 mACP.style.fInterimChar = FALSE; 554 } 555 MinOffset()556 LONG MinOffset() const { 557 MOZ_ASSERT(!mDirty); 558 LONG min = std::min(mACP.acpStart, mACP.acpEnd); 559 MOZ_ASSERT(min >= 0); 560 return min; 561 } 562 MaxOffset()563 LONG MaxOffset() const { 564 MOZ_ASSERT(!mDirty); 565 LONG max = std::max(mACP.acpStart, mACP.acpEnd); 566 MOZ_ASSERT(max >= 0); 567 return max; 568 } 569 StartOffset()570 LONG StartOffset() const { 571 MOZ_ASSERT(!mDirty); 572 MOZ_ASSERT(mACP.acpStart >= 0); 573 return mACP.acpStart; 574 } 575 EndOffset()576 LONG EndOffset() const { 577 MOZ_ASSERT(!mDirty); 578 MOZ_ASSERT(mACP.acpEnd >= 0); 579 return mACP.acpEnd; 580 } 581 Length()582 LONG Length() const { 583 MOZ_ASSERT(!mDirty); 584 MOZ_ASSERT(mACP.acpEnd >= mACP.acpStart); 585 return std::abs(mACP.acpEnd - mACP.acpStart); 586 } 587 IsReversed()588 bool IsReversed() const { 589 MOZ_ASSERT(!mDirty); 590 return (mACP.style.ase == TS_AE_START); 591 } 592 ActiveSelEnd()593 TsActiveSelEnd ActiveSelEnd() const { 594 MOZ_ASSERT(!mDirty); 595 return mACP.style.ase; 596 } 597 IsInterimChar()598 bool IsInterimChar() const { 599 MOZ_ASSERT(!mDirty); 600 return (mACP.style.fInterimChar != FALSE); 601 } 602 GetWritingMode()603 WritingMode GetWritingMode() const { 604 MOZ_ASSERT(!mDirty); 605 return mWritingMode; 606 } 607 EqualsExceptDirection(const TS_SELECTION_ACP & aACP)608 bool EqualsExceptDirection(const TS_SELECTION_ACP& aACP) const { 609 if (mACP.style.ase == aACP.style.ase) { 610 return mACP.acpStart == aACP.acpStart && mACP.acpEnd == aACP.acpEnd; 611 } 612 return mACP.acpStart == aACP.acpEnd && mACP.acpEnd == aACP.acpStart; 613 } 614 EqualsExceptDirection(const SelectionChangeDataBase & aChangedSelection)615 bool EqualsExceptDirection( 616 const SelectionChangeDataBase& aChangedSelection) const { 617 MOZ_ASSERT(!mDirty); 618 MOZ_ASSERT(aChangedSelection.IsValid()); 619 return aChangedSelection.Length() == static_cast<uint32_t>(Length()) && 620 aChangedSelection.mOffset == static_cast<uint32_t>(StartOffset()); 621 } 622 623 private: 624 TS_SELECTION_ACP mACP; 625 WritingMode mWritingMode; 626 bool mDirty; 627 }; 628 // Don't access mSelection directly except at calling MarkDirty(). 629 // Use SelectionForTSFRef() instead. This is modified immediately when 630 // TSF requests to set selection and not updated by selection change in 631 // content until mContentForTSF is cleared. 632 Selection mSelectionForTSF; 633 634 /** 635 * Get the selection expected by TSF. If mSelectionForTSF is already valid, 636 * this just return the reference to it. Otherwise, this initializes it 637 * with eQuerySelectedText. Please check if the result is valid before 638 * actually using it. 639 * Note that this is also called by ContentForTSFRef(). 640 */ 641 Selection& SelectionForTSFRef(); 642 643 class MOZ_STACK_CLASS AutoSetTemporarySelection final { 644 public: AutoSetTemporarySelection(Selection & aSelection)645 explicit AutoSetTemporarySelection(Selection& aSelection) 646 : mSelection(aSelection) { 647 mDirty = mSelection.IsDirty(); 648 if (mDirty) { 649 mSelection.CollapseAt(0); 650 } 651 } 652 ~AutoSetTemporarySelection()653 ~AutoSetTemporarySelection() { 654 if (mDirty) { 655 mSelection.MarkDirty(); 656 } 657 } 658 659 private: 660 Selection& mSelection; 661 bool mDirty; 662 }; 663 664 struct PendingAction final { 665 enum class Type : uint8_t { 666 eCompositionStart, 667 eCompositionUpdate, 668 eCompositionEnd, 669 eSetSelection, 670 eKeyboardEvent, 671 }; 672 Type mType; 673 // For eCompositionStart, eCompositionEnd and eSetSelection 674 LONG mSelectionStart; 675 // For eCompositionStart and eSetSelection 676 LONG mSelectionLength; 677 // For eCompositionStart, eCompositionUpdate and eCompositionEnd 678 nsString mData; 679 // For eCompositionUpdate 680 RefPtr<TextRangeArray> mRanges; 681 // For eKeyboardEvent 682 MSG mKeyMsg; 683 // For eSetSelection 684 bool mSelectionReversed; 685 // For eCompositionUpdate 686 bool mIncomplete; 687 // For eCompositionStart 688 bool mAdjustSelection; 689 }; 690 // Items of mPendingActions are appended when TSF tells us to need to dispatch 691 // DOM composition events. However, we cannot dispatch while the document is 692 // locked because it can cause modifying the locked document. So, the pending 693 // actions should be performed when document lock is unlocked. 694 nsTArray<PendingAction> mPendingActions; 695 LastOrNewPendingCompositionUpdate()696 PendingAction* LastOrNewPendingCompositionUpdate() { 697 if (!mPendingActions.IsEmpty()) { 698 PendingAction& lastAction = mPendingActions.LastElement(); 699 if (lastAction.mType == PendingAction::Type::eCompositionUpdate) { 700 return &lastAction; 701 } 702 } 703 PendingAction* newAction = mPendingActions.AppendElement(); 704 newAction->mType = PendingAction::Type::eCompositionUpdate; 705 newAction->mRanges = new TextRangeArray(); 706 newAction->mIncomplete = true; 707 return newAction; 708 } 709 710 /** 711 * IsLastPendingActionCompositionEndAt() checks whether the previous pending 712 * action is committing composition whose range starts from aStart and its 713 * length is aLength. In other words, this checks whether new composition 714 * which will replace same range as previous pending commit can be merged 715 * with the previous composition. 716 * 717 * @param aStart The inserted offset you expected. 718 * @param aLength The inserted text length you expected. 719 * @return true if the last pending action is 720 * eCompositionEnd and it inserted the text 721 * between aStart and aStart + aLength. 722 */ IsLastPendingActionCompositionEndAt(LONG aStart,LONG aLength)723 bool IsLastPendingActionCompositionEndAt(LONG aStart, LONG aLength) const { 724 if (mPendingActions.IsEmpty()) { 725 return false; 726 } 727 const PendingAction& pendingLastAction = mPendingActions.LastElement(); 728 return pendingLastAction.mType == PendingAction::Type::eCompositionEnd && 729 pendingLastAction.mSelectionStart == aStart && 730 pendingLastAction.mData.Length() == static_cast<ULONG>(aLength); 731 } 732 IsPendingCompositionUpdateIncomplete()733 bool IsPendingCompositionUpdateIncomplete() const { 734 if (mPendingActions.IsEmpty()) { 735 return false; 736 } 737 const PendingAction& lastAction = mPendingActions.LastElement(); 738 return lastAction.mType == PendingAction::Type::eCompositionUpdate && 739 lastAction.mIncomplete; 740 } 741 CompleteLastActionIfStillIncomplete()742 void CompleteLastActionIfStillIncomplete() { 743 if (!IsPendingCompositionUpdateIncomplete()) { 744 return; 745 } 746 RecordCompositionUpdateAction(); 747 } 748 RemoveLastCompositionUpdateActions()749 void RemoveLastCompositionUpdateActions() { 750 while (!mPendingActions.IsEmpty()) { 751 const PendingAction& lastAction = mPendingActions.LastElement(); 752 if (lastAction.mType != PendingAction::Type::eCompositionUpdate) { 753 break; 754 } 755 mPendingActions.RemoveLastElement(); 756 } 757 } 758 759 // When On*Composition() is called without document lock, we need to flush 760 // the recorded actions at quitting the method. 761 // AutoPendingActionAndContentFlusher class is usedful for it. 762 class MOZ_STACK_CLASS AutoPendingActionAndContentFlusher final { 763 public: AutoPendingActionAndContentFlusher(TSFTextStore * aTextStore)764 explicit AutoPendingActionAndContentFlusher(TSFTextStore* aTextStore) 765 : mTextStore(aTextStore) { 766 MOZ_ASSERT(!mTextStore->mIsRecordingActionsWithoutLock); 767 if (!mTextStore->IsReadWriteLocked()) { 768 mTextStore->mIsRecordingActionsWithoutLock = true; 769 } 770 } 771 ~AutoPendingActionAndContentFlusher()772 ~AutoPendingActionAndContentFlusher() { 773 if (!mTextStore->mIsRecordingActionsWithoutLock) { 774 return; 775 } 776 mTextStore->FlushPendingActions(); 777 mTextStore->mIsRecordingActionsWithoutLock = false; 778 } 779 780 private: AutoPendingActionAndContentFlusher()781 AutoPendingActionAndContentFlusher() {} 782 783 RefPtr<TSFTextStore> mTextStore; 784 }; 785 786 class Content final { 787 public: Content(TSFTextStore::Composition & aComposition,TSFTextStore::Selection & aSelection)788 Content(TSFTextStore::Composition& aComposition, 789 TSFTextStore::Selection& aSelection) 790 : mComposition(aComposition), mSelection(aSelection) { 791 Clear(); 792 } 793 Clear()794 void Clear() { 795 mText.Truncate(); 796 mLastCompositionString.Truncate(); 797 mLastCompositionStart = -1; 798 mInitialized = false; 799 } 800 IsInitialized()801 bool IsInitialized() const { return mInitialized; } 802 Init(const nsAString & aText)803 void Init(const nsAString& aText) { 804 mText = aText; 805 if (mComposition.IsComposing()) { 806 mLastCompositionString = mComposition.mString; 807 mLastCompositionStart = mComposition.mStart; 808 } else { 809 mLastCompositionString.Truncate(); 810 mLastCompositionStart = -1; 811 } 812 mMinTextModifiedOffset = NOT_MODIFIED; 813 mLatestCompositionStartOffset = mLatestCompositionEndOffset = LONG_MAX; 814 mInitialized = true; 815 } 816 OnLayoutChanged()817 void OnLayoutChanged() { mMinTextModifiedOffset = NOT_MODIFIED; } 818 819 // OnCompositionEventsHandled() is called when all pending composition 820 // events are handled in the focused content which may be in a remote 821 // process. OnCompositionEventsHandled()822 void OnCompositionEventsHandled() { 823 if (!mInitialized) { 824 return; 825 } 826 if (mComposition.IsComposing()) { 827 mLastCompositionString = mComposition.mString; 828 mLastCompositionStart = mComposition.mStart; 829 } else { 830 mLastCompositionString.Truncate(); 831 mLastCompositionStart = -1; 832 } 833 } 834 835 const nsDependentSubstring GetSelectedText() const; 836 const nsDependentSubstring GetSubstring(uint32_t aStart, 837 uint32_t aLength) const; 838 void ReplaceSelectedTextWith(const nsAString& aString); 839 void ReplaceTextWith(LONG aStart, LONG aLength, const nsAString& aString); 840 841 void StartComposition(ITfCompositionView* aCompositionView, 842 const PendingAction& aCompStart, 843 bool aPreserveSelection); 844 /** 845 * RestoreCommittedComposition() restores the committed string as 846 * composing string. If InsertTextAtSelection() or something is called 847 * before a call of OnStartComposition() or previous composition is 848 * committed and new composition is restarted to clean up the commited 849 * string, there is a pending compositionend. In this case, we need to 850 * cancel the pending compositionend and continue the composition. 851 * 852 * @param aCompositionView The composition view. 853 * @param aCanceledCompositionEnd The pending compositionend which is 854 * canceled for restarting the composition. 855 */ 856 void RestoreCommittedComposition( 857 ITfCompositionView* aCompositionView, 858 const PendingAction& aCanceledCompositionEnd); 859 void EndComposition(const PendingAction& aCompEnd); 860 Text()861 const nsString& Text() const { 862 MOZ_ASSERT(mInitialized); 863 return mText; 864 } LastCompositionString()865 const nsString& LastCompositionString() const { 866 MOZ_ASSERT(mInitialized); 867 return mLastCompositionString; 868 } LastCompositionStringEndOffset()869 LONG LastCompositionStringEndOffset() const { 870 MOZ_ASSERT(mInitialized); 871 MOZ_ASSERT(WasLastComposition()); 872 return mLastCompositionStart + mLastCompositionString.Length(); 873 } WasLastComposition()874 bool WasLastComposition() const { 875 MOZ_ASSERT(mInitialized); 876 return mLastCompositionStart >= 0; 877 } MinTextModifiedOffset()878 uint32_t MinTextModifiedOffset() const { 879 MOZ_ASSERT(mInitialized); 880 return mMinTextModifiedOffset; 881 } LatestCompositionStartOffset()882 LONG LatestCompositionStartOffset() const { 883 MOZ_ASSERT(mInitialized); 884 MOZ_ASSERT(HasOrHadComposition()); 885 return mLatestCompositionStartOffset; 886 } LatestCompositionEndOffset()887 LONG LatestCompositionEndOffset() const { 888 MOZ_ASSERT(mInitialized); 889 MOZ_ASSERT(HasOrHadComposition()); 890 return mLatestCompositionEndOffset; 891 } 892 893 // Returns true if layout of the character at the aOffset has not been 894 // calculated. IsLayoutChangedAt(uint32_t aOffset)895 bool IsLayoutChangedAt(uint32_t aOffset) const { 896 return IsLayoutChanged() && (mMinTextModifiedOffset <= aOffset); 897 } 898 // Returns true if layout of the content has been changed, i.e., the new 899 // layout has not been calculated. IsLayoutChanged()900 bool IsLayoutChanged() const { 901 return mInitialized && (mMinTextModifiedOffset != NOT_MODIFIED); 902 } 903 // Returns minimum offset of modified text range. MinOffsetOfLayoutChanged()904 uint32_t MinOffsetOfLayoutChanged() const { 905 return mInitialized ? mMinTextModifiedOffset : NOT_MODIFIED; 906 } 907 HasOrHadComposition()908 bool HasOrHadComposition() const { 909 return mInitialized && mLatestCompositionStartOffset != LONG_MAX && 910 mLatestCompositionEndOffset != LONG_MAX; 911 } 912 Composition()913 TSFTextStore::Composition& Composition() { return mComposition; } Selection()914 TSFTextStore::Selection& Selection() { return mSelection; } 915 916 private: 917 nsString mText; 918 // mLastCompositionString stores the composition string when the document 919 // is locked. This is necessary to compute mMinTextModifiedOffset. 920 nsString mLastCompositionString; 921 TSFTextStore::Composition& mComposition; 922 TSFTextStore::Selection& mSelection; 923 924 // mLastCompositionStart stores the start offset of composition when 925 // mLastCompositionString is set. 926 LONG mLastCompositionStart; 927 928 // The latest composition's start and end offset. If composition hasn't 929 // been started since this instance is initialized, they are LONG_MAX. 930 LONG mLatestCompositionStartOffset; 931 LONG mLatestCompositionEndOffset; 932 933 // The minimum offset of modified part of the text. 934 enum : uint32_t { NOT_MODIFIED = UINT32_MAX }; 935 uint32_t mMinTextModifiedOffset; 936 937 bool mInitialized; 938 }; 939 // mContentForTSF is cache of content. The information is expected by TSF 940 // and TIP. Therefore, this is useful for answering the query from TSF or 941 // TIP. 942 // This is initialized by ContentForTSFRef() automatically (therefore, don't 943 // access this member directly except at calling Clear(), IsInitialized(), 944 // IsLayoutChangeAfter() or IsLayoutChanged()). 945 // This is cleared when: 946 // - When there is no composition, the document is unlocked. 947 // - When there is a composition, all dispatched events are handled by 948 // the focused editor which may be in a remote process. 949 // So, if two compositions are created very quickly, this cache may not be 950 // cleared between eCompositionCommit(AsIs) and eCompositionStart. 951 Content mContentForTSF; 952 953 Content& ContentForTSFRef(); 954 955 // CanAccessActualContentDirectly() returns true when TSF/TIP can access 956 // actual content directly. In other words, mContentForTSF and/or 957 // mSelectionForTSF doesn't cache content or they matches with actual 958 // contents due to no pending text/selection change notifications. 959 bool CanAccessActualContentDirectly() const; 960 961 // While mContentForTSF is valid, this returns the text stored by it. 962 // Otherwise, return the current text content retrieved by eQueryTextContent. 963 bool GetCurrentText(nsAString& aTextContent); 964 965 class MouseTracker final { 966 public: 967 static const DWORD kInvalidCookie = static_cast<DWORD>(-1); 968 969 MouseTracker(); 970 971 HRESULT Init(TSFTextStore* aTextStore); 972 HRESULT AdviseSink(TSFTextStore* aTextStore, ITfRangeACP* aTextRange, 973 ITfMouseSink* aMouseSink); 974 void UnadviseSink(); 975 IsUsing()976 bool IsUsing() const { return mSink != nullptr; } InRange(uint32_t aOffset)977 bool InRange(uint32_t aOffset) const { 978 if (NS_WARN_IF(mStart < 0) || NS_WARN_IF(mLength <= 0)) { 979 return false; 980 } 981 return aOffset >= static_cast<uint32_t>(mStart) && 982 aOffset < static_cast<uint32_t>(mStart + mLength); 983 } Cookie()984 DWORD Cookie() const { return mCookie; } 985 bool OnMouseButtonEvent(ULONG aEdge, ULONG aQuadrant, DWORD aButtonStatus); RangeStart()986 LONG RangeStart() const { return mStart; } 987 988 private: 989 RefPtr<ITfMouseSink> mSink; 990 LONG mStart; 991 LONG mLength; 992 DWORD mCookie; 993 }; 994 // mMouseTrackers is an array to store each information of installed 995 // ITfMouseSink instance. 996 nsTArray<MouseTracker> mMouseTrackers; 997 998 // The input scopes for this context, defaults to IS_DEFAULT. 999 nsTArray<InputScope> mInputScopes; 1000 1001 // Support retrieving attributes. 1002 // TODO: We should support RightToLeft, perhaps. 1003 enum { 1004 // Used for result of GetRequestedAttrIndex() 1005 eNotSupported = -1, 1006 1007 // Supported attributes 1008 eInputScope = 0, 1009 eTextVerticalWriting, 1010 eTextOrientation, 1011 1012 // Count of the supported attributes 1013 NUM_OF_SUPPORTED_ATTRS 1014 }; 1015 bool mRequestedAttrs[NUM_OF_SUPPORTED_ATTRS]; 1016 1017 int32_t GetRequestedAttrIndex(const TS_ATTRID& aAttrID); 1018 TS_ATTRID GetAttrID(int32_t aIndex); 1019 1020 bool mRequestedAttrValues; 1021 1022 // If edit actions are being recorded without document lock, this is true. 1023 // Otherwise, false. 1024 bool mIsRecordingActionsWithoutLock; 1025 // If GetTextExt() or GetACPFromPoint() is called and the layout hasn't been 1026 // calculated yet, these methods return TS_E_NOLAYOUT. At that time, 1027 // mHasReturnedNoLayoutError is set to true. 1028 bool mHasReturnedNoLayoutError; 1029 // Before calling ITextStoreACPSink::OnLayoutChange() and 1030 // ITfContextOwnerServices::OnLayoutChange(), mWaitingQueryLayout is set to 1031 // true. This is set to false when GetTextExt() or GetACPFromPoint() is 1032 // called. 1033 bool mWaitingQueryLayout; 1034 // During the documet is locked, we shouldn't destroy the instance. 1035 // If this is true, the instance will be destroyed after unlocked. 1036 bool mPendingDestroy; 1037 // If this is false, MaybeFlushPendingNotifications() will clear the 1038 // mContentForTSF. 1039 bool mDeferClearingContentForTSF; 1040 // While the instance is dispatching events, the event may not be handled 1041 // synchronously in e10s mode. So, in such case, in strictly speaking, 1042 // we shouldn't query layout information. However, TS_E_NOLAYOUT bugs of 1043 // ITextStoreAPC::GetTextExt() blocks us to behave ideally. 1044 // For preventing it to be called, we should put off notifying TSF of 1045 // anything until layout information becomes available. 1046 bool mDeferNotifyingTSF; 1047 // While the document is locked, committing composition always fails since 1048 // TSF needs another document lock for modifying the composition, selection 1049 // and etc. So, committing composition should be performed after the 1050 // document is unlocked. 1051 bool mDeferCommittingComposition; 1052 bool mDeferCancellingComposition; 1053 // Immediately after a call of Destroy(), mDestroyed becomes true. If this 1054 // is true, the instance shouldn't grant any requests from the TIP anymore. 1055 bool mDestroyed; 1056 // While the instance is being destroyed, this is set to true for avoiding 1057 // recursive Destroy() calls. 1058 bool mBeingDestroyed; 1059 1060 // TSF thread manager object for the current application 1061 static StaticRefPtr<ITfThreadMgr> sThreadMgr; 1062 static already_AddRefed<ITfThreadMgr> GetThreadMgr(); 1063 // sMessagePump is QI'ed from sThreadMgr 1064 static StaticRefPtr<ITfMessagePump> sMessagePump; 1065 1066 public: 1067 // Expose GetMessagePump() for WinUtils. 1068 static already_AddRefed<ITfMessagePump> GetMessagePump(); 1069 1070 private: 1071 // sKeystrokeMgr is QI'ed from sThreadMgr 1072 static StaticRefPtr<ITfKeystrokeMgr> sKeystrokeMgr; 1073 // TSF display attribute manager 1074 static StaticRefPtr<ITfDisplayAttributeMgr> sDisplayAttrMgr; 1075 static already_AddRefed<ITfDisplayAttributeMgr> GetDisplayAttributeMgr(); 1076 // TSF category manager 1077 static StaticRefPtr<ITfCategoryMgr> sCategoryMgr; 1078 static already_AddRefed<ITfCategoryMgr> GetCategoryMgr(); 1079 // Compartment for (Get|Set)IMEOpenState() 1080 static StaticRefPtr<ITfCompartment> sCompartmentForOpenClose; 1081 static already_AddRefed<ITfCompartment> GetCompartmentForOpenClose(); 1082 1083 // Current text store which is managing a keyboard enabled editor (i.e., 1084 // editable editor). Currently only ONE TSFTextStore instance is ever used, 1085 // although Create is called when an editor is focused and Destroy called 1086 // when the focused editor is blurred. 1087 static StaticRefPtr<TSFTextStore> sEnabledTextStore; 1088 1089 // For IME (keyboard) disabled state: 1090 static StaticRefPtr<ITfDocumentMgr> sDisabledDocumentMgr; 1091 static StaticRefPtr<ITfContext> sDisabledContext; 1092 1093 static StaticRefPtr<ITfInputProcessorProfiles> sInputProcessorProfiles; 1094 static already_AddRefed<ITfInputProcessorProfiles> 1095 GetInputProcessorProfiles(); 1096 1097 // Handling key message. 1098 static const MSG* sHandlingKeyMsg; 1099 1100 // TSF client ID for the current application 1101 static DWORD sClientId; 1102 1103 // true if an eKeyDown or eKeyUp event for sHandlingKeyMsg has already 1104 // been dispatched. 1105 static bool sIsKeyboardEventDispatched; 1106 }; 1107 1108 } // namespace widget 1109 } // namespace mozilla 1110 1111 #endif // #ifndef TSFTextStore_h_ 1112