Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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 "nsCOMPtr.h"
11 #include "nsITimer.h"
13 #include "nsFrameSelection.h"
14 #include "nsIFrame.h"
15 #include "nsIScrollableFrame.h"
16 #include "nsIDOMNode.h"
17 #include "nsISelection.h"
18 #include "nsISelectionPrivate.h"
19 #include "nsIContent.h"
20 #include "nsIPresShell.h"
21 #include "nsRenderingContext.h"
22 #include "nsPresContext.h"
23 #include "nsBlockFrame.h"
24 #include "nsISelectionController.h"
25 #include "nsCaret.h"
26 #include "nsTextFrame.h"
27 #include "nsXULPopupManager.h"
28 #include "nsMenuPopupFrame.h"
29 #include "nsTextFragment.h"
30 #include "mozilla/Preferences.h"
31 #include "mozilla/LookAndFeel.h"
32 #include "mozilla/dom/Selection.h"
33 #include <algorithm>
35 // The bidi indicator hangs off the caret to one side, to show which
36 // direction the typing is in. It needs to be at least 2x2 to avoid looking like
37 // an insignificant dot
38 static const int32_t kMinBidiIndicatorPixels = 2;
40 #include "nsIBidiKeyboard.h"
41 #include "nsContentUtils.h"
43 using namespace mozilla;
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.
50 */
51 static nsIFrame*
52 CheckForTrailingTextFrameRecursive(nsIFrame* aFrame, nsIFrame* aStopAtFrame)
53 {
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())
62 {
63 nsIFrame* r = CheckForTrailingTextFrameRecursive(f, aStopAtFrame);
64 if (r)
65 return r;
66 }
67 return nullptr;
68 }
70 static nsLineBox*
71 FindContainingLine(nsIFrame* aFrame)
72 {
73 while (aFrame && aFrame->IsFrameOfType(nsIFrame::eLineParticipant))
74 {
75 nsIFrame* parent = aFrame->GetParent();
76 nsBlockFrame* blockParent = nsLayoutUtils::GetAsBlock(parent);
77 if (blockParent)
78 {
79 bool isValid;
80 nsBlockInFlowLineIterator iter(blockParent, aFrame, &isValid);
81 return isValid ? iter.GetLine().get() : nullptr;
82 }
83 aFrame = parent;
84 }
85 return nullptr;
86 }
88 static void
89 AdjustCaretFrameForLineEnd(nsIFrame** aFrame, int32_t* aOffset)
90 {
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())
96 {
97 nsIFrame* r = CheckForTrailingTextFrameRecursive(f, *aFrame);
98 if (r == *aFrame)
99 return;
100 if (r)
101 {
102 *aFrame = r;
103 NS_ASSERTION(r->GetType() == nsGkAtoms::textFrame, "Expected text frame");
104 *aOffset = (static_cast<nsTextFrame*>(r))->GetContentEnd();
105 return;
106 }
107 }
108 }
110 //-----------------------------------------------------------------------------
112 nsCaret::nsCaret()
113 : mPresShell(nullptr)
114 , mBlinkRate(500)
115 , mVisible(false)
116 , mDrawn(false)
117 , mPendingDraw(false)
118 , mReadOnly(false)
119 , mShowDuringSelection(false)
120 , mIgnoreUserModify(true)
121 , mKeyboardRTL(false)
122 , mLastBidiLevel(0)
123 , mLastContentOffset(0)
124 , mLastHint(nsFrameSelection::HINTLEFT)
125 {
126 }
128 //-----------------------------------------------------------------------------
129 nsCaret::~nsCaret()
130 {
131 KillTimer();
132 }
134 //-----------------------------------------------------------------------------
135 nsresult nsCaret::Init(nsIPresShell *inPresShell)
136 {
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 // XXX we should just do this LookAndFeel consultation every time
143 // we need these values.
144 mCaretWidthCSSPx = LookAndFeel::GetInt(LookAndFeel::eIntID_CaretWidth, 1);
145 mCaretAspectRatio =
146 LookAndFeel::GetFloat(LookAndFeel::eFloatID_CaretAspectRatio, 0.0f);
148 mBlinkRate = static_cast<uint32_t>(
149 LookAndFeel::GetInt(LookAndFeel::eIntID_CaretBlinkTime, mBlinkRate));
150 mShowDuringSelection =
151 LookAndFeel::GetInt(LookAndFeel::eIntID_ShowCaretDuringSelection,
152 mShowDuringSelection ? 1 : 0) != 0;
154 // get the selection from the pres shell, and set ourselves up as a selection
155 // listener
157 nsCOMPtr<nsISelectionController> selCon = do_QueryReferent(mPresShell);
158 if (!selCon)
159 return NS_ERROR_FAILURE;
161 nsCOMPtr<nsISelection> domSelection;
162 nsresult rv = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
163 getter_AddRefs(domSelection));
164 if (NS_FAILED(rv))
165 return rv;
166 if (!domSelection)
167 return NS_ERROR_FAILURE;
169 nsCOMPtr<nsISelectionPrivate> privateSelection = do_QueryInterface(domSelection);
170 if (privateSelection)
171 privateSelection->AddSelectionListener(this);
172 mDomSelectionWeak = do_GetWeakReference(domSelection);
174 // set up the blink timer
175 if (mVisible)
176 {
177 StartBlinking();
178 }
179 mBidiUI = Preferences::GetBool("bidi.browser.ui");
181 return NS_OK;
182 }
184 static bool
185 DrawCJKCaret(nsIFrame* aFrame, int32_t aOffset)
186 {
187 nsIContent* content = aFrame->GetContent();
188 const nsTextFragment* frag = content->GetText();
189 if (!frag)
190 return false;
191 if (aOffset < 0 || uint32_t(aOffset) >= frag->GetLength())
192 return false;
193 char16_t ch = frag->CharAt(aOffset);
194 return 0x2e80 <= ch && ch <= 0xd7ff;
195 }
197 nsCaret::Metrics nsCaret::ComputeMetrics(nsIFrame* aFrame, int32_t aOffset, nscoord aCaretHeight)
198 {
199 // Compute nominal sizes in appunits
200 nscoord caretWidth = (aCaretHeight * mCaretAspectRatio) +
201 nsPresContext::CSSPixelsToAppUnits(mCaretWidthCSSPx);
203 if (DrawCJKCaret(aFrame, aOffset)) {
204 caretWidth += nsPresContext::CSSPixelsToAppUnits(1);
205 }
206 nscoord bidiIndicatorSize = nsPresContext::CSSPixelsToAppUnits(kMinBidiIndicatorPixels);
207 bidiIndicatorSize = std::max(caretWidth, bidiIndicatorSize);
209 // Round them to device pixels. Always round down, except that anything
210 // between 0 and 1 goes up to 1 so we don't let the caret disappear.
211 int32_t tpp = aFrame->PresContext()->AppUnitsPerDevPixel();
212 Metrics result;
213 result.mCaretWidth = NS_ROUND_BORDER_TO_PIXELS(caretWidth, tpp);
214 result.mBidiIndicatorSize = NS_ROUND_BORDER_TO_PIXELS(bidiIndicatorSize, tpp);
215 return result;
216 }
218 //-----------------------------------------------------------------------------
219 void nsCaret::Terminate()
220 {
221 // this doesn't erase the caret if it's drawn. Should it? We might not have
222 // a good drawing environment during teardown.
224 KillTimer();
225 mBlinkTimer = nullptr;
227 // unregiser ourselves as a selection listener
228 nsCOMPtr<nsISelection> domSelection = do_QueryReferent(mDomSelectionWeak);
229 nsCOMPtr<nsISelectionPrivate> privateSelection(do_QueryInterface(domSelection));
230 if (privateSelection)
231 privateSelection->RemoveSelectionListener(this);
232 mDomSelectionWeak = nullptr;
233 mPresShell = nullptr;
235 mLastContent = nullptr;
236 }
238 //-----------------------------------------------------------------------------
239 NS_IMPL_ISUPPORTS(nsCaret, nsISelectionListener)
241 //-----------------------------------------------------------------------------
242 nsISelection* nsCaret::GetCaretDOMSelection()
243 {
244 nsCOMPtr<nsISelection> sel(do_QueryReferent(mDomSelectionWeak));
245 return sel;
246 }
248 //-----------------------------------------------------------------------------
249 nsresult nsCaret::SetCaretDOMSelection(nsISelection *aDOMSel)
250 {
251 NS_ENSURE_ARG_POINTER(aDOMSel);
252 mDomSelectionWeak = do_GetWeakReference(aDOMSel); // weak reference to pres shell
253 if (mVisible)
254 {
255 // Stop the caret from blinking in its previous location.
256 StopBlinking();
257 // Start the caret blinking in the new location.
258 StartBlinking();
259 }
260 return NS_OK;
261 }
264 //-----------------------------------------------------------------------------
265 void nsCaret::SetCaretVisible(bool inMakeVisible)
266 {
267 mVisible = inMakeVisible;
268 if (mVisible) {
269 SetIgnoreUserModify(true);
270 StartBlinking();
271 } else {
272 StopBlinking();
273 SetIgnoreUserModify(false);
274 }
275 }
278 //-----------------------------------------------------------------------------
279 nsresult nsCaret::GetCaretVisible(bool *outMakeVisible)
280 {
281 NS_ENSURE_ARG_POINTER(outMakeVisible);
282 *outMakeVisible = (mVisible && MustDrawCaret(true));
283 return NS_OK;
284 }
287 //-----------------------------------------------------------------------------
288 void nsCaret::SetCaretReadOnly(bool inMakeReadonly)
289 {
290 mReadOnly = inMakeReadonly;
291 }
293 nsresult
294 nsCaret::GetGeometryForFrame(nsIFrame* aFrame,
295 int32_t aFrameOffset,
296 nsRect* aRect,
297 nscoord* aBidiIndicatorSize)
298 {
299 nsPoint framePos(0, 0);
300 nsresult rv = aFrame->GetPointFromOffset(aFrameOffset, &framePos);
301 if (NS_FAILED(rv))
302 return rv;
304 nsIFrame *frame = aFrame->GetContentInsertionFrame();
305 NS_ASSERTION(frame, "We should not be in the middle of reflow");
306 nscoord baseline = frame->GetCaretBaseline();
307 nscoord ascent = 0, descent = 0;
308 nsRefPtr<nsFontMetrics> fm;
309 nsLayoutUtils::GetFontMetricsForFrame(aFrame, getter_AddRefs(fm),
310 nsLayoutUtils::FontSizeInflationFor(aFrame));
311 NS_ASSERTION(fm, "We should be able to get the font metrics");
312 if (fm) {
313 ascent = fm->MaxAscent();
314 descent = fm->MaxDescent();
315 }
316 nscoord height = ascent + descent;
317 framePos.y = baseline - ascent;
318 Metrics caretMetrics = ComputeMetrics(aFrame, aFrameOffset, height);
319 *aRect = nsRect(framePos, nsSize(caretMetrics.mCaretWidth, height));
321 // Clamp the x-position to be within our scroll frame. If we don't, then it
322 // clips us, and we don't appear at all. See bug 335560.
323 nsIFrame *scrollFrame =
324 nsLayoutUtils::GetClosestFrameOfType(aFrame, nsGkAtoms::scrollFrame);
325 if (scrollFrame) {
326 // First, use the scrollFrame to get at the scrollable view that we're in.
327 nsIScrollableFrame *sf = do_QueryFrame(scrollFrame);
328 nsIFrame *scrolled = sf->GetScrolledFrame();
329 nsRect caretInScroll = *aRect + aFrame->GetOffsetTo(scrolled);
331 // Now see if thet caret extends beyond the view's bounds. If it does,
332 // then snap it back, put it as close to the edge as it can.
333 nscoord overflow = caretInScroll.XMost() -
334 scrolled->GetVisualOverflowRectRelativeToSelf().width;
335 if (overflow > 0)
336 aRect->x -= overflow;
337 }
339 if (aBidiIndicatorSize)
340 *aBidiIndicatorSize = caretMetrics.mBidiIndicatorSize;
342 return NS_OK;
343 }
345 nsIFrame* nsCaret::GetGeometry(nsISelection* aSelection, nsRect* aRect,
346 nscoord* aBidiIndicatorSize)
347 {
348 nsCOMPtr<nsIDOMNode> focusNode;
349 nsresult rv = aSelection->GetFocusNode(getter_AddRefs(focusNode));
350 if (NS_FAILED(rv) || !focusNode)
351 return nullptr;
353 int32_t focusOffset;
354 rv = aSelection->GetFocusOffset(&focusOffset);
355 if (NS_FAILED(rv))
356 return nullptr;
358 nsCOMPtr<nsIContent> contentNode = do_QueryInterface(focusNode);
359 if (!contentNode)
360 return nullptr;
362 nsRefPtr<nsFrameSelection> frameSelection = GetFrameSelection();
363 if (!frameSelection)
364 return nullptr;
365 uint8_t bidiLevel = frameSelection->GetCaretBidiLevel();
366 nsIFrame* frame;
367 int32_t frameOffset;
368 rv = GetCaretFrameForNodeOffset(contentNode, focusOffset,
369 frameSelection->GetHint(), bidiLevel,
370 &frame, &frameOffset);
371 if (NS_FAILED(rv) || !frame)
372 return nullptr;
374 GetGeometryForFrame(frame, frameOffset, aRect, aBidiIndicatorSize);
375 return frame;
376 }
378 void nsCaret::DrawCaretAfterBriefDelay()
379 {
380 // Make sure readonly caret gets drawn again if it needs to be
381 if (!mBlinkTimer) {
382 nsresult err;
383 mBlinkTimer = do_CreateInstance("@mozilla.org/timer;1", &err);
384 if (NS_FAILED(err))
385 return;
386 }
388 mBlinkTimer->InitWithFuncCallback(CaretBlinkCallback, this, 0,
389 nsITimer::TYPE_ONE_SHOT);
390 }
392 void nsCaret::EraseCaret()
393 {
394 if (mDrawn) {
395 DrawCaret(true);
396 if (mReadOnly && mBlinkRate) {
397 // If readonly we don't have a blink timer set, so caret won't
398 // be redrawn automatically. We need to force the caret to get
399 // redrawn right after the paint
400 DrawCaretAfterBriefDelay();
401 }
402 }
403 }
405 void nsCaret::SetVisibilityDuringSelection(bool aVisibility)
406 {
407 mShowDuringSelection = aVisibility;
408 }
410 static
411 nsFrameSelection::HINT GetHintForPosition(nsIDOMNode* aNode, int32_t aOffset)
412 {
413 nsFrameSelection::HINT hint = nsFrameSelection::HINTLEFT;
414 nsCOMPtr<nsIContent> node = do_QueryInterface(aNode);
415 if (!node || aOffset < 1) {
416 return hint;
417 }
418 const nsTextFragment* text = node->GetText();
419 if (text && text->CharAt(aOffset - 1) == '\n') {
420 // Attach the caret to the next line if needed
421 hint = nsFrameSelection::HINTRIGHT;
422 }
423 return hint;
424 }
426 nsresult nsCaret::DrawAtPosition(nsIDOMNode* aNode, int32_t aOffset)
427 {
428 NS_ENSURE_ARG(aNode);
430 uint8_t bidiLevel;
431 nsRefPtr<nsFrameSelection> frameSelection = GetFrameSelection();
432 if (!frameSelection)
433 return NS_ERROR_FAILURE;
434 bidiLevel = frameSelection->GetCaretBidiLevel();
436 // DrawAtPosition is used by consumers who want us to stay drawn where they
437 // tell us. Setting mBlinkRate to 0 tells us to not set a timer to erase
438 // ourselves, our consumer will take care of that.
439 mBlinkRate = 0;
441 nsresult rv = DrawAtPositionWithHint(aNode, aOffset,
442 GetHintForPosition(aNode, aOffset),
443 bidiLevel, true)
444 ? NS_OK : NS_ERROR_FAILURE;
445 ToggleDrawnStatus();
446 return rv;
447 }
449 nsIFrame * nsCaret::GetCaretFrame(int32_t *aOffset)
450 {
451 // Return null if we're not drawn to prevent anybody from trying to draw us.
452 if (!mDrawn)
453 return nullptr;
455 // Recompute the frame that we're supposed to draw in to guarantee that
456 // we're not going to try to draw into a stale (dead) frame.
457 int32_t offset;
458 nsIFrame *frame = nullptr;
459 nsresult rv = GetCaretFrameForNodeOffset(mLastContent, mLastContentOffset,
460 mLastHint, mLastBidiLevel, &frame,
461 &offset);
462 if (NS_FAILED(rv))
463 return nullptr;
465 if (aOffset) {
466 *aOffset = offset;
467 }
468 return frame;
469 }
471 void nsCaret::InvalidateOutsideCaret()
472 {
473 nsIFrame *frame = GetCaretFrame();
475 // Only invalidate if we are not fully contained by our frame's rect.
476 if (frame && !frame->GetVisualOverflowRect().Contains(GetCaretRect())) {
477 frame->SchedulePaint();
478 }
479 }
481 void nsCaret::UpdateCaretPosition()
482 {
483 // We'll recalculate anyway if we're not drawn right now.
484 if (!mDrawn)
485 return;
487 // A trick! Make the DrawCaret code recalculate the caret's current
488 // position.
489 mDrawn = false;
490 DrawCaret(false);
491 }
493 void nsCaret::PaintCaret(nsDisplayListBuilder *aBuilder,
494 nsRenderingContext *aCtx,
495 nsIFrame* aForFrame,
496 const nsPoint &aOffset)
497 {
498 NS_ASSERTION(mDrawn, "The caret shouldn't be drawing");
500 const nsRect drawCaretRect = mCaretRect + aOffset;
501 int32_t contentOffset;
503 #ifdef DEBUG
504 nsIFrame* frame =
505 #endif
506 GetCaretFrame(&contentOffset);
507 NS_ASSERTION(frame == aForFrame, "We're referring different frame");
508 // If the offset falls outside of the frame, then don't paint the caret.
509 int32_t startOffset, endOffset;
510 if (aForFrame->GetType() == nsGkAtoms::textFrame &&
511 (NS_FAILED(aForFrame->GetOffsets(startOffset, endOffset)) ||
512 startOffset > contentOffset ||
513 endOffset < contentOffset)) {
514 return;
515 }
516 nscolor foregroundColor = aForFrame->GetCaretColorAt(contentOffset);
518 aCtx->SetColor(foregroundColor);
519 aCtx->FillRect(drawCaretRect);
520 if (!GetHookRect().IsEmpty())
521 aCtx->FillRect(GetHookRect() + aOffset);
522 }
525 //-----------------------------------------------------------------------------
526 NS_IMETHODIMP nsCaret::NotifySelectionChanged(nsIDOMDocument *, nsISelection *aDomSel, int16_t aReason)
527 {
528 if (aReason & nsISelectionListener::MOUSEUP_REASON)//this wont do
529 return NS_OK;
531 nsCOMPtr<nsISelection> domSel(do_QueryReferent(mDomSelectionWeak));
533 // The same caret is shared amongst the document and any text widgets it
534 // may contain. This means that the caret could get notifications from
535 // multiple selections.
536 //
537 // If this notification is for a selection that is not the one the
538 // the caret is currently interested in (mDomSelectionWeak), then there
539 // is nothing to do!
541 if (domSel != aDomSel)
542 return NS_OK;
544 if (mVisible)
545 {
546 // Stop the caret from blinking in its previous location.
547 StopBlinking();
549 // Start the caret blinking in the new location.
550 StartBlinking();
551 }
553 return NS_OK;
554 }
557 //-----------------------------------------------------------------------------
558 void nsCaret::KillTimer()
559 {
560 if (mBlinkTimer)
561 {
562 mBlinkTimer->Cancel();
563 }
564 }
567 //-----------------------------------------------------------------------------
568 nsresult nsCaret::PrimeTimer()
569 {
570 // set up the blink timer
571 if (!mReadOnly && mBlinkRate > 0)
572 {
573 if (!mBlinkTimer) {
574 nsresult err;
575 mBlinkTimer = do_CreateInstance("@mozilla.org/timer;1", &err);
576 if (NS_FAILED(err))
577 return err;
578 }
580 mBlinkTimer->InitWithFuncCallback(CaretBlinkCallback, this, mBlinkRate,
581 nsITimer::TYPE_REPEATING_SLACK);
582 }
584 return NS_OK;
585 }
587 //-----------------------------------------------------------------------------
588 void nsCaret::StartBlinking()
589 {
590 if (mReadOnly) {
591 // Make sure the one draw command we use for a readonly caret isn't
592 // done until the selection is set
593 DrawCaretAfterBriefDelay();
594 return;
595 }
596 PrimeTimer();
598 // If we are currently drawn, then the second call to DrawCaret below will
599 // actually erase the caret. That would cause the caret to spend an "off"
600 // cycle before it appears, which is not really what we want. This first
601 // call to DrawCaret makes sure that the first cycle after a call to
602 // StartBlinking is an "on" cycle.
603 if (mDrawn)
604 DrawCaret(true);
606 DrawCaret(true); // draw it right away
607 }
610 //-----------------------------------------------------------------------------
611 void nsCaret::StopBlinking()
612 {
613 if (mDrawn) // erase the caret if necessary
614 DrawCaret(true);
616 NS_ASSERTION(!mDrawn, "Caret still drawn after StopBlinking().");
617 KillTimer();
618 }
620 bool
621 nsCaret::DrawAtPositionWithHint(nsIDOMNode* aNode,
622 int32_t aOffset,
623 nsFrameSelection::HINT aFrameHint,
624 uint8_t aBidiLevel,
625 bool aInvalidate)
626 {
627 nsCOMPtr<nsIContent> contentNode = do_QueryInterface(aNode);
628 if (!contentNode)
629 return false;
631 nsIFrame* theFrame = nullptr;
632 int32_t theFrameOffset = 0;
634 nsresult rv = GetCaretFrameForNodeOffset(contentNode, aOffset, aFrameHint, aBidiLevel,
635 &theFrame, &theFrameOffset);
636 if (NS_FAILED(rv) || !theFrame)
637 return false;
639 // now we have a frame, check whether it's appropriate to show the caret here
640 const nsStyleUserInterface* userinterface = theFrame->StyleUserInterface();
641 if ((!mIgnoreUserModify &&
642 userinterface->mUserModify == NS_STYLE_USER_MODIFY_READ_ONLY) ||
643 (userinterface->mUserInput == NS_STYLE_USER_INPUT_NONE) ||
644 (userinterface->mUserInput == NS_STYLE_USER_INPUT_DISABLED))
645 {
646 return false;
647 }
649 if (!mDrawn)
650 {
651 // save stuff so we can figure out what frame we're in later.
652 mLastContent = contentNode;
653 mLastContentOffset = aOffset;
654 mLastHint = aFrameHint;
655 mLastBidiLevel = aBidiLevel;
657 // If there has been a reflow, set the caret Bidi level to the level of the current frame
658 if (aBidiLevel & BIDI_LEVEL_UNDEFINED) {
659 nsRefPtr<nsFrameSelection> frameSelection = GetFrameSelection();
660 if (!frameSelection)
661 return false;
662 frameSelection->SetCaretBidiLevel(NS_GET_EMBEDDING_LEVEL(theFrame));
663 }
665 // Only update the caret's rect when we're not currently drawn.
666 if (!UpdateCaretRects(theFrame, theFrameOffset))
667 return false;
668 }
670 if (aInvalidate)
671 theFrame->SchedulePaint();
673 return true;
674 }
676 nsresult
677 nsCaret::GetCaretFrameForNodeOffset(nsIContent* aContentNode,
678 int32_t aOffset,
679 nsFrameSelection::HINT aFrameHint,
680 uint8_t aBidiLevel,
681 nsIFrame** aReturnFrame,
682 int32_t* aReturnOffset)
683 {
685 //get frame selection and find out what frame to use...
686 nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
687 if (!presShell)
688 return NS_ERROR_FAILURE;
690 if (!aContentNode || !aContentNode->IsInDoc() ||
691 presShell->GetDocument() != aContentNode->GetCurrentDoc())
692 return NS_ERROR_FAILURE;
694 nsRefPtr<nsFrameSelection> frameSelection = GetFrameSelection();
695 if (!frameSelection)
696 return NS_ERROR_FAILURE;
698 nsIFrame* theFrame = nullptr;
699 int32_t theFrameOffset = 0;
701 theFrame = frameSelection->GetFrameForNodeOffset(aContentNode, aOffset,
702 aFrameHint, &theFrameOffset);
703 if (!theFrame)
704 return NS_ERROR_FAILURE;
706 // if theFrame is after a text frame that's logically at the end of the line
707 // (e.g. if theFrame is a <br> frame), then put the caret at the end of
708 // that text frame instead. This way, the caret will be positioned as if
709 // trailing whitespace was not trimmed.
710 AdjustCaretFrameForLineEnd(&theFrame, &theFrameOffset);
712 // Mamdouh : modification of the caret to work at rtl and ltr with Bidi
713 //
714 // Direction Style from visibility->mDirection
715 // ------------------
716 // NS_STYLE_DIRECTION_LTR : LTR or Default
717 // NS_STYLE_DIRECTION_RTL
718 // NS_STYLE_DIRECTION_INHERIT
719 if (mBidiUI)
720 {
721 // If there has been a reflow, take the caret Bidi level to be the level of the current frame
722 if (aBidiLevel & BIDI_LEVEL_UNDEFINED)
723 aBidiLevel = NS_GET_EMBEDDING_LEVEL(theFrame);
725 int32_t start;
726 int32_t end;
727 nsIFrame* frameBefore;
728 nsIFrame* frameAfter;
729 uint8_t levelBefore; // Bidi level of the character before the caret
730 uint8_t levelAfter; // Bidi level of the character after the caret
732 theFrame->GetOffsets(start, end);
733 if (start == 0 || end == 0 || start == theFrameOffset || end == theFrameOffset)
734 {
735 nsPrevNextBidiLevels levels = frameSelection->
736 GetPrevNextBidiLevels(aContentNode, aOffset, false);
738 /* Boundary condition, we need to know the Bidi levels of the characters before and after the caret */
739 if (levels.mFrameBefore || levels.mFrameAfter)
740 {
741 frameBefore = levels.mFrameBefore;
742 frameAfter = levels.mFrameAfter;
743 levelBefore = levels.mLevelBefore;
744 levelAfter = levels.mLevelAfter;
746 if ((levelBefore != levelAfter) || (aBidiLevel != levelBefore))
747 {
748 aBidiLevel = std::max(aBidiLevel, std::min(levelBefore, levelAfter)); // rule c3
749 aBidiLevel = std::min(aBidiLevel, std::max(levelBefore, levelAfter)); // rule c4
750 if (aBidiLevel == levelBefore // rule c1
751 || (aBidiLevel > levelBefore && aBidiLevel < levelAfter && !((aBidiLevel ^ levelBefore) & 1)) // rule c5
752 || (aBidiLevel < levelBefore && aBidiLevel > levelAfter && !((aBidiLevel ^ levelBefore) & 1))) // rule c9
753 {
754 if (theFrame != frameBefore)
755 {
756 if (frameBefore) // if there is a frameBefore, move into it
757 {
758 theFrame = frameBefore;
759 theFrame->GetOffsets(start, end);
760 theFrameOffset = end;
761 }
762 else
763 {
764 // if there is no frameBefore, we must be at the beginning of the line
765 // so we stay with the current frame.
766 // Exception: when the first frame on the line has a different Bidi level from the paragraph level, there is no
767 // real frame for the caret to be in. We have to find the visually first frame on the line.
768 uint8_t baseLevel = NS_GET_BASE_LEVEL(frameAfter);
769 if (baseLevel != levelAfter)
770 {
771 nsPeekOffsetStruct pos(eSelectBeginLine, eDirPrevious, 0, 0, false, true, false, true);
772 if (NS_SUCCEEDED(frameAfter->PeekOffset(&pos))) {
773 theFrame = pos.mResultFrame;
774 theFrameOffset = pos.mContentOffset;
775 }
776 }
777 }
778 }
779 }
780 else if (aBidiLevel == levelAfter // rule c2
781 || (aBidiLevel > levelBefore && aBidiLevel < levelAfter && !((aBidiLevel ^ levelAfter) & 1)) // rule c6
782 || (aBidiLevel < levelBefore && aBidiLevel > levelAfter && !((aBidiLevel ^ levelAfter) & 1))) // rule c10
783 {
784 if (theFrame != frameAfter)
785 {
786 if (frameAfter)
787 {
788 // if there is a frameAfter, move into it
789 theFrame = frameAfter;
790 theFrame->GetOffsets(start, end);
791 theFrameOffset = start;
792 }
793 else
794 {
795 // if there is no frameAfter, we must be at the end of the line
796 // so we stay with the current frame.
797 // Exception: when the last frame on the line has a different Bidi level from the paragraph level, there is no
798 // real frame for the caret to be in. We have to find the visually last frame on the line.
799 uint8_t baseLevel = NS_GET_BASE_LEVEL(frameBefore);
800 if (baseLevel != levelBefore)
801 {
802 nsPeekOffsetStruct pos(eSelectEndLine, eDirNext, 0, 0, false, true, false, true);
803 if (NS_SUCCEEDED(frameBefore->PeekOffset(&pos))) {
804 theFrame = pos.mResultFrame;
805 theFrameOffset = pos.mContentOffset;
806 }
807 }
808 }
809 }
810 }
811 else if (aBidiLevel > levelBefore && aBidiLevel < levelAfter // rule c7/8
812 && !((levelBefore ^ levelAfter) & 1) // before and after have the same parity
813 && ((aBidiLevel ^ levelAfter) & 1)) // caret has different parity
814 {
815 if (NS_SUCCEEDED(frameSelection->GetFrameFromLevel(frameAfter, eDirNext, aBidiLevel, &theFrame)))
816 {
817 theFrame->GetOffsets(start, end);
818 levelAfter = NS_GET_EMBEDDING_LEVEL(theFrame);
819 if (aBidiLevel & 1) // c8: caret to the right of the rightmost character
820 theFrameOffset = (levelAfter & 1) ? start : end;
821 else // c7: caret to the left of the leftmost character
822 theFrameOffset = (levelAfter & 1) ? end : start;
823 }
824 }
825 else if (aBidiLevel < levelBefore && aBidiLevel > levelAfter // rule c11/12
826 && !((levelBefore ^ levelAfter) & 1) // before and after have the same parity
827 && ((aBidiLevel ^ levelAfter) & 1)) // caret has different parity
828 {
829 if (NS_SUCCEEDED(frameSelection->GetFrameFromLevel(frameBefore, eDirPrevious, aBidiLevel, &theFrame)))
830 {
831 theFrame->GetOffsets(start, end);
832 levelBefore = NS_GET_EMBEDDING_LEVEL(theFrame);
833 if (aBidiLevel & 1) // c12: caret to the left of the leftmost character
834 theFrameOffset = (levelBefore & 1) ? end : start;
835 else // c11: caret to the right of the rightmost character
836 theFrameOffset = (levelBefore & 1) ? start : end;
837 }
838 }
839 }
840 }
841 }
842 }
844 NS_ASSERTION(!theFrame || theFrame->PresContext()->PresShell() == presShell,
845 "caret frame is in wrong document");
846 *aReturnFrame = theFrame;
847 *aReturnOffset = theFrameOffset;
848 return NS_OK;
849 }
851 void
852 nsCaret::CheckCaretDrawingState()
853 {
854 if (mDrawn) {
855 // The caret is drawn; if it shouldn't be, erase it.
856 if (!mVisible || !MustDrawCaret(true))
857 EraseCaret();
858 }
859 else
860 {
861 // The caret is not drawn; if it should be, draw it.
862 if (mPendingDraw && (mVisible && MustDrawCaret(true)))
863 DrawCaret(true);
864 }
865 }
867 /*-----------------------------------------------------------------------------
869 MustDrawCaret
871 Find out if we need to do any caret drawing. This returns true if
872 either:
873 a) The caret has been drawn, and we need to erase it.
874 b) The caret is not drawn, and the selection is collapsed.
875 c) The caret is not hidden due to open XUL popups
876 (see IsMenuPopupHidingCaret()).
878 ----------------------------------------------------------------------------- */
879 bool nsCaret::MustDrawCaret(bool aIgnoreDrawnState)
880 {
881 if (!aIgnoreDrawnState && mDrawn)
882 return true;
884 nsCOMPtr<nsISelection> domSelection = do_QueryReferent(mDomSelectionWeak);
885 if (!domSelection)
886 return false;
888 bool isCollapsed;
889 if (NS_FAILED(domSelection->GetIsCollapsed(&isCollapsed)))
890 return false;
892 if (mShowDuringSelection)
893 return true; // show the caret even in selections
895 if (IsMenuPopupHidingCaret())
896 return false;
898 return isCollapsed;
899 }
901 bool nsCaret::IsMenuPopupHidingCaret()
902 {
903 #ifdef MOZ_XUL
904 // Check if there are open popups.
905 nsXULPopupManager *popMgr = nsXULPopupManager::GetInstance();
906 nsTArray<nsIFrame*> popups;
907 popMgr->GetVisiblePopups(popups);
909 if (popups.Length() == 0)
910 return false; // No popups, so caret can't be hidden by them.
912 // Get the selection focus content, that's where the caret would
913 // go if it was drawn.
914 nsCOMPtr<nsIDOMNode> node;
915 nsCOMPtr<nsISelection> domSelection = do_QueryReferent(mDomSelectionWeak);
916 if (!domSelection)
917 return true; // No selection/caret to draw.
918 domSelection->GetFocusNode(getter_AddRefs(node));
919 if (!node)
920 return true; // No selection/caret to draw.
921 nsCOMPtr<nsIContent> caretContent = do_QueryInterface(node);
922 if (!caretContent)
923 return true; // No selection/caret to draw.
925 // If there's a menu popup open before the popup with
926 // the caret, don't show the caret.
927 for (uint32_t i=0; i<popups.Length(); i++) {
928 nsMenuPopupFrame* popupFrame = static_cast<nsMenuPopupFrame*>(popups[i]);
929 nsIContent* popupContent = popupFrame->GetContent();
931 if (nsContentUtils::ContentIsDescendantOf(caretContent, popupContent)) {
932 // The caret is in this popup. There were no menu popups before this
933 // popup, so don't hide the caret.
934 return false;
935 }
937 if (popupFrame->PopupType() == ePopupTypeMenu && !popupFrame->IsContextMenu()) {
938 // This is an open menu popup. It does not contain the caret (else we'd
939 // have returned above). Even if the caret is in a subsequent popup,
940 // or another document/frame, it should be hidden.
941 return true;
942 }
943 }
944 #endif
946 // There are no open menu popups, no need to hide the caret.
947 return false;
948 }
950 void nsCaret::DrawCaret(bool aInvalidate)
951 {
952 // Do we need to draw the caret at all?
953 if (!MustDrawCaret(false))
954 return;
956 // Can we draw the caret now?
957 nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
958 NS_ENSURE_TRUE_VOID(presShell);
959 {
960 if (presShell->IsPaintingSuppressed())
961 {
962 if (!mDrawn)
963 mPendingDraw = true;
965 // PresShell::UnsuppressAndInvalidate() will call CheckCaretDrawingState()
966 // to get us drawn.
967 return;
968 }
969 }
971 nsCOMPtr<nsIDOMNode> node;
972 int32_t offset;
973 nsFrameSelection::HINT hint;
974 uint8_t bidiLevel;
976 if (!mDrawn)
977 {
978 nsCOMPtr<nsISelection> domSelection = do_QueryReferent(mDomSelectionWeak);
979 nsCOMPtr<nsISelectionPrivate> privateSelection(do_QueryInterface(domSelection));
980 if (!privateSelection) return;
982 bool isCollapsed = false;
983 domSelection->GetIsCollapsed(&isCollapsed);
984 if (!mShowDuringSelection && !isCollapsed)
985 return;
987 bool hintRight;
988 privateSelection->GetInterlinePosition(&hintRight);//translate hint.
989 hint = hintRight ? nsFrameSelection::HINTRIGHT : nsFrameSelection::HINTLEFT;
991 // get the node and offset, which is where we want the caret to draw
992 domSelection->GetFocusNode(getter_AddRefs(node));
993 if (!node)
994 return;
996 if (NS_FAILED(domSelection->GetFocusOffset(&offset)))
997 return;
999 nsRefPtr<nsFrameSelection> frameSelection = GetFrameSelection();
1000 if (!frameSelection)
1001 return;
1003 bidiLevel = frameSelection->GetCaretBidiLevel();
1004 mPendingDraw = false;
1005 }
1006 else
1007 {
1008 if (!mLastContent)
1009 {
1010 mDrawn = false;
1011 return;
1012 }
1013 if (!mLastContent->IsInDoc() ||
1014 presShell->GetDocument() != mLastContent->GetCurrentDoc())
1015 {
1016 mLastContent = nullptr;
1017 mDrawn = false;
1018 return;
1019 }
1020 node = do_QueryInterface(mLastContent);
1021 offset = mLastContentOffset;
1022 hint = mLastHint;
1023 bidiLevel = mLastBidiLevel;
1024 }
1026 DrawAtPositionWithHint(node, offset, hint, bidiLevel, aInvalidate);
1027 ToggleDrawnStatus();
1028 }
1030 bool
1031 nsCaret::UpdateCaretRects(nsIFrame* aFrame, int32_t aFrameOffset)
1032 {
1033 NS_ASSERTION(aFrame, "Should have a frame here");
1035 nscoord bidiIndicatorSize;
1036 nsresult rv =
1037 GetGeometryForFrame(aFrame, aFrameOffset, &mCaretRect, &bidiIndicatorSize);
1038 if (NS_FAILED(rv)) {
1039 return false;
1040 }
1042 // on RTL frames the right edge of mCaretRect must be equal to framePos
1043 const nsStyleVisibility* vis = aFrame->StyleVisibility();
1044 if (NS_STYLE_DIRECTION_RTL == vis->mDirection)
1045 mCaretRect.x -= mCaretRect.width;
1047 mHookRect.SetEmpty();
1049 // Simon -- make a hook to draw to the left or right of the caret to show keyboard language direction
1050 bool isCaretRTL = false;
1051 nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard();
1052 // if bidiKeyboard->IsLangRTL() fails, there is no way to tell the
1053 // keyboard direction, or the user has no right-to-left keyboard
1054 // installed, so we never draw the hook.
1055 if (bidiKeyboard && NS_SUCCEEDED(bidiKeyboard->IsLangRTL(&isCaretRTL)) &&
1056 mBidiUI) {
1057 if (isCaretRTL != mKeyboardRTL) {
1058 /* if the caret bidi level and the keyboard language direction are not in
1059 * synch, the keyboard language must have been changed by the
1060 * user, and if the caret is in a boundary condition (between left-to-right and
1061 * right-to-left characters) it may have to change position to
1062 * reflect the location in which the next character typed will
1063 * appear. We will call |SelectionLanguageChange| and exit
1064 * without drawing the caret in the old position.
1065 */
1066 mKeyboardRTL = isCaretRTL;
1067 nsCOMPtr<nsISelectionPrivate> domSelection = do_QueryReferent(mDomSelectionWeak);
1068 if (!domSelection ||
1069 NS_SUCCEEDED(domSelection->SelectionLanguageChange(mKeyboardRTL)))
1070 return false;
1071 }
1072 // If keyboard language is RTL, draw the hook on the left; if LTR, to the right
1073 // The height of the hook rectangle is the same as the width of the caret
1074 // rectangle.
1075 mHookRect.SetRect(mCaretRect.x + ((isCaretRTL) ?
1076 bidiIndicatorSize * -1 :
1077 mCaretRect.width),
1078 mCaretRect.y + bidiIndicatorSize,
1079 bidiIndicatorSize,
1080 mCaretRect.width);
1081 }
1082 return true;
1083 }
1085 //-----------------------------------------------------------------------------
1086 /* static */
1087 void nsCaret::CaretBlinkCallback(nsITimer *aTimer, void *aClosure)
1088 {
1089 nsCaret *theCaret = reinterpret_cast<nsCaret*>(aClosure);
1090 if (!theCaret) return;
1092 theCaret->DrawCaret(true);
1093 }
1096 //-----------------------------------------------------------------------------
1097 nsFrameSelection*
1098 nsCaret::GetFrameSelection()
1099 {
1100 nsCOMPtr<nsISelection> sel = do_QueryReferent(mDomSelectionWeak);
1101 if (!sel)
1102 return nullptr;
1104 return static_cast<dom::Selection*>(sel.get())->GetFrameSelection();
1105 }
1107 void
1108 nsCaret::SetIgnoreUserModify(bool aIgnoreUserModify)
1109 {
1110 if (!aIgnoreUserModify && mIgnoreUserModify && mDrawn) {
1111 // We're turning off mIgnoreUserModify. If the caret's drawn
1112 // in a read-only node we must erase it, else the next call
1113 // to DrawCaret() won't erase the old caret, due to the new
1114 // mIgnoreUserModify value.
1115 nsIFrame *frame = GetCaretFrame();
1116 if (frame) {
1117 const nsStyleUserInterface* userinterface = frame->StyleUserInterface();
1118 if (userinterface->mUserModify == NS_STYLE_USER_MODIFY_READ_ONLY) {
1119 StopBlinking();
1120 }
1121 }
1122 }
1123 mIgnoreUserModify = aIgnoreUserModify;
1124 }