summaryrefslogtreecommitdiffstats
path: root/layout/base/nsCaret.cpp
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /layout/base/nsCaret.cpp
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'layout/base/nsCaret.cpp')
-rw-r--r--layout/base/nsCaret.cpp968
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();
+}