Files
palemoon27/layout/base/AccessibleCaretManager.cpp
T
roytam1 8a0a002cf2 import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1148708: Add missing 'override' annotations in DocAccessibleChild.h. rs=ehsan (d606358545)
- Bug 1210408 - make nsMaiInterfaceAction work with proxies, r=tbsaunde (f7c819c6ae)
- Bug 1210407 - teach nsMaiInterfaceTable to use proxies, r=tbsaunde (4ca4f10b5f)
- bug 1185157 make sure we don't send an event to a destroyed ipc document r=billm (23acf53f75)
- bug 1214864 - make SetCarretOffset() async r=davidb (e3079e9b2d)
- missing of Bug 1139972 - IPC Proxy for charAt, r=tbsaunde (e9593ed752)
- bug 1191598 - Pass MOZ_CURRENT_PROJECT in environment when running post-build automation steps for universal mac builds. r=gps (fc342c6ced)
- Bug 1164596 - Add mach android-emulator command; r=ahal (afeb9b27d1)
- Bug 1223149 - Add basic usage documentation for mach build; r=glandium (bfb802d175)
- Bug 1182301 - Improve 'mach build' notifications. r=gps (2c65a122d1)
- Bug 1184696 - Add clobber targets to |mach clobber|; Ability to clobber compiled python files, r=gps (35d8be292e)
- Bug 1117958 - Allow any debugging options to the run or gtest mach subcommands to automatically enable debugging. r=gps (32f986af4b)
- Bug 1180081 - Properly rebuild gtest/libxul before running gtests. r=gps (80db9a3d49)
- Bug 1171647, part 1 - Define a new function to convert the mode to a string. r=njn (61ad16f5ba)
- Bug 1171647, part 2 - Remove redundant assertion for dark matter mode. r=njn (b5ac9519f3)
- Bug 1058178, part 1 - Implement DMD heap scanning mode. r=njn (60e1079536)
- Bug 1058178, part 2 - Implement address clamping analysis for DMD scan logs. r=njn (45c0326b93)
- Bug 1102388 - Fix DMD static constructor ordering dependency. r=mccr8 (59b87897a1)
- Bug 1128705 - Don't redefine PAGE_SIZE in DMD if it's already defined. r=erahm (49216348ee)
- Bug 1179042 - Add a script for analyzing memory blocks using a heap scan DMD log. r=njn DONTBUILD (1c08d2d66e)
- Bug 1207245 - part 6 - rename nsRefPtr<T> to RefPtr<T>; r=ehsan; a=Tomcat (1c999d139b)
- Bug 1158772 - fix non-idiomatic memset call in nsDeque.cpp; r=erahm (de6b555245)
- Bug 1199400 - Part 1: Use CheckedInt when growing nsDeque capacity. r=froydnj (dfdf6814a3)
- Bug 1199400 - Part 0: Remove unused nsDequeIterator. r=froydnj (38d69d7f47)
- Bug 1199400 - Part 2: Add tests for possible nsDeque corner cases. r=froydnj (931baff195)
- Bug 1201997 - Part 1 - Converted compiled test to gtest for nsDeque class. Added tests to test untested methods. r=froydn (e893916651)
- Bug 1201997 - Part 2 - Removing unused methods from the nsDeque class. r=froydn (41595a90ac)
- Bug 1201997 - Part 3 - Make internally used methods private. r=froydn (8cd3afd96f)
- Bug 1201997 - Part 4 - Change size and offset variables to size_t.r=froydn (73eabc8d60)
- Bug 1215140 P1 Add an nsIConsoleReportCollector interface to support navigation channel logging. r=bz (8a41535e2b)
- Bug 1215140 P2 Make HttpBaseChannel implement nsIConsoleReportCollector. r=bz (75fca301f2)
- Bug 1197679 - If nsUnknownDecoder is involved in e10s DivertToParent can break. r=jduell (5d94a12504)
- Bug 1178991 - smartptr for http converter r=hurley (8e7fbc8443)
- bug 366559 - patch 2, fix nsHTTPCompressConv indentation r=bagder (ba762da587)
- bug 366559 - patch 3, fix nsHTTPCompressConv bracing style r=bagder (54195ab451)
- bits of  bug 366559 - patch 7, content-encoding brotli for http (f0b4051022)
- Bug 1205112 - Make PushEvent.data nullable. r=mt,smaug (775db32856)
- Bug 1193414 - SharedWorkers thread should be kept alive also when the SharedWorker object is CCed, r=khuey (b77ea8125c)
- Bug 1206520: Add about:config prefs to enable throwing on asm.js validation failures; r=bz (c42126665d)
- Bug 1193414 - Telemetry for SharedWorker spawning. r=bkelly (77984b7bcc)
- Bug 1205676 - Enable WPT service-worker/unregister-then-register-new-script.https.html in e10s, r=nsm (ec24939cf6)
- Bug 1193133 - Throw when calling postMessage from a Service Worker dom object with no global. r=bkelly (526dcacfab)
- Bug 1181871 P1 Only enforce Cache Context shared data destruction on target thread after init. r=ehsan (cdbf3ed3a8)
- Bug 1181871 P2 Fix ServiceWorkerManager usage of stack-based ErrorResult. r=ehsan (c449195d90)
- minor cleanup and missing bit of 1198230 (02f459db05)
- Bug 1143717 - Implement the ServiceWorkerMessageEvent interface. r=baku (027b3465f2)
- fix misspatch (708eee4e84)
- Bug 1188545 - Disentangle service workers from shared workers and refactor event dispatching code into a separate class. r=nsm,mrbkap (fb5b5341c9)
- Bug 1205228 - Change PackagedAppVerifier to notify the verification result asynchronously. r=valentin. (9edda0fa00)
- Bug 1178518 - Packaged App Utils. r=valentin (f60f3b7a93)
- Bug 1213150 - Part 1: Add a nsContentUtils::IsNonSubresourceRequest helper; r=jdm (b509cc3cc9)
- Bug 1213150 - Part 2: Rework ShouldPrepareForIntercept() in terms of subresource requests; r=jdm (2e92fe8780)
- Bug 1213150 - Part 3: Remove nsIInterceptedChannel.isNavigation; r=jdm (becf1cc12f)
- Bug 1213150 follow-up: fix build bustage (8d73d6ca73)
- Bug 1198394 - Part 1: Allow interception of HSTS upgraded connections in non-e10s mode; r=mcmanus (f504c5be08)
- Bug 1198394 - Part 2: Add a test for interception of HSTS upgraded connections; r=jdm (054e984eef)
- Bug 1187011 - Don't allow response body with null body status. r=bkelly (b1860741d1)
- missing bit of 1140788 (29d319712e)
- Bug 1213436 - Reject core dumps with node IDs that don't fit in an IEEE 754 double; r=sfink (3c1f6fdda0)
- Bug 1211006 - Add Debugger.Source.prototype.canonicalId; r=ejpbruel (eef7b79fce)
- Bug 1199218 - Implement JS::ubi::Node::size for js::LazyScript referents; r=sfink (098a48d240)
- Bug 1220031 - Add JS::ubi::Node::scriptFilename; r=sfink (6b824ae680)
- Bug 1143575. Remove unused MediaQueue::Empty. r=cpearce (de737f3433)
- Bug 1209933 - Make sure all parent runtime pointers are the topmost parent, r=billm. (fe824d967d)
- Bug 1197012 - Fix ThrowTypeError in Notification. r=mccr8 (0b1a097526)
- Bug 1197893 - Check the number of arguments for ThrowTypeError() and ThrowRangeError() at compile time. r=peterv (d98c7d78a0)
- Bug 1142083 - Add test for IDN Unicode domain redirect. r=mcmanus (0c8961fe17)
- Bug 1187159 - Add mochitest for loading packaged apps (iframe+fetch+mozapp) r=jduell (ce90ea561b)
- Bug 1186290 - Notify TabParent to switch process when loading a signed package. r=honzab, r=kanru. (c58a14554a)
- fix (15e2df75eb)
- Bug 1206124 P1 Fix "same-origin" CORS credentials in FetchDriver. r=ehsan (fae1bb6ab3)
- Bug 1206124 P2 Test fetch() with credentials and redirects. r=ehsan (ffc6254112)
- Bug 1211751: Remove nsIChannelEventSink-forwarding from EventSource and FetchDriver. It's never needed. r=smaug (adafe5737a)
- Bug 1212433 Fail fetch() calls that require preflight and also redirect. r=sicking a=abillings (c0d6742b9e)
- Bug 1193128 - Fix base64 decoding when fetching data URIs. r=baku (80bafa291a)
- Bug 1195167 part 1: Let necko handle all protocols. r=bkelly (bb932b0ada)
- Bug 1195167 part 2: Remove redundant aCORSFlag argument and instead use mCORSFlagEverSet. r=bkelly (beadafcad0)
- Bug 1195167 part 3: Remove more scheme-specific handling from FetchDriver. r=bkelly (d00b38db9e)
- Bug 1195167 part 4: Remove FetchDriver::BasicFetch since it is empty. r=bkelly (c5ed097267)
- Bug 1210413 P2 Test CORS credentials on cross-origin redirects. r=sicking a=dveditz (b4eeb8aac0)
- Bug 1210413 P1 Propagate new channel load flags from child to parent on redirect. r=jduell a=dveditz (8b329af4fa)
- Bug 1195167 part 5: Make FetchDriver use AsyncOpen2. r=bkelly (cc217c4cc1)
- Bug 1195167 part 6: Some code simplification since necko handles fetch recursion. r=bkelly (f3b6da2262)
- Bug 1195167: Followup to fix test which I forgot to change (81e7439a2e)
- Bug 1215746: Remove RequestMode::Cors_with_forced_preflight. r=bkelly (0336e812b6)
- Bug 1211000: Move CORS preflight logic from nsCORSListenerProxy to nsCORSPreflightListener. r=ehsan (bf2f71cf22)
- missing bit of Bug 1211443 - Drop scheduled update if decoder initialization isn't done yet. r=jya (f6bc074e33)
- Bug 1182571: Fix nsILoadInfo->GetContentPolicyType API to be less ambigious. Audit and fix all users of it. r=ckerschb (5af6fa7442)
- fix (e40c8e7625)
- Bug 1173811 - Part 1: Propagate the response URL to intercepted channels when necessary (non-e10s). r=mayhemer,bkelly (26f4f13c28)
- Bug 1173811 - Part 2: Propagate the response URL to intercepted channels when necessary (e10s). r=mayhemer,bkelly (a603fe1df2)
- Bug 1154309 - Add New Resource Timing Fields r=bz,hurley (1d14eb6bef)
- Bug 1175685 - add OriginAttribute to LoadInfo. r=jonas, r=ckerschb, r=michal (a5d18bb637)
- Bug 1175685 - add OriginAttribute to LoadInfo. r=jonas, r=ckerschb, r=michal (fb07d2c8aa)
- Bug 1212904 P1 Add a LoadTainting enumeration. r=jduell (a1db8a3e99)
- Bug 1212904 P2 Add LoadTainting information to nsILoadInfo. r=jduell (2482e5e334)
- Bug 1221151 - use [infallible] in nsILoadInfo.idl instead of manual %{C++ blocks; r=jduell (aae73129b6)
- Bug 1045891 - CSP 2 child-src implementation r=ckerschb (792920aeb9)
- Bug 1219931 - CSP: Don't allow removing a policy (r=sicking) (9daaab4186)
- Bug 1208661 - Dump client-side layer textures. r=BenWa (1f2d17d515)
2022-10-17 11:08:37 +08:00

1048 lines
28 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "AccessibleCaretManager.h"
#include "AccessibleCaret.h"
#include "AccessibleCaretEventHub.h"
#include "AccessibleCaretLogger.h"
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/dom/TreeWalker.h"
#include "nsCaret.h"
#include "nsContentUtils.h"
#include "nsFocusManager.h"
#include "nsFrame.h"
#include "nsFrameSelection.h"
#include "nsGenericHTMLElement.h"
namespace mozilla {
#undef AC_LOG
#define AC_LOG(message, ...) \
AC_LOG_BASE("AccessibleCaretManager (%p): " message, this, ##__VA_ARGS__);
#undef AC_LOGV
#define AC_LOGV(message, ...) \
AC_LOGV_BASE("AccessibleCaretManager (%p): " message, this, ##__VA_ARGS__);
using namespace dom;
using Appearance = AccessibleCaret::Appearance;
using PositionChangedResult = AccessibleCaret::PositionChangedResult;
#define AC_PROCESS_ENUM_TO_STREAM(e) case(e): aStream << #e; break;
std::ostream&
operator<<(std::ostream& aStream,
const AccessibleCaretManager::CaretMode& aCaretMode)
{
using CaretMode = AccessibleCaretManager::CaretMode;
switch (aCaretMode) {
AC_PROCESS_ENUM_TO_STREAM(CaretMode::None);
AC_PROCESS_ENUM_TO_STREAM(CaretMode::Cursor);
AC_PROCESS_ENUM_TO_STREAM(CaretMode::Selection);
}
return aStream;
}
std::ostream& operator<<(std::ostream& aStream,
const AccessibleCaretManager::UpdateCaretsHint& aHint)
{
using UpdateCaretsHint = AccessibleCaretManager::UpdateCaretsHint;
switch (aHint) {
AC_PROCESS_ENUM_TO_STREAM(UpdateCaretsHint::Default);
AC_PROCESS_ENUM_TO_STREAM(UpdateCaretsHint::RespectOldAppearance);
}
return aStream;
}
#undef AC_PROCESS_ENUM_TO_STREAM
AccessibleCaretManager::AccessibleCaretManager(nsIPresShell* aPresShell)
: mPresShell(aPresShell)
{
if (mPresShell) {
mFirstCaret = MakeUnique<AccessibleCaret>(mPresShell);
mSecondCaret = MakeUnique<AccessibleCaret>(mPresShell);
mCaretTimeoutTimer = do_CreateInstance("@mozilla.org/timer;1");
}
}
AccessibleCaretManager::~AccessibleCaretManager()
{
CancelCaretTimeoutTimer();
}
nsresult
AccessibleCaretManager::OnSelectionChanged(nsIDOMDocument* aDoc,
nsISelection* aSel, int16_t aReason)
{
AC_LOG("aSel: %p, GetSelection(): %p, aReason: %d", aSel, GetSelection(),
aReason);
if (aSel != GetSelection()) {
return NS_OK;
}
// Move the cursor by Javascript.
if (aReason == nsISelectionListener::NO_REASON) {
HideCarets();
return NS_OK;
}
// Move cursor by keyboard.
if (aReason & nsISelectionListener::KEYPRESS_REASON) {
HideCarets();
return NS_OK;
}
// Range will collapse after cutting or copying text.
if (aReason & (nsISelectionListener::COLLAPSETOSTART_REASON |
nsISelectionListener::COLLAPSETOEND_REASON)) {
HideCarets();
return NS_OK;
}
UpdateCarets();
return NS_OK;
}
void
AccessibleCaretManager::HideCarets()
{
if (mFirstCaret->IsLogicallyVisible() || mSecondCaret->IsLogicallyVisible()) {
AC_LOG("%s", __FUNCTION__);
mFirstCaret->SetAppearance(Appearance::None);
mSecondCaret->SetAppearance(Appearance::None);
DispatchCaretStateChangedEvent(CaretChangedReason::Visibilitychange);
CancelCaretTimeoutTimer();
}
}
void
AccessibleCaretManager::UpdateCarets(UpdateCaretsHint aHint)
{
mLastUpdateCaretMode = GetCaretMode();
switch (mLastUpdateCaretMode) {
case CaretMode::None:
HideCarets();
break;
case CaretMode::Cursor:
UpdateCaretsForCursorMode(aHint);
break;
case CaretMode::Selection:
UpdateCaretsForSelectionMode(aHint);
break;
}
}
bool
AccessibleCaretManager::IsCaretDisplayableInCursorMode(nsIFrame** aOutFrame,
int32_t* aOutOffset) const
{
RefPtr<nsCaret> caret = mPresShell->GetCaret();
if (!caret || !caret->IsVisible()) {
return false;
}
int32_t offset = 0;
nsIFrame* frame = nsCaret::GetFrameAndOffset(GetSelection(), nullptr, 0, &offset);
if (!frame) {
return false;
}
if (!GetEditingHostForFrame(frame)) {
return false;
}
if (aOutFrame) {
*aOutFrame = frame;
}
if (aOutOffset) {
*aOutOffset = offset;
}
return true;
}
bool
AccessibleCaretManager::HasNonEmptyTextContent(nsINode* aNode) const
{
return nsContentUtils::HasNonEmptyTextContent(
aNode, nsContentUtils::eRecurseIntoChildren);
}
void
AccessibleCaretManager::UpdateCaretsForCursorMode(UpdateCaretsHint aHint)
{
AC_LOG("%s, selection: %p", __FUNCTION__, GetSelection());
int32_t offset = 0;
nsIFrame* frame = nullptr;
if (!IsCaretDisplayableInCursorMode(&frame, &offset)) {
HideCarets();
return;
}
bool oldSecondCaretVisible = mSecondCaret->IsLogicallyVisible();
PositionChangedResult result = mFirstCaret->SetPosition(frame, offset);
switch (result) {
case PositionChangedResult::NotChanged:
// Do nothing
break;
case PositionChangedResult::Changed:
switch (aHint) {
case UpdateCaretsHint::Default:
if (HasNonEmptyTextContent(GetEditingHostForFrame(frame))) {
mFirstCaret->SetAppearance(Appearance::Normal);
} else {
mFirstCaret->SetAppearance(Appearance::NormalNotShown);
}
break;
case UpdateCaretsHint::RespectOldAppearance:
// Do nothing to prevent the appearance of the caret being
// changed from NormalNotShown to Normal.
break;
}
break;
case PositionChangedResult::Invisible:
mFirstCaret->SetAppearance(Appearance::NormalNotShown);
break;
}
mFirstCaret->SetSelectionBarEnabled(false);
mSecondCaret->SetAppearance(Appearance::None);
LaunchCaretTimeoutTimer();
if ((result != PositionChangedResult::NotChanged || oldSecondCaretVisible) &&
!mActiveCaret) {
DispatchCaretStateChangedEvent(CaretChangedReason::Updateposition);
}
}
void
AccessibleCaretManager::UpdateCaretsForSelectionMode(UpdateCaretsHint aHint)
{
AC_LOG("%s, selection: %p", __FUNCTION__, GetSelection());
int32_t startOffset = 0;
nsIFrame* startFrame = FindFirstNodeWithFrame(false, &startOffset);
int32_t endOffset = 0;
nsIFrame* endFrame = FindFirstNodeWithFrame(true, &endOffset);
if (!CompareTreePosition(startFrame, endFrame)) {
// XXX: Do we really have to hide carets if this condition isn't satisfied?
HideCarets();
return;
}
auto updateSingleCaret = [](AccessibleCaret * aCaret, nsIFrame * aFrame,
int32_t aOffset)->PositionChangedResult
{
PositionChangedResult result = aCaret->SetPosition(aFrame, aOffset);
aCaret->SetSelectionBarEnabled(true);
switch (result) {
case PositionChangedResult::NotChanged:
// Do nothing
break;
case PositionChangedResult::Changed:
aCaret->SetAppearance(Appearance::Normal);
break;
case PositionChangedResult::Invisible:
aCaret->SetAppearance(Appearance::NormalNotShown);
break;
}
return result;
};
PositionChangedResult firstCaretResult =
updateSingleCaret(mFirstCaret.get(), startFrame, startOffset);
PositionChangedResult secondCaretResult =
updateSingleCaret(mSecondCaret.get(), endFrame, endOffset);
if (firstCaretResult == PositionChangedResult::Changed ||
secondCaretResult == PositionChangedResult::Changed) {
// Flush layout to make the carets intersection correct.
FlushLayout();
}
UpdateCaretsForTilt();
if ((firstCaretResult == PositionChangedResult::Changed ||
secondCaretResult == PositionChangedResult::Changed ||
firstCaretResult == PositionChangedResult::Invisible ||
secondCaretResult == PositionChangedResult::Invisible) &&
!mActiveCaret) {
DispatchCaretStateChangedEvent(CaretChangedReason::Updateposition);
}
}
void
AccessibleCaretManager::UpdateCaretsForTilt()
{
if (mFirstCaret->IsVisuallyVisible() && mSecondCaret->IsVisuallyVisible()) {
if (mFirstCaret->Intersects(*mSecondCaret)) {
if (mFirstCaret->LogicalPosition().x <=
mSecondCaret->LogicalPosition().x) {
mFirstCaret->SetAppearance(Appearance::Left);
mSecondCaret->SetAppearance(Appearance::Right);
} else {
mFirstCaret->SetAppearance(Appearance::Right);
mSecondCaret->SetAppearance(Appearance::Left);
}
} else {
mFirstCaret->SetAppearance(Appearance::Normal);
mSecondCaret->SetAppearance(Appearance::Normal);
}
}
}
nsresult
AccessibleCaretManager::PressCaret(const nsPoint& aPoint)
{
nsresult rv = NS_ERROR_FAILURE;
if (mFirstCaret->Contains(aPoint)) {
mActiveCaret = mFirstCaret.get();
SetSelectionDirection(eDirPrevious);
} else if (mSecondCaret->Contains(aPoint)) {
mActiveCaret = mSecondCaret.get();
SetSelectionDirection(eDirNext);
}
if (mActiveCaret) {
mOffsetYToCaretLogicalPosition =
mActiveCaret->LogicalPosition().y - aPoint.y;
SetSelectionDragState(true);
DispatchCaretStateChangedEvent(CaretChangedReason::Presscaret);
CancelCaretTimeoutTimer();
rv = NS_OK;
}
return rv;
}
nsresult
AccessibleCaretManager::DragCaret(const nsPoint& aPoint)
{
MOZ_ASSERT(mActiveCaret);
MOZ_ASSERT(GetCaretMode() != CaretMode::None);
nsPoint point(aPoint.x, aPoint.y + mOffsetYToCaretLogicalPosition);
DragCaretInternal(point);
UpdateCarets();
return NS_OK;
}
nsresult
AccessibleCaretManager::ReleaseCaret()
{
MOZ_ASSERT(mActiveCaret);
mActiveCaret = nullptr;
SetSelectionDragState(false);
DispatchCaretStateChangedEvent(CaretChangedReason::Releasecaret);
LaunchCaretTimeoutTimer();
return NS_OK;
}
nsresult
AccessibleCaretManager::TapCaret(const nsPoint& aPoint)
{
MOZ_ASSERT(GetCaretMode() != CaretMode::None);
nsresult rv = NS_ERROR_FAILURE;
if (GetCaretMode() == CaretMode::Cursor) {
DispatchCaretStateChangedEvent(CaretChangedReason::Taponcaret);
rv = NS_OK;
}
return rv;
}
nsresult
AccessibleCaretManager::SelectWordOrShortcut(const nsPoint& aPoint)
{
if (!mPresShell) {
return NS_ERROR_UNEXPECTED;
}
nsIFrame* rootFrame = mPresShell->GetRootFrame();
if (!rootFrame) {
return NS_ERROR_NOT_AVAILABLE;
}
// Find the frame under point.
nsIFrame* ptFrame = nsLayoutUtils::GetFrameForPoint(rootFrame, aPoint,
nsLayoutUtils::IGNORE_PAINT_SUPPRESSION | nsLayoutUtils::IGNORE_CROSS_DOC);
if (!ptFrame) {
return NS_ERROR_FAILURE;
}
nsIFrame* focusableFrame = GetFocusableFrame(ptFrame);
#ifdef DEBUG_FRAME_DUMP
AC_LOG("%s: Found %s under (%d, %d)", __FUNCTION__, ptFrame->ListTag().get(),
aPoint.x, aPoint.y);
AC_LOG("%s: Found %s focusable", __FUNCTION__,
focusableFrame ? focusableFrame->ListTag().get() : "no frame");
#endif
// Firstly check long press on an empty editable content.
Element* newFocusEditingHost = GetEditingHostForFrame(ptFrame);
if (focusableFrame && newFocusEditingHost &&
!HasNonEmptyTextContent(newFocusEditingHost)) {
ChangeFocusToOrClearOldFocus(focusableFrame);
// We need to update carets to get correct information before dispatching
// CaretStateChangedEvent.
UpdateCarets();
DispatchCaretStateChangedEvent(CaretChangedReason::Longpressonemptycontent);
return NS_OK;
}
bool selectable = false;
ptFrame->IsSelectable(&selectable, nullptr);
#ifdef DEBUG_FRAME_DUMP
AC_LOG("%s: %s %s selectable.", __FUNCTION__, ptFrame->ListTag().get(),
selectable ? "is" : "is NOT");
#endif
if (!selectable) {
return NS_ERROR_FAILURE;
}
// ptFrame is selectable. Now change the focus.
ChangeFocusToOrClearOldFocus(focusableFrame);
// Then try select a word under point.
nsPoint ptInFrame = aPoint;
nsLayoutUtils::TransformPoint(rootFrame, ptFrame, ptInFrame);
nsresult rv = SelectWord(ptFrame, ptInFrame);
UpdateCarets();
return rv;
}
void
AccessibleCaretManager::OnScrollStart()
{
AC_LOG("%s", __FUNCTION__);
HideCarets();
}
void
AccessibleCaretManager::OnScrollEnd()
{
if (mLastUpdateCaretMode != GetCaretMode()) {
return;
}
if (GetCaretMode() == CaretMode::Cursor) {
AC_LOG("%s: HideCarets()", __FUNCTION__);
HideCarets();
} else {
AC_LOG("%s: UpdateCarets()", __FUNCTION__);
UpdateCarets();
}
}
void
AccessibleCaretManager::OnScrollPositionChanged()
{
if (mLastUpdateCaretMode != GetCaretMode()) {
return;
}
AC_LOG("%s: UpdateCarets(RespectOldAppearance)", __FUNCTION__);
UpdateCarets(UpdateCaretsHint::RespectOldAppearance);
}
void
AccessibleCaretManager::OnReflow()
{
if (mLastUpdateCaretMode != GetCaretMode()) {
return;
}
if (mFirstCaret->IsVisuallyVisible() || mSecondCaret->IsVisuallyVisible()) {
AC_LOG("%s: UpdateCarets()", __FUNCTION__);
UpdateCarets();
}
}
void
AccessibleCaretManager::OnBlur()
{
AC_LOG("%s: HideCarets()", __FUNCTION__);
HideCarets();
}
void
AccessibleCaretManager::OnKeyboardEvent()
{
if (GetCaretMode() == CaretMode::Cursor) {
AC_LOG("%s: HideCarets()", __FUNCTION__);
HideCarets();
}
}
Selection*
AccessibleCaretManager::GetSelection() const
{
RefPtr<nsFrameSelection> fs = GetFrameSelection();
if (!fs) {
return nullptr;
}
return fs->GetSelection(nsISelectionController::SELECTION_NORMAL);
}
already_AddRefed<nsFrameSelection>
AccessibleCaretManager::GetFrameSelection() const
{
if (!mPresShell) {
return nullptr;
}
nsFocusManager* fm = nsFocusManager::GetFocusManager();
MOZ_ASSERT(fm);
nsIContent* focusedContent = fm->GetFocusedContent();
if (focusedContent) {
nsIFrame* focusFrame = focusedContent->GetPrimaryFrame();
if (!focusFrame) {
return nullptr;
}
// Prevent us from touching the nsFrameSelection associated with other
// PresShell.
RefPtr<nsFrameSelection> fs = focusFrame->GetFrameSelection();
if (!fs || fs->GetShell() != mPresShell) {
return nullptr;
}
return fs.forget();
} else {
// For non-editable content
return mPresShell->FrameSelection();
}
}
Element*
AccessibleCaretManager::GetEditingHostForFrame(nsIFrame* aFrame) const
{
if (!aFrame) {
return nullptr;
}
auto content = aFrame->GetContent();
if (!content) {
return nullptr;
}
return content->GetEditingHost();
}
AccessibleCaretManager::CaretMode
AccessibleCaretManager::GetCaretMode() const
{
Selection* selection = GetSelection();
if (!selection) {
return CaretMode::None;
}
uint32_t rangeCount = selection->RangeCount();
if (rangeCount <= 0) {
return CaretMode::None;
}
if (selection->IsCollapsed()) {
return CaretMode::Cursor;
}
return CaretMode::Selection;
}
nsIFrame*
AccessibleCaretManager::GetFocusableFrame(nsIFrame* aFrame) const
{
// This implementation is similar to EventStateManager::PostHandleEvent().
// Look for the nearest enclosing focusable frame.
nsIFrame* focusableFrame = aFrame;
while (focusableFrame) {
if (focusableFrame->IsFocusable(nullptr, true)) {
break;
}
focusableFrame = focusableFrame->GetParent();
}
return focusableFrame;
}
void
AccessibleCaretManager::ChangeFocusToOrClearOldFocus(nsIFrame* aFrame) const
{
nsFocusManager* fm = nsFocusManager::GetFocusManager();
MOZ_ASSERT(fm);
if (aFrame) {
nsIContent* focusableContent = aFrame->GetContent();
MOZ_ASSERT(focusableContent, "Focusable frame must have content!");
nsCOMPtr<nsIDOMElement> focusableElement = do_QueryInterface(focusableContent);
fm->SetFocus(focusableElement, nsIFocusManager::FLAG_BYMOUSE);
} else {
nsIDOMWindow* win = mPresShell->GetDocument()->GetWindow();
if (win) {
fm->ClearFocus(win);
fm->SetFocusedWindow(win);
}
}
}
nsresult
AccessibleCaretManager::SelectWord(nsIFrame* aFrame, const nsPoint& aPoint) const
{
SetSelectionDragState(true);
nsFrame* frame = static_cast<nsFrame*>(aFrame);
nsresult rs = frame->SelectByTypeAtPoint(mPresShell->GetPresContext(), aPoint,
eSelectWord, eSelectWord, 0);
SetSelectionDragState(false);
ClearMaintainedSelection();
return rs;
}
void
AccessibleCaretManager::SetSelectionDragState(bool aState) const
{
RefPtr<nsFrameSelection> fs = GetFrameSelection();
if (fs) {
fs->SetDragState(aState);
}
}
void
AccessibleCaretManager::SetSelectionDirection(nsDirection aDir) const
{
Selection* selection = GetSelection();
if (selection) {
selection->AdjustAnchorFocusForMultiRange(aDir);
}
}
void
AccessibleCaretManager::ClearMaintainedSelection() const
{
// Selection made by double-clicking for example will maintain the original
// word selection. We should clear it so that we can drag caret freely.
RefPtr<nsFrameSelection> fs = GetFrameSelection();
if (fs) {
fs->MaintainSelection(eSelectNoAmount);
}
}
void
AccessibleCaretManager::FlushLayout() const
{
if (mPresShell) {
mPresShell->FlushPendingNotifications(Flush_Layout);
}
}
nsIFrame*
AccessibleCaretManager::FindFirstNodeWithFrame(bool aBackward,
int32_t* aOutOffset) const
{
if (!mPresShell) {
return nullptr;
}
RefPtr<Selection> selection = GetSelection();
if (!selection) {
return nullptr;
}
RefPtr<nsFrameSelection> fs = GetFrameSelection();
if (!fs) {
return nullptr;
}
uint32_t rangeCount = selection->RangeCount();
if (rangeCount <= 0) {
return nullptr;
}
nsRange* range = selection->GetRangeAt(aBackward ? rangeCount - 1 : 0);
RefPtr<nsINode> startNode =
aBackward ? range->GetEndParent() : range->GetStartParent();
RefPtr<nsINode> endNode =
aBackward ? range->GetStartParent() : range->GetEndParent();
int32_t offset = aBackward ? range->EndOffset() : range->StartOffset();
nsCOMPtr<nsIContent> startContent = do_QueryInterface(startNode);
CaretAssociationHint hintStart =
aBackward ? CARET_ASSOCIATE_BEFORE : CARET_ASSOCIATE_AFTER;
nsIFrame* startFrame =
fs->GetFrameForNodeOffset(startContent, offset, hintStart, aOutOffset);
if (startFrame) {
return startFrame;
}
ErrorResult err;
RefPtr<TreeWalker> walker = mPresShell->GetDocument()->CreateTreeWalker(
*startNode, nsIDOMNodeFilter::SHOW_ALL, nullptr, err);
if (!walker) {
return nullptr;
}
startFrame = startContent ? startContent->GetPrimaryFrame() : nullptr;
while (!startFrame && startNode != endNode) {
startNode = aBackward ? walker->PreviousNode(err) : walker->NextNode(err);
if (!startNode) {
break;
}
startContent = startNode->AsContent();
startFrame = startContent ? startContent->GetPrimaryFrame() : nullptr;
}
return startFrame;
}
bool
AccessibleCaretManager::CompareRangeWithContentOffset(nsIFrame::ContentOffsets& aOffsets)
{
Selection* selection = GetSelection();
if (!selection) {
return false;
}
uint32_t rangeCount = selection->RangeCount();
MOZ_ASSERT(rangeCount > 0);
int32_t rangeIndex = (mActiveCaret == mFirstCaret.get() ? rangeCount - 1 : 0);
RefPtr<nsRange> range = selection->GetRangeAt(rangeIndex);
nsINode* node = nullptr;
int32_t nodeOffset = 0;
CaretAssociationHint hint;
nsDirection dir;
if (mActiveCaret == mFirstCaret.get()) {
// Check previous character of end node offset
node = range->GetEndParent();
nodeOffset = range->EndOffset();
hint = CARET_ASSOCIATE_BEFORE;
dir = eDirPrevious;
} else {
// Check next character of start node offset
node = range->GetStartParent();
nodeOffset = range->StartOffset();
hint = CARET_ASSOCIATE_AFTER;
dir = eDirNext;
}
nsCOMPtr<nsIContent> content = do_QueryInterface(node);
RefPtr<nsFrameSelection> fs = GetFrameSelection();
if (!fs) {
return false;
}
int32_t offset = 0;
nsIFrame* theFrame =
fs->GetFrameForNodeOffset(content, nodeOffset, hint, &offset);
if (!theFrame) {
return false;
}
// Move one character forward/backward from point and get offset
nsPeekOffsetStruct pos(eSelectCluster,
dir,
offset,
nsPoint(0, 0),
true,
true, //limit on scrolled views
false,
false,
false);
nsresult rv = theFrame->PeekOffset(&pos);
if (NS_FAILED(rv)) {
pos.mResultContent = content;
pos.mContentOffset = nodeOffset;
}
// Compare with current point
int32_t result = nsContentUtils::ComparePoints(aOffsets.content,
aOffsets.StartOffset(),
pos.mResultContent,
pos.mContentOffset);
if ((mActiveCaret == mFirstCaret.get() && result == 1) ||
(mActiveCaret == mSecondCaret.get() && result == -1)) {
aOffsets.content = pos.mResultContent;
aOffsets.offset = pos.mContentOffset;
aOffsets.secondaryOffset = pos.mContentOffset;
}
return true;
}
bool
AccessibleCaretManager::CompareTreePosition(nsIFrame* aStartFrame,
nsIFrame* aEndFrame) const
{
return (aStartFrame && aEndFrame &&
nsLayoutUtils::CompareTreePosition(aStartFrame, aEndFrame) <= 0);
}
nsresult
AccessibleCaretManager::DragCaretInternal(const nsPoint& aPoint)
{
if (!mPresShell) {
return NS_ERROR_NULL_POINTER;
}
nsIFrame* rootFrame = mPresShell->GetRootFrame();
if (!rootFrame) {
return NS_ERROR_NULL_POINTER;
}
nsPoint point = AdjustDragBoundary(aPoint);
// Find out which content we point to
nsIFrame* ptFrame = nsLayoutUtils::GetFrameForPoint(
rootFrame, point,
nsLayoutUtils::IGNORE_PAINT_SUPPRESSION | nsLayoutUtils::IGNORE_CROSS_DOC);
if (!ptFrame) {
return NS_ERROR_FAILURE;
}
RefPtr<nsFrameSelection> fs = GetFrameSelection();
if (!fs) {
return NS_ERROR_NULL_POINTER;
}
nsresult result;
nsIFrame* newFrame = nullptr;
nsPoint newPoint;
nsPoint ptInFrame = point;
nsLayoutUtils::TransformPoint(rootFrame, ptFrame, ptInFrame);
result = fs->ConstrainFrameAndPointToAnchorSubtree(ptFrame, ptInFrame,
&newFrame, newPoint);
if (NS_FAILED(result) || !newFrame) {
return NS_ERROR_FAILURE;
}
bool selectable;
newFrame->IsSelectable(&selectable, nullptr);
if (!selectable) {
return NS_ERROR_FAILURE;
}
nsIFrame::ContentOffsets offsets =
newFrame->GetContentOffsetsFromPoint(newPoint);
if (!offsets.content) {
return NS_ERROR_FAILURE;
}
Selection* selection = GetSelection();
if (!selection) {
return NS_ERROR_NULL_POINTER;
}
if (GetCaretMode() == CaretMode::Selection &&
!CompareRangeWithContentOffset(offsets)) {
return NS_ERROR_FAILURE;
}
ClearMaintainedSelection();
nsIFrame* anchorFrame = nullptr;
selection->GetPrimaryFrameForAnchorNode(&anchorFrame);
nsIFrame* scrollable =
nsLayoutUtils::GetClosestFrameOfType(anchorFrame, nsGkAtoms::scrollFrame);
nsWeakFrame weakScrollable = scrollable;
fs->HandleClick(offsets.content, offsets.StartOffset(), offsets.EndOffset(),
GetCaretMode() == CaretMode::Selection, false,
offsets.associate);
if (!weakScrollable.IsAlive()) {
return NS_OK;
}
// Scroll scrolled frame.
nsIScrollableFrame* saf = do_QueryFrame(scrollable);
nsIFrame* capturingFrame = saf->GetScrolledFrame();
nsPoint ptInScrolled = point;
nsLayoutUtils::TransformPoint(rootFrame, capturingFrame, ptInScrolled);
fs->StartAutoScrollTimer(capturingFrame, ptInScrolled, kAutoScrollTimerDelay);
return NS_OK;
}
nsPoint
AccessibleCaretManager::AdjustDragBoundary(const nsPoint& aPoint) const
{
// Bug 1068474: Adjust the Y-coordinate so that the carets won't be in tilt
// mode when a caret is being dragged surpass the other caret.
//
// For example, when dragging the second caret, the horizontal boundary (lower
// bound) of its Y-coordinate is the logical position of the first caret.
// Likewise, when dragging the first caret, the horizontal boundary (upper
// bound) of its Y-coordinate is the logical position of the second caret.
nsPoint adjustedPoint = aPoint;
if (GetCaretMode() == CaretMode::Selection) {
if (mActiveCaret == mFirstCaret.get()) {
nscoord dragDownBoundaryY = mSecondCaret->LogicalPosition().y;
if (dragDownBoundaryY > 0 && adjustedPoint.y > dragDownBoundaryY) {
adjustedPoint.y = dragDownBoundaryY;
}
} else {
nscoord dragUpBoundaryY = mFirstCaret->LogicalPosition().y;
if (adjustedPoint.y < dragUpBoundaryY) {
adjustedPoint.y = dragUpBoundaryY;
}
}
}
return adjustedPoint;
}
uint32_t
AccessibleCaretManager::CaretTimeoutMs() const
{
static bool added = false;
static uint32_t caretTimeoutMs = 0;
if (!added) {
Preferences::AddUintVarCache(&caretTimeoutMs,
"layout.accessiblecaret.timeout_ms");
added = true;
}
return caretTimeoutMs;
}
void
AccessibleCaretManager::LaunchCaretTimeoutTimer()
{
if (!mCaretTimeoutTimer || CaretTimeoutMs() == 0 ||
GetCaretMode() != CaretMode::Cursor || mActiveCaret) {
return;
}
nsTimerCallbackFunc callback = [](nsITimer* aTimer, void* aClosure) {
auto self = static_cast<AccessibleCaretManager*>(aClosure);
if (self->GetCaretMode() == CaretMode::Cursor) {
self->HideCarets();
}
};
mCaretTimeoutTimer->InitWithFuncCallback(callback, this, CaretTimeoutMs(),
nsITimer::TYPE_ONE_SHOT);
}
void
AccessibleCaretManager::CancelCaretTimeoutTimer()
{
if (mCaretTimeoutTimer) {
mCaretTimeoutTimer->Cancel();
}
}
void
AccessibleCaretManager::DispatchCaretStateChangedEvent(CaretChangedReason aReason) const
{
// Holding PresShell to prevent AccessibleCaretManager to be destroyed.
nsCOMPtr<nsIPresShell> presShell = mPresShell;
FlushLayout();
if (presShell->IsDestroying()) {
return;
}
Selection* sel = GetSelection();
if (!sel) {
return;
}
nsIDocument* doc = mPresShell->GetDocument();
MOZ_ASSERT(doc);
CaretStateChangedEventInit init;
init.mBubbles = true;
const nsRange* range = sel->GetAnchorFocusRange();
nsINode* commonAncestorNode = nullptr;
if (range) {
commonAncestorNode = range->GetCommonAncestor();
}
if (!commonAncestorNode) {
commonAncestorNode = sel->GetFrameSelection()->GetAncestorLimiter();
}
RefPtr<DOMRect> domRect = new DOMRect(ToSupports(doc));
nsRect rect = nsContentUtils::GetSelectionBoundingRect(sel);
nsIFrame* commonAncestorFrame = nullptr;
nsIFrame* rootFrame = mPresShell->GetRootFrame();
if (commonAncestorNode && commonAncestorNode->IsContent()) {
commonAncestorFrame = commonAncestorNode->AsContent()->GetPrimaryFrame();
}
if (commonAncestorFrame && rootFrame) {
nsLayoutUtils::TransformRect(rootFrame, commonAncestorFrame, rect);
nsRect clampedRect = nsLayoutUtils::ClampRectToScrollFrames(commonAncestorFrame,
rect);
nsLayoutUtils::TransformRect(commonAncestorFrame, rootFrame, clampedRect);
domRect->SetLayoutRect(clampedRect);
init.mSelectionVisible = !clampedRect.IsEmpty();
} else {
domRect->SetLayoutRect(rect);
init.mSelectionVisible = true;
}
// Send isEditable info w/ event detail. This info can help determine
// whether to show cut command on selection dialog or not.
init.mSelectionEditable = commonAncestorFrame &&
GetEditingHostForFrame(commonAncestorFrame);
init.mBoundingClientRect = domRect;
init.mReason = aReason;
init.mCollapsed = sel->IsCollapsed();
init.mCaretVisible = mFirstCaret->IsLogicallyVisible() ||
mSecondCaret->IsLogicallyVisible();
RefPtr<CaretStateChangedEvent> event =
CaretStateChangedEvent::Constructor(doc, NS_LITERAL_STRING("mozcaretstatechanged"), init);
event->SetTrusted(true);
event->GetInternalNSEvent()->mFlags.mOnlyChromeDispatch = true;
AC_LOG("%s: reason %d, collapsed %d, caretVisible %d", __FUNCTION__,
init.mReason, init.mCollapsed, init.mCaretVisible);
(new AsyncEventDispatcher(doc, event))->RunDOMEventWhenSafe();
}
} // namespace mozilla