layout/base/nsCaret.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

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;
  1006   else
  1008     if (!mLastContent)
  1010       mDrawn = false;
  1011       return;
  1013     if (!mLastContent->IsInDoc() ||
  1014         presShell->GetDocument() != mLastContent->GetCurrentDoc())
  1016       mLastContent = nullptr;
  1017       mDrawn = false;
  1018       return;
  1020     node = do_QueryInterface(mLastContent);
  1021     offset = mLastContentOffset;
  1022     hint = mLastHint;
  1023     bidiLevel = mLastBidiLevel;
  1026   DrawAtPositionWithHint(node, offset, hint, bidiLevel, aInvalidate);
  1027   ToggleDrawnStatus();
  1030 bool
  1031 nsCaret::UpdateCaretRects(nsIFrame* aFrame, int32_t aFrameOffset)
  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;
  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;
  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);
  1082   return true;
  1085 //-----------------------------------------------------------------------------
  1086 /* static */
  1087 void nsCaret::CaretBlinkCallback(nsITimer *aTimer, void *aClosure)
  1089   nsCaret   *theCaret = reinterpret_cast<nsCaret*>(aClosure);
  1090   if (!theCaret) return;
  1092   theCaret->DrawCaret(true);
  1096 //-----------------------------------------------------------------------------
  1097 nsFrameSelection*
  1098 nsCaret::GetFrameSelection()
  1100   nsCOMPtr<nsISelection> sel = do_QueryReferent(mDomSelectionWeak);
  1101   if (!sel)
  1102     return nullptr;
  1104   return static_cast<dom::Selection*>(sel.get())->GetFrameSelection();
  1107 void
  1108 nsCaret::SetIgnoreUserModify(bool aIgnoreUserModify)
  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();
  1123   mIgnoreUserModify = aIgnoreUserModify;

mercurial