/src/mozilla-central/layout/forms/nsFieldSetFrame.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
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 "nsFieldSetFrame.h" |
8 | | |
9 | | #include <algorithm> |
10 | | #include "gfxContext.h" |
11 | | #include "mozilla/gfx/2D.h" |
12 | | #include "mozilla/Likely.h" |
13 | | #include "mozilla/Maybe.h" |
14 | | #include "nsCSSAnonBoxes.h" |
15 | | #include "nsCSSRendering.h" |
16 | | #include "nsDisplayList.h" |
17 | | #include "nsGkAtoms.h" |
18 | | #include "nsIFrameInlines.h" |
19 | | #include "nsLayoutUtils.h" |
20 | | #include "nsLegendFrame.h" |
21 | | #include "nsStyleConsts.h" |
22 | | |
23 | | using namespace mozilla; |
24 | | using namespace mozilla::gfx; |
25 | | using namespace mozilla::layout; |
26 | | |
27 | | nsContainerFrame* |
28 | | NS_NewFieldSetFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle) |
29 | 0 | { |
30 | 0 | return new (aPresShell) nsFieldSetFrame(aStyle); |
31 | 0 | } |
32 | | |
33 | | NS_IMPL_FRAMEARENA_HELPERS(nsFieldSetFrame) |
34 | | |
35 | | nsFieldSetFrame::nsFieldSetFrame(ComputedStyle* aStyle) |
36 | | : nsContainerFrame(aStyle, kClassID) |
37 | | , mLegendRect(GetWritingMode()) |
38 | 0 | { |
39 | 0 | mLegendSpace = 0; |
40 | 0 | } |
41 | | |
42 | | nsRect |
43 | | nsFieldSetFrame::VisualBorderRectRelativeToSelf() const |
44 | 0 | { |
45 | 0 | WritingMode wm = GetWritingMode(); |
46 | 0 | Side legendSide = wm.PhysicalSide(eLogicalSideBStart); |
47 | 0 | nscoord legendBorder = StyleBorder()->GetComputedBorderWidth(legendSide); |
48 | 0 | LogicalRect r(wm, LogicalPoint(wm, 0, 0), GetLogicalSize(wm)); |
49 | 0 | nsSize containerSize = r.Size(wm).GetPhysicalSize(wm); |
50 | 0 | if (legendBorder < mLegendRect.BSize(wm)) { |
51 | 0 | nscoord off = (mLegendRect.BSize(wm) - legendBorder) / 2; |
52 | 0 | r.BStart(wm) += off; |
53 | 0 | r.BSize(wm) -= off; |
54 | 0 | } |
55 | 0 | return r.GetPhysicalRect(wm, containerSize); |
56 | 0 | } |
57 | | |
58 | | nsIFrame* |
59 | | nsFieldSetFrame::GetInner() const |
60 | 0 | { |
61 | 0 | nsIFrame* last = mFrames.LastChild(); |
62 | 0 | if (last && |
63 | 0 | last->Style()->GetPseudo() == nsCSSAnonBoxes::fieldsetContent()) { |
64 | 0 | return last; |
65 | 0 | } |
66 | 0 | MOZ_ASSERT(mFrames.LastChild() == mFrames.FirstChild()); |
67 | 0 | return nullptr; |
68 | 0 | } |
69 | | |
70 | | nsIFrame* |
71 | | nsFieldSetFrame::GetLegend() const |
72 | 0 | { |
73 | 0 | if (mFrames.FirstChild() == GetInner()) { |
74 | 0 | MOZ_ASSERT(mFrames.LastChild() == mFrames.FirstChild()); |
75 | 0 | return nullptr; |
76 | 0 | } |
77 | 0 | MOZ_ASSERT(mFrames.FirstChild() && |
78 | 0 | mFrames.FirstChild()->GetContentInsertionFrame()->IsLegendFrame()); |
79 | 0 | return mFrames.FirstChild(); |
80 | 0 | } |
81 | | |
82 | | class nsDisplayFieldSetBorder final : public nsDisplayItem |
83 | | { |
84 | | public: |
85 | | nsDisplayFieldSetBorder(nsDisplayListBuilder* aBuilder, |
86 | | nsFieldSetFrame* aFrame) |
87 | 0 | : nsDisplayItem(aBuilder, aFrame) { |
88 | 0 | MOZ_COUNT_CTOR(nsDisplayFieldSetBorder); |
89 | 0 | } |
90 | | #ifdef NS_BUILD_REFCNT_LOGGING |
91 | | virtual ~nsDisplayFieldSetBorder() { |
92 | | MOZ_COUNT_DTOR(nsDisplayFieldSetBorder); |
93 | | } |
94 | | #endif |
95 | | virtual void Paint(nsDisplayListBuilder* aBuilder, |
96 | | gfxContext* aCtx) override; |
97 | | virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override; |
98 | | virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, |
99 | | const nsDisplayItemGeometry* aGeometry, |
100 | | nsRegion *aInvalidRegion) const override; |
101 | | bool CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder, |
102 | | mozilla::wr::IpcResourceUpdateQueue& aResources, |
103 | | const StackingContextHelper& aSc, |
104 | | mozilla::layers::WebRenderLayerManager* aManager, |
105 | | nsDisplayListBuilder* aDisplayListBuilder) override; |
106 | | virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, |
107 | | bool* aSnap) const override; |
108 | | NS_DISPLAY_DECL_NAME("FieldSetBorder", TYPE_FIELDSET_BORDER_BACKGROUND) |
109 | | }; |
110 | | |
111 | | void |
112 | | nsDisplayFieldSetBorder::Paint(nsDisplayListBuilder* aBuilder, |
113 | | gfxContext* aCtx) |
114 | 0 | { |
115 | 0 | image::ImgDrawResult result = static_cast<nsFieldSetFrame*>(mFrame)-> |
116 | 0 | PaintBorder(aBuilder, *aCtx, ToReferenceFrame(), GetPaintRect()); |
117 | 0 |
|
118 | 0 | nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result); |
119 | 0 | } |
120 | | |
121 | | nsDisplayItemGeometry* |
122 | | nsDisplayFieldSetBorder::AllocateGeometry(nsDisplayListBuilder* aBuilder) |
123 | 0 | { |
124 | 0 | return new nsDisplayItemGenericImageGeometry(this, aBuilder); |
125 | 0 | } |
126 | | |
127 | | void |
128 | | nsDisplayFieldSetBorder::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, |
129 | | const nsDisplayItemGeometry* aGeometry, |
130 | | nsRegion *aInvalidRegion) const |
131 | 0 | { |
132 | 0 | auto geometry = |
133 | 0 | static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry); |
134 | 0 |
|
135 | 0 | if (aBuilder->ShouldSyncDecodeImages() && |
136 | 0 | geometry->ShouldInvalidateToSyncDecodeImages()) { |
137 | 0 | bool snap; |
138 | 0 | aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap)); |
139 | 0 | } |
140 | 0 |
|
141 | 0 | nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion); |
142 | 0 | } |
143 | | |
144 | | nsRect |
145 | | nsDisplayFieldSetBorder::GetBounds(nsDisplayListBuilder* aBuilder, |
146 | | bool* aSnap) const |
147 | 0 | { |
148 | 0 | // Just go ahead and claim our frame's overflow rect as the bounds, because we |
149 | 0 | // may have border-image-outset or other features that cause borders to extend |
150 | 0 | // outside the border rect. We could try to duplicate all the complexity |
151 | 0 | // nsDisplayBorder has here, but keeping things in sync would be a pain, and |
152 | 0 | // this code is not typically performance-sensitive. |
153 | 0 | *aSnap = false; |
154 | 0 | return Frame()->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame(); |
155 | 0 | } |
156 | | |
157 | | bool |
158 | | nsDisplayFieldSetBorder::CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder, |
159 | | mozilla::wr::IpcResourceUpdateQueue& aResources, |
160 | | const StackingContextHelper& aSc, |
161 | | mozilla::layers::WebRenderLayerManager* aManager, |
162 | | nsDisplayListBuilder* aDisplayListBuilder) |
163 | 0 | { |
164 | 0 | auto frame = static_cast<nsFieldSetFrame*>(mFrame); |
165 | 0 | auto offset = ToReferenceFrame(); |
166 | 0 | nsRect rect; |
167 | 0 |
|
168 | 0 | if (nsIFrame* legend = frame->GetLegend()) { |
169 | 0 | rect = frame->VisualBorderRectRelativeToSelf() + offset; |
170 | 0 |
|
171 | 0 | // Legends require a "negative" clip around the text, which WR doesn't support yet. |
172 | 0 | nsRect legendRect = legend->GetNormalRect() + offset; |
173 | 0 | if (!legendRect.IsEmpty()) { |
174 | 0 | return false; |
175 | 0 | } |
176 | 0 | } else { |
177 | 0 | rect = nsRect(offset, frame->GetRect().Size()); |
178 | 0 | } |
179 | 0 |
|
180 | 0 | ImgDrawResult drawResult = |
181 | 0 | nsCSSRendering::CreateWebRenderCommandsForBorder(this, |
182 | 0 | mFrame, |
183 | 0 | rect, |
184 | 0 | aBuilder, |
185 | 0 | aResources, |
186 | 0 | aSc, |
187 | 0 | aManager, |
188 | 0 | aDisplayListBuilder); |
189 | 0 | if (drawResult == ImgDrawResult::NOT_SUPPORTED) { |
190 | 0 | return false; |
191 | 0 | } |
192 | 0 | |
193 | 0 | nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, drawResult); |
194 | 0 | return true; |
195 | 0 | }; |
196 | | |
197 | | void |
198 | | nsFieldSetFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, |
199 | 0 | const nsDisplayListSet& aLists) { |
200 | 0 | // Paint our background and border in a special way. |
201 | 0 | // REVIEW: We don't really need to check frame emptiness here; if it's empty, |
202 | 0 | // the background/border display item won't do anything, and if it isn't empty, |
203 | 0 | // we need to paint the outline |
204 | 0 | if (!(GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER) && |
205 | 0 | IsVisibleForPainting(aBuilder)) { |
206 | 0 | if (StyleEffects()->mBoxShadow) { |
207 | 0 | aLists.BorderBackground()->AppendToTop( |
208 | 0 | MakeDisplayItem<nsDisplayBoxShadowOuter>(aBuilder, this)); |
209 | 0 | } |
210 | 0 |
|
211 | 0 | nsDisplayBackgroundImage::AppendBackgroundItemsToTop( |
212 | 0 | aBuilder, this, VisualBorderRectRelativeToSelf(), |
213 | 0 | aLists.BorderBackground(), |
214 | 0 | /* aAllowWillPaintBorderOptimization = */ false); |
215 | 0 |
|
216 | 0 | aLists.BorderBackground()->AppendToTop( |
217 | 0 | MakeDisplayItem<nsDisplayFieldSetBorder>(aBuilder, this)); |
218 | 0 |
|
219 | 0 | DisplayOutlineUnconditional(aBuilder, aLists); |
220 | 0 |
|
221 | 0 | DO_GLOBAL_REFLOW_COUNT_DSP("nsFieldSetFrame"); |
222 | 0 | } |
223 | 0 |
|
224 | 0 | if (GetPrevInFlow()) { |
225 | 0 | DisplayOverflowContainers(aBuilder, aLists); |
226 | 0 | } |
227 | 0 |
|
228 | 0 | nsDisplayListCollection contentDisplayItems(aBuilder); |
229 | 0 | if (nsIFrame* inner = GetInner()) { |
230 | 0 | // Collect the inner frame's display items into their own collection. |
231 | 0 | // We need to be calling BuildDisplayList on it before the legend in |
232 | 0 | // case it contains out-of-flow frames whose placeholders are in the |
233 | 0 | // legend. However, we want the inner frame's display items to be |
234 | 0 | // after the legend's display items in z-order, so we need to save them |
235 | 0 | // and append them later. |
236 | 0 | BuildDisplayListForChild(aBuilder, inner, contentDisplayItems); |
237 | 0 | } |
238 | 0 | if (nsIFrame* legend = GetLegend()) { |
239 | 0 | // The legend's background goes on our BlockBorderBackgrounds list because |
240 | 0 | // it's a block child. |
241 | 0 | nsDisplayListSet set(aLists, aLists.BlockBorderBackgrounds()); |
242 | 0 | BuildDisplayListForChild(aBuilder, legend, set); |
243 | 0 | } |
244 | 0 | // Put the inner frame's display items on the master list. Note that this |
245 | 0 | // moves its border/background display items to our BorderBackground() list, |
246 | 0 | // which isn't really correct, but it's OK because the inner frame is |
247 | 0 | // anonymous and can't have its own border and background. |
248 | 0 | contentDisplayItems.MoveTo(aLists); |
249 | 0 | } |
250 | | |
251 | | image::ImgDrawResult |
252 | | nsFieldSetFrame::PaintBorder( |
253 | | nsDisplayListBuilder* aBuilder, |
254 | | gfxContext& aRenderingContext, |
255 | | nsPoint aPt, |
256 | | const nsRect& aDirtyRect) |
257 | 0 | { |
258 | 0 | // If the border is smaller than the legend, move the border down |
259 | 0 | // to be centered on the legend. We call VisualBorderRectRelativeToSelf() to |
260 | 0 | // compute the border positioning. |
261 | 0 | // FIXME: This means border-radius clamping is incorrect; we should |
262 | 0 | // override nsIFrame::GetBorderRadii. |
263 | 0 | nsRect rect = VisualBorderRectRelativeToSelf() + aPt; |
264 | 0 | nsPresContext* presContext = PresContext(); |
265 | 0 |
|
266 | 0 | PaintBorderFlags borderFlags = aBuilder->ShouldSyncDecodeImages() |
267 | 0 | ? PaintBorderFlags::SYNC_DECODE_IMAGES |
268 | 0 | : PaintBorderFlags(); |
269 | 0 |
|
270 | 0 | ImgDrawResult result = ImgDrawResult::SUCCESS; |
271 | 0 |
|
272 | 0 | nsCSSRendering::PaintBoxShadowInner(presContext, aRenderingContext, |
273 | 0 | this, rect); |
274 | 0 |
|
275 | 0 | if (nsIFrame* legend = GetLegend()) { |
276 | 0 | // We want to avoid drawing our border under the legend, so clip out the |
277 | 0 | // legend while drawing our border. We don't want to use mLegendRect here, |
278 | 0 | // because we do want to draw our border under the legend's inline-start and |
279 | 0 | // -end margins. And we use GetNormalRect(), not GetRect(), because we do |
280 | 0 | // not want relative positioning applied to the legend to change how our |
281 | 0 | // border looks. |
282 | 0 | nsRect legendRect = legend->GetNormalRect() + aPt; |
283 | 0 |
|
284 | 0 | // Make sure we clip all of the border in case the legend is smaller. |
285 | 0 | nscoord borderTopWidth = GetUsedBorder().top; |
286 | 0 | if (legendRect.height < borderTopWidth) { |
287 | 0 | legendRect.height = borderTopWidth; |
288 | 0 | legendRect.y = aPt.y; |
289 | 0 | } |
290 | 0 |
|
291 | 0 | DrawTarget* drawTarget = aRenderingContext.GetDrawTarget(); |
292 | 0 | // We set up a clip path which has our rect clockwise and the legend rect |
293 | 0 | // counterclockwise, with FILL_WINDING as the fill rule. That will allow us |
294 | 0 | // to paint within our rect but outside the legend rect. For "our rect" we |
295 | 0 | // use our visual overflow rect (relative to ourselves, so it's not affected |
296 | 0 | // by transforms), because we can have borders sticking outside our border |
297 | 0 | // box (e.g. due to border-image-outset). |
298 | 0 | RefPtr<PathBuilder> pathBuilder = |
299 | 0 | drawTarget->CreatePathBuilder(FillRule::FILL_WINDING); |
300 | 0 | int32_t appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel(); |
301 | 0 | AppendRectToPath(pathBuilder, |
302 | 0 | NSRectToSnappedRect(GetVisualOverflowRectRelativeToSelf() + aPt, |
303 | 0 | appUnitsPerDevPixel, |
304 | 0 | *drawTarget), |
305 | 0 | true); |
306 | 0 | AppendRectToPath(pathBuilder, |
307 | 0 | NSRectToSnappedRect(legendRect, appUnitsPerDevPixel, |
308 | 0 | *drawTarget), |
309 | 0 | false); |
310 | 0 | RefPtr<Path> clipPath = pathBuilder->Finish(); |
311 | 0 |
|
312 | 0 | aRenderingContext.Save(); |
313 | 0 | aRenderingContext.Clip(clipPath); |
314 | 0 | result &= |
315 | 0 | nsCSSRendering::PaintBorder(presContext, aRenderingContext, this, |
316 | 0 | aDirtyRect, rect, mComputedStyle, borderFlags); |
317 | 0 | aRenderingContext.Restore(); |
318 | 0 | } else { |
319 | 0 | result &= |
320 | 0 | nsCSSRendering::PaintBorder(presContext, aRenderingContext, this, |
321 | 0 | aDirtyRect, nsRect(aPt, mRect.Size()), |
322 | 0 | mComputedStyle, borderFlags); |
323 | 0 | } |
324 | 0 |
|
325 | 0 | return result; |
326 | 0 | } |
327 | | |
328 | | nscoord |
329 | | nsFieldSetFrame::GetIntrinsicISize(gfxContext* aRenderingContext, |
330 | | nsLayoutUtils::IntrinsicISizeType aType) |
331 | 0 | { |
332 | 0 | nscoord legendWidth = 0; |
333 | 0 | nscoord contentWidth = 0; |
334 | 0 | if (!StyleDisplay()->IsContainSize()) { |
335 | 0 | // Both inner and legend are children, and if the fieldset is |
336 | 0 | // size-contained they should not contribute to the intrinsic size. |
337 | 0 | if (nsIFrame* legend = GetLegend()) { |
338 | 0 | legendWidth = |
339 | 0 | nsLayoutUtils::IntrinsicForContainer(aRenderingContext, legend, aType); |
340 | 0 | } |
341 | 0 |
|
342 | 0 | if (nsIFrame* inner = GetInner()) { |
343 | 0 | // Ignore padding on the inner, since the padding will be applied to the |
344 | 0 | // outer instead, and the padding computed for the inner is wrong |
345 | 0 | // for percentage padding. |
346 | 0 | contentWidth = |
347 | 0 | nsLayoutUtils::IntrinsicForContainer(aRenderingContext, inner, aType, |
348 | 0 | nsLayoutUtils::IGNORE_PADDING); |
349 | 0 | } |
350 | 0 | } |
351 | 0 |
|
352 | 0 | return std::max(legendWidth, contentWidth); |
353 | 0 | } |
354 | | |
355 | | |
356 | | nscoord |
357 | | nsFieldSetFrame::GetMinISize(gfxContext* aRenderingContext) |
358 | 0 | { |
359 | 0 | nscoord result = 0; |
360 | 0 | DISPLAY_MIN_INLINE_SIZE(this, result); |
361 | 0 |
|
362 | 0 | result = GetIntrinsicISize(aRenderingContext, nsLayoutUtils::MIN_ISIZE); |
363 | 0 | return result; |
364 | 0 | } |
365 | | |
366 | | nscoord |
367 | | nsFieldSetFrame::GetPrefISize(gfxContext* aRenderingContext) |
368 | 0 | { |
369 | 0 | nscoord result = 0; |
370 | 0 | DISPLAY_PREF_INLINE_SIZE(this, result); |
371 | 0 |
|
372 | 0 | result = GetIntrinsicISize(aRenderingContext, nsLayoutUtils::PREF_ISIZE); |
373 | 0 | return result; |
374 | 0 | } |
375 | | |
376 | | /* virtual */ |
377 | | void |
378 | | nsFieldSetFrame::Reflow(nsPresContext* aPresContext, |
379 | | ReflowOutput& aDesiredSize, |
380 | | const ReflowInput& aReflowInput, |
381 | | nsReflowStatus& aStatus) |
382 | 0 | { |
383 | 0 | MarkInReflow(); |
384 | 0 | DO_GLOBAL_REFLOW_COUNT("nsFieldSetFrame"); |
385 | 0 | DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus); |
386 | 0 | MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); |
387 | 0 | NS_WARNING_ASSERTION(aReflowInput.ComputedISize() != NS_INTRINSICSIZE, |
388 | 0 | "Should have a precomputed inline-size!"); |
389 | 0 |
|
390 | 0 | nsOverflowAreas ocBounds; |
391 | 0 | nsReflowStatus ocStatus; |
392 | 0 | if (GetPrevInFlow()) { |
393 | 0 | ReflowOverflowContainerChildren(aPresContext, aReflowInput, ocBounds, 0, |
394 | 0 | ocStatus); |
395 | 0 | } |
396 | 0 |
|
397 | 0 | //------------ Handle Incremental Reflow ----------------- |
398 | 0 | bool reflowInner; |
399 | 0 | bool reflowLegend; |
400 | 0 | nsIFrame* legend = GetLegend(); |
401 | 0 | nsIFrame* inner = GetInner(); |
402 | 0 | if (aReflowInput.ShouldReflowAllKids()) { |
403 | 0 | reflowInner = inner != nullptr; |
404 | 0 | reflowLegend = legend != nullptr; |
405 | 0 | } else { |
406 | 0 | reflowInner = inner && NS_SUBTREE_DIRTY(inner); |
407 | 0 | reflowLegend = legend && NS_SUBTREE_DIRTY(legend); |
408 | 0 | } |
409 | 0 |
|
410 | 0 | // We don't allow fieldsets to break vertically. If we did, we'd |
411 | 0 | // need logic here to push and pull overflow frames. |
412 | 0 | // Since we're not applying our padding in this frame, we need to add it here |
413 | 0 | // to compute the available width for our children. |
414 | 0 | WritingMode wm = GetWritingMode(); |
415 | 0 | WritingMode innerWM = inner ? inner->GetWritingMode() : wm; |
416 | 0 | WritingMode legendWM = legend ? legend->GetWritingMode() : wm; |
417 | 0 | LogicalSize innerAvailSize = aReflowInput.ComputedSizeWithPadding(innerWM); |
418 | 0 | LogicalSize legendAvailSize = aReflowInput.ComputedSize(legendWM); |
419 | 0 | innerAvailSize.BSize(innerWM) = legendAvailSize.BSize(legendWM) = |
420 | 0 | NS_UNCONSTRAINEDSIZE; |
421 | 0 |
|
422 | 0 | // get our border and padding |
423 | 0 | LogicalMargin border = aReflowInput.ComputedLogicalBorderPadding() - |
424 | 0 | aReflowInput.ComputedLogicalPadding(); |
425 | 0 |
|
426 | 0 | // Figure out how big the legend is if there is one. |
427 | 0 | // get the legend's margin |
428 | 0 | LogicalMargin legendMargin(wm); |
429 | 0 | // reflow the legend only if needed |
430 | 0 | Maybe<ReflowInput> legendReflowInput; |
431 | 0 | if (legend) { |
432 | 0 | legendReflowInput.emplace(aPresContext, aReflowInput, legend, |
433 | 0 | legendAvailSize); |
434 | 0 | } |
435 | 0 | if (reflowLegend) { |
436 | 0 | ReflowOutput legendDesiredSize(aReflowInput); |
437 | 0 |
|
438 | 0 | // We'll move the legend to its proper place later, so the position |
439 | 0 | // and containerSize passed here are unimportant. |
440 | 0 | const nsSize dummyContainerSize; |
441 | 0 | ReflowChild(legend, aPresContext, legendDesiredSize, *legendReflowInput, |
442 | 0 | wm, LogicalPoint(wm), dummyContainerSize, |
443 | 0 | NS_FRAME_NO_MOVE_FRAME, aStatus); |
444 | | #ifdef NOISY_REFLOW |
445 | | printf(" returned (%d, %d)\n", |
446 | | legendDesiredSize.Width(), legendDesiredSize.Height()); |
447 | | #endif |
448 | | // figure out the legend's rectangle |
449 | 0 | legendMargin = legend->GetLogicalUsedMargin(wm); |
450 | 0 | mLegendRect = |
451 | 0 | LogicalRect(wm, 0, 0, |
452 | 0 | legendDesiredSize.ISize(wm) + legendMargin.IStartEnd(wm), |
453 | 0 | legendDesiredSize.BSize(wm) + legendMargin.BStartEnd(wm)); |
454 | 0 | nscoord oldSpace = mLegendSpace; |
455 | 0 | mLegendSpace = 0; |
456 | 0 | if (mLegendRect.BSize(wm) > border.BStart(wm)) { |
457 | 0 | // center the border on the legend |
458 | 0 | mLegendSpace = mLegendRect.BSize(wm) - border.BStart(wm); |
459 | 0 | } else { |
460 | 0 | mLegendRect.BStart(wm) = |
461 | 0 | (border.BStart(wm) - mLegendRect.BSize(wm)) / 2; |
462 | 0 | } |
463 | 0 |
|
464 | 0 | // if the legend space changes then we need to reflow the |
465 | 0 | // content area as well. |
466 | 0 | if (mLegendSpace != oldSpace && inner) { |
467 | 0 | reflowInner = true; |
468 | 0 | } |
469 | 0 |
|
470 | 0 | FinishReflowChild(legend, aPresContext, legendDesiredSize, |
471 | 0 | legendReflowInput.ptr(), wm, LogicalPoint(wm), |
472 | 0 | dummyContainerSize, NS_FRAME_NO_MOVE_FRAME); |
473 | 0 | } else if (!legend) { |
474 | 0 | mLegendRect.SetEmpty(); |
475 | 0 | mLegendSpace = 0; |
476 | 0 | } else { |
477 | 0 | // mLegendSpace and mLegendRect haven't changed, but we need |
478 | 0 | // the used margin when placing the legend. |
479 | 0 | legendMargin = legend->GetLogicalUsedMargin(wm); |
480 | 0 | } |
481 | 0 |
|
482 | 0 | // This containerSize is incomplete as yet: it does not include the size |
483 | 0 | // of the |inner| frame itself. |
484 | 0 | nsSize containerSize = (LogicalSize(wm, 0, mLegendSpace) + |
485 | 0 | border.Size(wm)).GetPhysicalSize(wm); |
486 | 0 | // reflow the content frame only if needed |
487 | 0 | if (reflowInner) { |
488 | 0 | ReflowInput kidReflowInput(aPresContext, aReflowInput, inner, |
489 | 0 | innerAvailSize, nullptr, |
490 | 0 | ReflowInput::CALLER_WILL_INIT); |
491 | 0 | // Override computed padding, in case it's percentage padding |
492 | 0 | kidReflowInput.Init(aPresContext, nullptr, nullptr, |
493 | 0 | &aReflowInput.ComputedPhysicalPadding()); |
494 | 0 | // Our child is "height:100%" but we actually want its height to be reduced |
495 | 0 | // by the amount of content-height the legend is eating up, unless our |
496 | 0 | // height is unconstrained (in which case the child's will be too). |
497 | 0 | if (aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE) { |
498 | 0 | kidReflowInput.SetComputedBSize( |
499 | 0 | std::max(0, aReflowInput.ComputedBSize() - mLegendSpace)); |
500 | 0 | } |
501 | 0 |
|
502 | 0 | if (aReflowInput.ComputedMinBSize() > 0) { |
503 | 0 | kidReflowInput.ComputedMinBSize() = |
504 | 0 | std::max(0, aReflowInput.ComputedMinBSize() - mLegendSpace); |
505 | 0 | } |
506 | 0 |
|
507 | 0 | if (aReflowInput.ComputedMaxBSize() != NS_UNCONSTRAINEDSIZE) { |
508 | 0 | kidReflowInput.ComputedMaxBSize() = |
509 | 0 | std::max(0, aReflowInput.ComputedMaxBSize() - mLegendSpace); |
510 | 0 | } |
511 | 0 |
|
512 | 0 | ReflowOutput kidDesiredSize(kidReflowInput); |
513 | 0 | // Reflow the frame |
514 | 0 | NS_ASSERTION(kidReflowInput.ComputedPhysicalMargin() == nsMargin(0,0,0,0), |
515 | 0 | "Margins on anonymous fieldset child not supported!"); |
516 | 0 | LogicalPoint pt(wm, border.IStart(wm), border.BStart(wm) + mLegendSpace); |
517 | 0 |
|
518 | 0 | // We don't know the correct containerSize until we have reflowed |inner|, |
519 | 0 | // so we use a dummy value for now; FinishReflowChild will fix the position |
520 | 0 | // if necessary. |
521 | 0 | const nsSize dummyContainerSize; |
522 | 0 | ReflowChild(inner, aPresContext, kidDesiredSize, kidReflowInput, |
523 | 0 | wm, pt, dummyContainerSize, 0, aStatus); |
524 | 0 |
|
525 | 0 | // Update containerSize to account for size of the inner frame, so that |
526 | 0 | // FinishReflowChild can position it correctly. |
527 | 0 | containerSize += kidDesiredSize.PhysicalSize(); |
528 | 0 | FinishReflowChild(inner, aPresContext, kidDesiredSize, |
529 | 0 | &kidReflowInput, wm, pt, containerSize, 0); |
530 | 0 | NS_FRAME_TRACE_REFLOW_OUT("FieldSet::Reflow", aStatus); |
531 | 0 | } else if (inner) { |
532 | 0 | // |inner| didn't need to be reflowed but we do need to include its size |
533 | 0 | // in containerSize. |
534 | 0 | containerSize += inner->GetSize(); |
535 | 0 | } |
536 | 0 |
|
537 | 0 | LogicalRect contentRect(wm); |
538 | 0 | if (inner) { |
539 | 0 | // We don't support margins on inner, so our content rect is just the |
540 | 0 | // inner's border-box. (We don't really care about container size at this |
541 | 0 | // point, as we'll figure out the actual positioning later.) |
542 | 0 | contentRect = inner->GetLogicalRect(wm, containerSize); |
543 | 0 | } |
544 | 0 |
|
545 | 0 | // Our content rect must fill up the available width |
546 | 0 | LogicalSize availSize = aReflowInput.ComputedSizeWithPadding(wm); |
547 | 0 | if (availSize.ISize(wm) > contentRect.ISize(wm)) { |
548 | 0 | contentRect.ISize(wm) = innerAvailSize.ISize(wm); |
549 | 0 | } |
550 | 0 |
|
551 | 0 | if (legend) { |
552 | 0 | // The legend is positioned inline-wards within the inner's content rect |
553 | 0 | // (so that padding on the fieldset affects the legend position). |
554 | 0 | LogicalRect innerContentRect = contentRect; |
555 | 0 | innerContentRect.Deflate(wm, aReflowInput.ComputedLogicalPadding()); |
556 | 0 | // If the inner content rect is larger than the legend, we can align the |
557 | 0 | // legend. |
558 | 0 | if (innerContentRect.ISize(wm) > mLegendRect.ISize(wm)) { |
559 | 0 | // NOTE legend @align values are: left/right/center/top/bottom. |
560 | 0 | // GetLogicalAlign converts left/right to start/end for the given WM. |
561 | 0 | // @see HTMLLegendElement::ParseAttribute, nsLegendFrame::GetLogicalAlign |
562 | 0 | int32_t align = static_cast<nsLegendFrame*> |
563 | 0 | (legend->GetContentInsertionFrame())->GetLogicalAlign(wm); |
564 | 0 | switch (align) { |
565 | 0 | case NS_STYLE_TEXT_ALIGN_END: |
566 | 0 | mLegendRect.IStart(wm) = |
567 | 0 | innerContentRect.IEnd(wm) - mLegendRect.ISize(wm); |
568 | 0 | break; |
569 | 0 | case NS_STYLE_TEXT_ALIGN_CENTER: |
570 | 0 | // Note: rounding removed; there doesn't seem to be any need |
571 | 0 | mLegendRect.IStart(wm) = innerContentRect.IStart(wm) + |
572 | 0 | (innerContentRect.ISize(wm) - mLegendRect.ISize(wm)) / 2; |
573 | 0 | break; |
574 | 0 | case NS_STYLE_TEXT_ALIGN_START: |
575 | 0 | case NS_STYLE_VERTICAL_ALIGN_TOP: |
576 | 0 | case NS_STYLE_VERTICAL_ALIGN_BOTTOM: |
577 | 0 | mLegendRect.IStart(wm) = innerContentRect.IStart(wm); |
578 | 0 | break; |
579 | 0 | default: |
580 | 0 | MOZ_ASSERT_UNREACHABLE("unexpected GetLogicalAlign value"); |
581 | 0 | } |
582 | 0 | } else { |
583 | 0 | // otherwise just start-align it. |
584 | 0 | mLegendRect.IStart(wm) = innerContentRect.IStart(wm); |
585 | 0 | } |
586 | 0 |
|
587 | 0 | // place the legend |
588 | 0 | LogicalRect actualLegendRect = mLegendRect; |
589 | 0 | actualLegendRect.Deflate(wm, legendMargin); |
590 | 0 | LogicalPoint actualLegendPos(actualLegendRect.Origin(wm)); |
591 | 0 |
|
592 | 0 | // Note that legend's writing mode may be different from the fieldset's, |
593 | 0 | // so we need to convert offsets before applying them to it (bug 1134534). |
594 | 0 | LogicalMargin offsets = |
595 | 0 | legendReflowInput->ComputedLogicalOffsets(). |
596 | 0 | ConvertTo(wm, legendReflowInput->GetWritingMode()); |
597 | 0 | ReflowInput::ApplyRelativePositioning(legend, wm, offsets, |
598 | 0 | &actualLegendPos, |
599 | 0 | containerSize); |
600 | 0 |
|
601 | 0 | legend->SetPosition(wm, actualLegendPos, containerSize); |
602 | 0 | nsContainerFrame::PositionFrameView(legend); |
603 | 0 | nsContainerFrame::PositionChildViews(legend); |
604 | 0 | } |
605 | 0 |
|
606 | 0 | // Return our size and our result. |
607 | 0 | LogicalSize finalSize(wm, contentRect.ISize(wm) + border.IStartEnd(wm), |
608 | 0 | mLegendSpace + border.BStartEnd(wm) + |
609 | 0 | (inner ? inner->BSize(wm) : 0)); |
610 | 0 | if (aReflowInput.mStyleDisplay->IsContainSize()) { |
611 | 0 | // If we're size-contained, then we must set finalSize to be what |
612 | 0 | // it'd be if we had no children (i.e. if we had no legend and if |
613 | 0 | // 'inner' were empty). Note: normally the fieldset's own padding |
614 | 0 | // (which we still must honor) would be accounted for as part of |
615 | 0 | // inner's size (see kidReflowInput.Init() call above). So: since |
616 | 0 | // we're disregarding sizing information from 'inner', we need to |
617 | 0 | // account for that padding ourselves here. |
618 | 0 | nscoord contentBoxBSize = |
619 | 0 | aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE |
620 | 0 | ? aReflowInput.ApplyMinMaxBSize(0) |
621 | 0 | : aReflowInput.ComputedBSize(); |
622 | 0 | finalSize.BSize(wm) = contentBoxBSize + |
623 | 0 | aReflowInput.ComputedLogicalBorderPadding().BStartEnd(wm); |
624 | 0 | } |
625 | 0 | aDesiredSize.SetSize(wm, finalSize); |
626 | 0 | aDesiredSize.SetOverflowAreasToDesiredBounds(); |
627 | 0 |
|
628 | 0 | if (legend) { |
629 | 0 | ConsiderChildOverflow(aDesiredSize.mOverflowAreas, legend); |
630 | 0 | } |
631 | 0 | if (inner) { |
632 | 0 | ConsiderChildOverflow(aDesiredSize.mOverflowAreas, inner); |
633 | 0 | } |
634 | 0 |
|
635 | 0 | // Merge overflow container bounds and status. |
636 | 0 | aDesiredSize.mOverflowAreas.UnionWith(ocBounds); |
637 | 0 | aStatus.MergeCompletionStatusFrom(ocStatus); |
638 | 0 |
|
639 | 0 | FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aReflowInput, aStatus); |
640 | 0 |
|
641 | 0 | InvalidateFrame(); |
642 | 0 |
|
643 | 0 | NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize); |
644 | 0 | } |
645 | | |
646 | | #ifdef DEBUG |
647 | | void |
648 | | nsFieldSetFrame::SetInitialChildList(ChildListID aListID, |
649 | | nsFrameList& aChildList) |
650 | | { |
651 | | nsContainerFrame::SetInitialChildList(aListID, aChildList); |
652 | | MOZ_ASSERT(aListID != kPrincipalList || GetInner(), |
653 | | "Setting principal child list should populate our inner frame"); |
654 | | } |
655 | | void |
656 | | nsFieldSetFrame::AppendFrames(ChildListID aListID, |
657 | | nsFrameList& aFrameList) |
658 | | { |
659 | | MOZ_CRASH("nsFieldSetFrame::AppendFrames not supported"); |
660 | | } |
661 | | |
662 | | void |
663 | | nsFieldSetFrame::InsertFrames(ChildListID aListID, |
664 | | nsIFrame* aPrevFrame, |
665 | | nsFrameList& aFrameList) |
666 | | { |
667 | | MOZ_CRASH("nsFieldSetFrame::InsertFrames not supported"); |
668 | | } |
669 | | |
670 | | void |
671 | | nsFieldSetFrame::RemoveFrame(ChildListID aListID, |
672 | | nsIFrame* aOldFrame) |
673 | | { |
674 | | MOZ_CRASH("nsFieldSetFrame::RemoveFrame not supported"); |
675 | | } |
676 | | #endif |
677 | | |
678 | | #ifdef ACCESSIBILITY |
679 | | a11y::AccType |
680 | | nsFieldSetFrame::AccessibleType() |
681 | 0 | { |
682 | 0 | return a11y::eHTMLGroupboxType; |
683 | 0 | } |
684 | | #endif |
685 | | |
686 | | nscoord |
687 | | nsFieldSetFrame::GetLogicalBaseline(WritingMode aWM) const |
688 | | { |
689 | | switch (StyleDisplay()->mDisplay) { |
690 | | case mozilla::StyleDisplay::Grid: |
691 | | case mozilla::StyleDisplay::InlineGrid: |
692 | | case mozilla::StyleDisplay::Flex: |
693 | | case mozilla::StyleDisplay::InlineFlex: |
694 | | return BaselineBOffset(aWM, BaselineSharingGroup::eFirst, |
695 | | AlignmentContext::eInline); |
696 | | default: |
697 | | return BSize(aWM) - BaselineBOffset(aWM, BaselineSharingGroup::eLast, |
698 | | AlignmentContext::eInline); |
699 | | } |
700 | | } |
701 | | |
702 | | bool |
703 | | nsFieldSetFrame::GetVerticalAlignBaseline(WritingMode aWM, |
704 | | nscoord* aBaseline) const |
705 | 0 | { |
706 | 0 | if (StyleDisplay()->IsContainSize()) { |
707 | 0 | // If we are size-contained, our child 'inner' should not |
708 | 0 | // affect how we calculate our baseline. |
709 | 0 | return false; |
710 | 0 | } |
711 | 0 | nsIFrame* inner = GetInner(); |
712 | 0 | MOZ_ASSERT(!inner->GetWritingMode().IsOrthogonalTo(aWM)); |
713 | 0 | if (!inner->GetVerticalAlignBaseline(aWM, aBaseline)) { |
714 | 0 | return false; |
715 | 0 | } |
716 | 0 | nscoord innerBStart = inner->BStart(aWM, GetSize()); |
717 | 0 | *aBaseline += innerBStart; |
718 | 0 | return true; |
719 | 0 | } |
720 | | |
721 | | bool |
722 | | nsFieldSetFrame::GetNaturalBaselineBOffset(WritingMode aWM, |
723 | | BaselineSharingGroup aBaselineGroup, |
724 | | nscoord* aBaseline) const |
725 | 0 | { |
726 | 0 | if (StyleDisplay()->IsContainSize()) { |
727 | 0 | // If we are size-contained, our child 'inner' should not |
728 | 0 | // affect how we calculate our baseline. |
729 | 0 | return false; |
730 | 0 | } |
731 | 0 | nsIFrame* inner = GetInner(); |
732 | 0 | MOZ_ASSERT(!inner->GetWritingMode().IsOrthogonalTo(aWM)); |
733 | 0 | if (!inner->GetNaturalBaselineBOffset(aWM, aBaselineGroup, aBaseline)) { |
734 | 0 | return false; |
735 | 0 | } |
736 | 0 | nscoord innerBStart = inner->BStart(aWM, GetSize()); |
737 | 0 | if (aBaselineGroup == BaselineSharingGroup::eFirst) { |
738 | 0 | *aBaseline += innerBStart; |
739 | 0 | } else { |
740 | 0 | *aBaseline += BSize(aWM) - (innerBStart + inner->BSize(aWM)); |
741 | 0 | } |
742 | 0 | return true; |
743 | 0 | } |
744 | | |
745 | | void |
746 | | nsFieldSetFrame::AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) |
747 | 0 | { |
748 | 0 | if (nsIFrame* kid = GetInner()) { |
749 | 0 | aResult.AppendElement(OwnedAnonBox(kid)); |
750 | 0 | } |
751 | 0 | } |