gecko-dev/layout/base/TouchCaret.cpp
Carsten "Tomcat" Book 5471309381 Backed out 14 changesets (bug 1165515) for linux x64 e10s m2 test failures
Backed out changeset d68dcf2ef372 (bug 1165515)
Backed out changeset 7c3b45a47811 (bug 1165515)
Backed out changeset b668b617bef2 (bug 1165515)
Backed out changeset d0916e1283a2 (bug 1165515)
Backed out changeset ac4dc7489942 (bug 1165515)
Backed out changeset e9632ce8bc65 (bug 1165515)
Backed out changeset c16d215cc7e4 (bug 1165515)
Backed out changeset e4d474f3c51a (bug 1165515)
Backed out changeset d87680bf9f7c (bug 1165515)
Backed out changeset b3c0a45ba99e (bug 1165515)
Backed out changeset 9370fa197674 (bug 1165515)
Backed out changeset 50970d668ca1 (bug 1165515)
Backed out changeset ffa4eb6d24b9 (bug 1165515)
Backed out changeset 5fcf1203cc1d (bug 1165515)

--HG--
extra : rebase_source : 6fb850d063cbabe738f97f0380302153e3eae97a
2015-06-02 13:05:56 +02:00

1204 lines
34 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 sw=2 et tw=78: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/Logging.h"
#include "TouchCaret.h"
#include <algorithm>
#include "nsBlockFrame.h"
#include "nsCanvasFrame.h"
#include "nsCaret.h"
#include "nsCOMPtr.h"
#include "nsContentUtils.h"
#include "nsDOMTokenList.h"
#include "nsFrameSelection.h"
#include "nsIContent.h"
#include "nsIDOMNode.h"
#include "nsIDOMWindow.h"
#include "nsIFrame.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIPresShell.h"
#include "nsIScrollableFrame.h"
#include "nsISelection.h"
#include "nsISelectionController.h"
#include "nsISelectionPrivate.h"
#include "nsPresContext.h"
#include "nsQueryContentEventResult.h"
#include "nsView.h"
#include "mozilla/dom/SelectionStateChangedEvent.h"
#include "mozilla/dom/CustomEvent.h"
#include "mozilla/BasicEvents.h"
#include "mozilla/Preferences.h"
using namespace mozilla;
static PRLogModuleInfo* gTouchCaretLog;
static const char* kTouchCaretLogModuleName = "TouchCaret";
// To enable all the TOUCHCARET_LOG print statements, set the environment
// variable NSPR_LOG_MODULES=TouchCaret:5
#define TOUCHCARET_LOG(message, ...) \
MOZ_LOG(gTouchCaretLog, PR_LOG_DEBUG, \
("TouchCaret (%p): %s:%d : " message "\n", this, __FUNCTION__, \
__LINE__, ##__VA_ARGS__));
#define TOUCHCARET_LOG_STATIC(message, ...) \
MOZ_LOG(gTouchCaretLog, PR_LOG_DEBUG, \
("TouchCaret: %s:%d : " message "\n", __FUNCTION__, __LINE__, \
##__VA_ARGS__));
// Click on the boundary of input/textarea will place the caret at the
// front/end of the content. To advoid this, we need to deflate the content
// boundary by 61 app units (1 pixel + 1 app unit).
static const int32_t kBoundaryAppUnits = 61;
NS_IMPL_ISUPPORTS(TouchCaret,
nsISelectionListener,
nsIScrollObserver,
nsISupportsWeakReference)
/*static*/ int32_t TouchCaret::sTouchCaretInflateSize = 0;
/*static*/ int32_t TouchCaret::sTouchCaretExpirationTime = 0;
/*static*/ bool TouchCaret::sCaretManagesAndroidActionbar = false;
/*static*/ bool TouchCaret::sTouchcaretExtendedvisibility = false;
/*static*/ uint32_t TouchCaret::sActionBarViewCount = 0;
TouchCaret::TouchCaret(nsIPresShell* aPresShell)
: mState(TOUCHCARET_NONE),
mActiveTouchId(-1),
mCaretCenterToDownPointOffsetY(0),
mInAsyncPanZoomGesture(false),
mVisible(false),
mIsValidTap(false),
mActionBarViewID(0)
{
MOZ_ASSERT(NS_IsMainThread());
if (!gTouchCaretLog) {
gTouchCaretLog = PR_NewLogModule(kTouchCaretLogModuleName);
}
TOUCHCARET_LOG("Constructor, PresShell=%p", aPresShell);
static bool addedTouchCaretPref = false;
if (!addedTouchCaretPref) {
Preferences::AddIntVarCache(&sTouchCaretInflateSize,
"touchcaret.inflatesize.threshold");
Preferences::AddIntVarCache(&sTouchCaretExpirationTime,
"touchcaret.expiration.time");
Preferences::AddBoolVarCache(&sCaretManagesAndroidActionbar,
"caret.manages-android-actionbar");
Preferences::AddBoolVarCache(&sTouchcaretExtendedvisibility,
"touchcaret.extendedvisibility");
addedTouchCaretPref = true;
}
// The presshell owns us, so no addref.
mPresShell = do_GetWeakReference(aPresShell);
MOZ_ASSERT(mPresShell, "Hey, pres shell should support weak refs");
}
void
TouchCaret::Init()
{
nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
if (!presShell) {
return;
}
nsPresContext* presContext = presShell->GetPresContext();
MOZ_ASSERT(presContext, "PresContext should be given in PresShell::Init()");
nsIDocShell* docShell = presContext->GetDocShell();
if (!docShell) {
return;
}
docShell->AddWeakScrollObserver(this);
mDocShell = static_cast<nsDocShell*>(docShell);
}
void
TouchCaret::Terminate()
{
nsRefPtr<nsDocShell> docShell(mDocShell.get());
if (docShell) {
docShell->RemoveWeakScrollObserver(this);
}
if (mScrollEndDetectorTimer) {
mScrollEndDetectorTimer->Cancel();
mScrollEndDetectorTimer = nullptr;
}
mDocShell = WeakPtr<nsDocShell>();
mPresShell = nullptr;
}
TouchCaret::~TouchCaret()
{
TOUCHCARET_LOG("Destructor");
MOZ_ASSERT(NS_IsMainThread());
if (mTouchCaretExpirationTimer) {
mTouchCaretExpirationTimer->Cancel();
mTouchCaretExpirationTimer = nullptr;
}
}
nsIFrame*
TouchCaret::GetCaretFocusFrame(nsRect* aOutRect)
{
nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
if (!presShell) {
return nullptr;
}
nsRefPtr<nsCaret> caret = presShell->GetCaret();
if (!caret) {
return nullptr;
}
nsRect rect;
nsIFrame* frame = caret->GetGeometry(&rect);
if (aOutRect) {
*aOutRect = rect;
}
return frame;
}
nsCanvasFrame*
TouchCaret::GetCanvasFrame()
{
nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
if (!presShell) {
return nullptr;
}
return presShell->GetCanvasFrame();
}
nsIFrame*
TouchCaret::GetRootFrame()
{
nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
if (!presShell) {
return nullptr;
}
return presShell->GetRootFrame();
}
void
TouchCaret::SetVisibility(bool aVisible)
{
if (mVisible == aVisible) {
TOUCHCARET_LOG("Set visibility %s, same as the old one",
(aVisible ? "shown" : "hidden"));
return;
}
nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
if (!presShell) {
return;
}
mozilla::dom::Element* touchCaretElement = presShell->GetTouchCaretElement();
if (!touchCaretElement) {
return;
}
mVisible = aVisible;
// Set touch caret visibility.
ErrorResult err;
touchCaretElement->ClassList()->Toggle(NS_LITERAL_STRING("hidden"),
dom::Optional<bool>(!mVisible),
err);
TOUCHCARET_LOG("Set visibility %s", (mVisible ? "shown" : "hidden"));
// Set touch caret expiration time.
mVisible ? LaunchExpirationTimer() : CancelExpirationTimer();
// If after a TouchCaret visibility change we become hidden, ensure
// the Android ActionBar handler is notified to close the current view.
if (!mVisible && sCaretManagesAndroidActionbar) {
UpdateAndroidActionBarVisibility(false, mActionBarViewID);
}
}
/**
* Open or close the Android TextSelection ActionBar, based on visibility.
* Each time we're called to open the actionbar, we increment / assign a
* unique view ID and return it to the caller. The ID is returned on calls
* to close the actionbar to ensure we don't close the shared view if it
* was already force closed by a subsequent callers open request.
*/
/* static */void
TouchCaret::UpdateAndroidActionBarVisibility(bool aVisibility, uint32_t& aViewID)
{
// Are we openning a new view?
if (aVisibility) {
// Assign a new view ID.
aViewID = ++sActionBarViewCount;
}
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
if (os) {
nsString topic = (aVisibility) ?
NS_LITERAL_STRING("ActionBar:OpenNew") : NS_LITERAL_STRING("ActionBar:Close");
nsAutoString viewCount;
viewCount.AppendInt(aViewID);
os->NotifyObservers(nullptr, NS_ConvertUTF16toUTF8(topic).get(), viewCount.get());
}
}
nsRect
TouchCaret::GetTouchFrameRect()
{
nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
if (!presShell) {
return nsRect();
}
dom::Element* touchCaretElement = presShell->GetTouchCaretElement();
nsIFrame* canvasFrame = GetCanvasFrame();
return nsLayoutUtils::GetRectRelativeToFrame(touchCaretElement, canvasFrame);
}
nsRect
TouchCaret::GetContentBoundary()
{
nsIFrame* focusFrame = GetCaretFocusFrame();
nsIFrame* canvasFrame = GetCanvasFrame();
if (!focusFrame || !canvasFrame) {
return nsRect();
}
// Get the editing host to determine the touch caret dragable boundary.
dom::Element* editingHost = focusFrame->GetContent()->GetEditingHost();
if (!editingHost) {
return nsRect();
}
nsRect resultRect;
for (nsIFrame* frame = editingHost->GetPrimaryFrame(); frame;
frame = frame->GetNextContinuation()) {
nsRect rect = frame->GetContentRectRelativeToSelf();
nsLayoutUtils::TransformRect(frame, canvasFrame, rect);
resultRect = resultRect.Union(rect);
mozilla::layout::FrameChildListIterator lists(frame);
for (; !lists.IsDone(); lists.Next()) {
// Loop over all children to take the overflow rect in to consideration.
nsFrameList::Enumerator childFrames(lists.CurrentList());
for (; !childFrames.AtEnd(); childFrames.Next()) {
nsIFrame* kid = childFrames.get();
nsRect overflowRect = kid->GetScrollableOverflowRect();
nsLayoutUtils::TransformRect(kid, canvasFrame, overflowRect);
resultRect = resultRect.Union(overflowRect);
}
}
}
// Shrink rect to make sure we never hit the boundary.
resultRect.Deflate(kBoundaryAppUnits);
return resultRect;
}
nscoord
TouchCaret::GetCaretYCenterPosition()
{
nsRect caretRect;
nsIFrame* focusFrame = GetCaretFocusFrame(&caretRect);
nsIFrame* canvasFrame = GetCanvasFrame();
nsLayoutUtils::TransformRect(focusFrame, canvasFrame, caretRect);
return (caretRect.y + caretRect.height / 2);
}
void
TouchCaret::SetTouchFramePos(const nsRect& aCaretRect)
{
nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
if (!presShell) {
return;
}
mozilla::dom::Element* touchCaretElement = presShell->GetTouchCaretElement();
if (!touchCaretElement) {
return;
}
// Convert aOrigin to CSS pixels.
nsRefPtr<nsPresContext> presContext = presShell->GetPresContext();
int32_t x = presContext->AppUnitsToIntCSSPixels(aCaretRect.Center().x);
int32_t y = presContext->AppUnitsToIntCSSPixels(aCaretRect.y);
int32_t padding = presContext->AppUnitsToIntCSSPixels(aCaretRect.height);
nsAutoString styleStr;
styleStr.AppendLiteral("left: ");
styleStr.AppendInt(x);
styleStr.AppendLiteral("px; top: ");
styleStr.AppendInt(y);
styleStr.AppendLiteral("px; padding-top: ");
styleStr.AppendInt(padding);
styleStr.AppendLiteral("px;");
TOUCHCARET_LOG("Set style: %s", NS_ConvertUTF16toUTF8(styleStr).get());
touchCaretElement->SetAttr(kNameSpaceID_None, nsGkAtoms::style,
styleStr, true);
}
void
TouchCaret::MoveCaret(const nsPoint& movePoint)
{
nsIFrame* focusFrame = GetCaretFocusFrame();
nsIFrame* canvasFrame = GetCanvasFrame();
if (!focusFrame && !canvasFrame) {
return;
}
nsIFrame* scrollable =
nsLayoutUtils::GetClosestFrameOfType(focusFrame, nsGkAtoms::scrollFrame);
// Convert touch/mouse position to frame coordinates.
nsPoint offsetToCanvasFrame = nsPoint(0,0);
nsLayoutUtils::TransformPoint(scrollable, canvasFrame, offsetToCanvasFrame);
nsPoint pt = movePoint - offsetToCanvasFrame;
// Evaluate offsets.
nsIFrame::ContentOffsets offsets =
scrollable->GetContentOffsetsFromPoint(pt, nsIFrame::SKIP_HIDDEN);
// Move caret position.
nsWeakFrame weakScrollable = scrollable;
nsRefPtr<nsFrameSelection> fs = scrollable->GetFrameSelection();
fs->HandleClick(offsets.content, offsets.StartOffset(),
offsets.EndOffset(),
false,
false,
offsets.associate);
if (!weakScrollable.IsAlive()) {
return;
}
// Scroll scrolled frame.
nsIScrollableFrame* saf = do_QueryFrame(scrollable);
nsIFrame* capturingFrame = saf->GetScrolledFrame();
offsetToCanvasFrame = nsPoint(0,0);
nsLayoutUtils::TransformPoint(capturingFrame, canvasFrame, offsetToCanvasFrame);
pt = movePoint - offsetToCanvasFrame;
fs->StartAutoScrollTimer(capturingFrame, pt, sAutoScrollTimerDelay);
}
bool
TouchCaret::IsOnTouchCaret(const nsPoint& aPoint)
{
return mVisible && nsLayoutUtils::ContainsPoint(GetTouchFrameRect(), aPoint,
TouchCaretInflateSize());
}
nsresult
TouchCaret::NotifySelectionChanged(nsIDOMDocument* aDoc, nsISelection* aSel,
int16_t aReason)
{
TOUCHCARET_LOG("aSel (%p), Reason=%d", aSel, aReason);
// Hide touch caret while no caret exists.
nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
if (!presShell) {
return NS_OK;
}
nsRefPtr<nsCaret> caret = presShell->GetCaret();
if (!caret) {
SetVisibility(false);
return NS_OK;
}
// The same touch caret is shared amongst the document and any text widgets it
// may contain. This means that the touch caret could get notifications from
// multiple selections.
// If this notification is for a selection that is not the one the
// the caret is currently interested in , then there is nothing to do!
if (aSel != caret->GetSelection()) {
TOUCHCARET_LOG("Return for selection mismatch!");
return NS_OK;
}
// Update touch caret position and visibility.
// Hide touch caret while key event causes selection change.
// Also hide touch caret when gecko or javascript collapse the selection.
if (aReason & nsISelectionListener::KEYPRESS_REASON ||
aReason & nsISelectionListener::COLLAPSETOSTART_REASON ||
aReason & nsISelectionListener::COLLAPSETOEND_REASON) {
TOUCHCARET_LOG("KEYPRESS_REASON");
SetVisibility(false);
} else {
SyncVisibilityWithCaret();
// Is the TouchCaret visible and we're showing/hiding the actionbar?
if (mVisible && sCaretManagesAndroidActionbar) {
// A selection change due to touch tap opens the actionbar.
if (aReason & nsISelectionListener::MOUSEUP_REASON) {
UpdateAndroidActionBarVisibility(true, mActionBarViewID);
} else {
// Update the ActionBar state for caret-specific selection changes.
// Ignore transient selection composition changes that occur while
// the TouchCaret is also visible.
bool isCollapsed;
if (NS_SUCCEEDED(aSel->GetIsCollapsed(&isCollapsed)) && isCollapsed) {
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
if (os) {
os->NotifyObservers(nullptr, "ActionBar:UpdateState", nullptr);
}
}
}
}
}
return NS_OK;
}
/**
* Used to update caret position after PanZoom stops for
* extended caret visibility. Never needed by MOZ_WIDGET_GONK.
*/
void
TouchCaret::AsyncPanZoomStarted()
{
if (mVisible) {
if (sTouchcaretExtendedvisibility) {
mInAsyncPanZoomGesture = true;
}
}
}
void
TouchCaret::AsyncPanZoomStopped()
{
if (mInAsyncPanZoomGesture) {
mInAsyncPanZoomGesture = false;
UpdatePosition();
}
}
/**
* Used to update caret position after Scroll stops for
* extended caret visibility. Never needed by MOZ_WIDGET_GONK.
*/
void
TouchCaret::ScrollPositionChanged()
{
if (mVisible) {
if (sTouchcaretExtendedvisibility) {
// Launch scroll end detector.
LaunchScrollEndDetector();
}
}
}
void
TouchCaret::LaunchScrollEndDetector()
{
if (!mScrollEndDetectorTimer) {
mScrollEndDetectorTimer = do_CreateInstance("@mozilla.org/timer;1");
}
MOZ_ASSERT(mScrollEndDetectorTimer);
mScrollEndDetectorTimer->InitWithFuncCallback(FireScrollEnd,
this,
sScrollEndTimerDelay,
nsITimer::TYPE_ONE_SHOT);
}
void
TouchCaret::CancelScrollEndDetector()
{
if (mScrollEndDetectorTimer) {
mScrollEndDetectorTimer->Cancel();
}
}
/* static */void
TouchCaret::FireScrollEnd(nsITimer* aTimer, void* aTouchCaret)
{
nsRefPtr<TouchCaret> self = static_cast<TouchCaret*>(aTouchCaret);
NS_PRECONDITION(aTimer == self->mScrollEndDetectorTimer,
"Unexpected timer");
self->UpdatePosition();
}
void
TouchCaret::SyncVisibilityWithCaret()
{
TOUCHCARET_LOG("SyncVisibilityWithCaret");
if (!IsDisplayable()) {
SetVisibility(false);
return;
}
SetVisibility(true);
if (mVisible) {
UpdatePosition();
}
}
void
TouchCaret::UpdatePositionIfNeeded()
{
TOUCHCARET_LOG("UpdatePositionIfNeeded");
if (!IsDisplayable()) {
SetVisibility(false);
return;
}
if (mVisible) {
UpdatePosition();
}
}
bool
TouchCaret::IsDisplayable()
{
nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
if (!presShell) {
TOUCHCARET_LOG("PresShell is nullptr!");
return false;
}
nsRefPtr<nsCaret> caret = presShell->GetCaret();
if (!caret) {
TOUCHCARET_LOG("Caret is nullptr!");
return false;
}
nsIFrame* canvasFrame = GetCanvasFrame();
if (!canvasFrame) {
TOUCHCARET_LOG("No canvas frame!");
return false;
}
nsIFrame* rootFrame = GetRootFrame();
if (!rootFrame) {
TOUCHCARET_LOG("No root frame!");
return false;
}
dom::Element* touchCaretElement = presShell->GetTouchCaretElement();
if (!touchCaretElement) {
TOUCHCARET_LOG("No touch caret frame element!");
return false;
}
if (presShell->IsPaintingSuppressed()) {
TOUCHCARET_LOG("PresShell is suppressing painting!");
return false;
}
if (!caret->IsVisible()) {
TOUCHCARET_LOG("Caret is not visible!");
return false;
}
nsRect focusRect;
nsIFrame* focusFrame = caret->GetGeometry(&focusRect);
if (!focusFrame) {
TOUCHCARET_LOG("Focus frame is not valid!");
return false;
}
dom::Element* editingHost = focusFrame->GetContent()->GetEditingHost();
if (!editingHost) {
TOUCHCARET_LOG("Cannot get editing host!");
return false;
}
// No further checks required if extended TouchCaret visibility.
if (sTouchcaretExtendedvisibility) {
return true;
}
if (focusRect.IsEmpty()) {
TOUCHCARET_LOG("Focus rect is empty!");
return false;
}
if (!nsContentUtils::HasNonEmptyTextContent(
editingHost, nsContentUtils::eRecurseIntoChildren)) {
TOUCHCARET_LOG("The content is empty!");
return false;
}
if (mState != TOUCHCARET_TOUCHDRAG_ACTIVE &&
!nsLayoutUtils::IsRectVisibleInScrollFrames(focusFrame, focusRect)) {
TOUCHCARET_LOG("Caret does not show in the scrollable frame!");
return false;
}
TOUCHCARET_LOG("Touch caret is displayable!");
return true;
}
void
TouchCaret::UpdatePosition()
{
MOZ_ASSERT(mVisible);
nsRect rect = GetTouchCaretRect();
rect = ClampRectToScrollFrame(rect);
SetTouchFramePos(rect);
}
nsRect
TouchCaret::GetTouchCaretRect()
{
nsRect focusRect;
nsIFrame* focusFrame = GetCaretFocusFrame(&focusRect);
nsIFrame* rootFrame = GetRootFrame();
// Transform the position to make it relative to root frame.
nsLayoutUtils::TransformRect(focusFrame, rootFrame, focusRect);
return focusRect;
}
nsRect
TouchCaret::ClampRectToScrollFrame(const nsRect& aRect)
{
nsRect rect = aRect;
nsIFrame* focusFrame = GetCaretFocusFrame();
nsIFrame* rootFrame = GetRootFrame();
// Clamp the touch caret position to the scrollframe boundary.
nsIFrame* closestScrollFrame =
nsLayoutUtils::GetClosestFrameOfType(focusFrame, nsGkAtoms::scrollFrame);
while (closestScrollFrame) {
nsIScrollableFrame* sf = do_QueryFrame(closestScrollFrame);
nsRect visualRect = sf->GetScrollPortRect();
// Clamp the touch caret in the scroll port.
nsLayoutUtils::TransformRect(closestScrollFrame, rootFrame, visualRect);
rect = rect.Intersect(visualRect);
// Get next ancestor scroll frame.
closestScrollFrame =
nsLayoutUtils::GetClosestFrameOfType(closestScrollFrame->GetParent(),
nsGkAtoms::scrollFrame);
}
return rect;
}
/* static */void
TouchCaret::DisableTouchCaretCallback(nsITimer* aTimer, void* aTouchCaret)
{
nsRefPtr<TouchCaret> self = static_cast<TouchCaret*>(aTouchCaret);
NS_PRECONDITION(aTimer == self->mTouchCaretExpirationTimer,
"Unexpected timer");
self->SetVisibility(false);
}
void
TouchCaret::LaunchExpirationTimer()
{
if (TouchCaretExpirationTime() > 0) {
if (!mTouchCaretExpirationTimer) {
mTouchCaretExpirationTimer = do_CreateInstance("@mozilla.org/timer;1");
}
if (mTouchCaretExpirationTimer) {
mTouchCaretExpirationTimer->Cancel();
mTouchCaretExpirationTimer->InitWithFuncCallback(DisableTouchCaretCallback,
this,
TouchCaretExpirationTime(),
nsITimer::TYPE_ONE_SHOT);
}
}
}
void
TouchCaret::CancelExpirationTimer()
{
if (mTouchCaretExpirationTimer) {
mTouchCaretExpirationTimer->Cancel();
}
}
void
TouchCaret::SetSelectionDragState(bool aState)
{
nsIFrame* caretFocusFrame = GetCaretFocusFrame();
if (!caretFocusFrame) {
return;
}
nsRefPtr<nsFrameSelection> fs = caretFocusFrame->GetFrameSelection();
fs->SetDragState(aState);
}
nsEventStatus
TouchCaret::HandleEvent(WidgetEvent* aEvent)
{
MOZ_ASSERT(NS_IsMainThread());
if (!IsDisplayable()) {
return nsEventStatus_eIgnore;
}
nsEventStatus status = nsEventStatus_eIgnore;
switch (aEvent->message) {
case NS_TOUCH_START:
status = HandleTouchDownEvent(aEvent->AsTouchEvent());
break;
case NS_MOUSE_BUTTON_DOWN:
status = HandleMouseDownEvent(aEvent->AsMouseEvent());
break;
case NS_TOUCH_END:
status = HandleTouchUpEvent(aEvent->AsTouchEvent());
break;
case NS_MOUSE_BUTTON_UP:
status = HandleMouseUpEvent(aEvent->AsMouseEvent());
break;
case NS_TOUCH_MOVE:
status = HandleTouchMoveEvent(aEvent->AsTouchEvent());
break;
case NS_MOUSE_MOVE:
status = HandleMouseMoveEvent(aEvent->AsMouseEvent());
break;
case NS_TOUCH_CANCEL:
mTouchesId.Clear();
SetState(TOUCHCARET_NONE);
LaunchExpirationTimer();
break;
case NS_KEY_UP:
case NS_KEY_DOWN:
case NS_KEY_PRESS:
case NS_WHEEL_WHEEL:
case NS_WHEEL_START:
case NS_WHEEL_STOP:
// Disable touch caret while key/wheel event is received.
TOUCHCARET_LOG("Receive key/wheel event %d", aEvent->message);
SetVisibility(false);
break;
case NS_MOUSE_MOZLONGTAP:
if (mState == TOUCHCARET_TOUCHDRAG_ACTIVE) {
// Disable long tap event from APZ while dragging the touch caret.
status = nsEventStatus_eConsumeNoDefault;
}
break;
default:
break;
}
return status;
}
nsPoint
TouchCaret::GetEventPosition(WidgetTouchEvent* aEvent, int32_t aIdentifier)
{
for (size_t i = 0; i < aEvent->touches.Length(); i++) {
if (aEvent->touches[i]->mIdentifier == aIdentifier) {
// Get event coordinate relative to canvas frame.
nsIFrame* canvasFrame = GetCanvasFrame();
LayoutDeviceIntPoint touchIntPoint = aEvent->touches[i]->mRefPoint;
return nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent,
touchIntPoint,
canvasFrame);
}
}
return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
}
nsPoint
TouchCaret::GetEventPosition(WidgetMouseEvent* aEvent)
{
// Get event coordinate relative to canvas frame.
nsIFrame* canvasFrame = GetCanvasFrame();
LayoutDeviceIntPoint mouseIntPoint = aEvent->AsGUIEvent()->refPoint;
return nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent,
mouseIntPoint,
canvasFrame);
}
nsEventStatus
TouchCaret::HandleMouseMoveEvent(WidgetMouseEvent* aEvent)
{
TOUCHCARET_LOG("Got a mouse-move in state %d", mState);
nsEventStatus status = nsEventStatus_eIgnore;
switch (mState) {
case TOUCHCARET_NONE:
break;
case TOUCHCARET_MOUSEDRAG_ACTIVE:
{
nsPoint movePoint = GetEventPosition(aEvent);
movePoint.y += mCaretCenterToDownPointOffsetY;
nsRect contentBoundary = GetContentBoundary();
movePoint = contentBoundary.ClampPoint(movePoint);
MoveCaret(movePoint);
mIsValidTap = false;
status = nsEventStatus_eConsumeNoDefault;
}
break;
case TOUCHCARET_TOUCHDRAG_ACTIVE:
case TOUCHCARET_TOUCHDRAG_INACTIVE:
// Consume mouse event in touch sequence.
status = nsEventStatus_eConsumeNoDefault;
break;
}
return status;
}
nsEventStatus
TouchCaret::HandleTouchMoveEvent(WidgetTouchEvent* aEvent)
{
TOUCHCARET_LOG("Got a touch-move in state %d", mState);
nsEventStatus status = nsEventStatus_eIgnore;
switch (mState) {
case TOUCHCARET_NONE:
break;
case TOUCHCARET_MOUSEDRAG_ACTIVE:
// Consume touch event in mouse sequence.
status = nsEventStatus_eConsumeNoDefault;
break;
case TOUCHCARET_TOUCHDRAG_ACTIVE:
{
nsPoint movePoint = GetEventPosition(aEvent, mActiveTouchId);
movePoint.y += mCaretCenterToDownPointOffsetY;
nsRect contentBoundary = GetContentBoundary();
movePoint = contentBoundary.ClampPoint(movePoint);
MoveCaret(movePoint);
mIsValidTap = false;
status = nsEventStatus_eConsumeNoDefault;
}
break;
case TOUCHCARET_TOUCHDRAG_INACTIVE:
// Consume NS_TOUCH_MOVE event in TOUCHCARET_TOUCHDRAG_INACTIVE state.
status = nsEventStatus_eConsumeNoDefault;
break;
}
return status;
}
nsEventStatus
TouchCaret::HandleMouseUpEvent(WidgetMouseEvent* aEvent)
{
TOUCHCARET_LOG("Got a mouse-up in state %d", mState);
nsEventStatus status = nsEventStatus_eIgnore;
switch (mState) {
case TOUCHCARET_NONE:
break;
case TOUCHCARET_MOUSEDRAG_ACTIVE:
if (aEvent->button == WidgetMouseEvent::eLeftButton) {
SetSelectionDragState(false);
LaunchExpirationTimer();
SetState(TOUCHCARET_NONE);
status = nsEventStatus_eConsumeNoDefault;
}
break;
case TOUCHCARET_TOUCHDRAG_ACTIVE:
case TOUCHCARET_TOUCHDRAG_INACTIVE:
// Consume mouse event in touch sequence.
status = nsEventStatus_eConsumeNoDefault;
break;
}
return status;
}
nsEventStatus
TouchCaret::HandleTouchUpEvent(WidgetTouchEvent* aEvent)
{
TOUCHCARET_LOG("Got a touch-end in state %d", mState);
// Remove touches from cache if the stroke is gone in TOUCHDRAG states.
if (mState == TOUCHCARET_TOUCHDRAG_ACTIVE ||
mState == TOUCHCARET_TOUCHDRAG_INACTIVE) {
for (size_t i = 0; i < aEvent->touches.Length(); i++) {
nsTArray<int32_t>::index_type index =
mTouchesId.IndexOf(aEvent->touches[i]->mIdentifier);
MOZ_ASSERT(index != nsTArray<int32_t>::NoIndex);
mTouchesId.RemoveElementAt(index);
}
}
nsEventStatus status = nsEventStatus_eIgnore;
switch (mState) {
case TOUCHCARET_NONE:
break;
case TOUCHCARET_MOUSEDRAG_ACTIVE:
// Consume touch event in mouse sequence.
status = nsEventStatus_eConsumeNoDefault;
break;
case TOUCHCARET_TOUCHDRAG_ACTIVE:
if (mTouchesId.Length() == 0) {
SetSelectionDragState(false);
// No more finger on the screen.
SetState(TOUCHCARET_NONE);
LaunchExpirationTimer();
} else {
// Still has finger touching on the screen.
if (aEvent->touches[0]->mIdentifier == mActiveTouchId) {
// Remove finger from the touch caret.
SetState(TOUCHCARET_TOUCHDRAG_INACTIVE);
LaunchExpirationTimer();
} else {
// If the finger removed is not the finger on touch caret, remain in
// TOUCHCARET_DRAG_ACTIVE state.
}
}
status = nsEventStatus_eConsumeNoDefault;
break;
case TOUCHCARET_TOUCHDRAG_INACTIVE:
if (mTouchesId.Length() == 0) {
// No more finger on the screen.
SetState(TOUCHCARET_NONE);
}
status = nsEventStatus_eConsumeNoDefault;
break;
}
return status;
}
nsEventStatus
TouchCaret::HandleMouseDownEvent(WidgetMouseEvent* aEvent)
{
TOUCHCARET_LOG("Got a mouse-down in state %d", mState);
if (!GetVisibility()) {
// If touch caret is invisible, bypass event.
return nsEventStatus_eIgnore;
}
nsEventStatus status = nsEventStatus_eIgnore;
switch (mState) {
case TOUCHCARET_NONE:
if (aEvent->button == WidgetMouseEvent::eLeftButton) {
nsPoint point = GetEventPosition(aEvent);
if (IsOnTouchCaret(point)) {
SetSelectionDragState(true);
// Cache distence of the event point to the center of touch caret.
mCaretCenterToDownPointOffsetY = GetCaretYCenterPosition() - point.y;
// Enter TOUCHCARET_MOUSEDRAG_ACTIVE state and cancel the timer.
SetState(TOUCHCARET_MOUSEDRAG_ACTIVE);
CancelExpirationTimer();
status = nsEventStatus_eConsumeNoDefault;
} else {
// Mousedown events that miss HitTest can be caused by soft-keyboard
// auto-suggestions. If extended visibility, update the caret position.
if (sTouchcaretExtendedvisibility) {
UpdatePositionIfNeeded();
break;
}
// Set touch caret invisible if HisTest fails. Bypass event.
SetVisibility(false);
status = nsEventStatus_eIgnore;
}
} else {
// Set touch caret invisible if not left button down event.
SetVisibility(false);
status = nsEventStatus_eIgnore;
}
break;
case TOUCHCARET_MOUSEDRAG_ACTIVE:
SetVisibility(false);
SetState(TOUCHCARET_NONE);
break;
case TOUCHCARET_TOUCHDRAG_ACTIVE:
case TOUCHCARET_TOUCHDRAG_INACTIVE:
// Consume mouse event in touch sequence.
status = nsEventStatus_eConsumeNoDefault;
break;
}
return status;
}
nsEventStatus
TouchCaret::HandleTouchDownEvent(WidgetTouchEvent* aEvent)
{
TOUCHCARET_LOG("Got a touch-start in state %d", mState);
nsEventStatus status = nsEventStatus_eIgnore;
switch (mState) {
case TOUCHCARET_NONE:
if (!GetVisibility()) {
// If touch caret is invisible, bypass event.
status = nsEventStatus_eIgnore;
} else {
// Loop over all the touches and see if any of them is on the touch
// caret.
for (size_t i = 0; i < aEvent->touches.Length(); ++i) {
int32_t touchId = aEvent->touches[i]->Identifier();
nsPoint point = GetEventPosition(aEvent, touchId);
if (IsOnTouchCaret(point)) {
SetSelectionDragState(true);
// Touch start position is contained in touch caret.
mActiveTouchId = touchId;
// Cache distance of the event point to the center of touch caret.
mCaretCenterToDownPointOffsetY = GetCaretYCenterPosition() - point.y;
// Enter TOUCHCARET_TOUCHDRAG_ACTIVE state and cancel the timer.
SetState(TOUCHCARET_TOUCHDRAG_ACTIVE);
CancelExpirationTimer();
status = nsEventStatus_eConsumeNoDefault;
break;
}
}
// No touch is on the touch caret. Set touch caret invisible, and bypass
// the event.
if (mActiveTouchId == -1) {
// Check touch caret visibility style.
if (sTouchcaretExtendedvisibility) {
// Update position on events associated with scroll and pan-zoom.
UpdatePositionIfNeeded();
} else {
SetVisibility(false);
status = nsEventStatus_eIgnore;
}
}
}
break;
case TOUCHCARET_MOUSEDRAG_ACTIVE:
case TOUCHCARET_TOUCHDRAG_ACTIVE:
case TOUCHCARET_TOUCHDRAG_INACTIVE:
// Consume NS_TOUCH_START event.
status = nsEventStatus_eConsumeNoDefault;
break;
}
// Cache active touch IDs in TOUCHDRAG states.
if (mState == TOUCHCARET_TOUCHDRAG_ACTIVE ||
mState == TOUCHCARET_TOUCHDRAG_INACTIVE) {
mTouchesId.Clear();
for (size_t i = 0; i < aEvent->touches.Length(); i++) {
mTouchesId.AppendElement(aEvent->touches[i]->mIdentifier);
}
}
return status;
}
void
TouchCaret::DispatchTapEvent()
{
nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
if (!presShell) {
return;
}
nsRefPtr<nsCaret> caret = presShell->GetCaret();
if (!caret) {
return;
}
dom::Selection* sel = static_cast<dom::Selection*>(caret->GetSelection());
if (!sel) {
return;
}
nsIDocument* doc = presShell->GetDocument();
MOZ_ASSERT(doc);
dom::SelectionStateChangedEventInit init;
init.mBubbles = true;
// XXX: Do we need to flush layout?
presShell->FlushPendingNotifications(Flush_Layout);
nsRect rect = nsContentUtils::GetSelectionBoundingRect(sel);
nsRefPtr<dom::DOMRect>domRect = new dom::DOMRect(ToSupports(doc));
domRect->SetLayoutRect(rect);
init.mBoundingClientRect = domRect;
init.mVisible = false;
sel->Stringify(init.mSelectedText);
dom::Sequence<dom::SelectionState> state;
state.AppendElement(dom::SelectionState::Taponcaret, fallible);
init.mStates = state;
nsRefPtr<dom::SelectionStateChangedEvent> event =
dom::SelectionStateChangedEvent::Constructor(doc, NS_LITERAL_STRING("mozselectionstatechanged"), init);
event->SetTrusted(true);
event->GetInternalNSEvent()->mFlags.mOnlyChromeDispatch = true;
bool ret;
doc->DispatchEvent(event, &ret);
}
void
TouchCaret::SetState(TouchCaretState aState)
{
TOUCHCARET_LOG("state changed from %d to %d", mState, aState);
if (mState == TOUCHCARET_NONE) {
MOZ_ASSERT(aState != TOUCHCARET_TOUCHDRAG_INACTIVE,
"mState: NONE => TOUCHDRAG_INACTIVE isn't allowed!");
}
if (mState == TOUCHCARET_TOUCHDRAG_ACTIVE) {
MOZ_ASSERT(aState != TOUCHCARET_MOUSEDRAG_ACTIVE,
"mState: TOUCHDRAG_ACTIVE => MOUSEDRAG_ACTIVE isn't allowed!");
}
if (mState == TOUCHCARET_MOUSEDRAG_ACTIVE) {
MOZ_ASSERT(aState == TOUCHCARET_MOUSEDRAG_ACTIVE ||
aState == TOUCHCARET_NONE,
"MOUSEDRAG_ACTIVE allowed next state: NONE!");
}
if (mState == TOUCHCARET_TOUCHDRAG_INACTIVE) {
MOZ_ASSERT(aState == TOUCHCARET_TOUCHDRAG_INACTIVE ||
aState == TOUCHCARET_NONE,
"TOUCHDRAG_INACTIVE allowed next state: NONE!");
}
mState = aState;
if (mState == TOUCHCARET_NONE) {
mActiveTouchId = -1;
mCaretCenterToDownPointOffsetY = 0;
if (mIsValidTap) {
DispatchTapEvent();
mIsValidTap = false;
}
} else if (mState == TOUCHCARET_TOUCHDRAG_ACTIVE ||
mState == TOUCHCARET_MOUSEDRAG_ACTIVE) {
mIsValidTap = true;
}
}