diff --git a/layout/base/nsCaret.cpp b/layout/base/nsCaret.cpp index 920d6b596a2..fb81f8297dd 100644 --- a/layout/base/nsCaret.cpp +++ b/layout/base/nsCaret.cpp @@ -81,6 +81,86 @@ static const PRInt32 kMinBidiIndicatorPixels = 2; #include "nsContentUtils.h" #endif //IBMBIDI +/** + * Find the first frame in an in-order traversal of the frame subtree rooted + * at aFrame which is either a text frame logically at the end of a line, + * or which is aStopAtFrame. Return null if no such frame is found. We don't + * descend into the children of non-eLineParticipant frames. + */ +static nsIFrame* +CheckForTrailingTextFrameRecursive(nsIFrame* aFrame, nsIFrame* aStopAtFrame) +{ + if (aFrame == aStopAtFrame || + ((aFrame->GetType() == nsGkAtoms::textFrame && + (static_cast(aFrame))->IsAtEndOfLine()))) + return aFrame; + if (!aFrame->IsFrameOfType(nsIFrame::eLineParticipant)) + return nsnull; + + for (nsIFrame* f = aFrame->GetFirstChild(nsnull); f; f = f->GetNextSibling()) + { + nsIFrame* r = CheckForTrailingTextFrameRecursive(f, aStopAtFrame); + if (r) + return r; + } + return nsnull; +} + +static nsLineBox* +FindContainingLine(nsIFrame* aFrame) +{ + while (aFrame && aFrame->IsFrameOfType(nsIFrame::eLineParticipant)) + { + nsIFrame* parent = aFrame->GetParent(); + nsBlockFrame* blockParent = nsLayoutUtils::GetAsBlock(parent); + if (blockParent) + { + PRBool isValid; + nsBlockInFlowLineIterator iter(blockParent, aFrame, &isValid); + return isValid ? iter.GetLine().get() : nsnull; + } + aFrame = parent; + } + return nsnull; +} + +static void +AdjustCaretFrameForLineEnd(nsIFrame** aFrame, PRInt32* aOffset) +{ + nsLineBox* line = FindContainingLine(*aFrame); + if (!line) + return; + PRInt32 count = line->GetChildCount(); + for (nsIFrame* f = line->mFirstChild; count > 0; --count, f = f->GetNextSibling()) + { + nsIFrame* r = CheckForTrailingTextFrameRecursive(f, *aFrame); + if (r == *aFrame) + return; + if (r) + { + *aFrame = r; + NS_ASSERTION(r->GetType() == nsGkAtoms::textFrame, "Expected text frame"); + *aOffset = (static_cast(r))->GetContentEnd(); + return; + } + } +} + +static PRBool +FramesOnSameLineHaveZeroHeight(nsIFrame* aFrame) +{ + nsLineBox* line = FindContainingLine(aFrame); + if (!line) + return aFrame->GetRect().height == 0; + PRInt32 count = line->GetChildCount(); + for (nsIFrame* f = line->mFirstChild; count > 0; --count, f = f->GetNextSibling()) + { + if (f->GetRect().height != 0) + return PR_FALSE; + } + return PR_TRUE; +} + //----------------------------------------------------------------------------- nsCaret::nsCaret() @@ -277,7 +357,8 @@ void nsCaret::SetCaretReadOnly(PRBool inMakeReadonly) mReadOnly = inMakeReadonly; } -nsIFrame* nsCaret::GetGeometry(nsISelection* aSelection, nsRect* aRect) +nsIFrame* nsCaret::GetGeometry(nsISelection* aSelection, nsRect* aRect, + nscoord* aBidiIndicatorSize) { nsCOMPtr focusNode; nsresult rv = aSelection->GetFocusNode(getter_AddRefs(focusNode)); @@ -312,10 +393,47 @@ nsIFrame* nsCaret::GetGeometry(nsISelection* aSelection, nsRect* aRect) if (NS_FAILED(rv)) return nsnull; - // now add the frame offset to the view offset, and we're done nscoord height = theFrame->GetContentRect().height; - nscoord width = ComputeMetrics(theFrame, theFrameOffset, height).mCaretWidth; - *aRect = nsRect(framePos.x, 0, width, height); + if (height == 0) { + nsCOMPtr fm; + nsLayoutUtils::GetFontMetricsForFrame(theFrame, getter_AddRefs(fm)); + if (fm) { + nscoord ascent, descent; + fm->GetMaxAscent(ascent); + fm->GetMaxDescent(descent); + height = ascent + descent; + + // Place the caret on the baseline for inline frames, except when there is + // a frame on the line with non-zero height. XXXmats why the exception? -- + // I don't know but it seems to be necessary, see bug 503531. + if (theFrame->GetStyleDisplay()->IsInlineOutside() && + !FramesOnSameLineHaveZeroHeight(theFrame)) + framePos.y -= ascent; + } + } + Metrics caretMetrics = ComputeMetrics(theFrame, theFrameOffset, height); + *aRect = nsRect(framePos, nsSize(caretMetrics.mCaretWidth, height)); + + // Clamp the x-position to be within our scroll frame. If we don't, then it + // clips us, and we don't appear at all. See bug 335560. + nsIFrame *scrollFrame = + nsLayoutUtils::GetClosestFrameOfType(theFrame, nsGkAtoms::scrollFrame); + if (scrollFrame) { + // First, use the scrollFrame to get at the scrolled frame that we're in. + nsIScrollableFrame *sf = do_QueryFrame(scrollFrame); + nsIFrame *scrolled = sf->GetScrolledFrame(); + nsRect caretInScroll = *aRect + theFrame->GetOffsetTo(scrolled); + + // Now see if thet caret extends beyond the frame's bounds. If it does, + // then snap it back, put it as close to the edge as it can. + nscoord overflow = caretInScroll.XMost() - + scrolled->GetOverflowRectRelativeToSelf().width; + if (overflow > 0) + aRect->x -= overflow; + } + + if (aBidiIndicatorSize) + *aBidiIndicatorSize = caretMetrics.mBidiIndicatorSize; return theFrame; } @@ -605,71 +723,6 @@ nsCaret::DrawAtPositionWithHint(nsIDOMNode* aNode, return PR_TRUE; } -/** - * Find the first frame in an in-order traversal of the frame subtree rooted - * at aFrame which is either a text frame logically at the end of a line, - * or which is aStopAtFrame. Return null if no such frame is found. We don't - * descend into the children of non-eLineParticipant frames. - */ -static nsIFrame* -CheckForTrailingTextFrameRecursive(nsIFrame* aFrame, nsIFrame* aStopAtFrame) -{ - if (aFrame == aStopAtFrame || - ((aFrame->GetType() == nsGkAtoms::textFrame && - (static_cast(aFrame))->IsAtEndOfLine()))) - return aFrame; - if (!aFrame->IsFrameOfType(nsIFrame::eLineParticipant)) - return nsnull; - - for (nsIFrame* f = aFrame->GetFirstChild(nsnull); f; f = f->GetNextSibling()) - { - nsIFrame* r = CheckForTrailingTextFrameRecursive(f, aStopAtFrame); - if (r) - return r; - } - return nsnull; -} - -static nsLineBox* -FindContainingLine(nsIFrame* aFrame) -{ - while (aFrame && aFrame->IsFrameOfType(nsIFrame::eLineParticipant)) - { - nsIFrame* parent = aFrame->GetParent(); - nsBlockFrame* blockParent = nsLayoutUtils::GetAsBlock(parent); - if (blockParent) - { - PRBool isValid; - nsBlockInFlowLineIterator iter(blockParent, aFrame, &isValid); - return isValid ? iter.GetLine().get() : nsnull; - } - aFrame = parent; - } - return nsnull; -} - -static void -AdjustCaretFrameForLineEnd(nsIFrame** aFrame, PRInt32* aOffset) -{ - nsLineBox* line = FindContainingLine(*aFrame); - if (!line) - return; - PRInt32 count = line->GetChildCount(); - for (nsIFrame* f = line->mFirstChild; count > 0; --count, f = f->GetNextSibling()) - { - nsIFrame* r = CheckForTrailingTextFrameRecursive(f, *aFrame); - if (r == *aFrame) - return; - if (r) - { - *aFrame = r; - NS_ASSERTION(r->GetType() == nsGkAtoms::textFrame, "Expected text frame"); - *aOffset = (static_cast(r))->GetContentEnd(); - return; - } - } -} - nsresult nsCaret::GetCaretFrameForNodeOffset(nsIContent* aContentNode, PRInt32 aOffset, @@ -1020,103 +1073,24 @@ void nsCaret::DrawCaret(PRBool aInvalidate) ToggleDrawnStatus(); } -static PRBool -FramesOnSameLineHaveZeroHeight(nsIFrame* aFrame) -{ - nsLineBox* line = FindContainingLine(aFrame); - if (!line) - return aFrame->GetRect().height == 0; - PRInt32 count = line->GetChildCount(); - for (nsIFrame* f = line->mFirstChild; count > 0; --count, f = f->GetNextSibling()) - { - if (f->GetRect().height != 0) - return PR_FALSE; - } - return PR_TRUE; -} - nsresult nsCaret::UpdateCaretRects(nsIFrame* aFrame, PRInt32 aFrameOffset) { NS_ASSERTION(aFrame, "Should have a frame here"); - nsRect frameRect = aFrame->GetContentRect(); - frameRect.x = 0; - frameRect.y = 0; - - nsCOMPtr presShell = do_QueryReferent(mPresShell); - if (!presShell) return NS_ERROR_FAILURE; - - // If we got a zero-height frame we should figure out a height. We have to do - // this after we've got an RC. - if (frameRect.height == 0) - { - nsCOMPtr fm; - nsLayoutUtils::GetFontMetricsForFrame(aFrame, getter_AddRefs(fm)); - - if (fm) - { - nscoord ascent, descent; - fm->GetMaxAscent(ascent); - fm->GetMaxDescent(descent); - frameRect.height = ascent + descent; - - // Place the caret on the baseline for inline frames, except when there is - // a frame on the line with non-zero height. XXXmats why the exception? -- - // I don't know but it seems to be necessary, see bug 503531. - if (aFrame->GetStyleDisplay()->IsInlineOutside() && - !FramesOnSameLineHaveZeroHeight(aFrame)) - frameRect.y -= ascent; - } - } - - mCaretRect = frameRect; nsCOMPtr domSelection = do_QueryReferent(mDomSelectionWeak); - nsCOMPtr privateSelection = do_QueryInterface(domSelection); - - nsPoint framePos; - - // if cache in selection is available, apply it, else refresh it - nsresult rv = privateSelection->GetCachedFrameOffset(aFrame, aFrameOffset, - framePos); - if (NS_FAILED(rv)) - { - mCaretRect.Empty(); - return rv; - } - - mCaretRect += framePos; - Metrics metrics = ComputeMetrics(aFrame, aFrameOffset, mCaretRect.height); - mCaretRect.width = metrics.mCaretWidth; - - // Clamp our position to be within our scroll frame. If we don't, then it - // clips us, and we don't appear at all. See bug 335560. - nsIFrame *scrollFrame = - nsLayoutUtils::GetClosestFrameOfType(aFrame, nsGkAtoms::scrollFrame); - if (scrollFrame) - { - // First, use the scrollFrame to get at the scrolled frame that we're in. - nsIScrollableFrame *sf = do_QueryFrame(scrollFrame); - nsIFrame *scrolled = sf->GetScrolledFrame(); - nsRect caretInScroll = mCaretRect + aFrame->GetOffsetTo(scrolled); - - // Now see if thet caret extends beyond the frame's bounds. If it does, - // then snap it back, put it as close to the edge as it can. - nscoord overflow = caretInScroll.XMost() - - scrolled->GetOverflowRectRelativeToSelf().width; - if (overflow > 0) - mCaretRect.x -= overflow; - } + nscoord bidiIndicatorSize; + GetGeometry(domSelection, &mCaretRect, &bidiIndicatorSize); // on RTL frames the right edge of mCaretRect must be equal to framePos const nsStyleVisibility* vis = aFrame->GetStyleVisibility(); if (NS_STYLE_DIRECTION_RTL == vis->mDirection) mCaretRect.x -= mCaretRect.width; - return UpdateHookRect(presShell->GetPresContext(), metrics); + return UpdateHookRect(domSelection, bidiIndicatorSize); } -nsresult nsCaret::UpdateHookRect(nsPresContext* aPresContext, - const Metrics& aMetrics) +nsresult nsCaret::UpdateHookRect(nsISelection* aSelection, + nscoord aBidiIndicatorSize) { mHookRect.Empty(); @@ -1142,24 +1116,19 @@ nsresult nsCaret::UpdateHookRect(nsPresContext* aPresContext, * without drawing the caret in the old position. */ mKeyboardRTL = isCaretRTL; - nsCOMPtr domSelection = do_QueryReferent(mDomSelectionWeak); - if (domSelection) + if (NS_SUCCEEDED(aSelection->SelectionLanguageChange(mKeyboardRTL))) { - if (NS_SUCCEEDED(domSelection->SelectionLanguageChange(mKeyboardRTL))) - { - return NS_ERROR_FAILURE; - } + return NS_ERROR_FAILURE; } } // If keyboard language is RTL, draw the hook on the left; if LTR, to the right // The height of the hook rectangle is the same as the width of the caret // rectangle. - nscoord bidiIndicatorSize = aMetrics.mBidiIndicatorSize; mHookRect.SetRect(mCaretRect.x + ((isCaretRTL) ? - bidiIndicatorSize * -1 : + aBidiIndicatorSize * -1 : mCaretRect.width), - mCaretRect.y + bidiIndicatorSize, - bidiIndicatorSize, + mCaretRect.y + aBidiIndicatorSize, + aBidiIndicatorSize, mCaretRect.width); } #endif //IBMBIDI diff --git a/layout/base/nsCaret.h b/layout/base/nsCaret.h index 2675d8c2d55..376b8188abb 100644 --- a/layout/base/nsCaret.h +++ b/layout/base/nsCaret.h @@ -108,9 +108,12 @@ class nsCaret : public nsISelectionListener * the focus node/offset of aSelection (assuming it would be drawn, * i.e., disregarding blink status). The geometry is stored in aRect, * and we return the frame aRect is relative to. + * @param aRect must be non-null + * @param aBidiIndicatorSize if non-null, set to the bidi indicator size. */ virtual nsIFrame* GetGeometry(nsISelection* aSelection, - nsRect* aRect); + nsRect* aRect, + nscoord* aBidiIndicatorSize = nsnull); /** EraseCaret * this will erase the caret if its drawn and reset drawn status @@ -223,8 +226,8 @@ protected: void DrawCaret(PRBool aInvalidate); void DrawCaretAfterBriefDelay(); nsresult UpdateCaretRects(nsIFrame* aFrame, PRInt32 aFrameOffset); - nsresult UpdateHookRect(nsPresContext* aPresContext, - const Metrics& aMetrics); + nsresult UpdateHookRect(nsISelection* aSelection, + nscoord aBidiIndicatorSize); static void InvalidateRects(const nsRect &aRect, const nsRect &aHook, nsIFrame *aFrame); nsRect GetHookRect()