Bumping manifests a=b2g-bump
[gecko.git] / layout / base / nsCaret.cpp
bloba67a2054564aac335cb6bf57c7920a5b4975adbf
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 "nsCOMPtr.h"
14 #include "nsITimer.h"
15 #include "nsFrameSelection.h"
16 #include "nsIFrame.h"
17 #include "nsIScrollableFrame.h"
18 #include "nsIDOMNode.h"
19 #include "nsISelection.h"
20 #include "nsISelectionPrivate.h"
21 #include "nsIContent.h"
22 #include "nsIPresShell.h"
23 #include "nsRenderingContext.h"
24 #include "nsPresContext.h"
25 #include "nsBlockFrame.h"
26 #include "nsISelectionController.h"
27 #include "nsTextFrame.h"
28 #include "nsXULPopupManager.h"
29 #include "nsMenuPopupFrame.h"
30 #include "nsTextFragment.h"
31 #include "mozilla/Preferences.h"
32 #include "mozilla/LookAndFeel.h"
33 #include "mozilla/dom/Selection.h"
34 #include "nsIBidiKeyboard.h"
35 #include "nsContentUtils.h"
37 using namespace mozilla;
38 using namespace mozilla::dom;
40 // The bidi indicator hangs off the caret to one side, to show which
41 // direction the typing is in. It needs to be at least 2x2 to avoid looking like
42 // an insignificant dot
43 static const int32_t kMinBidiIndicatorPixels = 2;
45 /**
46 * Find the first frame in an in-order traversal of the frame subtree rooted
47 * at aFrame which is either a text frame logically at the end of a line,
48 * or which is aStopAtFrame. Return null if no such frame is found. We don't
49 * descend into the children of non-eLineParticipant frames.
51 static nsIFrame*
52 CheckForTrailingTextFrameRecursive(nsIFrame* aFrame, nsIFrame* aStopAtFrame)
54 if (aFrame == aStopAtFrame ||
55 ((aFrame->GetType() == nsGkAtoms::textFrame &&
56 (static_cast<nsTextFrame*>(aFrame))->IsAtEndOfLine())))
57 return aFrame;
58 if (!aFrame->IsFrameOfType(nsIFrame::eLineParticipant))
59 return nullptr;
61 for (nsIFrame* f = aFrame->GetFirstPrincipalChild(); f; f = f->GetNextSibling())
63 nsIFrame* r = CheckForTrailingTextFrameRecursive(f, aStopAtFrame);
64 if (r)
65 return r;
67 return nullptr;
70 static nsLineBox*
71 FindContainingLine(nsIFrame* aFrame)
73 while (aFrame && aFrame->IsFrameOfType(nsIFrame::eLineParticipant))
75 nsIFrame* parent = aFrame->GetParent();
76 nsBlockFrame* blockParent = nsLayoutUtils::GetAsBlock(parent);
77 if (blockParent)
79 bool isValid;
80 nsBlockInFlowLineIterator iter(blockParent, aFrame, &isValid);
81 return isValid ? iter.GetLine().get() : nullptr;
83 aFrame = parent;
85 return nullptr;
88 static void
89 AdjustCaretFrameForLineEnd(nsIFrame** aFrame, int32_t* aOffset)
91 nsLineBox* line = FindContainingLine(*aFrame);
92 if (!line)
93 return;
94 int32_t count = line->GetChildCount();
95 for (nsIFrame* f = line->mFirstChild; count > 0; --count, f = f->GetNextSibling())
97 nsIFrame* r = CheckForTrailingTextFrameRecursive(f, *aFrame);
98 if (r == *aFrame)
99 return;
100 if (r)
102 *aFrame = r;
103 NS_ASSERTION(r->GetType() == nsGkAtoms::textFrame, "Expected text frame");
104 *aOffset = (static_cast<nsTextFrame*>(r))->GetContentEnd();
105 return;
110 static bool
111 IsBidiUI()
113 return Preferences::GetBool("bidi.browser.ui");
116 nsCaret::nsCaret()
117 : mOverrideOffset(0)
118 , mIsBlinkOn(false)
119 , mVisible(false)
120 , mReadOnly(false)
121 , mShowDuringSelection(false)
122 , mIgnoreUserModify(true)
126 nsCaret::~nsCaret()
128 StopBlinking();
131 nsresult nsCaret::Init(nsIPresShell *inPresShell)
133 NS_ENSURE_ARG(inPresShell);
135 mPresShell = do_GetWeakReference(inPresShell); // the presshell owns us, so no addref
136 NS_ASSERTION(mPresShell, "Hey, pres shell should support weak refs");
138 mShowDuringSelection =
139 LookAndFeel::GetInt(LookAndFeel::eIntID_ShowCaretDuringSelection,
140 mShowDuringSelection ? 1 : 0) != 0;
142 // get the selection from the pres shell, and set ourselves up as a selection
143 // listener
145 nsCOMPtr<nsISelectionController> selCon = do_QueryReferent(mPresShell);
146 if (!selCon)
147 return NS_ERROR_FAILURE;
149 nsCOMPtr<nsISelection> domSelection;
150 nsresult rv = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
151 getter_AddRefs(domSelection));
152 if (NS_FAILED(rv))
153 return rv;
154 if (!domSelection)
155 return NS_ERROR_FAILURE;
157 nsCOMPtr<nsISelectionPrivate> privateSelection = do_QueryInterface(domSelection);
158 if (privateSelection)
159 privateSelection->AddSelectionListener(this);
160 mDomSelectionWeak = do_GetWeakReference(domSelection);
162 return NS_OK;
165 static bool
166 DrawCJKCaret(nsIFrame* aFrame, int32_t aOffset)
168 nsIContent* content = aFrame->GetContent();
169 const nsTextFragment* frag = content->GetText();
170 if (!frag)
171 return false;
172 if (aOffset < 0 || uint32_t(aOffset) >= frag->GetLength())
173 return false;
174 char16_t ch = frag->CharAt(aOffset);
175 return 0x2e80 <= ch && ch <= 0xd7ff;
178 nsCaret::Metrics
179 nsCaret::ComputeMetrics(nsIFrame* aFrame, int32_t aOffset, nscoord aCaretHeight)
181 // Compute nominal sizes in appunits
182 nscoord caretWidth =
183 (aCaretHeight * LookAndFeel::GetFloat(LookAndFeel::eFloatID_CaretAspectRatio, 0.0f)) +
184 nsPresContext::CSSPixelsToAppUnits(
185 LookAndFeel::GetInt(LookAndFeel::eIntID_CaretWidth, 1));
187 if (DrawCJKCaret(aFrame, aOffset)) {
188 caretWidth += nsPresContext::CSSPixelsToAppUnits(1);
190 nscoord bidiIndicatorSize = nsPresContext::CSSPixelsToAppUnits(kMinBidiIndicatorPixels);
191 bidiIndicatorSize = std::max(caretWidth, bidiIndicatorSize);
193 // Round them to device pixels. Always round down, except that anything
194 // between 0 and 1 goes up to 1 so we don't let the caret disappear.
195 int32_t tpp = aFrame->PresContext()->AppUnitsPerDevPixel();
196 Metrics result;
197 result.mCaretWidth = NS_ROUND_BORDER_TO_PIXELS(caretWidth, tpp);
198 result.mBidiIndicatorSize = NS_ROUND_BORDER_TO_PIXELS(bidiIndicatorSize, tpp);
199 return result;
202 void nsCaret::Terminate()
204 // this doesn't erase the caret if it's drawn. Should it? We might not have
205 // a good drawing environment during teardown.
207 StopBlinking();
208 mBlinkTimer = nullptr;
210 // unregiser ourselves as a selection listener
211 nsCOMPtr<nsISelection> domSelection = do_QueryReferent(mDomSelectionWeak);
212 nsCOMPtr<nsISelectionPrivate> privateSelection(do_QueryInterface(domSelection));
213 if (privateSelection)
214 privateSelection->RemoveSelectionListener(this);
215 mDomSelectionWeak = nullptr;
216 mPresShell = nullptr;
218 mOverrideContent = nullptr;
221 NS_IMPL_ISUPPORTS(nsCaret, nsISelectionListener)
223 nsISelection* nsCaret::GetSelection()
225 nsCOMPtr<nsISelection> sel(do_QueryReferent(mDomSelectionWeak));
226 return sel;
229 void nsCaret::SetSelection(nsISelection *aDOMSel)
231 MOZ_ASSERT(aDOMSel);
232 mDomSelectionWeak = do_GetWeakReference(aDOMSel); // weak reference to pres shell
233 ResetBlinking();
234 SchedulePaint();
237 void nsCaret::SetVisible(bool inMakeVisible)
239 mVisible = inMakeVisible;
240 mIgnoreUserModify = mVisible;
241 ResetBlinking();
242 SchedulePaint();
245 bool nsCaret::IsVisible()
247 if (!mVisible) {
248 return false;
251 if (!mShowDuringSelection) {
252 Selection* selection = GetSelectionInternal();
253 if (!selection) {
254 return false;
256 bool isCollapsed;
257 if (NS_FAILED(selection->GetIsCollapsed(&isCollapsed)) || !isCollapsed) {
258 return false;
262 if (IsMenuPopupHidingCaret()) {
263 return false;
266 return true;
269 void nsCaret::SetCaretReadOnly(bool inMakeReadonly)
271 mReadOnly = inMakeReadonly;
272 ResetBlinking();
273 SchedulePaint();
276 /* static */ nsRect
277 nsCaret::GetGeometryForFrame(nsIFrame* aFrame,
278 int32_t aFrameOffset,
279 nscoord* aBidiIndicatorSize)
281 nsPoint framePos(0, 0);
282 nsRect rect;
283 nsresult rv = aFrame->GetPointFromOffset(aFrameOffset, &framePos);
284 if (NS_FAILED(rv)) {
285 if (aBidiIndicatorSize) {
286 *aBidiIndicatorSize = 0;
288 return rect;
291 nsIFrame* frame = aFrame->GetContentInsertionFrame();
292 if (!frame) {
293 frame = aFrame;
295 NS_ASSERTION(!(frame->GetStateBits() & NS_FRAME_IN_REFLOW),
296 "We should not be in the middle of reflow");
297 nscoord baseline = frame->GetCaretBaseline();
298 nscoord ascent = 0, descent = 0;
299 nsRefPtr<nsFontMetrics> fm;
300 nsLayoutUtils::GetFontMetricsForFrame(aFrame, getter_AddRefs(fm),
301 nsLayoutUtils::FontSizeInflationFor(aFrame));
302 NS_ASSERTION(fm, "We should be able to get the font metrics");
303 if (fm) {
304 ascent = fm->MaxAscent();
305 descent = fm->MaxDescent();
307 nscoord height = ascent + descent;
308 framePos.y = baseline - ascent;
309 Metrics caretMetrics = ComputeMetrics(aFrame, aFrameOffset, height);
310 rect = nsRect(framePos, nsSize(caretMetrics.mCaretWidth, height));
312 // Clamp the x-position to be within our scroll frame. If we don't, then it
313 // clips us, and we don't appear at all. See bug 335560.
314 nsIFrame *scrollFrame =
315 nsLayoutUtils::GetClosestFrameOfType(aFrame, nsGkAtoms::scrollFrame);
316 if (scrollFrame) {
317 // First, use the scrollFrame to get at the scrollable view that we're in.
318 nsIScrollableFrame *sf = do_QueryFrame(scrollFrame);
319 nsIFrame *scrolled = sf->GetScrolledFrame();
320 nsRect caretInScroll = rect + aFrame->GetOffsetTo(scrolled);
322 // Now see if thet caret extends beyond the view's bounds. If it does,
323 // then snap it back, put it as close to the edge as it can.
324 nscoord overflow = caretInScroll.XMost() -
325 scrolled->GetVisualOverflowRectRelativeToSelf().width;
326 if (overflow > 0) {
327 rect.x -= overflow;
331 if (aBidiIndicatorSize) {
332 *aBidiIndicatorSize = caretMetrics.mBidiIndicatorSize;
334 return rect;
337 static nsIFrame*
338 GetFrameAndOffset(Selection* aSelection,
339 nsINode* aOverrideNode, int32_t aOverrideOffset,
340 int32_t* aFrameOffset)
342 nsINode* focusNode;
343 int32_t focusOffset;
345 if (aOverrideNode) {
346 focusNode = aOverrideNode;
347 focusOffset = aOverrideOffset;
348 } else if (aSelection) {
349 focusNode = aSelection->GetFocusNode();
350 aSelection->GetFocusOffset(&focusOffset);
351 } else {
352 return nullptr;
355 if (!focusNode || !focusNode->IsContent()) {
356 return nullptr;
359 nsIContent* contentNode = focusNode->AsContent();
360 nsFrameSelection* frameSelection = aSelection->GetFrameSelection();
361 uint8_t bidiLevel = frameSelection->GetCaretBidiLevel();
362 nsIFrame* frame;
363 nsresult rv = nsCaret::GetCaretFrameForNodeOffset(
364 frameSelection, contentNode, focusOffset,
365 frameSelection->GetHint(), bidiLevel, &frame, aFrameOffset);
366 if (NS_FAILED(rv) || !frame) {
367 return nullptr;
370 return frame;
373 /* static */ nsIFrame*
374 nsCaret::GetGeometry(nsISelection* aSelection, nsRect* aRect)
376 int32_t frameOffset;
377 nsIFrame* frame = GetFrameAndOffset(
378 static_cast<Selection*>(aSelection), nullptr, 0, &frameOffset);
379 if (frame) {
380 *aRect = GetGeometryForFrame(frame, frameOffset, nullptr);
382 return frame;
385 Selection*
386 nsCaret::GetSelectionInternal()
388 return static_cast<Selection*>(GetSelection());
391 void nsCaret::SchedulePaint()
393 Selection* selection = GetSelectionInternal();
394 nsINode* focusNode;
395 if (mOverrideContent) {
396 focusNode = mOverrideContent;
397 } else if (selection) {
398 focusNode = selection->GetFocusNode();
399 } else {
400 return;
402 if (!focusNode || !focusNode->IsContent()) {
403 return;
405 nsIFrame* f = focusNode->AsContent()->GetPrimaryFrame();
406 if (!f) {
407 return;
409 // This may not be the correct continuation frame, but that's OK since we're
410 // just scheduling a paint of the window (or popup).
411 f->SchedulePaint();
414 void nsCaret::SetVisibilityDuringSelection(bool aVisibility)
416 mShowDuringSelection = aVisibility;
417 SchedulePaint();
420 void
421 nsCaret::SetCaretPosition(nsIDOMNode* aNode, int32_t aOffset)
423 mOverrideContent = do_QueryInterface(aNode);
424 mOverrideOffset = aOffset;
426 ResetBlinking();
427 SchedulePaint();
430 void
431 nsCaret::CheckSelectionLanguageChange()
433 if (!IsBidiUI()) {
434 return;
437 bool isKeyboardRTL = false;
438 nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard();
439 if (bidiKeyboard) {
440 bidiKeyboard->IsLangRTL(&isKeyboardRTL);
442 // Call SelectionLanguageChange on every paint. Mostly it will be a noop
443 // but it should be fast anyway. This guarantees we never paint the caret
444 // at the wrong place.
445 Selection* selection = GetSelectionInternal();
446 if (selection) {
447 selection->SelectionLanguageChange(isKeyboardRTL);
451 nsIFrame*
452 nsCaret::GetPaintGeometry(nsRect* aRect)
454 // Return null if we should not be visible.
455 if (!IsVisible() || !mIsBlinkOn) {
456 return nullptr;
459 // Update selection language direction now so the new direction will be
460 // taken into account when computing the caret position below.
461 CheckSelectionLanguageChange();
463 int32_t frameOffset;
464 nsIFrame *frame = GetFrameAndOffset(GetSelectionInternal(),
465 mOverrideContent, mOverrideOffset, &frameOffset);
466 if (!frame) {
467 return nullptr;
470 // now we have a frame, check whether it's appropriate to show the caret here
471 const nsStyleUserInterface* userinterface = frame->StyleUserInterface();
472 if ((!mIgnoreUserModify &&
473 userinterface->mUserModify == NS_STYLE_USER_MODIFY_READ_ONLY) ||
474 userinterface->mUserInput == NS_STYLE_USER_INPUT_NONE ||
475 userinterface->mUserInput == NS_STYLE_USER_INPUT_DISABLED) {
476 return nullptr;
479 // If the offset falls outside of the frame, then don't paint the caret.
480 int32_t startOffset, endOffset;
481 if (frame->GetType() == nsGkAtoms::textFrame &&
482 (NS_FAILED(frame->GetOffsets(startOffset, endOffset)) ||
483 startOffset > frameOffset ||
484 endOffset < frameOffset)) {
485 return nullptr;
488 nsRect caretRect;
489 nsRect hookRect;
490 ComputeCaretRects(frame, frameOffset, &caretRect, &hookRect);
492 aRect->UnionRect(caretRect, hookRect);
493 return frame;
496 void nsCaret::PaintCaret(nsDisplayListBuilder *aBuilder,
497 nsRenderingContext *aCtx,
498 nsIFrame* aForFrame,
499 const nsPoint &aOffset)
501 int32_t contentOffset;
502 nsIFrame* frame = GetFrameAndOffset(GetSelectionInternal(),
503 mOverrideContent, mOverrideOffset, &contentOffset);
504 if (!frame) {
505 return;
507 NS_ASSERTION(frame == aForFrame, "We're referring different frame");
509 nscolor foregroundColor = aForFrame->GetCaretColorAt(contentOffset);
510 aCtx->SetColor(foregroundColor);
512 nsRect caretRect;
513 nsRect hookRect;
514 ComputeCaretRects(frame, contentOffset, &caretRect, &hookRect);
515 aCtx->FillRect(caretRect + aOffset);
516 if (!hookRect.IsEmpty()) {
517 aCtx->FillRect(hookRect + aOffset);
521 NS_IMETHODIMP
522 nsCaret::NotifySelectionChanged(nsIDOMDocument *, nsISelection *aDomSel,
523 int16_t aReason)
525 if (aReason & nsISelectionListener::MOUSEUP_REASON)//this wont do
526 return NS_OK;
528 nsCOMPtr<nsISelection> domSel(do_QueryReferent(mDomSelectionWeak));
530 // The same caret is shared amongst the document and any text widgets it
531 // may contain. This means that the caret could get notifications from
532 // multiple selections.
534 // If this notification is for a selection that is not the one the
535 // the caret is currently interested in (mDomSelectionWeak), then there
536 // is nothing to do!
538 if (domSel != aDomSel)
539 return NS_OK;
541 ResetBlinking();
542 SchedulePaint();
544 return NS_OK;
547 void nsCaret::ResetBlinking()
549 mIsBlinkOn = true;
551 if (mReadOnly || !mVisible) {
552 StopBlinking();
553 return;
556 if (mBlinkTimer) {
557 mBlinkTimer->Cancel();
558 } else {
559 nsresult err;
560 mBlinkTimer = do_CreateInstance("@mozilla.org/timer;1", &err);
561 if (NS_FAILED(err))
562 return;
565 uint32_t blinkRate = static_cast<uint32_t>(
566 LookAndFeel::GetInt(LookAndFeel::eIntID_CaretBlinkTime, 500));
567 if (blinkRate > 0) {
568 mBlinkTimer->InitWithFuncCallback(CaretBlinkCallback, this, blinkRate,
569 nsITimer::TYPE_REPEATING_SLACK);
573 void nsCaret::StopBlinking()
575 if (mBlinkTimer)
577 mBlinkTimer->Cancel();
581 nsresult
582 nsCaret::GetCaretFrameForNodeOffset(nsFrameSelection* aFrameSelection,
583 nsIContent* aContentNode,
584 int32_t aOffset,
585 CaretAssociationHint aFrameHint,
586 uint8_t aBidiLevel,
587 nsIFrame** aReturnFrame,
588 int32_t* aReturnOffset)
590 if (!aFrameSelection)
591 return NS_ERROR_FAILURE;
592 nsIPresShell* presShell = aFrameSelection->GetShell();
593 if (!presShell)
594 return NS_ERROR_FAILURE;
596 if (!aContentNode || !aContentNode->IsInComposedDoc() ||
597 presShell->GetDocument() != aContentNode->GetComposedDoc())
598 return NS_ERROR_FAILURE;
600 nsIFrame* theFrame = nullptr;
601 int32_t theFrameOffset = 0;
603 theFrame = aFrameSelection->GetFrameForNodeOffset(
604 aContentNode, aOffset, aFrameHint, &theFrameOffset);
605 if (!theFrame)
606 return NS_ERROR_FAILURE;
608 // if theFrame is after a text frame that's logically at the end of the line
609 // (e.g. if theFrame is a <br> frame), then put the caret at the end of
610 // that text frame instead. This way, the caret will be positioned as if
611 // trailing whitespace was not trimmed.
612 AdjustCaretFrameForLineEnd(&theFrame, &theFrameOffset);
614 // Mamdouh : modification of the caret to work at rtl and ltr with Bidi
616 // Direction Style from visibility->mDirection
617 // ------------------
618 // NS_STYLE_DIRECTION_LTR : LTR or Default
619 // NS_STYLE_DIRECTION_RTL
620 // NS_STYLE_DIRECTION_INHERIT
621 if (IsBidiUI())
623 // If there has been a reflow, take the caret Bidi level to be the level of the current frame
624 if (aBidiLevel & BIDI_LEVEL_UNDEFINED)
625 aBidiLevel = NS_GET_EMBEDDING_LEVEL(theFrame);
627 int32_t start;
628 int32_t end;
629 nsIFrame* frameBefore;
630 nsIFrame* frameAfter;
631 uint8_t levelBefore; // Bidi level of the character before the caret
632 uint8_t levelAfter; // Bidi level of the character after the caret
634 theFrame->GetOffsets(start, end);
635 if (start == 0 || end == 0 || start == theFrameOffset || end == theFrameOffset)
637 nsPrevNextBidiLevels levels = aFrameSelection->
638 GetPrevNextBidiLevels(aContentNode, aOffset, false);
640 /* Boundary condition, we need to know the Bidi levels of the characters before and after the caret */
641 if (levels.mFrameBefore || levels.mFrameAfter)
643 frameBefore = levels.mFrameBefore;
644 frameAfter = levels.mFrameAfter;
645 levelBefore = levels.mLevelBefore;
646 levelAfter = levels.mLevelAfter;
648 if ((levelBefore != levelAfter) || (aBidiLevel != levelBefore))
650 aBidiLevel = std::max(aBidiLevel, std::min(levelBefore, levelAfter)); // rule c3
651 aBidiLevel = std::min(aBidiLevel, std::max(levelBefore, levelAfter)); // rule c4
652 if (aBidiLevel == levelBefore // rule c1
653 || (aBidiLevel > levelBefore && aBidiLevel < levelAfter && !((aBidiLevel ^ levelBefore) & 1)) // rule c5
654 || (aBidiLevel < levelBefore && aBidiLevel > levelAfter && !((aBidiLevel ^ levelBefore) & 1))) // rule c9
656 if (theFrame != frameBefore)
658 if (frameBefore) // if there is a frameBefore, move into it
660 theFrame = frameBefore;
661 theFrame->GetOffsets(start, end);
662 theFrameOffset = end;
664 else
666 // if there is no frameBefore, we must be at the beginning of the line
667 // so we stay with the current frame.
668 // Exception: when the first frame on the line has a different Bidi level from the paragraph level, there is no
669 // real frame for the caret to be in. We have to find the visually first frame on the line.
670 uint8_t baseLevel = NS_GET_BASE_LEVEL(frameAfter);
671 if (baseLevel != levelAfter)
673 nsPeekOffsetStruct pos(eSelectBeginLine, eDirPrevious, 0, 0, false, true, false, true);
674 if (NS_SUCCEEDED(frameAfter->PeekOffset(&pos))) {
675 theFrame = pos.mResultFrame;
676 theFrameOffset = pos.mContentOffset;
682 else if (aBidiLevel == levelAfter // rule c2
683 || (aBidiLevel > levelBefore && aBidiLevel < levelAfter && !((aBidiLevel ^ levelAfter) & 1)) // rule c6
684 || (aBidiLevel < levelBefore && aBidiLevel > levelAfter && !((aBidiLevel ^ levelAfter) & 1))) // rule c10
686 if (theFrame != frameAfter)
688 if (frameAfter)
690 // if there is a frameAfter, move into it
691 theFrame = frameAfter;
692 theFrame->GetOffsets(start, end);
693 theFrameOffset = start;
695 else
697 // if there is no frameAfter, we must be at the end of the line
698 // so we stay with the current frame.
699 // Exception: when the last frame on the line has a different Bidi level from the paragraph level, there is no
700 // real frame for the caret to be in. We have to find the visually last frame on the line.
701 uint8_t baseLevel = NS_GET_BASE_LEVEL(frameBefore);
702 if (baseLevel != levelBefore)
704 nsPeekOffsetStruct pos(eSelectEndLine, eDirNext, 0, 0, false, true, false, true);
705 if (NS_SUCCEEDED(frameBefore->PeekOffset(&pos))) {
706 theFrame = pos.mResultFrame;
707 theFrameOffset = pos.mContentOffset;
713 else if (aBidiLevel > levelBefore && aBidiLevel < levelAfter // rule c7/8
714 && !((levelBefore ^ levelAfter) & 1) // before and after have the same parity
715 && ((aBidiLevel ^ levelAfter) & 1)) // caret has different parity
717 if (NS_SUCCEEDED(aFrameSelection->GetFrameFromLevel(frameAfter, eDirNext, aBidiLevel, &theFrame)))
719 theFrame->GetOffsets(start, end);
720 levelAfter = NS_GET_EMBEDDING_LEVEL(theFrame);
721 if (aBidiLevel & 1) // c8: caret to the right of the rightmost character
722 theFrameOffset = (levelAfter & 1) ? start : end;
723 else // c7: caret to the left of the leftmost character
724 theFrameOffset = (levelAfter & 1) ? end : start;
727 else if (aBidiLevel < levelBefore && aBidiLevel > levelAfter // rule c11/12
728 && !((levelBefore ^ levelAfter) & 1) // before and after have the same parity
729 && ((aBidiLevel ^ levelAfter) & 1)) // caret has different parity
731 if (NS_SUCCEEDED(aFrameSelection->GetFrameFromLevel(frameBefore, eDirPrevious, aBidiLevel, &theFrame)))
733 theFrame->GetOffsets(start, end);
734 levelBefore = NS_GET_EMBEDDING_LEVEL(theFrame);
735 if (aBidiLevel & 1) // c12: caret to the left of the leftmost character
736 theFrameOffset = (levelBefore & 1) ? end : start;
737 else // c11: caret to the right of the rightmost character
738 theFrameOffset = (levelBefore & 1) ? start : end;
746 *aReturnFrame = theFrame;
747 *aReturnOffset = theFrameOffset;
748 return NS_OK;
751 size_t nsCaret::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
753 size_t total = aMallocSizeOf(this);
754 if (mPresShell) {
755 // We only want the size of the nsWeakReference object, not the PresShell
756 // (since we don't own the PresShell).
757 total += mPresShell->SizeOfOnlyThis(aMallocSizeOf);
759 if (mDomSelectionWeak) {
760 // We only want size of the nsWeakReference object, not the selection
761 // (again, we don't own the selection).
762 total += mDomSelectionWeak->SizeOfOnlyThis(aMallocSizeOf);
764 if (mBlinkTimer) {
765 total += mBlinkTimer->SizeOfIncludingThis(aMallocSizeOf);
767 return total;
770 bool nsCaret::IsMenuPopupHidingCaret()
772 #ifdef MOZ_XUL
773 // Check if there are open popups.
774 nsXULPopupManager *popMgr = nsXULPopupManager::GetInstance();
775 nsTArray<nsIFrame*> popups;
776 popMgr->GetVisiblePopups(popups);
778 if (popups.Length() == 0)
779 return false; // No popups, so caret can't be hidden by them.
781 // Get the selection focus content, that's where the caret would
782 // go if it was drawn.
783 nsCOMPtr<nsIDOMNode> node;
784 nsCOMPtr<nsISelection> domSelection = do_QueryReferent(mDomSelectionWeak);
785 if (!domSelection)
786 return true; // No selection/caret to draw.
787 domSelection->GetFocusNode(getter_AddRefs(node));
788 if (!node)
789 return true; // No selection/caret to draw.
790 nsCOMPtr<nsIContent> caretContent = do_QueryInterface(node);
791 if (!caretContent)
792 return true; // No selection/caret to draw.
794 // If there's a menu popup open before the popup with
795 // the caret, don't show the caret.
796 for (uint32_t i=0; i<popups.Length(); i++) {
797 nsMenuPopupFrame* popupFrame = static_cast<nsMenuPopupFrame*>(popups[i]);
798 nsIContent* popupContent = popupFrame->GetContent();
800 if (nsContentUtils::ContentIsDescendantOf(caretContent, popupContent)) {
801 // The caret is in this popup. There were no menu popups before this
802 // popup, so don't hide the caret.
803 return false;
806 if (popupFrame->PopupType() == ePopupTypeMenu && !popupFrame->IsContextMenu()) {
807 // This is an open menu popup. It does not contain the caret (else we'd
808 // have returned above). Even if the caret is in a subsequent popup,
809 // or another document/frame, it should be hidden.
810 return true;
813 #endif
815 // There are no open menu popups, no need to hide the caret.
816 return false;
819 void
820 nsCaret::ComputeCaretRects(nsIFrame* aFrame, int32_t aFrameOffset,
821 nsRect* aCaretRect, nsRect* aHookRect)
823 NS_ASSERTION(aFrame, "Should have a frame here");
825 nscoord bidiIndicatorSize;
826 *aCaretRect = GetGeometryForFrame(aFrame, aFrameOffset, &bidiIndicatorSize);
828 // on RTL frames the right edge of mCaretRect must be equal to framePos
829 const nsStyleVisibility* vis = aFrame->StyleVisibility();
830 if (NS_STYLE_DIRECTION_RTL == vis->mDirection) {
831 aCaretRect->x -= aCaretRect->width;
834 // Simon -- make a hook to draw to the left or right of the caret to show keyboard language direction
835 aHookRect->SetEmpty();
836 if (!IsBidiUI()) {
837 return;
840 bool isCaretRTL;
841 nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard();
842 // if bidiKeyboard->IsLangRTL() fails, there is no way to tell the
843 // keyboard direction, or the user has no right-to-left keyboard
844 // installed, so we never draw the hook.
845 if (bidiKeyboard && NS_SUCCEEDED(bidiKeyboard->IsLangRTL(&isCaretRTL))) {
846 // If keyboard language is RTL, draw the hook on the left; if LTR, to the right
847 // The height of the hook rectangle is the same as the width of the caret
848 // rectangle.
849 aHookRect->SetRect(aCaretRect->x + (isCaretRTL ? bidiIndicatorSize * -1 : aCaretRect->width),
850 aCaretRect->y + bidiIndicatorSize,
851 bidiIndicatorSize,
852 aCaretRect->width);
856 /* static */
857 void nsCaret::CaretBlinkCallback(nsITimer* aTimer, void* aClosure)
859 nsCaret* theCaret = reinterpret_cast<nsCaret*>(aClosure);
860 if (!theCaret) {
861 return;
863 theCaret->mIsBlinkOn = !theCaret->mIsBlinkOn;
864 theCaret->SchedulePaint();
867 void
868 nsCaret::SetIgnoreUserModify(bool aIgnoreUserModify)
870 mIgnoreUserModify = aIgnoreUserModify;
871 SchedulePaint();