1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /* the caret is the text cursor used, e.g., when editing */
14 #include "mozilla/CaretAssociationHint.h"
15 #include "mozilla/gfx/2D.h"
16 #include "mozilla/intl/BidiEmbeddingLevel.h"
17 #include "mozilla/StaticPrefs_bidi.h"
19 #include "nsFontMetrics.h"
21 #include "nsFrameSelection.h"
23 #include "nsIScrollableFrame.h"
24 #include "nsIContent.h"
25 #include "nsIFrameInlines.h"
26 #include "nsLayoutUtils.h"
27 #include "nsPresContext.h"
28 #include "nsBlockFrame.h"
29 #include "nsISelectionController.h"
30 #include "nsTextFrame.h"
31 #include "nsXULPopupManager.h"
32 #include "nsMenuPopupFrame.h"
33 #include "nsTextFragment.h"
34 #include "mozilla/Preferences.h"
35 #include "mozilla/PresShell.h"
36 #include "mozilla/LookAndFeel.h"
37 #include "mozilla/dom/Selection.h"
38 #include "nsIBidiKeyboard.h"
39 #include "nsContentUtils.h"
40 #include "SelectionMovementUtils.h"
42 using namespace mozilla
;
43 using namespace mozilla::dom
;
44 using namespace mozilla::gfx
;
46 using BidiEmbeddingLevel
= mozilla::intl::BidiEmbeddingLevel
;
48 // The bidi indicator hangs off the caret to one side, to show which
49 // direction the typing is in. It needs to be at least 2x2 to avoid looking
50 // like an insignificant dot
51 static const int32_t kMinBidiIndicatorPixels
= 2;
61 mShowDuringSelection(false),
62 mIgnoreUserModify(true) {}
64 nsCaret::~nsCaret() { StopBlinking(); }
66 nsresult
nsCaret::Init(PresShell
* aPresShell
) {
67 NS_ENSURE_ARG(aPresShell
);
70 do_GetWeakReference(aPresShell
); // the presshell owns us, so no addref
71 NS_ASSERTION(mPresShell
, "Hey, pres shell should support weak refs");
73 mShowDuringSelection
=
74 LookAndFeel::GetInt(LookAndFeel::IntID::ShowCaretDuringSelection
,
75 mShowDuringSelection
? 1 : 0) != 0;
77 RefPtr
<Selection
> selection
=
78 aPresShell
->GetSelection(nsISelectionController::SELECTION_NORMAL
);
80 return NS_ERROR_FAILURE
;
83 selection
->AddSelectionListener(this);
84 mDomSelectionWeak
= selection
;
89 static bool DrawCJKCaret(nsIFrame
* aFrame
, int32_t aOffset
) {
90 nsIContent
* content
= aFrame
->GetContent();
91 const nsTextFragment
* frag
= content
->GetText();
95 if (aOffset
< 0 || static_cast<uint32_t>(aOffset
) >= frag
->GetLength()) {
98 const char16_t ch
= frag
->CharAt(AssertedCast
<uint32_t>(aOffset
));
99 return 0x2e80 <= ch
&& ch
<= 0xd7ff;
102 nsCaret::Metrics
nsCaret::ComputeMetrics(nsIFrame
* aFrame
, int32_t aOffset
,
103 nscoord aCaretHeight
) {
104 // Compute nominal sizes in appunits
107 LookAndFeel::GetFloat(LookAndFeel::FloatID::CaretAspectRatio
, 0.0f
)) +
108 nsPresContext::CSSPixelsToAppUnits(
109 LookAndFeel::GetInt(LookAndFeel::IntID::CaretWidth
, 1));
111 if (DrawCJKCaret(aFrame
, aOffset
)) {
112 caretWidth
+= nsPresContext::CSSPixelsToAppUnits(1);
114 nscoord bidiIndicatorSize
=
115 nsPresContext::CSSPixelsToAppUnits(kMinBidiIndicatorPixels
);
116 bidiIndicatorSize
= std::max(caretWidth
, bidiIndicatorSize
);
118 // Round them to device pixels. Always round down, except that anything
119 // between 0 and 1 goes up to 1 so we don't let the caret disappear.
120 int32_t tpp
= aFrame
->PresContext()->AppUnitsPerDevPixel();
122 result
.mCaretWidth
= NS_ROUND_BORDER_TO_PIXELS(caretWidth
, tpp
);
123 result
.mBidiIndicatorSize
= NS_ROUND_BORDER_TO_PIXELS(bidiIndicatorSize
, tpp
);
127 void nsCaret::Terminate() {
128 // this doesn't erase the caret if it's drawn. Should it? We might not have
129 // a good drawing environment during teardown.
132 mBlinkTimer
= nullptr;
134 // unregiser ourselves as a selection listener
135 if (mDomSelectionWeak
) {
136 mDomSelectionWeak
->RemoveSelectionListener(this);
138 mDomSelectionWeak
= nullptr;
139 mPresShell
= nullptr;
141 mOverrideContent
= nullptr;
144 NS_IMPL_ISUPPORTS(nsCaret
, nsISelectionListener
)
146 Selection
* nsCaret::GetSelection() { return mDomSelectionWeak
; }
148 void nsCaret::SetSelection(Selection
* aDOMSel
) {
150 mDomSelectionWeak
= aDOMSel
;
152 SchedulePaint(aDOMSel
);
155 void nsCaret::SetVisible(bool inMakeVisible
) {
156 mVisible
= inMakeVisible
;
157 mIgnoreUserModify
= mVisible
;
162 void nsCaret::AddForceHide() {
163 MOZ_ASSERT(mHideCount
< UINT32_MAX
);
164 if (++mHideCount
> 1) {
171 void nsCaret::RemoveForceHide() {
172 if (!mHideCount
|| --mHideCount
) {
179 void nsCaret::SetCaretReadOnly(bool inMakeReadonly
) {
180 mReadOnly
= inMakeReadonly
;
185 // Clamp the inline-position to be within our closest scroll frame and any
186 // ancestor clips if any. If we don't, then it clips us, and we don't appear at
187 // all. See bug 335560 and bug 1539720.
188 static nsPoint
AdjustRectForClipping(const nsRect
& aRect
, nsIFrame
* aFrame
,
190 nsRect rectRelativeToClip
= aRect
;
191 nsIScrollableFrame
* sf
= nullptr;
192 nsIFrame
* scrollFrame
= nullptr;
193 for (nsIFrame
* current
= aFrame
; current
; current
= current
->GetParent()) {
194 if ((sf
= do_QueryFrame(current
))) {
195 scrollFrame
= current
;
198 if (current
->IsTransformed()) {
199 // We don't account for transforms in rectRelativeToCurrent, so stop
203 rectRelativeToClip
+= current
->GetPosition();
210 nsRect clipRect
= sf
->GetScrollPortRect();
212 const auto& disp
= *scrollFrame
->StyleDisplay();
213 if (disp
.mOverflowClipBoxBlock
== StyleOverflowClipBox::ContentBox
||
214 disp
.mOverflowClipBoxInline
== StyleOverflowClipBox::ContentBox
) {
215 const WritingMode wm
= scrollFrame
->GetWritingMode();
216 const bool cbH
= (wm
.IsVertical() ? disp
.mOverflowClipBoxBlock
217 : disp
.mOverflowClipBoxInline
) ==
218 StyleOverflowClipBox::ContentBox
;
219 const bool cbV
= (wm
.IsVertical() ? disp
.mOverflowClipBoxInline
220 : disp
.mOverflowClipBoxBlock
) ==
221 StyleOverflowClipBox::ContentBox
;
222 nsMargin padding
= scrollFrame
->GetUsedPadding();
224 padding
.left
= padding
.right
= 0;
227 padding
.top
= padding
.bottom
= 0;
229 clipRect
.Deflate(padding
);
233 // Now see if the caret extends beyond the view's bounds. If it does, then
234 // snap it back, put it as close to the edge as it can.
236 nscoord overflow
= rectRelativeToClip
.YMost() - clipRect
.YMost();
238 offset
.y
-= overflow
;
240 overflow
= rectRelativeToClip
.y
- clipRect
.y
;
242 offset
.y
-= overflow
;
246 nscoord overflow
= rectRelativeToClip
.XMost() - clipRect
.XMost();
248 offset
.x
-= overflow
;
250 overflow
= rectRelativeToClip
.x
- clipRect
.x
;
252 offset
.x
-= overflow
;
260 nsRect
nsCaret::GetGeometryForFrame(nsIFrame
* aFrame
, int32_t aFrameOffset
,
261 nscoord
* aBidiIndicatorSize
) {
262 nsPoint
framePos(0, 0);
264 nsresult rv
= aFrame
->GetPointFromOffset(aFrameOffset
, &framePos
);
266 if (aBidiIndicatorSize
) {
267 *aBidiIndicatorSize
= 0;
272 nsIFrame
* frame
= aFrame
->GetContentInsertionFrame();
276 NS_ASSERTION(!frame
->HasAnyStateBits(NS_FRAME_IN_REFLOW
),
277 "We should not be in the middle of reflow");
278 WritingMode wm
= aFrame
->GetWritingMode();
279 RefPtr
<nsFontMetrics
> fm
=
280 nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame
);
281 const auto caretBlockAxisMetrics
= frame
->GetCaretBlockAxisMetrics(wm
, *fm
);
282 const bool vertical
= wm
.IsVertical();
283 Metrics caretMetrics
=
284 ComputeMetrics(aFrame
, aFrameOffset
, caretBlockAxisMetrics
.mExtent
);
286 nscoord inlineOffset
= 0;
287 if (nsTextFrame
* textFrame
= do_QueryFrame(aFrame
)) {
288 if (gfxTextRun
* textRun
= textFrame
->GetTextRun(nsTextFrame::eInflated
)) {
289 // For "upstream" text where the textrun direction is reversed from the
290 // frame's inline-dir we want the caret to be painted before rather than
291 // after its nominal inline position, so we offset by its width.
292 const bool textRunDirIsReverseOfFrame
=
293 wm
.IsInlineReversed() != textRun
->IsInlineReversed();
294 // However, in sideways-lr mode we invert this behavior because this is
295 // the one writing mode where bidi-LTR corresponds to inline-reversed
296 // already, which reverses the desired caret placement behavior.
297 // Note that the following condition is equivalent to:
298 // if ( (!textRun->IsSidewaysLeft() && textRunDirIsReverseOfFrame) ||
299 // (textRun->IsSidewaysLeft() && !textRunDirIsReverseOfFrame) )
300 if (textRunDirIsReverseOfFrame
!= textRun
->IsSidewaysLeft()) {
301 inlineOffset
= wm
.IsBidiLTR() ? -caretMetrics
.mCaretWidth
302 : caretMetrics
.mCaretWidth
;
307 // on RTL frames the right edge of mCaretRect must be equal to framePos
308 if (aFrame
->StyleVisibility()->mDirection
== StyleDirection::Rtl
) {
310 inlineOffset
-= caretMetrics
.mCaretWidth
;
312 inlineOffset
-= caretMetrics
.mCaretWidth
;
317 framePos
.x
= caretBlockAxisMetrics
.mOffset
;
318 framePos
.y
+= inlineOffset
;
320 framePos
.x
+= inlineOffset
;
321 framePos
.y
= caretBlockAxisMetrics
.mOffset
;
324 rect
= nsRect(framePos
, vertical
? nsSize(caretBlockAxisMetrics
.mExtent
,
325 caretMetrics
.mCaretWidth
)
326 : nsSize(caretMetrics
.mCaretWidth
,
327 caretBlockAxisMetrics
.mExtent
));
329 rect
.MoveBy(AdjustRectForClipping(rect
, aFrame
, vertical
));
330 if (aBidiIndicatorSize
) {
331 *aBidiIndicatorSize
= caretMetrics
.mBidiIndicatorSize
;
336 nsIFrame
* nsCaret::GetFrameAndOffset(const Selection
* aSelection
,
337 nsINode
* aOverrideNode
,
338 int32_t aOverrideOffset
,
339 int32_t* aFrameOffset
,
340 nsIFrame
** aUnadjustedFrame
) {
341 if (aUnadjustedFrame
) {
342 *aUnadjustedFrame
= nullptr;
349 focusNode
= aOverrideNode
;
350 focusOffset
= aOverrideOffset
;
351 } else if (aSelection
) {
352 focusNode
= aSelection
->GetFocusNode();
353 focusOffset
= aSelection
->FocusOffset();
358 if (!focusNode
|| !focusNode
->IsContent() || !aSelection
) {
362 nsIContent
* contentNode
= focusNode
->AsContent();
363 nsFrameSelection
* frameSelection
= aSelection
->GetFrameSelection();
364 BidiEmbeddingLevel bidiLevel
= frameSelection
->GetCaretBidiLevel();
365 const CaretFrameData result
=
366 SelectionMovementUtils::GetCaretFrameForNodeOffset(
367 frameSelection
, contentNode
, focusOffset
, frameSelection
->GetHint(),
368 bidiLevel
, ForceEditableRegion::No
);
369 // FIXME: It's odd to update nsFrameSelection within this method which is
370 // named as a getter.
372 frameSelection
->SetHint(result
.mHint
);
374 if (aUnadjustedFrame
) {
375 *aUnadjustedFrame
= result
.mUnadjustedFrame
;
378 *aFrameOffset
= result
.mOffsetInFrameContent
;
380 return result
.mFrame
;
384 nsIFrame
* nsCaret::GetGeometry(const Selection
* aSelection
, nsRect
* aRect
) {
386 nsIFrame
* frame
= GetFrameAndOffset(aSelection
, nullptr, 0, &frameOffset
);
388 *aRect
= GetGeometryForFrame(frame
, frameOffset
, nullptr);
393 [[nodiscard
]] static nsIFrame
* GetContainingBlockIfNeeded(nsIFrame
* aFrame
) {
394 if (aFrame
->IsBlockOutside() || aFrame
->IsBlockFrameOrSubclass()) {
397 return aFrame
->GetContainingBlock();
400 void nsCaret::SchedulePaint(Selection
* aSelection
) {
401 Selection
* selection
;
403 selection
= aSelection
;
405 selection
= GetSelection();
409 nsIFrame
* frame
= GetFrameAndOffset(selection
, mOverrideContent
,
410 mOverrideOffset
, &frameOffset
);
415 if (nsIFrame
* cb
= GetContainingBlockIfNeeded(frame
)) {
418 frame
->SchedulePaint();
422 void nsCaret::SetVisibilityDuringSelection(bool aVisibility
) {
423 mShowDuringSelection
= aVisibility
;
427 void nsCaret::SetCaretPosition(nsINode
* aNode
, int32_t aOffset
) {
428 mOverrideContent
= aNode
;
429 mOverrideOffset
= aOffset
;
435 void nsCaret::CheckSelectionLanguageChange() {
436 if (!StaticPrefs::bidi_browser_ui()) {
440 bool isKeyboardRTL
= false;
441 nsIBidiKeyboard
* bidiKeyboard
= nsContentUtils::GetBidiKeyboard();
443 bidiKeyboard
->IsLangRTL(&isKeyboardRTL
);
445 // Call SelectionLanguageChange on every paint. Mostly it will be a noop
446 // but it should be fast anyway. This guarantees we never paint the caret
447 // at the wrong place.
448 Selection
* selection
= GetSelection();
450 selection
->SelectionLanguageChange(isKeyboardRTL
);
454 // This ensures that the caret is not affected by clips on inlines and so forth.
455 [[nodiscard
]] static nsIFrame
* MapToContainingBlock(nsIFrame
* aFrame
,
458 nsIFrame
* containingBlock
= GetContainingBlockIfNeeded(aFrame
);
459 if (!containingBlock
) {
463 *aCaretRect
= nsLayoutUtils::TransformFrameRectToAncestor(aFrame
, *aCaretRect
,
465 *aHookRect
= nsLayoutUtils::TransformFrameRectToAncestor(aFrame
, *aHookRect
,
467 return containingBlock
;
470 nsIFrame
* nsCaret::GetPaintGeometry(nsRect
* aCaretRect
, nsRect
* aHookRect
,
471 nscolor
* aCaretColor
) {
472 // Return null if we should not be visible.
473 if (!IsVisible() || !mIsBlinkOn
) {
477 // Update selection language direction now so the new direction will be
478 // taken into account when computing the caret position below.
479 CheckSelectionLanguageChange();
482 nsIFrame
* unadjustedFrame
= nullptr;
484 GetFrameAndOffset(GetSelection(), mOverrideContent
, mOverrideOffset
,
485 &frameOffset
, &unadjustedFrame
);
486 MOZ_ASSERT(!!frame
== !!unadjustedFrame
);
491 // Now we have a frame, check whether it's appropriate to show the caret here.
492 // Note we need to check the unadjusted frame, otherwise consider the
495 // <div contenteditable><span contenteditable=false>Text </span><br>
497 // Where the selection is targeting the <br>. We want to display the caret,
498 // since the <br> we're focused at is editable, but we do want to paint it at
499 // the adjusted frame offset, so that we can see the collapsed whitespace.
500 const nsStyleUI
* ui
= unadjustedFrame
->StyleUI();
501 if ((!mIgnoreUserModify
&& ui
->UserModify() == StyleUserModify::ReadOnly
) ||
502 unadjustedFrame
->IsContentDisabled()) {
506 // If the offset falls outside of the frame, then don't paint the caret.
507 if (frame
->IsTextFrame()) {
508 auto [startOffset
, endOffset
] = frame
->GetOffsets();
509 if (startOffset
> frameOffset
|| endOffset
< frameOffset
) {
515 *aCaretColor
= frame
->GetCaretColorAt(frameOffset
);
518 ComputeCaretRects(frame
, frameOffset
, aCaretRect
, aHookRect
);
519 return MapToContainingBlock(frame
, aCaretRect
, aHookRect
);
522 nsIFrame
* nsCaret::GetPaintGeometry(nsRect
* aRect
) {
525 nsIFrame
* frame
= GetPaintGeometry(&caretRect
, &hookRect
);
526 aRect
->UnionRect(caretRect
, hookRect
);
530 void nsCaret::PaintCaret(DrawTarget
& aDrawTarget
, nsIFrame
* aForFrame
,
531 const nsPoint
& aOffset
) {
535 nsIFrame
* frame
= GetPaintGeometry(&caretRect
, &hookRect
, &color
);
536 MOZ_ASSERT(frame
== aForFrame
, "We're referring different frame");
542 int32_t appUnitsPerDevPixel
= frame
->PresContext()->AppUnitsPerDevPixel();
543 Rect devPxCaretRect
= NSRectToSnappedRect(caretRect
+ aOffset
,
544 appUnitsPerDevPixel
, aDrawTarget
);
546 NSRectToSnappedRect(hookRect
+ aOffset
, appUnitsPerDevPixel
, aDrawTarget
);
548 ColorPattern
pattern(ToDeviceColor(color
));
549 aDrawTarget
.FillRect(devPxCaretRect
, pattern
);
550 if (!hookRect
.IsEmpty()) {
551 aDrawTarget
.FillRect(devPxHookRect
, pattern
);
556 nsCaret::NotifySelectionChanged(Document
*, Selection
* aDomSel
, int16_t aReason
,
558 // Note that aDomSel, per the comment below may not be the same as our
559 // selection, but that's OK since if that is the case, it wouldn't have
560 // mattered what IsVisible() returns here, so we just opt for checking
561 // the selection later down below.
562 if ((aReason
& nsISelectionListener::MOUSEUP_REASON
) ||
563 !IsVisible(aDomSel
)) // this wont do
566 // The same caret is shared amongst the document and any text widgets it
567 // may contain. This means that the caret could get notifications from
568 // multiple selections.
570 // If this notification is for a selection that is not the one the
571 // the caret is currently interested in (mDomSelectionWeak), then there
574 if (mDomSelectionWeak
!= aDomSel
) return NS_OK
;
577 SchedulePaint(aDomSel
);
582 void nsCaret::ResetBlinking() {
583 using IntID
= LookAndFeel::IntID
;
585 // The default caret blinking rate (in ms of blinking interval)
586 constexpr uint32_t kDefaultBlinkRate
= 500;
587 // The default caret blinking count (-1 for "never stop blinking")
588 constexpr int32_t kDefaultBlinkCount
= -1;
592 if (mReadOnly
|| !mVisible
|| mHideCount
) {
598 LookAndFeel::GetInt(IntID::CaretBlinkTime
, kDefaultBlinkRate
);
601 // Make sure to reset the remaining blink count even if the blink rate
604 LookAndFeel::GetInt(IntID::CaretBlinkCount
, kDefaultBlinkCount
);
607 if (mBlinkRate
== blinkRate
) {
608 // If the rate hasn't changed, then there is nothing else to do.
612 mBlinkRate
= blinkRate
;
615 mBlinkTimer
->Cancel();
617 mBlinkTimer
= NS_NewTimer(GetMainThreadSerialEventTarget());
624 mBlinkTimer
->InitWithNamedFuncCallback(CaretBlinkCallback
, this, blinkRate
,
625 nsITimer::TYPE_REPEATING_SLACK
,
626 "nsCaret::CaretBlinkCallback_timer");
630 void nsCaret::StopBlinking() {
632 mBlinkTimer
->Cancel();
637 size_t nsCaret::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf
) const {
638 size_t total
= aMallocSizeOf(this);
640 // We only want the size of the nsWeakReference object, not the PresShell
641 // (since we don't own the PresShell).
642 total
+= mPresShell
->SizeOfOnlyThis(aMallocSizeOf
);
645 total
+= mBlinkTimer
->SizeOfIncludingThis(aMallocSizeOf
);
650 bool nsCaret::IsMenuPopupHidingCaret() {
651 // Check if there are open popups.
652 nsXULPopupManager
* popMgr
= nsXULPopupManager::GetInstance();
653 nsTArray
<nsIFrame
*> popups
;
654 popMgr
->GetVisiblePopups(popups
);
656 if (popups
.Length() == 0)
657 return false; // No popups, so caret can't be hidden by them.
659 // Get the selection focus content, that's where the caret would
660 // go if it was drawn.
661 if (!mDomSelectionWeak
) {
662 return true; // No selection/caret to draw.
664 nsCOMPtr
<nsIContent
> caretContent
=
665 nsIContent::FromNodeOrNull(mDomSelectionWeak
->GetFocusNode());
666 if (!caretContent
) return true; // No selection/caret to draw.
668 // If there's a menu popup open before the popup with
669 // the caret, don't show the caret.
670 for (uint32_t i
= 0; i
< popups
.Length(); i
++) {
671 nsMenuPopupFrame
* popupFrame
= static_cast<nsMenuPopupFrame
*>(popups
[i
]);
672 nsIContent
* popupContent
= popupFrame
->GetContent();
674 if (caretContent
->IsInclusiveDescendantOf(popupContent
)) {
675 // The caret is in this popup. There were no menu popups before this
676 // popup, so don't hide the caret.
680 if (popupFrame
->GetPopupType() == widget::PopupType::Menu
&&
681 !popupFrame
->IsContextMenu()) {
682 // This is an open menu popup. It does not contain the caret (else we'd
683 // have returned above). Even if the caret is in a subsequent popup,
684 // or another document/frame, it should be hidden.
689 // There are no open menu popups, no need to hide the caret.
693 void nsCaret::ComputeCaretRects(nsIFrame
* aFrame
, int32_t aFrameOffset
,
694 nsRect
* aCaretRect
, nsRect
* aHookRect
) {
695 NS_ASSERTION(aFrame
, "Should have a frame here");
697 WritingMode wm
= aFrame
->GetWritingMode();
698 bool isVertical
= wm
.IsVertical();
700 nscoord bidiIndicatorSize
;
701 *aCaretRect
= GetGeometryForFrame(aFrame
, aFrameOffset
, &bidiIndicatorSize
);
703 // Simon -- make a hook to draw to the left or right of the caret to show
704 // keyboard language direction
705 aHookRect
->SetEmpty();
706 if (!StaticPrefs::bidi_browser_ui()) {
711 nsIBidiKeyboard
* bidiKeyboard
= nsContentUtils::GetBidiKeyboard();
712 // if bidiKeyboard->IsLangRTL() fails, there is no way to tell the
713 // keyboard direction, or the user has no right-to-left keyboard
714 // installed, so we never draw the hook.
715 if (bidiKeyboard
&& NS_SUCCEEDED(bidiKeyboard
->IsLangRTL(&isCaretRTL
))) {
716 // If keyboard language is RTL, draw the hook on the left; if LTR, to the
717 // right The height of the hook rectangle is the same as the width of the
720 if (wm
.IsSidewaysLR()) {
721 aHookRect
->SetRect(aCaretRect
->x
+ bidiIndicatorSize
,
722 aCaretRect
->y
+ (!isCaretRTL
? bidiIndicatorSize
* -1
723 : aCaretRect
->height
),
724 aCaretRect
->height
, bidiIndicatorSize
);
726 aHookRect
->SetRect(aCaretRect
->XMost() - bidiIndicatorSize
,
727 aCaretRect
->y
+ (isCaretRTL
? bidiIndicatorSize
* -1
728 : aCaretRect
->height
),
729 aCaretRect
->height
, bidiIndicatorSize
);
732 aHookRect
->SetRect(aCaretRect
->x
+ (isCaretRTL
? bidiIndicatorSize
* -1
733 : aCaretRect
->width
),
734 aCaretRect
->y
+ bidiIndicatorSize
, bidiIndicatorSize
,
741 void nsCaret::CaretBlinkCallback(nsITimer
* aTimer
, void* aClosure
) {
742 nsCaret
* theCaret
= reinterpret_cast<nsCaret
*>(aClosure
);
746 theCaret
->mIsBlinkOn
= !theCaret
->mIsBlinkOn
;
747 theCaret
->SchedulePaint();
749 // mBlinkCount of -1 means blink count is not enabled.
750 if (theCaret
->mBlinkCount
== -1) {
754 // Track the blink count, but only at end of a blink cycle.
755 if (theCaret
->mIsBlinkOn
) {
756 // If we exceeded the blink count, stop the timer.
757 if (--theCaret
->mBlinkCount
<= 0) {
758 theCaret
->StopBlinking();
763 void nsCaret::SetIgnoreUserModify(bool aIgnoreUserModify
) {
764 mIgnoreUserModify
= aIgnoreUserModify
;