Bug 1885602 - Part 5: Implement navigating to the SUMO help topic from the menu heade...
[gecko.git] / dom / events / IMEContentObserver.cpp
bloba8d946933a82116a3cf27986e63416d957172d58
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/. */
7 #include "mozilla/Logging.h"
9 #include "ContentEventHandler.h"
10 #include "IMEContentObserver.h"
11 #include "mozilla/AsyncEventDispatcher.h"
12 #include "mozilla/AutoRestore.h"
13 #include "mozilla/EventStateManager.h"
14 #include "mozilla/IMEStateManager.h"
15 #include "mozilla/MouseEvents.h"
16 #include "mozilla/PresShell.h"
17 #include "mozilla/TextComposition.h"
18 #include "mozilla/TextControlElement.h"
19 #include "mozilla/TextEvents.h"
20 #include "mozilla/dom/Element.h"
21 #include "mozilla/dom/Selection.h"
22 #include "nsContentUtils.h"
23 #include "nsGkAtoms.h"
24 #include "nsAtom.h"
25 #include "nsDocShell.h"
26 #include "nsIContent.h"
27 #include "mozilla/dom/Document.h"
28 #include "nsIFrame.h"
29 #include "nsINode.h"
30 #include "nsISelectionController.h"
31 #include "nsISupports.h"
32 #include "nsIWeakReferenceUtils.h"
33 #include "nsIWidget.h"
34 #include "nsPresContext.h"
35 #include "nsRange.h"
36 #include "nsRefreshDriver.h"
37 #include "WritingModes.h"
39 namespace mozilla {
41 using RawNodePosition = ContentEventHandler::RawNodePosition;
42 using RawNodePositionBefore = ContentEventHandler::RawNodePositionBefore;
44 using namespace dom;
45 using namespace widget;
47 LazyLogModule sIMECOLog("IMEContentObserver");
49 static const char* ToChar(bool aBool) { return aBool ? "true" : "false"; }
51 // This method determines the node to use for the point before the current node.
52 // If you have the following aContent and aContainer, and want to represent the
53 // following point for `RawNodePosition` or `RawRangeBoundary`:
55 // <parent> {node} {node} | {node} </parent>
56 // ^ ^ ^
57 // aContainer point aContent
59 // This function will shift `aContent` to the left into the format which
60 // `RawNodePosition` and `RawRangeBoundary` use:
62 // <parent> {node} {node} | {node} </parent>
63 // ^ ^ ^
64 // aContainer result point
65 static nsIContent* PointBefore(nsINode* aContainer, nsIContent* aContent) {
66 if (aContent) {
67 return aContent->GetPreviousSibling();
69 return aContainer->GetLastChild();
72 /******************************************************************************
73 * mozilla::IMEContentObserver
74 ******************************************************************************/
76 NS_IMPL_CYCLE_COLLECTION_CLASS(IMEContentObserver)
78 // Note that we don't need to add mFirstAddedContainer nor
79 // mLastAddedContainer to cycle collection because they are non-null only
80 // during short time and shouldn't be touched while they are non-null.
82 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IMEContentObserver)
83 nsAutoScriptBlocker scriptBlocker;
85 tmp->NotifyIMEOfBlur();
86 tmp->UnregisterObservers();
88 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelection)
89 NS_IMPL_CYCLE_COLLECTION_UNLINK(mRootElement)
90 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEditableNode)
91 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocShell)
92 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEditorBase)
93 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentObserver)
94 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEndOfAddedTextCache.mContainerNode)
95 NS_IMPL_CYCLE_COLLECTION_UNLINK(mStartOfRemovingTextRangeCache.mContainerNode)
96 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
98 tmp->mIMENotificationRequests = nullptr;
99 tmp->mESM = nullptr;
100 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
102 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IMEContentObserver)
103 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWidget)
104 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFocusedWidget)
105 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelection)
106 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRootElement)
107 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEditableNode)
108 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocShell)
109 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEditorBase)
110 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentObserver)
111 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEndOfAddedTextCache.mContainerNode)
112 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
113 mStartOfRemovingTextRangeCache.mContainerNode)
114 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
116 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IMEContentObserver)
117 NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
118 NS_INTERFACE_MAP_ENTRY(nsIReflowObserver)
119 NS_INTERFACE_MAP_ENTRY(nsIScrollObserver)
120 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
121 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIReflowObserver)
122 NS_INTERFACE_MAP_END
124 NS_IMPL_CYCLE_COLLECTING_ADDREF(IMEContentObserver)
125 NS_IMPL_CYCLE_COLLECTING_RELEASE(IMEContentObserver)
127 IMEContentObserver::IMEContentObserver() {
128 #ifdef DEBUG
129 // TODO: Make this test as GTest.
130 mTextChangeData.Test();
131 #endif
134 void IMEContentObserver::Init(nsIWidget& aWidget, nsPresContext& aPresContext,
135 Element* aElement, EditorBase& aEditorBase) {
136 State state = GetState();
137 if (NS_WARN_IF(state == eState_Observing)) {
138 return; // Nothing to do.
141 bool firstInitialization = state != eState_StoppedObserving;
142 if (!firstInitialization) {
143 // If this is now trying to initialize with new contents, all observers
144 // should be registered again for simpler implementation.
145 UnregisterObservers();
146 Clear();
149 mESM = aPresContext.EventStateManager();
150 mESM->OnStartToObserveContent(this);
152 mWidget = &aWidget;
153 mIMENotificationRequests = &mWidget->IMENotificationRequestsRef();
155 if (!InitWithEditor(aPresContext, aElement, aEditorBase)) {
156 MOZ_LOG(sIMECOLog, LogLevel::Error,
157 ("0x%p Init() FAILED, due to InitWithEditor() "
158 "failure",
159 this));
160 Clear();
161 return;
164 if (firstInitialization) {
165 // Now, try to send NOTIFY_IME_OF_FOCUS to IME via the widget.
166 MaybeNotifyIMEOfFocusSet();
167 // When this is called first time, IME has not received NOTIFY_IME_OF_FOCUS
168 // yet since NOTIFY_IME_OF_FOCUS will be sent to widget asynchronously.
169 // So, we need to do nothing here. After NOTIFY_IME_OF_FOCUS has been
170 // sent, OnIMEReceivedFocus() will be called and content, selection and/or
171 // position changes will be observed
172 return;
175 // When this is called after editor reframing (i.e., the root editable node
176 // is also recreated), IME has usually received NOTIFY_IME_OF_FOCUS. In this
177 // case, we need to restart to observe content, selection and/or position
178 // changes in new root editable node.
179 ObserveEditableNode();
181 if (!NeedsToNotifyIMEOfSomething()) {
182 return;
185 // Some change events may wait to notify IME because this was being
186 // initialized. It is the time to flush them.
187 FlushMergeableNotifications();
190 void IMEContentObserver::OnIMEReceivedFocus() {
191 // While Init() notifies IME of focus, pending layout may be flushed
192 // because the notification may cause querying content. Then, recursive
193 // call of Init() with the latest content may occur. In such case, we
194 // shouldn't keep first initialization which notified IME of focus.
195 if (GetState() != eState_Initializing) {
196 MOZ_LOG(sIMECOLog, LogLevel::Warning,
197 ("0x%p OnIMEReceivedFocus(), "
198 "but the state is not \"initializing\", so does nothing",
199 this));
200 return;
203 // NOTIFY_IME_OF_FOCUS might cause recreating IMEContentObserver
204 // instance via IMEStateManager::UpdateIMEState(). So, this
205 // instance might already have been destroyed, check it.
206 if (!mRootElement) {
207 MOZ_LOG(sIMECOLog, LogLevel::Warning,
208 ("0x%p OnIMEReceivedFocus(), "
209 "but mRootElement has already been cleared, so does nothing",
210 this));
211 return;
214 // Start to observe which is needed by IME when IME actually has focus.
215 ObserveEditableNode();
217 if (!NeedsToNotifyIMEOfSomething()) {
218 return;
221 // Some change events may wait to notify IME because this was being
222 // initialized. It is the time to flush them.
223 FlushMergeableNotifications();
226 bool IMEContentObserver::InitWithEditor(nsPresContext& aPresContext,
227 Element* aElement,
228 EditorBase& aEditorBase) {
229 mEditableNode = IMEStateManager::GetRootEditableNode(aPresContext, aElement);
230 if (NS_WARN_IF(!mEditableNode)) {
231 return false;
234 mEditorBase = &aEditorBase;
236 RefPtr<PresShell> presShell = aPresContext.GetPresShell();
238 // get selection and root content
239 nsCOMPtr<nsISelectionController> selCon;
240 if (mEditableNode->IsContent()) {
241 nsIFrame* frame = mEditableNode->AsContent()->GetPrimaryFrame();
242 if (NS_WARN_IF(!frame)) {
243 return false;
246 frame->GetSelectionController(&aPresContext, getter_AddRefs(selCon));
247 } else {
248 // mEditableNode is a document
249 selCon = presShell;
252 if (NS_WARN_IF(!selCon)) {
253 return false;
256 mSelection = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL);
257 if (NS_WARN_IF(!mSelection)) {
258 return false;
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);
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;
276 nsCOMPtr<nsINode> startContainer = selRange->GetStartContainer();
277 mRootElement = Element::FromNodeOrNull(
278 startContainer->GetSelectionRootContent(presShell));
279 } else {
280 MOZ_ASSERT(!mIsTextControl);
281 nsCOMPtr<nsINode> editableNode = mEditableNode;
282 mRootElement = Element::FromNodeOrNull(
283 editableNode->GetSelectionRootContent(presShell));
285 if (!mRootElement && mEditableNode->IsDocument()) {
286 // The document node is editable, but there are no contents, this document
287 // is not editable.
288 return false;
291 if (NS_WARN_IF(!mRootElement)) {
292 return false;
295 mDocShell = aPresContext.GetDocShell();
296 if (NS_WARN_IF(!mDocShell)) {
297 return false;
300 mDocumentObserver = new DocumentObserver(*this);
302 return true;
305 void IMEContentObserver::Clear() {
306 mEditorBase = nullptr;
307 mSelection = nullptr;
308 mEditableNode = nullptr;
309 mRootElement = nullptr;
310 mDocShell = nullptr;
311 // Should be safe to clear mDocumentObserver here even though it grabs
312 // this instance in most cases because this is called by Init() or Destroy().
313 // The callers of Init() grab this instance with local RefPtr.
314 // The caller of Destroy() also grabs this instance with local RefPtr.
315 // So, this won't cause refcount of this instance become 0.
316 mDocumentObserver = nullptr;
319 void IMEContentObserver::ObserveEditableNode() {
320 MOZ_RELEASE_ASSERT(mSelection);
321 MOZ_RELEASE_ASSERT(mRootElement);
322 MOZ_RELEASE_ASSERT(GetState() != eState_Observing);
324 // If this is called before sending NOTIFY_IME_OF_FOCUS (it's possible when
325 // the editor is reframed before sending NOTIFY_IME_OF_FOCUS asynchronously),
326 // the notification requests of mWidget may be different from after the widget
327 // receives NOTIFY_IME_OF_FOCUS. So, this should be called again by
328 // OnIMEReceivedFocus() which is called after sending NOTIFY_IME_OF_FOCUS.
329 if (!mIMEHasFocus) {
330 MOZ_ASSERT(!mWidget || mNeedsToNotifyIMEOfFocusSet ||
331 mSendingNotification == NOTIFY_IME_OF_FOCUS,
332 "Wow, OnIMEReceivedFocus() won't be called?");
333 return;
336 mIsObserving = true;
337 if (mEditorBase) {
338 mEditorBase->SetIMEContentObserver(this);
341 mRootElement->AddMutationObserver(this);
342 // If it's in a document (should be so), we can use document observer to
343 // reduce redundant computation of text change offsets.
344 Document* doc = mRootElement->GetComposedDoc();
345 if (doc) {
346 RefPtr<DocumentObserver> documentObserver = mDocumentObserver;
347 documentObserver->Observe(doc);
350 if (mDocShell) {
351 // Add scroll position listener and reflow observer to detect position
352 // and size changes
353 mDocShell->AddWeakScrollObserver(this);
354 mDocShell->AddWeakReflowObserver(this);
358 void IMEContentObserver::NotifyIMEOfBlur() {
359 // Prevent any notifications to be sent IME.
360 nsCOMPtr<nsIWidget> widget;
361 mWidget.swap(widget);
362 mIMENotificationRequests = nullptr;
364 // If we hasn't been set focus, we shouldn't send blur notification to IME.
365 if (!mIMEHasFocus) {
366 return;
369 // mWidget must have been non-nullptr if IME has focus.
370 MOZ_RELEASE_ASSERT(widget);
372 RefPtr<IMEContentObserver> kungFuDeathGrip(this);
374 MOZ_LOG(sIMECOLog, LogLevel::Info,
375 ("0x%p NotifyIMEOfBlur(), sending NOTIFY_IME_OF_BLUR", this));
377 // For now, we need to send blur notification in any condition because
378 // we don't have any simple ways to send blur notification asynchronously.
379 // After this call, Destroy() or Unlink() will stop observing the content
380 // and forget everything. Therefore, if it's not safe to send notification
381 // when script blocker is unlocked, we cannot send blur notification after
382 // that and before next focus notification.
383 // Anyway, as far as we know, IME doesn't try to query content when it loses
384 // focus. So, this may not cause any problem.
385 mIMEHasFocus = false;
386 IMEStateManager::NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR), widget);
388 MOZ_LOG(sIMECOLog, LogLevel::Debug,
389 ("0x%p NotifyIMEOfBlur(), sent NOTIFY_IME_OF_BLUR", this));
392 void IMEContentObserver::UnregisterObservers() {
393 if (!mIsObserving) {
394 return;
396 mIsObserving = false;
398 if (mEditorBase) {
399 mEditorBase->SetIMEContentObserver(nullptr);
402 if (mSelection) {
403 mSelectionData.Clear();
404 mFocusedWidget = nullptr;
407 if (mRootElement) {
408 mRootElement->RemoveMutationObserver(this);
411 if (mDocumentObserver) {
412 RefPtr<DocumentObserver> documentObserver = mDocumentObserver;
413 documentObserver->StopObserving();
416 if (mDocShell) {
417 mDocShell->RemoveWeakScrollObserver(this);
418 mDocShell->RemoveWeakReflowObserver(this);
422 nsPresContext* IMEContentObserver::GetPresContext() const {
423 return mESM ? mESM->GetPresContext() : nullptr;
426 void IMEContentObserver::Destroy() {
427 // WARNING: When you change this method, you have to check Unlink() too.
429 // Note that don't send any notifications later from here. I.e., notify
430 // IMEStateManager of the blur synchronously because IMEStateManager needs to
431 // stop notifying the main process if this is requested by the main process.
432 NotifyIMEOfBlur();
433 UnregisterObservers();
434 Clear();
436 mWidget = nullptr;
437 mIMENotificationRequests = nullptr;
439 if (mESM) {
440 mESM->OnStopObservingContent(this);
441 mESM = nullptr;
445 bool IMEContentObserver::Destroyed() const { return !mWidget; }
447 void IMEContentObserver::DisconnectFromEventStateManager() { mESM = nullptr; }
449 bool IMEContentObserver::MaybeReinitialize(nsIWidget& aWidget,
450 nsPresContext& aPresContext,
451 Element* aElement,
452 EditorBase& aEditorBase) {
453 if (!IsObservingContent(aPresContext, aElement)) {
454 return false;
457 if (GetState() == eState_StoppedObserving) {
458 Init(aWidget, aPresContext, aElement, aEditorBase);
460 return IsObserving(aPresContext, aElement);
463 bool IMEContentObserver::IsObserving(const nsPresContext& aPresContext,
464 const Element* aElement) const {
465 if (GetState() != eState_Observing) {
466 return false;
468 // If aElement is not a text control, aElement is an editing host or entire
469 // the document is editable in the design mode. Therefore, return false if
470 // we're observing an anonymous subtree of a text control.
471 if (!aElement || !aElement->IsTextControlElement() ||
472 !static_cast<const TextControlElement*>(aElement)
473 ->IsSingleLineTextControlOrTextArea()) {
474 if (mIsTextControl) {
475 return false;
478 // If aElement is a text control, return true if we're observing the anonymous
479 // subtree of aElement. Therefore, return false if we're observing with
480 // HTMLEditor.
481 else if (!mIsTextControl) {
482 return false;
484 return IsObservingContent(aPresContext, aElement);
487 bool IMEContentObserver::IsBeingInitializedFor(
488 const nsPresContext& aPresContext, const Element* aElement,
489 const EditorBase& aEditorBase) const {
490 return GetState() == eState_Initializing && mEditorBase == &aEditorBase &&
491 IsObservingContent(aPresContext, aElement);
494 bool IMEContentObserver::IsObserving(
495 const TextComposition& aTextComposition) const {
496 if (GetState() != eState_Observing) {
497 return false;
499 nsPresContext* const presContext = aTextComposition.GetPresContext();
500 if (NS_WARN_IF(!presContext)) {
501 return false;
503 if (presContext != GetPresContext()) {
504 return false; // observing different document
506 auto* const elementHavingComposition =
507 Element::FromNodeOrNull(aTextComposition.GetEventTargetNode());
508 bool isObserving = IsObservingContent(*presContext, elementHavingComposition);
509 #ifdef DEBUG
510 if (isObserving) {
511 if (mIsTextControl) {
512 MOZ_ASSERT(elementHavingComposition);
513 MOZ_ASSERT(elementHavingComposition->IsTextControlElement(),
514 "Should've never started to observe non-text-control element");
515 // XXX Our fake focus move has not been implemented properly. So, the
516 // following assertions may fail, but I don't like to make the failures
517 // cause crash even in debug builds because it may block developers to
518 // debug web-compat issues. On the other hand, it'd be nice if we can
519 // detect the bug with automated tests. Therefore, the following
520 // assertions are NS_ASSERTION.
521 NS_ASSERTION(static_cast<TextControlElement*>(elementHavingComposition)
522 ->IsSingleLineTextControlOrTextArea(),
523 "Should've stopped observing when the type is changed");
524 NS_ASSERTION(!elementHavingComposition->IsInDesignMode(),
525 "Should've stopped observing when the design mode started");
526 } else if (elementHavingComposition) {
527 NS_ASSERTION(
528 !elementHavingComposition->IsTextControlElement() ||
529 !static_cast<TextControlElement*>(elementHavingComposition)
530 ->IsSingleLineTextControlOrTextArea(),
531 "Should've never started to observe text-control element or "
532 "stopped observing it when the type is changed");
533 } else {
534 MOZ_ASSERT(presContext->GetPresShell());
535 MOZ_ASSERT(presContext->GetPresShell()->GetDocument());
536 NS_ASSERTION(
537 presContext->GetPresShell()->GetDocument()->IsInDesignMode(),
538 "Should be observing entire the document only in the design mode");
541 #endif // #ifdef DEBUG
542 return isObserving;
545 IMEContentObserver::State IMEContentObserver::GetState() const {
546 if (!mSelection || !mRootElement || !mEditableNode) {
547 return eState_NotObserving; // failed to initialize or finalized.
549 if (!mRootElement->IsInComposedDoc()) {
550 // the focused editor has already been reframed.
551 return eState_StoppedObserving;
553 return mIsObserving ? eState_Observing : eState_Initializing;
556 bool IMEContentObserver::IsObservingContent(const nsPresContext& aPresContext,
557 const Element* aElement) const {
558 return mEditableNode ==
559 IMEStateManager::GetRootEditableNode(aPresContext, aElement);
562 bool IMEContentObserver::IsEditorHandlingEventForComposition() const {
563 if (!mWidget) {
564 return false;
566 RefPtr<TextComposition> composition =
567 IMEStateManager::GetTextCompositionFor(mWidget);
568 if (!composition) {
569 return false;
571 return composition->IsEditorHandlingEvent();
574 bool IMEContentObserver::IsEditorComposing() const {
575 // Note that don't use TextComposition here. The important thing is,
576 // whether the editor already started to handle composition because
577 // web contents can change selection, text content and/or something from
578 // compositionstart event listener which is run before EditorBase handles it.
579 if (NS_WARN_IF(!mEditorBase)) {
580 return false;
582 return mEditorBase->IsIMEComposing();
585 nsresult IMEContentObserver::GetSelectionAndRoot(Selection** aSelection,
586 Element** aRootElement) const {
587 if (!mEditableNode || !mSelection) {
588 return NS_ERROR_NOT_AVAILABLE;
591 NS_ASSERTION(mSelection && mRootElement, "uninitialized content observer");
592 NS_ADDREF(*aSelection = mSelection);
593 NS_ADDREF(*aRootElement = mRootElement);
594 return NS_OK;
597 void IMEContentObserver::OnSelectionChange(Selection& aSelection) {
598 if (!mIsObserving) {
599 return;
602 if (mWidget) {
603 bool causedByComposition = IsEditorHandlingEventForComposition();
604 bool causedBySelectionEvent = TextComposition::IsHandlingSelectionEvent();
605 bool duringComposition = IsEditorComposing();
606 MaybeNotifyIMEOfSelectionChange(causedByComposition, causedBySelectionEvent,
607 duringComposition);
611 void IMEContentObserver::ScrollPositionChanged() {
612 if (!NeedsPositionChangeNotification()) {
613 return;
616 MaybeNotifyIMEOfPositionChange();
619 NS_IMETHODIMP
620 IMEContentObserver::Reflow(DOMHighResTimeStamp aStart,
621 DOMHighResTimeStamp aEnd) {
622 if (!NeedsPositionChangeNotification()) {
623 return NS_OK;
626 MaybeNotifyIMEOfPositionChange();
627 return NS_OK;
630 NS_IMETHODIMP
631 IMEContentObserver::ReflowInterruptible(DOMHighResTimeStamp aStart,
632 DOMHighResTimeStamp aEnd) {
633 if (!NeedsPositionChangeNotification()) {
634 return NS_OK;
637 MaybeNotifyIMEOfPositionChange();
638 return NS_OK;
641 nsresult IMEContentObserver::HandleQueryContentEvent(
642 WidgetQueryContentEvent* aEvent) {
643 // If the instance has normal selection cache and the query event queries
644 // normal selection's range, it should use the cached selection which was
645 // sent to the widget. However, if this instance has already received new
646 // selection change notification but hasn't updated the cache yet (i.e.,
647 // not sending selection change notification to IME, don't use the cached
648 // value. Note that don't update selection cache here since if you update
649 // selection cache here, IMENotificationSender won't notify IME of selection
650 // change because it looks like that the selection isn't actually changed.
651 const bool isSelectionCacheAvailable = aEvent->mUseNativeLineBreak &&
652 mSelectionData.IsInitialized() &&
653 !mNeedsToNotifyIMEOfSelectionChange;
654 if (isSelectionCacheAvailable && aEvent->mMessage == eQuerySelectedText &&
655 aEvent->mInput.mSelectionType == SelectionType::eNormal) {
656 aEvent->EmplaceReply();
657 if (mSelectionData.HasRange()) {
658 aEvent->mReply->mOffsetAndData.emplace(mSelectionData.mOffset,
659 mSelectionData.String(),
660 OffsetAndDataFor::SelectedString);
661 aEvent->mReply->mReversed = mSelectionData.mReversed;
663 aEvent->mReply->mContentsRoot = mRootElement;
664 aEvent->mReply->mWritingMode = mSelectionData.GetWritingMode();
665 // The selection cache in IMEContentObserver must always have been in
666 // an editing host (or an editable anonymous <div> element). Therefore,
667 // we set mIsEditableContent to true here even though it's already been
668 // blurred or changed its editable state but the selection cache has not
669 // been invalidated yet.
670 aEvent->mReply->mIsEditableContent = true;
671 MOZ_LOG(sIMECOLog, LogLevel::Debug,
672 ("0x%p HandleQueryContentEvent(aEvent={ "
673 "mMessage=%s, mReply=%s })",
674 this, ToChar(aEvent->mMessage), ToString(aEvent->mReply).c_str()));
675 return NS_OK;
678 MOZ_LOG(sIMECOLog, LogLevel::Info,
679 ("0x%p HandleQueryContentEvent(aEvent={ mMessage=%s })", this,
680 ToChar(aEvent->mMessage)));
682 // If we can make the event's input offset absolute with TextComposition or
683 // mSelection, we should set it here for reducing the cost of computing
684 // selection start offset. If ContentEventHandler receives a
685 // WidgetQueryContentEvent whose input offset is relative to insertion point,
686 // it computes current selection start offset (this may be expensive) and
687 // make the offset absolute value itself.
688 // Note that calling MakeOffsetAbsolute() makes the event a query event with
689 // absolute offset. So, ContentEventHandler doesn't pay any additional cost
690 // after calling MakeOffsetAbsolute() here.
691 if (aEvent->mInput.mRelativeToInsertionPoint &&
692 aEvent->mInput.IsValidEventMessage(aEvent->mMessage)) {
693 RefPtr<TextComposition> composition =
694 IMEStateManager::GetTextCompositionFor(aEvent->mWidget);
695 if (composition) {
696 uint32_t compositionStart = composition->NativeOffsetOfStartComposition();
697 if (NS_WARN_IF(!aEvent->mInput.MakeOffsetAbsolute(compositionStart))) {
698 return NS_ERROR_FAILURE;
700 } else if (isSelectionCacheAvailable && mSelectionData.HasRange()) {
701 const uint32_t selectionStart = mSelectionData.mOffset;
702 if (NS_WARN_IF(!aEvent->mInput.MakeOffsetAbsolute(selectionStart))) {
703 return NS_ERROR_FAILURE;
708 AutoRestore<bool> handling(mIsHandlingQueryContentEvent);
709 mIsHandlingQueryContentEvent = true;
710 ContentEventHandler handler(GetPresContext());
711 nsresult rv = handler.HandleQueryContentEvent(aEvent);
712 if (NS_WARN_IF(Destroyed())) {
713 // If this has already destroyed during querying the content, the query
714 // is outdated even if it's succeeded. So, make the query fail.
715 aEvent->mReply.reset();
716 MOZ_LOG(sIMECOLog, LogLevel::Warning,
717 ("0x%p HandleQueryContentEvent(), WARNING, "
718 "IMEContentObserver has been destroyed during the query, "
719 "making the query fail",
720 this));
721 return rv;
724 if (aEvent->Succeeded() &&
725 NS_WARN_IF(aEvent->mReply->mContentsRoot != mRootElement)) {
726 // Focus has changed unexpectedly, so make the query fail.
727 aEvent->mReply.reset();
729 return rv;
732 nsresult IMEContentObserver::MaybeHandleSelectionEvent(
733 nsPresContext* aPresContext, WidgetSelectionEvent* aEvent) {
734 MOZ_ASSERT(aEvent);
735 MOZ_ASSERT(aEvent->mMessage == eSetSelection);
736 NS_ASSERTION(!mNeedsToNotifyIMEOfSelectionChange,
737 "Selection cache has not been updated yet");
739 MOZ_LOG(
740 sIMECOLog, LogLevel::Debug,
741 ("0x%p MaybeHandleSelectionEvent(aEvent={ "
742 "mMessage=%s, mOffset=%u, mLength=%u, mReversed=%s, "
743 "mExpandToClusterBoundary=%s, mUseNativeLineBreak=%s }), "
744 "mSelectionData=%s",
745 this, ToChar(aEvent->mMessage), aEvent->mOffset, aEvent->mLength,
746 ToChar(aEvent->mReversed), ToChar(aEvent->mExpandToClusterBoundary),
747 ToChar(aEvent->mUseNativeLineBreak), ToString(mSelectionData).c_str()));
749 // When we have Selection cache, and the caller wants to set same selection
750 // range, we shouldn't try to compute same range because it may be impossible
751 // if the range boundary is around element boundaries which won't be
752 // serialized with line breaks like close tags of inline elements. In that
753 // case, inserting new text at different point may be different from intention
754 // of users or web apps which set current selection.
755 // FIXME: We cache only selection data computed with native line breaker
756 // lengths. Perhaps, we should improve the struct to have both data of
757 // offset and length. E.g., adding line break counts for both offset and
758 // length.
759 if (!mNeedsToNotifyIMEOfSelectionChange && aEvent->mUseNativeLineBreak &&
760 mSelectionData.IsInitialized() && mSelectionData.HasRange() &&
761 mSelectionData.StartOffset() == aEvent->mOffset &&
762 mSelectionData.Length() == aEvent->mLength) {
763 if (RefPtr<Selection> selection = mSelection) {
764 selection->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION,
765 ScrollAxis(), ScrollAxis(), 0);
767 aEvent->mSucceeded = true;
768 return NS_OK;
771 ContentEventHandler handler(aPresContext);
772 return handler.OnSelectionEvent(aEvent);
775 bool IMEContentObserver::OnMouseButtonEvent(nsPresContext& aPresContext,
776 WidgetMouseEvent& aMouseEvent) {
777 if (!mIMENotificationRequests ||
778 !mIMENotificationRequests->WantMouseButtonEventOnChar()) {
779 return false;
781 if (!aMouseEvent.IsTrusted() || aMouseEvent.DefaultPrevented() ||
782 !aMouseEvent.mWidget) {
783 return false;
785 // Now, we need to notify only mouse down and mouse up event.
786 switch (aMouseEvent.mMessage) {
787 case eMouseUp:
788 case eMouseDown:
789 break;
790 default:
791 return false;
793 if (NS_WARN_IF(!mWidget) || NS_WARN_IF(mWidget->Destroyed())) {
794 return false;
797 WidgetQueryContentEvent queryCharAtPointEvent(true, eQueryCharacterAtPoint,
798 aMouseEvent.mWidget);
799 queryCharAtPointEvent.mRefPoint = aMouseEvent.mRefPoint;
800 ContentEventHandler handler(&aPresContext);
801 handler.OnQueryCharacterAtPoint(&queryCharAtPointEvent);
802 if (NS_WARN_IF(queryCharAtPointEvent.Failed()) ||
803 queryCharAtPointEvent.DidNotFindChar()) {
804 return false;
807 // The widget might be destroyed during querying the content since it
808 // causes flushing layout.
809 if (!mWidget || NS_WARN_IF(mWidget->Destroyed())) {
810 return false;
813 // The result character rect is relative to the top level widget.
814 // We should notify it with offset in the widget.
815 nsIWidget* topLevelWidget = mWidget->GetTopLevelWidget();
816 if (topLevelWidget && topLevelWidget != mWidget) {
817 queryCharAtPointEvent.mReply->mRect.MoveBy(
818 topLevelWidget->WidgetToScreenOffset() -
819 mWidget->WidgetToScreenOffset());
821 // The refPt is relative to its widget.
822 // We should notify it with offset in the widget.
823 if (aMouseEvent.mWidget != mWidget) {
824 queryCharAtPointEvent.mRefPoint +=
825 aMouseEvent.mWidget->WidgetToScreenOffset() -
826 mWidget->WidgetToScreenOffset();
829 IMENotification notification(NOTIFY_IME_OF_MOUSE_BUTTON_EVENT);
830 notification.mMouseButtonEventData.mEventMessage = aMouseEvent.mMessage;
831 notification.mMouseButtonEventData.mOffset =
832 queryCharAtPointEvent.mReply->StartOffset();
833 notification.mMouseButtonEventData.mCursorPos =
834 queryCharAtPointEvent.mRefPoint;
835 notification.mMouseButtonEventData.mCharRect =
836 queryCharAtPointEvent.mReply->mRect;
837 notification.mMouseButtonEventData.mButton = aMouseEvent.mButton;
838 notification.mMouseButtonEventData.mButtons = aMouseEvent.mButtons;
839 notification.mMouseButtonEventData.mModifiers = aMouseEvent.mModifiers;
841 nsresult rv = IMEStateManager::NotifyIME(notification, mWidget);
842 if (NS_WARN_IF(NS_FAILED(rv))) {
843 return false;
846 bool consumed = (rv == NS_SUCCESS_EVENT_CONSUMED);
847 if (consumed) {
848 aMouseEvent.PreventDefault();
850 return consumed;
853 void IMEContentObserver::CharacterDataWillChange(
854 nsIContent* aContent, const CharacterDataChangeInfo& aInfo) {
855 if (!aContent->IsText()) {
856 return; // Ignore if it's a comment node or something other invisible data
857 // node.
859 MOZ_ASSERT(mPreCharacterDataChangeLength < 0,
860 "CharacterDataChanged() should've reset "
861 "mPreCharacterDataChangeLength");
863 if (!NeedsTextChangeNotification() ||
864 !nsContentUtils::IsInSameAnonymousTree(mRootElement, aContent)) {
865 return;
868 mEndOfAddedTextCache.Clear();
869 mStartOfRemovingTextRangeCache.Clear();
871 // Although we don't assume this change occurs while this is storing
872 // the range of added consecutive nodes, if it actually happens, we need to
873 // flush them since this change may occur before or in the range. So, it's
874 // safe to flush pending computation of mTextChangeData before handling this.
875 MaybeNotifyIMEOfAddedTextDuringDocumentChange();
877 mPreCharacterDataChangeLength = ContentEventHandler::GetNativeTextLength(
878 *aContent->AsText(), aInfo.mChangeStart, aInfo.mChangeEnd);
879 MOZ_ASSERT(
880 mPreCharacterDataChangeLength >= aInfo.mChangeEnd - aInfo.mChangeStart,
881 "The computed length must be same as or larger than XP length");
884 void IMEContentObserver::CharacterDataChanged(
885 nsIContent* aContent, const CharacterDataChangeInfo& aInfo) {
886 if (!aContent->IsText()) {
887 return; // Ignore if it's a comment node or something other invisible data
888 // node.
891 // Let TextComposition have a change to update composition string range in
892 // the text node if the change is caused by the web apps.
893 if (mWidget && !IsEditorHandlingEventForComposition()) {
894 if (RefPtr<TextComposition> composition =
895 IMEStateManager::GetTextCompositionFor(mWidget)) {
896 composition->OnCharacterDataChanged(*aContent->AsText(), aInfo);
900 if (!NeedsTextChangeNotification() ||
901 !nsContentUtils::IsInSameAnonymousTree(mRootElement, aContent)) {
902 return;
905 mEndOfAddedTextCache.Clear();
906 mStartOfRemovingTextRangeCache.Clear();
907 MOZ_ASSERT(
908 !HasAddedNodesDuringDocumentChange(),
909 "The stored range should be flushed before actually the data is changed");
911 int64_t removedLength = mPreCharacterDataChangeLength;
912 mPreCharacterDataChangeLength = -1;
914 MOZ_ASSERT(removedLength >= 0,
915 "mPreCharacterDataChangeLength should've been set by "
916 "CharacterDataWillChange()");
918 uint32_t offset = 0;
919 if (mIsTextControl) {
920 // If we're observing a text control, mRootElement is the anonymous <div>
921 // element which has only one text node and/or invisible <br> element.
922 // TextEditor assumes this structure when it handles editing commands.
923 // Therefore, it's safe to assume same things here.
924 MOZ_ASSERT(mRootElement->GetFirstChild() == aContent);
925 if (aInfo.mChangeStart) {
926 offset = ContentEventHandler::GetNativeTextLength(*aContent->AsText(), 0,
927 aInfo.mChangeStart);
929 } else {
930 nsresult rv = ContentEventHandler::GetFlatTextLengthInRange(
931 RawNodePosition(mRootElement, 0u),
932 RawNodePosition(aContent, aInfo.mChangeStart), mRootElement, &offset,
933 LINE_BREAK_TYPE_NATIVE);
934 if (NS_WARN_IF(NS_FAILED(rv))) {
935 return;
939 uint32_t newLength = ContentEventHandler::GetNativeTextLength(
940 *aContent->AsText(), aInfo.mChangeStart,
941 aInfo.mChangeStart + aInfo.mReplaceLength);
943 uint32_t oldEnd = offset + static_cast<uint32_t>(removedLength);
944 uint32_t newEnd = offset + newLength;
946 TextChangeData data(offset, oldEnd, newEnd,
947 IsEditorHandlingEventForComposition(),
948 IsEditorComposing());
949 MaybeNotifyIMEOfTextChange(data);
952 void IMEContentObserver::NotifyContentAdded(nsINode* aContainer,
953 nsIContent* aFirstContent,
954 nsIContent* aLastContent) {
955 if (!NeedsTextChangeNotification() ||
956 !nsContentUtils::IsInSameAnonymousTree(mRootElement, aFirstContent)) {
957 return;
960 MOZ_ASSERT_IF(aFirstContent, aFirstContent->GetParentNode() == aContainer);
961 MOZ_ASSERT_IF(aLastContent, aLastContent->GetParentNode() == aContainer);
963 mStartOfRemovingTextRangeCache.Clear();
965 // If it's in a document change, nodes are added consecutively. Therefore,
966 // if we cache the first node and the last node, we need to compute the
967 // range once.
968 // FYI: This is not true if the change caused by an operation in the editor.
969 if (IsInDocumentChange()) {
970 // Now, mEndOfAddedTextCache may be invalid if node is added before
971 // the last node in mEndOfAddedTextCache. Clear it.
972 mEndOfAddedTextCache.Clear();
973 if (!HasAddedNodesDuringDocumentChange()) {
974 mFirstAddedContainer = mLastAddedContainer = aContainer;
975 mFirstAddedContent = aFirstContent;
976 mLastAddedContent = aLastContent;
977 MOZ_LOG(sIMECOLog, LogLevel::Debug,
978 ("0x%p NotifyContentAdded(), starts to store consecutive added "
979 "nodes",
980 this));
981 return;
983 // If first node being added is not next node of the last node,
984 // notify IME of the previous range first, then, restart to cache the
985 // range.
986 if (NS_WARN_IF(!IsNextNodeOfLastAddedNode(aContainer, aFirstContent))) {
987 // Flush the old range first.
988 MaybeNotifyIMEOfAddedTextDuringDocumentChange();
989 mFirstAddedContainer = aContainer;
990 mFirstAddedContent = aFirstContent;
991 MOZ_LOG(sIMECOLog, LogLevel::Debug,
992 ("0x%p NotifyContentAdded(), starts to store consecutive added "
993 "nodes",
994 this));
996 mLastAddedContainer = aContainer;
997 mLastAddedContent = aLastContent;
998 return;
1000 MOZ_ASSERT(!HasAddedNodesDuringDocumentChange(),
1001 "The cache should be cleared when document change finished");
1003 uint32_t offset = 0;
1004 nsresult rv = NS_OK;
1005 if (!mEndOfAddedTextCache.Match(aContainer,
1006 aFirstContent->GetPreviousSibling())) {
1007 mEndOfAddedTextCache.Clear();
1008 rv = ContentEventHandler::GetFlatTextLengthInRange(
1009 RawNodePosition(mRootElement, 0u),
1010 RawNodePositionBefore(aContainer,
1011 PointBefore(aContainer, aFirstContent)),
1012 mRootElement, &offset, LINE_BREAK_TYPE_NATIVE);
1013 if (NS_WARN_IF(NS_FAILED((rv)))) {
1014 return;
1016 } else {
1017 offset = mEndOfAddedTextCache.mFlatTextLength;
1020 // get offset at the end of the last added node
1021 uint32_t addingLength = 0;
1022 rv = ContentEventHandler::GetFlatTextLengthInRange(
1023 RawNodePositionBefore(aContainer, PointBefore(aContainer, aFirstContent)),
1024 RawNodePosition(aContainer, aLastContent), mRootElement, &addingLength,
1025 LINE_BREAK_TYPE_NATIVE);
1026 if (NS_WARN_IF(NS_FAILED((rv)))) {
1027 mEndOfAddedTextCache.Clear();
1028 return;
1031 // If multiple lines are being inserted in an HTML editor, next call of
1032 // NotifyContentAdded() is for adding next node. Therefore, caching the text
1033 // length can skip to compute the text length before the adding node and
1034 // before of it.
1035 mEndOfAddedTextCache.Cache(aContainer, aLastContent, offset + addingLength);
1037 if (!addingLength) {
1038 return;
1041 TextChangeData data(offset, offset, offset + addingLength,
1042 IsEditorHandlingEventForComposition(),
1043 IsEditorComposing());
1044 MaybeNotifyIMEOfTextChange(data);
1047 void IMEContentObserver::ContentAppended(nsIContent* aFirstNewContent) {
1048 nsIContent* parent = aFirstNewContent->GetParent();
1049 MOZ_ASSERT(parent);
1050 NotifyContentAdded(parent, aFirstNewContent, parent->GetLastChild());
1053 void IMEContentObserver::ContentInserted(nsIContent* aChild) {
1054 MOZ_ASSERT(aChild);
1055 NotifyContentAdded(aChild->GetParentNode(), aChild, aChild);
1058 void IMEContentObserver::ContentRemoved(nsIContent* aChild,
1059 nsIContent* aPreviousSibling) {
1060 if (!NeedsTextChangeNotification() ||
1061 !nsContentUtils::IsInSameAnonymousTree(mRootElement, aChild)) {
1062 return;
1065 mEndOfAddedTextCache.Clear();
1066 MaybeNotifyIMEOfAddedTextDuringDocumentChange();
1068 nsINode* containerNode = aChild->GetParentNode();
1069 MOZ_ASSERT(containerNode);
1071 uint32_t offset = 0;
1072 nsresult rv = NS_OK;
1073 if (!mStartOfRemovingTextRangeCache.Match(containerNode, aPreviousSibling)) {
1074 // At removing a child node of aContainer, we need the line break caused
1075 // by open tag of aContainer. Be careful when aPreviousSibling is nullptr.
1077 rv = ContentEventHandler::GetFlatTextLengthInRange(
1078 RawNodePosition(mRootElement, 0u),
1079 RawNodePosition(containerNode, aPreviousSibling), mRootElement, &offset,
1080 LINE_BREAK_TYPE_NATIVE);
1081 if (NS_WARN_IF(NS_FAILED(rv))) {
1082 mStartOfRemovingTextRangeCache.Clear();
1083 return;
1085 mStartOfRemovingTextRangeCache.Cache(containerNode, aPreviousSibling,
1086 offset);
1087 } else {
1088 offset = mStartOfRemovingTextRangeCache.mFlatTextLength;
1091 // get offset at the end of the deleted node
1092 uint32_t textLength = 0;
1093 if (const Text* textNode = Text::FromNode(aChild)) {
1094 textLength = ContentEventHandler::GetNativeTextLength(*textNode);
1095 } else {
1096 nsresult rv = ContentEventHandler::GetFlatTextLengthInRange(
1097 RawNodePositionBefore(aChild, 0u),
1098 RawNodePosition(aChild, aChild->GetChildCount()), mRootElement,
1099 &textLength, LINE_BREAK_TYPE_NATIVE, true);
1100 if (NS_WARN_IF(NS_FAILED(rv))) {
1101 mStartOfRemovingTextRangeCache.Clear();
1102 return;
1106 if (!textLength) {
1107 return;
1110 TextChangeData data(offset, offset + textLength, offset,
1111 IsEditorHandlingEventForComposition(),
1112 IsEditorComposing());
1113 MaybeNotifyIMEOfTextChange(data);
1116 void IMEContentObserver::ClearAddedNodesDuringDocumentChange() {
1117 mFirstAddedContainer = mLastAddedContainer = nullptr;
1118 mFirstAddedContent = mLastAddedContent = nullptr;
1119 MOZ_LOG(sIMECOLog, LogLevel::Debug,
1120 ("0x%p ClearAddedNodesDuringDocumentChange(), finished storing "
1121 "consecutive nodes",
1122 this));
1125 bool IMEContentObserver::IsNextNodeOfLastAddedNode(nsINode* aParent,
1126 nsIContent* aChild) const {
1127 MOZ_ASSERT(aParent);
1128 MOZ_ASSERT(aChild && aChild->GetParentNode() == aParent);
1129 MOZ_ASSERT(mRootElement);
1130 MOZ_ASSERT(HasAddedNodesDuringDocumentChange());
1132 // If the parent node isn't changed, we can check that mLastAddedContent has
1133 // aChild as its next sibling.
1134 if (aParent == mLastAddedContainer) {
1135 return !NS_WARN_IF(mLastAddedContent->GetNextSibling() != aChild);
1138 // If the parent node is changed, that means that the recorded last added node
1139 // shouldn't have a sibling.
1140 if (NS_WARN_IF(mLastAddedContent->GetNextSibling())) {
1141 return false;
1144 // If the node is aParent is a descendant of mLastAddedContainer,
1145 // aChild should be the first child in the new container.
1146 if (mLastAddedContainer == aParent->GetParent()) {
1147 return !NS_WARN_IF(aChild->GetPreviousSibling());
1150 // Otherwise, we need to check it even with slow path.
1151 nsIContent* nextContentOfLastAddedContent =
1152 mLastAddedContent->GetNextNode(mRootElement->GetParentNode());
1153 if (NS_WARN_IF(!nextContentOfLastAddedContent)) {
1154 return false;
1156 if (NS_WARN_IF(nextContentOfLastAddedContent != aChild)) {
1157 return false;
1159 return true;
1162 void IMEContentObserver::MaybeNotifyIMEOfAddedTextDuringDocumentChange() {
1163 if (!HasAddedNodesDuringDocumentChange()) {
1164 return;
1167 MOZ_LOG(sIMECOLog, LogLevel::Debug,
1168 ("0x%p "
1169 "IMEContentObserver::MaybeNotifyIMEOfAddedTextDuringDocumentChange()"
1170 ", flushing stored consecutive nodes",
1171 this));
1173 // Notify IME of text change which is caused by added nodes now.
1175 // First, compute offset of start of first added node from start of the
1176 // editor.
1177 uint32_t offset;
1178 nsresult rv = ContentEventHandler::GetFlatTextLengthInRange(
1179 RawNodePosition(mRootElement, 0u),
1180 RawNodePosition(mFirstAddedContainer,
1181 PointBefore(mFirstAddedContainer, mFirstAddedContent)),
1182 mRootElement, &offset, LINE_BREAK_TYPE_NATIVE);
1183 if (NS_WARN_IF(NS_FAILED(rv))) {
1184 ClearAddedNodesDuringDocumentChange();
1185 return;
1188 // Next, compute the text length of added nodes.
1189 uint32_t length;
1190 rv = ContentEventHandler::GetFlatTextLengthInRange(
1191 RawNodePosition(mFirstAddedContainer,
1192 PointBefore(mFirstAddedContainer, mFirstAddedContent)),
1193 RawNodePosition(mLastAddedContainer, mLastAddedContent), mRootElement,
1194 &length, LINE_BREAK_TYPE_NATIVE);
1195 if (NS_WARN_IF(NS_FAILED(rv))) {
1196 ClearAddedNodesDuringDocumentChange();
1197 return;
1200 // Finally, try to notify IME of the range.
1201 TextChangeData data(offset, offset, offset + length,
1202 IsEditorHandlingEventForComposition(),
1203 IsEditorComposing());
1204 MaybeNotifyIMEOfTextChange(data);
1205 ClearAddedNodesDuringDocumentChange();
1208 void IMEContentObserver::OnTextControlValueChangedWhileNotObservable(
1209 const nsAString& aNewValue) {
1210 MOZ_ASSERT(mEditorBase);
1211 MOZ_ASSERT(mEditorBase->IsTextEditor());
1212 if (!mTextControlValueLength && aNewValue.IsEmpty()) {
1213 return;
1215 MOZ_LOG(sIMECOLog, LogLevel::Debug,
1216 ("0x%p OnTextControlValueChangedWhileNotObservable()", this));
1217 uint32_t newLength = ContentEventHandler::GetNativeTextLength(aNewValue);
1218 TextChangeData data(0, mTextControlValueLength, newLength, false, false);
1219 MaybeNotifyIMEOfTextChange(data);
1222 void IMEContentObserver::BeginDocumentUpdate() {
1223 MOZ_LOG(sIMECOLog, LogLevel::Debug,
1224 ("0x%p BeginDocumentUpdate(), HasAddedNodesDuringDocumentChange()=%s",
1225 this, ToChar(HasAddedNodesDuringDocumentChange())));
1227 // If we're not in a nested document update, this will return early,
1228 // otherwise, it will handle flusing any changes currently pending before
1229 // entering a nested document update.
1230 MaybeNotifyIMEOfAddedTextDuringDocumentChange();
1233 void IMEContentObserver::EndDocumentUpdate() {
1234 MOZ_LOG(sIMECOLog, LogLevel::Debug,
1235 ("0x%p EndDocumentUpdate(), HasAddedNodesDuringDocumentChange()=%s",
1236 this, ToChar(HasAddedNodesDuringDocumentChange())));
1238 MaybeNotifyIMEOfAddedTextDuringDocumentChange();
1241 void IMEContentObserver::SuppressNotifyingIME() {
1242 mSuppressNotifications++;
1244 MOZ_LOG(sIMECOLog, LogLevel::Debug,
1245 ("0x%p SuppressNotifyingIME(), mSuppressNotifications=%u", this,
1246 mSuppressNotifications));
1249 void IMEContentObserver::UnsuppressNotifyingIME() {
1250 MOZ_LOG(sIMECOLog, LogLevel::Debug,
1251 ("0x%p UnsuppressNotifyingIME(), mSuppressNotifications=%u", this,
1252 mSuppressNotifications));
1254 if (!mSuppressNotifications || --mSuppressNotifications) {
1255 return;
1257 FlushMergeableNotifications();
1260 void IMEContentObserver::OnEditActionHandled() {
1261 MOZ_LOG(sIMECOLog, LogLevel::Debug, ("0x%p EditAction()", this));
1263 mEndOfAddedTextCache.Clear();
1264 mStartOfRemovingTextRangeCache.Clear();
1265 FlushMergeableNotifications();
1268 void IMEContentObserver::BeforeEditAction() {
1269 MOZ_LOG(sIMECOLog, LogLevel::Debug, ("0x%p BeforeEditAction()", this));
1271 mEndOfAddedTextCache.Clear();
1272 mStartOfRemovingTextRangeCache.Clear();
1275 void IMEContentObserver::CancelEditAction() {
1276 MOZ_LOG(sIMECOLog, LogLevel::Debug, ("0x%p CancelEditAction()", this));
1278 mEndOfAddedTextCache.Clear();
1279 mStartOfRemovingTextRangeCache.Clear();
1280 FlushMergeableNotifications();
1283 void IMEContentObserver::PostFocusSetNotification() {
1284 MOZ_LOG(sIMECOLog, LogLevel::Debug,
1285 ("0x%p PostFocusSetNotification()", this));
1287 mNeedsToNotifyIMEOfFocusSet = true;
1290 void IMEContentObserver::PostTextChangeNotification() {
1291 MOZ_LOG(sIMECOLog, LogLevel::Debug,
1292 ("0x%p PostTextChangeNotification(mTextChangeData=%s)", this,
1293 ToString(mTextChangeData).c_str()));
1295 MOZ_ASSERT(mTextChangeData.IsValid(),
1296 "mTextChangeData must have text change data");
1297 mNeedsToNotifyIMEOfTextChange = true;
1298 // Even if the observer hasn't received selection change, selection in the
1299 // flat text may have already been changed. For example, when previous `<p>`
1300 // element of another `<p>` element which contains caret is removed by a DOM
1301 // mutation, selection change event won't be fired, but selection start offset
1302 // should be decreased by the length of removed `<p>` element.
1303 // In such case, HandleQueryContentEvent shouldn't use the selection cache
1304 // anymore. Therefore, we also need to post selection change notification
1305 // too. eQuerySelectedText event may be dispatched at sending a text change
1306 // notification.
1307 mNeedsToNotifyIMEOfSelectionChange = true;
1310 void IMEContentObserver::PostSelectionChangeNotification() {
1311 MOZ_LOG(sIMECOLog, LogLevel::Debug,
1312 ("0x%p PostSelectionChangeNotification(), mSelectionData={ "
1313 "mCausedByComposition=%s, mCausedBySelectionEvent=%s }",
1314 this, ToChar(mSelectionData.mCausedByComposition),
1315 ToChar(mSelectionData.mCausedBySelectionEvent)));
1317 mNeedsToNotifyIMEOfSelectionChange = true;
1320 void IMEContentObserver::MaybeNotifyIMEOfFocusSet() {
1321 MOZ_LOG(sIMECOLog, LogLevel::Debug,
1322 ("0x%p MaybeNotifyIMEOfFocusSet()", this));
1324 PostFocusSetNotification();
1325 FlushMergeableNotifications();
1328 void IMEContentObserver::MaybeNotifyIMEOfTextChange(
1329 const TextChangeDataBase& aTextChangeData) {
1330 MOZ_LOG(sIMECOLog, LogLevel::Debug,
1331 ("0x%p MaybeNotifyIMEOfTextChange(aTextChangeData=%s)", this,
1332 ToString(aTextChangeData).c_str()));
1334 if (mEditorBase && mEditorBase->IsTextEditor()) {
1335 MOZ_DIAGNOSTIC_ASSERT(static_cast<int64_t>(mTextControlValueLength) +
1336 aTextChangeData.Difference() >=
1338 mTextControlValueLength += aTextChangeData.Difference();
1341 mTextChangeData += aTextChangeData;
1342 PostTextChangeNotification();
1343 FlushMergeableNotifications();
1346 void IMEContentObserver::CancelNotifyingIMEOfTextChange() {
1347 MOZ_LOG(sIMECOLog, LogLevel::Debug,
1348 ("0x%p CancelNotifyingIMEOfTextChange()", this));
1349 mTextChangeData.Clear();
1350 mNeedsToNotifyIMEOfTextChange = false;
1353 void IMEContentObserver::MaybeNotifyIMEOfSelectionChange(
1354 bool aCausedByComposition, bool aCausedBySelectionEvent,
1355 bool aOccurredDuringComposition) {
1356 MOZ_LOG(
1357 sIMECOLog, LogLevel::Debug,
1358 ("0x%p MaybeNotifyIMEOfSelectionChange(aCausedByComposition=%s, "
1359 "aCausedBySelectionEvent=%s, aOccurredDuringComposition)",
1360 this, ToChar(aCausedByComposition), ToChar(aCausedBySelectionEvent)));
1362 mSelectionData.AssignReason(aCausedByComposition, aCausedBySelectionEvent,
1363 aOccurredDuringComposition);
1364 PostSelectionChangeNotification();
1365 FlushMergeableNotifications();
1368 void IMEContentObserver::MaybeNotifyIMEOfPositionChange() {
1369 MOZ_LOG(sIMECOLog, LogLevel::Verbose,
1370 ("0x%p MaybeNotifyIMEOfPositionChange()", this));
1371 // If reflow is caused by ContentEventHandler during PositionChangeEvent
1372 // sending NOTIFY_IME_OF_POSITION_CHANGE, we don't need to notify IME of it
1373 // again since ContentEventHandler returns the result including this reflow's
1374 // result.
1375 if (mIsHandlingQueryContentEvent &&
1376 mSendingNotification == NOTIFY_IME_OF_POSITION_CHANGE) {
1377 MOZ_LOG(sIMECOLog, LogLevel::Verbose,
1378 ("0x%p MaybeNotifyIMEOfPositionChange(), ignored since caused by "
1379 "ContentEventHandler during sending NOTIFY_IME_OF_POSITION_CHANGE",
1380 this));
1381 return;
1383 PostPositionChangeNotification();
1384 FlushMergeableNotifications();
1387 void IMEContentObserver::CancelNotifyingIMEOfPositionChange() {
1388 MOZ_LOG(sIMECOLog, LogLevel::Debug,
1389 ("0x%p CancelNotifyIMEOfPositionChange()", this));
1390 mNeedsToNotifyIMEOfPositionChange = false;
1393 void IMEContentObserver::MaybeNotifyCompositionEventHandled() {
1394 MOZ_LOG(sIMECOLog, LogLevel::Debug,
1395 ("0x%p MaybeNotifyCompositionEventHandled()", this));
1397 PostCompositionEventHandledNotification();
1398 FlushMergeableNotifications();
1401 bool IMEContentObserver::UpdateSelectionCache(bool aRequireFlush /* = true */) {
1402 MOZ_ASSERT(IsSafeToNotifyIME());
1404 mSelectionData.ClearSelectionData();
1406 // XXX Cannot we cache some information for reducing the cost to compute
1407 // selection offset and writing mode?
1408 WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
1409 mWidget);
1410 querySelectedTextEvent.mNeedsToFlushLayout = aRequireFlush;
1411 ContentEventHandler handler(GetPresContext());
1412 handler.OnQuerySelectedText(&querySelectedTextEvent);
1413 if (NS_WARN_IF(querySelectedTextEvent.Failed()) ||
1414 NS_WARN_IF(querySelectedTextEvent.mReply->mContentsRoot !=
1415 mRootElement)) {
1416 return false;
1419 mFocusedWidget = querySelectedTextEvent.mReply->mFocusedWidget;
1420 mSelectionData.Assign(querySelectedTextEvent);
1422 // WARNING: Don't set the reason of selection change here because it should be
1423 // set the reason at sending the notification.
1425 MOZ_LOG(sIMECOLog, LogLevel::Debug,
1426 ("0x%p UpdateSelectionCache(), mSelectionData=%s", this,
1427 ToString(mSelectionData).c_str()));
1429 return true;
1432 void IMEContentObserver::PostPositionChangeNotification() {
1433 MOZ_LOG(sIMECOLog, LogLevel::Debug,
1434 ("0x%p PostPositionChangeNotification()", this));
1436 mNeedsToNotifyIMEOfPositionChange = true;
1439 void IMEContentObserver::PostCompositionEventHandledNotification() {
1440 MOZ_LOG(sIMECOLog, LogLevel::Debug,
1441 ("0x%p PostCompositionEventHandledNotification()", this));
1443 mNeedsToNotifyIMEOfCompositionEventHandled = true;
1446 bool IMEContentObserver::IsReflowLocked() const {
1447 nsPresContext* presContext = GetPresContext();
1448 if (NS_WARN_IF(!presContext)) {
1449 return false;
1451 PresShell* presShell = presContext->GetPresShell();
1452 if (NS_WARN_IF(!presShell)) {
1453 return false;
1455 // During reflow, we shouldn't notify IME because IME may query content
1456 // synchronously. Then, it causes ContentEventHandler will try to flush
1457 // pending notifications during reflow.
1458 return presShell->IsReflowLocked();
1461 bool IMEContentObserver::IsSafeToNotifyIME() const {
1462 // If this is already detached from the widget, this doesn't need to notify
1463 // anything.
1464 if (!mWidget) {
1465 MOZ_LOG(sIMECOLog, LogLevel::Debug,
1466 ("0x%p IsSafeToNotifyIME(), it's not safe because of no widget",
1467 this));
1468 return false;
1471 // Don't notify IME of anything if it's not good time to do it.
1472 if (mSuppressNotifications) {
1473 MOZ_LOG(sIMECOLog, LogLevel::Debug,
1474 ("0x%p IsSafeToNotifyIME(), it's not safe because of no widget",
1475 this));
1476 return false;
1479 if (!mESM || NS_WARN_IF(!GetPresContext())) {
1480 MOZ_LOG(sIMECOLog, LogLevel::Debug,
1481 ("0x%p IsSafeToNotifyIME(), it's not safe because of no "
1482 "EventStateManager and/or PresContext",
1483 this));
1484 return false;
1487 // If it's in reflow, we should wait to finish the reflow.
1488 // FYI: This should be called again from Reflow() or ReflowInterruptible().
1489 if (IsReflowLocked()) {
1490 MOZ_LOG(
1491 sIMECOLog, LogLevel::Debug,
1492 ("0x%p IsSafeToNotifyIME(), it's not safe because of reflow locked",
1493 this));
1494 return false;
1497 // If we're in handling an edit action, this method will be called later.
1498 if (mEditorBase && mEditorBase->IsInEditSubAction()) {
1499 MOZ_LOG(sIMECOLog, LogLevel::Debug,
1500 ("0x%p IsSafeToNotifyIME(), it's not safe because of focused "
1501 "editor handling somethings",
1502 this));
1503 return false;
1506 return true;
1509 void IMEContentObserver::FlushMergeableNotifications() {
1510 if (!IsSafeToNotifyIME()) {
1511 // So, if this is already called, this should do nothing.
1512 MOZ_LOG(sIMECOLog, LogLevel::Warning,
1513 ("0x%p FlushMergeableNotifications(), Warning, do nothing due to "
1514 "unsafe to notify IME",
1515 this));
1516 return;
1519 // Notifying something may cause nested call of this method. For example,
1520 // when somebody notified one of the notifications may dispatch query content
1521 // event. Then, it causes flushing layout which may cause another layout
1522 // change notification.
1524 if (mQueuedSender) {
1525 // So, if this is already called, this should do nothing.
1526 MOZ_LOG(sIMECOLog, LogLevel::Warning,
1527 ("0x%p FlushMergeableNotifications(), Warning, do nothing due to "
1528 "already flushing pending notifications",
1529 this));
1530 return;
1533 // If text change notification and/or position change notification becomes
1534 // unnecessary, let's cancel them.
1535 if (mNeedsToNotifyIMEOfTextChange && !NeedsTextChangeNotification()) {
1536 CancelNotifyingIMEOfTextChange();
1538 if (mNeedsToNotifyIMEOfPositionChange && !NeedsPositionChangeNotification()) {
1539 CancelNotifyingIMEOfPositionChange();
1542 if (!NeedsToNotifyIMEOfSomething()) {
1543 MOZ_LOG(sIMECOLog, LogLevel::Warning,
1544 ("0x%p FlushMergeableNotifications(), Warning, due to no pending "
1545 "notifications",
1546 this));
1547 return;
1550 // NOTE: Reset each pending flag because sending notification may cause
1551 // another change.
1553 MOZ_LOG(
1554 sIMECOLog, LogLevel::Info,
1555 ("0x%p FlushMergeableNotifications(), creating IMENotificationSender...",
1556 this));
1558 // If contents in selection range is modified, the selection range still
1559 // has removed node from the tree. In such case, ContentIterator won't
1560 // work well. Therefore, we shouldn't use AddScriptRunner() here since
1561 // it may kick runnable event immediately after DOM tree is changed but
1562 // the selection range isn't modified yet.
1563 mQueuedSender = new IMENotificationSender(this);
1564 mQueuedSender->Dispatch(mDocShell);
1565 MOZ_LOG(sIMECOLog, LogLevel::Debug,
1566 ("0x%p FlushMergeableNotifications(), finished", this));
1569 void IMEContentObserver::TryToFlushPendingNotifications(bool aAllowAsync) {
1570 // If a sender instance is sending notifications, we shouldn't try to create
1571 // a new sender again because the sender will recreate by itself if there are
1572 // new pending notifications.
1573 if (mSendingNotification != NOTIFY_IME_OF_NOTHING) {
1574 return;
1577 // When the caller allows to put off notifying IME, we can wait the next
1578 // call of this method or to run the queued sender.
1579 if (mQueuedSender && XRE_IsContentProcess() && aAllowAsync) {
1580 return;
1583 if (!mQueuedSender) {
1584 // If it was not safe to dispatch notifications when the pending
1585 // notifications are posted, this may not have IMENotificationSender
1586 // instance because it couldn't dispatch it, e.g., when an edit sub-action
1587 // is being handled in the editor, we shouldn't do it even if it's safe to
1588 // run script. Therefore, we need to create the sender instance here in the
1589 // case.
1590 if (!NeedsToNotifyIMEOfSomething()) {
1591 return;
1593 mQueuedSender = new IMENotificationSender(this);
1596 MOZ_LOG(sIMECOLog, LogLevel::Debug,
1597 ("0x%p TryToFlushPendingNotifications(), performing queued "
1598 "IMENotificationSender forcibly",
1599 this));
1600 RefPtr<IMENotificationSender> queuedSender = mQueuedSender;
1601 queuedSender->Run();
1604 /******************************************************************************
1605 * mozilla::IMEContentObserver::AChangeEvent
1606 ******************************************************************************/
1608 bool IMEContentObserver::AChangeEvent::CanNotifyIME(
1609 ChangeEventType aChangeEventType) const {
1610 RefPtr<IMEContentObserver> observer = GetObserver();
1611 if (NS_WARN_IF(!observer)) {
1612 return false;
1615 const LogLevel debugOrVerbose =
1616 aChangeEventType == ChangeEventType::eChangeEventType_Position
1617 ? LogLevel::Verbose
1618 : LogLevel::Debug;
1620 if (aChangeEventType == eChangeEventType_CompositionEventHandled) {
1621 if (observer->mWidget) {
1622 return true;
1624 MOZ_LOG(sIMECOLog, debugOrVerbose,
1625 ("0x%p AChangeEvent::CanNotifyIME(), Cannot notify IME of "
1626 "composition event handled because of no widget",
1627 this));
1628 return false;
1630 State state = observer->GetState();
1631 // If it's not initialized, we should do nothing.
1632 if (state == eState_NotObserving) {
1633 MOZ_LOG(sIMECOLog, debugOrVerbose,
1634 ("0x%p AChangeEvent::CanNotifyIME(), Cannot notify IME because "
1635 "of not observing",
1636 this));
1637 return false;
1639 // If setting focus, just check the state.
1640 if (aChangeEventType == eChangeEventType_Focus) {
1641 if (!observer->mIMEHasFocus) {
1642 return true;
1644 MOZ_LOG(sIMECOLog, debugOrVerbose,
1645 ("0x%p AChangeEvent::CanNotifyIME(), Cannot notify IME of focus "
1646 "change because of already focused",
1647 this));
1648 NS_WARNING("IME already has focus");
1649 return false;
1651 // If we've not notified IME of focus yet, we shouldn't notify anything.
1652 if (!observer->mIMEHasFocus) {
1653 MOZ_LOG(sIMECOLog, debugOrVerbose,
1654 ("0x%p AChangeEvent::CanNotifyIME(), Cannot notify IME because "
1655 "of not focused",
1656 this));
1657 return false;
1660 // If IME has focus, IMEContentObserver must hold the widget.
1661 MOZ_ASSERT(observer->mWidget);
1663 return true;
1666 bool IMEContentObserver::AChangeEvent::IsSafeToNotifyIME(
1667 ChangeEventType aChangeEventType) const {
1668 const LogLevel warningOrVerbose =
1669 aChangeEventType == ChangeEventType::eChangeEventType_Position
1670 ? LogLevel::Verbose
1671 : LogLevel::Warning;
1673 if (NS_WARN_IF(!nsContentUtils::IsSafeToRunScript())) {
1674 MOZ_LOG(sIMECOLog, warningOrVerbose,
1675 ("0x%p AChangeEvent::IsSafeToNotifyIME(), Warning, Cannot notify "
1676 "IME because of not safe to run script",
1677 this));
1678 return false;
1681 RefPtr<IMEContentObserver> observer = GetObserver();
1682 if (!observer) {
1683 MOZ_LOG(sIMECOLog, warningOrVerbose,
1684 ("0x%p AChangeEvent::IsSafeToNotifyIME(), Warning, Cannot notify "
1685 "IME because of no observer",
1686 this));
1687 return false;
1690 // While we're sending a notification, we shouldn't send another notification
1691 // recursively.
1692 if (observer->mSendingNotification != NOTIFY_IME_OF_NOTHING) {
1693 MOZ_LOG(sIMECOLog, warningOrVerbose,
1694 ("0x%p AChangeEvent::IsSafeToNotifyIME(), Warning, Cannot notify "
1695 "IME because of the observer sending another notification",
1696 this));
1697 return false;
1699 State state = observer->GetState();
1700 if (aChangeEventType == eChangeEventType_Focus) {
1701 if (NS_WARN_IF(state != eState_Initializing && state != eState_Observing)) {
1702 MOZ_LOG(sIMECOLog, warningOrVerbose,
1703 ("0x%p AChangeEvent::IsSafeToNotifyIME(), Warning, Cannot "
1704 "notify IME of focus because of not observing",
1705 this));
1706 return false;
1708 } else if (aChangeEventType == eChangeEventType_CompositionEventHandled) {
1709 // It doesn't need to check the observing status.
1710 } else if (state != eState_Observing) {
1711 MOZ_LOG(sIMECOLog, warningOrVerbose,
1712 ("0x%p AChangeEvent::IsSafeToNotifyIME(), Warning, Cannot notify "
1713 "IME because of not observing",
1714 this));
1715 return false;
1717 return observer->IsSafeToNotifyIME();
1720 /******************************************************************************
1721 * mozilla::IMEContentObserver::IMENotificationSender
1722 ******************************************************************************/
1724 void IMEContentObserver::IMENotificationSender::Dispatch(
1725 nsIDocShell* aDocShell) {
1726 if (XRE_IsContentProcess() && aDocShell) {
1727 if (RefPtr<nsPresContext> presContext = aDocShell->GetPresContext()) {
1728 if (nsRefreshDriver* refreshDriver = presContext->RefreshDriver()) {
1729 refreshDriver->AddEarlyRunner(this);
1730 return;
1734 NS_DispatchToCurrentThread(this);
1737 NS_IMETHODIMP
1738 IMEContentObserver::IMENotificationSender::Run() {
1739 if (NS_WARN_IF(mIsRunning)) {
1740 MOZ_LOG(
1741 sIMECOLog, LogLevel::Error,
1742 ("0x%p IMENotificationSender::Run(), FAILED, due to called recursively",
1743 this));
1744 return NS_OK;
1747 RefPtr<IMEContentObserver> observer = GetObserver();
1748 if (!observer) {
1749 return NS_OK;
1752 AutoRestore<bool> running(mIsRunning);
1753 mIsRunning = true;
1755 // This instance was already performed forcibly.
1756 if (observer->mQueuedSender != this) {
1757 return NS_OK;
1760 // NOTE: Reset each pending flag because sending notification may cause
1761 // another change.
1763 if (observer->mNeedsToNotifyIMEOfFocusSet) {
1764 observer->mNeedsToNotifyIMEOfFocusSet = false;
1765 SendFocusSet();
1766 observer->mQueuedSender = nullptr;
1767 // If it's not safe to notify IME of focus, SendFocusSet() sets
1768 // mNeedsToNotifyIMEOfFocusSet true again. For guaranteeing to send the
1769 // focus notification later, we should put a new sender into the queue but
1770 // this case must be rare. Note that if mIMEContentObserver is already
1771 // destroyed, mNeedsToNotifyIMEOfFocusSet is never set true again.
1772 if (observer->mNeedsToNotifyIMEOfFocusSet) {
1773 MOZ_ASSERT(!observer->mIMEHasFocus);
1774 MOZ_LOG(sIMECOLog, LogLevel::Debug,
1775 ("0x%p IMENotificationSender::Run(), posting "
1776 "IMENotificationSender to current thread",
1777 this));
1778 observer->mQueuedSender = new IMENotificationSender(observer);
1779 observer->mQueuedSender->Dispatch(observer->mDocShell);
1780 return NS_OK;
1782 // This is the first notification to IME. So, we don't need to notify
1783 // anymore since IME starts to query content after it gets focus.
1784 observer->ClearPendingNotifications();
1785 return NS_OK;
1788 if (observer->mNeedsToNotifyIMEOfTextChange) {
1789 observer->mNeedsToNotifyIMEOfTextChange = false;
1790 SendTextChange();
1793 // If a text change notification causes another text change again, we should
1794 // notify IME of that before sending a selection change notification.
1795 if (!observer->mNeedsToNotifyIMEOfTextChange) {
1796 // Be aware, PuppetWidget depends on the order of this. A selection change
1797 // notification should not be sent before a text change notification because
1798 // PuppetWidget shouldn't query new text content every selection change.
1799 if (observer->mNeedsToNotifyIMEOfSelectionChange) {
1800 observer->mNeedsToNotifyIMEOfSelectionChange = false;
1801 SendSelectionChange();
1805 // If a text change notification causes another text change again or a
1806 // selection change notification causes either a text change or another
1807 // selection change, we should notify IME of those before sending a position
1808 // change notification.
1809 if (!observer->mNeedsToNotifyIMEOfTextChange &&
1810 !observer->mNeedsToNotifyIMEOfSelectionChange) {
1811 if (observer->mNeedsToNotifyIMEOfPositionChange) {
1812 observer->mNeedsToNotifyIMEOfPositionChange = false;
1813 SendPositionChange();
1817 // Composition event handled notification should be sent after all the
1818 // other notifications because this notifies widget of finishing all pending
1819 // events are handled completely.
1820 if (!observer->mNeedsToNotifyIMEOfTextChange &&
1821 !observer->mNeedsToNotifyIMEOfSelectionChange &&
1822 !observer->mNeedsToNotifyIMEOfPositionChange) {
1823 if (observer->mNeedsToNotifyIMEOfCompositionEventHandled) {
1824 observer->mNeedsToNotifyIMEOfCompositionEventHandled = false;
1825 SendCompositionEventHandled();
1829 observer->mQueuedSender = nullptr;
1831 // If notifications caused some new change, we should notify them now.
1832 if (observer->NeedsToNotifyIMEOfSomething()) {
1833 if (observer->GetState() == eState_StoppedObserving) {
1834 MOZ_LOG(sIMECOLog, LogLevel::Debug,
1835 ("0x%p IMENotificationSender::Run(), waiting "
1836 "IMENotificationSender to be reinitialized",
1837 this));
1838 } else {
1839 MOZ_LOG(sIMECOLog, LogLevel::Debug,
1840 ("0x%p IMENotificationSender::Run(), posting "
1841 "IMENotificationSender to current thread",
1842 this));
1843 observer->mQueuedSender = new IMENotificationSender(observer);
1844 observer->mQueuedSender->Dispatch(observer->mDocShell);
1847 return NS_OK;
1850 void IMEContentObserver::IMENotificationSender::SendFocusSet() {
1851 RefPtr<IMEContentObserver> observer = GetObserver();
1852 if (!observer) {
1853 return;
1856 if (!CanNotifyIME(eChangeEventType_Focus)) {
1857 // If IMEContentObserver has already gone, we don't need to notify IME of
1858 // focus.
1859 MOZ_LOG(sIMECOLog, LogLevel::Warning,
1860 ("0x%p IMENotificationSender::SendFocusSet(), Warning, does not "
1861 "send notification due to impossible to notify IME of focus",
1862 this));
1863 observer->ClearPendingNotifications();
1864 return;
1867 if (!IsSafeToNotifyIME(eChangeEventType_Focus)) {
1868 MOZ_LOG(
1869 sIMECOLog, LogLevel::Warning,
1870 ("0x%p IMENotificationSender::SendFocusSet(), Warning, does not send "
1871 "notification due to unsafe, retrying to send NOTIFY_IME_OF_FOCUS...",
1872 this));
1873 observer->PostFocusSetNotification();
1874 return;
1877 observer->mIMEHasFocus = true;
1878 // Initialize selection cache with the first selection data.
1879 #ifdef XP_MACOSX
1880 // We need to flush layout only on macOS because character coordinates are
1881 // cached by cocoa with this call, but we don't have a way to update them
1882 // after that. Therefore, we need the latest layout information right now.
1883 observer->UpdateSelectionCache(true);
1884 #else
1885 // We avoid flushing for focus in the general case.
1886 observer->UpdateSelectionCache(false);
1887 #endif // #ifdef XP_MACOSX #else
1888 MOZ_LOG(sIMECOLog, LogLevel::Info,
1889 ("0x%p IMENotificationSender::SendFocusSet(), sending "
1890 "NOTIFY_IME_OF_FOCUS...",
1891 this));
1893 MOZ_RELEASE_ASSERT(observer->mSendingNotification == NOTIFY_IME_OF_NOTHING);
1894 observer->mSendingNotification = NOTIFY_IME_OF_FOCUS;
1895 IMEStateManager::NotifyIME(IMENotification(NOTIFY_IME_OF_FOCUS),
1896 observer->mWidget);
1897 observer->mSendingNotification = NOTIFY_IME_OF_NOTHING;
1899 // IMENotificationRequests referred by ObserveEditableNode() may be different
1900 // before or after widget receives NOTIFY_IME_OF_FOCUS. Therefore, we need
1901 // to guarantee to call ObserveEditableNode() after sending
1902 // NOTIFY_IME_OF_FOCUS.
1903 observer->OnIMEReceivedFocus();
1905 MOZ_LOG(
1906 sIMECOLog, LogLevel::Debug,
1907 ("0x%p IMENotificationSender::SendFocusSet(), sent NOTIFY_IME_OF_FOCUS",
1908 this));
1911 void IMEContentObserver::IMENotificationSender::SendSelectionChange() {
1912 RefPtr<IMEContentObserver> observer = GetObserver();
1913 if (!observer) {
1914 return;
1917 if (!CanNotifyIME(eChangeEventType_Selection)) {
1918 MOZ_LOG(sIMECOLog, LogLevel::Warning,
1919 ("0x%p IMENotificationSender::SendSelectionChange(), Warning, "
1920 "does not send notification due to impossible to notify IME of "
1921 "selection change",
1922 this));
1923 return;
1926 if (!IsSafeToNotifyIME(eChangeEventType_Selection)) {
1927 MOZ_LOG(sIMECOLog, LogLevel::Warning,
1928 ("0x%p IMENotificationSender::SendSelectionChange(), Warning, "
1929 "does not send notification due to unsafe, retrying to send "
1930 "NOTIFY_IME_OF_SELECTION_CHANGE...",
1931 this));
1932 observer->PostSelectionChangeNotification();
1933 return;
1936 SelectionChangeData lastSelChangeData = observer->mSelectionData;
1937 if (NS_WARN_IF(!observer->UpdateSelectionCache())) {
1938 MOZ_LOG(sIMECOLog, LogLevel::Error,
1939 ("0x%p IMENotificationSender::SendSelectionChange(), FAILED, due "
1940 "to UpdateSelectionCache() failure",
1941 this));
1942 return;
1945 // The state may be changed since querying content causes flushing layout.
1946 if (!CanNotifyIME(eChangeEventType_Selection)) {
1947 MOZ_LOG(sIMECOLog, LogLevel::Error,
1948 ("0x%p IMENotificationSender::SendSelectionChange(), FAILED, due "
1949 "to flushing layout having changed something",
1950 this));
1951 return;
1954 // If the selection isn't changed actually, we shouldn't notify IME of
1955 // selection change.
1956 SelectionChangeData& newSelChangeData = observer->mSelectionData;
1957 if (lastSelChangeData.IsInitialized() &&
1958 lastSelChangeData.EqualsRangeAndDirectionAndWritingMode(
1959 newSelChangeData)) {
1960 MOZ_LOG(
1961 sIMECOLog, LogLevel::Debug,
1962 ("0x%p IMENotificationSender::SendSelectionChange(), not notifying IME "
1963 "of NOTIFY_IME_OF_SELECTION_CHANGE due to not changed actually",
1964 this));
1965 return;
1968 MOZ_LOG(sIMECOLog, LogLevel::Info,
1969 ("0x%p IMENotificationSender::SendSelectionChange(), sending "
1970 "NOTIFY_IME_OF_SELECTION_CHANGE... newSelChangeData=%s",
1971 this, ToString(newSelChangeData).c_str()));
1973 IMENotification notification(NOTIFY_IME_OF_SELECTION_CHANGE);
1974 notification.SetData(observer->mSelectionData);
1976 MOZ_RELEASE_ASSERT(observer->mSendingNotification == NOTIFY_IME_OF_NOTHING);
1977 observer->mSendingNotification = NOTIFY_IME_OF_SELECTION_CHANGE;
1978 IMEStateManager::NotifyIME(notification, observer->mWidget);
1979 observer->mSendingNotification = NOTIFY_IME_OF_NOTHING;
1981 MOZ_LOG(sIMECOLog, LogLevel::Debug,
1982 ("0x%p IMENotificationSender::SendSelectionChange(), sent "
1983 "NOTIFY_IME_OF_SELECTION_CHANGE",
1984 this));
1987 void IMEContentObserver::IMENotificationSender::SendTextChange() {
1988 RefPtr<IMEContentObserver> observer = GetObserver();
1989 if (!observer) {
1990 return;
1993 if (!CanNotifyIME(eChangeEventType_Text)) {
1994 MOZ_LOG(
1995 sIMECOLog, LogLevel::Warning,
1996 ("0x%p IMENotificationSender::SendTextChange(), Warning, does not "
1997 "send notification due to impossible to notify IME of text change",
1998 this));
1999 return;
2002 if (!IsSafeToNotifyIME(eChangeEventType_Text)) {
2003 MOZ_LOG(sIMECOLog, LogLevel::Warning,
2004 ("0x%p IMENotificationSender::SendTextChange(), Warning, does "
2005 "not send notification due to unsafe, retrying to send "
2006 "NOTIFY_IME_OF_TEXT_CHANGE...",
2007 this));
2008 observer->PostTextChangeNotification();
2009 return;
2012 // If text change notification is unnecessary anymore, just cancel it.
2013 if (!observer->NeedsTextChangeNotification()) {
2014 MOZ_LOG(sIMECOLog, LogLevel::Warning,
2015 ("0x%p IMENotificationSender::SendTextChange(), Warning, "
2016 "canceling sending NOTIFY_IME_OF_TEXT_CHANGE",
2017 this));
2018 observer->CancelNotifyingIMEOfTextChange();
2019 return;
2022 MOZ_LOG(sIMECOLog, LogLevel::Info,
2023 ("0x%p IMENotificationSender::SendTextChange(), sending "
2024 "NOTIFY_IME_OF_TEXT_CHANGE... mIMEContentObserver={ "
2025 "mTextChangeData=%s }",
2026 this, ToString(observer->mTextChangeData).c_str()));
2028 IMENotification notification(NOTIFY_IME_OF_TEXT_CHANGE);
2029 notification.SetData(observer->mTextChangeData);
2030 observer->mTextChangeData.Clear();
2032 MOZ_RELEASE_ASSERT(observer->mSendingNotification == NOTIFY_IME_OF_NOTHING);
2033 observer->mSendingNotification = NOTIFY_IME_OF_TEXT_CHANGE;
2034 IMEStateManager::NotifyIME(notification, observer->mWidget);
2035 observer->mSendingNotification = NOTIFY_IME_OF_NOTHING;
2037 MOZ_LOG(sIMECOLog, LogLevel::Debug,
2038 ("0x%p IMENotificationSender::SendTextChange(), sent "
2039 "NOTIFY_IME_OF_TEXT_CHANGE",
2040 this));
2043 void IMEContentObserver::IMENotificationSender::SendPositionChange() {
2044 RefPtr<IMEContentObserver> observer = GetObserver();
2045 if (!observer) {
2046 return;
2049 if (!CanNotifyIME(eChangeEventType_Position)) {
2050 MOZ_LOG(sIMECOLog, LogLevel::Verbose,
2051 ("0x%p IMENotificationSender::SendPositionChange(), Warning, "
2052 "does not send notification due to impossible to notify IME of "
2053 "position change",
2054 this));
2055 return;
2058 if (!IsSafeToNotifyIME(eChangeEventType_Position)) {
2059 MOZ_LOG(sIMECOLog, LogLevel::Verbose,
2060 ("0x%p IMENotificationSender::SendPositionChange(), Warning, "
2061 "does not send notification due to unsafe, retrying to send "
2062 "NOTIFY_IME_OF_POSITION_CHANGE...",
2063 this));
2064 observer->PostPositionChangeNotification();
2065 return;
2068 // If position change notification is unnecessary anymore, just cancel it.
2069 if (!observer->NeedsPositionChangeNotification()) {
2070 MOZ_LOG(sIMECOLog, LogLevel::Verbose,
2071 ("0x%p IMENotificationSender::SendPositionChange(), Warning, "
2072 "canceling sending NOTIFY_IME_OF_POSITION_CHANGE",
2073 this));
2074 observer->CancelNotifyingIMEOfPositionChange();
2075 return;
2078 MOZ_LOG(sIMECOLog, LogLevel::Info,
2079 ("0x%p IMENotificationSender::SendPositionChange(), sending "
2080 "NOTIFY_IME_OF_POSITION_CHANGE...",
2081 this));
2083 MOZ_RELEASE_ASSERT(observer->mSendingNotification == NOTIFY_IME_OF_NOTHING);
2084 observer->mSendingNotification = NOTIFY_IME_OF_POSITION_CHANGE;
2085 IMEStateManager::NotifyIME(IMENotification(NOTIFY_IME_OF_POSITION_CHANGE),
2086 observer->mWidget);
2087 observer->mSendingNotification = NOTIFY_IME_OF_NOTHING;
2089 MOZ_LOG(sIMECOLog, LogLevel::Debug,
2090 ("0x%p IMENotificationSender::SendPositionChange(), sent "
2091 "NOTIFY_IME_OF_POSITION_CHANGE",
2092 this));
2095 void IMEContentObserver::IMENotificationSender::SendCompositionEventHandled() {
2096 RefPtr<IMEContentObserver> observer = GetObserver();
2097 if (!observer) {
2098 return;
2101 if (!CanNotifyIME(eChangeEventType_CompositionEventHandled)) {
2102 MOZ_LOG(sIMECOLog, LogLevel::Warning,
2103 ("0x%p IMENotificationSender::SendCompositionEventHandled(), "
2104 "Warning, does not send notification due to impossible to notify "
2105 "IME of composition event handled",
2106 this));
2107 return;
2110 if (!IsSafeToNotifyIME(eChangeEventType_CompositionEventHandled)) {
2111 MOZ_LOG(sIMECOLog, LogLevel::Warning,
2112 ("0x%p IMENotificationSender::SendCompositionEventHandled(), "
2113 "Warning, does not send notification due to unsafe, retrying to "
2114 "send NOTIFY_IME_OF_POSITION_CHANGE...",
2115 this));
2116 observer->PostCompositionEventHandledNotification();
2117 return;
2120 MOZ_LOG(sIMECOLog, LogLevel::Info,
2121 ("0x%p IMENotificationSender::SendCompositionEventHandled(), sending "
2122 "NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED...",
2123 this));
2125 MOZ_RELEASE_ASSERT(observer->mSendingNotification == NOTIFY_IME_OF_NOTHING);
2126 observer->mSendingNotification = NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED;
2127 IMEStateManager::NotifyIME(
2128 IMENotification(NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED),
2129 observer->mWidget);
2130 observer->mSendingNotification = NOTIFY_IME_OF_NOTHING;
2132 MOZ_LOG(sIMECOLog, LogLevel::Debug,
2133 ("0x%p IMENotificationSender::SendCompositionEventHandled(), sent "
2134 "NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED",
2135 this));
2138 /******************************************************************************
2139 * mozilla::IMEContentObserver::DocumentObservingHelper
2140 ******************************************************************************/
2142 NS_IMPL_CYCLE_COLLECTION_CLASS(IMEContentObserver::DocumentObserver)
2144 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IMEContentObserver::DocumentObserver)
2145 // StopObserving() releases mIMEContentObserver and mDocument.
2146 tmp->StopObserving();
2147 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
2149 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IMEContentObserver::DocumentObserver)
2150 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIMEContentObserver)
2151 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
2152 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
2154 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IMEContentObserver::DocumentObserver)
2155 NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver)
2156 NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
2157 NS_INTERFACE_MAP_ENTRY(nsISupports)
2158 NS_INTERFACE_MAP_END
2160 NS_IMPL_CYCLE_COLLECTING_ADDREF(IMEContentObserver::DocumentObserver)
2161 NS_IMPL_CYCLE_COLLECTING_RELEASE(IMEContentObserver::DocumentObserver)
2163 void IMEContentObserver::DocumentObserver::Observe(Document* aDocument) {
2164 MOZ_ASSERT(aDocument);
2166 // Guarantee that aDocument won't be destroyed during a call of
2167 // StopObserving().
2168 RefPtr<Document> newDocument = aDocument;
2170 StopObserving();
2172 mDocument = std::move(newDocument);
2173 mDocument->AddObserver(this);
2176 void IMEContentObserver::DocumentObserver::StopObserving() {
2177 if (!IsObserving()) {
2178 return;
2181 // Grab IMEContentObserver which could be destroyed during method calls.
2182 RefPtr<IMEContentObserver> observer = std::move(mIMEContentObserver);
2184 // Stop observing the document first.
2185 RefPtr<Document> document = std::move(mDocument);
2186 document->RemoveObserver(this);
2188 // Notify IMEContentObserver of ending of document updates if this already
2189 // notified it of beginning of document updates.
2190 for (; IsUpdating(); --mDocumentUpdating) {
2191 // FYI: IsUpdating() returns true until mDocumentUpdating becomes 0.
2192 // However, IsObserving() returns false now because mDocument was
2193 // already cleared above. Therefore, this method won't be called
2194 // recursively.
2195 observer->EndDocumentUpdate();
2199 void IMEContentObserver::DocumentObserver::Destroy() {
2200 StopObserving();
2201 mIMEContentObserver = nullptr;
2204 void IMEContentObserver::DocumentObserver::BeginUpdate(Document* aDocument) {
2205 if (NS_WARN_IF(Destroyed()) || NS_WARN_IF(!IsObserving())) {
2206 return;
2208 mDocumentUpdating++;
2209 mIMEContentObserver->BeginDocumentUpdate();
2212 void IMEContentObserver::DocumentObserver::EndUpdate(Document* aDocument) {
2213 if (NS_WARN_IF(Destroyed()) || NS_WARN_IF(!IsObserving()) ||
2214 NS_WARN_IF(!IsUpdating())) {
2215 return;
2217 mDocumentUpdating--;
2218 mIMEContentObserver->EndDocumentUpdate();
2221 } // namespace mozilla