1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 sw=2 et 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 "ContentEventHandler.h"
8 #include "IMEContentObserver.h"
9 #include "mozilla/AsyncEventDispatcher.h"
10 #include "mozilla/EventStateManager.h"
11 #include "mozilla/IMEStateManager.h"
12 #include "mozilla/TextComposition.h"
13 #include "mozilla/dom/Element.h"
14 #include "nsAutoPtr.h"
15 #include "nsContentUtils.h"
16 #include "nsGkAtoms.h"
18 #include "nsIContent.h"
19 #include "nsIDocument.h"
20 #include "nsIDOMDocument.h"
21 #include "nsIDOMRange.h"
24 #include "nsIPresShell.h"
25 #include "nsISelectionController.h"
26 #include "nsISelectionPrivate.h"
27 #include "nsISupports.h"
28 #include "nsIWidget.h"
29 #include "nsPresContext.h"
30 #include "nsThreadUtils.h"
31 #include "nsWeakReference.h"
35 using namespace widget
;
37 NS_IMPL_CYCLE_COLLECTION(IMEContentObserver
,
39 mRootContent
, mEditableNode
, mDocShell
)
41 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IMEContentObserver
)
42 NS_INTERFACE_MAP_ENTRY(nsISelectionListener
)
43 NS_INTERFACE_MAP_ENTRY(nsIMutationObserver
)
44 NS_INTERFACE_MAP_ENTRY(nsIReflowObserver
)
45 NS_INTERFACE_MAP_ENTRY(nsIScrollObserver
)
46 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference
)
47 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsISelectionListener
)
50 NS_IMPL_CYCLE_COLLECTING_ADDREF(IMEContentObserver
)
51 NS_IMPL_CYCLE_COLLECTING_RELEASE(IMEContentObserver
)
53 IMEContentObserver::IMEContentObserver()
59 IMEContentObserver::Init(nsIWidget
* aWidget
,
60 nsPresContext
* aPresContext
,
63 mESM
= aPresContext
->EventStateManager();
64 mESM
->OnStartToObserveContent(this);
67 mEditableNode
= IMEStateManager::GetRootEditableNode(aPresContext
, aContent
);
72 nsIPresShell
* presShell
= aPresContext
->PresShell();
74 // get selection and root content
75 nsCOMPtr
<nsISelectionController
> selCon
;
76 if (mEditableNode
->IsNodeOfType(nsINode::eCONTENT
)) {
78 static_cast<nsIContent
*>(mEditableNode
.get())->GetPrimaryFrame();
79 NS_ENSURE_TRUE_VOID(frame
);
81 frame
->GetSelectionController(aPresContext
,
82 getter_AddRefs(selCon
));
84 // mEditableNode is a document
85 selCon
= do_QueryInterface(presShell
);
87 NS_ENSURE_TRUE_VOID(selCon
);
89 selCon
->GetSelection(nsISelectionController::SELECTION_NORMAL
,
90 getter_AddRefs(mSelection
));
91 NS_ENSURE_TRUE_VOID(mSelection
);
93 nsCOMPtr
<nsIDOMRange
> selDomRange
;
94 if (NS_SUCCEEDED(mSelection
->GetRangeAt(0, getter_AddRefs(selDomRange
)))) {
95 nsRange
* selRange
= static_cast<nsRange
*>(selDomRange
.get());
96 NS_ENSURE_TRUE_VOID(selRange
&& selRange
->GetStartParent());
98 mRootContent
= selRange
->GetStartParent()->
99 GetSelectionRootContent(presShell
);
101 mRootContent
= mEditableNode
->GetSelectionRootContent(presShell
);
103 if (!mRootContent
&& mEditableNode
->IsNodeOfType(nsINode::eDOCUMENT
)) {
104 // The document node is editable, but there are no contents, this document
108 NS_ENSURE_TRUE_VOID(mRootContent
);
110 if (IMEStateManager::IsTestingIME()) {
111 nsIDocument
* doc
= aPresContext
->Document();
112 (new AsyncEventDispatcher(doc
, NS_LITERAL_STRING("MozIMEFocusIn"),
113 false, false))->RunDOMEventWhenSafe();
116 aWidget
->NotifyIME(IMENotification(NOTIFY_IME_OF_FOCUS
));
118 // NOTIFY_IME_OF_FOCUS might cause recreating IMEContentObserver
119 // instance via IMEStateManager::UpdateIMEState(). So, this
120 // instance might already have been destroyed, check it.
125 mDocShell
= aPresContext
->GetDocShell();
127 ObserveEditableNode();
131 IMEContentObserver::ObserveEditableNode()
133 MOZ_ASSERT(mSelection
);
134 MOZ_ASSERT(mRootContent
);
136 mUpdatePreference
= mWidget
->GetIMEUpdatePreference();
137 if (mUpdatePreference
.WantSelectionChange()) {
138 // add selection change listener
139 nsCOMPtr
<nsISelectionPrivate
> selPrivate(do_QueryInterface(mSelection
));
140 NS_ENSURE_TRUE_VOID(selPrivate
);
141 nsresult rv
= selPrivate
->AddSelectionListener(this);
142 NS_ENSURE_SUCCESS_VOID(rv
);
145 if (mUpdatePreference
.WantTextChange()) {
146 // add text change observer
147 mRootContent
->AddMutationObserver(this);
150 if (mUpdatePreference
.WantPositionChanged() && mDocShell
) {
151 // Add scroll position listener and reflow observer to detect position and
153 mDocShell
->AddWeakScrollObserver(this);
154 mDocShell
->AddWeakReflowObserver(this);
159 IMEContentObserver::Destroy()
161 // If CreateTextStateManager failed, mRootContent will be null,
162 // and we should not call NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR))
164 if (IMEStateManager::IsTestingIME() && mEditableNode
) {
165 nsIDocument
* doc
= mEditableNode
->OwnerDoc();
166 (new AsyncEventDispatcher(doc
, NS_LITERAL_STRING("MozIMEFocusOut"),
167 false, false))->RunDOMEventWhenSafe();
169 mWidget
->NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR
));
171 // Even if there are some pending notification, it'll never notify the widget.
173 if (mUpdatePreference
.WantSelectionChange() && mSelection
) {
174 nsCOMPtr
<nsISelectionPrivate
> selPrivate(do_QueryInterface(mSelection
));
176 selPrivate
->RemoveSelectionListener(this);
179 mSelection
= nullptr;
180 if (mUpdatePreference
.WantTextChange() && mRootContent
) {
181 mRootContent
->RemoveMutationObserver(this);
183 if (mUpdatePreference
.WantPositionChanged() && mDocShell
) {
184 mDocShell
->RemoveWeakScrollObserver(this);
185 mDocShell
->RemoveWeakReflowObserver(this);
187 mRootContent
= nullptr;
188 mEditableNode
= nullptr;
190 mUpdatePreference
.mWantUpdates
= nsIMEUpdatePreference::NOTIFY_NOTHING
;
193 mESM
->OnStopObservingContent(this);
199 IMEContentObserver::DisconnectFromEventStateManager()
205 IMEContentObserver::IsManaging(nsPresContext
* aPresContext
,
206 nsIContent
* aContent
)
208 if (!mSelection
|| !mRootContent
|| !mEditableNode
) {
209 return false; // failed to initialize.
211 if (!mRootContent
->IsInDoc()) {
212 return false; // the focused editor has already been reframed.
214 return mEditableNode
== IMEStateManager::GetRootEditableNode(aPresContext
,
219 IMEContentObserver::IsEditorHandlingEventForComposition() const
224 nsRefPtr
<TextComposition
> composition
=
225 IMEStateManager::GetTextCompositionFor(mWidget
);
229 return composition
->IsEditorHandlingEvent();
233 IMEContentObserver::GetSelectionAndRoot(nsISelection
** aSelection
,
234 nsIContent
** aRootContent
) const
236 if (!mEditableNode
|| !mSelection
) {
237 return NS_ERROR_NOT_AVAILABLE
;
240 NS_ASSERTION(mSelection
&& mRootContent
, "uninitialized content observer");
241 NS_ADDREF(*aSelection
= mSelection
);
242 NS_ADDREF(*aRootContent
= mRootContent
);
246 // Helper class, used for selection change notification
247 class SelectionChangeEvent
: public nsRunnable
250 SelectionChangeEvent(IMEContentObserver
* aDispatcher
,
251 bool aCausedByComposition
)
252 : mDispatcher(aDispatcher
)
253 , mCausedByComposition(aCausedByComposition
)
255 MOZ_ASSERT(mDispatcher
);
260 if (mDispatcher
->GetWidget()) {
261 IMENotification
notification(NOTIFY_IME_OF_SELECTION_CHANGE
);
262 notification
.mSelectionChangeData
.mCausedByComposition
=
263 mCausedByComposition
;
264 mDispatcher
->GetWidget()->NotifyIME(notification
);
270 nsRefPtr
<IMEContentObserver
> mDispatcher
;
271 bool mCausedByComposition
;
275 IMEContentObserver::NotifySelectionChanged(nsIDOMDocument
* aDOMDocument
,
276 nsISelection
* aSelection
,
279 bool causedByComposition
= IsEditorHandlingEventForComposition();
280 if (causedByComposition
&&
281 !mUpdatePreference
.WantChangesCausedByComposition()) {
286 nsresult rv
= aSelection
->GetRangeCount(&count
);
287 NS_ENSURE_SUCCESS(rv
, rv
);
288 if (count
> 0 && mWidget
) {
289 nsContentUtils::AddScriptRunner(
290 new SelectionChangeEvent(this, causedByComposition
));
295 // Helper class, used for position change notification
296 class PositionChangeEvent MOZ_FINAL
: public nsRunnable
299 PositionChangeEvent(IMEContentObserver
* aDispatcher
)
300 : mDispatcher(aDispatcher
)
302 MOZ_ASSERT(mDispatcher
);
307 if (mDispatcher
->GetWidget()) {
308 mDispatcher
->GetWidget()->NotifyIME(
309 IMENotification(NOTIFY_IME_OF_POSITION_CHANGE
));
315 nsRefPtr
<IMEContentObserver
> mDispatcher
;
319 IMEContentObserver::ScrollPositionChanged()
322 nsContentUtils::AddScriptRunner(new PositionChangeEvent(this));
327 IMEContentObserver::Reflow(DOMHighResTimeStamp aStart
,
328 DOMHighResTimeStamp aEnd
)
331 nsContentUtils::AddScriptRunner(new PositionChangeEvent(this));
337 IMEContentObserver::ReflowInterruptible(DOMHighResTimeStamp aStart
,
338 DOMHighResTimeStamp aEnd
)
341 nsContentUtils::AddScriptRunner(new PositionChangeEvent(this));
346 // Helper class, used for text change notification
347 class TextChangeEvent
: public nsRunnable
350 TextChangeEvent(IMEContentObserver
* aDispatcher
,
351 uint32_t aStart
, uint32_t aOldEnd
, uint32_t aNewEnd
,
352 bool aCausedByComposition
)
353 : mDispatcher(aDispatcher
)
357 , mCausedByComposition(aCausedByComposition
)
359 MOZ_ASSERT(mDispatcher
);
364 if (mDispatcher
->GetWidget()) {
365 IMENotification
notification(NOTIFY_IME_OF_TEXT_CHANGE
);
366 notification
.mTextChangeData
.mStartOffset
= mStart
;
367 notification
.mTextChangeData
.mOldEndOffset
= mOldEnd
;
368 notification
.mTextChangeData
.mNewEndOffset
= mNewEnd
;
369 notification
.mTextChangeData
.mCausedByComposition
= mCausedByComposition
;
370 mDispatcher
->GetWidget()->NotifyIME(notification
);
376 nsRefPtr
<IMEContentObserver
> mDispatcher
;
377 uint32_t mStart
, mOldEnd
, mNewEnd
;
378 bool mCausedByComposition
;
382 IMEContentObserver::CharacterDataChanged(nsIDocument
* aDocument
,
383 nsIContent
* aContent
,
384 CharacterDataChangeInfo
* aInfo
)
386 NS_ASSERTION(aContent
->IsNodeOfType(nsINode::eTEXT
),
387 "character data changed for non-text node");
389 bool causedByComposition
= IsEditorHandlingEventForComposition();
390 if (causedByComposition
&&
391 !mUpdatePreference
.WantChangesCausedByComposition()) {
396 // get offsets of change and fire notification
398 ContentEventHandler::GetFlatTextOffsetOfRange(mRootContent
, aContent
,
401 LINE_BREAK_TYPE_NATIVE
);
402 NS_ENSURE_SUCCESS_VOID(rv
);
404 uint32_t oldEnd
= offset
+ aInfo
->mChangeEnd
- aInfo
->mChangeStart
;
405 uint32_t newEnd
= offset
+ aInfo
->mReplaceLength
;
407 nsContentUtils::AddScriptRunner(
408 new TextChangeEvent(this, offset
, oldEnd
, newEnd
, causedByComposition
));
412 IMEContentObserver::NotifyContentAdded(nsINode
* aContainer
,
416 bool causedByComposition
= IsEditorHandlingEventForComposition();
417 if (causedByComposition
&&
418 !mUpdatePreference
.WantChangesCausedByComposition()) {
424 ContentEventHandler::GetFlatTextOffsetOfRange(mRootContent
, aContainer
,
425 aStartIndex
, &offset
,
426 LINE_BREAK_TYPE_NATIVE
);
427 NS_ENSURE_SUCCESS_VOID(rv
);
429 // get offset at the end of the last added node
430 nsIContent
* childAtStart
= aContainer
->GetChildAt(aStartIndex
);
431 uint32_t addingLength
= 0;
432 rv
= ContentEventHandler::GetFlatTextOffsetOfRange(childAtStart
, aContainer
,
433 aEndIndex
, &addingLength
,
434 LINE_BREAK_TYPE_NATIVE
);
435 NS_ENSURE_SUCCESS_VOID(rv
);
441 nsContentUtils::AddScriptRunner(
442 new TextChangeEvent(this, offset
, offset
, offset
+ addingLength
,
443 causedByComposition
));
447 IMEContentObserver::ContentAppended(nsIDocument
* aDocument
,
448 nsIContent
* aContainer
,
449 nsIContent
* aFirstNewContent
,
450 int32_t aNewIndexInContainer
)
452 NotifyContentAdded(aContainer
, aNewIndexInContainer
,
453 aContainer
->GetChildCount());
457 IMEContentObserver::ContentInserted(nsIDocument
* aDocument
,
458 nsIContent
* aContainer
,
460 int32_t aIndexInContainer
)
462 NotifyContentAdded(NODE_FROM(aContainer
, aDocument
),
463 aIndexInContainer
, aIndexInContainer
+ 1);
467 IMEContentObserver::ContentRemoved(nsIDocument
* aDocument
,
468 nsIContent
* aContainer
,
470 int32_t aIndexInContainer
,
471 nsIContent
* aPreviousSibling
)
473 bool causedByComposition
= IsEditorHandlingEventForComposition();
474 if (causedByComposition
&&
475 !mUpdatePreference
.WantChangesCausedByComposition()) {
481 ContentEventHandler::GetFlatTextOffsetOfRange(mRootContent
,
482 NODE_FROM(aContainer
,
484 aIndexInContainer
, &offset
,
485 LINE_BREAK_TYPE_NATIVE
);
486 NS_ENSURE_SUCCESS_VOID(rv
);
488 // get offset at the end of the deleted node
490 aChild
->IsNodeOfType(nsINode::eTEXT
) ?
491 static_cast<int32_t>(aChild
->TextLength()) :
492 std::max(static_cast<int32_t>(aChild
->GetChildCount()), 1);
493 MOZ_ASSERT(nodeLength
>= 0, "The node length is out of range");
494 uint32_t textLength
= 0;
495 rv
= ContentEventHandler::GetFlatTextOffsetOfRange(aChild
, aChild
,
496 nodeLength
, &textLength
,
497 LINE_BREAK_TYPE_NATIVE
);
498 NS_ENSURE_SUCCESS_VOID(rv
);
504 nsContentUtils::AddScriptRunner(
505 new TextChangeEvent(this, offset
, offset
+ textLength
, offset
,
506 causedByComposition
));
510 GetContentBR(dom::Element
* aElement
)
512 if (!aElement
->IsNodeOfType(nsINode::eCONTENT
)) {
515 nsIContent
* content
= static_cast<nsIContent
*>(aElement
);
516 return content
->IsHTML(nsGkAtoms::br
) ? content
: nullptr;
520 IMEContentObserver::AttributeWillChange(nsIDocument
* aDocument
,
521 dom::Element
* aElement
,
522 int32_t aNameSpaceID
,
526 nsIContent
*content
= GetContentBR(aElement
);
527 mPreAttrChangeLength
= content
?
528 ContentEventHandler::GetNativeTextLength(content
) : 0;
532 IMEContentObserver::AttributeChanged(nsIDocument
* aDocument
,
533 dom::Element
* aElement
,
534 int32_t aNameSpaceID
,
538 bool causedByComposition
= IsEditorHandlingEventForComposition();
539 if (causedByComposition
&&
540 !mUpdatePreference
.WantChangesCausedByComposition()) {
544 nsIContent
*content
= GetContentBR(aElement
);
549 uint32_t postAttrChangeLength
=
550 ContentEventHandler::GetNativeTextLength(content
);
551 if (postAttrChangeLength
== mPreAttrChangeLength
) {
556 ContentEventHandler::GetFlatTextOffsetOfRange(mRootContent
, content
,
558 LINE_BREAK_TYPE_NATIVE
);
559 NS_ENSURE_SUCCESS_VOID(rv
);
561 nsContentUtils::AddScriptRunner(
562 new TextChangeEvent(this, start
, start
+ mPreAttrChangeLength
,
563 start
+ postAttrChangeLength
, causedByComposition
));
566 } // namespace mozilla