Bumping manifests a=b2g-bump
[gecko.git] / layout / base / TouchCaret.cpp
blobb66e1250ab81ced15add11aede798a8581ffd627
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 "prlog.h"
8 #include "TouchCaret.h"
10 #include <algorithm>
12 #include "nsBlockFrame.h"
13 #include "nsCanvasFrame.h"
14 #include "nsCaret.h"
15 #include "nsCOMPtr.h"
16 #include "nsContentUtils.h"
17 #include "nsDOMTokenList.h"
18 #include "nsFrameSelection.h"
19 #include "nsIContent.h"
20 #include "nsIDOMNode.h"
21 #include "nsIDOMWindow.h"
22 #include "nsIFrame.h"
23 #include "nsIInterfaceRequestorUtils.h"
24 #include "nsIPresShell.h"
25 #include "nsIScrollableFrame.h"
26 #include "nsISelection.h"
27 #include "nsISelectionController.h"
28 #include "nsISelectionPrivate.h"
29 #include "nsPresContext.h"
30 #include "nsQueryContentEventResult.h"
31 #include "nsView.h"
32 #include "mozilla/dom/SelectionStateChangedEvent.h"
33 #include "mozilla/dom/CustomEvent.h"
34 #include "mozilla/BasicEvents.h"
35 #include "mozilla/Preferences.h"
37 using namespace mozilla;
39 #ifdef PR_LOGGING
40 static PRLogModuleInfo* gTouchCaretLog;
41 static const char* kTouchCaretLogModuleName = "TouchCaret";
43 // To enable all the TOUCHCARET_LOG print statements, set the environment
44 // variable NSPR_LOG_MODULES=TouchCaret:5
45 #define TOUCHCARET_LOG(message, ...) \
46 PR_LOG(gTouchCaretLog, PR_LOG_DEBUG, \
47 ("TouchCaret (%p): %s:%d : " message "\n", this, __FUNCTION__, \
48 __LINE__, ##__VA_ARGS__));
50 #define TOUCHCARET_LOG_STATIC(message, ...) \
51 PR_LOG(gTouchCaretLog, PR_LOG_DEBUG, \
52 ("TouchCaret: %s:%d : " message "\n", __FUNCTION__, __LINE__, \
53 ##__VA_ARGS__));
54 #else
55 #define TOUCHCARET_LOG(message, ...)
56 #define TOUCHCARET_LOG_STATIC(message, ...)
57 #endif // #ifdef PR_LOGGING
59 // Click on the boundary of input/textarea will place the caret at the
60 // front/end of the content. To advoid this, we need to deflate the content
61 // boundary by 61 app units (1 pixel + 1 app unit).
62 static const int32_t kBoundaryAppUnits = 61;
64 NS_IMPL_ISUPPORTS(TouchCaret, nsISelectionListener)
66 /*static*/ int32_t TouchCaret::sTouchCaretInflateSize = 0;
67 /*static*/ int32_t TouchCaret::sTouchCaretExpirationTime = 0;
69 TouchCaret::TouchCaret(nsIPresShell* aPresShell)
70 : mState(TOUCHCARET_NONE),
71 mActiveTouchId(-1),
72 mCaretCenterToDownPointOffsetY(0),
73 mVisible(false),
74 mIsValidTap(false)
76 MOZ_ASSERT(NS_IsMainThread());
78 #ifdef PR_LOGGING
79 if (!gTouchCaretLog) {
80 gTouchCaretLog = PR_NewLogModule(kTouchCaretLogModuleName);
82 #endif
84 TOUCHCARET_LOG("Constructor, PresShell=%p", aPresShell);
86 static bool addedTouchCaretPref = false;
87 if (!addedTouchCaretPref) {
88 Preferences::AddIntVarCache(&sTouchCaretInflateSize,
89 "touchcaret.inflatesize.threshold");
90 Preferences::AddIntVarCache(&sTouchCaretExpirationTime,
91 "touchcaret.expiration.time");
92 addedTouchCaretPref = true;
95 // The presshell owns us, so no addref.
96 mPresShell = do_GetWeakReference(aPresShell);
97 MOZ_ASSERT(mPresShell, "Hey, pres shell should support weak refs");
100 TouchCaret::~TouchCaret()
102 TOUCHCARET_LOG("Destructor");
103 MOZ_ASSERT(NS_IsMainThread());
105 if (mTouchCaretExpirationTimer) {
106 mTouchCaretExpirationTimer->Cancel();
107 mTouchCaretExpirationTimer = nullptr;
111 nsIFrame*
112 TouchCaret::GetCaretFocusFrame(nsRect* aOutRect)
114 nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
115 if (!presShell) {
116 return nullptr;
119 nsRefPtr<nsCaret> caret = presShell->GetCaret();
120 if (!caret) {
121 return nullptr;
124 nsRect rect;
125 nsIFrame* frame = caret->GetGeometry(&rect);
127 if (aOutRect) {
128 *aOutRect = rect;
131 return frame;
134 nsCanvasFrame*
135 TouchCaret::GetCanvasFrame()
137 nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
138 if (!presShell) {
139 return nullptr;
141 return presShell->GetCanvasFrame();
144 nsIFrame*
145 TouchCaret::GetRootFrame()
147 nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
148 if (!presShell) {
149 return nullptr;
151 return presShell->GetRootFrame();
154 void
155 TouchCaret::SetVisibility(bool aVisible)
157 if (mVisible == aVisible) {
158 TOUCHCARET_LOG("Set visibility %s, same as the old one",
159 (aVisible ? "shown" : "hidden"));
160 return;
163 nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
164 if (!presShell) {
165 return;
168 mozilla::dom::Element* touchCaretElement = presShell->GetTouchCaretElement();
169 if (!touchCaretElement) {
170 return;
173 mVisible = aVisible;
175 // Set touch caret visibility.
176 ErrorResult err;
177 touchCaretElement->ClassList()->Toggle(NS_LITERAL_STRING("hidden"),
178 dom::Optional<bool>(!mVisible),
179 err);
180 TOUCHCARET_LOG("Set visibility %s", (mVisible ? "shown" : "hidden"));
182 // Set touch caret expiration time.
183 mVisible ? LaunchExpirationTimer() : CancelExpirationTimer();
185 // We must call SetMayHaveTouchCaret() in order to get APZC to wait until the
186 // event has been round-tripped and check whether it has been handled,
187 // otherwise B2G will end up panning the document when the user tries to drag
188 // touch caret.
189 presShell->SetMayHaveTouchCaret(mVisible);
192 nsRect
193 TouchCaret::GetTouchFrameRect()
195 nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
196 if (!presShell) {
197 return nsRect();
200 dom::Element* touchCaretElement = presShell->GetTouchCaretElement();
201 nsIFrame* canvasFrame = GetCanvasFrame();
202 return nsLayoutUtils::GetRectRelativeToFrame(touchCaretElement, canvasFrame);
205 nsRect
206 TouchCaret::GetContentBoundary()
208 nsIFrame* focusFrame = GetCaretFocusFrame();
209 nsIFrame* canvasFrame = GetCanvasFrame();
210 if (!focusFrame || !canvasFrame) {
211 return nsRect();
214 // Get the editing host to determine the touch caret dragable boundary.
215 dom::Element* editingHost = focusFrame->GetContent()->GetEditingHost();
216 if (!editingHost) {
217 return nsRect();
220 nsRect resultRect;
221 for (nsIFrame* frame = editingHost->GetPrimaryFrame(); frame;
222 frame = frame->GetNextContinuation()) {
223 nsRect rect = frame->GetContentRectRelativeToSelf();
224 nsLayoutUtils::TransformRect(frame, canvasFrame, rect);
225 resultRect = resultRect.Union(rect);
227 mozilla::layout::FrameChildListIterator lists(frame);
228 for (; !lists.IsDone(); lists.Next()) {
229 // Loop over all children to take the overflow rect in to consideration.
230 nsFrameList::Enumerator childFrames(lists.CurrentList());
231 for (; !childFrames.AtEnd(); childFrames.Next()) {
232 nsIFrame* kid = childFrames.get();
233 nsRect overflowRect = kid->GetScrollableOverflowRect();
234 nsLayoutUtils::TransformRect(kid, canvasFrame, overflowRect);
235 resultRect = resultRect.Union(overflowRect);
239 // Shrink rect to make sure we never hit the boundary.
240 resultRect.Deflate(kBoundaryAppUnits);
242 return resultRect;
245 nscoord
246 TouchCaret::GetCaretYCenterPosition()
248 nsRect caretRect;
249 nsIFrame* focusFrame = GetCaretFocusFrame(&caretRect);
250 nsIFrame* canvasFrame = GetCanvasFrame();
252 nsLayoutUtils::TransformRect(focusFrame, canvasFrame, caretRect);
254 return (caretRect.y + caretRect.height / 2);
257 void
258 TouchCaret::SetTouchFramePos(const nsRect& aCaretRect)
260 nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
261 if (!presShell) {
262 return;
265 mozilla::dom::Element* touchCaretElement = presShell->GetTouchCaretElement();
266 if (!touchCaretElement) {
267 return;
270 // Convert aOrigin to CSS pixels.
271 nsRefPtr<nsPresContext> presContext = presShell->GetPresContext();
272 int32_t x = presContext->AppUnitsToIntCSSPixels(aCaretRect.Center().x);
273 int32_t y = presContext->AppUnitsToIntCSSPixels(aCaretRect.y);
274 int32_t padding = presContext->AppUnitsToIntCSSPixels(aCaretRect.height);
276 nsAutoString styleStr;
277 styleStr.AppendLiteral("left: ");
278 styleStr.AppendInt(x);
279 styleStr.AppendLiteral("px; top: ");
280 styleStr.AppendInt(y);
281 styleStr.AppendLiteral("px; padding-top: ");
282 styleStr.AppendInt(padding);
283 styleStr.AppendLiteral("px;");
285 TOUCHCARET_LOG("Set style: %s", NS_ConvertUTF16toUTF8(styleStr).get());
287 touchCaretElement->SetAttr(kNameSpaceID_None, nsGkAtoms::style,
288 styleStr, true);
291 void
292 TouchCaret::MoveCaret(const nsPoint& movePoint)
294 nsIFrame* focusFrame = GetCaretFocusFrame();
295 nsIFrame* canvasFrame = GetCanvasFrame();
296 if (!focusFrame && !canvasFrame) {
297 return;
299 nsIFrame* scrollable =
300 nsLayoutUtils::GetClosestFrameOfType(focusFrame, nsGkAtoms::scrollFrame);
302 // Convert touch/mouse position to frame coordinates.
303 nsPoint offsetToCanvasFrame = nsPoint(0,0);
304 nsLayoutUtils::TransformPoint(scrollable, canvasFrame, offsetToCanvasFrame);
305 nsPoint pt = movePoint - offsetToCanvasFrame;
307 // Evaluate offsets.
308 nsIFrame::ContentOffsets offsets =
309 scrollable->GetContentOffsetsFromPoint(pt, nsIFrame::SKIP_HIDDEN);
311 // Move caret position.
312 nsWeakFrame weakScrollable = scrollable;
313 nsRefPtr<nsFrameSelection> fs = scrollable->GetFrameSelection();
314 fs->HandleClick(offsets.content, offsets.StartOffset(),
315 offsets.EndOffset(),
316 false,
317 false,
318 offsets.associate);
320 if (!weakScrollable.IsAlive()) {
321 return;
324 // Scroll scrolled frame.
325 nsIScrollableFrame* saf = do_QueryFrame(scrollable);
326 nsIFrame* capturingFrame = saf->GetScrolledFrame();
327 offsetToCanvasFrame = nsPoint(0,0);
328 nsLayoutUtils::TransformPoint(capturingFrame, canvasFrame, offsetToCanvasFrame);
329 pt = movePoint - offsetToCanvasFrame;
330 fs->StartAutoScrollTimer(capturingFrame, pt, sAutoScrollTimerDelay);
333 bool
334 TouchCaret::IsOnTouchCaret(const nsPoint& aPoint)
336 return mVisible && nsLayoutUtils::ContainsPoint(GetTouchFrameRect(), aPoint,
337 TouchCaretInflateSize());
340 nsresult
341 TouchCaret::NotifySelectionChanged(nsIDOMDocument* aDoc, nsISelection* aSel,
342 int16_t aReason)
344 TOUCHCARET_LOG("aSel (%p), Reason=%d", aSel, aReason);
346 // Hide touch caret while no caret exists.
347 nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
348 if (!presShell) {
349 return NS_OK;
352 nsRefPtr<nsCaret> caret = presShell->GetCaret();
353 if (!caret) {
354 SetVisibility(false);
355 return NS_OK;
358 // The same touch caret is shared amongst the document and any text widgets it
359 // may contain. This means that the touch caret could get notifications from
360 // multiple selections.
361 // If this notification is for a selection that is not the one the
362 // the caret is currently interested in , then there is nothing to do!
363 if (aSel != caret->GetSelection()) {
364 TOUCHCARET_LOG("Return for selection mismatch!");
365 return NS_OK;
368 // Update touch caret position and visibility.
369 // Hide touch caret while key event causes selection change.
370 // Also hide touch caret when gecko or javascript collapse the selection.
371 if (aReason & nsISelectionListener::KEYPRESS_REASON ||
372 aReason & nsISelectionListener::COLLAPSETOSTART_REASON ||
373 aReason & nsISelectionListener::COLLAPSETOEND_REASON) {
374 TOUCHCARET_LOG("KEYPRESS_REASON");
375 SetVisibility(false);
376 } else {
377 SyncVisibilityWithCaret();
380 return NS_OK;
383 void
384 TouchCaret::SyncVisibilityWithCaret()
386 TOUCHCARET_LOG("SyncVisibilityWithCaret");
388 if (!IsDisplayable()) {
389 SetVisibility(false);
390 return;
393 SetVisibility(true);
394 if (mVisible) {
395 UpdatePosition();
399 void
400 TouchCaret::UpdatePositionIfNeeded()
402 TOUCHCARET_LOG("UpdatePositionIfNeeded");
404 if (!IsDisplayable()) {
405 SetVisibility(false);
406 return;
409 if (mVisible) {
410 UpdatePosition();
414 bool
415 TouchCaret::IsDisplayable()
417 nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
418 if (!presShell) {
419 TOUCHCARET_LOG("PresShell is nullptr!");
420 return false;
423 nsRefPtr<nsCaret> caret = presShell->GetCaret();
424 if (!caret) {
425 TOUCHCARET_LOG("Caret is nullptr!");
426 return false;
429 nsIFrame* canvasFrame = GetCanvasFrame();
430 if (!canvasFrame) {
431 TOUCHCARET_LOG("No canvas frame!");
432 return false;
435 nsIFrame* rootFrame = GetRootFrame();
436 if (!rootFrame) {
437 TOUCHCARET_LOG("No root frame!");
438 return false;
441 dom::Element* touchCaretElement = presShell->GetTouchCaretElement();
442 if (!touchCaretElement) {
443 TOUCHCARET_LOG("No touch caret frame element!");
444 return false;
447 if (presShell->IsPaintingSuppressed()) {
448 TOUCHCARET_LOG("PresShell is suppressing painting!");
449 return false;
452 if (!caret->IsVisible()) {
453 TOUCHCARET_LOG("Caret is not visible!");
454 return false;
457 nsRect focusRect;
458 nsIFrame* focusFrame = caret->GetGeometry(&focusRect);
459 if (!focusFrame) {
460 TOUCHCARET_LOG("Focus frame is not valid!");
461 return false;
463 if (focusRect.IsEmpty()) {
464 TOUCHCARET_LOG("Focus rect is empty!");
465 return false;
468 dom::Element* editingHost = focusFrame->GetContent()->GetEditingHost();
469 if (!editingHost) {
470 TOUCHCARET_LOG("Cannot get editing host!");
471 return false;
474 if (!nsContentUtils::HasNonEmptyTextContent(
475 editingHost, nsContentUtils::eRecurseIntoChildren)) {
476 TOUCHCARET_LOG("The content is empty!");
477 return false;
480 if (mState != TOUCHCARET_TOUCHDRAG_ACTIVE &&
481 !nsLayoutUtils::IsRectVisibleInScrollFrames(focusFrame, focusRect)) {
482 TOUCHCARET_LOG("Caret does not show in the scrollable frame!");
483 return false;
486 TOUCHCARET_LOG("Touch caret is displayable!");
487 return true;
490 void
491 TouchCaret::UpdatePosition()
493 MOZ_ASSERT(mVisible);
495 nsRect rect = GetTouchCaretRect();
496 rect = ClampRectToScrollFrame(rect);
497 SetTouchFramePos(rect);
500 nsRect
501 TouchCaret::GetTouchCaretRect()
503 nsRect focusRect;
504 nsIFrame* focusFrame = GetCaretFocusFrame(&focusRect);
505 nsIFrame* rootFrame = GetRootFrame();
506 // Transform the position to make it relative to root frame.
507 nsLayoutUtils::TransformRect(focusFrame, rootFrame, focusRect);
509 return focusRect;
512 nsRect
513 TouchCaret::ClampRectToScrollFrame(const nsRect& aRect)
515 nsRect rect = aRect;
516 nsIFrame* focusFrame = GetCaretFocusFrame();
517 nsIFrame* rootFrame = GetRootFrame();
519 // Clamp the touch caret position to the scrollframe boundary.
520 nsIFrame* closestScrollFrame =
521 nsLayoutUtils::GetClosestFrameOfType(focusFrame, nsGkAtoms::scrollFrame);
523 while (closestScrollFrame) {
524 nsIScrollableFrame* sf = do_QueryFrame(closestScrollFrame);
525 nsRect visualRect = sf->GetScrollPortRect();
527 // Clamp the touch caret in the scroll port.
528 nsLayoutUtils::TransformRect(closestScrollFrame, rootFrame, visualRect);
529 rect = rect.Intersect(visualRect);
531 // Get next ancestor scroll frame.
532 closestScrollFrame =
533 nsLayoutUtils::GetClosestFrameOfType(closestScrollFrame->GetParent(),
534 nsGkAtoms::scrollFrame);
537 return rect;
540 /* static */void
541 TouchCaret::DisableTouchCaretCallback(nsITimer* aTimer, void* aTouchCaret)
543 nsRefPtr<TouchCaret> self = static_cast<TouchCaret*>(aTouchCaret);
544 NS_PRECONDITION(aTimer == self->mTouchCaretExpirationTimer,
545 "Unexpected timer");
547 self->SetVisibility(false);
550 void
551 TouchCaret::LaunchExpirationTimer()
553 if (TouchCaretExpirationTime() > 0) {
554 if (!mTouchCaretExpirationTimer) {
555 mTouchCaretExpirationTimer = do_CreateInstance("@mozilla.org/timer;1");
558 if (mTouchCaretExpirationTimer) {
559 mTouchCaretExpirationTimer->Cancel();
560 mTouchCaretExpirationTimer->InitWithFuncCallback(DisableTouchCaretCallback,
561 this,
562 TouchCaretExpirationTime(),
563 nsITimer::TYPE_ONE_SHOT);
568 void
569 TouchCaret::CancelExpirationTimer()
571 if (mTouchCaretExpirationTimer) {
572 mTouchCaretExpirationTimer->Cancel();
576 void
577 TouchCaret::SetSelectionDragState(bool aState)
579 nsIFrame* caretFocusFrame = GetCaretFocusFrame();
580 if (!caretFocusFrame) {
581 return;
584 nsRefPtr<nsFrameSelection> fs = caretFocusFrame->GetFrameSelection();
585 fs->SetDragState(aState);
588 nsEventStatus
589 TouchCaret::HandleEvent(WidgetEvent* aEvent)
591 MOZ_ASSERT(NS_IsMainThread());
592 if (!IsDisplayable()) {
593 return nsEventStatus_eIgnore;
596 nsEventStatus status = nsEventStatus_eIgnore;
598 switch (aEvent->message) {
599 case NS_TOUCH_START:
600 status = HandleTouchDownEvent(aEvent->AsTouchEvent());
601 break;
602 case NS_MOUSE_BUTTON_DOWN:
603 status = HandleMouseDownEvent(aEvent->AsMouseEvent());
604 break;
605 case NS_TOUCH_END:
606 status = HandleTouchUpEvent(aEvent->AsTouchEvent());
607 break;
608 case NS_MOUSE_BUTTON_UP:
609 status = HandleMouseUpEvent(aEvent->AsMouseEvent());
610 break;
611 case NS_TOUCH_MOVE:
612 status = HandleTouchMoveEvent(aEvent->AsTouchEvent());
613 break;
614 case NS_MOUSE_MOVE:
615 status = HandleMouseMoveEvent(aEvent->AsMouseEvent());
616 break;
617 case NS_TOUCH_CANCEL:
618 mTouchesId.Clear();
619 SetState(TOUCHCARET_NONE);
620 LaunchExpirationTimer();
621 break;
622 case NS_KEY_UP:
623 case NS_KEY_DOWN:
624 case NS_KEY_PRESS:
625 case NS_WHEEL_WHEEL:
626 case NS_WHEEL_START:
627 case NS_WHEEL_STOP:
628 // Disable touch caret while key/wheel event is received.
629 TOUCHCARET_LOG("Receive key/wheel event %d", aEvent->message);
630 SetVisibility(false);
631 break;
632 case NS_MOUSE_MOZLONGTAP:
633 if (mState == TOUCHCARET_TOUCHDRAG_ACTIVE) {
634 // Disable long tap event from APZ while dragging the touch caret.
635 status = nsEventStatus_eConsumeNoDefault;
637 break;
638 default:
639 break;
642 return status;
645 nsPoint
646 TouchCaret::GetEventPosition(WidgetTouchEvent* aEvent, int32_t aIdentifier)
648 for (size_t i = 0; i < aEvent->touches.Length(); i++) {
649 if (aEvent->touches[i]->mIdentifier == aIdentifier) {
650 // Get event coordinate relative to canvas frame.
651 nsIFrame* canvasFrame = GetCanvasFrame();
652 nsIntPoint touchIntPoint = aEvent->touches[i]->mRefPoint;
653 return nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent,
654 touchIntPoint,
655 canvasFrame);
658 return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
661 nsPoint
662 TouchCaret::GetEventPosition(WidgetMouseEvent* aEvent)
664 // Get event coordinate relative to canvas frame.
665 nsIFrame* canvasFrame = GetCanvasFrame();
666 nsIntPoint mouseIntPoint =
667 LayoutDeviceIntPoint::ToUntyped(aEvent->AsGUIEvent()->refPoint);
668 return nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent,
669 mouseIntPoint,
670 canvasFrame);
673 nsEventStatus
674 TouchCaret::HandleMouseMoveEvent(WidgetMouseEvent* aEvent)
676 TOUCHCARET_LOG("Got a mouse-move in state %d", mState);
677 nsEventStatus status = nsEventStatus_eIgnore;
679 switch (mState) {
680 case TOUCHCARET_NONE:
681 break;
683 case TOUCHCARET_MOUSEDRAG_ACTIVE:
685 nsPoint movePoint = GetEventPosition(aEvent);
686 movePoint.y += mCaretCenterToDownPointOffsetY;
687 nsRect contentBoundary = GetContentBoundary();
688 movePoint = contentBoundary.ClampPoint(movePoint);
690 MoveCaret(movePoint);
691 mIsValidTap = false;
692 status = nsEventStatus_eConsumeNoDefault;
694 break;
696 case TOUCHCARET_TOUCHDRAG_ACTIVE:
697 case TOUCHCARET_TOUCHDRAG_INACTIVE:
698 // Consume mouse event in touch sequence.
699 status = nsEventStatus_eConsumeNoDefault;
700 break;
703 return status;
706 nsEventStatus
707 TouchCaret::HandleTouchMoveEvent(WidgetTouchEvent* aEvent)
709 TOUCHCARET_LOG("Got a touch-move in state %d", mState);
710 nsEventStatus status = nsEventStatus_eIgnore;
712 switch (mState) {
713 case TOUCHCARET_NONE:
714 break;
716 case TOUCHCARET_MOUSEDRAG_ACTIVE:
717 // Consume touch event in mouse sequence.
718 status = nsEventStatus_eConsumeNoDefault;
719 break;
721 case TOUCHCARET_TOUCHDRAG_ACTIVE:
723 nsPoint movePoint = GetEventPosition(aEvent, mActiveTouchId);
724 movePoint.y += mCaretCenterToDownPointOffsetY;
725 nsRect contentBoundary = GetContentBoundary();
726 movePoint = contentBoundary.ClampPoint(movePoint);
728 MoveCaret(movePoint);
729 mIsValidTap = false;
730 status = nsEventStatus_eConsumeNoDefault;
732 break;
734 case TOUCHCARET_TOUCHDRAG_INACTIVE:
735 // Consume NS_TOUCH_MOVE event in TOUCHCARET_TOUCHDRAG_INACTIVE state.
736 status = nsEventStatus_eConsumeNoDefault;
737 break;
740 return status;
743 nsEventStatus
744 TouchCaret::HandleMouseUpEvent(WidgetMouseEvent* aEvent)
746 TOUCHCARET_LOG("Got a mouse-up in state %d", mState);
747 nsEventStatus status = nsEventStatus_eIgnore;
749 switch (mState) {
750 case TOUCHCARET_NONE:
751 break;
753 case TOUCHCARET_MOUSEDRAG_ACTIVE:
754 if (aEvent->button == WidgetMouseEvent::eLeftButton) {
755 SetSelectionDragState(false);
756 LaunchExpirationTimer();
757 SetState(TOUCHCARET_NONE);
758 status = nsEventStatus_eConsumeNoDefault;
760 break;
762 case TOUCHCARET_TOUCHDRAG_ACTIVE:
763 case TOUCHCARET_TOUCHDRAG_INACTIVE:
764 // Consume mouse event in touch sequence.
765 status = nsEventStatus_eConsumeNoDefault;
766 break;
769 return status;
772 nsEventStatus
773 TouchCaret::HandleTouchUpEvent(WidgetTouchEvent* aEvent)
775 TOUCHCARET_LOG("Got a touch-end in state %d", mState);
776 // Remove touches from cache if the stroke is gone in TOUCHDRAG states.
777 if (mState == TOUCHCARET_TOUCHDRAG_ACTIVE ||
778 mState == TOUCHCARET_TOUCHDRAG_INACTIVE) {
779 for (size_t i = 0; i < aEvent->touches.Length(); i++) {
780 nsTArray<int32_t>::index_type index =
781 mTouchesId.IndexOf(aEvent->touches[i]->mIdentifier);
782 MOZ_ASSERT(index != nsTArray<int32_t>::NoIndex);
783 mTouchesId.RemoveElementAt(index);
787 nsEventStatus status = nsEventStatus_eIgnore;
789 switch (mState) {
790 case TOUCHCARET_NONE:
791 break;
793 case TOUCHCARET_MOUSEDRAG_ACTIVE:
794 // Consume touch event in mouse sequence.
795 status = nsEventStatus_eConsumeNoDefault;
796 break;
798 case TOUCHCARET_TOUCHDRAG_ACTIVE:
799 if (mTouchesId.Length() == 0) {
800 SetSelectionDragState(false);
801 // No more finger on the screen.
802 SetState(TOUCHCARET_NONE);
803 LaunchExpirationTimer();
804 } else {
805 // Still has finger touching on the screen.
806 if (aEvent->touches[0]->mIdentifier == mActiveTouchId) {
807 // Remove finger from the touch caret.
808 SetState(TOUCHCARET_TOUCHDRAG_INACTIVE);
809 LaunchExpirationTimer();
810 } else {
811 // If the finger removed is not the finger on touch caret, remain in
812 // TOUCHCARET_DRAG_ACTIVE state.
815 status = nsEventStatus_eConsumeNoDefault;
816 break;
818 case TOUCHCARET_TOUCHDRAG_INACTIVE:
819 if (mTouchesId.Length() == 0) {
820 // No more finger on the screen.
821 SetState(TOUCHCARET_NONE);
823 status = nsEventStatus_eConsumeNoDefault;
824 break;
827 return status;
830 nsEventStatus
831 TouchCaret::HandleMouseDownEvent(WidgetMouseEvent* aEvent)
833 TOUCHCARET_LOG("Got a mouse-down in state %d", mState);
834 if (!GetVisibility()) {
835 // If touch caret is invisible, bypass event.
836 return nsEventStatus_eIgnore;
839 nsEventStatus status = nsEventStatus_eIgnore;
841 switch (mState) {
842 case TOUCHCARET_NONE:
843 if (aEvent->button == WidgetMouseEvent::eLeftButton) {
844 nsPoint point = GetEventPosition(aEvent);
845 if (IsOnTouchCaret(point)) {
846 SetSelectionDragState(true);
847 // Cache distence of the event point to the center of touch caret.
848 mCaretCenterToDownPointOffsetY = GetCaretYCenterPosition() - point.y;
849 // Enter TOUCHCARET_MOUSEDRAG_ACTIVE state and cancel the timer.
850 SetState(TOUCHCARET_MOUSEDRAG_ACTIVE);
851 CancelExpirationTimer();
852 status = nsEventStatus_eConsumeNoDefault;
853 } else {
854 // Set touch caret invisible if HisTest fails. Bypass event.
855 SetVisibility(false);
856 status = nsEventStatus_eIgnore;
858 } else {
859 // Set touch caret invisible if not left button down event.
860 SetVisibility(false);
861 status = nsEventStatus_eIgnore;
863 break;
865 case TOUCHCARET_MOUSEDRAG_ACTIVE:
866 SetVisibility(false);
867 SetState(TOUCHCARET_NONE);
868 break;
870 case TOUCHCARET_TOUCHDRAG_ACTIVE:
871 case TOUCHCARET_TOUCHDRAG_INACTIVE:
872 // Consume mouse event in touch sequence.
873 status = nsEventStatus_eConsumeNoDefault;
874 break;
877 return status;
880 nsEventStatus
881 TouchCaret::HandleTouchDownEvent(WidgetTouchEvent* aEvent)
883 TOUCHCARET_LOG("Got a touch-start in state %d", mState);
885 nsEventStatus status = nsEventStatus_eIgnore;
887 switch (mState) {
888 case TOUCHCARET_NONE:
889 if (!GetVisibility()) {
890 // If touch caret is invisible, bypass event.
891 status = nsEventStatus_eIgnore;
892 } else {
893 // Loop over all the touches and see if any of them is on the touch
894 // caret.
895 for (size_t i = 0; i < aEvent->touches.Length(); ++i) {
896 int32_t touchId = aEvent->touches[i]->Identifier();
897 nsPoint point = GetEventPosition(aEvent, touchId);
898 if (IsOnTouchCaret(point)) {
899 SetSelectionDragState(true);
900 // Touch start position is contained in touch caret.
901 mActiveTouchId = touchId;
902 // Cache distance of the event point to the center of touch caret.
903 mCaretCenterToDownPointOffsetY = GetCaretYCenterPosition() - point.y;
904 // Enter TOUCHCARET_TOUCHDRAG_ACTIVE state and cancel the timer.
905 SetState(TOUCHCARET_TOUCHDRAG_ACTIVE);
906 CancelExpirationTimer();
907 status = nsEventStatus_eConsumeNoDefault;
908 break;
911 // No touch is on the touch caret. Set touch caret invisible, and bypass
912 // the event.
913 if (mActiveTouchId == -1) {
914 SetVisibility(false);
915 status = nsEventStatus_eIgnore;
918 break;
920 case TOUCHCARET_MOUSEDRAG_ACTIVE:
921 case TOUCHCARET_TOUCHDRAG_ACTIVE:
922 case TOUCHCARET_TOUCHDRAG_INACTIVE:
923 // Consume NS_TOUCH_START event.
924 status = nsEventStatus_eConsumeNoDefault;
925 break;
928 // Cache active touch IDs in TOUCHDRAG states.
929 if (mState == TOUCHCARET_TOUCHDRAG_ACTIVE ||
930 mState == TOUCHCARET_TOUCHDRAG_INACTIVE) {
931 mTouchesId.Clear();
932 for (size_t i = 0; i < aEvent->touches.Length(); i++) {
933 mTouchesId.AppendElement(aEvent->touches[i]->mIdentifier);
937 return status;
940 void
941 TouchCaret::DispatchTapEvent()
943 nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
944 if (!presShell) {
945 return;
948 nsRefPtr<nsCaret> caret = presShell->GetCaret();
949 if (!caret) {
950 return;
953 dom::Selection* sel = static_cast<dom::Selection*>(caret->GetSelection());
954 if (!sel) {
955 return;
958 nsIDocument* doc = presShell->GetDocument();
960 MOZ_ASSERT(doc);
962 dom::SelectionStateChangedEventInit init;
963 init.mBubbles = true;
965 // XXX: Do we need to flush layout?
966 presShell->FlushPendingNotifications(Flush_Layout);
967 nsRect rect = nsContentUtils::GetSelectionBoundingRect(sel);
968 nsRefPtr<dom::DOMRect>domRect = new dom::DOMRect(ToSupports(doc));
970 domRect->SetLayoutRect(rect);
971 init.mBoundingClientRect = domRect;
972 init.mVisible = false;
974 sel->Stringify(init.mSelectedText);
976 dom::Sequence<dom::SelectionState> state;
977 state.AppendElement(dom::SelectionState::Taponcaret);
978 init.mStates = state;
980 nsRefPtr<dom::SelectionStateChangedEvent> event =
981 dom::SelectionStateChangedEvent::Constructor(doc, NS_LITERAL_STRING("mozselectionstatechanged"), init);
983 event->SetTrusted(true);
984 event->GetInternalNSEvent()->mFlags.mOnlyChromeDispatch = true;
985 bool ret;
986 doc->DispatchEvent(event, &ret);
989 void
990 TouchCaret::SetState(TouchCaretState aState)
992 TOUCHCARET_LOG("state changed from %d to %d", mState, aState);
993 if (mState == TOUCHCARET_NONE) {
994 MOZ_ASSERT(aState != TOUCHCARET_TOUCHDRAG_INACTIVE,
995 "mState: NONE => TOUCHDRAG_INACTIVE isn't allowed!");
998 if (mState == TOUCHCARET_TOUCHDRAG_ACTIVE) {
999 MOZ_ASSERT(aState != TOUCHCARET_MOUSEDRAG_ACTIVE,
1000 "mState: TOUCHDRAG_ACTIVE => MOUSEDRAG_ACTIVE isn't allowed!");
1003 if (mState == TOUCHCARET_MOUSEDRAG_ACTIVE) {
1004 MOZ_ASSERT(aState == TOUCHCARET_MOUSEDRAG_ACTIVE ||
1005 aState == TOUCHCARET_NONE,
1006 "MOUSEDRAG_ACTIVE allowed next state: NONE!");
1009 if (mState == TOUCHCARET_TOUCHDRAG_INACTIVE) {
1010 MOZ_ASSERT(aState == TOUCHCARET_TOUCHDRAG_INACTIVE ||
1011 aState == TOUCHCARET_NONE,
1012 "TOUCHDRAG_INACTIVE allowed next state: NONE!");
1015 mState = aState;
1017 if (mState == TOUCHCARET_NONE) {
1018 mActiveTouchId = -1;
1019 mCaretCenterToDownPointOffsetY = 0;
1020 if (mIsValidTap) {
1021 DispatchTapEvent();
1022 mIsValidTap = false;
1024 } else if (mState == TOUCHCARET_TOUCHDRAG_ACTIVE ||
1025 mState == TOUCHCARET_MOUSEDRAG_ACTIVE) {
1026 mIsValidTap = true;