Bumping manifests a=b2g-bump
[gecko.git] / layout / base / nsCaret.cpp
blob3a596d9426508a7b7e6a5e9dc65cfe575892d125
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 */
9 #include "nsCaret.h"
11 #include <algorithm>
13 #include "gfxUtils.h"
14 #include "mozilla/gfx/2D.h"
15 #include "nsCOMPtr.h"
16 #include "nsFontMetrics.h"
17 #include "nsITimer.h"
18 #include "nsFrameSelection.h"
19 #include "nsIFrame.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;
49 /**
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.
55 static nsIFrame*
56 CheckForTrailingTextFrameRecursive(nsIFrame* aFrame, nsIFrame* aStopAtFrame)
58 if (aFrame == aStopAtFrame ||
59 ((aFrame->GetType() == nsGkAtoms::textFrame &&
60 (static_cast<nsTextFrame*>(aFrame))->IsAtEndOfLine())))
61 return aFrame;
62 if (!aFrame->IsFrameOfType(nsIFrame::eLineParticipant))
63 return nullptr;
65 for (nsIFrame* f = aFrame->GetFirstPrincipalChild(); f; f = f->GetNextSibling())
67 nsIFrame* r = CheckForTrailingTextFrameRecursive(f, aStopAtFrame);
68 if (r)
69 return r;
71 return nullptr;
74 static nsLineBox*
75 FindContainingLine(nsIFrame* aFrame)
77 while (aFrame && aFrame->IsFrameOfType(nsIFrame::eLineParticipant))
79 nsIFrame* parent = aFrame->GetParent();
80 nsBlockFrame* blockParent = nsLayoutUtils::GetAsBlock(parent);
81 if (blockParent)
83 bool isValid;
84 nsBlockInFlowLineIterator iter(blockParent, aFrame, &isValid);
85 return isValid ? iter.GetLine().get() : nullptr;
87 aFrame = parent;
89 return nullptr;
92 static void
93 AdjustCaretFrameForLineEnd(nsIFrame** aFrame, int32_t* aOffset)
95 nsLineBox* line = FindContainingLine(*aFrame);
96 if (!line)
97 return;
98 int32_t count = line->GetChildCount();
99 for (nsIFrame* f = line->mFirstChild; count > 0; --count, f = f->GetNextSibling())
101 nsIFrame* r = CheckForTrailingTextFrameRecursive(f, *aFrame);
102 if (r == *aFrame)
103 return;
104 if (r)
106 *aFrame = r;
107 NS_ASSERTION(r->GetType() == nsGkAtoms::textFrame, "Expected text frame");
108 *aOffset = (static_cast<nsTextFrame*>(r))->GetContentEnd();
109 return;
114 static bool
115 IsBidiUI()
117 return Preferences::GetBool("bidi.browser.ui");
120 nsCaret::nsCaret()
121 : mOverrideOffset(0)
122 , mIsBlinkOn(false)
123 , mVisible(false)
124 , mReadOnly(false)
125 , mShowDuringSelection(false)
126 , mIgnoreUserModify(true)
130 nsCaret::~nsCaret()
132 StopBlinking();
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
147 // listener
149 nsCOMPtr<nsISelectionController> selCon = do_QueryReferent(mPresShell);
150 if (!selCon)
151 return NS_ERROR_FAILURE;
153 nsCOMPtr<nsISelection> domSelection;
154 nsresult rv = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
155 getter_AddRefs(domSelection));
156 if (NS_FAILED(rv))
157 return rv;
158 if (!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);
166 return NS_OK;
169 static bool
170 DrawCJKCaret(nsIFrame* aFrame, int32_t aOffset)
172 nsIContent* content = aFrame->GetContent();
173 const nsTextFragment* frag = content->GetText();
174 if (!frag)
175 return false;
176 if (aOffset < 0 || uint32_t(aOffset) >= frag->GetLength())
177 return false;
178 char16_t ch = frag->CharAt(aOffset);
179 return 0x2e80 <= ch && ch <= 0xd7ff;
182 nsCaret::Metrics
183 nsCaret::ComputeMetrics(nsIFrame* aFrame, int32_t aOffset, nscoord aCaretHeight)
185 // Compute nominal sizes in appunits
186 nscoord caretWidth =
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();
200 Metrics result;
201 result.mCaretWidth = NS_ROUND_BORDER_TO_PIXELS(caretWidth, tpp);
202 result.mBidiIndicatorSize = NS_ROUND_BORDER_TO_PIXELS(bidiIndicatorSize, tpp);
203 return result;
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.
211 StopBlinking();
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));
230 return sel;
233 void nsCaret::SetSelection(nsISelection *aDOMSel)
235 MOZ_ASSERT(aDOMSel);
236 mDomSelectionWeak = do_GetWeakReference(aDOMSel); // weak reference to pres shell
237 ResetBlinking();
238 SchedulePaint();
241 void nsCaret::SetVisible(bool inMakeVisible)
243 mVisible = inMakeVisible;
244 mIgnoreUserModify = mVisible;
245 ResetBlinking();
246 SchedulePaint();
249 bool nsCaret::IsVisible()
251 if (!mVisible) {
252 return false;
255 if (!mShowDuringSelection) {
256 Selection* selection = GetSelectionInternal();
257 if (!selection) {
258 return false;
260 bool isCollapsed;
261 if (NS_FAILED(selection->GetIsCollapsed(&isCollapsed)) || !isCollapsed) {
262 return false;
266 if (IsMenuPopupHidingCaret()) {
267 return false;
270 return true;
273 void nsCaret::SetCaretReadOnly(bool inMakeReadonly)
275 mReadOnly = inMakeReadonly;
276 ResetBlinking();
277 SchedulePaint();
280 /* static */ nsRect
281 nsCaret::GetGeometryForFrame(nsIFrame* aFrame,
282 int32_t aFrameOffset,
283 nscoord* aBidiIndicatorSize)
285 nsPoint framePos(0, 0);
286 nsRect rect;
287 nsresult rv = aFrame->GetPointFromOffset(aFrameOffset, &framePos);
288 if (NS_FAILED(rv)) {
289 if (aBidiIndicatorSize) {
290 *aBidiIndicatorSize = 0;
292 return rect;
295 nsIFrame* frame = aFrame->GetContentInsertionFrame();
296 if (!frame) {
297 frame = aFrame;
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");
307 if (fm) {
308 ascent = fm->MaxAscent();
309 descent = fm->MaxDescent();
311 nscoord height = ascent + descent;
312 WritingMode wm = aFrame->GetWritingMode();
313 bool vertical = wm.IsVertical();
314 if (vertical) {
315 if (wm.IsLineInverted()) {
316 framePos.x = baseline - descent;
317 } else {
318 framePos.x = baseline - ascent;
320 } else {
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);
331 if (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.
339 if (vertical) {
340 nscoord overflow = caretInScroll.YMost() -
341 scrolled->GetVisualOverflowRectRelativeToSelf().height;
342 if (overflow > 0) {
343 rect.y -= overflow;
345 } else {
346 nscoord overflow = caretInScroll.XMost() -
347 scrolled->GetVisualOverflowRectRelativeToSelf().width;
348 if (overflow > 0) {
349 rect.x -= overflow;
354 if (aBidiIndicatorSize) {
355 *aBidiIndicatorSize = caretMetrics.mBidiIndicatorSize;
357 return rect;
360 static nsIFrame*
361 GetFrameAndOffset(Selection* aSelection,
362 nsINode* aOverrideNode, int32_t aOverrideOffset,
363 int32_t* aFrameOffset)
365 nsINode* focusNode;
366 int32_t focusOffset;
368 if (aOverrideNode) {
369 focusNode = aOverrideNode;
370 focusOffset = aOverrideOffset;
371 } else if (aSelection) {
372 focusNode = aSelection->GetFocusNode();
373 aSelection->GetFocusOffset(&focusOffset);
374 } else {
375 return nullptr;
378 if (!focusNode || !focusNode->IsContent()) {
379 return nullptr;
382 nsIContent* contentNode = focusNode->AsContent();
383 nsFrameSelection* frameSelection = aSelection->GetFrameSelection();
384 nsBidiLevel bidiLevel = frameSelection->GetCaretBidiLevel();
385 nsIFrame* frame;
386 nsresult rv = nsCaret::GetCaretFrameForNodeOffset(
387 frameSelection, contentNode, focusOffset,
388 frameSelection->GetHint(), bidiLevel, &frame, aFrameOffset);
389 if (NS_FAILED(rv) || !frame) {
390 return nullptr;
393 return frame;
396 /* static */ nsIFrame*
397 nsCaret::GetGeometry(nsISelection* aSelection, nsRect* aRect)
399 int32_t frameOffset;
400 nsIFrame* frame = GetFrameAndOffset(
401 static_cast<Selection*>(aSelection), nullptr, 0, &frameOffset);
402 if (frame) {
403 *aRect = GetGeometryForFrame(frame, frameOffset, nullptr);
405 return frame;
408 Selection*
409 nsCaret::GetSelectionInternal()
411 return static_cast<Selection*>(GetSelection());
414 void nsCaret::SchedulePaint()
416 Selection* selection = GetSelectionInternal();
417 nsINode* focusNode;
418 if (mOverrideContent) {
419 focusNode = mOverrideContent;
420 } else if (selection) {
421 focusNode = selection->GetFocusNode();
422 } else {
423 return;
425 if (!focusNode || !focusNode->IsContent()) {
426 return;
428 nsIFrame* f = focusNode->AsContent()->GetPrimaryFrame();
429 if (!f) {
430 return;
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).
434 f->SchedulePaint();
437 void nsCaret::SetVisibilityDuringSelection(bool aVisibility)
439 mShowDuringSelection = aVisibility;
440 SchedulePaint();
443 void
444 nsCaret::SetCaretPosition(nsIDOMNode* aNode, int32_t aOffset)
446 mOverrideContent = do_QueryInterface(aNode);
447 mOverrideOffset = aOffset;
449 ResetBlinking();
450 SchedulePaint();
453 void
454 nsCaret::CheckSelectionLanguageChange()
456 if (!IsBidiUI()) {
457 return;
460 bool isKeyboardRTL = false;
461 nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard();
462 if (bidiKeyboard) {
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();
469 if (selection) {
470 selection->SelectionLanguageChange(isKeyboardRTL);
474 nsIFrame*
475 nsCaret::GetPaintGeometry(nsRect* aRect)
477 // Return null if we should not be visible.
478 if (!IsVisible() || !mIsBlinkOn) {
479 return nullptr;
482 // Update selection language direction now so the new direction will be
483 // taken into account when computing the caret position below.
484 CheckSelectionLanguageChange();
486 int32_t frameOffset;
487 nsIFrame *frame = GetFrameAndOffset(GetSelectionInternal(),
488 mOverrideContent, mOverrideOffset, &frameOffset);
489 if (!frame) {
490 return nullptr;
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) {
499 return nullptr;
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)) {
508 return nullptr;
511 nsRect caretRect;
512 nsRect hookRect;
513 ComputeCaretRects(frame, frameOffset, &caretRect, &hookRect);
515 aRect->UnionRect(caretRect, hookRect);
516 return frame;
519 void nsCaret::PaintCaret(nsDisplayListBuilder *aBuilder,
520 DrawTarget& aDrawTarget,
521 nsIFrame* aForFrame,
522 const nsPoint &aOffset)
524 int32_t contentOffset;
525 nsIFrame* frame = GetFrameAndOffset(GetSelectionInternal(),
526 mOverrideContent, mOverrideOffset, &contentOffset);
527 if (!frame) {
528 return;
530 NS_ASSERTION(frame == aForFrame, "We're referring different frame");
532 int32_t appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel();
534 nsRect caretRect;
535 nsRect hookRect;
536 ComputeCaretRects(frame, contentOffset, &caretRect, &hookRect);
538 Rect devPxCaretRect =
539 NSRectToSnappedRect(caretRect + aOffset, appUnitsPerDevPixel, aDrawTarget);
540 Rect devPxHookRect =
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);
550 NS_IMETHODIMP
551 nsCaret::NotifySelectionChanged(nsIDOMDocument *, nsISelection *aDomSel,
552 int16_t aReason)
554 if (aReason & nsISelectionListener::MOUSEUP_REASON)//this wont do
555 return NS_OK;
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
565 // is nothing to do!
567 if (domSel != aDomSel)
568 return NS_OK;
570 ResetBlinking();
571 SchedulePaint();
573 return NS_OK;
576 void nsCaret::ResetBlinking()
578 mIsBlinkOn = true;
580 if (mReadOnly || !mVisible) {
581 StopBlinking();
582 return;
585 if (mBlinkTimer) {
586 mBlinkTimer->Cancel();
587 } else {
588 nsresult err;
589 mBlinkTimer = do_CreateInstance("@mozilla.org/timer;1", &err);
590 if (NS_FAILED(err))
591 return;
594 uint32_t blinkRate = static_cast<uint32_t>(
595 LookAndFeel::GetInt(LookAndFeel::eIntID_CaretBlinkTime, 500));
596 if (blinkRate > 0) {
597 mBlinkTimer->InitWithFuncCallback(CaretBlinkCallback, this, blinkRate,
598 nsITimer::TYPE_REPEATING_SLACK);
602 void nsCaret::StopBlinking()
604 if (mBlinkTimer)
606 mBlinkTimer->Cancel();
610 nsresult
611 nsCaret::GetCaretFrameForNodeOffset(nsFrameSelection* aFrameSelection,
612 nsIContent* aContentNode,
613 int32_t aOffset,
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();
622 if (!presShell)
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);
634 if (!theFrame)
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
649 if (IsBidiUI())
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);
655 int32_t start;
656 int32_t end;
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;
694 else
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,
705 true);
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)
722 if (frameAfter)
724 // if there is a frameAfter, move into it
725 theFrame = frameAfter;
726 theFrame->GetOffsets(start, end);
727 theFrameOffset = start;
729 else
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,
740 true);
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;
784 return NS_OK;
787 size_t nsCaret::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
789 size_t total = aMallocSizeOf(this);
790 if (mPresShell) {
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);
800 if (mBlinkTimer) {
801 total += mBlinkTimer->SizeOfIncludingThis(aMallocSizeOf);
803 return total;
806 bool nsCaret::IsMenuPopupHidingCaret()
808 #ifdef MOZ_XUL
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);
821 if (!domSelection)
822 return true; // No selection/caret to draw.
823 domSelection->GetFocusNode(getter_AddRefs(node));
824 if (!node)
825 return true; // No selection/caret to draw.
826 nsCOMPtr<nsIContent> caretContent = do_QueryInterface(node);
827 if (!caretContent)
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.
839 return false;
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.
846 return true;
849 #endif
851 // There are no open menu popups, no need to hide the caret.
852 return false;
855 void
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) {
869 if (isVertical) {
870 aCaretRect->y -= aCaretRect->height;
871 } else {
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();
878 if (!IsBidiUI()) {
879 return;
882 bool isCaretRTL;
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
890 // rectangle.
891 if (isVertical) {
892 aHookRect->SetRect(aCaretRect->XMost() - bidiIndicatorSize,
893 aCaretRect->y + (isCaretRTL ? bidiIndicatorSize * -1 :
894 aCaretRect->height),
895 aCaretRect->height,
896 bidiIndicatorSize);
897 } else {
898 aHookRect->SetRect(aCaretRect->x + (isCaretRTL ? bidiIndicatorSize * -1 :
899 aCaretRect->width),
900 aCaretRect->y + bidiIndicatorSize,
901 bidiIndicatorSize,
902 aCaretRect->width);
907 /* static */
908 void nsCaret::CaretBlinkCallback(nsITimer* aTimer, void* aClosure)
910 nsCaret* theCaret = reinterpret_cast<nsCaret*>(aClosure);
911 if (!theCaret) {
912 return;
914 theCaret->mIsBlinkOn = !theCaret->mIsBlinkOn;
915 theCaret->SchedulePaint();
918 void
919 nsCaret::SetIgnoreUserModify(bool aIgnoreUserModify)
921 mIgnoreUserModify = aIgnoreUserModify;
922 SchedulePaint();