1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 sw=2 et tw=78: */
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/gfx/2D.h"
16 #include "nsFontMetrics.h"
18 #include "nsFrameSelection.h"
20 #include "nsIScrollableFrame.h"
21 #include "nsIDOMNode.h"
22 #include "nsISelection.h"
23 #include "nsISelectionPrivate.h"
24 #include "nsIContent.h"
25 #include "nsIPresShell.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/LookAndFeel.h"
36 #include "mozilla/dom/Selection.h"
37 #include "nsIBidiKeyboard.h"
38 #include "nsContentUtils.h"
40 using namespace mozilla
;
41 using namespace mozilla::dom
;
42 using namespace mozilla::gfx
;
44 // The bidi indicator hangs off the caret to one side, to show which
45 // direction the typing is in. It needs to be at least 2x2 to avoid looking like
46 // an insignificant dot
47 static const int32_t kMinBidiIndicatorPixels
= 2;
50 * Find the first frame in an in-order traversal of the frame subtree rooted
51 * at aFrame which is either a text frame logically at the end of a line,
52 * or which is aStopAtFrame. Return null if no such frame is found. We don't
53 * descend into the children of non-eLineParticipant frames.
56 CheckForTrailingTextFrameRecursive(nsIFrame
* aFrame
, nsIFrame
* aStopAtFrame
)
58 if (aFrame
== aStopAtFrame
||
59 ((aFrame
->GetType() == nsGkAtoms::textFrame
&&
60 (static_cast<nsTextFrame
*>(aFrame
))->IsAtEndOfLine())))
62 if (!aFrame
->IsFrameOfType(nsIFrame::eLineParticipant
))
65 for (nsIFrame
* f
= aFrame
->GetFirstPrincipalChild(); f
; f
= f
->GetNextSibling())
67 nsIFrame
* r
= CheckForTrailingTextFrameRecursive(f
, aStopAtFrame
);
75 FindContainingLine(nsIFrame
* aFrame
)
77 while (aFrame
&& aFrame
->IsFrameOfType(nsIFrame::eLineParticipant
))
79 nsIFrame
* parent
= aFrame
->GetParent();
80 nsBlockFrame
* blockParent
= nsLayoutUtils::GetAsBlock(parent
);
84 nsBlockInFlowLineIterator
iter(blockParent
, aFrame
, &isValid
);
85 return isValid
? iter
.GetLine().get() : nullptr;
93 AdjustCaretFrameForLineEnd(nsIFrame
** aFrame
, int32_t* aOffset
)
95 nsLineBox
* line
= FindContainingLine(*aFrame
);
98 int32_t count
= line
->GetChildCount();
99 for (nsIFrame
* f
= line
->mFirstChild
; count
> 0; --count
, f
= f
->GetNextSibling())
101 nsIFrame
* r
= CheckForTrailingTextFrameRecursive(f
, *aFrame
);
107 NS_ASSERTION(r
->GetType() == nsGkAtoms::textFrame
, "Expected text frame");
108 *aOffset
= (static_cast<nsTextFrame
*>(r
))->GetContentEnd();
117 return Preferences::GetBool("bidi.browser.ui");
125 , mShowDuringSelection(false)
126 , mIgnoreUserModify(true)
135 nsresult
nsCaret::Init(nsIPresShell
*inPresShell
)
137 NS_ENSURE_ARG(inPresShell
);
139 mPresShell
= do_GetWeakReference(inPresShell
); // the presshell owns us, so no addref
140 NS_ASSERTION(mPresShell
, "Hey, pres shell should support weak refs");
142 mShowDuringSelection
=
143 LookAndFeel::GetInt(LookAndFeel::eIntID_ShowCaretDuringSelection
,
144 mShowDuringSelection
? 1 : 0) != 0;
146 // get the selection from the pres shell, and set ourselves up as a selection
149 nsCOMPtr
<nsISelectionController
> selCon
= do_QueryReferent(mPresShell
);
151 return NS_ERROR_FAILURE
;
153 nsCOMPtr
<nsISelection
> domSelection
;
154 nsresult rv
= selCon
->GetSelection(nsISelectionController::SELECTION_NORMAL
,
155 getter_AddRefs(domSelection
));
159 return NS_ERROR_FAILURE
;
161 nsCOMPtr
<nsISelectionPrivate
> privateSelection
= do_QueryInterface(domSelection
);
162 if (privateSelection
)
163 privateSelection
->AddSelectionListener(this);
164 mDomSelectionWeak
= do_GetWeakReference(domSelection
);
170 DrawCJKCaret(nsIFrame
* aFrame
, int32_t aOffset
)
172 nsIContent
* content
= aFrame
->GetContent();
173 const nsTextFragment
* frag
= content
->GetText();
176 if (aOffset
< 0 || uint32_t(aOffset
) >= frag
->GetLength())
178 char16_t ch
= frag
->CharAt(aOffset
);
179 return 0x2e80 <= ch
&& ch
<= 0xd7ff;
183 nsCaret::ComputeMetrics(nsIFrame
* aFrame
, int32_t aOffset
, nscoord aCaretHeight
)
185 // Compute nominal sizes in appunits
187 (aCaretHeight
* LookAndFeel::GetFloat(LookAndFeel::eFloatID_CaretAspectRatio
, 0.0f
)) +
188 nsPresContext::CSSPixelsToAppUnits(
189 LookAndFeel::GetInt(LookAndFeel::eIntID_CaretWidth
, 1));
191 if (DrawCJKCaret(aFrame
, aOffset
)) {
192 caretWidth
+= nsPresContext::CSSPixelsToAppUnits(1);
194 nscoord bidiIndicatorSize
= nsPresContext::CSSPixelsToAppUnits(kMinBidiIndicatorPixels
);
195 bidiIndicatorSize
= std::max(caretWidth
, bidiIndicatorSize
);
197 // Round them to device pixels. Always round down, except that anything
198 // between 0 and 1 goes up to 1 so we don't let the caret disappear.
199 int32_t tpp
= aFrame
->PresContext()->AppUnitsPerDevPixel();
201 result
.mCaretWidth
= NS_ROUND_BORDER_TO_PIXELS(caretWidth
, tpp
);
202 result
.mBidiIndicatorSize
= NS_ROUND_BORDER_TO_PIXELS(bidiIndicatorSize
, tpp
);
206 void nsCaret::Terminate()
208 // this doesn't erase the caret if it's drawn. Should it? We might not have
209 // a good drawing environment during teardown.
212 mBlinkTimer
= nullptr;
214 // unregiser ourselves as a selection listener
215 nsCOMPtr
<nsISelection
> domSelection
= do_QueryReferent(mDomSelectionWeak
);
216 nsCOMPtr
<nsISelectionPrivate
> privateSelection(do_QueryInterface(domSelection
));
217 if (privateSelection
)
218 privateSelection
->RemoveSelectionListener(this);
219 mDomSelectionWeak
= nullptr;
220 mPresShell
= nullptr;
222 mOverrideContent
= nullptr;
225 NS_IMPL_ISUPPORTS(nsCaret
, nsISelectionListener
)
227 nsISelection
* nsCaret::GetSelection()
229 nsCOMPtr
<nsISelection
> sel(do_QueryReferent(mDomSelectionWeak
));
233 void nsCaret::SetSelection(nsISelection
*aDOMSel
)
236 mDomSelectionWeak
= do_GetWeakReference(aDOMSel
); // weak reference to pres shell
241 void nsCaret::SetVisible(bool inMakeVisible
)
243 mVisible
= inMakeVisible
;
244 mIgnoreUserModify
= mVisible
;
249 bool nsCaret::IsVisible()
255 if (!mShowDuringSelection
) {
256 Selection
* selection
= GetSelectionInternal();
261 if (NS_FAILED(selection
->GetIsCollapsed(&isCollapsed
)) || !isCollapsed
) {
266 if (IsMenuPopupHidingCaret()) {
273 void nsCaret::SetCaretReadOnly(bool inMakeReadonly
)
275 mReadOnly
= inMakeReadonly
;
281 nsCaret::GetGeometryForFrame(nsIFrame
* aFrame
,
282 int32_t aFrameOffset
,
283 nscoord
* aBidiIndicatorSize
)
285 nsPoint
framePos(0, 0);
287 nsresult rv
= aFrame
->GetPointFromOffset(aFrameOffset
, &framePos
);
289 if (aBidiIndicatorSize
) {
290 *aBidiIndicatorSize
= 0;
295 nsIFrame
* frame
= aFrame
->GetContentInsertionFrame();
299 NS_ASSERTION(!(frame
->GetStateBits() & NS_FRAME_IN_REFLOW
),
300 "We should not be in the middle of reflow");
301 nscoord baseline
= frame
->GetCaretBaseline();
302 nscoord ascent
= 0, descent
= 0;
303 nsRefPtr
<nsFontMetrics
> fm
;
304 nsLayoutUtils::GetFontMetricsForFrame(aFrame
, getter_AddRefs(fm
),
305 nsLayoutUtils::FontSizeInflationFor(aFrame
));
306 NS_ASSERTION(fm
, "We should be able to get the font metrics");
308 ascent
= fm
->MaxAscent();
309 descent
= fm
->MaxDescent();
311 nscoord height
= ascent
+ descent
;
312 WritingMode wm
= aFrame
->GetWritingMode();
313 bool vertical
= wm
.IsVertical();
315 if (wm
.IsLineInverted()) {
316 framePos
.x
= baseline
- descent
;
318 framePos
.x
= baseline
- ascent
;
321 framePos
.y
= baseline
- ascent
;
323 Metrics caretMetrics
= ComputeMetrics(aFrame
, aFrameOffset
, height
);
324 rect
= nsRect(framePos
, vertical
? nsSize(height
, caretMetrics
.mCaretWidth
) :
325 nsSize(caretMetrics
.mCaretWidth
, height
));
327 // Clamp the inline-position to be within our scroll frame. If we don't, then
328 // it clips us, and we don't appear at all. See bug 335560.
329 nsIFrame
*scrollFrame
=
330 nsLayoutUtils::GetClosestFrameOfType(aFrame
, nsGkAtoms::scrollFrame
);
332 // First, use the scrollFrame to get at the scrollable view that we're in.
333 nsIScrollableFrame
*sf
= do_QueryFrame(scrollFrame
);
334 nsIFrame
*scrolled
= sf
->GetScrolledFrame();
335 nsRect caretInScroll
= rect
+ aFrame
->GetOffsetTo(scrolled
);
337 // Now see if the caret extends beyond the view's bounds. If it does,
338 // then snap it back, put it as close to the edge as it can.
340 nscoord overflow
= caretInScroll
.YMost() -
341 scrolled
->GetVisualOverflowRectRelativeToSelf().height
;
346 nscoord overflow
= caretInScroll
.XMost() -
347 scrolled
->GetVisualOverflowRectRelativeToSelf().width
;
354 if (aBidiIndicatorSize
) {
355 *aBidiIndicatorSize
= caretMetrics
.mBidiIndicatorSize
;
361 GetFrameAndOffset(Selection
* aSelection
,
362 nsINode
* aOverrideNode
, int32_t aOverrideOffset
,
363 int32_t* aFrameOffset
)
369 focusNode
= aOverrideNode
;
370 focusOffset
= aOverrideOffset
;
371 } else if (aSelection
) {
372 focusNode
= aSelection
->GetFocusNode();
373 aSelection
->GetFocusOffset(&focusOffset
);
378 if (!focusNode
|| !focusNode
->IsContent()) {
382 nsIContent
* contentNode
= focusNode
->AsContent();
383 nsFrameSelection
* frameSelection
= aSelection
->GetFrameSelection();
384 nsBidiLevel bidiLevel
= frameSelection
->GetCaretBidiLevel();
386 nsresult rv
= nsCaret::GetCaretFrameForNodeOffset(
387 frameSelection
, contentNode
, focusOffset
,
388 frameSelection
->GetHint(), bidiLevel
, &frame
, aFrameOffset
);
389 if (NS_FAILED(rv
) || !frame
) {
396 /* static */ nsIFrame
*
397 nsCaret::GetGeometry(nsISelection
* aSelection
, nsRect
* aRect
)
400 nsIFrame
* frame
= GetFrameAndOffset(
401 static_cast<Selection
*>(aSelection
), nullptr, 0, &frameOffset
);
403 *aRect
= GetGeometryForFrame(frame
, frameOffset
, nullptr);
409 nsCaret::GetSelectionInternal()
411 return static_cast<Selection
*>(GetSelection());
414 void nsCaret::SchedulePaint()
416 Selection
* selection
= GetSelectionInternal();
418 if (mOverrideContent
) {
419 focusNode
= mOverrideContent
;
420 } else if (selection
) {
421 focusNode
= selection
->GetFocusNode();
425 if (!focusNode
|| !focusNode
->IsContent()) {
428 nsIFrame
* f
= focusNode
->AsContent()->GetPrimaryFrame();
432 // This may not be the correct continuation frame, but that's OK since we're
433 // just scheduling a paint of the window (or popup).
437 void nsCaret::SetVisibilityDuringSelection(bool aVisibility
)
439 mShowDuringSelection
= aVisibility
;
444 nsCaret::SetCaretPosition(nsIDOMNode
* aNode
, int32_t aOffset
)
446 mOverrideContent
= do_QueryInterface(aNode
);
447 mOverrideOffset
= aOffset
;
454 nsCaret::CheckSelectionLanguageChange()
460 bool isKeyboardRTL
= false;
461 nsIBidiKeyboard
* bidiKeyboard
= nsContentUtils::GetBidiKeyboard();
463 bidiKeyboard
->IsLangRTL(&isKeyboardRTL
);
465 // Call SelectionLanguageChange on every paint. Mostly it will be a noop
466 // but it should be fast anyway. This guarantees we never paint the caret
467 // at the wrong place.
468 Selection
* selection
= GetSelectionInternal();
470 selection
->SelectionLanguageChange(isKeyboardRTL
);
475 nsCaret::GetPaintGeometry(nsRect
* aRect
)
477 // Return null if we should not be visible.
478 if (!IsVisible() || !mIsBlinkOn
) {
482 // Update selection language direction now so the new direction will be
483 // taken into account when computing the caret position below.
484 CheckSelectionLanguageChange();
487 nsIFrame
*frame
= GetFrameAndOffset(GetSelectionInternal(),
488 mOverrideContent
, mOverrideOffset
, &frameOffset
);
493 // now we have a frame, check whether it's appropriate to show the caret here
494 const nsStyleUserInterface
* userinterface
= frame
->StyleUserInterface();
495 if ((!mIgnoreUserModify
&&
496 userinterface
->mUserModify
== NS_STYLE_USER_MODIFY_READ_ONLY
) ||
497 userinterface
->mUserInput
== NS_STYLE_USER_INPUT_NONE
||
498 userinterface
->mUserInput
== NS_STYLE_USER_INPUT_DISABLED
) {
502 // If the offset falls outside of the frame, then don't paint the caret.
503 int32_t startOffset
, endOffset
;
504 if (frame
->GetType() == nsGkAtoms::textFrame
&&
505 (NS_FAILED(frame
->GetOffsets(startOffset
, endOffset
)) ||
506 startOffset
> frameOffset
||
507 endOffset
< frameOffset
)) {
513 ComputeCaretRects(frame
, frameOffset
, &caretRect
, &hookRect
);
515 aRect
->UnionRect(caretRect
, hookRect
);
519 void nsCaret::PaintCaret(nsDisplayListBuilder
*aBuilder
,
520 DrawTarget
& aDrawTarget
,
522 const nsPoint
&aOffset
)
524 int32_t contentOffset
;
525 nsIFrame
* frame
= GetFrameAndOffset(GetSelectionInternal(),
526 mOverrideContent
, mOverrideOffset
, &contentOffset
);
530 NS_ASSERTION(frame
== aForFrame
, "We're referring different frame");
532 int32_t appUnitsPerDevPixel
= frame
->PresContext()->AppUnitsPerDevPixel();
536 ComputeCaretRects(frame
, contentOffset
, &caretRect
, &hookRect
);
538 Rect devPxCaretRect
=
539 NSRectToSnappedRect(caretRect
+ aOffset
, appUnitsPerDevPixel
, aDrawTarget
);
541 NSRectToSnappedRect(hookRect
+ aOffset
, appUnitsPerDevPixel
, aDrawTarget
);
542 ColorPattern
color(ToDeviceColor(frame
->GetCaretColorAt(contentOffset
)));
544 aDrawTarget
.FillRect(devPxCaretRect
, color
);
545 if (!hookRect
.IsEmpty()) {
546 aDrawTarget
.FillRect(devPxHookRect
, color
);
551 nsCaret::NotifySelectionChanged(nsIDOMDocument
*, nsISelection
*aDomSel
,
554 if (aReason
& nsISelectionListener::MOUSEUP_REASON
)//this wont do
557 nsCOMPtr
<nsISelection
> domSel(do_QueryReferent(mDomSelectionWeak
));
559 // The same caret is shared amongst the document and any text widgets it
560 // may contain. This means that the caret could get notifications from
561 // multiple selections.
563 // If this notification is for a selection that is not the one the
564 // the caret is currently interested in (mDomSelectionWeak), then there
567 if (domSel
!= aDomSel
)
576 void nsCaret::ResetBlinking()
580 if (mReadOnly
|| !mVisible
) {
586 mBlinkTimer
->Cancel();
589 mBlinkTimer
= do_CreateInstance("@mozilla.org/timer;1", &err
);
594 uint32_t blinkRate
= static_cast<uint32_t>(
595 LookAndFeel::GetInt(LookAndFeel::eIntID_CaretBlinkTime
, 500));
597 mBlinkTimer
->InitWithFuncCallback(CaretBlinkCallback
, this, blinkRate
,
598 nsITimer::TYPE_REPEATING_SLACK
);
602 void nsCaret::StopBlinking()
606 mBlinkTimer
->Cancel();
611 nsCaret::GetCaretFrameForNodeOffset(nsFrameSelection
* aFrameSelection
,
612 nsIContent
* aContentNode
,
614 CaretAssociationHint aFrameHint
,
615 nsBidiLevel aBidiLevel
,
616 nsIFrame
** aReturnFrame
,
617 int32_t* aReturnOffset
)
619 if (!aFrameSelection
)
620 return NS_ERROR_FAILURE
;
621 nsIPresShell
* presShell
= aFrameSelection
->GetShell();
623 return NS_ERROR_FAILURE
;
625 if (!aContentNode
|| !aContentNode
->IsInComposedDoc() ||
626 presShell
->GetDocument() != aContentNode
->GetComposedDoc())
627 return NS_ERROR_FAILURE
;
629 nsIFrame
* theFrame
= nullptr;
630 int32_t theFrameOffset
= 0;
632 theFrame
= aFrameSelection
->GetFrameForNodeOffset(
633 aContentNode
, aOffset
, aFrameHint
, &theFrameOffset
);
635 return NS_ERROR_FAILURE
;
637 // if theFrame is after a text frame that's logically at the end of the line
638 // (e.g. if theFrame is a <br> frame), then put the caret at the end of
639 // that text frame instead. This way, the caret will be positioned as if
640 // trailing whitespace was not trimmed.
641 AdjustCaretFrameForLineEnd(&theFrame
, &theFrameOffset
);
643 // Mamdouh : modification of the caret to work at rtl and ltr with Bidi
645 // Direction Style from visibility->mDirection
646 // ------------------
647 // NS_STYLE_DIRECTION_LTR : LTR or Default
648 // NS_STYLE_DIRECTION_RTL
651 // If there has been a reflow, take the caret Bidi level to be the level of the current frame
652 if (aBidiLevel
& BIDI_LEVEL_UNDEFINED
)
653 aBidiLevel
= NS_GET_EMBEDDING_LEVEL(theFrame
);
657 nsIFrame
* frameBefore
;
658 nsIFrame
* frameAfter
;
659 nsBidiLevel levelBefore
; // Bidi level of the character before the caret
660 nsBidiLevel levelAfter
; // Bidi level of the character after the caret
662 theFrame
->GetOffsets(start
, end
);
663 if (start
== 0 || end
== 0 || start
== theFrameOffset
|| end
== theFrameOffset
)
665 nsPrevNextBidiLevels levels
= aFrameSelection
->
666 GetPrevNextBidiLevels(aContentNode
, aOffset
, false);
668 /* Boundary condition, we need to know the Bidi levels of the characters before and after the caret */
669 if (levels
.mFrameBefore
|| levels
.mFrameAfter
)
671 frameBefore
= levels
.mFrameBefore
;
672 frameAfter
= levels
.mFrameAfter
;
673 levelBefore
= levels
.mLevelBefore
;
674 levelAfter
= levels
.mLevelAfter
;
676 if ((levelBefore
!= levelAfter
) || (aBidiLevel
!= levelBefore
))
678 aBidiLevel
= std::max(aBidiLevel
, std::min(levelBefore
, levelAfter
)); // rule c3
679 aBidiLevel
= std::min(aBidiLevel
, std::max(levelBefore
, levelAfter
)); // rule c4
680 if (aBidiLevel
== levelBefore
// rule c1
681 || (aBidiLevel
> levelBefore
&& aBidiLevel
< levelAfter
&&
682 IS_SAME_DIRECTION(aBidiLevel
, levelBefore
)) // rule c5
683 || (aBidiLevel
< levelBefore
&& aBidiLevel
> levelAfter
&&
684 IS_SAME_DIRECTION(aBidiLevel
, levelBefore
))) // rule c9
686 if (theFrame
!= frameBefore
)
688 if (frameBefore
) // if there is a frameBefore, move into it
690 theFrame
= frameBefore
;
691 theFrame
->GetOffsets(start
, end
);
692 theFrameOffset
= end
;
696 // if there is no frameBefore, we must be at the beginning of the line
697 // so we stay with the current frame.
698 // Exception: when the first frame on the line has a different Bidi level from the paragraph level, there is no
699 // real frame for the caret to be in. We have to find the visually first frame on the line.
700 nsBidiLevel baseLevel
= NS_GET_BASE_LEVEL(frameAfter
);
701 if (baseLevel
!= levelAfter
)
703 nsPeekOffsetStruct
pos(eSelectBeginLine
, eDirPrevious
, 0,
704 nsPoint(0, 0), false, true, false,
706 if (NS_SUCCEEDED(frameAfter
->PeekOffset(&pos
))) {
707 theFrame
= pos
.mResultFrame
;
708 theFrameOffset
= pos
.mContentOffset
;
714 else if (aBidiLevel
== levelAfter
// rule c2
715 || (aBidiLevel
> levelBefore
&& aBidiLevel
< levelAfter
&&
716 IS_SAME_DIRECTION(aBidiLevel
, levelAfter
)) // rule c6
717 || (aBidiLevel
< levelBefore
&& aBidiLevel
> levelAfter
&&
718 IS_SAME_DIRECTION(aBidiLevel
, levelAfter
))) // rule c10
720 if (theFrame
!= frameAfter
)
724 // if there is a frameAfter, move into it
725 theFrame
= frameAfter
;
726 theFrame
->GetOffsets(start
, end
);
727 theFrameOffset
= start
;
731 // if there is no frameAfter, we must be at the end of the line
732 // so we stay with the current frame.
733 // Exception: when the last frame on the line has a different Bidi level from the paragraph level, there is no
734 // real frame for the caret to be in. We have to find the visually last frame on the line.
735 nsBidiLevel baseLevel
= NS_GET_BASE_LEVEL(frameBefore
);
736 if (baseLevel
!= levelBefore
)
738 nsPeekOffsetStruct
pos(eSelectEndLine
, eDirNext
, 0,
739 nsPoint(0, 0), false, true, false,
741 if (NS_SUCCEEDED(frameBefore
->PeekOffset(&pos
))) {
742 theFrame
= pos
.mResultFrame
;
743 theFrameOffset
= pos
.mContentOffset
;
749 else if (aBidiLevel
> levelBefore
&& aBidiLevel
< levelAfter
// rule c7/8
750 && IS_SAME_DIRECTION(levelBefore
, levelAfter
) // before and after have the same parity
751 && !IS_SAME_DIRECTION(aBidiLevel
, levelAfter
)) // caret has different parity
753 if (NS_SUCCEEDED(aFrameSelection
->GetFrameFromLevel(frameAfter
, eDirNext
, aBidiLevel
, &theFrame
)))
755 theFrame
->GetOffsets(start
, end
);
756 levelAfter
= NS_GET_EMBEDDING_LEVEL(theFrame
);
757 if (IS_LEVEL_RTL(aBidiLevel
)) // c8: caret to the right of the rightmost character
758 theFrameOffset
= IS_LEVEL_RTL(levelAfter
) ? start
: end
;
759 else // c7: caret to the left of the leftmost character
760 theFrameOffset
= IS_LEVEL_RTL(levelAfter
) ? end
: start
;
763 else if (aBidiLevel
< levelBefore
&& aBidiLevel
> levelAfter
// rule c11/12
764 && IS_SAME_DIRECTION(levelBefore
, levelAfter
) // before and after have the same parity
765 && !IS_SAME_DIRECTION(aBidiLevel
, levelAfter
)) // caret has different parity
767 if (NS_SUCCEEDED(aFrameSelection
->GetFrameFromLevel(frameBefore
, eDirPrevious
, aBidiLevel
, &theFrame
)))
769 theFrame
->GetOffsets(start
, end
);
770 levelBefore
= NS_GET_EMBEDDING_LEVEL(theFrame
);
771 if (IS_LEVEL_RTL(aBidiLevel
)) // c12: caret to the left of the leftmost character
772 theFrameOffset
= IS_LEVEL_RTL(levelBefore
) ? end
: start
;
773 else // c11: caret to the right of the rightmost character
774 theFrameOffset
= IS_LEVEL_RTL(levelBefore
) ? start
: end
;
782 *aReturnFrame
= theFrame
;
783 *aReturnOffset
= theFrameOffset
;
787 size_t nsCaret::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf
) const
789 size_t total
= aMallocSizeOf(this);
791 // We only want the size of the nsWeakReference object, not the PresShell
792 // (since we don't own the PresShell).
793 total
+= mPresShell
->SizeOfOnlyThis(aMallocSizeOf
);
795 if (mDomSelectionWeak
) {
796 // We only want size of the nsWeakReference object, not the selection
797 // (again, we don't own the selection).
798 total
+= mDomSelectionWeak
->SizeOfOnlyThis(aMallocSizeOf
);
801 total
+= mBlinkTimer
->SizeOfIncludingThis(aMallocSizeOf
);
806 bool nsCaret::IsMenuPopupHidingCaret()
809 // Check if there are open popups.
810 nsXULPopupManager
*popMgr
= nsXULPopupManager::GetInstance();
811 nsTArray
<nsIFrame
*> popups
;
812 popMgr
->GetVisiblePopups(popups
);
814 if (popups
.Length() == 0)
815 return false; // No popups, so caret can't be hidden by them.
817 // Get the selection focus content, that's where the caret would
818 // go if it was drawn.
819 nsCOMPtr
<nsIDOMNode
> node
;
820 nsCOMPtr
<nsISelection
> domSelection
= do_QueryReferent(mDomSelectionWeak
);
822 return true; // No selection/caret to draw.
823 domSelection
->GetFocusNode(getter_AddRefs(node
));
825 return true; // No selection/caret to draw.
826 nsCOMPtr
<nsIContent
> caretContent
= do_QueryInterface(node
);
828 return true; // No selection/caret to draw.
830 // If there's a menu popup open before the popup with
831 // the caret, don't show the caret.
832 for (uint32_t i
=0; i
<popups
.Length(); i
++) {
833 nsMenuPopupFrame
* popupFrame
= static_cast<nsMenuPopupFrame
*>(popups
[i
]);
834 nsIContent
* popupContent
= popupFrame
->GetContent();
836 if (nsContentUtils::ContentIsDescendantOf(caretContent
, popupContent
)) {
837 // The caret is in this popup. There were no menu popups before this
838 // popup, so don't hide the caret.
842 if (popupFrame
->PopupType() == ePopupTypeMenu
&& !popupFrame
->IsContextMenu()) {
843 // This is an open menu popup. It does not contain the caret (else we'd
844 // have returned above). Even if the caret is in a subsequent popup,
845 // or another document/frame, it should be hidden.
851 // There are no open menu popups, no need to hide the caret.
856 nsCaret::ComputeCaretRects(nsIFrame
* aFrame
, int32_t aFrameOffset
,
857 nsRect
* aCaretRect
, nsRect
* aHookRect
)
859 NS_ASSERTION(aFrame
, "Should have a frame here");
861 bool isVertical
= aFrame
->GetWritingMode().IsVertical();
863 nscoord bidiIndicatorSize
;
864 *aCaretRect
= GetGeometryForFrame(aFrame
, aFrameOffset
, &bidiIndicatorSize
);
866 // on RTL frames the right edge of mCaretRect must be equal to framePos
867 const nsStyleVisibility
* vis
= aFrame
->StyleVisibility();
868 if (NS_STYLE_DIRECTION_RTL
== vis
->mDirection
) {
870 aCaretRect
->y
-= aCaretRect
->height
;
872 aCaretRect
->x
-= aCaretRect
->width
;
876 // Simon -- make a hook to draw to the left or right of the caret to show keyboard language direction
877 aHookRect
->SetEmpty();
883 nsIBidiKeyboard
* bidiKeyboard
= nsContentUtils::GetBidiKeyboard();
884 // if bidiKeyboard->IsLangRTL() fails, there is no way to tell the
885 // keyboard direction, or the user has no right-to-left keyboard
886 // installed, so we never draw the hook.
887 if (bidiKeyboard
&& NS_SUCCEEDED(bidiKeyboard
->IsLangRTL(&isCaretRTL
))) {
888 // If keyboard language is RTL, draw the hook on the left; if LTR, to the right
889 // The height of the hook rectangle is the same as the width of the caret
892 aHookRect
->SetRect(aCaretRect
->XMost() - bidiIndicatorSize
,
893 aCaretRect
->y
+ (isCaretRTL
? bidiIndicatorSize
* -1 :
898 aHookRect
->SetRect(aCaretRect
->x
+ (isCaretRTL
? bidiIndicatorSize
* -1 :
900 aCaretRect
->y
+ bidiIndicatorSize
,
908 void nsCaret::CaretBlinkCallback(nsITimer
* aTimer
, void* aClosure
)
910 nsCaret
* theCaret
= reinterpret_cast<nsCaret
*>(aClosure
);
914 theCaret
->mIsBlinkOn
= !theCaret
->mIsBlinkOn
;
915 theCaret
->SchedulePaint();
919 nsCaret::SetIgnoreUserModify(bool aIgnoreUserModify
)
921 mIgnoreUserModify
= aIgnoreUserModify
;