Bug 1032573 part 4 - Add AnimationTimeline::ToTimelineTime helper method; r=dbaron
[gecko.git] / dom / events / IMEContentObserver.cpp
blobdfecbbc260ccbf5c24dda49b69a68540260fd752
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"
17 #include "nsIAtom.h"
18 #include "nsIContent.h"
19 #include "nsIDocument.h"
20 #include "nsIDOMDocument.h"
21 #include "nsIDOMRange.h"
22 #include "nsIFrame.h"
23 #include "nsINode.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"
33 namespace mozilla {
35 using namespace widget;
37 NS_IMPL_CYCLE_COLLECTION(IMEContentObserver,
38 mWidget, mSelection,
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)
48 NS_INTERFACE_MAP_END
50 NS_IMPL_CYCLE_COLLECTING_ADDREF(IMEContentObserver)
51 NS_IMPL_CYCLE_COLLECTING_RELEASE(IMEContentObserver)
53 IMEContentObserver::IMEContentObserver()
54 : mESM(nullptr)
58 void
59 IMEContentObserver::Init(nsIWidget* aWidget,
60 nsPresContext* aPresContext,
61 nsIContent* aContent)
63 mESM = aPresContext->EventStateManager();
64 mESM->OnStartToObserveContent(this);
66 mWidget = aWidget;
67 mEditableNode = IMEStateManager::GetRootEditableNode(aPresContext, aContent);
68 if (!mEditableNode) {
69 return;
72 nsIPresShell* presShell = aPresContext->PresShell();
74 // get selection and root content
75 nsCOMPtr<nsISelectionController> selCon;
76 if (mEditableNode->IsNodeOfType(nsINode::eCONTENT)) {
77 nsIFrame* frame =
78 static_cast<nsIContent*>(mEditableNode.get())->GetPrimaryFrame();
79 NS_ENSURE_TRUE_VOID(frame);
81 frame->GetSelectionController(aPresContext,
82 getter_AddRefs(selCon));
83 } else {
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);
100 } else {
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
105 // is not editable.
106 return;
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.
121 if (!mRootContent) {
122 return;
125 mDocShell = aPresContext->GetDocShell();
127 ObserveEditableNode();
130 void
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
152 // size changes
153 mDocShell->AddWeakScrollObserver(this);
154 mDocShell->AddWeakReflowObserver(this);
158 void
159 IMEContentObserver::Destroy()
161 // If CreateTextStateManager failed, mRootContent will be null,
162 // and we should not call NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR))
163 if (mRootContent) {
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.
172 mWidget = nullptr;
173 if (mUpdatePreference.WantSelectionChange() && mSelection) {
174 nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(mSelection));
175 if (selPrivate) {
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;
189 mDocShell = nullptr;
190 mUpdatePreference.mWantUpdates = nsIMEUpdatePreference::NOTIFY_NOTHING;
192 if (mESM) {
193 mESM->OnStopObservingContent(this);
194 mESM = nullptr;
198 void
199 IMEContentObserver::DisconnectFromEventStateManager()
201 mESM = nullptr;
204 bool
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,
215 aContent);
218 bool
219 IMEContentObserver::IsEditorHandlingEventForComposition() const
221 if (!mWidget) {
222 return false;
224 nsRefPtr<TextComposition> composition =
225 IMEStateManager::GetTextCompositionFor(mWidget);
226 if (!composition) {
227 return false;
229 return composition->IsEditorHandlingEvent();
232 nsresult
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);
243 return NS_OK;
246 // Helper class, used for selection change notification
247 class SelectionChangeEvent : public nsRunnable
249 public:
250 SelectionChangeEvent(IMEContentObserver* aDispatcher,
251 bool aCausedByComposition)
252 : mDispatcher(aDispatcher)
253 , mCausedByComposition(aCausedByComposition)
255 MOZ_ASSERT(mDispatcher);
258 NS_IMETHOD Run()
260 if (mDispatcher->GetWidget()) {
261 IMENotification notification(NOTIFY_IME_OF_SELECTION_CHANGE);
262 notification.mSelectionChangeData.mCausedByComposition =
263 mCausedByComposition;
264 mDispatcher->GetWidget()->NotifyIME(notification);
266 return NS_OK;
269 private:
270 nsRefPtr<IMEContentObserver> mDispatcher;
271 bool mCausedByComposition;
274 nsresult
275 IMEContentObserver::NotifySelectionChanged(nsIDOMDocument* aDOMDocument,
276 nsISelection* aSelection,
277 int16_t aReason)
279 bool causedByComposition = IsEditorHandlingEventForComposition();
280 if (causedByComposition &&
281 !mUpdatePreference.WantChangesCausedByComposition()) {
282 return NS_OK;
285 int32_t count = 0;
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));
292 return NS_OK;
295 // Helper class, used for position change notification
296 class PositionChangeEvent MOZ_FINAL : public nsRunnable
298 public:
299 PositionChangeEvent(IMEContentObserver* aDispatcher)
300 : mDispatcher(aDispatcher)
302 MOZ_ASSERT(mDispatcher);
305 NS_IMETHOD Run()
307 if (mDispatcher->GetWidget()) {
308 mDispatcher->GetWidget()->NotifyIME(
309 IMENotification(NOTIFY_IME_OF_POSITION_CHANGE));
311 return NS_OK;
314 private:
315 nsRefPtr<IMEContentObserver> mDispatcher;
318 void
319 IMEContentObserver::ScrollPositionChanged()
321 if (mWidget) {
322 nsContentUtils::AddScriptRunner(new PositionChangeEvent(this));
326 NS_IMETHODIMP
327 IMEContentObserver::Reflow(DOMHighResTimeStamp aStart,
328 DOMHighResTimeStamp aEnd)
330 if (mWidget) {
331 nsContentUtils::AddScriptRunner(new PositionChangeEvent(this));
333 return NS_OK;
336 NS_IMETHODIMP
337 IMEContentObserver::ReflowInterruptible(DOMHighResTimeStamp aStart,
338 DOMHighResTimeStamp aEnd)
340 if (mWidget) {
341 nsContentUtils::AddScriptRunner(new PositionChangeEvent(this));
343 return NS_OK;
346 // Helper class, used for text change notification
347 class TextChangeEvent : public nsRunnable
349 public:
350 TextChangeEvent(IMEContentObserver* aDispatcher,
351 uint32_t aStart, uint32_t aOldEnd, uint32_t aNewEnd,
352 bool aCausedByComposition)
353 : mDispatcher(aDispatcher)
354 , mStart(aStart)
355 , mOldEnd(aOldEnd)
356 , mNewEnd(aNewEnd)
357 , mCausedByComposition(aCausedByComposition)
359 MOZ_ASSERT(mDispatcher);
362 NS_IMETHOD Run()
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);
372 return NS_OK;
375 private:
376 nsRefPtr<IMEContentObserver> mDispatcher;
377 uint32_t mStart, mOldEnd, mNewEnd;
378 bool mCausedByComposition;
381 void
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()) {
392 return;
395 uint32_t offset = 0;
396 // get offsets of change and fire notification
397 nsresult rv =
398 ContentEventHandler::GetFlatTextOffsetOfRange(mRootContent, aContent,
399 aInfo->mChangeStart,
400 &offset,
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));
411 void
412 IMEContentObserver::NotifyContentAdded(nsINode* aContainer,
413 int32_t aStartIndex,
414 int32_t aEndIndex)
416 bool causedByComposition = IsEditorHandlingEventForComposition();
417 if (causedByComposition &&
418 !mUpdatePreference.WantChangesCausedByComposition()) {
419 return;
422 uint32_t offset = 0;
423 nsresult rv =
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);
437 if (!addingLength) {
438 return;
441 nsContentUtils::AddScriptRunner(
442 new TextChangeEvent(this, offset, offset, offset + addingLength,
443 causedByComposition));
446 void
447 IMEContentObserver::ContentAppended(nsIDocument* aDocument,
448 nsIContent* aContainer,
449 nsIContent* aFirstNewContent,
450 int32_t aNewIndexInContainer)
452 NotifyContentAdded(aContainer, aNewIndexInContainer,
453 aContainer->GetChildCount());
456 void
457 IMEContentObserver::ContentInserted(nsIDocument* aDocument,
458 nsIContent* aContainer,
459 nsIContent* aChild,
460 int32_t aIndexInContainer)
462 NotifyContentAdded(NODE_FROM(aContainer, aDocument),
463 aIndexInContainer, aIndexInContainer + 1);
466 void
467 IMEContentObserver::ContentRemoved(nsIDocument* aDocument,
468 nsIContent* aContainer,
469 nsIContent* aChild,
470 int32_t aIndexInContainer,
471 nsIContent* aPreviousSibling)
473 bool causedByComposition = IsEditorHandlingEventForComposition();
474 if (causedByComposition &&
475 !mUpdatePreference.WantChangesCausedByComposition()) {
476 return;
479 uint32_t offset = 0;
480 nsresult rv =
481 ContentEventHandler::GetFlatTextOffsetOfRange(mRootContent,
482 NODE_FROM(aContainer,
483 aDocument),
484 aIndexInContainer, &offset,
485 LINE_BREAK_TYPE_NATIVE);
486 NS_ENSURE_SUCCESS_VOID(rv);
488 // get offset at the end of the deleted node
489 int32_t nodeLength =
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);
500 if (!textLength) {
501 return;
504 nsContentUtils::AddScriptRunner(
505 new TextChangeEvent(this, offset, offset + textLength, offset,
506 causedByComposition));
509 static nsIContent*
510 GetContentBR(dom::Element* aElement)
512 if (!aElement->IsNodeOfType(nsINode::eCONTENT)) {
513 return nullptr;
515 nsIContent* content = static_cast<nsIContent*>(aElement);
516 return content->IsHTML(nsGkAtoms::br) ? content : nullptr;
519 void
520 IMEContentObserver::AttributeWillChange(nsIDocument* aDocument,
521 dom::Element* aElement,
522 int32_t aNameSpaceID,
523 nsIAtom* aAttribute,
524 int32_t aModType)
526 nsIContent *content = GetContentBR(aElement);
527 mPreAttrChangeLength = content ?
528 ContentEventHandler::GetNativeTextLength(content) : 0;
531 void
532 IMEContentObserver::AttributeChanged(nsIDocument* aDocument,
533 dom::Element* aElement,
534 int32_t aNameSpaceID,
535 nsIAtom* aAttribute,
536 int32_t aModType)
538 bool causedByComposition = IsEditorHandlingEventForComposition();
539 if (causedByComposition &&
540 !mUpdatePreference.WantChangesCausedByComposition()) {
541 return;
544 nsIContent *content = GetContentBR(aElement);
545 if (!content) {
546 return;
549 uint32_t postAttrChangeLength =
550 ContentEventHandler::GetNativeTextLength(content);
551 if (postAttrChangeLength == mPreAttrChangeLength) {
552 return;
554 uint32_t start;
555 nsresult rv =
556 ContentEventHandler::GetFlatTextOffsetOfRange(mRootContent, content,
557 0, &start,
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