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/. */
6 
7 #include "nsTextBoxFrame.h"
8 
9 #include "gfx2DGlue.h"
10 #include "gfxUtils.h"
11 #include "mozilla/Attributes.h"
12 #include "mozilla/ComputedStyle.h"
13 #include "mozilla/Preferences.h"
14 #include "mozilla/PresShell.h"
15 #include "mozilla/layers/RenderRootStateManager.h"
16 #include "mozilla/gfx/2D.h"
17 #include "nsFontMetrics.h"
18 #include "nsReadableUtils.h"
19 #include "nsCOMPtr.h"
20 #include "nsCRT.h"
21 #include "nsGkAtoms.h"
22 #include "nsPresContext.h"
23 #include "gfxContext.h"
24 #include "nsIContent.h"
25 #include "nsNameSpaceManager.h"
26 #include "nsBoxLayoutState.h"
27 #include "nsMenuBarListener.h"
28 #include "nsString.h"
29 #include "nsITheme.h"
30 #include "nsUnicharUtils.h"
31 #include "nsContentUtils.h"
32 #include "nsDisplayList.h"
33 #include "nsCSSRendering.h"
34 #include "nsIReflowCallback.h"
35 #include "nsBoxFrame.h"
36 #include "nsLayoutUtils.h"
37 #include "nsUnicodeProperties.h"
38 #include "TextDrawTarget.h"
39 
40 #ifdef ACCESSIBILITY
41 #  include "nsAccessibilityService.h"
42 #endif
43 
44 #include "nsBidiUtils.h"
45 #include "nsBidiPresUtils.h"
46 
47 using namespace mozilla;
48 using namespace mozilla::gfx;
49 
50 class nsAccessKeyInfo {
51  public:
52   int32_t mAccesskeyIndex;
53   nscoord mBeforeWidth, mAccessWidth, mAccessUnderlineSize, mAccessOffset;
54 };
55 
56 bool nsTextBoxFrame::gAlwaysAppendAccessKey = false;
57 bool nsTextBoxFrame::gAccessKeyPrefInitialized = false;
58 bool nsTextBoxFrame::gInsertSeparatorBeforeAccessKey = false;
59 bool nsTextBoxFrame::gInsertSeparatorPrefInitialized = false;
60 
NS_NewTextBoxFrame(PresShell * aPresShell,ComputedStyle * aStyle)61 nsIFrame* NS_NewTextBoxFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
62   return new (aPresShell) nsTextBoxFrame(aStyle, aPresShell->GetPresContext());
63 }
64 
65 NS_IMPL_FRAMEARENA_HELPERS(nsTextBoxFrame)
66 
NS_QUERYFRAME_HEAD(nsTextBoxFrame)67 NS_QUERYFRAME_HEAD(nsTextBoxFrame)
68   NS_QUERYFRAME_ENTRY(nsTextBoxFrame)
69 NS_QUERYFRAME_TAIL_INHERITING(nsLeafBoxFrame)
70 
71 nsresult nsTextBoxFrame::AttributeChanged(int32_t aNameSpaceID,
72                                           nsAtom* aAttribute,
73                                           int32_t aModType) {
74   bool aResize;
75   bool aRedraw;
76 
77   UpdateAttributes(aAttribute, aResize, aRedraw);
78 
79   if (aResize) {
80     PresShell()->FrameNeedsReflow(this, IntrinsicDirty::StyleChange,
81                                   NS_FRAME_IS_DIRTY);
82   } else if (aRedraw) {
83     nsBoxLayoutState state(PresContext());
84     XULRedraw(state);
85   }
86 
87   return NS_OK;
88 }
89 
nsTextBoxFrame(ComputedStyle * aStyle,nsPresContext * aPresContext)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();
98 }
99 
~nsTextBoxFrame()100 nsTextBoxFrame::~nsTextBoxFrame() { delete mAccessKeyInfo; }
101 
Init(nsIContent * aContent,nsContainerFrame * aParent,nsIFrame * aPrevInFlow)102 void nsTextBoxFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
103                           nsIFrame* aPrevInFlow) {
104   nsLeafBoxFrame::Init(aContent, aParent, aPrevInFlow);
105 
106   bool aResize;
107   bool aRedraw;
108   UpdateAttributes(nullptr, aResize, aRedraw); /* update all */
109 }
110 
AlwaysAppendAccessKey()111 bool nsTextBoxFrame::AlwaysAppendAccessKey() {
112   if (!gAccessKeyPrefInitialized) {
113     gAccessKeyPrefInitialized = true;
114 
115     const char* prefName = "intl.menuitems.alwaysappendaccesskeys";
116     nsAutoString val;
117     Preferences::GetLocalizedString(prefName, val);
118     gAlwaysAppendAccessKey = val.EqualsLiteral("true");
119   }
120   return gAlwaysAppendAccessKey;
121 }
122 
InsertSeparatorBeforeAccessKey()123 bool nsTextBoxFrame::InsertSeparatorBeforeAccessKey() {
124   if (!gInsertSeparatorPrefInitialized) {
125     gInsertSeparatorPrefInitialized = true;
126 
127     const char* prefName = "intl.menuitems.insertseparatorbeforeaccesskeys";
128     nsAutoString val;
129     Preferences::GetLocalizedString(prefName, val);
130     gInsertSeparatorBeforeAccessKey = val.EqualsLiteral("true");
131   }
132   return gInsertSeparatorBeforeAccessKey;
133 }
134 
135 class nsAsyncAccesskeyUpdate final : public nsIReflowCallback {
136  public:
nsAsyncAccesskeyUpdate(nsIFrame * aFrame)137   explicit nsAsyncAccesskeyUpdate(nsIFrame* aFrame) : mWeakFrame(aFrame) {}
138 
ReflowFinished()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);
144     }
145     delete this;
146     return shouldFlush;
147   }
148 
ReflowCallbackCanceled()149   virtual void ReflowCallbackCanceled() override { delete this; }
150 
151   WeakFrame mWeakFrame;
152 };
153 
UpdateAccesskey(WeakFrame & aWeakThis)154 bool nsTextBoxFrame::UpdateAccesskey(WeakFrame& aWeakThis) {
155   nsAutoString accesskey;
156   mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey,
157                                  accesskey);
158 
159   if (!accesskey.Equals(mAccessKey)) {
160     // Need to get clean mTitle.
161     RecomputeTitle();
162     mAccessKey = accesskey;
163     UpdateAccessTitle();
164     PresShell()->FrameNeedsReflow(this, IntrinsicDirty::StyleChange,
165                                   NS_FRAME_IS_DIRTY);
166     return true;
167   }
168   return false;
169 }
170 
UpdateAttributes(nsAtom * aAttribute,bool & aResize,bool & aRedraw)171 void nsTextBoxFrame::UpdateAttributes(nsAtom* aAttribute, bool& aResize,
172                                       bool& aRedraw) {
173   bool doUpdateTitle = false;
174   aResize = false;
175   aRedraw = false;
176 
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;
202     }
203 
204     if (cropType != mCropType) {
205       aResize = true;
206       mCropType = cropType;
207     }
208   }
209 
210   if (aAttribute == nullptr || aAttribute == nsGkAtoms::value) {
211     RecomputeTitle();
212     doUpdateTitle = true;
213   }
214 
215   if (aAttribute == nullptr || aAttribute == nsGkAtoms::accesskey) {
216     mNeedsReflowCallback = true;
217     // Ensure that layout is refreshed and reflow callback called.
218     aResize = true;
219   }
220 
221   if (doUpdateTitle) {
222     UpdateAccessTitle();
223     aResize = true;
224   }
225 }
226 
227 class nsDisplayXULTextBox final : public nsPaintedDisplayItem {
228  public:
nsDisplayXULTextBox(nsDisplayListBuilder * aBuilder,nsTextBoxFrame * aFrame)229   nsDisplayXULTextBox(nsDisplayListBuilder* aBuilder, nsTextBoxFrame* aFrame)
230       : nsPaintedDisplayItem(aBuilder, aFrame) {
231     MOZ_COUNT_CTOR(nsDisplayXULTextBox);
232   }
233 #ifdef NS_BUILD_REFCNT_LOGGING
234   MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayXULTextBox)
235 #endif
236 
237   virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
238   virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
239                            bool* aSnap) const override;
240   NS_DISPLAY_DECL_NAME("XULTextBox", TYPE_XUL_TEXT_BOX)
241 
242   virtual nsRect GetComponentAlphaBounds(
243       nsDisplayListBuilder* aBuilder) const override;
244 
245   void PaintTextToContext(gfxContext* aCtx, nsPoint aOffset,
246                           const nscolor* aColor);
247 
248   virtual bool CreateWebRenderCommands(
249       mozilla::wr::DisplayListBuilder& aBuilder,
250       mozilla::wr::IpcResourceUpdateQueue& aResources,
251       const StackingContextHelper& aSc,
252       mozilla::layers::RenderRootStateManager* aManager,
253       nsDisplayListBuilder* aDisplayListBuilder) override;
254 };
255 
PaintTextShadowCallback(gfxContext * aCtx,nsPoint aShadowOffset,const nscolor & aShadowColor,void * aData)256 static void PaintTextShadowCallback(gfxContext* aCtx, nsPoint aShadowOffset,
257                                     const nscolor& aShadowColor, void* aData) {
258   reinterpret_cast<nsDisplayXULTextBox*>(aData)->PaintTextToContext(
259       aCtx, aShadowOffset, &aShadowColor);
260 }
261 
Paint(nsDisplayListBuilder * aBuilder,gfxContext * aCtx)262 void nsDisplayXULTextBox::Paint(nsDisplayListBuilder* aBuilder,
263                                 gfxContext* aCtx) {
264   DrawTargetAutoDisableSubpixelAntialiasing disable(aCtx->GetDrawTarget(),
265                                                     IsSubpixelAADisabled());
266 
267   // Paint the text shadow before doing any foreground stuff
268   nsRect drawRect =
269       static_cast<nsTextBoxFrame*>(mFrame)->mTextDrawRect + ToReferenceFrame();
270   nsLayoutUtils::PaintTextShadow(mFrame, aCtx, drawRect, GetPaintRect(),
271                                  mFrame->StyleText()->mColor.ToColor(),
272                                  PaintTextShadowCallback, (void*)this);
273 
274   PaintTextToContext(aCtx, nsPoint(0, 0), nullptr);
275 }
276 
PaintTextToContext(gfxContext * aCtx,nsPoint aOffset,const nscolor * aColor)277 void nsDisplayXULTextBox::PaintTextToContext(gfxContext* aCtx, nsPoint aOffset,
278                                              const nscolor* aColor) {
279   static_cast<nsTextBoxFrame*>(mFrame)->PaintTitle(
280       *aCtx, GetPaintRect(), ToReferenceFrame() + aOffset, aColor);
281 }
282 
CreateWebRenderCommands(mozilla::wr::DisplayListBuilder & aBuilder,mozilla::wr::IpcResourceUpdateQueue & aResources,const StackingContextHelper & aSc,mozilla::layers::RenderRootStateManager * aManager,nsDisplayListBuilder * aDisplayListBuilder)283 bool nsDisplayXULTextBox::CreateWebRenderCommands(
284     mozilla::wr::DisplayListBuilder& aBuilder,
285     mozilla::wr::IpcResourceUpdateQueue& aResources,
286     const StackingContextHelper& aSc,
287     mozilla::layers::RenderRootStateManager* aManager,
288     nsDisplayListBuilder* aDisplayListBuilder) {
289   bool snap = false;
290   auto bounds = GetBounds(aDisplayListBuilder, &snap);
291 
292   if (bounds.IsEmpty()) {
293     return true;
294   }
295 
296   auto appUnitsPerDevPixel = Frame()->PresContext()->AppUnitsPerDevPixel();
297   gfx::Point deviceOffset =
298       LayoutDevicePoint::FromAppUnits(bounds.TopLeft(), appUnitsPerDevPixel)
299           .ToUnknownPoint();
300 
301   RefPtr<mozilla::layout::TextDrawTarget> textDrawer =
302       new mozilla::layout::TextDrawTarget(aBuilder, aResources, aSc, aManager,
303                                           this, bounds);
304   RefPtr<gfxContext> captureCtx =
305       gfxContext::CreateOrNull(textDrawer, deviceOffset);
306 
307   Paint(aDisplayListBuilder, captureCtx);
308   textDrawer->TerminateShadows();
309 
310   return textDrawer->Finish();
311 }
312 
GetBounds(nsDisplayListBuilder * aBuilder,bool * aSnap) const313 nsRect nsDisplayXULTextBox::GetBounds(nsDisplayListBuilder* aBuilder,
314                                       bool* aSnap) const {
315   *aSnap = false;
316   return mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
317 }
318 
GetComponentAlphaBounds(nsDisplayListBuilder * aBuilder) const319 nsRect nsDisplayXULTextBox::GetComponentAlphaBounds(
320     nsDisplayListBuilder* aBuilder) const {
321   return static_cast<nsTextBoxFrame*>(mFrame)->GetComponentAlphaBounds() +
322          ToReferenceFrame();
323 }
324 
BuildDisplayList(nsDisplayListBuilder * aBuilder,const nsDisplayListSet & aLists)325 void nsTextBoxFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
326                                       const nsDisplayListSet& aLists) {
327   if (!IsVisibleForPainting()) return;
328 
329   nsLeafBoxFrame::BuildDisplayList(aBuilder, aLists);
330 
331   aLists.Content()->AppendNewToTop<nsDisplayXULTextBox>(aBuilder, this);
332 }
333 
PaintTitle(gfxContext & aRenderingContext,const nsRect & aDirtyRect,nsPoint aPt,const nscolor * aOverrideColor)334 void nsTextBoxFrame::PaintTitle(gfxContext& aRenderingContext,
335                                 const nsRect& aDirtyRect, nsPoint aPt,
336                                 const nscolor* aOverrideColor) {
337   if (mTitle.IsEmpty()) return;
338 
339   DrawText(aRenderingContext, aDirtyRect, mTextDrawRect + aPt, aOverrideColor);
340 }
341 
DrawText(gfxContext & aRenderingContext,const nsRect & aDirtyRect,const nsRect & aTextRect,const nscolor * aOverrideColor)342 void nsTextBoxFrame::DrawText(gfxContext& aRenderingContext,
343                               const nsRect& aDirtyRect, const nsRect& aTextRect,
344                               const nscolor* aOverrideColor) {
345   nsPresContext* presContext = PresContext();
346   int32_t appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
347   DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
348 
349   // paint the title
350   nscolor overColor = 0;
351   nscolor underColor = 0;
352   nscolor strikeColor = 0;
353   uint8_t overStyle = 0;
354   uint8_t underStyle = 0;
355   uint8_t strikeStyle = 0;
356 
357   // Begin with no decorations
358   auto decorations = StyleTextDecorationLine::NONE;
359   // A mask of all possible line decorations.
360   auto decorMask = StyleTextDecorationLine::UNDERLINE |
361                    StyleTextDecorationLine::OVERLINE |
362                    StyleTextDecorationLine::LINE_THROUGH;
363 
364   WritingMode wm = GetWritingMode();
365   bool vertical = wm.IsVertical();
366 
367   nsIFrame* f = this;
368   do {  // find decoration colors
369     ComputedStyle* context = f->Style();
370     if (!context->HasTextDecorationLines()) {
371       break;
372     }
373     const nsStyleTextReset* styleText = context->StyleTextReset();
374 
375     // a decoration defined here
376     if (decorMask & styleText->mTextDecorationLine) {
377       nscolor color;
378       if (aOverrideColor) {
379         color = *aOverrideColor;
380       } else {
381         color = styleText->mTextDecorationColor.CalcColor(*context);
382       }
383       uint8_t style = styleText->mTextDecorationStyle;
384 
385       if (StyleTextDecorationLine::UNDERLINE & decorMask &
386           styleText->mTextDecorationLine) {
387         underColor = color;
388         underStyle = style;
389         decorMask &= ~StyleTextDecorationLine::UNDERLINE;
390         decorations |= StyleTextDecorationLine::UNDERLINE;
391       }
392       if (StyleTextDecorationLine::OVERLINE & decorMask &
393           styleText->mTextDecorationLine) {
394         overColor = color;
395         overStyle = style;
396         decorMask &= ~StyleTextDecorationLine::OVERLINE;
397         decorations |= StyleTextDecorationLine::OVERLINE;
398       }
399       if (StyleTextDecorationLine::LINE_THROUGH & decorMask &
400           styleText->mTextDecorationLine) {
401         strikeColor = color;
402         strikeStyle = style;
403         decorMask &= ~StyleTextDecorationLine::LINE_THROUGH;
404         decorations |= StyleTextDecorationLine::LINE_THROUGH;
405       }
406     }
407   } while (decorMask && (f = nsLayoutUtils::GetParentOrPlaceholderFor(f)));
408 
409   RefPtr<nsFontMetrics> fontMet =
410       nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f);
411   fontMet->SetVertical(wm.IsVertical());
412   fontMet->SetTextOrientation(StyleVisibility()->mTextOrientation);
413 
414   nscoord offset;
415   nscoord size;
416   nscoord ascent = fontMet->MaxAscent();
417 
418   nsPoint baselinePt;
419   if (wm.IsVertical()) {
420     baselinePt.x = presContext->RoundAppUnitsToNearestDevPixels(
421         aTextRect.x + (wm.IsVerticalRL() ? aTextRect.width - ascent : ascent));
422     baselinePt.y = aTextRect.y;
423   } else {
424     baselinePt.x = aTextRect.x;
425     baselinePt.y =
426         presContext->RoundAppUnitsToNearestDevPixels(aTextRect.y + ascent);
427   }
428 
429   nsCSSRendering::PaintDecorationLineParams params;
430   params.dirtyRect = ToRect(presContext->AppUnitsToGfxUnits(aDirtyRect));
431   params.pt = Point(presContext->AppUnitsToGfxUnits(aTextRect.x),
432                     presContext->AppUnitsToGfxUnits(aTextRect.y));
433   params.icoordInFrame =
434       Float(PresContext()->AppUnitsToGfxUnits(mTextDrawRect.x));
435   params.lineSize = Size(presContext->AppUnitsToGfxUnits(aTextRect.width), 0);
436   params.ascent = presContext->AppUnitsToGfxUnits(ascent);
437   params.vertical = vertical;
438 
439   // XXX todo: vertical-mode support for decorations not tested yet,
440   // probably won't be positioned correctly
441 
442   // Underlines are drawn before overlines, and both before the text
443   // itself, per http://www.w3.org/TR/CSS21/zindex.html point 7.2.1.4.1.1.
444   // (We don't apply this rule to the access-key underline because we only
445   // find out where that is as a side effect of drawing the text, in the
446   // general case -- see below.)
447   if (decorations & (StyleTextDecorationLine::OVERLINE |
448                      StyleTextDecorationLine::UNDERLINE)) {
449     fontMet->GetUnderline(offset, size);
450     params.lineSize.height = presContext->AppUnitsToGfxUnits(size);
451     if ((decorations & StyleTextDecorationLine::UNDERLINE) &&
452         underStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
453       params.color = underColor;
454       params.offset = presContext->AppUnitsToGfxUnits(offset);
455       params.decoration = StyleTextDecorationLine::UNDERLINE;
456       params.style = underStyle;
457       nsCSSRendering::PaintDecorationLine(this, *drawTarget, params);
458     }
459     if ((decorations & StyleTextDecorationLine::OVERLINE) &&
460         overStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
461       params.color = overColor;
462       params.offset = params.ascent;
463       params.decoration = StyleTextDecorationLine::OVERLINE;
464       params.style = overStyle;
465       nsCSSRendering::PaintDecorationLine(this, *drawTarget, params);
466     }
467   }
468 
469   RefPtr<gfxContext> refContext =
470       PresShell()->CreateReferenceRenderingContext();
471   DrawTarget* refDrawTarget = refContext->GetDrawTarget();
472 
473   CalculateUnderline(refDrawTarget, *fontMet);
474 
475   DeviceColor color = ToDeviceColor(
476       aOverrideColor ? *aOverrideColor : StyleText()->mColor.ToColor());
477   ColorPattern colorPattern(color);
478   aRenderingContext.SetDeviceColor(color);
479 
480   nsresult rv = NS_ERROR_FAILURE;
481 
482   if (mState & NS_FRAME_IS_BIDI) {
483     presContext->SetBidiEnabled();
484     nsBidiLevel level = nsBidiPresUtils::BidiLevelFromStyle(Style());
485     if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) {
486       // We let the RenderText function calculate the mnemonic's
487       // underline position for us.
488       nsBidiPositionResolve posResolve;
489       posResolve.logicalIndex = mAccessKeyInfo->mAccesskeyIndex;
490       rv = nsBidiPresUtils::RenderText(
491           mCroppedTitle.get(), mCroppedTitle.Length(), level, presContext,
492           aRenderingContext, refDrawTarget, *fontMet, baselinePt.x,
493           baselinePt.y, &posResolve, 1);
494       mAccessKeyInfo->mBeforeWidth = posResolve.visualLeftTwips;
495       mAccessKeyInfo->mAccessWidth = posResolve.visualWidth;
496     } else {
497       rv = nsBidiPresUtils::RenderText(
498           mCroppedTitle.get(), mCroppedTitle.Length(), level, presContext,
499           aRenderingContext, refDrawTarget, *fontMet, baselinePt.x,
500           baselinePt.y);
501     }
502   }
503   if (NS_FAILED(rv)) {
504     fontMet->SetTextRunRTL(false);
505 
506     if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) {
507       // In the simple (non-BiDi) case, we calculate the mnemonic's
508       // underline position by getting the text metric.
509       // XXX are attribute values always two byte?
510       if (mAccessKeyInfo->mAccesskeyIndex > 0)
511         mAccessKeyInfo->mBeforeWidth = nsLayoutUtils::AppUnitWidthOfString(
512             mCroppedTitle.get(), mAccessKeyInfo->mAccesskeyIndex, *fontMet,
513             refDrawTarget);
514       else
515         mAccessKeyInfo->mBeforeWidth = 0;
516     }
517 
518     fontMet->DrawString(mCroppedTitle.get(), mCroppedTitle.Length(),
519                         baselinePt.x, baselinePt.y, &aRenderingContext,
520                         refDrawTarget);
521   }
522 
523   if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) {
524     nsRect r(aTextRect.x + mAccessKeyInfo->mBeforeWidth,
525              aTextRect.y + mAccessKeyInfo->mAccessOffset,
526              mAccessKeyInfo->mAccessWidth,
527              mAccessKeyInfo->mAccessUnderlineSize);
528     Rect devPxRect = NSRectToSnappedRect(r, appUnitsPerDevPixel, *drawTarget);
529     drawTarget->FillRect(devPxRect, colorPattern);
530   }
531 
532   // Strikeout is drawn on top of the text, per
533   // http://www.w3.org/TR/CSS21/zindex.html point 7.2.1.4.1.1.
534   if ((decorations & StyleTextDecorationLine::LINE_THROUGH) &&
535       strikeStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
536     fontMet->GetStrikeout(offset, size);
537     params.color = strikeColor;
538     params.lineSize.height = presContext->AppUnitsToGfxUnits(size);
539     params.offset = presContext->AppUnitsToGfxUnits(offset);
540     params.decoration = StyleTextDecorationLine::LINE_THROUGH;
541     params.style = strikeStyle;
542     nsCSSRendering::PaintDecorationLine(this, *drawTarget, params);
543   }
544 }
545 
CalculateUnderline(DrawTarget * aDrawTarget,nsFontMetrics & aFontMetrics)546 void nsTextBoxFrame::CalculateUnderline(DrawTarget* aDrawTarget,
547                                         nsFontMetrics& aFontMetrics) {
548   if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) {
549     // Calculate all fields of mAccessKeyInfo which
550     // are the same for both BiDi and non-BiDi frames.
551     const char16_t* titleString = mCroppedTitle.get();
552     aFontMetrics.SetTextRunRTL(false);
553     mAccessKeyInfo->mAccessWidth = nsLayoutUtils::AppUnitWidthOfString(
554         titleString[mAccessKeyInfo->mAccesskeyIndex], aFontMetrics,
555         aDrawTarget);
556 
557     nscoord offset, baseline;
558     aFontMetrics.GetUnderline(offset, mAccessKeyInfo->mAccessUnderlineSize);
559     baseline = aFontMetrics.MaxAscent();
560     mAccessKeyInfo->mAccessOffset = baseline - offset;
561   }
562 }
563 
CalculateTitleForWidth(gfxContext & aRenderingContext,nscoord aWidth)564 nscoord nsTextBoxFrame::CalculateTitleForWidth(gfxContext& aRenderingContext,
565                                                nscoord aWidth) {
566   DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
567 
568   if (mTitle.IsEmpty()) {
569     mCroppedTitle.Truncate();
570     return 0;
571   }
572 
573   RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f);
574 
575   // see if the text will completely fit in the width given
576   nscoord titleWidth = nsLayoutUtils::AppUnitWidthOfStringBidi(
577       mTitle, this, *fm, aRenderingContext);
578   if (titleWidth <= aWidth) {
579     mCroppedTitle = mTitle;
580     if (HasRTLChars(mTitle) ||
581         StyleVisibility()->mDirection == StyleDirection::Rtl) {
582       AddStateBits(NS_FRAME_IS_BIDI);
583     }
584     return titleWidth;  // fits, done.
585   }
586 
587   const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis();
588   if (mCropType != CropNone) {
589     // start with an ellipsis
590     mCroppedTitle.Assign(kEllipsis);
591 
592     // see if the width is even smaller than the ellipsis
593     // if so, clear the text (XXX set as many '.' as we can?).
594     fm->SetTextRunRTL(false);
595     titleWidth =
596         nsLayoutUtils::AppUnitWidthOfString(kEllipsis, *fm, drawTarget);
597 
598     if (titleWidth > aWidth) {
599       mCroppedTitle.SetLength(0);
600       return 0;
601     }
602 
603     // if the ellipsis fits perfectly, no use in trying to insert
604     if (titleWidth == aWidth) return titleWidth;
605 
606     aWidth -= titleWidth;
607   } else {
608     mCroppedTitle.Truncate(0);
609     titleWidth = 0;
610   }
611 
612   using mozilla::unicode::ClusterIterator;
613   using mozilla::unicode::ClusterReverseIterator;
614 
615   // ok crop things
616   switch (mCropType) {
617     case CropAuto:
618     case CropNone:
619     case CropRight: {
620       ClusterIterator iter(mTitle.Data(), mTitle.Length());
621       const char16_t* dataBegin = iter;
622       const char16_t* pos = dataBegin;
623       nscoord charWidth;
624       nscoord totalWidth = 0;
625 
626       while (!iter.AtEnd()) {
627         iter.Next();
628         const char16_t* nextPos = iter;
629         ptrdiff_t length = nextPos - pos;
630         charWidth =
631             nsLayoutUtils::AppUnitWidthOfString(pos, length, *fm, drawTarget);
632         if (totalWidth + charWidth > aWidth) {
633           break;
634         }
635 
636         if (UTF16_CODE_UNIT_IS_BIDI(*pos)) {
637           AddStateBits(NS_FRAME_IS_BIDI);
638         }
639         pos = nextPos;
640         totalWidth += charWidth;
641       }
642 
643       if (pos == dataBegin) {
644         return titleWidth;
645       }
646 
647       // insert what character we can in.
648       nsAutoString title(mTitle);
649       title.Truncate(pos - dataBegin);
650       mCroppedTitle.Insert(title, 0);
651     } break;
652 
653     case CropLeft: {
654       ClusterReverseIterator iter(mTitle.Data(), mTitle.Length());
655       const char16_t* dataEnd = iter;
656       const char16_t* prevPos = dataEnd;
657       nscoord charWidth;
658       nscoord totalWidth = 0;
659 
660       while (!iter.AtEnd()) {
661         iter.Next();
662         const char16_t* pos = iter;
663         ptrdiff_t length = prevPos - pos;
664         charWidth =
665             nsLayoutUtils::AppUnitWidthOfString(pos, length, *fm, drawTarget);
666         if (totalWidth + charWidth > aWidth) {
667           break;
668         }
669 
670         if (UTF16_CODE_UNIT_IS_BIDI(*pos)) {
671           AddStateBits(NS_FRAME_IS_BIDI);
672         }
673         prevPos = pos;
674         totalWidth += charWidth;
675       }
676 
677       if (prevPos == dataEnd) {
678         return titleWidth;
679       }
680 
681       nsAutoString copy;
682       mTitle.Right(copy, dataEnd - prevPos);
683       mCroppedTitle += copy;
684     } break;
685 
686     case CropCenter: {
687       nscoord stringWidth = nsLayoutUtils::AppUnitWidthOfStringBidi(
688           mTitle, this, *fm, aRenderingContext);
689       if (stringWidth <= aWidth) {
690         // the entire string will fit in the maximum width
691         mCroppedTitle.Insert(mTitle, 0);
692         break;
693       }
694 
695       // determine how much of the string will fit in the max width
696       nscoord charWidth = 0;
697       nscoord totalWidth = 0;
698       ClusterIterator leftIter(mTitle.Data(), mTitle.Length());
699       ClusterReverseIterator rightIter(mTitle.Data(), mTitle.Length());
700       const char16_t* dataBegin = leftIter;
701       const char16_t* dataEnd = rightIter;
702       const char16_t* leftPos = dataBegin;
703       const char16_t* rightPos = dataEnd;
704       const char16_t* pos;
705       ptrdiff_t length;
706       nsAutoString leftString, rightString;
707 
708       while (leftPos < rightPos) {
709         leftIter.Next();
710         pos = leftIter;
711         length = pos - leftPos;
712         charWidth = nsLayoutUtils::AppUnitWidthOfString(leftPos, length, *fm,
713                                                         drawTarget);
714         if (totalWidth + charWidth > aWidth) {
715           break;
716         }
717 
718         if (UTF16_CODE_UNIT_IS_BIDI(*leftPos)) {
719           AddStateBits(NS_FRAME_IS_BIDI);
720         }
721 
722         leftString.Append(leftPos, length);
723         leftPos = pos;
724         totalWidth += charWidth;
725 
726         if (leftPos >= rightPos) {
727           break;
728         }
729 
730         rightIter.Next();
731         pos = rightIter;
732         length = rightPos - pos;
733         charWidth =
734             nsLayoutUtils::AppUnitWidthOfString(pos, length, *fm, drawTarget);
735         if (totalWidth + charWidth > aWidth) {
736           break;
737         }
738 
739         if (UTF16_CODE_UNIT_IS_BIDI(*pos)) {
740           AddStateBits(NS_FRAME_IS_BIDI);
741         }
742 
743         rightString.Insert(pos, 0, length);
744         rightPos = pos;
745         totalWidth += charWidth;
746       }
747 
748       mCroppedTitle = leftString + kEllipsis + rightString;
749     } break;
750   }
751 
752   return nsLayoutUtils::AppUnitWidthOfStringBidi(mCroppedTitle, this, *fm,
753                                                  aRenderingContext);
754 }
755 
756 #define OLD_ELLIPSIS u"..."_ns
757 
758 // the following block is to append the accesskey to mTitle if there is an
759 // accesskey but the mTitle doesn't have the character
UpdateAccessTitle()760 void nsTextBoxFrame::UpdateAccessTitle() {
761   /*
762    * Note that if you change appending access key label spec,
763    * you need to maintain same logic in following methods. See bug 324159.
764    * toolkit/components/prompts/src/CommonDialog.jsm (setLabelForNode)
765    * toolkit/content/widgets/text.js (formatAccessKey)
766    */
767   int32_t menuAccessKey;
768   nsMenuBarListener::GetMenuAccessKey(&menuAccessKey);
769   if (!menuAccessKey || mAccessKey.IsEmpty()) return;
770 
771   if (!AlwaysAppendAccessKey() &&
772       FindInReadable(mAccessKey, mTitle, nsCaseInsensitiveStringComparator))
773     return;
774 
775   nsAutoString accessKeyLabel;
776   accessKeyLabel += '(';
777   accessKeyLabel += mAccessKey;
778   ToUpperCase(accessKeyLabel);
779   accessKeyLabel += ')';
780 
781   if (mTitle.IsEmpty()) {
782     mTitle = accessKeyLabel;
783     return;
784   }
785 
786   if (StringEndsWith(mTitle, accessKeyLabel)) {
787     // Never append another "(X)" if the title already ends with "(X)".
788     return;
789   }
790 
791   const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis();
792   uint32_t offset = mTitle.Length();
793   if (StringEndsWith(mTitle, kEllipsis)) {
794     offset -= kEllipsis.Length();
795   } else if (StringEndsWith(mTitle, OLD_ELLIPSIS)) {
796     // Try to check with our old ellipsis (for old addons)
797     offset -= OLD_ELLIPSIS.Length();
798   } else {
799     // Try to check with
800     // our default ellipsis (for non-localized addons) or ':'
801     const char16_t kLastChar = mTitle.Last();
802     if (kLastChar == char16_t(0x2026) || kLastChar == char16_t(':')) offset--;
803   }
804 
805   if (InsertSeparatorBeforeAccessKey() && offset > 0 &&
806       !NS_IS_SPACE(mTitle[offset - 1])) {
807     mTitle.Insert(' ', offset);
808     offset++;
809   }
810 
811   mTitle.Insert(accessKeyLabel, offset);
812 }
813 
UpdateAccessIndex()814 void nsTextBoxFrame::UpdateAccessIndex() {
815   int32_t menuAccessKey;
816   nsMenuBarListener::GetMenuAccessKey(&menuAccessKey);
817   if (menuAccessKey) {
818     if (mAccessKey.IsEmpty()) {
819       if (mAccessKeyInfo) {
820         delete mAccessKeyInfo;
821         mAccessKeyInfo = nullptr;
822       }
823     } else {
824       if (!mAccessKeyInfo) {
825         mAccessKeyInfo = new nsAccessKeyInfo();
826         if (!mAccessKeyInfo) return;
827       }
828 
829       nsAString::const_iterator start, end;
830 
831       mCroppedTitle.BeginReading(start);
832       mCroppedTitle.EndReading(end);
833 
834       // remember the beginning of the string
835       nsAString::const_iterator originalStart = start;
836 
837       bool found;
838       if (!AlwaysAppendAccessKey()) {
839         // not appending access key - do case-sensitive search
840         // first
841         found = FindInReadable(mAccessKey, start, end);
842         if (!found) {
843           // didn't find it - perform a case-insensitive search
844           start = originalStart;
845           found = FindInReadable(mAccessKey, start, end,
846                                  nsCaseInsensitiveStringComparator);
847         }
848       } else {
849         found = RFindInReadable(mAccessKey, start, end,
850                                 nsCaseInsensitiveStringComparator);
851       }
852 
853       if (found)
854         mAccessKeyInfo->mAccesskeyIndex = Distance(originalStart, start);
855       else
856         mAccessKeyInfo->mAccesskeyIndex = kNotFound;
857     }
858   }
859 }
860 
RecomputeTitle()861 void nsTextBoxFrame::RecomputeTitle() {
862   mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::value, mTitle);
863 
864   // This doesn't handle language-specific uppercasing/lowercasing
865   // rules, unlike textruns.
866   StyleTextTransform textTransform = StyleText()->mTextTransform;
867   if (textTransform.case_ == StyleTextTransformCase::Uppercase) {
868     ToUpperCase(mTitle);
869   } else if (textTransform.case_ == StyleTextTransformCase::Lowercase) {
870     ToLowerCase(mTitle);
871   }
872   // We can't handle StyleTextTransformCase::Capitalize because we
873   // have no clue about word boundaries here.  We also don't handle
874   // the full-width or full-size-kana transforms.
875 }
876 
DidSetComputedStyle(ComputedStyle * aOldComputedStyle)877 void nsTextBoxFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
878   nsLeafBoxFrame::DidSetComputedStyle(aOldComputedStyle);
879 
880   if (!aOldComputedStyle) {
881     // We're just being initialized
882     return;
883   }
884 
885   const nsStyleText* oldTextStyle = aOldComputedStyle->StyleText();
886   if (oldTextStyle->mTextTransform != StyleText()->mTextTransform) {
887     RecomputeTitle();
888     UpdateAccessTitle();
889   }
890 }
891 
892 NS_IMETHODIMP
DoXULLayout(nsBoxLayoutState & aBoxLayoutState)893 nsTextBoxFrame::DoXULLayout(nsBoxLayoutState& aBoxLayoutState) {
894   if (mNeedsReflowCallback) {
895     nsIReflowCallback* cb = new nsAsyncAccesskeyUpdate(this);
896     if (cb) {
897       PresShell()->PostReflowCallback(cb);
898     }
899     mNeedsReflowCallback = false;
900   }
901 
902   nsresult rv = nsLeafBoxFrame::DoXULLayout(aBoxLayoutState);
903 
904   CalcDrawRect(*aBoxLayoutState.GetRenderingContext());
905 
906   const nsStyleText* textStyle = StyleText();
907 
908   nsRect scrollBounds(nsPoint(0, 0), GetSize());
909   nsRect textRect = mTextDrawRect;
910 
911   RefPtr<nsFontMetrics> fontMet =
912       nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f);
913   nsBoundingMetrics metrics = fontMet->GetInkBoundsForInkOverflow(
914       mCroppedTitle.get(), mCroppedTitle.Length(),
915       aBoxLayoutState.GetRenderingContext()->GetDrawTarget());
916 
917   WritingMode wm = GetWritingMode();
918   LogicalRect tr(wm, textRect, GetSize());
919 
920   tr.IStart(wm) -= metrics.leftBearing;
921   tr.ISize(wm) = metrics.width;
922   // In DrawText() we always draw with the baseline at MaxAscent() (relative to
923   // mTextDrawRect),
924   tr.BStart(wm) += fontMet->MaxAscent() - metrics.ascent;
925   tr.BSize(wm) = metrics.ascent + metrics.descent;
926 
927   textRect = tr.GetPhysicalRect(wm, GetSize());
928 
929   // Our scrollable overflow is our bounds; our ink overflow may
930   // extend beyond that.
931   nsRect visualBounds;
932   visualBounds.UnionRect(scrollBounds, textRect);
933   OverflowAreas overflow(visualBounds, scrollBounds);
934 
935   if (textStyle->HasTextShadow()) {
936     // text-shadow extends our visual but not scrollable bounds
937     nsRect& vis = overflow.InkOverflow();
938     vis.UnionRect(vis,
939                   nsLayoutUtils::GetTextShadowRectsUnion(mTextDrawRect, this));
940   }
941   FinishAndStoreOverflow(overflow, GetSize());
942 
943   return rv;
944 }
945 
GetComponentAlphaBounds() const946 nsRect nsTextBoxFrame::GetComponentAlphaBounds() const {
947   if (StyleText()->HasTextShadow()) {
948     return InkOverflowRectRelativeToSelf();
949   }
950   return mTextDrawRect;
951 }
952 
XULComputesOwnOverflowArea()953 bool nsTextBoxFrame::XULComputesOwnOverflowArea() { return true; }
954 
955 /* virtual */
MarkIntrinsicISizesDirty()956 void nsTextBoxFrame::MarkIntrinsicISizesDirty() {
957   mNeedsRecalc = true;
958   nsLeafBoxFrame::MarkIntrinsicISizesDirty();
959 }
960 
GetTextSize(gfxContext & aRenderingContext,const nsString & aString,nsSize & aSize,nscoord & aAscent)961 void nsTextBoxFrame::GetTextSize(gfxContext& aRenderingContext,
962                                  const nsString& aString, nsSize& aSize,
963                                  nscoord& aAscent) {
964   RefPtr<nsFontMetrics> fontMet =
965       nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f);
966   aSize.height = fontMet->MaxHeight();
967   aSize.width = nsLayoutUtils::AppUnitWidthOfStringBidi(aString, this, *fontMet,
968                                                         aRenderingContext);
969   aAscent = fontMet->MaxAscent();
970 }
971 
CalcTextSize(nsBoxLayoutState & aBoxLayoutState)972 void nsTextBoxFrame::CalcTextSize(nsBoxLayoutState& aBoxLayoutState) {
973   if (mNeedsRecalc) {
974     nsSize size;
975     gfxContext* rendContext = aBoxLayoutState.GetRenderingContext();
976     if (rendContext) {
977       GetTextSize(*rendContext, mTitle, size, mAscent);
978       if (GetWritingMode().IsVertical()) {
979         std::swap(size.width, size.height);
980       }
981       mTextSize = size;
982       mNeedsRecalc = false;
983     }
984   }
985 }
986 
CalcDrawRect(gfxContext & aRenderingContext)987 void nsTextBoxFrame::CalcDrawRect(gfxContext& aRenderingContext) {
988   WritingMode wm = GetWritingMode();
989 
990   LogicalRect textRect(wm, LogicalPoint(wm, 0, 0), GetLogicalSize(wm));
991   nsMargin borderPadding;
992   GetXULBorderAndPadding(borderPadding);
993   textRect.Deflate(wm, LogicalMargin(wm, borderPadding));
994 
995   // determine (cropped) title and underline position
996   // determine (cropped) title which fits in aRect, and its width
997   // (where "width" is the text measure along its baseline, i.e. actually
998   // a physical height in vertical writing modes)
999   nscoord titleWidth =
1000       CalculateTitleForWidth(aRenderingContext, textRect.ISize(wm));
1001 
1002 #ifdef ACCESSIBILITY
1003   // Make sure to update the accessible tree in case when cropped title is
1004   // changed.
1005   nsAccessibilityService* accService = GetAccService();
1006   if (accService) {
1007     accService->UpdateLabelValue(PresShell(), mContent, mCroppedTitle);
1008   }
1009 #endif
1010 
1011   // determine if and at which position to put the underline
1012   UpdateAccessIndex();
1013 
1014   // make the rect as small as our (cropped) text.
1015   nscoord outerISize = textRect.ISize(wm);
1016   textRect.ISize(wm) = titleWidth;
1017 
1018   // Align our text within the overall rect by checking our text-align property.
1019   const nsStyleText* textStyle = StyleText();
1020   if (textStyle->mTextAlign == StyleTextAlign::Center) {
1021     textRect.IStart(wm) += (outerISize - textRect.ISize(wm)) / 2;
1022   } else if (textStyle->mTextAlign == StyleTextAlign::End ||
1023              (textStyle->mTextAlign == StyleTextAlign::Left &&
1024               wm.IsBidiRTL()) ||
1025              (textStyle->mTextAlign == StyleTextAlign::Right &&
1026               wm.IsBidiLTR())) {
1027     textRect.IStart(wm) += (outerISize - textRect.ISize(wm));
1028   }
1029 
1030   mTextDrawRect = textRect.GetPhysicalRect(wm, GetSize());
1031 }
1032 
1033 /**
1034  * Ok return our dimensions
1035  */
GetXULPrefSize(nsBoxLayoutState & aBoxLayoutState)1036 nsSize nsTextBoxFrame::GetXULPrefSize(nsBoxLayoutState& aBoxLayoutState) {
1037   CalcTextSize(aBoxLayoutState);
1038 
1039   nsSize size = mTextSize;
1040   DISPLAY_PREF_SIZE(this, size);
1041 
1042   AddXULBorderAndPadding(size);
1043   bool widthSet, heightSet;
1044   nsIFrame::AddXULPrefSize(this, size, widthSet, heightSet);
1045 
1046   return size;
1047 }
1048 
1049 /**
1050  * Ok return our dimensions
1051  */
GetXULMinSize(nsBoxLayoutState & aBoxLayoutState)1052 nsSize nsTextBoxFrame::GetXULMinSize(nsBoxLayoutState& aBoxLayoutState) {
1053   CalcTextSize(aBoxLayoutState);
1054 
1055   nsSize size = mTextSize;
1056   DISPLAY_MIN_SIZE(this, size);
1057 
1058   // if there is cropping our min width becomes our border and padding
1059   if (mCropType != CropNone && mCropType != CropAuto) {
1060     if (GetWritingMode().IsVertical()) {
1061       size.height = 0;
1062     } else {
1063       size.width = 0;
1064     }
1065   }
1066 
1067   AddXULBorderAndPadding(size);
1068   bool widthSet, heightSet;
1069   nsIFrame::AddXULMinSize(this, size, widthSet, heightSet);
1070 
1071   return size;
1072 }
1073 
GetXULBoxAscent(nsBoxLayoutState & aBoxLayoutState)1074 nscoord nsTextBoxFrame::GetXULBoxAscent(nsBoxLayoutState& aBoxLayoutState) {
1075   CalcTextSize(aBoxLayoutState);
1076 
1077   nscoord ascent = mAscent;
1078 
1079   nsMargin m(0, 0, 0, 0);
1080   GetXULBorderAndPadding(m);
1081 
1082   WritingMode wm = GetWritingMode();
1083   ascent += LogicalMargin(wm, m).BStart(wm);
1084 
1085   return ascent;
1086 }
1087 
1088 #ifdef DEBUG_FRAME_DUMP
GetFrameName(nsAString & aResult) const1089 nsresult nsTextBoxFrame::GetFrameName(nsAString& aResult) const {
1090   MakeFrameName(u"TextBox"_ns, aResult);
1091   aResult += u"[value="_ns + mTitle + u"]"_ns;
1092   return NS_OK;
1093 }
1094 #endif
1095