summaryrefslogtreecommitdiffstats
path: root/layout/xul/nsTextBoxFrame.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/xul/nsTextBoxFrame.cpp')
-rw-r--r--layout/xul/nsTextBoxFrame.cpp1055
1 files changed, 1055 insertions, 0 deletions
diff --git a/layout/xul/nsTextBoxFrame.cpp b/layout/xul/nsTextBoxFrame.cpp
new file mode 100644
index 0000000000..d31b8418da
--- /dev/null
+++ b/layout/xul/nsTextBoxFrame.cpp
@@ -0,0 +1,1055 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#include "nsTextBoxFrame.h"
+
+#include "gfx2DGlue.h"
+#include "gfxUtils.h"
+#include "mozilla/intl/BidiEmbeddingLevel.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/intl/Segmenter.h"
+#include "mozilla/layers/RenderRootStateManager.h"
+#include "mozilla/gfx/2D.h"
+#include "nsFontMetrics.h"
+#include "nsReadableUtils.h"
+#include "nsCOMPtr.h"
+#include "nsCRT.h"
+#include "nsGkAtoms.h"
+#include "nsPresContext.h"
+#include "gfxContext.h"
+#include "nsIContent.h"
+#include "nsNameSpaceManager.h"
+#include "nsBoxLayoutState.h"
+#include "nsMenuBarListener.h"
+#include "nsString.h"
+#include "nsITheme.h"
+#include "nsUnicharUtils.h"
+#include "nsContentUtils.h"
+#include "nsDisplayList.h"
+#include "nsCSSRendering.h"
+#include "nsIReflowCallback.h"
+#include "nsBoxFrame.h"
+#include "nsLayoutUtils.h"
+#include "TextDrawTarget.h"
+
+#ifdef ACCESSIBILITY
+# include "nsAccessibilityService.h"
+#endif
+
+#include "nsBidiUtils.h"
+#include "nsBidiPresUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+class nsAccessKeyInfo {
+ public:
+ int32_t mAccesskeyIndex;
+ nscoord mBeforeWidth, mAccessWidth, mAccessUnderlineSize, mAccessOffset;
+};
+
+bool nsTextBoxFrame::gAlwaysAppendAccessKey = false;
+bool nsTextBoxFrame::gAccessKeyPrefInitialized = false;
+bool nsTextBoxFrame::gInsertSeparatorBeforeAccessKey = false;
+bool nsTextBoxFrame::gInsertSeparatorPrefInitialized = false;
+
+nsIFrame* NS_NewTextBoxFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
+ return new (aPresShell) nsTextBoxFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsTextBoxFrame)
+
+NS_QUERYFRAME_HEAD(nsTextBoxFrame)
+ NS_QUERYFRAME_ENTRY(nsTextBoxFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsLeafBoxFrame)
+
+nsresult nsTextBoxFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute,
+ int32_t aModType) {
+ bool aResize;
+ bool aRedraw;
+
+ UpdateAttributes(aAttribute, aResize, aRedraw);
+
+ if (aResize) {
+ PresShell()->FrameNeedsReflow(
+ this, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY);
+ } else if (aRedraw) {
+ nsBoxLayoutState state(PresContext());
+ XULRedraw(state);
+ }
+
+ return NS_OK;
+}
+
+nsTextBoxFrame::nsTextBoxFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : nsLeafBoxFrame(aStyle, aPresContext, kClassID),
+ mAccessKeyInfo(nullptr),
+ mCropType(CropRight),
+ mAscent(0),
+ mNeedsReflowCallback(false) {
+ MarkIntrinsicISizesDirty();
+}
+
+nsTextBoxFrame::~nsTextBoxFrame() { delete mAccessKeyInfo; }
+
+void nsTextBoxFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ nsLeafBoxFrame::Init(aContent, aParent, aPrevInFlow);
+
+ bool aResize;
+ bool aRedraw;
+ UpdateAttributes(nullptr, aResize, aRedraw); /* update all */
+}
+
+bool nsTextBoxFrame::AlwaysAppendAccessKey() {
+ if (!gAccessKeyPrefInitialized) {
+ gAccessKeyPrefInitialized = true;
+
+ const char* prefName = "intl.menuitems.alwaysappendaccesskeys";
+ nsAutoString val;
+ Preferences::GetLocalizedString(prefName, val);
+ gAlwaysAppendAccessKey = val.EqualsLiteral("true");
+ }
+ return gAlwaysAppendAccessKey;
+}
+
+bool nsTextBoxFrame::InsertSeparatorBeforeAccessKey() {
+ if (!gInsertSeparatorPrefInitialized) {
+ gInsertSeparatorPrefInitialized = true;
+
+ const char* prefName = "intl.menuitems.insertseparatorbeforeaccesskeys";
+ nsAutoString val;
+ Preferences::GetLocalizedString(prefName, val);
+ gInsertSeparatorBeforeAccessKey = val.EqualsLiteral("true");
+ }
+ return gInsertSeparatorBeforeAccessKey;
+}
+
+class nsAsyncAccesskeyUpdate final : public nsIReflowCallback {
+ public:
+ explicit nsAsyncAccesskeyUpdate(nsIFrame* aFrame) : mWeakFrame(aFrame) {}
+
+ virtual bool ReflowFinished() override {
+ bool shouldFlush = false;
+ nsTextBoxFrame* frame = static_cast<nsTextBoxFrame*>(mWeakFrame.GetFrame());
+ if (frame) {
+ shouldFlush = frame->UpdateAccesskey(mWeakFrame);
+ }
+ delete this;
+ return shouldFlush;
+ }
+
+ virtual void ReflowCallbackCanceled() override { delete this; }
+
+ WeakFrame mWeakFrame;
+};
+
+bool nsTextBoxFrame::UpdateAccesskey(WeakFrame& aWeakThis) {
+ nsAutoString accesskey;
+ mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey,
+ accesskey);
+
+ if (!accesskey.Equals(mAccessKey)) {
+ // Need to get clean mTitle.
+ RecomputeTitle();
+ mAccessKey = accesskey;
+ UpdateAccessTitle();
+ PresShell()->FrameNeedsReflow(
+ this, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY);
+ return true;
+ }
+ return false;
+}
+
+void nsTextBoxFrame::UpdateAttributes(nsAtom* aAttribute, bool& aResize,
+ bool& aRedraw) {
+ bool doUpdateTitle = false;
+ aResize = false;
+ aRedraw = false;
+
+ if (aAttribute == nullptr || aAttribute == nsGkAtoms::crop) {
+ static dom::Element::AttrValuesArray strings[] = {
+ nsGkAtoms::left, nsGkAtoms::start, nsGkAtoms::center,
+ nsGkAtoms::right, nsGkAtoms::end, nsGkAtoms::none,
+ nullptr};
+ CroppingStyle cropType;
+ switch (mContent->AsElement()->FindAttrValueIn(
+ kNameSpaceID_None, nsGkAtoms::crop, strings, eCaseMatters)) {
+ case 0:
+ case 1:
+ cropType = CropLeft;
+ break;
+ case 2:
+ cropType = CropCenter;
+ break;
+ case 3:
+ case 4:
+ cropType = CropRight;
+ break;
+ case 5:
+ cropType = CropNone;
+ break;
+ default:
+ cropType = CropAuto;
+ break;
+ }
+
+ if (cropType != mCropType) {
+ aResize = true;
+ mCropType = cropType;
+ }
+ }
+
+ if (aAttribute == nullptr || aAttribute == nsGkAtoms::value) {
+ RecomputeTitle();
+ doUpdateTitle = true;
+ }
+
+ if (aAttribute == nullptr || aAttribute == nsGkAtoms::accesskey) {
+ mNeedsReflowCallback = true;
+ // Ensure that layout is refreshed and reflow callback called.
+ aResize = true;
+ }
+
+ if (doUpdateTitle) {
+ UpdateAccessTitle();
+ aResize = true;
+ }
+}
+
+namespace mozilla {
+
+class nsDisplayXULTextBox final : public nsPaintedDisplayItem {
+ public:
+ nsDisplayXULTextBox(nsDisplayListBuilder* aBuilder, nsTextBoxFrame* aFrame)
+ : nsPaintedDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayXULTextBox);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayXULTextBox)
+#endif
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const override;
+ NS_DISPLAY_DECL_NAME("XULTextBox", TYPE_XUL_TEXT_BOX)
+
+ virtual nsRect GetComponentAlphaBounds(
+ nsDisplayListBuilder* aBuilder) const override;
+
+ void PaintTextToContext(gfxContext* aCtx, nsPoint aOffset,
+ const nscolor* aColor);
+
+ virtual bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+};
+
+static void PaintTextShadowCallback(gfxContext* aCtx, nsPoint aShadowOffset,
+ const nscolor& aShadowColor, void* aData) {
+ reinterpret_cast<nsDisplayXULTextBox*>(aData)->PaintTextToContext(
+ aCtx, aShadowOffset, &aShadowColor);
+}
+
+void nsDisplayXULTextBox::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ // Paint the text shadow before doing any foreground stuff
+ nsRect drawRect =
+ static_cast<nsTextBoxFrame*>(mFrame)->mTextDrawRect + ToReferenceFrame();
+ nsLayoutUtils::PaintTextShadow(mFrame, aCtx, drawRect,
+ GetPaintRect(aBuilder, aCtx),
+ mFrame->StyleText()->mColor.ToColor(),
+ PaintTextShadowCallback, (void*)this);
+
+ PaintTextToContext(aCtx, nsPoint(0, 0), nullptr);
+}
+
+void nsDisplayXULTextBox::PaintTextToContext(gfxContext* aCtx, nsPoint aOffset,
+ const nscolor* aColor) {
+ static_cast<nsTextBoxFrame*>(mFrame)->PaintTitle(
+ *aCtx, mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame(),
+ ToReferenceFrame() + aOffset, aColor);
+}
+
+bool nsDisplayXULTextBox::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ bool snap = false;
+ auto bounds = GetBounds(aDisplayListBuilder, &snap);
+
+ if (bounds.IsEmpty()) {
+ return true;
+ }
+
+ auto appUnitsPerDevPixel = Frame()->PresContext()->AppUnitsPerDevPixel();
+ gfx::Point deviceOffset =
+ LayoutDevicePoint::FromAppUnits(bounds.TopLeft(), appUnitsPerDevPixel)
+ .ToUnknownPoint();
+
+ RefPtr<mozilla::layout::TextDrawTarget> textDrawer =
+ new mozilla::layout::TextDrawTarget(aBuilder, aResources, aSc, aManager,
+ this, bounds);
+ RefPtr<gfxContext> captureCtx =
+ gfxContext::CreateOrNull(textDrawer, deviceOffset);
+
+ Paint(aDisplayListBuilder, captureCtx);
+ textDrawer->TerminateShadows();
+
+ return textDrawer->Finish();
+}
+
+nsRect nsDisplayXULTextBox::GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ *aSnap = false;
+ return mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
+}
+
+nsRect nsDisplayXULTextBox::GetComponentAlphaBounds(
+ nsDisplayListBuilder* aBuilder) const {
+ return static_cast<nsTextBoxFrame*>(mFrame)->GetComponentAlphaBounds() +
+ ToReferenceFrame();
+}
+
+} // namespace mozilla
+
+void nsTextBoxFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ if (!IsVisibleForPainting()) return;
+
+ nsLeafBoxFrame::BuildDisplayList(aBuilder, aLists);
+
+ aLists.Content()->AppendNewToTop<nsDisplayXULTextBox>(aBuilder, this);
+}
+
+void nsTextBoxFrame::PaintTitle(gfxContext& aRenderingContext,
+ const nsRect& aDirtyRect, nsPoint aPt,
+ const nscolor* aOverrideColor) {
+ if (mTitle.IsEmpty()) return;
+
+ DrawText(aRenderingContext, aDirtyRect, mTextDrawRect + aPt, aOverrideColor);
+}
+
+void nsTextBoxFrame::DrawText(gfxContext& aRenderingContext,
+ const nsRect& aDirtyRect, const nsRect& aTextRect,
+ const nscolor* aOverrideColor) {
+ nsPresContext* presContext = PresContext();
+ int32_t appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
+ DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
+
+ // paint the title
+ nscolor overColor = 0;
+ nscolor underColor = 0;
+ nscolor strikeColor = 0;
+ auto overStyle = StyleTextDecorationStyle::None;
+ auto underStyle = StyleTextDecorationStyle::None;
+ auto strikeStyle = StyleTextDecorationStyle::None;
+
+ // Begin with no decorations
+ auto decorations = StyleTextDecorationLine::NONE;
+ // A mask of all possible line decorations.
+ auto decorMask = StyleTextDecorationLine::UNDERLINE |
+ StyleTextDecorationLine::OVERLINE |
+ StyleTextDecorationLine::LINE_THROUGH;
+
+ WritingMode wm = GetWritingMode();
+ bool vertical = wm.IsVertical();
+
+ nsIFrame* f = this;
+ do { // find decoration colors
+ ComputedStyle* context = f->Style();
+ if (!context->HasTextDecorationLines()) {
+ break;
+ }
+ const nsStyleTextReset* styleText = context->StyleTextReset();
+
+ // a decoration defined here
+ if (decorMask & styleText->mTextDecorationLine) {
+ nscolor color;
+ if (aOverrideColor) {
+ color = *aOverrideColor;
+ } else {
+ color = styleText->mTextDecorationColor.CalcColor(*context);
+ }
+ const auto style = styleText->mTextDecorationStyle;
+
+ if (StyleTextDecorationLine::UNDERLINE & decorMask &
+ styleText->mTextDecorationLine) {
+ underColor = color;
+ underStyle = style;
+ decorMask &= ~StyleTextDecorationLine::UNDERLINE;
+ decorations |= StyleTextDecorationLine::UNDERLINE;
+ }
+ if (StyleTextDecorationLine::OVERLINE & decorMask &
+ styleText->mTextDecorationLine) {
+ overColor = color;
+ overStyle = style;
+ decorMask &= ~StyleTextDecorationLine::OVERLINE;
+ decorations |= StyleTextDecorationLine::OVERLINE;
+ }
+ if (StyleTextDecorationLine::LINE_THROUGH & decorMask &
+ styleText->mTextDecorationLine) {
+ strikeColor = color;
+ strikeStyle = style;
+ decorMask &= ~StyleTextDecorationLine::LINE_THROUGH;
+ decorations |= StyleTextDecorationLine::LINE_THROUGH;
+ }
+ }
+ } while (decorMask && (f = nsLayoutUtils::GetParentOrPlaceholderFor(f)));
+
+ RefPtr<nsFontMetrics> fontMet =
+ nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f);
+ fontMet->SetVertical(wm.IsVertical());
+ fontMet->SetTextOrientation(StyleVisibility()->mTextOrientation);
+
+ nscoord offset;
+ nscoord size;
+ nscoord ascent = fontMet->MaxAscent();
+
+ nsPoint baselinePt;
+ if (wm.IsVertical()) {
+ baselinePt.x = presContext->RoundAppUnitsToNearestDevPixels(
+ aTextRect.x + (wm.IsVerticalRL() ? aTextRect.width - ascent : ascent));
+ baselinePt.y = aTextRect.y;
+ } else {
+ baselinePt.x = aTextRect.x;
+ baselinePt.y =
+ presContext->RoundAppUnitsToNearestDevPixels(aTextRect.y + ascent);
+ }
+
+ nsCSSRendering::PaintDecorationLineParams params;
+ params.dirtyRect = ToRect(presContext->AppUnitsToGfxUnits(aDirtyRect));
+ params.pt = Point(presContext->AppUnitsToGfxUnits(aTextRect.x),
+ presContext->AppUnitsToGfxUnits(aTextRect.y));
+ params.icoordInFrame =
+ Float(PresContext()->AppUnitsToGfxUnits(mTextDrawRect.x));
+ params.lineSize = Size(presContext->AppUnitsToGfxUnits(aTextRect.width), 0);
+ params.ascent = presContext->AppUnitsToGfxUnits(ascent);
+ params.vertical = vertical;
+
+ // XXX todo: vertical-mode support for decorations not tested yet,
+ // probably won't be positioned correctly
+
+ // Underlines are drawn before overlines, and both before the text
+ // itself, per http://www.w3.org/TR/CSS21/zindex.html point 7.2.1.4.1.1.
+ // (We don't apply this rule to the access-key underline because we only
+ // find out where that is as a side effect of drawing the text, in the
+ // general case -- see below.)
+ if (decorations & (StyleTextDecorationLine::OVERLINE |
+ StyleTextDecorationLine::UNDERLINE)) {
+ fontMet->GetUnderline(offset, size);
+ params.lineSize.height = presContext->AppUnitsToGfxUnits(size);
+ if ((decorations & StyleTextDecorationLine::UNDERLINE) &&
+ underStyle != StyleTextDecorationStyle::None) {
+ params.color = underColor;
+ params.offset = presContext->AppUnitsToGfxUnits(offset);
+ params.decoration = StyleTextDecorationLine::UNDERLINE;
+ params.style = underStyle;
+ nsCSSRendering::PaintDecorationLine(this, *drawTarget, params);
+ }
+ if ((decorations & StyleTextDecorationLine::OVERLINE) &&
+ overStyle != StyleTextDecorationStyle::None) {
+ params.color = overColor;
+ params.offset = params.ascent;
+ params.decoration = StyleTextDecorationLine::OVERLINE;
+ params.style = overStyle;
+ nsCSSRendering::PaintDecorationLine(this, *drawTarget, params);
+ }
+ }
+
+ RefPtr<gfxContext> refContext =
+ PresShell()->CreateReferenceRenderingContext();
+ DrawTarget* refDrawTarget = refContext->GetDrawTarget();
+
+ CalculateUnderline(refDrawTarget, *fontMet);
+
+ DeviceColor color = ToDeviceColor(
+ aOverrideColor ? *aOverrideColor : StyleText()->mColor.ToColor());
+ ColorPattern colorPattern(color);
+ aRenderingContext.SetDeviceColor(color);
+
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (mState & NS_FRAME_IS_BIDI) {
+ presContext->SetBidiEnabled();
+ mozilla::intl::BidiEmbeddingLevel level =
+ nsBidiPresUtils::BidiLevelFromStyle(Style());
+ if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) {
+ // We let the RenderText function calculate the mnemonic's
+ // underline position for us.
+ nsBidiPositionResolve posResolve;
+ posResolve.logicalIndex = mAccessKeyInfo->mAccesskeyIndex;
+ rv = nsBidiPresUtils::RenderText(
+ mCroppedTitle.get(), mCroppedTitle.Length(), level, presContext,
+ aRenderingContext, refDrawTarget, *fontMet, baselinePt.x,
+ baselinePt.y, &posResolve, 1);
+ mAccessKeyInfo->mBeforeWidth = posResolve.visualLeftTwips;
+ mAccessKeyInfo->mAccessWidth = posResolve.visualWidth;
+ } else {
+ rv = nsBidiPresUtils::RenderText(
+ mCroppedTitle.get(), mCroppedTitle.Length(), level, presContext,
+ aRenderingContext, refDrawTarget, *fontMet, baselinePt.x,
+ baselinePt.y);
+ }
+ }
+ if (NS_FAILED(rv)) {
+ fontMet->SetTextRunRTL(false);
+
+ if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) {
+ // In the simple (non-BiDi) case, we calculate the mnemonic's
+ // underline position by getting the text metric.
+ // XXX are attribute values always two byte?
+ if (mAccessKeyInfo->mAccesskeyIndex > 0)
+ mAccessKeyInfo->mBeforeWidth = nsLayoutUtils::AppUnitWidthOfString(
+ mCroppedTitle.get(), mAccessKeyInfo->mAccesskeyIndex, *fontMet,
+ refDrawTarget);
+ else
+ mAccessKeyInfo->mBeforeWidth = 0;
+ }
+
+ fontMet->DrawString(mCroppedTitle.get(), mCroppedTitle.Length(),
+ baselinePt.x, baselinePt.y, &aRenderingContext,
+ refDrawTarget);
+ }
+
+ if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) {
+ nsRect r(aTextRect.x + mAccessKeyInfo->mBeforeWidth,
+ aTextRect.y + mAccessKeyInfo->mAccessOffset,
+ mAccessKeyInfo->mAccessWidth,
+ mAccessKeyInfo->mAccessUnderlineSize);
+ Rect devPxRect = NSRectToSnappedRect(r, appUnitsPerDevPixel, *drawTarget);
+ drawTarget->FillRect(devPxRect, colorPattern);
+ }
+
+ // Strikeout is drawn on top of the text, per
+ // http://www.w3.org/TR/CSS21/zindex.html point 7.2.1.4.1.1.
+ if ((decorations & StyleTextDecorationLine::LINE_THROUGH) &&
+ strikeStyle != StyleTextDecorationStyle::None) {
+ fontMet->GetStrikeout(offset, size);
+ params.color = strikeColor;
+ params.lineSize.height = presContext->AppUnitsToGfxUnits(size);
+ params.offset = presContext->AppUnitsToGfxUnits(offset);
+ params.decoration = StyleTextDecorationLine::LINE_THROUGH;
+ params.style = strikeStyle;
+ nsCSSRendering::PaintDecorationLine(this, *drawTarget, params);
+ }
+}
+
+void nsTextBoxFrame::CalculateUnderline(DrawTarget* aDrawTarget,
+ nsFontMetrics& aFontMetrics) {
+ if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) {
+ // Calculate all fields of mAccessKeyInfo which
+ // are the same for both BiDi and non-BiDi frames.
+ const char16_t* titleString = mCroppedTitle.get();
+ aFontMetrics.SetTextRunRTL(false);
+ mAccessKeyInfo->mAccessWidth = nsLayoutUtils::AppUnitWidthOfString(
+ titleString[mAccessKeyInfo->mAccesskeyIndex], aFontMetrics,
+ aDrawTarget);
+
+ nscoord offset, baseline;
+ aFontMetrics.GetUnderline(offset, mAccessKeyInfo->mAccessUnderlineSize);
+ baseline = aFontMetrics.MaxAscent();
+ mAccessKeyInfo->mAccessOffset = baseline - offset;
+ }
+}
+
+void nsTextBoxFrame::CropStringForWidth(nsAString& aText,
+ gfxContext& aRenderingContext,
+ nsFontMetrics& aFontMetrics,
+ nscoord aWidth,
+ CroppingStyle aCropType) {
+ DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
+
+ // See if the width is even smaller than the ellipsis
+ // If so, clear the text completely.
+ const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis();
+ aFontMetrics.SetTextRunRTL(false);
+ nscoord ellipsisWidth =
+ nsLayoutUtils::AppUnitWidthOfString(kEllipsis, aFontMetrics, drawTarget);
+
+ if (ellipsisWidth > aWidth) {
+ aText.Truncate(0);
+ return;
+ }
+ if (ellipsisWidth == aWidth) {
+ aText.Assign(kEllipsis);
+ return;
+ }
+
+ // We will be drawing an ellipsis, thank you very much.
+ // Subtract out the required width of the ellipsis.
+ // This is the total remaining width we have to play with.
+ aWidth -= ellipsisWidth;
+
+ using mozilla::intl::GraphemeClusterBreakIteratorUtf16;
+ using mozilla::intl::GraphemeClusterBreakReverseIteratorUtf16;
+
+ // Now we crop. This is quite basic: it will not be really accurate in the
+ // presence of complex scripts with contextual shaping, etc., as it measures
+ // each grapheme cluster in isolation, not in its proper context.
+ switch (aCropType) {
+ case CropAuto:
+ case CropNone:
+ case CropRight: {
+ const Span text(aText);
+ GraphemeClusterBreakIteratorUtf16 iter(text);
+ uint32_t pos = 0;
+ nscoord totalWidth = 0;
+
+ while (Maybe<uint32_t> nextPos = iter.Next()) {
+ const nscoord charWidth = nsLayoutUtils::AppUnitWidthOfString(
+ text.FromTo(pos, *nextPos), aFontMetrics, drawTarget);
+ if (totalWidth + charWidth > aWidth) {
+ break;
+ }
+ pos = *nextPos;
+ totalWidth += charWidth;
+ }
+
+ if (pos < aText.Length()) {
+ aText.Replace(pos, aText.Length() - pos, kEllipsis);
+ }
+ } break;
+
+ case CropLeft: {
+ const Span text(aText);
+ GraphemeClusterBreakReverseIteratorUtf16 iter(text);
+ uint32_t pos = text.Length();
+ nscoord totalWidth = 0;
+
+ // nextPos is decreasing since we use a reverse iterator.
+ while (Maybe<uint32_t> nextPos = iter.Next()) {
+ const nscoord charWidth = nsLayoutUtils::AppUnitWidthOfString(
+ text.FromTo(*nextPos, pos), aFontMetrics, drawTarget);
+ if (totalWidth + charWidth > aWidth) {
+ break;
+ }
+
+ pos = *nextPos;
+ totalWidth += charWidth;
+ }
+
+ if (pos > 0) {
+ aText.Replace(0, pos, kEllipsis);
+ }
+ } break;
+
+ case CropCenter: {
+ const Span text(aText);
+ nscoord totalWidth = 0;
+ GraphemeClusterBreakIteratorUtf16 leftIter(text);
+ GraphemeClusterBreakReverseIteratorUtf16 rightIter(text);
+ uint32_t leftPos = 0;
+ uint32_t rightPos = text.Length();
+
+ while (leftPos < rightPos) {
+ Maybe<uint32_t> nextPos = leftIter.Next();
+ nscoord charWidth = nsLayoutUtils::AppUnitWidthOfString(
+ text.FromTo(leftPos, *nextPos), aFontMetrics, drawTarget);
+ if (totalWidth + charWidth > aWidth) {
+ break;
+ }
+
+ leftPos = *nextPos;
+ totalWidth += charWidth;
+
+ if (leftPos >= rightPos) {
+ break;
+ }
+
+ nextPos = rightIter.Next();
+ charWidth = nsLayoutUtils::AppUnitWidthOfString(
+ text.FromTo(*nextPos, rightPos), aFontMetrics, drawTarget);
+ if (totalWidth + charWidth > aWidth) {
+ break;
+ }
+
+ rightPos = *nextPos;
+ totalWidth += charWidth;
+ }
+
+ if (leftPos < rightPos) {
+ aText.Replace(leftPos, rightPos - leftPos, kEllipsis);
+ }
+ } break;
+ }
+}
+
+nscoord nsTextBoxFrame::CalculateTitleForWidth(gfxContext& aRenderingContext,
+ nscoord aMaxWidth) {
+ if (mTitle.IsEmpty()) {
+ mCroppedTitle.Truncate();
+ return 0;
+ }
+
+ RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f);
+
+ // See if the text needs to be cropped to fit in the width given.
+ mCroppedTitle = mTitle;
+ nscoord width = nsLayoutUtils::AppUnitWidthOfStringBidi(
+ mCroppedTitle, this, *fm, aRenderingContext);
+ if (width > aMaxWidth && mCropType != CropNone) {
+ CropStringForWidth(mCroppedTitle, aRenderingContext, *fm, aMaxWidth,
+ mCropType);
+ width = nsLayoutUtils::AppUnitWidthOfStringBidi(mCroppedTitle, this, *fm,
+ aRenderingContext);
+ }
+
+ if (StyleVisibility()->mDirection == StyleDirection::Rtl ||
+ HasRTLChars(mCroppedTitle)) {
+ AddStateBits(NS_FRAME_IS_BIDI);
+ }
+
+ return width;
+}
+
+#define OLD_ELLIPSIS u"..."_ns
+
+// the following block is to append the accesskey to mTitle if there is an
+// accesskey but the mTitle doesn't have the character
+void nsTextBoxFrame::UpdateAccessTitle() {
+ /*
+ * Note that if you change appending access key label spec,
+ * you need to maintain same logic in following methods. See bug 324159.
+ * toolkit/components/prompts/src/CommonDialog.jsm (setLabelForNode)
+ * toolkit/content/widgets/text.js (formatAccessKey)
+ */
+ int32_t menuAccessKey = nsMenuBarListener::GetMenuAccessKey();
+ if (!menuAccessKey || mAccessKey.IsEmpty()) return;
+
+ if (!AlwaysAppendAccessKey() &&
+ FindInReadable(mAccessKey, mTitle, nsCaseInsensitiveStringComparator))
+ return;
+
+ nsAutoString accessKeyLabel;
+ accessKeyLabel += '(';
+ accessKeyLabel += mAccessKey;
+ ToUpperCase(accessKeyLabel);
+ accessKeyLabel += ')';
+
+ if (mTitle.IsEmpty()) {
+ mTitle = accessKeyLabel;
+ return;
+ }
+
+ if (StringEndsWith(mTitle, accessKeyLabel)) {
+ // Never append another "(X)" if the title already ends with "(X)".
+ return;
+ }
+
+ const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis();
+ uint32_t offset = mTitle.Length();
+ if (StringEndsWith(mTitle, kEllipsis)) {
+ offset -= kEllipsis.Length();
+ } else if (StringEndsWith(mTitle, OLD_ELLIPSIS)) {
+ // Try to check with our old ellipsis (for old addons)
+ offset -= OLD_ELLIPSIS.Length();
+ } else {
+ // Try to check with
+ // our default ellipsis (for non-localized addons) or ':'
+ const char16_t kLastChar = mTitle.Last();
+ if (kLastChar == char16_t(0x2026) || kLastChar == char16_t(':')) offset--;
+ }
+
+ if (InsertSeparatorBeforeAccessKey() && offset > 0 &&
+ !NS_IS_SPACE(mTitle[offset - 1])) {
+ mTitle.Insert(' ', offset);
+ offset++;
+ }
+
+ mTitle.Insert(accessKeyLabel, offset);
+}
+
+void nsTextBoxFrame::UpdateAccessIndex() {
+ int32_t menuAccessKey = nsMenuBarListener::GetMenuAccessKey();
+ if (menuAccessKey) {
+ if (mAccessKey.IsEmpty()) {
+ if (mAccessKeyInfo) {
+ delete mAccessKeyInfo;
+ mAccessKeyInfo = nullptr;
+ }
+ } else {
+ if (!mAccessKeyInfo) {
+ mAccessKeyInfo = new nsAccessKeyInfo();
+ if (!mAccessKeyInfo) return;
+ }
+
+ nsAString::const_iterator start, end;
+
+ mCroppedTitle.BeginReading(start);
+ mCroppedTitle.EndReading(end);
+
+ // remember the beginning of the string
+ nsAString::const_iterator originalStart = start;
+
+ bool found;
+ if (!AlwaysAppendAccessKey()) {
+ // not appending access key - do case-sensitive search
+ // first
+ found = FindInReadable(mAccessKey, start, end);
+ if (!found) {
+ // didn't find it - perform a case-insensitive search
+ start = originalStart;
+ found = FindInReadable(mAccessKey, start, end,
+ nsCaseInsensitiveStringComparator);
+ }
+ } else {
+ found = RFindInReadable(mAccessKey, start, end,
+ nsCaseInsensitiveStringComparator);
+ }
+
+ if (found)
+ mAccessKeyInfo->mAccesskeyIndex = Distance(originalStart, start);
+ else
+ mAccessKeyInfo->mAccesskeyIndex = kNotFound;
+ }
+ }
+}
+
+void nsTextBoxFrame::RecomputeTitle() {
+ mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::value, mTitle);
+
+ // This doesn't handle language-specific uppercasing/lowercasing
+ // rules, unlike textruns.
+ StyleTextTransform textTransform = StyleText()->mTextTransform;
+ if (textTransform.case_ == StyleTextTransformCase::Uppercase) {
+ ToUpperCase(mTitle);
+ } else if (textTransform.case_ == StyleTextTransformCase::Lowercase) {
+ ToLowerCase(mTitle);
+ }
+ // We can't handle StyleTextTransformCase::Capitalize because we
+ // have no clue about word boundaries here. We also don't handle
+ // the full-width or full-size-kana transforms.
+}
+
+void nsTextBoxFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
+ nsLeafBoxFrame::DidSetComputedStyle(aOldComputedStyle);
+
+ if (!aOldComputedStyle) {
+ // We're just being initialized
+ return;
+ }
+
+ const nsStyleText* oldTextStyle = aOldComputedStyle->StyleText();
+ if (oldTextStyle->mTextTransform != StyleText()->mTextTransform) {
+ RecomputeTitle();
+ UpdateAccessTitle();
+ }
+}
+
+NS_IMETHODIMP
+nsTextBoxFrame::DoXULLayout(nsBoxLayoutState& aBoxLayoutState) {
+ if (mNeedsReflowCallback) {
+ nsIReflowCallback* cb = new nsAsyncAccesskeyUpdate(this);
+ if (cb) {
+ PresShell()->PostReflowCallback(cb);
+ }
+ mNeedsReflowCallback = false;
+ }
+
+ nsresult rv = nsLeafBoxFrame::DoXULLayout(aBoxLayoutState);
+
+ CalcDrawRect(*aBoxLayoutState.GetRenderingContext());
+
+ const nsStyleText* textStyle = StyleText();
+
+ nsRect scrollBounds(nsPoint(0, 0), GetSize());
+ nsRect textRect = mTextDrawRect;
+
+ RefPtr<nsFontMetrics> fontMet =
+ nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f);
+ nsBoundingMetrics metrics = fontMet->GetInkBoundsForInkOverflow(
+ mCroppedTitle.get(), mCroppedTitle.Length(),
+ aBoxLayoutState.GetRenderingContext()->GetDrawTarget());
+
+ WritingMode wm = GetWritingMode();
+ LogicalRect tr(wm, textRect, GetSize());
+
+ tr.IStart(wm) -= metrics.leftBearing;
+ tr.ISize(wm) = metrics.width;
+ // In DrawText() we always draw with the baseline at MaxAscent() (relative to
+ // mTextDrawRect),
+ tr.BStart(wm) += fontMet->MaxAscent() - metrics.ascent;
+ tr.BSize(wm) = metrics.ascent + metrics.descent;
+
+ textRect = tr.GetPhysicalRect(wm, GetSize());
+
+ // Our scrollable overflow is our bounds; our ink overflow may
+ // extend beyond that.
+ nsRect visualBounds;
+ visualBounds.UnionRect(scrollBounds, textRect);
+ OverflowAreas overflow(visualBounds, scrollBounds);
+
+ if (textStyle->HasTextShadow()) {
+ // text-shadow extends our visual but not scrollable bounds
+ nsRect& vis = overflow.InkOverflow();
+ vis.UnionRect(vis,
+ nsLayoutUtils::GetTextShadowRectsUnion(mTextDrawRect, this));
+ }
+ FinishAndStoreOverflow(overflow, GetSize());
+
+ return rv;
+}
+
+nsRect nsTextBoxFrame::GetComponentAlphaBounds() const {
+ if (StyleText()->HasTextShadow()) {
+ return InkOverflowRectRelativeToSelf();
+ }
+ return mTextDrawRect;
+}
+
+bool nsTextBoxFrame::XULComputesOwnOverflowArea() { return true; }
+
+/* virtual */
+void nsTextBoxFrame::MarkIntrinsicISizesDirty() {
+ mNeedsRecalc = true;
+ nsLeafBoxFrame::MarkIntrinsicISizesDirty();
+}
+
+void nsTextBoxFrame::GetTextSize(gfxContext& aRenderingContext,
+ const nsString& aString, nsSize& aSize,
+ nscoord& aAscent) {
+ RefPtr<nsFontMetrics> fontMet =
+ nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f);
+ aSize.height = fontMet->MaxHeight();
+ aSize.width = nsLayoutUtils::AppUnitWidthOfStringBidi(aString, this, *fontMet,
+ aRenderingContext);
+ aAscent = fontMet->MaxAscent();
+}
+
+void nsTextBoxFrame::CalcTextSize(nsBoxLayoutState& aBoxLayoutState) {
+ if (mNeedsRecalc) {
+ nsSize size;
+ gfxContext* rendContext = aBoxLayoutState.GetRenderingContext();
+ if (rendContext) {
+ GetTextSize(*rendContext, mTitle, size, mAscent);
+ if (GetWritingMode().IsVertical()) {
+ std::swap(size.width, size.height);
+ }
+ mTextSize = size;
+ mNeedsRecalc = false;
+ }
+ }
+}
+
+void nsTextBoxFrame::CalcDrawRect(gfxContext& aRenderingContext) {
+ WritingMode wm = GetWritingMode();
+
+ LogicalRect textRect(wm, LogicalPoint(wm, 0, 0), GetLogicalSize(wm));
+ nsMargin borderPadding;
+ GetXULBorderAndPadding(borderPadding);
+ textRect.Deflate(wm, LogicalMargin(wm, borderPadding));
+
+ // determine (cropped) title and underline position
+ // determine (cropped) title which fits in aRect, and its width
+ // (where "width" is the text measure along its baseline, i.e. actually
+ // a physical height in vertical writing modes)
+ nscoord titleWidth =
+ CalculateTitleForWidth(aRenderingContext, textRect.ISize(wm));
+
+#ifdef ACCESSIBILITY
+ // Make sure to update the accessible tree in case when cropped title is
+ // changed.
+ nsAccessibilityService* accService = GetAccService();
+ if (accService) {
+ accService->UpdateLabelValue(PresShell(), mContent, mCroppedTitle);
+ }
+#endif
+
+ // determine if and at which position to put the underline
+ UpdateAccessIndex();
+
+ // make the rect as small as our (cropped) text.
+ nscoord outerISize = textRect.ISize(wm);
+ textRect.ISize(wm) = titleWidth;
+
+ // Align our text within the overall rect by checking our text-align property.
+ const nsStyleText* textStyle = StyleText();
+ if (textStyle->mTextAlign == StyleTextAlign::Center) {
+ textRect.IStart(wm) += (outerISize - textRect.ISize(wm)) / 2;
+ } else if (textStyle->mTextAlign == StyleTextAlign::End ||
+ (textStyle->mTextAlign == StyleTextAlign::Left &&
+ wm.IsBidiRTL()) ||
+ (textStyle->mTextAlign == StyleTextAlign::Right &&
+ wm.IsBidiLTR())) {
+ textRect.IStart(wm) += (outerISize - textRect.ISize(wm));
+ }
+
+ mTextDrawRect = textRect.GetPhysicalRect(wm, GetSize());
+}
+
+/**
+ * Ok return our dimensions
+ */
+nsSize nsTextBoxFrame::GetXULPrefSize(nsBoxLayoutState& aBoxLayoutState) {
+ CalcTextSize(aBoxLayoutState);
+
+ nsSize size = mTextSize;
+ DISPLAY_PREF_SIZE(this, size);
+
+ AddXULBorderAndPadding(size);
+ bool widthSet, heightSet;
+ nsIFrame::AddXULPrefSize(this, size, widthSet, heightSet);
+
+ return size;
+}
+
+/**
+ * Ok return our dimensions
+ */
+nsSize nsTextBoxFrame::GetXULMinSize(nsBoxLayoutState& aBoxLayoutState) {
+ CalcTextSize(aBoxLayoutState);
+
+ nsSize size = mTextSize;
+ DISPLAY_MIN_SIZE(this, size);
+
+ // if there is cropping our min width becomes our border and padding
+ if (mCropType != CropNone && mCropType != CropAuto) {
+ if (GetWritingMode().IsVertical()) {
+ size.height = 0;
+ } else {
+ size.width = 0;
+ }
+ }
+
+ AddXULBorderAndPadding(size);
+ bool widthSet, heightSet;
+ nsIFrame::AddXULMinSize(this, size, widthSet, heightSet);
+
+ return size;
+}
+
+nscoord nsTextBoxFrame::GetXULBoxAscent(nsBoxLayoutState& aBoxLayoutState) {
+ CalcTextSize(aBoxLayoutState);
+
+ nscoord ascent = mAscent;
+
+ nsMargin m(0, 0, 0, 0);
+ GetXULBorderAndPadding(m);
+
+ WritingMode wm = GetWritingMode();
+ ascent += LogicalMargin(wm, m).BStart(wm);
+
+ return ascent;
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult nsTextBoxFrame::GetFrameName(nsAString& aResult) const {
+ MakeFrameName(u"TextBox"_ns, aResult);
+ aResult += u"[value="_ns + mTitle + u"]"_ns;
+ return NS_OK;
+}
+#endif