/* -*- 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/. */
/* the caret is the text cursor used, e.g., when editing */
using BidiEmbeddingLevel = mozilla::intl::BidiEmbeddingLevel;
// 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 staticconst int32_t kMinBidiIndicatorPixels = 2;
mPresShell =
do_GetWeakReference(aPresShell); // the presshell owns us, so no addref
NS_ASSERTION(mPresShell, "Hey, pres shell should support weak refs");
RefPtr<Selection> selection =
aPresShell->GetSelection(nsISelectionController::SELECTION_NORMAL); if (!selection) { return NS_ERROR_FAILURE;
}
// 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 if (mDomSelectionWeak) {
mDomSelectionWeak->RemoveSelectionListener(this);
}
mDomSelectionWeak = nullptr;
mPresShell = nullptr;
mCaretPosition = {};
}
void nsCaret::CaretVisibilityMaybeChanged() {
ResetBlinking();
SchedulePaint(); if (IsVisible()) { // We ignore caret position updates when the caret is not visible, so we // update the caret position here if needed.
UpdateCaretPositionFromSelectionIfNeeded();
}
}
// Clamp the inline-position to be within our closest scroll frame and any // ancestor clips if any. If we don't, then it clips us, and we don't appear at // all. See bug 335560 and bug 1539720. static nsPoint AdjustRectForClipping(const nsRect& aRect, nsIFrame* aFrame, bool aVertical) {
nsRect rectRelativeToClip = aRect;
ScrollContainerFrame* sf = nullptr;
nsIFrame* scrollFrame = nullptr; for (nsIFrame* current = aFrame; current; current = current->GetParent()) { if ((sf = do_QueryFrame(current))) {
scrollFrame = current; break;
} if (current->IsTransformed()) { // We don't account for transforms in rectRelativeToCurrent, so stop // adjusting here. break;
}
rectRelativeToClip += current->GetPosition();
}
if (!sf) { return {};
}
nsRect clipRect = sf->GetScrollPortRect();
{ constauto& disp = *scrollFrame->StyleDisplay(); if (disp.mOverflowClipBoxBlock == StyleOverflowClipBox::ContentBox ||
disp.mOverflowClipBoxInline == StyleOverflowClipBox::ContentBox) { const WritingMode wm = scrollFrame->GetWritingMode(); constbool cbH = (wm.IsVertical() ? disp.mOverflowClipBoxBlock
: disp.mOverflowClipBoxInline) ==
StyleOverflowClipBox::ContentBox; constbool cbV = (wm.IsVertical() ? disp.mOverflowClipBoxInline
: disp.mOverflowClipBoxBlock) ==
StyleOverflowClipBox::ContentBox;
nsMargin padding = scrollFrame->GetUsedPadding(); if (!cbH) {
padding.left = padding.right = 0;
} if (!cbV) {
padding.top = padding.bottom = 0;
}
clipRect.Deflate(padding);
}
}
nsPoint offset; // 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 (aVertical) {
nscoord overflow = rectRelativeToClip.YMost() - clipRect.YMost(); if (overflow > 0) {
offset.y -= overflow;
} else {
overflow = rectRelativeToClip.y - clipRect.y; if (overflow < 0) {
offset.y -= overflow;
}
}
} else {
nscoord overflow = rectRelativeToClip.XMost() - clipRect.XMost(); if (overflow > 0) {
offset.x -= overflow;
} else {
overflow = rectRelativeToClip.x - clipRect.x; if (overflow < 0) {
offset.x -= overflow;
}
}
} return offset;
}
nsIFrame* frame = aFrame->GetContentInsertionFrame(); if (!frame) {
frame = aFrame;
}
NS_ASSERTION(!frame->HasAnyStateBits(NS_FRAME_IN_REFLOW), "We should not be in the middle of reflow");
WritingMode wm = aFrame->GetWritingMode();
RefPtr<nsFontMetrics> fm =
nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame); constauto caretBlockAxisMetrics = frame->GetCaretBlockAxisMetrics(wm, *fm); constbool vertical = wm.IsVertical();
Metrics caretMetrics =
ComputeMetrics(aFrame, aFrameOffset, caretBlockAxisMetrics.mExtent);
nscoord inlineOffset = 0; if (nsTextFrame* textFrame = do_QueryFrame(aFrame)) { if (gfxTextRun* textRun = textFrame->GetTextRun(nsTextFrame::eInflated)) { // For "upstream" text where the textrun direction is reversed from the // frame's inline-dir we want the caret to be painted before rather than // after its nominal inline position, so we offset by its width. constbool textRunDirIsReverseOfFrame =
wm.IsInlineReversed() != textRun->IsInlineReversed(); // However, in sideways-lr mode we invert this behavior because this is // the one writing mode where bidi-LTR corresponds to inline-reversed // already, which reverses the desired caret placement behavior. // Note that the following condition is equivalent to: // if ( (!textRun->IsSidewaysLeft() && textRunDirIsReverseOfFrame) || // (textRun->IsSidewaysLeft() && !textRunDirIsReverseOfFrame) ) if (textRunDirIsReverseOfFrame != textRun->IsSidewaysLeft()) {
inlineOffset = wm.IsBidiLTR() ? -caretMetrics.mCaretWidth
: caretMetrics.mCaretWidth;
}
}
}
// on RTL frames the right edge of mCaretRect must be equal to framePos if (aFrame->StyleVisibility()->mDirection == StyleDirection::Rtl) { if (vertical) {
inlineOffset -= caretMetrics.mCaretWidth;
} else {
inlineOffset -= caretMetrics.mCaretWidth;
}
}
void nsCaret::SetCaretPosition(nsINode* aNode, int32_t aOffset) { // Schedule a paint with the old position to invalidate.
mFixedCaretPosition = !!aNode; if (mFixedCaretPosition) {
mCaretPosition = {aNode, aOffset};
SchedulePaint();
} else {
UpdateCaretPositionFromSelectionIfNeeded();
}
ResetBlinking();
}
void nsCaret::CheckSelectionLanguageChange() { if (!StaticPrefs::bidi_browser_ui()) { return;
}
bool isKeyboardRTL = false;
nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard(); if (bidiKeyboard) {
bidiKeyboard->IsLangRTL(&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 = GetSelection(); if (selection) {
selection->SelectionLanguageChange(isKeyboardRTL);
}
}
// This ensures that the caret is not affected by clips on inlines and so forth.
[[nodiscard]] static nsIFrame* MapToContainingBlock(nsIFrame* aFrame,
nsRect* aCaretRect,
nsRect* aHookRect) {
nsIFrame* containingBlock = GetContainingBlockIfNeeded(aFrame); if (!containingBlock) { return aFrame;
}
// 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();
auto data = GetFrameAndOffset(mCaretPosition);
MOZ_ASSERT(!!data.mFrame == !!data.mUnadjustedFrame); if (!data.mFrame) { return nullptr;
}
nsIFrame* frame = data.mFrame;
nsIFrame* unadjustedFrame = data.mUnadjustedFrame;
int32_t frameOffset(data.mOffsetInFrameContent); // Now we have a frame, check whether it's appropriate to show the caret here. // Note we need to check the unadjusted frame, otherwise consider the // following case: // // <div contenteditable><span contenteditable=false>Text </span><br> // // Where the selection is targeting the <br>. We want to display the caret, // since the <br> we're focused at is editable, but we do want to paint it at // the adjusted frame offset, so that we can see the collapsed whitespace. if (unadjustedFrame->IsContentDisabled()) { return nullptr;
}
// If the offset falls outside of the frame, then don't paint the caret. if (frame->IsTextFrame()) { auto [startOffset, endOffset] = frame->GetOffsets(); if (startOffset > frameOffset || endOffset < frameOffset) { return nullptr;
}
}
if (aCaretColor) {
*aCaretColor = frame->GetCaretColorAt(frameOffset);
}
NS_IMETHODIMP
nsCaret::NotifySelectionChanged(Document*, Selection* aDomSel, int16_t aReason,
int32_t aAmount) { // 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), or the caret // position is fixed, then there is nothing to do! if (mDomSelectionWeak != aDomSel) { return NS_OK;
}
// Check if we need to hide / un-hide the caret due to the selection being // collapsed. if (!mShowDuringSelection &&
!aDomSel->IsCollapsed() != mHiddenDuringSelection) { if (mHiddenDuringSelection) {
RemoveForceHide();
} else {
AddForceHide();
}
mHiddenDuringSelection = !mHiddenDuringSelection;
}
// We don't bother computing the caret position when invisible. We'll do it if // we become visible in CaretVisibilityMaybeChanged(). if (IsVisible()) {
UpdateCaretPositionFromSelectionIfNeeded();
ResetBlinking();
}
return NS_OK;
}
void nsCaret::ResetBlinking() { using IntID = LookAndFeel::IntID;
// The default caret blinking rate (in ms of blinking interval)
constexpr uint32_t kDefaultBlinkRate = 500; // The default caret blinking count (-1 for "never stop blinking")
constexpr int32_t kDefaultBlinkCount = -1;
mIsBlinkOn = true;
if (mReadOnly || !IsVisible()) {
StopBlinking(); return;
}
auto blinkRate =
LookAndFeel::GetInt(IntID::CaretBlinkTime, kDefaultBlinkRate);
if (blinkRate > 0) { // Make sure to reset the remaining blink count even if the blink rate // hasn't changed.
mBlinkCount =
LookAndFeel::GetInt(IntID::CaretBlinkCount, kDefaultBlinkCount);
}
if (mBlinkRate == blinkRate) { // If the rate hasn't changed, then there is nothing else to do. return;
}
mBlinkRate = blinkRate;
if (mBlinkTimer) {
mBlinkTimer->Cancel();
} else {
mBlinkTimer = NS_NewTimer(GetMainThreadSerialEventTarget()); if (!mBlinkTimer) { return;
}
}
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 (mBlinkTimer) {
total += mBlinkTimer->SizeOfIncludingThis(aMallocSizeOf);
} return total;
}
void nsCaret::ComputeCaretRects(nsIFrame* aFrame, int32_t aFrameOffset,
nsRect* aCaretRect, nsRect* aHookRect) {
MOZ_ASSERT(aCaretRect && aHookRect);
NS_ASSERTION(aFrame, "Should have a frame here");
// Simon -- make a hook to draw to the left or right of the caret to show // keyboard language direction
aHookRect->SetEmpty(); if (!StaticPrefs::bidi_browser_ui()) { return;
}
bool isCaretRTL;
nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard(); // if bidiKeyboard->IsLangRTL() fails, there is no way to tell the // keyboard direction, or the user has no right-to-left keyboard // installed, so we never draw the hook. if (bidiKeyboard && NS_SUCCEEDED(bidiKeyboard->IsLangRTL(&isCaretRTL))) { // If keyboard language 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. if (isVertical) { if (wm.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);
}
}
}
// 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();
}
}
}
¤ Dauer der Verarbeitung: 0.3 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.