1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 /* the caret is the text cursor used, e.g., when editing */
8 
9 #include "nsCaret.h"
10 
11 #include <algorithm>
12 
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 "nsIBidiKeyboard.h"
37 #include "nsContentUtils.h"
38 
39 using namespace mozilla;
40 using namespace mozilla::dom;
41 using namespace mozilla::gfx;
42 
43 // The bidi indicator hangs off the caret to one side, to show which
44 // direction the typing is in. It needs to be at least 2x2 to avoid looking like
45 // an insignificant dot
46 static const int32_t kMinBidiIndicatorPixels = 2;
47 
48 // The default caret blinking rate (in ms of blinking interval)
49 static const uint32_t kDefaultCaretBlinkRate = 500;
50 
51 /**
52  * Find the first frame in an in-order traversal of the frame subtree rooted
53  * at aFrame which is either a text frame logically at the end of a line,
54  * or which is aStopAtFrame. Return null if no such frame is found. We don't
55  * descend into the children of non-eLineParticipant frames.
56  */
CheckForTrailingTextFrameRecursive(nsIFrame * aFrame,nsIFrame * aStopAtFrame)57 static nsIFrame* CheckForTrailingTextFrameRecursive(nsIFrame* aFrame,
58                                                     nsIFrame* aStopAtFrame) {
59   if (aFrame == aStopAtFrame ||
60       ((aFrame->IsTextFrame() &&
61         (static_cast<nsTextFrame*>(aFrame))->IsAtEndOfLine())))
62     return aFrame;
63   if (!aFrame->IsFrameOfType(nsIFrame::eLineParticipant)) return nullptr;
64 
65   for (nsIFrame* f : aFrame->PrincipalChildList()) {
66     nsIFrame* r = CheckForTrailingTextFrameRecursive(f, aStopAtFrame);
67     if (r) return r;
68   }
69   return nullptr;
70 }
71 
FindContainingLine(nsIFrame * aFrame)72 static nsLineBox* FindContainingLine(nsIFrame* aFrame) {
73   while (aFrame && aFrame->IsFrameOfType(nsIFrame::eLineParticipant)) {
74     nsIFrame* parent = aFrame->GetParent();
75     nsBlockFrame* blockParent = nsLayoutUtils::GetAsBlock(parent);
76     if (blockParent) {
77       bool isValid;
78       nsBlockInFlowLineIterator iter(blockParent, aFrame, &isValid);
79       return isValid ? iter.GetLine().get() : nullptr;
80     }
81     aFrame = parent;
82   }
83   return nullptr;
84 }
85 
AdjustCaretFrameForLineEnd(nsIFrame ** aFrame,int32_t * aOffset)86 static void AdjustCaretFrameForLineEnd(nsIFrame** aFrame, int32_t* aOffset) {
87   nsLineBox* line = FindContainingLine(*aFrame);
88   if (!line) return;
89   int32_t count = line->GetChildCount();
90   for (nsIFrame *f = line->mFirstChild; count > 0;
91        --count, f = f->GetNextSibling()) {
92     nsIFrame* r = CheckForTrailingTextFrameRecursive(f, *aFrame);
93     if (r == *aFrame) return;
94     if (r) {
95       *aFrame = r;
96       NS_ASSERTION(r->IsTextFrame(), "Expected text frame");
97       *aOffset = (static_cast<nsTextFrame*>(r))->GetContentEnd();
98       return;
99     }
100   }
101 }
102 
IsBidiUI()103 static bool IsBidiUI() { return Preferences::GetBool("bidi.browser.ui"); }
104 
nsCaret()105 nsCaret::nsCaret()
106     : mOverrideOffset(0),
107       mBlinkCount(-1),
108       mBlinkRate(0),
109       mHideCount(0),
110       mIsBlinkOn(false),
111       mVisible(false),
112       mReadOnly(false),
113       mShowDuringSelection(false),
114       mIgnoreUserModify(true) {}
115 
~nsCaret()116 nsCaret::~nsCaret() { StopBlinking(); }
117 
Init(nsIPresShell * inPresShell)118 nsresult nsCaret::Init(nsIPresShell* inPresShell) {
119   NS_ENSURE_ARG(inPresShell);
120 
121   mPresShell =
122       do_GetWeakReference(inPresShell);  // the presshell owns us, so no addref
123   NS_ASSERTION(mPresShell, "Hey, pres shell should support weak refs");
124 
125   mShowDuringSelection =
126       LookAndFeel::GetInt(LookAndFeel::eIntID_ShowCaretDuringSelection,
127                           mShowDuringSelection ? 1 : 0) != 0;
128 
129   // get the selection from the pres shell, and set ourselves up as a selection
130   // listener
131 
132   nsCOMPtr<nsISelectionController> selCon = do_QueryReferent(mPresShell);
133   if (!selCon) return NS_ERROR_FAILURE;
134 
135   nsCOMPtr<nsISelection> domSelection;
136   nsresult rv = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
137                                      getter_AddRefs(domSelection));
138   if (NS_FAILED(rv)) return rv;
139   if (!domSelection) return NS_ERROR_FAILURE;
140 
141   nsCOMPtr<nsISelectionPrivate> privateSelection =
142       do_QueryInterface(domSelection);
143   if (privateSelection) privateSelection->AddSelectionListener(this);
144   mDomSelectionWeak = do_GetWeakReference(domSelection);
145 
146   return NS_OK;
147 }
148 
DrawCJKCaret(nsIFrame * aFrame,int32_t aOffset)149 static bool DrawCJKCaret(nsIFrame* aFrame, int32_t aOffset) {
150   nsIContent* content = aFrame->GetContent();
151   const nsTextFragment* frag = content->GetText();
152   if (!frag) return false;
153   if (aOffset < 0 || uint32_t(aOffset) >= frag->GetLength()) return false;
154   char16_t ch = frag->CharAt(aOffset);
155   return 0x2e80 <= ch && ch <= 0xd7ff;
156 }
157 
ComputeMetrics(nsIFrame * aFrame,int32_t aOffset,nscoord aCaretHeight)158 nsCaret::Metrics nsCaret::ComputeMetrics(nsIFrame* aFrame, int32_t aOffset,
159                                          nscoord aCaretHeight) {
160   // Compute nominal sizes in appunits
161   nscoord caretWidth =
162       (aCaretHeight *
163        LookAndFeel::GetFloat(LookAndFeel::eFloatID_CaretAspectRatio, 0.0f)) +
164       nsPresContext::CSSPixelsToAppUnits(
165           LookAndFeel::GetInt(LookAndFeel::eIntID_CaretWidth, 1));
166 
167   if (DrawCJKCaret(aFrame, aOffset)) {
168     caretWidth += nsPresContext::CSSPixelsToAppUnits(1);
169   }
170   nscoord bidiIndicatorSize =
171       nsPresContext::CSSPixelsToAppUnits(kMinBidiIndicatorPixels);
172   bidiIndicatorSize = std::max(caretWidth, bidiIndicatorSize);
173 
174   // Round them to device pixels. Always round down, except that anything
175   // between 0 and 1 goes up to 1 so we don't let the caret disappear.
176   int32_t tpp = aFrame->PresContext()->AppUnitsPerDevPixel();
177   Metrics result;
178   result.mCaretWidth = NS_ROUND_BORDER_TO_PIXELS(caretWidth, tpp);
179   result.mBidiIndicatorSize = NS_ROUND_BORDER_TO_PIXELS(bidiIndicatorSize, tpp);
180   return result;
181 }
182 
Terminate()183 void nsCaret::Terminate() {
184   // this doesn't erase the caret if it's drawn. Should it? We might not have
185   // a good drawing environment during teardown.
186 
187   StopBlinking();
188   mBlinkTimer = nullptr;
189 
190   // unregiser ourselves as a selection listener
191   nsCOMPtr<nsISelection> domSelection = do_QueryReferent(mDomSelectionWeak);
192   nsCOMPtr<nsISelectionPrivate> privateSelection(
193       do_QueryInterface(domSelection));
194   if (privateSelection) privateSelection->RemoveSelectionListener(this);
195   mDomSelectionWeak = nullptr;
196   mPresShell = nullptr;
197 
198   mOverrideContent = nullptr;
199 }
200 
NS_IMPL_ISUPPORTS(nsCaret,nsISelectionListener)201 NS_IMPL_ISUPPORTS(nsCaret, nsISelectionListener)
202 
203 nsISelection* nsCaret::GetSelection() {
204   nsCOMPtr<nsISelection> sel(do_QueryReferent(mDomSelectionWeak));
205   return sel;
206 }
207 
SetSelection(nsISelection * aDOMSel)208 void nsCaret::SetSelection(nsISelection* aDOMSel) {
209   MOZ_ASSERT(aDOMSel);
210   mDomSelectionWeak =
211       do_GetWeakReference(aDOMSel);  // weak reference to pres shell
212   ResetBlinking();
213   SchedulePaint(aDOMSel);
214 }
215 
SetVisible(bool inMakeVisible)216 void nsCaret::SetVisible(bool inMakeVisible) {
217   mVisible = inMakeVisible;
218   mIgnoreUserModify = mVisible;
219   ResetBlinking();
220   SchedulePaint();
221 }
222 
AddForceHide()223 void nsCaret::AddForceHide() {
224   MOZ_ASSERT(mHideCount < UINT32_MAX);
225   if (++mHideCount > 1) {
226     return;
227   }
228   ResetBlinking();
229   SchedulePaint();
230 }
231 
RemoveForceHide()232 void nsCaret::RemoveForceHide() {
233   if (!mHideCount || --mHideCount) {
234     return;
235   }
236   ResetBlinking();
237   SchedulePaint();
238 }
239 
SetCaretReadOnly(bool inMakeReadonly)240 void nsCaret::SetCaretReadOnly(bool inMakeReadonly) {
241   mReadOnly = inMakeReadonly;
242   ResetBlinking();
243   SchedulePaint();
244 }
245 
GetGeometryForFrame(nsIFrame * aFrame,int32_t aFrameOffset,nscoord * aBidiIndicatorSize)246 /* static */ nsRect nsCaret::GetGeometryForFrame(nsIFrame* aFrame,
247                                                  int32_t aFrameOffset,
248                                                  nscoord* aBidiIndicatorSize) {
249   nsPoint framePos(0, 0);
250   nsRect rect;
251   nsresult rv = aFrame->GetPointFromOffset(aFrameOffset, &framePos);
252   if (NS_FAILED(rv)) {
253     if (aBidiIndicatorSize) {
254       *aBidiIndicatorSize = 0;
255     }
256     return rect;
257   }
258 
259   nsIFrame* frame = aFrame->GetContentInsertionFrame();
260   if (!frame) {
261     frame = aFrame;
262   }
263   NS_ASSERTION(!(frame->GetStateBits() & NS_FRAME_IN_REFLOW),
264                "We should not be in the middle of reflow");
265   nscoord baseline = frame->GetCaretBaseline();
266   nscoord ascent = 0, descent = 0;
267   RefPtr<nsFontMetrics> fm =
268       nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame);
269   NS_ASSERTION(fm, "We should be able to get the font metrics");
270   if (fm) {
271     ascent = fm->MaxAscent();
272     descent = fm->MaxDescent();
273   }
274   nscoord height = ascent + descent;
275   WritingMode wm = aFrame->GetWritingMode();
276   bool vertical = wm.IsVertical();
277   if (vertical) {
278     if (wm.IsLineInverted()) {
279       framePos.x = baseline - descent;
280     } else {
281       framePos.x = baseline - ascent;
282     }
283   } else {
284     framePos.y = baseline - ascent;
285   }
286   Metrics caretMetrics = ComputeMetrics(aFrame, aFrameOffset, height);
287 
288   nsTextFrame* textFrame = do_QueryFrame(aFrame);
289   if (textFrame) {
290     gfxTextRun* textRun =
291         textFrame->GetTextRun(nsTextFrame::TextRunType::eInflated);
292     if (textRun) {
293       // For "upstream" text where the textrun direction is reversed from the
294       // frame's inline-dir we want the caret to be painted before rather than
295       // after its nominal inline position, so we offset by its width.
296       bool textRunDirIsReverseOfFrame =
297           wm.IsInlineReversed() != textRun->IsInlineReversed();
298       // However, in sideways-lr mode we invert this behavior because this is
299       // the one writing mode where bidi-LTR corresponds to inline-reversed
300       // already, which reverses the desired caret placement behavior.
301       // Note that the following condition is equivalent to:
302       //   if ( (!textRun->IsSidewaysLeft() && textRunDirIsReverseOfFrame) ||
303       //        (textRun->IsSidewaysLeft()  && !textRunDirIsReverseOfFrame) )
304       if (textRunDirIsReverseOfFrame != textRun->IsSidewaysLeft()) {
305         int dir = wm.IsBidiLTR() ? -1 : 1;
306         if (vertical) {
307           framePos.y += dir * caretMetrics.mCaretWidth;
308         } else {
309           framePos.x += dir * caretMetrics.mCaretWidth;
310         }
311       }
312     }
313   }
314 
315   rect = nsRect(framePos, vertical ? nsSize(height, caretMetrics.mCaretWidth)
316                                    : nsSize(caretMetrics.mCaretWidth, height));
317 
318   // Clamp the inline-position to be within our scroll frame. If we don't, then
319   // it clips us, and we don't appear at all. See bug 335560.
320   nsIFrame* scrollFrame =
321       nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::Scroll);
322   if (scrollFrame) {
323     // First, use the scrollFrame to get at the scrollable view that we're in.
324     nsIScrollableFrame* sf = do_QueryFrame(scrollFrame);
325     nsIFrame* scrolled = sf->GetScrolledFrame();
326     nsRect caretInScroll = rect + aFrame->GetOffsetTo(scrolled);
327 
328     // Now see if the caret extends beyond the view's bounds. If it does,
329     // then snap it back, put it as close to the edge as it can.
330     if (vertical) {
331       nscoord overflow = caretInScroll.YMost() -
332                          scrolled->GetVisualOverflowRectRelativeToSelf().height;
333       if (overflow > 0) {
334         rect.y -= overflow;
335       }
336     } else {
337       nscoord overflow = caretInScroll.XMost() -
338                          scrolled->GetVisualOverflowRectRelativeToSelf().width;
339       if (overflow > 0) {
340         rect.x -= overflow;
341       }
342     }
343   }
344 
345   if (aBidiIndicatorSize) {
346     *aBidiIndicatorSize = caretMetrics.mBidiIndicatorSize;
347   }
348   return rect;
349 }
350 
GetFrameAndOffset(Selection * aSelection,nsINode * aOverrideNode,int32_t aOverrideOffset,int32_t * aFrameOffset)351 nsIFrame* nsCaret::GetFrameAndOffset(Selection* aSelection,
352                                      nsINode* aOverrideNode,
353                                      int32_t aOverrideOffset,
354                                      int32_t* aFrameOffset) {
355   nsINode* focusNode;
356   int32_t focusOffset;
357 
358   if (aOverrideNode) {
359     focusNode = aOverrideNode;
360     focusOffset = aOverrideOffset;
361   } else if (aSelection) {
362     focusNode = aSelection->GetFocusNode();
363     aSelection->GetFocusOffset(&focusOffset);
364   } else {
365     return nullptr;
366   }
367 
368   if (!focusNode || !focusNode->IsContent()) {
369     return nullptr;
370   }
371 
372   nsIContent* contentNode = focusNode->AsContent();
373   nsFrameSelection* frameSelection = aSelection->GetFrameSelection();
374   nsBidiLevel bidiLevel = frameSelection->GetCaretBidiLevel();
375   nsIFrame* frame;
376   nsresult rv = nsCaret::GetCaretFrameForNodeOffset(
377       frameSelection, contentNode, focusOffset, frameSelection->GetHint(),
378       bidiLevel, &frame, aFrameOffset);
379   if (NS_FAILED(rv) || !frame) {
380     return nullptr;
381   }
382 
383   return frame;
384 }
385 
GetGeometry(nsISelection * aSelection,nsRect * aRect)386 /* static */ nsIFrame* nsCaret::GetGeometry(nsISelection* aSelection,
387                                             nsRect* aRect) {
388   int32_t frameOffset;
389   Selection* selection = aSelection ? aSelection->AsSelection() : nullptr;
390   nsIFrame* frame = GetFrameAndOffset(selection, nullptr, 0, &frameOffset);
391   if (frame) {
392     *aRect = GetGeometryForFrame(frame, frameOffset, nullptr);
393   }
394   return frame;
395 }
396 
GetSelectionInternal()397 Selection* nsCaret::GetSelectionInternal() {
398   nsISelection* domSelection = GetSelection();
399   return domSelection ? domSelection->AsSelection() : nullptr;
400 }
401 
SchedulePaint(nsISelection * aSelection)402 void nsCaret::SchedulePaint(nsISelection* aSelection) {
403   Selection* selection;
404   if (aSelection) {
405     selection = aSelection->AsSelection();
406   } else {
407     selection = GetSelectionInternal();
408   }
409   nsINode* focusNode;
410   if (mOverrideContent) {
411     focusNode = mOverrideContent;
412   } else if (selection) {
413     focusNode = selection->GetFocusNode();
414   } else {
415     return;
416   }
417   if (!focusNode || !focusNode->IsContent()) {
418     return;
419   }
420   nsIFrame* f = focusNode->AsContent()->GetPrimaryFrame();
421   if (!f) {
422     return;
423   }
424   // This may not be the correct continuation frame, but that's OK since we're
425   // just scheduling a paint of the window (or popup).
426   f->SchedulePaint();
427 }
428 
SetVisibilityDuringSelection(bool aVisibility)429 void nsCaret::SetVisibilityDuringSelection(bool aVisibility) {
430   mShowDuringSelection = aVisibility;
431   SchedulePaint();
432 }
433 
SetCaretPosition(nsIDOMNode * aNode,int32_t aOffset)434 void nsCaret::SetCaretPosition(nsIDOMNode* aNode, int32_t aOffset) {
435   mOverrideContent = do_QueryInterface(aNode);
436   mOverrideOffset = aOffset;
437 
438   ResetBlinking();
439   SchedulePaint();
440 }
441 
CheckSelectionLanguageChange()442 void nsCaret::CheckSelectionLanguageChange() {
443   if (!IsBidiUI()) {
444     return;
445   }
446 
447   bool isKeyboardRTL = false;
448   nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard();
449   if (bidiKeyboard) {
450     bidiKeyboard->IsLangRTL(&isKeyboardRTL);
451   }
452   // Call SelectionLanguageChange on every paint. Mostly it will be a noop
453   // but it should be fast anyway. This guarantees we never paint the caret
454   // at the wrong place.
455   Selection* selection = GetSelectionInternal();
456   if (selection) {
457     selection->SelectionLanguageChange(isKeyboardRTL);
458   }
459 }
460 
GetPaintGeometry(nsRect * aRect)461 nsIFrame* nsCaret::GetPaintGeometry(nsRect* aRect) {
462   // Return null if we should not be visible.
463   if (!IsVisible() || !mIsBlinkOn) {
464     return nullptr;
465   }
466 
467   // Update selection language direction now so the new direction will be
468   // taken into account when computing the caret position below.
469   CheckSelectionLanguageChange();
470 
471   int32_t frameOffset;
472   nsIFrame* frame = GetFrameAndOffset(GetSelectionInternal(), mOverrideContent,
473                                       mOverrideOffset, &frameOffset);
474   if (!frame) {
475     return nullptr;
476   }
477 
478   // now we have a frame, check whether it's appropriate to show the caret here
479   const nsStyleUserInterface* userinterface = frame->StyleUserInterface();
480   if ((!mIgnoreUserModify &&
481        userinterface->mUserModify == StyleUserModify::ReadOnly) ||
482       frame->IsContentDisabled()) {
483     return nullptr;
484   }
485 
486   // If the offset falls outside of the frame, then don't paint the caret.
487   int32_t startOffset, endOffset;
488   if (frame->IsTextFrame() &&
489       (NS_FAILED(frame->GetOffsets(startOffset, endOffset)) ||
490        startOffset > frameOffset || endOffset < frameOffset)) {
491     return nullptr;
492   }
493 
494   nsRect caretRect;
495   nsRect hookRect;
496   ComputeCaretRects(frame, frameOffset, &caretRect, &hookRect);
497 
498   aRect->UnionRect(caretRect, hookRect);
499   return frame;
500 }
501 
GetFrame(int32_t * aContentOffset)502 nsIFrame* nsCaret::GetFrame(int32_t* aContentOffset) {
503   return GetFrameAndOffset(GetSelectionInternal(), mOverrideContent,
504                            mOverrideOffset, aContentOffset);
505 }
506 
PaintCaret(DrawTarget & aDrawTarget,nsIFrame * aForFrame,const nsPoint & aOffset)507 void nsCaret::PaintCaret(DrawTarget& aDrawTarget, nsIFrame* aForFrame,
508                          const nsPoint& aOffset) {
509   int32_t contentOffset;
510   nsIFrame* frame = GetFrame(&contentOffset);
511   if (!frame) {
512     return;
513   }
514   NS_ASSERTION(frame == aForFrame, "We're referring different frame");
515 
516   int32_t appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel();
517 
518   nsRect caretRect;
519   nsRect hookRect;
520   ComputeCaretRects(frame, contentOffset, &caretRect, &hookRect);
521 
522   Rect devPxCaretRect = NSRectToSnappedRect(caretRect + aOffset,
523                                             appUnitsPerDevPixel, aDrawTarget);
524   Rect devPxHookRect =
525       NSRectToSnappedRect(hookRect + aOffset, appUnitsPerDevPixel, aDrawTarget);
526   ColorPattern color(ToDeviceColor(frame->GetCaretColorAt(contentOffset)));
527 
528   aDrawTarget.FillRect(devPxCaretRect, color);
529   if (!hookRect.IsEmpty()) {
530     aDrawTarget.FillRect(devPxHookRect, color);
531   }
532 }
533 
534 NS_IMETHODIMP
NotifySelectionChanged(nsIDOMDocument *,nsISelection * aDomSel,int16_t aReason)535 nsCaret::NotifySelectionChanged(nsIDOMDocument*, nsISelection* aDomSel,
536                                 int16_t aReason) {
537   // Note that aDomSel, per the comment below may not be the same as our
538   // selection, but that's OK since if that is the case, it wouldn't have
539   // mattered what IsVisible() returns here, so we just opt for checking
540   // the selection later down below.
541   if ((aReason & nsISelectionListener::MOUSEUP_REASON) ||
542       !IsVisible(aDomSel))  // this wont do
543     return NS_OK;
544 
545   nsCOMPtr<nsISelection> domSel(do_QueryReferent(mDomSelectionWeak));
546 
547   // The same caret is shared amongst the document and any text widgets it
548   // may contain. This means that the caret could get notifications from
549   // multiple selections.
550   //
551   // If this notification is for a selection that is not the one the
552   // the caret is currently interested in (mDomSelectionWeak), then there
553   // is nothing to do!
554 
555   if (domSel != aDomSel) return NS_OK;
556 
557   ResetBlinking();
558   SchedulePaint(aDomSel);
559 
560   return NS_OK;
561 }
562 
ResetBlinking()563 void nsCaret::ResetBlinking() {
564   mIsBlinkOn = true;
565 
566   if (mReadOnly || !mVisible || mHideCount) {
567     StopBlinking();
568     return;
569   }
570 
571   uint32_t blinkRate = static_cast<uint32_t>(LookAndFeel::GetInt(
572       LookAndFeel::eIntID_CaretBlinkTime, kDefaultCaretBlinkRate));
573   if (mBlinkRate == blinkRate) {
574     // If the rate hasn't changed, then there is nothing to do.
575     return;
576   }
577   mBlinkRate = blinkRate;
578 
579   if (mBlinkTimer) {
580     mBlinkTimer->Cancel();
581   } else {
582     nsIEventTarget* target = nullptr;
583     if (nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell)) {
584       if (nsCOMPtr<nsIDocument> doc = presShell->GetDocument()) {
585         target = doc->EventTargetFor(TaskCategory::Other);
586       }
587     }
588 
589     mBlinkTimer = NS_NewTimer(target);
590     if (!mBlinkTimer) {
591       return;
592     }
593   }
594 
595   if (blinkRate > 0) {
596     mBlinkCount = Preferences::GetInt("ui.caretBlinkCount", -1);
597     mBlinkTimer->InitWithNamedFuncCallback(CaretBlinkCallback, this, blinkRate,
598                                            nsITimer::TYPE_REPEATING_SLACK,
599                                            "nsCaret::CaretBlinkCallback_timer");
600   }
601 }
602 
StopBlinking()603 void nsCaret::StopBlinking() {
604   if (mBlinkTimer) {
605     mBlinkTimer->Cancel();
606     mBlinkRate = 0;
607   }
608 }
609 
GetCaretFrameForNodeOffset(nsFrameSelection * aFrameSelection,nsIContent * aContentNode,int32_t aOffset,CaretAssociationHint aFrameHint,nsBidiLevel aBidiLevel,nsIFrame ** aReturnFrame,int32_t * aReturnOffset)610 nsresult nsCaret::GetCaretFrameForNodeOffset(
611     nsFrameSelection* aFrameSelection, nsIContent* aContentNode,
612     int32_t aOffset, CaretAssociationHint aFrameHint, nsBidiLevel aBidiLevel,
613     nsIFrame** aReturnFrame, int32_t* aReturnOffset) {
614   if (!aFrameSelection) return NS_ERROR_FAILURE;
615   nsIPresShell* presShell = aFrameSelection->GetShell();
616   if (!presShell) return NS_ERROR_FAILURE;
617 
618   if (!aContentNode || !aContentNode->IsInComposedDoc() ||
619       presShell->GetDocument() != aContentNode->GetComposedDoc())
620     return NS_ERROR_FAILURE;
621 
622   nsIFrame* theFrame = nullptr;
623   int32_t theFrameOffset = 0;
624 
625   theFrame = aFrameSelection->GetFrameForNodeOffset(
626       aContentNode, aOffset, aFrameHint, &theFrameOffset);
627   if (!theFrame) return NS_ERROR_FAILURE;
628 
629   // if theFrame is after a text frame that's logically at the end of the line
630   // (e.g. if theFrame is a <br> frame), then put the caret at the end of
631   // that text frame instead. This way, the caret will be positioned as if
632   // trailing whitespace was not trimmed.
633   AdjustCaretFrameForLineEnd(&theFrame, &theFrameOffset);
634 
635   // Mamdouh : modification of the caret to work at rtl and ltr with Bidi
636   //
637   // Direction Style from visibility->mDirection
638   // ------------------
639   // NS_STYLE_DIRECTION_LTR : LTR or Default
640   // NS_STYLE_DIRECTION_RTL
641   if (theFrame->PresContext()->BidiEnabled()) {
642     // If there has been a reflow, take the caret Bidi level to be the level of
643     // the current frame
644     if (aBidiLevel & BIDI_LEVEL_UNDEFINED) {
645       aBidiLevel = theFrame->GetEmbeddingLevel();
646     }
647 
648     int32_t start;
649     int32_t end;
650     nsIFrame* frameBefore;
651     nsIFrame* frameAfter;
652     nsBidiLevel levelBefore;  // Bidi level of the character before the caret
653     nsBidiLevel levelAfter;   // Bidi level of the character after the caret
654 
655     theFrame->GetOffsets(start, end);
656     if (start == 0 || end == 0 || start == theFrameOffset ||
657         end == theFrameOffset) {
658       nsPrevNextBidiLevels levels =
659           aFrameSelection->GetPrevNextBidiLevels(aContentNode, aOffset, false);
660 
661       /* Boundary condition, we need to know the Bidi levels of the characters
662        * before and after the caret */
663       if (levels.mFrameBefore || levels.mFrameAfter) {
664         frameBefore = levels.mFrameBefore;
665         frameAfter = levels.mFrameAfter;
666         levelBefore = levels.mLevelBefore;
667         levelAfter = levels.mLevelAfter;
668 
669         if ((levelBefore != levelAfter) || (aBidiLevel != levelBefore)) {
670           aBidiLevel = std::max(aBidiLevel,
671                                 std::min(levelBefore, levelAfter));  // rule c3
672           aBidiLevel = std::min(aBidiLevel,
673                                 std::max(levelBefore, levelAfter));  // rule c4
674           if (aBidiLevel == levelBefore                              // rule c1
675               || (aBidiLevel > levelBefore && aBidiLevel < levelAfter &&
676                   IS_SAME_DIRECTION(aBidiLevel, levelBefore))  // rule c5
677               || (aBidiLevel < levelBefore && aBidiLevel > levelAfter &&
678                   IS_SAME_DIRECTION(aBidiLevel, levelBefore)))  // rule c9
679           {
680             if (theFrame != frameBefore) {
681               if (frameBefore)  // if there is a frameBefore, move into it
682               {
683                 theFrame = frameBefore;
684                 theFrame->GetOffsets(start, end);
685                 theFrameOffset = end;
686               } else {
687                 // if there is no frameBefore, we must be at the beginning of
688                 // the line so we stay with the current frame. Exception: when
689                 // the first frame on the line has a different Bidi level from
690                 // the paragraph level, there is no real frame for the caret to
691                 // be in. We have to find the visually first frame on the line.
692                 nsBidiLevel baseLevel = frameAfter->GetBaseLevel();
693                 if (baseLevel != levelAfter) {
694                   nsPeekOffsetStruct pos(eSelectBeginLine, eDirPrevious, 0,
695                                          nsPoint(0, 0), false, true, false,
696                                          true, false);
697                   if (NS_SUCCEEDED(frameAfter->PeekOffset(&pos))) {
698                     theFrame = pos.mResultFrame;
699                     theFrameOffset = pos.mContentOffset;
700                   }
701                 }
702               }
703             }
704           } else if (aBidiLevel == levelAfter  // rule c2
705                      || (aBidiLevel > levelBefore && aBidiLevel < levelAfter &&
706                          IS_SAME_DIRECTION(aBidiLevel, levelAfter))  // rule c6
707                      ||
708                      (aBidiLevel < levelBefore && aBidiLevel > levelAfter &&
709                       IS_SAME_DIRECTION(aBidiLevel, levelAfter)))  // rule c10
710           {
711             if (theFrame != frameAfter) {
712               if (frameAfter) {
713                 // if there is a frameAfter, move into it
714                 theFrame = frameAfter;
715                 theFrame->GetOffsets(start, end);
716                 theFrameOffset = start;
717               } else {
718                 // if there is no frameAfter, we must be at the end of the line
719                 // so we stay with the current frame.
720                 // Exception: when the last frame on the line has a different
721                 // Bidi level from the paragraph level, there is no real frame
722                 // for the caret to be in. We have to find the visually last
723                 // frame on the line.
724                 nsBidiLevel baseLevel = frameBefore->GetBaseLevel();
725                 if (baseLevel != levelBefore) {
726                   nsPeekOffsetStruct pos(eSelectEndLine, eDirNext, 0,
727                                          nsPoint(0, 0), false, true, false,
728                                          true, false);
729                   if (NS_SUCCEEDED(frameBefore->PeekOffset(&pos))) {
730                     theFrame = pos.mResultFrame;
731                     theFrameOffset = pos.mContentOffset;
732                   }
733                 }
734               }
735             }
736           } else if (aBidiLevel > levelBefore &&
737                      aBidiLevel < levelAfter  // rule c7/8
738                      &&
739                      IS_SAME_DIRECTION(
740                          levelBefore,
741                          levelAfter)  // before and after have the same parity
742                      &&
743                      !IS_SAME_DIRECTION(
744                          aBidiLevel, levelAfter))  // caret has different parity
745           {
746             if (NS_SUCCEEDED(aFrameSelection->GetFrameFromLevel(
747                     frameAfter, eDirNext, aBidiLevel, &theFrame))) {
748               theFrame->GetOffsets(start, end);
749               levelAfter = theFrame->GetEmbeddingLevel();
750               if (IS_LEVEL_RTL(aBidiLevel))  // c8: caret to the right of the
751                                              // rightmost character
752                 theFrameOffset = IS_LEVEL_RTL(levelAfter) ? start : end;
753               else  // c7: caret to the left of the leftmost character
754                 theFrameOffset = IS_LEVEL_RTL(levelAfter) ? end : start;
755             }
756           } else if (aBidiLevel < levelBefore &&
757                      aBidiLevel > levelAfter  // rule c11/12
758                      &&
759                      IS_SAME_DIRECTION(
760                          levelBefore,
761                          levelAfter)  // before and after have the same parity
762                      &&
763                      !IS_SAME_DIRECTION(
764                          aBidiLevel, levelAfter))  // caret has different parity
765           {
766             if (NS_SUCCEEDED(aFrameSelection->GetFrameFromLevel(
767                     frameBefore, eDirPrevious, aBidiLevel, &theFrame))) {
768               theFrame->GetOffsets(start, end);
769               levelBefore = theFrame->GetEmbeddingLevel();
770               if (IS_LEVEL_RTL(aBidiLevel))  // c12: caret to the left of the
771                                              // 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;
775             }
776           }
777         }
778       }
779     }
780   }
781 
782   *aReturnFrame = theFrame;
783   *aReturnOffset = theFrameOffset;
784   return NS_OK;
785 }
786 
SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const787 size_t nsCaret::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
788   size_t total = aMallocSizeOf(this);
789   if (mPresShell) {
790     // We only want the size of the nsWeakReference object, not the PresShell
791     // (since we don't own the PresShell).
792     total += mPresShell->SizeOfOnlyThis(aMallocSizeOf);
793   }
794   if (mDomSelectionWeak) {
795     // We only want size of the nsWeakReference object, not the selection
796     // (again, we don't own the selection).
797     total += mDomSelectionWeak->SizeOfOnlyThis(aMallocSizeOf);
798   }
799   if (mBlinkTimer) {
800     total += mBlinkTimer->SizeOfIncludingThis(aMallocSizeOf);
801   }
802   return total;
803 }
804 
IsMenuPopupHidingCaret()805 bool nsCaret::IsMenuPopupHidingCaret() {
806 #ifdef MOZ_XUL
807   // Check if there are open popups.
808   nsXULPopupManager* popMgr = nsXULPopupManager::GetInstance();
809   nsTArray<nsIFrame*> popups;
810   popMgr->GetVisiblePopups(popups);
811 
812   if (popups.Length() == 0)
813     return false;  // No popups, so caret can't be hidden by them.
814 
815   // Get the selection focus content, that's where the caret would
816   // go if it was drawn.
817   nsCOMPtr<nsIDOMNode> node;
818   nsCOMPtr<nsISelection> domSelection = do_QueryReferent(mDomSelectionWeak);
819   if (!domSelection) return true;  // No selection/caret to draw.
820   domSelection->GetFocusNode(getter_AddRefs(node));
821   if (!node) return true;  // No selection/caret to draw.
822   nsCOMPtr<nsIContent> caretContent = do_QueryInterface(node);
823   if (!caretContent) return true;  // No selection/caret to draw.
824 
825   // If there's a menu popup open before the popup with
826   // the caret, don't show the caret.
827   for (uint32_t i = 0; i < popups.Length(); i++) {
828     nsMenuPopupFrame* popupFrame = static_cast<nsMenuPopupFrame*>(popups[i]);
829     nsIContent* popupContent = popupFrame->GetContent();
830 
831     if (nsContentUtils::ContentIsDescendantOf(caretContent, popupContent)) {
832       // The caret is in this popup. There were no menu popups before this
833       // popup, so don't hide the caret.
834       return false;
835     }
836 
837     if (popupFrame->PopupType() == ePopupTypeMenu &&
838         !popupFrame->IsContextMenu()) {
839       // This is an open menu popup. It does not contain the caret (else we'd
840       // have returned above). Even if the caret is in a subsequent popup,
841       // or another document/frame, it should be hidden.
842       return true;
843     }
844   }
845 #endif
846 
847   // There are no open menu popups, no need to hide the caret.
848   return false;
849 }
850 
ComputeCaretRects(nsIFrame * aFrame,int32_t aFrameOffset,nsRect * aCaretRect,nsRect * aHookRect)851 void nsCaret::ComputeCaretRects(nsIFrame* aFrame, int32_t aFrameOffset,
852                                 nsRect* aCaretRect, nsRect* aHookRect) {
853   NS_ASSERTION(aFrame, "Should have a frame here");
854 
855   WritingMode wm = aFrame->GetWritingMode();
856   bool isVertical = wm.IsVertical();
857 
858   nscoord bidiIndicatorSize;
859   *aCaretRect = GetGeometryForFrame(aFrame, aFrameOffset, &bidiIndicatorSize);
860 
861   // on RTL frames the right edge of mCaretRect must be equal to framePos
862   const nsStyleVisibility* vis = aFrame->StyleVisibility();
863   if (NS_STYLE_DIRECTION_RTL == vis->mDirection) {
864     if (isVertical) {
865       aCaretRect->y -= aCaretRect->height;
866     } else {
867       aCaretRect->x -= aCaretRect->width;
868     }
869   }
870 
871   // Simon -- make a hook to draw to the left or right of the caret to show
872   // keyboard language direction
873   aHookRect->SetEmpty();
874   if (!IsBidiUI()) {
875     return;
876   }
877 
878   bool isCaretRTL;
879   nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard();
880   // if bidiKeyboard->IsLangRTL() fails, there is no way to tell the
881   // keyboard direction, or the user has no right-to-left keyboard
882   // installed, so we never draw the hook.
883   if (bidiKeyboard && NS_SUCCEEDED(bidiKeyboard->IsLangRTL(&isCaretRTL))) {
884     // If keyboard language is RTL, draw the hook on the left; if LTR, to the
885     // right The height of the hook rectangle is the same as the width of the
886     // caret rectangle.
887     if (isVertical) {
888       bool isSidewaysLR = wm.IsVerticalLR() && !wm.IsLineInverted();
889       if (isSidewaysLR) {
890         aHookRect->SetRect(aCaretRect->x + bidiIndicatorSize,
891                            aCaretRect->y + (!isCaretRTL ? bidiIndicatorSize * -1
892                                                         : aCaretRect->height),
893                            aCaretRect->height, bidiIndicatorSize);
894       } else {
895         aHookRect->SetRect(aCaretRect->XMost() - bidiIndicatorSize,
896                            aCaretRect->y + (isCaretRTL ? bidiIndicatorSize * -1
897                                                        : aCaretRect->height),
898                            aCaretRect->height, bidiIndicatorSize);
899       }
900     } else {
901       aHookRect->SetRect(aCaretRect->x + (isCaretRTL ? bidiIndicatorSize * -1
902                                                      : aCaretRect->width),
903                          aCaretRect->y + bidiIndicatorSize, bidiIndicatorSize,
904                          aCaretRect->width);
905     }
906   }
907 }
908 
909 /* static */
CaretBlinkCallback(nsITimer * aTimer,void * aClosure)910 void nsCaret::CaretBlinkCallback(nsITimer* aTimer, void* aClosure) {
911   nsCaret* theCaret = reinterpret_cast<nsCaret*>(aClosure);
912   if (!theCaret) {
913     return;
914   }
915   theCaret->mIsBlinkOn = !theCaret->mIsBlinkOn;
916   theCaret->SchedulePaint();
917 
918   // mBlinkCount of -1 means blink count is not enabled.
919   if (theCaret->mBlinkCount == -1) {
920     return;
921   }
922 
923   // Track the blink count, but only at end of a blink cycle.
924   if (!theCaret->mIsBlinkOn) {
925     // If we exceeded the blink count, stop the timer.
926     if (--theCaret->mBlinkCount <= 0) {
927       theCaret->StopBlinking();
928     }
929   }
930 }
931 
SetIgnoreUserModify(bool aIgnoreUserModify)932 void nsCaret::SetIgnoreUserModify(bool aIgnoreUserModify) {
933   mIgnoreUserModify = aIgnoreUserModify;
934   SchedulePaint();
935 }
936