Bug 1869043 allow a device to be specified with MediaTrackGraph::NotifyWhenDeviceStar...
[gecko.git] / layout / base / nsCaret.cpp
blobd66d55d6bb2b336d6e51eaa59ca9f1e86c7908b1
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/StaticPrefs_bidi.h"
18 #include "nsCOMPtr.h"
19 #include "nsFontMetrics.h"
20 #include "nsITimer.h"
21 #include "nsFrameSelection.h"
22 #include "nsIFrame.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;
53 nsCaret::nsCaret()
54 : mOverrideOffset(0),
55 mBlinkCount(-1),
56 mBlinkRate(0),
57 mHideCount(0),
58 mIsBlinkOn(false),
59 mVisible(false),
60 mReadOnly(false),
61 mShowDuringSelection(false),
62 mIgnoreUserModify(true) {}
64 nsCaret::~nsCaret() { StopBlinking(); }
66 nsresult nsCaret::Init(PresShell* aPresShell) {
67 NS_ENSURE_ARG(aPresShell);
69 mPresShell =
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);
79 if (!selection) {
80 return NS_ERROR_FAILURE;
83 selection->AddSelectionListener(this);
84 mDomSelectionWeak = selection;
86 return NS_OK;
89 static bool DrawCJKCaret(nsIFrame* aFrame, int32_t aOffset) {
90 nsIContent* content = aFrame->GetContent();
91 const nsTextFragment* frag = content->GetText();
92 if (!frag) {
93 return false;
95 if (aOffset < 0 || static_cast<uint32_t>(aOffset) >= frag->GetLength()) {
96 return false;
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
105 nscoord caretWidth =
106 (aCaretHeight *
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();
121 Metrics result;
122 result.mCaretWidth = NS_ROUND_BORDER_TO_PIXELS(caretWidth, tpp);
123 result.mBidiIndicatorSize = NS_ROUND_BORDER_TO_PIXELS(bidiIndicatorSize, tpp);
124 return result;
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.
131 StopBlinking();
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) {
149 MOZ_ASSERT(aDOMSel);
150 mDomSelectionWeak = aDOMSel;
151 ResetBlinking();
152 SchedulePaint(aDOMSel);
155 void nsCaret::SetVisible(bool inMakeVisible) {
156 mVisible = inMakeVisible;
157 mIgnoreUserModify = mVisible;
158 ResetBlinking();
159 SchedulePaint();
162 void nsCaret::AddForceHide() {
163 MOZ_ASSERT(mHideCount < UINT32_MAX);
164 if (++mHideCount > 1) {
165 return;
167 ResetBlinking();
168 SchedulePaint();
171 void nsCaret::RemoveForceHide() {
172 if (!mHideCount || --mHideCount) {
173 return;
175 ResetBlinking();
176 SchedulePaint();
179 void nsCaret::SetCaretReadOnly(bool inMakeReadonly) {
180 mReadOnly = inMakeReadonly;
181 ResetBlinking();
182 SchedulePaint();
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,
189 bool aVertical) {
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;
196 break;
198 if (current->IsTransformed()) {
199 // We don't account for transforms in rectRelativeToCurrent, so stop
200 // adjusting here.
201 break;
203 rectRelativeToClip += current->GetPosition();
206 if (!sf) {
207 return {};
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();
223 if (!cbH) {
224 padding.left = padding.right = 0;
226 if (!cbV) {
227 padding.top = padding.bottom = 0;
229 clipRect.Deflate(padding);
232 nsPoint offset;
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.
235 if (aVertical) {
236 nscoord overflow = rectRelativeToClip.YMost() - clipRect.YMost();
237 if (overflow > 0) {
238 offset.y -= overflow;
239 } else {
240 overflow = rectRelativeToClip.y - clipRect.y;
241 if (overflow < 0) {
242 offset.y -= overflow;
245 } else {
246 nscoord overflow = rectRelativeToClip.XMost() - clipRect.XMost();
247 if (overflow > 0) {
248 offset.x -= overflow;
249 } else {
250 overflow = rectRelativeToClip.x - clipRect.x;
251 if (overflow < 0) {
252 offset.x -= overflow;
256 return offset;
259 /* static */
260 nsRect nsCaret::GetGeometryForFrame(nsIFrame* aFrame, int32_t aFrameOffset,
261 nscoord* aBidiIndicatorSize) {
262 nsPoint framePos(0, 0);
263 nsRect rect;
264 nsresult rv = aFrame->GetPointFromOffset(aFrameOffset, &framePos);
265 if (NS_FAILED(rv)) {
266 if (aBidiIndicatorSize) {
267 *aBidiIndicatorSize = 0;
269 return rect;
272 nsIFrame* frame = aFrame->GetContentInsertionFrame();
273 if (!frame) {
274 frame = aFrame;
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) {
309 if (vertical) {
310 inlineOffset -= caretMetrics.mCaretWidth;
311 } else {
312 inlineOffset -= caretMetrics.mCaretWidth;
316 if (vertical) {
317 framePos.x = caretBlockAxisMetrics.mOffset;
318 framePos.y += inlineOffset;
319 } else {
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;
333 return rect;
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;
345 nsINode* focusNode;
346 int32_t focusOffset;
348 if (aOverrideNode) {
349 focusNode = aOverrideNode;
350 focusOffset = aOverrideOffset;
351 } else if (aSelection) {
352 focusNode = aSelection->GetFocusNode();
353 focusOffset = aSelection->FocusOffset();
354 } else {
355 return nullptr;
358 if (!focusNode || !focusNode->IsContent() || !aSelection) {
359 return nullptr;
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.
371 if (result.mFrame) {
372 frameSelection->SetHint(result.mHint);
374 if (aUnadjustedFrame) {
375 *aUnadjustedFrame = result.mUnadjustedFrame;
377 if (aFrameOffset) {
378 *aFrameOffset = result.mOffsetInFrameContent;
380 return result.mFrame;
383 /* static */
384 nsIFrame* nsCaret::GetGeometry(const Selection* aSelection, nsRect* aRect) {
385 int32_t frameOffset;
386 nsIFrame* frame = GetFrameAndOffset(aSelection, nullptr, 0, &frameOffset);
387 if (frame) {
388 *aRect = GetGeometryForFrame(frame, frameOffset, nullptr);
390 return frame;
393 [[nodiscard]] static nsIFrame* GetContainingBlockIfNeeded(nsIFrame* aFrame) {
394 if (aFrame->IsBlockOutside() || aFrame->IsBlockFrameOrSubclass()) {
395 return nullptr;
397 return aFrame->GetContainingBlock();
400 void nsCaret::SchedulePaint(Selection* aSelection) {
401 Selection* selection;
402 if (aSelection) {
403 selection = aSelection;
404 } else {
405 selection = GetSelection();
408 int32_t frameOffset;
409 nsIFrame* frame = GetFrameAndOffset(selection, mOverrideContent,
410 mOverrideOffset, &frameOffset);
411 if (!frame) {
412 return;
415 if (nsIFrame* cb = GetContainingBlockIfNeeded(frame)) {
416 cb->SchedulePaint();
417 } else {
418 frame->SchedulePaint();
422 void nsCaret::SetVisibilityDuringSelection(bool aVisibility) {
423 mShowDuringSelection = aVisibility;
424 SchedulePaint();
427 void nsCaret::SetCaretPosition(nsINode* aNode, int32_t aOffset) {
428 mOverrideContent = aNode;
429 mOverrideOffset = aOffset;
431 ResetBlinking();
432 SchedulePaint();
435 void nsCaret::CheckSelectionLanguageChange() {
436 if (!StaticPrefs::bidi_browser_ui()) {
437 return;
440 bool isKeyboardRTL = false;
441 nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard();
442 if (bidiKeyboard) {
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();
449 if (selection) {
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,
456 nsRect* aCaretRect,
457 nsRect* aHookRect) {
458 nsIFrame* containingBlock = GetContainingBlockIfNeeded(aFrame);
459 if (!containingBlock) {
460 return aFrame;
463 *aCaretRect = nsLayoutUtils::TransformFrameRectToAncestor(aFrame, *aCaretRect,
464 containingBlock);
465 *aHookRect = nsLayoutUtils::TransformFrameRectToAncestor(aFrame, *aHookRect,
466 containingBlock);
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) {
474 return nullptr;
477 // Update selection language direction now so the new direction will be
478 // taken into account when computing the caret position below.
479 CheckSelectionLanguageChange();
481 int32_t frameOffset;
482 nsIFrame* unadjustedFrame = nullptr;
483 nsIFrame* frame =
484 GetFrameAndOffset(GetSelection(), mOverrideContent, mOverrideOffset,
485 &frameOffset, &unadjustedFrame);
486 MOZ_ASSERT(!!frame == !!unadjustedFrame);
487 if (!frame) {
488 return nullptr;
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
493 // following case:
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()) {
503 return nullptr;
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) {
510 return nullptr;
514 if (aCaretColor) {
515 *aCaretColor = frame->GetCaretColorAt(frameOffset);
518 ComputeCaretRects(frame, frameOffset, aCaretRect, aHookRect);
519 return MapToContainingBlock(frame, aCaretRect, aHookRect);
522 nsIFrame* nsCaret::GetPaintGeometry(nsRect* aRect) {
523 nsRect caretRect;
524 nsRect hookRect;
525 nsIFrame* frame = GetPaintGeometry(&caretRect, &hookRect);
526 aRect->UnionRect(caretRect, hookRect);
527 return frame;
530 void nsCaret::PaintCaret(DrawTarget& aDrawTarget, nsIFrame* aForFrame,
531 const nsPoint& aOffset) {
532 nsRect caretRect;
533 nsRect hookRect;
534 nscolor color;
535 nsIFrame* frame = GetPaintGeometry(&caretRect, &hookRect, &color);
536 MOZ_ASSERT(frame == aForFrame, "We're referring different frame");
538 if (!frame) {
539 return;
542 int32_t appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel();
543 Rect devPxCaretRect = NSRectToSnappedRect(caretRect + aOffset,
544 appUnitsPerDevPixel, aDrawTarget);
545 Rect devPxHookRect =
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);
555 NS_IMETHODIMP
556 nsCaret::NotifySelectionChanged(Document*, Selection* aDomSel, int16_t aReason,
557 int32_t aAmount) {
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
564 return NS_OK;
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
572 // is nothing to do!
574 if (mDomSelectionWeak != aDomSel) return NS_OK;
576 ResetBlinking();
577 SchedulePaint(aDomSel);
579 return NS_OK;
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;
590 mIsBlinkOn = true;
592 if (mReadOnly || !mVisible || mHideCount) {
593 StopBlinking();
594 return;
597 auto blinkRate =
598 LookAndFeel::GetInt(IntID::CaretBlinkTime, kDefaultBlinkRate);
600 if (blinkRate > 0) {
601 // Make sure to reset the remaining blink count even if the blink rate
602 // hasn't changed.
603 mBlinkCount =
604 LookAndFeel::GetInt(IntID::CaretBlinkCount, kDefaultBlinkCount);
607 if (mBlinkRate == blinkRate) {
608 // If the rate hasn't changed, then there is nothing else to do.
609 return;
612 mBlinkRate = blinkRate;
614 if (mBlinkTimer) {
615 mBlinkTimer->Cancel();
616 } else {
617 mBlinkTimer = NS_NewTimer(GetMainThreadSerialEventTarget());
618 if (!mBlinkTimer) {
619 return;
623 if (blinkRate > 0) {
624 mBlinkTimer->InitWithNamedFuncCallback(CaretBlinkCallback, this, blinkRate,
625 nsITimer::TYPE_REPEATING_SLACK,
626 "nsCaret::CaretBlinkCallback_timer");
630 void nsCaret::StopBlinking() {
631 if (mBlinkTimer) {
632 mBlinkTimer->Cancel();
633 mBlinkRate = 0;
637 size_t nsCaret::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
638 size_t total = aMallocSizeOf(this);
639 if (mPresShell) {
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);
644 if (mBlinkTimer) {
645 total += mBlinkTimer->SizeOfIncludingThis(aMallocSizeOf);
647 return total;
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.
677 return false;
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.
685 return true;
689 // There are no open menu popups, no need to hide the caret.
690 return false;
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()) {
707 return;
710 bool isCaretRTL;
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
718 // caret rectangle.
719 if (isVertical) {
720 if (wm.IsSidewaysLR()) {
721 aHookRect->SetRect(aCaretRect->x + bidiIndicatorSize,
722 aCaretRect->y + (!isCaretRTL ? bidiIndicatorSize * -1
723 : aCaretRect->height),
724 aCaretRect->height, bidiIndicatorSize);
725 } else {
726 aHookRect->SetRect(aCaretRect->XMost() - bidiIndicatorSize,
727 aCaretRect->y + (isCaretRTL ? bidiIndicatorSize * -1
728 : aCaretRect->height),
729 aCaretRect->height, bidiIndicatorSize);
731 } else {
732 aHookRect->SetRect(aCaretRect->x + (isCaretRTL ? bidiIndicatorSize * -1
733 : aCaretRect->width),
734 aCaretRect->y + bidiIndicatorSize, bidiIndicatorSize,
735 aCaretRect->width);
740 /* static */
741 void nsCaret::CaretBlinkCallback(nsITimer* aTimer, void* aClosure) {
742 nsCaret* theCaret = reinterpret_cast<nsCaret*>(aClosure);
743 if (!theCaret) {
744 return;
746 theCaret->mIsBlinkOn = !theCaret->mIsBlinkOn;
747 theCaret->SchedulePaint();
749 // mBlinkCount of -1 means blink count is not enabled.
750 if (theCaret->mBlinkCount == -1) {
751 return;
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;
765 SchedulePaint();