2014-06-03 00:08:45 -07:00
|
|
|
/* -*- 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 "TouchCaret.h"
|
|
|
|
|
|
|
|
#include "nsCOMPtr.h"
|
|
|
|
#include "nsFrameSelection.h"
|
|
|
|
#include "nsIFrame.h"
|
|
|
|
#include "nsIScrollableFrame.h"
|
|
|
|
#include "nsIDOMNode.h"
|
|
|
|
#include "nsISelection.h"
|
|
|
|
#include "nsISelectionPrivate.h"
|
|
|
|
#include "nsIContent.h"
|
|
|
|
#include "nsIPresShell.h"
|
|
|
|
#include "nsCanvasFrame.h"
|
|
|
|
#include "nsRenderingContext.h"
|
|
|
|
#include "nsPresContext.h"
|
|
|
|
#include "nsBlockFrame.h"
|
|
|
|
#include "nsISelectionController.h"
|
|
|
|
#include "mozilla/Preferences.h"
|
|
|
|
#include "mozilla/BasicEvents.h"
|
|
|
|
#include "nsIDOMWindow.h"
|
|
|
|
#include "nsQueryContentEventResult.h"
|
|
|
|
#include "nsIInterfaceRequestorUtils.h"
|
|
|
|
#include "nsView.h"
|
|
|
|
#include "nsDOMTokenList.h"
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
using namespace mozilla;
|
|
|
|
|
2014-07-13 12:29:00 -07:00
|
|
|
// To enable all the TOUCHCARET_LOG print statements, change the 0 to 1 in the
|
|
|
|
// following #define.
|
|
|
|
#define ENABLE_TOUCHCARET_LOG 0
|
|
|
|
|
|
|
|
#if ENABLE_TOUCHCARET_LOG
|
|
|
|
#define TOUCHCARET_LOG(message, ...) \
|
|
|
|
printf_stderr("TouchCaret (%p): %s:%d : " message "\n", this, __func__, __LINE__, ##__VA_ARGS__);
|
|
|
|
#else
|
|
|
|
#define TOUCHCARET_LOG(message, ...)
|
|
|
|
#endif
|
2014-06-03 00:08:45 -07:00
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
|
|
|
/*static*/ int32_t TouchCaret::sTouchCaretMaxDistance = 0;
|
|
|
|
/*static*/ int32_t TouchCaret::sTouchCaretExpirationTime = 0;
|
|
|
|
|
|
|
|
TouchCaret::TouchCaret(nsIPresShell* aPresShell)
|
|
|
|
: mState(TOUCHCARET_NONE),
|
|
|
|
mActiveTouchId(-1),
|
|
|
|
mCaretCenterToDownPointOffsetY(0),
|
|
|
|
mVisible(false)
|
|
|
|
{
|
2014-07-31 10:29:30 -07:00
|
|
|
TOUCHCARET_LOG("Constructor");
|
2014-06-03 00:08:45 -07:00
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
|
|
|
|
static bool addedTouchCaretPref = false;
|
|
|
|
if (!addedTouchCaretPref) {
|
|
|
|
Preferences::AddIntVarCache(&sTouchCaretMaxDistance,
|
|
|
|
"touchcaret.distance.threshold");
|
|
|
|
Preferences::AddIntVarCache(&sTouchCaretExpirationTime,
|
|
|
|
"touchcaret.expiration.time");
|
|
|
|
addedTouchCaretPref = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The presshell owns us, so no addref.
|
|
|
|
mPresShell = do_GetWeakReference(aPresShell);
|
|
|
|
MOZ_ASSERT(mPresShell, "Hey, pres shell should support weak refs");
|
|
|
|
}
|
|
|
|
|
|
|
|
TouchCaret::~TouchCaret()
|
|
|
|
{
|
2014-07-13 12:29:00 -07:00
|
|
|
TOUCHCARET_LOG("Destructor");
|
2014-06-03 00:08:45 -07:00
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
|
|
|
|
if (mTouchCaretExpirationTimer) {
|
|
|
|
mTouchCaretExpirationTimer->Cancel();
|
|
|
|
mTouchCaretExpirationTimer = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
nsIFrame*
|
|
|
|
TouchCaret::GetCanvasFrame()
|
|
|
|
{
|
|
|
|
nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
|
|
|
|
if (!presShell) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
return presShell->GetCanvasFrame();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
TouchCaret::SetVisibility(bool aVisible)
|
|
|
|
{
|
|
|
|
if (mVisible == aVisible) {
|
2014-07-31 10:29:30 -07:00
|
|
|
TOUCHCARET_LOG("Visibility not changed");
|
2014-06-03 00:08:45 -07:00
|
|
|
return;
|
|
|
|
}
|
2014-07-13 12:29:00 -07:00
|
|
|
|
2014-07-31 10:29:30 -07:00
|
|
|
mVisible = aVisible;
|
|
|
|
|
2014-06-03 00:08:45 -07:00
|
|
|
nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
|
|
|
|
if (!presShell) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
mozilla::dom::Element* touchCaretElement = presShell->GetTouchCaretElement();
|
|
|
|
if (!touchCaretElement) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set touch caret visibility.
|
|
|
|
ErrorResult err;
|
|
|
|
touchCaretElement->ClassList()->Toggle(NS_LITERAL_STRING("hidden"),
|
|
|
|
dom::Optional<bool>(!mVisible),
|
|
|
|
err);
|
2014-07-31 10:29:30 -07:00
|
|
|
TOUCHCARET_LOG("Visibility %s", (mVisible ? "shown" : "hidden"));
|
2014-07-13 12:29:00 -07:00
|
|
|
|
2014-06-03 00:08:45 -07:00
|
|
|
// Set touch caret expiration time.
|
|
|
|
mVisible ? LaunchExpirationTimer() : CancelExpirationTimer();
|
2014-06-03 00:08:54 -07:00
|
|
|
|
2014-07-31 10:29:30 -07:00
|
|
|
// We must call SetHasTouchCaret() in order to get APZC to wait until the
|
2014-06-03 00:08:54 -07:00
|
|
|
// event has been round-tripped and check whether it has been handled,
|
|
|
|
// otherwise B2G will end up panning the document when the user tries to drag
|
|
|
|
// touch caret.
|
|
|
|
presShell->SetMayHaveTouchCaret(mVisible);
|
2014-06-03 00:08:45 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
nsRect
|
|
|
|
TouchCaret::GetTouchFrameRect()
|
|
|
|
{
|
|
|
|
nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
|
|
|
|
if (!presShell) {
|
|
|
|
return nsRect();
|
|
|
|
}
|
|
|
|
|
|
|
|
dom::Element* touchCaretElement = presShell->GetTouchCaretElement();
|
|
|
|
if (!touchCaretElement) {
|
|
|
|
return nsRect();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get touch caret position relative to canvas frame.
|
|
|
|
nsIFrame* touchCaretFrame = touchCaretElement->GetPrimaryFrame();
|
|
|
|
nsRect tcRect = touchCaretFrame->GetRectRelativeToSelf();
|
|
|
|
nsIFrame* canvasFrame = GetCanvasFrame();
|
|
|
|
|
|
|
|
nsLayoutUtils::TransformResult rv =
|
|
|
|
nsLayoutUtils::TransformRect(touchCaretFrame, canvasFrame, tcRect);
|
|
|
|
return rv == nsLayoutUtils::TRANSFORM_SUCCEEDED ? tcRect : nsRect();
|
|
|
|
}
|
|
|
|
|
|
|
|
nsRect
|
|
|
|
TouchCaret::GetContentBoundary()
|
|
|
|
{
|
|
|
|
nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
|
|
|
|
if (!presShell) {
|
|
|
|
return nsRect();
|
|
|
|
}
|
|
|
|
|
|
|
|
nsRefPtr<nsCaret> caret = presShell->GetCaret();
|
|
|
|
nsISelection* caretSelection = caret->GetCaretDOMSelection();
|
|
|
|
nsRect focusRect;
|
|
|
|
nsIFrame* focusFrame = caret->GetGeometry(caretSelection, &focusRect);
|
|
|
|
nsIFrame* canvasFrame = GetCanvasFrame();
|
|
|
|
|
|
|
|
// 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()
|
|
|
|
{
|
|
|
|
nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
|
|
|
|
if (!presShell) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsRefPtr<nsCaret> caret = presShell->GetCaret();
|
|
|
|
nsISelection* caretSelection = caret->GetCaretDOMSelection();
|
|
|
|
nsRect focusRect;
|
|
|
|
nsIFrame* focusFrame = caret->GetGeometry(caretSelection, &focusRect);
|
|
|
|
nsRect caretRect = focusFrame->GetRectRelativeToSelf();
|
|
|
|
nsIFrame *canvasFrame = GetCanvasFrame();
|
|
|
|
nsLayoutUtils::TransformRect(focusFrame, canvasFrame, caretRect);
|
|
|
|
|
|
|
|
return (caretRect.y + caretRect.height / 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
TouchCaret::SetTouchFramePos(const nsPoint& aOrigin)
|
|
|
|
{
|
|
|
|
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(aOrigin.x);
|
|
|
|
int32_t y = presContext->AppUnitsToIntCSSPixels(aOrigin.y);
|
|
|
|
|
|
|
|
nsAutoString styleStr;
|
|
|
|
styleStr.AppendLiteral("left: ");
|
|
|
|
styleStr.AppendInt(x);
|
|
|
|
styleStr.AppendLiteral("px; top: ");
|
|
|
|
styleStr.AppendInt(y);
|
|
|
|
styleStr.AppendLiteral("px;");
|
|
|
|
|
2014-07-13 12:29:00 -07:00
|
|
|
TOUCHCARET_LOG("Set style: %s", NS_ConvertUTF16toUTF8(styleStr).get());
|
|
|
|
|
2014-06-03 00:08:45 -07:00
|
|
|
touchCaretElement->SetAttr(kNameSpaceID_None, nsGkAtoms::style,
|
|
|
|
styleStr, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
TouchCaret::MoveCaret(const nsPoint& movePoint)
|
|
|
|
{
|
|
|
|
nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
|
|
|
|
if (!presShell) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get scrollable frame.
|
|
|
|
nsRefPtr<nsCaret> caret = presShell->GetCaret();
|
|
|
|
nsISelection* caretSelection = caret->GetCaretDOMSelection();
|
|
|
|
nsRect focusRect;
|
|
|
|
nsIFrame* focusFrame = caret->GetGeometry(caretSelection, &focusRect);
|
|
|
|
nsIFrame* scrollable =
|
|
|
|
nsLayoutUtils::GetClosestFrameOfType(focusFrame, nsGkAtoms::scrollFrame);
|
|
|
|
|
|
|
|
// Convert touch/mouse position to frame coordinates.
|
|
|
|
nsIFrame* canvasFrame = GetCanvasFrame();
|
|
|
|
if (!canvasFrame) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
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.associateWithNext);
|
|
|
|
|
|
|
|
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;
|
2014-06-04 13:57:00 -07:00
|
|
|
fs->StartAutoScrollTimer(capturingFrame, pt, sAutoScrollTimerDelay);
|
2014-06-03 00:08:45 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
TouchCaret::IsOnTouchCaret(const nsPoint& aPoint)
|
|
|
|
{
|
|
|
|
// Return false if touch caret is not visible.
|
|
|
|
if (!mVisible) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsRect tcRect = GetTouchFrameRect();
|
|
|
|
|
|
|
|
// Check if the click was in the bounding box of the touch caret.
|
|
|
|
int32_t distance;
|
|
|
|
if (tcRect.Contains(aPoint.x, aPoint.y)) {
|
|
|
|
distance = 0;
|
|
|
|
} else {
|
|
|
|
// If click is outside the bounding box of the touch caret, check the
|
|
|
|
// distance to the center of the touch caret.
|
|
|
|
int32_t posX = (tcRect.x + (tcRect.width / 2));
|
|
|
|
int32_t posY = (tcRect.y + (tcRect.height / 2));
|
|
|
|
int32_t dx = Abs(aPoint.x - posX);
|
|
|
|
int32_t dy = Abs(aPoint.y - posY);
|
|
|
|
distance = dx + dy;
|
|
|
|
}
|
|
|
|
return (distance <= TouchCaretMaxDistance());
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
|
|
|
TouchCaret::NotifySelectionChanged(nsIDOMDocument* aDoc, nsISelection* aSel,
|
2014-07-31 10:29:30 -07:00
|
|
|
int16_t aReason)
|
2014-06-03 00:08:45 -07:00
|
|
|
{
|
|
|
|
// 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->GetCaretDOMSelection()) {
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update touch caret position and visibility.
|
|
|
|
// Hide touch caret while key event causes selection change.
|
2014-07-31 10:29:30 -07:00
|
|
|
if ((aReason == nsISelectionListener::NO_REASON) ||
|
|
|
|
(aReason & nsISelectionListener::KEYPRESS_REASON)) {
|
|
|
|
UpdateTouchCaret(false);
|
2014-06-03 00:08:45 -07:00
|
|
|
} else {
|
2014-07-31 10:29:30 -07:00
|
|
|
UpdateTouchCaret(true);
|
2014-06-03 00:08:45 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2014-07-31 10:29:30 -07:00
|
|
|
TouchCaret::UpdateTouchCaret(bool aVisible)
|
2014-06-03 00:08:45 -07:00
|
|
|
{
|
2014-07-31 10:29:30 -07:00
|
|
|
// Hide touch caret while no caret exists.
|
2014-06-03 00:08:45 -07:00
|
|
|
nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
|
|
|
|
if (!presShell) {
|
2014-07-31 10:29:30 -07:00
|
|
|
return;
|
2014-06-03 00:08:45 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
nsRefPtr<nsCaret> caret = presShell->GetCaret();
|
|
|
|
if (!caret) {
|
2014-07-31 10:29:30 -07:00
|
|
|
SetVisibility(false);
|
|
|
|
return;
|
2014-06-03 00:08:45 -07:00
|
|
|
}
|
|
|
|
|
2014-07-31 10:29:30 -07:00
|
|
|
// Hide touch caret while caret is not visible.
|
2014-06-03 00:08:45 -07:00
|
|
|
bool caretVisible = false;
|
|
|
|
caret->GetCaretVisible(&caretVisible);
|
|
|
|
if (!caretVisible) {
|
2014-07-31 10:29:30 -07:00
|
|
|
TOUCHCARET_LOG("Caret is not visible");
|
|
|
|
SetVisibility(false);
|
2014-07-26 15:15:00 -07:00
|
|
|
return;
|
|
|
|
}
|
2014-07-31 10:29:30 -07:00
|
|
|
|
|
|
|
// Caret is visible and shown, update touch caret.
|
2014-06-03 00:08:45 -07:00
|
|
|
nsISelection* caretSelection = caret->GetCaretDOMSelection();
|
|
|
|
nsRect focusRect;
|
|
|
|
nsIFrame* focusFrame = caret->GetGeometry(caretSelection, &focusRect);
|
|
|
|
if (!focusFrame || focusRect.IsEmpty()) {
|
2014-07-31 10:29:30 -07:00
|
|
|
TOUCHCARET_LOG("Focus frame not valid");
|
|
|
|
SetVisibility(false);
|
2014-06-03 00:08:45 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Position of the touch caret relative to focusFrame.
|
|
|
|
nsPoint pos = nsPoint(focusRect.x + (focusRect.width / 2),
|
|
|
|
focusRect.y + focusRect.height);
|
|
|
|
|
|
|
|
// Transform the position to make it relative to canvas frame.
|
|
|
|
nsIFrame* canvasFrame = GetCanvasFrame();
|
|
|
|
if (!canvasFrame) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
nsLayoutUtils::TransformPoint(focusFrame, canvasFrame, pos);
|
|
|
|
|
|
|
|
// 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, canvasFrame, visualRect);
|
|
|
|
pos = visualRect.ClampPoint(pos);
|
|
|
|
|
|
|
|
// Get next ancestor scroll frame.
|
|
|
|
closestScrollFrame =
|
|
|
|
nsLayoutUtils::GetClosestFrameOfType(closestScrollFrame->GetParent(),
|
|
|
|
nsGkAtoms::scrollFrame);
|
|
|
|
}
|
|
|
|
|
|
|
|
SetTouchFramePos(pos);
|
2014-07-31 10:29:30 -07:00
|
|
|
SetVisibility(aVisible);
|
2014-06-03 00:08:45 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/* 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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
nsEventStatus
|
|
|
|
TouchCaret::HandleEvent(WidgetEvent* aEvent)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
|
|
|
|
if (!presShell) {
|
|
|
|
return nsEventStatus_eIgnore;
|
|
|
|
}
|
|
|
|
|
|
|
|
mozilla::dom::Element* touchCaretElement = presShell->GetTouchCaretElement();
|
|
|
|
if (!touchCaretElement) {
|
|
|
|
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_EVENT_START:
|
|
|
|
// Disable touch caret while key/wheel event is received.
|
2014-07-13 12:29:00 -07:00
|
|
|
TOUCHCARET_LOG("Receive key/wheel event");
|
2014-06-03 00:08:45 -07:00
|
|
|
SetVisibility(false);
|
|
|
|
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();
|
|
|
|
nsIntPoint 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();
|
|
|
|
nsIntPoint mouseIntPoint =
|
|
|
|
LayoutDeviceIntPoint::ToUntyped(aEvent->AsGUIEvent()->refPoint);
|
|
|
|
return nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent,
|
|
|
|
mouseIntPoint,
|
|
|
|
canvasFrame);
|
|
|
|
}
|
|
|
|
|
|
|
|
nsEventStatus
|
|
|
|
TouchCaret::HandleMouseMoveEvent(WidgetMouseEvent* aEvent)
|
|
|
|
{
|
2014-07-13 12:29:00 -07:00
|
|
|
TOUCHCARET_LOG("Got a mouse-move in state %d", mState);
|
2014-06-03 00:08:45 -07:00
|
|
|
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);
|
|
|
|
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)
|
|
|
|
{
|
2014-07-13 12:29:00 -07:00
|
|
|
TOUCHCARET_LOG("Got a touch-move in state %d", mState);
|
2014-06-03 00:08:45 -07:00
|
|
|
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);
|
|
|
|
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)
|
|
|
|
{
|
2014-07-13 12:29:00 -07:00
|
|
|
TOUCHCARET_LOG("Got a mouse-up in state %d", mState);
|
2014-06-03 00:08:45 -07:00
|
|
|
nsEventStatus status = nsEventStatus_eIgnore;
|
|
|
|
|
|
|
|
switch (mState) {
|
|
|
|
case TOUCHCARET_NONE:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TOUCHCARET_MOUSEDRAG_ACTIVE:
|
|
|
|
if (aEvent->button == WidgetMouseEvent::eLeftButton) {
|
|
|
|
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)
|
|
|
|
{
|
2014-07-13 12:29:00 -07:00
|
|
|
TOUCHCARET_LOG("Got a touch-end in state %d", mState);
|
2014-06-03 00:08:45 -07:00
|
|
|
// 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) {
|
|
|
|
// 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)
|
|
|
|
{
|
2014-07-13 12:29:00 -07:00
|
|
|
TOUCHCARET_LOG("Got a mouse-down in state %d", mState);
|
2014-06-03 00:08:45 -07:00
|
|
|
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)) {
|
|
|
|
// 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 {
|
|
|
|
// 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)
|
|
|
|
{
|
2014-07-13 12:29:00 -07:00
|
|
|
TOUCHCARET_LOG("Got a touch-start in state %d", mState);
|
2014-06-03 00:08:45 -07:00
|
|
|
|
|
|
|
nsEventStatus status = nsEventStatus_eIgnore;
|
|
|
|
|
|
|
|
switch (mState) {
|
|
|
|
case TOUCHCARET_NONE:
|
|
|
|
if (!GetVisibility()) {
|
|
|
|
// If touch caret is invisible, bypass event.
|
|
|
|
status = nsEventStatus_eIgnore;
|
|
|
|
} else {
|
2014-06-12 19:58:00 -07:00
|
|
|
// 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)) {
|
|
|
|
// 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) {
|
2014-06-03 00:08:45 -07:00
|
|
|
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::SetState(TouchCaretState aState)
|
|
|
|
{
|
2014-07-13 12:29:00 -07:00
|
|
|
TOUCHCARET_LOG("state changed from %d to %d", mState, aState);
|
2014-06-03 00:08:45 -07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|