Bumping manifests a=b2g-bump
[gecko.git] / layout / xul / nsTextBoxFrame.cpp
blob4d74fc0b8184f28f55f8de6133d9be5ebbfa0d6a
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsTextBoxFrame.h"
8 #include "gfx2DGlue.h"
9 #include "gfxUtils.h"
10 #include "mozilla/gfx/2D.h"
11 #include "nsFontMetrics.h"
12 #include "nsReadableUtils.h"
13 #include "nsCOMPtr.h"
14 #include "nsGkAtoms.h"
15 #include "nsPresContext.h"
16 #include "nsRenderingContext.h"
17 #include "nsStyleContext.h"
18 #include "nsIContent.h"
19 #include "nsNameSpaceManager.h"
20 #include "nsBoxLayoutState.h"
21 #include "nsMenuBarListener.h"
22 #include "nsXPIDLString.h"
23 #include "nsIServiceManager.h"
24 #include "nsIDOMElement.h"
25 #include "nsIDOMXULLabelElement.h"
26 #include "mozilla/EventStateManager.h"
27 #include "nsITheme.h"
28 #include "nsUnicharUtils.h"
29 #include "nsContentUtils.h"
30 #include "nsDisplayList.h"
31 #include "nsCSSRendering.h"
32 #include "nsIReflowCallback.h"
33 #include "nsBoxFrame.h"
34 #include "mozilla/Preferences.h"
35 #include "nsLayoutUtils.h"
36 #include "mozilla/Attributes.h"
38 #ifdef ACCESSIBILITY
39 #include "nsAccessibilityService.h"
40 #endif
42 #include "nsBidiUtils.h"
43 #include "nsBidiPresUtils.h"
45 using namespace mozilla;
46 using namespace mozilla::gfx;
48 class nsAccessKeyInfo
50 public:
51 int32_t mAccesskeyIndex;
52 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*
62 NS_NewTextBoxFrame (nsIPresShell* aPresShell, nsStyleContext* aContext)
64 return new (aPresShell) nsTextBoxFrame(aContext);
67 NS_IMPL_FRAMEARENA_HELPERS(nsTextBoxFrame)
69 NS_QUERYFRAME_HEAD(nsTextBoxFrame)
70 NS_QUERYFRAME_ENTRY(nsTextBoxFrame)
71 NS_QUERYFRAME_TAIL_INHERITING(nsTextBoxFrameSuper)
73 nsresult
74 nsTextBoxFrame::AttributeChanged(int32_t aNameSpaceID,
75 nsIAtom* aAttribute,
76 int32_t aModType)
78 bool aResize;
79 bool aRedraw;
81 UpdateAttributes(aAttribute, aResize, aRedraw);
83 if (aResize) {
84 PresContext()->PresShell()->
85 FrameNeedsReflow(this, nsIPresShell::eStyleChange,
86 NS_FRAME_IS_DIRTY);
87 } else if (aRedraw) {
88 nsBoxLayoutState state(PresContext());
89 Redraw(state);
92 // If the accesskey changed, register for the new value
93 // The old value has been unregistered in nsXULElement::SetAttr
94 if (aAttribute == nsGkAtoms::accesskey || aAttribute == nsGkAtoms::control)
95 RegUnregAccessKey(true);
97 return NS_OK;
100 nsTextBoxFrame::nsTextBoxFrame(nsStyleContext* aContext):
101 nsLeafBoxFrame(aContext), mAccessKeyInfo(nullptr), mCropType(CropRight),
102 mNeedsReflowCallback(false)
104 MarkIntrinsicISizesDirty();
107 nsTextBoxFrame::~nsTextBoxFrame()
109 delete mAccessKeyInfo;
113 void
114 nsTextBoxFrame::Init(nsIContent* aContent,
115 nsContainerFrame* aParent,
116 nsIFrame* aPrevInFlow)
118 nsTextBoxFrameSuper::Init(aContent, aParent, aPrevInFlow);
120 bool aResize;
121 bool aRedraw;
122 UpdateAttributes(nullptr, aResize, aRedraw); /* update all */
124 // register access key
125 RegUnregAccessKey(true);
128 void
129 nsTextBoxFrame::DestroyFrom(nsIFrame* aDestructRoot)
131 // unregister access key
132 RegUnregAccessKey(false);
133 nsTextBoxFrameSuper::DestroyFrom(aDestructRoot);
136 bool
137 nsTextBoxFrame::AlwaysAppendAccessKey()
139 if (!gAccessKeyPrefInitialized)
141 gAccessKeyPrefInitialized = true;
143 const char* prefName = "intl.menuitems.alwaysappendaccesskeys";
144 nsAdoptingString val = Preferences::GetLocalizedString(prefName);
145 gAlwaysAppendAccessKey = val.EqualsLiteral("true");
147 return gAlwaysAppendAccessKey;
150 bool
151 nsTextBoxFrame::InsertSeparatorBeforeAccessKey()
153 if (!gInsertSeparatorPrefInitialized)
155 gInsertSeparatorPrefInitialized = true;
157 const char* prefName = "intl.menuitems.insertseparatorbeforeaccesskeys";
158 nsAdoptingString val = Preferences::GetLocalizedString(prefName);
159 gInsertSeparatorBeforeAccessKey = val.EqualsLiteral("true");
161 return gInsertSeparatorBeforeAccessKey;
164 class nsAsyncAccesskeyUpdate MOZ_FINAL : public nsIReflowCallback
166 public:
167 explicit nsAsyncAccesskeyUpdate(nsIFrame* aFrame) : mWeakFrame(aFrame)
171 virtual bool ReflowFinished() MOZ_OVERRIDE
173 bool shouldFlush = false;
174 nsTextBoxFrame* frame =
175 static_cast<nsTextBoxFrame*>(mWeakFrame.GetFrame());
176 if (frame) {
177 shouldFlush = frame->UpdateAccesskey(mWeakFrame);
179 delete this;
180 return shouldFlush;
183 virtual void ReflowCallbackCanceled() MOZ_OVERRIDE
185 delete this;
188 nsWeakFrame mWeakFrame;
191 bool
192 nsTextBoxFrame::UpdateAccesskey(nsWeakFrame& aWeakThis)
194 nsAutoString accesskey;
195 nsCOMPtr<nsIDOMXULLabelElement> labelElement = do_QueryInterface(mContent);
196 NS_ENSURE_TRUE(aWeakThis.IsAlive(), false);
197 if (labelElement) {
198 // Accesskey may be stored on control.
199 labelElement->GetAccessKey(accesskey);
200 NS_ENSURE_TRUE(aWeakThis.IsAlive(), false);
202 else {
203 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, accesskey);
206 if (!accesskey.Equals(mAccessKey)) {
207 // Need to get clean mTitle.
208 RecomputeTitle();
209 mAccessKey = accesskey;
210 UpdateAccessTitle();
211 PresContext()->PresShell()->
212 FrameNeedsReflow(this, nsIPresShell::eStyleChange,
213 NS_FRAME_IS_DIRTY);
214 return true;
216 return false;
219 void
220 nsTextBoxFrame::UpdateAttributes(nsIAtom* aAttribute,
221 bool& aResize,
222 bool& aRedraw)
224 bool doUpdateTitle = false;
225 aResize = false;
226 aRedraw = false;
228 if (aAttribute == nullptr || aAttribute == nsGkAtoms::crop) {
229 static nsIContent::AttrValuesArray strings[] =
230 {&nsGkAtoms::left, &nsGkAtoms::start, &nsGkAtoms::center,
231 &nsGkAtoms::right, &nsGkAtoms::end, nullptr};
232 CroppingStyle cropType;
233 switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::crop,
234 strings, eCaseMatters)) {
235 case 0:
236 case 1:
237 cropType = CropLeft;
238 break;
239 case 2:
240 cropType = CropCenter;
241 break;
242 case 3:
243 case 4:
244 cropType = CropRight;
245 break;
246 default:
247 cropType = CropNone;
248 break;
251 if (cropType != mCropType) {
252 aResize = true;
253 mCropType = cropType;
257 if (aAttribute == nullptr || aAttribute == nsGkAtoms::value) {
258 RecomputeTitle();
259 doUpdateTitle = true;
262 if (aAttribute == nullptr || aAttribute == nsGkAtoms::accesskey) {
263 mNeedsReflowCallback = true;
264 // Ensure that layout is refreshed and reflow callback called.
265 aResize = true;
268 if (doUpdateTitle) {
269 UpdateAccessTitle();
270 aResize = true;
275 class nsDisplayXULTextBox : public nsDisplayItem {
276 public:
277 nsDisplayXULTextBox(nsDisplayListBuilder* aBuilder,
278 nsTextBoxFrame* aFrame) :
279 nsDisplayItem(aBuilder, aFrame),
280 mDisableSubpixelAA(false)
282 MOZ_COUNT_CTOR(nsDisplayXULTextBox);
284 #ifdef NS_BUILD_REFCNT_LOGGING
285 virtual ~nsDisplayXULTextBox() {
286 MOZ_COUNT_DTOR(nsDisplayXULTextBox);
288 #endif
290 virtual void Paint(nsDisplayListBuilder* aBuilder,
291 nsRenderingContext* aCtx) MOZ_OVERRIDE;
292 virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
293 bool* aSnap) MOZ_OVERRIDE;
294 NS_DISPLAY_DECL_NAME("XULTextBox", TYPE_XUL_TEXT_BOX)
296 virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) MOZ_OVERRIDE;
298 virtual void DisableComponentAlpha() MOZ_OVERRIDE {
299 mDisableSubpixelAA = true;
302 void PaintTextToContext(nsRenderingContext* aCtx,
303 nsPoint aOffset,
304 const nscolor* aColor);
306 bool mDisableSubpixelAA;
309 static void
310 PaintTextShadowCallback(nsRenderingContext* aCtx,
311 nsPoint aShadowOffset,
312 const nscolor& aShadowColor,
313 void* aData)
315 reinterpret_cast<nsDisplayXULTextBox*>(aData)->
316 PaintTextToContext(aCtx, aShadowOffset, &aShadowColor);
319 void
320 nsDisplayXULTextBox::Paint(nsDisplayListBuilder* aBuilder,
321 nsRenderingContext* aCtx)
323 gfxContextAutoDisableSubpixelAntialiasing disable(aCtx->ThebesContext(),
324 mDisableSubpixelAA);
326 // Paint the text shadow before doing any foreground stuff
327 nsRect drawRect = static_cast<nsTextBoxFrame*>(mFrame)->mTextDrawRect +
328 ToReferenceFrame();
329 nsLayoutUtils::PaintTextShadow(mFrame, aCtx,
330 drawRect, mVisibleRect,
331 mFrame->StyleColor()->mColor,
332 PaintTextShadowCallback,
333 (void*)this);
335 PaintTextToContext(aCtx, nsPoint(0, 0), nullptr);
338 void
339 nsDisplayXULTextBox::PaintTextToContext(nsRenderingContext* aCtx,
340 nsPoint aOffset,
341 const nscolor* aColor)
343 static_cast<nsTextBoxFrame*>(mFrame)->
344 PaintTitle(*aCtx, mVisibleRect, ToReferenceFrame() + aOffset, aColor);
347 nsRect
348 nsDisplayXULTextBox::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) {
349 *aSnap = false;
350 return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
353 nsRect
354 nsDisplayXULTextBox::GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder)
356 return static_cast<nsTextBoxFrame*>(mFrame)->GetComponentAlphaBounds() +
357 ToReferenceFrame();
360 void
361 nsTextBoxFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
362 const nsRect& aDirtyRect,
363 const nsDisplayListSet& aLists)
365 if (!IsVisibleForPainting(aBuilder))
366 return;
368 nsLeafBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
370 aLists.Content()->AppendNewToTop(new (aBuilder)
371 nsDisplayXULTextBox(aBuilder, this));
374 void
375 nsTextBoxFrame::PaintTitle(nsRenderingContext& aRenderingContext,
376 const nsRect& aDirtyRect,
377 nsPoint aPt,
378 const nscolor* aOverrideColor)
380 if (mTitle.IsEmpty())
381 return;
383 DrawText(aRenderingContext, aDirtyRect, mTextDrawRect + aPt, aOverrideColor);
386 void
387 nsTextBoxFrame::DrawText(nsRenderingContext& aRenderingContext,
388 const nsRect& aDirtyRect,
389 const nsRect& aTextRect,
390 const nscolor* aOverrideColor)
392 nsPresContext* presContext = PresContext();
393 int32_t appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
394 DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
396 // paint the title
397 nscolor overColor;
398 nscolor underColor;
399 nscolor strikeColor;
400 uint8_t overStyle;
401 uint8_t underStyle;
402 uint8_t strikeStyle;
404 // Begin with no decorations
405 uint8_t decorations = NS_STYLE_TEXT_DECORATION_LINE_NONE;
406 // A mask of all possible decorations.
407 uint8_t decorMask = NS_STYLE_TEXT_DECORATION_LINE_LINES_MASK;
409 bool vertical = GetWritingMode().IsVertical();
411 nsIFrame* f = this;
412 do { // find decoration colors
413 nsStyleContext* context = f->StyleContext();
414 if (!context->HasTextDecorationLines()) {
415 break;
417 const nsStyleTextReset* styleText = context->StyleTextReset();
419 if (decorMask & styleText->mTextDecorationLine) { // a decoration defined here
420 nscolor color;
421 if (aOverrideColor) {
422 color = *aOverrideColor;
423 } else {
424 bool isForeground;
425 styleText->GetDecorationColor(color, isForeground);
426 if (isForeground) {
427 color = nsLayoutUtils::GetColor(f, eCSSProperty_color);
430 uint8_t style = styleText->GetDecorationStyle();
432 if (NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE & decorMask &
433 styleText->mTextDecorationLine) {
434 underColor = color;
435 underStyle = style;
436 decorMask &= ~NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE;
437 decorations |= NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE;
439 if (NS_STYLE_TEXT_DECORATION_LINE_OVERLINE & decorMask &
440 styleText->mTextDecorationLine) {
441 overColor = color;
442 overStyle = style;
443 decorMask &= ~NS_STYLE_TEXT_DECORATION_LINE_OVERLINE;
444 decorations |= NS_STYLE_TEXT_DECORATION_LINE_OVERLINE;
446 if (NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH & decorMask &
447 styleText->mTextDecorationLine) {
448 strikeColor = color;
449 strikeStyle = style;
450 decorMask &= ~NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH;
451 decorations |= NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH;
454 } while (0 != decorMask &&
455 (f = nsLayoutUtils::GetParentOrPlaceholderFor(f)));
457 nsRefPtr<nsFontMetrics> fontMet;
458 nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fontMet));
460 nscoord offset;
461 nscoord size;
462 nscoord ascent = fontMet->MaxAscent();
464 nscoord baseline =
465 presContext->RoundAppUnitsToNearestDevPixels(aTextRect.y + ascent);
466 nsRefPtr<gfxContext> ctx = aRenderingContext.ThebesContext();
467 gfxPoint pt(presContext->AppUnitsToGfxUnits(aTextRect.x),
468 presContext->AppUnitsToGfxUnits(aTextRect.y));
469 gfxFloat width = presContext->AppUnitsToGfxUnits(aTextRect.width);
470 gfxFloat ascentPixel = presContext->AppUnitsToGfxUnits(ascent);
471 Float xInFrame = Float(PresContext()->AppUnitsToGfxUnits(mTextDrawRect.x));
472 gfxRect dirtyRect(presContext->AppUnitsToGfxUnits(aDirtyRect));
474 // Underlines are drawn before overlines, and both before the text
475 // itself, per http://www.w3.org/TR/CSS21/zindex.html point 7.2.1.4.1.1.
476 // (We don't apply this rule to the access-key underline because we only
477 // find out where that is as a side effect of drawing the text, in the
478 // general case -- see below.)
479 if (decorations & (NS_FONT_DECORATION_OVERLINE |
480 NS_FONT_DECORATION_UNDERLINE)) {
481 fontMet->GetUnderline(offset, size);
482 gfxFloat offsetPixel = presContext->AppUnitsToGfxUnits(offset);
483 gfxFloat sizePixel = presContext->AppUnitsToGfxUnits(size);
484 if ((decorations & NS_FONT_DECORATION_UNDERLINE) &&
485 underStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
486 nsCSSRendering::PaintDecorationLine(this, *drawTarget,
487 ToRect(dirtyRect), underColor,
488 pt, xInFrame, gfxSize(width, sizePixel),
489 ascentPixel, offsetPixel,
490 NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, underStyle,
491 vertical);
493 if ((decorations & NS_FONT_DECORATION_OVERLINE) &&
494 overStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
495 nsCSSRendering::PaintDecorationLine(this, *drawTarget,
496 ToRect(dirtyRect), overColor,
497 pt, xInFrame, gfxSize(width, sizePixel),
498 ascentPixel, ascentPixel,
499 NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, overStyle,
500 vertical);
504 nsRenderingContext refContext(
505 PresContext()->PresShell()->CreateReferenceRenderingContext());
507 CalculateUnderline(refContext, *fontMet);
509 nscolor c = aOverrideColor ? *aOverrideColor : StyleColor()->mColor;
510 ColorPattern color(ToDeviceColor(c));
511 aRenderingContext.ThebesContext()->SetColor(c);
513 nsresult rv = NS_ERROR_FAILURE;
515 if (mState & NS_FRAME_IS_BIDI) {
516 presContext->SetBidiEnabled();
517 nsBidiLevel level = nsBidiPresUtils::BidiLevelFromStyle(StyleContext());
518 if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) {
519 // We let the RenderText function calculate the mnemonic's
520 // underline position for us.
521 nsBidiPositionResolve posResolve;
522 posResolve.logicalIndex = mAccessKeyInfo->mAccesskeyIndex;
523 rv = nsBidiPresUtils::RenderText(mCroppedTitle.get(), mCroppedTitle.Length(), level,
524 presContext, aRenderingContext,
525 refContext, *fontMet,
526 aTextRect.x, baseline,
527 &posResolve,
529 mAccessKeyInfo->mBeforeWidth = posResolve.visualLeftTwips;
530 mAccessKeyInfo->mAccessWidth = posResolve.visualWidth;
532 else
534 rv = nsBidiPresUtils::RenderText(mCroppedTitle.get(), mCroppedTitle.Length(), level,
535 presContext, aRenderingContext,
536 refContext, *fontMet,
537 aTextRect.x, baseline);
540 if (NS_FAILED(rv)) {
541 fontMet->SetTextRunRTL(false);
543 if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) {
544 // In the simple (non-BiDi) case, we calculate the mnemonic's
545 // underline position by getting the text metric.
546 // XXX are attribute values always two byte?
547 if (mAccessKeyInfo->mAccesskeyIndex > 0)
548 mAccessKeyInfo->mBeforeWidth = nsLayoutUtils::
549 AppUnitWidthOfString(mCroppedTitle.get(),
550 mAccessKeyInfo->mAccesskeyIndex,
551 *fontMet, refContext);
552 else
553 mAccessKeyInfo->mBeforeWidth = 0;
556 fontMet->DrawString(mCroppedTitle.get(), mCroppedTitle.Length(),
557 aTextRect.x, baseline, &aRenderingContext,
558 &refContext);
561 if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) {
562 nsRect r(aTextRect.x + mAccessKeyInfo->mBeforeWidth,
563 aTextRect.y + mAccessKeyInfo->mAccessOffset,
564 mAccessKeyInfo->mAccessWidth,
565 mAccessKeyInfo->mAccessUnderlineSize);
566 Rect devPxRect =
567 NSRectToSnappedRect(r, appUnitsPerDevPixel, *drawTarget);
568 drawTarget->FillRect(devPxRect, color);
571 // Strikeout is drawn on top of the text, per
572 // http://www.w3.org/TR/CSS21/zindex.html point 7.2.1.4.1.1.
573 if ((decorations & NS_FONT_DECORATION_LINE_THROUGH) &&
574 strikeStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
575 fontMet->GetStrikeout(offset, size);
576 gfxFloat offsetPixel = presContext->AppUnitsToGfxUnits(offset);
577 gfxFloat sizePixel = presContext->AppUnitsToGfxUnits(size);
578 nsCSSRendering::PaintDecorationLine(this, *drawTarget, ToRect(dirtyRect),
579 strikeColor,
580 pt, xInFrame, gfxSize(width, sizePixel), ascentPixel,
581 offsetPixel, NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH,
582 strikeStyle, vertical);
586 void
587 nsTextBoxFrame::CalculateUnderline(nsRenderingContext& aRenderingContext,
588 nsFontMetrics& aFontMetrics)
590 if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) {
591 // Calculate all fields of mAccessKeyInfo which
592 // are the same for both BiDi and non-BiDi frames.
593 const char16_t *titleString = mCroppedTitle.get();
594 aFontMetrics.SetTextRunRTL(false);
595 mAccessKeyInfo->mAccessWidth = nsLayoutUtils::
596 AppUnitWidthOfString(titleString[mAccessKeyInfo->mAccesskeyIndex],
597 aFontMetrics, aRenderingContext);
599 nscoord offset, baseline;
600 aFontMetrics.GetUnderline(offset, mAccessKeyInfo->mAccessUnderlineSize);
601 baseline = aFontMetrics.MaxAscent();
602 mAccessKeyInfo->mAccessOffset = baseline - offset;
606 nscoord
607 nsTextBoxFrame::CalculateTitleForWidth(nsPresContext* aPresContext,
608 nsRenderingContext& aRenderingContext,
609 nscoord aWidth)
611 if (mTitle.IsEmpty()) {
612 mCroppedTitle.Truncate();
613 return 0;
616 nsRefPtr<nsFontMetrics> fm;
617 nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm));
619 // see if the text will completely fit in the width given
620 nscoord titleWidth =
621 nsLayoutUtils::AppUnitWidthOfStringBidi(mTitle, this, *fm,
622 aRenderingContext);
623 if (titleWidth <= aWidth) {
624 mCroppedTitle = mTitle;
625 if (HasRTLChars(mTitle)) {
626 mState |= NS_FRAME_IS_BIDI;
628 return titleWidth; // fits, done.
631 const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis();
632 // start with an ellipsis
633 mCroppedTitle.Assign(kEllipsis);
635 // see if the width is even smaller than the ellipsis
636 // if so, clear the text (XXX set as many '.' as we can?).
637 fm->SetTextRunRTL(false);
638 titleWidth = nsLayoutUtils::AppUnitWidthOfString(kEllipsis, *fm,
639 aRenderingContext);
641 if (titleWidth > aWidth) {
642 mCroppedTitle.SetLength(0);
643 return 0;
646 // if the ellipsis fits perfectly, no use in trying to insert
647 if (titleWidth == aWidth)
648 return titleWidth;
650 aWidth -= titleWidth;
652 // XXX: This whole block should probably take surrogates into account
653 // XXX and clusters!
654 // ok crop things
655 switch (mCropType)
657 case CropNone:
658 case CropRight:
660 nscoord cwidth;
661 nscoord twidth = 0;
662 int length = mTitle.Length();
663 int i;
664 for (i = 0; i < length; ++i) {
665 char16_t ch = mTitle.CharAt(i);
666 // still in LTR mode
667 cwidth = nsLayoutUtils::AppUnitWidthOfString(ch, *fm,
668 aRenderingContext);
669 if (twidth + cwidth > aWidth)
670 break;
672 twidth += cwidth;
673 if (UCS2_CHAR_IS_BIDI(ch) ) {
674 mState |= NS_FRAME_IS_BIDI;
678 if (i == 0)
679 return titleWidth;
681 // insert what character we can in.
682 nsAutoString title( mTitle );
683 title.Truncate(i);
684 mCroppedTitle.Insert(title, 0);
686 break;
688 case CropLeft:
690 nscoord cwidth;
691 nscoord twidth = 0;
692 int length = mTitle.Length();
693 int i;
694 for (i=length-1; i >= 0; --i) {
695 char16_t ch = mTitle.CharAt(i);
696 cwidth = nsLayoutUtils::AppUnitWidthOfString(ch, *fm,
697 aRenderingContext);
698 if (twidth + cwidth > aWidth)
699 break;
701 twidth += cwidth;
702 if (UCS2_CHAR_IS_BIDI(ch) ) {
703 mState |= NS_FRAME_IS_BIDI;
707 if (i == length-1)
708 return titleWidth;
710 nsAutoString copy;
711 mTitle.Right(copy, length-1-i);
712 mCroppedTitle += copy;
714 break;
716 case CropCenter:
718 nscoord stringWidth =
719 nsLayoutUtils::AppUnitWidthOfStringBidi(mTitle, this, *fm,
720 aRenderingContext);
721 if (stringWidth <= aWidth) {
722 // the entire string will fit in the maximum width
723 mCroppedTitle.Insert(mTitle, 0);
724 break;
727 // determine how much of the string will fit in the max width
728 nscoord charWidth = 0;
729 nscoord totalWidth = 0;
730 char16_t ch;
731 int leftPos, rightPos;
732 nsAutoString leftString, rightString;
734 rightPos = mTitle.Length() - 1;
735 fm->SetTextRunRTL(false);
736 for (leftPos = 0; leftPos <= rightPos;) {
737 // look at the next character on the left end
738 ch = mTitle.CharAt(leftPos);
739 charWidth = nsLayoutUtils::AppUnitWidthOfString(ch, *fm,
740 aRenderingContext);
741 totalWidth += charWidth;
742 if (totalWidth > aWidth)
743 // greater than the allowable width
744 break;
745 leftString.Insert(ch, leftString.Length());
747 if (UCS2_CHAR_IS_BIDI(ch))
748 mState |= NS_FRAME_IS_BIDI;
750 // look at the next character on the right end
751 if (rightPos > leftPos) {
752 // haven't looked at this character yet
753 ch = mTitle.CharAt(rightPos);
754 charWidth =
755 nsLayoutUtils::AppUnitWidthOfString(ch, *fm,
756 aRenderingContext);
757 totalWidth += charWidth;
758 if (totalWidth > aWidth)
759 // greater than the allowable width
760 break;
761 rightString.Insert(ch, 0);
763 if (UCS2_CHAR_IS_BIDI(ch))
764 mState |= NS_FRAME_IS_BIDI;
767 // look at the next two characters
768 leftPos++;
769 rightPos--;
772 mCroppedTitle = leftString + kEllipsis + rightString;
774 break;
777 return nsLayoutUtils::AppUnitWidthOfStringBidi(mCroppedTitle, this, *fm,
778 aRenderingContext);
781 #define OLD_ELLIPSIS NS_LITERAL_STRING("...")
783 // the following block is to append the accesskey to mTitle if there is an accesskey
784 // but the mTitle doesn't have the character
785 void
786 nsTextBoxFrame::UpdateAccessTitle()
789 * Note that if you change appending access key label spec,
790 * you need to maintain same logic in following methods. See bug 324159.
791 * toolkit/content/commonDialog.js (setLabelForNode)
792 * toolkit/content/widgets/text.xml (formatAccessKey)
794 int32_t menuAccessKey;
795 nsMenuBarListener::GetMenuAccessKey(&menuAccessKey);
796 if (!menuAccessKey || mAccessKey.IsEmpty())
797 return;
799 if (!AlwaysAppendAccessKey() &&
800 FindInReadable(mAccessKey, mTitle, nsCaseInsensitiveStringComparator()))
801 return;
803 nsAutoString accessKeyLabel;
804 accessKeyLabel += '(';
805 accessKeyLabel += mAccessKey;
806 ToUpperCase(accessKeyLabel);
807 accessKeyLabel += ')';
809 if (mTitle.IsEmpty()) {
810 mTitle = accessKeyLabel;
811 return;
814 const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis();
815 uint32_t offset = mTitle.Length();
816 if (StringEndsWith(mTitle, kEllipsis)) {
817 offset -= kEllipsis.Length();
818 } else if (StringEndsWith(mTitle, OLD_ELLIPSIS)) {
819 // Try to check with our old ellipsis (for old addons)
820 offset -= OLD_ELLIPSIS.Length();
821 } else {
822 // Try to check with
823 // our default ellipsis (for non-localized addons) or ':'
824 const char16_t kLastChar = mTitle.Last();
825 if (kLastChar == char16_t(0x2026) || kLastChar == char16_t(':'))
826 offset--;
829 if (InsertSeparatorBeforeAccessKey() &&
830 offset > 0 && !NS_IS_SPACE(mTitle[offset - 1])) {
831 mTitle.Insert(' ', offset);
832 offset++;
835 mTitle.Insert(accessKeyLabel, offset);
838 void
839 nsTextBoxFrame::UpdateAccessIndex()
841 int32_t menuAccessKey;
842 nsMenuBarListener::GetMenuAccessKey(&menuAccessKey);
843 if (menuAccessKey) {
844 if (mAccessKey.IsEmpty()) {
845 if (mAccessKeyInfo) {
846 delete mAccessKeyInfo;
847 mAccessKeyInfo = nullptr;
849 } else {
850 if (!mAccessKeyInfo) {
851 mAccessKeyInfo = new nsAccessKeyInfo();
852 if (!mAccessKeyInfo)
853 return;
856 nsAString::const_iterator start, end;
858 mCroppedTitle.BeginReading(start);
859 mCroppedTitle.EndReading(end);
861 // remember the beginning of the string
862 nsAString::const_iterator originalStart = start;
864 bool found;
865 if (!AlwaysAppendAccessKey()) {
866 // not appending access key - do case-sensitive search
867 // first
868 found = FindInReadable(mAccessKey, start, end);
869 if (!found) {
870 // didn't find it - perform a case-insensitive search
871 start = originalStart;
872 found = FindInReadable(mAccessKey, start, end,
873 nsCaseInsensitiveStringComparator());
875 } else {
876 found = RFindInReadable(mAccessKey, start, end,
877 nsCaseInsensitiveStringComparator());
880 if (found)
881 mAccessKeyInfo->mAccesskeyIndex = Distance(originalStart, start);
882 else
883 mAccessKeyInfo->mAccesskeyIndex = kNotFound;
888 void
889 nsTextBoxFrame::RecomputeTitle()
891 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::value, mTitle);
893 // This doesn't handle language-specific uppercasing/lowercasing
894 // rules, unlike textruns.
895 uint8_t textTransform = StyleText()->mTextTransform;
896 if (textTransform == NS_STYLE_TEXT_TRANSFORM_UPPERCASE) {
897 ToUpperCase(mTitle);
898 } else if (textTransform == NS_STYLE_TEXT_TRANSFORM_LOWERCASE) {
899 ToLowerCase(mTitle);
901 // We can't handle NS_STYLE_TEXT_TRANSFORM_CAPITALIZE because we
902 // have no clue about word boundaries here. We also don't handle
903 // NS_STYLE_TEXT_TRANSFORM_FULLWIDTH.
906 void
907 nsTextBoxFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
909 if (!aOldStyleContext) {
910 // We're just being initialized
911 return;
914 const nsStyleText* oldTextStyle = aOldStyleContext->PeekStyleText();
915 // We should really have oldTextStyle here, since we asked for our
916 // nsStyleText during Init(), but if it's not there for some reason
917 // just assume the worst and recompute mTitle.
918 if (!oldTextStyle ||
919 oldTextStyle->mTextTransform != StyleText()->mTextTransform) {
920 RecomputeTitle();
921 UpdateAccessTitle();
925 NS_IMETHODIMP
926 nsTextBoxFrame::DoLayout(nsBoxLayoutState& aBoxLayoutState)
928 if (mNeedsReflowCallback) {
929 nsIReflowCallback* cb = new nsAsyncAccesskeyUpdate(this);
930 if (cb) {
931 PresContext()->PresShell()->PostReflowCallback(cb);
933 mNeedsReflowCallback = false;
936 nsresult rv = nsLeafBoxFrame::DoLayout(aBoxLayoutState);
938 CalcDrawRect(*aBoxLayoutState.GetRenderingContext());
940 const nsStyleText* textStyle = StyleText();
942 nsRect scrollBounds(nsPoint(0, 0), GetSize());
943 nsRect textRect = mTextDrawRect;
945 nsRefPtr<nsFontMetrics> fontMet;
946 nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fontMet));
947 nsBoundingMetrics metrics =
948 fontMet->GetInkBoundsForVisualOverflow(mCroppedTitle.get(),
949 mCroppedTitle.Length(),
950 aBoxLayoutState.GetRenderingContext());
952 textRect.x -= metrics.leftBearing;
953 textRect.width = metrics.width;
954 // In DrawText() we always draw with the baseline at MaxAscent() (relative to mTextDrawRect),
955 textRect.y += fontMet->MaxAscent() - metrics.ascent;
956 textRect.height = metrics.ascent + metrics.descent;
958 // Our scrollable overflow is our bounds; our visual overflow may
959 // extend beyond that.
960 nsRect visualBounds;
961 visualBounds.UnionRect(scrollBounds, textRect);
962 nsOverflowAreas overflow(visualBounds, scrollBounds);
964 if (textStyle->mTextShadow) {
965 // text-shadow extends our visual but not scrollable bounds
966 nsRect &vis = overflow.VisualOverflow();
967 vis.UnionRect(vis, nsLayoutUtils::GetTextShadowRectsUnion(mTextDrawRect, this));
969 FinishAndStoreOverflow(overflow, GetSize());
971 return rv;
974 nsRect
975 nsTextBoxFrame::GetComponentAlphaBounds()
977 if (StyleText()->mTextShadow) {
978 return GetVisualOverflowRectRelativeToSelf();
980 return mTextDrawRect;
983 bool
984 nsTextBoxFrame::ComputesOwnOverflowArea()
986 return true;
989 /* virtual */ void
990 nsTextBoxFrame::MarkIntrinsicISizesDirty()
992 mNeedsRecalc = true;
993 nsTextBoxFrameSuper::MarkIntrinsicISizesDirty();
996 void
997 nsTextBoxFrame::GetTextSize(nsPresContext* aPresContext,
998 nsRenderingContext& aRenderingContext,
999 const nsString& aString,
1000 nsSize& aSize, nscoord& aAscent)
1002 nsRefPtr<nsFontMetrics> fontMet;
1003 nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fontMet));
1004 aSize.height = fontMet->MaxHeight();
1005 aSize.width =
1006 nsLayoutUtils::AppUnitWidthOfStringBidi(aString, this, *fontMet,
1007 aRenderingContext);
1008 aAscent = fontMet->MaxAscent();
1011 void
1012 nsTextBoxFrame::CalcTextSize(nsBoxLayoutState& aBoxLayoutState)
1014 if (mNeedsRecalc)
1016 nsSize size;
1017 nsPresContext* presContext = aBoxLayoutState.PresContext();
1018 nsRenderingContext* rendContext = aBoxLayoutState.GetRenderingContext();
1019 if (rendContext) {
1020 GetTextSize(presContext, *rendContext,
1021 mTitle, size, mAscent);
1022 mTextSize = size;
1023 mNeedsRecalc = false;
1028 void
1029 nsTextBoxFrame::CalcDrawRect(nsRenderingContext &aRenderingContext)
1031 nsRect textRect(nsPoint(0, 0), GetSize());
1032 nsMargin borderPadding;
1033 GetBorderAndPadding(borderPadding);
1034 textRect.Deflate(borderPadding);
1036 // determine (cropped) title and underline position
1037 nsPresContext* presContext = PresContext();
1038 // determine (cropped) title which fits in aRect.width and its width
1039 nscoord titleWidth =
1040 CalculateTitleForWidth(presContext, aRenderingContext, textRect.width);
1042 #ifdef ACCESSIBILITY
1043 // Make sure to update the accessible tree in case when cropped title is
1044 // changed.
1045 nsAccessibilityService* accService = GetAccService();
1046 if (accService) {
1047 accService->UpdateLabelValue(PresContext()->PresShell(), mContent,
1048 mCroppedTitle);
1050 #endif
1052 // determine if and at which position to put the underline
1053 UpdateAccessIndex();
1055 // make the rect as small as our (cropped) text.
1056 nscoord outerWidth = textRect.width;
1057 textRect.width = titleWidth;
1059 // Align our text within the overall rect by checking our text-align property.
1060 const nsStyleVisibility* vis = StyleVisibility();
1061 const nsStyleText* textStyle = StyleText();
1063 if (textStyle->mTextAlign == NS_STYLE_TEXT_ALIGN_CENTER)
1064 textRect.x += (outerWidth - textRect.width)/2;
1065 else if (textStyle->mTextAlign == NS_STYLE_TEXT_ALIGN_RIGHT ||
1066 (textStyle->mTextAlign == NS_STYLE_TEXT_ALIGN_DEFAULT &&
1067 vis->mDirection == NS_STYLE_DIRECTION_RTL) ||
1068 (textStyle->mTextAlign == NS_STYLE_TEXT_ALIGN_END &&
1069 vis->mDirection == NS_STYLE_DIRECTION_LTR)) {
1070 textRect.x += (outerWidth - textRect.width);
1073 mTextDrawRect = textRect;
1077 * Ok return our dimensions
1079 nsSize
1080 nsTextBoxFrame::GetPrefSize(nsBoxLayoutState& aBoxLayoutState)
1082 CalcTextSize(aBoxLayoutState);
1084 nsSize size = mTextSize;
1085 DISPLAY_PREF_SIZE(this, size);
1087 AddBorderAndPadding(size);
1088 bool widthSet, heightSet;
1089 nsIFrame::AddCSSPrefSize(this, size, widthSet, heightSet);
1091 return size;
1095 * Ok return our dimensions
1097 nsSize
1098 nsTextBoxFrame::GetMinSize(nsBoxLayoutState& aBoxLayoutState)
1100 CalcTextSize(aBoxLayoutState);
1102 nsSize size = mTextSize;
1103 DISPLAY_MIN_SIZE(this, size);
1105 // if there is cropping our min width becomes our border and padding
1106 if (mCropType != CropNone)
1107 size.width = 0;
1109 AddBorderAndPadding(size);
1110 bool widthSet, heightSet;
1111 nsIFrame::AddCSSMinSize(aBoxLayoutState, this, size, widthSet, heightSet);
1113 return size;
1116 nscoord
1117 nsTextBoxFrame::GetBoxAscent(nsBoxLayoutState& aBoxLayoutState)
1119 CalcTextSize(aBoxLayoutState);
1121 nscoord ascent = mAscent;
1123 nsMargin m(0,0,0,0);
1124 GetBorderAndPadding(m);
1125 ascent += m.top;
1127 return ascent;
1130 #ifdef DEBUG_FRAME_DUMP
1131 nsresult
1132 nsTextBoxFrame::GetFrameName(nsAString& aResult) const
1134 MakeFrameName(NS_LITERAL_STRING("TextBox"), aResult);
1135 aResult += NS_LITERAL_STRING("[value=") + mTitle + NS_LITERAL_STRING("]");
1136 return NS_OK;
1138 #endif
1140 // If you make changes to this function, check its counterparts
1141 // in nsBoxFrame and nsXULLabelFrame
1142 nsresult
1143 nsTextBoxFrame::RegUnregAccessKey(bool aDoReg)
1145 // if we have no content, we can't do anything
1146 if (!mContent)
1147 return NS_ERROR_FAILURE;
1149 // check if we have a |control| attribute
1150 // do this check first because few elements have control attributes, and we
1151 // can weed out most of the elements quickly.
1153 // XXXjag a side-effect is that we filter out anonymous <label>s
1154 // in e.g. <menu>, <menuitem>, <button>. These <label>s inherit
1155 // |accesskey| and would otherwise register themselves, overwriting
1156 // the content we really meant to be registered.
1157 if (!mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::control))
1158 return NS_OK;
1160 // see if we even have an access key
1161 nsAutoString accessKey;
1162 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, accessKey);
1164 if (accessKey.IsEmpty())
1165 return NS_OK;
1167 // With a valid PresContext we can get the ESM
1168 // and (un)register the access key
1169 EventStateManager* esm = PresContext()->EventStateManager();
1171 uint32_t key = accessKey.First();
1172 if (aDoReg)
1173 esm->RegisterAccessKey(mContent, key);
1174 else
1175 esm->UnregisterAccessKey(mContent, key);
1177 return NS_OK;