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/Selection.h" 13 #include "nsCOMPtr.h" 14 #include "nsCycleCollectionParticipant.h" 15 #include "nsIDocShell.h" // XXX Why does only this need to be included here? 16 #include "nsIReflowObserver.h" 17 #include "nsIScrollObserver.h" 18 #include "nsIWidget.h" 19 #include "nsStubDocumentObserver.h" 20 #include "nsStubMutationObserver.h" 21 #include "nsThreadUtils.h" 22 #include "nsWeakReference.h" 23 24 class nsIContent; 25 class nsINode; 26 class nsPresContext; 27 28 namespace mozilla { 29 30 class EventStateManager; 31 class TextComposition; 32 33 namespace dom { 34 class Selection; 35 } // namespace dom 36 37 // IMEContentObserver notifies widget of any text and selection changes 38 // in the currently focused editor 39 class IMEContentObserver final : public nsStubMutationObserver, 40 public nsIReflowObserver, 41 public nsIScrollObserver, 42 public nsSupportsWeakReference { 43 public: 44 typedef widget::IMENotification::SelectionChangeData SelectionChangeData; 45 typedef widget::IMENotification::TextChangeData TextChangeData; 46 typedef widget::IMENotification::TextChangeDataBase TextChangeDataBase; 47 typedef widget::IMENotificationRequests IMENotificationRequests; 48 typedef widget::IMEMessage IMEMessage; 49 50 IMEContentObserver(); 51 52 NS_DECL_CYCLE_COLLECTING_ISUPPORTS 53 NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(IMEContentObserver, 54 nsIReflowObserver) 55 NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATAWILLCHANGE 56 NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED 57 NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED 58 NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED 59 NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED 60 NS_DECL_NSIREFLOWOBSERVER 61 62 // nsIScrollObserver 63 virtual void ScrollPositionChanged() override; 64 65 /** 66 * OnSelectionChange() is called when selection is changed in the editor. 67 */ 68 void OnSelectionChange(dom::Selection& aSelection); 69 70 MOZ_CAN_RUN_SCRIPT bool OnMouseButtonEvent(nsPresContext* aPresContext, 71 WidgetMouseEvent* aMouseEvent); 72 73 MOZ_CAN_RUN_SCRIPT nsresult 74 HandleQueryContentEvent(WidgetQueryContentEvent* aEvent); 75 76 /** 77 * Init() initializes the instance, i.e., retrieving necessary objects and 78 * starts to observe something. 79 * Be aware, callers of this method need to guarantee that the instance 80 * won't be released during calling this. 81 * 82 * @param aWidget The widget which can access native IME. 83 * @param aPresContext The PresContext which has aContent. 84 * @param aContent An editable element or nullptr if this will observe 85 * design mode document. 86 * @param aEditorBase The editor which is associated with aContent. 87 */ 88 MOZ_CAN_RUN_SCRIPT void Init(nsIWidget& aWidget, nsPresContext& aPresContext, 89 nsIContent* aContent, EditorBase& aEditorBase); 90 91 /** 92 * Destroy() finalizes the instance, i.e., stops observing contents and 93 * clearing the members. 94 * Be aware, callers of this method need to guarantee that the instance 95 * won't be released during calling this. 96 */ 97 void Destroy(); 98 99 /** 100 * Returns false if the instance refers some objects and observing them. 101 * Otherwise, true. 102 */ 103 bool Destroyed() const; 104 105 /** 106 * IMEContentObserver is stored by EventStateManager during observing. 107 * DisconnectFromEventStateManager() is called when EventStateManager stops 108 * storing the instance. 109 */ 110 void DisconnectFromEventStateManager(); 111 112 /** 113 * MaybeReinitialize() tries to restart to observe the editor's root node. 114 * This is useful when the editor is reframed and all children are replaced 115 * with new node instances. 116 * Be aware, callers of this method need to guarantee that the instance 117 * won't be released during calling this. 118 * 119 * @return Returns true if the instance is managing the content. 120 * Otherwise, false. 121 */ 122 MOZ_CAN_RUN_SCRIPT bool MaybeReinitialize(nsIWidget& aWidget, 123 nsPresContext& aPresContext, 124 nsIContent* aContent, 125 EditorBase& aEditorBase); 126 127 bool IsManaging(nsPresContext* aPresContext, nsIContent* aContent) const; 128 bool IsManaging(const TextComposition* aTextComposition) const; WasInitializedWith(const EditorBase & aEditorBase)129 bool WasInitializedWith(const EditorBase& aEditorBase) const { 130 return mEditorBase == &aEditorBase; 131 } 132 bool IsEditorHandlingEventForComposition() const; KeepAliveDuringDeactive()133 bool KeepAliveDuringDeactive() const { 134 return mIMENotificationRequests && 135 mIMENotificationRequests->WantDuringDeactive(); 136 } GetWidget()137 nsIWidget* GetWidget() const { return mWidget; } 138 void SuppressNotifyingIME(); 139 void UnsuppressNotifyingIME(); 140 nsPresContext* GetPresContext() const; 141 nsresult GetSelectionAndRoot(dom::Selection** aSelection, 142 nsIContent** aRoot) const; 143 144 /** 145 * TryToFlushPendingNotifications() should be called when pending events 146 * should be flushed. This tries to run the queued IMENotificationSender. 147 * Doesn't do anything in child processes where flushing happens 148 * asynchronously unless aAllowAsync is false. 149 */ 150 void TryToFlushPendingNotifications(bool aAllowAsync); 151 152 /** 153 * MaybeNotifyCompositionEventHandled() posts composition event handled 154 * notification into the pseudo queue. 155 */ 156 void MaybeNotifyCompositionEventHandled(); 157 158 /** 159 * Following methods are called when the editor: 160 * - an edit action handled. 161 * - before handling an edit action. 162 * - canceled handling an edit action after calling BeforeEditAction(). 163 */ 164 void OnEditActionHandled(); 165 void BeforeEditAction(); 166 void CancelEditAction(); 167 168 private: 169 ~IMEContentObserver() = default; 170 171 enum State { 172 eState_NotObserving, 173 eState_Initializing, 174 eState_StoppedObserving, 175 eState_Observing 176 }; 177 State GetState() const; 178 MOZ_CAN_RUN_SCRIPT bool InitWithEditor(nsPresContext& aPresContext, 179 nsIContent* aContent, 180 EditorBase& aEditorBase); 181 void OnIMEReceivedFocus(); 182 void Clear(); 183 bool IsObservingContent(nsPresContext* aPresContext, 184 nsIContent* aContent) const; 185 bool IsReflowLocked() const; 186 bool IsSafeToNotifyIME() const; 187 bool IsEditorComposing() const; 188 189 // Following methods are called by DocumentObserver when 190 // beginning to update the contents and ending updating the contents. 191 void BeginDocumentUpdate(); 192 void EndDocumentUpdate(); 193 194 // Following methods manages added nodes during a document change. 195 196 /** 197 * MaybeNotifyIMEOfAddedTextDuringDocumentChange() may send text change 198 * notification caused by the nodes added between mFirstAddedContent in 199 * mFirstAddedContainer and mLastAddedContent in 200 * mLastAddedContainer and forgets the range. 201 */ 202 void MaybeNotifyIMEOfAddedTextDuringDocumentChange(); 203 204 /** 205 * IsInDocumentChange() returns true while the DOM tree is being modified 206 * with mozAutoDocUpdate. E.g., it's being modified by setting innerHTML or 207 * insertAdjacentHTML(). This returns false when user types something in 208 * the focused editor editor. 209 */ IsInDocumentChange()210 bool IsInDocumentChange() const { 211 return mDocumentObserver && mDocumentObserver->IsUpdating(); 212 } 213 214 /** 215 * Forget the range of added nodes during a document change. 216 */ 217 void ClearAddedNodesDuringDocumentChange(); 218 219 /** 220 * HasAddedNodesDuringDocumentChange() returns true when this stores range 221 * of nodes which were added into the DOM tree during a document change but 222 * have not been sent to IME. Note that this should always return false when 223 * IsInDocumentChange() returns false. 224 */ HasAddedNodesDuringDocumentChange()225 bool HasAddedNodesDuringDocumentChange() const { 226 return mFirstAddedContainer && mLastAddedContainer; 227 } 228 229 /** 230 * Returns true if the passed-in node in aParent is the next node of 231 * mLastAddedContent in pre-order tree traversal of the DOM. 232 */ 233 bool IsNextNodeOfLastAddedNode(nsINode* aParent, nsIContent* aChild) const; 234 235 void PostFocusSetNotification(); 236 void MaybeNotifyIMEOfFocusSet(); 237 void PostTextChangeNotification(); 238 void MaybeNotifyIMEOfTextChange(const TextChangeDataBase& aTextChangeData); 239 void CancelNotifyingIMEOfTextChange(); 240 void PostSelectionChangeNotification(); 241 void MaybeNotifyIMEOfSelectionChange(bool aCausedByComposition, 242 bool aCausedBySelectionEvent, 243 bool aOccurredDuringComposition); 244 void PostPositionChangeNotification(); 245 void MaybeNotifyIMEOfPositionChange(); 246 void CancelNotifyingIMEOfPositionChange(); 247 void PostCompositionEventHandledNotification(); 248 249 void NotifyContentAdded(nsINode* aContainer, nsIContent* aFirstContent, 250 nsIContent* aLastContent); 251 void ObserveEditableNode(); 252 /** 253 * NotifyIMEOfBlur() notifies IME of blur. 254 */ 255 void NotifyIMEOfBlur(); 256 /** 257 * UnregisterObservers() unregisters all listeners and observers. 258 */ 259 void UnregisterObservers(); 260 void FlushMergeableNotifications(); NeedsTextChangeNotification()261 bool NeedsTextChangeNotification() const { 262 return mIMENotificationRequests && 263 mIMENotificationRequests->WantTextChange(); 264 } NeedsPositionChangeNotification()265 bool NeedsPositionChangeNotification() const { 266 return mIMENotificationRequests && 267 mIMENotificationRequests->WantPositionChanged(); 268 } ClearPendingNotifications()269 void ClearPendingNotifications() { 270 mNeedsToNotifyIMEOfFocusSet = false; 271 mNeedsToNotifyIMEOfTextChange = false; 272 mNeedsToNotifyIMEOfSelectionChange = false; 273 mNeedsToNotifyIMEOfPositionChange = false; 274 mNeedsToNotifyIMEOfCompositionEventHandled = false; 275 mTextChangeData.Clear(); 276 } NeedsToNotifyIMEOfSomething()277 bool NeedsToNotifyIMEOfSomething() const { 278 return mNeedsToNotifyIMEOfFocusSet || mNeedsToNotifyIMEOfTextChange || 279 mNeedsToNotifyIMEOfSelectionChange || 280 mNeedsToNotifyIMEOfPositionChange || 281 mNeedsToNotifyIMEOfCompositionEventHandled; 282 } 283 284 /** 285 * UpdateSelectionCache() updates mSelectionData with the latest selection. 286 * This should be called only when IsSafeToNotifyIME() returns true. 287 */ 288 MOZ_CAN_RUN_SCRIPT bool UpdateSelectionCache(bool aRequireFlush = true); 289 290 nsCOMPtr<nsIWidget> mWidget; 291 // mFocusedWidget has the editor observed by the instance. E.g., if the 292 // focused editor is in XUL panel, this should be the widget of the panel. 293 // On the other hand, mWidget is its parent which handles IME. 294 nsCOMPtr<nsIWidget> mFocusedWidget; 295 RefPtr<dom::Selection> mSelection; 296 nsCOMPtr<nsIContent> mRootContent; 297 nsCOMPtr<nsINode> mEditableNode; 298 nsCOMPtr<nsIDocShell> mDocShell; 299 RefPtr<EditorBase> mEditorBase; 300 301 /** 302 * Helper classes to notify IME. 303 */ 304 305 class AChangeEvent : public Runnable { 306 protected: 307 enum ChangeEventType { 308 eChangeEventType_Focus, 309 eChangeEventType_Selection, 310 eChangeEventType_Text, 311 eChangeEventType_Position, 312 eChangeEventType_CompositionEventHandled 313 }; 314 AChangeEvent(const char * aName,IMEContentObserver * aIMEContentObserver)315 explicit AChangeEvent(const char* aName, 316 IMEContentObserver* aIMEContentObserver) 317 : Runnable(aName), 318 mIMEContentObserver(do_GetWeakReference( 319 static_cast<nsIReflowObserver*>(aIMEContentObserver))) { 320 MOZ_ASSERT(aIMEContentObserver); 321 } 322 GetObserver()323 already_AddRefed<IMEContentObserver> GetObserver() const { 324 nsCOMPtr<nsIReflowObserver> observer = 325 do_QueryReferent(mIMEContentObserver); 326 return observer.forget().downcast<IMEContentObserver>(); 327 } 328 329 nsWeakPtr mIMEContentObserver; 330 331 /** 332 * CanNotifyIME() checks if mIMEContentObserver can and should notify IME. 333 */ 334 bool CanNotifyIME(ChangeEventType aChangeEventType) const; 335 336 /** 337 * IsSafeToNotifyIME() checks if it's safe to noitify IME. 338 */ 339 bool IsSafeToNotifyIME(ChangeEventType aChangeEventType) const; 340 }; 341 342 class IMENotificationSender : public AChangeEvent { 343 public: IMENotificationSender(IMEContentObserver * aIMEContentObserver)344 explicit IMENotificationSender(IMEContentObserver* aIMEContentObserver) 345 : AChangeEvent("IMENotificationSender", aIMEContentObserver), 346 mIsRunning(false) {} 347 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override; 348 349 void Dispatch(nsIDocShell* aDocShell); 350 351 private: 352 MOZ_CAN_RUN_SCRIPT void SendFocusSet(); 353 MOZ_CAN_RUN_SCRIPT void SendSelectionChange(); 354 void SendTextChange(); 355 void SendPositionChange(); 356 void SendCompositionEventHandled(); 357 358 bool mIsRunning; 359 }; 360 361 // mQueuedSender is, it was put into the event queue but not run yet. 362 RefPtr<IMENotificationSender> mQueuedSender; 363 364 /** 365 * IMEContentObserver is a mutation observer of mRootContent. However, 366 * it needs to know the beginning of content changes and end of it too for 367 * reducing redundant computation of text offset with ContentEventHandler. 368 * Therefore, it needs helper class to listen only them since if 369 * both mutations were observed by IMEContentObserver directly, each 370 * methods need to check if the changing node is in mRootContent but it's 371 * too expensive. 372 */ 373 class DocumentObserver final : public nsStubDocumentObserver { 374 public: DocumentObserver(IMEContentObserver & aIMEContentObserver)375 explicit DocumentObserver(IMEContentObserver& aIMEContentObserver) 376 : mIMEContentObserver(&aIMEContentObserver), mDocumentUpdating(0) {} 377 378 NS_DECL_CYCLE_COLLECTION_CLASS(DocumentObserver) 379 NS_DECL_CYCLE_COLLECTING_ISUPPORTS 380 NS_DECL_NSIDOCUMENTOBSERVER_BEGINUPDATE 381 NS_DECL_NSIDOCUMENTOBSERVER_ENDUPDATE 382 383 void Observe(dom::Document*); 384 void StopObserving(); 385 void Destroy(); 386 Destroyed()387 bool Destroyed() const { return !mIMEContentObserver; } IsObserving()388 bool IsObserving() const { return mDocument != nullptr; } IsUpdating()389 bool IsUpdating() const { return mDocumentUpdating != 0; } 390 391 private: 392 DocumentObserver() = delete; ~DocumentObserver()393 virtual ~DocumentObserver() { Destroy(); } 394 395 RefPtr<IMEContentObserver> mIMEContentObserver; 396 RefPtr<dom::Document> mDocument; 397 uint32_t mDocumentUpdating; 398 }; 399 RefPtr<DocumentObserver> mDocumentObserver; 400 401 /** 402 * FlatTextCache stores flat text length from start of the content to 403 * mNodeOffset of mContainerNode. 404 */ 405 struct FlatTextCache { 406 // mContainerNode and mNode represent a point in DOM tree. E.g., 407 // if mContainerNode is a div element, mNode is a child. 408 nsCOMPtr<nsINode> mContainerNode; 409 // mNode points to the last child which participates in the current 410 // mFlatTextLength. If mNode is null, then that means that the end point for 411 // mFlatTextLength is immediately before the first child of mContainerNode. 412 nsCOMPtr<nsINode> mNode; 413 // Length of flat text generated from contents between the start of content 414 // and a child node whose index is mNodeOffset of mContainerNode. 415 uint32_t mFlatTextLength; 416 FlatTextCacheFlatTextCache417 FlatTextCache() : mFlatTextLength(0) {} 418 ClearFlatTextCache419 void Clear() { 420 mContainerNode = nullptr; 421 mNode = nullptr; 422 mFlatTextLength = 0; 423 } 424 CacheFlatTextCache425 void Cache(nsINode* aContainer, nsINode* aNode, uint32_t aFlatTextLength) { 426 MOZ_ASSERT(aContainer, "aContainer must not be null"); 427 MOZ_ASSERT(!aNode || aNode->GetParentNode() == aContainer, 428 "aNode must be either null or a child of aContainer"); 429 mContainerNode = aContainer; 430 mNode = aNode; 431 mFlatTextLength = aFlatTextLength; 432 } 433 MatchFlatTextCache434 bool Match(nsINode* aContainer, nsINode* aNode) const { 435 return aContainer == mContainerNode && aNode == mNode; 436 } 437 }; 438 // mEndOfAddedTextCache caches text length from the start of content to 439 // the end of the last added content only while an edit action is being 440 // handled by the editor and no other mutation (e.g., removing node) 441 // occur. 442 FlatTextCache mEndOfAddedTextCache; 443 // mStartOfRemovingTextRangeCache caches text length from the start of content 444 // to the start of the last removed content only while an edit action is being 445 // handled by the editor and no other mutation (e.g., adding node) occur. 446 FlatTextCache mStartOfRemovingTextRangeCache; 447 448 // mFirstAddedContainer is parent node of first added node in current 449 // document change. So, this is not nullptr only when a node was added 450 // during a document change and the change has not been included into 451 // mTextChangeData yet. 452 // Note that this shouldn't be in cycle collection since this is not nullptr 453 // only during a document change. 454 nsCOMPtr<nsINode> mFirstAddedContainer; 455 // mLastAddedContainer is parent node of last added node in current 456 // document change. So, this is not nullptr only when a node was added 457 // during a document change and the change has not been included into 458 // mTextChangeData yet. 459 // Note that this shouldn't be in cycle collection since this is not nullptr 460 // only during a document change. 461 nsCOMPtr<nsINode> mLastAddedContainer; 462 463 // mFirstAddedContent is the first node added in mFirstAddedContainer. 464 nsCOMPtr<nsIContent> mFirstAddedContent; 465 // mLastAddedContent is the last node added in mLastAddedContainer; 466 nsCOMPtr<nsIContent> mLastAddedContent; 467 468 TextChangeData mTextChangeData; 469 470 // mSelectionData is the last selection data which was notified. The 471 // selection information is modified by UpdateSelectionCache(). The reason 472 // of the selection change is modified by MaybeNotifyIMEOfSelectionChange(). 473 SelectionChangeData mSelectionData; 474 475 EventStateManager* mESM; 476 477 const IMENotificationRequests* mIMENotificationRequests; 478 uint32_t mSuppressNotifications; 479 int64_t mPreCharacterDataChangeLength; 480 481 // mSendingNotification is a notification which is now sending from 482 // IMENotificationSender. When the value is NOTIFY_IME_OF_NOTHING, it's 483 // not sending any notification. 484 IMEMessage mSendingNotification; 485 486 bool mIsObserving; 487 bool mIMEHasFocus; 488 bool mNeedsToNotifyIMEOfFocusSet; 489 bool mNeedsToNotifyIMEOfTextChange; 490 bool mNeedsToNotifyIMEOfSelectionChange; 491 bool mNeedsToNotifyIMEOfPositionChange; 492 bool mNeedsToNotifyIMEOfCompositionEventHandled; 493 // mIsHandlingQueryContentEvent is true when IMEContentObserver is handling 494 // WidgetQueryContentEvent with ContentEventHandler. 495 bool mIsHandlingQueryContentEvent; 496 }; 497 498 } // namespace mozilla 499 500 #endif // mozilla_IMEContentObserver_h 501