Bug 1275914 part.5 Support special selections at handling eQuerySelectedText r?smaug draft
authorMasayuki Nakano <masayuki@d-toybox.com>
Tue, 21 Jun 2016 13:13:11 +0900
changeset 380218 d91d6527a822d62a6abb24904b3f23a606ef39e2
parent 380217 75ebfce7be10045e212b8ad4c6363bd590c5f5e8
child 380219 707cdb4c9d7c07b1bc232cd07ecd1c53a8310666
push id21167
push usermasayuki@d-toybox.com
push dateTue, 21 Jun 2016 06:55:55 +0000
reviewerssmaug
bugs1275914
milestone50.0a1
Bug 1275914 part.5 Support special selections at handling eQuerySelectedText r?smaug TextComposition needs to query first IME selection. Therefore, we need to add support to query special selection range with eQuerySelectedText. First, WidgetQueryContentEvent::mInput should have mSelectionType which should be initialized with InitForQuerySelectedText() but unfortunately, there is no method for eQuerySelectedText. Therefore, this patch initializes WidgetQueryContentEvent::mInit::mSelectionType with SelectionType::eNormal in its constructor. Next, ContentEventHandler needs to support to handle eQuerySelectedText with special selection types. However, we need to create 2 paths in some cases, one is for normal selection and the other is for special selections because there are no selection ranges may be usual case for special selections but not so for normal selection. Therefore, ContentEventHandler::InitCommon() becomes a little bit more complicated. ContentEventHandler::mSelection and ContentEventHandler::mFirstSelectedRange is initialized with the specified selection type but normal selection type is also necessary to compute the selection root since the selection root is computed from the first selected range which may not be anywhere if its selection type is not normal. Finally, ContentEventHandler::OnQuerySelectedText() returns "there are no selections" as succeeded case in special selection type cases. MozReview-Commit-ID: 9WzUx8b5piw
dom/events/ContentEventHandler.cpp
dom/events/ContentEventHandler.h
dom/events/IMEContentObserver.cpp
widget/TextEvents.h
widget/nsGUIEventIPC.h
--- a/dom/events/ContentEventHandler.cpp
+++ b/dom/events/ContentEventHandler.cpp
@@ -15,17 +15,16 @@
 #include "nsContentUtils.h"
 #include "nsCopySupport.h"
 #include "nsFocusManager.h"
 #include "nsFontMetrics.h"
 #include "nsFrameSelection.h"
 #include "nsIContentIterator.h"
 #include "nsIPresShell.h"
 #include "nsISelection.h"
-#include "nsISelectionController.h"
 #include "nsIFrame.h"
 #include "nsIObjectFrame.h"
 #include "nsLayoutUtils.h"
 #include "nsPresContext.h"
 #include "nsQueryObject.h"
 #include "nsRange.h"
 #include "nsTextFragment.h"
 #include "nsTextFrame.h"
@@ -121,89 +120,172 @@ ContentEventHandler::InitBasic()
 
   // Flushing notifications can cause mPresShell to be destroyed (bug 577963).
   NS_ENSURE_TRUE(!mPresShell->IsDestroying(), NS_ERROR_FAILURE);
 
   return NS_OK;
 }
 
 nsresult
