diff options
Diffstat (limited to 'layout/base/nsCaret.cpp')
-rw-r--r-- | layout/base/nsCaret.cpp | 968 |
1 files changed, 968 insertions, 0 deletions
diff --git a/layout/base/nsCaret.cpp b/layout/base/nsCaret.cpp new file mode 100644 index 000000000..2f08d156e --- /dev/null +++ b/layout/base/nsCaret.cpp @@ -0,0 +1,968 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sw=2 et tw=78: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* the caret is the text cursor used, e.g., when editing */ + +#include "nsCaret.h" + +#include <algorithm> + +#include "gfxUtils.h" +#include "mozilla/gfx/2D.h" +#include "nsCOMPtr.h" +#include "nsFontMetrics.h" +#include "nsITimer.h" +#include "nsFrameSelection.h" +#include "nsIFrame.h" +#include "nsIScrollableFrame.h" +#include "nsIDOMNode.h" +#include "nsISelection.h" +#include "nsISelectionPrivate.h" +#include "nsIContent.h" +#include "nsIPresShell.h" +#include "nsLayoutUtils.h" +#include "nsPresContext.h" +#include "nsBlockFrame.h" +#include "nsISelectionController.h" +#include "nsTextFrame.h" +#include "nsXULPopupManager.h" +#include "nsMenuPopupFrame.h" +#include "nsTextFragment.h" +#include "mozilla/Preferences.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/dom/Selection.h" +#include "nsIBidiKeyboard.h" +#include "nsContentUtils.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::gfx; + +// The bidi indicator hangs off the caret to one side, to show which +// direction the typing is in. It needs to be at least 2x2 to avoid looking like +// an insignificant dot +static const int32_t kMinBidiIndicatorPixels = 2; + +/** + * 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<nsTextFrame*>(aFrame))->IsAtEndOfLine()))) + return aFrame; + if (!aFrame->IsFrameOfType(nsIFrame::eLineParticipant)) + return nullptr; + + for (nsIFrame* f : aFrame->PrincipalChildList()) + { + nsIFrame* r = CheckForTrailingTextFrameRecursive(f, aStopAtFrame); + if (r) + return r; + } + return nullptr; +} + +static nsLineBox* +FindContainingLine(nsIFrame* aFrame) +{ + while (aFrame && aFrame->IsFrameOfType(nsIFrame::eLineParticipant)) + { + nsIFrame* parent = aFrame->GetParent(); + nsBlockFrame* blockParent = nsLayoutUtils::GetAsBlock(parent); + if (blockParent) + { + bool isValid; + nsBlockInFlowLineIterator iter(blockParent, aFrame, &isValid); + return isValid ? iter.GetLine().get() : nullptr; + } + aFrame = parent; + } + return nullptr; +} + +static void +AdjustCaretFrameForLineEnd(nsIFrame** aFrame, int32_t* aOffset) +{ + nsLineBox* line = FindContainingLine(*aFrame); + if (!line) + return; + int32_t 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<nsTextFrame*>(r))->GetContentEnd(); + return; + } + } +} + +static bool +IsBidiUI() +{ + return Preferences::GetBool("bidi.browser.ui"); +} + +nsCaret::nsCaret() +: mOverrideOffset(0) +, mBlinkCount(-1) +, mHideCount(0) +, mIsBlinkOn(false) +, mVisible(false) +, mReadOnly(false) +, mShowDuringSelection(false) +, mIgnoreUserModify(true) +{ +} + +nsCaret::~nsCaret() +{ + StopBlinking(); +} + +nsresult nsCaret::Init(nsIPresShell *inPresShell) +{ + NS_ENSURE_ARG(inPresShell); + + mPresShell = do_GetWeakReference(inPresShell); // the presshell owns us, so no addref + NS_ASSERTION(mPresShell, "Hey, pres shell should support weak refs"); + + mShowDuringSelection = + LookAndFeel::GetInt(LookAndFeel::eIntID_ShowCaretDuringSelection, + mShowDuringSelection ? 1 : 0) != 0; + + // get the selection from the pres shell, and set ourselves up as a selection + // listener + + nsCOMPtr<nsISelectionController> selCon = do_QueryReferent(mPresShell); + if (!selCon) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsISelection> domSelection; + nsresult rv = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(domSelection)); + if (NS_FAILED(rv)) + return rv; + if (!domSelection) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsISelectionPrivate> privateSelection = do_QueryInterface(domSelection); + if (privateSelection) + privateSelection->AddSelectionListener(this); + mDomSelectionWeak = do_GetWeakReference(domSelection); + + return NS_OK; +} + +static bool +DrawCJKCaret(nsIFrame* aFrame, int32_t aOffset) +{ + nsIContent* content = aFrame->GetContent(); + const nsTextFragment* frag = content->GetText(); + if (!frag) + return false; + if (aOffset < 0 || uint32_t(aOffset) >= frag->GetLength()) + return false; + char16_t ch = frag->CharAt(aOffset); + return 0x2e80 <= ch && ch <= 0xd7ff; +} + +nsCaret::Metrics +nsCaret::ComputeMetrics(nsIFrame* aFrame, int32_t aOffset, nscoord aCaretHeight) +{ + // Compute nominal sizes in appunits + nscoord caretWidth = + (aCaretHeight * LookAndFeel::GetFloat(LookAndFeel::eFloatID_CaretAspectRatio, 0.0f)) + + nsPresContext::CSSPixelsToAppUnits( + LookAndFeel::GetInt(LookAndFeel::eIntID_CaretWidth, 1)); + + if (DrawCJKCaret(aFrame, aOffset)) { + caretWidth += nsPresContext::CSSPixelsToAppUnits(1); + } + nscoord bidiIndicatorSize = nsPresContext::CSSPixelsToAppUnits(kMinBidiIndicatorPixels); + bidiIndicatorSize = std::max(caretWidth, bidiIndicatorSize); + + // Round them to device pixels. Always round down, except that anything + // between 0 and 1 goes up to 1 so we don't let the caret disappear. + int32_t tpp = aFrame->PresContext()->AppUnitsPerDevPixel(); + Metrics result; + result.mCaretWidth = NS_ROUND_BORDER_TO_PIXELS(caretWidth, tpp); + result.mBidiIndicatorSize = NS_ROUND_BORDER_TO_PIXELS(bidiIndicatorSize, tpp); + return result; +} + +void nsCaret::Terminate() +{ + // this doesn't erase the caret if it's drawn. Should it? We might not have + // a good drawing environment during teardown. + + StopBlinking(); + mBlinkTimer = nullptr; + + // unregiser ourselves as a selection listener + nsCOMPtr<nsISelection> domSelection = do_QueryReferent(mDomSelectionWeak); + nsCOMPtr<nsISelectionPrivate> privateSelection(do_QueryInterface(domSelection)); + if (privateSelection) + privateSelection->RemoveSelectionListener(this); + mDomSelectionWeak = nullptr; + mPresShell = nullptr; + + mOverrideContent = nullptr; +} + +NS_IMPL_ISUPPORTS(nsCaret, nsISelectionListener) + +nsISelection* nsCaret::GetSelection() +{ + nsCOMPtr<nsISelection> sel(do_QueryReferent(mDomSelectionWeak)); + return sel; +} + +void nsCaret::SetSelection(nsISelection *aDOMSel) +{ + MOZ_ASSERT(aDOMSel); + mDomSelectionWeak = do_GetWeakReference(aDOMSel); // weak reference to pres shell + ResetBlinking(); + SchedulePaint(); +} + +void nsCaret::SetVisible(bool inMakeVisible) +{ + mVisible = inMakeVisible; + mIgnoreUserModify = mVisible; + ResetBlinking(); + SchedulePaint(); +} + +bool nsCaret::IsVisible() +{ + if (!mVisible || mHideCount) { + return false; + } + + if (!mShowDuringSelection) { + Selection* selection = GetSelectionInternal(); + if (!selection) { + return false; + } + bool isCollapsed; + if (NS_FAILED(selection->GetIsCollapsed(&isCollapsed)) || !isCollapsed) { + return false; + } + } + + if (IsMenuPopupHidingCaret()) { + return false; + } + + return true; +} + +void nsCaret::AddForceHide() +{ + MOZ_ASSERT(mHideCount < UINT32_MAX); + if (++mHideCount > 1) { + return; + } + ResetBlinking(); + SchedulePaint(); +} + +void nsCaret::RemoveForceHide() +{ + if (!mHideCount || --mHideCount) { + return; + } + ResetBlinking(); + SchedulePaint(); +} + +void nsCaret::SetCaretReadOnly(bool inMakeReadonly) +{ + mReadOnly = inMakeReadonly; + ResetBlinking(); + SchedulePaint(); +} + +/* static */ nsRect +nsCaret::GetGeometryForFrame(nsIFrame* aFrame, + int32_t aFrameOffset, + nscoord* aBidiIndicatorSize) +{ + nsPoint framePos(0, 0); + nsRect rect; + nsresult rv = aFrame->GetPointFromOffset(aFrameOffset, &framePos); + if (NS_FAILED(rv)) { + if (aBidiIndicatorSize) { + *aBidiIndicatorSize = 0; + } + return rect; + } + + nsIFrame* frame = aFrame->GetContentInsertionFrame(); + if (!frame) { + frame = aFrame; + } + NS_ASSERTION(!(frame->GetStateBits() & NS_FRAME_IN_REFLOW), + "We should not be in the middle of reflow"); + nscoord baseline = frame->GetCaretBaseline(); + nscoord ascent = 0, descent = 0; + RefPtr<nsFontMetrics> fm = + nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame); + NS_ASSERTION(fm, "We should be able to get the font metrics"); + if (fm) { + ascent = fm->MaxAscent(); + descent = fm->MaxDescent(); + } + nscoord height = ascent + descent; + WritingMode wm = aFrame->GetWritingMode(); + bool vertical = wm.IsVertical(); + if (vertical) { + if (wm.IsLineInverted()) { + framePos.x = baseline - descent; + } else { + framePos.x = baseline - ascent; + } + } else { + framePos.y = baseline - ascent; + } + Metrics caretMetrics = ComputeMetrics(aFrame, aFrameOffset, height); + rect = nsRect(framePos, vertical ? nsSize(height, caretMetrics.mCaretWidth) : + nsSize(caretMetrics.mCaretWidth, height)); + + // Clamp the inline-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 scrollable view that we're in. + nsIScrollableFrame *sf = do_QueryFrame(scrollFrame); + nsIFrame *scrolled = sf->GetScrolledFrame(); + nsRect caretInScroll = rect + aFrame->GetOffsetTo(scrolled); + + // Now see if the caret extends beyond the view's bounds. If it does, + // then snap it back, put it as close to the edge as it can. + if (vertical) { + nscoord overflow = caretInScroll.YMost() - + scrolled->GetVisualOverflowRectRelativeToSelf().height; + if (overflow > 0) { + rect.y -= overflow; + } + } else { + nscoord overflow = caretInScroll.XMost() - + scrolled->GetVisualOverflowRectRelativeToSelf().width; + if (overflow > 0) { + rect.x -= overflow; + } + } + } + + if (aBidiIndicatorSize) { + *aBidiIndicatorSize = caretMetrics.mBidiIndicatorSize; + } + return rect; +} + +nsIFrame* +nsCaret::GetFrameAndOffset(Selection* aSelection, + nsINode* aOverrideNode, int32_t aOverrideOffset, + int32_t* aFrameOffset) +{ + nsINode* focusNode; + int32_t focusOffset; + + if (aOverrideNode) { + focusNode = aOverrideNode; + focusOffset = aOverrideOffset; + } else if (aSelection) { + focusNode = aSelection->GetFocusNode(); + aSelection->GetFocusOffset(&focusOffset); + } else { + return nullptr; + } + + if (!focusNode || !focusNode->IsContent()) { + return nullptr; + } + + nsIContent* contentNode = focusNode->AsContent(); + nsFrameSelection* frameSelection = aSelection->GetFrameSelection(); + nsBidiLevel bidiLevel = frameSelection->GetCaretBidiLevel(); + nsIFrame* frame; + nsresult rv = nsCaret::GetCaretFrameForNodeOffset( + frameSelection, contentNode, focusOffset, + frameSelection->GetHint(), bidiLevel, &frame, aFrameOffset); + if (NS_FAILED(rv) || !frame) { + return nullptr; + } + + return frame; +} + +/* static */ nsIFrame* +nsCaret::GetGeometry(nsISelection* aSelection, nsRect* aRect) +{ + int32_t frameOffset; + Selection* selection = aSelection ? aSelection->AsSelection() : nullptr; + nsIFrame* frame = GetFrameAndOffset(selection, nullptr, 0, &frameOffset); + if (frame) { + *aRect = GetGeometryForFrame(frame, frameOffset, nullptr); + } + return frame; +} + +Selection* +nsCaret::GetSelectionInternal() +{ + nsISelection* domSelection = GetSelection(); + return domSelection ? domSelection->AsSelection() : nullptr; +} + +void nsCaret::SchedulePaint() +{ + Selection* selection = GetSelectionInternal(); + nsINode* focusNode; + if (mOverrideContent) { + focusNode = mOverrideContent; + } else if (selection) { + focusNode = selection->GetFocusNode(); + } else { + return; + } + if (!focusNode || !focusNode->IsContent()) { + return; + } + nsIFrame* f = focusNode->AsContent()->GetPrimaryFrame(); + if (!f) { + return; + } + // This may not be the correct continuation frame, but that's OK since we're + // just scheduling a paint of the window (or popup). + f->SchedulePaint(); +} + +void nsCaret::SetVisibilityDuringSelection(bool aVisibility) +{ + mShowDuringSelection = aVisibility; + SchedulePaint(); +} + +void +nsCaret::SetCaretPosition(nsIDOMNode* aNode, int32_t aOffset) +{ + mOverrideContent = do_QueryInterface(aNode); + mOverrideOffset = aOffset; + + ResetBlinking(); + SchedulePaint(); +} + +void +nsCaret::CheckSelectionLanguageChange() +{ + if (!IsBidiUI()) { + return; + } + + bool isKeyboardRTL = false; + nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard(); + if (bidiKeyboard) { + bidiKeyboard->IsLangRTL(&isKeyboardRTL); + } + // Call SelectionLanguageChange on every paint. Mostly it will be a noop + // but it should be fast anyway. This guarantees we never paint the caret + // at the wrong place. + Selection* selection = GetSelectionInternal(); + if (selection) { + selection->SelectionLanguageChange(isKeyboardRTL); + } +} + +nsIFrame* +nsCaret::GetPaintGeometry(nsRect* aRect) +{ + // Return null if we should not be visible. + if (!IsVisible() || !mIsBlinkOn) { + return nullptr; + } + + // Update selection language direction now so the new direction will be + // taken into account when computing the caret position below. + CheckSelectionLanguageChange(); + + int32_t frameOffset; + nsIFrame *frame = GetFrameAndOffset(GetSelectionInternal(), + mOverrideContent, mOverrideOffset, &frameOffset); + if (!frame) { + return nullptr; + } + + // now we have a frame, check whether it's appropriate to show the caret here + const nsStyleUserInterface* userinterface = frame->StyleUserInterface(); + if ((!mIgnoreUserModify && + userinterface->mUserModify == StyleUserModify::ReadOnly) || + userinterface->mUserInput == StyleUserInput::None || + userinterface->mUserInput == StyleUserInput::Disabled) { + return nullptr; + } + + // If the offset falls outside of the frame, then don't paint the caret. + int32_t startOffset, endOffset; + if (frame->GetType() == nsGkAtoms::textFrame && + (NS_FAILED(frame->GetOffsets(startOffset, endOffset)) || + startOffset > frameOffset || + endOffset < frameOffset)) { + return nullptr; + } + + nsRect caretRect; + nsRect hookRect; + ComputeCaretRects(frame, frameOffset, &caretRect, &hookRect); + + aRect->UnionRect(caretRect, hookRect); + return frame; +} + +void nsCaret::PaintCaret(DrawTarget& aDrawTarget, + nsIFrame* aForFrame, + const nsPoint &aOffset) +{ + int32_t contentOffset; + nsIFrame* frame = GetFrameAndOffset(GetSelectionInternal(), + mOverrideContent, mOverrideOffset, &contentOffset); + if (!frame) { + return; + } + NS_ASSERTION(frame == aForFrame, "We're referring different frame"); + + int32_t appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel(); + + nsRect caretRect; + nsRect hookRect; + ComputeCaretRects(frame, contentOffset, &caretRect, &hookRect); + + Rect devPxCaretRect = + NSRectToSnappedRect(caretRect + aOffset, appUnitsPerDevPixel, aDrawTarget); + Rect devPxHookRect = + NSRectToSnappedRect(hookRect + aOffset, appUnitsPerDevPixel, aDrawTarget); + ColorPattern color(ToDeviceColor(frame->GetCaretColorAt(contentOffset))); + + aDrawTarget.FillRect(devPxCaretRect, color); + if (!hookRect.IsEmpty()) { + aDrawTarget.FillRect(devPxHookRect, color); + } +} + +NS_IMETHODIMP +nsCaret::NotifySelectionChanged(nsIDOMDocument *, nsISelection *aDomSel, + int16_t aReason) +{ + if ((aReason & nsISelectionListener::MOUSEUP_REASON) || !IsVisible())//this wont do + return NS_OK; + + nsCOMPtr<nsISelection> domSel(do_QueryReferent(mDomSelectionWeak)); + + // The same caret is shared amongst the document and any text widgets it + // may contain. This means that the caret could get notifications from + // multiple selections. + // + // If this notification is for a selection that is not the one the + // the caret is currently interested in (mDomSelectionWeak), then there + // is nothing to do! + + if (domSel != aDomSel) + return NS_OK; + + ResetBlinking(); + SchedulePaint(); + + return NS_OK; +} + +void nsCaret::ResetBlinking() +{ + mIsBlinkOn = true; + + if (mReadOnly || !mVisible || mHideCount) { + StopBlinking(); + return; + } + + if (mBlinkTimer) { + mBlinkTimer->Cancel(); + } else { + nsresult err; + mBlinkTimer = do_CreateInstance("@mozilla.org/timer;1", &err); + if (NS_FAILED(err)) + return; + } + + uint32_t blinkRate = static_cast<uint32_t>( + LookAndFeel::GetInt(LookAndFeel::eIntID_CaretBlinkTime, 500)); + if (blinkRate > 0) { + mBlinkCount = Preferences::GetInt("ui.caretBlinkCount", -1); + mBlinkTimer->InitWithFuncCallback(CaretBlinkCallback, this, blinkRate, + nsITimer::TYPE_REPEATING_SLACK); + } +} + +void nsCaret::StopBlinking() +{ + if (mBlinkTimer) + { + mBlinkTimer->Cancel(); + } +} + +nsresult +nsCaret::GetCaretFrameForNodeOffset(nsFrameSelection* aFrameSelection, + nsIContent* aContentNode, + int32_t aOffset, + CaretAssociationHint aFrameHint, + nsBidiLevel aBidiLevel, + nsIFrame** aReturnFrame, + int32_t* aReturnOffset) +{ + if (!aFrameSelection) + return NS_ERROR_FAILURE; + nsIPresShell* presShell = aFrameSelection->GetShell(); + if (!presShell) + return NS_ERROR_FAILURE; + + if (!aContentNode || !aContentNode->IsInComposedDoc() || + presShell->GetDocument() != aContentNode->GetComposedDoc()) + return NS_ERROR_FAILURE; + + nsIFrame* theFrame = nullptr; + int32_t theFrameOffset = 0; + + theFrame = aFrameSelection->GetFrameForNodeOffset( + aContentNode, aOffset, aFrameHint, &theFrameOffset); + if (!theFrame) + return NS_ERROR_FAILURE; + + // if theFrame is after a text frame that's logically at the end of the line + // (e.g. if theFrame is a <br> frame), then put the caret at the end of + // that text frame instead. This way, the caret will be positioned as if + // trailing whitespace was not trimmed. + AdjustCaretFrameForLineEnd(&theFrame, &theFrameOffset); + + // Mamdouh : modification of the caret to work at rtl and ltr with Bidi + // + // Direction Style from visibility->mDirection + // ------------------ + // NS_STYLE_DIRECTION_LTR : LTR or Default + // NS_STYLE_DIRECTION_RTL + if (theFrame->PresContext()->BidiEnabled()) + { + // If there has been a reflow, take the caret Bidi level to be the level of the current frame + if (aBidiLevel & BIDI_LEVEL_UNDEFINED) { + aBidiLevel = theFrame->GetEmbeddingLevel(); + } + + int32_t start; + int32_t end; + nsIFrame* frameBefore; + nsIFrame* frameAfter; + nsBidiLevel levelBefore; // Bidi level of the character before the caret + nsBidiLevel levelAfter; // Bidi level of the character after the caret + + theFrame->GetOffsets(start, end); + if (start == 0 || end == 0 || start == theFrameOffset || end == theFrameOffset) + { + nsPrevNextBidiLevels levels = aFrameSelection-> + GetPrevNextBidiLevels(aContentNode, aOffset, false); + + /* Boundary condition, we need to know the Bidi levels of the characters before and after the caret */ + if (levels.mFrameBefore || levels.mFrameAfter) + { + frameBefore = levels.mFrameBefore; + frameAfter = levels.mFrameAfter; + levelBefore = levels.mLevelBefore; + levelAfter = levels.mLevelAfter; + + if ((levelBefore != levelAfter) || (aBidiLevel != levelBefore)) + { + aBidiLevel = std::max(aBidiLevel, std::min(levelBefore, levelAfter)); // rule c3 + aBidiLevel = std::min(aBidiLevel, std::max(levelBefore, levelAfter)); // rule c4 + if (aBidiLevel == levelBefore // rule c1 + || (aBidiLevel > levelBefore && aBidiLevel < levelAfter && + IS_SAME_DIRECTION(aBidiLevel, levelBefore)) // rule c5 + || (aBidiLevel < levelBefore && aBidiLevel > levelAfter && + IS_SAME_DIRECTION(aBidiLevel, levelBefore))) // rule c9 + { + if (theFrame != frameBefore) + { + if (frameBefore) // if there is a frameBefore, move into it + { + theFrame = frameBefore; + theFrame->GetOffsets(start, end); + theFrameOffset = end; + } + else + { + // if there is no frameBefore, we must be at the beginning of the line + // so we stay with the current frame. + // Exception: when the first frame on the line has a different Bidi level from the paragraph level, there is no + // real frame for the caret to be in. We have to find the visually first frame on the line. + nsBidiLevel baseLevel = frameAfter->GetBaseLevel(); + if (baseLevel != levelAfter) + { + nsPeekOffsetStruct pos(eSelectBeginLine, eDirPrevious, 0, + nsPoint(0, 0), false, true, false, + true, false); + if (NS_SUCCEEDED(frameAfter->PeekOffset(&pos))) { + theFrame = pos.mResultFrame; + theFrameOffset = pos.mContentOffset; + } + } + } + } + } + else if (aBidiLevel == levelAfter // rule c2 + || (aBidiLevel > levelBefore && aBidiLevel < levelAfter && + IS_SAME_DIRECTION(aBidiLevel, levelAfter)) // rule c6 + || (aBidiLevel < levelBefore && aBidiLevel > levelAfter && + IS_SAME_DIRECTION(aBidiLevel, levelAfter))) // rule c10 + { + if (theFrame != frameAfter) + { + if (frameAfter) + { + // if there is a frameAfter, move into it + theFrame = frameAfter; + theFrame->GetOffsets(start, end); + theFrameOffset = start; + } + else + { + // if there is no frameAfter, we must be at the end of the line + // so we stay with the current frame. + // Exception: when the last frame on the line has a different Bidi level from the paragraph level, there is no + // real frame for the caret to be in. We have to find the visually last frame on the line. + nsBidiLevel baseLevel = frameBefore->GetBaseLevel(); + if (baseLevel != levelBefore) + { + nsPeekOffsetStruct pos(eSelectEndLine, eDirNext, 0, + nsPoint(0, 0), false, true, false, + true, false); + if (NS_SUCCEEDED(frameBefore->PeekOffset(&pos))) { + theFrame = pos.mResultFrame; + theFrameOffset = pos.mContentOffset; + } + } + } + } + } + else if (aBidiLevel > levelBefore && aBidiLevel < levelAfter // rule c7/8 + && IS_SAME_DIRECTION(levelBefore, levelAfter) // before and after have the same parity + && !IS_SAME_DIRECTION(aBidiLevel, levelAfter)) // caret has different parity + { + if (NS_SUCCEEDED(aFrameSelection->GetFrameFromLevel(frameAfter, eDirNext, aBidiLevel, &theFrame))) + { + theFrame->GetOffsets(start, end); + levelAfter = theFrame->GetEmbeddingLevel(); + if (IS_LEVEL_RTL(aBidiLevel)) // c8: caret to the right of the rightmost character + theFrameOffset = IS_LEVEL_RTL(levelAfter) ? start : end; + else // c7: caret to the left of the leftmost character + theFrameOffset = IS_LEVEL_RTL(levelAfter) ? end : start; + } + } + else if (aBidiLevel < levelBefore && aBidiLevel > levelAfter // rule c11/12 + && IS_SAME_DIRECTION(levelBefore, levelAfter) // before and after have the same parity + && !IS_SAME_DIRECTION(aBidiLevel, levelAfter)) // caret has different parity + { + if (NS_SUCCEEDED(aFrameSelection->GetFrameFromLevel(frameBefore, eDirPrevious, aBidiLevel, &theFrame))) + { + theFrame->GetOffsets(start, end); + levelBefore = theFrame->GetEmbeddingLevel(); + if (IS_LEVEL_RTL(aBidiLevel)) // c12: caret to the left of the leftmost character + theFrameOffset = IS_LEVEL_RTL(levelBefore) ? end : start; + else // c11: caret to the right of the rightmost character + theFrameOffset = IS_LEVEL_RTL(levelBefore) ? start : end; + } + } + } + } + } + } + + *aReturnFrame = theFrame; + *aReturnOffset = theFrameOffset; + return NS_OK; +} + +size_t nsCaret::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const +{ + size_t total = aMallocSizeOf(this); + if (mPresShell) { + // We only want the size of the nsWeakReference object, not the PresShell + // (since we don't own the PresShell). + total += mPresShell->SizeOfOnlyThis(aMallocSizeOf); + } + if (mDomSelectionWeak) { + // We only want size of the nsWeakReference object, not the selection + // (again, we don't own the selection). + total += mDomSelectionWeak->SizeOfOnlyThis(aMallocSizeOf); + } + if (mBlinkTimer) { + total += mBlinkTimer->SizeOfIncludingThis(aMallocSizeOf); + } + return total; +} + +bool nsCaret::IsMenuPopupHidingCaret() +{ +#ifdef MOZ_XUL + // Check if there are open popups. + nsXULPopupManager *popMgr = nsXULPopupManager::GetInstance(); + nsTArray<nsIFrame*> popups; + popMgr->GetVisiblePopups(popups); + + if (popups.Length() == 0) + return false; // No popups, so caret can't be hidden by them. + + // Get the selection focus content, that's where the caret would + // go if it was drawn. + nsCOMPtr<nsIDOMNode> node; + nsCOMPtr<nsISelection> domSelection = do_QueryReferent(mDomSelectionWeak); + if (!domSelection) + return true; // No selection/caret to draw. + domSelection->GetFocusNode(getter_AddRefs(node)); + if (!node) + return true; // No selection/caret to draw. + nsCOMPtr<nsIContent> caretContent = do_QueryInterface(node); + if (!caretContent) + return true; // No selection/caret to draw. + + // If there's a menu popup open before the popup with + // the caret, don't show the caret. + for (uint32_t i=0; i<popups.Length(); i++) { + nsMenuPopupFrame* popupFrame = static_cast<nsMenuPopupFrame*>(popups[i]); + nsIContent* popupContent = popupFrame->GetContent(); + + if (nsContentUtils::ContentIsDescendantOf(caretContent, popupContent)) { + // The caret is in this popup. There were no menu popups before this + // popup, so don't hide the caret. + return false; + } + + if (popupFrame->PopupType() == ePopupTypeMenu && !popupFrame->IsContextMenu()) { + // This is an open menu popup. It does not contain the caret (else we'd + // have returned above). Even if the caret is in a subsequent popup, + // or another document/frame, it should be hidden. + return true; + } + } +#endif + + // There are no open menu popups, no need to hide the caret. + return false; +} + +void +nsCaret::ComputeCaretRects(nsIFrame* aFrame, int32_t aFrameOffset, + nsRect* aCaretRect, nsRect* aHookRect) +{ + NS_ASSERTION(aFrame, "Should have a frame here"); + + WritingMode wm = aFrame->GetWritingMode(); + bool isVertical = wm.IsVertical(); + + nscoord bidiIndicatorSize; + *aCaretRect = GetGeometryForFrame(aFrame, aFrameOffset, &bidiIndicatorSize); + + // on RTL frames the right edge of mCaretRect must be equal to framePos + const nsStyleVisibility* vis = aFrame->StyleVisibility(); + if (NS_STYLE_DIRECTION_RTL == vis->mDirection) { + if (isVertical) { + aCaretRect->y -= aCaretRect->height; + } else { + aCaretRect->x -= aCaretRect->width; + } + } + + // Simon -- make a hook to draw to the left or right of the caret to show keyboard language direction + aHookRect->SetEmpty(); + if (!IsBidiUI()) { + return; + } + + bool isCaretRTL; + nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard(); + // if bidiKeyboard->IsLangRTL() fails, there is no way to tell the + // keyboard direction, or the user has no right-to-left keyboard + // installed, so we never draw the hook. + if (bidiKeyboard && NS_SUCCEEDED(bidiKeyboard->IsLangRTL(&isCaretRTL))) { + // 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. + if (isVertical) { + bool isSidewaysLR = wm.IsVerticalLR() && !wm.IsLineInverted(); + if (isSidewaysLR) { + aHookRect->SetRect(aCaretRect->x + bidiIndicatorSize, + aCaretRect->y + (!isCaretRTL ? bidiIndicatorSize * -1 : + aCaretRect->height), + aCaretRect->height, + bidiIndicatorSize); + } else { + aHookRect->SetRect(aCaretRect->XMost() - bidiIndicatorSize, + aCaretRect->y + (isCaretRTL ? bidiIndicatorSize * -1 : + aCaretRect->height), + aCaretRect->height, + bidiIndicatorSize); + } + } else { + aHookRect->SetRect(aCaretRect->x + (isCaretRTL ? bidiIndicatorSize * -1 : + aCaretRect->width), + aCaretRect->y + bidiIndicatorSize, + bidiIndicatorSize, + aCaretRect->width); + } + } +} + +/* static */ +void nsCaret::CaretBlinkCallback(nsITimer* aTimer, void* aClosure) +{ + nsCaret* theCaret = reinterpret_cast<nsCaret*>(aClosure); + if (!theCaret) { + return; + } + theCaret->mIsBlinkOn = !theCaret->mIsBlinkOn; + theCaret->SchedulePaint(); + + // mBlinkCount of -1 means blink count is not enabled. + if (theCaret->mBlinkCount == -1) { + return; + } + + // Track the blink count, but only at end of a blink cycle. + if (!theCaret->mIsBlinkOn) { + // If we exceeded the blink count, stop the timer. + if (--theCaret->mBlinkCount <= 0) { + theCaret->StopBlinking(); + } + } +} + +void +nsCaret::SetIgnoreUserModify(bool aIgnoreUserModify) +{ + mIgnoreUserModify = aIgnoreUserModify; + SchedulePaint(); +} |