Files
palemoon27/layout/base/nsCaret.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

1015 lines
32 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/. */
/* the caret is the text cursor used, e.g., when editing */
#include "nsCaret.h"
#include <algorithm>
#include "gfxUtils.h"
#include "mozilla/gfx/2D.h"
#include "nsCOMPtr.h"
#include "nsFontMetrics.h"
#include "nsITimer.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 "nsLayoutUtils.h"
#include "nsPresContext.h"
#include "nsBlockFrame.h"
#include "nsISelectionController.h"
#include "nsTextFrame.h"
#include "nsXULPopupManager.h"
#include "nsMenuPopupFrame.h"
#include "nsTextFragment.h"
#include "mozilla/Preferences.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/dom/Selection.h"
#include "nsIBidiKeyboard.h"
#include "nsContentUtils.h"
using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::gfx;
// The bidi indicator hangs off the caret to one side, to show which
// direction the typing is in. It needs to be at least 2x2 to avoid looking like
// an insignificant dot
static const int32_t kMinBidiIndicatorPixels = 2;
/*static*/ bool nsCaret::sSelectionCaretEnabled = false;
/*static*/ bool nsCaret::sSelectionCaretsAffectCaret = false;
/**
* Find the first frame in an in-order traversal of the frame subtree rooted
* at aFrame which is either a text frame logically at the end of a line,
* or which is aStopAtFrame. Return null if no such frame is found. We don't
* descend into the children of non-eLineParticipant frames.
*/
static nsIFrame*
CheckForTrailingTextFrameRecursive(nsIFrame* aFrame, nsIFrame* aStopAtFrame)
{
if (aFrame == aStopAtFrame ||
((aFrame->GetType() == nsGkAtoms::textFrame &&
(static_cast<nsTextFrame*>(aFrame))->IsAtEndOfLine())))
return aFrame;
if (!aFrame->IsFrameOfType(nsIFrame::eLineParticipant))
return nullptr;
for (nsIFrame* f = aFrame->GetFirstPrincipalChild(); f; f = f->GetNextSibling())
{
nsIFrame* r = CheckForTrailingTextFrameRecursive(f, aStopAtFrame);
if (r)
return r;
}
return nullptr;
}
static nsLineBox*
FindContainingLine(nsIFrame* aFrame)
{
while (aFrame && aFrame->IsFrameOfType(nsIFrame::eLineParticipant))
{
nsIFrame* parent = aFrame->GetParent();
nsBlockFrame* blockParent = nsLayoutUtils::GetAsBlock(parent);
if (blockParent)
{
bool isValid;
nsBlockInFlowLineIterator iter(blockParent, aFrame, &isValid);
return isValid ? iter.GetLine().get() : nullptr;
}
aFrame = parent;
}
return nullptr;
}
static void
AdjustCaretFrameForLineEnd(nsIFrame** aFrame, int32_t* aOffset)
{
nsLineBox* line = FindContainingLine(*aFrame);
if (!line)
return;
int32_t count = line->GetChildCount();
for (nsIFrame* f = line->mFirstChild; count > 0; --count, f = f->GetNextSibling())
{
nsIFrame* r = CheckForTrailingTextFrameRecursive(f, *aFrame);
if (r == *aFrame)
return;
if (r)
{
*aFrame = r;
NS_ASSERTION(r->GetType() == nsGkAtoms::textFrame, "Expected text frame");
*aOffset = (static_cast<nsTextFrame*>(r))->GetContentEnd();
return;
}
}
}
static bool
IsKeyboardRTL()
{
bool isKeyboardRTL = false;
nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard();
if (bidiKeyboard) {
bidiKeyboard->IsLangRTL(&isKeyboardRTL);
}
return isKeyboardRTL;
}
nsCaret::nsCaret()
: mOverrideOffset(0)
, mBlinkCount(-1)
, mHideCount(0)
, mIsBlinkOn(false)
, mVisible(false)
, mReadOnly(false)
, mShowDuringSelection(false)
, mIgnoreUserModify(true)
{
}
nsCaret::~nsCaret()
{
StopBlinking();
}
nsresult nsCaret::Init(nsIPresShell *inPresShell)
{
NS_ENSURE_ARG(inPresShell);
mPresShell = do_GetWeakReference(inPresShell); // the presshell owns us, so no addref
NS_ASSERTION(mPresShell, "Hey, pres shell should support weak refs");
mShowDuringSelection =
LookAndFeel::GetInt(LookAndFeel::eIntID_ShowCaretDuringSelection,
mShowDuringSelection ? 1 : 0) != 0;
static bool addedCaretPref = false;
if (!addedCaretPref) {
Preferences::AddBoolVarCache(&sSelectionCaretEnabled,
"selectioncaret.enabled");
Preferences::AddBoolVarCache(&sSelectionCaretsAffectCaret,
"selectioncaret.visibility.affectscaret");
addedCaretPref = true;
}
// get the selection from the pres shell, and set ourselves up as a selection
// listener
nsCOMPtr<nsISelectionController> selCon = do_QueryReferent(mPresShell);
if (!selCon)
return NS_ERROR_FAILURE;
nsCOMPtr<nsISelection> domSelection;
nsresult rv = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
getter_AddRefs(domSelection));
if (NS_FAILED(rv))
return rv;
if (!domSelection)
return NS_ERROR_FAILURE;
nsCOMPtr<nsISelectionPrivate> privateSelection = do_QueryInterface(domSelection);
if (privateSelection)
privateSelection->AddSelectionListener(this);
mDomSelectionWeak = do_GetWeakReference(domSelection);
return NS_OK;
}
static bool
DrawCJKCaret(nsIFrame* aFrame, int32_t aOffset)
{
nsIContent* content = aFrame->GetContent();
const nsTextFragment* frag = content->GetText();
if (!frag)
return false;
if (aOffset < 0 || uint32_t(aOffset) >= frag->GetLength())
return false;
char16_t ch = frag->CharAt(aOffset);
return 0x2e80 <= ch && ch <= 0xd7ff;
}
nsCaret::Metrics
nsCaret::ComputeMetrics(nsIFrame* aFrame, int32_t aOffset, nscoord aCaretHeight)
{
// Compute nominal sizes in appunits
nscoord caretWidth =
(aCaretHeight * LookAndFeel::GetFloat(LookAndFeel::eFloatID_CaretAspectRatio, 0.0f)) +
nsPresContext::CSSPixelsToAppUnits(
LookAndFeel::GetInt(LookAndFeel::eIntID_CaretWidth, 1));
if (DrawCJKCaret(aFrame, aOffset)) {
caretWidth += nsPresContext::CSSPixelsToAppUnits(1);
}
nscoord bidiIndicatorSize = nsPresContext::CSSPixelsToAppUnits(kMinBidiIndicatorPixels);
bidiIndicatorSize = std::max(caretWidth, bidiIndicatorSize);
// Round them to device pixels. Always round down, except that anything
// between 0 and 1 goes up to 1 so we don't let the caret disappear.
int32_t tpp = aFrame->PresContext()->AppUnitsPerDevPixel();
Metrics result;
result.mCaretWidth = NS_ROUND_BORDER_TO_PIXELS(caretWidth, tpp);
result.mBidiIndicatorSize = NS_ROUND_BORDER_TO_PIXELS(bidiIndicatorSize, tpp);
return result;
}
void nsCaret::Terminate()
{
// this doesn't erase the caret if it's drawn. Should it? We might not have
// a good drawing environment during teardown.
StopBlinking();
mBlinkTimer = nullptr;
// unregiser ourselves as a selection listener
nsCOMPtr<nsISelection> domSelection = do_QueryReferent(mDomSelectionWeak);
nsCOMPtr<nsISelectionPrivate> privateSelection(do_QueryInterface(domSelection));
if (privateSelection)
privateSelection->RemoveSelectionListener(this);
mDomSelectionWeak = nullptr;
mPresShell = nullptr;
mOverrideContent = nullptr;
}
NS_IMPL_ISUPPORTS(nsCaret, nsISelectionListener)
nsISelection* nsCaret::GetSelection()
{
nsCOMPtr<nsISelection> sel(do_QueryReferent(mDomSelectionWeak));
return sel;
}
void nsCaret::SetSelection(nsISelection *aDOMSel)
{
MOZ_ASSERT(aDOMSel);
mDomSelectionWeak = do_GetWeakReference(aDOMSel); // weak reference to pres shell
ResetBlinking();
SchedulePaint();
}
void nsCaret::SetVisible(bool inMakeVisible)
{
mVisible = inMakeVisible;
mIgnoreUserModify = mVisible;
ResetBlinking();
SchedulePaint();
}
bool nsCaret::IsVisible()
{
if (!mVisible || mHideCount) {
return false;
}
if (!mShowDuringSelection &&
!(sSelectionCaretEnabled && sSelectionCaretsAffectCaret)) {
Selection* selection = GetSelectionInternal();
if (!selection) {
return false;
}
bool isCollapsed;
if (NS_FAILED(selection->GetIsCollapsed(&isCollapsed)) || !isCollapsed) {
return false;
}
}
// The Android IME can have a visible caret when there is a composition
// selection, due to auto-suggest/auto-correct styling (underlining),
// but never when the SelectionCarets are visible.
if (sSelectionCaretEnabled && sSelectionCaretsAffectCaret) {
nsCOMPtr<nsISelectionController> selCon = do_QueryReferent(mPresShell);
if (selCon) {
bool visible = false;
selCon->GetSelectionCaretsVisibility(&visible);
if (visible) {
return false;
}
}
}
if (IsMenuPopupHidingCaret()) {
return false;
}
return true;
}
void nsCaret::AddForceHide()
{
MOZ_ASSERT(mHideCount < UINT32_MAX);
if (++mHideCount > 1) {
return;
}
ResetBlinking();
SchedulePaint();
}
void nsCaret::RemoveForceHide()
{
if (!mHideCount || --mHideCount) {
return;
}
ResetBlinking();
SchedulePaint();
}
void nsCaret::SetCaretReadOnly(bool inMakeReadonly)
{
mReadOnly = inMakeReadonly;
ResetBlinking();
SchedulePaint();
}
/* static */ nsRect
nsCaret::GetGeometryForFrame(nsIFrame* aFrame,
int32_t aFrameOffset,
nscoord* aBidiIndicatorSize)
{
nsPoint framePos(0, 0);
nsRect rect;
nsresult rv = aFrame->GetPointFromOffset(aFrameOffset, &framePos);
if (NS_FAILED(rv)) {
if (aBidiIndicatorSize) {
*aBidiIndicatorSize = 0;
}
return rect;
}
nsIFrame* frame = aFrame->GetContentInsertionFrame();
if (!frame) {
frame = aFrame;
}
NS_ASSERTION(!(frame->GetStateBits() & NS_FRAME_IN_REFLOW),
"We should not be in the middle of reflow");
nscoord baseline = frame->GetCaretBaseline();
nscoord ascent = 0, descent = 0;
RefPtr<nsFontMetrics> fm;
nsLayoutUtils::GetFontMetricsForFrame(aFrame, getter_AddRefs(fm),
nsLayoutUtils::FontSizeInflationFor(aFrame));
NS_ASSERTION(fm, "We should be able to get the font metrics");
if (fm) {
ascent = fm->MaxAscent();
descent = fm->MaxDescent();
}
nscoord height = ascent + descent;
WritingMode wm = aFrame->GetWritingMode();
bool vertical = wm.IsVertical();
if (vertical) {
if (wm.IsLineInverted()) {
framePos.x = baseline - descent;
} else {
framePos.x = baseline - ascent;
}
} else {
framePos.y = baseline - ascent;
}
Metrics caretMetrics = ComputeMetrics(aFrame, aFrameOffset, height);
rect = nsRect(framePos, vertical ? nsSize(height, caretMetrics.mCaretWidth) :
nsSize(caretMetrics.mCaretWidth, height));
// Clamp the inline-position to be within our scroll frame. If we don't, then
// it clips us, and we don't appear at all. See bug 335560.
nsIFrame *scrollFrame =
nsLayoutUtils::GetClosestFrameOfType(aFrame, nsGkAtoms::scrollFrame);
if (scrollFrame) {
// First, use the scrollFrame to get at the scrollable view that we're in.
nsIScrollableFrame *sf = do_QueryFrame(scrollFrame);
nsIFrame *scrolled = sf->GetScrolledFrame();
nsRect caretInScroll = rect + aFrame->GetOffsetTo(scrolled);
// Now see if the caret extends beyond the view's bounds. If it does,
// then snap it back, put it as close to the edge as it can.
if (vertical) {
nscoord overflow = caretInScroll.YMost() -
scrolled->GetVisualOverflowRectRelativeToSelf().height;
if (overflow > 0) {
rect.y -= overflow;
}
} else {
nscoord overflow = caretInScroll.XMost() -
scrolled->GetVisualOverflowRectRelativeToSelf().width;
if (overflow > 0) {
rect.x -= overflow;
}
}
}
if (aBidiIndicatorSize) {
*aBidiIndicatorSize = caretMetrics.mBidiIndicatorSize;
}
return rect;
}
nsIFrame*
nsCaret::GetFrameAndOffset(Selection* aSelection,
nsINode* aOverrideNode, int32_t aOverrideOffset,
int32_t* aFrameOffset)
{
nsINode* focusNode;
int32_t focusOffset;
if (aOverrideNode) {
focusNode = aOverrideNode;
focusOffset = aOverrideOffset;
} else if (aSelection) {
focusNode = aSelection->GetFocusNode();
aSelection->GetFocusOffset(&focusOffset);
} else {
return nullptr;
}
if (!focusNode || !focusNode->IsContent()) {
return nullptr;
}
nsIContent* contentNode = focusNode->AsContent();
nsFrameSelection* frameSelection = aSelection->GetFrameSelection();
nsBidiLevel bidiLevel = frameSelection->GetCaretBidiLevel();
nsIFrame* frame;
nsresult rv = nsCaret::GetCaretFrameForNodeOffset(
frameSelection, contentNode, focusOffset,
frameSelection->GetHint(), bidiLevel, &frame, aFrameOffset);
if (NS_FAILED(rv) || !frame) {
return nullptr;
}
return frame;
}
/* static */ nsIFrame*
nsCaret::GetGeometry(nsISelection* aSelection, nsRect* aRect)
{
int32_t frameOffset;
nsIFrame* frame = GetFrameAndOffset(
static_cast<Selection*>(aSelection), nullptr, 0, &frameOffset);
if (frame) {
*aRect = GetGeometryForFrame(frame, frameOffset, nullptr);
}
return frame;
}
Selection*
nsCaret::GetSelectionInternal()
{
return static_cast<Selection*>(GetSelection());
}
void nsCaret::SchedulePaint()
{
Selection* selection = GetSelectionInternal();
nsINode* focusNode;
if (mOverrideContent) {
focusNode = mOverrideContent;
} else if (selection) {
focusNode = selection->GetFocusNode();
} else {
return;
}
if (!focusNode || !focusNode->IsContent()) {
return;
}
nsIFrame* f = focusNode->AsContent()->GetPrimaryFrame();
if (!f) {
return;
}
// This may not be the correct continuation frame, but that's OK since we're
// just scheduling a paint of the window (or popup).
f->SchedulePaint();
}
void nsCaret::SetVisibilityDuringSelection(bool aVisibility)
{
mShowDuringSelection = aVisibility;
SchedulePaint();
}
void
nsCaret::SetCaretPosition(nsIDOMNode* aNode, int32_t aOffset)
{
mOverrideContent = do_QueryInterface(aNode);
mOverrideOffset = aOffset;
ResetBlinking();
SchedulePaint();
}
bool
nsCaret::IsBidiUI()
{
nsIFrame* frame = nullptr;
if(Selection* selection = GetSelectionInternal()) {
int32_t contentOffset;
frame = GetFrameAndOffset(selection, mOverrideContent, mOverrideOffset,
&contentOffset);
}
return (frame && frame->GetStateBits() & NS_FRAME_IS_BIDI) ||
Preferences::GetBool("bidi.browser.ui");
}
void
nsCaret::CheckSelectionLanguageChange()
{
if (!IsBidiUI()) {
return;
}
bool isKeyboardRTL = IsKeyboardRTL();
// Call SelectionLanguageChange on every paint. Mostly it will be a noop
// but it should be fast anyway. This guarantees we never paint the caret
// at the wrong place.
Selection* selection = GetSelectionInternal();
if (selection) {
selection->SelectionLanguageChange(isKeyboardRTL);
}
}
nsIFrame*
nsCaret::GetPaintGeometry(nsRect* aRect)
{
// Return null if we should not be visible.
if (!IsVisible() || !mIsBlinkOn) {
return nullptr;
}
// Update selection language direction now so the new direction will be
// taken into account when computing the caret position below.
CheckSelectionLanguageChange();
int32_t frameOffset;
nsIFrame *frame = GetFrameAndOffset(GetSelectionInternal(),
mOverrideContent, mOverrideOffset, &frameOffset);
if (!frame) {
return nullptr;
}
// now we have a frame, check whether it's appropriate to show the caret here
const nsStyleUserInterface* userinterface = frame->StyleUserInterface();
if ((!mIgnoreUserModify &&
userinterface->mUserModify == NS_STYLE_USER_MODIFY_READ_ONLY) ||
userinterface->mUserInput == NS_STYLE_USER_INPUT_NONE ||
userinterface->mUserInput == NS_STYLE_USER_INPUT_DISABLED) {
return nullptr;
}
// If the offset falls outside of the frame, then don't paint the caret.
int32_t startOffset, endOffset;
if (frame->GetType() == nsGkAtoms::textFrame &&
(NS_FAILED(frame->GetOffsets(startOffset, endOffset)) ||
startOffset > frameOffset ||
endOffset < frameOffset)) {
return nullptr;
}
nsRect caretRect;
nsRect hookRect;
ComputeCaretRects(frame, frameOffset, &caretRect, &hookRect);
aRect->UnionRect(caretRect, hookRect);
return frame;
}
void nsCaret::PaintCaret(nsDisplayListBuilder *aBuilder,
DrawTarget& aDrawTarget,
nsIFrame* aForFrame,
const nsPoint &aOffset)
{
int32_t contentOffset;
nsIFrame* frame = GetFrameAndOffset(GetSelectionInternal(),
mOverrideContent, mOverrideOffset, &contentOffset);
if (!frame) {
return;
}
NS_ASSERTION(frame == aForFrame, "We're referring different frame");
int32_t appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel();
nsRect caretRect;
nsRect hookRect;
ComputeCaretRects(frame, contentOffset, &caretRect, &hookRect);
Rect devPxCaretRect =
NSRectToSnappedRect(caretRect + aOffset, appUnitsPerDevPixel, aDrawTarget);
Rect devPxHookRect =
NSRectToSnappedRect(hookRect + aOffset, appUnitsPerDevPixel, aDrawTarget);
ColorPattern color(ToDeviceColor(frame->GetCaretColorAt(contentOffset)));
aDrawTarget.FillRect(devPxCaretRect, color);
if (!hookRect.IsEmpty()) {
aDrawTarget.FillRect(devPxHookRect, color);
}
}
NS_IMETHODIMP
nsCaret::NotifySelectionChanged(nsIDOMDocument *, nsISelection *aDomSel,
int16_t aReason)
{
if ((aReason & nsISelectionListener::MOUSEUP_REASON) || !IsVisible())//this wont do
return NS_OK;
nsCOMPtr<nsISelection> domSel(do_QueryReferent(mDomSelectionWeak));
// The same caret is shared amongst the document and any text widgets it
// may contain. This means that the 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 (mDomSelectionWeak), then there
// is nothing to do!
if (domSel != aDomSel)
return NS_OK;
ResetBlinking();
SchedulePaint();
return NS_OK;
}
void nsCaret::ResetBlinking()
{
mIsBlinkOn = true;
if (mReadOnly || !mVisible || mHideCount) {
StopBlinking();
return;
}
if (mBlinkTimer) {
mBlinkTimer->Cancel();
} else {
nsresult err;
mBlinkTimer = do_CreateInstance("@mozilla.org/timer;1", &err);
if (NS_FAILED(err))
return;
}
uint32_t blinkRate = static_cast<uint32_t>(
LookAndFeel::GetInt(LookAndFeel::eIntID_CaretBlinkTime, 500));
if (blinkRate > 0) {
mBlinkCount = Preferences::GetInt("ui.caretBlinkCount", -1);
mBlinkTimer->InitWithFuncCallback(CaretBlinkCallback, this, blinkRate,
nsITimer::TYPE_REPEATING_SLACK);
}
}
void nsCaret::StopBlinking()
{
if (mBlinkTimer)
{
mBlinkTimer->Cancel();
}
}
nsresult
nsCaret::GetCaretFrameForNodeOffset(nsFrameSelection* aFrameSelection,
nsIContent* aContentNode,
int32_t aOffset,
CaretAssociationHint aFrameHint,
nsBidiLevel aBidiLevel,
nsIFrame** aReturnFrame,
int32_t* aReturnOffset)
{
if (!aFrameSelection)
return NS_ERROR_FAILURE;
nsIPresShell* presShell = aFrameSelection->GetShell();
if (!presShell)
return NS_ERROR_FAILURE;
if (!aContentNode || !aContentNode->IsInComposedDoc() ||
presShell->GetDocument() != aContentNode->GetComposedDoc())
return NS_ERROR_FAILURE;
nsIFrame* theFrame = nullptr;
int32_t theFrameOffset = 0;
theFrame = aFrameSelection->GetFrameForNodeOffset(
aContentNode, aOffset, aFrameHint, &theFrameOffset);
if (!theFrame)
return NS_ERROR_FAILURE;
// if theFrame is after a text frame that's logically at the end of the line
// (e.g. if theFrame is a <br> frame), then put the caret at the end of
// that text frame instead. This way, the caret will be positioned as if
// trailing whitespace was not trimmed.
AdjustCaretFrameForLineEnd(&theFrame, &theFrameOffset);
// Mamdouh : modification of the caret to work at rtl and ltr with Bidi
//
// Direction Style from visibility->mDirection
// ------------------
// NS_STYLE_DIRECTION_LTR : LTR or Default
// NS_STYLE_DIRECTION_RTL
if (theFrame->PresContext()->BidiEnabled())
{
// If there has been a reflow, take the caret Bidi level to be the level of the current frame
if (aBidiLevel & BIDI_LEVEL_UNDEFINED) {
aBidiLevel = NS_GET_EMBEDDING_LEVEL(theFrame);
}
int32_t start;
int32_t end;
nsIFrame* frameBefore;
nsIFrame* frameAfter;
nsBidiLevel levelBefore; // Bidi level of the character before the caret
nsBidiLevel levelAfter; // Bidi level of the character after the caret
theFrame->GetOffsets(start, end);
if (start == 0 || end == 0 || start == theFrameOffset || end == theFrameOffset)
{
nsPrevNextBidiLevels levels = aFrameSelection->
GetPrevNextBidiLevels(aContentNode, aOffset, false);
/* Boundary condition, we need to know the Bidi levels of the characters before and after the caret */
if (levels.mFrameBefore || levels.mFrameAfter)
{
frameBefore = levels.mFrameBefore;
frameAfter = levels.mFrameAfter;
levelBefore = levels.mLevelBefore;
levelAfter = levels.mLevelAfter;
if ((levelBefore != levelAfter) || (aBidiLevel != levelBefore))
{
aBidiLevel = std::max(aBidiLevel, std::min(levelBefore, levelAfter)); // rule c3
aBidiLevel = std::min(aBidiLevel, std::max(levelBefore, levelAfter)); // rule c4
if (aBidiLevel == levelBefore // rule c1
|| (aBidiLevel > levelBefore && aBidiLevel < levelAfter &&
IS_SAME_DIRECTION(aBidiLevel, levelBefore)) // rule c5
|| (aBidiLevel < levelBefore && aBidiLevel > levelAfter &&
IS_SAME_DIRECTION(aBidiLevel, levelBefore))) // rule c9
{
if (theFrame != frameBefore)
{
if (frameBefore) // if there is a frameBefore, move into it
{
theFrame = frameBefore;
theFrame->GetOffsets(start, end);
theFrameOffset = end;
}
else
{
// if there is no frameBefore, we must be at the beginning of the line
// so we stay with the current frame.
// Exception: when the first frame on the line has a different Bidi level from the paragraph level, there is no
// real frame for the caret to be in. We have to find the visually first frame on the line.
nsBidiLevel baseLevel = NS_GET_BASE_LEVEL(frameAfter);
if (baseLevel != levelAfter)
{
nsPeekOffsetStruct pos(eSelectBeginLine, eDirPrevious, 0,
nsPoint(0, 0), false, true, false,
true, false);
if (NS_SUCCEEDED(frameAfter->PeekOffset(&pos))) {
theFrame = pos.mResultFrame;
theFrameOffset = pos.mContentOffset;
}
}
}
}
}
else if (aBidiLevel == levelAfter // rule c2
|| (aBidiLevel > levelBefore && aBidiLevel < levelAfter &&
IS_SAME_DIRECTION(aBidiLevel, levelAfter)) // rule c6
|| (aBidiLevel < levelBefore && aBidiLevel > levelAfter &&
IS_SAME_DIRECTION(aBidiLevel, levelAfter))) // rule c10
{
if (theFrame != frameAfter)
{
if (frameAfter)
{
// if there is a frameAfter, move into it
theFrame = frameAfter;
theFrame->GetOffsets(start, end);
theFrameOffset = start;
}
else
{
// if there is no frameAfter, we must be at the end of the line
// so we stay with the current frame.
// Exception: when the last frame on the line has a different Bidi level from the paragraph level, there is no
// real frame for the caret to be in. We have to find the visually last frame on the line.
nsBidiLevel baseLevel = NS_GET_BASE_LEVEL(frameBefore);
if (baseLevel != levelBefore)
{
nsPeekOffsetStruct pos(eSelectEndLine, eDirNext, 0,
nsPoint(0, 0), false, true, false,
true, false);
if (NS_SUCCEEDED(frameBefore->PeekOffset(&pos))) {
theFrame = pos.mResultFrame;
theFrameOffset = pos.mContentOffset;
}
}
}
}
}
else if (aBidiLevel > levelBefore && aBidiLevel < levelAfter // rule c7/8
&& IS_SAME_DIRECTION(levelBefore, levelAfter) // before and after have the same parity
&& !IS_SAME_DIRECTION(aBidiLevel, levelAfter)) // caret has different parity
{
if (NS_SUCCEEDED(aFrameSelection->GetFrameFromLevel(frameAfter, eDirNext, aBidiLevel, &theFrame)))
{
theFrame->GetOffsets(start, end);
levelAfter = NS_GET_EMBEDDING_LEVEL(theFrame);
if (IS_LEVEL_RTL(aBidiLevel)) // c8: caret to the right of the rightmost character
theFrameOffset = IS_LEVEL_RTL(levelAfter) ? start : end;
else // c7: caret to the left of the leftmost character
theFrameOffset = IS_LEVEL_RTL(levelAfter) ? end : start;
}
}
else if (aBidiLevel < levelBefore && aBidiLevel > levelAfter // rule c11/12
&& IS_SAME_DIRECTION(levelBefore, levelAfter) // before and after have the same parity
&& !IS_SAME_DIRECTION(aBidiLevel, levelAfter)) // caret has different parity
{
if (NS_SUCCEEDED(aFrameSelection->GetFrameFromLevel(frameBefore, eDirPrevious, aBidiLevel, &theFrame)))
{
theFrame->GetOffsets(start, end);
levelBefore = NS_GET_EMBEDDING_LEVEL(theFrame);
if (IS_LEVEL_RTL(aBidiLevel)) // c12: caret to the left of the leftmost character
theFrameOffset = IS_LEVEL_RTL(levelBefore) ? end : start;
else // c11: caret to the right of the rightmost character
theFrameOffset = IS_LEVEL_RTL(levelBefore) ? start : end;
}
}
}
}
}
}
*aReturnFrame = theFrame;
*aReturnOffset = theFrameOffset;
return NS_OK;
}
size_t nsCaret::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
{
size_t total = aMallocSizeOf(this);
if (mPresShell) {
// We only want the size of the nsWeakReference object, not the PresShell
// (since we don't own the PresShell).
total += mPresShell->SizeOfOnlyThis(aMallocSizeOf);
}
if (mDomSelectionWeak) {
// We only want size of the nsWeakReference object, not the selection
// (again, we don't own the selection).
total += mDomSelectionWeak->SizeOfOnlyThis(aMallocSizeOf);
}
if (mBlinkTimer) {
total += mBlinkTimer->SizeOfIncludingThis(aMallocSizeOf);
}
return total;
}
bool nsCaret::IsMenuPopupHidingCaret()
{
#ifdef MOZ_XUL
// Check if there are open popups.
nsXULPopupManager *popMgr = nsXULPopupManager::GetInstance();
nsTArray<nsIFrame*> popups;
popMgr->GetVisiblePopups(popups);
if (popups.Length() == 0)
return false; // No popups, so caret can't be hidden by them.
// Get the selection focus content, that's where the caret would
// go if it was drawn.
nsCOMPtr<nsIDOMNode> node;
nsCOMPtr<nsISelection> domSelection = do_QueryReferent(mDomSelectionWeak);
if (!domSelection)
return true; // No selection/caret to draw.
domSelection->GetFocusNode(getter_AddRefs(node));
if (!node)
return true; // No selection/caret to draw.
nsCOMPtr<nsIContent> caretContent = do_QueryInterface(node);
if (!caretContent)
return true; // No selection/caret to draw.
// If there's a menu popup open before the popup with
// the caret, don't show the caret.
for (uint32_t i=0; i<popups.Length(); i++) {
nsMenuPopupFrame* popupFrame = static_cast<nsMenuPopupFrame*>(popups[i]);
nsIContent* popupContent = popupFrame->GetContent();
if (nsContentUtils::ContentIsDescendantOf(caretContent, popupContent)) {
// The caret is in this popup. There were no menu popups before this
// popup, so don't hide the caret.
return false;
}
if (popupFrame->PopupType() == ePopupTypeMenu && !popupFrame->IsContextMenu()) {
// This is an open menu popup. It does not contain the caret (else we'd
// have returned above). Even if the caret is in a subsequent popup,
// or another document/frame, it should be hidden.
return true;
}
}
#endif
// There are no open menu popups, no need to hide the caret.
return false;
}
void
nsCaret::ComputeCaretRects(nsIFrame* aFrame, int32_t aFrameOffset,
nsRect* aCaretRect, nsRect* aHookRect)
{
NS_ASSERTION(aFrame, "Should have a frame here");
WritingMode wm = aFrame->GetWritingMode();
bool isVertical = wm.IsVertical();
nscoord bidiIndicatorSize;
*aCaretRect = GetGeometryForFrame(aFrame, aFrameOffset, &bidiIndicatorSize);
// on RTL frames the right edge of mCaretRect must be equal to framePos
const nsStyleVisibility* vis = aFrame->StyleVisibility();
if (NS_STYLE_DIRECTION_RTL == vis->mDirection) {
if (isVertical) {
aCaretRect->y -= aCaretRect->height;
} else {
aCaretRect->x -= aCaretRect->width;
}
}
aHookRect->SetEmpty();
Selection* selection = GetSelectionInternal();
if (!selection || !selection->GetFrameSelection()) {
return;
}
if (IsBidiUI() || IsKeyboardRTL()) {
// If caret level is RTL, draw the hook on the left; if LTR, to the right
// The height of the hook rectangle is the same as the width of the caret
// rectangle.
int caretBidiLevel = selection->GetFrameSelection()->GetCaretBidiLevel();
if (caretBidiLevel & BIDI_LEVEL_UNDEFINED) {
caretBidiLevel = NS_GET_EMBEDDING_LEVEL(aFrame);
}
bool isCaretRTL = caretBidiLevel % 2;
if (isVertical) {
bool isSidewaysLR = wm.IsVerticalLR() && !wm.IsLineInverted();
if (isSidewaysLR) {
aHookRect->SetRect(aCaretRect->x + bidiIndicatorSize,
aCaretRect->y + (!isCaretRTL ? bidiIndicatorSize * -1 :
aCaretRect->height),
aCaretRect->height,
bidiIndicatorSize);
} else {
aHookRect->SetRect(aCaretRect->XMost() - bidiIndicatorSize,
aCaretRect->y + (isCaretRTL ? bidiIndicatorSize * -1 :
aCaretRect->height),
aCaretRect->height,
bidiIndicatorSize);
}
} else {
aHookRect->SetRect(aCaretRect->x + (isCaretRTL ? bidiIndicatorSize * -1 :
aCaretRect->width),
aCaretRect->y + bidiIndicatorSize,
bidiIndicatorSize,
aCaretRect->width);
}
}
}
/* static */
void nsCaret::CaretBlinkCallback(nsITimer* aTimer, void* aClosure)
{
nsCaret* theCaret = reinterpret_cast<nsCaret*>(aClosure);
if (!theCaret) {
return;
}
theCaret->mIsBlinkOn = !theCaret->mIsBlinkOn;
theCaret->SchedulePaint();
// mBlinkCount of -1 means blink count is not enabled.
if (theCaret->mBlinkCount == -1) {
return;
}
// Track the blink count, but only at end of a blink cycle.
if (!theCaret->mIsBlinkOn) {
// If we exceeded the blink count, stop the timer.
if (--theCaret->mBlinkCount <= 0) {
theCaret->StopBlinking();
}
}
}
void
nsCaret::SetIgnoreUserModify(bool aIgnoreUserModify)
{
mIgnoreUserModify = aIgnoreUserModify;
SchedulePaint();
}