fune/layout/xul/nsTextBoxFrame.cpp
Ciure Andrei 00a73f5055 Backed out 17 changesets (bug 1550554, bug 1549593, bug 1551991, bug 1529002) for failing multiple Android tests and Windows 2012 bustages CLOSED TREE
Backed out changeset 3bb3fafa62e2 (bug 1551991)
Backed out changeset e12a979de502 (bug 1551991)
Backed out changeset d81e4aa6bf0c (bug 1551991)
Backed out changeset c354e61f2a34 (bug 1551991)
Backed out changeset 37fd602bebc2 (bug 1551991)
Backed out changeset 6c1f00cc30ca (bug 1551991)
Backed out changeset 8a7a0329bdc3 (bug 1551991)
Backed out changeset 86159475ddd3 (bug 1551991)
Backed out changeset 35f91a9ea82a (bug 1529002)
Backed out changeset 6798155e71dc (bug 1529002)
Backed out changeset b90c2cf5b8c5 (bug 1550554)
Backed out changeset 882ab9868c95 (bug 1550554)
Backed out changeset b28a48e2ed21 (bug 1550554)
Backed out changeset 2c31fe18eefd (bug 1550554)
Backed out changeset 57f2362aa538 (bug 1550554)
Backed out changeset 45f171b26e95 (bug 1550554)
Backed out changeset 2e4b263c9410 (bug 1549593)
2019-05-16 13:17:10 +03:00

1147 lines
37 KiB
C++

