IMEContentObserver.h (37452B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef mozilla_IMEContentObserver_h 8 #define mozilla_IMEContentObserver_h 9 10 #include "mozilla/Attributes.h" 11 #include "mozilla/EditorBase.h" 12 #include "mozilla/dom/Element.h" 13 #include "mozilla/dom/Selection.h" 14 #include "mozilla/dom/Text.h" 15 #include "nsCOMPtr.h" 16 #include "nsCycleCollectionParticipant.h" 17 #include "nsIDocShell.h" // XXX Why does only this need to be included here? 18 #include "nsIMutationObserver.h" 19 #include "nsIReflowObserver.h" 20 #include "nsIScrollObserver.h" 21 #include "nsIWidget.h" 22 #include "nsStubDocumentObserver.h" 23 #include "nsStubMutationObserver.h" 24 #include "nsThreadUtils.h" 25 #include "nsWeakReference.h" 26 27 class nsIContent; 28 class nsINode; 29 class nsPresContext; 30 31 namespace mozilla { 32 33 class EventStateManager; 34 class TextComposition; 35 36 namespace dom { 37 class Selection; 38 } // namespace dom 39 40 // IMEContentObserver notifies widget of any text and selection changes 41 // in the currently focused editor 42 class IMEContentObserver final : public nsStubMutationObserver, 43 public nsIReflowObserver, 44 public nsIScrollObserver, 45 public nsSupportsWeakReference { 46 public: 47 using SelectionChangeData = widget::IMENotification::SelectionChangeData; 48 using TextChangeData = widget::IMENotification::TextChangeData; 49 using TextChangeDataBase = widget::IMENotification::TextChangeDataBase; 50 using IMENotificationRequests = widget::IMENotificationRequests; 51 using IMEMessage = widget::IMEMessage; 52 enum class ForRemoval : bool { No, Yes }; 53 54 IMEContentObserver(); 55 56 NS_DECL_CYCLE_COLLECTING_ISUPPORTS 57 NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(IMEContentObserver, 58 nsIReflowObserver) 59 NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATAWILLCHANGE 60 NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED 61 NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED 62 NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED 63 NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED 64 NS_DECL_NSIMUTATIONOBSERVER_PARENTCHAINCHANGED 65 NS_DECL_NSIREFLOWOBSERVER 66 67 // nsIScrollObserver 68 virtual void ScrollPositionChanged() override; 69 70 /** 71 * OnSelectionChange() is called when selection is changed in the editor. 72 */ 73 void OnSelectionChange(dom::Selection& aSelection); 74 75 MOZ_CAN_RUN_SCRIPT bool OnMouseButtonEvent(nsPresContext& aPresContext, 76 WidgetMouseEvent& aMouseEvent); 77 78 MOZ_CAN_RUN_SCRIPT nsresult 79 HandleQueryContentEvent(WidgetQueryContentEvent* aEvent); 80 81 /** 82 * Handle eSetSelection event if and only if aEvent changes selection offset 83 * or length. Doing nothing when selection range is same is important to 84 * honer users' intention or web app's intention because ContentEventHandler 85 * does not support to put range boundaries to arbitrary side of element 86 * boundaries. E.g., `<b>bold[]</b> normal` vs. `<b>bold</b>[] normal`. 87 * Note that this compares given range with selection cache which has been 88 * notified IME via widget. Therefore, the caller needs to guarantee that 89 * pending notifications should've been flushed. If you test this, you need 90 * to wait 2 animation frames before sending eSetSelection event. 91 */ 92 MOZ_CAN_RUN_SCRIPT nsresult MaybeHandleSelectionEvent( 93 nsPresContext* aPresContext, WidgetSelectionEvent* aEvent); 94 95 /** 96 * Init() initializes the instance, i.e., retrieving necessary objects and 97 * starts to observe something. 98 * Be aware, callers of this method need to guarantee that the instance 99 * won't be released during calling this. 100 * 101 * @param aWidget The widget which can access native IME. 102 * @param aPresContext The PresContext which has aContent. 103 * @param aElement An editable element or nullptr if this will observe 104 * design mode document. 105 * @param aEditorBase The editor which is associated with aContent. 106 */ 107 MOZ_CAN_RUN_SCRIPT void Init(nsIWidget& aWidget, nsPresContext& aPresContext, 108 dom::Element* aElement, EditorBase& aEditorBase); 109 110 /** 111 * Destroy() finalizes the instance, i.e., stops observing contents and 112 * clearing the members. 113 * Be aware, callers of this method need to guarantee that the instance 114 * won't be released during calling this. 115 */ 116 void Destroy(); 117 118 /** 119 * Returns false if the instance refers some objects and observing them. 120 * Otherwise, true. 121 */ 122 bool Destroyed() const; 123 124 /** 125 * IMEContentObserver is stored by EventStateManager during observing. 126 * DisconnectFromEventStateManager() is called when EventStateManager stops 127 * storing the instance. 128 */ 129 void DisconnectFromEventStateManager(); 130 131 /** 132 * MaybeReinitialize() tries to restart to observe the editor's root node. 133 * This is useful when the editor is reframed and all children are replaced 134 * with new node instances. 135 * Be aware, callers of this method need to guarantee that the instance 136 * won't be released during calling this. 137 * 138 * @return Returns true if the instance is managing the content. 139 * Otherwise, false. 140 */ 141 MOZ_CAN_RUN_SCRIPT bool MaybeReinitialize(nsIWidget& aWidget, 142 nsPresContext& aPresContext, 143 dom::Element* aElement, 144 EditorBase& aEditorBase); 145 146 /** 147 * Return true if this is observing editable content and aElement has focus. 148 * If aElement is a text control, check if this is observing its anonymous 149 * subtree. Otherwise, check if this is observing the children of aElement in 150 * the DOM tree. If aElement is nullptr, this returns true if entire the 151 * document is editable, e.g., in the designMode. 152 */ 153 [[nodiscard]] bool IsObserving(const nsPresContext& aPresContext, 154 const dom::Element* aElement) const; 155 156 [[nodiscard]] bool IsBeingInitializedFor(const nsPresContext& aPresContext, 157 const dom::Element* aElement, 158 const EditorBase& aEditorBase) const; 159 bool IsObserving(const TextComposition& aTextComposition) const; 160 bool WasInitializedWith(const EditorBase& aEditorBase) const { 161 return mEditorBase == &aEditorBase; 162 } 163 bool IsEditorHandlingEventForComposition() const; 164 bool KeepAliveDuringDeactive() const { 165 return mIMENotificationRequests && 166 mIMENotificationRequests->WantDuringDeactive(); 167 } 168 [[nodiscard]] bool EditorIsTextEditor() const { 169 return mEditorBase && mEditorBase->IsTextEditor(); 170 } 171 nsIWidget* GetWidget() const { return mWidget; } 172 void SuppressNotifyingIME(); 173 void UnsuppressNotifyingIME(); 174 nsPresContext* GetPresContext() const; 175 nsresult GetSelectionAndRoot(dom::Selection** aSelection, 176 dom::Element** aRootElement) const; 177 178 /** 179 * TryToFlushPendingNotifications() should be called when pending events 180 * should be flushed. This tries to run the queued IMENotificationSender. 181 * Doesn't do anything in child processes where flushing happens 182 * asynchronously unless aAllowAsync is false. 183 */ 184 void TryToFlushPendingNotifications(bool aAllowAsync); 185 186 /** 187 * MaybeNotifyCompositionEventHandled() posts composition event handled 188 * notification into the pseudo queue. 189 */ 190 void MaybeNotifyCompositionEventHandled(); 191 192 /** 193 * Following methods are called when the editor: 194 * - an edit action handled. 195 * - before handling an edit action. 196 * - canceled handling an edit action after calling BeforeEditAction(). 197 */ 198 void OnEditActionHandled(); 199 void BeforeEditAction(); 200 void CancelEditAction(); 201 202 /** 203 * Called when text control value is changed while this is not observing 204 * mRootElement. This is typically there is no frame for the editor (i.e., 205 * no proper anonymous <div> element for the editor yet) or the TextEditor 206 * has not been created (i.e., IMEStateManager has not been reinitialized 207 * this instance with new anonymous <div> element yet). 208 */ 209 void OnTextControlValueChangedWhileNotObservable(const nsAString& aNewValue); 210 211 /** 212 * Return an Element if and only if this instance is observing the element. 213 * The element is the anonymous <div> of a text control element if this is 214 * initialized with a TextEditor. Otherwise, the focused editing host. 215 * If you want the text control if this is initialized with a TextEditor, use 216 * GetObservingEditingHostOrTextControlElement() instead. 217 */ 218 dom::Element* GetObservingElement() const { 219 return mIsObserving ? mRootElement.get() : nullptr; 220 } 221 222 /** 223 * Return an Element if and only if this instance is observing the element. 224 * The element is a text control element if this is initalized with a 225 * TextEditor. Otherwise, the focused editing host. 226 * If you want the anonymous <div> in the text control if this is initialized 227 * with TextEditor, use GetObservingElement() instead. 228 */ 229 dom::Element* GetObservingEditingHostOrTextControlElement() const { 230 return mIsTextControl ? GetObservingTextControlElement() 231 : GetObservingElement(); 232 } 233 234 /** 235 * Return an element if and only if this instance is initialized with a 236 * TextEditor and observing its anonymous <div>. 237 */ 238 dom::Element* GetObservingTextControlElement() const { 239 return mIsObserving && mIsTextControl 240 ? dom::Element::FromNodeOrNull( 241 mRootEditableNodeOrTextControlElement) 242 : nullptr; 243 } 244 245 private: 246 ~IMEContentObserver() = default; 247 248 enum State { 249 eState_NotObserving, 250 eState_Initializing, 251 eState_StoppedObserving, 252 eState_Observing 253 }; 254 State GetState() const; 255 MOZ_CAN_RUN_SCRIPT bool InitWithEditor(nsPresContext& aPresContext, 256 dom::Element* aElement, 257 EditorBase& aEditorBase); 258 void OnIMEReceivedFocus(); 259 void Clear(); 260 261 /** 262 * Return true if aElement is observed by this instance. 263 */ 264 [[nodiscard]] bool IsObservingElement(const nsPresContext& aPresContext, 265 const dom::Element* aElement) const; 266 267 [[nodiscard]] bool IsReflowLocked() const; 268 [[nodiscard]] bool IsSafeToNotifyIME() const; 269 [[nodiscard]] bool IsEditorComposing() const; 270 271 // Following methods are called by DocumentObserver when 272 // beginning to update the contents and ending updating the contents. 273 void BeginDocumentUpdate(); 274 void EndDocumentUpdate(); 275 276 // Following methods manages added nodes during a document change. 277 278 /** 279 * IsInDocumentChange() returns true while the DOM tree is being modified 280 * with mozAutoDocUpdate. E.g., it's being modified by setting innerHTML or 281 * insertAdjacentHTML(). This returns false when user types something in 282 * the focused editor editor. 283 */ 284 bool IsInDocumentChange() const { 285 return mDocumentObserver && mDocumentObserver->IsUpdating(); 286 } 287 288 [[nodiscard]] bool EditorIsHandlingEditSubAction() const; 289 290 void PostFocusSetNotification(); 291 void MaybeNotifyIMEOfFocusSet(); 292 void PostTextChangeNotification(); 293 void MaybeNotifyIMEOfTextChange(const TextChangeDataBase& aTextChangeData); 294 void CancelNotifyingIMEOfTextChange(); 295 void PostSelectionChangeNotification(); 296 void MaybeNotifyIMEOfSelectionChange(bool aCausedByComposition, 297 bool aCausedBySelectionEvent, 298 bool aOccurredDuringComposition); 299 void PostPositionChangeNotification(); 300 void MaybeNotifyIMEOfPositionChange(); 301 void CancelNotifyingIMEOfPositionChange(); 302 void PostCompositionEventHandledNotification(); 303 304 void ContentAdded(nsINode* aContainer, nsIContent* aFirstContent, 305 nsIContent* aLastContent); 306 307 struct MOZ_STACK_CLASS OffsetAndLengthAdjustments { 308 [[nodiscard]] uint32_t AdjustedOffset(uint32_t aOffset) const { 309 MOZ_ASSERT_IF(mOffsetAdjustment < 0, aOffset >= mOffsetAdjustment); 310 return aOffset + mOffsetAdjustment; 311 } 312 [[nodiscard]] uint32_t AdjustedLength(uint32_t aLength) const { 313 MOZ_ASSERT_IF(mOffsetAdjustment < 0, aLength >= mLengthAdjustment); 314 return aLength + mLengthAdjustment; 315 } 316 [[nodiscard]] uint32_t AdjustedEndOffset(uint32_t aEndOffset) const { 317 MOZ_ASSERT_IF(mOffsetAdjustment + mLengthAdjustment < 0, 318 aEndOffset >= mOffsetAdjustment + mLengthAdjustment); 319 return aEndOffset + (mOffsetAdjustment + mLengthAdjustment); 320 } 321 322 int64_t mOffsetAdjustment = 0; 323 int64_t mLengthAdjustment = 0; 324 }; 325 326 /** 327 * Posts a text change caused by cached added content in mAddedContentCache. 328 * 329 * @param aOffsetOfFirstContent 330 * Flattened text offset of mFirst. This can be 331 * different value from the computed value in the 332 * current tree. However, in the case, 333 * aAdjustments should have the difference. If this 334 * is Nothing, it's computed with the current DOM. 335 * @param aLengthOfContentNNodes 336 * Flattened text length starting from mFirst and 337 * ending by end of mLast. This can be different 338 * value from the computed value in the current 339 * tree. However, in the case, aAdjustments should 340 * have the difference. If this is Nothing, it's 341 * computed with the current DOM. 342 * @param aAdjustments When aOffsetOfFirstContent and/or 343 * aLengthOfContentNodes are specified different 344 * value(s) from the computed value(s) in the 345 * current DOM, these members should have non-zero 346 * values of the differences. 347 */ 348 void NotifyIMEOfCachedConsecutiveNewNodes( 349 const char* aCallerName, 350 const Maybe<uint32_t>& aOffsetOfFirstContent = Nothing(), 351 const Maybe<uint32_t>& aLengthOfContentNNodes = Nothing(), 352 const OffsetAndLengthAdjustments& aAdjustments = 353 OffsetAndLengthAdjustments{0, 0}); 354 355 void ObserveEditableNode(); 356 /** 357 * NotifyIMEOfBlur() notifies IME of blur. 358 */ 359 void NotifyIMEOfBlur(); 360 /** 361 * UnregisterObservers() unregisters all listeners and observers. 362 */ 363 void UnregisterObservers(); 364 void FlushMergeableNotifications(); 365 bool NeedsTextChangeNotification() const { 366 return mIMENotificationRequests && 367 mIMENotificationRequests->WantTextChange(); 368 } 369 bool NeedsPositionChangeNotification() const { 370 return mIMENotificationRequests && 371 mIMENotificationRequests->WantPositionChanged(); 372 } 373 void ClearPendingNotifications() { 374 mNeedsToNotifyIMEOfFocusSet = false; 375 mNeedsToNotifyIMEOfTextChange = false; 376 mNeedsToNotifyIMEOfSelectionChange = false; 377 mNeedsToNotifyIMEOfPositionChange = false; 378 mNeedsToNotifyIMEOfCompositionEventHandled = false; 379 mTextChangeData.Clear(); 380 } 381 bool NeedsToNotifyIMEOfSomething() const { 382 return mNeedsToNotifyIMEOfFocusSet || mNeedsToNotifyIMEOfTextChange || 383 mNeedsToNotifyIMEOfSelectionChange || 384 mNeedsToNotifyIMEOfPositionChange || 385 mNeedsToNotifyIMEOfCompositionEventHandled; 386 } 387 388 /** 389 * UpdateSelectionCache() updates mSelectionData with the latest selection. 390 * This should be called only when IsSafeToNotifyIME() returns true. 391 */ 392 MOZ_CAN_RUN_SCRIPT bool UpdateSelectionCache(bool aRequireFlush = true); 393 394 /** 395 * Return the document node if aNode is in the design mode. Return the 396 * editing host of aNode if and only if it's editable. Otherwise, nullptr. 397 */ 398 [[nodiscard]] static nsINode* GetMostDistantInclusiveEditableAncestorNode( 399 const nsPresContext& aPresContext, const dom::Element* aElement); 400 401 nsCOMPtr<nsIWidget> mWidget; 402 // mFocusedWidget has the editor observed by the instance. E.g., if the 403 // focused editor is in XUL panel, this should be the widget of the panel. 404 // On the other hand, mWidget is its parent which handles IME. 405 nsCOMPtr<nsIWidget> mFocusedWidget; 406 RefPtr<dom::Selection> mSelection; 407 // The anonymous <div> element if mEditorBase is a TextEditor or an editing 408 // host if mEditorBase is an HTMLEditor. 409 RefPtr<dom::Element> mRootElement; 410 // If it's in the design mode, this is set to the document node. 411 // If it's initialized with a TextEditor, this is the text control element. 412 // Otherwise, this is an editing host element. 413 nsCOMPtr<nsINode> mRootEditableNodeOrTextControlElement; 414 nsCOMPtr<nsIDocShell> mDocShell; 415 RefPtr<EditorBase> mEditorBase; 416 417 /** 418 * Helper classes to notify IME. 419 */ 420 421 class AChangeEvent : public Runnable { 422 protected: 423 enum ChangeEventType { 424 eChangeEventType_Focus, 425 eChangeEventType_Selection, 426 eChangeEventType_Text, 427 eChangeEventType_Position, 428 eChangeEventType_CompositionEventHandled 429 }; 430 431 explicit AChangeEvent(const char* aName, 432 IMEContentObserver* aIMEContentObserver) 433 : Runnable(aName), 434 mIMEContentObserver(do_GetWeakReference( 435 static_cast<nsIReflowObserver*>(aIMEContentObserver))) { 436 MOZ_ASSERT(aIMEContentObserver); 437 } 438 439 already_AddRefed<IMEContentObserver> GetObserver() const { 440 nsCOMPtr<nsIReflowObserver> observer = 441 do_QueryReferent(mIMEContentObserver); 442 return observer.forget().downcast<IMEContentObserver>(); 443 } 444 445 nsWeakPtr mIMEContentObserver; 446 447 /** 448 * CanNotifyIME() checks if mIMEContentObserver can and should notify IME. 449 */ 450 bool CanNotifyIME(ChangeEventType aChangeEventType) const; 451 452 /** 453 * IsSafeToNotifyIME() checks if it's safe to noitify IME. 454 */ 455 bool IsSafeToNotifyIME(ChangeEventType aChangeEventType) const; 456 }; 457 458 class IMENotificationSender : public AChangeEvent { 459 public: 460 explicit IMENotificationSender(IMEContentObserver* aIMEContentObserver) 461 : AChangeEvent("IMENotificationSender", aIMEContentObserver), 462 mIsRunning(false) {} 463 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override; 464 465 void Dispatch(nsIDocShell* aDocShell); 466 467 private: 468 MOZ_CAN_RUN_SCRIPT void SendFocusSet(); 469 MOZ_CAN_RUN_SCRIPT void SendSelectionChange(); 470 void SendTextChange(); 471 void SendPositionChange(); 472 void SendCompositionEventHandled(); 473 474 bool mIsRunning; 475 }; 476 477 // mQueuedSender is, it was put into the event queue but not run yet. 478 RefPtr<IMENotificationSender> mQueuedSender; 479 480 /** 481 * IMEContentObserver is a mutation observer of mRootContent. However, 482 * it needs to know the beginning of content changes and end of it too for 483 * reducing redundant computation of text offset with ContentEventHandler. 484 * Therefore, it needs helper class to listen only them since if 485 * both mutations were observed by IMEContentObserver directly, each 486 * methods need to check if the changing node is in mRootContent but it's 487 * too expensive. 488 */ 489 class DocumentObserver final : public nsStubDocumentObserver { 490 public: 491 DocumentObserver() = delete; 492 explicit DocumentObserver(IMEContentObserver& aIMEContentObserver) 493 : mIMEContentObserver(&aIMEContentObserver), mDocumentUpdating(0) { 494 SetEnabledCallbacks(nsIMutationObserver::kBeginUpdate | 495 nsIMutationObserver::kEndUpdate); 496 } 497 498 NS_DECL_CYCLE_COLLECTION_CLASS(DocumentObserver) 499 NS_DECL_CYCLE_COLLECTING_ISUPPORTS 500 NS_DECL_NSIDOCUMENTOBSERVER_BEGINUPDATE 501 NS_DECL_NSIDOCUMENTOBSERVER_ENDUPDATE 502 503 void Observe(dom::Document*); 504 void StopObserving(); 505 void Destroy(); 506 507 bool Destroyed() const { return !mIMEContentObserver; } 508 bool IsObserving() const { return mDocument != nullptr; } 509 bool IsUpdating() const { return mDocumentUpdating != 0; } 510 511 private: 512 virtual ~DocumentObserver() { Destroy(); } 513 514 RefPtr<IMEContentObserver> mIMEContentObserver; 515 RefPtr<dom::Document> mDocument; 516 uint32_t mDocumentUpdating; 517 }; 518 RefPtr<DocumentObserver> mDocumentObserver; 519 520 /** 521 * FlatTextCache stores length of flattened text starting from start of 522 * the observing node (typically editing host or the anonymous <div> of 523 * TextEditor) to: 524 * - end of mContent if it's set (IsCachingToEndOfContent() returns true) 525 * - before first content of mContainerNode if mContent is not set 526 * (IsCachingToStartOfContainer() returns true). In this case, the text 527 * length includes a line break length which is caused by the open tag of 528 * mContainerNode if and only if it's an element node and the open tag causes 529 * a line break. 530 */ 531 struct FlatTextCache { 532 public: 533 explicit FlatTextCache(const char* aInstanceName) 534 : mInstanceName(aInstanceName) {} 535 536 void Clear(const char* aCallerName); 537 538 [[nodiscard]] bool HasCache() const { return !!mContainerNode; } 539 540 /** 541 * Return true if mFlatTextLength caches flattened text length starting from 542 * start of the observing node to the end of mContent. 543 */ 544 [[nodiscard]] bool IsCachingToEndOfContent() const { 545 return mContainerNode && mContent; 546 } 547 548 /** 549 * Return true if mFlatTextLength caches flattened text length starting from 550 * start of the observing node to the start of mContainerNode. Note that if 551 * mContainerNode is an element and whose open tag causes a line break, 552 * mFlatTextLength includes the line break length too. 553 */ 554 [[nodiscard]] bool IsCachingToStartOfContainer() const { 555 return mContainerNode && !mContent; 556 } 557 558 /** 559 * Compute flattened text length starting from first content of aRootElement 560 * and ending at end of aContent. 561 * 562 * @param aContent This will be set to mContent which points the 563 * last child content node which participates in 564 * the computed mFlatTextLength. 565 * @param aRootElement The root element of the editor, i.e., editing 566 * host or the anonymous <div> in a text control. 567 * (This is required to suppress 568 * ContentEventHandler to generate a line break 569 * caused by open tag of the editable root element 570 * due to not editable. Therefore, we need to call 571 * ContentEventHandler methods with this.) 572 */ 573 [[nodiscard]] nsresult ComputeAndCacheFlatTextLengthBeforeEndOfContent( 574 const char* aCallerName, const nsIContent& aContent, 575 const dom::Element* aRootElement); 576 577 void CacheFlatTextLengthBeforeEndOfContent( 578 const char* aCallerName, const nsIContent& aContent, 579 uint32_t aFlatTextLength, const dom::Element* aRootElement); 580 581 /** 582 * Compute flattened text length starting from first content of aRootElement 583 * and ending at start of the first content of aContainer. 584 * 585 * @param aContainer This will be set to mContainer and mContent will 586 * be set to nullptr. 587 * @param aRootElement The root element of the editor, i.e., editing 588 * host or the anonymous <div> in a text control. 589 * (This is required to suppress 590 * ContentEventHandler to generate a line break 591 * caused by open tag of the editable root element 592 * due to not editable. Therefore, we need to call 593 * ContentEventHandler methods with this.) 594 */ 595 [[nodiscard]] nsresult ComputeAndCacheFlatTextLengthBeforeFirstContent( 596 const char* aCallerName, const nsINode& aContainer, 597 const dom::Element* aRootElement); 598 599 void CacheFlatTextLengthBeforeFirstContent( 600 const char* aCallerName, const nsINode& aContainer, 601 uint32_t aFlatTextLength, const dom::Element* aRootElement); 602 603 /** 604 * Return flattened text length of aContent. I.e., the length includes a 605 * line break caused by the open tag of aContent if it's an element node. 606 * 607 * @param aRemovingContent The content node which is being removed. 608 * @param aRootElement The root element of the editor, i.e., editing 609 * host or the anonymous <div> in a text control. 610 * For avoiding to generate a redundant line break 611 * at open tag of this element, this is required 612 * to call methods of ContentEventHandler. 613 * @param aForRemoval Whether aContent is about to be removed. 614 */ 615 [[nodiscard]] static Result<uint32_t, nsresult> ComputeTextLengthOfContent( 616 const nsIContent& aContent, const dom::Element* aRootElement, 617 ForRemoval = ForRemoval::No); 618 619 /** 620 * Return flattened text length of starting from first content of 621 * aRootElement and ending at before aContent (if ContentEventHandler 622 * generates a line break at open tag of aContent, the result does not 623 * contain the line break length). 624 * 625 * @param aContent The content node which is immediately after a 626 * content which you want to compute the flattened 627 * text length before end of it. 628 * @param aRootElement The root element of the editor, i.e., editing 629 * host or the anonymous <div> in a text control. 630 * For avoiding to generate a redundant line break 631 * at open tag of this element, this is required 632 * to call methods of ContentEventHandler. 633 */ 634 [[nodiscard]] static Result<uint32_t, nsresult> 635 ComputeTextLengthBeforeContent(const nsIContent& aContent, 636 const dom::Element* aRootElement); 637 638 /** 639 * Return flattened text length starting from first content of aRootElement 640 * and ending at start of the first content of aContainer. This means that 641 * if ContentEventHandler generates a line break at the open tag of 642 * aContainer, the result includes the line break length. 643 * NOTE: The difference from ComputeTextLengthBeforeContent() is, result of 644 * this method includes a line break caused by the open tag of aContainer 645 * if and only if it's an element node and ContentEventHandler generates 646 * a line break for its open tag. 647 * 648 * @param aContainer The container node which you want to compute the 649 * flattened text length before the first content 650 * of. 651 * @param aRootElement The root element of the editor, i.e., editing 652 * host or the anonymous <div> in a text control. 653 * For avoiding to generate a redundant line break 654 * at open tag of this element, this is required 655 * to call methods of ContentEventHandler. 656 */ 657 [[nodiscard]] static Result<uint32_t, nsresult> 658 ComputeTextLengthBeforeFirstContentOf(const nsINode& aContainer, 659 const dom::Element* aRootElement); 660 661 /** 662 * Return flattened text length of starting from start of aStartContent and 663 * ending at end of aEndContent. If ContentEventHandler generates a line 664 * break at open tag of aStartContent, the result includes the line break 665 * length. 666 * 667 * @param aStartContent The first content node of consecutive nodes 668 * which you want to compute flattened text length 669 * starting from. 670 * @param aEndContent The last content node of consecutive nodes 671 * which you want to compute flattened text length 672 * ending at. 673 * @param aRootElement The root element of the editor, i.e., editing 674 * host or the anonymous <div> in a text control. 675 * For avoiding to generate a redundant line break 676 * at open tag of this element, this is required 677 * to call methods of ContentEventHandler. 678 */ 679 [[nodiscard]] static Result<uint32_t, nsresult> 680 ComputeTextLengthStartOfContentToEndOfContent( 681 const nsIContent& aStartContent, const nsIContent& aEndContent, 682 const dom::Element* aRootElement); 683 684 [[nodiscard]] uint32_t GetFlatTextLength() const { return mFlatTextLength; } 685 686 /** 687 * Return text length if it's exactly cached or can compute it quickly from 688 * the cached data. aContent must not be new node which is inserted before 689 * mContent because the cached text length does not include the text length 690 * of aContent in such case. 691 */ 692 [[nodiscard]] Maybe<uint32_t> GetFlatTextLengthBeforeContent( 693 const nsIContent& aContent, const dom::Element* aRootElement, 694 ForRemoval = ForRemoval::No) const; 695 696 /** 697 * Return text length before aFirstContent if it's exactly cached or can 698 * compute it quickly from the caching data. This is called when the nodes 699 * between aFirstContent and aLastContent are inserted into the tree. 700 */ 701 [[nodiscard]] Maybe<uint32_t> GetFlatTextOffsetOnInsertion( 702 const nsIContent& aFirstContent, const nsIContent& aLastContent, 703 const dom::Element* aRootElement) const; 704 705 /** 706 * This works only in the debug build and 707 * test.ime_content_observer.assert_valid_cache pref is enabled. This 708 * checks with expensive computation, therefore, the pref is enabled only 709 * when running automated tests for editors. 710 */ 711 void AssertValidCache(const dom::Element* aRootElement) const; 712 713 /** 714 * Called when content nodes from aFirstContent to aLastContent are added. 715 * aAddedFlatTextLength may be flattened text length from start of 716 * aFirstContent to end of aLastContent if it's computed by the caller. 717 * Note that aFirstContent and aLastContent can be in different container 718 * nodes, but this method is currently called with (maybe indirect) siblings 719 * in the same container. 720 */ 721 void ContentAdded(const char* aCallerName, const nsIContent& aFirstContent, 722 const nsIContent& aLastContent, 723 const Maybe<uint32_t>& aAddedFlatTextLength, 724 const dom::Element* aRootElement); 725 726 /** 727 * Called when aContent will be removed. aFlatTextLengthOfContent is 728 * flattened text length of aContent. 729 */ 730 void ContentWillBeRemoved(const nsIContent& aContent, 731 uint32_t aFlatTextLengthOfContent, 732 const dom::Element* aRootElement); 733 734 public: 735 // mContainerNode is parent node of mContent when it's cached. 736 nsCOMPtr<nsINode> mContainerNode; 737 // mContent points to the last child which participates in the current 738 // mFlatTextLength. If this is nullptr, mFlatTextLength means that it 739 // length before the first content of mContainerNode, i.e., including the 740 // line break of that caused by the open tag of mContainerNode. 741 nsCOMPtr<nsIContent> mContent; 742 743 private: 744 // Length of flat text generated from contents between the start of the 745 // observing node (typically editing host or the anonymous <div> of 746 // TextEditor) and the end of mContent. 747 uint32_t mFlatTextLength = 0; 748 MOZ_DEFINE_DBG(FlatTextCache, mContainerNode, mContent, mFlatTextLength); 749 750 const char* mInstanceName; 751 }; 752 753 friend std::ostream& operator<<(std::ostream& aStream, 754 const FlatTextCache& aCache); 755 756 // mEndOfAddedTextCache caches text length from the start of the observing 757 // node to the end of the last added content only while an edit action is 758 // being handled by the editor and no other mutation (e.g., removing node) 759 // occur. 760 FlatTextCache mEndOfAddedTextCache = FlatTextCache("mEndOfAddedTextCache"); 761 // mStartOfRemovingTextRangeCache caches text length from the start of the 762 // observing node to the start of the last removed content only while an edit 763 // action is being handled by the editor and no other mutation (e.g., adding 764 // node) occur. In other words, this caches text length before end of 765 // mContent or before first child of mContainerNode. 766 FlatTextCache mStartOfRemovingTextRangeCache = 767 FlatTextCache("mStartOfRemovingTextRangeCache"); 768 769 /** 770 * Caches the DOM node ranges with storing the first node and the last node. 771 * This is designed for mAddedContentCache. See comment at declaration of it 772 * for the detail. 773 */ 774 struct AddedContentCache { 775 /** 776 * Clear the range. Callers should call this with __FUNCTION__ which will be 777 * used to log which caller did it. 778 */ 779 void Clear(const char* aCallerName); 780 781 [[nodiscard]] bool HasCache() const { return mFirst && mLast; } 782 783 /** 784 * Return true if aFirstContent and aLastContent can be merged into the 785 * cached range. This should be called only when the instance caches 786 * something. 787 */ 788 [[nodiscard]] bool CanMergeWith(const nsIContent& aFirstContent, 789 const nsIContent& aLastContent, 790 const dom::Element* aRootElement) const; 791 792 /** 793 * Return true if aContent is in the cached range. aContent can be not 794 * a child of the common container of the caching range. 795 */ 796 [[nodiscard]] bool IsInRange(const nsIContent& aContent, 797 const dom::Element* aRootElement) const; 798 799 /** 800 * Try to cache the range represented by aFirstContent and aLastContent. 801 * If there is a cache, this will extend the caching range to contain 802 * the new range. 803 * 804 * @return true if cached, otherwise, false. 805 */ 806 bool TryToCache(const nsIContent& aFirstContent, 807 const nsIContent& aLastContent, 808 const dom::Element* aRootElement); 809 810 /** 811 * Compute offset and length of the cached range before the nodes between 812 * aNewFirstContent and aNewLastContent are inserted. 813 * 814 * @return The first one is offset, the other is length. 815 */ 816 [[nodiscard]] Result<std::pair<uint32_t, uint32_t>, nsresult> 817 ComputeFlatTextRangeBeforeInsertingNewContent( 818 const nsIContent& aNewFirstContent, const nsIContent& aNewLastContent, 819 const dom::Element* aRootElement, 820 OffsetAndLengthAdjustments& aDifferences) const; 821 822 MOZ_DEFINE_DBG(AddedContentCache, mFirst, mLast); 823 824 nsCOMPtr<nsIContent> mFirst; 825 nsCOMPtr<nsIContent> mLast; 826 }; 827 828 // Caches the first node and the last node of new inserted nodes while editor 829 // handles an editing command/operation. Therefore, the range is always in 830 // the same container node. So, the range means that the direct siblings 831 // between the first node and the last node are the inserted nodes, but not 832 // yet post a text change notification. 833 // FYI: This is cleared when editor ends handling current edit 834 // operation/command. Therefore, the strong pointers in this member don't 835 // need to be added to the cycle collection. 836 AddedContentCache mAddedContentCache; 837 838 TextChangeData mTextChangeData; 839 840 // mSelectionData is the last selection data which was notified. The 841 // selection information is modified by UpdateSelectionCache(). The reason 842 // of the selection change is modified by MaybeNotifyIMEOfSelectionChange(). 843 SelectionChangeData mSelectionData; 844 845 EventStateManager* mESM = nullptr; 846 847 const IMENotificationRequests* mIMENotificationRequests = nullptr; 848 int64_t mPreCharacterDataChangeLength = -1; 849 uint32_t mSuppressNotifications = 0; 850 851 // If the observing editor is a text control's one, this is set to the value 852 // length. 853 uint32_t mTextControlValueLength = 0; 854 855 // mSendingNotification is a notification which is now sending from 856 // IMENotificationSender. When the value is NOTIFY_IME_OF_NOTHING, it's 857 // not sending any notification. 858 IMEMessage mSendingNotification = widget::NOTIFY_IME_OF_NOTHING; 859 860 bool mIsObserving = false; 861 bool mIsTextControl = false; 862 bool mIMEHasFocus = false; 863 bool mNeedsToNotifyIMEOfFocusSet = false; 864 bool mNeedsToNotifyIMEOfTextChange = false; 865 bool mNeedsToNotifyIMEOfSelectionChange = false; 866 bool mNeedsToNotifyIMEOfPositionChange = false; 867 bool mNeedsToNotifyIMEOfCompositionEventHandled = false; 868 // mIsHandlingQueryContentEvent is true when IMEContentObserver is handling 869 // WidgetQueryContentEvent with ContentEventHandler. 870 bool mIsHandlingQueryContentEvent = false; 871 }; 872 873 } // namespace mozilla 874 875 #endif // mozilla_IMEContentObserver_h