Bug 1062735 - Part 4: Support non-editable fields for selection carets. r=roc
authorMorris Tseng <mtseng@mozilla.com>
Thu, 16 Oct 2014 23:17:00 +0200
changeset 210997 6a1757feb3ff387a3c852ae7a0106902fd0f1c55
parent 210996 0d49c600a6d592795ccf3bb111e1106d6d2a5fe9
child 210998 4e6fd04ea4bae06a9e9c1bbf5824625796c16790
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersroc
bugs1062735
milestone36.0a1
Bug 1062735 - Part 4: Support non-editable fields for selection carets. r=roc
layout/base/SelectionCarets.cpp
layout/base/SelectionCarets.h
--- a/layout/base/SelectionCarets.cpp
+++ b/layout/base/SelectionCarets.cpp
@@ -8,16 +8,17 @@
 
 #include "gfxPrefs.h"
 #include "nsBidiPresUtils.h"
 #include "nsCanvasFrame.h"
 #include "nsCaret.h"
 #include "nsContentUtils.h"
 #include "nsDebug.h"
 #include "nsDOMTokenList.h"
+#include "nsFocusManager.h"
 #include "nsFrame.h"
 #include "nsIDocument.h"
 #include "nsIDocShell.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMNodeFilter.h"
 #include "nsIPresShell.h"
 #include "nsPresContext.h"
 #include "nsRect.h"