/* -*- 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/Attributes.h"
#include "mozilla/ComputedStyle.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/Preferences.h"
#include "mozilla/PresShell.h"
#include "mozilla/layers/RenderRootStateManager.h"
#include "mozilla/gfx/2D.h"
#include "nsFontMetrics.h"
#include "nsReadableUtils.h"
#include "nsCOMPtr.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 "nsIServiceManager.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 "nsUnicodeProperties.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::StyleChange,
NS_FRAME_IS_DIRTY);
} else if (aRedraw) {
nsBoxLayoutState state(PresContext());
XULRedraw(state);
}
// If the accesskey changed, register for the new value
// The old value has been unregistered in nsXULElement::SetAttr
if (aAttribute == nsGkAtoms::accesskey || aAttribute == nsGkAtoms::control)
RegUnregAccessKey(true);
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 */
// register access key
RegUnregAccessKey(true);
}
void nsTextBoxFrame::DestroyFrom(nsIFrame* aDestructRoot,
PostDestroyData& aPostDestroyData) {
// unregister access key
RegUnregAccessKey(false);
nsLeafBoxFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
}
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::StyleChange,
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 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;
}
}
class nsDisplayXULTextBox final : public nsPaintedDisplayItem {
public:
nsDisplayXULTextBox(nsDisplayListBuilder* aBuilder, nsTextBoxFrame* aFrame)
: nsPaintedDisplayItem(aBuilder, aFrame) {
MOZ_COUNT_CTOR(nsDisplayXULTextBox);
}
#ifdef NS_BUILD_REFCNT_LOGGING
virtual ~nsDisplayXULTextBox() { MOZ_COUNT_DTOR(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) {
DrawTargetAutoDisableSubpixelAntialiasing disable(aCtx->GetDrawTarget(),
IsSubpixelAADisabled());
// Paint the text shadow before doing any foreground stuff
nsRect drawRect =
static_cast<nsTextBoxFrame*>(mFrame)->mTextDrawRect + ToReferenceFrame();
nsLayoutUtils::PaintTextShadow(mFrame, aCtx, drawRect, GetPaintRect(),
mFrame->StyleColor()->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, GetPaintRect(), 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->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
}
nsRect nsDisplayXULTextBox::GetComponentAlphaBounds(
nsDisplayListBuilder* aBuilder) const {
return static_cast<nsTextBoxFrame*>(mFrame)->GetComponentAlphaBounds() +
ToReferenceFrame();
}
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;
uint8_t overStyle = 0;
uint8_t underStyle = 0;
uint8_t strikeStyle = 0;
// 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();
if (decorMask &
styleText->mTextDecorationLine) { // a decoration defined here
nscolor color;
if (aOverrideColor) {
color = *aOverrideColor;
} else {
color = styleText->mTextDecorationColor.CalcColor(*context);
}
uint8_t style = styleText->mTextDecorationStyle;
if (StyleTextDecorationLine_UNDERLINE & decorMask &
styleText->mTextDecorationLine) {
underColor = color;
underStyle = style;
// TODO(emilio): Could add `operator~` or `remove()` to cbindgen.
decorMask.bits &= ~StyleTextDecorationLine_UNDERLINE.bits;
decorations |= StyleTextDecorationLine_UNDERLINE;
}
if (StyleTextDecorationLine_OVERLINE & decorMask &
styleText->mTextDecorationLine) {
overColor = color;
overStyle = style;
decorMask.bits &= ~StyleTextDecorationLine_OVERLINE.bits;
decorations |= StyleTextDecorationLine_OVERLINE;
}
if (StyleTextDecorationLine_LINE_THROUGH & decorMask &
styleText->mTextDecorationLine) {
strikeColor = color;
strikeStyle = style;
decorMask.bits &= ~StyleTextDecorationLine_LINE_THROUGH.bits;
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 != NS_STYLE_TEXT_DECORATION_STYLE_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 != NS_STYLE_TEXT_DECORATION_STYLE_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);
nscolor c = aOverrideColor ? *aOverrideColor : StyleColor()->mColor.ToColor();
ColorPattern color(ToDeviceColor(c));
aRenderingContext.SetColor(Color::FromABGR(c));
nsresult rv = NS_ERROR_FAILURE;
if (mState & NS_FRAME_IS_BIDI) {
presContext->SetBidiEnabled();
nsBidiLevel 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, color);
}
// 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 != NS_STYLE_TEXT_DECORATION_STYLE_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;
}
}
nscoord nsTextBoxFrame::CalculateTitleForWidth(gfxContext& aRenderingContext,
nscoord aWidth) {
DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
if (mTitle.IsEmpty()) {
mCroppedTitle.Truncate();
return 0;
}
RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f);
// see if the text will completely fit in the width given
nscoord titleWidth = nsLayoutUtils::AppUnitWidthOfStringBidi(
mTitle, this, *fm, aRenderingContext);
if (titleWidth <= aWidth) {
mCroppedTitle = mTitle;
if (HasRTLChars(mTitle) ||
StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
AddStateBits(NS_FRAME_IS_BIDI);
}
return titleWidth; // fits, done.
}
const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis();
if (mCropType != CropNone) {
// start with an ellipsis
mCroppedTitle.Assign(kEllipsis);
// see if the width is even smaller than the ellipsis
// if so, clear the text (XXX set as many '.' as we can?).
fm->SetTextRunRTL(false);
titleWidth =
nsLayoutUtils::AppUnitWidthOfString(kEllipsis, *fm, drawTarget);
if (titleWidth > aWidth) {
mCroppedTitle.SetLength(0);
return 0;
}
// if the ellipsis fits perfectly, no use in trying to insert
if (titleWidth == aWidth) return titleWidth;
aWidth -= titleWidth;
} else {
mCroppedTitle.Truncate(0);
titleWidth = 0;
}
using mozilla::unicode::ClusterIterator;
using mozilla::unicode::ClusterReverseIterator;
// ok crop things
switch (mCropType) {
case CropAuto:
case CropNone:
case CropRight: {
ClusterIterator iter(mTitle.Data(), mTitle.Length());
const char16_t* dataBegin = iter;
const char16_t* pos = dataBegin;
nscoord charWidth;
nscoord totalWidth = 0;
while (!iter.AtEnd()) {
iter.Next();
const char16_t* nextPos = iter;
ptrdiff_t length = nextPos - pos;
charWidth =
nsLayoutUtils::AppUnitWidthOfString(pos, length, *fm, drawTarget);
if (totalWidth + charWidth > aWidth) {
break;
}
if (UTF16_CODE_UNIT_IS_BIDI(*pos)) {
AddStateBits(NS_FRAME_IS_BIDI);
}
pos = nextPos;
totalWidth += charWidth;
}
if (pos == dataBegin) {
return titleWidth;
}
// insert what character we can in.
nsAutoString title(mTitle);
title.Truncate(pos - dataBegin);
mCroppedTitle.Insert(title, 0);
} break;
case CropLeft: {
ClusterReverseIterator iter(mTitle.Data(), mTitle.Length());
const char16_t* dataEnd = iter;
const char16_t* prevPos = dataEnd;
nscoord charWidth;
nscoord totalWidth = 0;
while (!iter.AtEnd()) {
iter.Next();
const char16_t* pos = iter;
ptrdiff_t length = prevPos - pos;
charWidth =
nsLayoutUtils::AppUnitWidthOfString(pos, length, *fm, drawTarget);
if (totalWidth + charWidth > aWidth) {
break;
}
if (UTF16_CODE_UNIT_IS_BIDI(*pos)) {
AddStateBits(NS_FRAME_IS_BIDI);
}
prevPos = pos;
totalWidth += charWidth;
}
if (prevPos == dataEnd) {
return titleWidth;
}
nsAutoString copy;
mTitle.Right(copy, dataEnd - prevPos);
mCroppedTitle += copy;
} break;
case CropCenter: {
nscoord stringWidth = nsLayoutUtils::AppUnitWidthOfStringBidi(
mTitle, this, *fm, aRenderingContext);
if (stringWidth <= aWidth) {
// the entire string will fit in the maximum width
mCroppedTitle.Insert(mTitle, 0);
break;
}
// determine how much of the string will fit in the max width
nscoord charWidth = 0;
nscoord totalWidth = 0;
ClusterIterator leftIter(mTitle.Data(), mTitle.Length());
ClusterReverseIterator rightIter(mTitle.Data(), mTitle.Length());
const char16_t* dataBegin = leftIter;
const char16_t* dataEnd = rightIter;
const char16_t* leftPos = dataBegin;
const char16_t* rightPos = dataEnd;
const char16_t* pos;
ptrdiff_t length;
nsAutoString leftString, rightString;
while (leftPos < rightPos) {
leftIter.Next();
pos = leftIter;
length = pos - leftPos;
charWidth = nsLayoutUtils::AppUnitWidthOfString(leftPos, length, *fm,
drawTarget);
if (totalWidth + charWidth > aWidth) {
break;
}
if (UTF16_CODE_UNIT_IS_BIDI(*leftPos)) {
AddStateBits(NS_FRAME_IS_BIDI);
}
leftString.Append(leftPos, length);
leftPos = pos;
totalWidth += charWidth;
if (leftPos >= rightPos) {
break;
}
rightIter.Next();
pos = rightIter;
length = rightPos - pos;
charWidth =
nsLayoutUtils::AppUnitWidthOfString(pos, length, *fm, drawTarget);
if (totalWidth + charWidth > aWidth) {
break;
}
if (UTF16_CODE_UNIT_IS_BIDI(*pos)) {
AddStateBits(NS_FRAME_IS_BIDI);
}
rightString.Insert(pos, 0, length);
rightPos = pos;
totalWidth += charWidth;
}
mCroppedTitle = leftString + kEllipsis + rightString;
} break;
}
return nsLayoutUtils::AppUnitWidthOfStringBidi(mCroppedTitle, this, *fm,
aRenderingContext);
}
#define OLD_ELLIPSIS NS_LITERAL_STRING("...")
// 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(&menuAccessKey);
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(&menuAccessKey);
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->GetInkBoundsForVisualOverflow(
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 visual overflow may
// extend beyond that.
nsRect visualBounds;
visualBounds.UnionRect(scrollBounds, textRect);
nsOverflowAreas overflow(visualBounds, scrollBounds);
if (textStyle->mTextShadow) {
// text-shadow extends our visual but not scrollable bounds
nsRect& vis = overflow.VisualOverflow();
vis.UnionRect(vis,
nsLayoutUtils::GetTextShadowRectsUnion(mTextDrawRect, this));
}
FinishAndStoreOverflow(overflow, GetSize());
return rv;
}
nsRect nsTextBoxFrame::GetComponentAlphaBounds() const {
if (StyleText()->mTextShadow) {
return GetVisualOverflowRectRelativeToSelf();
}
return mTextDrawRect;
}
bool nsTextBoxFrame::ComputesOwnOverflowArea() { 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()) {
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 == NS_STYLE_TEXT_ALIGN_CENTER) {
textRect.IStart(wm) += (outerISize - textRect.ISize(wm)) / 2;
} else if (textStyle->mTextAlign == NS_STYLE_TEXT_ALIGN_END ||
(textStyle->mTextAlign == NS_STYLE_TEXT_ALIGN_LEFT &&
!wm.IsBidiLTR()) ||
(textStyle->mTextAlign == NS_STYLE_TEXT_ALIGN_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);
AddBorderAndPadding(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;
}
}
AddBorderAndPadding(size);
bool widthSet, heightSet;
nsIFrame::AddXULMinSize(aBoxLayoutState, 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(NS_LITERAL_STRING("TextBox"), aResult);
aResult += NS_LITERAL_STRING("[value=") + mTitle + NS_LITERAL_STRING("]");
return NS_OK;
}
#endif
// If you make changes to this function, check its counterparts
// in nsBoxFrame and nsXULLabelFrame
nsresult nsTextBoxFrame::RegUnregAccessKey(bool aDoReg) {
// if we have no content, we can't do anything
if (!mContent) return NS_ERROR_FAILURE;
// check if we have a |control| attribute
// do this check first because few elements have control attributes, and we
// can weed out most of the elements quickly.
// XXXjag a side-effect is that we filter out anonymous <label>s
// in e.g. <menu>, <menuitem>, <button>. These <label>s inherit
// |accesskey| and would otherwise register themselves, overwriting
// the content we really meant to be registered.
if (!mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::control))
return NS_OK;
// see if we even have an access key
nsAutoString accessKey;
mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey,
accessKey);
if (accessKey.IsEmpty()) return NS_OK;
// With a valid PresContext we can get the ESM
// and (un)register the access key
EventStateManager* esm = PresContext()->EventStateManager();
uint32_t key = accessKey.First();
if (aDoReg)
esm->RegisterAccessKey(mContent->AsElement(), key);
else
esm->UnregisterAccessKey(mContent->AsElement(), key);
return NS_OK;
}