Files
palemoon27/layout/base/TouchCaret.cpp
T
roytam1 a7ea204c19 import changes from `dev' branch of rmottola/Arctic-Fox:
- fix restore button on MacOS 10.6 fullscreen (08151078bd)
- Bug 1216580 - Make background-position:fixed clip stealing work with display items that create their own layer. r=mattwoodrow (7760c8ddd8)
- Bug 1222661 - Replace ToUntyped(obj) calls with obj.ToUnknown*(). r=nical (76e36824f0)
- Bug 1223310 (part 1) - Pass a LayoutDeviceIntPoint instead of an nsIntPoint to InitEvent(). r=kats. (ea95207afe)
- Bug 1223310 (part 2) - Use LayoutDeviceIntRect for bounds-related functions in nsIWidget. r=kats. (4993cccff8)
- Bug 1218552 - Fix GTK drag-and-drop coordinate scaling on HiDPI displays (r=karlt) (d03cf04136)
- fix preprocessing (2f4c1a1c94)
- Bug 1205923: Make VectorImage::GetWidth/GetHeight set outparam to 0 (not -1) on failure, to accomodate callers that don't check error codes. r=seth (487de31340)
- Bug 1212954 - Make BaseSize::IsEmpty return true for negative sizes to avoid NS_ERROR in VectorImage::GetFrameAtSize when root svg doesn't have intrinsic width or height. r=roc (402b154f56)
- fix ordering (ac8483c966)
- Bug 1216056 - Always sent OpRemoveTextureAsync as pending async Messages r=nica (66c9ee8708)
- Bug 121976 - Add a destruction handshake to PCompositable so as to avoid races between messages and the protocol. r=sotaro (636a669e44)
- Bug 1209724 - Cancel ImageBridge proxy functions if they are called too late. r=sotao (5bf81453ef)
- Bug 1221371 - Switch chromium IPC code to use mozilla::Tuple (r=jld,cpearce,kats) (eebcd419e9)
- Bug 1221371 - Remove unused Chromium callback code (r=jld) (3f2e882485)
- Bug 1221368 - Change MakeTuple to decay the types of its arguments (r=froydnj) (bed635f565)
- Bug 1181303. Set composition time earlier so that computing invalid regions works properly. r=mattwoodrow (7a3e41cd96)
- Bug 1150552 - Fix partial present bug with FPS counter. r=mattwoodrow (6e6ae92909)
- Bug 1216287 - Properly invalidate the debug overlay. r=mattwoodrow (60d4f3451a)
- Compute the compositor's damage region before composites, rather than layers updates. (bug 1217560, r=mattwoodrow) (6efcac085b)
- Bug 1216248 - Recomposite when the window overlay changes. r=mattwoodrow (8a8c5ccd07)
- Bug 1224021 - Support fractional async scroll offsets in the reftest harness. r=tn (353f75d3aa)
- Bug 1121072 - Always notify when layers cleared (r=dvander) (aa6ffc67e2)
- Bug 1224403 (part 1) - Make Configuration::mBounds a LayoutDeviceIntRect. r=kats. (7617b9da7e)
- Bug 1224403 (part 2) - Split GetClientOffset() into typed and untyped versions. r=kats. (06916479b1)
- Bug 1224403 (part 3) - Make mNonClientOffset a LayoutDeviceIntMargin. r=kats. (76efba1fb6)
- Bug 1224403 (part 4) - Make {Get,Set}NonClientMargins() return/take aLayoutDeviceIntMargin. r=kats. (99cfc2fdbc)
- Bug 1224403 (part 5) - Make OnDefaultButtonLoaded() return/take a LayoutDeviceIntMargin. r=kats (c402452f97)
- Bug 1224403 (part 6) - Remove one WidgetToScreenOffsetUntyped() call. r=kats. (8e4485a404)
- Bug 1224403 (part 7) - Use LayoutDeviceIntMargin more in IMMHandler. r=kats. (6b5e9124fc)
- Bug 1224403 (part 8) - Use LayoutDeviceIntMargin more in HyperTextAccessible. r=kats. (5bc1a04cc7)
- Bug 1224403 (part 9) - Remove GetRestoredBoundsUntyped(). r=kats. (20e2b28335)
- Bug 1224403 (part 10) - Make nsScreenGonk::GetNaturalBoundsUntyped() typed. r=kats. (9d19986f22)
- Bug 1224403 (part 11) - Remove a GetBoundsUntyped() call. r=kats. (58ce962157)
- Bug 1214267 - Followup to fix missing propagation of 'fixedPositionSides' to the compositor, and add it to the layers dump. r=mattwoodrow (7dc122eb62)
- Use compositor clip rects, not client clip rects, when computing the compositor's backbuffer damage region. (bug 1189159, r=mattwoodrow) (b70d71b1b7)
- Bug 1210784 - Layer tree invalidation with Preserves3D. r=roc (8a7bfa1a22)
- Bug 1166165 - Check if the timestamp is null to prevent assertion. r=nical (189a8d42d0)
- Bug 1137004 - Changed the location of the counter so when it is used in a maximized window it is no longer cut off, and added a little left padding. r=mattwoodrow (140d294445)
- Bug 1222661 - Replace FromUntyped(obj) calls with FromUnknown*(obj). r=nical (b399ccef2e)
- Bug 1216506. Properly initialize min inset box shadow color to black. r=mstange (335259200c)
- Bug 1223690 - Remove implicit Rect conversions. r=jrmuizel. (5aef25158f)
- Bug 1056356 - Allow calling NewRunnableMethod() with a const pointer to the callee object. r=froydnj (145279b91c)
- Bug 1209649. Part 2: Reftest to test box shadows and border radii. r=mstange (9405ccfe23)
- Bug 1211363. Part 2: reftest with int border radius. r=mstange (9edb37635e)
- Bug 1216200. Inset box shadow reftest with negative spread value. r=mstange (8350478107)
- Bug 1211264. Fallback to render dest rect with outer box shadows on non-int transforms. r=mstange (a244775d24)
- Bug 1216200. Correct for negative spread values with inset box shadows. r=mstange (4851e9c792)
- cleanup (1f78332ad8)
- Bug 1213970 - Enable APZ on iOS r=kats (1d170e32e3)
- Bug 1217939 - part 1 - remove nsContentUtils::GetViewportInfo; r=smaug (8c25236851)
- Bug 1217939 - part 2 - move nsContentUtils::GetSelectionBoundingRect to nsLayoutUtils; r=smaug (6beb37157c)
- Bug 1146713 - Don't export NO_PKG_FILES (fix ssltunnel in b2g); r=glandium (a21152c79c)
- Bug 1222323 - Avoid passing extra defines to the compiler from config.mk. r=gps (2d1076d513)
- Bug 1222321 - Avoid the duplication of OS_CPPFLAGS on the compiler command line. r=gps (cf762f1644)
- Bug 1210687 - Separate out jar.mn parsing in a separate class. r=gps (d54cc5576b)
- Bug 1219147 - Use addEntriesToListFile in mozbuild.jar.JarMaker.updateManifest. r=mshal (3cd74ff478)
- Bug 1208160 - Show information when Xcode requires accepting license agreement; r=gps (352bb43d73)
- Bug 1174524 - Make objdir path comparison case-insensitive on Windows. r=mshal (6f74f10845)
- Bug 1158898 - Cache config.guess output; r=mshal (a7b01184fa)
- Bug 1187245 - Make .mozconfig.mk environment variables available to mach valgrind-test. r=gps (2033642ceb)
2022-12-20 12:06:53 +08:00

1206 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, LogLevel::Debug, \
("TouchCaret (%p): %s:%d : " message "\n", this, __FUNCTION__, \
__LINE__, ##__VA_ARGS__));
#define TOUCHCARET_LOG_STATIC(message, ...) \
MOZ_LOG(gTouchCaretLog, LogLevel::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()
{
RefPtr<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;
}
RefPtr<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 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.
RefPtr<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;");
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;
RefPtr<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;
}
RefPtr<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)
{
RefPtr<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;
}
RefPtr<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);
nsPoint pos = GetTouchCaretPosition();
pos = ClampPositionToScrollFrame(pos);
SetTouchFramePos(pos);
}
nsPoint
TouchCaret::GetTouchCaretPosition()
{
nsRect focusRect;
nsIFrame* focusFrame = GetCaretFocusFrame(&focusRect);
nsIFrame* rootFrame = GetRootFrame();
// 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 root frame.
nsLayoutUtils::TransformPoint(focusFrame, rootFrame, pos);
return pos;
}
nsPoint
TouchCaret::ClampPositionToScrollFrame(const nsPoint& aPosition)
{
nsPoint pos = aPosition;
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);
pos = visualRect.ClampPoint(pos);
// Get next ancestor scroll frame.
closestScrollFrame =
nsLayoutUtils::GetClosestFrameOfType(closestScrollFrame->GetParent(),
nsGkAtoms::scrollFrame);
}
return pos;
}
/* static */void
TouchCaret::DisableTouchCaretCallback(nsITimer* aTimer, void* aTouchCaret)
{
RefPtr<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;
}
RefPtr<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->mMessage) {
case eTouchStart:
status = HandleTouchDownEvent(aEvent->AsTouchEvent());
break;
case eMouseDown:
status = HandleMouseDownEvent(aEvent->AsMouseEvent());
break;
case eTouchEnd:
status = HandleTouchUpEvent(aEvent->AsTouchEvent());
break;
case eMouseUp:
status = HandleMouseUpEvent(aEvent->AsMouseEvent());
break;
case eTouchMove:
status = HandleTouchMoveEvent(aEvent->AsTouchEvent());
break;
case eMouseMove:
status = HandleMouseMoveEvent(aEvent->AsMouseEvent());
break;
case eTouchCancel:
mTouchesId.Clear();
SetState(TOUCHCARET_NONE);
LaunchExpirationTimer();
break;
case eKeyUp:
case eKeyDown:
case eKeyPress:
case eWheel:
case eWheelOperationStart:
case eWheelOperationEnd:
// Disable touch caret while key/wheel event is received.
TOUCHCARET_LOG("Receive key/wheel event %d", aEvent->mMessage);
SetVisibility(false);
break;
case eMouseLongTap:
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 eTouchMove 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 eTouchStart 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;
}
RefPtr<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 = nsLayoutUtils::GetSelectionBoundingRect(sel);
RefPtr<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;
RefPtr<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;
}
}