-ContentEventHandler::InitCommon()
+ContentEventHandler::InitRootContent(Selection* aNormalSelection)
 {
-  if (mSelection) {
-    return NS_OK;
-  }
-
-  nsresult rv = InitBasic();
-  NS_ENSURE_SUCCESS(rv, rv);
+  MOZ_ASSERT(aNormalSelection);
 
-  nsCOMPtr<nsISelection> sel;
-  nsCopySupport::GetSelectionForCopy(mPresShell->GetDocument(),
-                                     getter_AddRefs(sel));
-  mSelection = static_cast<Selection*>(sel.get());
-  if (NS_WARN_IF(!mSelection)) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
+  // Root content should be computed with normal selection because normal
+  // selection is typically has at least one range but the other selections
+  // not so.  If there is a range, computing its root is easy, but if
+  // there are no ranges, we need to use ancestor limit instead.
+  MOZ_ASSERT(aNormalSelection->Type() == SelectionType::eNormal);
 
-  if (!mSelection->RangeCount()) {
+  if (!aNormalSelection->RangeCount()) {
     // If there is no selection range, we should compute the selection root
     // from ancestor limiter or root content of the document.
-    rv = mSelection->GetAncestorLimiter(getter_AddRefs(mRootContent));
+    nsresult rv =
+      aNormalSelection->GetAncestorLimiter(getter_AddRefs(mRootContent));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return NS_ERROR_FAILURE;
     }
     if (!mRootContent) {
       mRootContent = mPresShell->GetDocument()->GetRootElement();
       if (NS_WARN_IF(!mRootContent)) {
         return NS_ERROR_NOT_AVAILABLE;
       }
     }
-
-    // Assume that there is selection at beginning of the root content.
-    rv = nsRange::CreateRange(mRootContent, 0, mRootContent, 0,
-                              getter_AddRefs(mFirstSelectedRange));
-    if (NS_WARN_IF(NS_FAILED(rv)) || NS_WARN_IF(!mFirstSelectedRange)) {
-      return NS_ERROR_UNEXPECTED;
-    }
     return NS_OK;
   }
 
-  mFirstSelectedRange = mSelection->GetRangeAt(0);
-  if (NS_WARN_IF(!mFirstSelectedRange)) {
+  RefPtr<nsRange> range(aNormalSelection->GetRangeAt(0));
+  if (NS_WARN_IF(!range)) {
     return NS_ERROR_UNEXPECTED;
   }
 
   // If there is a selection, we should retrieve the selection root from
   // the range since when the window is inactivated, the ancestor limiter
-  // of mSelection was cleared by blur event handler of nsEditor but the
+  // of selection was cleared by blur event handler of nsEditor but the
   // selection range still keeps storing the nodes.  If the active element of
-  // the deactive window is <input> or <textarea>, we can compute the selection
-  // root from them.
-  nsINode* startNode = mFirstSelectedRange->GetStartParent();
-  NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
-  nsINode* endNode = mFirstSelectedRange->GetEndParent();
-  NS_ENSURE_TRUE(endNode, NS_ERROR_FAILURE);
+  // the deactive window is <input> or <textarea>, we can compute the
+  // selection root from them.
+  nsINode* startNode = range->GetStartParent();
+  nsINode* endNode = range->GetEndParent();
+  if (NS_WARN_IF(!startNode) || NS_WARN_IF(!endNode)) {
+    return NS_ERROR_FAILURE;
+  }
 
   // See bug 537041 comment 5, the range could have removed node.
-  NS_ENSURE_TRUE(startNode->GetUncomposedDoc() == mPresShell->GetDocument(),
-                 NS_ERROR_NOT_AVAILABLE);
+  if (NS_WARN_IF(startNode->GetUncomposedDoc() != mPresShell->GetDocument())) {
+    return NS_ERROR_FAILURE;
+  }
+
   NS_ASSERTION(startNode->GetUncomposedDoc() == endNode->GetUncomposedDoc(),
-               "mFirstSelectedRange crosses the document boundary");
+               "firstNormalSelectionRange crosses the document boundary");
 
   mRootContent = startNode->GetSelectionRootContent(mPresShell);
-  NS_ENSURE_TRUE(mRootContent, NS_ERROR_FAILURE);
+  if (NS_WARN_IF(!mRootContent)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+ContentEventHandler::InitCommon(SelectionType aSelectionType)
+{
+  if (mSelection && mSelection->Type() == aSelectionType) {
+    return NS_OK;
+  }
+
+  mSelection = nullptr;
+  mFirstSelectedRange = nullptr;
+  mRootContent = nullptr;
+
+  nsresult rv = InitBasic();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsISelectionController> selectionController =
+    mPresShell->GetSelectionControllerForFocusedContent();
+  if (NS_WARN_IF(!selectionController)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+  nsCOMPtr<nsISelection> selection;
+  rv = selectionController->GetSelection(ToRawSelectionType(aSelectionType),
+                                         getter_AddRefs(selection));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  mSelection = static_cast<Selection*>(selection.get());
+  if (NS_WARN_IF(!mSelection)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  RefPtr<Selection> normalSelection;
+  if (mSelection->Type() == SelectionType::eNormal) {
+    normalSelection = mSelection;
+  } else {
+    nsCOMPtr<nsISelection> domSelection;
+    nsresult rv =
+      selectionController->GetSelection(
+                             nsISelectionController::SELECTION_NORMAL,
+                             getter_AddRefs(domSelection));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return NS_ERROR_UNEXPECTED;
+    }
+    normalSelection = static_cast<Selection*>(domSelection.get());
+    if (NS_WARN_IF(!normalSelection)) {
+      return NS_ERROR_NOT_AVAILABLE;
+    }
+  }
+
+  rv = InitRootContent(normalSelection);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (mSelection->RangeCount()) {
+    mFirstSelectedRange = mSelection->GetRangeAt(0);
+    if (NS_WARN_IF(!mFirstSelectedRange)) {
+      return NS_ERROR_UNEXPECTED;
+    }
+    return NS_OK;
+  }
+
+  // Even if there are no selection ranges, it's usual case if aSelectionType
+  // is a special selection.
+  if (aSelectionType != SelectionType::eNormal) {
+    MOZ_ASSERT(!mFirstSelectedRange);
+    return NS_OK;
+  }
+
+  // But otherwise, we need to assume that there is a selection range at the
+  // beginning of the root content if aSelectionType is eNormal.
+  rv = nsRange::CreateRange(mRootContent, 0, mRootContent, 0,
+                            getter_AddRefs(mFirstSelectedRange));
+  if (NS_WARN_IF(NS_FAILED(rv)) || NS_WARN_IF(!mFirstSelectedRange)) {
+    return NS_ERROR_UNEXPECTED;
+  }
   return NS_OK;
 }
 
 nsresult
 ContentEventHandler::Init(WidgetQueryContentEvent* aEvent)
 {
   NS_ASSERTION(aEvent, "aEvent must not be null");
+  MOZ_ASSERT(aEvent->mMessage == eQuerySelectedText ||
+             aEvent->mInput.mSelectionType == SelectionType::eNormal);
 
-  nsresult rv = InitCommon();
+  // Note that we should ignore WidgetQueryContentEvent::Input::mSelectionType
+  // if the event isn't eQuerySelectedText.
+  SelectionType selectionType =
+    aEvent->mMessage == eQuerySelectedText ? aEvent->mInput.mSelectionType :
+                                             SelectionType::eNormal;
+  if (NS_WARN_IF(selectionType == SelectionType::eNone)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsresult rv = InitCommon(selectionType);
   NS_ENSURE_SUCCESS(rv, rv);
 
   aEvent->mSucceeded = false;
 
   aEvent->mReply.mContentsRoot = mRootContent.get();
 
   aEvent->mReply.mHasSelection = !mSelection->IsCollapsed();
 
@@ -1117,16 +1199,25 @@ static nsresult GetFrameForTextRect(nsIN
 nsresult
 ContentEventHandler::OnQuerySelectedText(WidgetQueryContentEvent* aEvent)
 {
   nsresult rv = Init(aEvent);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
+  if (!mFirstSelectedRange) {
+    MOZ_ASSERT(aEvent->mInput.mSelectionType != SelectionType::eNormal);
+    MOZ_ASSERT(aEvent->mReply.mOffset == WidgetQueryContentEvent::NOT_FOUND);
+    MOZ_ASSERT(aEvent->mReply.mString.IsEmpty());
+    MOZ_ASSERT(!aEvent->mReply.mHasSelection);
+    aEvent->mSucceeded = true;
+    return NS_OK;
+  }
+
   nsINode* const startNode = mFirstSelectedRange->GetStartParent();
   nsINode* const endNode = mFirstSelectedRange->GetEndParent();
 
   // Make sure the selection is within the root content range.
   if (!nsContentUtils::ContentIsDescendantOf(startNode, mRootContent) ||
       !nsContentUtils::ContentIsDescendantOf(endNode, mRootContent)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
--- a/dom/events/ContentEventHandler.h
+++ b/dom/events/ContentEventHandler.h
@@ -7,16 +7,17 @@
 #ifndef mozilla_ContentEventHandler_h_
 #define mozilla_ContentEventHandler_h_
 
 #include "mozilla/EventForwards.h"
 #include "mozilla/dom/Selection.h"
 #include "nsCOMPtr.h"
 #include "nsIFrame.h"
 #include "nsINode.h"
+#include "nsISelectionController.h"
 #include "nsRange.h"
 
 class nsPresContext;
 
 struct nsRect;
 
 namespace mozilla {
 
@@ -64,25 +65,39 @@ public:
   nsresult OnQueryDOMWidgetHittest(WidgetQueryContentEvent* aEvent);
 
   // NS_SELECTION_* event
   nsresult OnSelectionEvent(WidgetSelectionEvent* aEvent);
 
 protected:
   nsPresContext* mPresContext;
   nsCOMPtr<nsIPresShell> mPresShell;
+  // mSelection is typically normal selection but if OnQuerySelectedText()
+  // is called, i.e., handling eQuerySelectedText, it's the specified selection
+  // by WidgetQueryContentEvent::mInput::mSelectionType.
   RefPtr<Selection> mSelection;
+  // mFirstSelectedRange is the first selected range of mSelection.  If
+  // mSelection is normal selection, this must not be nullptr if Init()
+  // succeed.  Otherwise, this may be nullptr if there are no selection
+  // ranges.
   RefPtr<nsRange> mFirstSelectedRange;
   nsCOMPtr<nsIContent> mRootContent;
 
   nsresult Init(WidgetQueryContentEvent* aEvent);
   nsresult Init(WidgetSelectionEvent* aEvent);
 
   nsresult InitBasic();
-  nsresult InitCommon();
+  nsresult InitCommon(SelectionType aSelectionType = SelectionType::eNormal);
+  /**
+   * InitRootContent() computes the root content of current focused editor.
+   *
+   * @param aNormalSelection    This must be a Selection instance whose type is
+   *                            SelectionType::eNormal.
+   */
+  nsresult InitRootContent(Selection* aNormalSelection);
 
 public:
   // FlatText means the text that is generated from DOM tree. The BR elements
   // are replaced to native linefeeds. Other elements are ignored.
 
   // NodePosition stores a pair of node and offset in the node.
   // When mNode is an element and mOffset is 0, the start position means after
   // the open tag of mNode.
--- a/dom/events/IMEContentObserver.cpp
+++ b/dom/events/IMEContentObserver.cpp
@@ -695,24 +695,26 @@ IMEContentObserver::ReflowInterruptible(
 {
   MaybeNotifyIMEOfPositionChange();
   return NS_OK;
 }
 
 nsresult
 IMEContentObserver::HandleQueryContentEvent(WidgetQueryContentEvent* aEvent)
 {
-  // If the instance has cache, it should use the cached selection which was
+  // If the instance has normal selection cache and the query event queries
+  // normal selection's range, it should use the cached selection which was
   // sent to the widget.  However, if this instance has already received new
   // selection change notification but hasn't updated the cache yet (i.e.,
   // not sending selection change notification to IME, don't use the cached
   // value.  Note that don't update selection cache here since if you update
   // selection cache here, IMENotificationSender won't notify IME of selection
   // change because it looks like that the selection isn't actually changed.
   if (aEvent->mMessage == eQuerySelectedText && aEvent->mUseNativeLineBreak &&
+      aEvent->mInput.mSelectionType == SelectionType::eNormal &&
       mSelectionData.IsValid() && !mNeedsToNotifyIMEOfSelectionChange) {
     aEvent->mReply.mContentsRoot = mRootContent;
     aEvent->mReply.mHasSelection = !mSelectionData.IsCollapsed();
     aEvent->mReply.mOffset = mSelectionData.mOffset;
     aEvent->mReply.mString = mSelectionData.String();
     aEvent->mReply.mWritingMode = mSelectionData.GetWritingMode();
     aEvent->mReply.mReversed = mSelectionData.mReversed;
     aEvent->mSucceeded = true;
--- a/widget/TextEvents.h
+++ b/widget/TextEvents.h
@@ -11,16 +11,17 @@
 #include "mozilla/Assertions.h"
 #include "mozilla/BasicEvents.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/EventForwards.h" // for KeyNameIndex, temporarily
 #include "mozilla/TextRange.h"
 #include "mozilla/FontRange.h"
 #include "nsCOMPtr.h"
 #include "nsIDOMKeyEvent.h"
+#include "nsISelectionController.h"
 #include "nsISelectionListener.h"
 #include "nsITransferable.h"
 #include "nsRect.h"
 #include "nsStringGlue.h"
 #include "nsTArray.h"
 #include "WritingModes.h"
 
 class nsStringHashKey;
@@ -662,16 +663,25 @@ public:
   {
     NS_ASSERTION(mMessage == eQueryTextRect,
                  "wrong initializer is called");
     mInput.mOffset = aOffset;
     mInput.mLength = aLength;
     mUseNativeLineBreak = aUseNativeLineBreak;
   }
 
+  void InitForQuerySelectedText(SelectionType aSelectionType,
+                                bool aUseNativeLineBreak = true)
+  {
+    MOZ_ASSERT(mMessage == eQuerySelectedText);
+    MOZ_ASSERT(aSelectionType != SelectionType::eNone);
+    mInput.mSelectionType = aSelectionType;
+    mUseNativeLineBreak = aUseNativeLineBreak;
+  }
+
   void InitForQueryDOMWidgetHittest(const mozilla::LayoutDeviceIntPoint& aPoint)
   {
     NS_ASSERTION(mMessage == eQueryDOMWidgetHittest,
                  "wrong initializer is called");
     mRefPoint = aPoint;
   }
 
   void RequestFontRanges()
@@ -702,30 +712,38 @@ public:
                  mMessage == eQueryTextRect,
                  "not querying selection or text rect");
     return mReply.mWritingMode;
   }
 
   bool mSucceeded;
   bool mUseNativeLineBreak;
   bool mWithFontRanges;
-  struct
+  struct Input final
   {
     uint32_t EndOffset() const
     {
       CheckedInt<uint32_t> endOffset =
         CheckedInt<uint32_t>(mOffset) + mLength;
       return NS_WARN_IF(!endOffset.isValid()) ? UINT32_MAX : endOffset.value();
     }
 
     uint32_t mOffset;
     uint32_t mLength;
+    SelectionType mSelectionType;
+
+    Input()
+      : mOffset(0)
+      , mLength(0)
+      , mSelectionType(SelectionType::eNormal)
+    {
+    }
   } mInput;
 
-  struct Reply
+  struct Reply final
   {
     void* mContentsRoot;
     uint32_t mOffset;
     // mTentativeCaretOffset is used by only eQueryCharacterAtPoint.
     // This is the offset where caret would be if user clicked at the mRefPoint.
     uint32_t mTentativeCaretOffset;
     nsString mString;
     // mRect is used by eQueryTextRect, eQueryCaretRect, eQueryCharacterAtPoint
--- a/widget/nsGUIEventIPC.h
+++ b/widget/nsGUIEventIPC.h
@@ -631,28 +631,51 @@ struct ParamTraits<mozilla::FontRange>
   {
     return ReadParam(aMsg, aIter, &aResult->mStartOffset) &&
            ReadParam(aMsg, aIter, &aResult->mFontName) &&
            ReadParam(aMsg, aIter, &aResult->mFontSize);
   }
 };
 
 template<>
+struct ParamTraits<mozilla::WidgetQueryContentEvent::Input>
+{
+  typedef mozilla::WidgetQueryContentEvent::Input paramType;
+  typedef mozilla::WidgetQueryContentEvent event;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, aParam.mOffset);
+    WriteParam(aMsg, aParam.mLength);
+    WriteParam(aMsg, mozilla::ToRawSelectionType(aParam.mSelectionType));
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+  {
+    mozilla::RawSelectionType rawSelectionType = 0;
+    bool ok = ReadParam(aMsg, aIter, &aResult->mOffset) &&
+              ReadParam(aMsg, aIter, &aResult->mLength) &&
+              ReadParam(aMsg, aIter, &rawSelectionType);
+    aResult->mSelectionType = mozilla::ToSelectionType(rawSelectionType);
+    return ok;
+  }
+};
+
+template<>
 struct ParamTraits<mozilla::WidgetQueryContentEvent>
 {
   typedef mozilla::WidgetQueryContentEvent paramType;
 
   static void Write(Message* aMsg, const paramType& aParam)
   {
     WriteParam(aMsg, static_cast<mozilla::WidgetGUIEvent>(aParam));
     WriteParam(aMsg, aParam.mSucceeded);
     WriteParam(aMsg, aParam.mUseNativeLineBreak);
     WriteParam(aMsg, aParam.mWithFontRanges);
-    WriteParam(aMsg, aParam.mInput.mOffset);
-    WriteParam(aMsg, aParam.mInput.mLength);
+    WriteParam(aMsg, aParam.mInput);
     WriteParam(aMsg, aParam.mReply.mOffset);
     WriteParam(aMsg, aParam.mReply.mTentativeCaretOffset);
     WriteParam(aMsg, aParam.mReply.mString);
     WriteParam(aMsg, aParam.mReply.mRect);
     WriteParam(aMsg, aParam.mReply.mReversed);
     WriteParam(aMsg, aParam.mReply.mHasSelection);
     WriteParam(aMsg, aParam.mReply.mWidgetIsHit);
     WriteParam(aMsg, aParam.mReply.mFontRanges);
@@ -660,18 +683,17 @@ struct ParamTraits<mozilla::WidgetQueryC
 
   static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
   {
     return ReadParam(aMsg, aIter,
                      static_cast<mozilla::WidgetGUIEvent*>(aResult)) &&
            ReadParam(aMsg, aIter, &aResult->mSucceeded) &&
            ReadParam(aMsg, aIter, &aResult->mUseNativeLineBreak) &&
            ReadParam(aMsg, aIter, &aResult->mWithFontRanges) &&
-           ReadParam(aMsg, aIter, &aResult->mInput.mOffset) &&
-           ReadParam(aMsg, aIter, &aResult->mInput.mLength) &&
+           ReadParam(aMsg, aIter, &aResult->mInput) &&
            ReadParam(aMsg, aIter, &aResult->mReply.mOffset) &&
            ReadParam(aMsg, aIter, &aResult->mReply.mTentativeCaretOffset) &&
            ReadParam(aMsg, aIter, &aResult->mReply.mString) &&
            ReadParam(aMsg, aIter, &aResult->mReply.mRect) &&
            ReadParam(aMsg, aIter, &aResult->mReply.mReversed) &&
            ReadParam(aMsg, aIter, &aResult->mReply.mHasSelection) &&
            ReadParam(aMsg, aIter, &aResult->mReply.mWidgetIsHit) &&
            ReadParam(aMsg, aIter, &aResult->mReply.mFontRanges);