Backed out 2 changesets (bug 1943998) for causing wd failures @ phases.py CLOSED...
[gecko.git] / layout / base / nsCaret.cpp
blobf76e54f844f3f55372e37caafc7e394322759eac
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 */
9 #include "nsCaret.h"
11 #include <algorithm>
13 #include "gfxUtils.h"
14 #include "mozilla/CaretAssociationHint.h"
15 #include "mozilla/gfx/2D.h"
16 #include "mozilla/intl/BidiEmbeddingLevel.h"
17 #include "mozilla/ScrollContainerFrame.h"
18 #include "mozilla/StaticPrefs_bidi.h"
19 #include "nsCOMPtr.h"
20 #include "nsFontMetrics.h"
21 #include "nsITimer.h"
22 #include "nsFrameSelection.h"
23 #include "nsIFrame.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;
53 nsCaret::nsCaret() = default;
55 nsCaret::~nsCaret() { StopBlinking(); }
57 nsresult nsCaret::Init(PresShell* aPresShell) {
58 NS_ENSURE_ARG(aPresShell);
60 mPresShell =
61 do_GetWeakReference(aPresShell); // the presshell owns us, so no addref
62 NS_ASSERTION(mPresShell, "Hey, pres shell should support weak refs");
64 RefPtr<Selection> selection =
65 aPresShell->GetSelection(nsISelectionController::SELECTION_NORMAL);
66 if (!selection) {
67 return NS_ERROR_FAILURE;
70 selection->AddSelectionListener(this);
71 mDomSelectionWeak = selection;
72 UpdateCaretPositionFromSelectionIfNeeded();
74 return NS_OK;
77 static bool DrawCJKCaret(nsIFrame* aFrame, int32_t aOffset) {
78 nsIContent* content = aFrame->GetContent();
79 const nsTextFragment* frag = content->GetText();
80 if (!frag) {
81 return false;
83 if (aOffset < 0 || static_cast<uint32_t>(aOffset) >= frag->GetLength()) {
84 return false;
86 const char16_t ch = frag->CharAt(AssertedCast<uint32_t>(aOffset));
87 return 0x2e80 <= ch && ch <= 0xd7ff;
90 nsCaret::Metrics nsCaret::ComputeMetrics(nsIFrame* aFrame, int32_t aOffset,
91 nscoord aCaretHeight) {
92 // Compute nominal sizes in appunits
93 nscoord caretWidth =
94 (aCaretHeight *
95 LookAndFeel::GetFloat(LookAndFeel::FloatID::CaretAspectRatio, 0.0f)) +
96 nsPresContext::CSSPixelsToAppUnits(
97 LookAndFeel::GetInt(LookAndFeel::IntID::CaretWidth, 1));
99 if (DrawCJKCaret(aFrame, aOffset)) {
100 caretWidth += nsPresContext::CSSPixelsToAppUnits(1);
102 nscoord bidiIndicatorSize =
103 nsPresContext::CSSPixelsToAppUnits(kMinBidiIndicatorPixels);
104 bidiIndicatorSize = std::max(caretWidth, bidiIndicatorSize);
106 // Round them to device pixels. Always round down, except that anything
107 // between 0 and 1 goes up to 1 so we don't let the caret disappear.
108 int32_t tpp = aFrame->PresContext()->AppUnitsPerDevPixel();
109 Metrics result;
110 result.mCaretWidth = NS_ROUND_BORDER_TO_PIXELS(caretWidth, tpp);
111 result.mBidiIndicatorSize = NS_ROUND_BORDER_TO_PIXELS(bidiIndicatorSize, tpp);
112 return result;
115 void nsCaret::Terminate() {
116 // this doesn't erase the caret if it's drawn. Should it? We might not have
117 // a good drawing environment during teardown.
119 StopBlinking();
120 mBlinkTimer = nullptr;
122 // unregiser ourselves as a selection listener
123 if (mDomSelectionWeak) {
124 mDomSelectionWeak->RemoveSelectionListener(this);
126 mDomSelectionWeak = nullptr;
127 mPresShell = nullptr;
128 mCaretPosition = {};
131 NS_IMPL_ISUPPORTS(nsCaret, nsISelectionListener)
133 Selection* nsCaret::GetSelection() { return mDomSelectionWeak; }
135 void nsCaret::SetSelection(Selection* aDOMSel) {
136 MOZ_ASSERT(aDOMSel);
137 mDomSelectionWeak = aDOMSel;
138 UpdateCaretPositionFromSelectionIfNeeded();
139 ResetBlinking();
140 SchedulePaint();
143 void nsCaret::SetVisible(bool aVisible) {
144 const bool wasVisible = mVisible;
145 mVisible = aVisible;
146 if (mVisible != wasVisible) {
147 CaretVisibilityMaybeChanged();
151 bool nsCaret::IsVisible() const { return mVisible && !mHideCount; }
153 void nsCaret::CaretVisibilityMaybeChanged() {
154 ResetBlinking();
155 SchedulePaint();
156 if (IsVisible()) {
157 // We ignore caret position updates when the caret is not visible, so we
158 // update the caret position here if needed.
159 UpdateCaretPositionFromSelectionIfNeeded();
163 void nsCaret::AddForceHide() {
164 MOZ_ASSERT(mHideCount < UINT32_MAX);
165 if (++mHideCount > 1) {
166 return;
168 CaretVisibilityMaybeChanged();
171 void nsCaret::RemoveForceHide() {
172 if (!mHideCount || --mHideCount) {
173 return;
175 CaretVisibilityMaybeChanged();
178 void nsCaret::SetCaretReadOnly(bool aReadOnly) {
179 mReadOnly = aReadOnly;
180 ResetBlinking();
181 SchedulePaint();
184 // Clamp the inline-position to be within our closest scroll frame and any
185 // ancestor clips if any. If we don't, then it clips us, and we don't appear at
186 // all. See bug 335560 and bug 1539720.
187 static nsPoint AdjustRectForClipping(const nsRect& aRect, nsIFrame* aFrame,
188 bool aVertical) {
189 nsRect rectRelativeToClip = aRect;
190 ScrollContainerFrame* sf = nullptr;
191 nsIFrame* scrollFrame = nullptr;
192 for (nsIFrame* current = aFrame; current; current = current->GetParent()) {
193 if ((sf = do_QueryFrame(current))) {
194 scrollFrame = current;
195 break;
197 if (current->IsTransformed()) {
198 // We don't account for transforms in rectRelativeToCurrent, so stop
199 // adjusting here.
200 break;
202 rectRelativeToClip += current->GetPosition();
205 if (!sf) {
206 return {};
209 nsRect clipRect = sf->GetScrollPortRect();
211 const auto& disp = *scrollFrame->StyleDisplay();
212 if (disp.mOverflowClipBoxBlock == StyleOverflowClipBox::ContentBox ||
213 disp.mOverflowClipBoxInline == StyleOverflowClipBox::ContentBox) {
214 const WritingMode wm = scrollFrame->GetWritingMode();
215 const bool cbH = (wm.IsVertical() ? disp.mOverflowClipBoxBlock
216 : disp.mOverflowClipBoxInline) ==
217 StyleOverflowClipBox::ContentBox;
218 const bool cbV = (wm.IsVertical() ? disp.mOverflowClipBoxInline
219 : disp.mOverflowClipBoxBlock) ==
220 StyleOverflowClipBox::ContentBox;
221 nsMargin padding = scrollFrame->GetUsedPadding();
222 if (!cbH) {
223 padding.left = padding.right = 0;
225 if (!cbV) {
226 padding.top = padding.bottom = 0;
228 clipRect.Deflate(padding);
231 nsPoint offset;
232 // Now see if the caret extends beyond the view's bounds. If it does, then
233 // snap it back, put it as close to the edge as it can.
234 if (aVertical) {
235 nscoord overflow = rectRelativeToClip.YMost() - clipRect.YMost();
236 if (overflow > 0) {
237 offset.y -= overflow;
238 } else {
239 overflow = rectRelativeToClip.y - clipRect.y;
240 if (overflow < 0) {
241 offset.y -= overflow;
244 } else {
245 nscoord overflow = rectRelativeToClip.XMost() - clipRect.XMost();
246 if (overflow > 0) {
247 offset.x -= overflow;
248 } else {
249 overflow = rectRelativeToClip.x - clipRect.x;
250 if (overflow < 0) {
251 offset.x -= overflow;
255 return offset;
258 /* static */
259 nsRect nsCaret::GetGeometryForFrame(nsIFrame* aFrame, int32_t aFrameOffset,
260 nscoord* aBidiIndicatorSize) {
261 nsPoint framePos(0, 0);
262 nsRect rect;
263 nsresult rv = aFrame->GetPointFromOffset(aFrameOffset, &framePos);
264 if (NS_FAILED(rv)) {
265 if (aBidiIndicatorSize) {
266 *aBidiIndicatorSize = 0;
268 return rect;
271 nsIFrame* frame = aFrame->GetContentInsertionFrame();
272 if (!frame) {
273 frame = aFrame;
275 NS_ASSERTION(!frame->HasAnyStateBits(NS_FRAME_IN_REFLOW),
276 "We should not be in the middle of reflow");
277 WritingMode wm = aFrame->GetWritingMode();
278 RefPtr<nsFontMetrics> fm =
279 nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame);
280 const auto caretBlockAxisMetrics = frame->GetCaretBlockAxisMetrics(wm, *fm);
281 const bool vertical = wm.IsVertical();
282 Metrics caretMetrics =
283 ComputeMetrics(aFrame, aFrameOffset, caretBlockAxisMetrics.mExtent);
285 nscoord inlineOffset = 0;
286 if (nsTextFrame* textFrame = do_QueryFrame(aFrame)) {
287 if (gfxTextRun* textRun = textFrame->GetTextRun(nsTextFrame::eInflated)) {
288 // For "upstream" text where the textrun direction is reversed from the
289 // frame's inline-dir we want the caret to be painted before rather than
290 // after its nominal inline position, so we offset by its width.
291 const bool textRunDirIsReverseOfFrame =
292 wm.IsInlineReversed() != textRun->IsInlineReversed();
293 // However, in sideways-lr mode we invert this behavior because this is
294 // the one writing mode where bidi-LTR corresponds to inline-reversed
295 // already, which reverses the desired caret placement behavior.
296 // Note that the following condition is equivalent to:
297 // if ( (!textRun->IsSidewaysLeft() && textRunDirIsReverseOfFrame) ||
298 // (textRun->IsSidewaysLeft() && !textRunDirIsReverseOfFrame) )
299 if (textRunDirIsReverseOfFrame != textRun->IsSidewaysLeft()) {
300 inlineOffset = wm.IsBidiLTR() ? -caretMetrics.mCaretWidth
301 : caretMetrics.mCaretWidth;
306 // on RTL frames the right edge of mCaretRect must be equal to framePos
307 if (aFrame->StyleVisibility()->mDirection == StyleDirection::Rtl) {
308 if (vertical) {
309 inlineOffset -= caretMetrics.mCaretWidth;
310 } else {
311 inlineOffset -= caretMetrics.mCaretWidth;
315 if (vertical) {
316 framePos.x = caretBlockAxisMetrics.mOffset;
317 framePos.y += inlineOffset;
318 } else {
319 framePos.x += inlineOffset;
320 framePos.y = caretBlockAxisMetrics.mOffset;
323 rect = nsRect(framePos, vertical ? nsSize(caretBlockAxisMetrics.mExtent,
324 caretMetrics.mCaretWidth)
325 : nsSize(caretMetrics.mCaretWidth,
326 caretBlockAxisMetrics.mExtent));
328 rect.MoveBy(AdjustRectForClipping(rect, aFrame, vertical));
329 if (aBidiIndicatorSize) {
330 *aBidiIndicatorSize = caretMetrics.mBidiIndicatorSize;
332 return rect;
335 auto nsCaret::CaretPositionFor(const Selection* aSelection) -> CaretPosition {
336 if (!aSelection) {
337 return {};
339 const nsFrameSelection* frameSelection = aSelection->GetFrameSelection();
340 if (!frameSelection) {
341 return {};
343 nsINode* node = aSelection->GetFocusNode();
344 if (!node) {
345 return {};
347 return {
348 node,
349 int32_t(aSelection->FocusOffset()),
350 frameSelection->GetHint(),
351 frameSelection->GetCaretBidiLevel(),
355 CaretFrameData nsCaret::GetFrameAndOffset(const CaretPosition& aPosition) {
356 nsINode* focusNode = aPosition.mContent;
357 int32_t focusOffset = aPosition.mOffset;
359 if (!focusNode || !focusNode->IsContent()) {
360 return {};
363 nsIContent* contentNode = focusNode->AsContent();
364 return SelectionMovementUtils::GetCaretFrameForNodeOffset(
365 nullptr, contentNode, focusOffset, aPosition.mHint, aPosition.mBidiLevel,
366 ForceEditableRegion::No);
369 /* static */
370 nsIFrame* nsCaret::GetGeometry(const Selection* aSelection, nsRect* aRect) {
371 auto data = GetFrameAndOffset(CaretPositionFor(aSelection));
372 if (data.mFrame) {
373 *aRect =
374 GetGeometryForFrame(data.mFrame, data.mOffsetInFrameContent, nullptr);
376 return data.mFrame;
379 [[nodiscard]] static nsIFrame* GetContainingBlockIfNeeded(nsIFrame* aFrame) {
380 if (aFrame->IsBlockOutside() || aFrame->IsBlockFrameOrSubclass()) {
381 return nullptr;
383 return aFrame->GetContainingBlock();
386 void nsCaret::SchedulePaint() {
387 if (mLastPaintedFrame) {
388 mLastPaintedFrame->SchedulePaint();
389 mLastPaintedFrame = nullptr;
391 auto data = GetFrameAndOffset(mCaretPosition);
392 if (!data.mFrame) {
393 return;
395 nsIFrame* frame = data.mFrame;
396 if (nsIFrame* cb = GetContainingBlockIfNeeded(frame)) {
397 frame = cb;
399 frame->SchedulePaint();
402 void nsCaret::SetVisibilityDuringSelection(bool aVisibility) {
403 if (mShowDuringSelection == aVisibility) {
404 return;
406 mShowDuringSelection = aVisibility;
407 if (mHiddenDuringSelection && aVisibility) {
408 RemoveForceHide();
409 mHiddenDuringSelection = false;
411 SchedulePaint();
414 void nsCaret::UpdateCaretPositionFromSelectionIfNeeded() {
415 if (mFixedCaretPosition) {
416 return;
418 CaretPosition newPos = CaretPositionFor(GetSelection());
419 if (newPos == mCaretPosition) {
420 return;
422 mCaretPosition = newPos;
423 SchedulePaint();
426 void nsCaret::SetCaretPosition(nsINode* aNode, int32_t aOffset) {
427 // Schedule a paint with the old position to invalidate.
428 mFixedCaretPosition = !!aNode;
429 if (mFixedCaretPosition) {
430 mCaretPosition = {aNode, aOffset};
431 SchedulePaint();
432 } else {
433 UpdateCaretPositionFromSelectionIfNeeded();
435 ResetBlinking();
438 void nsCaret::CheckSelectionLanguageChange() {
439 if (!StaticPrefs::bidi_browser_ui()) {
440 return;
443 bool isKeyboardRTL = false;
444 nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard();
445 if (bidiKeyboard) {
446 bidiKeyboard->IsLangRTL(&isKeyboardRTL);
448 // Call SelectionLanguageChange on every paint. Mostly it will be a noop
449 // but it should be fast anyway. This guarantees we never paint the caret
450 // at the wrong place.
451 Selection* selection = GetSelection();
452 if (selection) {
453 selection->SelectionLanguageChange(isKeyboardRTL);
457 // This ensures that the caret is not affected by clips on inlines and so forth.
458 [[nodiscard]] static nsIFrame* MapToContainingBlock(nsIFrame* aFrame,
459 nsRect* aCaretRect,
460 nsRect* aHookRect) {
461 nsIFrame* containingBlock = GetContainingBlockIfNeeded(aFrame);
462 if (!containingBlock) {
463 return aFrame;
466 if (aCaretRect) {
467 *aCaretRect = nsLayoutUtils::TransformFrameRectToAncestor(
468 aFrame, *aCaretRect, containingBlock);
470 if (aHookRect) {
471 *aHookRect = nsLayoutUtils::TransformFrameRectToAncestor(aFrame, *aHookRect,
472 containingBlock);
474 return containingBlock;
477 nsIFrame* nsCaret::GetPaintGeometry(nsRect* aCaretRect, nsRect* aHookRect,
478 nscolor* aCaretColor) {
479 MOZ_ASSERT(!!aCaretRect == !!aHookRect);
481 // Return null if we should not be visible.
482 if (!IsVisible() || !mIsBlinkOn) {
483 return nullptr;
486 // Update selection language direction now so the new direction will be
487 // taken into account when computing the caret position below.
488 CheckSelectionLanguageChange();
490 auto data = GetFrameAndOffset(mCaretPosition);
491 MOZ_ASSERT(!!data.mFrame == !!data.mUnadjustedFrame);
492 if (!data.mFrame) {
493 return nullptr;
496 nsIFrame* frame = data.mFrame;
497 nsIFrame* unadjustedFrame = data.mUnadjustedFrame;
498 int32_t frameOffset(data.mOffsetInFrameContent);
499 // Now we have a frame, check whether it's appropriate to show the caret here.
500 // Note we need to check the unadjusted frame, otherwise consider the
501 // following case:
503 // <div contenteditable><span contenteditable=false>Text </span><br>
505 // Where the selection is targeting the <br>. We want to display the caret,
506 // since the <br> we're focused at is editable, but we do want to paint it at
507 // the adjusted frame offset, so that we can see the collapsed whitespace.
508 if (unadjustedFrame->IsContentDisabled()) {
509 return nullptr;
512 // If the offset falls outside of the frame, then don't paint the caret.
513 if (frame->IsTextFrame()) {
514 auto [startOffset, endOffset] = frame->GetOffsets();
515 if (startOffset > frameOffset || endOffset < frameOffset) {
516 return nullptr;
520 if (aCaretColor) {
521 *aCaretColor = frame->GetCaretColorAt(frameOffset);
524 if (aCaretRect || aHookRect) {
525 ComputeCaretRects(frame, frameOffset, aCaretRect, aHookRect);
527 return MapToContainingBlock(frame, aCaretRect, aHookRect);
530 nsIFrame* nsCaret::GetPaintGeometry() {
531 return GetPaintGeometry(nullptr, nullptr);
534 nsIFrame* nsCaret::GetPaintGeometry(nsRect* aRect) {
535 nsRect caretRect;
536 nsRect hookRect;
537 nsIFrame* frame = GetPaintGeometry(&caretRect, &hookRect);
538 aRect->UnionRect(caretRect, hookRect);
539 return frame;
542 void nsCaret::PaintCaret(DrawTarget& aDrawTarget, nsIFrame* aForFrame,
543 const nsPoint& aOffset) {
544 nsRect caretRect;
545 nsRect hookRect;
546 nscolor color;
547 nsIFrame* frame = GetPaintGeometry(&caretRect, &hookRect, &color);
548 MOZ_ASSERT(frame == aForFrame, "We're referring different frame");
550 if (!frame) {
551 return;
554 int32_t appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel();
555 Rect devPxCaretRect = NSRectToSnappedRect(caretRect + aOffset,
556 appUnitsPerDevPixel, aDrawTarget);
557 Rect devPxHookRect =
558 NSRectToSnappedRect(hookRect + aOffset, appUnitsPerDevPixel, aDrawTarget);
560 ColorPattern pattern(ToDeviceColor(color));
561 aDrawTarget.FillRect(devPxCaretRect, pattern);
562 if (!hookRect.IsEmpty()) {
563 aDrawTarget.FillRect(devPxHookRect, pattern);
567 NS_IMETHODIMP
568 nsCaret::NotifySelectionChanged(Document*, Selection* aDomSel, int16_t aReason,
569 int32_t aAmount) {
570 // The same caret is shared amongst the document and any text widgets it
571 // may contain. This means that the caret could get notifications from
572 // multiple selections.
574 // If this notification is for a selection that is not the one the
575 // the caret is currently interested in (mDomSelectionWeak), or the caret
576 // position is fixed, then there is nothing to do!
577 if (mDomSelectionWeak != aDomSel) {
578 return NS_OK;
581 // Check if we need to hide / un-hide the caret due to the selection being
582 // collapsed.
583 if (!mShowDuringSelection &&
584 !aDomSel->IsCollapsed() != mHiddenDuringSelection) {
585 if (mHiddenDuringSelection) {
586 RemoveForceHide();
587 } else {
588 AddForceHide();
590 mHiddenDuringSelection = !mHiddenDuringSelection;
593 // We don't bother computing the caret position when invisible. We'll do it if
594 // we become visible in CaretVisibilityMaybeChanged().
595 if (IsVisible()) {
596 UpdateCaretPositionFromSelectionIfNeeded();
597 ResetBlinking();
600 return NS_OK;
603 void nsCaret::ResetBlinking() {
604 mIsBlinkOn = true;
606 if (mReadOnly || !IsVisible()) {
607 StopBlinking();
608 return;
611 const auto blinkTime = LookAndFeel::CaretBlinkTime();
612 if (blinkTime <= 0) {
613 StopBlinking();
614 return;
617 mBlinkCount = LookAndFeel::CaretBlinkCount();
618 if (!mBlinkTimer) {
619 mBlinkTimer = NS_NewTimer();
621 mBlinkTimer->InitWithNamedFuncCallback(CaretBlinkCallback, this, blinkTime,
622 nsITimer::TYPE_REPEATING_SLACK,
623 "CaretBlinkCallback");
626 void nsCaret::StopBlinking() {
627 if (mBlinkTimer) {
628 mBlinkTimer->Cancel();
632 size_t nsCaret::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
633 size_t total = aMallocSizeOf(this);
634 if (mPresShell) {
635 // We only want the size of the nsWeakReference object, not the PresShell
636 // (since we don't own the PresShell).
637 total += mPresShell->SizeOfOnlyThis(aMallocSizeOf);
639 if (mBlinkTimer) {
640 total += mBlinkTimer->SizeOfIncludingThis(aMallocSizeOf);
642 return total;
645 void nsCaret::ComputeCaretRects(nsIFrame* aFrame, int32_t aFrameOffset,
646 nsRect* aCaretRect, nsRect* aHookRect) {
647 MOZ_ASSERT(aCaretRect && aHookRect);
648 NS_ASSERTION(aFrame, "Should have a frame here");
650 WritingMode wm = aFrame->GetWritingMode();
651 bool isVertical = wm.IsVertical();
653 nscoord bidiIndicatorSize;
654 *aCaretRect = GetGeometryForFrame(aFrame, aFrameOffset, &bidiIndicatorSize);
656 // Simon -- make a hook to draw to the left or right of the caret to show
657 // keyboard language direction
658 aHookRect->SetEmpty();
659 if (!StaticPrefs::bidi_browser_ui()) {
660 return;
663 bool isCaretRTL;
664 nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard();
665 // if bidiKeyboard->IsLangRTL() fails, there is no way to tell the
666 // keyboard direction, or the user has no right-to-left keyboard
667 // installed, so we never draw the hook.
668 if (bidiKeyboard && NS_SUCCEEDED(bidiKeyboard->IsLangRTL(&isCaretRTL))) {
669 // If keyboard language is RTL, draw the hook on the left; if LTR, to the
670 // right The height of the hook rectangle is the same as the width of the
671 // caret rectangle.
672 if (isVertical) {
673 if (wm.IsSidewaysLR()) {
674 aHookRect->SetRect(aCaretRect->x + bidiIndicatorSize,
675 aCaretRect->y + (!isCaretRTL ? bidiIndicatorSize * -1
676 : aCaretRect->height),
677 aCaretRect->height, bidiIndicatorSize);
678 } else {
679 aHookRect->SetRect(aCaretRect->XMost() - bidiIndicatorSize,
680 aCaretRect->y + (isCaretRTL ? bidiIndicatorSize * -1
681 : aCaretRect->height),
682 aCaretRect->height, bidiIndicatorSize);
684 } else {
685 aHookRect->SetRect(aCaretRect->x + (isCaretRTL ? bidiIndicatorSize * -1
686 : aCaretRect->width),
687 aCaretRect->y + bidiIndicatorSize, bidiIndicatorSize,
688 aCaretRect->width);
693 /* static */
694 void nsCaret::CaretBlinkCallback(nsITimer* aTimer, void* aClosure) {
695 nsCaret* theCaret = static_cast<nsCaret*>(aClosure);
696 if (!theCaret) {
697 return;
699 theCaret->mIsBlinkOn = !theCaret->mIsBlinkOn;
700 theCaret->SchedulePaint();
702 // mBlinkCount of -1 means blink count is not enabled.
703 if (theCaret->mBlinkCount == -1) {
704 return;
707 // Track the blink count, but only at end of a blink cycle.
708 if (theCaret->mIsBlinkOn) {
709 // If we exceeded the blink count, stop the timer.
710 if (--theCaret->mBlinkCount <= 0) {
711 theCaret->StopBlinking();