Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
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 "nsReadableUtils.h"
9 #include "nsCOMPtr.h"
10 #include "nsGkAtoms.h"
11 #include "nsPresContext.h"
12 #include "nsRenderingContext.h"
13 #include "nsStyleContext.h"
14 #include "nsIContent.h"
15 #include "nsNameSpaceManager.h"
16 #include "nsBoxLayoutState.h"
17 #include "nsMenuBarListener.h"
18 #include "nsXPIDLString.h"
19 #include "nsIServiceManager.h"
20 #include "nsIDOMElement.h"
21 #include "nsIDOMXULLabelElement.h"
22 #include "mozilla/EventStateManager.h"
23 #include "nsITheme.h"
24 #include "nsUnicharUtils.h"
25 #include "nsContentUtils.h"
26 #include "nsCxPusher.h"
27 #include "nsDisplayList.h"
28 #include "nsCSSRendering.h"
29 #include "nsIReflowCallback.h"
30 #include "nsBoxFrame.h"
31 #include "mozilla/Preferences.h"
32 #include "nsLayoutUtils.h"
33 #include "mozilla/Attributes.h"
35 #ifdef ACCESSIBILITY
36 #include "nsAccessibilityService.h"
37 #endif
39 #include "nsBidiUtils.h"
40 #include "nsBidiPresUtils.h"
42 using namespace mozilla;
44 class nsAccessKeyInfo
45 {
46 public:
47 int32_t mAccesskeyIndex;
48 nscoord mBeforeWidth, mAccessWidth, mAccessUnderlineSize, mAccessOffset;
49 };
52 bool nsTextBoxFrame::gAlwaysAppendAccessKey = false;
53 bool nsTextBoxFrame::gAccessKeyPrefInitialized = false;
54 bool nsTextBoxFrame::gInsertSeparatorBeforeAccessKey = false;
55 bool nsTextBoxFrame::gInsertSeparatorPrefInitialized = false;
57 nsIFrame*
58 NS_NewTextBoxFrame (nsIPresShell* aPresShell, nsStyleContext* aContext)
59 {
60 return new (aPresShell) nsTextBoxFrame (aPresShell, aContext);
61 }
63 NS_IMPL_FRAMEARENA_HELPERS(nsTextBoxFrame)
65 NS_QUERYFRAME_HEAD(nsTextBoxFrame)
66 NS_QUERYFRAME_ENTRY(nsTextBoxFrame)
67 NS_QUERYFRAME_TAIL_INHERITING(nsTextBoxFrameSuper)
69 nsresult
70 nsTextBoxFrame::AttributeChanged(int32_t aNameSpaceID,
71 nsIAtom* aAttribute,
72 int32_t aModType)
73 {
74 bool aResize;
75 bool aRedraw;
77 UpdateAttributes(aAttribute, aResize, aRedraw);
79 if (aResize) {
80 PresContext()->PresShell()->
81 FrameNeedsReflow(this, nsIPresShell::eStyleChange,
82 NS_FRAME_IS_DIRTY);
83 } else if (aRedraw) {
84 nsBoxLayoutState state(PresContext());
85 Redraw(state);
86 }
88 // If the accesskey changed, register for the new value
89 // The old value has been unregistered in nsXULElement::SetAttr
90 if (aAttribute == nsGkAtoms::accesskey || aAttribute == nsGkAtoms::control)
91 RegUnregAccessKey(true);
93 return NS_OK;
94 }
96 nsTextBoxFrame::nsTextBoxFrame(nsIPresShell* aShell, nsStyleContext* aContext):
97 nsLeafBoxFrame(aShell, aContext), mAccessKeyInfo(nullptr), mCropType(CropRight),
98 mNeedsReflowCallback(false)
99 {
100 MarkIntrinsicWidthsDirty();
101 }
103 nsTextBoxFrame::~nsTextBoxFrame()
104 {
105 delete mAccessKeyInfo;
106 }
109 void
110 nsTextBoxFrame::Init(nsIContent* aContent,
111 nsIFrame* aParent,
112 nsIFrame* aPrevInFlow)
113 {
114 nsTextBoxFrameSuper::Init(aContent, aParent, aPrevInFlow);
116 bool aResize;
117 bool aRedraw;
118 UpdateAttributes(nullptr, aResize, aRedraw); /* update all */
120 // register access key
121 RegUnregAccessKey(true);
122 }
124 void
125 nsTextBoxFrame::DestroyFrom(nsIFrame* aDestructRoot)
126 {
127 // unregister access key
128 RegUnregAccessKey(false);
129 nsTextBoxFrameSuper::DestroyFrom(aDestructRoot);
130 }
132 bool
133 nsTextBoxFrame::AlwaysAppendAccessKey()
134 {
135 if (!gAccessKeyPrefInitialized)
136 {
137 gAccessKeyPrefInitialized = true;
139 const char* prefName = "intl.menuitems.alwaysappendaccesskeys";
140 nsAdoptingString val = Preferences::GetLocalizedString(prefName);
141 gAlwaysAppendAccessKey = val.Equals(NS_LITERAL_STRING("true"));
142 }
143 return gAlwaysAppendAccessKey;
144 }
146 bool
147 nsTextBoxFrame::InsertSeparatorBeforeAccessKey()
148 {
149 if (!gInsertSeparatorPrefInitialized)
150 {
151 gInsertSeparatorPrefInitialized = true;
153 const char* prefName = "intl.menuitems.insertseparatorbeforeaccesskeys";
154 nsAdoptingString val = Preferences::GetLocalizedString(prefName);
155 gInsertSeparatorBeforeAccessKey = val.EqualsLiteral("true");
156 }
157 return gInsertSeparatorBeforeAccessKey;
158 }
160 class nsAsyncAccesskeyUpdate MOZ_FINAL : public nsIReflowCallback
161 {
162 public:
163 nsAsyncAccesskeyUpdate(nsIFrame* aFrame) : mWeakFrame(aFrame)
164 {
165 }
167 virtual bool ReflowFinished() MOZ_OVERRIDE
168 {
169 bool shouldFlush = false;
170 nsTextBoxFrame* frame =
171 static_cast<nsTextBoxFrame*>(mWeakFrame.GetFrame());
172 if (frame) {
173 shouldFlush = frame->UpdateAccesskey(mWeakFrame);
174 }
175 delete this;
176 return shouldFlush;
177 }
179 virtual void ReflowCallbackCanceled() MOZ_OVERRIDE
180 {
181 delete this;
182 }
184 nsWeakFrame mWeakFrame;
185 };
187 bool
188 nsTextBoxFrame::UpdateAccesskey(nsWeakFrame& aWeakThis)
189 {
190 nsAutoString accesskey;
191 nsCOMPtr<nsIDOMXULLabelElement> labelElement = do_QueryInterface(mContent);
192 NS_ENSURE_TRUE(aWeakThis.IsAlive(), false);
193 if (labelElement) {
194 // Accesskey may be stored on control.
195 // Because this method is called by the reflow callback, current context
196 // may not be the right one. Pushing the context of mContent so that
197 // if nsIDOMXULLabelElement is implemented in XBL, we don't get a
198 // security exception.
199 nsCxPusher cx;
200 if (cx.Push(mContent)) {
201 labelElement->GetAccessKey(accesskey);
202 NS_ENSURE_TRUE(aWeakThis.IsAlive(), false);
203 }
204 }
205 else {
206 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, accesskey);
207 }
209 if (!accesskey.Equals(mAccessKey)) {
210 // Need to get clean mTitle.
211 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::value, mTitle);
212 mAccessKey = accesskey;
213 UpdateAccessTitle();
214 PresContext()->PresShell()->
215 FrameNeedsReflow(this, nsIPresShell::eStyleChange,
216 NS_FRAME_IS_DIRTY);
217 return true;
218 }
219 return false;
220 }
222 void
223 nsTextBoxFrame::UpdateAttributes(nsIAtom* aAttribute,
224 bool& aResize,
225 bool& aRedraw)
226 {
227 bool doUpdateTitle = false;
228 aResize = false;
229 aRedraw = false;
231 if (aAttribute == nullptr || aAttribute == nsGkAtoms::crop) {
232 static nsIContent::AttrValuesArray strings[] =
233 {&nsGkAtoms::left, &nsGkAtoms::start, &nsGkAtoms::center,
234 &nsGkAtoms::right, &nsGkAtoms::end, nullptr};
235 CroppingStyle cropType;
236 switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::crop,
237 strings, eCaseMatters)) {
238 case 0:
239 case 1:
240 cropType = CropLeft;
241 break;
242 case 2:
243 cropType = CropCenter;
244 break;
245 case 3:
246 case 4:
247 cropType = CropRight;
248 break;
249 default:
250 cropType = CropNone;
251 break;
252 }
254 if (cropType != mCropType) {
255 aResize = true;
256 mCropType = cropType;
257 }
258 }
260 if (aAttribute == nullptr || aAttribute == nsGkAtoms::value) {
261 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::value, mTitle);
262 doUpdateTitle = true;
263 }
265 if (aAttribute == nullptr || aAttribute == nsGkAtoms::accesskey) {
266 mNeedsReflowCallback = true;
267 // Ensure that layout is refreshed and reflow callback called.
268 aResize = true;
269 }
271 if (doUpdateTitle) {
272 UpdateAccessTitle();
273 aResize = true;
274 }
276 }
278 class nsDisplayXULTextBox : public nsDisplayItem {
279 public:
280 nsDisplayXULTextBox(nsDisplayListBuilder* aBuilder,
281 nsTextBoxFrame* aFrame) :
282 nsDisplayItem(aBuilder, aFrame),
283 mDisableSubpixelAA(false)
284 {
285 MOZ_COUNT_CTOR(nsDisplayXULTextBox);
286 }
287 #ifdef NS_BUILD_REFCNT_LOGGING
288 virtual ~nsDisplayXULTextBox() {
289 MOZ_COUNT_DTOR(nsDisplayXULTextBox);
290 }
291 #endif
293 virtual void Paint(nsDisplayListBuilder* aBuilder,
294 nsRenderingContext* aCtx) MOZ_OVERRIDE;
295 virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
296 bool* aSnap) MOZ_OVERRIDE;
297 NS_DISPLAY_DECL_NAME("XULTextBox", TYPE_XUL_TEXT_BOX)
299 virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) MOZ_OVERRIDE;
301 virtual void DisableComponentAlpha() MOZ_OVERRIDE {
302 mDisableSubpixelAA = true;
303 }
305 void PaintTextToContext(nsRenderingContext* aCtx,
306 nsPoint aOffset,
307 const nscolor* aColor);
309 bool mDisableSubpixelAA;
310 };
312 static void
313 PaintTextShadowCallback(nsRenderingContext* aCtx,
314 nsPoint aShadowOffset,
315 const nscolor& aShadowColor,
316 void* aData)
317 {
318 reinterpret_cast<nsDisplayXULTextBox*>(aData)->
319 PaintTextToContext(aCtx, aShadowOffset, &aShadowColor);
320 }
322 void
323 nsDisplayXULTextBox::Paint(nsDisplayListBuilder* aBuilder,
324 nsRenderingContext* aCtx)
325 {
326 gfxContextAutoDisableSubpixelAntialiasing disable(aCtx->ThebesContext(),
327 mDisableSubpixelAA);
329 // Paint the text shadow before doing any foreground stuff
330 nsRect drawRect = static_cast<nsTextBoxFrame*>(mFrame)->mTextDrawRect +
331 ToReferenceFrame();
332 nsLayoutUtils::PaintTextShadow(mFrame, aCtx,
333 drawRect, mVisibleRect,
334 mFrame->StyleColor()->mColor,
335 PaintTextShadowCallback,
336 (void*)this);
338 PaintTextToContext(aCtx, nsPoint(0, 0), nullptr);
339 }
341 void
342 nsDisplayXULTextBox::PaintTextToContext(nsRenderingContext* aCtx,
343 nsPoint aOffset,
344 const nscolor* aColor)
345 {
346 static_cast<nsTextBoxFrame*>(mFrame)->
347 PaintTitle(*aCtx, mVisibleRect, ToReferenceFrame() + aOffset, aColor);
348 }
350 nsRect
351 nsDisplayXULTextBox::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) {
352 *aSnap = false;
353 return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
354 }
356 nsRect
357 nsDisplayXULTextBox::GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder)
358 {
359 return static_cast<nsTextBoxFrame*>(mFrame)->GetComponentAlphaBounds() +
360 ToReferenceFrame();
361 }
363 void
364 nsTextBoxFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
365 const nsRect& aDirtyRect,
366 const nsDisplayListSet& aLists)
367 {
368 if (!IsVisibleForPainting(aBuilder))
369 return;
371 nsLeafBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
373 aLists.Content()->AppendNewToTop(new (aBuilder)
374 nsDisplayXULTextBox(aBuilder, this));
375 }
377 void
378 nsTextBoxFrame::PaintTitle(nsRenderingContext& aRenderingContext,
379 const nsRect& aDirtyRect,
380 nsPoint aPt,
381 const nscolor* aOverrideColor)
382 {
383 if (mTitle.IsEmpty())
384 return;
386 DrawText(aRenderingContext, aDirtyRect, mTextDrawRect + aPt, aOverrideColor);
387 }
389 void
390 nsTextBoxFrame::DrawText(nsRenderingContext& aRenderingContext,
391 const nsRect& aDirtyRect,
392 const nsRect& aTextRect,
393 const nscolor* aOverrideColor)
394 {
395 nsPresContext* presContext = PresContext();
397 // paint the title
398 nscolor overColor;
399 nscolor underColor;
400 nscolor strikeColor;
401 uint8_t overStyle;
402 uint8_t underStyle;
403 uint8_t strikeStyle;
405 // Begin with no decorations
406 uint8_t decorations = NS_STYLE_TEXT_DECORATION_LINE_NONE;
407 // A mask of all possible decorations.
408 uint8_t decorMask = NS_STYLE_TEXT_DECORATION_LINE_LINES_MASK;
410 nsIFrame* f = this;
411 do { // find decoration colors
412 nsStyleContext* context = f->StyleContext();
413 if (!context->HasTextDecorationLines()) {
414 break;
415 }
416 const nsStyleTextReset* styleText = context->StyleTextReset();
418 if (decorMask & styleText->mTextDecorationLine) { // a decoration defined here
419 nscolor color;
420 if (aOverrideColor) {
421 color = *aOverrideColor;
422 } else {
423 bool isForeground;
424 styleText->GetDecorationColor(color, isForeground);
425 if (isForeground) {
426 color = nsLayoutUtils::GetColor(f, eCSSProperty_color);
427 }
428 }
429 uint8_t style = styleText->GetDecorationStyle();
431 if (NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE & decorMask &
432 styleText->mTextDecorationLine) {
433 underColor = color;
434 underStyle = style;
435 decorMask &= ~NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE;
436 decorations |= NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE;
437 }
438 if (NS_STYLE_TEXT_DECORATION_LINE_OVERLINE & decorMask &
439 styleText->mTextDecorationLine) {
440 overColor = color;
441 overStyle = style;
442 decorMask &= ~NS_STYLE_TEXT_DECORATION_LINE_OVERLINE;
443 decorations |= NS_STYLE_TEXT_DECORATION_LINE_OVERLINE;
444 }
445 if (NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH & decorMask &
446 styleText->mTextDecorationLine) {
447 strikeColor = color;
448 strikeStyle = style;
449 decorMask &= ~NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH;
450 decorations |= NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH;
451 }
452 }
453 } while (0 != decorMask &&
454 (f = nsLayoutUtils::GetParentOrPlaceholderFor(f)));
456 nsRefPtr<nsFontMetrics> fontMet;
457 nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fontMet));
459 nscoord offset;
460 nscoord size;
461 nscoord ascent = fontMet->MaxAscent();
463 nscoord baseline =
464 presContext->RoundAppUnitsToNearestDevPixels(aTextRect.y + ascent);
465 nsRefPtr<gfxContext> ctx = aRenderingContext.ThebesContext();
466 gfxPoint pt(presContext->AppUnitsToGfxUnits(aTextRect.x),
467 presContext->AppUnitsToGfxUnits(aTextRect.y));
468 gfxFloat width = presContext->AppUnitsToGfxUnits(aTextRect.width);
469 gfxFloat ascentPixel = presContext->AppUnitsToGfxUnits(ascent);
470 gfxFloat xInFrame = PresContext()->AppUnitsToGfxUnits(mTextDrawRect.x);
471 gfxRect dirtyRect(presContext->AppUnitsToGfxUnits(aDirtyRect));
473 // Underlines are drawn before overlines, and both before the text
474 // itself, per http://www.w3.org/TR/CSS21/zindex.html point 7.2.1.4.1.1.
475 // (We don't apply this rule to the access-key underline because we only
476 // find out where that is as a side effect of drawing the text, in the
477 // general case -- see below.)
478 if (decorations & (NS_FONT_DECORATION_OVERLINE |
479 NS_FONT_DECORATION_UNDERLINE)) {
480 fontMet->GetUnderline(offset, size);
481 gfxFloat offsetPixel = presContext->AppUnitsToGfxUnits(offset);
482 gfxFloat sizePixel = presContext->AppUnitsToGfxUnits(size);
483 if ((decorations & NS_FONT_DECORATION_UNDERLINE) &&
484 underStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
485 nsCSSRendering::PaintDecorationLine(this, ctx, dirtyRect, underColor,
486 pt, xInFrame, gfxSize(width, sizePixel),
487 ascentPixel, offsetPixel,
488 NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, underStyle);
489 }
490 if ((decorations & NS_FONT_DECORATION_OVERLINE) &&
491 overStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
492 nsCSSRendering::PaintDecorationLine(this, ctx, dirtyRect, overColor,
493 pt, xInFrame, gfxSize(width, sizePixel),
494 ascentPixel, ascentPixel,
495 NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, overStyle);
496 }
497 }
499 nsRefPtr<nsRenderingContext> refContext =
500 PresContext()->PresShell()->CreateReferenceRenderingContext();
502 aRenderingContext.SetFont(fontMet);
503 refContext->SetFont(fontMet);
505 CalculateUnderline(*refContext);
507 aRenderingContext.SetColor(aOverrideColor ? *aOverrideColor : StyleColor()->mColor);
509 nsresult rv = NS_ERROR_FAILURE;
511 if (mState & NS_FRAME_IS_BIDI) {
512 presContext->SetBidiEnabled();
513 nsBidiLevel level = nsBidiPresUtils::BidiLevelFromStyle(StyleContext());
514 if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) {
515 // We let the RenderText function calculate the mnemonic's
516 // underline position for us.
517 nsBidiPositionResolve posResolve;
518 posResolve.logicalIndex = mAccessKeyInfo->mAccesskeyIndex;
519 rv = nsBidiPresUtils::RenderText(mCroppedTitle.get(), mCroppedTitle.Length(), level,
520 presContext, aRenderingContext,
521 *refContext,
522 aTextRect.x, baseline,
523 &posResolve,
524 1);
525 mAccessKeyInfo->mBeforeWidth = posResolve.visualLeftTwips;
526 mAccessKeyInfo->mAccessWidth = posResolve.visualWidth;
527 }
528 else
529 {
530 rv = nsBidiPresUtils::RenderText(mCroppedTitle.get(), mCroppedTitle.Length(), level,
531 presContext, aRenderingContext,
532 *refContext,
533 aTextRect.x, baseline);
534 }
535 }
536 if (NS_FAILED(rv)) {
537 aRenderingContext.SetTextRunRTL(false);
539 if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) {
540 // In the simple (non-BiDi) case, we calculate the mnemonic's
541 // underline position by getting the text metric.
542 // XXX are attribute values always two byte?
543 if (mAccessKeyInfo->mAccesskeyIndex > 0)
544 mAccessKeyInfo->mBeforeWidth =
545 refContext->GetWidth(mCroppedTitle.get(),
546 mAccessKeyInfo->mAccesskeyIndex);
547 else
548 mAccessKeyInfo->mBeforeWidth = 0;
549 }
551 fontMet->DrawString(mCroppedTitle.get(), mCroppedTitle.Length(),
552 aTextRect.x, baseline, &aRenderingContext,
553 refContext.get());
554 }
556 if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) {
557 aRenderingContext.FillRect(aTextRect.x + mAccessKeyInfo->mBeforeWidth,
558 aTextRect.y + mAccessKeyInfo->mAccessOffset,
559 mAccessKeyInfo->mAccessWidth,
560 mAccessKeyInfo->mAccessUnderlineSize);
561 }
563 // Strikeout is drawn on top of the text, per
564 // http://www.w3.org/TR/CSS21/zindex.html point 7.2.1.4.1.1.
565 if ((decorations & NS_FONT_DECORATION_LINE_THROUGH) &&
566 strikeStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
567 fontMet->GetStrikeout(offset, size);
568 gfxFloat offsetPixel = presContext->AppUnitsToGfxUnits(offset);
569 gfxFloat sizePixel = presContext->AppUnitsToGfxUnits(size);
570 nsCSSRendering::PaintDecorationLine(this, ctx, dirtyRect, strikeColor,
571 pt, xInFrame, gfxSize(width, sizePixel), ascentPixel,
572 offsetPixel, NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH,
573 strikeStyle);
574 }
575 }
577 void
578 nsTextBoxFrame::CalculateUnderline(nsRenderingContext& aRenderingContext)
579 {
580 if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) {
581 // Calculate all fields of mAccessKeyInfo which
582 // are the same for both BiDi and non-BiDi frames.
583 const char16_t *titleString = mCroppedTitle.get();
584 aRenderingContext.SetTextRunRTL(false);
585 mAccessKeyInfo->mAccessWidth =
586 aRenderingContext.GetWidth(titleString[mAccessKeyInfo->
587 mAccesskeyIndex]);
589 nscoord offset, baseline;
590 nsFontMetrics* metrics = aRenderingContext.FontMetrics();
591 metrics->GetUnderline(offset, mAccessKeyInfo->mAccessUnderlineSize);
592 baseline = metrics->MaxAscent();
593 mAccessKeyInfo->mAccessOffset = baseline - offset;
594 }
595 }
597 nscoord
598 nsTextBoxFrame::CalculateTitleForWidth(nsPresContext* aPresContext,
599 nsRenderingContext& aRenderingContext,
600 nscoord aWidth)
601 {
602 if (mTitle.IsEmpty()) {
603 mCroppedTitle.Truncate();
604 return 0;
605 }
607 nsRefPtr<nsFontMetrics> fm;
608 nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm));
609 aRenderingContext.SetFont(fm);
611 // see if the text will completely fit in the width given
612 nscoord titleWidth = nsLayoutUtils::GetStringWidth(this, &aRenderingContext,
613 mTitle.get(), mTitle.Length());
615 if (titleWidth <= aWidth) {
616 mCroppedTitle = mTitle;
617 if (HasRTLChars(mTitle)) {
618 mState |= NS_FRAME_IS_BIDI;
619 }
620 return titleWidth; // fits, done.
621 }
623 const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis();
624 // start with an ellipsis
625 mCroppedTitle.Assign(kEllipsis);
627 // see if the width is even smaller than the ellipsis
628 // if so, clear the text (XXX set as many '.' as we can?).
629 aRenderingContext.SetTextRunRTL(false);
630 titleWidth = aRenderingContext.GetWidth(kEllipsis);
632 if (titleWidth > aWidth) {
633 mCroppedTitle.SetLength(0);
634 return 0;
635 }
637 // if the ellipsis fits perfectly, no use in trying to insert
638 if (titleWidth == aWidth)
639 return titleWidth;
641 aWidth -= titleWidth;
643 // XXX: This whole block should probably take surrogates into account
644 // XXX and clusters!
645 // ok crop things
646 switch (mCropType)
647 {
648 case CropNone:
649 case CropRight:
650 {
651 nscoord cwidth;
652 nscoord twidth = 0;
653 int length = mTitle.Length();
654 int i;
655 for (i = 0; i < length; ++i) {
656 char16_t ch = mTitle.CharAt(i);
657 // still in LTR mode
658 cwidth = aRenderingContext.GetWidth(ch);
659 if (twidth + cwidth > aWidth)
660 break;
662 twidth += cwidth;
663 if (UCS2_CHAR_IS_BIDI(ch) ) {
664 mState |= NS_FRAME_IS_BIDI;
665 }
666 }
668 if (i == 0)
669 return titleWidth;
671 // insert what character we can in.
672 nsAutoString title( mTitle );
673 title.Truncate(i);
674 mCroppedTitle.Insert(title, 0);
675 }
676 break;
678 case CropLeft:
679 {
680 nscoord cwidth;
681 nscoord twidth = 0;
682 int length = mTitle.Length();
683 int i;
684 for (i=length-1; i >= 0; --i) {
685 char16_t ch = mTitle.CharAt(i);
686 cwidth = aRenderingContext.GetWidth(ch);
687 if (twidth + cwidth > aWidth)
688 break;
690 twidth += cwidth;
691 if (UCS2_CHAR_IS_BIDI(ch) ) {
692 mState |= NS_FRAME_IS_BIDI;
693 }
694 }
696 if (i == length-1)
697 return titleWidth;
699 nsAutoString copy;
700 mTitle.Right(copy, length-1-i);
701 mCroppedTitle += copy;
702 }
703 break;
705 case CropCenter:
706 {
707 nscoord stringWidth =
708 nsLayoutUtils::GetStringWidth(this, &aRenderingContext,
709 mTitle.get(), mTitle.Length());
710 if (stringWidth <= aWidth) {
711 // the entire string will fit in the maximum width
712 mCroppedTitle.Insert(mTitle, 0);
713 break;
714 }
716 // determine how much of the string will fit in the max width
717 nscoord charWidth = 0;
718 nscoord totalWidth = 0;
719 char16_t ch;
720 int leftPos, rightPos;
721 nsAutoString leftString, rightString;
723 rightPos = mTitle.Length() - 1;
724 aRenderingContext.SetTextRunRTL(false);
725 for (leftPos = 0; leftPos <= rightPos;) {
726 // look at the next character on the left end
727 ch = mTitle.CharAt(leftPos);
728 charWidth = aRenderingContext.GetWidth(ch);
729 totalWidth += charWidth;
730 if (totalWidth > aWidth)
731 // greater than the allowable width
732 break;
733 leftString.Insert(ch, leftString.Length());
735 if (UCS2_CHAR_IS_BIDI(ch))
736 mState |= NS_FRAME_IS_BIDI;
738 // look at the next character on the right end
739 if (rightPos > leftPos) {
740 // haven't looked at this character yet
741 ch = mTitle.CharAt(rightPos);
742 charWidth = aRenderingContext.GetWidth(ch);
743 totalWidth += charWidth;
744 if (totalWidth > aWidth)
745 // greater than the allowable width
746 break;
747 rightString.Insert(ch, 0);
749 if (UCS2_CHAR_IS_BIDI(ch))
750 mState |= NS_FRAME_IS_BIDI;
751 }
753 // look at the next two characters
754 leftPos++;
755 rightPos--;
756 }
758 mCroppedTitle = leftString + kEllipsis + rightString;
759 }
760 break;
761 }
763 return nsLayoutUtils::GetStringWidth(this, &aRenderingContext,
764 mCroppedTitle.get(), mCroppedTitle.Length());
765 }
767 #define OLD_ELLIPSIS NS_LITERAL_STRING("...")
769 // the following block is to append the accesskey to mTitle if there is an accesskey
770 // but the mTitle doesn't have the character
771 void
772 nsTextBoxFrame::UpdateAccessTitle()
773 {
774 /*
775 * Note that if you change appending access key label spec,
776 * you need to maintain same logic in following methods. See bug 324159.
777 * toolkit/content/commonDialog.js (setLabelForNode)
778 * toolkit/content/widgets/text.xml (formatAccessKey)
779 */
780 int32_t menuAccessKey;
781 nsMenuBarListener::GetMenuAccessKey(&menuAccessKey);
782 if (!menuAccessKey || mAccessKey.IsEmpty())
783 return;
785 if (!AlwaysAppendAccessKey() &&
786 FindInReadable(mAccessKey, mTitle, nsCaseInsensitiveStringComparator()))
787 return;
789 nsAutoString accessKeyLabel;
790 accessKeyLabel += '(';
791 accessKeyLabel += mAccessKey;
792 ToUpperCase(accessKeyLabel);
793 accessKeyLabel += ')';
795 if (mTitle.IsEmpty()) {
796 mTitle = accessKeyLabel;
797 return;
798 }
800 const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis();
801 uint32_t offset = mTitle.Length();
802 if (StringEndsWith(mTitle, kEllipsis)) {
803 offset -= kEllipsis.Length();
804 } else if (StringEndsWith(mTitle, OLD_ELLIPSIS)) {
805 // Try to check with our old ellipsis (for old addons)
806 offset -= OLD_ELLIPSIS.Length();
807 } else {
808 // Try to check with
809 // our default ellipsis (for non-localized addons) or ':'
810 const char16_t kLastChar = mTitle.Last();
811 if (kLastChar == char16_t(0x2026) || kLastChar == char16_t(':'))
812 offset--;
813 }
815 if (InsertSeparatorBeforeAccessKey() &&
816 offset > 0 && !NS_IS_SPACE(mTitle[offset - 1])) {
817 mTitle.Insert(' ', offset);
818 offset++;
819 }
821 mTitle.Insert(accessKeyLabel, offset);
822 }
824 void
825 nsTextBoxFrame::UpdateAccessIndex()
826 {
827 int32_t menuAccessKey;
828 nsMenuBarListener::GetMenuAccessKey(&menuAccessKey);
829 if (menuAccessKey) {
830 if (mAccessKey.IsEmpty()) {
831 if (mAccessKeyInfo) {
832 delete mAccessKeyInfo;
833 mAccessKeyInfo = nullptr;
834 }
835 } else {
836 if (!mAccessKeyInfo) {
837 mAccessKeyInfo = new nsAccessKeyInfo();
838 if (!mAccessKeyInfo)
839 return;
840 }
842 nsAString::const_iterator start, end;
844 mCroppedTitle.BeginReading(start);
845 mCroppedTitle.EndReading(end);
847 // remember the beginning of the string
848 nsAString::const_iterator originalStart = start;
850 bool found;
851 if (!AlwaysAppendAccessKey()) {
852 // not appending access key - do case-sensitive search
853 // first
854 found = FindInReadable(mAccessKey, start, end);
855 if (!found) {
856 // didn't find it - perform a case-insensitive search
857 start = originalStart;
858 found = FindInReadable(mAccessKey, start, end,
859 nsCaseInsensitiveStringComparator());
860 }
861 } else {
862 found = RFindInReadable(mAccessKey, start, end,
863 nsCaseInsensitiveStringComparator());
864 }
866 if (found)
867 mAccessKeyInfo->mAccesskeyIndex = Distance(originalStart, start);
868 else
869 mAccessKeyInfo->mAccesskeyIndex = kNotFound;
870 }
871 }
872 }
874 NS_IMETHODIMP
875 nsTextBoxFrame::DoLayout(nsBoxLayoutState& aBoxLayoutState)
876 {
877 if (mNeedsReflowCallback) {
878 nsIReflowCallback* cb = new nsAsyncAccesskeyUpdate(this);
879 if (cb) {
880 PresContext()->PresShell()->PostReflowCallback(cb);
881 }
882 mNeedsReflowCallback = false;
883 }
885 nsresult rv = nsLeafBoxFrame::DoLayout(aBoxLayoutState);
887 CalcDrawRect(*aBoxLayoutState.GetRenderingContext());
889 const nsStyleText* textStyle = StyleText();
891 nsRect scrollBounds(nsPoint(0, 0), GetSize());
892 nsRect textRect = mTextDrawRect;
894 nsRefPtr<nsFontMetrics> fontMet;
895 nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fontMet));
896 nsBoundingMetrics metrics =
897 fontMet->GetInkBoundsForVisualOverflow(mCroppedTitle.get(),
898 mCroppedTitle.Length(),
899 aBoxLayoutState.GetRenderingContext());
901 textRect.x -= metrics.leftBearing;
902 textRect.width = metrics.width;
903 // In DrawText() we always draw with the baseline at MaxAscent() (relative to mTextDrawRect),
904 textRect.y += fontMet->MaxAscent() - metrics.ascent;
905 textRect.height = metrics.ascent + metrics.descent;
907 // Our scrollable overflow is our bounds; our visual overflow may
908 // extend beyond that.
909 nsRect visualBounds;
910 visualBounds.UnionRect(scrollBounds, textRect);
911 nsOverflowAreas overflow(visualBounds, scrollBounds);
913 if (textStyle->mTextShadow) {
914 // text-shadow extends our visual but not scrollable bounds
915 nsRect &vis = overflow.VisualOverflow();
916 vis.UnionRect(vis, nsLayoutUtils::GetTextShadowRectsUnion(mTextDrawRect, this));
917 }
918 FinishAndStoreOverflow(overflow, GetSize());
920 return rv;
921 }
923 nsRect
924 nsTextBoxFrame::GetComponentAlphaBounds()
925 {
926 if (StyleText()->mTextShadow) {
927 return GetVisualOverflowRectRelativeToSelf();
928 }
929 return mTextDrawRect;
930 }
932 bool
933 nsTextBoxFrame::ComputesOwnOverflowArea()
934 {
935 return true;
936 }
938 /* virtual */ void
939 nsTextBoxFrame::MarkIntrinsicWidthsDirty()
940 {
941 mNeedsRecalc = true;
942 nsTextBoxFrameSuper::MarkIntrinsicWidthsDirty();
943 }
945 void
946 nsTextBoxFrame::GetTextSize(nsPresContext* aPresContext,
947 nsRenderingContext& aRenderingContext,
948 const nsString& aString,
949 nsSize& aSize, nscoord& aAscent)
950 {
951 nsRefPtr<nsFontMetrics> fontMet;
952 nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fontMet));
953 aSize.height = fontMet->MaxHeight();
954 aRenderingContext.SetFont(fontMet);
955 aSize.width =
956 nsLayoutUtils::GetStringWidth(this, &aRenderingContext,
957 aString.get(), aString.Length());
958 aAscent = fontMet->MaxAscent();
959 }
961 void
962 nsTextBoxFrame::CalcTextSize(nsBoxLayoutState& aBoxLayoutState)
963 {
964 if (mNeedsRecalc)
965 {
966 nsSize size;
967 nsPresContext* presContext = aBoxLayoutState.PresContext();
968 nsRenderingContext* rendContext = aBoxLayoutState.GetRenderingContext();
969 if (rendContext) {
970 GetTextSize(presContext, *rendContext,
971 mTitle, size, mAscent);
972 mTextSize = size;
973 mNeedsRecalc = false;
974 }
975 }
976 }
978 void
979 nsTextBoxFrame::CalcDrawRect(nsRenderingContext &aRenderingContext)
980 {
981 nsRect textRect(nsPoint(0, 0), GetSize());
982 nsMargin borderPadding;
983 GetBorderAndPadding(borderPadding);
984 textRect.Deflate(borderPadding);
986 // determine (cropped) title and underline position
987 nsPresContext* presContext = PresContext();
988 // determine (cropped) title which fits in aRect.width and its width
989 nscoord titleWidth =
990 CalculateTitleForWidth(presContext, aRenderingContext, textRect.width);
992 #ifdef ACCESSIBILITY
993 // Make sure to update the accessible tree in case when cropped title is
994 // changed.
995 nsAccessibilityService* accService = GetAccService();
996 if (accService) {
997 accService->UpdateLabelValue(PresContext()->PresShell(), mContent,
998 mCroppedTitle);
999 }
1000 #endif
1002 // determine if and at which position to put the underline
1003 UpdateAccessIndex();
1005 // make the rect as small as our (cropped) text.
1006 nscoord outerWidth = textRect.width;
1007 textRect.width = titleWidth;
1009 // Align our text within the overall rect by checking our text-align property.
1010 const nsStyleVisibility* vis = StyleVisibility();
1011 const nsStyleText* textStyle = StyleText();
1013 if (textStyle->mTextAlign == NS_STYLE_TEXT_ALIGN_CENTER)
1014 textRect.x += (outerWidth - textRect.width)/2;
1015 else if (textStyle->mTextAlign == NS_STYLE_TEXT_ALIGN_RIGHT ||
1016 (textStyle->mTextAlign == NS_STYLE_TEXT_ALIGN_DEFAULT &&
1017 vis->mDirection == NS_STYLE_DIRECTION_RTL) ||
1018 (textStyle->mTextAlign == NS_STYLE_TEXT_ALIGN_END &&
1019 vis->mDirection == NS_STYLE_DIRECTION_LTR)) {
1020 textRect.x += (outerWidth - textRect.width);
1021 }
1023 mTextDrawRect = textRect;
1024 }
1026 /**
1027 * Ok return our dimensions
1028 */
1029 nsSize
1030 nsTextBoxFrame::GetPrefSize(nsBoxLayoutState& aBoxLayoutState)
1031 {
1032 CalcTextSize(aBoxLayoutState);
1034 nsSize size = mTextSize;
1035 DISPLAY_PREF_SIZE(this, size);
1037 AddBorderAndPadding(size);
1038 bool widthSet, heightSet;
1039 nsIFrame::AddCSSPrefSize(this, size, widthSet, heightSet);
1041 return size;
1042 }
1044 /**
1045 * Ok return our dimensions
1046 */
1047 nsSize
1048 nsTextBoxFrame::GetMinSize(nsBoxLayoutState& aBoxLayoutState)
1049 {
1050 CalcTextSize(aBoxLayoutState);
1052 nsSize size = mTextSize;
1053 DISPLAY_MIN_SIZE(this, size);
1055 // if there is cropping our min width becomes our border and padding
1056 if (mCropType != CropNone)
1057 size.width = 0;
1059 AddBorderAndPadding(size);
1060 bool widthSet, heightSet;
1061 nsIFrame::AddCSSMinSize(aBoxLayoutState, this, size, widthSet, heightSet);
1063 return size;
1064 }
1066 nscoord
1067 nsTextBoxFrame::GetBoxAscent(nsBoxLayoutState& aBoxLayoutState)
1068 {
1069 CalcTextSize(aBoxLayoutState);
1071 nscoord ascent = mAscent;
1073 nsMargin m(0,0,0,0);
1074 GetBorderAndPadding(m);
1075 ascent += m.top;
1077 return ascent;
1078 }
1080 #ifdef DEBUG_FRAME_DUMP
1081 nsresult
1082 nsTextBoxFrame::GetFrameName(nsAString& aResult) const
1083 {
1084 MakeFrameName(NS_LITERAL_STRING("TextBox"), aResult);
1085 aResult += NS_LITERAL_STRING("[value=") + mTitle + NS_LITERAL_STRING("]");
1086 return NS_OK;
1087 }
1088 #endif
1090 // If you make changes to this function, check its counterparts
1091 // in nsBoxFrame and nsXULLabelFrame
1092 nsresult
1093 nsTextBoxFrame::RegUnregAccessKey(bool aDoReg)
1094 {
1095 // if we have no content, we can't do anything
1096 if (!mContent)
1097 return NS_ERROR_FAILURE;
1099 // check if we have a |control| attribute
1100 // do this check first because few elements have control attributes, and we
1101 // can weed out most of the elements quickly.
1103 // XXXjag a side-effect is that we filter out anonymous <label>s
1104 // in e.g. <menu>, <menuitem>, <button>. These <label>s inherit
1105 // |accesskey| and would otherwise register themselves, overwriting
1106 // the content we really meant to be registered.
1107 if (!mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::control))
1108 return NS_OK;
1110 // see if we even have an access key
1111 nsAutoString accessKey;
1112 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, accessKey);
1114 if (accessKey.IsEmpty())
1115 return NS_OK;
1117 // With a valid PresContext we can get the ESM
1118 // and (un)register the access key
1119 EventStateManager* esm = PresContext()->EventStateManager();
1121 uint32_t key = accessKey.First();
1122 if (aDoReg)
1123 esm->RegisterAccessKey(mContent, key);
1124 else
1125 esm->UnregisterAccessKey(mContent, key);
1127 return NS_OK;
1128 }