IMEContentObserver.cpp (120253B)
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 #include "IMEContentObserver.h" 8 9 #include "ContentEventHandler.h" 10 #include "WritingModes.h" 11 #include "mozilla/Assertions.h" 12 #include "mozilla/AsyncEventDispatcher.h" 13 #include "mozilla/AutoRestore.h" 14 #include "mozilla/ErrorResult.h" 15 #include "mozilla/EventStateManager.h" 16 #include "mozilla/IMEStateManager.h" 17 #include "mozilla/Logging.h" 18 #include "mozilla/MouseEvents.h" 19 #include "mozilla/PresShell.h" 20 #include "mozilla/StaticPrefs_test.h" 21 #include "mozilla/TextComposition.h" 22 #include "mozilla/TextControlElement.h" 23 #include "mozilla/TextEvents.h" 24 #include "mozilla/dom/AncestorIterator.h" 25 #include "mozilla/dom/Document.h" 26 #include "mozilla/dom/Element.h" 27 #include "mozilla/dom/Selection.h" 28 #include "nsAtom.h" 29 #include "nsContentUtils.h" 30 #include "nsDocShell.h" 31 #include "nsGkAtoms.h" 32 #include "nsIContent.h" 33 #include "nsIFrame.h" 34 #include "nsINode.h" 35 #include "nsISelectionController.h" 36 #include "nsISupports.h" 37 #include "nsIWeakReferenceUtils.h" 38 #include "nsIWidget.h" 39 #include "nsPresContext.h" 40 #include "nsRange.h" 41 #include "nsRefreshDriver.h" 42 #include "nsString.h" 43 44 namespace mozilla { 45 46 using RawNodePosition = ContentEventHandler::RawNodePosition; 47 48 using namespace dom; 49 using namespace widget; 50 51 LazyLogModule sIMECOLog("IMEContentObserver"); 52 LazyLogModule sCacheLog("IMEContentObserverCache"); 53 54 static const char* ToChar(bool aBool) { return aBool ? "true" : "false"; } 55 56 static const char* ShortenFunctionName(const char* aFunctionName) { 57 const nsDependentCString name(aFunctionName); 58 const int32_t startIndexOfIMEContentObserverPrefix = 59 name.Find("IMEContentObserver::", 0); 60 if (startIndexOfIMEContentObserverPrefix >= 0) { 61 return aFunctionName + startIndexOfIMEContentObserverPrefix + 62 strlen("IMEContentObserver::"); 63 } 64 return aFunctionName; 65 } 66 67 /****************************************************************************** 68 * mozilla::IMEContentObserver 69 ******************************************************************************/ 70 71 NS_IMPL_CYCLE_COLLECTION_CLASS(IMEContentObserver) 72 73 // Note that we don't need to add mFirstAddedContainer nor 74 // mLastAddedContainer to cycle collection because they are non-null only 75 // during short time and shouldn't be touched while they are non-null. 76 77 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IMEContentObserver) 78 nsAutoScriptBlocker scriptBlocker; 79 80 tmp->NotifyIMEOfBlur(); 81 tmp->UnregisterObservers(); 82 83 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelection) 84 NS_IMPL_CYCLE_COLLECTION_UNLINK(mRootElement) 85 NS_IMPL_CYCLE_COLLECTION_UNLINK(mRootEditableNodeOrTextControlElement) 86 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocShell) 87 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEditorBase) 88 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentObserver) 89 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEndOfAddedTextCache.mContainerNode) 90 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEndOfAddedTextCache.mContent) 91 NS_IMPL_CYCLE_COLLECTION_UNLINK(mStartOfRemovingTextRangeCache.mContainerNode) 92 NS_IMPL_CYCLE_COLLECTION_UNLINK(mStartOfRemovingTextRangeCache.mContent) 93 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE 94 95 tmp->mIMENotificationRequests = nullptr; 96 tmp->mESM = nullptr; 97 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 98 99 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IMEContentObserver) 100 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWidget) 101 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFocusedWidget) 102 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelection) 103 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRootElement) 104 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRootEditableNodeOrTextControlElement) 105 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocShell) 106 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEditorBase) 107 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentObserver) 108 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEndOfAddedTextCache.mContainerNode) 109 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEndOfAddedTextCache.mContent) 110 NS_IMPL_CYCLE_COLLECTION_TRAVERSE( 111 mStartOfRemovingTextRangeCache.mContainerNode) 112 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStartOfRemovingTextRangeCache.mContent) 113 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 114 115 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IMEContentObserver) 116 NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) 117 NS_INTERFACE_MAP_ENTRY(nsIReflowObserver) 118 NS_INTERFACE_MAP_ENTRY(nsIScrollObserver) 119 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) 120 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIReflowObserver) 121 NS_INTERFACE_MAP_END 122 123 NS_IMPL_CYCLE_COLLECTING_ADDREF(IMEContentObserver) 124 NS_IMPL_CYCLE_COLLECTING_RELEASE(IMEContentObserver) 125 126 IMEContentObserver::IMEContentObserver() { 127 #ifdef DEBUG 128 // TODO: Make this test as GTest. 129 mTextChangeData.Test(); 130 #endif 131 } 132 133 void IMEContentObserver::Init(nsIWidget& aWidget, nsPresContext& aPresContext, 134 Element* aElement, EditorBase& aEditorBase) { 135 State state = GetState(); 136 if (NS_WARN_IF(state == eState_Observing)) { 137 return; // Nothing to do. 138 } 139 140 bool firstInitialization = state != eState_StoppedObserving; 141 if (!firstInitialization) { 142 // If this is now trying to initialize with new contents, all observers 143 // should be registered again for simpler implementation. 144 UnregisterObservers(); 145 Clear(); 146 } 147 148 mESM = aPresContext.EventStateManager(); 149 mESM->OnStartToObserveContent(this); 150 151 mWidget = &aWidget; 152 mIMENotificationRequests = &mWidget->IMENotificationRequestsRef(); 153 154 if (!InitWithEditor(aPresContext, aElement, aEditorBase)) { 155 MOZ_LOG(sIMECOLog, LogLevel::Error, 156 ("0x%p Init() FAILED, due to InitWithEditor() " 157 "failure", 158 this)); 159 Clear(); 160 return; 161 } 162 163 if (firstInitialization) { 164 // Now, try to send NOTIFY_IME_OF_FOCUS to IME via the widget. 165 MaybeNotifyIMEOfFocusSet(); 166 // When this is called first time, IME has not received NOTIFY_IME_OF_FOCUS 167 // yet since NOTIFY_IME_OF_FOCUS will be sent to widget asynchronously. 168 // So, we need to do nothing here. After NOTIFY_IME_OF_FOCUS has been 169 // sent, OnIMEReceivedFocus() will be called and content, selection and/or 170 // position changes will be observed 171 return; 172 } 173 174 // When this is called after editor reframing (i.e., the root editable node 175 // is also recreated), IME has usually received NOTIFY_IME_OF_FOCUS. In this 176 // case, we need to restart to observe content, selection and/or position 177 // changes in new root editable node. 178 ObserveEditableNode(); 179 180 if (!NeedsToNotifyIMEOfSomething()) { 181 return; 182 } 183 184 // Some change events may wait to notify IME because this was being 185 // initialized. It is the time to flush them. 186 FlushMergeableNotifications(); 187 } 188 189 void IMEContentObserver::OnIMEReceivedFocus() { 190 // While Init() notifies IME of focus, pending layout may be flushed 191 // because the notification may cause querying content. Then, recursive 192 // call of Init() with the latest content may occur. In such case, we 193 // shouldn't keep first initialization which notified IME of focus. 194 if (GetState() != eState_Initializing) { 195 MOZ_LOG(sIMECOLog, LogLevel::Warning, 196 ("0x%p OnIMEReceivedFocus(), " 197 "but the state is not \"initializing\", so does nothing", 198 this)); 199 return; 200 } 201 202 // NOTIFY_IME_OF_FOCUS might cause recreating IMEContentObserver 203 // instance via IMEStateManager::UpdateIMEState(). So, this 204 // instance might already have been destroyed, check it. 205 if (!mRootElement) { 206 MOZ_LOG(sIMECOLog, LogLevel::Warning, 207 ("0x%p OnIMEReceivedFocus(), " 208 "but mRootElement has already been cleared, so does nothing", 209 this)); 210 return; 211 } 212 213 // Start to observe which is needed by IME when IME actually has focus. 214 ObserveEditableNode(); 215 216 if (!NeedsToNotifyIMEOfSomething()) { 217 return; 218 } 219 220 // Some change events may wait to notify IME because this was being 221 // initialized. It is the time to flush them. 222 FlushMergeableNotifications(); 223 } 224 225 bool IMEContentObserver::InitWithEditor(nsPresContext& aPresContext, 226 Element* aElement, 227 EditorBase& aEditorBase) { 228 mRootEditableNodeOrTextControlElement = 229 aEditorBase.IsTextEditor() 230 ? aEditorBase.GetExposedRoot() 231 : IMEContentObserver::GetMostDistantInclusiveEditableAncestorNode( 232 aPresContext, aElement); 233 if (NS_WARN_IF(!mRootEditableNodeOrTextControlElement)) { 234 return false; 235 } 236 237 mEditorBase = &aEditorBase; 238 239 RefPtr<PresShell> presShell = aPresContext.GetPresShell(); 240 241 // get selection and root content 242 nsCOMPtr<nsISelectionController> selCon; 243 if (mRootEditableNodeOrTextControlElement->IsElement()) { 244 selCon = aEditorBase.GetSelectionController(); 245 if (NS_WARN_IF(!selCon)) { 246 return false; 247 } 248 } else { 249 MOZ_ASSERT(mRootEditableNodeOrTextControlElement->IsDocument()); 250 selCon = presShell; 251 if (NS_WARN_IF(!selCon)) { 252 return false; 253 } 254 } 255 256 mSelection = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL); 257 if (NS_WARN_IF(!mSelection)) { 258 return false; 259 } 260 261 if (mEditorBase->IsTextEditor()) { 262 mRootElement = mEditorBase->GetRoot(); // The anonymous <div> 263 MOZ_ASSERT(mRootElement); 264 MOZ_ASSERT(mRootElement->GetFirstChild()); 265 if (auto* text = Text::FromNodeOrNull( 266 mRootElement ? mRootElement->GetFirstChild() : nullptr)) { 267 mTextControlValueLength = ContentEventHandler::GetNativeTextLength(*text); 268 } 269 mIsTextControl = true; 270 } else if (const nsRange* selRange = mSelection->GetRangeAt(0)) { 271 MOZ_ASSERT(!mIsTextControl); 272 if (NS_WARN_IF(!selRange->GetStartContainer())) { 273 return false; 274 } 275 276 // If an editing host has focus, mRootElement is it. 277 // Otherwise, if we're in the design mode, mRootElement is the <body> if 278 // there is and startContainer is not outside of the <body>. Otherwise, the 279 // document element is used instead. 280 nsCOMPtr<nsINode> startContainer = selRange->GetStartContainer(); 281 mRootElement = 282 Element::FromNodeOrNull(startContainer->GetSelectionRootContent( 283 presShell, nsINode::IgnoreOwnIndependentSelection::Yes, 284 nsINode::AllowCrossShadowBoundary::No)); 285 } else { 286 MOZ_ASSERT(!mIsTextControl); 287 // If an editing host has focus, mRootElement is it. 288 // Otherwise, if we're in the design mode, mRootElement is the <body> if 289 // there is. Otherwise, the document element is used instead. 290 const OwningNonNull<nsINode> rootEditableNode( 291 *mRootEditableNodeOrTextControlElement); 292 mRootElement = 293 Element::FromNodeOrNull(rootEditableNode->GetSelectionRootContent( 294 presShell, nsINode::IgnoreOwnIndependentSelection::Yes, 295 nsINode::AllowCrossShadowBoundary::No)); 296 } 297 if (!mRootElement && mRootEditableNodeOrTextControlElement->IsDocument()) { 298 // The document node is editable, but there are no contents, this document 299 // is not editable. 300 return false; 301 } 302 303 if (NS_WARN_IF(!mRootElement)) { 304 return false; 305 } 306 307 mDocShell = aPresContext.GetDocShell(); 308 if (NS_WARN_IF(!mDocShell)) { 309 return false; 310 } 311 312 mDocumentObserver = new DocumentObserver(*this); 313 314 return true; 315 } 316 317 void IMEContentObserver::Clear() { 318 mEditorBase = nullptr; 319 mSelection = nullptr; 320 mRootEditableNodeOrTextControlElement = nullptr; 321 mRootElement = nullptr; 322 mDocShell = nullptr; 323 // Should be safe to clear mDocumentObserver here even though it grabs 324 // this instance in most cases because this is called by Init() or Destroy(). 325 // The callers of Init() grab this instance with local RefPtr. 326 // The caller of Destroy() also grabs this instance with local RefPtr. 327 // So, this won't cause refcount of this instance become 0. 328 mDocumentObserver = nullptr; 329 } 330 331 void IMEContentObserver::ObserveEditableNode() { 332 MOZ_RELEASE_ASSERT(mSelection); 333 MOZ_RELEASE_ASSERT(mRootElement); 334 MOZ_RELEASE_ASSERT(GetState() != eState_Observing); 335 336 // If this is called before sending NOTIFY_IME_OF_FOCUS (it's possible when 337 // the editor is reframed before sending NOTIFY_IME_OF_FOCUS asynchronously), 338 // the notification requests of mWidget may be different from after the widget 339 // receives NOTIFY_IME_OF_FOCUS. So, this should be called again by 340 // OnIMEReceivedFocus() which is called after sending NOTIFY_IME_OF_FOCUS. 341 if (!mIMEHasFocus) { 342 MOZ_ASSERT(!mWidget || mNeedsToNotifyIMEOfFocusSet || 343 mSendingNotification == NOTIFY_IME_OF_FOCUS, 344 "Wow, OnIMEReceivedFocus() won't be called?"); 345 return; 346 } 347 348 mIsObserving = true; 349 if (mEditorBase) { 350 mEditorBase->SetIMEContentObserver(this); 351 } 352 353 MOZ_LOG(sIMECOLog, LogLevel::Info, 354 ("0x%p ObserveEditableNode(), starting to observe 0x%p (%s)", this, 355 mRootElement.get(), ToString(*mRootElement).c_str())); 356 357 mRootElement->AddMutationObserver(this); 358 // If it's in a document (should be so), we can use document observer to 359 // reduce redundant computation of text change offsets. 360 Document* doc = mRootElement->GetComposedDoc(); 361 if (doc) { 362 RefPtr<DocumentObserver> documentObserver = mDocumentObserver; 363 documentObserver->Observe(doc); 364 } 365 366 if (mDocShell) { 367 // Add scroll position listener and reflow observer to detect position 368 // and size changes 369 mDocShell->AddWeakScrollObserver(this); 370 mDocShell->AddWeakReflowObserver(this); 371 } 372 } 373 374 void IMEContentObserver::NotifyIMEOfBlur() { 375 // Prevent any notifications to be sent IME. 376 nsCOMPtr<nsIWidget> widget; 377 mWidget.swap(widget); 378 mIMENotificationRequests = nullptr; 379 380 // If we hasn't been set focus, we shouldn't send blur notification to IME. 381 if (!mIMEHasFocus) { 382 return; 383 } 384 385 // mWidget must have been non-nullptr if IME has focus. 386 MOZ_RELEASE_ASSERT(widget); 387 388 RefPtr<IMEContentObserver> kungFuDeathGrip(this); 389 390 MOZ_LOG(sIMECOLog, LogLevel::Info, 391 ("0x%p NotifyIMEOfBlur(), sending NOTIFY_IME_OF_BLUR", this)); 392 393 // For now, we need to send blur notification in any condition because 394 // we don't have any simple ways to send blur notification asynchronously. 395 // After this call, Destroy() or Unlink() will stop observing the content 396 // and forget everything. Therefore, if it's not safe to send notification 397 // when script blocker is unlocked, we cannot send blur notification after 398 // that and before next focus notification. 399 // Anyway, as far as we know, IME doesn't try to query content when it loses 400 // focus. So, this may not cause any problem. 401 mIMEHasFocus = false; 402 IMEStateManager::NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR), widget); 403 404 MOZ_LOG(sIMECOLog, LogLevel::Debug, 405 ("0x%p NotifyIMEOfBlur(), sent NOTIFY_IME_OF_BLUR", this)); 406 } 407 408 void IMEContentObserver::UnregisterObservers() { 409 if (!mIsObserving) { 410 return; 411 } 412 413 MOZ_LOG(sIMECOLog, LogLevel::Info, 414 ("0x%p UnregisterObservers(), stop observing 0x%p (%s)", this, 415 mRootElement.get(), 416 mRootElement ? ToString(*mRootElement).c_str() : "nullptr")); 417 418 mIsObserving = false; 419 420 if (mEditorBase) { 421 mEditorBase->SetIMEContentObserver(nullptr); 422 } 423 424 if (mSelection) { 425 mSelectionData.Clear(); 426 mFocusedWidget = nullptr; 427 } 428 429 if (mRootElement) { 430 mRootElement->RemoveMutationObserver(this); 431 } 432 433 if (mDocumentObserver) { 434 RefPtr<DocumentObserver> documentObserver = mDocumentObserver; 435 documentObserver->StopObserving(); 436 } 437 438 if (mDocShell) { 439 mDocShell->RemoveWeakScrollObserver(this); 440 mDocShell->RemoveWeakReflowObserver(this); 441 } 442 } 443 444 nsPresContext* IMEContentObserver::GetPresContext() const { 445 return mESM ? mESM->GetPresContext() : nullptr; 446 } 447 448 void IMEContentObserver::Destroy() { 449 // WARNING: When you change this method, you have to check Unlink() too. 450 451 // Note that don't send any notifications later from here. I.e., notify 452 // IMEStateManager of the blur synchronously because IMEStateManager needs to 453 // stop notifying the main process if this is requested by the main process. 454 NotifyIMEOfBlur(); 455 UnregisterObservers(); 456 Clear(); 457 458 mWidget = nullptr; 459 mIMENotificationRequests = nullptr; 460 461 if (mESM) { 462 mESM->OnStopObservingContent(this); 463 mESM = nullptr; 464 } 465 } 466 467 bool IMEContentObserver::Destroyed() const { return !mWidget; } 468 469 void IMEContentObserver::DisconnectFromEventStateManager() { mESM = nullptr; } 470 471 bool IMEContentObserver::MaybeReinitialize(nsIWidget& aWidget, 472 nsPresContext& aPresContext, 473 Element* aElement, 474 EditorBase& aEditorBase) { 475 if (!IsObservingElement(aPresContext, aElement)) { 476 return false; 477 } 478 479 if (GetState() == eState_StoppedObserving) { 480 Init(aWidget, aPresContext, aElement, aEditorBase); 481 } 482 return IsObserving(aPresContext, aElement); 483 } 484 485 bool IMEContentObserver::IsObserving(const nsPresContext& aPresContext, 486 const Element* aElement) const { 487 if (GetState() != eState_Observing) { 488 return false; 489 } 490 // If aElement is not a text control, aElement is an editing host or entire 491 // the document is editable in the design mode. Therefore, return false if 492 // we're observing an anonymous subtree of a text control. 493 const auto* const textControlElement = 494 TextControlElement::FromNodeOrNull(aElement); 495 if (!textControlElement || 496 !textControlElement->IsSingleLineTextControlOrTextArea()) { 497 if (mIsTextControl) { 498 return false; 499 } 500 } 501 // If aElement is a text control, return true if we're observing the anonymous 502 // subtree of aElement. Therefore, return false if we're observing with 503 // HTMLEditor. 504 else if (!mIsTextControl) { 505 return false; 506 } 507 return IsObservingElement(aPresContext, aElement); 508 } 509 510 bool IMEContentObserver::IsBeingInitializedFor( 511 const nsPresContext& aPresContext, const Element* aElement, 512 const EditorBase& aEditorBase) const { 513 return GetState() == eState_Initializing && mEditorBase == &aEditorBase && 514 IsObservingElement(aPresContext, aElement); 515 } 516 517 bool IMEContentObserver::IsObserving( 518 const TextComposition& aTextComposition) const { 519 if (GetState() != eState_Observing) { 520 return false; 521 } 522 nsPresContext* const presContext = aTextComposition.GetPresContext(); 523 if (NS_WARN_IF(!presContext)) { 524 return false; 525 } 526 if (presContext != GetPresContext()) { 527 return false; // observing different document 528 } 529 auto* const elementHavingComposition = 530 Element::FromNodeOrNull(aTextComposition.GetEventTargetNode()); 531 bool isObserving = IsObservingElement(*presContext, elementHavingComposition); 532 #ifdef DEBUG 533 if (isObserving) { 534 if (mIsTextControl) { 535 MOZ_ASSERT(elementHavingComposition); 536 MOZ_ASSERT(elementHavingComposition->IsTextControlElement(), 537 "Should've never started to observe non-text-control element"); 538 // XXX Our fake focus move has not been implemented properly. So, the 539 // following assertions may fail, but I don't like to make the failures 540 // cause crash even in debug builds because it may block developers to 541 // debug web-compat issues. On the other hand, it'd be nice if we can 542 // detect the bug with automated tests. Therefore, the following 543 // assertions are NS_ASSERTION. 544 NS_ASSERTION(static_cast<TextControlElement*>(elementHavingComposition) 545 ->IsSingleLineTextControlOrTextArea(), 546 "Should've stopped observing when the type is changed"); 547 NS_ASSERTION(!elementHavingComposition->IsInDesignMode(), 548 "Should've stopped observing when the design mode started"); 549 } else if (elementHavingComposition) { 550 NS_ASSERTION( 551 !elementHavingComposition->IsTextControlElement() || 552 !static_cast<TextControlElement*>(elementHavingComposition) 553 ->IsSingleLineTextControlOrTextArea(), 554 "Should've never started to observe text-control element or " 555 "stopped observing it when the type is changed"); 556 } else { 557 MOZ_ASSERT(presContext->GetPresShell()); 558 MOZ_ASSERT(presContext->GetPresShell()->GetDocument()); 559 NS_ASSERTION( 560 presContext->GetPresShell()->GetDocument()->IsInDesignMode(), 561 "Should be observing entire the document only in the design mode"); 562 } 563 } 564 #endif // #ifdef DEBUG 565 return isObserving; 566 } 567 568 IMEContentObserver::State IMEContentObserver::GetState() const { 569 if (!mSelection || !mRootElement || !mRootEditableNodeOrTextControlElement) { 570 return eState_NotObserving; // failed to initialize or finalized. 571 } 572 if (!mRootElement->IsInComposedDoc()) { 573 // the focused editor has already been reframed. 574 return eState_StoppedObserving; 575 } 576 return mIsObserving ? eState_Observing : eState_Initializing; 577 } 578 579 bool IMEContentObserver::IsObservingElement(const nsPresContext& aPresContext, 580 const Element* aElement) const { 581 MOZ_ASSERT_IF(aElement, 582 aElement->GetPresContext( 583 Element::PresContextFor::eForComposedDoc) == &aPresContext); 584 585 if (GetPresContext() != &aPresContext) { 586 return false; 587 } 588 // If this is initialized with a TextEditor, 589 // mRootEditableNodeOrTextControlElement is a text control element. Therefore, 590 // aElement should be aElement. 591 if (mIsTextControl) { 592 return !aElement->IsInDesignMode() && 593 aElement == mRootEditableNodeOrTextControlElement; 594 } 595 // If this is initialized with an HTMLEditor, 596 // mRootEditableNodeOrTextControlElement is an editing host when this is 597 // initialized. However, its ancestor may become editable. Therefore, we 598 // need to check whether it's still an editing host. 599 return mRootEditableNodeOrTextControlElement == 600 IMEContentObserver::GetMostDistantInclusiveEditableAncestorNode( 601 aPresContext, aElement); 602 } 603 604 // static 605 nsINode* IMEContentObserver::GetMostDistantInclusiveEditableAncestorNode( 606 const nsPresContext& aPresContext, const Element* aElement) { 607 if (aElement) { 608 // If the focused content is in design mode, return is composed document 609 // because aElement may be in UA widget shadow tree. 610 if (aElement->IsInDesignMode()) { 611 return aElement->GetComposedDoc(); 612 } 613 // Otherwise, return the editing host. 614 return aElement->GetEditingHost(); 615 } 616 617 return aPresContext.Document() && aPresContext.Document()->IsInDesignMode() 618 ? aPresContext.Document() 619 : nullptr; 620 } 621 622 bool IMEContentObserver::IsEditorHandlingEventForComposition() const { 623 if (!mWidget) { 624 return false; 625 } 626 RefPtr<TextComposition> composition = 627 IMEStateManager::GetTextCompositionFor(mWidget); 628 if (!composition) { 629 return false; 630 } 631 return composition->EditorIsHandlingLatestChange(); 632 } 633 634 bool IMEContentObserver::IsEditorComposing() const { 635 // Note that don't use TextComposition here. The important thing is, 636 // whether the editor already started to handle composition because 637 // web contents can change selection, text content and/or something from 638 // compositionstart event listener which is run before EditorBase handles it. 639 if (NS_WARN_IF(!mEditorBase)) { 640 return false; 641 } 642 return mEditorBase->IsIMEComposing(); 643 } 644 645 nsresult IMEContentObserver::GetSelectionAndRoot(Selection** aSelection, 646 Element** aRootElement) const { 647 if (!mRootEditableNodeOrTextControlElement || !mSelection) { 648 return NS_ERROR_NOT_AVAILABLE; 649 } 650 651 NS_ASSERTION(mSelection && mRootElement, "uninitialized content observer"); 652 NS_ADDREF(*aSelection = mSelection); 653 NS_ADDREF(*aRootElement = mRootElement); 654 return NS_OK; 655 } 656 657 void IMEContentObserver::OnSelectionChange(Selection& aSelection) { 658 if (!mIsObserving) { 659 return; 660 } 661 662 if (mWidget) { 663 bool causedByComposition = IsEditorHandlingEventForComposition(); 664 bool causedBySelectionEvent = TextComposition::IsHandlingSelectionEvent(); 665 bool duringComposition = IsEditorComposing(); 666 MaybeNotifyIMEOfSelectionChange(causedByComposition, causedBySelectionEvent, 667 duringComposition); 668 } 669 } 670 671 void IMEContentObserver::ScrollPositionChanged() { 672 if (!NeedsPositionChangeNotification()) { 673 return; 674 } 675 676 MaybeNotifyIMEOfPositionChange(); 677 } 678 679 NS_IMETHODIMP 680 IMEContentObserver::Reflow(DOMHighResTimeStamp aStart, 681 DOMHighResTimeStamp aEnd) { 682 if (!NeedsPositionChangeNotification()) { 683 return NS_OK; 684 } 685 686 MaybeNotifyIMEOfPositionChange(); 687 return NS_OK; 688 } 689 690 NS_IMETHODIMP 691 IMEContentObserver::ReflowInterruptible(DOMHighResTimeStamp aStart, 692 DOMHighResTimeStamp aEnd) { 693 if (!NeedsPositionChangeNotification()) { 694 return NS_OK; 695 } 696 697 MaybeNotifyIMEOfPositionChange(); 698 return NS_OK; 699 } 700 701 nsresult IMEContentObserver::HandleQueryContentEvent( 702 WidgetQueryContentEvent* aEvent) { 703 // If the instance has normal selection cache and the query event queries 704 // normal selection's range, it should use the cached selection which was 705 // sent to the widget. However, if this instance has already received new 706 // selection change notification but hasn't updated the cache yet (i.e., 707 // not sending selection change notification to IME, don't use the cached 708 // value. Note that don't update selection cache here since if you update 709 // selection cache here, IMENotificationSender won't notify IME of selection 710 // change because it looks like that the selection isn't actually changed. 711 const bool isSelectionCacheAvailable = aEvent->mUseNativeLineBreak && 712 mSelectionData.IsInitialized() && 713 !mNeedsToNotifyIMEOfSelectionChange; 714 if (isSelectionCacheAvailable && aEvent->mMessage == eQuerySelectedText && 715 aEvent->mInput.mSelectionType == SelectionType::eNormal) { 716 aEvent->EmplaceReply(); 717 if (mSelectionData.HasRange()) { 718 aEvent->mReply->mOffsetAndData.emplace(mSelectionData.mOffset, 719 mSelectionData.String(), 720 OffsetAndDataFor::SelectedString); 721 aEvent->mReply->mReversed = mSelectionData.mReversed; 722 } 723 aEvent->mReply->mContentsRoot = mRootElement; 724 aEvent->mReply->mWritingMode = mSelectionData.GetWritingMode(); 725 // The selection cache in IMEContentObserver must always have been in 726 // an editing host (or an editable anonymous <div> element). Therefore, 727 // we set mIsEditableContent to true here even though it's already been 728 // blurred or changed its editable state but the selection cache has not 729 // been invalidated yet. 730 aEvent->mReply->mIsEditableContent = true; 731 MOZ_LOG(sIMECOLog, LogLevel::Debug, 732 ("0x%p HandleQueryContentEvent(aEvent={ " 733 "mMessage=%s, mReply=%s })", 734 this, ToChar(aEvent->mMessage), ToString(aEvent->mReply).c_str())); 735 return NS_OK; 736 } 737 738 MOZ_LOG(sIMECOLog, LogLevel::Info, 739 ("0x%p HandleQueryContentEvent(aEvent={ mMessage=%s })", this, 740 ToChar(aEvent->mMessage))); 741 742 // If we can make the event's input offset absolute with TextComposition or 743 // mSelection, we should set it here for reducing the cost of computing 744 // selection start offset. If ContentEventHandler receives a 745 // WidgetQueryContentEvent whose input offset is relative to insertion point, 746 // it computes current selection start offset (this may be expensive) and 747 // make the offset absolute value itself. 748 // Note that calling MakeOffsetAbsolute() makes the event a query event with 749 // absolute offset. So, ContentEventHandler doesn't pay any additional cost 750 // after calling MakeOffsetAbsolute() here. 751 if (aEvent->mInput.mRelativeToInsertionPoint && 752 aEvent->mInput.IsValidEventMessage(aEvent->mMessage)) { 753 RefPtr<TextComposition> composition = 754 IMEStateManager::GetTextCompositionFor(aEvent->mWidget); 755 if (composition) { 756 uint32_t compositionStart = composition->NativeOffsetOfStartComposition(); 757 if (NS_WARN_IF(!aEvent->mInput.MakeOffsetAbsolute(compositionStart))) { 758 return NS_ERROR_FAILURE; 759 } 760 } else if (isSelectionCacheAvailable && mSelectionData.HasRange()) { 761 const uint32_t selectionStart = mSelectionData.mOffset; 762 if (NS_WARN_IF(!aEvent->mInput.MakeOffsetAbsolute(selectionStart))) { 763 return NS_ERROR_FAILURE; 764 } 765 } 766 } 767 768 AutoRestore<bool> handling(mIsHandlingQueryContentEvent); 769 mIsHandlingQueryContentEvent = true; 770 ContentEventHandler handler(GetPresContext()); 771 nsresult rv = handler.HandleQueryContentEvent(aEvent); 772 if (NS_WARN_IF(Destroyed())) { 773 // If this has already destroyed during querying the content, the query 774 // is outdated even if it's succeeded. So, make the query fail. 775 aEvent->mReply.reset(); 776 MOZ_LOG(sIMECOLog, LogLevel::Warning, 777 ("0x%p HandleQueryContentEvent(), WARNING, " 778 "IMEContentObserver has been destroyed during the query, " 779 "making the query fail", 780 this)); 781 return rv; 782 } 783 784 if (aEvent->Succeeded() && 785 NS_WARN_IF(aEvent->mReply->mContentsRoot != mRootElement)) { 786 // Focus has changed unexpectedly, so make the query fail. 787 aEvent->mReply.reset(); 788 } 789 return rv; 790 } 791 792 nsresult IMEContentObserver::MaybeHandleSelectionEvent( 793 nsPresContext* aPresContext, WidgetSelectionEvent* aEvent) { 794 MOZ_ASSERT(aEvent); 795 MOZ_ASSERT(aEvent->mMessage == eSetSelection); 796 NS_ASSERTION(!mNeedsToNotifyIMEOfSelectionChange, 797 "Selection cache has not been updated yet"); 798 799 MOZ_LOG( 800 sIMECOLog, LogLevel::Debug, 801 ("0x%p MaybeHandleSelectionEvent(aEvent={ " 802 "mMessage=%s, mOffset=%u, mLength=%u, mReversed=%s, " 803 "mExpandToClusterBoundary=%s, mUseNativeLineBreak=%s }), " 804 "mSelectionData=%s", 805 this, ToChar(aEvent->mMessage), aEvent->mOffset, aEvent->mLength, 806 ToChar(aEvent->mReversed), ToChar(aEvent->mExpandToClusterBoundary), 807 ToChar(aEvent->mUseNativeLineBreak), ToString(mSelectionData).c_str())); 808 809 // When we have Selection cache, and the caller wants to set same selection 810 // range, we shouldn't try to compute same range because it may be impossible 811 // if the range boundary is around element boundaries which won't be 812 // serialized with line breaks like close tags of inline elements. In that 813 // case, inserting new text at different point may be different from intention 814 // of users or web apps which set current selection. 815 // FIXME: We cache only selection data computed with native line breaker 816 // lengths. Perhaps, we should improve the struct to have both data of 817 // offset and length. E.g., adding line break counts for both offset and 818 // length. 819 if (!mNeedsToNotifyIMEOfSelectionChange && aEvent->mUseNativeLineBreak && 820 mSelectionData.IsInitialized() && mSelectionData.HasRange() && 821 mSelectionData.StartOffset() == aEvent->mOffset && 822 mSelectionData.Length() == aEvent->mLength) { 823 if (RefPtr<Selection> selection = mSelection) { 824 selection->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION); 825 } 826 aEvent->mSucceeded = true; 827 return NS_OK; 828 } 829 830 ContentEventHandler handler(aPresContext); 831 return handler.OnSelectionEvent(aEvent); 832 } 833 834 bool IMEContentObserver::OnMouseButtonEvent(nsPresContext& aPresContext, 835 WidgetMouseEvent& aMouseEvent) { 836 if (!mIMENotificationRequests || 837 !mIMENotificationRequests->WantMouseButtonEventOnChar()) { 838 return false; 839 } 840 if (!aMouseEvent.IsTrusted() || aMouseEvent.DefaultPrevented() || 841 !aMouseEvent.mWidget) { 842 return false; 843 } 844 // Now, we need to notify only mouse down and mouse up event. 845 switch (aMouseEvent.mMessage) { 846 case eMouseUp: 847 case eMouseDown: 848 break; 849 default: 850 return false; 851 } 852 if (NS_WARN_IF(!mWidget) || NS_WARN_IF(mWidget->Destroyed())) { 853 return false; 854 } 855 856 WidgetQueryContentEvent queryCharAtPointEvent(true, eQueryCharacterAtPoint, 857 aMouseEvent.mWidget); 858 queryCharAtPointEvent.mRefPoint = aMouseEvent.mRefPoint; 859 ContentEventHandler handler(&aPresContext); 860 handler.OnQueryCharacterAtPoint(&queryCharAtPointEvent); 861 if (NS_WARN_IF(queryCharAtPointEvent.Failed()) || 862 queryCharAtPointEvent.DidNotFindChar()) { 863 return false; 864 } 865 866 // The widget might be destroyed during querying the content since it 867 // causes flushing layout. 868 if (!mWidget || NS_WARN_IF(mWidget->Destroyed())) { 869 return false; 870 } 871 872 // The result character rect is relative to the top level widget. 873 // We should notify it with offset in the widget. 874 nsIWidget* topLevelWidget = mWidget->GetTopLevelWidget(); 875 if (topLevelWidget && topLevelWidget != mWidget) { 876 queryCharAtPointEvent.mReply->mRect.MoveBy( 877 topLevelWidget->WidgetToScreenOffset() - 878 mWidget->WidgetToScreenOffset()); 879 } 880 // The refPt is relative to its widget. 881 // We should notify it with offset in the widget. 882 if (aMouseEvent.mWidget != mWidget) { 883 queryCharAtPointEvent.mRefPoint += 884 aMouseEvent.mWidget->WidgetToScreenOffset() - 885 mWidget->WidgetToScreenOffset(); 886 } 887 888 IMENotification notification(NOTIFY_IME_OF_MOUSE_BUTTON_EVENT); 889 notification.mMouseButtonEventData.mEventMessage = aMouseEvent.mMessage; 890 notification.mMouseButtonEventData.mOffset = 891 queryCharAtPointEvent.mReply->StartOffset(); 892 notification.mMouseButtonEventData.mCursorPos = 893 queryCharAtPointEvent.mRefPoint; 894 notification.mMouseButtonEventData.mCharRect = 895 queryCharAtPointEvent.mReply->mRect; 896 notification.mMouseButtonEventData.mButton = aMouseEvent.mButton; 897 notification.mMouseButtonEventData.mButtons = aMouseEvent.mButtons; 898 notification.mMouseButtonEventData.mModifiers = aMouseEvent.mModifiers; 899 900 nsresult rv = IMEStateManager::NotifyIME(notification, mWidget); 901 if (NS_WARN_IF(NS_FAILED(rv))) { 902 return false; 903 } 904 905 bool consumed = (rv == NS_SUCCESS_EVENT_CONSUMED); 906 if (consumed) { 907 aMouseEvent.PreventDefault(); 908 } 909 return consumed; 910 } 911 912 void IMEContentObserver::CharacterDataWillChange( 913 nsIContent* aContent, const CharacterDataChangeInfo& aInfo) { 914 if (!aContent->IsText()) { 915 return; // Ignore if it's a comment node or something other invisible data 916 // node. 917 } 918 MOZ_ASSERT(mPreCharacterDataChangeLength < 0, 919 "CharacterDataChanged() should've reset " 920 "mPreCharacterDataChangeLength"); 921 922 if (!NeedsTextChangeNotification() || 923 !nsContentUtils::IsInSameAnonymousTree(mRootElement, aContent)) { 924 return; 925 } 926 927 mEndOfAddedTextCache.Clear(__FUNCTION__); 928 mStartOfRemovingTextRangeCache.Clear(__FUNCTION__); 929 930 // Although we don't assume this change occurs while this is storing 931 // the range of added consecutive nodes, if it actually happens, we need to 932 // flush them since this change may occur before or in the range. So, it's 933 // safe to flush pending computation of mTextChangeData before handling this. 934 if (mAddedContentCache.HasCache()) { 935 NotifyIMEOfCachedConsecutiveNewNodes(__FUNCTION__); 936 } 937 938 mPreCharacterDataChangeLength = ContentEventHandler::GetNativeTextLength( 939 *aContent->AsText(), aInfo.mChangeStart, aInfo.mChangeEnd); 940 MOZ_ASSERT( 941 mPreCharacterDataChangeLength >= aInfo.mChangeEnd - aInfo.mChangeStart, 942 "The computed length must be same as or larger than XP length"); 943 } 944 945 void IMEContentObserver::CharacterDataChanged( 946 nsIContent* aContent, const CharacterDataChangeInfo& aInfo) { 947 if (!aContent->IsText()) { 948 return; // Ignore if it's a comment node or something other invisible data 949 // node. 950 } 951 952 // Let TextComposition have a change to update composition string range in 953 // the text node if the change is caused by the web apps. 954 if (mWidget && !IsEditorHandlingEventForComposition()) { 955 if (RefPtr<TextComposition> composition = 956 IMEStateManager::GetTextCompositionFor(mWidget)) { 957 composition->OnCharacterDataChanged(*aContent->AsText(), aInfo); 958 } 959 } 960 961 if (!NeedsTextChangeNotification() || 962 !nsContentUtils::IsInSameAnonymousTree(mRootElement, aContent)) { 963 return; 964 } 965 966 if (mAddedContentCache.HasCache()) { 967 NotifyIMEOfCachedConsecutiveNewNodes(__FUNCTION__); 968 } 969 mEndOfAddedTextCache.Clear(__FUNCTION__); 970 mStartOfRemovingTextRangeCache.Clear(__FUNCTION__); 971 MOZ_ASSERT( 972 !mAddedContentCache.HasCache(), 973 "The stored range should be flushed before actually the data is changed"); 974 975 int64_t removedLength = mPreCharacterDataChangeLength; 976 mPreCharacterDataChangeLength = -1; 977 978 MOZ_ASSERT(removedLength >= 0, 979 "mPreCharacterDataChangeLength should've been set by " 980 "CharacterDataWillChange()"); 981 982 uint32_t offset = 0; 983 if (mIsTextControl) { 984 // If we're observing a text control, mRootElement is the anonymous <div> 985 // element which has only one text node and/or invisible <br> element. 986 // TextEditor assumes this structure when it handles editing commands. 987 // Therefore, it's safe to assume same things here. 988 MOZ_ASSERT(mRootElement->GetFirstChild() == aContent); 989 if (aInfo.mChangeStart) { 990 offset = ContentEventHandler::GetNativeTextLength(*aContent->AsText(), 0, 991 aInfo.mChangeStart); 992 } 993 } else { 994 nsresult rv = ContentEventHandler::GetFlatTextLengthInRange( 995 RawNodePosition::BeforeFirstContentOf(*mRootElement), 996 RawNodePosition(aContent, aInfo.mChangeStart), mRootElement, &offset, 997 LINE_BREAK_TYPE_NATIVE); 998 if (NS_WARN_IF(NS_FAILED(rv))) { 999 return; 1000 } 1001 } 1002 1003 uint32_t newLength = ContentEventHandler::GetNativeTextLength( 1004 *aContent->AsText(), aInfo.mChangeStart, 1005 aInfo.mChangeStart + aInfo.mReplaceLength); 1006 1007 uint32_t oldEnd = offset + static_cast<uint32_t>(removedLength); 1008 uint32_t newEnd = offset + newLength; 1009 1010 TextChangeData data(offset, oldEnd, newEnd, 1011 IsEditorHandlingEventForComposition(), 1012 IsEditorComposing()); 1013 MaybeNotifyIMEOfTextChange(data); 1014 } 1015 1016 void IMEContentObserver::ContentAdded(nsINode* aContainer, 1017 nsIContent* aFirstContent, 1018 nsIContent* aLastContent) { 1019 if (!NeedsTextChangeNotification() || 1020 !nsContentUtils::IsInSameAnonymousTree(mRootElement, aFirstContent)) { 1021 return; 1022 } 1023 1024 // We can skip everything when a padding <br> element is added since its text 1025 // length is 0. 1026 if (aFirstContent == aLastContent) { 1027 if (const auto* brElement = HTMLBRElement::FromNode(aFirstContent)) { 1028 if (MOZ_LIKELY(!brElement->HasChildNodes()) && 1029 (brElement->IsPaddingForEmptyEditor() || 1030 brElement->IsPaddingForEmptyLastLine())) { 1031 return; 1032 } 1033 } 1034 } 1035 1036 MOZ_ASSERT(IsInDocumentChange()); 1037 MOZ_ASSERT_IF(aFirstContent, aFirstContent->GetParentNode() == aContainer); 1038 MOZ_ASSERT_IF(aLastContent, aLastContent->GetParentNode() == aContainer); 1039 1040 // While a document change, new nodes should be added consecutively in a 1041 // container node. Therefore, we can cache the first added node and the last 1042 // added node until ending the document change at least. Then, we can avoid 1043 // to compute first added node offset in the flattened text repeatedly. 1044 bool needToCache = true; 1045 if (mAddedContentCache.HasCache()) { 1046 MOZ_DIAGNOSTIC_ASSERT(aFirstContent->GetParentNode() == 1047 aLastContent->GetParentNode()); 1048 if (mAddedContentCache.IsInRange(*aFirstContent, mRootElement)) { 1049 // The new content nodes are in the range, we can include their text 1050 // length when we flush the cached range later. Therefore, we need to 1051 // do nothing in this case. 1052 needToCache = false; 1053 MOZ_LOG(sCacheLog, LogLevel::Info, 1054 ("ContentAdded: mAddedContentCache already caches the give " 1055 "content nodes")); 1056 MOZ_ASSERT(mAddedContentCache.IsInRange(*aLastContent, mRootElement)); 1057 } 1058 // When new nodes are inserted in a different container, let's flush the 1059 // preceding content first. Then, we should restart to cache the new 1060 // inserted nodes. 1061 else if (!mAddedContentCache.CanMergeWith(*aFirstContent, *aLastContent, 1062 mRootElement)) { 1063 MOZ_LOG(sCacheLog, LogLevel::Info, 1064 ("ContentAdded: mAddedContentCache was cached not in current " 1065 "document change and new content nodes cannot be merged")); 1066 mEndOfAddedTextCache.Clear(__FUNCTION__); 1067 mStartOfRemovingTextRangeCache.Clear(__FUNCTION__); 1068 OffsetAndLengthAdjustments differences; 1069 Result<std::pair<uint32_t, uint32_t>, nsresult> offsetAndLength = 1070 mAddedContentCache.ComputeFlatTextRangeBeforeInsertingNewContent( 1071 *aFirstContent, *aLastContent, mRootElement, differences); 1072 if (NS_WARN_IF(offsetAndLength.isErr())) { 1073 MOZ_LOG(sCacheLog, LogLevel::Error, 1074 ("ContentAdded: " 1075 "AddedContentCache::" 1076 "ComputeFlatTextRangeExcludingInsertingNewContent() failed")); 1077 mAddedContentCache.Clear(__FUNCTION__); 1078 return; 1079 } 1080 NotifyIMEOfCachedConsecutiveNewNodes( 1081 __FUNCTION__, Some(offsetAndLength.inspect().first), 1082 Some(offsetAndLength.inspect().second), differences); 1083 mAddedContentCache.Clear(__FUNCTION__); 1084 } 1085 } 1086 1087 mEndOfAddedTextCache.ContentAdded(__FUNCTION__, *aFirstContent, *aLastContent, 1088 Nothing(), mRootElement); 1089 mStartOfRemovingTextRangeCache.ContentAdded( 1090 __FUNCTION__, *aFirstContent, *aLastContent, Nothing(), mRootElement); 1091 1092 if (!needToCache) { 1093 return; 1094 } 1095 1096 // Okay, now, we can start to cache new nodes or merge the range of new 1097 // nodes with the cached range. 1098 if (!mAddedContentCache.TryToCache(*aFirstContent, *aLastContent, 1099 mRootElement)) { 1100 // Flush the old range first. 1101 MOZ_LOG(sCacheLog, LogLevel::Info, 1102 ("ContentAdded: called during a document change flushed " 1103 "previous added nodes (aFirstContent=%s, aLastContent=%s)", 1104 ToString(RefPtr<nsINode>(aFirstContent)).c_str(), 1105 ToString(RefPtr<nsINode>(aLastContent)).c_str())); 1106 NotifyIMEOfCachedConsecutiveNewNodes(__FUNCTION__); 1107 MOZ_ASSERT(!mAddedContentCache.HasCache()); 1108 MOZ_ALWAYS_TRUE(mAddedContentCache.TryToCache(*aFirstContent, *aLastContent, 1109 mRootElement)); 1110 } 1111 } 1112 1113 void IMEContentObserver::NotifyIMEOfCachedConsecutiveNewNodes( 1114 const char* aCallerName, 1115 const Maybe<uint32_t>& aOffsetOfFirstContent /* = Nothing() */, 1116 const Maybe<uint32_t>& aLengthOfContentNNodes /* = Nothing() */, 1117 const OffsetAndLengthAdjustments& aAdjustments /* = Nothing() */) { 1118 MOZ_ASSERT(mAddedContentCache.HasCache()); 1119 1120 MOZ_LOG(sIMECOLog, LogLevel::Debug, 1121 ("0x%p " 1122 "IMEContentObserver::NotifyIMEOfCachedConsecutiveNewNodes(), " 1123 "flushing stored consecutive nodes", 1124 this)); 1125 MOZ_LOG( 1126 sCacheLog, LogLevel::Info, 1127 ("NotifyIMEOfCachedConsecutiveNewNodes: called by %s " 1128 "(mAddedContentCache=%s)", 1129 ShortenFunctionName(aCallerName), ToString(mAddedContentCache).c_str())); 1130 1131 // If 2 <div> elements are inserted into the DOM, we wan't the text length 1132 // from start of the first <div> (including line break caused by its open 1133 // tag) to end of the second <div>. I.e., we want to compute: 1134 // ...{<div>.....</div><div>......</div>}... 1135 // ^ ^ ^ ^ 1136 // | mFirst | | 1137 // | mLast | 1138 // offset (offset + length) 1139 Maybe<uint32_t> offset = 1140 aOffsetOfFirstContent.isSome() 1141 ? aOffsetOfFirstContent 1142 : mEndOfAddedTextCache.GetFlatTextLengthBeforeContent( 1143 *mAddedContentCache.mFirst, mRootElement); 1144 if (offset.isNothing()) { 1145 Result<uint32_t, nsresult> textLengthBeforeFirstContentOrError = 1146 FlatTextCache::ComputeTextLengthBeforeContent( 1147 *mAddedContentCache.mFirst, mRootElement); 1148 if (NS_WARN_IF(textLengthBeforeFirstContentOrError.isErr())) { 1149 mEndOfAddedTextCache.Clear(__FUNCTION__); 1150 mStartOfRemovingTextRangeCache.Clear(__FUNCTION__); 1151 MOZ_LOG( 1152 sCacheLog, LogLevel::Error, 1153 ("NotifyContentAdded: failed to compute text length before mFirst")); 1154 mAddedContentCache.Clear(__FUNCTION__); 1155 return; 1156 } 1157 offset = Some(textLengthBeforeFirstContentOrError.unwrap()); 1158 } 1159 Maybe<uint32_t> length = aLengthOfContentNNodes; 1160 if (aLengthOfContentNNodes.isNothing()) { 1161 Result<uint32_t, nsresult> addingLengthOrError = 1162 FlatTextCache::ComputeTextLengthStartOfContentToEndOfContent( 1163 *mAddedContentCache.mFirst, *mAddedContentCache.mLast, 1164 mRootElement); 1165 if (NS_WARN_IF(addingLengthOrError.isErr())) { 1166 mEndOfAddedTextCache.Clear(__FUNCTION__); 1167 mStartOfRemovingTextRangeCache.Clear(__FUNCTION__); 1168 MOZ_LOG(sCacheLog, LogLevel::Error, 1169 ("NotifyContentAdded: failed to compute text length of added")); 1170 mAddedContentCache.Clear(__FUNCTION__); 1171 return; 1172 } 1173 length = Some(addingLengthOrError.inspect()); 1174 } 1175 1176 // If multiple lines are being inserted in an HTML editor, next call of 1177 // NotifyContentAdded() is for adding next node. Therefore, caching the text 1178 // length can skip to compute the text length before the adding node and 1179 // before of it. 1180 mEndOfAddedTextCache.CacheFlatTextLengthBeforeEndOfContent( 1181 __FUNCTION__, *mAddedContentCache.mLast, 1182 aAdjustments.AdjustedEndOffset(*offset + *length), mRootElement); 1183 mStartOfRemovingTextRangeCache.ContentAdded( 1184 __FUNCTION__, *mAddedContentCache.mFirst, *mAddedContentCache.mLast, 1185 Some(aAdjustments.AdjustedEndOffset(*offset + *length)), mRootElement); 1186 1187 mAddedContentCache.Clear(__FUNCTION__); 1188 1189 if (*length == 0u) { 1190 return; 1191 } 1192 1193 TextChangeData data(*offset, *offset, *offset + *length, 1194 IsEditorHandlingEventForComposition(), 1195 IsEditorComposing()); 1196 MaybeNotifyIMEOfTextChange(data); 1197 } 1198 1199 void IMEContentObserver::ContentAppended(nsIContent* aFirstNewContent, 1200 const ContentAppendInfo&) { 1201 nsIContent* parent = aFirstNewContent->GetParent(); 1202 MOZ_ASSERT(parent); 1203 ContentAdded(parent, aFirstNewContent, parent->GetLastChild()); 1204 } 1205 1206 void IMEContentObserver::ContentInserted(nsIContent* aChild, 1207 const ContentInsertInfo&) { 1208 MOZ_ASSERT(aChild); 1209 ContentAdded(aChild->GetParentNode(), aChild, aChild); 1210 } 1211 1212 void IMEContentObserver::ContentWillBeRemoved(nsIContent* aChild, 1213 const ContentRemoveInfo&) { 1214 if (!NeedsTextChangeNotification() || 1215 !nsContentUtils::IsInSameAnonymousTree(mRootElement, aChild)) { 1216 return; 1217 } 1218 1219 // We can skip everything when padding <br> element is removed since its text 1220 // length is 0. 1221 if (const auto* brElement = HTMLBRElement::FromNode(aChild)) { 1222 if (MOZ_LIKELY(!brElement->HasChildNodes()) && 1223 (brElement->IsPaddingForEmptyEditor() || 1224 brElement->IsPaddingForEmptyLastLine())) { 1225 return; 1226 } 1227 } 1228 1229 const Result<uint32_t, nsresult> textLengthOrError = 1230 FlatTextCache::ComputeTextLengthOfContent(*aChild, mRootElement, 1231 ForRemoval::Yes); 1232 if (NS_WARN_IF(textLengthOrError.isErr())) { 1233 mEndOfAddedTextCache.Clear(__FUNCTION__); 1234 mStartOfRemovingTextRangeCache.Clear(__FUNCTION__); 1235 mAddedContentCache.Clear(__FUNCTION__); 1236 return; 1237 } 1238 1239 if (mAddedContentCache.HasCache()) { 1240 mEndOfAddedTextCache.ContentWillBeRemoved( 1241 *aChild, textLengthOrError.inspect(), mRootElement); 1242 mStartOfRemovingTextRangeCache.ContentWillBeRemoved( 1243 *aChild, textLengthOrError.inspect(), mRootElement); 1244 NotifyIMEOfCachedConsecutiveNewNodes(__FUNCTION__); 1245 MOZ_DIAGNOSTIC_ASSERT(!mAddedContentCache.HasCache()); 1246 } 1247 1248 nsINode* containerNode = aChild->GetParentNode(); 1249 MOZ_ASSERT(containerNode); 1250 1251 mEndOfAddedTextCache.ContentWillBeRemoved( 1252 *aChild, textLengthOrError.inspect(), mRootElement); 1253 1254 Maybe<uint32_t> offset = 1255 mStartOfRemovingTextRangeCache.GetFlatTextLengthBeforeContent( 1256 *aChild, mRootElement, ForRemoval::Yes); 1257 nsIContent* const prevSibling = aChild->GetPreviousSibling(); 1258 if (offset.isSome()) { 1259 // Update the cache because next remove may be the previous or the next 1260 // sibling removal. So, caching offset of currently removing content node 1261 // makes us skip computing offset of next removal. 1262 if (prevSibling) { 1263 mStartOfRemovingTextRangeCache.CacheFlatTextLengthBeforeEndOfContent( 1264 __FUNCTION__, *prevSibling, *offset, mRootElement); 1265 } else { 1266 mStartOfRemovingTextRangeCache.CacheFlatTextLengthBeforeFirstContent( 1267 __FUNCTION__, *containerNode, *offset, mRootElement); 1268 } 1269 } else { 1270 if (prevSibling) { 1271 // When we compute preceding text length of the removing content node, we 1272 // cannot make the range cross the removing node boundary because 1273 // containerNode->ComputeIndexOf(aChild) returns Nothing so that 1274 // ContentEventHandler fails to compute the length. Therefore, if a <div> 1275 // is being removed, we want to compute the length of `...}<div>`. 1276 if (NS_WARN_IF( 1277 NS_FAILED(mStartOfRemovingTextRangeCache 1278 .ComputeAndCacheFlatTextLengthBeforeEndOfContent( 1279 __FUNCTION__, *prevSibling, mRootElement)))) { 1280 return; 1281 } 1282 } else { 1283 // At removing a child node of containerNode, we need the line break 1284 // caused by open tag of containerNode. 1285 if (NS_WARN_IF( 1286 NS_FAILED(mStartOfRemovingTextRangeCache 1287 .ComputeAndCacheFlatTextLengthBeforeFirstContent( 1288 __FUNCTION__, *containerNode, mRootElement)))) { 1289 return; 1290 } 1291 } 1292 offset = Some(mStartOfRemovingTextRangeCache.GetFlatTextLength()); 1293 } 1294 1295 // We do not need a text change notification since removing aChild does not 1296 // change flattened text and no pending added length. 1297 if (textLengthOrError.inspect() == 0u) { 1298 return; 1299 } 1300 1301 TextChangeData data(*offset, *offset + textLengthOrError.inspect(), *offset, 1302 IsEditorHandlingEventForComposition(), 1303 IsEditorComposing()); 1304 MaybeNotifyIMEOfTextChange(data); 1305 } 1306 1307 MOZ_CAN_RUN_SCRIPT_BOUNDARY void IMEContentObserver::ParentChainChanged( 1308 nsIContent* aContent) { 1309 MOZ_ASSERT(aContent); 1310 // When the observing element itself is directly removed from the document 1311 // without a focus move, i.e., it's the root of the removed document fragment 1312 // and the editor was handling the design mode, we have already stopped 1313 // observing the element because IMEStateManager::OnRemoveContent() should 1314 // have already been called for it and the instance which was observing the 1315 // node has already been destroyed. Therefore, this is called only when 1316 // this is observing the <body> in the design mode and it's disconnected from 1317 // the tree by an <html> element removal. Even in this case, IMEStateManager 1318 // never gets a focus change notification, but we need to notify IME of focus 1319 // change because we cannot interact with IME anymore due to no editable 1320 // content. Therefore, this method notifies IMEStateManager of the 1321 // disconnection of the observing node to emulate a blur from the editable 1322 // content. 1323 MOZ_ASSERT(mIsObserving); 1324 OwningNonNull<IMEContentObserver> observer(*this); 1325 IMEStateManager::OnParentChainChangedOfObservingElement(observer, *aContent); 1326 } 1327 1328 void IMEContentObserver::OnTextControlValueChangedWhileNotObservable( 1329 const nsAString& aNewValue) { 1330 MOZ_ASSERT(mEditorBase); 1331 MOZ_ASSERT(mEditorBase->IsTextEditor()); 1332 if (!mTextControlValueLength && aNewValue.IsEmpty()) { 1333 return; 1334 } 1335 MOZ_LOG(sIMECOLog, LogLevel::Debug, 1336 ("0x%p OnTextControlValueChangedWhileNotObservable()", this)); 1337 uint32_t newLength = ContentEventHandler::GetNativeTextLength(aNewValue); 1338 TextChangeData data(0, mTextControlValueLength, newLength, false, false); 1339 MaybeNotifyIMEOfTextChange(data); 1340 } 1341 1342 void IMEContentObserver::BeginDocumentUpdate() { 1343 MOZ_LOG(sIMECOLog, LogLevel::Debug, ("0x%p BeginDocumentUpdate()", this)); 1344 } 1345 1346 void IMEContentObserver::EndDocumentUpdate() { 1347 MOZ_LOG(sIMECOLog, LogLevel::Debug, ("0x%p EndDocumentUpdate()", this)); 1348 1349 if (mAddedContentCache.HasCache() && !EditorIsHandlingEditSubAction()) { 1350 NotifyIMEOfCachedConsecutiveNewNodes(__FUNCTION__); 1351 } 1352 } 1353 1354 void IMEContentObserver::SuppressNotifyingIME() { 1355 mSuppressNotifications++; 1356 1357 MOZ_LOG(sIMECOLog, LogLevel::Debug, 1358 ("0x%p SuppressNotifyingIME(), mSuppressNotifications=%u", this, 1359 mSuppressNotifications)); 1360 } 1361 1362 void IMEContentObserver::UnsuppressNotifyingIME() { 1363 MOZ_LOG(sIMECOLog, LogLevel::Debug, 1364 ("0x%p UnsuppressNotifyingIME(), mSuppressNotifications=%u", this, 1365 mSuppressNotifications)); 1366 if (!mSuppressNotifications || --mSuppressNotifications) { 1367 return; 1368 } 1369 FlushMergeableNotifications(); 1370 } 1371 1372 void IMEContentObserver::OnEditActionHandled() { 1373 MOZ_LOG(sIMECOLog, LogLevel::Debug, ("0x%p OnEditActionHandled()", this)); 1374 1375 if (mAddedContentCache.HasCache()) { 1376 NotifyIMEOfCachedConsecutiveNewNodes(__FUNCTION__); 1377 } 1378 mEndOfAddedTextCache.Clear(__FUNCTION__); 1379 mStartOfRemovingTextRangeCache.Clear(__FUNCTION__); 1380 FlushMergeableNotifications(); 1381 } 1382 1383 void IMEContentObserver::BeforeEditAction() { 1384 MOZ_LOG(sIMECOLog, LogLevel::Debug, ("0x%p BeforeEditAction()", this)); 1385 1386 if (mAddedContentCache.HasCache()) { 1387 NotifyIMEOfCachedConsecutiveNewNodes(__FUNCTION__); 1388 } 1389 mEndOfAddedTextCache.Clear(__FUNCTION__); 1390 mStartOfRemovingTextRangeCache.Clear(__FUNCTION__); 1391 } 1392 1393 void IMEContentObserver::CancelEditAction() { 1394 MOZ_LOG(sIMECOLog, LogLevel::Debug, ("0x%p CancelEditAction()", this)); 1395 1396 if (mAddedContentCache.HasCache()) { 1397 NotifyIMEOfCachedConsecutiveNewNodes(__FUNCTION__); 1398 } 1399 mEndOfAddedTextCache.Clear(__FUNCTION__); 1400 mStartOfRemovingTextRangeCache.Clear(__FUNCTION__); 1401 FlushMergeableNotifications(); 1402 } 1403 1404 bool IMEContentObserver::EditorIsHandlingEditSubAction() const { 1405 return mEditorBase && mEditorBase->IsInEditSubAction(); 1406 } 1407 1408 void IMEContentObserver::PostFocusSetNotification() { 1409 MOZ_LOG(sIMECOLog, LogLevel::Debug, 1410 ("0x%p PostFocusSetNotification()", this)); 1411 1412 mNeedsToNotifyIMEOfFocusSet = true; 1413 } 1414 1415 void IMEContentObserver::PostTextChangeNotification() { 1416 MOZ_LOG(sIMECOLog, LogLevel::Debug, 1417 ("0x%p PostTextChangeNotification(mTextChangeData=%s)", this, 1418 ToString(mTextChangeData).c_str())); 1419 1420 MOZ_ASSERT(mTextChangeData.IsValid(), 1421 "mTextChangeData must have text change data"); 1422 mNeedsToNotifyIMEOfTextChange = true; 1423 // Even if the observer hasn't received selection change, selection in the 1424 // flat text may have already been changed. For example, when previous `<p>` 1425 // element of another `<p>` element which contains caret is removed by a DOM 1426 // mutation, selection change event won't be fired, but selection start offset 1427 // should be decreased by the length of removed `<p>` element. 1428 // In such case, HandleQueryContentEvent shouldn't use the selection cache 1429 // anymore. Therefore, we also need to post selection change notification 1430 // too. eQuerySelectedText event may be dispatched at sending a text change 1431 // notification. 1432 mNeedsToNotifyIMEOfSelectionChange = true; 1433 } 1434 1435 void IMEContentObserver::PostSelectionChangeNotification() { 1436 MOZ_LOG(sIMECOLog, LogLevel::Debug, 1437 ("0x%p PostSelectionChangeNotification(), mSelectionData={ " 1438 "mCausedByComposition=%s, mCausedBySelectionEvent=%s }", 1439 this, ToChar(mSelectionData.mCausedByComposition), 1440 ToChar(mSelectionData.mCausedBySelectionEvent))); 1441 1442 mNeedsToNotifyIMEOfSelectionChange = true; 1443 } 1444 1445 void IMEContentObserver::MaybeNotifyIMEOfFocusSet() { 1446 MOZ_LOG(sIMECOLog, LogLevel::Debug, 1447 ("0x%p MaybeNotifyIMEOfFocusSet()", this)); 1448 1449 PostFocusSetNotification(); 1450 FlushMergeableNotifications(); 1451 } 1452 1453 void IMEContentObserver::MaybeNotifyIMEOfTextChange( 1454 const TextChangeDataBase& aTextChangeData) { 1455 MOZ_LOG(sIMECOLog, LogLevel::Debug, 1456 ("0x%p MaybeNotifyIMEOfTextChange(aTextChangeData=%s)", this, 1457 ToString(aTextChangeData).c_str())); 1458 1459 if (mEditorBase && mEditorBase->IsTextEditor()) { 1460 MOZ_DIAGNOSTIC_ASSERT(static_cast<int64_t>(mTextControlValueLength) + 1461 aTextChangeData.Difference() >= 1462 0); 1463 mTextControlValueLength += aTextChangeData.Difference(); 1464 } 1465 1466 mTextChangeData += aTextChangeData; 1467 PostTextChangeNotification(); 1468 FlushMergeableNotifications(); 1469 } 1470 1471 void IMEContentObserver::CancelNotifyingIMEOfTextChange() { 1472 MOZ_LOG(sIMECOLog, LogLevel::Debug, 1473 ("0x%p CancelNotifyingIMEOfTextChange()", this)); 1474 mTextChangeData.Clear(); 1475 mNeedsToNotifyIMEOfTextChange = false; 1476 } 1477 1478 void IMEContentObserver::MaybeNotifyIMEOfSelectionChange( 1479 bool aCausedByComposition, bool aCausedBySelectionEvent, 1480 bool aOccurredDuringComposition) { 1481 MOZ_LOG( 1482 sIMECOLog, LogLevel::Debug, 1483 ("0x%p MaybeNotifyIMEOfSelectionChange(aCausedByComposition=%s, " 1484 "aCausedBySelectionEvent=%s, aOccurredDuringComposition)", 1485 this, ToChar(aCausedByComposition), ToChar(aCausedBySelectionEvent))); 1486 1487 mSelectionData.AssignReason(aCausedByComposition, aCausedBySelectionEvent, 1488 aOccurredDuringComposition); 1489 PostSelectionChangeNotification(); 1490 FlushMergeableNotifications(); 1491 } 1492 1493 void IMEContentObserver::MaybeNotifyIMEOfPositionChange() { 1494 MOZ_LOG(sIMECOLog, LogLevel::Verbose, 1495 ("0x%p MaybeNotifyIMEOfPositionChange()", this)); 1496 // If reflow is caused by ContentEventHandler during PositionChangeEvent 1497 // sending NOTIFY_IME_OF_POSITION_CHANGE, we don't need to notify IME of it 1498 // again since ContentEventHandler returns the result including this reflow's 1499 // result. 1500 if (mIsHandlingQueryContentEvent && 1501 mSendingNotification == NOTIFY_IME_OF_POSITION_CHANGE) { 1502 MOZ_LOG(sIMECOLog, LogLevel::Verbose, 1503 ("0x%p MaybeNotifyIMEOfPositionChange(), ignored since caused by " 1504 "ContentEventHandler during sending NOTIFY_IME_OF_POSITION_CHANGE", 1505 this)); 1506 return; 1507 } 1508 PostPositionChangeNotification(); 1509 FlushMergeableNotifications(); 1510 } 1511 1512 void IMEContentObserver::CancelNotifyingIMEOfPositionChange() { 1513 MOZ_LOG(sIMECOLog, LogLevel::Debug, 1514 ("0x%p CancelNotifyIMEOfPositionChange()", this)); 1515 mNeedsToNotifyIMEOfPositionChange = false; 1516 } 1517 1518 void IMEContentObserver::MaybeNotifyCompositionEventHandled() { 1519 MOZ_LOG(sIMECOLog, LogLevel::Debug, 1520 ("0x%p MaybeNotifyCompositionEventHandled()", this)); 1521 1522 PostCompositionEventHandledNotification(); 1523 FlushMergeableNotifications(); 1524 } 1525 1526 bool IMEContentObserver::UpdateSelectionCache(bool aRequireFlush /* = true */) { 1527 MOZ_ASSERT(IsSafeToNotifyIME()); 1528 1529 mSelectionData.ClearSelectionData(); 1530 1531 // XXX Cannot we cache some information for reducing the cost to compute 1532 // selection offset and writing mode? 1533 WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText, 1534 mWidget); 1535 querySelectedTextEvent.mNeedsToFlushLayout = aRequireFlush; 1536 ContentEventHandler handler(GetPresContext()); 1537 handler.OnQuerySelectedText(&querySelectedTextEvent); 1538 if (NS_WARN_IF(querySelectedTextEvent.Failed()) || 1539 NS_WARN_IF(querySelectedTextEvent.mReply->mContentsRoot != 1540 mRootElement)) { 1541 return false; 1542 } 1543 1544 mFocusedWidget = querySelectedTextEvent.mReply->mFocusedWidget; 1545 mSelectionData.Assign(querySelectedTextEvent); 1546 1547 // WARNING: Don't set the reason of selection change here because it should be 1548 // set the reason at sending the notification. 1549 1550 MOZ_LOG(sIMECOLog, LogLevel::Debug, 1551 ("0x%p UpdateSelectionCache(), mSelectionData=%s", this, 1552 ToString(mSelectionData).c_str())); 1553 1554 return true; 1555 } 1556 1557 void IMEContentObserver::PostPositionChangeNotification() { 1558 MOZ_LOG(sIMECOLog, LogLevel::Debug, 1559 ("0x%p PostPositionChangeNotification()", this)); 1560 1561 mNeedsToNotifyIMEOfPositionChange = true; 1562 } 1563 1564 void IMEContentObserver::PostCompositionEventHandledNotification() { 1565 MOZ_LOG(sIMECOLog, LogLevel::Debug, 1566 ("0x%p PostCompositionEventHandledNotification()", this)); 1567 1568 mNeedsToNotifyIMEOfCompositionEventHandled = true; 1569 } 1570 1571 bool IMEContentObserver::IsReflowLocked() const { 1572 nsPresContext* presContext = GetPresContext(); 1573 if (NS_WARN_IF(!presContext)) { 1574 return false; 1575 } 1576 PresShell* presShell = presContext->GetPresShell(); 1577 if (NS_WARN_IF(!presShell)) { 1578 return false; 1579 } 1580 // During reflow, we shouldn't notify IME because IME may query content 1581 // synchronously. Then, it causes ContentEventHandler will try to flush 1582 // pending notifications during reflow. 1583 return presShell->IsReflowLocked(); 1584 } 1585 1586 bool IMEContentObserver::IsSafeToNotifyIME() const { 1587 // If this is already detached from the widget, this doesn't need to notify 1588 // anything. 1589 if (!mWidget) { 1590 MOZ_LOG(sIMECOLog, LogLevel::Debug, 1591 ("0x%p IsSafeToNotifyIME(), it's not safe because of no widget", 1592 this)); 1593 return false; 1594 } 1595 1596 // Don't notify IME of anything if it's not good time to do it. 1597 if (mSuppressNotifications) { 1598 MOZ_LOG(sIMECOLog, LogLevel::Debug, 1599 ("0x%p IsSafeToNotifyIME(), it's not safe because of no widget", 1600 this)); 1601 return false; 1602 } 1603 1604 if (!mESM || NS_WARN_IF(!GetPresContext())) { 1605 MOZ_LOG(sIMECOLog, LogLevel::Debug, 1606 ("0x%p IsSafeToNotifyIME(), it's not safe because of no " 1607 "EventStateManager and/or PresContext", 1608 this)); 1609 return false; 1610 } 1611 1612 // If it's in reflow, we should wait to finish the reflow. 1613 // FYI: This should be called again from Reflow() or ReflowInterruptible(). 1614 if (IsReflowLocked()) { 1615 MOZ_LOG( 1616 sIMECOLog, LogLevel::Debug, 1617 ("0x%p IsSafeToNotifyIME(), it's not safe because of reflow locked", 1618 this)); 1619 return false; 1620 } 1621 1622 // If we're in handling an edit action, this method will be called later. 1623 if (EditorIsHandlingEditSubAction()) { 1624 MOZ_LOG(sIMECOLog, LogLevel::Debug, 1625 ("0x%p IsSafeToNotifyIME(), it's not safe because of focused " 1626 "editor handling somethings", 1627 this)); 1628 return false; 1629 } 1630 1631 return true; 1632 } 1633 1634 void IMEContentObserver::FlushMergeableNotifications() { 1635 if (!IsSafeToNotifyIME()) { 1636 // So, if this is already called, this should do nothing. 1637 MOZ_LOG(sIMECOLog, LogLevel::Warning, 1638 ("0x%p FlushMergeableNotifications(), Warning, do nothing due to " 1639 "unsafe to notify IME", 1640 this)); 1641 return; 1642 } 1643 1644 // Notifying something may cause nested call of this method. For example, 1645 // when somebody notified one of the notifications may dispatch query content 1646 // event. Then, it causes flushing layout which may cause another layout 1647 // change notification. 1648 1649 if (mQueuedSender) { 1650 // So, if this is already called, this should do nothing. 1651 MOZ_LOG(sIMECOLog, LogLevel::Warning, 1652 ("0x%p FlushMergeableNotifications(), Warning, do nothing due to " 1653 "already flushing pending notifications", 1654 this)); 1655 return; 1656 } 1657 1658 // If text change notification and/or position change notification becomes 1659 // unnecessary, let's cancel them. 1660 if (mNeedsToNotifyIMEOfTextChange && !NeedsTextChangeNotification()) { 1661 CancelNotifyingIMEOfTextChange(); 1662 } 1663 if (mNeedsToNotifyIMEOfPositionChange && !NeedsPositionChangeNotification()) { 1664 CancelNotifyingIMEOfPositionChange(); 1665 } 1666 1667 if (!NeedsToNotifyIMEOfSomething()) { 1668 MOZ_LOG(sIMECOLog, LogLevel::Warning, 1669 ("0x%p FlushMergeableNotifications(), Warning, due to no pending " 1670 "notifications", 1671 this)); 1672 return; 1673 } 1674 1675 // NOTE: Reset each pending flag because sending notification may cause 1676 // another change. 1677 1678 MOZ_LOG( 1679 sIMECOLog, LogLevel::Info, 1680 ("0x%p FlushMergeableNotifications(), creating IMENotificationSender...", 1681 this)); 1682 1683 // If contents in selection range is modified, the selection range still 1684 // has removed node from the tree. In such case, ContentIterator won't 1685 // work well. Therefore, we shouldn't use AddScriptRunner() here since 1686 // it may kick runnable event immediately after DOM tree is changed but 1687 // the selection range isn't modified yet. 1688 mQueuedSender = new IMENotificationSender(this); 1689 mQueuedSender->Dispatch(mDocShell); 1690 MOZ_LOG(sIMECOLog, LogLevel::Debug, 1691 ("0x%p FlushMergeableNotifications(), finished", this)); 1692 } 1693 1694 void IMEContentObserver::TryToFlushPendingNotifications(bool aAllowAsync) { 1695 // If a sender instance is sending notifications, we shouldn't try to create 1696 // a new sender again because the sender will recreate by itself if there are 1697 // new pending notifications. 1698 if (mSendingNotification != NOTIFY_IME_OF_NOTHING) { 1699 return; 1700 } 1701 1702 // When the caller allows to put off notifying IME, we can wait the next 1703 // call of this method or to run the queued sender. 1704 if (mQueuedSender && XRE_IsContentProcess() && aAllowAsync) { 1705 return; 1706 } 1707 1708 if (!mQueuedSender) { 1709 // If it was not safe to dispatch notifications when the pending 1710 // notifications are posted, this may not have IMENotificationSender 1711 // instance because it couldn't dispatch it, e.g., when an edit sub-action 1712 // is being handled in the editor, we shouldn't do it even if it's safe to 1713 // run script. Therefore, we need to create the sender instance here in the 1714 // case. 1715 if (!NeedsToNotifyIMEOfSomething()) { 1716 return; 1717 } 1718 mQueuedSender = new IMENotificationSender(this); 1719 } 1720 1721 MOZ_LOG(sIMECOLog, LogLevel::Debug, 1722 ("0x%p TryToFlushPendingNotifications(), performing queued " 1723 "IMENotificationSender forcibly", 1724 this)); 1725 RefPtr<IMENotificationSender> queuedSender = mQueuedSender; 1726 queuedSender->Run(); 1727 } 1728 1729 /****************************************************************************** 1730 * mozilla::IMEContentObserver::AChangeEvent 1731 ******************************************************************************/ 1732 1733 bool IMEContentObserver::AChangeEvent::CanNotifyIME( 1734 ChangeEventType aChangeEventType) const { 1735 RefPtr<IMEContentObserver> observer = GetObserver(); 1736 if (NS_WARN_IF(!observer)) { 1737 return false; 1738 } 1739 1740 const LogLevel debugOrVerbose = 1741 aChangeEventType == ChangeEventType::eChangeEventType_Position 1742 ? LogLevel::Verbose 1743 : LogLevel::Debug; 1744 1745 if (aChangeEventType == eChangeEventType_CompositionEventHandled) { 1746 if (observer->mWidget) { 1747 return true; 1748 } 1749 MOZ_LOG(sIMECOLog, debugOrVerbose, 1750 ("0x%p AChangeEvent::CanNotifyIME(), Cannot notify IME of " 1751 "composition event handled because of no widget", 1752 this)); 1753 return false; 1754 } 1755 State state = observer->GetState(); 1756 // If it's not initialized, we should do nothing. 1757 if (state == eState_NotObserving) { 1758 MOZ_LOG(sIMECOLog, debugOrVerbose, 1759 ("0x%p AChangeEvent::CanNotifyIME(), Cannot notify IME because " 1760 "of not observing", 1761 this)); 1762 return false; 1763 } 1764 // If setting focus, just check the state. 1765 if (aChangeEventType == eChangeEventType_Focus) { 1766 if (!observer->mIMEHasFocus) { 1767 return true; 1768 } 1769 MOZ_LOG(sIMECOLog, debugOrVerbose, 1770 ("0x%p AChangeEvent::CanNotifyIME(), Cannot notify IME of focus " 1771 "change because of already focused", 1772 this)); 1773 NS_WARNING("IME already has focus"); 1774 return false; 1775 } 1776 // If we've not notified IME of focus yet, we shouldn't notify anything. 1777 if (!observer->mIMEHasFocus) { 1778 MOZ_LOG(sIMECOLog, debugOrVerbose, 1779 ("0x%p AChangeEvent::CanNotifyIME(), Cannot notify IME because " 1780 "of not focused", 1781 this)); 1782 return false; 1783 } 1784 1785 // If IME has focus, IMEContentObserver must hold the widget. 1786 MOZ_ASSERT(observer->mWidget); 1787 1788 return true; 1789 } 1790 1791 bool IMEContentObserver::AChangeEvent::IsSafeToNotifyIME( 1792 ChangeEventType aChangeEventType) const { 1793 const LogLevel warningOrVerbose = 1794 aChangeEventType == ChangeEventType::eChangeEventType_Position 1795 ? LogLevel::Verbose 1796 : LogLevel::Warning; 1797 1798 if (NS_WARN_IF(!nsContentUtils::IsSafeToRunScript())) { 1799 MOZ_LOG(sIMECOLog, warningOrVerbose, 1800 ("0x%p AChangeEvent::IsSafeToNotifyIME(), Warning, Cannot notify " 1801 "IME because of not safe to run script", 1802 this)); 1803 return false; 1804 } 1805 1806 RefPtr<IMEContentObserver> observer = GetObserver(); 1807 if (!observer) { 1808 MOZ_LOG(sIMECOLog, warningOrVerbose, 1809 ("0x%p AChangeEvent::IsSafeToNotifyIME(), Warning, Cannot notify " 1810 "IME because of no observer", 1811 this)); 1812 return false; 1813 } 1814 1815 // While we're sending a notification, we shouldn't send another notification 1816 // recursively. 1817 if (observer->mSendingNotification != NOTIFY_IME_OF_NOTHING) { 1818 MOZ_LOG(sIMECOLog, warningOrVerbose, 1819 ("0x%p AChangeEvent::IsSafeToNotifyIME(), Warning, Cannot notify " 1820 "IME because of the observer sending another notification", 1821 this)); 1822 return false; 1823 } 1824 State state = observer->GetState(); 1825 if (aChangeEventType == eChangeEventType_Focus) { 1826 if (NS_WARN_IF(state != eState_Initializing && state != eState_Observing)) { 1827 MOZ_LOG(sIMECOLog, warningOrVerbose, 1828 ("0x%p AChangeEvent::IsSafeToNotifyIME(), Warning, Cannot " 1829 "notify IME of focus because of not observing", 1830 this)); 1831 return false; 1832 } 1833 } else if (aChangeEventType == eChangeEventType_CompositionEventHandled) { 1834 // It doesn't need to check the observing status. 1835 } else if (state != eState_Observing) { 1836 MOZ_LOG(sIMECOLog, warningOrVerbose, 1837 ("0x%p AChangeEvent::IsSafeToNotifyIME(), Warning, Cannot notify " 1838 "IME because of not observing", 1839 this)); 1840 return false; 1841 } 1842 return observer->IsSafeToNotifyIME(); 1843 } 1844 1845 /****************************************************************************** 1846 * mozilla::IMEContentObserver::IMENotificationSender 1847 ******************************************************************************/ 1848 1849 void IMEContentObserver::IMENotificationSender::Dispatch( 1850 nsIDocShell* aDocShell) { 1851 if (XRE_IsContentProcess() && aDocShell) { 1852 if (RefPtr<nsPresContext> presContext = aDocShell->GetPresContext()) { 1853 if (nsRefreshDriver* refreshDriver = presContext->RefreshDriver()) { 1854 refreshDriver->AddEarlyRunner(this); 1855 return; 1856 } 1857 } 1858 } 1859 NS_DispatchToCurrentThread(this); 1860 } 1861 1862 NS_IMETHODIMP 1863 IMEContentObserver::IMENotificationSender::Run() { 1864 if (NS_WARN_IF(mIsRunning)) { 1865 MOZ_LOG( 1866 sIMECOLog, LogLevel::Error, 1867 ("0x%p IMENotificationSender::Run(), FAILED, due to called recursively", 1868 this)); 1869 return NS_OK; 1870 } 1871 1872 RefPtr<IMEContentObserver> observer = GetObserver(); 1873 if (!observer) { 1874 return NS_OK; 1875 } 1876 1877 AutoRestore<bool> running(mIsRunning); 1878 mIsRunning = true; 1879 1880 // This instance was already performed forcibly. 1881 if (observer->mQueuedSender != this) { 1882 return NS_OK; 1883 } 1884 1885 // NOTE: Reset each pending flag because sending notification may cause 1886 // another change. 1887 1888 if (observer->mNeedsToNotifyIMEOfFocusSet) { 1889 observer->mNeedsToNotifyIMEOfFocusSet = false; 1890 SendFocusSet(); 1891 observer->mQueuedSender = nullptr; 1892 // If it's not safe to notify IME of focus, SendFocusSet() sets 1893 // mNeedsToNotifyIMEOfFocusSet true again. For guaranteeing to send the 1894 // focus notification later, we should put a new sender into the queue but 1895 // this case must be rare. Note that if mIMEContentObserver is already 1896 // destroyed, mNeedsToNotifyIMEOfFocusSet is never set true again. 1897 if (observer->mNeedsToNotifyIMEOfFocusSet) { 1898 MOZ_ASSERT(!observer->mIMEHasFocus); 1899 MOZ_LOG(sIMECOLog, LogLevel::Debug, 1900 ("0x%p IMENotificationSender::Run(), posting " 1901 "IMENotificationSender to current thread", 1902 this)); 1903 observer->mQueuedSender = new IMENotificationSender(observer); 1904 observer->mQueuedSender->Dispatch(observer->mDocShell); 1905 return NS_OK; 1906 } 1907 // This is the first notification to IME. So, we don't need to notify 1908 // anymore since IME starts to query content after it gets focus. 1909 observer->ClearPendingNotifications(); 1910 return NS_OK; 1911 } 1912 1913 if (observer->mNeedsToNotifyIMEOfTextChange) { 1914 observer->mNeedsToNotifyIMEOfTextChange = false; 1915 SendTextChange(); 1916 } 1917 1918 // If a text change notification causes another text change again, we should 1919 // notify IME of that before sending a selection change notification. 1920 if (!observer->mNeedsToNotifyIMEOfTextChange) { 1921 // Be aware, PuppetWidget depends on the order of this. A selection change 1922 // notification should not be sent before a text change notification because 1923 // PuppetWidget shouldn't query new text content every selection change. 1924 if (observer->mNeedsToNotifyIMEOfSelectionChange) { 1925 observer->mNeedsToNotifyIMEOfSelectionChange = false; 1926 SendSelectionChange(); 1927 } 1928 } 1929 1930 // If a text change notification causes another text change again or a 1931 // selection change notification causes either a text change or another 1932 // selection change, we should notify IME of those before sending a position 1933 // change notification. 1934 if (!observer->mNeedsToNotifyIMEOfTextChange && 1935 !observer->mNeedsToNotifyIMEOfSelectionChange) { 1936 if (observer->mNeedsToNotifyIMEOfPositionChange) { 1937 observer->mNeedsToNotifyIMEOfPositionChange = false; 1938 SendPositionChange(); 1939 } 1940 } 1941 1942 // Composition event handled notification should be sent after all the 1943 // other notifications because this notifies widget of finishing all pending 1944 // events are handled completely. 1945 if (!observer->mNeedsToNotifyIMEOfTextChange && 1946 !observer->mNeedsToNotifyIMEOfSelectionChange && 1947 !observer->mNeedsToNotifyIMEOfPositionChange) { 1948 if (observer->mNeedsToNotifyIMEOfCompositionEventHandled) { 1949 observer->mNeedsToNotifyIMEOfCompositionEventHandled = false; 1950 SendCompositionEventHandled(); 1951 } 1952 } 1953 1954 observer->mQueuedSender = nullptr; 1955 1956 // If notifications caused some new change, we should notify them now. 1957 if (observer->NeedsToNotifyIMEOfSomething()) { 1958 if (observer->GetState() == eState_StoppedObserving) { 1959 MOZ_LOG(sIMECOLog, LogLevel::Debug, 1960 ("0x%p IMENotificationSender::Run(), waiting " 1961 "IMENotificationSender to be reinitialized", 1962 this)); 1963 } else { 1964 MOZ_LOG(sIMECOLog, LogLevel::Debug, 1965 ("0x%p IMENotificationSender::Run(), posting " 1966 "IMENotificationSender to current thread", 1967 this)); 1968 observer->mQueuedSender = new IMENotificationSender(observer); 1969 observer->mQueuedSender->Dispatch(observer->mDocShell); 1970 } 1971 } 1972 return NS_OK; 1973 } 1974 1975 void IMEContentObserver::IMENotificationSender::SendFocusSet() { 1976 RefPtr<IMEContentObserver> observer = GetObserver(); 1977 if (!observer) { 1978 return; 1979 } 1980 1981 if (!CanNotifyIME(eChangeEventType_Focus)) { 1982 // If IMEContentObserver has already gone, we don't need to notify IME of 1983 // focus. 1984 MOZ_LOG(sIMECOLog, LogLevel::Warning, 1985 ("0x%p IMENotificationSender::SendFocusSet(), Warning, does not " 1986 "send notification due to impossible to notify IME of focus", 1987 this)); 1988 observer->ClearPendingNotifications(); 1989 return; 1990 } 1991 1992 if (!IsSafeToNotifyIME(eChangeEventType_Focus)) { 1993 MOZ_LOG( 1994 sIMECOLog, LogLevel::Warning, 1995 ("0x%p IMENotificationSender::SendFocusSet(), Warning, does not send " 1996 "notification due to unsafe, retrying to send NOTIFY_IME_OF_FOCUS...", 1997 this)); 1998 observer->PostFocusSetNotification(); 1999 return; 2000 } 2001 2002 observer->mIMEHasFocus = true; 2003 // Initialize selection cache with the first selection data. However, this 2004 // may be handled synchronously when the editor gets focus. In that case, 2005 // some frames may be dirty and they may be required to get caret frame in 2006 // ContentEventHandler::Init() to get the nearest widget from the selection. 2007 // Therefore, we need to update selection cache with flushing the pending 2008 // notifications. 2009 observer->UpdateSelectionCache(true); 2010 MOZ_LOG(sIMECOLog, LogLevel::Info, 2011 ("0x%p IMENotificationSender::SendFocusSet(), sending " 2012 "NOTIFY_IME_OF_FOCUS...", 2013 this)); 2014 2015 MOZ_RELEASE_ASSERT(observer->mSendingNotification == NOTIFY_IME_OF_NOTHING); 2016 observer->mSendingNotification = NOTIFY_IME_OF_FOCUS; 2017 IMEStateManager::NotifyIME(IMENotification(NOTIFY_IME_OF_FOCUS), 2018 observer->mWidget); 2019 observer->mSendingNotification = NOTIFY_IME_OF_NOTHING; 2020 2021 // IMENotificationRequests referred by ObserveEditableNode() may be different 2022 // before or after widget receives NOTIFY_IME_OF_FOCUS. Therefore, we need 2023 // to guarantee to call ObserveEditableNode() after sending 2024 // NOTIFY_IME_OF_FOCUS. 2025 observer->OnIMEReceivedFocus(); 2026 2027 MOZ_LOG( 2028 sIMECOLog, LogLevel::Debug, 2029 ("0x%p IMENotificationSender::SendFocusSet(), sent NOTIFY_IME_OF_FOCUS", 2030 this)); 2031 } 2032 2033 void IMEContentObserver::IMENotificationSender::SendSelectionChange() { 2034 RefPtr<IMEContentObserver> observer = GetObserver(); 2035 if (!observer) { 2036 return; 2037 } 2038 2039 if (!CanNotifyIME(eChangeEventType_Selection)) { 2040 MOZ_LOG(sIMECOLog, LogLevel::Warning, 2041 ("0x%p IMENotificationSender::SendSelectionChange(), Warning, " 2042 "does not send notification due to impossible to notify IME of " 2043 "selection change", 2044 this)); 2045 return; 2046 } 2047 2048 if (!IsSafeToNotifyIME(eChangeEventType_Selection)) { 2049 MOZ_LOG(sIMECOLog, LogLevel::Warning, 2050 ("0x%p IMENotificationSender::SendSelectionChange(), Warning, " 2051 "does not send notification due to unsafe, retrying to send " 2052 "NOTIFY_IME_OF_SELECTION_CHANGE...", 2053 this)); 2054 observer->PostSelectionChangeNotification(); 2055 return; 2056 } 2057 2058 SelectionChangeData lastSelChangeData = observer->mSelectionData; 2059 if (NS_WARN_IF(!observer->UpdateSelectionCache())) { 2060 MOZ_LOG(sIMECOLog, LogLevel::Error, 2061 ("0x%p IMENotificationSender::SendSelectionChange(), FAILED, due " 2062 "to UpdateSelectionCache() failure", 2063 this)); 2064 return; 2065 } 2066 2067 // The state may be changed since querying content causes flushing layout. 2068 if (!CanNotifyIME(eChangeEventType_Selection)) { 2069 MOZ_LOG(sIMECOLog, LogLevel::Error, 2070 ("0x%p IMENotificationSender::SendSelectionChange(), FAILED, due " 2071 "to flushing layout having changed something", 2072 this)); 2073 return; 2074 } 2075 2076 // If the selection isn't changed actually, we shouldn't notify IME of 2077 // selection change. 2078 SelectionChangeData& newSelChangeData = observer->mSelectionData; 2079 if (lastSelChangeData.IsInitialized() && 2080 lastSelChangeData.EqualsRangeAndDirectionAndWritingMode( 2081 newSelChangeData)) { 2082 MOZ_LOG( 2083 sIMECOLog, LogLevel::Debug, 2084 ("0x%p IMENotificationSender::SendSelectionChange(), not notifying IME " 2085 "of NOTIFY_IME_OF_SELECTION_CHANGE due to not changed actually", 2086 this)); 2087 return; 2088 } 2089 2090 MOZ_LOG(sIMECOLog, LogLevel::Info, 2091 ("0x%p IMENotificationSender::SendSelectionChange(), sending " 2092 "NOTIFY_IME_OF_SELECTION_CHANGE... newSelChangeData=%s", 2093 this, ToString(newSelChangeData).c_str())); 2094 2095 IMENotification notification(NOTIFY_IME_OF_SELECTION_CHANGE); 2096 notification.SetData(observer->mSelectionData); 2097 2098 MOZ_RELEASE_ASSERT(observer->mSendingNotification == NOTIFY_IME_OF_NOTHING); 2099 observer->mSendingNotification = NOTIFY_IME_OF_SELECTION_CHANGE; 2100 IMEStateManager::NotifyIME(notification, observer->mWidget); 2101 observer->mSendingNotification = NOTIFY_IME_OF_NOTHING; 2102 2103 MOZ_LOG(sIMECOLog, LogLevel::Debug, 2104 ("0x%p IMENotificationSender::SendSelectionChange(), sent " 2105 "NOTIFY_IME_OF_SELECTION_CHANGE", 2106 this)); 2107 } 2108 2109 void IMEContentObserver::IMENotificationSender::SendTextChange() { 2110 RefPtr<IMEContentObserver> observer = GetObserver(); 2111 if (!observer) { 2112 return; 2113 } 2114 2115 if (!CanNotifyIME(eChangeEventType_Text)) { 2116 MOZ_LOG( 2117 sIMECOLog, LogLevel::Warning, 2118 ("0x%p IMENotificationSender::SendTextChange(), Warning, does not " 2119 "send notification due to impossible to notify IME of text change", 2120 this)); 2121 return; 2122 } 2123 2124 if (!IsSafeToNotifyIME(eChangeEventType_Text)) { 2125 MOZ_LOG(sIMECOLog, LogLevel::Warning, 2126 ("0x%p IMENotificationSender::SendTextChange(), Warning, does " 2127 "not send notification due to unsafe, retrying to send " 2128 "NOTIFY_IME_OF_TEXT_CHANGE...", 2129 this)); 2130 observer->PostTextChangeNotification(); 2131 return; 2132 } 2133 2134 // If text change notification is unnecessary anymore, just cancel it. 2135 if (!observer->NeedsTextChangeNotification()) { 2136 MOZ_LOG(sIMECOLog, LogLevel::Warning, 2137 ("0x%p IMENotificationSender::SendTextChange(), Warning, " 2138 "canceling sending NOTIFY_IME_OF_TEXT_CHANGE", 2139 this)); 2140 observer->CancelNotifyingIMEOfTextChange(); 2141 return; 2142 } 2143 2144 MOZ_LOG(sIMECOLog, LogLevel::Info, 2145 ("0x%p IMENotificationSender::SendTextChange(), sending " 2146 "NOTIFY_IME_OF_TEXT_CHANGE... mIMEContentObserver={ " 2147 "mTextChangeData=%s }", 2148 this, ToString(observer->mTextChangeData).c_str())); 2149 2150 IMENotification notification(NOTIFY_IME_OF_TEXT_CHANGE); 2151 notification.SetData(observer->mTextChangeData); 2152 observer->mTextChangeData.Clear(); 2153 2154 MOZ_RELEASE_ASSERT(observer->mSendingNotification == NOTIFY_IME_OF_NOTHING); 2155 observer->mSendingNotification = NOTIFY_IME_OF_TEXT_CHANGE; 2156 IMEStateManager::NotifyIME(notification, observer->mWidget); 2157 observer->mSendingNotification = NOTIFY_IME_OF_NOTHING; 2158 2159 MOZ_LOG(sIMECOLog, LogLevel::Debug, 2160 ("0x%p IMENotificationSender::SendTextChange(), sent " 2161 "NOTIFY_IME_OF_TEXT_CHANGE", 2162 this)); 2163 } 2164 2165 void IMEContentObserver::IMENotificationSender::SendPositionChange() { 2166 RefPtr<IMEContentObserver> observer = GetObserver(); 2167 if (!observer) { 2168 return; 2169 } 2170 2171 if (!CanNotifyIME(eChangeEventType_Position)) { 2172 MOZ_LOG(sIMECOLog, LogLevel::Verbose, 2173 ("0x%p IMENotificationSender::SendPositionChange(), Warning, " 2174 "does not send notification due to impossible to notify IME of " 2175 "position change", 2176 this)); 2177 return; 2178 } 2179 2180 if (!IsSafeToNotifyIME(eChangeEventType_Position)) { 2181 MOZ_LOG(sIMECOLog, LogLevel::Verbose, 2182 ("0x%p IMENotificationSender::SendPositionChange(), Warning, " 2183 "does not send notification due to unsafe, retrying to send " 2184 "NOTIFY_IME_OF_POSITION_CHANGE...", 2185 this)); 2186 observer->PostPositionChangeNotification(); 2187 return; 2188 } 2189 2190 // If position change notification is unnecessary anymore, just cancel it. 2191 if (!observer->NeedsPositionChangeNotification()) { 2192 MOZ_LOG(sIMECOLog, LogLevel::Verbose, 2193 ("0x%p IMENotificationSender::SendPositionChange(), Warning, " 2194 "canceling sending NOTIFY_IME_OF_POSITION_CHANGE", 2195 this)); 2196 observer->CancelNotifyingIMEOfPositionChange(); 2197 return; 2198 } 2199 2200 MOZ_LOG(sIMECOLog, LogLevel::Info, 2201 ("0x%p IMENotificationSender::SendPositionChange(), sending " 2202 "NOTIFY_IME_OF_POSITION_CHANGE...", 2203 this)); 2204 2205 MOZ_RELEASE_ASSERT(observer->mSendingNotification == NOTIFY_IME_OF_NOTHING); 2206 observer->mSendingNotification = NOTIFY_IME_OF_POSITION_CHANGE; 2207 IMEStateManager::NotifyIME(IMENotification(NOTIFY_IME_OF_POSITION_CHANGE), 2208 observer->mWidget); 2209 observer->mSendingNotification = NOTIFY_IME_OF_NOTHING; 2210 2211 MOZ_LOG(sIMECOLog, LogLevel::Debug, 2212 ("0x%p IMENotificationSender::SendPositionChange(), sent " 2213 "NOTIFY_IME_OF_POSITION_CHANGE", 2214 this)); 2215 } 2216 2217 void IMEContentObserver::IMENotificationSender::SendCompositionEventHandled() { 2218 RefPtr<IMEContentObserver> observer = GetObserver(); 2219 if (!observer) { 2220 return; 2221 } 2222 2223 if (!CanNotifyIME(eChangeEventType_CompositionEventHandled)) { 2224 MOZ_LOG(sIMECOLog, LogLevel::Warning, 2225 ("0x%p IMENotificationSender::SendCompositionEventHandled(), " 2226 "Warning, does not send notification due to impossible to notify " 2227 "IME of composition event handled", 2228 this)); 2229 return; 2230 } 2231 2232 if (!IsSafeToNotifyIME(eChangeEventType_CompositionEventHandled)) { 2233 MOZ_LOG(sIMECOLog, LogLevel::Warning, 2234 ("0x%p IMENotificationSender::SendCompositionEventHandled(), " 2235 "Warning, does not send notification due to unsafe, retrying to " 2236 "send NOTIFY_IME_OF_POSITION_CHANGE...", 2237 this)); 2238 observer->PostCompositionEventHandledNotification(); 2239 return; 2240 } 2241 2242 MOZ_LOG(sIMECOLog, LogLevel::Info, 2243 ("0x%p IMENotificationSender::SendCompositionEventHandled(), sending " 2244 "NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED...", 2245 this)); 2246 2247 MOZ_RELEASE_ASSERT(observer->mSendingNotification == NOTIFY_IME_OF_NOTHING); 2248 observer->mSendingNotification = NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED; 2249 IMEStateManager::NotifyIME( 2250 IMENotification(NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED), 2251 observer->mWidget); 2252 observer->mSendingNotification = NOTIFY_IME_OF_NOTHING; 2253 2254 MOZ_LOG(sIMECOLog, LogLevel::Debug, 2255 ("0x%p IMENotificationSender::SendCompositionEventHandled(), sent " 2256 "NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED", 2257 this)); 2258 } 2259 2260 /****************************************************************************** 2261 * mozilla::IMEContentObserver::DocumentObservingHelper 2262 ******************************************************************************/ 2263 2264 NS_IMPL_CYCLE_COLLECTION_CLASS(IMEContentObserver::DocumentObserver) 2265 2266 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IMEContentObserver::DocumentObserver) 2267 // StopObserving() releases mIMEContentObserver and mDocument. 2268 tmp->StopObserving(); 2269 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 2270 2271 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IMEContentObserver::DocumentObserver) 2272 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIMEContentObserver) 2273 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument) 2274 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 2275 2276 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IMEContentObserver::DocumentObserver) 2277 NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver) 2278 NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) 2279 NS_INTERFACE_MAP_ENTRY(nsISupports) 2280 NS_INTERFACE_MAP_END 2281 2282 NS_IMPL_CYCLE_COLLECTING_ADDREF(IMEContentObserver::DocumentObserver) 2283 NS_IMPL_CYCLE_COLLECTING_RELEASE(IMEContentObserver::DocumentObserver) 2284 2285 void IMEContentObserver::DocumentObserver::Observe(Document* aDocument) { 2286 MOZ_ASSERT(aDocument); 2287 2288 // Guarantee that aDocument won't be destroyed during a call of 2289 // StopObserving(). 2290 RefPtr<Document> newDocument = aDocument; 2291 2292 StopObserving(); 2293 2294 mDocument = std::move(newDocument); 2295 mDocument->AddObserver(this); 2296 } 2297 2298 void IMEContentObserver::DocumentObserver::StopObserving() { 2299 if (!IsObserving()) { 2300 return; 2301 } 2302 2303 // Grab IMEContentObserver which could be destroyed during method calls. 2304 RefPtr<IMEContentObserver> observer = std::move(mIMEContentObserver); 2305 2306 // Stop observing the document first. 2307 RefPtr<Document> document = std::move(mDocument); 2308 document->RemoveObserver(this); 2309 2310 // Notify IMEContentObserver of ending of document updates if this already 2311 // notified it of beginning of document updates. 2312 for (; IsUpdating(); --mDocumentUpdating) { 2313 // FYI: IsUpdating() returns true until mDocumentUpdating becomes 0. 2314 // However, IsObserving() returns false now because mDocument was 2315 // already cleared above. Therefore, this method won't be called 2316 // recursively. 2317 observer->EndDocumentUpdate(); 2318 } 2319 } 2320 2321 void IMEContentObserver::DocumentObserver::Destroy() { 2322 StopObserving(); 2323 mIMEContentObserver = nullptr; 2324 } 2325 2326 void IMEContentObserver::DocumentObserver::BeginUpdate(Document* aDocument) { 2327 if (NS_WARN_IF(Destroyed()) || NS_WARN_IF(!IsObserving())) { 2328 return; 2329 } 2330 mIMEContentObserver->BeginDocumentUpdate(); 2331 mDocumentUpdating++; 2332 } 2333 2334 void IMEContentObserver::DocumentObserver::EndUpdate(Document* aDocument) { 2335 if (NS_WARN_IF(Destroyed()) || NS_WARN_IF(!IsObserving()) || 2336 NS_WARN_IF(!IsUpdating())) { 2337 return; 2338 } 2339 mDocumentUpdating--; 2340 mIMEContentObserver->EndDocumentUpdate(); 2341 } 2342 2343 /****************************************************************************** 2344 * mozilla::IMEContentObserver::FlatTextCache 2345 ******************************************************************************/ 2346 2347 void IMEContentObserver::FlatTextCache::Clear(const char* aCallerName) { 2348 if (!HasCache()) { 2349 return; 2350 } 2351 MOZ_LOG(sCacheLog, LogLevel::Info, 2352 ("%s.Clear: called by %s", mInstanceName, aCallerName)); 2353 mContainerNode = nullptr; 2354 mContent = nullptr; 2355 mFlatTextLength = 0; 2356 } 2357 2358 nsresult IMEContentObserver::FlatTextCache:: 2359 ComputeAndCacheFlatTextLengthBeforeEndOfContent( 2360 const char* aCallerName, const nsIContent& aContent, 2361 const Element* aRootElement) { 2362 MOZ_ASSERT(aRootElement); 2363 MOZ_ASSERT(aContent.GetParentNode()); 2364 2365 uint32_t length = 0; 2366 nsresult rv = ContentEventHandler::GetFlatTextLengthInRange( 2367 RawNodePosition::BeforeFirstContentOf(*aRootElement), 2368 RawNodePosition::After(aContent), aRootElement, &length, 2369 LineBreakType::LINE_BREAK_TYPE_NATIVE); 2370 if (NS_FAILED(rv)) { 2371 Clear(aCallerName); 2372 return rv; 2373 } 2374 2375 CacheFlatTextLengthBeforeEndOfContent(aCallerName, aContent, length, 2376 aRootElement); 2377 return NS_OK; 2378 } 2379 2380 void IMEContentObserver::FlatTextCache::CacheFlatTextLengthBeforeEndOfContent( 2381 const char* aCallerName, const nsIContent& aContent, 2382 uint32_t aFlatTextLength, const dom::Element* aRootElement) { 2383 mContainerNode = aContent.GetParentNode(); 2384 mContent = const_cast<nsIContent*>(&aContent); 2385 mFlatTextLength = aFlatTextLength; 2386 MOZ_ASSERT(IsCachingToEndOfContent()); 2387 MOZ_LOG(sCacheLog, LogLevel::Info, 2388 ("%s.%s: called by %s -> %s", mInstanceName, __func__, 2389 ShortenFunctionName(aCallerName), ToString(*this).c_str())); 2390 AssertValidCache(aRootElement); 2391 } 2392 2393 nsresult IMEContentObserver::FlatTextCache:: 2394 ComputeAndCacheFlatTextLengthBeforeFirstContent( 2395 const char* aCallerName, const nsINode& aContainer, 2396 const Element* aRootElement) { 2397 MOZ_ASSERT(aRootElement); 2398 2399 const Result<uint32_t, nsresult> 2400 lengthIncludingLineBreakCausedByOpenTagOfContainer = 2401 FlatTextCache::ComputeTextLengthBeforeFirstContentOf(aContainer, 2402 aRootElement); 2403 if (MOZ_UNLIKELY( 2404 lengthIncludingLineBreakCausedByOpenTagOfContainer.isErr())) { 2405 Clear(__FUNCTION__); 2406 return lengthIncludingLineBreakCausedByOpenTagOfContainer.inspectErr(); 2407 } 2408 2409 CacheFlatTextLengthBeforeFirstContent( 2410 aCallerName, aContainer, 2411 lengthIncludingLineBreakCausedByOpenTagOfContainer.inspect(), 2412 aRootElement); 2413 return NS_OK; 2414 } 2415 2416 void IMEContentObserver::FlatTextCache::CacheFlatTextLengthBeforeFirstContent( 2417 const char* aCallerName, const nsINode& aContainer, 2418 uint32_t aFlatTextLength, const dom::Element* aRootElement) { 2419 mContainerNode = const_cast<nsINode*>(&aContainer); 2420 mContent = nullptr; 2421 mFlatTextLength = aFlatTextLength; 2422 MOZ_ASSERT(IsCachingToStartOfContainer()); 2423 MOZ_LOG(sCacheLog, LogLevel::Info, 2424 ("%s.%s: called by %s -> %s", mInstanceName, __func__, 2425 ShortenFunctionName(aCallerName), ToString(*this).c_str())); 2426 AssertValidCache(aRootElement); 2427 } 2428 2429 Maybe<uint32_t> 2430 IMEContentObserver::FlatTextCache::GetFlatTextLengthBeforeContent( 2431 const nsIContent& aContent, const dom::Element* aRootElement, 2432 ForRemoval aForRemoval) const { 2433 MOZ_ASSERT(aRootElement); 2434 if (!mContainerNode) { 2435 return Nothing(); 2436 } 2437 2438 nsIContent* const prevSibling = aContent.GetPreviousSibling(); 2439 if (IsCachingToStartOfContainer()) { 2440 MOZ_ASSERT(!mContent); 2441 // If aContent is the first child of mContainerNode and we're caching text 2442 // length before first child of mContainerNode, we're caching the result 2443 // as-is.. 2444 if (!prevSibling && mContainerNode == aContent.GetParentNode()) { 2445 return Some(mFlatTextLength); 2446 } 2447 return Nothing(); 2448 } 2449 2450 MOZ_ASSERT(IsCachingToEndOfContent()); 2451 MOZ_ASSERT(mContent); 2452 2453 // If we're caching text length before end of previous sibling of aContent, 2454 // the cached length is the result of this call. 2455 if (mContent == prevSibling) { 2456 return Some(mFlatTextLength); 2457 } 2458 2459 // If we're caching text length before end of aContent, aContent siblings 2460 // may be being removed backward because aContent is the previous sibling of 2461 // previously removed node. We should return the length with computing the 2462 // text length of aContent because it's much faster than computing the length 2463 // starting from the root element especially when there are a lot of preceding 2464 // content. 2465 if (mContent == &aContent) { 2466 const Result<uint32_t, nsresult> textLength = 2467 FlatTextCache::ComputeTextLengthOfContent(aContent, aRootElement, 2468 aForRemoval); 2469 if (NS_WARN_IF(textLength.isErr()) || 2470 NS_WARN_IF(mFlatTextLength < textLength.inspect())) { 2471 return Nothing(); 2472 } 2473 return Some(mFlatTextLength - textLength.inspect()); 2474 } 2475 return Nothing(); 2476 } 2477 2478 Maybe<uint32_t> IMEContentObserver::FlatTextCache::GetFlatTextOffsetOnInsertion( 2479 const nsIContent& aFirstContent, const nsIContent& aLastContent, 2480 const dom::Element* aRootElement) const { 2481 MOZ_ASSERT(aRootElement); 2482 MOZ_ASSERT(aFirstContent.GetParentNode() == aLastContent.GetParentNode()); 2483 MOZ_ASSERT(!aFirstContent.IsBeingRemoved()); 2484 MOZ_ASSERT(!aLastContent.IsBeingRemoved()); 2485 2486 if (!mContainerNode || mContainerNode != aFirstContent.GetParentNode()) { 2487 return Nothing(); 2488 } 2489 2490 if (IsCachingToStartOfContainer()) { 2491 MOZ_ASSERT(!mContent); 2492 // If aFirstContent is the first child of mContainerNode, we're caching the 2493 // result as-is. 2494 if (mContainerNode->GetFirstChild() == &aFirstContent) { 2495 return Some(mFlatTextLength); 2496 } 2497 return Nothing(); 2498 } 2499 2500 MOZ_ASSERT(IsCachingToEndOfContent()); 2501 MOZ_ASSERT(mContent); 2502 MOZ_ASSERT(mContent != &aFirstContent); 2503 MOZ_ASSERT(mContent != &aLastContent); 2504 2505 // When the content nodes are inserted forward, we may cache text length 2506 // before end of last inserted content. If so, mContent should be the 2507 // previous sibling of aFirstContent. Then, we can return the cached length 2508 // simply. 2509 if (mContent == aFirstContent.GetPreviousSibling()) { 2510 return Some(mFlatTextLength); 2511 } 2512 // When the content nodes inserted backward, we may cache text length before 2513 // the end of the last inserted content which is next or latter sibling of 2514 // aLastContent. In this case, we can compute the length with the cache with 2515 // computing text length starting from the next sibling of aLastContent to 2516 // mContent which were previously inserted. That must be faster than 2517 // computing the length starting from the root element. 2518 if (mContent == aLastContent.GetNextSibling() || 2519 aLastContent.ComputeIndexInParentNode().valueOr(UINT32_MAX) < 2520 mContent->ComputeIndexInParentNode().valueOr(0u)) { 2521 Result<uint32_t, nsresult> previouslyInsertedTextLengthOrError = 2522 FlatTextCache::ComputeTextLengthStartOfContentToEndOfContent( 2523 *aLastContent.GetNextSibling(), *mContent, aRootElement); 2524 if (NS_WARN_IF(previouslyInsertedTextLengthOrError.isErr()) || 2525 NS_WARN_IF(mFlatTextLength < 2526 previouslyInsertedTextLengthOrError.inspect())) { 2527 return Nothing(); 2528 } 2529 // mFlatTextLength contains the last inserted text length, but it does not 2530 // contain text length starting from aFirstContent to aLastContent. 2531 // Therefore, subtracting the last inserted text length from mFlatTextLength 2532 // equals the text length before aFirstContent. 2533 return Some(mFlatTextLength - previouslyInsertedTextLengthOrError.unwrap()); 2534 } 2535 return Nothing(); 2536 } 2537 2538 /* static */ 2539 Result<uint32_t, nsresult> 2540 IMEContentObserver::FlatTextCache::ComputeTextLengthOfContent( 2541 const nsIContent& aContent, const dom::Element* aRootElement, 2542 ForRemoval aForRemoval) { 2543 MOZ_ASSERT(aRootElement); 2544 2545 if (const Text* textNode = Text::FromNode(aContent)) { 2546 return ContentEventHandler::GetNativeTextLength(*textNode); 2547 } 2548 2549 if (aForRemoval == ForRemoval::Yes) { 2550 // When we compute the text length of the removing content node, we need to 2551 // select all children in the removing node because of the same reason 2552 // above. Therefore, if a <div> is being removed, we want to compute 2553 // `{<div>...}</div>`. In this case, we want to include the open tag of 2554 // aRemovingContent if it's an element to add the line break if it's caused 2555 // by the open tag. However, we have no way to specify it with 2556 // RawNodePosition, but ContentEventHandler::GetFlatTextLengthInRange() 2557 // treats the range as the start container is selected. Therefore, we 2558 // should use a RawNodePosition setting its container to the removed node. 2559 uint32_t textLength = 0; 2560 RawNodePosition start(const_cast<nsIContent*>(&aContent), 0u); 2561 start.mAfterOpenTag = false; 2562 nsresult rv = ContentEventHandler::GetFlatTextLengthInRange( 2563 start, RawNodePosition::AtEndOf(aContent), aRootElement, &textLength, 2564 LineBreakType::LINE_BREAK_TYPE_NATIVE, /* aIsRemovingNode = */ true); 2565 if (NS_FAILED(rv)) { 2566 return Err(rv); 2567 } 2568 return textLength; 2569 } 2570 2571 return ComputeTextLengthStartOfContentToEndOfContent(aContent, aContent, 2572 aRootElement); 2573 } 2574 2575 /* static */ 2576 Result<uint32_t, nsresult> 2577 IMEContentObserver::FlatTextCache::ComputeTextLengthBeforeContent( 2578 const nsIContent& aContent, const dom::Element* aRootElement) { 2579 uint32_t textLengthBeforeContent = 0; 2580 nsresult rv = ContentEventHandler::GetFlatTextLengthInRange( 2581 RawNodePosition::BeforeFirstContentOf(*aRootElement), 2582 RawNodePosition::Before(aContent), aRootElement, &textLengthBeforeContent, 2583 LineBreakType::LINE_BREAK_TYPE_NATIVE); 2584 if (NS_FAILED(rv)) { 2585 return Err(rv); 2586 } 2587 return textLengthBeforeContent; 2588 } 2589 2590 /* static */ 2591 Result<uint32_t, nsresult> IMEContentObserver::FlatTextCache:: 2592 ComputeTextLengthStartOfContentToEndOfContent( 2593 const nsIContent& aStartContent, const nsIContent& aEndContent, 2594 const dom::Element* aRootElement) { 2595 uint32_t textLength = 0; 2596 nsresult rv = ContentEventHandler::GetFlatTextLengthInRange( 2597 RawNodePosition::Before(aStartContent), 2598 RawNodePosition::After(aEndContent), aRootElement, &textLength, 2599 LineBreakType::LINE_BREAK_TYPE_NATIVE); 2600 if (NS_FAILED(rv)) { 2601 return Err(rv); 2602 } 2603 return textLength; 2604 } 2605 2606 /* static */ 2607 Result<uint32_t, nsresult> 2608 IMEContentObserver::FlatTextCache::ComputeTextLengthBeforeFirstContentOf( 2609 const nsINode& aContainer, const dom::Element* aRootElement) { 2610 uint32_t lengthIncludingLineBreakCausedByOpenTagOfContent = 0; 2611 nsresult rv = ContentEventHandler::GetFlatTextLengthInRange( 2612 RawNodePosition::BeforeFirstContentOf(*aRootElement), 2613 // Include the line break caused by open tag of aContainer if it's an 2614 // element when we cache text length before first content of aContainer. 2615 RawNodePosition(const_cast<nsINode*>(&aContainer), nullptr), aRootElement, 2616 &lengthIncludingLineBreakCausedByOpenTagOfContent, 2617 LineBreakType::LINE_BREAK_TYPE_NATIVE); 2618 if (NS_FAILED(rv)) { 2619 return Err(rv); 2620 } 2621 return lengthIncludingLineBreakCausedByOpenTagOfContent; 2622 } 2623 2624 void IMEContentObserver::FlatTextCache::AssertValidCache( 2625 const Element* aRootElement) const { 2626 #ifdef DEBUG 2627 if (MOZ_LIKELY( 2628 !StaticPrefs::test_ime_content_observer_assert_valid_cache())) { 2629 return; 2630 } 2631 MOZ_ASSERT(aRootElement); 2632 if (!mContainerNode) { 2633 return; 2634 } 2635 MOZ_ASSERT(mContainerNode->IsInclusiveDescendantOf(aRootElement)); 2636 MOZ_ASSERT_IF(mContent, mContent->IsInclusiveDescendantOf(aRootElement)); 2637 2638 if (IsCachingToEndOfContent()) { 2639 MOZ_ASSERT(mContent); 2640 Result<uint32_t, nsresult> offset = 2641 FlatTextCache::ComputeTextLengthBeforeContent(*mContent, aRootElement); 2642 MOZ_ASSERT(offset.isOk()); 2643 Result<uint32_t, nsresult> length = 2644 FlatTextCache::ComputeTextLengthStartOfContentToEndOfContent( 2645 *mContent, *mContent, aRootElement); 2646 MOZ_ASSERT(length.isOk()); 2647 if (mFlatTextLength != offset.inspect() + length.inspect()) { 2648 nsAutoString innerHTMLOfEditable; 2649 const_cast<Element*>(aRootElement) 2650 ->GetInnerHTML(innerHTMLOfEditable, IgnoreErrors()); 2651 NS_WARNING( 2652 nsPrintfCString( 2653 "mFlatTextLength=%u, offset: %u, length: %u, mContainerNode:%s, " 2654 "mContent=%s (%s)", 2655 mFlatTextLength, offset.inspect(), length.inspect(), 2656 ToString(mContainerNode).c_str(), ToString(*mContent).c_str(), 2657 NS_ConvertUTF16toUTF8(innerHTMLOfEditable).get()) 2658 .get()); 2659 } 2660 MOZ_ASSERT(mFlatTextLength == offset.inspect() + length.inspect()); 2661 return; 2662 } 2663 2664 MOZ_ASSERT(!mContent); 2665 MOZ_ASSERT(mContainerNode->IsContent()); 2666 Result<uint32_t, nsresult> offset = 2667 ComputeTextLengthBeforeFirstContentOf(*mContainerNode, aRootElement); 2668 MOZ_ASSERT(offset.isOk()); 2669 if (mFlatTextLength != offset.inspect()) { 2670 nsAutoString innerHTMLOfEditable; 2671 const_cast<Element*>(aRootElement) 2672 ->GetInnerHTML(innerHTMLOfEditable, IgnoreErrors()); 2673 NS_WARNING(nsPrintfCString( 2674 "mFlatTextLength=%u, offset: %u, mContainerNode:%s (%s)", 2675 mFlatTextLength, offset.inspect(), 2676 ToString(mContainerNode).c_str(), 2677 NS_ConvertUTF16toUTF8(innerHTMLOfEditable).get()) 2678 .get()); 2679 } 2680 MOZ_ASSERT(mFlatTextLength == offset.inspect()); 2681 #endif // #ifdef DEBUG 2682 } 2683 2684 void IMEContentObserver::FlatTextCache::ContentAdded( 2685 const char* aCallerName, const nsIContent& aFirstContent, 2686 const nsIContent& aLastContent, const Maybe<uint32_t>& aAddedFlatTextLength, 2687 const Element* aRootElement) { 2688 MOZ_ASSERT(nsContentUtils::ComparePoints( 2689 RawRangeBoundary(aFirstContent.GetParentNode(), 2690 aFirstContent.GetPreviousSibling()), 2691 RawRangeBoundary(aLastContent.GetParentNode(), 2692 aLastContent.GetPreviousSibling())) 2693 .value() <= 0); 2694 if (!mContainerNode) { 2695 return; // No cache. 2696 } 2697 2698 // We can keep cache without anything if the next sibling is the first added 2699 // content. 2700 if (mContent && &aFirstContent == mContent->GetNextSibling()) { 2701 return; 2702 } 2703 2704 if (IsCachingToStartOfContainer()) { 2705 MOZ_ASSERT(!mContent); 2706 // We can keep the cache if added nodes are children of mContainerNode since 2707 // we cache the text length before its first child. 2708 if (mContainerNode == aFirstContent.GetParentNode()) { 2709 AssertValidCache(aRootElement); 2710 return; 2711 } 2712 2713 // Let's clear the cache for avoiding to do anything expensive for a hot 2714 // path only for not frequent cases. Be aware, this is a hot code path 2715 // here. Therefore, expensive computation would make the DOM mutation 2716 // slower. 2717 Clear(aCallerName); 2718 return; 2719 } 2720 2721 MOZ_ASSERT(IsCachingToEndOfContent()); 2722 MOZ_ASSERT(mContent); 2723 if (aAddedFlatTextLength.isSome() && 2724 aLastContent.GetNextSibling() == mContent) { 2725 // If we cache test length before end of next sibling of the last added 2726 // content node, we can update the cached text simply. 2727 CacheFlatTextLengthBeforeEndOfContent( 2728 aCallerName, *mContent, mFlatTextLength + *aAddedFlatTextLength, 2729 aRootElement); 2730 return; 2731 } 2732 2733 // If empty nodes are appended, we can avoid to clear the cache because the 2734 // new nodes won't affect to the flattened text content. 2735 const bool addingEmptyNode = [&]() { 2736 if (aAddedFlatTextLength.isSome()) { 2737 return !aAddedFlatTextLength.value(); 2738 } 2739 if (&aFirstContent != &aLastContent) { 2740 return false; // If it's better to check here strictly, please do that. 2741 } 2742 if (aFirstContent.IsText()) { 2743 return !aFirstContent.AsText()->TextDataLength(); 2744 } 2745 if (aFirstContent.IsCharacterData()) { 2746 return true; // Should be invisible. 2747 } 2748 if (aFirstContent.HasChildren()) { 2749 return false; // If it's better to check here strictly, please do that. 2750 } 2751 Result<uint32_t, nsresult> lengthOrError = 2752 ComputeTextLengthOfContent(aFirstContent, aRootElement, ForRemoval::No); 2753 return lengthOrError.isOk() && !lengthOrError.unwrap(); 2754 }(); 2755 if (addingEmptyNode) { 2756 return; 2757 } 2758 2759 // Let's clear the cache for avoiding to do anything expensive for a hot 2760 // path only for not frequent cases. Be aware, this is a hot code path here. 2761 // Therefore, expensive computation would make the DOM mutation slower. 2762 Clear(aCallerName); 2763 } 2764 2765 void IMEContentObserver::FlatTextCache::ContentWillBeRemoved( 2766 const nsIContent& aContent, uint32_t aFlatTextLengthOfContent, 2767 const Element* aRootElement) { 2768 if (!mContainerNode) { 2769 return; // No cache. 2770 } 2771 2772 // We can keep the cache without anything if the next sibling is removed. 2773 if (mContent && mContent == aContent.GetPreviousSibling()) { 2774 return; 2775 } 2776 2777 if (IsCachingToStartOfContainer()) { 2778 MOZ_ASSERT(!mContent); 2779 // We're caching text length before first child of mContainerNode. 2780 // Therefore, if a child of mContainerNode is being removed, we can keep the 2781 // cache. 2782 if (mContainerNode == aContent.GetParentNode()) { 2783 AssertValidCache(aRootElement); 2784 return; 2785 } 2786 2787 // Let's clear the cache for avoiding to do anything expensive for a hot 2788 // path only for not frequent cases. Be aware, this is a hot code path 2789 // here. Therefore, expensive computation would make the DOM mutation 2790 // slower. 2791 Clear(__FUNCTION__); 2792 return; 2793 } 2794 2795 MOZ_ASSERT(IsCachingToEndOfContent()); 2796 if (&aContent == mContent) { 2797 MOZ_ASSERT(mFlatTextLength >= aFlatTextLengthOfContent); 2798 if (NS_WARN_IF(mFlatTextLength < aFlatTextLengthOfContent)) { 2799 Clear(__FUNCTION__); 2800 return; 2801 } 2802 // We're caching text length before end of aContent. So, if there is a 2803 // previous sibling, we can cache text length before aContent with 2804 // subtracting the text length caused by aContent from the cached value. 2805 if (nsIContent* prevSibling = aContent.GetPreviousSibling()) { 2806 CacheFlatTextLengthBeforeEndOfContent( 2807 __FUNCTION__, *prevSibling, 2808 mFlatTextLength - aFlatTextLengthOfContent, aRootElement); 2809 return; 2810 } 2811 // Otherwise, i.e., if aContent is first child of mContainerNode, we can 2812 // cache text length before first content of mContainerNode with subtracting 2813 // the text length caused by aContent from the cached value. 2814 CacheFlatTextLengthBeforeFirstContent( 2815 __FUNCTION__, *mContainerNode, 2816 mFlatTextLength - aFlatTextLengthOfContent, aRootElement); 2817 return; 2818 } 2819 2820 // If the removing content is empty, removing that won't affect to the 2821 // flattened text. Therefore, we can avoid to clear the cache. 2822 if (!aFlatTextLengthOfContent) { 2823 return; 2824 } 2825 2826 // Let's clear the cache for avoiding to do anything expensive for a hot 2827 // path only for not frequent cases. Be aware, this is a hot code path here. 2828 // Therefore, expensive computation would make the DOM mutation slower. 2829 Clear(__FUNCTION__); 2830 } 2831 2832 /****************************************************************************** 2833 * mozilla::IMEContentObserver::AddedContentCache 2834 ******************************************************************************/ 2835 2836 void IMEContentObserver::AddedContentCache::Clear(const char* aCallerName) { 2837 mFirst = nullptr; 2838 mLast = nullptr; 2839 MOZ_LOG(sCacheLog, LogLevel::Info, 2840 ("AddedContentCache::Clear: called by %s", 2841 ShortenFunctionName(aCallerName))); 2842 } 2843 2844 bool IMEContentObserver::AddedContentCache::IsInRange( 2845 const nsIContent& aContent, const dom::Element* aRootElement) const { 2846 MOZ_ASSERT(HasCache()); 2847 2848 // First, try to find sibling of mFirst from the ancestor chain of aContent. 2849 const nsIContent* sibling = [&]() -> const nsIContent* { 2850 const nsIContent* maybeSibling = &aContent; 2851 const nsIContent* const container = mFirst->GetParent(); 2852 for (const nsIContent* ancestor : aContent.AncestorsOfType<nsIContent>()) { 2853 if (ancestor == container) { 2854 return maybeSibling; 2855 } 2856 if (ancestor == aRootElement) { 2857 return nullptr; 2858 } 2859 maybeSibling = ancestor; 2860 } 2861 return nullptr; 2862 }(); 2863 if (!sibling) { 2864 return false; // Not in same container node 2865 } 2866 // Let's avoid to compute indices... 2867 if (mFirst == sibling || mLast == sibling || 2868 (mFirst != mLast && (mFirst->GetNextSibling() == sibling || 2869 sibling->GetNextSibling() == mLast))) { 2870 return true; 2871 } 2872 if (mFirst == mLast || sibling->GetNextSibling() == mFirst || 2873 mLast->GetNextSibling() == sibling || !sibling->GetPreviousSibling() || 2874 !sibling->GetNextSibling()) { 2875 return false; 2876 } 2877 const Maybe<uint32_t> index = aContent.ComputeIndexInParentNode(); 2878 MOZ_ASSERT(index.isSome()); 2879 const Maybe<uint32_t> firstIndex = mFirst->ComputeIndexInParentNode(); 2880 MOZ_ASSERT(firstIndex.isSome()); 2881 const Maybe<uint32_t> lastIndex = mLast->ComputeIndexInParentNode(); 2882 MOZ_ASSERT(lastIndex.isSome()); 2883 return firstIndex.value() < index.value() && 2884 index.value() < lastIndex.value(); 2885 } 2886 2887 bool IMEContentObserver::AddedContentCache::CanMergeWith( 2888 const nsIContent& aFirstContent, const nsIContent& aLastContent, 2889 const dom::Element* aRootElement) const { 2890 MOZ_ASSERT(HasCache()); 2891 if (aLastContent.GetNextSibling() == mFirst || 2892 mLast->GetNextSibling() == &aFirstContent) { 2893 return true; 2894 } 2895 MOZ_DIAGNOSTIC_ASSERT(aFirstContent.GetParentNode() == 2896 aLastContent.GetParentNode()); 2897 if (mFirst->GetParentNode() != aFirstContent.GetParentNode()) { 2898 return false; 2899 } 2900 const Maybe<uint32_t> newFirstIndex = 2901 aFirstContent.ComputeIndexInParentNode(); 2902 MOZ_RELEASE_ASSERT(newFirstIndex.isSome()); 2903 const Maybe<uint32_t> newLastIndex = 2904 &aFirstContent == &aLastContent ? newFirstIndex 2905 : aLastContent.ComputeIndexInParentNode(); 2906 MOZ_RELEASE_ASSERT(newLastIndex.isSome()); 2907 const Maybe<uint32_t> currentFirstIndex = mFirst->ComputeIndexInParentNode(); 2908 MOZ_RELEASE_ASSERT(currentFirstIndex.isSome()); 2909 const Maybe<uint32_t> currentLastIndex = 2910 mFirst == mLast ? currentFirstIndex : mLast->ComputeIndexInParentNode(); 2911 MOZ_RELEASE_ASSERT(currentLastIndex.isSome()); 2912 MOZ_ASSERT(!(newFirstIndex.value() < currentFirstIndex.value() && 2913 newLastIndex.value() > currentLastIndex.value()), 2914 "New content nodes shouldn't contain mFirst nor mLast"); 2915 MOZ_ASSERT(!(newFirstIndex.value() < currentFirstIndex.value() && 2916 newLastIndex.value() > currentFirstIndex.value()), 2917 "New content nodes shouldn't contain mFirst"); 2918 MOZ_ASSERT(!(newFirstIndex.value() < currentLastIndex.value() && 2919 newLastIndex.value() > currentLastIndex.value()), 2920 "New content nodes shouldn't contain mLast"); 2921 return *newFirstIndex > *currentFirstIndex && 2922 *newLastIndex < *currentLastIndex; 2923 } 2924 2925 bool IMEContentObserver::AddedContentCache::TryToCache( 2926 const nsIContent& aFirstContent, const nsIContent& aLastContent, 2927 const dom::Element* aRootElement) { 2928 if (!HasCache()) { 2929 mFirst = const_cast<nsIContent*>(&aFirstContent); 2930 mLast = const_cast<nsIContent*>(&aLastContent); 2931 MOZ_LOG( 2932 sCacheLog, LogLevel::Info, 2933 ("AddedContentCache::TryToCache: Starting to cache the range: %s - %s", 2934 ToString(mFirst).c_str(), ToString(mLast).c_str())); 2935 return true; 2936 } 2937 MOZ_ASSERT(mFirst != &aFirstContent); 2938 MOZ_ASSERT(mLast != &aLastContent); 2939 if (aLastContent.GetNextSibling() == mFirst) { 2940 MOZ_ASSERT(CanMergeWith(aFirstContent, aLastContent, aRootElement)); 2941 mFirst = const_cast<nsIContent*>(&aFirstContent); 2942 MOZ_LOG( 2943 sCacheLog, LogLevel::Info, 2944 ("AddedContentCache::TryToCache: Extending the range backward (to %s)", 2945 ToString(mFirst).c_str())); 2946 return true; 2947 } 2948 if (mLast->GetNextSibling() == &aFirstContent) { 2949 MOZ_ASSERT(CanMergeWith(aFirstContent, aLastContent, aRootElement)); 2950 mLast = const_cast<nsIContent*>(&aLastContent); 2951 MOZ_LOG( 2952 sCacheLog, LogLevel::Info, 2953 ("AddedContentCache::TryToCache: Extending the range forward (to %s)", 2954 ToString(mLast).c_str())); 2955 return true; 2956 } 2957 2958 MOZ_DIAGNOSTIC_ASSERT(aFirstContent.GetParentNode() == 2959 aLastContent.GetParentNode()); 2960 if (mFirst->GetParentNode() != aFirstContent.GetParentNode()) { 2961 MOZ_ASSERT(!CanMergeWith(aFirstContent, aLastContent, aRootElement)); 2962 return false; 2963 } 2964 const Maybe<uint32_t> newFirstIndex = 2965 aFirstContent.ComputeIndexInParentNode(); 2966 MOZ_RELEASE_ASSERT(newFirstIndex.isSome()); 2967 const Maybe<uint32_t> newLastIndex = 2968 &aFirstContent == &aLastContent ? newFirstIndex 2969 : aLastContent.ComputeIndexInParentNode(); 2970 MOZ_RELEASE_ASSERT(newLastIndex.isSome()); 2971 const Maybe<uint32_t> currentFirstIndex = mFirst->ComputeIndexInParentNode(); 2972 MOZ_RELEASE_ASSERT(currentFirstIndex.isSome()); 2973 const Maybe<uint32_t> currentLastIndex = 2974 mFirst == mLast ? currentFirstIndex : mLast->ComputeIndexInParentNode(); 2975 MOZ_RELEASE_ASSERT(currentLastIndex.isSome()); 2976 MOZ_ASSERT(!(newFirstIndex.value() < currentFirstIndex.value() && 2977 newLastIndex.value() > currentLastIndex.value()), 2978 "New content nodes shouldn't contain mFirst nor mLast"); 2979 MOZ_ASSERT(!(newFirstIndex.value() < currentFirstIndex.value() && 2980 newLastIndex.value() > currentFirstIndex.value()), 2981 "New content nodes shouldn't contain mFirst"); 2982 MOZ_ASSERT(!(newFirstIndex.value() < currentLastIndex.value() && 2983 newLastIndex.value() > currentLastIndex.value()), 2984 "New content nodes shouldn't contain mLast"); 2985 if (*newFirstIndex > *currentFirstIndex && 2986 *newLastIndex < *currentLastIndex) { 2987 MOZ_ASSERT(CanMergeWith(aFirstContent, aLastContent, aRootElement)); 2988 MOZ_LOG(sCacheLog, LogLevel::Info, 2989 ("AddedContentCache::TryToCache: New nodes in the range")); 2990 return true; 2991 } 2992 MOZ_ASSERT(!CanMergeWith(aFirstContent, aLastContent, aRootElement)); 2993 return false; 2994 } 2995 2996 Result<std::pair<uint32_t, uint32_t>, nsresult> IMEContentObserver:: 2997 AddedContentCache::ComputeFlatTextRangeBeforeInsertingNewContent( 2998 const nsIContent& aNewFirstContent, const nsIContent& aNewLastContent, 2999 const dom::Element* aRootElement, 3000 OffsetAndLengthAdjustments& aDifferences) const { 3001 MOZ_ASSERT(HasCache()); 3002 const Maybe<int32_t> newLastContentComparedWithCachedFirstContent = 3003 nsContentUtils::ComparePoints( 3004 RawRangeBoundary(aNewLastContent.GetParentNode(), 3005 aNewLastContent.GetPreviousSibling()), 3006 RawRangeBoundary(mFirst->GetParentNode(), 3007 mFirst->GetPreviousSibling())); 3008 MOZ_RELEASE_ASSERT(newLastContentComparedWithCachedFirstContent.isSome()); 3009 MOZ_ASSERT(*newLastContentComparedWithCachedFirstContent != 0); 3010 MOZ_ASSERT((*nsContentUtils::ComparePoints( 3011 RawRangeBoundary(aNewFirstContent.GetParentNode(), 3012 aNewFirstContent.GetPreviousSibling()), 3013 RawRangeBoundary(mFirst->GetParentNode(), 3014 mFirst->GetPreviousSibling())) > 0) == 3015 (*newLastContentComparedWithCachedFirstContent > 0), 3016 "New nodes shouldn't contain mFirst"); 3017 const Maybe<int32_t> newFirstContentComparedWithCachedLastContent = 3018 mLast->GetNextSibling() == &aNewFirstContent 3019 ? Some(1) 3020 : nsContentUtils::ComparePoints( 3021 RawRangeBoundary(aNewFirstContent.GetParentNode(), 3022 aNewFirstContent.GetPreviousSibling()), 3023 // aNewFirstContent and aNewLastContent may be descendants of 3024 // mLast. Then, we need to ignore the new length. Therefore, 3025 // we need to compare aNewFirstContent position with next 3026 // sibling of mLast. 3027 RawRangeBoundary(mLast->GetParentNode(), mLast)); 3028 MOZ_RELEASE_ASSERT(newFirstContentComparedWithCachedLastContent.isSome()); 3029 MOZ_ASSERT(*newFirstContentComparedWithCachedLastContent != 0); 3030 MOZ_ASSERT((*newFirstContentComparedWithCachedLastContent > 0) == 3031 (*nsContentUtils::ComparePoints( 3032 RawRangeBoundary(aNewLastContent.GetParentNode(), 3033 aNewLastContent.GetPreviousSibling()), 3034 RawRangeBoundary(mLast->GetParentNode(), mLast)) > 0), 3035 "New nodes shouldn't contain mLast"); 3036 3037 Result<uint32_t, nsresult> length = 3038 FlatTextCache::ComputeTextLengthStartOfContentToEndOfContent( 3039 *mFirst, *mLast, aRootElement); 3040 if (NS_WARN_IF(length.isErr())) { 3041 return length.propagateErr(); 3042 } 3043 Result<uint32_t, nsresult> offset = 3044 FlatTextCache::ComputeTextLengthBeforeContent(*mFirst, aRootElement); 3045 if (NS_WARN_IF(offset.isErr())) { 3046 return offset.propagateErr(); 3047 } 3048 3049 // If new content nodes are after the cached range, we can just ignore the 3050 // new content nodes. 3051 if (*newFirstContentComparedWithCachedLastContent == 1u) { 3052 aDifferences = OffsetAndLengthAdjustments{0, 0}; 3053 return std::make_pair(offset.inspect(), length.inspect()); 3054 } 3055 3056 Result<uint32_t, nsresult> newLength = 3057 FlatTextCache::ComputeTextLengthStartOfContentToEndOfContent( 3058 aNewFirstContent, aNewLastContent, aRootElement); 3059 if (NS_WARN_IF(newLength.isErr())) { 3060 return newLength.propagateErr(); 3061 } 3062 3063 // If new content nodes are in the cached range, we need to subtract the new 3064 // content length from cached content length. 3065 if (*newLastContentComparedWithCachedFirstContent == 1u) { 3066 MOZ_RELEASE_ASSERT(length.inspect() >= newLength.inspect()); 3067 aDifferences = OffsetAndLengthAdjustments{0, newLength.inspect()}; 3068 return std::make_pair(offset.inspect(), 3069 length.inspect() - newLength.inspect()); 3070 } 3071 3072 // If new content nodes are before the cached range, we need to subtract the 3073 // new content length from cached offset. 3074 MOZ_RELEASE_ASSERT(offset.inspect() >= newLength.inspect()); 3075 aDifferences = OffsetAndLengthAdjustments{newLength.inspect(), 0}; 3076 return std::make_pair(offset.inspect() - newLength.inspect(), 3077 length.inspect()); 3078 } 3079 3080 } // namespace mozilla