@@ -325,17 +326,16 @@ FindFirstNodeWithFrame(nsIDocument* aDoc
 
   nsCOMPtr<nsINode> startNode =
     do_QueryInterface(aBackward ? aRange->GetEndParent() : aRange->GetStartParent());
   nsCOMPtr<nsINode> endNode =
     do_QueryInterface(aBackward ? aRange->GetStartParent() : aRange->GetEndParent());
   int32_t offset = aBackward ? aRange->EndOffset() : aRange->StartOffset();
 
   nsCOMPtr<nsIContent> startContent = do_QueryInterface(startNode);
-  nsCOMPtr<nsIContent> endContent = do_QueryInterface(endNode);
   CaretAssociationHint hintStart =
     aBackward ? CARET_ASSOCIATE_BEFORE : CARET_ASSOCIATE_AFTER;
   nsIFrame* startFrame = aFrameSelection->GetFrameForNodeOffset(startContent,
                                                                 offset,
                                                                 hintStart,
                                                                 &aOutOffset);
 
   if (startFrame) {
@@ -355,36 +355,40 @@ FindFirstNodeWithFrame(nsIDocument* aDoc
 
   startFrame = startContent ? startContent->GetPrimaryFrame() : nullptr;
   while (!startFrame && startNode != endNode) {
     if (aBackward) {
       startNode = walker->PreviousNode(err);
     } else {
       startNode = walker->NextNode(err);
     }
+
+    if (!startNode) {
+      break;
+    }
+
     startContent = do_QueryInterface(startNode);
     startFrame = startContent ? startContent->GetPrimaryFrame() : nullptr;
   }
   return startFrame;
 }
 
 void
 SelectionCarets::UpdateSelectionCarets()
 {
   if (!mPresShell) {
     return;
   }
 
-  nsISelection* caretSelection = GetSelection();
-  if (!caretSelection) {
+  nsRefPtr<dom::Selection> selection = GetSelection();
+  if (!selection) {
     SetVisibility(false);
     return;
   }
 
-  nsRefPtr<dom::Selection> selection = static_cast<dom::Selection*>(caretSelection);
   if (selection->GetRangeCount() <= 0) {
     SetVisibility(false);
     return;
   }
 
   nsRefPtr<nsRange> range = selection->GetRangeAt(0);
   if (range->Collapsed()) {
     SetVisibility(false);
@@ -399,53 +403,39 @@ SelectionCarets::UpdateSelectionCarets()
   nsIFrame* canvasFrame = mPresShell->GetCanvasFrame();
   nsIFrame* rootFrame = mPresShell->GetRootFrame();
 
   if (!canvasFrame || !rootFrame) {
     SetVisibility(false);
     return;
   }
 
-  // Check if caret inside the scroll frame's boundary
-  nsIFrame* caretFocusFrame = GetCaretFocusFrame();
-  if (!caretFocusFrame) {
-    SetVisibility(false);
-    return;
-  }
-  nsIContent *editableAncestor = caretFocusFrame->GetContent()->GetEditingHost();
-
-  if (!editableAncestor) {
-    SetVisibility(false);
-    return;
-  }
-
-  nsRect resultRect;
-  for (nsIFrame* frame = editableAncestor->GetPrimaryFrame();
-      frame != nullptr;
-      frame = frame->GetNextContinuation()) {
-    nsRect rect = frame->GetRectRelativeToSelf();
-    nsLayoutUtils::TransformRect(frame, rootFrame, rect);
-    resultRect = resultRect.Union(rect);
-  }
-
   // Check start and end frame is rtl or ltr text
-  nsRefPtr<nsFrameSelection> fs = caretFocusFrame->GetFrameSelection();
+  nsRefPtr<nsFrameSelection> fs = GetFrameSelection();
   int32_t startOffset;
   nsIFrame* startFrame = FindFirstNodeWithFrame(mPresShell->GetDocument(),
                                                 range, fs, false, startOffset);
 
   int32_t endOffset;
   nsIFrame* endFrame = FindFirstNodeWithFrame(mPresShell->GetDocument(),
                                               range, fs, true, endOffset);
 
   if (!startFrame || !endFrame) {
     SetVisibility(false);
     return;
   }
 
+  // If frame isn't editable and we don't support non-editable fields, bail
+  // out.
+  if (!kSupportNonEditableFields &&
+      (!startFrame->GetContent()->IsEditable() ||
+       !endFrame->GetContent()->IsEditable())) {
+    return;
+  }
+
   // Check if startFrame is after endFrame.
   if (nsLayoutUtils::CompareTreePosition(startFrame, endFrame) > 0) {
     SetVisibility(false);
     return;
   }
 
   bool startFrameIsRTL = IsRightToLeft(startFrame);
   bool endFrameIsRTL = IsRightToLeft(endFrame);
@@ -453,22 +443,38 @@ SelectionCarets::UpdateSelectionCarets()
   // If start frame is LTR, then place start caret in first rect's leftmost
   // otherwise put it to first rect's rightmost.
   ReduceRectToVerticalEdge(collector.mFirstRect, startFrameIsRTL);
 
   // Contrary to start frame, if end frame is LTR, put end caret to last
   // rect's rightmost position, otherwise, put it to last rect's leftmost.
   ReduceRectToVerticalEdge(collector.mLastRect, !endFrameIsRTL);
 
-  SetStartFrameVisibility(resultRect.Intersects(collector.mFirstRect));
-  SetEndFrameVisibility(resultRect.Intersects(collector.mLastRect));
-
   nsLayoutUtils::TransformRect(rootFrame, canvasFrame, collector.mFirstRect);
   nsLayoutUtils::TransformRect(rootFrame, canvasFrame, collector.mLastRect);
 
+  nsAutoTArray<nsIFrame*, 16> hitFramesInFirstRect;
+  nsLayoutUtils::GetFramesForArea(canvasFrame,
+    collector.mFirstRect,
+    hitFramesInFirstRect,
+    nsLayoutUtils::IGNORE_PAINT_SUPPRESSION |
+      nsLayoutUtils::IGNORE_CROSS_DOC |
+      nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME);
+
+  nsAutoTArray<nsIFrame*, 16> hitFramesInLastRect;
+  nsLayoutUtils::GetFramesForArea(canvasFrame,
+    collector.mLastRect,
+    hitFramesInLastRect,
+    nsLayoutUtils::IGNORE_PAINT_SUPPRESSION |
+      nsLayoutUtils::IGNORE_CROSS_DOC |
+      nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME);
+
+  SetStartFrameVisibility(hitFramesInFirstRect.Contains(startFrame));
+  SetEndFrameVisibility(hitFramesInLastRect.Contains(endFrame));
+
   SetStartFramePos(collector.mFirstRect.BottomLeft());
   SetEndFramePos(collector.mLastRect.BottomRight());
   SetVisibility(true);
 
   // If range select only one character, append tilt class name to it.
   bool isTilt = false;
   if (startFrame && endFrame) {
     // In this case <textarea>abc</textarea> and we select 'c' character,
@@ -509,53 +515,69 @@ SelectionCarets::UpdateSelectionCarets()
   SetCaretDirection(mPresShell->GetSelectionCaretsStartElement(), startFrameIsRTL);
   SetCaretDirection(mPresShell->GetSelectionCaretsEndElement(), !endFrameIsRTL);
   SetTilted(isTilt);
 }
 
 nsresult
 SelectionCarets::SelectWord()
 {
-  // If caret isn't visible, the word is not selectable
-  if (!GetCaretVisible()) {
-    return NS_OK;
-  }
-
   if (!mPresShell) {
     return NS_OK;
   }
 
   nsIFrame* canvasFrame = mPresShell->GetCanvasFrame();
   if (!canvasFrame) {
     return NS_OK;
   }
 
   // Find content offsets for mouse down point
   nsIFrame *ptFrame = nsLayoutUtils::GetFrameForPoint(canvasFrame, mDownPoint,
     nsLayoutUtils::IGNORE_PAINT_SUPPRESSION | nsLayoutUtils::IGNORE_CROSS_DOC);
   if (!ptFrame) {
     return NS_OK;
   }
 
+  // If frame isn't editable and we don't support non-editable fields, bail
+  // out.
+  if (!kSupportNonEditableFields && !ptFrame->GetContent()->IsEditable()) {
+    return NS_OK;
+  }
+
   nsPoint ptInFrame = mDownPoint;
   nsLayoutUtils::TransformPoint(canvasFrame, ptFrame, ptInFrame);
 
-  nsIFrame* caretFocusFrame = GetCaretFocusFrame();
-  if (!caretFocusFrame) {
-    return NS_OK;
+  // If target frame is editable, we should move focus to targe frame. If
+  // target frame isn't editable and our focus content is editable, we should
+  // clear focus.
+  nsFocusManager* fm = nsFocusManager::GetFocusManager();
+  nsIContent* editingHost = ptFrame->GetContent()->GetEditingHost();
+  if (editingHost) {
+    nsCOMPtr<nsIDOMElement> elt = do_QueryInterface(editingHost->GetParent());
+    if (elt) {
+      fm->SetFocus(elt, 0);
+    }
+  } else {
+    nsIContent* focusedContent = GetFocusedContent();
+    if (focusedContent && focusedContent->GetTextEditorRootContent()) {
+      nsIDOMWindow* win = mPresShell->GetDocument()->GetWindow();
+      if (win) {
+        fm->ClearFocus(win);
+      }
+    }
   }
 
   SetSelectionDragState(true);
   nsFrame* frame = static_cast<nsFrame*>(ptFrame);
   nsresult rs = frame->SelectByTypeAtPoint(mPresShell->GetPresContext(), ptInFrame,
                                            eSelectWord, eSelectWord, 0);
   SetSelectionDragState(false);
 
   // Clear maintain selection otherwise we cannot select less than a word
-  nsRefPtr<nsFrameSelection> fs = caretFocusFrame->GetFrameSelection();
+  nsRefPtr<nsFrameSelection> fs = GetFrameSelection();
   fs->MaintainSelection();
   return rs;
 }
 
 /*
  * If we're dragging start caret, we do not want to drag over previous
  * character of end caret. Same as end caret. So we check if content offset
  * exceed previous/next character of end/start caret base on aDragMode.
@@ -635,53 +657,59 @@ SelectionCarets::DragSelection(const nsP
 
   // Find out which content we point to
   nsIFrame *ptFrame = nsLayoutUtils::GetFrameForPoint(canvasFrame, movePoint,
     nsLayoutUtils::IGNORE_PAINT_SUPPRESSION | nsLayoutUtils::IGNORE_CROSS_DOC);
   if (!ptFrame) {
     return nsEventStatus_eConsumeNoDefault;
   }
 
-  nsIFrame* caretFocusFrame = GetCaretFocusFrame();
-  if (!caretFocusFrame) {
-    return nsEventStatus_eConsumeNoDefault;
-  }
-
-  nsRefPtr<nsFrameSelection> fs = caretFocusFrame->GetFrameSelection();
+  nsRefPtr<nsFrameSelection> fs = GetFrameSelection();
 
   nsresult result;
   nsIFrame *newFrame = nullptr;
   nsPoint newPoint;
   nsPoint ptInFrame = movePoint;
   nsLayoutUtils::TransformPoint(canvasFrame, ptFrame, ptInFrame);
   result = fs->ConstrainFrameAndPointToAnchorSubtree(ptFrame, ptInFrame, &newFrame, newPoint);
   if (NS_FAILED(result) || !newFrame) {
     return nsEventStatus_eConsumeNoDefault;
   }
 
+  bool selectable;
+  newFrame->IsSelectable(&selectable, nullptr);
+  if (!selectable) {
+    return nsEventStatus_eConsumeNoDefault;
+  }
+
   nsFrame::ContentOffsets offsets =
     newFrame->GetContentOffsetsFromPoint(newPoint);
   if (!offsets.content) {
     return nsEventStatus_eConsumeNoDefault;
   }
 
-  nsISelection* caretSelection = GetSelection();
-  nsRefPtr<dom::Selection> selection = static_cast<dom::Selection*>(caretSelection);
+  nsRefPtr<dom::Selection> selection = GetSelection();
   if (selection->GetRangeCount() <= 0) {
     return nsEventStatus_eConsumeNoDefault;
   }
 
   nsRefPtr<nsRange> range = selection->GetRangeAt(0);
   if (!CompareRangeWithContentOffset(range, fs, offsets, mDragMode)) {
     return nsEventStatus_eConsumeNoDefault;
   }
 
+  nsIFrame* anchorFrame;
+  selection->GetPrimaryFrameForAnchorNode(&anchorFrame);
+  if (!anchorFrame) {
+    return nsEventStatus_eConsumeNoDefault;
+  }
+
   // Move caret postion.
   nsIFrame *scrollable =
-    nsLayoutUtils::GetClosestFrameOfType(caretFocusFrame, nsGkAtoms::scrollFrame);
+    nsLayoutUtils::GetClosestFrameOfType(anchorFrame, nsGkAtoms::scrollFrame);
   nsWeakFrame weakScrollable = scrollable;
   fs->HandleClick(offsets.content, offsets.StartOffset(),
                   offsets.EndOffset(),
                   true,
                   false,
                   offsets.associate);
   if (!weakScrollable.IsAlive()) {
     return nsEventStatus_eConsumeNoDefault;
@@ -696,28 +724,28 @@ SelectionCarets::DragSelection(const nsP
   UpdateSelectionCarets();
   return nsEventStatus_eConsumeNoDefault;
 }
 
 nscoord
 SelectionCarets::GetCaretYCenterPosition()
 {
   nsIFrame* canvasFrame = mPresShell->GetCanvasFrame();
-  nsIFrame* caretFocusFrame = GetCaretFocusFrame();
 
-  if (!canvasFrame || !caretFocusFrame) {
+  if (!canvasFrame) {
     return 0;
   }
-  nsISelection* caretSelection = GetSelection();
-  nsRefPtr<dom::Selection> selection = static_cast<dom::Selection*>(caretSelection);
+
+  nsRefPtr<dom::Selection> selection = GetSelection();
   if (selection->GetRangeCount() <= 0) {
     return 0;
   }
+
   nsRefPtr<nsRange> range = selection->GetRangeAt(0);
-  nsRefPtr<nsFrameSelection> fs = caretFocusFrame->GetFrameSelection();
+  nsRefPtr<nsFrameSelection> fs = GetFrameSelection();
 
   MOZ_ASSERT(mDragMode != NONE);
   nsCOMPtr<nsIContent> node;
   uint32_t nodeOffset;
   if (mDragMode == START_FRAME) {
     node = do_QueryInterface(range->GetStartParent());
     nodeOffset = range->StartOffset();
   } else {
@@ -737,30 +765,24 @@ SelectionCarets::GetCaretYCenterPosition
   nsRect frameRect = theFrame->GetRectRelativeToSelf();
   nsLayoutUtils::TransformRect(theFrame, canvasFrame, frameRect);
   return frameRect.Center().y;
 }
 
 void
 SelectionCarets::SetSelectionDragState(bool aState)
 {
-  nsIFrame* caretFocusFrame = GetCaretFocusFrame();
-  if (!caretFocusFrame) {
-    return;
-  }
-
-  nsRefPtr<nsFrameSelection> fs = caretFocusFrame->GetFrameSelection();
+  nsRefPtr<nsFrameSelection> fs = GetFrameSelection();
   fs->SetDragState(aState);
 }
 
 void
 SelectionCarets::SetSelectionDirection(bool aForward)
 {
-  nsISelection* caretSelection = GetSelection();
-  nsRefPtr<dom::Selection> selection = static_cast<dom::Selection*>(caretSelection);
+  nsRefPtr<dom::Selection> selection = GetSelection();
   selection->SetDirection(aForward ? eDirNext : eDirPrevious);
 }
 
 static void
 SetFramePos(dom::Element* aElement, const nsPoint& aPosition)
 {
   if (!aElement) {
     return;
@@ -815,48 +837,50 @@ SelectionCarets::GetStartFrameRect()
 nsRect
 SelectionCarets::GetEndFrameRect()
 {
   dom::Element* element = mPresShell->GetSelectionCaretsEndElement();
   nsIFrame* canvasFrame = mPresShell->GetCanvasFrame();
   return nsLayoutUtils::GetRectRelativeToFrame(element, canvasFrame);
 }
 
-nsIFrame*
-SelectionCarets::GetCaretFocusFrame()
+nsIContent*
+SelectionCarets::GetFocusedContent()
 {
-  nsRefPtr<nsCaret> caret = mPresShell->GetCaret();
-  if (!caret) {
-    return nullptr;
+  nsFocusManager* fm = nsFocusManager::GetFocusManager();
+  if (fm) {
+    return fm->GetFocusedContent();
   }
 
-  nsRect focusRect;
-  return caret->GetGeometry(&focusRect);
+  return nullptr;
 }
 
-bool
-SelectionCarets::GetCaretVisible()
+Selection*
+SelectionCarets::GetSelection()
 {
-  if (!mPresShell) {
-    return false;
+  nsRefPtr<nsFrameSelection> fs = GetFrameSelection();
+  if (fs) {
+    return fs->GetSelection(nsISelectionController::SELECTION_NORMAL);
   }
-
-  nsRefPtr<nsCaret> caret = mPresShell->GetCaret();
-  if (!caret) {
-    return false;
-  }
-
-  return caret->IsVisible();
+  return nullptr;
 }
 
-nsISelection*
-SelectionCarets::GetSelection()
+already_AddRefed<nsFrameSelection>
+SelectionCarets::GetFrameSelection()
 {
-  nsRefPtr<nsCaret> caret = mPresShell->GetCaret();
-  return caret->GetSelection();
+  nsIContent* focusNode = GetFocusedContent();
+  if (focusNode) {
+    nsIFrame* focusFrame = focusNode->GetPrimaryFrame();
+    if (!focusFrame) {
+      return nullptr;
+    }
+    return focusFrame->GetFrameSelection();
+  } else {
+    return mPresShell->FrameSelection();
+  }
 }
 
 nsresult
 SelectionCarets::NotifySelectionChanged(nsIDOMDocument* aDoc,
                                        nsISelection* aSel,
                                        int16_t aReason)
 {
   bool isCollapsed;
--- a/layout/base/SelectionCarets.h
+++ b/layout/base/SelectionCarets.h
@@ -10,25 +10,31 @@
 #include "nsIScrollObserver.h"
 #include "nsISelectionListener.h"
 #include "nsWeakPtr.h"
 #include "nsWeakReference.h"
 #include "Units.h"
 #include "mozilla/EventForwards.h"
 
 class nsCanvasFrame;
+class nsFrameSelection;
+class nsIContent;
 class nsIDocument;
 class nsIFrame;
 class nsIPresShell;
 class nsITimer;
 class nsIWidget;
 class nsPresContext;
 
 namespace mozilla {
 
+namespace dom {
+class Selection;
+}
+
 /**
  * The SelectionCarets draw a pair of carets when the selection is not
  * collapsed, one at each end of the selection.
  * SelectionCarets also handle visibility, dragging caret and selecting word
  * when long tap event fired.
  *
  * The DOM structure is 2 div elements for showing start and end caret.
  *
@@ -177,19 +183,19 @@ private:
   void SetEndFrameVisibility(bool aVisible);
 
   /**
    * Set tilt class name to start and end frame of selection caret.
    */
   void SetTilted(bool aIsTilt);
 
   // Utility function
-  nsIFrame* GetCaretFocusFrame();
-  bool GetCaretVisible();
-  nsISelection* GetSelection();
+  dom::Selection* GetSelection();
+  already_AddRefed<nsFrameSelection> GetFrameSelection();
+  nsIContent* GetFocusedContent();
 
   /**
    * Detecting long tap using timer
    */
   void LaunchLongTapDetector();
   void CancelLongTapDetector();
   static void FireLongTap(nsITimer* aTimer, void* aSelectionCarets);