dom/events/IMEContentObserver.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     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)
    55 {
    56 }
    58 void
    59 IMEContentObserver::Init(nsIWidget* aWidget,
    60                          nsPresContext* aPresContext,
    61                          nsIContent* aContent)
    62 {
    63   mESM = aPresContext->EventStateManager();
    64   mESM->OnStartToObserveContent(this);
    66   mWidget = aWidget;
    67   mEditableNode = IMEStateManager::GetRootEditableNode(aPresContext, aContent);
    68   if (!mEditableNode) {
    69     return;
    70   }
    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);
    86   }
    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);
   102   }
   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;
   107   }
   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();
   114   }
   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;
   123   }
   125   mDocShell = aPresContext->GetDocShell();
   127   ObserveEditableNode();
   128 }
   130 void
   131 IMEContentObserver::ObserveEditableNode()
   132 {
   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);
   143   }
   145   if (mUpdatePreference.WantTextChange()) {
   146     // add text change observer
   147     mRootContent->AddMutationObserver(this);
   148   }
   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);
   155   }
   156 }
   158 void
   159 IMEContentObserver::Destroy()
   160 {
   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();
   168     }
   169     mWidget->NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR));
   170   }
   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);
   177     }
   178   }
   179   mSelection = nullptr;
   180   if (mUpdatePreference.WantTextChange() && mRootContent) {
   181     mRootContent->RemoveMutationObserver(this);
   182   }
   183   if (mUpdatePreference.WantPositionChanged() && mDocShell) {
   184     mDocShell->RemoveWeakScrollObserver(this);
   185     mDocShell->RemoveWeakReflowObserver(this);
   186   }
   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;
   195   }
   196 }
   198 void
   199 IMEContentObserver::DisconnectFromEventStateManager()
   200 {
   201   mESM = nullptr;
   202 }
   204 bool
   205 IMEContentObserver::IsManaging(nsPresContext* aPresContext,
   206                                nsIContent* aContent)
   207 {
   208   if (!mSelection || !mRootContent || !mEditableNode) {
   209     return false; // failed to initialize.
   210   }
   211   if (!mRootContent->IsInDoc()) {
   212     return false; // the focused editor has already been reframed.
   213   }
   214   return mEditableNode == IMEStateManager::GetRootEditableNode(aPresContext,
   215                                                                aContent);
   216 }
   218 bool
   219 IMEContentObserver::IsEditorHandlingEventForComposition() const
   220 {
   221   if (!mWidget) {
   222     return false;
   223   }
   224   nsRefPtr<TextComposition> composition =
   225     IMEStateManager::GetTextCompositionFor(mWidget);
   226   if (!composition) {
   227     return false;
   228   }
   229   return composition->IsEditorHandlingEvent();
   230 }
   232 nsresult
   233 IMEContentObserver::GetSelectionAndRoot(nsISelection** aSelection,
   234                                         nsIContent** aRootContent) const
   235 {
   236   if (!mEditableNode || !mSelection) {
   237     return NS_ERROR_NOT_AVAILABLE;
   238   }
   240   NS_ASSERTION(mSelection && mRootContent, "uninitialized content observer");
   241   NS_ADDREF(*aSelection = mSelection);
   242   NS_ADDREF(*aRootContent = mRootContent);
   243   return NS_OK;
   244 }
   246 // Helper class, used for selection change notification
   247 class SelectionChangeEvent : public nsRunnable
   248 {
   249 public:
   250   SelectionChangeEvent(IMEContentObserver* aDispatcher,
   251                        bool aCausedByComposition)
   252     : mDispatcher(aDispatcher)
   253     , mCausedByComposition(aCausedByComposition)
   254   {
   255     MOZ_ASSERT(mDispatcher);
   256   }
   258   NS_IMETHOD Run()
   259   {
   260     if (mDispatcher->GetWidget()) {
   261       IMENotification notification(NOTIFY_IME_OF_SELECTION_CHANGE);
   262       notification.mSelectionChangeData.mCausedByComposition =
   263          mCausedByComposition;
   264       mDispatcher->GetWidget()->NotifyIME(notification);
   265     }
   266     return NS_OK;
   267   }
   269 private:
   270   nsRefPtr<IMEContentObserver> mDispatcher;
   271   bool mCausedByComposition;
   272 };
   274 nsresult
   275 IMEContentObserver::NotifySelectionChanged(nsIDOMDocument* aDOMDocument,
   276                                            nsISelection* aSelection,
   277                                            int16_t aReason)
   278 {
   279   bool causedByComposition = IsEditorHandlingEventForComposition();
   280   if (causedByComposition &&
   281       !mUpdatePreference.WantChangesCausedByComposition()) {
   282     return NS_OK;
   283   }
   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));
   291   }
   292   return NS_OK;
   293 }
   295 // Helper class, used for position change notification
   296 class PositionChangeEvent MOZ_FINAL : public nsRunnable
   297 {
   298 public:
   299   PositionChangeEvent(IMEContentObserver* aDispatcher)
   300     : mDispatcher(aDispatcher)
   301   {
   302     MOZ_ASSERT(mDispatcher);
   303   }
   305   NS_IMETHOD Run()
   306   {
   307     if (mDispatcher->GetWidget()) {
   308       mDispatcher->GetWidget()->NotifyIME(
   309         IMENotification(NOTIFY_IME_OF_POSITION_CHANGE));
   310     }
   311     return NS_OK;
   312   }
   314 private:
   315   nsRefPtr<IMEContentObserver> mDispatcher;
   316 };
   318 void
   319 IMEContentObserver::ScrollPositionChanged()
   320 {
   321   if (mWidget) {
   322     nsContentUtils::AddScriptRunner(new PositionChangeEvent(this));
   323   }
   324 }
   326 NS_IMETHODIMP
   327 IMEContentObserver::Reflow(DOMHighResTimeStamp aStart,
   328                            DOMHighResTimeStamp aEnd)
   329 {
   330   if (mWidget) {
   331     nsContentUtils::AddScriptRunner(new PositionChangeEvent(this));
   332   }
   333   return NS_OK;
   334 }
   336 NS_IMETHODIMP
   337 IMEContentObserver::ReflowInterruptible(DOMHighResTimeStamp aStart,
   338                                         DOMHighResTimeStamp aEnd)
   339 {
   340   if (mWidget) {
   341     nsContentUtils::AddScriptRunner(new PositionChangeEvent(this));
   342   }
   343   return NS_OK;
   344 }
   346 // Helper class, used for text change notification
   347 class TextChangeEvent : public nsRunnable
   348 {
   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)
   358   {
   359     MOZ_ASSERT(mDispatcher);
   360   }
   362   NS_IMETHOD Run()
   363   {
   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);
   371     }
   372     return NS_OK;
   373   }
   375 private:
   376   nsRefPtr<IMEContentObserver> mDispatcher;
   377   uint32_t mStart, mOldEnd, mNewEnd;
   378   bool mCausedByComposition;
   379 };
   381 void
   382 IMEContentObserver::CharacterDataChanged(nsIDocument* aDocument,
   383                                          nsIContent* aContent,
   384                                          CharacterDataChangeInfo* aInfo)
   385 {
   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;
   393   }
   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));
   409 }
   411 void
   412 IMEContentObserver::NotifyContentAdded(nsINode* aContainer,
   413                                        int32_t aStartIndex,
   414                                        int32_t aEndIndex)
   415 {
   416   bool causedByComposition = IsEditorHandlingEventForComposition();
   417   if (causedByComposition &&
   418       !mUpdatePreference.WantChangesCausedByComposition()) {
   419     return;
   420   }
   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;
   439   }
   441   nsContentUtils::AddScriptRunner(
   442     new TextChangeEvent(this, offset, offset, offset + addingLength,
   443                         causedByComposition));
   444 }
   446 void
   447 IMEContentObserver::ContentAppended(nsIDocument* aDocument,
   448                                     nsIContent* aContainer,
   449                                     nsIContent* aFirstNewContent,
   450                                     int32_t aNewIndexInContainer)
   451 {
   452   NotifyContentAdded(aContainer, aNewIndexInContainer,
   453                      aContainer->GetChildCount());
   454 }
   456 void
   457 IMEContentObserver::ContentInserted(nsIDocument* aDocument,
   458                                     nsIContent* aContainer,
   459                                     nsIContent* aChild,
   460                                     int32_t aIndexInContainer)
   461 {
   462   NotifyContentAdded(NODE_FROM(aContainer, aDocument),
   463                      aIndexInContainer, aIndexInContainer + 1);
   464 }
   466 void
   467 IMEContentObserver::ContentRemoved(nsIDocument* aDocument,
   468                                    nsIContent* aContainer,
   469                                    nsIContent* aChild,
   470                                    int32_t aIndexInContainer,
   471                                    nsIContent* aPreviousSibling)
   472 {
   473   bool causedByComposition = IsEditorHandlingEventForComposition();
   474   if (causedByComposition &&
   475       !mUpdatePreference.WantChangesCausedByComposition()) {
   476     return;
   477   }
   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;
   502   }
   504   nsContentUtils::AddScriptRunner(
   505     new TextChangeEvent(this, offset, offset + textLength, offset,
   506                         causedByComposition));
   507 }
   509 static nsIContent*
   510 GetContentBR(dom::Element* aElement)
   511 {
   512   if (!aElement->IsNodeOfType(nsINode::eCONTENT)) {
   513     return nullptr;
   514   }
   515   nsIContent* content = static_cast<nsIContent*>(aElement);
   516   return content->IsHTML(nsGkAtoms::br) ? content : nullptr;
   517 }
   519 void
   520 IMEContentObserver::AttributeWillChange(nsIDocument* aDocument,
   521                                         dom::Element* aElement,
   522                                         int32_t aNameSpaceID,
   523                                         nsIAtom* aAttribute,
   524                                         int32_t aModType)
   525 {
   526   nsIContent *content = GetContentBR(aElement);
   527   mPreAttrChangeLength = content ?
   528     ContentEventHandler::GetNativeTextLength(content) : 0;
   529 }
   531 void
   532 IMEContentObserver::AttributeChanged(nsIDocument* aDocument,
   533                                      dom::Element* aElement,
   534                                      int32_t aNameSpaceID,
   535                                      nsIAtom* aAttribute,
   536                                      int32_t aModType)
   537 {
   538   bool causedByComposition = IsEditorHandlingEventForComposition();
   539   if (causedByComposition &&
   540       !mUpdatePreference.WantChangesCausedByComposition()) {
   541     return;
   542   }
   544   nsIContent *content = GetContentBR(aElement);
   545   if (!content) {
   546     return;
   547   }
   549   uint32_t postAttrChangeLength =
   550     ContentEventHandler::GetNativeTextLength(content);
   551   if (postAttrChangeLength == mPreAttrChangeLength) {
   552     return;
   553   }
   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));
   564 }
   566 } // namespace mozilla

mercurial