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