Bug 1709347 - Add CanvasRenderingContext2D.reset(). r=lsalzman,webidl,smaug
[gecko.git] / layout / xul / nsTextBoxFrame.cpp
blob6da6bc7b9daa73d66c2564dc068b53d68c0658bb
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=4 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsTextBoxFrame.h"
9 #include "gfx2DGlue.h"
10 #include "gfxUtils.h"
11 #include "mozilla/intl/BidiEmbeddingLevel.h"
12 #include "mozilla/Attributes.h"
13 #include "mozilla/ComputedStyle.h"
14 #include "mozilla/Preferences.h"
15 #include "mozilla/PresShell.h"
16 #include "mozilla/intl/Segmenter.h"
17 #include "mozilla/layers/RenderRootStateManager.h"
18 #include "mozilla/gfx/2D.h"
19 #include "nsFontMetrics.h"
20 #include "nsReadableUtils.h"
21 #include "nsCOMPtr.h"
22 #include "nsCRT.h"
23 #include "nsGkAtoms.h"
24 #include "nsPresContext.h"
25 #include "gfxContext.h"
26 #include "nsIContent.h"
27 #include "nsNameSpaceManager.h"
28 #include "nsBoxLayoutState.h"
29 #include "nsString.h"
30 #include "nsITheme.h"
31 #include "nsUnicharUtils.h"
32 #include "nsContentUtils.h"
33 #include "nsDisplayList.h"
34 #include "nsCSSRendering.h"
35 #include "nsIReflowCallback.h"
36 #include "nsBoxFrame.h"
37 #include "nsLayoutUtils.h"
38 #include "TextDrawTarget.h"
40 #ifdef ACCESSIBILITY
41 # include "nsAccessibilityService.h"
42 #endif
44 #include "nsBidiUtils.h"
45 #include "nsBidiPresUtils.h"
47 using namespace mozilla;
48 using namespace mozilla::gfx;
50 class nsAccessKeyInfo {
51 public:
52 int32_t mAccesskeyIndex;
53 nscoord mBeforeWidth, mAccessWidth, mAccessUnderlineSize, mAccessOffset;
56 bool nsTextBoxFrame::gAlwaysAppendAccessKey = false;
57 bool nsTextBoxFrame::gAccessKeyPrefInitialized = false;
58 bool nsTextBoxFrame::gInsertSeparatorBeforeAccessKey = false;
59 bool nsTextBoxFrame::gInsertSeparatorPrefInitialized = false;
61 nsIFrame* NS_NewTextBoxFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
62 return new (aPresShell) nsTextBoxFrame(aStyle, aPresShell->GetPresContext());
65 NS_IMPL_FRAMEARENA_HELPERS(nsTextBoxFrame)
67 NS_QUERYFRAME_HEAD(nsTextBoxFrame)
68 NS_QUERYFRAME_ENTRY(nsTextBoxFrame)
69 NS_QUERYFRAME_TAIL_INHERITING(nsLeafBoxFrame)
71 nsresult nsTextBoxFrame::AttributeChanged(int32_t aNameSpaceID,
72 nsAtom* aAttribute,
73 int32_t aModType) {
74 bool aResize;
75 bool aRedraw;
77 UpdateAttributes(aAttribute, aResize, aRedraw);
79 if (aResize) {
80 PresShell()->FrameNeedsReflow(
81 this, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY);
82 } else if (aRedraw) {
83 nsBoxLayoutState state(PresContext());
84 XULRedraw(state);
87 return NS_OK;
90 nsTextBoxFrame::nsTextBoxFrame(ComputedStyle* aStyle,
91 nsPresContext* aPresContext)
92 : nsLeafBoxFrame(aStyle, aPresContext, kClassID),
93 mAccessKeyInfo(nullptr),
94 mCropType(CropRight),
95 mAscent(0),
96 mNeedsReflowCallback(false) {
97 MarkIntrinsicISizesDirty();
100 nsTextBoxFrame::~nsTextBoxFrame() { delete mAccessKeyInfo; }
102 void nsTextBoxFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
103 nsIFrame* aPrevInFlow) {
104 nsLeafBoxFrame::Init(aContent, aParent, aPrevInFlow);
106 bool aResize;
107 bool aRedraw;
108 UpdateAttributes(nullptr, aResize, aRedraw); /* update all */
111 bool nsTextBoxFrame::AlwaysAppendAccessKey() {
112 if (!gAccessKeyPrefInitialized) {
113 gAccessKeyPrefInitialized = true;
115 const char* prefName = "intl.menuitems.alwaysappendaccesskeys";
116 nsAutoString val;
117 Preferences::GetLocalizedString(prefName, val);
118 gAlwaysAppendAccessKey = val.EqualsLiteral("true");
120 return gAlwaysAppendAccessKey;
123 bool nsTextBoxFrame::InsertSeparatorBeforeAccessKey() {
124 if (!gInsertSeparatorPrefInitialized) {
125 gInsertSeparatorPrefInitialized = true;
127 const char* prefName = "intl.menuitems.insertseparatorbeforeaccesskeys";
128 nsAutoString val;
129 Preferences::GetLocalizedString(prefName, val);
130 gInsertSeparatorBeforeAccessKey = val.EqualsLiteral("true");
132 return gInsertSeparatorBeforeAccessKey;
135 class nsAsyncAccesskeyUpdate final : public nsIReflowCallback {
136 public:
137 explicit nsAsyncAccesskeyUpdate(nsIFrame* aFrame) : mWeakFrame(aFrame) {}
139 virtual bool ReflowFinished() override {
140 bool shouldFlush = false;
141 nsTextBoxFrame* frame = static_cast<nsTextBoxFrame*>(mWeakFrame.GetFrame());
142 if (frame) {
143 shouldFlush = frame->UpdateAccesskey(mWeakFrame);
145 delete this;
146 return shouldFlush;
149 virtual void ReflowCallbackCanceled() override { delete this; }
151 WeakFrame mWeakFrame;
154 bool nsTextBoxFrame::UpdateAccesskey(WeakFrame& aWeakThis) {
155 nsAutoString accesskey;
156 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey,
157 accesskey);
159 if (!accesskey.Equals(mAccessKey)) {
160 // Need to get clean mTitle.
161 RecomputeTitle();
162 mAccessKey = accesskey;
163 UpdateAccessTitle();
164 PresShell()->FrameNeedsReflow(
165 this, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY);
166 return true;
168 return false;
171 void nsTextBoxFrame::UpdateAttributes(nsAtom* aAttribute, bool& aResize,
172 bool& aRedraw) {
173 bool doUpdateTitle = false;
174 aResize = false;
175 aRedraw = false;
177 if (aAttribute == nullptr || aAttribute == nsGkAtoms::crop) {
178 static dom::Element::AttrValuesArray strings[] = {
179 nsGkAtoms::left, nsGkAtoms::start, nsGkAtoms::center,
180 nsGkAtoms::right, nsGkAtoms::end, nsGkAtoms::none,
181 nullptr};
182 CroppingStyle cropType;
183 switch (mContent->AsElement()->FindAttrValueIn(
184 kNameSpaceID_None, nsGkAtoms::crop, strings, eCaseMatters)) {
185 case 0:
186 case 1:
187 cropType = CropLeft;
188 break;
189 case 2:
190 cropType = CropCenter;
191 break;
192 case 3:
193 case 4:
194 cropType = CropRight;
195 break;
196 case 5:
197 cropType = CropNone;
198 break;
199 default:
200 cropType = CropAuto;
201 break;
204 if (cropType != mCropType) {
205 aResize = true;
206 mCropType = cropType;
210 if (aAttribute == nullptr || aAttribute == nsGkAtoms::value) {
211 RecomputeTitle();
212 doUpdateTitle = true;
215 if (aAttribute == nullptr || aAttribute == nsGkAtoms::accesskey) {
216 mNeedsReflowCallback = true;
217 // Ensure that layout is refreshed and reflow callback called.
218 aResize = true;
221 if (doUpdateTitle) {
222 UpdateAccessTitle();
223 aResize = true;
227 namespace mozilla {
229 class nsDisplayXULTextBox final : public nsPaintedDisplayItem {
230 public:
231 nsDisplayXULTextBox(nsDisplayListBuilder* aBuilder, nsTextBoxFrame* aFrame)
232 : nsPaintedDisplayItem(aBuilder, aFrame) {
233 MOZ_COUNT_CTOR(nsDisplayXULTextBox);
235 #ifdef NS_BUILD_REFCNT_LOGGING
236 MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayXULTextBox)
237 #endif
239 virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
240 virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
241 bool* aSnap) const override;
242 NS_DISPLAY_DECL_NAME("XULTextBox", TYPE_XUL_TEXT_BOX)
244 virtual nsRect GetComponentAlphaBounds(
245 nsDisplayListBuilder* aBuilder) const override;
247 void PaintTextToContext(gfxContext* aCtx, nsPoint aOffset,
248 const nscolor* aColor);
250 virtual bool CreateWebRenderCommands(
251 mozilla::wr::DisplayListBuilder& aBuilder,
252 mozilla::wr::IpcResourceUpdateQueue& aResources,
253 const StackingContextHelper& aSc,
254 mozilla::layers::RenderRootStateManager* aManager,
255 nsDisplayListBuilder* aDisplayListBuilder) override;
258 static void PaintTextShadowCallback(gfxContext* aCtx, nsPoint aShadowOffset,
259 const nscolor& aShadowColor, void* aData) {
260 reinterpret_cast<nsDisplayXULTextBox*>(aData)->PaintTextToContext(
261 aCtx, aShadowOffset, &aShadowColor);
264 void nsDisplayXULTextBox::Paint(nsDisplayListBuilder* aBuilder,
265 gfxContext* aCtx) {
266 // Paint the text shadow before doing any foreground stuff
267 nsRect drawRect =
268 static_cast<nsTextBoxFrame*>(mFrame)->mTextDrawRect + ToReferenceFrame();
269 nsLayoutUtils::PaintTextShadow(mFrame, aCtx, drawRect,
270 GetPaintRect(aBuilder, aCtx),
271 mFrame->StyleText()->mColor.ToColor(),
272 PaintTextShadowCallback, (void*)this);
274 PaintTextToContext(aCtx, nsPoint(0, 0), nullptr);
277 void nsDisplayXULTextBox::PaintTextToContext(gfxContext* aCtx, nsPoint aOffset,
278 const nscolor* aColor) {
279 static_cast<nsTextBoxFrame*>(mFrame)->PaintTitle(
280 *aCtx, mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame(),
281 ToReferenceFrame() + aOffset, aColor);
284 bool nsDisplayXULTextBox::CreateWebRenderCommands(
285 mozilla::wr::DisplayListBuilder& aBuilder,
286 mozilla::wr::IpcResourceUpdateQueue& aResources,
287 const StackingContextHelper& aSc,
288 mozilla::layers::RenderRootStateManager* aManager,
289 nsDisplayListBuilder* aDisplayListBuilder) {
290 bool snap = false;
291 auto bounds = GetBounds(aDisplayListBuilder, &snap);
293 if (bounds.IsEmpty()) {
294 return true;
297 auto appUnitsPerDevPixel = Frame()->PresContext()->AppUnitsPerDevPixel();
298 gfx::Point deviceOffset =
299 LayoutDevicePoint::FromAppUnits(bounds.TopLeft(), appUnitsPerDevPixel)
300 .ToUnknownPoint();
302 RefPtr<mozilla::layout::TextDrawTarget> textDrawer =
303 new mozilla::layout::TextDrawTarget(aBuilder, aResources, aSc, aManager,
304 this, bounds);
305 if (!textDrawer->IsValid()) {
306 return false;
308 gfxContext captureCtx(textDrawer, deviceOffset);
310 Paint(aDisplayListBuilder, &captureCtx);
311 textDrawer->TerminateShadows();
313 return textDrawer->Finish();
316 nsRect nsDisplayXULTextBox::GetBounds(nsDisplayListBuilder* aBuilder,
317 bool* aSnap) const {
318 *aSnap = false;
319 return mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
322 nsRect nsDisplayXULTextBox::GetComponentAlphaBounds(
323 nsDisplayListBuilder* aBuilder) const {
324 return static_cast<nsTextBoxFrame*>(mFrame)->GetComponentAlphaBounds() +
325 ToReferenceFrame();
328 } // namespace mozilla
330 void nsTextBoxFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
331 const nsDisplayListSet& aLists) {
332 if (!IsVisibleForPainting()) return;
334 nsLeafBoxFrame::BuildDisplayList(aBuilder, aLists);
336 aLists.Content()->AppendNewToTop<nsDisplayXULTextBox>(aBuilder, this);
339 void nsTextBoxFrame::PaintTitle(gfxContext& aRenderingContext,
340 const nsRect& aDirtyRect, nsPoint aPt,
341 const nscolor* aOverrideColor) {
342 if (mTitle.IsEmpty()) return;
344 DrawText(aRenderingContext, aDirtyRect, mTextDrawRect + aPt, aOverrideColor);
347 void nsTextBoxFrame::DrawText(gfxContext& aRenderingContext,
348 const nsRect& aDirtyRect, const nsRect& aTextRect,
349 const nscolor* aOverrideColor) {
350 nsPresContext* presContext = PresContext();
351 int32_t appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
352 DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
354 // paint the title
355 nscolor overColor = 0;
356 nscolor underColor = 0;
357 nscolor strikeColor = 0;
358 auto overStyle = StyleTextDecorationStyle::None;
359 auto underStyle = StyleTextDecorationStyle::None;
360 auto strikeStyle = StyleTextDecorationStyle::None;
362 // Begin with no decorations
363 auto decorations = StyleTextDecorationLine::NONE;
364 // A mask of all possible line decorations.
365 auto decorMask = StyleTextDecorationLine::UNDERLINE |
366 StyleTextDecorationLine::OVERLINE |
367 StyleTextDecorationLine::LINE_THROUGH;
369 WritingMode wm = GetWritingMode();
370 bool vertical = wm.IsVertical();
372 nsIFrame* f = this;
373 do { // find decoration colors
374 ComputedStyle* context = f->Style();
375 if (!context->HasTextDecorationLines()) {
376 break;
378 const nsStyleTextReset* styleText = context->StyleTextReset();
380 // a decoration defined here
381 if (decorMask & styleText->mTextDecorationLine) {
382 nscolor color;
383 if (aOverrideColor) {
384 color = *aOverrideColor;
385 } else {
386 color = styleText->mTextDecorationColor.CalcColor(*context);
388 const auto style = styleText->mTextDecorationStyle;
390 if (StyleTextDecorationLine::UNDERLINE & decorMask &
391 styleText->mTextDecorationLine) {
392 underColor = color;
393 underStyle = style;
394 decorMask &= ~StyleTextDecorationLine::UNDERLINE;
395 decorations |= StyleTextDecorationLine::UNDERLINE;
397 if (StyleTextDecorationLine::OVERLINE & decorMask &
398 styleText->mTextDecorationLine) {
399 overColor = color;
400 overStyle = style;
401 decorMask &= ~StyleTextDecorationLine::OVERLINE;
402 decorations |= StyleTextDecorationLine::OVERLINE;
404 if (StyleTextDecorationLine::LINE_THROUGH & decorMask &
405 styleText->mTextDecorationLine) {
406 strikeColor = color;
407 strikeStyle = style;
408 decorMask &= ~StyleTextDecorationLine::LINE_THROUGH;
409 decorations |= StyleTextDecorationLine::LINE_THROUGH;
412 } while (decorMask && (f = nsLayoutUtils::GetParentOrPlaceholderFor(f)));
414 RefPtr<nsFontMetrics> fontMet =
415 nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f);
416 fontMet->SetVertical(wm.IsVertical());
417 fontMet->SetTextOrientation(StyleVisibility()->mTextOrientation);
419 nscoord offset;
420 nscoord size;
421 nscoord ascent = fontMet->MaxAscent();
423 nsPoint baselinePt;
424 if (wm.IsVertical()) {
425 baselinePt.x = presContext->RoundAppUnitsToNearestDevPixels(
426 aTextRect.x + (wm.IsVerticalRL() ? aTextRect.width - ascent : ascent));
427 baselinePt.y = aTextRect.y;
428 } else {
429 baselinePt.x = aTextRect.x;
430 baselinePt.y =
431 presContext->RoundAppUnitsToNearestDevPixels(aTextRect.y + ascent);
434 nsCSSRendering::PaintDecorationLineParams params;
435 params.dirtyRect = ToRect(presContext->AppUnitsToGfxUnits(aDirtyRect));
436 params.pt = Point(presContext->AppUnitsToGfxUnits(aTextRect.x),
437 presContext->AppUnitsToGfxUnits(aTextRect.y));
438 params.icoordInFrame =
439 Float(PresContext()->AppUnitsToGfxUnits(mTextDrawRect.x));
440 params.lineSize = Size(presContext->AppUnitsToGfxUnits(aTextRect.width), 0);
441 params.ascent = presContext->AppUnitsToGfxUnits(ascent);
442 params.vertical = vertical;
444 // XXX todo: vertical-mode support for decorations not tested yet,
445 // probably won't be positioned correctly
447 // Underlines are drawn before overlines, and both before the text
448 // itself, per http://www.w3.org/TR/CSS21/zindex.html point 7.2.1.4.1.1.
449 // (We don't apply this rule to the access-key underline because we only
450 // find out where that is as a side effect of drawing the text, in the
451 // general case -- see below.)
452 if (decorations & (StyleTextDecorationLine::OVERLINE |
453 StyleTextDecorationLine::UNDERLINE)) {
454 fontMet->GetUnderline(offset, size);
455 params.lineSize.height = presContext->AppUnitsToGfxUnits(size);
456 if ((decorations & StyleTextDecorationLine::UNDERLINE) &&
457 underStyle != StyleTextDecorationStyle::None) {
458 params.color = underColor;
459 params.offset = presContext->AppUnitsToGfxUnits(offset);
460 params.decoration = StyleTextDecorationLine::UNDERLINE;
461 params.style = underStyle;
462 nsCSSRendering::PaintDecorationLine(this, *drawTarget, params);
464 if ((decorations & StyleTextDecorationLine::OVERLINE) &&
465 overStyle != StyleTextDecorationStyle::None) {
466 params.color = overColor;
467 params.offset = params.ascent;
468 params.decoration = StyleTextDecorationLine::OVERLINE;
469 params.style = overStyle;
470 nsCSSRendering::PaintDecorationLine(this, *drawTarget, params);
474 UniquePtr<gfxContext> refContext =
475 PresShell()->CreateReferenceRenderingContext();
476 DrawTarget* refDrawTarget = refContext->GetDrawTarget();
478 CalculateUnderline(refDrawTarget, *fontMet);
480 DeviceColor color = ToDeviceColor(
481 aOverrideColor ? *aOverrideColor : StyleText()->mColor.ToColor());
482 ColorPattern colorPattern(color);
483 aRenderingContext.SetDeviceColor(color);
485 nsresult rv = NS_ERROR_FAILURE;
487 if (HasAnyStateBits(NS_FRAME_IS_BIDI)) {
488 presContext->SetBidiEnabled();
489 mozilla::intl::BidiEmbeddingLevel level =
490 nsBidiPresUtils::BidiLevelFromStyle(Style());
491 if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) {
492 // We let the RenderText function calculate the mnemonic's
493 // underline position for us.
494 nsBidiPositionResolve posResolve;
495 posResolve.logicalIndex = mAccessKeyInfo->mAccesskeyIndex;
496 rv = nsBidiPresUtils::RenderText(
497 mCroppedTitle.get(), mCroppedTitle.Length(), level, presContext,
498 aRenderingContext, refDrawTarget, *fontMet, baselinePt.x,
499 baselinePt.y, &posResolve, 1);
500 mAccessKeyInfo->mBeforeWidth = posResolve.visualLeftTwips;
501 mAccessKeyInfo->mAccessWidth = posResolve.visualWidth;
502 } else {
503 rv = nsBidiPresUtils::RenderText(
504 mCroppedTitle.get(), mCroppedTitle.Length(), level, presContext,
505 aRenderingContext, refDrawTarget, *fontMet, baselinePt.x,
506 baselinePt.y);
509 if (NS_FAILED(rv)) {
510 fontMet->SetTextRunRTL(false);
512 if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) {
513 // In the simple (non-BiDi) case, we calculate the mnemonic's
514 // underline position by getting the text metric.
515 // XXX are attribute values always two byte?
516 if (mAccessKeyInfo->mAccesskeyIndex > 0)
517 mAccessKeyInfo->mBeforeWidth = nsLayoutUtils::AppUnitWidthOfString(
518 mCroppedTitle.get(), mAccessKeyInfo->mAccesskeyIndex, *fontMet,
519 refDrawTarget);
520 else
521 mAccessKeyInfo->mBeforeWidth = 0;
524 fontMet->DrawString(mCroppedTitle.get(), mCroppedTitle.Length(),
525 baselinePt.x, baselinePt.y, &aRenderingContext,
526 refDrawTarget);
529 if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) {
530 nsRect r(aTextRect.x + mAccessKeyInfo->mBeforeWidth,
531 aTextRect.y + mAccessKeyInfo->mAccessOffset,
532 mAccessKeyInfo->mAccessWidth,
533 mAccessKeyInfo->mAccessUnderlineSize);
534 Rect devPxRect = NSRectToSnappedRect(r, appUnitsPerDevPixel, *drawTarget);
535 drawTarget->FillRect(devPxRect, colorPattern);
538 // Strikeout is drawn on top of the text, per
539 // http://www.w3.org/TR/CSS21/zindex.html point 7.2.1.4.1.1.
540 if ((decorations & StyleTextDecorationLine::LINE_THROUGH) &&
541 strikeStyle != StyleTextDecorationStyle::None) {
542 fontMet->GetStrikeout(offset, size);
543 params.color = strikeColor;
544 params.lineSize.height = presContext->AppUnitsToGfxUnits(size);
545 params.offset = presContext->AppUnitsToGfxUnits(offset);
546 params.decoration = StyleTextDecorationLine::LINE_THROUGH;
547 params.style = strikeStyle;
548 nsCSSRendering::PaintDecorationLine(this, *drawTarget, params);
552 void nsTextBoxFrame::CalculateUnderline(DrawTarget* aDrawTarget,
553 nsFontMetrics& aFontMetrics) {
554 if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) {
555 // Calculate all fields of mAccessKeyInfo which
556 // are the same for both BiDi and non-BiDi frames.
557 const char16_t* titleString = mCroppedTitle.get();
558 aFontMetrics.SetTextRunRTL(false);
559 mAccessKeyInfo->mAccessWidth = nsLayoutUtils::AppUnitWidthOfString(
560 titleString[mAccessKeyInfo->mAccesskeyIndex], aFontMetrics,
561 aDrawTarget);
563 nscoord offset, baseline;
564 aFontMetrics.GetUnderline(offset, mAccessKeyInfo->mAccessUnderlineSize);
565 baseline = aFontMetrics.MaxAscent();
566 mAccessKeyInfo->mAccessOffset = baseline - offset;
570 void nsTextBoxFrame::CropStringForWidth(nsAString& aText,
571 gfxContext& aRenderingContext,
572 nsFontMetrics& aFontMetrics,
573 nscoord aWidth,
574 CroppingStyle aCropType) {
575 DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
577 // See if the width is even smaller than the ellipsis
578 // If so, clear the text completely.
579 const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis();
580 aFontMetrics.SetTextRunRTL(false);
581 nscoord ellipsisWidth =
582 nsLayoutUtils::AppUnitWidthOfString(kEllipsis, aFontMetrics, drawTarget);
584 if (ellipsisWidth > aWidth) {
585 aText.Truncate(0);
586 return;
588 if (ellipsisWidth == aWidth) {
589 aText.Assign(kEllipsis);
590 return;
593 // We will be drawing an ellipsis, thank you very much.
594 // Subtract out the required width of the ellipsis.
595 // This is the total remaining width we have to play with.
596 aWidth -= ellipsisWidth;
598 using mozilla::intl::GraphemeClusterBreakIteratorUtf16;
599 using mozilla::intl::GraphemeClusterBreakReverseIteratorUtf16;
601 // Now we crop. This is quite basic: it will not be really accurate in the
602 // presence of complex scripts with contextual shaping, etc., as it measures
603 // each grapheme cluster in isolation, not in its proper context.
604 switch (aCropType) {
605 case CropAuto:
606 case CropNone:
607 case CropRight: {
608 const Span text(aText);
609 GraphemeClusterBreakIteratorUtf16 iter(text);
610 uint32_t pos = 0;
611 nscoord totalWidth = 0;
613 while (Maybe<uint32_t> nextPos = iter.Next()) {
614 const nscoord charWidth = nsLayoutUtils::AppUnitWidthOfString(
615 text.FromTo(pos, *nextPos), aFontMetrics, drawTarget);
616 if (totalWidth + charWidth > aWidth) {
617 break;
619 pos = *nextPos;
620 totalWidth += charWidth;
623 if (pos < aText.Length()) {
624 aText.Replace(pos, aText.Length() - pos, kEllipsis);
626 } break;
628 case CropLeft: {
629 const Span text(aText);
630 GraphemeClusterBreakReverseIteratorUtf16 iter(text);
631 uint32_t pos = text.Length();
632 nscoord totalWidth = 0;
634 // nextPos is decreasing since we use a reverse iterator.
635 while (Maybe<uint32_t> nextPos = iter.Next()) {
636 const nscoord charWidth = nsLayoutUtils::AppUnitWidthOfString(
637 text.FromTo(*nextPos, pos), aFontMetrics, drawTarget);
638 if (totalWidth + charWidth > aWidth) {
639 break;
642 pos = *nextPos;
643 totalWidth += charWidth;
646 if (pos > 0) {
647 aText.Replace(0, pos, kEllipsis);
649 } break;
651 case CropCenter: {
652 const Span text(aText);
653 nscoord totalWidth = 0;
654 GraphemeClusterBreakIteratorUtf16 leftIter(text);
655 GraphemeClusterBreakReverseIteratorUtf16 rightIter(text);
656 uint32_t leftPos = 0;
657 uint32_t rightPos = text.Length();
659 while (leftPos < rightPos) {
660 Maybe<uint32_t> nextPos = leftIter.Next();
661 nscoord charWidth = nsLayoutUtils::AppUnitWidthOfString(
662 text.FromTo(leftPos, *nextPos), aFontMetrics, drawTarget);
663 if (totalWidth + charWidth > aWidth) {
664 break;
667 leftPos = *nextPos;
668 totalWidth += charWidth;
670 if (leftPos >= rightPos) {
671 break;
674 nextPos = rightIter.Next();
675 charWidth = nsLayoutUtils::AppUnitWidthOfString(
676 text.FromTo(*nextPos, rightPos), aFontMetrics, drawTarget);
677 if (totalWidth + charWidth > aWidth) {
678 break;
681 rightPos = *nextPos;
682 totalWidth += charWidth;
685 if (leftPos < rightPos) {
686 aText.Replace(leftPos, rightPos - leftPos, kEllipsis);
688 } break;
692 nscoord nsTextBoxFrame::CalculateTitleForWidth(gfxContext& aRenderingContext,
693 nscoord aMaxWidth) {
694 if (mTitle.IsEmpty()) {
695 mCroppedTitle.Truncate();
696 return 0;
699 RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f);
701 // See if the text needs to be cropped to fit in the width given.
702 mCroppedTitle = mTitle;
703 nscoord width = nsLayoutUtils::AppUnitWidthOfStringBidi(
704 mCroppedTitle, this, *fm, aRenderingContext);
705 if (width > aMaxWidth && mCropType != CropNone) {
706 CropStringForWidth(mCroppedTitle, aRenderingContext, *fm, aMaxWidth,
707 mCropType);
708 width = nsLayoutUtils::AppUnitWidthOfStringBidi(mCroppedTitle, this, *fm,
709 aRenderingContext);
712 if (StyleVisibility()->mDirection == StyleDirection::Rtl ||
713 HasRTLChars(mCroppedTitle)) {
714 AddStateBits(NS_FRAME_IS_BIDI);
717 return width;
720 #define OLD_ELLIPSIS u"..."_ns
722 // the following block is to append the accesskey to mTitle if there is an
723 // accesskey but the mTitle doesn't have the character
724 void nsTextBoxFrame::UpdateAccessTitle() {
726 * Note that if you change appending access key label spec,
727 * you need to maintain same logic in following methods. See bug 324159.
728 * toolkit/components/prompts/src/CommonDialog.jsm (setLabelForNode)
729 * toolkit/content/widgets/text.js (formatAccessKey)
731 uint32_t menuAccessKey = LookAndFeel::GetMenuAccessKey();
732 if (!menuAccessKey || mAccessKey.IsEmpty()) {
733 return;
736 if (!AlwaysAppendAccessKey() &&
737 FindInReadable(mAccessKey, mTitle, nsCaseInsensitiveStringComparator))
738 return;
740 nsAutoString accessKeyLabel;
741 accessKeyLabel += '(';
742 accessKeyLabel += mAccessKey;
743 ToUpperCase(accessKeyLabel);
744 accessKeyLabel += ')';
746 if (mTitle.IsEmpty()) {
747 mTitle = accessKeyLabel;
748 return;
751 if (StringEndsWith(mTitle, accessKeyLabel)) {
752 // Never append another "(X)" if the title already ends with "(X)".
753 return;
756 const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis();
757 uint32_t offset = mTitle.Length();
758 if (StringEndsWith(mTitle, kEllipsis)) {
759 offset -= kEllipsis.Length();
760 } else if (StringEndsWith(mTitle, OLD_ELLIPSIS)) {
761 // Try to check with our old ellipsis (for old addons)
762 offset -= OLD_ELLIPSIS.Length();
763 } else {
764 // Try to check with
765 // our default ellipsis (for non-localized addons) or ':'
766 const char16_t kLastChar = mTitle.Last();
767 if (kLastChar == char16_t(0x2026) || kLastChar == char16_t(':')) offset--;
770 if (InsertSeparatorBeforeAccessKey() && offset > 0 &&
771 !NS_IS_SPACE(mTitle[offset - 1])) {
772 mTitle.Insert(' ', offset);
773 offset++;
776 mTitle.Insert(accessKeyLabel, offset);
779 void nsTextBoxFrame::UpdateAccessIndex() {
780 uint32_t menuAccessKey = LookAndFeel::GetMenuAccessKey();
781 if (!menuAccessKey) {
782 return;
784 if (mAccessKey.IsEmpty()) {
785 if (mAccessKeyInfo) {
786 delete mAccessKeyInfo;
787 mAccessKeyInfo = nullptr;
789 return;
791 if (!mAccessKeyInfo) {
792 mAccessKeyInfo = new nsAccessKeyInfo();
795 nsAString::const_iterator start, end;
797 mCroppedTitle.BeginReading(start);
798 mCroppedTitle.EndReading(end);
800 // remember the beginning of the string
801 nsAString::const_iterator originalStart = start;
803 bool found;
804 if (!AlwaysAppendAccessKey()) {
805 // not appending access key - do case-sensitive search
806 // first
807 found = FindInReadable(mAccessKey, start, end);
808 if (!found) {
809 // didn't find it - perform a case-insensitive search
810 start = originalStart;
811 found = FindInReadable(mAccessKey, start, end,
812 nsCaseInsensitiveStringComparator);
814 } else {
815 found = RFindInReadable(mAccessKey, start, end,
816 nsCaseInsensitiveStringComparator);
819 if (found)
820 mAccessKeyInfo->mAccesskeyIndex = Distance(originalStart, start);
821 else
822 mAccessKeyInfo->mAccesskeyIndex = kNotFound;
825 void nsTextBoxFrame::RecomputeTitle() {
826 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::value, mTitle);
828 // This doesn't handle language-specific uppercasing/lowercasing
829 // rules, unlike textruns.
830 StyleTextTransform textTransform = StyleText()->mTextTransform;
831 if (textTransform.case_ == StyleTextTransformCase::Uppercase) {
832 ToUpperCase(mTitle);
833 } else if (textTransform.case_ == StyleTextTransformCase::Lowercase) {
834 ToLowerCase(mTitle);
836 // We can't handle StyleTextTransformCase::Capitalize because we
837 // have no clue about word boundaries here. We also don't handle
838 // the full-width or full-size-kana transforms.
841 void nsTextBoxFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
842 nsLeafBoxFrame::DidSetComputedStyle(aOldComputedStyle);
844 if (!aOldComputedStyle) {
845 // We're just being initialized
846 return;
849 const nsStyleText* oldTextStyle = aOldComputedStyle->StyleText();
850 if (oldTextStyle->mTextTransform != StyleText()->mTextTransform) {
851 RecomputeTitle();
852 UpdateAccessTitle();
856 NS_IMETHODIMP
857 nsTextBoxFrame::DoXULLayout(nsBoxLayoutState& aBoxLayoutState) {
858 if (mNeedsReflowCallback) {
859 nsIReflowCallback* cb = new nsAsyncAccesskeyUpdate(this);
860 if (cb) {
861 PresShell()->PostReflowCallback(cb);
863 mNeedsReflowCallback = false;
866 nsresult rv = nsLeafBoxFrame::DoXULLayout(aBoxLayoutState);
868 CalcDrawRect(*aBoxLayoutState.GetRenderingContext());
870 const nsStyleText* textStyle = StyleText();
872 nsRect scrollBounds(nsPoint(0, 0), GetSize());
873 nsRect textRect = mTextDrawRect;
875 RefPtr<nsFontMetrics> fontMet =
876 nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f);
877 nsBoundingMetrics metrics = fontMet->GetInkBoundsForInkOverflow(
878 mCroppedTitle.get(), mCroppedTitle.Length(),
879 aBoxLayoutState.GetRenderingContext()->GetDrawTarget());
881 WritingMode wm = GetWritingMode();
882 LogicalRect tr(wm, textRect, GetSize());
884 tr.IStart(wm) -= metrics.leftBearing;
885 tr.ISize(wm) = metrics.width;
886 // In DrawText() we always draw with the baseline at MaxAscent() (relative to
887 // mTextDrawRect),
888 tr.BStart(wm) += fontMet->MaxAscent() - metrics.ascent;
889 tr.BSize(wm) = metrics.ascent + metrics.descent;
891 textRect = tr.GetPhysicalRect(wm, GetSize());
893 // Our scrollable overflow is our bounds; our ink overflow may
894 // extend beyond that.
895 nsRect visualBounds;
896 visualBounds.UnionRect(scrollBounds, textRect);
897 OverflowAreas overflow(visualBounds, scrollBounds);
899 if (textStyle->HasTextShadow()) {
900 // text-shadow extends our visual but not scrollable bounds
901 nsRect& vis = overflow.InkOverflow();
902 vis.UnionRect(vis,
903 nsLayoutUtils::GetTextShadowRectsUnion(mTextDrawRect, this));
905 FinishAndStoreOverflow(overflow, GetSize());
907 return rv;
910 nsRect nsTextBoxFrame::GetComponentAlphaBounds() const {
911 if (StyleText()->HasTextShadow()) {
912 return InkOverflowRectRelativeToSelf();
914 return mTextDrawRect;
917 bool nsTextBoxFrame::XULComputesOwnOverflowArea() { return true; }
919 /* virtual */
920 void nsTextBoxFrame::MarkIntrinsicISizesDirty() {
921 mNeedsRecalc = true;
922 nsLeafBoxFrame::MarkIntrinsicISizesDirty();
925 void nsTextBoxFrame::GetTextSize(gfxContext& aRenderingContext,
926 const nsString& aString, nsSize& aSize,
927 nscoord& aAscent) {
928 RefPtr<nsFontMetrics> fontMet =
929 nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f);
930 aSize.height = fontMet->MaxHeight();
931 aSize.width = nsLayoutUtils::AppUnitWidthOfStringBidi(aString, this, *fontMet,
932 aRenderingContext);
933 aAscent = fontMet->MaxAscent();
936 void nsTextBoxFrame::CalcTextSize(nsBoxLayoutState& aBoxLayoutState) {
937 if (mNeedsRecalc) {
938 nsSize size;
939 gfxContext* rendContext = aBoxLayoutState.GetRenderingContext();
940 if (rendContext) {
941 GetTextSize(*rendContext, mTitle, size, mAscent);
942 if (GetWritingMode().IsVertical()) {
943 std::swap(size.width, size.height);
945 mTextSize = size;
946 mNeedsRecalc = false;
951 void nsTextBoxFrame::CalcDrawRect(gfxContext& aRenderingContext) {
952 WritingMode wm = GetWritingMode();
954 LogicalRect textRect(wm, LogicalPoint(wm, 0, 0), GetLogicalSize(wm));
955 nsMargin borderPadding;
956 GetXULBorderAndPadding(borderPadding);
957 textRect.Deflate(wm, LogicalMargin(wm, borderPadding));
959 // determine (cropped) title and underline position
960 // determine (cropped) title which fits in aRect, and its width
961 // (where "width" is the text measure along its baseline, i.e. actually
962 // a physical height in vertical writing modes)
963 nscoord titleWidth =
964 CalculateTitleForWidth(aRenderingContext, textRect.ISize(wm));
966 #ifdef ACCESSIBILITY
967 // Make sure to update the accessible tree in case when cropped title is
968 // changed.
969 nsAccessibilityService* accService = GetAccService();
970 if (accService) {
971 accService->UpdateLabelValue(PresShell(), mContent, mCroppedTitle);
973 #endif
975 // determine if and at which position to put the underline
976 UpdateAccessIndex();
978 // make the rect as small as our (cropped) text.
979 nscoord outerISize = textRect.ISize(wm);
980 textRect.ISize(wm) = titleWidth;
982 // Align our text within the overall rect by checking our text-align property.
983 const nsStyleText* textStyle = StyleText();
984 if (textStyle->mTextAlign == StyleTextAlign::Center) {
985 textRect.IStart(wm) += (outerISize - textRect.ISize(wm)) / 2;
986 } else if (textStyle->mTextAlign == StyleTextAlign::End ||
987 (textStyle->mTextAlign == StyleTextAlign::Left &&
988 wm.IsBidiRTL()) ||
989 (textStyle->mTextAlign == StyleTextAlign::Right &&
990 wm.IsBidiLTR())) {
991 textRect.IStart(wm) += (outerISize - textRect.ISize(wm));
994 mTextDrawRect = textRect.GetPhysicalRect(wm, GetSize());
998 * Ok return our dimensions
1000 nsSize nsTextBoxFrame::GetXULPrefSize(nsBoxLayoutState& aBoxLayoutState) {
1001 CalcTextSize(aBoxLayoutState);
1003 nsSize size = mTextSize;
1004 DISPLAY_PREF_SIZE(this, size);
1006 AddXULBorderAndPadding(size);
1007 bool widthSet, heightSet;
1008 nsIFrame::AddXULPrefSize(this, size, widthSet, heightSet);
1010 return size;
1014 * Ok return our dimensions
1016 nsSize nsTextBoxFrame::GetXULMinSize(nsBoxLayoutState& aBoxLayoutState) {
1017 CalcTextSize(aBoxLayoutState);
1019 nsSize size = mTextSize;
1020 DISPLAY_MIN_SIZE(this, size);
1022 // if there is cropping our min width becomes our border and padding
1023 if (mCropType != CropNone && mCropType != CropAuto) {
1024 if (GetWritingMode().IsVertical()) {
1025 size.height = 0;
1026 } else {
1027 size.width = 0;
1031 AddXULBorderAndPadding(size);
1032 bool widthSet, heightSet;
1033 nsIFrame::AddXULMinSize(this, size, widthSet, heightSet);
1035 return size;
1038 nscoord nsTextBoxFrame::GetXULBoxAscent(nsBoxLayoutState& aBoxLayoutState) {
1039 CalcTextSize(aBoxLayoutState);
1041 nscoord ascent = mAscent;
1043 nsMargin m(0, 0, 0, 0);
1044 GetXULBorderAndPadding(m);
1046 WritingMode wm = GetWritingMode();
1047 ascent += LogicalMargin(wm, m).BStart(wm);
1049 return ascent;
1052 #ifdef DEBUG_FRAME_DUMP
1053 nsresult nsTextBoxFrame::GetFrameName(nsAString& aResult) const {
1054 MakeFrameName(u"TextBox"_ns, aResult);
1055 aResult += u"[value="_ns + mTitle + u"]"_ns;
1056 return NS_OK;
1058 #endif