Bumping manifests a=b2g-bump
[gecko.git] / layout / base / SelectionCarets.cpp
blob0bf4d5d5c22fbbe16798047cd8734ffff885ea0f
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 sw=2 et tw=78: */
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 "SelectionCarets.h"
9 #include "gfxPrefs.h"
10 #include "nsBidiPresUtils.h"
11 #include "nsCanvasFrame.h"
12 #include "nsCaret.h"
13 #include "nsContentUtils.h"
14 #include "nsDebug.h"
15 #include "nsDOMTokenList.h"
16 #include "nsFrame.h"
17 #include "nsIDocument.h"
18 #include "nsIDocShell.h"
19 #include "nsIDOMDocument.h"
20 #include "nsIDOMNodeFilter.h"
21 #include "nsIPresShell.h"
22 #include "nsPresContext.h"
23 #include "nsRect.h"
24 #include "nsView.h"
25 #include "mozilla/dom/DOMRect.h"
26 #include "mozilla/dom/Element.h"
27 #include "mozilla/dom/Selection.h"
28 #include "mozilla/dom/TreeWalker.h"
29 #include "mozilla/Preferences.h"
30 #include "mozilla/TouchEvents.h"
31 #include "TouchCaret.h"
32 #include "nsFrameSelection.h"
34 using namespace mozilla;
36 // We treat mouse/touch move as "REAL" move event once its move distance
37 // exceed this value, in CSS pixel.
38 static const int32_t kMoveStartTolerancePx = 5;
39 // Time for trigger scroll end event, in miliseconds.
40 static const int32_t kScrollEndTimerDelay = 300;
42 NS_IMPL_ISUPPORTS(SelectionCarets,
43 nsISelectionListener,
44 nsIScrollObserver,
45 nsISupportsWeakReference)
47 /*static*/ int32_t SelectionCarets::sSelectionCaretsInflateSize = 0;
49 SelectionCarets::SelectionCarets(nsIPresShell *aPresShell)
50 : mActiveTouchId(-1)
51 , mCaretCenterToDownPointOffsetY(0)
52 , mDragMode(NONE)
53 , mVisible(false)
54 , mStartCaretVisible(false)
55 , mEndCaretVisible(false)
57 MOZ_ASSERT(NS_IsMainThread());
59 static bool addedPref = false;
60 if (!addedPref) {
61 Preferences::AddIntVarCache(&sSelectionCaretsInflateSize,
62 "selectioncaret.inflatesize.threshold");
63 addedPref = true;
66 mPresShell = aPresShell;
69 SelectionCarets::~SelectionCarets()
71 MOZ_ASSERT(NS_IsMainThread());
73 if (mLongTapDetectorTimer) {
74 mLongTapDetectorTimer->Cancel();
75 mLongTapDetectorTimer = nullptr;
78 if (mScrollEndDetectorTimer) {
79 mScrollEndDetectorTimer->Cancel();
80 mScrollEndDetectorTimer = nullptr;
83 mPresShell = nullptr;
86 static bool
87 IsOnRect(const nsRect& aRect,
88 const nsPoint& aPoint,
89 int32_t aInflateSize)
91 // Check if the click was in the bounding box of the selection caret
92 nsRect rect = aRect;
93 rect.Inflate(aInflateSize);
94 return rect.Contains(aPoint);
97 nsEventStatus
98 SelectionCarets::HandleEvent(WidgetEvent* aEvent)
100 WidgetMouseEvent *mouseEvent = aEvent->AsMouseEvent();
101 if (mouseEvent && mouseEvent->reason == WidgetMouseEvent::eSynthesized) {
102 return nsEventStatus_eIgnore;
105 WidgetTouchEvent *touchEvent = aEvent->AsTouchEvent();
106 nsIntPoint movePoint;
107 int32_t nowTouchId = -1;
108 if (touchEvent && !touchEvent->touches.IsEmpty()) {
109 // If touch happened, just grab event with same identifier
110 if (mActiveTouchId >= 0) {
111 for (uint32_t i = 0; i < touchEvent->touches.Length(); ++i) {
112 if (touchEvent->touches[i]->Identifier() == mActiveTouchId) {
113 movePoint = touchEvent->touches[i]->mRefPoint;
114 nowTouchId = touchEvent->touches[i]->Identifier();
115 break;
119 // not found, consume it
120 if (nowTouchId == -1) {
121 return nsEventStatus_eConsumeNoDefault;
123 } else {
124 movePoint = touchEvent->touches[0]->mRefPoint;
125 nowTouchId = touchEvent->touches[0]->Identifier();
127 } else if (mouseEvent) {
128 movePoint = LayoutDeviceIntPoint::ToUntyped(mouseEvent->AsGUIEvent()->refPoint);
131 // Get event coordinate relative to canvas frame
132 nsIFrame* canvasFrame = mPresShell->GetCanvasFrame();
133 if (!canvasFrame) {
134 return nsEventStatus_eIgnore;
136 nsPoint ptInCanvas =
137 nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, movePoint, canvasFrame);
139 if (aEvent->message == NS_TOUCH_START ||
140 (aEvent->message == NS_MOUSE_BUTTON_DOWN &&
141 mouseEvent->button == WidgetMouseEvent::eLeftButton)) {
142 // If having a active touch, ignore other touch down event
143 if (aEvent->message == NS_TOUCH_START && mActiveTouchId >= 0) {
144 return nsEventStatus_eConsumeNoDefault;
147 mActiveTouchId = nowTouchId;
148 mDownPoint = ptInCanvas;
149 int32_t inflateSize = SelectionCaretsInflateSize();
150 if (mVisible && IsOnRect(GetStartFrameRect(), ptInCanvas, inflateSize)) {
151 mDragMode = START_FRAME;
152 mCaretCenterToDownPointOffsetY = GetCaretYCenterPosition() - ptInCanvas.y;
153 SetSelectionDirection(false);
154 SetSelectionDragState(true);
155 return nsEventStatus_eConsumeNoDefault;
156 } else if (mVisible && IsOnRect(GetEndFrameRect(), ptInCanvas, inflateSize)) {
157 mDragMode = END_FRAME;
158 mCaretCenterToDownPointOffsetY = GetCaretYCenterPosition() - ptInCanvas.y;
159 SetSelectionDirection(true);
160 SetSelectionDragState(true);
161 return nsEventStatus_eConsumeNoDefault;
162 } else {
163 mDragMode = NONE;
164 mActiveTouchId = -1;
165 SetVisibility(false);
166 LaunchLongTapDetector();
168 } else if (aEvent->message == NS_TOUCH_END ||
169 aEvent->message == NS_TOUCH_CANCEL ||
170 aEvent->message == NS_MOUSE_BUTTON_UP) {
171 CancelLongTapDetector();
172 if (mDragMode != NONE) {
173 // Only care about same id
174 if (mActiveTouchId == nowTouchId) {
175 SetSelectionDragState(false);
176 mDragMode = NONE;
177 mActiveTouchId = -1;
179 return nsEventStatus_eConsumeNoDefault;
181 } else if (aEvent->message == NS_TOUCH_MOVE ||
182 aEvent->message == NS_MOUSE_MOVE) {
183 if (mDragMode == START_FRAME || mDragMode == END_FRAME) {
184 if (mActiveTouchId == nowTouchId) {
185 ptInCanvas.y += mCaretCenterToDownPointOffsetY;
186 return DragSelection(ptInCanvas);
189 return nsEventStatus_eConsumeNoDefault;
192 nsPoint delta = mDownPoint - ptInCanvas;
193 if (NS_hypot(delta.x, delta.y) >
194 nsPresContext::AppUnitsPerCSSPixel() * kMoveStartTolerancePx) {
195 CancelLongTapDetector();
197 } else if (aEvent->message == NS_MOUSE_MOZLONGTAP) {
198 if (!mVisible) {
199 SelectWord();
200 return nsEventStatus_eConsumeNoDefault;
203 return nsEventStatus_eIgnore;
206 static void
207 SetElementVisibility(dom::Element* aElement, bool aVisible)
209 if (!aElement) {
210 return;
213 ErrorResult err;
214 aElement->ClassList()->Toggle(NS_LITERAL_STRING("hidden"),
215 dom::Optional<bool>(!aVisible), err);
218 void
219 SelectionCarets::SetVisibility(bool aVisible)
221 if (!mPresShell) {
222 return;
225 if (mVisible == aVisible) {
226 return;
228 mVisible = aVisible;
230 dom::Element* startElement = mPresShell->GetSelectionCaretsStartElement();
231 SetElementVisibility(startElement, mVisible && mStartCaretVisible);
233 dom::Element* endElement = mPresShell->GetSelectionCaretsEndElement();
234 SetElementVisibility(endElement, mVisible && mEndCaretVisible);
236 // We must call SetHasTouchCaret() in order to get APZC to wait until the
237 // event has been round-tripped and check whether it has been handled,
238 // otherwise B2G will end up panning the document when the user tries to drag
239 // selection caret.
240 mPresShell->SetMayHaveTouchCaret(mVisible);
243 void
244 SelectionCarets::SetStartFrameVisibility(bool aVisible)
246 mStartCaretVisible = aVisible;
247 dom::Element* element = mPresShell->GetSelectionCaretsStartElement();
248 SetElementVisibility(element, mVisible && mStartCaretVisible);
251 void
252 SelectionCarets::SetEndFrameVisibility(bool aVisible)
254 mEndCaretVisible = aVisible;
255 dom::Element* element = mPresShell->GetSelectionCaretsEndElement();
256 SetElementVisibility(element, mVisible && mEndCaretVisible);
259 void
260 SelectionCarets::SetTilted(bool aIsTilt)
262 dom::Element* startElement = mPresShell->GetSelectionCaretsStartElement();
263 dom::Element* endElement = mPresShell->GetSelectionCaretsEndElement();
265 if (!startElement || !endElement) {
266 return;
269 ErrorResult err;
270 startElement->ClassList()->Toggle(NS_LITERAL_STRING("tilt"),
271 dom::Optional<bool>(aIsTilt), err);
273 endElement->ClassList()->Toggle(NS_LITERAL_STRING("tilt"),
274 dom::Optional<bool>(aIsTilt), err);
277 static void
278 SetCaretDirection(dom::Element* aElement, bool aIsRight)
280 MOZ_ASSERT(aElement);
282 ErrorResult err;
283 if (aIsRight) {
284 aElement->ClassList()->Add(NS_LITERAL_STRING("moz-selectioncaret-right"), err);
285 aElement->ClassList()->Remove(NS_LITERAL_STRING("moz-selectioncaret-left"), err);
286 } else {
287 aElement->ClassList()->Add(NS_LITERAL_STRING("moz-selectioncaret-left"), err);
288 aElement->ClassList()->Remove(NS_LITERAL_STRING("moz-selectioncaret-right"), err);
292 static bool
293 IsRightToLeft(nsIFrame* aFrame)
295 MOZ_ASSERT(aFrame);
297 return aFrame->IsFrameOfType(nsIFrame::eLineParticipant) ?
298 (nsBidiPresUtils::GetFrameEmbeddingLevel(aFrame) & 1) :
299 aFrame->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
303 * Reduce rect to 1 app unit width along either left or right edge base on
304 * aToRightEdge parameter.
306 static void
307 ReduceRectToVerticalEdge(nsRect& aRect, bool aToRightEdge)
309 if (aToRightEdge) {
310 aRect.x = aRect.XMost() - 1;
312 aRect.width = 1;
315 static nsIFrame*
316 FindFirstNodeWithFrame(nsIDocument* aDocument,
317 nsRange* aRange,
318 nsFrameSelection* aFrameSelection,
319 bool aBackward,
320 int& aOutOffset)
322 if (!aDocument || !aRange || !aFrameSelection) {
323 return nullptr;
326 nsCOMPtr<nsINode> startNode =
327 do_QueryInterface(aBackward ? aRange->GetEndParent() : aRange->GetStartParent());
328 nsCOMPtr<nsINode> endNode =
329 do_QueryInterface(aBackward ? aRange->GetStartParent() : aRange->GetEndParent());
330 int32_t offset = aBackward ? aRange->EndOffset() : aRange->StartOffset();
332 nsCOMPtr<nsIContent> startContent = do_QueryInterface(startNode);
333 nsCOMPtr<nsIContent> endContent = do_QueryInterface(endNode);
334 CaretAssociationHint hintStart =
335 nsFrameSelection::GetHintForPosition(startContent, offset);
336 nsIFrame* startFrame = aFrameSelection->GetFrameForNodeOffset(startContent,
337 offset,
338 hintStart,
339 &aOutOffset);
341 if (startFrame) {
342 return startFrame;
345 ErrorResult err;
346 nsRefPtr<dom::TreeWalker> walker =
347 aDocument->CreateTreeWalker(*startNode,
348 nsIDOMNodeFilter::SHOW_ALL,
349 nullptr,
350 err);
352 if (!walker) {
353 return nullptr;
356 startFrame = startContent ? startContent->GetPrimaryFrame() : nullptr;
357 while (!startFrame && startNode != endNode) {
358 if (aBackward) {
359 startNode = walker->PreviousNode(err);
360 } else {
361 startNode = walker->NextNode(err);
363 startContent = do_QueryInterface(startNode);
364 startFrame = startContent ? startContent->GetPrimaryFrame() : nullptr;
366 return startFrame;
369 void
370 SelectionCarets::UpdateSelectionCarets()
372 if (!mPresShell) {
373 return;
376 nsISelection* caretSelection = GetSelection();
377 if (!caretSelection) {
378 SetVisibility(false);
379 return;
382 nsRefPtr<dom::Selection> selection = static_cast<dom::Selection*>(caretSelection);
383 if (selection->GetRangeCount() <= 0) {
384 SetVisibility(false);
385 return;
388 nsRefPtr<nsRange> range = selection->GetRangeAt(0);
389 if (range->Collapsed()) {
390 SetVisibility(false);
391 return;
394 nsLayoutUtils::FirstAndLastRectCollector collector;
395 nsRange::CollectClientRects(&collector, range,
396 range->GetStartParent(), range->StartOffset(),
397 range->GetEndParent(), range->EndOffset(), true, true);
399 nsIFrame* canvasFrame = mPresShell->GetCanvasFrame();
400 nsIFrame* rootFrame = mPresShell->GetRootFrame();
402 if (!canvasFrame || !rootFrame) {
403 SetVisibility(false);
404 return;
407 // Check if caret inside the scroll frame's boundary
408 nsIFrame* caretFocusFrame = GetCaretFocusFrame();
409 if (!caretFocusFrame) {
410 SetVisibility(false);
411 return;
413 nsIContent *editableAncestor = caretFocusFrame->GetContent()->GetEditingHost();
415 if (!editableAncestor) {
416 SetVisibility(false);
417 return;
420 nsRect resultRect;
421 for (nsIFrame* frame = editableAncestor->GetPrimaryFrame();
422 frame != nullptr;
423 frame = frame->GetNextContinuation()) {
424 nsRect rect = frame->GetRectRelativeToSelf();
425 nsLayoutUtils::TransformRect(frame, rootFrame, rect);
426 resultRect = resultRect.Union(rect);
429 // Check start and end frame is rtl or ltr text
430 nsRefPtr<nsFrameSelection> fs = caretFocusFrame->GetFrameSelection();
431 int32_t startOffset;
432 nsIFrame* startFrame = FindFirstNodeWithFrame(mPresShell->GetDocument(),
433 range, fs, false, startOffset);
435 int32_t endOffset;
436 nsIFrame* endFrame = FindFirstNodeWithFrame(mPresShell->GetDocument(),
437 range, fs, true, endOffset);
439 if (!startFrame || !endFrame) {
440 SetVisibility(false);
441 return;
444 // Check if startFrame is after endFrame.
445 if (nsLayoutUtils::CompareTreePosition(startFrame, endFrame) > 0) {
446 SetVisibility(false);
447 return;
450 bool startFrameIsRTL = IsRightToLeft(startFrame);
451 bool endFrameIsRTL = IsRightToLeft(endFrame);
453 // If start frame is LTR, then place start caret in first rect's leftmost
454 // otherwise put it to first rect's rightmost.
455 ReduceRectToVerticalEdge(collector.mFirstRect, startFrameIsRTL);
457 // Contrary to start frame, if end frame is LTR, put end caret to last
458 // rect's rightmost position, otherwise, put it to last rect's leftmost.
459 ReduceRectToVerticalEdge(collector.mLastRect, !endFrameIsRTL);
461 SetStartFrameVisibility(resultRect.Intersects(collector.mFirstRect));
462 SetEndFrameVisibility(resultRect.Intersects(collector.mLastRect));
464 nsLayoutUtils::TransformRect(rootFrame, canvasFrame, collector.mFirstRect);
465 nsLayoutUtils::TransformRect(rootFrame, canvasFrame, collector.mLastRect);
467 SetStartFramePos(collector.mFirstRect.BottomLeft());
468 SetEndFramePos(collector.mLastRect.BottomRight());
469 SetVisibility(true);
471 // If range select only one character, append tilt class name to it.
472 bool isTilt = false;
473 if (startFrame && endFrame) {
474 // In this case <textarea>abc</textarea> and we select 'c' character,
475 // EndContent would be HTMLDivElement and mResultContent which get by
476 // calling startFrame->PeekOffset() with selecting next cluster would be
477 // TextNode. Although the position is same, nsContentUtils::ComparePoints
478 // still shows HTMLDivElement is after TextNode. So that we cannot use
479 // EndContent or StartContent to compare with result of PeekOffset().
480 // So we compare between next charater of startFrame and previous character
481 // of endFrame.
482 nsPeekOffsetStruct posNext(eSelectCluster,
483 eDirNext,
484 startOffset,
486 false,
487 true, //limit on scrolled views
488 false,
489 false);
491 nsPeekOffsetStruct posPrev(eSelectCluster,
492 eDirPrevious,
493 endOffset,
495 false,
496 true, //limit on scrolled views
497 false,
498 false);
499 startFrame->PeekOffset(&posNext);
500 endFrame->PeekOffset(&posPrev);
502 if (posNext.mResultContent && posPrev.mResultContent &&
503 nsContentUtils::ComparePoints(posNext.mResultContent, posNext.mContentOffset,
504 posPrev.mResultContent, posPrev.mContentOffset) > 0) {
505 isTilt = true;
509 SetCaretDirection(mPresShell->GetSelectionCaretsStartElement(), startFrameIsRTL);
510 SetCaretDirection(mPresShell->GetSelectionCaretsEndElement(), !endFrameIsRTL);
511 SetTilted(isTilt);
514 nsresult
515 SelectionCarets::SelectWord()
517 // If caret isn't visible, the word is not selectable
518 if (!GetCaretVisible()) {
519 return NS_OK;
522 if (!mPresShell) {
523 return NS_OK;
526 nsIFrame* canvasFrame = mPresShell->GetCanvasFrame();
527 if (!canvasFrame) {
528 return NS_OK;
531 // Find content offsets for mouse down point
532 nsIFrame *ptFrame = nsLayoutUtils::GetFrameForPoint(canvasFrame, mDownPoint,
533 nsLayoutUtils::IGNORE_PAINT_SUPPRESSION | nsLayoutUtils::IGNORE_CROSS_DOC);
534 if (!ptFrame) {
535 return NS_OK;
538 nsPoint ptInFrame = mDownPoint;
539 nsLayoutUtils::TransformPoint(canvasFrame, ptFrame, ptInFrame);
541 nsIFrame* caretFocusFrame = GetCaretFocusFrame();
542 if (!caretFocusFrame) {
543 return NS_OK;
546 SetSelectionDragState(true);
547 nsFrame* frame = static_cast<nsFrame*>(ptFrame);
548 nsresult rs = frame->SelectByTypeAtPoint(mPresShell->GetPresContext(), ptInFrame,
549 eSelectWord, eSelectWord, 0);
550 SetSelectionDragState(false);
552 // Clear maintain selection otherwise we cannot select less than a word
553 nsRefPtr<nsFrameSelection> fs = caretFocusFrame->GetFrameSelection();
554 fs->MaintainSelection();
555 return rs;
559 * If we're dragging start caret, we do not want to drag over previous
560 * character of end caret. Same as end caret. So we check if content offset
561 * exceed previous/next character of end/start caret base on aDragMode.
563 static bool
564 CompareRangeWithContentOffset(nsRange* aRange,
565 nsFrameSelection* aSelection,
566 nsIFrame::ContentOffsets& aOffsets,
567 SelectionCarets::DragMode aDragMode)
569 MOZ_ASSERT(aDragMode != SelectionCarets::NONE);
570 nsINode* node = nullptr;
571 int32_t nodeOffset = 0;
572 CaretAssociationHint hint;
573 nsDirection dir;
575 if (aDragMode == SelectionCarets::START_FRAME) {
576 // Check previous character of end node offset
577 node = aRange->GetEndParent();
578 nodeOffset = aRange->EndOffset();
579 hint = CARET_ASSOCIATE_BEFORE;
580 dir = eDirPrevious;
581 } else {
582 // Check next character of start node offset
583 node = aRange->GetStartParent();
584 nodeOffset = aRange->StartOffset();
585 hint = CARET_ASSOCIATE_AFTER;
586 dir = eDirNext;
588 nsCOMPtr<nsIContent> content = do_QueryInterface(node);
590 int32_t offset = 0;
591 nsIFrame* theFrame =
592 aSelection->GetFrameForNodeOffset(content, nodeOffset, hint, &offset);
594 if (!theFrame) {
595 return false;
598 // Move one character forward/backward from point and get offset
599 nsPeekOffsetStruct pos(eSelectCluster,
600 dir,
601 offset,
603 true,
604 true, //limit on scrolled views
605 false,
606 false);
607 nsresult rv = theFrame->PeekOffset(&pos);
608 if (NS_FAILED(rv)) {
609 pos.mResultContent = content;
610 pos.mContentOffset = nodeOffset;
613 // Compare with current point
614 int32_t result = nsContentUtils::ComparePoints(aOffsets.content,
615 aOffsets.StartOffset(),
616 pos.mResultContent,
617 pos.mContentOffset);
618 if ((aDragMode == SelectionCarets::START_FRAME && result == 1) ||
619 (aDragMode == SelectionCarets::END_FRAME && result == -1)) {
620 aOffsets.content = pos.mResultContent;
621 aOffsets.offset = pos.mContentOffset;
622 aOffsets.secondaryOffset = pos.mContentOffset;
625 return true;
628 nsEventStatus
629 SelectionCarets::DragSelection(const nsPoint &movePoint)
631 nsIFrame* canvasFrame = mPresShell->GetCanvasFrame();
632 if (!canvasFrame) {
633 return nsEventStatus_eConsumeNoDefault;
636 // Find out which content we point to
637 nsIFrame *ptFrame = nsLayoutUtils::GetFrameForPoint(canvasFrame, movePoint,
638 nsLayoutUtils::IGNORE_PAINT_SUPPRESSION | nsLayoutUtils::IGNORE_CROSS_DOC);
639 if (!ptFrame) {
640 return nsEventStatus_eConsumeNoDefault;
643 nsIFrame* caretFocusFrame = GetCaretFocusFrame();
644 if (!caretFocusFrame) {
645 return nsEventStatus_eConsumeNoDefault;
648 nsRefPtr<nsFrameSelection> fs = caretFocusFrame->GetFrameSelection();
650 nsresult result;
651 nsIFrame *newFrame = nullptr;
652 nsPoint newPoint;
653 nsPoint ptInFrame = movePoint;
654 nsLayoutUtils::TransformPoint(canvasFrame, ptFrame, ptInFrame);
655 result = fs->ConstrainFrameAndPointToAnchorSubtree(ptFrame, ptInFrame, &newFrame, newPoint);
656 if (NS_FAILED(result) || !newFrame) {
657 return nsEventStatus_eConsumeNoDefault;
660 nsFrame::ContentOffsets offsets =
661 newFrame->GetContentOffsetsFromPoint(newPoint);
662 if (!offsets.content) {
663 return nsEventStatus_eConsumeNoDefault;
666 nsISelection* caretSelection = GetSelection();
667 nsRefPtr<dom::Selection> selection = static_cast<dom::Selection*>(caretSelection);
668 if (selection->GetRangeCount() <= 0) {
669 return nsEventStatus_eConsumeNoDefault;
672 nsRefPtr<nsRange> range = selection->GetRangeAt(0);
673 if (!CompareRangeWithContentOffset(range, fs, offsets, mDragMode)) {
674 return nsEventStatus_eConsumeNoDefault;
677 // Move caret postion.
678 nsIFrame *scrollable =
679 nsLayoutUtils::GetClosestFrameOfType(caretFocusFrame, nsGkAtoms::scrollFrame);
680 nsWeakFrame weakScrollable = scrollable;
681 fs->HandleClick(offsets.content, offsets.StartOffset(),
682 offsets.EndOffset(),
683 true,
684 false,
685 offsets.associate);
686 if (!weakScrollable.IsAlive()) {
687 return nsEventStatus_eConsumeNoDefault;
690 // Scroll scrolled frame.
691 nsIScrollableFrame *saf = do_QueryFrame(scrollable);
692 nsIFrame *capturingFrame = saf->GetScrolledFrame();
693 nsPoint ptInScrolled = movePoint;
694 nsLayoutUtils::TransformPoint(canvasFrame, capturingFrame, ptInScrolled);
695 fs->StartAutoScrollTimer(capturingFrame, ptInScrolled, TouchCaret::sAutoScrollTimerDelay);
696 UpdateSelectionCarets();
697 return nsEventStatus_eConsumeNoDefault;
700 nscoord
701 SelectionCarets::GetCaretYCenterPosition()
703 nsIFrame* canvasFrame = mPresShell->GetCanvasFrame();
704 nsIFrame* caretFocusFrame = GetCaretFocusFrame();
706 if (!canvasFrame || !caretFocusFrame) {
707 return 0;
709 nsISelection* caretSelection = GetSelection();
710 nsRefPtr<dom::Selection> selection = static_cast<dom::Selection*>(caretSelection);
711 if (selection->GetRangeCount() <= 0) {
712 return 0;
714 nsRefPtr<nsRange> range = selection->GetRangeAt(0);
715 nsRefPtr<nsFrameSelection> fs = caretFocusFrame->GetFrameSelection();
717 MOZ_ASSERT(mDragMode != NONE);
718 nsCOMPtr<nsIContent> node;
719 uint32_t nodeOffset;
720 if (mDragMode == START_FRAME) {
721 node = do_QueryInterface(range->GetStartParent());
722 nodeOffset = range->StartOffset();
723 } else {
724 node = do_QueryInterface(range->GetEndParent());
725 nodeOffset = range->EndOffset();
728 int32_t offset;
729 CaretAssociationHint hint =
730 nsFrameSelection::GetHintForPosition(node, nodeOffset);
731 nsIFrame* theFrame =
732 fs->GetFrameForNodeOffset(node, nodeOffset, hint, &offset);
734 if (!theFrame) {
735 return 0;
737 nsRect frameRect = theFrame->GetRectRelativeToSelf();
738 nsLayoutUtils::TransformRect(theFrame, canvasFrame, frameRect);
739 return frameRect.Center().y;
742 void
743 SelectionCarets::SetSelectionDragState(bool aState)
745 nsIFrame* caretFocusFrame = GetCaretFocusFrame();
746 if (!caretFocusFrame) {
747 return;
750 nsRefPtr<nsFrameSelection> fs = caretFocusFrame->GetFrameSelection();
751 fs->SetDragState(aState);
754 void
755 SelectionCarets::SetSelectionDirection(bool aForward)
757 nsISelection* caretSelection = GetSelection();
758 nsRefPtr<dom::Selection> selection = static_cast<dom::Selection*>(caretSelection);
759 selection->SetDirection(aForward ? eDirNext : eDirPrevious);
762 static void
763 SetFramePos(dom::Element* aElement, const nsPoint& aPosition)
765 if (!aElement) {
766 return;
769 nsAutoString styleStr;
770 styleStr.AppendLiteral("left:");
771 styleStr.AppendFloat(nsPresContext::AppUnitsToFloatCSSPixels(aPosition.x));
772 styleStr.AppendLiteral("px;top:");
773 styleStr.AppendFloat(nsPresContext::AppUnitsToFloatCSSPixels(aPosition.y));
774 styleStr.AppendLiteral("px;");
776 aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::style, styleStr, true);
779 void
780 SelectionCarets::SetStartFramePos(const nsPoint& aPosition)
782 SetFramePos(mPresShell->GetSelectionCaretsStartElement(), aPosition);
785 void
786 SelectionCarets::SetEndFramePos(const nsPoint& aPosition)
788 SetFramePos(mPresShell->GetSelectionCaretsEndElement(), aPosition);
791 nsRect
792 SelectionCarets::GetStartFrameRect()
794 nsIFrame* canvasFrame = mPresShell->GetCanvasFrame();
795 dom::Element* element = mPresShell->GetSelectionCaretsStartElement();
796 if (!element) {
797 return nsRect();
800 nsIFrame* frame = element->GetPrimaryFrame();
801 if (!frame) {
802 return nsRect();
805 nsRect frameRect = frame->GetRectRelativeToSelf();
806 nsLayoutUtils::TransformRect(frame, canvasFrame, frameRect);
807 return frameRect;
810 nsRect
811 SelectionCarets::GetEndFrameRect()
813 nsIFrame* canvasFrame = mPresShell->GetCanvasFrame();
814 dom::Element* element = mPresShell->GetSelectionCaretsEndElement();
815 if (!element) {
816 return nsRect();
819 nsIFrame* frame = element->GetPrimaryFrame();
820 if (!frame) {
821 return nsRect();
824 nsRect frameRect = frame->GetRectRelativeToSelf();
825 nsLayoutUtils::TransformRect(frame, canvasFrame, frameRect);
826 return frameRect;
829 nsIFrame*
830 SelectionCarets::GetCaretFocusFrame()
832 nsRefPtr<nsCaret> caret = mPresShell->GetCaret();
833 if (!caret) {
834 return nullptr;
837 nsRect focusRect;
838 return caret->GetGeometry(&focusRect);
841 bool
842 SelectionCarets::GetCaretVisible()
844 if (!mPresShell) {
845 return false;
848 nsRefPtr<nsCaret> caret = mPresShell->GetCaret();
849 if (!caret) {
850 return false;
853 return caret->IsVisible();
856 nsISelection*
857 SelectionCarets::GetSelection()
859 nsRefPtr<nsCaret> caret = mPresShell->GetCaret();
860 return caret->GetSelection();
863 nsresult
864 SelectionCarets::NotifySelectionChanged(nsIDOMDocument* aDoc,
865 nsISelection* aSel,
866 int16_t aReason)
868 bool isCollapsed;
869 aSel->GetIsCollapsed(&isCollapsed);
870 if (isCollapsed) {
871 SetVisibility(false);
872 return NS_OK;
874 if (aReason & nsISelectionListener::KEYPRESS_REASON) {
875 SetVisibility(false);
876 } else {
877 UpdateSelectionCarets();
879 return NS_OK;
882 void
883 SelectionCarets::ScrollPositionChanged()
885 SetVisibility(false);
886 LaunchScrollEndDetector();
889 void
890 SelectionCarets::LaunchLongTapDetector()
892 if (XRE_GetProcessType() != GeckoProcessType_Default) {
893 return;
896 if (!mLongTapDetectorTimer) {
897 mLongTapDetectorTimer = do_CreateInstance("@mozilla.org/timer;1");
900 MOZ_ASSERT(mLongTapDetectorTimer);
901 CancelLongTapDetector();
902 int32_t longTapDelay = gfxPrefs::UiClickHoldContextMenusDelay();
903 mLongTapDetectorTimer->InitWithFuncCallback(FireLongTap,
904 this,
905 longTapDelay,
906 nsITimer::TYPE_ONE_SHOT);
909 void
910 SelectionCarets::CancelLongTapDetector()
912 if (XRE_GetProcessType() != GeckoProcessType_Default) {
913 return;
916 if (!mLongTapDetectorTimer) {
917 return;
920 mLongTapDetectorTimer->Cancel();
923 /* static */void
924 SelectionCarets::FireLongTap(nsITimer* aTimer, void* aSelectionCarets)
926 nsRefPtr<SelectionCarets> self = static_cast<SelectionCarets*>(aSelectionCarets);
927 NS_PRECONDITION(aTimer == self->mLongTapDetectorTimer,
928 "Unexpected timer");
930 self->SelectWord();
933 void
934 SelectionCarets::LaunchScrollEndDetector()
936 if (!mScrollEndDetectorTimer) {
937 mScrollEndDetectorTimer = do_CreateInstance("@mozilla.org/timer;1");
940 MOZ_ASSERT(mScrollEndDetectorTimer);
941 mScrollEndDetectorTimer->InitWithFuncCallback(FireScrollEnd,
942 this,
943 kScrollEndTimerDelay,
944 nsITimer::TYPE_ONE_SHOT);
947 /* static */void
948 SelectionCarets::FireScrollEnd(nsITimer* aTimer, void* aSelectionCarets)
950 nsRefPtr<SelectionCarets> self = static_cast<SelectionCarets*>(aSelectionCarets);
951 NS_PRECONDITION(aTimer == self->mScrollEndDetectorTimer,
952 "Unexpected timer");
953 self->SetVisibility(true);
954 self->UpdateSelectionCarets();