/src/mozilla-central/layout/generic/nsBlockFrame.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 | | /* |
8 | | * rendering object for CSS display:block, inline-block, and list-item |
9 | | * boxes, also used for various anonymous boxes |
10 | | */ |
11 | | |
12 | | #include "nsBlockFrame.h" |
13 | | |
14 | | #include "gfxContext.h" |
15 | | |
16 | | #include "mozilla/ComputedStyle.h" |
17 | | #include "mozilla/DebugOnly.h" |
18 | | #include "mozilla/Maybe.h" |
19 | | #include "mozilla/ToString.h" |
20 | | #include "mozilla/UniquePtr.h" |
21 | | |
22 | | #include "nsCOMPtr.h" |
23 | | #include "nsAbsoluteContainingBlock.h" |
24 | | #include "nsBlockReflowContext.h" |
25 | | #include "BlockReflowInput.h" |
26 | | #include "nsBulletFrame.h" |
27 | | #include "nsFontMetrics.h" |
28 | | #include "nsGenericHTMLElement.h" |
29 | | #include "nsLineBox.h" |
30 | | #include "nsLineLayout.h" |
31 | | #include "nsPlaceholderFrame.h" |
32 | | #include "nsStyleConsts.h" |
33 | | #include "nsFrameManager.h" |
34 | | #include "nsPresContext.h" |
35 | | #include "nsIPresShell.h" |
36 | | #include "nsHTMLParts.h" |
37 | | #include "nsGkAtoms.h" |
38 | | #include "nsGenericHTMLElement.h" |
39 | | #include "nsAttrValueInlines.h" |
40 | | #include "mozilla/Sprintf.h" |
41 | | #include "nsFloatManager.h" |
42 | | #include "prenv.h" |
43 | | #include "plstr.h" |
44 | | #include "nsError.h" |
45 | | #include "nsIScrollableFrame.h" |
46 | | #include <algorithm> |
47 | | #include "nsLayoutUtils.h" |
48 | | #include "nsDisplayList.h" |
49 | | #include "nsCSSAnonBoxes.h" |
50 | | #include "nsCSSFrameConstructor.h" |
51 | | #include "TextOverflow.h" |
52 | | #include "nsIFrameInlines.h" |
53 | | #include "CounterStyleManager.h" |
54 | | #include "mozilla/dom/HTMLDetailsElement.h" |
55 | | #include "mozilla/dom/HTMLSummaryElement.h" |
56 | | #include "mozilla/dom/Selection.h" |
57 | | #include "mozilla/RestyleManager.h" |
58 | | #include "mozilla/ServoStyleSet.h" |
59 | | #include "mozilla/Telemetry.h" |
60 | | |
61 | | #include "nsBidiPresUtils.h" |
62 | | |
63 | | #include <inttypes.h> |
64 | | |
65 | | static const int MIN_LINES_NEEDING_CURSOR = 20; |
66 | | |
67 | | static const char16_t kDiscCharacter = 0x2022; |
68 | | |
69 | | using namespace mozilla; |
70 | | using namespace mozilla::css; |
71 | | using namespace mozilla::dom; |
72 | | using namespace mozilla::layout; |
73 | | using ShapeType = nsFloatManager::ShapeType; |
74 | | typedef nsAbsoluteContainingBlock::AbsPosReflowFlags AbsPosReflowFlags; |
75 | | |
76 | | static void MarkAllDescendantLinesDirty(nsBlockFrame* aBlock) |
77 | 0 | { |
78 | 0 | nsLineList::iterator line = aBlock->LinesBegin(); |
79 | 0 | nsLineList::iterator endLine = aBlock->LinesEnd(); |
80 | 0 | while (line != endLine) { |
81 | 0 | if (line->IsBlock()) { |
82 | 0 | nsIFrame* f = line->mFirstChild; |
83 | 0 | nsBlockFrame* bf = nsLayoutUtils::GetAsBlock(f); |
84 | 0 | if (bf) { |
85 | 0 | MarkAllDescendantLinesDirty(bf); |
86 | 0 | } |
87 | 0 | } |
88 | 0 | line->MarkDirty(); |
89 | 0 | ++line; |
90 | 0 | } |
91 | 0 | } |
92 | | |
93 | | static void MarkSameFloatManagerLinesDirty(nsBlockFrame* aBlock) |
94 | 0 | { |
95 | 0 | nsBlockFrame* blockWithFloatMgr = aBlock; |
96 | 0 | while (!(blockWithFloatMgr->GetStateBits() & NS_BLOCK_FLOAT_MGR)) { |
97 | 0 | nsBlockFrame* bf = nsLayoutUtils::GetAsBlock(blockWithFloatMgr->GetParent()); |
98 | 0 | if (!bf) { |
99 | 0 | break; |
100 | 0 | } |
101 | 0 | blockWithFloatMgr = bf; |
102 | 0 | } |
103 | 0 |
|
104 | 0 | // Mark every line at and below the line where the float was |
105 | 0 | // dirty, and mark their lines dirty too. We could probably do |
106 | 0 | // something more efficient --- e.g., just dirty the lines that intersect |
107 | 0 | // the float vertically. |
108 | 0 | MarkAllDescendantLinesDirty(blockWithFloatMgr); |
109 | 0 | } |
110 | | |
111 | | /** |
112 | | * Returns true if aFrame is a block that has one or more float children. |
113 | | */ |
114 | | static bool BlockHasAnyFloats(nsIFrame* aFrame) |
115 | 0 | { |
116 | 0 | nsBlockFrame* block = nsLayoutUtils::GetAsBlock(aFrame); |
117 | 0 | if (!block) |
118 | 0 | return false; |
119 | 0 | if (block->GetChildList(nsIFrame::kFloatList).FirstChild()) |
120 | 0 | return true; |
121 | 0 | |
122 | 0 | nsLineList::iterator line = block->LinesBegin(); |
123 | 0 | nsLineList::iterator endLine = block->LinesEnd(); |
124 | 0 | while (line != endLine) { |
125 | 0 | if (line->IsBlock() && BlockHasAnyFloats(line->mFirstChild)) |
126 | 0 | return true; |
127 | 0 | ++line; |
128 | 0 | } |
129 | 0 | return false; |
130 | 0 | } |
131 | | |
132 | | #ifdef DEBUG |
133 | | #include "nsBlockDebugFlags.h" |
134 | | |
135 | | bool nsBlockFrame::gLamePaintMetrics; |
136 | | bool nsBlockFrame::gLameReflowMetrics; |
137 | | bool nsBlockFrame::gNoisy; |
138 | | bool nsBlockFrame::gNoisyDamageRepair; |
139 | | bool nsBlockFrame::gNoisyIntrinsic; |
140 | | bool nsBlockFrame::gNoisyReflow; |
141 | | bool nsBlockFrame::gReallyNoisyReflow; |
142 | | bool nsBlockFrame::gNoisyFloatManager; |
143 | | bool nsBlockFrame::gVerifyLines; |
144 | | bool nsBlockFrame::gDisableResizeOpt; |
145 | | |
146 | | int32_t nsBlockFrame::gNoiseIndent; |
147 | | |
148 | | struct BlockDebugFlags { |
149 | | const char* name; |
150 | | bool* on; |
151 | | }; |
152 | | |
153 | | static const BlockDebugFlags gFlags[] = { |
154 | | { "reflow", &nsBlockFrame::gNoisyReflow }, |
155 | | { "really-noisy-reflow", &nsBlockFrame::gReallyNoisyReflow }, |
156 | | { "intrinsic", &nsBlockFrame::gNoisyIntrinsic }, |
157 | | { "float-manager", &nsBlockFrame::gNoisyFloatManager }, |
158 | | { "verify-lines", &nsBlockFrame::gVerifyLines }, |
159 | | { "damage-repair", &nsBlockFrame::gNoisyDamageRepair }, |
160 | | { "lame-paint-metrics", &nsBlockFrame::gLamePaintMetrics }, |
161 | | { "lame-reflow-metrics", &nsBlockFrame::gLameReflowMetrics }, |
162 | | { "disable-resize-opt", &nsBlockFrame::gDisableResizeOpt }, |
163 | | }; |
164 | | #define NUM_DEBUG_FLAGS (sizeof(gFlags) / sizeof(gFlags[0])) |
165 | | |
166 | | static void |
167 | | ShowDebugFlags() |
168 | | { |
169 | | printf("Here are the available GECKO_BLOCK_DEBUG_FLAGS:\n"); |
170 | | const BlockDebugFlags* bdf = gFlags; |
171 | | const BlockDebugFlags* end = gFlags + NUM_DEBUG_FLAGS; |
172 | | for (; bdf < end; bdf++) { |
173 | | printf(" %s\n", bdf->name); |
174 | | } |
175 | | printf("Note: GECKO_BLOCK_DEBUG_FLAGS is a comma separated list of flag\n"); |
176 | | printf("names (no whitespace)\n"); |
177 | | } |
178 | | |
179 | | void |
180 | | nsBlockFrame::InitDebugFlags() |
181 | | { |
182 | | static bool firstTime = true; |
183 | | if (firstTime) { |
184 | | firstTime = false; |
185 | | char* flags = PR_GetEnv("GECKO_BLOCK_DEBUG_FLAGS"); |
186 | | if (flags) { |
187 | | bool error = false; |
188 | | for (;;) { |
189 | | char* cm = PL_strchr(flags, ','); |
190 | | if (cm) *cm = '\0'; |
191 | | |
192 | | bool found = false; |
193 | | const BlockDebugFlags* bdf = gFlags; |
194 | | const BlockDebugFlags* end = gFlags + NUM_DEBUG_FLAGS; |
195 | | for (; bdf < end; bdf++) { |
196 | | if (PL_strcasecmp(bdf->name, flags) == 0) { |
197 | | *(bdf->on) = true; |
198 | | printf("nsBlockFrame: setting %s debug flag on\n", bdf->name); |
199 | | gNoisy = true; |
200 | | found = true; |
201 | | break; |
202 | | } |
203 | | } |
204 | | if (!found) { |
205 | | error = true; |
206 | | } |
207 | | |
208 | | if (!cm) break; |
209 | | *cm = ','; |
210 | | flags = cm + 1; |
211 | | } |
212 | | if (error) { |
213 | | ShowDebugFlags(); |
214 | | } |
215 | | } |
216 | | } |
217 | | } |
218 | | |
219 | | #endif |
220 | | |
221 | | //---------------------------------------------------------------------- |
222 | | |
223 | | // Debugging support code |
224 | | |
225 | | #ifdef DEBUG |
226 | | const char* nsBlockFrame::kReflowCommandType[] = { |
227 | | "ContentChanged", |
228 | | "StyleChanged", |
229 | | "ReflowDirty", |
230 | | "Timeout", |
231 | | "UserDefined", |
232 | | }; |
233 | | |
234 | | const char* |
235 | | nsBlockFrame::LineReflowStatusToString(LineReflowStatus aLineReflowStatus) const |
236 | | { |
237 | | switch (aLineReflowStatus) { |
238 | | case LineReflowStatus::OK: return "LINE_REFLOW_OK"; |
239 | | case LineReflowStatus::Stop: return "LINE_REFLOW_STOP"; |
240 | | case LineReflowStatus::RedoNoPull: return "LINE_REFLOW_REDO_NO_PULL"; |
241 | | case LineReflowStatus::RedoMoreFloats: return "LINE_REFLOW_REDO_MORE_FLOATS"; |
242 | | case LineReflowStatus::RedoNextBand: return "LINE_REFLOW_REDO_NEXT_BAND"; |
243 | | case LineReflowStatus::Truncated: return "LINE_REFLOW_TRUNCATED"; |
244 | | } |
245 | | return "unknown"; |
246 | | } |
247 | | |
248 | | #endif |
249 | | |
250 | | #ifdef REFLOW_STATUS_COVERAGE |
251 | | static void |
252 | | RecordReflowStatus(bool aChildIsBlock, const nsReflowStatus& aFrameReflowStatus) |
253 | | { |
254 | | static uint32_t record[2]; |
255 | | |
256 | | // 0: child-is-block |
257 | | // 1: child-is-inline |
258 | | int index = 0; |
259 | | if (!aChildIsBlock) index |= 1; |
260 | | |
261 | | // Compute new status |
262 | | uint32_t newS = record[index]; |
263 | | if (aFrameReflowStatus.IsInlineBreak()) { |
264 | | if (aFrameReflowStatus.IsInlineBreakBefore()) { |
265 | | newS |= 1; |
266 | | } |
267 | | else if (aFrameReflowStatus.IsIncomplete()) { |
268 | | newS |= 2; |
269 | | } |
270 | | else { |
271 | | newS |= 4; |
272 | | } |
273 | | } |
274 | | else if (aFrameReflowStatus.IsIncomplete()) { |
275 | | newS |= 8; |
276 | | } |
277 | | else { |
278 | | newS |= 16; |
279 | | } |
280 | | |
281 | | // Log updates to the status that yield different values |
282 | | if (record[index] != newS) { |
283 | | record[index] = newS; |
284 | | printf("record(%d): %02x %02x\n", index, record[0], record[1]); |
285 | | } |
286 | | } |
287 | | #endif |
288 | | |
289 | | static nscoord |
290 | | ResolveTextIndent(const nsStyleCoord& aStyle, nscoord aPercentageBasis) |
291 | 0 | { |
292 | 0 | return nsLayoutUtils::ResolveToLength<false>(aStyle, aPercentageBasis); |
293 | 0 | } |
294 | | |
295 | | NS_DECLARE_FRAME_PROPERTY_WITH_DTOR_NEVER_CALLED(OverflowLinesProperty, |
296 | | nsBlockFrame::FrameLines) |
297 | | NS_DECLARE_FRAME_PROPERTY_FRAMELIST(OverflowOutOfFlowsProperty) |
298 | | NS_DECLARE_FRAME_PROPERTY_FRAMELIST(PushedFloatProperty) |
299 | | NS_DECLARE_FRAME_PROPERTY_FRAMELIST(OutsideBulletProperty) |
300 | | NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(InsideBulletProperty, nsBulletFrame) |
301 | | NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(BlockEndEdgeOfChildrenProperty, nscoord) |
302 | | |
303 | | //---------------------------------------------------------------------- |
304 | | |
305 | | nsBlockFrame* |
306 | | NS_NewBlockFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle) |
307 | 0 | { |
308 | 0 | return new (aPresShell) nsBlockFrame(aStyle); |
309 | 0 | } |
310 | | |
311 | | nsBlockFrame* |
312 | | NS_NewBlockFormattingContext(nsIPresShell* aPresShell, |
313 | | ComputedStyle* aComputedStyle) |
314 | 0 | { |
315 | 0 | nsBlockFrame* blockFrame = NS_NewBlockFrame(aPresShell, aComputedStyle); |
316 | 0 | blockFrame->AddStateBits(NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS); |
317 | 0 | return blockFrame; |
318 | 0 | } |
319 | | |
320 | | NS_IMPL_FRAMEARENA_HELPERS(nsBlockFrame) |
321 | | |
322 | | nsBlockFrame::~nsBlockFrame() |
323 | 0 | { |
324 | 0 | } |
325 | | |
326 | | void |
327 | | nsBlockFrame::AddSizeOfExcludingThisForTree(nsWindowSizes& aWindowSizes) const |
328 | 0 | { |
329 | 0 | nsContainerFrame::AddSizeOfExcludingThisForTree(aWindowSizes); |
330 | 0 |
|
331 | 0 | // Add the size of any nsLineBox::mFrames hashtables we might have: |
332 | 0 | for (ConstLineIterator line = LinesBegin(), line_end = LinesEnd(); |
333 | 0 | line != line_end; ++line) { |
334 | 0 | line->AddSizeOfExcludingThis(aWindowSizes); |
335 | 0 | } |
336 | 0 | const FrameLines* overflowLines = GetOverflowLines(); |
337 | 0 | if (overflowLines) { |
338 | 0 | ConstLineIterator line = overflowLines->mLines.begin(), |
339 | 0 | line_end = overflowLines->mLines.end(); |
340 | 0 | for (; line != line_end; ++line) { |
341 | 0 | line->AddSizeOfExcludingThis(aWindowSizes); |
342 | 0 | } |
343 | 0 | } |
344 | 0 | } |
345 | | |
346 | | void |
347 | | nsBlockFrame::DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData) |
348 | 0 | { |
349 | 0 | ClearLineCursor(); |
350 | 0 | DestroyAbsoluteFrames(aDestructRoot, aPostDestroyData); |
351 | 0 | mFloats.DestroyFramesFrom(aDestructRoot, aPostDestroyData); |
352 | 0 | nsPresContext* presContext = PresContext(); |
353 | 0 | nsIPresShell* shell = presContext->PresShell(); |
354 | 0 | nsLineBox::DeleteLineList(presContext, mLines, aDestructRoot, |
355 | 0 | &mFrames, aPostDestroyData); |
356 | 0 |
|
357 | 0 | if (HasPushedFloats()) { |
358 | 0 | SafelyDestroyFrameListProp(aDestructRoot, aPostDestroyData, shell, |
359 | 0 | PushedFloatProperty()); |
360 | 0 | RemoveStateBits(NS_BLOCK_HAS_PUSHED_FLOATS); |
361 | 0 | } |
362 | 0 |
|
363 | 0 | // destroy overflow lines now |
364 | 0 | FrameLines* overflowLines = RemoveOverflowLines(); |
365 | 0 | if (overflowLines) { |
366 | 0 | nsLineBox::DeleteLineList(presContext, overflowLines->mLines, |
367 | 0 | aDestructRoot, &overflowLines->mFrames, |
368 | 0 | aPostDestroyData); |
369 | 0 | delete overflowLines; |
370 | 0 | } |
371 | 0 |
|
372 | 0 | if (GetStateBits() & NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS) { |
373 | 0 | SafelyDestroyFrameListProp(aDestructRoot, aPostDestroyData, shell, |
374 | 0 | OverflowOutOfFlowsProperty()); |
375 | 0 | RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS); |
376 | 0 | } |
377 | 0 |
|
378 | 0 | if (HasOutsideBullet()) { |
379 | 0 | SafelyDestroyFrameListProp(aDestructRoot, aPostDestroyData, shell, |
380 | 0 | OutsideBulletProperty()); |
381 | 0 | RemoveStateBits(NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET); |
382 | 0 | } |
383 | 0 |
|
384 | 0 | nsContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData); |
385 | 0 | } |
386 | | |
387 | | /* virtual */ nsILineIterator* |
388 | | nsBlockFrame::GetLineIterator() |
389 | 0 | { |
390 | 0 | nsLineIterator* it = new nsLineIterator; |
391 | 0 | if (!it) |
392 | 0 | return nullptr; |
393 | 0 | |
394 | 0 | const nsStyleVisibility* visibility = StyleVisibility(); |
395 | 0 | nsresult rv = it->Init(mLines, visibility->mDirection == NS_STYLE_DIRECTION_RTL); |
396 | 0 | if (NS_FAILED(rv)) { |
397 | 0 | delete it; |
398 | 0 | return nullptr; |
399 | 0 | } |
400 | 0 | return it; |
401 | 0 | } |
402 | | |
403 | 0 | NS_QUERYFRAME_HEAD(nsBlockFrame) |
404 | 0 | NS_QUERYFRAME_ENTRY(nsBlockFrame) |
405 | 0 | NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) |
406 | | |
407 | | nsSplittableType |
408 | | nsBlockFrame::GetSplittableType() const |
409 | 0 | { |
410 | 0 | return NS_FRAME_SPLITTABLE_NON_RECTANGULAR; |
411 | 0 | } |
412 | | |
413 | | #ifdef DEBUG_FRAME_DUMP |
414 | | void |
415 | | nsBlockFrame::List(FILE* out, const char* aPrefix, uint32_t aFlags) const |
416 | | { |
417 | | nsCString str; |
418 | | ListGeneric(str, aPrefix, aFlags); |
419 | | |
420 | | fprintf_stderr(out, "%s<\n", str.get()); |
421 | | |
422 | | nsCString pfx(aPrefix); |
423 | | pfx += " "; |
424 | | |
425 | | // Output the lines |
426 | | if (!mLines.empty()) { |
427 | | ConstLineIterator line = LinesBegin(), line_end = LinesEnd(); |
428 | | for ( ; line != line_end; ++line) { |
429 | | line->List(out, pfx.get(), aFlags); |
430 | | } |
431 | | } |
432 | | |
433 | | // Output the overflow lines. |
434 | | const FrameLines* overflowLines = GetOverflowLines(); |
435 | | if (overflowLines && !overflowLines->mLines.empty()) { |
436 | | fprintf_stderr(out, "%sOverflow-lines %p/%p <\n", pfx.get(), overflowLines, &overflowLines->mFrames); |
437 | | nsCString nestedPfx(pfx); |
438 | | nestedPfx += " "; |
439 | | ConstLineIterator line = overflowLines->mLines.begin(), |
440 | | line_end = overflowLines->mLines.end(); |
441 | | for ( ; line != line_end; ++line) { |
442 | | line->List(out, nestedPfx.get(), aFlags); |
443 | | } |
444 | | fprintf_stderr(out, "%s>\n", pfx.get()); |
445 | | } |
446 | | |
447 | | // skip the principal list - we printed the lines above |
448 | | // skip the overflow list - we printed the overflow lines above |
449 | | ChildListIterator lists(this); |
450 | | ChildListIDs skip(kPrincipalList | kOverflowList); |
451 | | for (; !lists.IsDone(); lists.Next()) { |
452 | | if (skip.Contains(lists.CurrentID())) { |
453 | | continue; |
454 | | } |
455 | | fprintf_stderr(out, "%s%s %p <\n", pfx.get(), |
456 | | mozilla::layout::ChildListName(lists.CurrentID()), |
457 | | &GetChildList(lists.CurrentID())); |
458 | | nsCString nestedPfx(pfx); |
459 | | nestedPfx += " "; |
460 | | nsFrameList::Enumerator childFrames(lists.CurrentList()); |
461 | | for (; !childFrames.AtEnd(); childFrames.Next()) { |
462 | | nsIFrame* kid = childFrames.get(); |
463 | | kid->List(out, nestedPfx.get(), aFlags); |
464 | | } |
465 | | fprintf_stderr(out, "%s>\n", pfx.get()); |
466 | | } |
467 | | |
468 | | fprintf_stderr(out, "%s>\n", aPrefix); |
469 | | } |
470 | | |
471 | | nsresult |
472 | | nsBlockFrame::GetFrameName(nsAString& aResult) const |
473 | | { |
474 | | return MakeFrameName(NS_LITERAL_STRING("Block"), aResult); |
475 | | } |
476 | | #endif |
477 | | |
478 | | void |
479 | | nsBlockFrame::InvalidateFrame(uint32_t aDisplayItemKey, bool aRebuildDisplayItems) |
480 | 0 | { |
481 | 0 | if (nsSVGUtils::IsInSVGTextSubtree(this)) { |
482 | 0 | NS_ASSERTION(GetParent()->IsSVGTextFrame(), |
483 | 0 | "unexpected block frame in SVG text"); |
484 | 0 | GetParent()->InvalidateFrame(); |
485 | 0 | return; |
486 | 0 | } |
487 | 0 | nsContainerFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems); |
488 | 0 | } |
489 | | |
490 | | void |
491 | | nsBlockFrame::InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey, bool aRebuildDisplayItems) |
492 | 0 | { |
493 | 0 | if (nsSVGUtils::IsInSVGTextSubtree(this)) { |
494 | 0 | NS_ASSERTION(GetParent()->IsSVGTextFrame(), |
495 | 0 | "unexpected block frame in SVG text"); |
496 | 0 | GetParent()->InvalidateFrame(); |
497 | 0 | return; |
498 | 0 | } |
499 | 0 | nsContainerFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey, aRebuildDisplayItems); |
500 | 0 | } |
501 | | |
502 | | nscoord |
503 | | nsBlockFrame::GetLogicalBaseline(WritingMode aWM) const |
504 | 0 | { |
505 | 0 | auto lastBaseline = |
506 | 0 | BaselineBOffset(aWM, BaselineSharingGroup::eLast, AlignmentContext::eInline); |
507 | 0 | return BSize(aWM) - lastBaseline; |
508 | 0 | } |
509 | | |
510 | | bool |
511 | | nsBlockFrame::GetNaturalBaselineBOffset(mozilla::WritingMode aWM, |
512 | | BaselineSharingGroup aBaselineGroup, |
513 | | nscoord* aBaseline) const |
514 | 0 | { |
515 | 0 | if (aBaselineGroup == BaselineSharingGroup::eFirst) { |
516 | 0 | return nsLayoutUtils::GetFirstLineBaseline(aWM, this, aBaseline); |
517 | 0 | } |
518 | 0 | |
519 | 0 | if (StyleDisplay()->IsContainSize()) { |
520 | 0 | return false; |
521 | 0 | } |
522 | 0 | |
523 | 0 | for (ConstReverseLineIterator line = LinesRBegin(), line_end = LinesREnd(); |
524 | 0 | line != line_end; ++line) { |
525 | 0 | if (line->IsBlock()) { |
526 | 0 | nscoord offset; |
527 | 0 | nsIFrame* kid = line->mFirstChild; |
528 | 0 | if (kid->GetVerticalAlignBaseline(aWM, &offset)) { |
529 | 0 | // Ignore relative positioning for baseline calculations. |
530 | 0 | const nsSize& sz = line->mContainerSize; |
531 | 0 | offset += kid->GetLogicalNormalPosition(aWM, sz).B(aWM); |
532 | 0 | *aBaseline = BSize(aWM) - offset; |
533 | 0 | return true; |
534 | 0 | } |
535 | 0 | } else { |
536 | 0 | // XXX Is this the right test? We have some bogus empty lines |
537 | 0 | // floating around, but IsEmpty is perhaps too weak. |
538 | 0 | if (line->BSize() != 0 || !line->IsEmpty()) { |
539 | 0 | *aBaseline = BSize(aWM) - (line->BStart() + line->GetLogicalAscent()); |
540 | 0 | return true; |
541 | 0 | } |
542 | 0 | } |
543 | 0 | } |
544 | 0 | return false; |
545 | 0 | } |
546 | | |
547 | | nscoord |
548 | | nsBlockFrame::GetCaretBaseline() const |
549 | 0 | { |
550 | 0 | nsRect contentRect = GetContentRect(); |
551 | 0 | nsMargin bp = GetUsedBorderAndPadding(); |
552 | 0 |
|
553 | 0 | if (!mLines.empty()) { |
554 | 0 | ConstLineIterator line = LinesBegin(); |
555 | 0 | const nsLineBox* firstLine = line; |
556 | 0 | if (firstLine->GetChildCount()) { |
557 | 0 | return bp.top + firstLine->mFirstChild->GetCaretBaseline(); |
558 | 0 | } |
559 | 0 | } |
560 | 0 | float inflation = nsLayoutUtils::FontSizeInflationFor(this); |
561 | 0 | RefPtr<nsFontMetrics> fm = |
562 | 0 | nsLayoutUtils::GetFontMetricsForFrame(this, inflation); |
563 | 0 | nscoord lineHeight = |
564 | 0 | ReflowInput::CalcLineHeight(GetContent(), |
565 | 0 | Style(), |
566 | 0 | PresContext(), |
567 | 0 | contentRect.height, |
568 | 0 | inflation); |
569 | 0 | const WritingMode wm = GetWritingMode(); |
570 | 0 | return nsLayoutUtils::GetCenteredFontBaseline(fm, lineHeight, |
571 | 0 | wm.IsLineInverted()) + bp.top; |
572 | 0 | } |
573 | | |
574 | | ///////////////////////////////////////////////////////////////////////////// |
575 | | // Child frame enumeration |
576 | | |
577 | | const nsFrameList& |
578 | | nsBlockFrame::GetChildList(ChildListID aListID) const |
579 | 0 | { |
580 | 0 | switch (aListID) { |
581 | 0 | case kPrincipalList: |
582 | 0 | return mFrames; |
583 | 0 | case kOverflowList: { |
584 | 0 | FrameLines* overflowLines = GetOverflowLines(); |
585 | 0 | return overflowLines ? overflowLines->mFrames : nsFrameList::EmptyList(); |
586 | 0 | } |
587 | 0 | case kFloatList: |
588 | 0 | return mFloats; |
589 | 0 | case kOverflowOutOfFlowList: { |
590 | 0 | const nsFrameList* list = GetOverflowOutOfFlows(); |
591 | 0 | return list ? *list : nsFrameList::EmptyList(); |
592 | 0 | } |
593 | 0 | case kPushedFloatsList: { |
594 | 0 | const nsFrameList* list = GetPushedFloats(); |
595 | 0 | return list ? *list : nsFrameList::EmptyList(); |
596 | 0 | } |
597 | 0 | case kBulletList: { |
598 | 0 | const nsFrameList* list = GetOutsideBulletList(); |
599 | 0 | return list ? *list : nsFrameList::EmptyList(); |
600 | 0 | } |
601 | 0 | default: |
602 | 0 | return nsContainerFrame::GetChildList(aListID); |
603 | 0 | } |
604 | 0 | } |
605 | | |
606 | | void |
607 | | nsBlockFrame::GetChildLists(nsTArray<ChildList>* aLists) const |
608 | 0 | { |
609 | 0 | nsContainerFrame::GetChildLists(aLists); |
610 | 0 | FrameLines* overflowLines = GetOverflowLines(); |
611 | 0 | if (overflowLines) { |
612 | 0 | overflowLines->mFrames.AppendIfNonempty(aLists, kOverflowList); |
613 | 0 | } |
614 | 0 | const nsFrameList* list = GetOverflowOutOfFlows(); |
615 | 0 | if (list) { |
616 | 0 | list->AppendIfNonempty(aLists, kOverflowOutOfFlowList); |
617 | 0 | } |
618 | 0 | mFloats.AppendIfNonempty(aLists, kFloatList); |
619 | 0 | list = GetOutsideBulletList(); |
620 | 0 | if (list) { |
621 | 0 | list->AppendIfNonempty(aLists, kBulletList); |
622 | 0 | } |
623 | 0 | list = GetPushedFloats(); |
624 | 0 | if (list) { |
625 | 0 | list->AppendIfNonempty(aLists, kPushedFloatsList); |
626 | 0 | } |
627 | 0 | } |
628 | | |
629 | | /* virtual */ bool |
630 | | nsBlockFrame::IsFloatContainingBlock() const |
631 | 0 | { |
632 | 0 | return true; |
633 | 0 | } |
634 | | |
635 | | static void |
636 | | ReparentFrameInternal(nsIFrame* aFrame, nsContainerFrame* aOldParent, |
637 | | nsContainerFrame* aNewParent, bool aMarkDirty) |
638 | 0 | { |
639 | 0 | NS_ASSERTION(aOldParent == aFrame->GetParent(), |
640 | 0 | "Parent not consistent with expectations"); |
641 | 0 |
|
642 | 0 | aFrame->SetParent(aNewParent); |
643 | 0 | if (aMarkDirty) { |
644 | 0 | aFrame->AddStateBits(NS_FRAME_IS_DIRTY); |
645 | 0 | } |
646 | 0 |
|
647 | 0 | // When pushing and pulling frames we need to check for whether any |
648 | 0 | // views need to be reparented |
649 | 0 | nsContainerFrame::ReparentFrameView(aFrame, aOldParent, aNewParent); |
650 | 0 | } |
651 | | |
652 | | static bool |
653 | | ShouldMarkReparentedFramesDirty( |
654 | | #ifdef DEBUG |
655 | | nsContainerFrame* aOldParent, |
656 | | #endif |
657 | | nsIFrame* aNewParent, |
658 | | ReparentingDirection aDirection) |
659 | 0 | { |
660 | | #ifdef DEBUG |
661 | | MOZ_ASSERT(aOldParent->FirstInFlow() == aNewParent->FirstInFlow(), |
662 | | "Reparenting should be between continuations of the same frame"); |
663 | | if (aDirection != ReparentingDirection::Variable && |
664 | | aOldParent->FirstInFlow() == aNewParent->FirstInFlow()) { |
665 | | auto IndexInFlow = |
666 | | [](const nsIFrame* f) { |
667 | | int i = 0; while ((f = f->GetPrevInFlow())) { ++i; } return i; |
668 | | }; |
669 | | MOZ_ASSERT((IndexInFlow(aOldParent) < IndexInFlow(aNewParent)) == |
670 | | (aDirection == ReparentingDirection::Forwards), |
671 | | "Parents not in expected order"); |
672 | | } |
673 | | #endif |
674 | | // Frames going forward must have already been reflowed, or at least marked |
675 | 0 | // dirty. Otherwise frames going backwards (or direction is unknown) may not |
676 | 0 | // be marked dirty yet. |
677 | 0 | return (aDirection != ReparentingDirection::Forwards) && |
678 | 0 | (aNewParent->GetStateBits() & NS_FRAME_IS_DIRTY); |
679 | 0 | } |
680 | | |
681 | | // Because a frame with NS_FRAME_IS_DIRTY marks all of its children dirty at |
682 | | // the start of its reflow, when we move a frame from a later frame backwards to |
683 | | // an earlier frame, and the earlier frame has NS_FRAME_IS_DIRTY (though that |
684 | | // should corresponded with the later frame having NS_FRAME_IS_DIRTY), we need |
685 | | // to add NS_FRAME_IS_DIRTY to the reparented frame. |
686 | | static void |
687 | | ReparentFrame(nsIFrame* aFrame, nsContainerFrame* aOldParent, |
688 | | nsContainerFrame* aNewParent, ReparentingDirection aDirection) |
689 | 0 | { |
690 | 0 | const bool markDirty = ShouldMarkReparentedFramesDirty( |
691 | | #ifdef DEBUG |
692 | | aOldParent, |
693 | | #endif |
694 | | aNewParent, aDirection); |
695 | 0 | ReparentFrameInternal(aFrame, aOldParent, aNewParent, markDirty); |
696 | 0 | } |
697 | | |
698 | | static void |
699 | | ReparentFrames(nsFrameList& aFrameList, nsContainerFrame* aOldParent, |
700 | | nsContainerFrame* aNewParent, ReparentingDirection aDirection) |
701 | 0 | { |
702 | 0 | const bool markDirty = ShouldMarkReparentedFramesDirty( |
703 | | #ifdef DEBUG |
704 | | aOldParent, |
705 | | #endif |
706 | | aNewParent, aDirection); |
707 | 0 | for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) { |
708 | 0 | ReparentFrameInternal(e.get(), aOldParent, aNewParent, markDirty); |
709 | 0 | } |
710 | 0 | } |
711 | | |
712 | | /** |
713 | | * Remove the first line from aFromLines and adjust the associated frame list |
714 | | * aFromFrames accordingly. The removed line is assigned to *aOutLine and |
715 | | * a frame list with its frames is assigned to *aOutFrames, i.e. the frames |
716 | | * that were extracted from the head of aFromFrames. |
717 | | * aFromLines must contain at least one line, the line may be empty. |
718 | | * @return true if aFromLines becomes empty |
719 | | */ |
720 | | static bool |
721 | | RemoveFirstLine(nsLineList& aFromLines, nsFrameList& aFromFrames, |
722 | | nsLineBox** aOutLine, nsFrameList* aOutFrames) |
723 | 0 | { |
724 | 0 | nsLineList_iterator removedLine = aFromLines.begin(); |
725 | 0 | *aOutLine = removedLine; |
726 | 0 | nsLineList_iterator next = aFromLines.erase(removedLine); |
727 | 0 | bool isLastLine = next == aFromLines.end(); |
728 | 0 | nsIFrame* lastFrame = isLastLine ? aFromFrames.LastChild() |
729 | 0 | : next->mFirstChild->GetPrevSibling(); |
730 | 0 | nsFrameList::FrameLinkEnumerator linkToBreak(aFromFrames, lastFrame); |
731 | 0 | *aOutFrames = aFromFrames.ExtractHead(linkToBreak); |
732 | 0 | return isLastLine; |
733 | 0 | } |
734 | | |
735 | | ////////////////////////////////////////////////////////////////////// |
736 | | // Reflow methods |
737 | | |
738 | | /* virtual */ void |
739 | | nsBlockFrame::MarkIntrinsicISizesDirty() |
740 | 0 | { |
741 | 0 | nsBlockFrame* dirtyBlock = static_cast<nsBlockFrame*>(FirstContinuation()); |
742 | 0 | dirtyBlock->mMinWidth = NS_INTRINSIC_WIDTH_UNKNOWN; |
743 | 0 | dirtyBlock->mPrefWidth = NS_INTRINSIC_WIDTH_UNKNOWN; |
744 | 0 | if (!(GetStateBits() & NS_BLOCK_NEEDS_BIDI_RESOLUTION)) { |
745 | 0 | for (nsIFrame* frame = dirtyBlock; frame; |
746 | 0 | frame = frame->GetNextContinuation()) { |
747 | 0 | frame->AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION); |
748 | 0 | } |
749 | 0 | } |
750 | 0 |
|
751 | 0 | nsContainerFrame::MarkIntrinsicISizesDirty(); |
752 | 0 | } |
753 | | |
754 | | void |
755 | | nsBlockFrame::CheckIntrinsicCacheAgainstShrinkWrapState() |
756 | 0 | { |
757 | 0 | nsPresContext *presContext = PresContext(); |
758 | 0 | if (!nsLayoutUtils::FontSizeInflationEnabled(presContext)) { |
759 | 0 | return; |
760 | 0 | } |
761 | 0 | bool inflationEnabled = |
762 | 0 | !presContext->mInflationDisabledForShrinkWrap; |
763 | 0 | if (inflationEnabled != |
764 | 0 | !!(GetStateBits() & NS_BLOCK_FRAME_INTRINSICS_INFLATED)) { |
765 | 0 | mMinWidth = NS_INTRINSIC_WIDTH_UNKNOWN; |
766 | 0 | mPrefWidth = NS_INTRINSIC_WIDTH_UNKNOWN; |
767 | 0 | if (inflationEnabled) { |
768 | 0 | AddStateBits(NS_BLOCK_FRAME_INTRINSICS_INFLATED); |
769 | 0 | } else { |
770 | 0 | RemoveStateBits(NS_BLOCK_FRAME_INTRINSICS_INFLATED); |
771 | 0 | } |
772 | 0 | } |
773 | 0 | } |
774 | | |
775 | | /* virtual */ nscoord |
776 | | nsBlockFrame::GetMinISize(gfxContext *aRenderingContext) |
777 | 0 | { |
778 | 0 | nsIFrame* firstInFlow = FirstContinuation(); |
779 | 0 | if (firstInFlow != this) |
780 | 0 | return firstInFlow->GetMinISize(aRenderingContext); |
781 | 0 | |
782 | 0 | DISPLAY_MIN_INLINE_SIZE(this, mMinWidth); |
783 | 0 |
|
784 | 0 | CheckIntrinsicCacheAgainstShrinkWrapState(); |
785 | 0 |
|
786 | 0 | if (mMinWidth != NS_INTRINSIC_WIDTH_UNKNOWN) |
787 | 0 | return mMinWidth; |
788 | 0 | |
789 | 0 | if (StyleDisplay()->IsContainSize()) { |
790 | 0 | mMinWidth = 0; |
791 | 0 | return mMinWidth; |
792 | 0 | } |
793 | 0 | |
794 | | #ifdef DEBUG |
795 | | if (gNoisyIntrinsic) { |
796 | | IndentBy(stdout, gNoiseIndent); |
797 | | ListTag(stdout); |
798 | | printf(": GetMinISize\n"); |
799 | | } |
800 | | AutoNoisyIndenter indenter(gNoisyIntrinsic); |
801 | | #endif |
802 | | |
803 | 0 | for (nsBlockFrame* curFrame = this; curFrame; |
804 | 0 | curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) { |
805 | 0 | curFrame->LazyMarkLinesDirty(); |
806 | 0 | } |
807 | 0 |
|
808 | 0 | if (RenumberList()) { |
809 | 0 | AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); |
810 | 0 | } |
811 | 0 | if (GetStateBits() & NS_BLOCK_NEEDS_BIDI_RESOLUTION) |
812 | 0 | ResolveBidi(); |
813 | 0 | InlineMinISizeData data; |
814 | 0 | for (nsBlockFrame* curFrame = this; curFrame; |
815 | 0 | curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) { |
816 | 0 | for (LineIterator line = curFrame->LinesBegin(), line_end = curFrame->LinesEnd(); |
817 | 0 | line != line_end; ++line) |
818 | 0 | { |
819 | | #ifdef DEBUG |
820 | | if (gNoisyIntrinsic) { |
821 | | IndentBy(stdout, gNoiseIndent); |
822 | | printf("line (%s%s)\n", |
823 | | line->IsBlock() ? "block" : "inline", |
824 | | line->IsEmpty() ? ", empty" : ""); |
825 | | } |
826 | | AutoNoisyIndenter lineindent(gNoisyIntrinsic); |
827 | | #endif |
828 | 0 | if (line->IsBlock()) { |
829 | 0 | data.ForceBreak(); |
830 | 0 | data.mCurrentLine = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, |
831 | 0 | line->mFirstChild, nsLayoutUtils::MIN_ISIZE); |
832 | 0 | data.ForceBreak(); |
833 | 0 | } else { |
834 | 0 | if (!curFrame->GetPrevContinuation() && |
835 | 0 | line == curFrame->LinesBegin()) { |
836 | 0 | data.mCurrentLine += ::ResolveTextIndent(StyleText()->mTextIndent, 0); |
837 | 0 | } |
838 | 0 | data.mLine = &line; |
839 | 0 | data.SetLineContainer(curFrame); |
840 | 0 | nsIFrame *kid = line->mFirstChild; |
841 | 0 | for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end; |
842 | 0 | ++i, kid = kid->GetNextSibling()) { |
843 | 0 | kid->AddInlineMinISize(aRenderingContext, &data); |
844 | 0 | } |
845 | 0 | } |
846 | | #ifdef DEBUG |
847 | | if (gNoisyIntrinsic) { |
848 | | IndentBy(stdout, gNoiseIndent); |
849 | | printf("min: [prevLines=%d currentLine=%d]\n", |
850 | | data.mPrevLines, data.mCurrentLine); |
851 | | } |
852 | | #endif |
853 | | } |
854 | 0 | } |
855 | 0 | data.ForceBreak(); |
856 | 0 |
|
857 | 0 | mMinWidth = data.mPrevLines; |
858 | 0 | return mMinWidth; |
859 | 0 | } |
860 | | |
861 | | /* virtual */ nscoord |
862 | | nsBlockFrame::GetPrefISize(gfxContext *aRenderingContext) |
863 | 0 | { |
864 | 0 | nsIFrame* firstInFlow = FirstContinuation(); |
865 | 0 | if (firstInFlow != this) |
866 | 0 | return firstInFlow->GetPrefISize(aRenderingContext); |
867 | 0 | |
868 | 0 | DISPLAY_PREF_INLINE_SIZE(this, mPrefWidth); |
869 | 0 |
|
870 | 0 | CheckIntrinsicCacheAgainstShrinkWrapState(); |
871 | 0 |
|
872 | 0 | if (mPrefWidth != NS_INTRINSIC_WIDTH_UNKNOWN) |
873 | 0 | return mPrefWidth; |
874 | 0 | |
875 | 0 | if (StyleDisplay()->IsContainSize()) { |
876 | 0 | mPrefWidth = 0; |
877 | 0 | return mPrefWidth; |
878 | 0 | } |
879 | 0 | |
880 | | #ifdef DEBUG |
881 | | if (gNoisyIntrinsic) { |
882 | | IndentBy(stdout, gNoiseIndent); |
883 | | ListTag(stdout); |
884 | | printf(": GetPrefISize\n"); |
885 | | } |
886 | | AutoNoisyIndenter indenter(gNoisyIntrinsic); |
887 | | #endif |
888 | | |
889 | 0 | for (nsBlockFrame* curFrame = this; curFrame; |
890 | 0 | curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) { |
891 | 0 | curFrame->LazyMarkLinesDirty(); |
892 | 0 | } |
893 | 0 |
|
894 | 0 | if (RenumberList()) { |
895 | 0 | AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); |
896 | 0 | } |
897 | 0 | if (GetStateBits() & NS_BLOCK_NEEDS_BIDI_RESOLUTION) |
898 | 0 | ResolveBidi(); |
899 | 0 | InlinePrefISizeData data; |
900 | 0 | for (nsBlockFrame* curFrame = this; curFrame; |
901 | 0 | curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) { |
902 | 0 | for (LineIterator line = curFrame->LinesBegin(), line_end = curFrame->LinesEnd(); |
903 | 0 | line != line_end; ++line) |
904 | 0 | { |
905 | | #ifdef DEBUG |
906 | | if (gNoisyIntrinsic) { |
907 | | IndentBy(stdout, gNoiseIndent); |
908 | | printf("line (%s%s)\n", |
909 | | line->IsBlock() ? "block" : "inline", |
910 | | line->IsEmpty() ? ", empty" : ""); |
911 | | } |
912 | | AutoNoisyIndenter lineindent(gNoisyIntrinsic); |
913 | | #endif |
914 | 0 | if (line->IsBlock()) { |
915 | 0 | StyleClear breakType; |
916 | 0 | if (!data.mLineIsEmpty || BlockCanIntersectFloats(line->mFirstChild)) { |
917 | 0 | breakType = StyleClear::Both; |
918 | 0 | } else { |
919 | 0 | breakType = line->mFirstChild->StyleDisplay()->mBreakType; |
920 | 0 | } |
921 | 0 | data.ForceBreak(breakType); |
922 | 0 | data.mCurrentLine = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, |
923 | 0 | line->mFirstChild, nsLayoutUtils::PREF_ISIZE); |
924 | 0 | data.ForceBreak(); |
925 | 0 | } else { |
926 | 0 | if (!curFrame->GetPrevContinuation() && |
927 | 0 | line == curFrame->LinesBegin()) { |
928 | 0 | nscoord indent = ::ResolveTextIndent(StyleText()->mTextIndent, 0); |
929 | 0 | data.mCurrentLine += indent; |
930 | 0 | // XXXmats should the test below be indent > 0? |
931 | 0 | if (indent != nscoord(0)) { |
932 | 0 | data.mLineIsEmpty = false; |
933 | 0 | } |
934 | 0 | } |
935 | 0 | data.mLine = &line; |
936 | 0 | data.SetLineContainer(curFrame); |
937 | 0 | nsIFrame *kid = line->mFirstChild; |
938 | 0 | for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end; |
939 | 0 | ++i, kid = kid->GetNextSibling()) { |
940 | 0 | kid->AddInlinePrefISize(aRenderingContext, &data); |
941 | 0 | } |
942 | 0 | } |
943 | | #ifdef DEBUG |
944 | | if (gNoisyIntrinsic) { |
945 | | IndentBy(stdout, gNoiseIndent); |
946 | | printf("pref: [prevLines=%d currentLine=%d]\n", |
947 | | data.mPrevLines, data.mCurrentLine); |
948 | | } |
949 | | #endif |
950 | | } |
951 | 0 | } |
952 | 0 | data.ForceBreak(); |
953 | 0 |
|
954 | 0 | mPrefWidth = data.mPrevLines; |
955 | 0 | return mPrefWidth; |
956 | 0 | } |
957 | | |
958 | | nsRect |
959 | | nsBlockFrame::ComputeTightBounds(DrawTarget* aDrawTarget) const |
960 | 0 | { |
961 | 0 | // be conservative |
962 | 0 | if (Style()->HasTextDecorationLines()) { |
963 | 0 | return GetVisualOverflowRect(); |
964 | 0 | } |
965 | 0 | return ComputeSimpleTightBounds(aDrawTarget); |
966 | 0 | } |
967 | | |
968 | | /* virtual */ nsresult |
969 | | nsBlockFrame::GetPrefWidthTightBounds(gfxContext* aRenderingContext, |
970 | | nscoord* aX, |
971 | | nscoord* aXMost) |
972 | 0 | { |
973 | 0 | nsIFrame* firstInFlow = FirstContinuation(); |
974 | 0 | if (firstInFlow != this) { |
975 | 0 | return firstInFlow->GetPrefWidthTightBounds(aRenderingContext, aX, aXMost); |
976 | 0 | } |
977 | 0 | |
978 | 0 | *aX = 0; |
979 | 0 | *aXMost = 0; |
980 | 0 |
|
981 | 0 | nsresult rv; |
982 | 0 | InlinePrefISizeData data; |
983 | 0 | for (nsBlockFrame* curFrame = this; curFrame; |
984 | 0 | curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) { |
985 | 0 | for (LineIterator line = curFrame->LinesBegin(), line_end = curFrame->LinesEnd(); |
986 | 0 | line != line_end; ++line) |
987 | 0 | { |
988 | 0 | nscoord childX, childXMost; |
989 | 0 | if (line->IsBlock()) { |
990 | 0 | data.ForceBreak(); |
991 | 0 | rv = line->mFirstChild->GetPrefWidthTightBounds(aRenderingContext, |
992 | 0 | &childX, &childXMost); |
993 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
994 | 0 | *aX = std::min(*aX, childX); |
995 | 0 | *aXMost = std::max(*aXMost, childXMost); |
996 | 0 | } else { |
997 | 0 | if (!curFrame->GetPrevContinuation() && |
998 | 0 | line == curFrame->LinesBegin()) { |
999 | 0 | data.mCurrentLine += ::ResolveTextIndent(StyleText()->mTextIndent, 0); |
1000 | 0 | } |
1001 | 0 | data.mLine = &line; |
1002 | 0 | data.SetLineContainer(curFrame); |
1003 | 0 | nsIFrame *kid = line->mFirstChild; |
1004 | 0 | for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end; |
1005 | 0 | ++i, kid = kid->GetNextSibling()) { |
1006 | 0 | rv = kid->GetPrefWidthTightBounds(aRenderingContext, &childX, |
1007 | 0 | &childXMost); |
1008 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1009 | 0 | *aX = std::min(*aX, data.mCurrentLine + childX); |
1010 | 0 | *aXMost = std::max(*aXMost, data.mCurrentLine + childXMost); |
1011 | 0 | kid->AddInlinePrefISize(aRenderingContext, &data); |
1012 | 0 | } |
1013 | 0 | } |
1014 | 0 | } |
1015 | 0 | } |
1016 | 0 | data.ForceBreak(); |
1017 | 0 |
|
1018 | 0 | return NS_OK; |
1019 | 0 | } |
1020 | | |
1021 | | /** |
1022 | | * Return whether aNewAvailableSpace is smaller *on either side* |
1023 | | * (inline-start or inline-end) than aOldAvailableSpace, so that we know |
1024 | | * if we need to redo layout on an line, replaced block, or block |
1025 | | * formatting context, because its height (which we used to compute |
1026 | | * aNewAvailableSpace) caused it to intersect additional floats. |
1027 | | */ |
1028 | | static bool |
1029 | | AvailableSpaceShrunk(WritingMode aWM, |
1030 | | const LogicalRect& aOldAvailableSpace, |
1031 | | const LogicalRect& aNewAvailableSpace, |
1032 | | bool aCanGrow /* debug-only */) |
1033 | 0 | { |
1034 | 0 | if (aNewAvailableSpace.ISize(aWM) == 0) { |
1035 | 0 | // Positions are not significant if the inline size is zero. |
1036 | 0 | return aOldAvailableSpace.ISize(aWM) != 0; |
1037 | 0 | } |
1038 | 0 | if (aCanGrow) { |
1039 | 0 | NS_ASSERTION(aNewAvailableSpace.IStart(aWM) <= |
1040 | 0 | aOldAvailableSpace.IStart(aWM) || |
1041 | 0 | aNewAvailableSpace.IEnd(aWM) <= aOldAvailableSpace.IEnd(aWM), |
1042 | 0 | "available space should not shrink on the start side and " |
1043 | 0 | "grow on the end side"); |
1044 | 0 | NS_ASSERTION(aNewAvailableSpace.IStart(aWM) >= |
1045 | 0 | aOldAvailableSpace.IStart(aWM) || |
1046 | 0 | aNewAvailableSpace.IEnd(aWM) >= aOldAvailableSpace.IEnd(aWM), |
1047 | 0 | "available space should not grow on the start side and " |
1048 | 0 | "shrink on the end side"); |
1049 | 0 | } else { |
1050 | 0 | NS_ASSERTION(aOldAvailableSpace.IStart(aWM) <= |
1051 | 0 | aNewAvailableSpace.IStart(aWM) && |
1052 | 0 | aOldAvailableSpace.IEnd(aWM) >= |
1053 | 0 | aNewAvailableSpace.IEnd(aWM), |
1054 | 0 | "available space should never grow"); |
1055 | 0 | } |
1056 | 0 | // Have we shrunk on either side? |
1057 | 0 | return aNewAvailableSpace.IStart(aWM) > aOldAvailableSpace.IStart(aWM) || |
1058 | 0 | aNewAvailableSpace.IEnd(aWM) < aOldAvailableSpace.IEnd(aWM); |
1059 | 0 | } |
1060 | | |
1061 | | static LogicalSize |
1062 | | CalculateContainingBlockSizeForAbsolutes(WritingMode aWM, |
1063 | | const ReflowInput& aReflowInput, |
1064 | | LogicalSize aFrameSize) |
1065 | 0 | { |
1066 | 0 | // The issue here is that for a 'height' of 'auto' the reflow state |
1067 | 0 | // code won't know how to calculate the containing block height |
1068 | 0 | // because it's calculated bottom up. So we use our own computed |
1069 | 0 | // size as the dimensions. |
1070 | 0 | nsIFrame* frame = aReflowInput.mFrame; |
1071 | 0 |
|
1072 | 0 | LogicalSize cbSize(aFrameSize); |
1073 | 0 | // Containing block is relative to the padding edge |
1074 | 0 | const LogicalMargin& border = |
1075 | 0 | LogicalMargin(aWM, aReflowInput.ComputedPhysicalBorderPadding() - |
1076 | 0 | aReflowInput.ComputedPhysicalPadding()); |
1077 | 0 | cbSize.ISize(aWM) -= border.IStartEnd(aWM); |
1078 | 0 | cbSize.BSize(aWM) -= border.BStartEnd(aWM); |
1079 | 0 |
|
1080 | 0 | if (frame->GetParent()->GetContent() == frame->GetContent() && |
1081 | 0 | !frame->GetParent()->IsCanvasFrame()) { |
1082 | 0 | // We are a wrapped frame for the content (and the wrapper is not the |
1083 | 0 | // canvas frame, whose size is not meaningful here). |
1084 | 0 | // Use the container's dimensions, if they have been precomputed. |
1085 | 0 | // XXX This is a hack! We really should be waiting until the outermost |
1086 | 0 | // frame is fully reflowed and using the resulting dimensions, even |
1087 | 0 | // if they're intrinsic. |
1088 | 0 | // In fact we should be attaching absolute children to the outermost |
1089 | 0 | // frame and not always sticking them in block frames. |
1090 | 0 |
|
1091 | 0 | // First, find the reflow state for the outermost frame for this |
1092 | 0 | // content, except for fieldsets where the inner anonymous frame has |
1093 | 0 | // the correct padding area with the legend taken into account. |
1094 | 0 | const ReflowInput* aLastRI = &aReflowInput; |
1095 | 0 | const ReflowInput* lastButOneRI = &aReflowInput; |
1096 | 0 | while (aLastRI->mParentReflowInput && |
1097 | 0 | aLastRI->mParentReflowInput->mFrame->GetContent() == frame->GetContent() && |
1098 | 0 | !aLastRI->mParentReflowInput->mFrame->IsFieldSetFrame()) { |
1099 | 0 | lastButOneRI = aLastRI; |
1100 | 0 | aLastRI = aLastRI->mParentReflowInput; |
1101 | 0 | } |
1102 | 0 | if (aLastRI != &aReflowInput) { |
1103 | 0 | // Scrollbars need to be specifically excluded, if present, because they are outside the |
1104 | 0 | // padding-edge. We need better APIs for getting the various boxes from a frame. |
1105 | 0 | nsIScrollableFrame* scrollFrame = do_QueryFrame(aLastRI->mFrame); |
1106 | 0 | nsMargin scrollbars(0,0,0,0); |
1107 | 0 | if (scrollFrame) { |
1108 | 0 | scrollbars = |
1109 | 0 | scrollFrame->GetDesiredScrollbarSizes(aLastRI->mFrame->PresContext(), |
1110 | 0 | aLastRI->mRenderingContext); |
1111 | 0 | if (!lastButOneRI->mFlags.mAssumingHScrollbar) { |
1112 | 0 | scrollbars.top = scrollbars.bottom = 0; |
1113 | 0 | } |
1114 | 0 | if (!lastButOneRI->mFlags.mAssumingVScrollbar) { |
1115 | 0 | scrollbars.left = scrollbars.right = 0; |
1116 | 0 | } |
1117 | 0 | } |
1118 | 0 | // We found a reflow state for the outermost wrapping frame, so use |
1119 | 0 | // its computed metrics if available, converted to our writing mode |
1120 | 0 | WritingMode lastWM = aLastRI->GetWritingMode(); |
1121 | 0 | LogicalSize lastRISize = |
1122 | 0 | LogicalSize(lastWM, |
1123 | 0 | aLastRI->ComputedISize(), |
1124 | 0 | aLastRI->ComputedBSize()).ConvertTo(aWM, lastWM); |
1125 | 0 | LogicalMargin lastRIPadding = |
1126 | 0 | aLastRI->ComputedLogicalPadding().ConvertTo(aWM, lastWM); |
1127 | 0 | LogicalMargin logicalScrollbars(aWM, scrollbars); |
1128 | 0 | if (lastRISize.ISize(aWM) != NS_UNCONSTRAINEDSIZE) { |
1129 | 0 | cbSize.ISize(aWM) = std::max(0, lastRISize.ISize(aWM) + |
1130 | 0 | lastRIPadding.IStartEnd(aWM) - |
1131 | 0 | logicalScrollbars.IStartEnd(aWM)); |
1132 | 0 | } |
1133 | 0 | if (lastRISize.BSize(aWM) != NS_UNCONSTRAINEDSIZE) { |
1134 | 0 | cbSize.BSize(aWM) = std::max(0, lastRISize.BSize(aWM) + |
1135 | 0 | lastRIPadding.BStartEnd(aWM) - |
1136 | 0 | logicalScrollbars.BStartEnd(aWM)); |
1137 | 0 | } |
1138 | 0 | } |
1139 | 0 | } |
1140 | 0 |
|
1141 | 0 | return cbSize; |
1142 | 0 | } |
1143 | | |
1144 | | void |
1145 | | nsBlockFrame::Reflow(nsPresContext* aPresContext, |
1146 | | ReflowOutput& aMetrics, |
1147 | | const ReflowInput& aReflowInput, |
1148 | | nsReflowStatus& aStatus) |
1149 | 0 | { |
1150 | 0 | MarkInReflow(); |
1151 | 0 | DO_GLOBAL_REFLOW_COUNT("nsBlockFrame"); |
1152 | 0 | DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus); |
1153 | 0 | MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); |
1154 | 0 |
|
1155 | | #ifdef DEBUG |
1156 | | if (gNoisyReflow) { |
1157 | | IndentBy(stdout, gNoiseIndent); |
1158 | | ListTag(stdout); |
1159 | | printf(": begin reflow availSize=%d,%d computedSize=%d,%d\n", |
1160 | | aReflowInput.AvailableISize(), aReflowInput.AvailableBSize(), |
1161 | | aReflowInput.ComputedISize(), aReflowInput.ComputedBSize()); |
1162 | | } |
1163 | | AutoNoisyIndenter indent(gNoisy); |
1164 | | PRTime start = 0; // Initialize these variablies to silence the compiler. |
1165 | | int32_t ctc = 0; // We only use these if they are set (gLameReflowMetrics). |
1166 | | if (gLameReflowMetrics) { |
1167 | | start = PR_Now(); |
1168 | | ctc = nsLineBox::GetCtorCount(); |
1169 | | } |
1170 | | #endif |
1171 | |
|
1172 | 0 | const ReflowInput *reflowInput = &aReflowInput; |
1173 | 0 | WritingMode wm = aReflowInput.GetWritingMode(); |
1174 | 0 | nscoord consumedBSize = ConsumedBSize(wm); |
1175 | 0 | nscoord effectiveComputedBSize = GetEffectiveComputedBSize(aReflowInput, |
1176 | 0 | consumedBSize); |
1177 | 0 | Maybe<ReflowInput> mutableReflowInput; |
1178 | 0 | // If we have non-auto block size, we're clipping our kids and we fit, |
1179 | 0 | // make sure our kids fit too. |
1180 | 0 | if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE && |
1181 | 0 | aReflowInput.ComputedBSize() != NS_AUTOHEIGHT && |
1182 | 0 | ShouldApplyOverflowClipping(this, aReflowInput.mStyleDisplay)) { |
1183 | 0 | LogicalMargin blockDirExtras = aReflowInput.ComputedLogicalBorderPadding(); |
1184 | 0 | if (GetLogicalSkipSides().BStart()) { |
1185 | 0 | blockDirExtras.BStart(wm) = 0; |
1186 | 0 | } else { |
1187 | 0 | // Block-end margin never causes us to create continuations, so we |
1188 | 0 | // don't need to worry about whether it fits in its entirety. |
1189 | 0 | blockDirExtras.BStart(wm) += |
1190 | 0 | aReflowInput.ComputedLogicalMargin().BStart(wm); |
1191 | 0 | } |
1192 | 0 |
|
1193 | 0 | if (effectiveComputedBSize + blockDirExtras.BStartEnd(wm) <= |
1194 | 0 | aReflowInput.AvailableBSize()) { |
1195 | 0 | mutableReflowInput.emplace(aReflowInput); |
1196 | 0 | mutableReflowInput->AvailableBSize() = NS_UNCONSTRAINEDSIZE; |
1197 | 0 | reflowInput = mutableReflowInput.ptr(); |
1198 | 0 | } |
1199 | 0 | } |
1200 | 0 |
|
1201 | 0 | // See comment below about oldSize. Use *only* for the |
1202 | 0 | // abs-pos-containing-block-size-change optimization! |
1203 | 0 | nsSize oldSize = GetSize(); |
1204 | 0 |
|
1205 | 0 | // Should we create a float manager? |
1206 | 0 | nsAutoFloatManager autoFloatManager(const_cast<ReflowInput&>(*reflowInput)); |
1207 | 0 |
|
1208 | 0 | // XXXldb If we start storing the float manager in the frame rather |
1209 | 0 | // than keeping it around only during reflow then we should create it |
1210 | 0 | // only when there are actually floats to manage. Otherwise things |
1211 | 0 | // like tables will gain significant bloat. |
1212 | 0 | bool needFloatManager = nsBlockFrame::BlockNeedsFloatManager(this); |
1213 | 0 | if (needFloatManager) |
1214 | 0 | autoFloatManager.CreateFloatManager(aPresContext); |
1215 | 0 |
|
1216 | 0 | // OK, some lines may be reflowed. Blow away any saved line cursor |
1217 | 0 | // because we may invalidate the nondecreasing |
1218 | 0 | // overflowArea.VisualOverflow().y/yMost invariant, and we may even |
1219 | 0 | // delete the line with the line cursor. |
1220 | 0 | ClearLineCursor(); |
1221 | 0 |
|
1222 | 0 | if (IsFrameTreeTooDeep(*reflowInput, aMetrics, aStatus)) { |
1223 | 0 | return; |
1224 | 0 | } |
1225 | 0 | |
1226 | | #ifdef DEBUG |
1227 | | // Between when we drain pushed floats and when we complete reflow, |
1228 | | // we're allowed to have multiple continuations of the same float on |
1229 | | // our floats list, since a first-in-flow might get pushed to a later |
1230 | | // continuation of its containing block. But it's not permitted |
1231 | | // outside that time. |
1232 | | nsLayoutUtils::AssertNoDuplicateContinuations(this, mFloats); |
1233 | | #endif |
1234 | | |
1235 | 0 | // ALWAYS drain overflow. We never want to leave the previnflow's |
1236 | 0 | // overflow lines hanging around; block reflow depends on the |
1237 | 0 | // overflow line lists being cleared out between reflow passes. |
1238 | 0 | DrainOverflowLines(); |
1239 | 0 |
|
1240 | 0 | bool blockStartMarginRoot, blockEndMarginRoot; |
1241 | 0 | IsMarginRoot(&blockStartMarginRoot, &blockEndMarginRoot); |
1242 | 0 |
|
1243 | 0 | // Cache the consumed height in the block reflow state so that we don't have |
1244 | 0 | // to continually recompute it. |
1245 | 0 | BlockReflowInput state(*reflowInput, aPresContext, this, |
1246 | 0 | blockStartMarginRoot, blockEndMarginRoot, |
1247 | 0 | needFloatManager, consumedBSize); |
1248 | 0 |
|
1249 | 0 | if (GetStateBits() & NS_BLOCK_NEEDS_BIDI_RESOLUTION) |
1250 | 0 | static_cast<nsBlockFrame*>(FirstContinuation())->ResolveBidi(); |
1251 | 0 |
|
1252 | 0 | if (RenumberList()) { |
1253 | 0 | AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); |
1254 | 0 | } |
1255 | 0 |
|
1256 | 0 | // Handle paginated overflow (see nsContainerFrame.h) |
1257 | 0 | nsOverflowAreas ocBounds; |
1258 | 0 | nsReflowStatus ocStatus; |
1259 | 0 | if (GetPrevInFlow()) { |
1260 | 0 | ReflowOverflowContainerChildren(aPresContext, *reflowInput, ocBounds, 0, |
1261 | 0 | ocStatus); |
1262 | 0 | } |
1263 | 0 |
|
1264 | 0 | // Now that we're done cleaning up our overflow container lists, we can |
1265 | 0 | // give |state| its nsOverflowContinuationTracker. |
1266 | 0 | nsOverflowContinuationTracker tracker(this, false); |
1267 | 0 | state.mOverflowTracker = &tracker; |
1268 | 0 |
|
1269 | 0 | // Drain & handle pushed floats |
1270 | 0 | DrainPushedFloats(); |
1271 | 0 | nsOverflowAreas fcBounds; |
1272 | 0 | nsReflowStatus fcStatus; |
1273 | 0 | ReflowPushedFloats(state, fcBounds, fcStatus); |
1274 | 0 |
|
1275 | 0 | // If we're not dirty (which means we'll mark everything dirty later) |
1276 | 0 | // and our inline-size has changed, mark the lines dirty that we need to |
1277 | 0 | // mark dirty for a resize reflow. |
1278 | 0 | if (!(GetStateBits() & NS_FRAME_IS_DIRTY) && reflowInput->IsIResize()) { |
1279 | 0 | PrepareResizeReflow(state); |
1280 | 0 | } |
1281 | 0 |
|
1282 | 0 | // The same for percentage text-indent, except conditioned on the |
1283 | 0 | // parent resizing. |
1284 | 0 | if (!(GetStateBits() & NS_FRAME_IS_DIRTY) && |
1285 | 0 | reflowInput->mCBReflowInput && |
1286 | 0 | reflowInput->mCBReflowInput->IsIResize() && |
1287 | 0 | reflowInput->mStyleText->mTextIndent.HasPercent() && |
1288 | 0 | !mLines.empty()) { |
1289 | 0 | mLines.front()->MarkDirty(); |
1290 | 0 | } |
1291 | 0 |
|
1292 | 0 | LazyMarkLinesDirty(); |
1293 | 0 |
|
1294 | 0 | RemoveStateBits(NS_FRAME_FIRST_REFLOW); |
1295 | 0 |
|
1296 | 0 | // Now reflow... |
1297 | 0 | ReflowDirtyLines(state); |
1298 | 0 |
|
1299 | 0 | // If we have a next-in-flow, and that next-in-flow has pushed floats from |
1300 | 0 | // this frame from a previous iteration of reflow, then we should not return |
1301 | 0 | // a status with IsFullyComplete() equals to true, since we actually have |
1302 | 0 | // overflow, it's just already been handled. |
1303 | 0 |
|
1304 | 0 | // NOTE: This really shouldn't happen, since we _should_ pull back our floats |
1305 | 0 | // and reflow them, but just in case it does, this is a safety precaution so |
1306 | 0 | // we don't end up with a placeholder pointing to frames that have already |
1307 | 0 | // been deleted as part of removing our next-in-flow. |
1308 | 0 | if (state.mReflowStatus.IsFullyComplete()) { |
1309 | 0 | nsBlockFrame* nif = static_cast<nsBlockFrame*>(GetNextInFlow()); |
1310 | 0 | while (nif) { |
1311 | 0 | if (nif->HasPushedFloatsFromPrevContinuation()) { |
1312 | 0 | if (nif->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER) { |
1313 | 0 | state.mReflowStatus.SetOverflowIncomplete(); |
1314 | 0 | } else { |
1315 | 0 | state.mReflowStatus.SetIncomplete(); |
1316 | 0 | } |
1317 | 0 | break; |
1318 | 0 | } |
1319 | 0 |
|
1320 | 0 | nif = static_cast<nsBlockFrame*>(nif->GetNextInFlow()); |
1321 | 0 | } |
1322 | 0 | } |
1323 | 0 |
|
1324 | 0 | state.mReflowStatus.MergeCompletionStatusFrom(ocStatus); |
1325 | 0 | state.mReflowStatus.MergeCompletionStatusFrom(fcStatus); |
1326 | 0 |
|
1327 | 0 | // If we end in a BR with clear and affected floats continue, |
1328 | 0 | // we need to continue, too. |
1329 | 0 | if (NS_UNCONSTRAINEDSIZE != reflowInput->AvailableBSize() && |
1330 | 0 | state.mReflowStatus.IsComplete() && |
1331 | 0 | state.FloatManager()->ClearContinues(FindTrailingClear())) { |
1332 | 0 | state.mReflowStatus.SetIncomplete(); |
1333 | 0 | } |
1334 | 0 |
|
1335 | 0 | if (!state.mReflowStatus.IsFullyComplete()) { |
1336 | 0 | if (HasOverflowLines() || HasPushedFloats()) { |
1337 | 0 | state.mReflowStatus.SetNextInFlowNeedsReflow(); |
1338 | 0 | } |
1339 | 0 |
|
1340 | | #ifdef DEBUG_kipp |
1341 | | ListTag(stdout); printf(": block is not fully complete\n"); |
1342 | | #endif |
1343 | | } |
1344 | 0 |
|
1345 | 0 | // Place the "marker" (bullet) frame if it is placed next to a block |
1346 | 0 | // child. |
1347 | 0 | // |
1348 | 0 | // According to the CSS2 spec, section 12.6.1, the "marker" box |
1349 | 0 | // participates in the height calculation of the list-item box's |
1350 | 0 | // first line box. |
1351 | 0 | // |
1352 | 0 | // There are exactly two places a bullet can be placed: near the |
1353 | 0 | // first or second line. It's only placed on the second line in a |
1354 | 0 | // rare case: an empty first line followed by a second line that |
1355 | 0 | // contains a block (example: <LI>\n<P>... ). This is where |
1356 | 0 | // the second case can happen. |
1357 | 0 | if (HasOutsideBullet() && !mLines.empty() && |
1358 | 0 | (mLines.front()->IsBlock() || |
1359 | 0 | (0 == mLines.front()->BSize() && |
1360 | 0 | mLines.front() != mLines.back() && |
1361 | 0 | mLines.begin().next()->IsBlock()))) { |
1362 | 0 | // Reflow the bullet |
1363 | 0 | ReflowOutput reflowOutput(aReflowInput); |
1364 | 0 | // XXX Use the entire line when we fix bug 25888. |
1365 | 0 | nsLayoutUtils::LinePosition position; |
1366 | 0 | WritingMode wm = aReflowInput.GetWritingMode(); |
1367 | 0 | bool havePosition = nsLayoutUtils::GetFirstLinePosition(wm, this, |
1368 | 0 | &position); |
1369 | 0 | nscoord lineBStart = havePosition ? |
1370 | 0 | position.mBStart : |
1371 | 0 | reflowInput->ComputedLogicalBorderPadding().BStart(wm); |
1372 | 0 | nsIFrame* bullet = GetOutsideBullet(); |
1373 | 0 | ReflowBullet(bullet, state, reflowOutput, lineBStart); |
1374 | 0 | NS_ASSERTION(!BulletIsEmpty() || reflowOutput.BSize(wm) == 0, |
1375 | 0 | "empty bullet took up space"); |
1376 | 0 |
|
1377 | 0 | if (havePosition && !BulletIsEmpty()) { |
1378 | 0 | // We have some lines to align the bullet with. |
1379 | 0 |
|
1380 | 0 | // Doing the alignment using the baseline will also cater for |
1381 | 0 | // bullets that are placed next to a child block (bug 92896) |
1382 | 0 |
|
1383 | 0 | // Tall bullets won't look particularly nice here... |
1384 | 0 | LogicalRect bbox = bullet->GetLogicalRect(wm, reflowOutput.PhysicalSize()); |
1385 | 0 | bbox.BStart(wm) = position.mBaseline - reflowOutput.BlockStartAscent(); |
1386 | 0 | bullet->SetRect(wm, bbox, reflowOutput.PhysicalSize()); |
1387 | 0 | } |
1388 | 0 | // Otherwise just leave the bullet where it is, up against our |
1389 | 0 | // block-start padding. |
1390 | 0 | } |
1391 | 0 |
|
1392 | 0 | CheckFloats(state); |
1393 | 0 |
|
1394 | 0 | // Compute our final size |
1395 | 0 | nscoord blockEndEdgeOfChildren; |
1396 | 0 | ComputeFinalSize(*reflowInput, state, aMetrics, &blockEndEdgeOfChildren); |
1397 | 0 |
|
1398 | 0 | // If the block direction is right-to-left, we need to update the bounds of |
1399 | 0 | // lines that were placed relative to mContainerSize during reflow, as |
1400 | 0 | // we typically do not know the true container size until we've reflowed all |
1401 | 0 | // its children. So we use a dummy mContainerSize during reflow (see |
1402 | 0 | // BlockReflowInput's constructor) and then fix up the positions of the |
1403 | 0 | // lines here, once the final block size is known. |
1404 | 0 | // |
1405 | 0 | // Note that writing-mode:vertical-rl is the only case where the block |
1406 | 0 | // logical direction progresses in a negative physical direction, and |
1407 | 0 | // therefore block-dir coordinate conversion depends on knowing the width |
1408 | 0 | // of the coordinate space in order to translate between the logical and |
1409 | 0 | // physical origins. |
1410 | 0 | if (wm.IsVerticalRL()) { |
1411 | 0 | nsSize containerSize = aMetrics.PhysicalSize(); |
1412 | 0 | nscoord deltaX = containerSize.width - state.ContainerSize().width; |
1413 | 0 | if (deltaX != 0) { |
1414 | 0 | for (LineIterator line = LinesBegin(), end = LinesEnd(); |
1415 | 0 | line != end; line++) { |
1416 | 0 | UpdateLineContainerSize(line, containerSize); |
1417 | 0 | } |
1418 | 0 | for (nsIFrame* f : mFloats) { |
1419 | 0 | nsPoint physicalDelta(deltaX, 0); |
1420 | 0 | f->MovePositionBy(physicalDelta); |
1421 | 0 | } |
1422 | 0 | nsFrameList* bulletList = GetOutsideBulletList(); |
1423 | 0 | if (bulletList) { |
1424 | 0 | nsPoint physicalDelta(deltaX, 0); |
1425 | 0 | for (nsIFrame* f : *bulletList) { |
1426 | 0 | f->MovePositionBy(physicalDelta); |
1427 | 0 | } |
1428 | 0 | } |
1429 | 0 | } |
1430 | 0 | } |
1431 | 0 |
|
1432 | 0 | nsRect areaBounds = nsRect(0, 0, aMetrics.Width(), aMetrics.Height()); |
1433 | 0 | ComputeOverflowAreas(areaBounds, reflowInput->mStyleDisplay, |
1434 | 0 | blockEndEdgeOfChildren, aMetrics.mOverflowAreas); |
1435 | 0 | // Factor overflow container child bounds into the overflow area |
1436 | 0 | aMetrics.mOverflowAreas.UnionWith(ocBounds); |
1437 | 0 | // Factor pushed float child bounds into the overflow area |
1438 | 0 | aMetrics.mOverflowAreas.UnionWith(fcBounds); |
1439 | 0 |
|
1440 | 0 | // Let the absolutely positioned container reflow any absolutely positioned |
1441 | 0 | // child frames that need to be reflowed, e.g., elements with a percentage |
1442 | 0 | // based width/height |
1443 | 0 | // We want to do this under either of two conditions: |
1444 | 0 | // 1. If we didn't do the incremental reflow above. |
1445 | 0 | // 2. If our size changed. |
1446 | 0 | // Even though it's the padding edge that's the containing block, we |
1447 | 0 | // can use our rect (the border edge) since if the border style |
1448 | 0 | // changed, the reflow would have been targeted at us so we'd satisfy |
1449 | 0 | // condition 1. |
1450 | 0 | // XXX checking oldSize is bogus, there are various reasons we might have |
1451 | 0 | // reflowed but our size might not have been changed to what we |
1452 | 0 | // asked for (e.g., we ended up being pushed to a new page) |
1453 | 0 | // When WillReflowAgainForClearance is true, we will reflow again without |
1454 | 0 | // resetting the size. Because of this, we must not reflow our abs-pos children |
1455 | 0 | // in that situation --- what we think is our "new size" |
1456 | 0 | // will not be our real new size. This also happens to be more efficient. |
1457 | 0 | WritingMode parentWM = aMetrics.GetWritingMode(); |
1458 | 0 | if (HasAbsolutelyPositionedChildren()) { |
1459 | 0 | nsAbsoluteContainingBlock* absoluteContainer = GetAbsoluteContainingBlock(); |
1460 | 0 | bool haveInterrupt = aPresContext->HasPendingInterrupt(); |
1461 | 0 | if (reflowInput->WillReflowAgainForClearance() || |
1462 | 0 | haveInterrupt) { |
1463 | 0 | // Make sure that when we reflow again we'll actually reflow all the abs |
1464 | 0 | // pos frames that might conceivably depend on our size (or all of them, |
1465 | 0 | // if we're dirty right now and interrupted; in that case we also need |
1466 | 0 | // to mark them all with NS_FRAME_IS_DIRTY). Sadly, we can't do much |
1467 | 0 | // better than that, because we don't really know what our size will be, |
1468 | 0 | // and it might in fact not change on the followup reflow! |
1469 | 0 | if (haveInterrupt && (GetStateBits() & NS_FRAME_IS_DIRTY)) { |
1470 | 0 | absoluteContainer->MarkAllFramesDirty(); |
1471 | 0 | } else { |
1472 | 0 | absoluteContainer->MarkSizeDependentFramesDirty(); |
1473 | 0 | } |
1474 | 0 | } else { |
1475 | 0 | LogicalSize containingBlockSize = |
1476 | 0 | CalculateContainingBlockSizeForAbsolutes(parentWM, *reflowInput, |
1477 | 0 | aMetrics.Size(parentWM)); |
1478 | 0 |
|
1479 | 0 | // Mark frames that depend on changes we just made to this frame as dirty: |
1480 | 0 | // Now we can assume that the padding edge hasn't moved. |
1481 | 0 | // We need to reflow the absolutes if one of them depends on |
1482 | 0 | // its placeholder position, or the containing block size in a |
1483 | 0 | // direction in which the containing block size might have |
1484 | 0 | // changed. |
1485 | 0 |
|
1486 | 0 | // XXX "width" and "height" in this block will become ISize and BSize |
1487 | 0 | // when nsAbsoluteContainingBlock is logicalized |
1488 | 0 | bool cbWidthChanged = aMetrics.Width() != oldSize.width; |
1489 | 0 | bool isRoot = !GetContent()->GetParent(); |
1490 | 0 | // If isRoot and we have auto height, then we are the initial |
1491 | 0 | // containing block and the containing block height is the |
1492 | 0 | // viewport height, which can't change during incremental |
1493 | 0 | // reflow. |
1494 | 0 | bool cbHeightChanged = |
1495 | 0 | !(isRoot && NS_UNCONSTRAINEDSIZE == reflowInput->ComputedHeight()) && |
1496 | 0 | aMetrics.Height() != oldSize.height; |
1497 | 0 |
|
1498 | 0 | nsRect containingBlock(nsPoint(0, 0), |
1499 | 0 | containingBlockSize.GetPhysicalSize(parentWM)); |
1500 | 0 | AbsPosReflowFlags flags = AbsPosReflowFlags::eConstrainHeight; |
1501 | 0 | if (cbWidthChanged) { |
1502 | 0 | flags |= AbsPosReflowFlags::eCBWidthChanged; |
1503 | 0 | } |
1504 | 0 | if (cbHeightChanged) { |
1505 | 0 | flags |= AbsPosReflowFlags::eCBHeightChanged; |
1506 | 0 | } |
1507 | 0 | // Setup the line cursor here to optimize line searching for |
1508 | 0 | // calculating hypothetical position of absolutely-positioned |
1509 | 0 | // frames. The line cursor is immediately cleared afterward to |
1510 | 0 | // avoid affecting the display list generation. |
1511 | 0 | AutoLineCursorSetup autoLineCursor(this); |
1512 | 0 | absoluteContainer->Reflow(this, aPresContext, *reflowInput, |
1513 | 0 | state.mReflowStatus, |
1514 | 0 | containingBlock, flags, |
1515 | 0 | &aMetrics.mOverflowAreas); |
1516 | 0 | } |
1517 | 0 | } |
1518 | 0 |
|
1519 | 0 | FinishAndStoreOverflow(&aMetrics, reflowInput->mStyleDisplay); |
1520 | 0 |
|
1521 | 0 | aStatus = state.mReflowStatus; |
1522 | 0 |
|
1523 | | #ifdef DEBUG |
1524 | | // Between when we drain pushed floats and when we complete reflow, |
1525 | | // we're allowed to have multiple continuations of the same float on |
1526 | | // our floats list, since a first-in-flow might get pushed to a later |
1527 | | // continuation of its containing block. But it's not permitted |
1528 | | // outside that time. |
1529 | | nsLayoutUtils::AssertNoDuplicateContinuations(this, mFloats); |
1530 | | |
1531 | | if (gNoisyReflow) { |
1532 | | IndentBy(stdout, gNoiseIndent); |
1533 | | ListTag(stdout); |
1534 | | printf(": status=%s metrics=%d,%d carriedMargin=%d", |
1535 | | ToString(aStatus).c_str(), |
1536 | | aMetrics.ISize(parentWM), aMetrics.BSize(parentWM), |
1537 | | aMetrics.mCarriedOutBEndMargin.get()); |
1538 | | if (HasOverflowAreas()) { |
1539 | | printf(" overflow-vis={%d,%d,%d,%d}", |
1540 | | aMetrics.VisualOverflow().x, |
1541 | | aMetrics.VisualOverflow().y, |
1542 | | aMetrics.VisualOverflow().width, |
1543 | | aMetrics.VisualOverflow().height); |
1544 | | printf(" overflow-scr={%d,%d,%d,%d}", |
1545 | | aMetrics.ScrollableOverflow().x, |
1546 | | aMetrics.ScrollableOverflow().y, |
1547 | | aMetrics.ScrollableOverflow().width, |
1548 | | aMetrics.ScrollableOverflow().height); |
1549 | | } |
1550 | | printf("\n"); |
1551 | | } |
1552 | | |
1553 | | if (gLameReflowMetrics) { |
1554 | | PRTime end = PR_Now(); |
1555 | | |
1556 | | int32_t ectc = nsLineBox::GetCtorCount(); |
1557 | | int32_t numLines = mLines.size(); |
1558 | | if (!numLines) numLines = 1; |
1559 | | PRTime delta, perLineDelta, lines; |
1560 | | lines = int64_t(numLines); |
1561 | | delta = end - start; |
1562 | | perLineDelta = delta / lines; |
1563 | | |
1564 | | ListTag(stdout); |
1565 | | char buf[400]; |
1566 | | SprintfLiteral(buf, |
1567 | | ": %" PRId64 " elapsed (%" PRId64 " per line) (%d lines; %d new lines)", |
1568 | | delta, perLineDelta, numLines, ectc - ctc); |
1569 | | printf("%s\n", buf); |
1570 | | } |
1571 | | #endif |
1572 | |
|
1573 | 0 | NS_FRAME_SET_TRUNCATION(aStatus, (*reflowInput), aMetrics); |
1574 | 0 | } |
1575 | | |
1576 | | bool |
1577 | | nsBlockFrame::CheckForCollapsedBEndMarginFromClearanceLine() |
1578 | 0 | { |
1579 | 0 | LineIterator begin = LinesBegin(); |
1580 | 0 | LineIterator line = LinesEnd(); |
1581 | 0 |
|
1582 | 0 | while (true) { |
1583 | 0 | if (begin == line) { |
1584 | 0 | return false; |
1585 | 0 | } |
1586 | 0 | --line; |
1587 | 0 | if (line->BSize() != 0 || !line->CachedIsEmpty()) { |
1588 | 0 | return false; |
1589 | 0 | } |
1590 | 0 | if (line->HasClearance()) { |
1591 | 0 | return true; |
1592 | 0 | } |
1593 | 0 | } |
1594 | 0 | // not reached |
1595 | 0 | } |
1596 | | |
1597 | | void |
1598 | | nsBlockFrame::ComputeFinalSize(const ReflowInput& aReflowInput, |
1599 | | BlockReflowInput& aState, |
1600 | | ReflowOutput& aMetrics, |
1601 | | nscoord* aBEndEdgeOfChildren) |
1602 | 0 | { |
1603 | 0 | WritingMode wm = aState.mReflowInput.GetWritingMode(); |
1604 | 0 | const LogicalMargin& borderPadding = aState.BorderPadding(); |
1605 | | #ifdef NOISY_FINAL_SIZE |
1606 | | ListTag(stdout); |
1607 | | printf(": mBCoord=%d mIsBEndMarginRoot=%s mPrevBEndMargin=%d bp=%d,%d\n", |
1608 | | aState.mBCoord, aState.mFlags.mIsBEndMarginRoot ? "yes" : "no", |
1609 | | aState.mPrevBEndMargin.get(), |
1610 | | borderPadding.BStart(wm), borderPadding.BEnd(wm)); |
1611 | | #endif |
1612 | |
|
1613 | 0 | // Compute final inline size |
1614 | 0 | LogicalSize finalSize(wm); |
1615 | 0 | finalSize.ISize(wm) = |
1616 | 0 | NSCoordSaturatingAdd(NSCoordSaturatingAdd(borderPadding.IStart(wm), |
1617 | 0 | aReflowInput.ComputedISize()), |
1618 | 0 | borderPadding.IEnd(wm)); |
1619 | 0 |
|
1620 | 0 | // Return block-end margin information |
1621 | 0 | // rbs says he hit this assertion occasionally (see bug 86947), so |
1622 | 0 | // just set the margin to zero and we'll figure out why later |
1623 | 0 | //NS_ASSERTION(aMetrics.mCarriedOutBEndMargin.IsZero(), |
1624 | 0 | // "someone else set the margin"); |
1625 | 0 | nscoord nonCarriedOutBDirMargin = 0; |
1626 | 0 | if (!aState.mFlags.mIsBEndMarginRoot) { |
1627 | 0 | // Apply rule from CSS 2.1 section 8.3.1. If we have some empty |
1628 | 0 | // line with clearance and a non-zero block-start margin and all |
1629 | 0 | // subsequent lines are empty, then we do not allow our children's |
1630 | 0 | // carried out block-end margin to be carried out of us and collapse |
1631 | 0 | // with our own block-end margin. |
1632 | 0 | if (CheckForCollapsedBEndMarginFromClearanceLine()) { |
1633 | 0 | // Convert the children's carried out margin to something that |
1634 | 0 | // we will include in our height |
1635 | 0 | nonCarriedOutBDirMargin = aState.mPrevBEndMargin.get(); |
1636 | 0 | aState.mPrevBEndMargin.Zero(); |
1637 | 0 | } |
1638 | 0 | aMetrics.mCarriedOutBEndMargin = aState.mPrevBEndMargin; |
1639 | 0 | } else { |
1640 | 0 | aMetrics.mCarriedOutBEndMargin.Zero(); |
1641 | 0 | } |
1642 | 0 |
|
1643 | 0 | nscoord blockEndEdgeOfChildren = aState.mBCoord + nonCarriedOutBDirMargin; |
1644 | 0 | // Shrink wrap our height around our contents. |
1645 | 0 | if (aState.mFlags.mIsBEndMarginRoot || |
1646 | 0 | NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize()) { |
1647 | 0 | // When we are a block-end-margin root make sure that our last |
1648 | 0 | // childs block-end margin is fully applied. We also do this when |
1649 | 0 | // we have a computed height, since in that case the carried out |
1650 | 0 | // margin is not going to be applied anywhere, so we should note it |
1651 | 0 | // here to be included in the overflow area. |
1652 | 0 | // Apply the margin only if there's space for it. |
1653 | 0 | if (blockEndEdgeOfChildren < aState.mReflowInput.AvailableBSize()) |
1654 | 0 | { |
1655 | 0 | // Truncate block-end margin if it doesn't fit to our available BSize. |
1656 | 0 | blockEndEdgeOfChildren = |
1657 | 0 | std::min(blockEndEdgeOfChildren + aState.mPrevBEndMargin.get(), |
1658 | 0 | aState.mReflowInput.AvailableBSize()); |
1659 | 0 | } |
1660 | 0 | } |
1661 | 0 | if (aState.mFlags.mBlockNeedsFloatManager) { |
1662 | 0 | // Include the float manager's state to properly account for the |
1663 | 0 | // block-end margin of any floated elements; e.g., inside a table cell. |
1664 | 0 | nscoord floatHeight = |
1665 | 0 | aState.ClearFloats(blockEndEdgeOfChildren, StyleClear::Both, |
1666 | 0 | nullptr, nsFloatManager::DONT_CLEAR_PUSHED_FLOATS); |
1667 | 0 | blockEndEdgeOfChildren = std::max(blockEndEdgeOfChildren, floatHeight); |
1668 | 0 | } |
1669 | 0 |
|
1670 | 0 | if (NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize() |
1671 | 0 | && (!GetParent()->IsColumnSetFrame() || |
1672 | 0 | aReflowInput.mParentReflowInput->AvailableBSize() == NS_UNCONSTRAINEDSIZE)) { |
1673 | 0 | ComputeFinalBSize(aReflowInput, &aState.mReflowStatus, |
1674 | 0 | aState.mBCoord + nonCarriedOutBDirMargin, |
1675 | 0 | borderPadding, finalSize, aState.mConsumedBSize); |
1676 | 0 | if (!aState.mReflowStatus.IsComplete()) { |
1677 | 0 | // Use the current height; continuations will take up the rest. |
1678 | 0 | // Do extend the height to at least consume the available |
1679 | 0 | // height, otherwise our left/right borders (for example) won't |
1680 | 0 | // extend all the way to the break. |
1681 | 0 | finalSize.BSize(wm) = std::max(aReflowInput.AvailableBSize(), |
1682 | 0 | aState.mBCoord + nonCarriedOutBDirMargin); |
1683 | 0 | // ... but don't take up more block size than is available |
1684 | 0 | nscoord effectiveComputedBSize = |
1685 | 0 | GetEffectiveComputedBSize(aReflowInput, aState.ConsumedBSize()); |
1686 | 0 | finalSize.BSize(wm) = |
1687 | 0 | std::min(finalSize.BSize(wm), |
1688 | 0 | borderPadding.BStart(wm) + effectiveComputedBSize); |
1689 | 0 | // XXX It's pretty wrong that our bottom border still gets drawn on |
1690 | 0 | // on its own on the last-in-flow, even if we ran out of height |
1691 | 0 | // here. We need GetSkipSides to check whether we ran out of content |
1692 | 0 | // height in the current frame, not whether it's last-in-flow. |
1693 | 0 | } |
1694 | 0 |
|
1695 | 0 | // Don't carry out a block-end margin when our BSize is fixed. |
1696 | 0 | aMetrics.mCarriedOutBEndMargin.Zero(); |
1697 | 0 | } |
1698 | 0 | else if (aReflowInput.mStyleDisplay->IsContainSize()) { |
1699 | 0 | // If we're size-containing and we don't have a specified size, then our |
1700 | 0 | // final size should actually be computed from only our border and padding, |
1701 | 0 | // as though we were empty. |
1702 | 0 | // Hence this case is a simplified version of the case below. |
1703 | 0 | nscoord contentBSize = 0; |
1704 | 0 | nscoord autoBSize = aReflowInput.ApplyMinMaxBSize(contentBSize); |
1705 | 0 | aMetrics.mCarriedOutBEndMargin.Zero(); |
1706 | 0 | autoBSize += borderPadding.BStartEnd(wm); |
1707 | 0 | finalSize.BSize(wm) = autoBSize; |
1708 | 0 | } |
1709 | 0 | else if (aState.mReflowStatus.IsComplete()) { |
1710 | 0 | nscoord contentBSize = blockEndEdgeOfChildren - borderPadding.BStart(wm); |
1711 | 0 | nscoord autoBSize = aReflowInput.ApplyMinMaxBSize(contentBSize); |
1712 | 0 | if (autoBSize != contentBSize) { |
1713 | 0 | // Our min- or max-bsize value made our bsize change. Don't carry out |
1714 | 0 | // our kids' block-end margins. |
1715 | 0 | aMetrics.mCarriedOutBEndMargin.Zero(); |
1716 | 0 | } |
1717 | 0 | autoBSize += borderPadding.BStart(wm) + borderPadding.BEnd(wm); |
1718 | 0 | finalSize.BSize(wm) = autoBSize; |
1719 | 0 | } |
1720 | 0 | else { |
1721 | 0 | NS_ASSERTION(aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE, |
1722 | 0 | "Shouldn't be incomplete if availableBSize is UNCONSTRAINED."); |
1723 | 0 | finalSize.BSize(wm) = std::max(aState.mBCoord, |
1724 | 0 | aReflowInput.AvailableBSize()); |
1725 | 0 | if (aReflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) { |
1726 | 0 | // This should never happen, but it does. See bug 414255 |
1727 | 0 | finalSize.BSize(wm) = aState.mBCoord; |
1728 | 0 | } |
1729 | 0 | } |
1730 | 0 |
|
1731 | 0 | if (IS_TRUE_OVERFLOW_CONTAINER(this)) { |
1732 | 0 | if (aState.mReflowStatus.IsIncomplete()) { |
1733 | 0 | // Overflow containers can only be overflow complete. |
1734 | 0 | // Note that auto height overflow containers have no normal children |
1735 | 0 | NS_ASSERTION(finalSize.BSize(wm) == 0, |
1736 | 0 | "overflow containers must be zero-block-size"); |
1737 | 0 | aState.mReflowStatus.SetOverflowIncomplete(); |
1738 | 0 | } |
1739 | 0 | } else if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE && |
1740 | 0 | !aState.mReflowStatus.IsInlineBreakBefore() && |
1741 | 0 | aState.mReflowStatus.IsComplete()) { |
1742 | 0 | // Currently only used for grid items, but could be used in other contexts. |
1743 | 0 | // The FragStretchBSizeProperty is our expected non-fragmented block-size |
1744 | 0 | // we should stretch to (for align-self:stretch etc). In some fragmentation |
1745 | 0 | // cases though, the last fragment (this frame since we're complete), needs |
1746 | 0 | // to have extra size applied because earlier fragments consumed too much of |
1747 | 0 | // our computed size due to overflowing their containing block. (E.g. this |
1748 | 0 | // ensures we fill the last row when a multi-row grid item is fragmented). |
1749 | 0 | bool found; |
1750 | 0 | nscoord bSize = GetProperty(FragStretchBSizeProperty(), &found); |
1751 | 0 | if (found) { |
1752 | 0 | finalSize.BSize(wm) = std::max(bSize, finalSize.BSize(wm)); |
1753 | 0 | } |
1754 | 0 | } |
1755 | 0 |
|
1756 | 0 | // Clamp the content size to fit within the margin-box clamp size, if any. |
1757 | 0 | if (MOZ_UNLIKELY(aReflowInput.mFlags.mBClampMarginBoxMinSize) && |
1758 | 0 | aState.mReflowStatus.IsComplete()) { |
1759 | 0 | bool found; |
1760 | 0 | nscoord cbSize = GetProperty(BClampMarginBoxMinSizeProperty(), &found); |
1761 | 0 | if (found) { |
1762 | 0 | auto marginBoxBSize = finalSize.BSize(wm) + |
1763 | 0 | aReflowInput.ComputedLogicalMargin().BStartEnd(wm); |
1764 | 0 | auto overflow = marginBoxBSize - cbSize; |
1765 | 0 | if (overflow > 0) { |
1766 | 0 | auto contentBSize = finalSize.BSize(wm) - borderPadding.BStartEnd(wm); |
1767 | 0 | auto newContentBSize = std::max(nscoord(0), contentBSize - overflow); |
1768 | 0 | // XXXmats deal with percentages better somehow? |
1769 | 0 | finalSize.BSize(wm) -= contentBSize - newContentBSize; |
1770 | 0 | } |
1771 | 0 | } |
1772 | 0 | } |
1773 | 0 |
|
1774 | 0 | // Screen out negative block sizes --- can happen due to integer overflows :-( |
1775 | 0 | finalSize.BSize(wm) = std::max(0, finalSize.BSize(wm)); |
1776 | 0 | *aBEndEdgeOfChildren = blockEndEdgeOfChildren; |
1777 | 0 |
|
1778 | 0 | if (blockEndEdgeOfChildren != finalSize.BSize(wm) - borderPadding.BEnd(wm)) { |
1779 | 0 | SetProperty(BlockEndEdgeOfChildrenProperty(), blockEndEdgeOfChildren); |
1780 | 0 | } else { |
1781 | 0 | DeleteProperty(BlockEndEdgeOfChildrenProperty()); |
1782 | 0 | } |
1783 | 0 |
|
1784 | 0 | aMetrics.SetSize(wm, finalSize); |
1785 | 0 |
|
1786 | | #ifdef DEBUG_blocks |
1787 | | if ((CRAZY_SIZE(aMetrics.Width()) || CRAZY_SIZE(aMetrics.Height())) && |
1788 | | !GetParent()->IsCrazySizeAssertSuppressed()) { |
1789 | | ListTag(stdout); |
1790 | | printf(": WARNING: desired:%d,%d\n", aMetrics.Width(), aMetrics.Height()); |
1791 | | } |
1792 | | #endif |
1793 | | } |
1794 | | |
1795 | | static void |
1796 | | ConsiderBlockEndEdgeOfChildren(const WritingMode aWritingMode, |
1797 | | nscoord aBEndEdgeOfChildren, |
1798 | | nsOverflowAreas& aOverflowAreas, |
1799 | | const nsStyleDisplay* aDisplay) |
1800 | 0 | { |
1801 | 0 | // Factor in the block-end edge of the children. Child frames will be added |
1802 | 0 | // to the overflow area as we iterate through the lines, but their margins |
1803 | 0 | // won't, so we need to account for block-end margins here. |
1804 | 0 | // REVIEW: For now, we do this for both visual and scrollable area, |
1805 | 0 | // although when we make scrollable overflow area not be a subset of |
1806 | 0 | // visual, we can change this. |
1807 | 0 | // XXX Currently, overflow areas are stored as physical rects, so we have |
1808 | 0 | // to handle writing modes explicitly here. If we change overflow rects |
1809 | 0 | // to be stored logically, this can be simplified again. |
1810 | 0 | if (aWritingMode.IsVertical()) { |
1811 | 0 | if (aWritingMode.IsVerticalLR()) { |
1812 | 0 | NS_FOR_FRAME_OVERFLOW_TYPES(otype) { |
1813 | 0 | if (!(aDisplay->IsContainLayout() && otype == eScrollableOverflow)) { |
1814 | 0 | // Layout containment should force all overflow to be ink (visual) |
1815 | 0 | // overflow, so if we're layout-contained, we only add our children's |
1816 | 0 | // block-end edge to the ink (visual) overflow -- not to the |
1817 | 0 | // scrollable overflow. |
1818 | 0 | nsRect& o = aOverflowAreas.Overflow(otype); |
1819 | 0 | o.width = std::max(o.XMost(), aBEndEdgeOfChildren) - o.x; |
1820 | 0 | } |
1821 | 0 | } |
1822 | 0 | } else { |
1823 | 0 | NS_FOR_FRAME_OVERFLOW_TYPES(otype) { |
1824 | 0 | if (!(aDisplay->IsContainLayout() && otype == eScrollableOverflow)) { |
1825 | 0 | nsRect& o = aOverflowAreas.Overflow(otype); |
1826 | 0 | nscoord xmost = o.XMost(); |
1827 | 0 | o.x = std::min(o.x, xmost - aBEndEdgeOfChildren); |
1828 | 0 | o.width = xmost - o.x; |
1829 | 0 | } |
1830 | 0 | } |
1831 | 0 | } |
1832 | 0 | } else { |
1833 | 0 | NS_FOR_FRAME_OVERFLOW_TYPES(otype) { |
1834 | 0 | if (!(aDisplay->IsContainLayout() && otype == eScrollableOverflow)) { |
1835 | 0 | nsRect& o = aOverflowAreas.Overflow(otype); |
1836 | 0 | o.height = std::max(o.YMost(), aBEndEdgeOfChildren) - o.y; |
1837 | 0 | } |
1838 | 0 | } |
1839 | 0 | } |
1840 | 0 | } |
1841 | | |
1842 | | void |
1843 | | nsBlockFrame::ComputeOverflowAreas(const nsRect& aBounds, |
1844 | | const nsStyleDisplay* aDisplay, |
1845 | | nscoord aBEndEdgeOfChildren, |
1846 | | nsOverflowAreas& aOverflowAreas) |
1847 | 0 | { |
1848 | 0 | // Compute the overflow areas of our children |
1849 | 0 | // XXX_perf: This can be done incrementally. It is currently one of |
1850 | 0 | // the things that makes incremental reflow O(N^2). |
1851 | 0 | nsOverflowAreas areas(aBounds, aBounds); |
1852 | 0 | if (!ShouldApplyOverflowClipping(this, aDisplay)) { |
1853 | 0 | for (LineIterator line = LinesBegin(), line_end = LinesEnd(); |
1854 | 0 | line != line_end; |
1855 | 0 | ++line) { |
1856 | 0 | if (aDisplay->IsContainLayout()) { |
1857 | 0 | // If we have layout containment, we should only consider our child's |
1858 | 0 | // visual overflow, leaving the scrollable regions of the parent |
1859 | 0 | // unaffected. |
1860 | 0 | // Note: scrollable overflow is a subset of visual overflow, |
1861 | 0 | // so this has the same affect as unioning the child's visual and |
1862 | 0 | // scrollable overflow with its parent's visual overflow. |
1863 | 0 | nsRect childVisualRect = line->GetVisualOverflowArea(); |
1864 | 0 | nsOverflowAreas childVisualArea = nsOverflowAreas( |
1865 | 0 | childVisualRect, |
1866 | 0 | nsRect()); |
1867 | 0 | areas.UnionWith(childVisualArea); |
1868 | 0 | } else { |
1869 | 0 | areas.UnionWith(line->GetOverflowAreas()); |
1870 | 0 | } |
1871 | 0 | } |
1872 | 0 |
|
1873 | 0 | // Factor an outside bullet in; normally the bullet will be factored into |
1874 | 0 | // the line-box's overflow areas. However, if the line is a block |
1875 | 0 | // line then it won't; if there are no lines, it won't. So just |
1876 | 0 | // factor it in anyway (it can't hurt if it was already done). |
1877 | 0 | // XXXldb Can we just fix GetOverflowArea instead? |
1878 | 0 | nsIFrame* outsideBullet = GetOutsideBullet(); |
1879 | 0 | if (outsideBullet) { |
1880 | 0 | areas.UnionAllWith(outsideBullet->GetRect()); |
1881 | 0 | } |
1882 | 0 |
|
1883 | 0 | ConsiderBlockEndEdgeOfChildren(GetWritingMode(), |
1884 | 0 | aBEndEdgeOfChildren, areas, aDisplay); |
1885 | 0 | } |
1886 | 0 |
|
1887 | | #ifdef NOISY_COMBINED_AREA |
1888 | | ListTag(stdout); |
1889 | | const nsRect& vis = areas.VisualOverflow(); |
1890 | | printf(": VisualOverflowArea CA=%d,%d,%d,%d\n", vis.x, vis.y, vis.width, vis.height); |
1891 | | const nsRect& scr = areas.ScrollableOverflow(); |
1892 | | printf(": ScrollableOverflowArea CA=%d,%d,%d,%d\n", scr.x, scr.y, scr.width, scr.height); |
1893 | | #endif |
1894 | |
|
1895 | 0 | aOverflowAreas = areas; |
1896 | 0 | } |
1897 | | |
1898 | | void |
1899 | | nsBlockFrame::UnionChildOverflow(nsOverflowAreas& aOverflowAreas) |
1900 | 0 | { |
1901 | 0 | // We need to update the overflow areas of lines manually, as they |
1902 | 0 | // get cached and re-used otherwise. Lines aren't exposed as normal |
1903 | 0 | // frame children, so calling UnionChildOverflow alone will end up |
1904 | 0 | // using the old cached values. |
1905 | 0 | for (LineIterator line = LinesBegin(), line_end = LinesEnd(); |
1906 | 0 | line != line_end; |
1907 | 0 | ++line) { |
1908 | 0 | nsRect bounds = line->GetPhysicalBounds(); |
1909 | 0 | nsOverflowAreas lineAreas(bounds, bounds); |
1910 | 0 |
|
1911 | 0 | int32_t n = line->GetChildCount(); |
1912 | 0 | for (nsIFrame* lineFrame = line->mFirstChild; |
1913 | 0 | n > 0; lineFrame = lineFrame->GetNextSibling(), --n) { |
1914 | 0 | ConsiderChildOverflow(lineAreas, lineFrame); |
1915 | 0 | } |
1916 | 0 |
|
1917 | 0 | // Consider the overflow areas of the floats attached to the line as well |
1918 | 0 | if (line->HasFloats()) { |
1919 | 0 | for (nsFloatCache* fc = line->GetFirstFloat(); fc; fc = fc->Next()) { |
1920 | 0 | ConsiderChildOverflow(lineAreas, fc->mFloat); |
1921 | 0 | } |
1922 | 0 | } |
1923 | 0 |
|
1924 | 0 | line->SetOverflowAreas(lineAreas); |
1925 | 0 | aOverflowAreas.UnionWith(lineAreas); |
1926 | 0 | } |
1927 | 0 |
|
1928 | 0 | // Union with child frames, skipping the principal and float lists |
1929 | 0 | // since we already handled those using the line boxes. |
1930 | 0 | nsLayoutUtils::UnionChildOverflow(this, aOverflowAreas, |
1931 | 0 | kPrincipalList | kFloatList); |
1932 | 0 | } |
1933 | | |
1934 | | bool |
1935 | | nsBlockFrame::ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) |
1936 | 0 | { |
1937 | 0 | bool found; |
1938 | 0 | nscoord blockEndEdgeOfChildren = |
1939 | 0 | GetProperty(BlockEndEdgeOfChildrenProperty(), &found); |
1940 | 0 | if (found) { |
1941 | 0 | ConsiderBlockEndEdgeOfChildren(GetWritingMode(), |
1942 | 0 | blockEndEdgeOfChildren, aOverflowAreas, |
1943 | 0 | StyleDisplay()); |
1944 | 0 | } |
1945 | 0 |
|
1946 | 0 | // Line cursor invariants depend on the overflow areas of the lines, so |
1947 | 0 | // we must clear the line cursor since those areas may have changed. |
1948 | 0 | ClearLineCursor(); |
1949 | 0 | return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas); |
1950 | 0 | } |
1951 | | |
1952 | | void |
1953 | | nsBlockFrame::LazyMarkLinesDirty() |
1954 | 0 | { |
1955 | 0 | if (GetStateBits() & NS_BLOCK_LOOK_FOR_DIRTY_FRAMES) { |
1956 | 0 | for (LineIterator line = LinesBegin(), line_end = LinesEnd(); |
1957 | 0 | line != line_end; ++line) { |
1958 | 0 | int32_t n = line->GetChildCount(); |
1959 | 0 | for (nsIFrame* lineFrame = line->mFirstChild; |
1960 | 0 | n > 0; lineFrame = lineFrame->GetNextSibling(), --n) { |
1961 | 0 | if (NS_SUBTREE_DIRTY(lineFrame)) { |
1962 | 0 | // NOTE: MarkLineDirty does more than just marking the line dirty. |
1963 | 0 | MarkLineDirty(line, &mLines); |
1964 | 0 | break; |
1965 | 0 | } |
1966 | 0 | } |
1967 | 0 | } |
1968 | 0 | RemoveStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES); |
1969 | 0 | } |
1970 | 0 | } |
1971 | | |
1972 | | void |
1973 | | nsBlockFrame::MarkLineDirty(LineIterator aLine, const nsLineList* aLineList) |
1974 | 0 | { |
1975 | 0 | // Mark aLine dirty |
1976 | 0 | aLine->MarkDirty(); |
1977 | 0 | aLine->SetInvalidateTextRuns(true); |
1978 | | #ifdef DEBUG |
1979 | | if (gNoisyReflow) { |
1980 | | IndentBy(stdout, gNoiseIndent); |
1981 | | ListTag(stdout); |
1982 | | printf(": mark line %p dirty\n", static_cast<void*>(aLine.get())); |
1983 | | } |
1984 | | #endif |
1985 | |
|
1986 | 0 | // Mark previous line dirty if it's an inline line so that it can |
1987 | 0 | // maybe pullup something from the line just affected. |
1988 | 0 | // XXX We don't need to do this if aPrevLine ends in a break-after... |
1989 | 0 | if (aLine != aLineList->front() && aLine->IsInline() && |
1990 | 0 | aLine.prev()->IsInline()) { |
1991 | 0 | aLine.prev()->MarkDirty(); |
1992 | 0 | aLine.prev()->SetInvalidateTextRuns(true); |
1993 | | #ifdef DEBUG |
1994 | | if (gNoisyReflow) { |
1995 | | IndentBy(stdout, gNoiseIndent); |
1996 | | ListTag(stdout); |
1997 | | printf(": mark prev-line %p dirty\n", |
1998 | | static_cast<void*>(aLine.prev().get())); |
1999 | | } |
2000 | | #endif |
2001 | | } |
2002 | 0 | } |
2003 | | |
2004 | | /** |
2005 | | * Test whether lines are certain to be aligned left so that we can make |
2006 | | * resizing optimizations |
2007 | | */ |
2008 | | static inline bool |
2009 | | IsAlignedLeft(uint8_t aAlignment, |
2010 | | uint8_t aDirection, |
2011 | | uint8_t aUnicodeBidi, |
2012 | | nsIFrame* aFrame) |
2013 | 0 | { |
2014 | 0 | return nsSVGUtils::IsInSVGTextSubtree(aFrame) || |
2015 | 0 | NS_STYLE_TEXT_ALIGN_LEFT == aAlignment || |
2016 | 0 | (((NS_STYLE_TEXT_ALIGN_START == aAlignment && |
2017 | 0 | NS_STYLE_DIRECTION_LTR == aDirection) || |
2018 | 0 | (NS_STYLE_TEXT_ALIGN_END == aAlignment && |
2019 | 0 | NS_STYLE_DIRECTION_RTL == aDirection)) && |
2020 | 0 | !(NS_STYLE_UNICODE_BIDI_PLAINTEXT & aUnicodeBidi)); |
2021 | 0 | } |
2022 | | |
2023 | | void |
2024 | | nsBlockFrame::PrepareResizeReflow(BlockReflowInput& aState) |
2025 | 0 | { |
2026 | 0 | // See if we can try and avoid marking all the lines as dirty |
2027 | 0 | bool tryAndSkipLines = |
2028 | 0 | // The left content-edge must be a constant distance from the left |
2029 | 0 | // border-edge. |
2030 | 0 | !StylePadding()->mPadding.GetLeft().HasPercent(); |
2031 | 0 |
|
2032 | | #ifdef DEBUG |
2033 | | if (gDisableResizeOpt) { |
2034 | | tryAndSkipLines = false; |
2035 | | } |
2036 | | if (gNoisyReflow) { |
2037 | | if (!tryAndSkipLines) { |
2038 | | IndentBy(stdout, gNoiseIndent); |
2039 | | ListTag(stdout); |
2040 | | printf(": marking all lines dirty: availISize=%d\n", |
2041 | | aState.mReflowInput.AvailableISize()); |
2042 | | } |
2043 | | } |
2044 | | #endif |
2045 | |
|
2046 | 0 | if (tryAndSkipLines) { |
2047 | 0 | WritingMode wm = aState.mReflowInput.GetWritingMode(); |
2048 | 0 | nscoord newAvailISize = |
2049 | 0 | aState.mReflowInput.ComputedLogicalBorderPadding().IStart(wm) + |
2050 | 0 | aState.mReflowInput.ComputedISize(); |
2051 | 0 |
|
2052 | | #ifdef DEBUG |
2053 | | if (gNoisyReflow) { |
2054 | | IndentBy(stdout, gNoiseIndent); |
2055 | | ListTag(stdout); |
2056 | | printf(": trying to avoid marking all lines dirty\n"); |
2057 | | } |
2058 | | #endif |
2059 | |
|
2060 | 0 | for (LineIterator line = LinesBegin(), line_end = LinesEnd(); |
2061 | 0 | line != line_end; |
2062 | 0 | ++line) |
2063 | 0 | { |
2064 | 0 | // We let child blocks make their own decisions the same |
2065 | 0 | // way we are here. |
2066 | 0 | bool isLastLine = line == mLines.back() && !GetNextInFlow(); |
2067 | 0 | if (line->IsBlock() || |
2068 | 0 | line->HasFloats() || |
2069 | 0 | (!isLastLine && !line->HasBreakAfter()) || |
2070 | 0 | ((isLastLine || !line->IsLineWrapped())) || |
2071 | 0 | line->ResizeReflowOptimizationDisabled() || |
2072 | 0 | line->IsImpactedByFloat() || |
2073 | 0 | (line->IEnd() > newAvailISize)) { |
2074 | 0 | line->MarkDirty(); |
2075 | 0 | } |
2076 | 0 |
|
2077 | | #ifdef REALLY_NOISY_REFLOW |
2078 | | if (!line->IsBlock()) { |
2079 | | printf("PrepareResizeReflow thinks line %p is %simpacted by floats\n", |
2080 | | line.get(), line->IsImpactedByFloat() ? "" : "not "); |
2081 | | } |
2082 | | #endif |
2083 | | #ifdef DEBUG |
2084 | | if (gNoisyReflow && !line->IsDirty()) { |
2085 | | IndentBy(stdout, gNoiseIndent + 1); |
2086 | | printf("skipped: line=%p next=%p %s %s%s%s breakTypeBefore/After=%s/%s xmost=%d\n", |
2087 | | static_cast<void*>(line.get()), |
2088 | | static_cast<void*>((line.next() != LinesEnd() ? line.next().get() : nullptr)), |
2089 | | line->IsBlock() ? "block" : "inline", |
2090 | | line->HasBreakAfter() ? "has-break-after " : "", |
2091 | | line->HasFloats() ? "has-floats " : "", |
2092 | | line->IsImpactedByFloat() ? "impacted " : "", |
2093 | | line->BreakTypeToString(line->GetBreakTypeBefore()), |
2094 | | line->BreakTypeToString(line->GetBreakTypeAfter()), |
2095 | | line->IEnd()); |
2096 | | } |
2097 | | #endif |
2098 | | } |
2099 | 0 | } |
2100 | 0 | else { |
2101 | 0 | // Mark everything dirty |
2102 | 0 | for (LineIterator line = LinesBegin(), line_end = LinesEnd(); |
2103 | 0 | line != line_end; |
2104 | 0 | ++line) |
2105 | 0 | { |
2106 | 0 | line->MarkDirty(); |
2107 | 0 | } |
2108 | 0 | } |
2109 | 0 | } |
2110 | | |
2111 | | //---------------------------------------- |
2112 | | |
2113 | | /** |
2114 | | * Propagate reflow "damage" from from earlier lines to the current |
2115 | | * line. The reflow damage comes from the following sources: |
2116 | | * 1. The regions of float damage remembered during reflow. |
2117 | | * 2. The combination of nonzero |aDeltaBCoord| and any impact by a |
2118 | | * float, either the previous reflow or now. |
2119 | | * |
2120 | | * When entering this function, |aLine| is still at its old position and |
2121 | | * |aDeltaBCoord| indicates how much it will later be slid (assuming it |
2122 | | * doesn't get marked dirty and reflowed entirely). |
2123 | | */ |
2124 | | void |
2125 | | nsBlockFrame::PropagateFloatDamage(BlockReflowInput& aState, |
2126 | | nsLineBox* aLine, |
2127 | | nscoord aDeltaBCoord) |
2128 | 0 | { |
2129 | 0 | nsFloatManager* floatManager = aState.FloatManager(); |
2130 | 0 | NS_ASSERTION((aState.mReflowInput.mParentReflowInput && |
2131 | 0 | aState.mReflowInput.mParentReflowInput->mFloatManager == floatManager) || |
2132 | 0 | aState.mReflowInput.mBlockDelta == 0, "Bad block delta passed in"); |
2133 | 0 |
|
2134 | 0 | // Check to see if there are any floats; if there aren't, there can't |
2135 | 0 | // be any float damage |
2136 | 0 | if (!floatManager->HasAnyFloats()) |
2137 | 0 | return; |
2138 | 0 | |
2139 | 0 | // Check the damage region recorded in the float damage. |
2140 | 0 | if (floatManager->HasFloatDamage()) { |
2141 | 0 | // Need to check mBounds *and* mCombinedArea to find intersections |
2142 | 0 | // with aLine's floats |
2143 | 0 | nscoord lineBCoordBefore = aLine->BStart() + aDeltaBCoord; |
2144 | 0 | nscoord lineBCoordAfter = lineBCoordBefore + aLine->BSize(); |
2145 | 0 | // Scrollable overflow should be sufficient for things that affect |
2146 | 0 | // layout. |
2147 | 0 | WritingMode wm = aState.mReflowInput.GetWritingMode(); |
2148 | 0 | nsSize containerSize = aState.ContainerSize(); |
2149 | 0 | LogicalRect overflow = aLine->GetOverflowArea(eScrollableOverflow, wm, |
2150 | 0 | containerSize); |
2151 | 0 | nscoord lineBCoordCombinedBefore = overflow.BStart(wm) + aDeltaBCoord; |
2152 | 0 | nscoord lineBCoordCombinedAfter = lineBCoordCombinedBefore + |
2153 | 0 | overflow.BSize(wm); |
2154 | 0 |
|
2155 | 0 | bool isDirty = floatManager->IntersectsDamage(lineBCoordBefore, |
2156 | 0 | lineBCoordAfter) || |
2157 | 0 | floatManager->IntersectsDamage(lineBCoordCombinedBefore, |
2158 | 0 | lineBCoordCombinedAfter); |
2159 | 0 | if (isDirty) { |
2160 | 0 | aLine->MarkDirty(); |
2161 | 0 | return; |
2162 | 0 | } |
2163 | 0 | } |
2164 | 0 | |
2165 | 0 | // Check if the line is moving relative to the float manager |
2166 | 0 | if (aDeltaBCoord + aState.mReflowInput.mBlockDelta != 0) { |
2167 | 0 | if (aLine->IsBlock()) { |
2168 | 0 | // Unconditionally reflow sliding blocks; we only really need to reflow |
2169 | 0 | // if there's a float impacting this block, but the current float manager |
2170 | 0 | // makes it difficult to check that. Therefore, we let the child block |
2171 | 0 | // decide what it needs to reflow. |
2172 | 0 | aLine->MarkDirty(); |
2173 | 0 | } else { |
2174 | 0 | bool wasImpactedByFloat = aLine->IsImpactedByFloat(); |
2175 | 0 | nsFlowAreaRect floatAvailableSpace = |
2176 | 0 | aState.GetFloatAvailableSpaceForBSize(aLine->BStart() + aDeltaBCoord, |
2177 | 0 | aLine->BSize(), |
2178 | 0 | nullptr); |
2179 | 0 |
|
2180 | | #ifdef REALLY_NOISY_REFLOW |
2181 | | printf("nsBlockFrame::PropagateFloatDamage %p was = %d, is=%d\n", |
2182 | | this, wasImpactedByFloat, floatAvailableSpace.HasFloats()); |
2183 | | #endif |
2184 | |
|
2185 | 0 | // Mark the line dirty if it was or is affected by a float |
2186 | 0 | // We actually only really need to reflow if the amount of impact |
2187 | 0 | // changes, but that's not straightforward to check |
2188 | 0 | if (wasImpactedByFloat || floatAvailableSpace.HasFloats()) { |
2189 | 0 | aLine->MarkDirty(); |
2190 | 0 | } |
2191 | 0 | } |
2192 | 0 | } |
2193 | 0 | } |
2194 | | |
2195 | 0 | static bool LineHasClear(nsLineBox* aLine) { |
2196 | 0 | return aLine->IsBlock() |
2197 | 0 | ? (aLine->GetBreakTypeBefore() != StyleClear::None || |
2198 | 0 | (aLine->mFirstChild->GetStateBits() & NS_BLOCK_HAS_CLEAR_CHILDREN) || |
2199 | 0 | !nsBlockFrame::BlockCanIntersectFloats(aLine->mFirstChild)) |
2200 | 0 | : aLine->HasFloatBreakAfter(); |
2201 | 0 | } |
2202 | | |
2203 | | |
2204 | | /** |
2205 | | * Reparent a whole list of floats from aOldParent to this block. The |
2206 | | * floats might be taken from aOldParent's overflow list. They will be |
2207 | | * removed from the list. They end up appended to our mFloats list. |
2208 | | */ |
2209 | | void |
2210 | | nsBlockFrame::ReparentFloats(nsIFrame* aFirstFrame, nsBlockFrame* aOldParent, |
2211 | | bool aReparentSiblings, |
2212 | | ReparentingDirection aDirection) |
2213 | 0 | { |
2214 | 0 | nsFrameList list; |
2215 | 0 | aOldParent->CollectFloats(aFirstFrame, list, aReparentSiblings); |
2216 | 0 | if (list.NotEmpty()) { |
2217 | 0 | for (nsIFrame* f : list) { |
2218 | 0 | MOZ_ASSERT(!(f->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT), |
2219 | 0 | "CollectFloats should've removed that bit"); |
2220 | 0 | ReparentFrame(f, aOldParent, this, aDirection); |
2221 | 0 | } |
2222 | 0 | mFloats.AppendFrames(nullptr, list); |
2223 | 0 | } |
2224 | 0 | } |
2225 | | |
2226 | | static void DumpLine(const BlockReflowInput& aState, nsLineBox* aLine, |
2227 | 0 | nscoord aDeltaBCoord, int32_t aDeltaIndent) { |
2228 | | #ifdef DEBUG |
2229 | | if (nsBlockFrame::gNoisyReflow) { |
2230 | | nsRect ovis(aLine->GetVisualOverflowArea()); |
2231 | | nsRect oscr(aLine->GetScrollableOverflowArea()); |
2232 | | nsBlockFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent + aDeltaIndent); |
2233 | | printf("line=%p mBCoord=%d dirty=%s oldBounds={%d,%d,%d,%d} oldoverflow-vis={%d,%d,%d,%d} oldoverflow-scr={%d,%d,%d,%d} deltaBCoord=%d mPrevBEndMargin=%d childCount=%d\n", |
2234 | | static_cast<void*>(aLine), aState.mBCoord, |
2235 | | aLine->IsDirty() ? "yes" : "no", |
2236 | | aLine->IStart(), aLine->BStart(), |
2237 | | aLine->ISize(), aLine->BSize(), |
2238 | | ovis.x, ovis.y, ovis.width, ovis.height, |
2239 | | oscr.x, oscr.y, oscr.width, oscr.height, |
2240 | | aDeltaBCoord, aState.mPrevBEndMargin.get(), aLine->GetChildCount()); |
2241 | | } |
2242 | | #endif |
2243 | | } |
2244 | | |
2245 | | void |
2246 | | nsBlockFrame::ReflowDirtyLines(BlockReflowInput& aState) |
2247 | 0 | { |
2248 | 0 | bool keepGoing = true; |
2249 | 0 | bool repositionViews = false; // should we really need this? |
2250 | 0 | bool foundAnyClears = aState.mFloatBreakType != StyleClear::None; |
2251 | 0 | bool willReflowAgain = false; |
2252 | 0 |
|
2253 | | #ifdef DEBUG |
2254 | | if (gNoisyReflow) { |
2255 | | IndentBy(stdout, gNoiseIndent); |
2256 | | ListTag(stdout); |
2257 | | printf(": reflowing dirty lines"); |
2258 | | printf(" computedISize=%d\n", aState.mReflowInput.ComputedISize()); |
2259 | | } |
2260 | | AutoNoisyIndenter indent(gNoisyReflow); |
2261 | | #endif |
2262 | |
|
2263 | 0 | bool selfDirty = (GetStateBits() & NS_FRAME_IS_DIRTY) || |
2264 | 0 | (aState.mReflowInput.IsBResize() && |
2265 | 0 | (GetStateBits() & NS_FRAME_CONTAINS_RELATIVE_BSIZE)); |
2266 | 0 |
|
2267 | 0 | // Reflow our last line if our availableBSize has increased |
2268 | 0 | // so that we (and our last child) pull up content as necessary |
2269 | 0 | if (aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE |
2270 | 0 | && GetNextInFlow() && aState.mReflowInput.AvailableBSize() > |
2271 | 0 | GetLogicalSize().BSize(aState.mReflowInput.GetWritingMode())) { |
2272 | 0 | LineIterator lastLine = LinesEnd(); |
2273 | 0 | if (lastLine != LinesBegin()) { |
2274 | 0 | --lastLine; |
2275 | 0 | lastLine->MarkDirty(); |
2276 | 0 | } |
2277 | 0 | } |
2278 | 0 | // the amount by which we will slide the current line if it is not |
2279 | 0 | // dirty |
2280 | 0 | nscoord deltaBCoord = 0; |
2281 | 0 |
|
2282 | 0 | // whether we did NOT reflow the previous line and thus we need to |
2283 | 0 | // recompute the carried out margin before the line if we want to |
2284 | 0 | // reflow it or if its previous margin is dirty |
2285 | 0 | bool needToRecoverState = false; |
2286 | 0 | // Float continuations were reflowed in ReflowPushedFloats |
2287 | 0 | bool reflowedFloat = mFloats.NotEmpty() && |
2288 | 0 | (mFloats.FirstChild()->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT); |
2289 | 0 | bool lastLineMovedUp = false; |
2290 | 0 | // We save up information about BR-clearance here |
2291 | 0 | StyleClear inlineFloatBreakType = aState.mFloatBreakType; |
2292 | 0 |
|
2293 | 0 | LineIterator line = LinesBegin(), line_end = LinesEnd(); |
2294 | 0 |
|
2295 | 0 | // Reflow the lines that are already ours |
2296 | 0 | for ( ; line != line_end; ++line, aState.AdvanceToNextLine()) { |
2297 | 0 | DumpLine(aState, line, deltaBCoord, 0); |
2298 | | #ifdef DEBUG |
2299 | | AutoNoisyIndenter indent2(gNoisyReflow); |
2300 | | #endif |
2301 | |
|
2302 | 0 | if (selfDirty) |
2303 | 0 | line->MarkDirty(); |
2304 | 0 |
|
2305 | 0 | // This really sucks, but we have to look inside any blocks that have clear |
2306 | 0 | // elements inside them. |
2307 | 0 | // XXX what can we do smarter here? |
2308 | 0 | if (!line->IsDirty() && line->IsBlock() && |
2309 | 0 | (line->mFirstChild->GetStateBits() & NS_BLOCK_HAS_CLEAR_CHILDREN)) { |
2310 | 0 | line->MarkDirty(); |
2311 | 0 | } |
2312 | 0 |
|
2313 | 0 | nsIFrame *replacedBlock = nullptr; |
2314 | 0 | if (line->IsBlock() && |
2315 | 0 | !nsBlockFrame::BlockCanIntersectFloats(line->mFirstChild)) { |
2316 | 0 | replacedBlock = line->mFirstChild; |
2317 | 0 | } |
2318 | 0 |
|
2319 | 0 | // We have to reflow the line if it's a block whose clearance |
2320 | 0 | // might have changed, so detect that. |
2321 | 0 | if (!line->IsDirty() && |
2322 | 0 | (line->GetBreakTypeBefore() != StyleClear::None || |
2323 | 0 | replacedBlock)) { |
2324 | 0 | nscoord curBCoord = aState.mBCoord; |
2325 | 0 | // See where we would be after applying any clearance due to |
2326 | 0 | // BRs. |
2327 | 0 | if (inlineFloatBreakType != StyleClear::None) { |
2328 | 0 | curBCoord = aState.ClearFloats(curBCoord, inlineFloatBreakType); |
2329 | 0 | } |
2330 | 0 |
|
2331 | 0 | nscoord newBCoord = |
2332 | 0 | aState.ClearFloats(curBCoord, line->GetBreakTypeBefore(), replacedBlock); |
2333 | 0 |
|
2334 | 0 | if (line->HasClearance()) { |
2335 | 0 | // Reflow the line if it might not have clearance anymore. |
2336 | 0 | if (newBCoord == curBCoord |
2337 | 0 | // aState.mBCoord is the clearance point which should be the |
2338 | 0 | // block-start border-edge of the block frame. If sliding the |
2339 | 0 | // block by deltaBCoord isn't going to put it in the predicted |
2340 | 0 | // position, then we'd better reflow the line. |
2341 | 0 | || newBCoord != line->BStart() + deltaBCoord) { |
2342 | 0 | line->MarkDirty(); |
2343 | 0 | } |
2344 | 0 | } else { |
2345 | 0 | // Reflow the line if the line might have clearance now. |
2346 | 0 | if (curBCoord != newBCoord) { |
2347 | 0 | line->MarkDirty(); |
2348 | 0 | } |
2349 | 0 | } |
2350 | 0 | } |
2351 | 0 |
|
2352 | 0 | // We might have to reflow a line that is after a clearing BR. |
2353 | 0 | if (inlineFloatBreakType != StyleClear::None) { |
2354 | 0 | aState.mBCoord = aState.ClearFloats(aState.mBCoord, inlineFloatBreakType); |
2355 | 0 | if (aState.mBCoord != line->BStart() + deltaBCoord) { |
2356 | 0 | // SlideLine is not going to put the line where the clearance |
2357 | 0 | // put it. Reflow the line to be sure. |
2358 | 0 | line->MarkDirty(); |
2359 | 0 | } |
2360 | 0 | inlineFloatBreakType = StyleClear::None; |
2361 | 0 | } |
2362 | 0 |
|
2363 | 0 | bool previousMarginWasDirty = line->IsPreviousMarginDirty(); |
2364 | 0 | if (previousMarginWasDirty) { |
2365 | 0 | // If the previous margin is dirty, reflow the current line |
2366 | 0 | line->MarkDirty(); |
2367 | 0 | line->ClearPreviousMarginDirty(); |
2368 | 0 | } else if (line->BEnd() + deltaBCoord > aState.mBEndEdge) { |
2369 | 0 | // Lines that aren't dirty but get slid past our height constraint must |
2370 | 0 | // be reflowed. |
2371 | 0 | line->MarkDirty(); |
2372 | 0 | } |
2373 | 0 |
|
2374 | 0 | // If we have a constrained height (i.e., breaking columns/pages), |
2375 | 0 | // and the distance to the bottom might have changed, then we need |
2376 | 0 | // to reflow any line that might have floats in it, both because the |
2377 | 0 | // breakpoints within those floats may have changed and because we |
2378 | 0 | // might have to push/pull the floats in their entirety. |
2379 | 0 | // FIXME: What about a deltaBCoord or block-size change that forces us to |
2380 | 0 | // push lines? Why does that work? |
2381 | 0 | if (!line->IsDirty() && |
2382 | 0 | aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE && |
2383 | 0 | (deltaBCoord != 0 || aState.mReflowInput.IsBResize() || |
2384 | 0 | aState.mReflowInput.mFlags.mMustReflowPlaceholders) && |
2385 | 0 | (line->IsBlock() || line->HasFloats() || line->HadFloatPushed())) { |
2386 | 0 | line->MarkDirty(); |
2387 | 0 | } |
2388 | 0 |
|
2389 | 0 | if (!line->IsDirty()) { |
2390 | 0 | // See if there's any reflow damage that requires that we mark the |
2391 | 0 | // line dirty. |
2392 | 0 | PropagateFloatDamage(aState, line, deltaBCoord); |
2393 | 0 | } |
2394 | 0 |
|
2395 | 0 | // If the container size has changed, reset mContainerSize. If the |
2396 | 0 | // line's writing mode is not ltr, or if the line is not left-aligned, also |
2397 | 0 | // mark the line dirty. |
2398 | 0 | if (aState.ContainerSize() != line->mContainerSize) { |
2399 | 0 | line->mContainerSize = aState.ContainerSize(); |
2400 | 0 |
|
2401 | 0 | bool isLastLine = line == mLines.back() && |
2402 | 0 | !GetNextInFlow() && |
2403 | 0 | NS_STYLE_TEXT_ALIGN_AUTO == StyleText()->mTextAlignLast; |
2404 | 0 | uint8_t align = isLastLine ? |
2405 | 0 | StyleText()->mTextAlign : StyleText()->mTextAlignLast; |
2406 | 0 |
|
2407 | 0 | if (line->mWritingMode.IsVertical() || |
2408 | 0 | !line->mWritingMode.IsBidiLTR() || |
2409 | 0 | !IsAlignedLeft(align, |
2410 | 0 | aState.mReflowInput.mStyleVisibility->mDirection, |
2411 | 0 | StyleTextReset()->mUnicodeBidi, this)) { |
2412 | 0 | line->MarkDirty(); |
2413 | 0 | } |
2414 | 0 | } |
2415 | 0 |
|
2416 | 0 | if (needToRecoverState && line->IsDirty()) { |
2417 | 0 | // We need to reconstruct the block-end margin only if we didn't |
2418 | 0 | // reflow the previous line and we do need to reflow (or repair |
2419 | 0 | // the block-start position of) the next line. |
2420 | 0 | aState.ReconstructMarginBefore(line); |
2421 | 0 | } |
2422 | 0 |
|
2423 | 0 | bool reflowedPrevLine = !needToRecoverState; |
2424 | 0 | if (needToRecoverState) { |
2425 | 0 | needToRecoverState = false; |
2426 | 0 |
|
2427 | 0 | // Update aState.mPrevChild as if we had reflowed all of the frames in |
2428 | 0 | // this line. |
2429 | 0 | if (line->IsDirty()) { |
2430 | 0 | NS_ASSERTION(line->mFirstChild->GetPrevSibling() == |
2431 | 0 | line.prev()->LastChild(), "unexpected line frames"); |
2432 | 0 | aState.mPrevChild = line->mFirstChild->GetPrevSibling(); |
2433 | 0 | } |
2434 | 0 | } |
2435 | 0 |
|
2436 | 0 | // Now repair the line and update |aState.mBCoord| by calling |
2437 | 0 | // |ReflowLine| or |SlideLine|. |
2438 | 0 | // If we're going to reflow everything again, then no need to reflow |
2439 | 0 | // the dirty line ... unless the line has floats, in which case we'd |
2440 | 0 | // better reflow it now to refresh its float cache, which may contain |
2441 | 0 | // dangling frame pointers! Ugh! This reflow of the line may be |
2442 | 0 | // incorrect because we skipped reflowing previous lines (e.g., floats |
2443 | 0 | // may be placed incorrectly), but that's OK because we'll mark the |
2444 | 0 | // line dirty below under "if (aState.mReflowInput.mDiscoveredClearance..." |
2445 | 0 | if (line->IsDirty() && (line->HasFloats() || !willReflowAgain)) { |
2446 | 0 | lastLineMovedUp = true; |
2447 | 0 |
|
2448 | 0 | bool maybeReflowingForFirstTime = |
2449 | 0 | line->IStart() == 0 && line->BStart() == 0 && |
2450 | 0 | line->ISize() == 0 && line->BSize() == 0; |
2451 | 0 |
|
2452 | 0 | // Compute the dirty lines "before" BEnd, after factoring in |
2453 | 0 | // the running deltaBCoord value - the running value is implicit in |
2454 | 0 | // aState.mBCoord. |
2455 | 0 | nscoord oldB = line->BStart(); |
2456 | 0 | nscoord oldBMost = line->BEnd(); |
2457 | 0 |
|
2458 | 0 | NS_ASSERTION(!willReflowAgain || !line->IsBlock(), |
2459 | 0 | "Don't reflow blocks while willReflowAgain is true, reflow of block abs-pos children depends on this"); |
2460 | 0 |
|
2461 | 0 | // Reflow the dirty line. If it's an incremental reflow, then force |
2462 | 0 | // it to invalidate the dirty area if necessary |
2463 | 0 | ReflowLine(aState, line, &keepGoing); |
2464 | 0 |
|
2465 | 0 | if (aState.mReflowInput.WillReflowAgainForClearance()) { |
2466 | 0 | line->MarkDirty(); |
2467 | 0 | willReflowAgain = true; |
2468 | 0 | // Note that once we've entered this state, every line that gets here |
2469 | 0 | // (e.g. because it has floats) gets marked dirty and reflowed again. |
2470 | 0 | // in the next pass. This is important, see above. |
2471 | 0 | } |
2472 | 0 |
|
2473 | 0 | if (line->HasFloats()) { |
2474 | 0 | reflowedFloat = true; |
2475 | 0 | } |
2476 | 0 |
|
2477 | 0 | if (!keepGoing) { |
2478 | 0 | DumpLine(aState, line, deltaBCoord, -1); |
2479 | 0 | if (0 == line->GetChildCount()) { |
2480 | 0 | DeleteLine(aState, line, line_end); |
2481 | 0 | } |
2482 | 0 | break; |
2483 | 0 | } |
2484 | 0 |
|
2485 | 0 | // Test to see whether the margin that should be carried out |
2486 | 0 | // to the next line (NL) might have changed. In ReflowBlockFrame |
2487 | 0 | // we call nextLine->MarkPreviousMarginDirty if the block's |
2488 | 0 | // actual carried-out block-end margin changed. So here we only |
2489 | 0 | // need to worry about the following effects: |
2490 | 0 | // 1) the line was just created, and it might now be blocking |
2491 | 0 | // a carried-out block-end margin from previous lines that |
2492 | 0 | // used to reach NL from reaching NL |
2493 | 0 | // 2) the line used to be empty, and is now not empty, |
2494 | 0 | // thus blocking a carried-out block-end margin from previous lines |
2495 | 0 | // that used to reach NL from reaching NL |
2496 | 0 | // 3) the line wasn't empty, but now is, so a carried-out |
2497 | 0 | // block-end margin from previous lines that didn't used to reach NL |
2498 | 0 | // now does |
2499 | 0 | // 4) the line might have changed in a way that affects NL's |
2500 | 0 | // ShouldApplyBStartMargin decision. The three things that matter |
2501 | 0 | // are the line's emptiness, its adjacency to the block-start edge of the |
2502 | 0 | // block, and whether it has clearance (the latter only matters if the |
2503 | 0 | // block was and is adjacent to the block-start and empty). |
2504 | 0 | // |
2505 | 0 | // If the line is empty now, we can't reliably tell if the line was empty |
2506 | 0 | // before, so we just assume it was and do nextLine->MarkPreviousMarginDirty. |
2507 | 0 | // This means the checks in 4) are redundant; if the line is empty now |
2508 | 0 | // we don't need to check 4), but if the line is not empty now and we're sure |
2509 | 0 | // it wasn't empty before, any adjacency and clearance changes are irrelevant |
2510 | 0 | // to the result of nextLine->ShouldApplyBStartMargin. |
2511 | 0 | if (line.next() != LinesEnd()) { |
2512 | 0 | bool maybeWasEmpty = oldB == line.next()->BStart(); |
2513 | 0 | bool isEmpty = line->CachedIsEmpty(); |
2514 | 0 | if (maybeReflowingForFirstTime /*1*/ || |
2515 | 0 | (isEmpty || maybeWasEmpty) /*2/3/4*/) { |
2516 | 0 | line.next()->MarkPreviousMarginDirty(); |
2517 | 0 | // since it's marked dirty, nobody will care about |deltaBCoord| |
2518 | 0 | } |
2519 | 0 | } |
2520 | 0 |
|
2521 | 0 | // If the line was just reflowed for the first time, then its |
2522 | 0 | // old mBounds cannot be trusted so this deltaBCoord computation is |
2523 | 0 | // bogus. But that's OK because we just did |
2524 | 0 | // MarkPreviousMarginDirty on the next line which will force it |
2525 | 0 | // to be reflowed, so this computation of deltaBCoord will not be |
2526 | 0 | // used. |
2527 | 0 | deltaBCoord = line->BEnd() - oldBMost; |
2528 | 0 |
|
2529 | 0 | // Now do an interrupt check. We want to do this only in the case when we |
2530 | 0 | // actually reflow the line, so that if we get back in here we'll get |
2531 | 0 | // further on the reflow before interrupting. |
2532 | 0 | aState.mPresContext->CheckForInterrupt(this); |
2533 | 0 | } else { |
2534 | 0 | aState.mOverflowTracker->Skip(line->mFirstChild, aState.mReflowStatus); |
2535 | 0 | // Nop except for blocks (we don't create overflow container |
2536 | 0 | // continuations for any inlines atm), so only checking mFirstChild |
2537 | 0 | // is enough |
2538 | 0 |
|
2539 | 0 | lastLineMovedUp = deltaBCoord < 0; |
2540 | 0 |
|
2541 | 0 | if (deltaBCoord != 0) |
2542 | 0 | SlideLine(aState, line, deltaBCoord); |
2543 | 0 | else |
2544 | 0 | repositionViews = true; |
2545 | 0 |
|
2546 | 0 | NS_ASSERTION(!line->IsDirty() || !line->HasFloats(), |
2547 | 0 | "Possibly stale float cache here!"); |
2548 | 0 | if (willReflowAgain && line->IsBlock()) { |
2549 | 0 | // If we're going to reflow everything again, and this line is a block, |
2550 | 0 | // then there is no need to recover float state. The line may contain |
2551 | 0 | // other lines with floats, but in that case RecoverStateFrom would only |
2552 | 0 | // add floats to the float manager. We don't need to do that because |
2553 | 0 | // everything's going to get reflowed again "for real". Calling |
2554 | 0 | // RecoverStateFrom in this situation could be lethal because the |
2555 | 0 | // block's descendant lines may have float caches containing dangling |
2556 | 0 | // frame pointers. Ugh! |
2557 | 0 | // If this line is inline, then we need to recover its state now |
2558 | 0 | // to make sure that we don't forget to move its floats by deltaBCoord. |
2559 | 0 | } else { |
2560 | 0 | // XXX EVIL O(N^2) EVIL |
2561 | 0 | aState.RecoverStateFrom(line, deltaBCoord); |
2562 | 0 | } |
2563 | 0 |
|
2564 | 0 | // Keep mBCoord up to date in case we're propagating reflow damage |
2565 | 0 | // and also because our final height may depend on it. If the |
2566 | 0 | // line is inlines, then only update mBCoord if the line is not |
2567 | 0 | // empty, because that's what PlaceLine does. (Empty blocks may |
2568 | 0 | // want to update mBCoord, e.g. if they have clearance.) |
2569 | 0 | if (line->IsBlock() || !line->CachedIsEmpty()) { |
2570 | 0 | aState.mBCoord = line->BEnd(); |
2571 | 0 | } |
2572 | 0 |
|
2573 | 0 | needToRecoverState = true; |
2574 | 0 |
|
2575 | 0 | if (reflowedPrevLine && !line->IsBlock() && |
2576 | 0 | aState.mPresContext->HasPendingInterrupt()) { |
2577 | 0 | // Need to make sure to pull overflows from any prev-in-flows |
2578 | 0 | for (nsIFrame* inlineKid = line->mFirstChild; inlineKid; |
2579 | 0 | inlineKid = inlineKid->PrincipalChildList().FirstChild()) { |
2580 | 0 | inlineKid->PullOverflowsFromPrevInFlow(); |
2581 | 0 | } |
2582 | 0 | } |
2583 | 0 | } |
2584 | 0 |
|
2585 | 0 | // Record if we need to clear floats before reflowing the next |
2586 | 0 | // line. Note that inlineFloatBreakType will be handled and |
2587 | 0 | // cleared before the next line is processed, so there is no |
2588 | 0 | // need to combine break types here. |
2589 | 0 | if (line->HasFloatBreakAfter()) { |
2590 | 0 | inlineFloatBreakType = line->GetBreakTypeAfter(); |
2591 | 0 | } |
2592 | 0 |
|
2593 | 0 | if (LineHasClear(line.get())) { |
2594 | 0 | foundAnyClears = true; |
2595 | 0 | } |
2596 | 0 |
|
2597 | 0 | DumpLine(aState, line, deltaBCoord, -1); |
2598 | 0 |
|
2599 | 0 | if (aState.mPresContext->HasPendingInterrupt()) { |
2600 | 0 | willReflowAgain = true; |
2601 | 0 | // Another option here might be to leave |line| clean if |
2602 | 0 | // !HasPendingInterrupt() before the CheckForInterrupt() call, since in |
2603 | 0 | // that case the line really did reflow as it should have. Not sure |
2604 | 0 | // whether that would be safe, so doing this for now instead. Also not |
2605 | 0 | // sure whether we really want to mark all lines dirty after an |
2606 | 0 | // interrupt, but until we get better at propagating float damage we |
2607 | 0 | // really do need to do it this way; see comments inside MarkLineDirty. |
2608 | 0 | MarkLineDirtyForInterrupt(line); |
2609 | 0 | } |
2610 | 0 | } |
2611 | 0 |
|
2612 | 0 | // Handle BR-clearance from the last line of the block |
2613 | 0 | if (inlineFloatBreakType != StyleClear::None) { |
2614 | 0 | aState.mBCoord = aState.ClearFloats(aState.mBCoord, inlineFloatBreakType); |
2615 | 0 | } |
2616 | 0 |
|
2617 | 0 | if (needToRecoverState) { |
2618 | 0 | // Is this expensive? |
2619 | 0 | aState.ReconstructMarginBefore(line); |
2620 | 0 |
|
2621 | 0 | // Update aState.mPrevChild as if we had reflowed all of the frames in |
2622 | 0 | // the last line. |
2623 | 0 | NS_ASSERTION(line == line_end || line->mFirstChild->GetPrevSibling() == |
2624 | 0 | line.prev()->LastChild(), "unexpected line frames"); |
2625 | 0 | aState.mPrevChild = |
2626 | 0 | line == line_end ? mFrames.LastChild() : line->mFirstChild->GetPrevSibling(); |
2627 | 0 | } |
2628 | 0 |
|
2629 | 0 | // Should we really have to do this? |
2630 | 0 | if (repositionViews) |
2631 | 0 | nsContainerFrame::PlaceFrameView(this); |
2632 | 0 |
|
2633 | 0 | // We can skip trying to pull up the next line if our height is constrained |
2634 | 0 | // (so we can report being incomplete) and there is no next in flow or we |
2635 | 0 | // were told not to or we know it will be futile, i.e., |
2636 | 0 | // -- the next in flow is not changing |
2637 | 0 | // -- and we cannot have added more space for its first line to be |
2638 | 0 | // pulled up into, |
2639 | 0 | // -- it's an incremental reflow of a descendant |
2640 | 0 | // -- and we didn't reflow any floats (so the available space |
2641 | 0 | // didn't change) |
2642 | 0 | // -- my chain of next-in-flows either has no first line, or its first |
2643 | 0 | // line isn't dirty. |
2644 | 0 | bool heightConstrained = |
2645 | 0 | aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE; |
2646 | 0 | bool skipPull = willReflowAgain && heightConstrained; |
2647 | 0 | if (!skipPull && heightConstrained && aState.mNextInFlow && |
2648 | 0 | (aState.mReflowInput.mFlags.mNextInFlowUntouched && |
2649 | 0 | !lastLineMovedUp && |
2650 | 0 | !(GetStateBits() & NS_FRAME_IS_DIRTY) && |
2651 | 0 | !reflowedFloat)) { |
2652 | 0 | // We'll place lineIter at the last line of this block, so that |
2653 | 0 | // nsBlockInFlowLineIterator::Next() will take us to the first |
2654 | 0 | // line of my next-in-flow-chain. (But first, check that I |
2655 | 0 | // have any lines -- if I don't, just bail out of this |
2656 | 0 | // optimization.) |
2657 | 0 | LineIterator lineIter = this->LinesEnd(); |
2658 | 0 | if (lineIter != this->LinesBegin()) { |
2659 | 0 | lineIter--; // I have lines; step back from dummy iterator to last line. |
2660 | 0 | nsBlockInFlowLineIterator bifLineIter(this, lineIter); |
2661 | 0 |
|
2662 | 0 | // Check for next-in-flow-chain's first line. |
2663 | 0 | // (First, see if there is such a line, and second, see if it's clean) |
2664 | 0 | if (!bifLineIter.Next() || |
2665 | 0 | !bifLineIter.GetLine()->IsDirty()) { |
2666 | 0 | skipPull=true; |
2667 | 0 | } |
2668 | 0 | } |
2669 | 0 | } |
2670 | 0 |
|
2671 | 0 | if (skipPull && aState.mNextInFlow) { |
2672 | 0 | NS_ASSERTION(heightConstrained, "Height should be constrained here\n"); |
2673 | 0 | if (IS_TRUE_OVERFLOW_CONTAINER(aState.mNextInFlow)) |
2674 | 0 | aState.mReflowStatus.SetOverflowIncomplete(); |
2675 | 0 | else |
2676 | 0 | aState.mReflowStatus.SetIncomplete(); |
2677 | 0 | } |
2678 | 0 |
|
2679 | 0 | if (!skipPull && aState.mNextInFlow) { |
2680 | 0 | // Pull data from a next-in-flow if there's still room for more |
2681 | 0 | // content here. |
2682 | 0 | while (keepGoing && aState.mNextInFlow) { |
2683 | 0 | // Grab first line from our next-in-flow |
2684 | 0 | nsBlockFrame* nextInFlow = aState.mNextInFlow; |
2685 | 0 | nsLineBox* pulledLine; |
2686 | 0 | nsFrameList pulledFrames; |
2687 | 0 | if (!nextInFlow->mLines.empty()) { |
2688 | 0 | RemoveFirstLine(nextInFlow->mLines, nextInFlow->mFrames, |
2689 | 0 | &pulledLine, &pulledFrames); |
2690 | 0 | } else { |
2691 | 0 | // Grab an overflow line if there are any |
2692 | 0 | FrameLines* overflowLines = nextInFlow->GetOverflowLines(); |
2693 | 0 | if (!overflowLines) { |
2694 | 0 | aState.mNextInFlow = |
2695 | 0 | static_cast<nsBlockFrame*>(nextInFlow->GetNextInFlow()); |
2696 | 0 | continue; |
2697 | 0 | } |
2698 | 0 | bool last = |
2699 | 0 | RemoveFirstLine(overflowLines->mLines, overflowLines->mFrames, |
2700 | 0 | &pulledLine, &pulledFrames); |
2701 | 0 | if (last) { |
2702 | 0 | nextInFlow->DestroyOverflowLines(); |
2703 | 0 | } |
2704 | 0 | } |
2705 | 0 |
|
2706 | 0 | if (pulledFrames.IsEmpty()) { |
2707 | 0 | // The line is empty. Try the next one. |
2708 | 0 | NS_ASSERTION(pulledLine->GetChildCount() == 0 && |
2709 | 0 | !pulledLine->mFirstChild, "bad empty line"); |
2710 | 0 | nextInFlow->FreeLineBox(pulledLine); |
2711 | 0 | continue; |
2712 | 0 | } |
2713 | 0 |
|
2714 | 0 | if (pulledLine == nextInFlow->GetLineCursor()) { |
2715 | 0 | nextInFlow->ClearLineCursor(); |
2716 | 0 | } |
2717 | 0 | ReparentFrames(pulledFrames, nextInFlow, this, |
2718 | 0 | ReparentingDirection::Backwards); |
2719 | 0 |
|
2720 | 0 | NS_ASSERTION(pulledFrames.LastChild() == pulledLine->LastChild(), |
2721 | 0 | "Unexpected last frame"); |
2722 | 0 | NS_ASSERTION(aState.mPrevChild || mLines.empty(), "should have a prevchild here"); |
2723 | 0 | NS_ASSERTION(aState.mPrevChild == mFrames.LastChild(), |
2724 | 0 | "Incorrect aState.mPrevChild before inserting line at end"); |
2725 | 0 |
|
2726 | 0 | // Shift pulledLine's frames into our mFrames list. |
2727 | 0 | mFrames.AppendFrames(nullptr, pulledFrames); |
2728 | 0 |
|
2729 | 0 | // Add line to our line list, and set its last child as our new prev-child |
2730 | 0 | line = mLines.before_insert(LinesEnd(), pulledLine); |
2731 | 0 | aState.mPrevChild = mFrames.LastChild(); |
2732 | 0 |
|
2733 | 0 | // Reparent floats whose placeholders are in the line. |
2734 | 0 | ReparentFloats(pulledLine->mFirstChild, nextInFlow, true, |
2735 | 0 | ReparentingDirection::Backwards); |
2736 | 0 |
|
2737 | 0 | DumpLine(aState, pulledLine, deltaBCoord, 0); |
2738 | | #ifdef DEBUG |
2739 | | AutoNoisyIndenter indent2(gNoisyReflow); |
2740 | | #endif |
2741 | |
|
2742 | 0 | if (aState.mPresContext->HasPendingInterrupt()) { |
2743 | 0 | MarkLineDirtyForInterrupt(line); |
2744 | 0 | } else { |
2745 | 0 | // Now reflow it and any lines that it makes during it's reflow |
2746 | 0 | // (we have to loop here because reflowing the line may cause a new |
2747 | 0 | // line to be created; see SplitLine's callers for examples of |
2748 | 0 | // when this happens). |
2749 | 0 | while (line != LinesEnd()) { |
2750 | 0 | ReflowLine(aState, line, &keepGoing); |
2751 | 0 |
|
2752 | 0 | if (aState.mReflowInput.WillReflowAgainForClearance()) { |
2753 | 0 | line->MarkDirty(); |
2754 | 0 | keepGoing = false; |
2755 | 0 | aState.mReflowStatus.SetIncomplete(); |
2756 | 0 | break; |
2757 | 0 | } |
2758 | 0 | |
2759 | 0 | DumpLine(aState, line, deltaBCoord, -1); |
2760 | 0 | if (!keepGoing) { |
2761 | 0 | if (0 == line->GetChildCount()) { |
2762 | 0 | DeleteLine(aState, line, line_end); |
2763 | 0 | } |
2764 | 0 | break; |
2765 | 0 | } |
2766 | 0 |
|
2767 | 0 | if (LineHasClear(line.get())) { |
2768 | 0 | foundAnyClears = true; |
2769 | 0 | } |
2770 | 0 |
|
2771 | 0 | if (aState.mPresContext->CheckForInterrupt(this)) { |
2772 | 0 | MarkLineDirtyForInterrupt(line); |
2773 | 0 | break; |
2774 | 0 | } |
2775 | 0 | |
2776 | 0 | // If this is an inline frame then its time to stop |
2777 | 0 | ++line; |
2778 | 0 | aState.AdvanceToNextLine(); |
2779 | 0 | } |
2780 | 0 | } |
2781 | 0 | } |
2782 | 0 |
|
2783 | 0 | if (aState.mReflowStatus.IsIncomplete()) { |
2784 | 0 | aState.mReflowStatus.SetNextInFlowNeedsReflow(); |
2785 | 0 | } //XXXfr shouldn't set this flag when nextinflow has no lines |
2786 | 0 | } |
2787 | 0 |
|
2788 | 0 | // Handle an odd-ball case: a list-item with no lines |
2789 | 0 | if (HasOutsideBullet() && mLines.empty()) { |
2790 | 0 | ReflowOutput metrics(aState.mReflowInput); |
2791 | 0 | nsIFrame* bullet = GetOutsideBullet(); |
2792 | 0 | WritingMode wm = aState.mReflowInput.GetWritingMode(); |
2793 | 0 | ReflowBullet(bullet, aState, metrics, |
2794 | 0 | aState.mReflowInput.ComputedPhysicalBorderPadding().top); |
2795 | 0 | NS_ASSERTION(!BulletIsEmpty() || metrics.BSize(wm) == 0, |
2796 | 0 | "empty bullet took up space"); |
2797 | 0 |
|
2798 | 0 | if (!BulletIsEmpty()) { |
2799 | 0 | // There are no lines so we have to fake up some y motion so that |
2800 | 0 | // we end up with *some* height. |
2801 | 0 |
|
2802 | 0 | if (metrics.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE) { |
2803 | 0 | nscoord ascent; |
2804 | 0 | WritingMode wm = aState.mReflowInput.GetWritingMode(); |
2805 | 0 | if (nsLayoutUtils::GetFirstLineBaseline(wm, bullet, &ascent)) { |
2806 | 0 | metrics.SetBlockStartAscent(ascent); |
2807 | 0 | } else { |
2808 | 0 | metrics.SetBlockStartAscent(metrics.BSize(wm)); |
2809 | 0 | } |
2810 | 0 | } |
2811 | 0 |
|
2812 | 0 | RefPtr<nsFontMetrics> fm = |
2813 | 0 | nsLayoutUtils::GetInflatedFontMetricsForFrame(this); |
2814 | 0 |
|
2815 | 0 | nscoord minAscent = |
2816 | 0 | nsLayoutUtils::GetCenteredFontBaseline(fm, aState.mMinLineHeight, |
2817 | 0 | wm.IsLineInverted()); |
2818 | 0 | nscoord minDescent = aState.mMinLineHeight - minAscent; |
2819 | 0 |
|
2820 | 0 | aState.mBCoord += std::max(minAscent, metrics.BlockStartAscent()) + |
2821 | 0 | std::max(minDescent, metrics.BSize(wm) - |
2822 | 0 | metrics.BlockStartAscent()); |
2823 | 0 |
|
2824 | 0 | nscoord offset = minAscent - metrics.BlockStartAscent(); |
2825 | 0 | if (offset > 0) { |
2826 | 0 | bullet->SetRect(bullet->GetRect() + nsPoint(0, offset)); |
2827 | 0 | } |
2828 | 0 | } |
2829 | 0 | } |
2830 | 0 |
|
2831 | 0 | if (foundAnyClears) { |
2832 | 0 | AddStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN); |
2833 | 0 | } else { |
2834 | 0 | RemoveStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN); |
2835 | 0 | } |
2836 | 0 |
|
2837 | | #ifdef DEBUG |
2838 | | VerifyLines(true); |
2839 | | VerifyOverflowSituation(); |
2840 | | if (gNoisyReflow) { |
2841 | | IndentBy(stdout, gNoiseIndent - 1); |
2842 | | ListTag(stdout); |
2843 | | printf(": done reflowing dirty lines (status=%s)\n", |
2844 | | ToString(aState.mReflowStatus).c_str()); |
2845 | | } |
2846 | | #endif |
2847 | | } |
2848 | | |
2849 | | void |
2850 | | nsBlockFrame::MarkLineDirtyForInterrupt(nsLineBox* aLine) |
2851 | 0 | { |
2852 | 0 | aLine->MarkDirty(); |
2853 | 0 |
|
2854 | 0 | // Just checking NS_FRAME_IS_DIRTY is ok, because we've already |
2855 | 0 | // marked the lines that need to be marked dirty based on our |
2856 | 0 | // vertical resize stuff. So we'll definitely reflow all those kids; |
2857 | 0 | // the only question is how they should behave. |
2858 | 0 | if (GetStateBits() & NS_FRAME_IS_DIRTY) { |
2859 | 0 | // Mark all our child frames dirty so we make sure to reflow them |
2860 | 0 | // later. |
2861 | 0 | int32_t n = aLine->GetChildCount(); |
2862 | 0 | for (nsIFrame* f = aLine->mFirstChild; n > 0; |
2863 | 0 | f = f->GetNextSibling(), --n) { |
2864 | 0 | f->AddStateBits(NS_FRAME_IS_DIRTY); |
2865 | 0 | } |
2866 | 0 | // And mark all the floats whose reflows we might be skipping dirty too. |
2867 | 0 | if (aLine->HasFloats()) { |
2868 | 0 | for (nsFloatCache* fc = aLine->GetFirstFloat(); fc; fc = fc->Next()) { |
2869 | 0 | fc->mFloat->AddStateBits(NS_FRAME_IS_DIRTY); |
2870 | 0 | } |
2871 | 0 | } |
2872 | 0 | } else { |
2873 | 0 | // Dirty all the descendant lines of block kids to handle float damage, |
2874 | 0 | // since our nsFloatManager will go away by the next time we're reflowing. |
2875 | 0 | // XXXbz Can we do something more like what PropagateFloatDamage does? |
2876 | 0 | // Would need to sort out the exact business with mBlockDelta for that.... |
2877 | 0 | // This marks way too much dirty. If we ever make this better, revisit |
2878 | 0 | // which lines we mark dirty in the interrupt case in ReflowDirtyLines. |
2879 | 0 | nsBlockFrame* bf = nsLayoutUtils::GetAsBlock(aLine->mFirstChild); |
2880 | 0 | if (bf) { |
2881 | 0 | MarkAllDescendantLinesDirty(bf); |
2882 | 0 | } |
2883 | 0 | } |
2884 | 0 | } |
2885 | | |
2886 | | void |
2887 | | nsBlockFrame::DeleteLine(BlockReflowInput& aState, |
2888 | | nsLineList::iterator aLine, |
2889 | | nsLineList::iterator aLineEnd) |
2890 | 0 | { |
2891 | 0 | MOZ_ASSERT(0 == aLine->GetChildCount(), "can't delete !empty line"); |
2892 | 0 | if (0 == aLine->GetChildCount()) { |
2893 | 0 | NS_ASSERTION(aState.mCurrentLine == aLine, |
2894 | 0 | "using function more generally than designed, " |
2895 | 0 | "but perhaps OK now"); |
2896 | 0 | nsLineBox* line = aLine; |
2897 | 0 | aLine = mLines.erase(aLine); |
2898 | 0 | FreeLineBox(line); |
2899 | 0 | // Mark the previous margin of the next line dirty since we need to |
2900 | 0 | // recompute its top position. |
2901 | 0 | if (aLine != aLineEnd) |
2902 | 0 | aLine->MarkPreviousMarginDirty(); |
2903 | 0 | } |
2904 | 0 | } |
2905 | | |
2906 | | /** |
2907 | | * Reflow a line. The line will either contain a single block frame |
2908 | | * or contain 1 or more inline frames. aKeepReflowGoing indicates |
2909 | | * whether or not the caller should continue to reflow more lines. |
2910 | | */ |
2911 | | void |
2912 | | nsBlockFrame::ReflowLine(BlockReflowInput& aState, |
2913 | | LineIterator aLine, |
2914 | | bool* aKeepReflowGoing) |
2915 | 0 | { |
2916 | 0 | MOZ_ASSERT(aLine->GetChildCount(), "reflowing empty line"); |
2917 | 0 |
|
2918 | 0 | // Setup the line-layout for the new line |
2919 | 0 | aState.mCurrentLine = aLine; |
2920 | 0 | aLine->ClearDirty(); |
2921 | 0 | aLine->InvalidateCachedIsEmpty(); |
2922 | 0 | aLine->ClearHadFloatPushed(); |
2923 | 0 |
|
2924 | 0 | // Now that we know what kind of line we have, reflow it |
2925 | 0 | if (aLine->IsBlock()) { |
2926 | 0 | ReflowBlockFrame(aState, aLine, aKeepReflowGoing); |
2927 | 0 | } else { |
2928 | 0 | aLine->SetLineWrapped(false); |
2929 | 0 | ReflowInlineFrames(aState, aLine, aKeepReflowGoing); |
2930 | 0 |
|
2931 | 0 | // Store the line's float edges for text-overflow analysis if needed. |
2932 | 0 | aLine->ClearFloatEdges(); |
2933 | 0 | if (aState.mFlags.mCanHaveTextOverflow) { |
2934 | 0 | WritingMode wm = aLine->mWritingMode; |
2935 | 0 | nsFlowAreaRect r = aState.GetFloatAvailableSpaceForBSize(aLine->BStart(), |
2936 | 0 | aLine->BSize(), |
2937 | 0 | nullptr); |
2938 | 0 | if (r.HasFloats()) { |
2939 | 0 | LogicalRect so = |
2940 | 0 | aLine->GetOverflowArea(eScrollableOverflow, wm, aLine->mContainerSize); |
2941 | 0 | nscoord s = r.mRect.IStart(wm); |
2942 | 0 | nscoord e = r.mRect.IEnd(wm); |
2943 | 0 | if (so.IEnd(wm) > e || so.IStart(wm) < s) { |
2944 | 0 | // This line is overlapping a float - store the edges marking the area |
2945 | 0 | // between the floats for text-overflow analysis. |
2946 | 0 | aLine->SetFloatEdges(s, e); |
2947 | 0 | } |
2948 | 0 | } |
2949 | 0 | } |
2950 | 0 | } |
2951 | 0 | } |
2952 | | |
2953 | | nsIFrame* |
2954 | | nsBlockFrame::PullFrame(BlockReflowInput& aState, |
2955 | | LineIterator aLine) |
2956 | 0 | { |
2957 | 0 | // First check our remaining lines. |
2958 | 0 | if (LinesEnd() != aLine.next()) { |
2959 | 0 | return PullFrameFrom(aLine, this, aLine.next()); |
2960 | 0 | } |
2961 | 0 | |
2962 | 0 | NS_ASSERTION(!GetOverflowLines(), |
2963 | 0 | "Our overflow lines should have been removed at the start of reflow"); |
2964 | 0 |
|
2965 | 0 | // Try each next-in-flow. |
2966 | 0 | nsBlockFrame* nextInFlow = aState.mNextInFlow; |
2967 | 0 | while (nextInFlow) { |
2968 | 0 | if (nextInFlow->mLines.empty()) { |
2969 | 0 | nextInFlow->DrainSelfOverflowList(); |
2970 | 0 | } |
2971 | 0 | if (!nextInFlow->mLines.empty()) { |
2972 | 0 | return PullFrameFrom(aLine, nextInFlow, nextInFlow->mLines.begin()); |
2973 | 0 | } |
2974 | 0 | nextInFlow = static_cast<nsBlockFrame*>(nextInFlow->GetNextInFlow()); |
2975 | 0 | aState.mNextInFlow = nextInFlow; |
2976 | 0 | } |
2977 | 0 |
|
2978 | 0 | return nullptr; |
2979 | 0 | } |
2980 | | |
2981 | | nsIFrame* |
2982 | | nsBlockFrame::PullFrameFrom(nsLineBox* aLine, |
2983 | | nsBlockFrame* aFromContainer, |
2984 | | nsLineList::iterator aFromLine) |
2985 | 0 | { |
2986 | 0 | nsLineBox* fromLine = aFromLine; |
2987 | 0 | MOZ_ASSERT(fromLine, "bad line to pull from"); |
2988 | 0 | MOZ_ASSERT(fromLine->GetChildCount(), "empty line"); |
2989 | 0 | MOZ_ASSERT(aLine->GetChildCount(), "empty line"); |
2990 | 0 |
|
2991 | 0 | NS_ASSERTION(fromLine->IsBlock() == fromLine->mFirstChild->IsBlockOutside(), |
2992 | 0 | "Disagreement about whether it's a block or not"); |
2993 | 0 |
|
2994 | 0 | if (fromLine->IsBlock()) { |
2995 | 0 | // If our line is not empty and the child in aFromLine is a block |
2996 | 0 | // then we cannot pull up the frame into this line. In this case |
2997 | 0 | // we stop pulling. |
2998 | 0 | return nullptr; |
2999 | 0 | } |
3000 | 0 | // Take frame from fromLine |
3001 | 0 | nsIFrame* frame = fromLine->mFirstChild; |
3002 | 0 | nsIFrame* newFirstChild = frame->GetNextSibling(); |
3003 | 0 |
|
3004 | 0 | if (aFromContainer != this) { |
3005 | 0 | // The frame is being pulled from a next-in-flow; therefore we |
3006 | 0 | // need to add it to our sibling list. |
3007 | 0 | MOZ_ASSERT(aLine == mLines.back()); |
3008 | 0 | MOZ_ASSERT(aFromLine == aFromContainer->mLines.begin(), |
3009 | 0 | "should only pull from first line"); |
3010 | 0 | aFromContainer->mFrames.RemoveFrame(frame); |
3011 | 0 |
|
3012 | 0 | // When pushing and pulling frames we need to check for whether any |
3013 | 0 | // views need to be reparented. |
3014 | 0 | ReparentFrame(frame, aFromContainer, this, ReparentingDirection::Backwards); |
3015 | 0 | mFrames.AppendFrame(nullptr, frame); |
3016 | 0 |
|
3017 | 0 | // The frame might have (or contain) floats that need to be brought |
3018 | 0 | // over too. (pass 'false' since there are no siblings to check) |
3019 | 0 | ReparentFloats(frame, aFromContainer, false, |
3020 | 0 | ReparentingDirection::Backwards); |
3021 | 0 | } else { |
3022 | 0 | MOZ_ASSERT(aLine == aFromLine.prev()); |
3023 | 0 | } |
3024 | 0 |
|
3025 | 0 | aLine->NoteFrameAdded(frame); |
3026 | 0 | fromLine->NoteFrameRemoved(frame); |
3027 | 0 |
|
3028 | 0 | if (fromLine->GetChildCount() > 0) { |
3029 | 0 | // Mark line dirty now that we pulled a child |
3030 | 0 | fromLine->MarkDirty(); |
3031 | 0 | fromLine->mFirstChild = newFirstChild; |
3032 | 0 | } else { |
3033 | 0 | // Free up the fromLine now that it's empty. |
3034 | 0 | // Its bounds might need to be redrawn, though. |
3035 | 0 | if (aFromLine.next() != aFromContainer->mLines.end()) { |
3036 | 0 | aFromLine.next()->MarkPreviousMarginDirty(); |
3037 | 0 | } |
3038 | 0 | aFromContainer->mLines.erase(aFromLine); |
3039 | 0 | // aFromLine is now invalid |
3040 | 0 | aFromContainer->FreeLineBox(fromLine); |
3041 | 0 | } |
3042 | 0 |
|
3043 | | #ifdef DEBUG |
3044 | | VerifyLines(true); |
3045 | | VerifyOverflowSituation(); |
3046 | | #endif |
3047 | |
|
3048 | 0 | return frame; |
3049 | 0 | } |
3050 | | |
3051 | | void |
3052 | | nsBlockFrame::SlideLine(BlockReflowInput& aState, |
3053 | | nsLineBox* aLine, nscoord aDeltaBCoord) |
3054 | 0 | { |
3055 | 0 | MOZ_ASSERT(aDeltaBCoord != 0, "why slide a line nowhere?"); |
3056 | 0 |
|
3057 | 0 | // Adjust line state |
3058 | 0 | aLine->SlideBy(aDeltaBCoord, aState.ContainerSize()); |
3059 | 0 |
|
3060 | 0 | // Adjust the frames in the line |
3061 | 0 | MoveChildFramesOfLine(aLine, aDeltaBCoord); |
3062 | 0 | } |
3063 | | |
3064 | | void |
3065 | | nsBlockFrame::UpdateLineContainerSize(nsLineBox* aLine, |
3066 | | const nsSize& aNewContainerSize) |
3067 | 0 | { |
3068 | 0 | if (aNewContainerSize == aLine->mContainerSize) { |
3069 | 0 | return; |
3070 | 0 | } |
3071 | 0 | |
3072 | 0 | // Adjust line state |
3073 | 0 | nsSize sizeDelta = aLine->UpdateContainerSize(aNewContainerSize); |
3074 | 0 |
|
3075 | 0 | // Changing container width only matters if writing mode is vertical-rl |
3076 | 0 | if (GetWritingMode().IsVerticalRL()) { |
3077 | 0 | MoveChildFramesOfLine(aLine, sizeDelta.width); |
3078 | 0 | } |
3079 | 0 | } |
3080 | | |
3081 | | void |
3082 | | nsBlockFrame::MoveChildFramesOfLine(nsLineBox* aLine, nscoord aDeltaBCoord) |
3083 | 0 | { |
3084 | 0 | // Adjust the frames in the line |
3085 | 0 | nsIFrame* kid = aLine->mFirstChild; |
3086 | 0 | if (!kid) { |
3087 | 0 | return; |
3088 | 0 | } |
3089 | 0 | |
3090 | 0 | WritingMode wm = GetWritingMode(); |
3091 | 0 | LogicalPoint translation(wm, 0, aDeltaBCoord); |
3092 | 0 |
|
3093 | 0 | if (aLine->IsBlock()) { |
3094 | 0 | if (aDeltaBCoord) { |
3095 | 0 | kid->MovePositionBy(wm, translation); |
3096 | 0 | } |
3097 | 0 |
|
3098 | 0 | // Make sure the frame's view and any child views are updated |
3099 | 0 | nsContainerFrame::PlaceFrameView(kid); |
3100 | 0 | } |
3101 | 0 | else { |
3102 | 0 | // Adjust the block-dir coordinate of the frames in the line. |
3103 | 0 | // Note: we need to re-position views even if aDeltaBCoord is 0, because |
3104 | 0 | // one of our parent frames may have moved and so the view's position |
3105 | 0 | // relative to its parent may have changed. |
3106 | 0 | int32_t n = aLine->GetChildCount(); |
3107 | 0 | while (--n >= 0) { |
3108 | 0 | if (aDeltaBCoord) { |
3109 | 0 | kid->MovePositionBy(wm, translation); |
3110 | 0 | } |
3111 | 0 | // Make sure the frame's view and any child views are updated |
3112 | 0 | nsContainerFrame::PlaceFrameView(kid); |
3113 | 0 | kid = kid->GetNextSibling(); |
3114 | 0 | } |
3115 | 0 | } |
3116 | 0 | } |
3117 | | |
3118 | | nsresult |
3119 | | nsBlockFrame::AttributeChanged(int32_t aNameSpaceID, |
3120 | | nsAtom* aAttribute, |
3121 | | int32_t aModType) |
3122 | 0 | { |
3123 | 0 | nsresult rv = nsContainerFrame::AttributeChanged(aNameSpaceID, |
3124 | 0 | aAttribute, aModType); |
3125 | 0 | if (NS_FAILED(rv)) { |
3126 | 0 | return rv; |
3127 | 0 | } |
3128 | 0 | if (nsGkAtoms::value == aAttribute) { |
3129 | 0 | const nsStyleDisplay* styleDisplay = StyleDisplay(); |
3130 | 0 | if (mozilla::StyleDisplay::ListItem == styleDisplay->mDisplay) { |
3131 | 0 | // Search for the closest ancestor that's a block frame. We |
3132 | 0 | // make the assumption that all related list items share a |
3133 | 0 | // common block/grid/flex ancestor. |
3134 | 0 | // XXXldb I think that's a bad assumption. |
3135 | 0 | nsContainerFrame* ancestor = GetParent(); |
3136 | 0 | for (; ancestor; ancestor = ancestor->GetParent()) { |
3137 | 0 | auto frameType = ancestor->Type(); |
3138 | 0 | if (frameType == LayoutFrameType::Block || |
3139 | 0 | frameType == LayoutFrameType::FlexContainer || |
3140 | 0 | frameType == LayoutFrameType::GridContainer) { |
3141 | 0 | break; |
3142 | 0 | } |
3143 | 0 | } |
3144 | 0 | // Tell the ancestor to renumber list items within itself. |
3145 | 0 | if (ancestor) { |
3146 | 0 | // XXX Not sure if this is necessary anymore |
3147 | 0 | if (ancestor->RenumberList()) { |
3148 | 0 | PresShell()->FrameNeedsReflow(ancestor, nsIPresShell::eStyleChange, |
3149 | 0 | NS_FRAME_HAS_DIRTY_CHILDREN); |
3150 | 0 | } |
3151 | 0 | } |
3152 | 0 | } |
3153 | 0 | } |
3154 | 0 | return rv; |
3155 | 0 | } |
3156 | | |
3157 | | static inline bool |
3158 | | IsNonAutoNonZeroBSize(const nsStyleCoord& aCoord) |
3159 | 0 | { |
3160 | 0 | nsStyleUnit unit = aCoord.GetUnit(); |
3161 | 0 | if (unit == eStyleUnit_Auto || |
3162 | 0 | // The enumerated values were originally aimed at inline-size |
3163 | 0 | // (or width, as it was before logicalization). For now, let them |
3164 | 0 | // return false here, so we treat them like 'auto' pending a |
3165 | 0 | // real implementation. (See bug 1126420.) |
3166 | 0 | // |
3167 | 0 | // FIXME (bug 567039, bug 527285) |
3168 | 0 | // This isn't correct for the 'fill' value, which should more |
3169 | 0 | // likely (but not necessarily, depending on the available space) |
3170 | 0 | // be returning true. |
3171 | 0 | unit == eStyleUnit_Enumerated) { |
3172 | 0 | return false; |
3173 | 0 | } |
3174 | 0 | if (aCoord.IsCoordPercentCalcUnit()) { |
3175 | 0 | // If we evaluate the length/percent/calc at a percentage basis of |
3176 | 0 | // both nscoord_MAX and 0, and it's zero both ways, then it's a zero |
3177 | 0 | // length, percent, or combination thereof. Test > 0 so we clamp |
3178 | 0 | // negative calc() results to 0. |
3179 | 0 | return aCoord.ComputeCoordPercentCalc(nscoord_MAX) > 0 || |
3180 | 0 | aCoord.ComputeCoordPercentCalc(0) > 0; |
3181 | 0 | } |
3182 | 0 | MOZ_ASSERT(false, "unexpected unit for height or min-height"); |
3183 | 0 | return true; |
3184 | 0 | } |
3185 | | |
3186 | | /* virtual */ bool |
3187 | | nsBlockFrame::IsSelfEmpty() |
3188 | 0 | { |
3189 | 0 | // Blocks which are margin-roots (including inline-blocks) cannot be treated |
3190 | 0 | // as empty for margin-collapsing and other purposes. They're more like |
3191 | 0 | // replaced elements. |
3192 | 0 | if (GetStateBits() & NS_BLOCK_MARGIN_ROOT) { |
3193 | 0 | return false; |
3194 | 0 | } |
3195 | 0 | |
3196 | 0 | WritingMode wm = GetWritingMode(); |
3197 | 0 | const nsStylePosition* position = StylePosition(); |
3198 | 0 |
|
3199 | 0 | if (IsNonAutoNonZeroBSize(position->MinBSize(wm)) || |
3200 | 0 | IsNonAutoNonZeroBSize(position->BSize(wm))) { |
3201 | 0 | return false; |
3202 | 0 | } |
3203 | 0 | |
3204 | 0 | const nsStyleBorder* border = StyleBorder(); |
3205 | 0 | const nsStylePadding* padding = StylePadding(); |
3206 | 0 |
|
3207 | 0 | if (border->GetComputedBorderWidth(wm.PhysicalSide(eLogicalSideBStart)) != 0 || |
3208 | 0 | border->GetComputedBorderWidth(wm.PhysicalSide(eLogicalSideBEnd)) != 0 || |
3209 | 0 | !nsLayoutUtils::IsPaddingZero(padding->mPadding.GetBStart(wm)) || |
3210 | 0 | !nsLayoutUtils::IsPaddingZero(padding->mPadding.GetBEnd(wm))) { |
3211 | 0 | return false; |
3212 | 0 | } |
3213 | 0 | |
3214 | 0 | if (HasOutsideBullet() && !BulletIsEmpty()) { |
3215 | 0 | return false; |
3216 | 0 | } |
3217 | 0 | |
3218 | 0 | return true; |
3219 | 0 | } |
3220 | | |
3221 | | bool |
3222 | | nsBlockFrame::CachedIsEmpty() |
3223 | 0 | { |
3224 | 0 | if (!IsSelfEmpty()) { |
3225 | 0 | return false; |
3226 | 0 | } |
3227 | 0 | |
3228 | 0 | for (LineIterator line = LinesBegin(), line_end = LinesEnd(); |
3229 | 0 | line != line_end; |
3230 | 0 | ++line) |
3231 | 0 | { |
3232 | 0 | if (!line->CachedIsEmpty()) |
3233 | 0 | return false; |
3234 | 0 | } |
3235 | 0 |
|
3236 | 0 | return true; |
3237 | 0 | } |
3238 | | |
3239 | | bool |
3240 | | nsBlockFrame::IsEmpty() |
3241 | 0 | { |
3242 | 0 | if (!IsSelfEmpty()) { |
3243 | 0 | return false; |
3244 | 0 | } |
3245 | 0 | |
3246 | 0 | for (LineIterator line = LinesBegin(), line_end = LinesEnd(); |
3247 | 0 | line != line_end; |
3248 | 0 | ++line) |
3249 | 0 | { |
3250 | 0 | if (!line->IsEmpty()) |
3251 | 0 | return false; |
3252 | 0 | } |
3253 | 0 |
|
3254 | 0 | return true; |
3255 | 0 | } |
3256 | | |
3257 | | bool |
3258 | | nsBlockFrame::ShouldApplyBStartMargin(BlockReflowInput& aState, |
3259 | | nsLineBox* aLine, |
3260 | | nsIFrame* aChildFrame) |
3261 | 0 | { |
3262 | 0 | if (aState.mFlags.mShouldApplyBStartMargin) { |
3263 | 0 | // Apply short-circuit check to avoid searching the line list |
3264 | 0 | return true; |
3265 | 0 | } |
3266 | 0 | |
3267 | 0 | if (!aState.IsAdjacentWithTop() || |
3268 | 0 | aChildFrame->StyleBorder()->mBoxDecorationBreak == |
3269 | 0 | StyleBoxDecorationBreak::Clone) { |
3270 | 0 | // If we aren't at the start block-coordinate then something of non-zero |
3271 | 0 | // height must have been placed. Therefore the childs block-start margin |
3272 | 0 | // applies. |
3273 | 0 | aState.mFlags.mShouldApplyBStartMargin = true; |
3274 | 0 | return true; |
3275 | 0 | } |
3276 | 0 | |
3277 | 0 | // Determine if this line is "essentially" the first line |
3278 | 0 | LineIterator line = LinesBegin(); |
3279 | 0 | if (aState.mFlags.mHasLineAdjacentToTop) { |
3280 | 0 | line = aState.mLineAdjacentToTop; |
3281 | 0 | } |
3282 | 0 | while (line != aLine) { |
3283 | 0 | if (!line->CachedIsEmpty() || line->HasClearance()) { |
3284 | 0 | // A line which precedes aLine is non-empty, or has clearance, |
3285 | 0 | // so therefore the block-start margin applies. |
3286 | 0 | aState.mFlags.mShouldApplyBStartMargin = true; |
3287 | 0 | return true; |
3288 | 0 | } |
3289 | 0 | // No need to apply the block-start margin if the line has floats. We |
3290 | 0 | // should collapse anyway (bug 44419) |
3291 | 0 | ++line; |
3292 | 0 | aState.mFlags.mHasLineAdjacentToTop = true; |
3293 | 0 | aState.mLineAdjacentToTop = line; |
3294 | 0 | } |
3295 | 0 |
|
3296 | 0 | // The line being reflowed is "essentially" the first line in the |
3297 | 0 | // block. Therefore its block-start margin will be collapsed by the |
3298 | 0 | // generational collapsing logic with its parent (us). |
3299 | 0 | return false; |
3300 | 0 | } |
3301 | | |
3302 | | void |
3303 | | nsBlockFrame::ReflowBlockFrame(BlockReflowInput& aState, |
3304 | | LineIterator aLine, |
3305 | | bool* aKeepReflowGoing) |
3306 | 0 | { |
3307 | 0 | MOZ_ASSERT(*aKeepReflowGoing, "bad caller"); |
3308 | 0 |
|
3309 | 0 | nsIFrame* frame = aLine->mFirstChild; |
3310 | 0 | if (!frame) { |
3311 | 0 | NS_ASSERTION(false, "program error - unexpected empty line"); |
3312 | 0 | return; |
3313 | 0 | } |
3314 | 0 |
|
3315 | 0 | // Prepare the block reflow engine |
3316 | 0 | nsBlockReflowContext brc(aState.mPresContext, aState.mReflowInput); |
3317 | 0 |
|
3318 | 0 | StyleClear breakType = frame->StyleDisplay()->mBreakType; |
3319 | 0 | if (StyleClear::None != aState.mFloatBreakType) { |
3320 | 0 | breakType = nsLayoutUtils::CombineBreakType(breakType, |
3321 | 0 | aState.mFloatBreakType); |
3322 | 0 | aState.mFloatBreakType = StyleClear::None; |
3323 | 0 | } |
3324 | 0 |
|
3325 | 0 | // Clear past floats before the block if the clear style is not none |
3326 | 0 | aLine->SetBreakTypeBefore(breakType); |
3327 | 0 |
|
3328 | 0 | // See if we should apply the block-start margin. If the block frame being |
3329 | 0 | // reflowed is a continuation (non-null prev-in-flow) then we don't |
3330 | 0 | // apply its block-start margin because it's not significant unless it has |
3331 | 0 | // 'box-decoration-break:clone'. Otherwise, dig deeper. |
3332 | 0 | bool applyBStartMargin = (frame->StyleBorder()->mBoxDecorationBreak == |
3333 | 0 | StyleBoxDecorationBreak::Clone || |
3334 | 0 | !frame->GetPrevInFlow()) && |
3335 | 0 | ShouldApplyBStartMargin(aState, aLine, frame); |
3336 | 0 | if (applyBStartMargin) { |
3337 | 0 | // The HasClearance setting is only valid if ShouldApplyBStartMargin |
3338 | 0 | // returned false (in which case the block-start margin-root set our |
3339 | 0 | // clearance flag). Otherwise clear it now. We'll set it later on |
3340 | 0 | // ourselves if necessary. |
3341 | 0 | aLine->ClearHasClearance(); |
3342 | 0 | } |
3343 | 0 | bool treatWithClearance = aLine->HasClearance(); |
3344 | 0 |
|
3345 | 0 | bool mightClearFloats = breakType != StyleClear::None; |
3346 | 0 | nsIFrame *replacedBlock = nullptr; |
3347 | 0 | if (!nsBlockFrame::BlockCanIntersectFloats(frame)) { |
3348 | 0 | mightClearFloats = true; |
3349 | 0 | replacedBlock = frame; |
3350 | 0 | } |
3351 | 0 |
|
3352 | 0 | // If our block-start margin was counted as part of some parent's block-start |
3353 | 0 | // margin collapse, and we are being speculatively reflowed assuming this |
3354 | 0 | // frame DID NOT need clearance, then we need to check that |
3355 | 0 | // assumption. |
3356 | 0 | if (!treatWithClearance && !applyBStartMargin && mightClearFloats && |
3357 | 0 | aState.mReflowInput.mDiscoveredClearance) { |
3358 | 0 | nscoord curBCoord = aState.mBCoord + aState.mPrevBEndMargin.get(); |
3359 | 0 | nscoord clearBCoord = aState.ClearFloats(curBCoord, breakType, replacedBlock); |
3360 | 0 | if (clearBCoord != curBCoord) { |
3361 | 0 | // Only record the first frame that requires clearance |
3362 | 0 | if (!*aState.mReflowInput.mDiscoveredClearance) { |
3363 | 0 | *aState.mReflowInput.mDiscoveredClearance = frame; |
3364 | 0 | } |
3365 | 0 | aState.mPrevChild = frame; |
3366 | 0 | // Exactly what we do now is flexible since we'll definitely be |
3367 | 0 | // reflowed. |
3368 | 0 | return; |
3369 | 0 | } |
3370 | 0 | } |
3371 | 0 | if (treatWithClearance) { |
3372 | 0 | applyBStartMargin = true; |
3373 | 0 | } |
3374 | 0 |
|
3375 | 0 | nsIFrame* clearanceFrame = nullptr; |
3376 | 0 | nscoord startingBCoord = aState.mBCoord; |
3377 | 0 | nsCollapsingMargin incomingMargin = aState.mPrevBEndMargin; |
3378 | 0 | nscoord clearance; |
3379 | 0 | // Save the original position of the frame so that we can reposition |
3380 | 0 | // its view as needed. |
3381 | 0 | nsPoint originalPosition = frame->GetPosition(); |
3382 | 0 | while (true) { |
3383 | 0 | clearance = 0; |
3384 | 0 | nscoord bStartMargin = 0; |
3385 | 0 | bool mayNeedRetry = false; |
3386 | 0 | bool clearedFloats = false; |
3387 | 0 | if (applyBStartMargin) { |
3388 | 0 | // Precompute the blocks block-start margin value so that we can get the |
3389 | 0 | // correct available space (there might be a float that's |
3390 | 0 | // already been placed below the aState.mPrevBEndMargin |
3391 | 0 |
|
3392 | 0 | // Setup a reflowInput to get the style computed block-start margin |
3393 | 0 | // value. We'll use a reason of `resize' so that we don't fudge |
3394 | 0 | // any incremental reflow state. |
3395 | 0 |
|
3396 | 0 | // The availSpace here is irrelevant to our needs - all we want |
3397 | 0 | // out if this setup is the block-start margin value which doesn't depend |
3398 | 0 | // on the childs available space. |
3399 | 0 | // XXX building a complete ReflowInput just to get the block-start |
3400 | 0 | // margin seems like a waste. And we do this for almost every block! |
3401 | 0 | WritingMode wm = frame->GetWritingMode(); |
3402 | 0 | LogicalSize availSpace = aState.ContentSize(wm); |
3403 | 0 | ReflowInput reflowInput(aState.mPresContext, aState.mReflowInput, |
3404 | 0 | frame, availSpace); |
3405 | 0 |
|
3406 | 0 | if (treatWithClearance) { |
3407 | 0 | aState.mBCoord += aState.mPrevBEndMargin.get(); |
3408 | 0 | aState.mPrevBEndMargin.Zero(); |
3409 | 0 | } |
3410 | 0 |
|
3411 | 0 | // Now compute the collapsed margin-block-start value into |
3412 | 0 | // aState.mPrevBEndMargin, assuming that all child margins |
3413 | 0 | // collapse down to clearanceFrame. |
3414 | 0 | brc.ComputeCollapsedBStartMargin(reflowInput, |
3415 | 0 | &aState.mPrevBEndMargin, |
3416 | 0 | clearanceFrame, |
3417 | 0 | &mayNeedRetry); |
3418 | 0 |
|
3419 | 0 | // XXX optimization; we could check the collapsing children to see if they are sure |
3420 | 0 | // to require clearance, and so avoid retrying them |
3421 | 0 |
|
3422 | 0 | if (clearanceFrame) { |
3423 | 0 | // Don't allow retries on the second pass. The clearance decisions for the |
3424 | 0 | // blocks whose block-start margins collapse with ours are now fixed. |
3425 | 0 | mayNeedRetry = false; |
3426 | 0 | } |
3427 | 0 |
|
3428 | 0 | if (!treatWithClearance && !clearanceFrame && mightClearFloats) { |
3429 | 0 | // We don't know if we need clearance and this is the first, |
3430 | 0 | // optimistic pass. So determine whether *this block* needs |
3431 | 0 | // clearance. Note that we do not allow the decision for whether |
3432 | 0 | // this block has clearance to change on the second pass; that |
3433 | 0 | // decision is only allowed to be made under the optimistic |
3434 | 0 | // first pass. |
3435 | 0 | nscoord curBCoord = aState.mBCoord + aState.mPrevBEndMargin.get(); |
3436 | 0 | nscoord clearBCoord = aState.ClearFloats(curBCoord, breakType, replacedBlock); |
3437 | 0 | if (clearBCoord != curBCoord) { |
3438 | 0 | // Looks like we need clearance and we didn't know about it already. So |
3439 | 0 | // recompute collapsed margin |
3440 | 0 | treatWithClearance = true; |
3441 | 0 | // Remember this decision, needed for incremental reflow |
3442 | 0 | aLine->SetHasClearance(); |
3443 | 0 |
|
3444 | 0 | // Apply incoming margins |
3445 | 0 | aState.mBCoord += aState.mPrevBEndMargin.get(); |
3446 | 0 | aState.mPrevBEndMargin.Zero(); |
3447 | 0 |
|
3448 | 0 | // Compute the collapsed margin again, ignoring the incoming margin this time |
3449 | 0 | mayNeedRetry = false; |
3450 | 0 | brc.ComputeCollapsedBStartMargin(reflowInput, |
3451 | 0 | &aState.mPrevBEndMargin, |
3452 | 0 | clearanceFrame, |
3453 | 0 | &mayNeedRetry); |
3454 | 0 | } |
3455 | 0 | } |
3456 | 0 |
|
3457 | 0 | // Temporarily advance the running Y value so that the |
3458 | 0 | // GetAvailableSpace method will return the right available |
3459 | 0 | // space. This undone as soon as the horizontal margins are |
3460 | 0 | // computed. |
3461 | 0 | bStartMargin = aState.mPrevBEndMargin.get(); |
3462 | 0 |
|
3463 | 0 | if (treatWithClearance) { |
3464 | 0 | nscoord currentBCoord = aState.mBCoord; |
3465 | 0 | // advance mBCoord to the clear position. |
3466 | 0 | aState.mBCoord = aState.ClearFloats(aState.mBCoord, breakType, |
3467 | 0 | replacedBlock); |
3468 | 0 |
|
3469 | 0 | clearedFloats = aState.mBCoord != currentBCoord; |
3470 | 0 |
|
3471 | 0 | // Compute clearance. It's the amount we need to add to the block-start |
3472 | 0 | // border-edge of the frame, after applying collapsed margins |
3473 | 0 | // from the frame and its children, to get it to line up with |
3474 | 0 | // the block-end of the floats. The former is |
3475 | 0 | // currentBCoord + bStartMargin, the latter is the current |
3476 | 0 | // aState.mBCoord. |
3477 | 0 | // Note that negative clearance is possible |
3478 | 0 | clearance = aState.mBCoord - (currentBCoord + bStartMargin); |
3479 | 0 |
|
3480 | 0 | // Add clearance to our block-start margin while we compute available |
3481 | 0 | // space for the frame |
3482 | 0 | bStartMargin += clearance; |
3483 | 0 |
|
3484 | 0 | // Note that aState.mBCoord should stay where it is: at the block-start |
3485 | 0 | // border-edge of the frame |
3486 | 0 | } else { |
3487 | 0 | // Advance aState.mBCoord to the block-start border-edge of the frame. |
3488 | 0 | aState.mBCoord += bStartMargin; |
3489 | 0 | } |
3490 | 0 | } |
3491 | 0 |
|
3492 | 0 | aLine->SetLineIsImpactedByFloat(false); |
3493 | 0 |
|
3494 | 0 | // Here aState.mBCoord is the block-start border-edge of the block. |
3495 | 0 | // Compute the available space for the block |
3496 | 0 | nsFlowAreaRect floatAvailableSpace = aState.GetFloatAvailableSpace(); |
3497 | 0 | WritingMode wm = aState.mReflowInput.GetWritingMode(); |
3498 | 0 | LogicalRect availSpace(wm); |
3499 | 0 | aState.ComputeBlockAvailSpace(frame, floatAvailableSpace, |
3500 | 0 | replacedBlock != nullptr, availSpace); |
3501 | 0 |
|
3502 | 0 | // The check for |
3503 | 0 | // (!aState.mReflowInput.mFlags.mIsTopOfPage || clearedFloats) |
3504 | 0 | // is to some degree out of paranoia: if we reliably eat up block-start |
3505 | 0 | // margins at the top of the page as we ought to, it wouldn't be |
3506 | 0 | // needed. |
3507 | 0 | if ((!aState.mReflowInput.mFlags.mIsTopOfPage || clearedFloats) && |
3508 | 0 | availSpace.BSize(wm) < 0) { |
3509 | 0 | // We know already that this child block won't fit on this |
3510 | 0 | // page/column due to the block-start margin or the clearance. So we |
3511 | 0 | // need to get out of here now. (If we don't, most blocks will handle |
3512 | 0 | // things fine, and report break-before, but zero-height blocks |
3513 | 0 | // won't, and will thus make their parent overly-large and force |
3514 | 0 | // *it* to be pushed in its entirety.) |
3515 | 0 | // Doing this means that we also don't need to worry about the |
3516 | 0 | // |availSpace.BSize(wm) += bStartMargin| below interacting with |
3517 | 0 | // pushed floats (which force nscoord_MAX clearance) to cause a |
3518 | 0 | // constrained block size to turn into an unconstrained one. |
3519 | 0 | aState.mBCoord = startingBCoord; |
3520 | 0 | aState.mPrevBEndMargin = incomingMargin; |
3521 | 0 | *aKeepReflowGoing = false; |
3522 | 0 | if (ShouldAvoidBreakInside(aState.mReflowInput)) { |
3523 | 0 | aState.mReflowStatus.SetInlineLineBreakBeforeAndReset(); |
3524 | 0 | } else { |
3525 | 0 | PushLines(aState, aLine.prev()); |
3526 | 0 | aState.mReflowStatus.SetIncomplete(); |
3527 | 0 | } |
3528 | 0 | return; |
3529 | 0 | } |
3530 | 0 |
|
3531 | 0 | // Now put the block-dir coordinate back to the start of the |
3532 | 0 | // block-start-margin + clearance. |
3533 | 0 | aState.mBCoord -= bStartMargin; |
3534 | 0 | availSpace.BStart(wm) -= bStartMargin; |
3535 | 0 | if (NS_UNCONSTRAINEDSIZE != availSpace.BSize(wm)) { |
3536 | 0 | availSpace.BSize(wm) += bStartMargin; |
3537 | 0 | } |
3538 | 0 |
|
3539 | 0 | // construct the html reflow state for the block. ReflowBlock |
3540 | 0 | // will initialize it. |
3541 | 0 | Maybe<ReflowInput> blockHtmlRI; |
3542 | 0 | blockHtmlRI.emplace( |
3543 | 0 | aState.mPresContext, aState.mReflowInput, frame, |
3544 | 0 | availSpace.Size(wm).ConvertTo(frame->GetWritingMode(), wm)); |
3545 | 0 |
|
3546 | 0 | nsFloatManager::SavedState floatManagerState; |
3547 | 0 | nsReflowStatus frameReflowStatus; |
3548 | 0 | do { |
3549 | 0 | if (floatAvailableSpace.HasFloats()) { |
3550 | 0 | // Set if floatAvailableSpace.HasFloats() is true for any |
3551 | 0 | // iteration of the loop. |
3552 | 0 | aLine->SetLineIsImpactedByFloat(true); |
3553 | 0 | } |
3554 | 0 |
|
3555 | 0 | // We might need to store into mDiscoveredClearance later if it's |
3556 | 0 | // currently null; we want to overwrite any writes that |
3557 | 0 | // brc.ReflowBlock() below does, so we need to remember now |
3558 | 0 | // whether it's empty. |
3559 | 0 | const bool shouldStoreClearance = |
3560 | 0 | aState.mReflowInput.mDiscoveredClearance && |
3561 | 0 | !*aState.mReflowInput.mDiscoveredClearance; |
3562 | 0 |
|
3563 | 0 | // Reflow the block into the available space |
3564 | 0 | if (mayNeedRetry || replacedBlock) { |
3565 | 0 | aState.FloatManager()->PushState(&floatManagerState); |
3566 | 0 | } |
3567 | 0 |
|
3568 | 0 | if (mayNeedRetry) { |
3569 | 0 | blockHtmlRI->mDiscoveredClearance = &clearanceFrame; |
3570 | 0 | } else if (!applyBStartMargin) { |
3571 | 0 | blockHtmlRI->mDiscoveredClearance = |
3572 | 0 | aState.mReflowInput.mDiscoveredClearance; |
3573 | 0 | } |
3574 | 0 |
|
3575 | 0 | frameReflowStatus.Reset(); |
3576 | 0 | brc.ReflowBlock(availSpace, applyBStartMargin, aState.mPrevBEndMargin, |
3577 | 0 | clearance, aState.IsAdjacentWithTop(), |
3578 | 0 | aLine.get(), *blockHtmlRI, frameReflowStatus, aState); |
3579 | 0 |
|
3580 | 0 | // Now the block has a height. Using that height, get the |
3581 | 0 | // available space again and call ComputeBlockAvailSpace again. |
3582 | 0 | // If ComputeBlockAvailSpace gives a different result, we need to |
3583 | 0 | // reflow again. |
3584 | 0 | if (!replacedBlock) { |
3585 | 0 | break; |
3586 | 0 | } |
3587 | 0 | |
3588 | 0 | LogicalRect oldFloatAvailableSpaceRect(floatAvailableSpace.mRect); |
3589 | 0 | floatAvailableSpace = aState.GetFloatAvailableSpaceForBSize( |
3590 | 0 | aState.mBCoord + bStartMargin, |
3591 | 0 | brc.GetMetrics().BSize(wm), |
3592 | 0 | &floatManagerState); |
3593 | 0 | NS_ASSERTION(floatAvailableSpace.mRect.BStart(wm) == |
3594 | 0 | oldFloatAvailableSpaceRect.BStart(wm), |
3595 | 0 | "yikes"); |
3596 | 0 | // Restore the height to the position of the next band. |
3597 | 0 | floatAvailableSpace.mRect.BSize(wm) = |
3598 | 0 | oldFloatAvailableSpaceRect.BSize(wm); |
3599 | 0 | // Determine whether the available space shrunk on either side, |
3600 | 0 | // because (the first time round) we now know the block's height, |
3601 | 0 | // and it may intersect additional floats, or (on later |
3602 | 0 | // iterations) because narrowing the width relative to the |
3603 | 0 | // previous time may cause the block to become taller. Note that |
3604 | 0 | // since we're reflowing the block, narrowing the width might also |
3605 | 0 | // make it shorter, so we must pass aCanGrow as true. |
3606 | 0 | if (!AvailableSpaceShrunk(wm, oldFloatAvailableSpaceRect, |
3607 | 0 | floatAvailableSpace.mRect, true)) { |
3608 | 0 | // The size and position we chose before are fine (i.e., they |
3609 | 0 | // don't cause intersecting with floats that requires a change |
3610 | 0 | // in size or position), so we're done. |
3611 | 0 | break; |
3612 | 0 | } |
3613 | 0 | |
3614 | 0 | bool advanced = false; |
3615 | 0 | if (!aState.ReplacedBlockFitsInAvailSpace(replacedBlock, |
3616 | 0 | floatAvailableSpace)) { |
3617 | 0 | // Advance to the next band. |
3618 | 0 | nscoord newBCoord = aState.mBCoord; |
3619 | 0 | if (aState.AdvanceToNextBand(floatAvailableSpace.mRect, &newBCoord)) { |
3620 | 0 | advanced = true; |
3621 | 0 | } |
3622 | 0 | // ClearFloats might be able to advance us further once we're there. |
3623 | 0 | aState.mBCoord = |
3624 | 0 | aState.ClearFloats(newBCoord, StyleClear::None, replacedBlock); |
3625 | 0 | // Start over with a new available space rect at the new height. |
3626 | 0 | floatAvailableSpace = |
3627 | 0 | aState.GetFloatAvailableSpaceWithState(aState.mBCoord, |
3628 | 0 | ShapeType::ShapeOutside, |
3629 | 0 | &floatManagerState); |
3630 | 0 | } |
3631 | 0 |
|
3632 | 0 | LogicalRect oldAvailSpace(availSpace); |
3633 | 0 | aState.ComputeBlockAvailSpace(frame, floatAvailableSpace, |
3634 | 0 | replacedBlock != nullptr, availSpace); |
3635 | 0 |
|
3636 | 0 | if (!advanced && availSpace.IsEqualEdges(oldAvailSpace)) { |
3637 | 0 | break; |
3638 | 0 | } |
3639 | 0 | |
3640 | 0 | // We need another reflow. |
3641 | 0 | aState.FloatManager()->PopState(&floatManagerState); |
3642 | 0 |
|
3643 | 0 | if (!treatWithClearance && !applyBStartMargin && |
3644 | 0 | aState.mReflowInput.mDiscoveredClearance) { |
3645 | 0 | // We set shouldStoreClearance above to record only the first |
3646 | 0 | // frame that requires clearance. |
3647 | 0 | if (shouldStoreClearance) { |
3648 | 0 | *aState.mReflowInput.mDiscoveredClearance = frame; |
3649 | 0 | } |
3650 | 0 | aState.mPrevChild = frame; |
3651 | 0 | // Exactly what we do now is flexible since we'll definitely be |
3652 | 0 | // reflowed. |
3653 | 0 | return; |
3654 | 0 | } |
3655 | 0 |
|
3656 | 0 | if (advanced) { |
3657 | 0 | // We're pushing down the border-box, so we don't apply margin anymore. |
3658 | 0 | // This should never cause us to move up since the call to |
3659 | 0 | // GetFloatAvailableSpaceForBSize above included the margin. |
3660 | 0 | applyBStartMargin = false; |
3661 | 0 | bStartMargin = 0; |
3662 | 0 | treatWithClearance = true; // avoid hitting test above |
3663 | 0 | clearance = 0; |
3664 | 0 | } |
3665 | 0 |
|
3666 | 0 | blockHtmlRI.reset(); |
3667 | 0 | blockHtmlRI.emplace( |
3668 | 0 | aState.mPresContext, aState.mReflowInput, frame, |
3669 | 0 | availSpace.Size(wm).ConvertTo(frame->GetWritingMode(), wm)); |
3670 | 0 | } while (true); |
3671 | 0 |
|
3672 | 0 | if (mayNeedRetry && clearanceFrame) { |
3673 | 0 | aState.FloatManager()->PopState(&floatManagerState); |
3674 | 0 | aState.mBCoord = startingBCoord; |
3675 | 0 | aState.mPrevBEndMargin = incomingMargin; |
3676 | 0 | continue; |
3677 | 0 | } |
3678 | 0 | |
3679 | 0 | aState.mPrevChild = frame; |
3680 | 0 |
|
3681 | 0 | if (blockHtmlRI->WillReflowAgainForClearance()) { |
3682 | 0 | // If an ancestor of ours is going to reflow for clearance, we |
3683 | 0 | // need to avoid calling PlaceBlock, because it unsets dirty bits |
3684 | 0 | // on the child block (both itself, and through its call to |
3685 | 0 | // nsFrame::DidReflow), and those dirty bits imply dirtiness for |
3686 | 0 | // all of the child block, including the lines it didn't reflow. |
3687 | 0 | NS_ASSERTION(originalPosition == frame->GetPosition(), |
3688 | 0 | "we need to call PositionChildViews"); |
3689 | 0 | return; |
3690 | 0 | } |
3691 | 0 |
|
3692 | | #if defined(REFLOW_STATUS_COVERAGE) |
3693 | | RecordReflowStatus(true, frameReflowStatus); |
3694 | | #endif |
3695 | | |
3696 | 0 | if (frameReflowStatus.IsInlineBreakBefore()) { |
3697 | 0 | // None of the child block fits. |
3698 | 0 | *aKeepReflowGoing = false; |
3699 | 0 | if (ShouldAvoidBreakInside(aState.mReflowInput)) { |
3700 | 0 | aState.mReflowStatus.SetInlineLineBreakBeforeAndReset(); |
3701 | 0 | } else { |
3702 | 0 | PushLines(aState, aLine.prev()); |
3703 | 0 | aState.mReflowStatus.SetIncomplete(); |
3704 | 0 | } |
3705 | 0 | } |
3706 | 0 | else { |
3707 | 0 | // Note: line-break-after a block is a nop |
3708 | 0 |
|
3709 | 0 | // Try to place the child block. |
3710 | 0 | // Don't force the block to fit if we have positive clearance, because |
3711 | 0 | // pushing it to the next page would give it more room. |
3712 | 0 | // Don't force the block to fit if it's impacted by a float. If it is, |
3713 | 0 | // then pushing it to the next page would give it more room. Note that |
3714 | 0 | // isImpacted doesn't include impact from the block's own floats. |
3715 | 0 | bool forceFit = aState.IsAdjacentWithTop() && clearance <= 0 && |
3716 | 0 | !floatAvailableSpace.HasFloats(); |
3717 | 0 | nsCollapsingMargin collapsedBEndMargin; |
3718 | 0 | nsOverflowAreas overflowAreas; |
3719 | 0 | *aKeepReflowGoing = brc.PlaceBlock(*blockHtmlRI, forceFit, aLine.get(), |
3720 | 0 | collapsedBEndMargin, |
3721 | 0 | overflowAreas, |
3722 | 0 | frameReflowStatus); |
3723 | 0 | if (!frameReflowStatus.IsFullyComplete() && |
3724 | 0 | ShouldAvoidBreakInside(aState.mReflowInput)) { |
3725 | 0 | *aKeepReflowGoing = false; |
3726 | 0 | } |
3727 | 0 |
|
3728 | 0 | if (aLine->SetCarriedOutBEndMargin(collapsedBEndMargin)) { |
3729 | 0 | LineIterator nextLine = aLine; |
3730 | 0 | ++nextLine; |
3731 | 0 | if (nextLine != LinesEnd()) { |
3732 | 0 | nextLine->MarkPreviousMarginDirty(); |
3733 | 0 | } |
3734 | 0 | } |
3735 | 0 |
|
3736 | 0 | aLine->SetOverflowAreas(overflowAreas); |
3737 | 0 | if (*aKeepReflowGoing) { |
3738 | 0 | // Some of the child block fit |
3739 | 0 |
|
3740 | 0 | // Advance to new Y position |
3741 | 0 | nscoord newBCoord = aLine->BEnd(); |
3742 | 0 | aState.mBCoord = newBCoord; |
3743 | 0 |
|
3744 | 0 |
|
3745 | 0 | // Continue the block frame now if it didn't completely fit in |
3746 | 0 | // the available space. |
3747 | 0 | if (!frameReflowStatus.IsFullyComplete()) { |
3748 | 0 | bool madeContinuation = |
3749 | 0 | CreateContinuationFor(aState, nullptr, frame); |
3750 | 0 |
|
3751 | 0 | nsIFrame* nextFrame = frame->GetNextInFlow(); |
3752 | 0 | NS_ASSERTION(nextFrame, "We're supposed to have a next-in-flow by now"); |
3753 | 0 |
|
3754 | 0 | if (frameReflowStatus.IsIncomplete()) { |
3755 | 0 | // If nextFrame used to be an overflow container, make it a normal block |
3756 | 0 | if (!madeContinuation && |
3757 | 0 | (NS_FRAME_IS_OVERFLOW_CONTAINER & nextFrame->GetStateBits())) { |
3758 | 0 | nsOverflowContinuationTracker::AutoFinish fini(aState.mOverflowTracker, frame); |
3759 | 0 | nsContainerFrame* parent = nextFrame->GetParent(); |
3760 | 0 | nsresult rv = parent->StealFrame(nextFrame); |
3761 | 0 | if (NS_FAILED(rv)) { |
3762 | 0 | return; |
3763 | 0 | } |
3764 | 0 | if (parent != this) { |
3765 | 0 | ReparentFrame(nextFrame, parent, this, |
3766 | 0 | ReparentingDirection::Variable); |
3767 | 0 | } |
3768 | 0 | mFrames.InsertFrame(nullptr, frame, nextFrame); |
3769 | 0 | madeContinuation = true; // needs to be added to mLines |
3770 | 0 | nextFrame->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER); |
3771 | 0 | frameReflowStatus.SetNextInFlowNeedsReflow(); |
3772 | 0 | } |
3773 | 0 |
|
3774 | 0 | // Push continuation to a new line, but only if we actually made one. |
3775 | 0 | if (madeContinuation) { |
3776 | 0 | nsLineBox* line = NewLineBox(nextFrame, true); |
3777 | 0 | mLines.after_insert(aLine, line); |
3778 | 0 | } |
3779 | 0 |
|
3780 | 0 | PushLines(aState, aLine); |
3781 | 0 | aState.mReflowStatus.SetIncomplete(); |
3782 | 0 |
|
3783 | 0 | // If we need to reflow the continuation of the block child, |
3784 | 0 | // then we'd better reflow our continuation |
3785 | 0 | if (frameReflowStatus.NextInFlowNeedsReflow()) { |
3786 | 0 | aState.mReflowStatus.SetNextInFlowNeedsReflow(); |
3787 | 0 | // We also need to make that continuation's line dirty so |
3788 | 0 | // it gets reflowed when we reflow our next in flow. The |
3789 | 0 | // nif's line must always be either a line of the nif's |
3790 | 0 | // parent block (only if we didn't make a continuation) or |
3791 | 0 | // else one of our own overflow lines. In the latter case |
3792 | 0 | // the line is already marked dirty, so just handle the |
3793 | 0 | // first case. |
3794 | 0 | if (!madeContinuation) { |
3795 | 0 | nsBlockFrame* nifBlock = |
3796 | 0 | nsLayoutUtils::GetAsBlock(nextFrame->GetParent()); |
3797 | 0 | NS_ASSERTION(nifBlock, |
3798 | 0 | "A block's child's next in flow's parent must be a block!"); |
3799 | 0 | for (LineIterator line = nifBlock->LinesBegin(), |
3800 | 0 | line_end = nifBlock->LinesEnd(); line != line_end; ++line) { |
3801 | 0 | if (line->Contains(nextFrame)) { |
3802 | 0 | line->MarkDirty(); |
3803 | 0 | break; |
3804 | 0 | } |
3805 | 0 | } |
3806 | 0 | } |
3807 | 0 | } |
3808 | 0 | *aKeepReflowGoing = false; |
3809 | 0 |
|
3810 | 0 | // The block-end margin for a block is only applied on the last |
3811 | 0 | // flow block. Since we just continued the child block frame, |
3812 | 0 | // we know that line->mFirstChild is not the last flow block |
3813 | 0 | // therefore zero out the running margin value. |
3814 | | #ifdef NOISY_BLOCK_DIR_MARGINS |
3815 | | ListTag(stdout); |
3816 | | printf(": reflow incomplete, frame="); |
3817 | | nsFrame::ListTag(stdout, mFrame); |
3818 | | printf(" prevBEndMargin=%d, setting to zero\n", |
3819 | | aState.mPrevBEndMargin.get()); |
3820 | | #endif |
3821 | | aState.mPrevBEndMargin.Zero(); |
3822 | 0 | } |
3823 | 0 | else { // frame is complete but its overflow is not complete |
3824 | 0 | // Disconnect the next-in-flow and put it in our overflow tracker |
3825 | 0 | if (!madeContinuation && |
3826 | 0 | !(NS_FRAME_IS_OVERFLOW_CONTAINER & nextFrame->GetStateBits())) { |
3827 | 0 | // It already exists, but as a normal next-in-flow, so we need |
3828 | 0 | // to dig it out of the child lists. |
3829 | 0 | nsresult rv = nextFrame->GetParent()->StealFrame(nextFrame); |
3830 | 0 | if (NS_FAILED(rv)) { |
3831 | 0 | return; |
3832 | 0 | } |
3833 | 0 | } |
3834 | 0 | else if (madeContinuation) { |
3835 | 0 | mFrames.RemoveFrame(nextFrame); |
3836 | 0 | } |
3837 | 0 |
|
3838 | 0 | // Put it in our overflow list |
3839 | 0 | aState.mOverflowTracker->Insert(nextFrame, frameReflowStatus); |
3840 | 0 | aState.mReflowStatus.MergeCompletionStatusFrom(frameReflowStatus); |
3841 | 0 |
|
3842 | | #ifdef NOISY_BLOCK_DIR_MARGINS |
3843 | | ListTag(stdout); |
3844 | | printf(": reflow complete but overflow incomplete for "); |
3845 | | nsFrame::ListTag(stdout, mFrame); |
3846 | | printf(" prevBEndMargin=%d collapsedBEndMargin=%d\n", |
3847 | | aState.mPrevBEndMargin.get(), collapsedBEndMargin.get()); |
3848 | | #endif |
3849 | | aState.mPrevBEndMargin = collapsedBEndMargin; |
3850 | 0 | } |
3851 | 0 | } |
3852 | 0 | else { // frame is fully complete |
3853 | | #ifdef NOISY_BLOCK_DIR_MARGINS |
3854 | | ListTag(stdout); |
3855 | | printf(": reflow complete for "); |
3856 | | nsFrame::ListTag(stdout, mFrame); |
3857 | | printf(" prevBEndMargin=%d collapsedBEndMargin=%d\n", |
3858 | | aState.mPrevBEndMargin.get(), collapsedBEndMargin.get()); |
3859 | | #endif |
3860 | | aState.mPrevBEndMargin = collapsedBEndMargin; |
3861 | 0 | } |
3862 | | #ifdef NOISY_BLOCK_DIR_MARGINS |
3863 | | ListTag(stdout); |
3864 | | printf(": frame="); |
3865 | | nsFrame::ListTag(stdout, mFrame); |
3866 | | printf(" carriedOutBEndMargin=%d collapsedBEndMargin=%d => %d\n", |
3867 | | brc.GetCarriedOutBEndMargin().get(), collapsedBEndMargin.get(), |
3868 | | aState.mPrevBEndMargin.get()); |
3869 | | #endif |
3870 | 0 | } else { |
3871 | 0 | if ((aLine == mLines.front() && !GetPrevInFlow()) || |
3872 | 0 | ShouldAvoidBreakInside(aState.mReflowInput)) { |
3873 | 0 | // If it's our very first line *or* we're not at the top of the page |
3874 | 0 | // and we have page-break-inside:avoid, then we need to be pushed to |
3875 | 0 | // our parent's next-in-flow. |
3876 | 0 | aState.mReflowStatus.SetInlineLineBreakBeforeAndReset(); |
3877 | 0 | // When we reflow in the new position, we need to reflow this |
3878 | 0 | // line again. |
3879 | 0 | aLine->MarkDirty(); |
3880 | 0 | } else { |
3881 | 0 | // Push the line that didn't fit and any lines that follow it |
3882 | 0 | // to our next-in-flow. |
3883 | 0 | PushLines(aState, aLine.prev()); |
3884 | 0 | aState.mReflowStatus.SetIncomplete(); |
3885 | 0 | } |
3886 | 0 | } |
3887 | 0 | } |
3888 | 0 | break; // out of the reflow retry loop |
3889 | 0 | } |
3890 | 0 |
|
3891 | 0 | // Now that we've got its final position all figured out, position any child |
3892 | 0 | // views it may have. Note that the case when frame has a view got handled |
3893 | 0 | // by FinishReflowChild, but that function didn't have the coordinates needed |
3894 | 0 | // to correctly decide whether to reposition child views. |
3895 | 0 | if (originalPosition != frame->GetPosition() && !frame->HasView()) { |
3896 | 0 | nsContainerFrame::PositionChildViews(frame); |
3897 | 0 | } |
3898 | 0 |
|
3899 | | #ifdef DEBUG |
3900 | | VerifyLines(true); |
3901 | | #endif |
3902 | | } |
3903 | | |
3904 | | void |
3905 | | nsBlockFrame::ReflowInlineFrames(BlockReflowInput& aState, |
3906 | | LineIterator aLine, |
3907 | | bool* aKeepReflowGoing) |
3908 | 0 | { |
3909 | 0 | *aKeepReflowGoing = true; |
3910 | 0 |
|
3911 | 0 | aLine->SetLineIsImpactedByFloat(false); |
3912 | 0 |
|
3913 | 0 | // Setup initial coordinate system for reflowing the inline frames |
3914 | 0 | // into. Apply a previous block frame's block-end margin first. |
3915 | 0 | if (ShouldApplyBStartMargin(aState, aLine, aLine->mFirstChild)) { |
3916 | 0 | aState.mBCoord += aState.mPrevBEndMargin.get(); |
3917 | 0 | } |
3918 | 0 | nsFlowAreaRect floatAvailableSpace = aState.GetFloatAvailableSpace(); |
3919 | 0 |
|
3920 | 0 | LineReflowStatus lineReflowStatus; |
3921 | 0 | do { |
3922 | 0 | nscoord availableSpaceBSize = 0; |
3923 | 0 | aState.mLineBSize.reset(); |
3924 | 0 | do { |
3925 | 0 | bool allowPullUp = true; |
3926 | 0 | nsIFrame* forceBreakInFrame = nullptr; |
3927 | 0 | int32_t forceBreakOffset = -1; |
3928 | 0 | gfxBreakPriority forceBreakPriority = gfxBreakPriority::eNoBreak; |
3929 | 0 | do { |
3930 | 0 | nsFloatManager::SavedState floatManagerState; |
3931 | 0 | aState.FloatManager()->PushState(&floatManagerState); |
3932 | 0 |
|
3933 | 0 | // Once upon a time we allocated the first 30 nsLineLayout objects |
3934 | 0 | // on the stack, and then we switched to the heap. At that time |
3935 | 0 | // these objects were large (1100 bytes on a 32 bit system). |
3936 | 0 | // Then the nsLineLayout object was shrunk to 156 bytes by |
3937 | 0 | // removing some internal buffers. Given that it is so much |
3938 | 0 | // smaller, the complexity of 2 different ways of allocating |
3939 | 0 | // no longer makes sense. Now we always allocate on the stack. |
3940 | 0 | nsLineLayout lineLayout(aState.mPresContext, |
3941 | 0 | aState.FloatManager(), |
3942 | 0 | &aState.mReflowInput, &aLine, nullptr); |
3943 | 0 | lineLayout.Init(&aState, aState.mMinLineHeight, aState.mLineNumber); |
3944 | 0 | if (forceBreakInFrame) { |
3945 | 0 | lineLayout.ForceBreakAtPosition(forceBreakInFrame, forceBreakOffset); |
3946 | 0 | } |
3947 | 0 | DoReflowInlineFrames(aState, lineLayout, aLine, |
3948 | 0 | floatAvailableSpace, availableSpaceBSize, |
3949 | 0 | &floatManagerState, aKeepReflowGoing, |
3950 | 0 | &lineReflowStatus, allowPullUp); |
3951 | 0 | lineLayout.EndLineReflow(); |
3952 | 0 |
|
3953 | 0 | if (LineReflowStatus::RedoNoPull == lineReflowStatus || |
3954 | 0 | LineReflowStatus::RedoMoreFloats == lineReflowStatus || |
3955 | 0 | LineReflowStatus::RedoNextBand == lineReflowStatus) { |
3956 | 0 | if (lineLayout.NeedsBackup()) { |
3957 | 0 | NS_ASSERTION(!forceBreakInFrame, "Backing up twice; this should never be necessary"); |
3958 | 0 | // If there is no saved break position, then this will set |
3959 | 0 | // set forceBreakInFrame to null and we won't back up, which is |
3960 | 0 | // correct. |
3961 | 0 | forceBreakInFrame = |
3962 | 0 | lineLayout.GetLastOptionalBreakPosition(&forceBreakOffset, &forceBreakPriority); |
3963 | 0 | } else { |
3964 | 0 | forceBreakInFrame = nullptr; |
3965 | 0 | } |
3966 | 0 | // restore the float manager state |
3967 | 0 | aState.FloatManager()->PopState(&floatManagerState); |
3968 | 0 | // Clear out float lists |
3969 | 0 | aState.mCurrentLineFloats.DeleteAll(); |
3970 | 0 | aState.mNoWrapFloats.Clear(); |
3971 | 0 | aState.mBelowCurrentLineFloats.DeleteAll(); |
3972 | 0 | } |
3973 | 0 |
|
3974 | 0 | // Don't allow pullup on a subsequent LineReflowStatus::RedoNoPull pass |
3975 | 0 | allowPullUp = false; |
3976 | 0 | } while (LineReflowStatus::RedoNoPull == lineReflowStatus); |
3977 | 0 | } while (LineReflowStatus::RedoMoreFloats == lineReflowStatus); |
3978 | 0 | } while (LineReflowStatus::RedoNextBand == lineReflowStatus); |
3979 | 0 | } |
3980 | | |
3981 | | void |
3982 | | nsBlockFrame::PushTruncatedLine(BlockReflowInput& aState, |
3983 | | LineIterator aLine, |
3984 | | bool* aKeepReflowGoing) |
3985 | 0 | { |
3986 | 0 | PushLines(aState, aLine.prev()); |
3987 | 0 | *aKeepReflowGoing = false; |
3988 | 0 | aState.mReflowStatus.SetIncomplete(); |
3989 | 0 | } |
3990 | | |
3991 | | void |
3992 | | nsBlockFrame::DoReflowInlineFrames(BlockReflowInput& aState, |
3993 | | nsLineLayout& aLineLayout, |
3994 | | LineIterator aLine, |
3995 | | nsFlowAreaRect& aFloatAvailableSpace, |
3996 | | nscoord& aAvailableSpaceBSize, |
3997 | | nsFloatManager::SavedState* |
3998 | | aFloatStateBeforeLine, |
3999 | | bool* aKeepReflowGoing, |
4000 | | LineReflowStatus* aLineReflowStatus, |
4001 | | bool aAllowPullUp) |
4002 | 0 | { |
4003 | 0 | // Forget all of the floats on the line |
4004 | 0 | aLine->FreeFloats(aState.mFloatCacheFreeList); |
4005 | 0 | aState.mFloatOverflowAreas.Clear(); |
4006 | 0 |
|
4007 | 0 | // We need to set this flag on the line if any of our reflow passes |
4008 | 0 | // are impacted by floats. |
4009 | 0 | if (aFloatAvailableSpace.HasFloats()) |
4010 | 0 | aLine->SetLineIsImpactedByFloat(true); |
4011 | | #ifdef REALLY_NOISY_REFLOW |
4012 | | printf("nsBlockFrame::DoReflowInlineFrames %p impacted = %d\n", |
4013 | | this, aFloatAvailableSpace.HasFloats()); |
4014 | | #endif |
4015 | |
|
4016 | 0 | WritingMode outerWM = aState.mReflowInput.GetWritingMode(); |
4017 | 0 | WritingMode lineWM = WritingModeForLine(outerWM, aLine->mFirstChild); |
4018 | 0 | LogicalRect lineRect = |
4019 | 0 | aFloatAvailableSpace.mRect.ConvertTo(lineWM, outerWM, |
4020 | 0 | aState.ContainerSize()); |
4021 | 0 |
|
4022 | 0 | nscoord iStart = lineRect.IStart(lineWM); |
4023 | 0 | nscoord availISize = lineRect.ISize(lineWM); |
4024 | 0 | nscoord availBSize; |
4025 | 0 | if (aState.mFlags.mHasUnconstrainedBSize) { |
4026 | 0 | availBSize = NS_UNCONSTRAINEDSIZE; |
4027 | 0 | } |
4028 | 0 | else { |
4029 | 0 | /* XXX get the height right! */ |
4030 | 0 | availBSize = lineRect.BSize(lineWM); |
4031 | 0 | } |
4032 | 0 |
|
4033 | 0 | // Make sure to enable resize optimization before we call BeginLineReflow |
4034 | 0 | // because it might get disabled there |
4035 | 0 | aLine->EnableResizeReflowOptimization(); |
4036 | 0 |
|
4037 | 0 | aLineLayout.BeginLineReflow(iStart, aState.mBCoord, |
4038 | 0 | availISize, availBSize, |
4039 | 0 | aFloatAvailableSpace.HasFloats(), |
4040 | 0 | false, /*XXX isTopOfPage*/ |
4041 | 0 | lineWM, aState.mContainerSize); |
4042 | 0 |
|
4043 | 0 | aState.mFlags.mIsLineLayoutEmpty = false; |
4044 | 0 |
|
4045 | 0 | // XXX Unfortunately we need to know this before reflowing the first |
4046 | 0 | // inline frame in the line. FIX ME. |
4047 | 0 | if ((0 == aLineLayout.GetLineNumber()) && |
4048 | 0 | (NS_BLOCK_HAS_FIRST_LETTER_CHILD & mState) && |
4049 | 0 | (NS_BLOCK_HAS_FIRST_LETTER_STYLE & mState)) { |
4050 | 0 | aLineLayout.SetFirstLetterStyleOK(true); |
4051 | 0 | } |
4052 | 0 | NS_ASSERTION(!((NS_BLOCK_HAS_FIRST_LETTER_CHILD & mState) && |
4053 | 0 | GetPrevContinuation()), |
4054 | 0 | "first letter child bit should only be on first continuation"); |
4055 | 0 |
|
4056 | 0 | // Reflow the frames that are already on the line first |
4057 | 0 | LineReflowStatus lineReflowStatus = LineReflowStatus::OK; |
4058 | 0 | int32_t i; |
4059 | 0 | nsIFrame* frame = aLine->mFirstChild; |
4060 | 0 |
|
4061 | 0 | if (aFloatAvailableSpace.HasFloats()) { |
4062 | 0 | // There is a soft break opportunity at the start of the line, because |
4063 | 0 | // we can always move this line down below float(s). |
4064 | 0 | if (aLineLayout.NotifyOptionalBreakPosition( |
4065 | 0 | frame, 0, true, gfxBreakPriority::eNormalBreak)) { |
4066 | 0 | lineReflowStatus = LineReflowStatus::RedoNextBand; |
4067 | 0 | } |
4068 | 0 | } |
4069 | 0 |
|
4070 | 0 | // need to repeatedly call GetChildCount here, because the child |
4071 | 0 | // count can change during the loop! |
4072 | 0 | for (i = 0; LineReflowStatus::OK == lineReflowStatus && i < aLine->GetChildCount(); |
4073 | 0 | i++, frame = frame->GetNextSibling()) { |
4074 | 0 | ReflowInlineFrame(aState, aLineLayout, aLine, frame, &lineReflowStatus); |
4075 | 0 | if (LineReflowStatus::OK != lineReflowStatus) { |
4076 | 0 | // It is possible that one or more of next lines are empty |
4077 | 0 | // (because of DeleteNextInFlowChild). If so, delete them now |
4078 | 0 | // in case we are finished. |
4079 | 0 | ++aLine; |
4080 | 0 | while ((aLine != LinesEnd()) && (0 == aLine->GetChildCount())) { |
4081 | 0 | // XXX Is this still necessary now that DeleteNextInFlowChild |
4082 | 0 | // uses DoRemoveFrame? |
4083 | 0 | nsLineBox *toremove = aLine; |
4084 | 0 | aLine = mLines.erase(aLine); |
4085 | 0 | NS_ASSERTION(nullptr == toremove->mFirstChild, "bad empty line"); |
4086 | 0 | FreeLineBox(toremove); |
4087 | 0 | } |
4088 | 0 | --aLine; |
4089 | 0 |
|
4090 | 0 | NS_ASSERTION(lineReflowStatus != LineReflowStatus::Truncated, |
4091 | 0 | "ReflowInlineFrame should never determine that a line " |
4092 | 0 | "needs to go to the next page/column"); |
4093 | 0 | } |
4094 | 0 | } |
4095 | 0 |
|
4096 | 0 | // Don't pull up new frames into lines with continuation placeholders |
4097 | 0 | if (aAllowPullUp) { |
4098 | 0 | // Pull frames and reflow them until we can't |
4099 | 0 | while (LineReflowStatus::OK == lineReflowStatus) { |
4100 | 0 | frame = PullFrame(aState, aLine); |
4101 | 0 | if (!frame) { |
4102 | 0 | break; |
4103 | 0 | } |
4104 | 0 | |
4105 | 0 | while (LineReflowStatus::OK == lineReflowStatus) { |
4106 | 0 | int32_t oldCount = aLine->GetChildCount(); |
4107 | 0 | ReflowInlineFrame(aState, aLineLayout, aLine, frame, &lineReflowStatus); |
4108 | 0 | if (aLine->GetChildCount() != oldCount) { |
4109 | 0 | // We just created a continuation for aFrame AND its going |
4110 | 0 | // to end up on this line (e.g. :first-letter |
4111 | 0 | // situation). Therefore we have to loop here before trying |
4112 | 0 | // to pull another frame. |
4113 | 0 | frame = frame->GetNextSibling(); |
4114 | 0 | } |
4115 | 0 | else { |
4116 | 0 | break; |
4117 | 0 | } |
4118 | 0 | } |
4119 | 0 | } |
4120 | 0 | } |
4121 | 0 |
|
4122 | 0 | aState.mFlags.mIsLineLayoutEmpty = aLineLayout.LineIsEmpty(); |
4123 | 0 |
|
4124 | 0 | // We only need to backup if the line isn't going to be reflowed again anyway |
4125 | 0 | bool needsBackup = aLineLayout.NeedsBackup() && |
4126 | 0 | (lineReflowStatus == LineReflowStatus::Stop || |
4127 | 0 | lineReflowStatus == LineReflowStatus::OK); |
4128 | 0 | if (needsBackup && aLineLayout.HaveForcedBreakPosition()) { |
4129 | 0 | NS_WARNING("We shouldn't be backing up more than once! " |
4130 | 0 | "Someone must have set a break opportunity beyond the available width, " |
4131 | 0 | "even though there were better break opportunities before it"); |
4132 | 0 | needsBackup = false; |
4133 | 0 | } |
4134 | 0 | if (needsBackup) { |
4135 | 0 | // We need to try backing up to before a text run |
4136 | 0 | // XXX It's possible, in fact not unusual, for the break opportunity to already |
4137 | 0 | // be the end of the line. We should detect that and optimize to not |
4138 | 0 | // re-do the line. |
4139 | 0 | if (aLineLayout.HasOptionalBreakPosition()) { |
4140 | 0 | // We can back up! |
4141 | 0 | lineReflowStatus = LineReflowStatus::RedoNoPull; |
4142 | 0 | } |
4143 | 0 | } else { |
4144 | 0 | // In case we reflow this line again, remember that we don't |
4145 | 0 | // need to force any breaking |
4146 | 0 | aLineLayout.ClearOptionalBreakPosition(); |
4147 | 0 | } |
4148 | 0 |
|
4149 | 0 | if (LineReflowStatus::RedoNextBand == lineReflowStatus) { |
4150 | 0 | // This happens only when we have a line that is impacted by |
4151 | 0 | // floats and the first element in the line doesn't fit with |
4152 | 0 | // the floats. |
4153 | 0 | // |
4154 | 0 | // If there's block space available, we either try to reflow the line |
4155 | 0 | // past the current band (if it's non-zero and the band definitely won't |
4156 | 0 | // widen around a shape-outside), otherwise we try one pixel down. If |
4157 | 0 | // there's no block space available, we push the line to the next |
4158 | 0 | // page/column. |
4159 | 0 | NS_ASSERTION(NS_UNCONSTRAINEDSIZE != |
4160 | 0 | aFloatAvailableSpace.mRect.BSize(outerWM), |
4161 | 0 | "unconstrained block size on totally empty line"); |
4162 | 0 |
|
4163 | 0 | // See the analogous code for blocks in BlockReflowInput::ClearFloats. |
4164 | 0 | nscoord bandBSize = aFloatAvailableSpace.mRect.BSize(outerWM); |
4165 | 0 | if (bandBSize > 0 || |
4166 | 0 | NS_UNCONSTRAINEDSIZE == aState.mReflowInput.AvailableBSize()) { |
4167 | 0 | NS_ASSERTION(bandBSize == 0 || aFloatAvailableSpace.HasFloats(), |
4168 | 0 | "redo line on totally empty line with non-empty band..."); |
4169 | 0 | // We should never hit this case if we've placed floats on the |
4170 | 0 | // line; if we have, then the GetFloatAvailableSpace call is wrong |
4171 | 0 | // and needs to happen after the caller pops the float manager |
4172 | 0 | // state. |
4173 | 0 | aState.FloatManager()->AssertStateMatches(aFloatStateBeforeLine); |
4174 | 0 |
|
4175 | 0 | if (!aFloatAvailableSpace.MayWiden() && bandBSize > 0) { |
4176 | 0 | // Move it down far enough to clear the current band. |
4177 | 0 | aState.mBCoord += bandBSize; |
4178 | 0 | } else { |
4179 | 0 | // Move it down by one dev pixel. |
4180 | 0 | aState.mBCoord += aState.mPresContext->DevPixelsToAppUnits(1); |
4181 | 0 | } |
4182 | 0 |
|
4183 | 0 | aFloatAvailableSpace = aState.GetFloatAvailableSpace(); |
4184 | 0 | } else { |
4185 | 0 | // There's nowhere to retry placing the line, so we want to push |
4186 | 0 | // it to the next page/column where its contents can fit not |
4187 | 0 | // next to a float. |
4188 | 0 | lineReflowStatus = LineReflowStatus::Truncated; |
4189 | 0 | PushTruncatedLine(aState, aLine, aKeepReflowGoing); |
4190 | 0 | } |
4191 | 0 |
|
4192 | 0 | // XXX: a small optimization can be done here when paginating: |
4193 | 0 | // if the new Y coordinate is past the end of the block then |
4194 | 0 | // push the line and return now instead of later on after we are |
4195 | 0 | // past the float. |
4196 | 0 | } |
4197 | 0 | else if (LineReflowStatus::Truncated != lineReflowStatus && |
4198 | 0 | LineReflowStatus::RedoNoPull != lineReflowStatus) { |
4199 | 0 | // If we are propagating out a break-before status then there is |
4200 | 0 | // no point in placing the line. |
4201 | 0 | if (!aState.mReflowStatus.IsInlineBreakBefore()) { |
4202 | 0 | if (!PlaceLine(aState, aLineLayout, aLine, aFloatStateBeforeLine, |
4203 | 0 | aFloatAvailableSpace, aAvailableSpaceBSize, |
4204 | 0 | aKeepReflowGoing)) { |
4205 | 0 | lineReflowStatus = LineReflowStatus::RedoMoreFloats; |
4206 | 0 | // PlaceLine already called GetAvailableSpaceForBSize for us. |
4207 | 0 | } |
4208 | 0 | } |
4209 | 0 | } |
4210 | | #ifdef DEBUG |
4211 | | if (gNoisyReflow) { |
4212 | | printf("Line reflow status = %s\n", LineReflowStatusToString(lineReflowStatus)); |
4213 | | } |
4214 | | #endif |
4215 | |
|
4216 | 0 | if (aLineLayout.GetDirtyNextLine()) { |
4217 | 0 | // aLine may have been pushed to the overflow lines. |
4218 | 0 | FrameLines* overflowLines = GetOverflowLines(); |
4219 | 0 | // We can't just compare iterators front() to aLine here, since they may be in |
4220 | 0 | // different lists. |
4221 | 0 | bool pushedToOverflowLines = overflowLines && |
4222 | 0 | overflowLines->mLines.front() == aLine.get(); |
4223 | 0 | if (pushedToOverflowLines) { |
4224 | 0 | // aLine is stale, it's associated with the main line list but it should |
4225 | 0 | // be associated with the overflow line list now |
4226 | 0 | aLine = overflowLines->mLines.begin(); |
4227 | 0 | } |
4228 | 0 | nsBlockInFlowLineIterator iter(this, aLine, pushedToOverflowLines); |
4229 | 0 | if (iter.Next() && iter.GetLine()->IsInline()) { |
4230 | 0 | iter.GetLine()->MarkDirty(); |
4231 | 0 | if (iter.GetContainer() != this) { |
4232 | 0 | aState.mReflowStatus.SetNextInFlowNeedsReflow(); |
4233 | 0 | } |
4234 | 0 | } |
4235 | 0 | } |
4236 | 0 |
|
4237 | 0 | *aLineReflowStatus = lineReflowStatus; |
4238 | 0 | } |
4239 | | |
4240 | | /** |
4241 | | * Reflow an inline frame. The reflow status is mapped from the frames |
4242 | | * reflow status to the lines reflow status (not to our reflow status). |
4243 | | * The line reflow status is simple: true means keep placing frames |
4244 | | * on the line; false means don't (the line is done). If the line |
4245 | | * has some sort of breaking affect then aLine's break-type will be set |
4246 | | * to something other than StyleClear::None. |
4247 | | */ |
4248 | | void |
4249 | | nsBlockFrame::ReflowInlineFrame(BlockReflowInput& aState, |
4250 | | nsLineLayout& aLineLayout, |
4251 | | LineIterator aLine, |
4252 | | nsIFrame* aFrame, |
4253 | | LineReflowStatus* aLineReflowStatus) |
4254 | 0 | { |
4255 | 0 | MOZ_ASSERT(aFrame); |
4256 | 0 | *aLineReflowStatus = LineReflowStatus::OK; |
4257 | 0 |
|
4258 | | #ifdef NOISY_FIRST_LETTER |
4259 | | ListTag(stdout); |
4260 | | printf(": reflowing "); |
4261 | | nsFrame::ListTag(stdout, aFrame); |
4262 | | printf(" reflowingFirstLetter=%s\n", |
4263 | | aLineLayout.GetFirstLetterStyleOK() ? "on" : "off"); |
4264 | | #endif |
4265 | |
|
4266 | 0 | if (aFrame->IsPlaceholderFrame()) { |
4267 | 0 | auto ph = static_cast<nsPlaceholderFrame*>(aFrame); |
4268 | 0 | ph->ForgetLineIsEmptySoFar(); |
4269 | 0 | } |
4270 | 0 |
|
4271 | 0 | // Reflow the inline frame |
4272 | 0 | nsReflowStatus frameReflowStatus; |
4273 | 0 | bool pushedFrame; |
4274 | 0 | aLineLayout.ReflowFrame(aFrame, frameReflowStatus, nullptr, pushedFrame); |
4275 | 0 |
|
4276 | 0 | if (frameReflowStatus.NextInFlowNeedsReflow()) { |
4277 | 0 | aLineLayout.SetDirtyNextLine(); |
4278 | 0 | } |
4279 | 0 |
|
4280 | | #ifdef REALLY_NOISY_REFLOW |
4281 | | nsFrame::ListTag(stdout, aFrame); |
4282 | | printf(": status=%s\n", ToString(frameReflowStatus).c_str()); |
4283 | | #endif |
4284 | |
|
4285 | | #if defined(REFLOW_STATUS_COVERAGE) |
4286 | | RecordReflowStatus(false, frameReflowStatus); |
4287 | | #endif |
4288 | |
|
4289 | 0 | // Send post-reflow notification |
4290 | 0 | aState.mPrevChild = aFrame; |
4291 | 0 |
|
4292 | 0 | /* XXX |
4293 | 0 | This is where we need to add logic to handle some odd behavior. |
4294 | 0 | For one thing, we should usually place at least one thing next |
4295 | 0 | to a left float, even when that float takes up all the width on a line. |
4296 | 0 | see bug 22496 |
4297 | 0 | */ |
4298 | 0 |
|
4299 | 0 | // Process the child frames reflow status. There are 5 cases: |
4300 | 0 | // complete, not-complete, break-before, break-after-complete, |
4301 | 0 | // break-after-not-complete. There are two situations: we are a |
4302 | 0 | // block or we are an inline. This makes a total of 10 cases |
4303 | 0 | // (fortunately, there is some overlap). |
4304 | 0 | aLine->SetBreakTypeAfter(StyleClear::None); |
4305 | 0 | if (frameReflowStatus.IsInlineBreak() || |
4306 | 0 | StyleClear::None != aState.mFloatBreakType) { |
4307 | 0 | // Always abort the line reflow (because a line break is the |
4308 | 0 | // minimal amount of break we do). |
4309 | 0 | *aLineReflowStatus = LineReflowStatus::Stop; |
4310 | 0 |
|
4311 | 0 | // XXX what should aLine's break-type be set to in all these cases? |
4312 | 0 | StyleClear breakType = frameReflowStatus.BreakType(); |
4313 | 0 | MOZ_ASSERT(StyleClear::None != breakType || |
4314 | 0 | StyleClear::None != aState.mFloatBreakType, "bad break type"); |
4315 | 0 |
|
4316 | 0 | if (frameReflowStatus.IsInlineBreakBefore()) { |
4317 | 0 | // Break-before cases. |
4318 | 0 | if (aFrame == aLine->mFirstChild) { |
4319 | 0 | // If we break before the first frame on the line then we must |
4320 | 0 | // be trying to place content where there's no room (e.g. on a |
4321 | 0 | // line with wide floats). Inform the caller to reflow the |
4322 | 0 | // line after skipping past a float. |
4323 | 0 | *aLineReflowStatus = LineReflowStatus::RedoNextBand; |
4324 | 0 | } |
4325 | 0 | else { |
4326 | 0 | // It's not the first child on this line so go ahead and split |
4327 | 0 | // the line. We will see the frame again on the next-line. |
4328 | 0 | SplitLine(aState, aLineLayout, aLine, aFrame, aLineReflowStatus); |
4329 | 0 |
|
4330 | 0 | // If we're splitting the line because the frame didn't fit and it |
4331 | 0 | // was pushed, then mark the line as having word wrapped. We need to |
4332 | 0 | // know that if we're shrink wrapping our width |
4333 | 0 | if (pushedFrame) { |
4334 | 0 | aLine->SetLineWrapped(true); |
4335 | 0 | } |
4336 | 0 | } |
4337 | 0 | } |
4338 | 0 | else { |
4339 | 0 | // If a float split and its prev-in-flow was followed by a <BR>, then combine |
4340 | 0 | // the <BR>'s break type with the inline's break type (the inline will be the very |
4341 | 0 | // next frame after the split float). |
4342 | 0 | if (StyleClear::None != aState.mFloatBreakType) { |
4343 | 0 | breakType = nsLayoutUtils::CombineBreakType(breakType, |
4344 | 0 | aState.mFloatBreakType); |
4345 | 0 | aState.mFloatBreakType = StyleClear::None; |
4346 | 0 | } |
4347 | 0 | // Break-after cases |
4348 | 0 | if (breakType == StyleClear::Line) { |
4349 | 0 | if (!aLineLayout.GetLineEndsInBR()) { |
4350 | 0 | breakType = StyleClear::None; |
4351 | 0 | } |
4352 | 0 | } |
4353 | 0 | aLine->SetBreakTypeAfter(breakType); |
4354 | 0 | if (frameReflowStatus.IsComplete()) { |
4355 | 0 | // Split line, but after the frame just reflowed |
4356 | 0 | SplitLine(aState, aLineLayout, aLine, aFrame->GetNextSibling(), aLineReflowStatus); |
4357 | 0 |
|
4358 | 0 | if (frameReflowStatus.IsInlineBreakAfter() && |
4359 | 0 | !aLineLayout.GetLineEndsInBR()) { |
4360 | 0 | aLineLayout.SetDirtyNextLine(); |
4361 | 0 | } |
4362 | 0 | } |
4363 | 0 | } |
4364 | 0 | } |
4365 | 0 |
|
4366 | 0 | if (!frameReflowStatus.IsFullyComplete()) { |
4367 | 0 | // Create a continuation for the incomplete frame. Note that the |
4368 | 0 | // frame may already have a continuation. |
4369 | 0 | CreateContinuationFor(aState, aLine, aFrame); |
4370 | 0 |
|
4371 | 0 | // Remember that the line has wrapped |
4372 | 0 | if (!aLineLayout.GetLineEndsInBR()) { |
4373 | 0 | aLine->SetLineWrapped(true); |
4374 | 0 | } |
4375 | 0 |
|
4376 | 0 | // If we just ended a first-letter frame or reflowed a placeholder then |
4377 | 0 | // don't split the line and don't stop the line reflow... |
4378 | 0 | // But if we are going to stop anyways we'd better split the line. |
4379 | 0 | if ((!frameReflowStatus.FirstLetterComplete() && |
4380 | 0 | !aFrame->IsPlaceholderFrame()) || |
4381 | 0 | *aLineReflowStatus == LineReflowStatus::Stop) { |
4382 | 0 | // Split line after the current frame |
4383 | 0 | *aLineReflowStatus = LineReflowStatus::Stop; |
4384 | 0 | SplitLine(aState, aLineLayout, aLine, aFrame->GetNextSibling(), aLineReflowStatus); |
4385 | 0 | } |
4386 | 0 | } |
4387 | 0 | } |
4388 | | |
4389 | | bool |
4390 | | nsBlockFrame::CreateContinuationFor(BlockReflowInput& aState, |
4391 | | nsLineBox* aLine, |
4392 | | nsIFrame* aFrame) |
4393 | 0 | { |
4394 | 0 | nsIFrame* newFrame = nullptr; |
4395 | 0 |
|
4396 | 0 | if (!aFrame->GetNextInFlow()) { |
4397 | 0 | newFrame = aState.mPresContext->PresShell()->FrameConstructor()-> |
4398 | 0 | CreateContinuingFrame(aState.mPresContext, aFrame, this); |
4399 | 0 |
|
4400 | 0 | mFrames.InsertFrame(nullptr, aFrame, newFrame); |
4401 | 0 |
|
4402 | 0 | if (aLine) { |
4403 | 0 | aLine->NoteFrameAdded(newFrame); |
4404 | 0 | } |
4405 | 0 | } |
4406 | | #ifdef DEBUG |
4407 | | VerifyLines(false); |
4408 | | #endif |
4409 | | return !!newFrame; |
4410 | 0 | } |
4411 | | |
4412 | | void |
4413 | | nsBlockFrame::SplitFloat(BlockReflowInput& aState, |
4414 | | nsIFrame* aFloat, |
4415 | | const nsReflowStatus& aFloatStatus) |
4416 | 0 | { |
4417 | 0 | MOZ_ASSERT(!aFloatStatus.IsFullyComplete(), |
4418 | 0 | "why split the frame if it's fully complete?"); |
4419 | 0 | MOZ_ASSERT(aState.mBlock == this); |
4420 | 0 |
|
4421 | 0 | nsIFrame* nextInFlow = aFloat->GetNextInFlow(); |
4422 | 0 | if (nextInFlow) { |
4423 | 0 | nsContainerFrame *oldParent = nextInFlow->GetParent(); |
4424 | 0 | DebugOnly<nsresult> rv = oldParent->StealFrame(nextInFlow); |
4425 | 0 | NS_ASSERTION(NS_SUCCEEDED(rv), "StealFrame failed"); |
4426 | 0 | if (oldParent != this) { |
4427 | 0 | ReparentFrame(nextInFlow, oldParent, this, ReparentingDirection::Backwards); |
4428 | 0 | } |
4429 | 0 | if (!aFloatStatus.IsOverflowIncomplete()) { |
4430 | 0 | nextInFlow->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER); |
4431 | 0 | } |
4432 | 0 | } else { |
4433 | 0 | nextInFlow = aState.mPresContext->PresShell()->FrameConstructor()-> |
4434 | 0 | CreateContinuingFrame(aState.mPresContext, aFloat, this); |
4435 | 0 | } |
4436 | 0 | if (aFloatStatus.IsOverflowIncomplete()) { |
4437 | 0 | nextInFlow->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER); |
4438 | 0 | } |
4439 | 0 |
|
4440 | 0 | StyleFloat floatStyle = aFloat->StyleDisplay()->mFloat; |
4441 | 0 | if (floatStyle == StyleFloat::Left) { |
4442 | 0 | aState.FloatManager()->SetSplitLeftFloatAcrossBreak(); |
4443 | 0 | } else { |
4444 | 0 | MOZ_ASSERT(floatStyle == StyleFloat::Right, "Unexpected float side!"); |
4445 | 0 | aState.FloatManager()->SetSplitRightFloatAcrossBreak(); |
4446 | 0 | } |
4447 | 0 |
|
4448 | 0 | aState.AppendPushedFloatChain(nextInFlow); |
4449 | 0 | aState.mReflowStatus.SetOverflowIncomplete(); |
4450 | 0 | } |
4451 | | |
4452 | | static nsFloatCache* |
4453 | | GetLastFloat(nsLineBox* aLine) |
4454 | 0 | { |
4455 | 0 | nsFloatCache* fc = aLine->GetFirstFloat(); |
4456 | 0 | while (fc && fc->Next()) { |
4457 | 0 | fc = fc->Next(); |
4458 | 0 | } |
4459 | 0 | return fc; |
4460 | 0 | } |
4461 | | |
4462 | | static bool |
4463 | | CheckPlaceholderInLine(nsIFrame* aBlock, nsLineBox* aLine, nsFloatCache* aFC) |
4464 | 0 | { |
4465 | 0 | if (!aFC) |
4466 | 0 | return true; |
4467 | 0 | NS_ASSERTION(!aFC->mFloat->GetPrevContinuation(), |
4468 | 0 | "float in a line should never be a continuation"); |
4469 | 0 | NS_ASSERTION(!(aFC->mFloat->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT), |
4470 | 0 | "float in a line should never be a pushed float"); |
4471 | 0 | nsIFrame* ph = aFC->mFloat->FirstInFlow()->GetPlaceholderFrame(); |
4472 | 0 | for (nsIFrame* f = ph; f; f = f->GetParent()) { |
4473 | 0 | if (f->GetParent() == aBlock) |
4474 | 0 | return aLine->Contains(f); |
4475 | 0 | } |
4476 | 0 | NS_ASSERTION(false, "aBlock is not an ancestor of aFrame!"); |
4477 | 0 | return true; |
4478 | 0 | } |
4479 | | |
4480 | | void |
4481 | | nsBlockFrame::SplitLine(BlockReflowInput& aState, |
4482 | | nsLineLayout& aLineLayout, |
4483 | | LineIterator aLine, |
4484 | | nsIFrame* aFrame, |
4485 | | LineReflowStatus* aLineReflowStatus) |
4486 | 0 | { |
4487 | 0 | MOZ_ASSERT(aLine->IsInline(), "illegal SplitLine on block line"); |
4488 | 0 |
|
4489 | 0 | int32_t pushCount = aLine->GetChildCount() - aLineLayout.GetCurrentSpanCount(); |
4490 | 0 | MOZ_ASSERT(pushCount >= 0, "bad push count"); |
4491 | 0 |
|
4492 | | #ifdef DEBUG |
4493 | | if (gNoisyReflow) { |
4494 | | nsFrame::IndentBy(stdout, gNoiseIndent); |
4495 | | printf("split line: from line=%p pushCount=%d aFrame=", |
4496 | | static_cast<void*>(aLine.get()), pushCount); |
4497 | | if (aFrame) { |
4498 | | nsFrame::ListTag(stdout, aFrame); |
4499 | | } |
4500 | | else { |
4501 | | printf("(null)"); |
4502 | | } |
4503 | | printf("\n"); |
4504 | | if (gReallyNoisyReflow) { |
4505 | | aLine->List(stdout, gNoiseIndent+1); |
4506 | | } |
4507 | | } |
4508 | | #endif |
4509 | |
|
4510 | 0 | if (0 != pushCount) { |
4511 | 0 | MOZ_ASSERT(aLine->GetChildCount() > pushCount, "bad push"); |
4512 | 0 | MOZ_ASSERT(nullptr != aFrame, "whoops"); |
4513 | | #ifdef DEBUG |
4514 | | { |
4515 | | nsIFrame *f = aFrame; |
4516 | | int32_t count = pushCount; |
4517 | | while (f && count > 0) { |
4518 | | f = f->GetNextSibling(); |
4519 | | --count; |
4520 | | } |
4521 | | NS_ASSERTION(count == 0, "Not enough frames to push"); |
4522 | | } |
4523 | | #endif |
4524 | |
|
4525 | 0 | // Put frames being split out into their own line |
4526 | 0 | nsLineBox* newLine = NewLineBox(aLine, aFrame, pushCount); |
4527 | 0 | mLines.after_insert(aLine, newLine); |
4528 | | #ifdef DEBUG |
4529 | | if (gReallyNoisyReflow) { |
4530 | | newLine->List(stdout, gNoiseIndent+1); |
4531 | | } |
4532 | | #endif |
4533 | |
|
4534 | 0 | // Let line layout know that some frames are no longer part of its |
4535 | 0 | // state. |
4536 | 0 | aLineLayout.SplitLineTo(aLine->GetChildCount()); |
4537 | 0 |
|
4538 | 0 | // If floats have been placed whose placeholders have been pushed to the new |
4539 | 0 | // line, we need to reflow the old line again. We don't want to look at the |
4540 | 0 | // frames in the new line, because as a large paragraph is laid out the |
4541 | 0 | // we'd get O(N^2) performance. So instead we just check that the last |
4542 | 0 | // float and the last below-current-line float are still in aLine. |
4543 | 0 | if (!CheckPlaceholderInLine(this, aLine, GetLastFloat(aLine)) || |
4544 | 0 | !CheckPlaceholderInLine(this, aLine, aState.mBelowCurrentLineFloats.Tail())) { |
4545 | 0 | *aLineReflowStatus = LineReflowStatus::RedoNoPull; |
4546 | 0 | } |
4547 | 0 |
|
4548 | | #ifdef DEBUG |
4549 | | VerifyLines(true); |
4550 | | #endif |
4551 | | } |
4552 | 0 | } |
4553 | | |
4554 | | bool |
4555 | | nsBlockFrame::IsLastLine(BlockReflowInput& aState, |
4556 | | LineIterator aLine) |
4557 | 0 | { |
4558 | 0 | while (++aLine != LinesEnd()) { |
4559 | 0 | // There is another line |
4560 | 0 | if (0 != aLine->GetChildCount()) { |
4561 | 0 | // If the next line is a block line then this line is the last in a |
4562 | 0 | // group of inline lines. |
4563 | 0 | return aLine->IsBlock(); |
4564 | 0 | } |
4565 | 0 | // The next line is empty, try the next one |
4566 | 0 | } |
4567 | 0 |
|
4568 | 0 | // XXX Not sure about this part |
4569 | 0 | // Try our next-in-flows lines to answer the question |
4570 | 0 | nsBlockFrame* nextInFlow = (nsBlockFrame*) GetNextInFlow(); |
4571 | 0 | while (nullptr != nextInFlow) { |
4572 | 0 | for (LineIterator line = nextInFlow->LinesBegin(), |
4573 | 0 | line_end = nextInFlow->LinesEnd(); |
4574 | 0 | line != line_end; |
4575 | 0 | ++line) |
4576 | 0 | { |
4577 | 0 | if (0 != line->GetChildCount()) |
4578 | 0 | return line->IsBlock(); |
4579 | 0 | } |
4580 | 0 | nextInFlow = (nsBlockFrame*) nextInFlow->GetNextInFlow(); |
4581 | 0 | } |
4582 | 0 |
|
4583 | 0 | // This is the last line - so don't allow justification |
4584 | 0 | return true; |
4585 | 0 | } |
4586 | | |
4587 | | bool |
4588 | | nsBlockFrame::PlaceLine(BlockReflowInput& aState, |
4589 | | nsLineLayout& aLineLayout, |
4590 | | LineIterator aLine, |
4591 | | nsFloatManager::SavedState *aFloatStateBeforeLine, |
4592 | | nsFlowAreaRect& aFlowArea, |
4593 | | nscoord& aAvailableSpaceBSize, |
4594 | | bool* aKeepReflowGoing) |
4595 | 0 | { |
4596 | 0 | // Try to position the floats in a nowrap context. |
4597 | 0 | aLineLayout.FlushNoWrapFloats(); |
4598 | 0 |
|
4599 | 0 | // Trim extra white-space from the line before placing the frames |
4600 | 0 | aLineLayout.TrimTrailingWhiteSpace(); |
4601 | 0 |
|
4602 | 0 | // Vertically align the frames on this line. |
4603 | 0 | // |
4604 | 0 | // According to the CSS2 spec, section 12.6.1, the "marker" box |
4605 | 0 | // participates in the height calculation of the list-item box's |
4606 | 0 | // first line box. |
4607 | 0 | // |
4608 | 0 | // There are exactly two places a bullet can be placed: near the |
4609 | 0 | // first or second line. It's only placed on the second line in a |
4610 | 0 | // rare case: when the first line is empty. |
4611 | 0 | WritingMode wm = aState.mReflowInput.GetWritingMode(); |
4612 | 0 | bool addedBullet = false; |
4613 | 0 | if (HasOutsideBullet() && |
4614 | 0 | ((aLine == mLines.front() && |
4615 | 0 | (!aLineLayout.IsZeroBSize() || (aLine == mLines.back()))) || |
4616 | 0 | (mLines.front() != mLines.back() && |
4617 | 0 | 0 == mLines.front()->BSize() && |
4618 | 0 | aLine == mLines.begin().next()))) { |
4619 | 0 | ReflowOutput metrics(aState.mReflowInput); |
4620 | 0 | nsBulletFrame* bullet = GetOutsideBullet(); |
4621 | 0 | ReflowBullet(bullet, aState, metrics, aState.mBCoord); |
4622 | 0 | NS_ASSERTION(!BulletIsEmpty() || metrics.BSize(wm) == 0, |
4623 | 0 | "empty bullet took up space"); |
4624 | 0 | aLineLayout.AddBulletFrame(bullet, metrics); |
4625 | 0 | addedBullet = true; |
4626 | 0 | } |
4627 | 0 | aLineLayout.VerticalAlignLine(); |
4628 | 0 |
|
4629 | 0 | // We want to consider the floats in the current line when determining |
4630 | 0 | // whether the float available space is shrunk. If mLineBSize doesn't |
4631 | 0 | // exist, we are in the first pass trying to place the line. Calling |
4632 | 0 | // GetFloatAvailableSpace() like we did in BlockReflowInput::AddFloat() |
4633 | 0 | // for UpdateBand(). |
4634 | 0 |
|
4635 | 0 | // floatAvailableSpaceWithOldLineBSize is the float available space with |
4636 | 0 | // the old BSize, but including the floats that were added in this line. |
4637 | 0 | LogicalRect floatAvailableSpaceWithOldLineBSize = |
4638 | 0 | aState.mLineBSize.isNothing() |
4639 | 0 | ? aState.GetFloatAvailableSpace(aLine->BStart()).mRect |
4640 | 0 | : aState.GetFloatAvailableSpaceForBSize(aLine->BStart(), |
4641 | 0 | aState.mLineBSize.value(), |
4642 | 0 | nullptr).mRect; |
4643 | 0 |
|
4644 | 0 | // As we redo for floats, we can't reduce the amount of BSize we're |
4645 | 0 | // checking. |
4646 | 0 | aAvailableSpaceBSize = std::max(aAvailableSpaceBSize, aLine->BSize()); |
4647 | 0 | LogicalRect floatAvailableSpaceWithLineBSize = |
4648 | 0 | aState.GetFloatAvailableSpaceForBSize(aLine->BStart(), |
4649 | 0 | aAvailableSpaceBSize, |
4650 | 0 | nullptr).mRect; |
4651 | 0 |
|
4652 | 0 | // If the available space between the floats is smaller now that we |
4653 | 0 | // know the BSize, return false (and cause another pass with |
4654 | 0 | // LineReflowStatus::RedoMoreFloats). We ensure aAvailableSpaceBSize |
4655 | 0 | // never decreases, which means that we can't reduce the set of floats |
4656 | 0 | // we intersect, which means that the available space cannot grow. |
4657 | 0 | if (AvailableSpaceShrunk(wm, floatAvailableSpaceWithOldLineBSize, |
4658 | 0 | floatAvailableSpaceWithLineBSize, false)) { |
4659 | 0 | // Prepare data for redoing the line. |
4660 | 0 | aState.mLineBSize = Some(aLine->BSize()); |
4661 | 0 |
|
4662 | 0 | // Since we want to redo the line, we update aFlowArea by using the |
4663 | 0 | // aFloatStateBeforeLine, which is the float manager's state before the |
4664 | 0 | // line is placed. |
4665 | 0 | LogicalRect oldFloatAvailableSpace(aFlowArea.mRect); |
4666 | 0 | aFlowArea = aState.GetFloatAvailableSpaceForBSize(aLine->BStart(), |
4667 | 0 | aAvailableSpaceBSize, |
4668 | 0 | aFloatStateBeforeLine); |
4669 | 0 |
|
4670 | 0 | NS_ASSERTION(aFlowArea.mRect.BStart(wm) == |
4671 | 0 | oldFloatAvailableSpace.BStart(wm), "yikes"); |
4672 | 0 | // Restore the BSize to the position of the next band. |
4673 | 0 | aFlowArea.mRect.BSize(wm) = oldFloatAvailableSpace.BSize(wm); |
4674 | 0 |
|
4675 | 0 | // Enforce both IStart() and IEnd() never move outwards to prevent |
4676 | 0 | // infinite grow-shrink loops. |
4677 | 0 | const nscoord iStartDiff = |
4678 | 0 | aFlowArea.mRect.IStart(wm) - oldFloatAvailableSpace.IStart(wm); |
4679 | 0 | const nscoord iEndDiff = |
4680 | 0 | aFlowArea.mRect.IEnd(wm) - oldFloatAvailableSpace.IEnd(wm); |
4681 | 0 | if (iStartDiff < 0) { |
4682 | 0 | aFlowArea.mRect.IStart(wm) -= iStartDiff; |
4683 | 0 | aFlowArea.mRect.ISize(wm) += iStartDiff; |
4684 | 0 | } |
4685 | 0 | if (iEndDiff > 0) { |
4686 | 0 | aFlowArea.mRect.ISize(wm) -= iEndDiff; |
4687 | 0 | } |
4688 | 0 |
|
4689 | 0 | return false; |
4690 | 0 | } |
4691 | 0 |
|
4692 | | #ifdef DEBUG |
4693 | | if (!GetParent()->IsCrazySizeAssertSuppressed()) { |
4694 | | static nscoord lastHeight = 0; |
4695 | | if (CRAZY_SIZE(aLine->BStart())) { |
4696 | | lastHeight = aLine->BStart(); |
4697 | | if (abs(aLine->BStart() - lastHeight) > CRAZY_COORD/10) { |
4698 | | nsFrame::ListTag(stdout); |
4699 | | printf(": line=%p y=%d line.bounds.height=%d\n", |
4700 | | static_cast<void*>(aLine.get()), |
4701 | | aLine->BStart(), aLine->BSize()); |
4702 | | } |
4703 | | } |
4704 | | else { |
4705 | | lastHeight = 0; |
4706 | | } |
4707 | | } |
4708 | | #endif |
4709 | | |
4710 | 0 | // Only block frames horizontally align their children because |
4711 | 0 | // inline frames "shrink-wrap" around their children (therefore |
4712 | 0 | // there is no extra horizontal space). |
4713 | 0 | const nsStyleText* styleText = StyleText(); |
4714 | 0 |
|
4715 | 0 | /** |
4716 | 0 | * text-align-last defaults to the same value as text-align when |
4717 | 0 | * text-align-last is set to auto (except when text-align is set to justify), |
4718 | 0 | * so in that case we don't need to set isLastLine. |
4719 | 0 | * |
4720 | 0 | * In other words, isLastLine really means isLastLineAndWeCare. |
4721 | 0 | */ |
4722 | 0 | bool isLastLine = |
4723 | 0 | !nsSVGUtils::IsInSVGTextSubtree(this) && |
4724 | 0 | ((NS_STYLE_TEXT_ALIGN_AUTO != styleText->mTextAlignLast || |
4725 | 0 | NS_STYLE_TEXT_ALIGN_JUSTIFY == styleText->mTextAlign) && |
4726 | 0 | (aLineLayout.GetLineEndsInBR() || |
4727 | 0 | IsLastLine(aState, aLine))); |
4728 | 0 |
|
4729 | 0 | aLineLayout.TextAlignLine(aLine, isLastLine); |
4730 | 0 |
|
4731 | 0 | // From here on, pfd->mBounds rectangles are incorrect because bidi |
4732 | 0 | // might have moved frames around! |
4733 | 0 | nsOverflowAreas overflowAreas; |
4734 | 0 | aLineLayout.RelativePositionFrames(overflowAreas); |
4735 | 0 | aLine->SetOverflowAreas(overflowAreas); |
4736 | 0 | if (addedBullet) { |
4737 | 0 | aLineLayout.RemoveBulletFrame(GetOutsideBullet()); |
4738 | 0 | } |
4739 | 0 |
|
4740 | 0 | // Inline lines do not have margins themselves; however they are |
4741 | 0 | // impacted by prior block margins. If this line ends up having some |
4742 | 0 | // height then we zero out the previous block-end margin value that was |
4743 | 0 | // already applied to the line's starting Y coordinate. Otherwise we |
4744 | 0 | // leave it be so that the previous blocks block-end margin can be |
4745 | 0 | // collapsed with a block that follows. |
4746 | 0 | nscoord newBCoord; |
4747 | 0 |
|
4748 | 0 | if (!aLine->CachedIsEmpty()) { |
4749 | 0 | // This line has some height. Therefore the application of the |
4750 | 0 | // previous-bottom-margin should stick. |
4751 | 0 | aState.mPrevBEndMargin.Zero(); |
4752 | 0 | newBCoord = aLine->BEnd(); |
4753 | 0 | } |
4754 | 0 | else { |
4755 | 0 | // Don't let the previous-bottom-margin value affect the newBCoord |
4756 | 0 | // coordinate (it was applied in ReflowInlineFrames speculatively) |
4757 | 0 | // since the line is empty. |
4758 | 0 | // We already called |ShouldApplyBStartMargin|, and if we applied it |
4759 | 0 | // then mShouldApplyBStartMargin is set. |
4760 | 0 | nscoord dy = aState.mFlags.mShouldApplyBStartMargin |
4761 | 0 | ? -aState.mPrevBEndMargin.get() : 0; |
4762 | 0 | newBCoord = aState.mBCoord + dy; |
4763 | 0 | } |
4764 | 0 |
|
4765 | 0 | if (!aState.mReflowStatus.IsFullyComplete() && |
4766 | 0 | ShouldAvoidBreakInside(aState.mReflowInput)) { |
4767 | 0 | aLine->AppendFloats(aState.mCurrentLineFloats); |
4768 | 0 | aState.mReflowStatus.SetInlineLineBreakBeforeAndReset(); |
4769 | 0 | // Reflow the line again when we reflow at our new position. |
4770 | 0 | aLine->MarkDirty(); |
4771 | 0 | *aKeepReflowGoing = false; |
4772 | 0 | return true; |
4773 | 0 | } |
4774 | 0 | |
4775 | 0 | // See if the line fit (our first line always does). |
4776 | 0 | if (mLines.front() != aLine && |
4777 | 0 | newBCoord > aState.mBEndEdge && |
4778 | 0 | aState.mBEndEdge != NS_UNCONSTRAINEDSIZE) { |
4779 | 0 | NS_ASSERTION(aState.mCurrentLine == aLine, "oops"); |
4780 | 0 | if (ShouldAvoidBreakInside(aState.mReflowInput)) { |
4781 | 0 | // All our content doesn't fit, start on the next page. |
4782 | 0 | aState.mReflowStatus.SetInlineLineBreakBeforeAndReset(); |
4783 | 0 | *aKeepReflowGoing = false; |
4784 | 0 | } else { |
4785 | 0 | // Push aLine and all of its children and anything else that |
4786 | 0 | // follows to our next-in-flow. |
4787 | 0 | PushTruncatedLine(aState, aLine, aKeepReflowGoing); |
4788 | 0 | } |
4789 | 0 | return true; |
4790 | 0 | } |
4791 | 0 |
|
4792 | 0 | // Note that any early return before this update of aState.mBCoord |
4793 | 0 | // must either (a) return false or (b) set aKeepReflowGoing to false. |
4794 | 0 | // Otherwise we'll keep reflowing later lines at an incorrect |
4795 | 0 | // position, and we might not come back and clean up the damage later. |
4796 | 0 | aState.mBCoord = newBCoord; |
4797 | 0 |
|
4798 | 0 | // Add the already placed current-line floats to the line |
4799 | 0 | aLine->AppendFloats(aState.mCurrentLineFloats); |
4800 | 0 |
|
4801 | 0 | // Any below current line floats to place? |
4802 | 0 | if (aState.mBelowCurrentLineFloats.NotEmpty()) { |
4803 | 0 | // Reflow the below-current-line floats, which places on the line's |
4804 | 0 | // float list. |
4805 | 0 | aState.PlaceBelowCurrentLineFloats(aLine); |
4806 | 0 | } |
4807 | 0 |
|
4808 | 0 | // When a line has floats, factor them into the combined-area |
4809 | 0 | // computations. |
4810 | 0 | if (aLine->HasFloats()) { |
4811 | 0 | // Combine the float combined area (stored in aState) and the |
4812 | 0 | // value computed by the line layout code. |
4813 | 0 | nsOverflowAreas lineOverflowAreas; |
4814 | 0 | NS_FOR_FRAME_OVERFLOW_TYPES(otype) { |
4815 | 0 | nsRect &o = lineOverflowAreas.Overflow(otype); |
4816 | 0 | o = aLine->GetOverflowArea(otype); |
4817 | | #ifdef NOISY_COMBINED_AREA |
4818 | | ListTag(stdout); |
4819 | | printf(": overflow %d lineCA=%d,%d,%d,%d floatCA=%d,%d,%d,%d\n", |
4820 | | otype, |
4821 | | o.x, o.y, o.width, o.height, |
4822 | | aState.mFloatOverflowAreas.Overflow(otype).x, |
4823 | | aState.mFloatOverflowAreas.Overflow(otype).y, |
4824 | | aState.mFloatOverflowAreas.Overflow(otype).width, |
4825 | | aState.mFloatOverflowAreas.Overflow(otype).height); |
4826 | | #endif |
4827 | | o.UnionRect(aState.mFloatOverflowAreas.Overflow(otype), o); |
4828 | 0 |
|
4829 | | #ifdef NOISY_COMBINED_AREA |
4830 | | printf(" ==> final lineCA=%d,%d,%d,%d\n", |
4831 | | o.x, o.y, o.width, o.height); |
4832 | | #endif |
4833 | | } |
4834 | 0 | aLine->SetOverflowAreas(lineOverflowAreas); |
4835 | 0 | } |
4836 | 0 |
|
4837 | 0 | // Apply break-after clearing if necessary |
4838 | 0 | // This must stay in sync with |ReflowDirtyLines|. |
4839 | 0 | if (aLine->HasFloatBreakAfter()) { |
4840 | 0 | aState.mBCoord = aState.ClearFloats(aState.mBCoord, aLine->GetBreakTypeAfter()); |
4841 | 0 | } |
4842 | 0 | return true; |
4843 | 0 | } |
4844 | | |
4845 | | void |
4846 | | nsBlockFrame::PushLines(BlockReflowInput& aState, |
4847 | | nsLineList::iterator aLineBefore) |
4848 | 0 | { |
4849 | 0 | // NOTE: aLineBefore is always a normal line, not an overflow line. |
4850 | 0 | // The following expression will assert otherwise. |
4851 | 0 | DebugOnly<bool> check = aLineBefore == mLines.begin(); |
4852 | 0 |
|
4853 | 0 | nsLineList::iterator overBegin(aLineBefore.next()); |
4854 | 0 |
|
4855 | 0 | // PushTruncatedPlaceholderLine sometimes pushes the first line. Ugh. |
4856 | 0 | bool firstLine = overBegin == LinesBegin(); |
4857 | 0 |
|
4858 | 0 | if (overBegin != LinesEnd()) { |
4859 | 0 | // Remove floats in the lines from mFloats |
4860 | 0 | nsFrameList floats; |
4861 | 0 | CollectFloats(overBegin->mFirstChild, floats, true); |
4862 | 0 |
|
4863 | 0 | if (floats.NotEmpty()) { |
4864 | | #ifdef DEBUG |
4865 | | for (nsIFrame* f : floats) { |
4866 | | MOZ_ASSERT(!(f->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT), |
4867 | | "CollectFloats should've removed that bit"); |
4868 | | } |
4869 | | #endif |
4870 | | // Push the floats onto the front of the overflow out-of-flows list |
4871 | 0 | nsAutoOOFFrameList oofs(this); |
4872 | 0 | oofs.mList.InsertFrames(nullptr, nullptr, floats); |
4873 | 0 | } |
4874 | 0 |
|
4875 | 0 | // overflow lines can already exist in some cases, in particular, |
4876 | 0 | // when shrinkwrapping and we discover that the shrinkwap causes |
4877 | 0 | // the height of some child block to grow which creates additional |
4878 | 0 | // overflowing content. In such cases we must prepend the new |
4879 | 0 | // overflow to the existing overflow. |
4880 | 0 | FrameLines* overflowLines = RemoveOverflowLines(); |
4881 | 0 | if (!overflowLines) { |
4882 | 0 | // XXXldb use presshell arena! |
4883 | 0 | overflowLines = new FrameLines(); |
4884 | 0 | } |
4885 | 0 | if (overflowLines) { |
4886 | 0 | nsIFrame* lineBeforeLastFrame; |
4887 | 0 | if (firstLine) { |
4888 | 0 | lineBeforeLastFrame = nullptr; // removes all frames |
4889 | 0 | } else { |
4890 | 0 | nsIFrame* f = overBegin->mFirstChild; |
4891 | 0 | lineBeforeLastFrame = f ? f->GetPrevSibling() : mFrames.LastChild(); |
4892 | 0 | NS_ASSERTION(!f || lineBeforeLastFrame == aLineBefore->LastChild(), |
4893 | 0 | "unexpected line frames"); |
4894 | 0 | } |
4895 | 0 | nsFrameList pushedFrames = mFrames.RemoveFramesAfter(lineBeforeLastFrame); |
4896 | 0 | overflowLines->mFrames.InsertFrames(nullptr, nullptr, pushedFrames); |
4897 | 0 |
|
4898 | 0 | overflowLines->mLines.splice(overflowLines->mLines.begin(), mLines, |
4899 | 0 | overBegin, LinesEnd()); |
4900 | 0 | NS_ASSERTION(!overflowLines->mLines.empty(), "should not be empty"); |
4901 | 0 | // this takes ownership but it won't delete it immediately so we |
4902 | 0 | // can keep using it. |
4903 | 0 | SetOverflowLines(overflowLines); |
4904 | 0 |
|
4905 | 0 | // Mark all the overflow lines dirty so that they get reflowed when |
4906 | 0 | // they are pulled up by our next-in-flow. |
4907 | 0 |
|
4908 | 0 | // XXXldb Can this get called O(N) times making the whole thing O(N^2)? |
4909 | 0 | for (LineIterator line = overflowLines->mLines.begin(), |
4910 | 0 | line_end = overflowLines->mLines.end(); |
4911 | 0 | line != line_end; |
4912 | 0 | ++line) |
4913 | 0 | { |
4914 | 0 | line->MarkDirty(); |
4915 | 0 | line->MarkPreviousMarginDirty(); |
4916 | 0 | line->SetBoundsEmpty(); |
4917 | 0 | if (line->HasFloats()) { |
4918 | 0 | line->FreeFloats(aState.mFloatCacheFreeList); |
4919 | 0 | } |
4920 | 0 | } |
4921 | 0 | } |
4922 | 0 | } |
4923 | 0 |
|
4924 | | #ifdef DEBUG |
4925 | | VerifyOverflowSituation(); |
4926 | | #endif |
4927 | | } |
4928 | | |
4929 | | // The overflowLines property is stored as a pointer to a line list, |
4930 | | // which must be deleted. However, the following functions all maintain |
4931 | | // the invariant that the property is never set if the list is empty. |
4932 | | |
4933 | | bool |
4934 | | nsBlockFrame::DrainOverflowLines() |
4935 | 0 | { |
4936 | | #ifdef DEBUG |
4937 | | VerifyOverflowSituation(); |
4938 | | #endif |
4939 | |
|
4940 | 0 | // Steal the prev-in-flow's overflow lines and prepend them. |
4941 | 0 | bool didFindOverflow = false; |
4942 | 0 | nsBlockFrame* prevBlock = static_cast<nsBlockFrame*>(GetPrevInFlow()); |
4943 | 0 | if (prevBlock) { |
4944 | 0 | prevBlock->ClearLineCursor(); |
4945 | 0 | FrameLines* overflowLines = prevBlock->RemoveOverflowLines(); |
4946 | 0 | if (overflowLines) { |
4947 | 0 | // Make all the frames on the overflow line list mine. |
4948 | 0 | ReparentFrames(overflowLines->mFrames, prevBlock, this, |
4949 | 0 | ReparentingDirection::Forwards); |
4950 | 0 |
|
4951 | 0 | // Make the overflow out-of-flow frames mine too. |
4952 | 0 | nsAutoOOFFrameList oofs(prevBlock); |
4953 | 0 | if (oofs.mList.NotEmpty()) { |
4954 | 0 | // In case we own a next-in-flow of any of the drained frames, then |
4955 | 0 | // those are now not PUSHED_FLOATs anymore. |
4956 | 0 | for (nsFrameList::Enumerator e(oofs.mList); !e.AtEnd(); e.Next()) { |
4957 | 0 | nsIFrame* nif = e.get()->GetNextInFlow(); |
4958 | 0 | for (; nif && nif->GetParent() == this; nif = nif->GetNextInFlow()) { |
4959 | 0 | MOZ_ASSERT(mFloats.ContainsFrame(nif)); |
4960 | 0 | nif->RemoveStateBits(NS_FRAME_IS_PUSHED_FLOAT); |
4961 | 0 | } |
4962 | 0 | } |
4963 | 0 | ReparentFrames(oofs.mList, prevBlock, this, |
4964 | 0 | ReparentingDirection::Forwards); |
4965 | 0 | mFloats.InsertFrames(nullptr, nullptr, oofs.mList); |
4966 | 0 | } |
4967 | 0 |
|
4968 | 0 | if (!mLines.empty()) { |
4969 | 0 | // Remember to recompute the margins on the first line. This will |
4970 | 0 | // also recompute the correct deltaBCoord if necessary. |
4971 | 0 | mLines.front()->MarkPreviousMarginDirty(); |
4972 | 0 | } |
4973 | 0 | // The overflow lines have already been marked dirty and their previous |
4974 | 0 | // margins marked dirty also. |
4975 | 0 |
|
4976 | 0 | // Prepend the overflow frames/lines to our principal list. |
4977 | 0 | mFrames.InsertFrames(nullptr, nullptr, overflowLines->mFrames); |
4978 | 0 | mLines.splice(mLines.begin(), overflowLines->mLines); |
4979 | 0 | NS_ASSERTION(overflowLines->mLines.empty(), "splice should empty list"); |
4980 | 0 | delete overflowLines; |
4981 | 0 | didFindOverflow = true; |
4982 | 0 | } |
4983 | 0 | } |
4984 | 0 |
|
4985 | 0 | // Now append our own overflow lines. |
4986 | 0 | return DrainSelfOverflowList() || didFindOverflow; |
4987 | 0 | } |
4988 | | |
4989 | | bool |
4990 | | nsBlockFrame::DrainSelfOverflowList() |
4991 | 0 | { |
4992 | 0 | UniquePtr<FrameLines> ourOverflowLines(RemoveOverflowLines()); |
4993 | 0 | if (!ourOverflowLines) { |
4994 | 0 | return false; |
4995 | 0 | } |
4996 | 0 | |
4997 | 0 | // No need to reparent frames in our own overflow lines/oofs, because they're |
4998 | 0 | // already ours. But we should put overflow floats back in mFloats. |
4999 | 0 | // (explicit scope to remove the OOF list before VerifyOverflowSituation) |
5000 | 0 | { |
5001 | 0 | nsAutoOOFFrameList oofs(this); |
5002 | 0 | if (oofs.mList.NotEmpty()) { |
5003 | | #ifdef DEBUG |
5004 | | for (nsIFrame* f : oofs.mList) { |
5005 | | MOZ_ASSERT(!(f->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT), |
5006 | | "CollectFloats should've removed that bit"); |
5007 | | } |
5008 | | #endif |
5009 | | // The overflow floats go after our regular floats. |
5010 | 0 | mFloats.AppendFrames(nullptr, oofs.mList); |
5011 | 0 | } |
5012 | 0 | } |
5013 | 0 | if (!ourOverflowLines->mLines.empty()) { |
5014 | 0 | mFrames.AppendFrames(nullptr, ourOverflowLines->mFrames); |
5015 | 0 | mLines.splice(mLines.end(), ourOverflowLines->mLines); |
5016 | 0 | } |
5017 | 0 |
|
5018 | | #ifdef DEBUG |
5019 | | VerifyOverflowSituation(); |
5020 | | #endif |
5021 | | return true; |
5022 | 0 | } |
5023 | | |
5024 | | /** |
5025 | | * Pushed floats are floats whose placeholders are in a previous |
5026 | | * continuation. They might themselves be next-continuations of a float |
5027 | | * that partially fit in an earlier continuation, or they might be the |
5028 | | * first continuation of a float that couldn't be placed at all. |
5029 | | * |
5030 | | * Pushed floats live permanently at the beginning of a block's float |
5031 | | * list, where they must live *before* any floats whose placeholders are |
5032 | | * in that block. |
5033 | | * |
5034 | | * Temporarily, during reflow, they also live on the pushed floats list, |
5035 | | * which only holds them between (a) when one continuation pushes them to |
5036 | | * its pushed floats list because they don't fit and (b) when the next |
5037 | | * continuation pulls them onto the beginning of its float list. |
5038 | | * |
5039 | | * DrainPushedFloats sets up pushed floats the way we need them at the |
5040 | | * start of reflow; they are then reflowed by ReflowPushedFloats (which |
5041 | | * might push some of them on). Floats with placeholders in this block |
5042 | | * are reflowed by (BlockReflowInput/nsLineLayout)::AddFloat, which |
5043 | | * also maintains these invariants. |
5044 | | * |
5045 | | * DrainSelfPushedFloats moves any pushed floats from this block's own |
5046 | | * PushedFloats list back into mFloats. DrainPushedFloats additionally |
5047 | | * moves frames from its prev-in-flow's PushedFloats list into mFloats. |
5048 | | */ |
5049 | | void |
5050 | | nsBlockFrame::DrainSelfPushedFloats() |
5051 | 0 | { |
5052 | 0 | // If we're getting reflowed multiple times without our |
5053 | 0 | // next-continuation being reflowed, we might need to pull back floats |
5054 | 0 | // that we just put in the list to be pushed to our next-in-flow. |
5055 | 0 | // We don't want to pull back any next-in-flows of floats on our own |
5056 | 0 | // float list, and we only need to pull back first-in-flows whose |
5057 | 0 | // placeholders were in earlier blocks (since first-in-flows whose |
5058 | 0 | // placeholders are in this block will get pulled appropriately by |
5059 | 0 | // AddFloat, and will then be more likely to be in the correct order). |
5060 | 0 | // FIXME: What if there's a continuation in our pushed floats list |
5061 | 0 | // whose prev-in-flow is in a previous continuation of this block |
5062 | 0 | // rather than this block? Might we need to pull it back so we don't |
5063 | 0 | // report ourselves complete? |
5064 | 0 | // FIXME: Maybe we should just pull all of them back? |
5065 | 0 | nsPresContext* presContext = PresContext(); |
5066 | 0 | nsFrameList* ourPushedFloats = GetPushedFloats(); |
5067 | 0 | if (ourPushedFloats) { |
5068 | 0 | // When we pull back floats, we want to put them with the pushed |
5069 | 0 | // floats, which must live at the start of our float list, but we |
5070 | 0 | // want them at the end of those pushed floats. |
5071 | 0 | // FIXME: This isn't quite right! What if they're all pushed floats? |
5072 | 0 | nsIFrame *insertionPrevSibling = nullptr; /* beginning of list */ |
5073 | 0 | for (nsIFrame* f = mFloats.FirstChild(); |
5074 | 0 | f && (f->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT); |
5075 | 0 | f = f->GetNextSibling()) { |
5076 | 0 | insertionPrevSibling = f; |
5077 | 0 | } |
5078 | 0 |
|
5079 | 0 | for (nsIFrame *f = ourPushedFloats->LastChild(), *next; f; f = next) { |
5080 | 0 | next = f->GetPrevSibling(); |
5081 | 0 |
|
5082 | 0 | if (f->GetPrevContinuation()) { |
5083 | 0 | // FIXME |
5084 | 0 | } else { |
5085 | 0 | nsPlaceholderFrame* placeholder = f->GetPlaceholderFrame(); |
5086 | 0 | nsIFrame* floatOriginalParent = presContext->PresShell()-> |
5087 | 0 | FrameConstructor()->GetFloatContainingBlock(placeholder); |
5088 | 0 | if (floatOriginalParent != this) { |
5089 | 0 | // This is a first continuation that was pushed from one of our |
5090 | 0 | // previous continuations. Take it out of the pushed floats |
5091 | 0 | // list and put it in our floats list, before any of our |
5092 | 0 | // floats, but after other pushed floats. |
5093 | 0 | ourPushedFloats->RemoveFrame(f); |
5094 | 0 | mFloats.InsertFrame(nullptr, insertionPrevSibling, f); |
5095 | 0 | } |
5096 | 0 | } |
5097 | 0 | } |
5098 | 0 |
|
5099 | 0 | if (ourPushedFloats->IsEmpty()) { |
5100 | 0 | RemovePushedFloats()->Delete(presContext->PresShell()); |
5101 | 0 | } |
5102 | 0 | } |
5103 | 0 | } |
5104 | | |
5105 | | void |
5106 | | nsBlockFrame::DrainPushedFloats() |
5107 | 0 | { |
5108 | 0 | DrainSelfPushedFloats(); |
5109 | 0 |
|
5110 | 0 | // After our prev-in-flow has completed reflow, it may have a pushed |
5111 | 0 | // floats list, containing floats that we need to own. Take these. |
5112 | 0 | nsBlockFrame* prevBlock = static_cast<nsBlockFrame*>(GetPrevInFlow()); |
5113 | 0 | if (prevBlock) { |
5114 | 0 | AutoFrameListPtr list(PresContext(), prevBlock->RemovePushedFloats()); |
5115 | 0 | if (list && list->NotEmpty()) { |
5116 | 0 | mFloats.InsertFrames(this, nullptr, *list); |
5117 | 0 | } |
5118 | 0 | } |
5119 | 0 | } |
5120 | | |
5121 | | nsBlockFrame::FrameLines* |
5122 | | nsBlockFrame::GetOverflowLines() const |
5123 | 0 | { |
5124 | 0 | if (!HasOverflowLines()) { |
5125 | 0 | return nullptr; |
5126 | 0 | } |
5127 | 0 | FrameLines* prop = GetProperty(OverflowLinesProperty()); |
5128 | 0 | NS_ASSERTION(prop && !prop->mLines.empty() && |
5129 | 0 | prop->mLines.front()->GetChildCount() == 0 ? prop->mFrames.IsEmpty() : |
5130 | 0 | prop->mLines.front()->mFirstChild == prop->mFrames.FirstChild(), |
5131 | 0 | "value should always be stored and non-empty when state set"); |
5132 | 0 | return prop; |
5133 | 0 | } |
5134 | | |
5135 | | nsBlockFrame::FrameLines* |
5136 | | nsBlockFrame::RemoveOverflowLines() |
5137 | 0 | { |
5138 | 0 | if (!HasOverflowLines()) { |
5139 | 0 | return nullptr; |
5140 | 0 | } |
5141 | 0 | FrameLines* prop = RemoveProperty(OverflowLinesProperty()); |
5142 | 0 | NS_ASSERTION(prop && !prop->mLines.empty() && |
5143 | 0 | prop->mLines.front()->GetChildCount() == 0 ? prop->mFrames.IsEmpty() : |
5144 | 0 | prop->mLines.front()->mFirstChild == prop->mFrames.FirstChild(), |
5145 | 0 | "value should always be stored and non-empty when state set"); |
5146 | 0 | RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_LINES); |
5147 | 0 | return prop; |
5148 | 0 | } |
5149 | | |
5150 | | void |
5151 | | nsBlockFrame::DestroyOverflowLines() |
5152 | 0 | { |
5153 | 0 | NS_ASSERTION(HasOverflowLines(), "huh?"); |
5154 | 0 | FrameLines* prop = RemoveProperty(OverflowLinesProperty()); |
5155 | 0 | NS_ASSERTION(prop && prop->mLines.empty(), |
5156 | 0 | "value should always be stored but empty when destroying"); |
5157 | 0 | RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_LINES); |
5158 | 0 | delete prop; |
5159 | 0 | } |
5160 | | |
5161 | | // This takes ownership of aOverflowLines. |
5162 | | // XXX We should allocate overflowLines from presShell arena! |
5163 | | void |
5164 | | nsBlockFrame::SetOverflowLines(FrameLines* aOverflowLines) |
5165 | 0 | { |
5166 | 0 | NS_ASSERTION(aOverflowLines, "null lines"); |
5167 | 0 | NS_ASSERTION(!aOverflowLines->mLines.empty(), "empty lines"); |
5168 | 0 | NS_ASSERTION(aOverflowLines->mLines.front()->mFirstChild == |
5169 | 0 | aOverflowLines->mFrames.FirstChild(), |
5170 | 0 | "invalid overflow lines / frames"); |
5171 | 0 | NS_ASSERTION(!(GetStateBits() & NS_BLOCK_HAS_OVERFLOW_LINES), |
5172 | 0 | "Overwriting existing overflow lines"); |
5173 | 0 |
|
5174 | 0 | // Verify that we won't overwrite an existing overflow list |
5175 | 0 | NS_ASSERTION(!GetProperty(OverflowLinesProperty()), "existing overflow list"); |
5176 | 0 | SetProperty(OverflowLinesProperty(), aOverflowLines); |
5177 | 0 | AddStateBits(NS_BLOCK_HAS_OVERFLOW_LINES); |
5178 | 0 | } |
5179 | | |
5180 | | nsFrameList* |
5181 | | nsBlockFrame::GetOverflowOutOfFlows() const |
5182 | 0 | { |
5183 | 0 | if (!(GetStateBits() & NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) { |
5184 | 0 | return nullptr; |
5185 | 0 | } |
5186 | 0 | nsFrameList* result = |
5187 | 0 | GetPropTableFrames(OverflowOutOfFlowsProperty()); |
5188 | 0 | NS_ASSERTION(result, "value should always be non-empty when state set"); |
5189 | 0 | return result; |
5190 | 0 | } |
5191 | | |
5192 | | // This takes ownership of the frames |
5193 | | void |
5194 | | nsBlockFrame::SetOverflowOutOfFlows(const nsFrameList& aList, |
5195 | | nsFrameList* aPropValue) |
5196 | 0 | { |
5197 | 0 | MOZ_ASSERT(!!(GetStateBits() & NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS) == |
5198 | 0 | !!aPropValue, "state does not match value"); |
5199 | 0 |
|
5200 | 0 | if (aList.IsEmpty()) { |
5201 | 0 | if (!(GetStateBits() & NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) { |
5202 | 0 | return; |
5203 | 0 | } |
5204 | 0 | nsFrameList* list = RemovePropTableFrames(OverflowOutOfFlowsProperty()); |
5205 | 0 | NS_ASSERTION(aPropValue == list, "prop value mismatch"); |
5206 | 0 | list->Clear(); |
5207 | 0 | list->Delete(PresShell()); |
5208 | 0 | RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS); |
5209 | 0 | } |
5210 | 0 | else if (GetStateBits() & NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS) { |
5211 | 0 | NS_ASSERTION(aPropValue == GetPropTableFrames(OverflowOutOfFlowsProperty()), |
5212 | 0 | "prop value mismatch"); |
5213 | 0 | *aPropValue = aList; |
5214 | 0 | } |
5215 | 0 | else { |
5216 | 0 | SetPropTableFrames(new (PresShell()) nsFrameList(aList), |
5217 | 0 | OverflowOutOfFlowsProperty()); |
5218 | 0 | AddStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS); |
5219 | 0 | } |
5220 | 0 | } |
5221 | | |
5222 | | nsBulletFrame* |
5223 | | nsBlockFrame::GetInsideBullet() const |
5224 | 0 | { |
5225 | 0 | if (!HasInsideBullet()) { |
5226 | 0 | return nullptr; |
5227 | 0 | } |
5228 | 0 | NS_ASSERTION(!HasOutsideBullet(), "invalid bullet state"); |
5229 | 0 | nsBulletFrame* frame = GetProperty(InsideBulletProperty()); |
5230 | 0 | NS_ASSERTION(frame && frame->IsBulletFrame(), "bogus inside bullet frame"); |
5231 | 0 | return frame; |
5232 | 0 | } |
5233 | | |
5234 | | nsBulletFrame* |
5235 | | nsBlockFrame::GetOutsideBullet() const |
5236 | 0 | { |
5237 | 0 | nsFrameList* list = GetOutsideBulletList(); |
5238 | 0 | return list ? static_cast<nsBulletFrame*>(list->FirstChild()) |
5239 | 0 | : nullptr; |
5240 | 0 | } |
5241 | | |
5242 | | nsFrameList* |
5243 | | nsBlockFrame::GetOutsideBulletList() const |
5244 | 0 | { |
5245 | 0 | if (!HasOutsideBullet()) { |
5246 | 0 | return nullptr; |
5247 | 0 | } |
5248 | 0 | NS_ASSERTION(!HasInsideBullet(), "invalid bullet state"); |
5249 | 0 | nsFrameList* list = GetProperty(OutsideBulletProperty()); |
5250 | 0 | NS_ASSERTION(list && list->GetLength() == 1 && |
5251 | 0 | list->FirstChild()->IsBulletFrame(), |
5252 | 0 | "bogus outside bullet list"); |
5253 | 0 | return list; |
5254 | 0 | } |
5255 | | |
5256 | | nsFrameList* |
5257 | | nsBlockFrame::GetPushedFloats() const |
5258 | 0 | { |
5259 | 0 | if (!HasPushedFloats()) { |
5260 | 0 | return nullptr; |
5261 | 0 | } |
5262 | 0 | nsFrameList* result = GetProperty(PushedFloatProperty()); |
5263 | 0 | NS_ASSERTION(result, "value should always be non-empty when state set"); |
5264 | 0 | return result; |
5265 | 0 | } |
5266 | | |
5267 | | nsFrameList* |
5268 | | nsBlockFrame::EnsurePushedFloats() |
5269 | 0 | { |
5270 | 0 | nsFrameList *result = GetPushedFloats(); |
5271 | 0 | if (result) |
5272 | 0 | return result; |
5273 | 0 | |
5274 | 0 | result = new (PresShell()) nsFrameList; |
5275 | 0 | SetProperty(PushedFloatProperty(), result); |
5276 | 0 | AddStateBits(NS_BLOCK_HAS_PUSHED_FLOATS); |
5277 | 0 |
|
5278 | 0 | return result; |
5279 | 0 | } |
5280 | | |
5281 | | nsFrameList* |
5282 | | nsBlockFrame::RemovePushedFloats() |
5283 | 0 | { |
5284 | 0 | if (!HasPushedFloats()) { |
5285 | 0 | return nullptr; |
5286 | 0 | } |
5287 | 0 | nsFrameList *result = RemoveProperty(PushedFloatProperty()); |
5288 | 0 | RemoveStateBits(NS_BLOCK_HAS_PUSHED_FLOATS); |
5289 | 0 | NS_ASSERTION(result, "value should always be non-empty when state set"); |
5290 | 0 | return result; |
5291 | 0 | } |
5292 | | |
5293 | | ////////////////////////////////////////////////////////////////////// |
5294 | | // Frame list manipulation routines |
5295 | | |
5296 | | void |
5297 | | nsBlockFrame::AppendFrames(ChildListID aListID, |
5298 | | nsFrameList& aFrameList) |
5299 | 0 | { |
5300 | 0 | if (aFrameList.IsEmpty()) { |
5301 | 0 | return; |
5302 | 0 | } |
5303 | 0 | if (aListID != kPrincipalList) { |
5304 | 0 | if (kFloatList == aListID) { |
5305 | 0 | DrainSelfPushedFloats(); // ensure the last frame is in mFloats |
5306 | 0 | mFloats.AppendFrames(nullptr, aFrameList); |
5307 | 0 | return; |
5308 | 0 | } |
5309 | 0 | MOZ_ASSERT(kNoReflowPrincipalList == aListID, "unexpected child list"); |
5310 | 0 | } |
5311 | 0 |
|
5312 | 0 | // Find the proper last-child for where the append should go |
5313 | 0 | nsIFrame* lastKid = mFrames.LastChild(); |
5314 | 0 | NS_ASSERTION((mLines.empty() ? nullptr : mLines.back()->LastChild()) == |
5315 | 0 | lastKid, "out-of-sync mLines / mFrames"); |
5316 | 0 |
|
5317 | | #ifdef NOISY_REFLOW_REASON |
5318 | | ListTag(stdout); |
5319 | | printf(": append "); |
5320 | | nsFrame::ListTag(stdout, aFrameList); |
5321 | | if (lastKid) { |
5322 | | printf(" after "); |
5323 | | nsFrame::ListTag(stdout, lastKid); |
5324 | | } |
5325 | | printf("\n"); |
5326 | | #endif |
5327 | |
|
5328 | 0 | if (nsSVGUtils::IsInSVGTextSubtree(this)) { |
5329 | 0 | MOZ_ASSERT(GetParent()->IsSVGTextFrame(), |
5330 | 0 | "unexpected block frame in SVG text"); |
5331 | 0 | // Workaround for bug 1399425 in case this bit has been removed from the |
5332 | 0 | // SVGTextFrame just before the parser adds more descendant nodes. |
5333 | 0 | GetParent()->AddStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY); |
5334 | 0 | } |
5335 | 0 |
|
5336 | 0 | AddFrames(aFrameList, lastKid); |
5337 | 0 | if (aListID != kNoReflowPrincipalList) { |
5338 | 0 | PresShell()-> |
5339 | 0 | FrameNeedsReflow(this, nsIPresShell::eTreeChange, |
5340 | 0 | NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient? |
5341 | 0 | } |
5342 | 0 | } |
5343 | | |
5344 | | void |
5345 | | nsBlockFrame::InsertFrames(ChildListID aListID, |
5346 | | nsIFrame* aPrevFrame, |
5347 | | nsFrameList& aFrameList) |
5348 | 0 | { |
5349 | 0 | NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this, |
5350 | 0 | "inserting after sibling frame with different parent"); |
5351 | 0 |
|
5352 | 0 | if (aListID != kPrincipalList) { |
5353 | 0 | if (kFloatList == aListID) { |
5354 | 0 | DrainSelfPushedFloats(); // ensure aPrevFrame is in mFloats |
5355 | 0 | mFloats.InsertFrames(this, aPrevFrame, aFrameList); |
5356 | 0 | return; |
5357 | 0 | } |
5358 | 0 | MOZ_ASSERT(kNoReflowPrincipalList == aListID, "unexpected child list"); |
5359 | 0 | } |
5360 | 0 |
|
5361 | | #ifdef NOISY_REFLOW_REASON |
5362 | | ListTag(stdout); |
5363 | | printf(": insert "); |
5364 | | nsFrame::ListTag(stdout, aFrameList); |
5365 | | if (aPrevFrame) { |
5366 | | printf(" after "); |
5367 | | nsFrame::ListTag(stdout, aPrevFrame); |
5368 | | } |
5369 | | printf("\n"); |
5370 | | #endif |
5371 | |
|
5372 | 0 | AddFrames(aFrameList, aPrevFrame); |
5373 | 0 | if (aListID != kNoReflowPrincipalList) { |
5374 | 0 | PresShell()-> |
5375 | 0 | FrameNeedsReflow(this, nsIPresShell::eTreeChange, |
5376 | 0 | NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient? |
5377 | 0 | } |
5378 | 0 | } |
5379 | | |
5380 | | void |
5381 | | nsBlockFrame::RemoveFrame(ChildListID aListID, |
5382 | | nsIFrame* aOldFrame) |
5383 | 0 | { |
5384 | | #ifdef NOISY_REFLOW_REASON |
5385 | | ListTag(stdout); |
5386 | | printf(": remove "); |
5387 | | nsFrame::ListTag(stdout, aOldFrame); |
5388 | | printf("\n"); |
5389 | | #endif |
5390 | |
|
5391 | 0 | if (aListID == kPrincipalList) { |
5392 | 0 | bool hasFloats = BlockHasAnyFloats(aOldFrame); |
5393 | 0 | DoRemoveFrame(aOldFrame, REMOVE_FIXED_CONTINUATIONS); |
5394 | 0 | if (hasFloats) { |
5395 | 0 | MarkSameFloatManagerLinesDirty(this); |
5396 | 0 | } |
5397 | 0 | } |
5398 | 0 | else if (kFloatList == aListID) { |
5399 | 0 | // Make sure to mark affected lines dirty for the float frame |
5400 | 0 | // we are removing; this way is a bit messy, but so is the rest of the code. |
5401 | 0 | // See bug 390762. |
5402 | 0 | NS_ASSERTION(!aOldFrame->GetPrevContinuation(), |
5403 | 0 | "RemoveFrame should not be called on pushed floats."); |
5404 | 0 | for (nsIFrame* f = aOldFrame; |
5405 | 0 | f && !(f->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER); |
5406 | 0 | f = f->GetNextContinuation()) { |
5407 | 0 | MarkSameFloatManagerLinesDirty(static_cast<nsBlockFrame*>(f->GetParent())); |
5408 | 0 | } |
5409 | 0 | DoRemoveOutOfFlowFrame(aOldFrame); |
5410 | 0 | } |
5411 | 0 | else if (kNoReflowPrincipalList == aListID) { |
5412 | 0 | // Skip the call to |FrameNeedsReflow| below by returning now. |
5413 | 0 | DoRemoveFrame(aOldFrame, REMOVE_FIXED_CONTINUATIONS); |
5414 | 0 | return; |
5415 | 0 | } |
5416 | 0 | else { |
5417 | 0 | MOZ_CRASH("unexpected child list"); |
5418 | 0 | } |
5419 | 0 |
|
5420 | 0 | PresShell()-> |
5421 | 0 | FrameNeedsReflow(this, nsIPresShell::eTreeChange, |
5422 | 0 | NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient? |
5423 | 0 | } |
5424 | | |
5425 | | static bool |
5426 | | ShouldPutNextSiblingOnNewLine(nsIFrame* aLastFrame) |
5427 | 0 | { |
5428 | 0 | LayoutFrameType type = aLastFrame->Type(); |
5429 | 0 | if (type == LayoutFrameType::Br) { |
5430 | 0 | return true; |
5431 | 0 | } |
5432 | 0 | // XXX the TEXT_OFFSETS_NEED_FIXING check is a wallpaper for bug 822910. |
5433 | 0 | if (type == LayoutFrameType::Text && |
5434 | 0 | !(aLastFrame->GetStateBits() & TEXT_OFFSETS_NEED_FIXING)) { |
5435 | 0 | return aLastFrame->HasSignificantTerminalNewline(); |
5436 | 0 | } |
5437 | 0 | return false; |
5438 | 0 | } |
5439 | | |
5440 | | void |
5441 | | nsBlockFrame::AddFrames(nsFrameList& aFrameList, nsIFrame* aPrevSibling) |
5442 | 0 | { |
5443 | 0 | // Clear our line cursor, since our lines may change. |
5444 | 0 | ClearLineCursor(); |
5445 | 0 |
|
5446 | 0 | if (aFrameList.IsEmpty()) { |
5447 | 0 | return; |
5448 | 0 | } |
5449 | 0 | |
5450 | 0 | // If we're inserting at the beginning of our list and we have an |
5451 | 0 | // inside bullet, insert after that bullet. |
5452 | 0 | if (!aPrevSibling && HasInsideBullet()) { |
5453 | 0 | aPrevSibling = GetInsideBullet(); |
5454 | 0 | } |
5455 | 0 |
|
5456 | 0 | // Attempt to find the line that contains the previous sibling |
5457 | 0 | nsLineList* lineList = &mLines; |
5458 | 0 | nsFrameList* frames = &mFrames; |
5459 | 0 | nsLineList::iterator prevSibLine = lineList->end(); |
5460 | 0 | int32_t prevSiblingIndex = -1; |
5461 | 0 | if (aPrevSibling) { |
5462 | 0 | // XXX_perf This is technically O(N^2) in some cases, but by using |
5463 | 0 | // RFind instead of Find, we make it O(N) in the most common case, |
5464 | 0 | // which is appending content. |
5465 | 0 |
|
5466 | 0 | // Find the line that contains the previous sibling |
5467 | 0 | if (!nsLineBox::RFindLineContaining(aPrevSibling, lineList->begin(), |
5468 | 0 | prevSibLine, mFrames.LastChild(), |
5469 | 0 | &prevSiblingIndex)) { |
5470 | 0 | // Not in mLines - try overflow lines. |
5471 | 0 | FrameLines* overflowLines = GetOverflowLines(); |
5472 | 0 | bool found = false; |
5473 | 0 | if (overflowLines) { |
5474 | 0 | prevSibLine = overflowLines->mLines.end(); |
5475 | 0 | prevSiblingIndex = -1; |
5476 | 0 | found = nsLineBox::RFindLineContaining(aPrevSibling, |
5477 | 0 | overflowLines->mLines.begin(), |
5478 | 0 | prevSibLine, |
5479 | 0 | overflowLines->mFrames.LastChild(), |
5480 | 0 | &prevSiblingIndex); |
5481 | 0 | } |
5482 | 0 | if (MOZ_LIKELY(found)) { |
5483 | 0 | lineList = &overflowLines->mLines; |
5484 | 0 | frames = &overflowLines->mFrames; |
5485 | 0 | } else { |
5486 | 0 | // Note: defensive code! RFindLineContaining must not return |
5487 | 0 | // false in this case, so if it does... |
5488 | 0 | MOZ_ASSERT_UNREACHABLE("prev sibling not in line list"); |
5489 | 0 | aPrevSibling = nullptr; |
5490 | 0 | prevSibLine = lineList->end(); |
5491 | 0 | } |
5492 | 0 | } |
5493 | 0 | } |
5494 | 0 |
|
5495 | 0 | // Find the frame following aPrevSibling so that we can join up the |
5496 | 0 | // two lists of frames. |
5497 | 0 | if (aPrevSibling) { |
5498 | 0 | // Split line containing aPrevSibling in two if the insertion |
5499 | 0 | // point is somewhere in the middle of the line. |
5500 | 0 | int32_t rem = prevSibLine->GetChildCount() - prevSiblingIndex - 1; |
5501 | 0 | if (rem) { |
5502 | 0 | // Split the line in two where the frame(s) are being inserted. |
5503 | 0 | nsLineBox* line = NewLineBox(prevSibLine, aPrevSibling->GetNextSibling(), rem); |
5504 | 0 | lineList->after_insert(prevSibLine, line); |
5505 | 0 | // Mark prevSibLine dirty and as needing textrun invalidation, since |
5506 | 0 | // we may be breaking up text in the line. Its previous line may also |
5507 | 0 | // need to be invalidated because it may be able to pull some text up. |
5508 | 0 | MarkLineDirty(prevSibLine, lineList); |
5509 | 0 | // The new line will also need its textruns recomputed because of the |
5510 | 0 | // frame changes. |
5511 | 0 | line->MarkDirty(); |
5512 | 0 | line->SetInvalidateTextRuns(true); |
5513 | 0 | } |
5514 | 0 | } |
5515 | 0 | else if (! lineList->empty()) { |
5516 | 0 | lineList->front()->MarkDirty(); |
5517 | 0 | lineList->front()->SetInvalidateTextRuns(true); |
5518 | 0 | } |
5519 | 0 | const nsFrameList::Slice& newFrames = |
5520 | 0 | frames->InsertFrames(nullptr, aPrevSibling, aFrameList); |
5521 | 0 |
|
5522 | 0 | // Walk through the new frames being added and update the line data |
5523 | 0 | // structures to fit. |
5524 | 0 | for (nsFrameList::Enumerator e(newFrames); !e.AtEnd(); e.Next()) { |
5525 | 0 | nsIFrame* newFrame = e.get(); |
5526 | 0 | NS_ASSERTION(!aPrevSibling || aPrevSibling->GetNextSibling() == newFrame, |
5527 | 0 | "Unexpected aPrevSibling"); |
5528 | 0 | NS_ASSERTION(!newFrame->IsPlaceholderFrame() || |
5529 | 0 | (!newFrame->IsAbsolutelyPositioned() && |
5530 | 0 | !newFrame->IsFloating()), |
5531 | 0 | "Placeholders should not float or be positioned"); |
5532 | 0 |
|
5533 | 0 | bool isBlock = newFrame->IsBlockOutside(); |
5534 | 0 |
|
5535 | 0 | // If the frame is a block frame, or if there is no previous line or if the |
5536 | 0 | // previous line is a block line we need to make a new line. We also make |
5537 | 0 | // a new line, as an optimization, in the two cases we know we'll need it: |
5538 | 0 | // if the previous line ended with a <br>, or if it has significant whitespace |
5539 | 0 | // and ended in a newline. |
5540 | 0 | if (isBlock || prevSibLine == lineList->end() || prevSibLine->IsBlock() || |
5541 | 0 | (aPrevSibling && ShouldPutNextSiblingOnNewLine(aPrevSibling))) { |
5542 | 0 | // Create a new line for the frame and add its line to the line |
5543 | 0 | // list. |
5544 | 0 | nsLineBox* line = NewLineBox(newFrame, isBlock); |
5545 | 0 | if (prevSibLine != lineList->end()) { |
5546 | 0 | // Append new line after prevSibLine |
5547 | 0 | lineList->after_insert(prevSibLine, line); |
5548 | 0 | ++prevSibLine; |
5549 | 0 | } |
5550 | 0 | else { |
5551 | 0 | // New line is going before the other lines |
5552 | 0 | lineList->push_front(line); |
5553 | 0 | prevSibLine = lineList->begin(); |
5554 | 0 | } |
5555 | 0 | } |
5556 | 0 | else { |
5557 | 0 | prevSibLine->NoteFrameAdded(newFrame); |
5558 | 0 | // We're adding inline content to prevSibLine, so we need to mark it |
5559 | 0 | // dirty, ensure its textruns are recomputed, and possibly do the same |
5560 | 0 | // to its previous line since that line may be able to pull content up. |
5561 | 0 | MarkLineDirty(prevSibLine, lineList); |
5562 | 0 | } |
5563 | 0 |
|
5564 | 0 | aPrevSibling = newFrame; |
5565 | 0 | } |
5566 | 0 |
|
5567 | | #ifdef DEBUG |
5568 | | MOZ_ASSERT(aFrameList.IsEmpty()); |
5569 | | VerifyLines(true); |
5570 | | #endif |
5571 | | } |
5572 | | |
5573 | | void |
5574 | | nsBlockFrame::RemoveFloatFromFloatCache(nsIFrame* aFloat) |
5575 | 0 | { |
5576 | 0 | // Find which line contains the float, so we can update |
5577 | 0 | // the float cache. |
5578 | 0 | LineIterator line = LinesBegin(), line_end = LinesEnd(); |
5579 | 0 | for ( ; line != line_end; ++line) { |
5580 | 0 | if (line->IsInline() && line->RemoveFloat(aFloat)) { |
5581 | 0 | break; |
5582 | 0 | } |
5583 | 0 | } |
5584 | 0 | } |
5585 | | |
5586 | | void |
5587 | | nsBlockFrame::RemoveFloat(nsIFrame* aFloat) |
5588 | 0 | { |
5589 | | #ifdef DEBUG |
5590 | | // Floats live in mFloats, or in the PushedFloat or OverflowOutOfFlows |
5591 | | // frame list properties. |
5592 | | if (!mFloats.ContainsFrame(aFloat)) { |
5593 | | MOZ_ASSERT((GetOverflowOutOfFlows() && |
5594 | | GetOverflowOutOfFlows()->ContainsFrame(aFloat)) || |
5595 | | (GetPushedFloats() && |
5596 | | GetPushedFloats()->ContainsFrame(aFloat)), |
5597 | | "aFloat is not our child or on an unexpected frame list"); |
5598 | | } |
5599 | | #endif |
5600 | |
|
5601 | 0 | if (mFloats.StartRemoveFrame(aFloat)) { |
5602 | 0 | return; |
5603 | 0 | } |
5604 | 0 | |
5605 | 0 | nsFrameList* list = GetPushedFloats(); |
5606 | 0 | if (list && list->ContinueRemoveFrame(aFloat)) { |
5607 | | #if 0 |
5608 | | // XXXmats not yet - need to investigate BlockReflowInput::mPushedFloats |
5609 | | // first so we don't leave it pointing to a deleted list. |
5610 | | if (list->IsEmpty()) { |
5611 | | delete RemovePushedFloats(); |
5612 | | } |
5613 | | #endif |
5614 | | return; |
5615 | 0 | } |
5616 | 0 |
|
5617 | 0 | { |
5618 | 0 | nsAutoOOFFrameList oofs(this); |
5619 | 0 | if (oofs.mList.ContinueRemoveFrame(aFloat)) { |
5620 | 0 | return; |
5621 | 0 | } |
5622 | 0 | } |
5623 | 0 | } |
5624 | | |
5625 | | void |
5626 | | nsBlockFrame::DoRemoveOutOfFlowFrame(nsIFrame* aFrame) |
5627 | 0 | { |
5628 | 0 | // The containing block is always the parent of aFrame. |
5629 | 0 | nsBlockFrame* block = (nsBlockFrame*)aFrame->GetParent(); |
5630 | 0 |
|
5631 | 0 | // Remove aFrame from the appropriate list. |
5632 | 0 | if (aFrame->IsAbsolutelyPositioned()) { |
5633 | 0 | // This also deletes the next-in-flows |
5634 | 0 | block->GetAbsoluteContainingBlock()->RemoveFrame(block, |
5635 | 0 | kAbsoluteList, |
5636 | 0 | aFrame); |
5637 | 0 | } |
5638 | 0 | else { |
5639 | 0 | // First remove aFrame's next-in-flows. |
5640 | 0 | nsIFrame* nif = aFrame->GetNextInFlow(); |
5641 | 0 | if (nif) { |
5642 | 0 | nif->GetParent()->DeleteNextInFlowChild(nif, false); |
5643 | 0 | } |
5644 | 0 | // Now remove aFrame from its child list and Destroy it. |
5645 | 0 | block->RemoveFloatFromFloatCache(aFrame); |
5646 | 0 | block->RemoveFloat(aFrame); |
5647 | 0 | aFrame->Destroy(); |
5648 | 0 | } |
5649 | 0 | } |
5650 | | |
5651 | | /** |
5652 | | * This helps us iterate over the list of all normal + overflow lines |
5653 | | */ |
5654 | | void |
5655 | | nsBlockFrame::TryAllLines(nsLineList::iterator* aIterator, |
5656 | | nsLineList::iterator* aStartIterator, |
5657 | | nsLineList::iterator* aEndIterator, |
5658 | | bool* aInOverflowLines, |
5659 | | FrameLines** aOverflowLines) |
5660 | 0 | { |
5661 | 0 | if (*aIterator == *aEndIterator) { |
5662 | 0 | if (!*aInOverflowLines) { |
5663 | 0 | // Try the overflow lines |
5664 | 0 | *aInOverflowLines = true; |
5665 | 0 | FrameLines* lines = GetOverflowLines(); |
5666 | 0 | if (lines) { |
5667 | 0 | *aStartIterator = lines->mLines.begin(); |
5668 | 0 | *aIterator = *aStartIterator; |
5669 | 0 | *aEndIterator = lines->mLines.end(); |
5670 | 0 | *aOverflowLines = lines; |
5671 | 0 | } |
5672 | 0 | } |
5673 | 0 | } |
5674 | 0 | } |
5675 | | |
5676 | | nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame, |
5677 | | LineIterator aLine) |
5678 | | : mFrame(aFrame), mLine(aLine), mLineList(&aFrame->mLines) |
5679 | 0 | { |
5680 | 0 | // This will assert if aLine isn't in mLines of aFrame: |
5681 | 0 | DebugOnly<bool> check = aLine == mFrame->LinesBegin(); |
5682 | 0 | } |
5683 | | |
5684 | | nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame, |
5685 | | LineIterator aLine, bool aInOverflow) |
5686 | | : mFrame(aFrame), mLine(aLine), |
5687 | | mLineList(aInOverflow ? &aFrame->GetOverflowLines()->mLines |
5688 | | : &aFrame->mLines) |
5689 | 0 | { |
5690 | 0 | } |
5691 | | |
5692 | | nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame, |
5693 | | bool* aFoundValidLine) |
5694 | | : mFrame(aFrame), mLineList(&aFrame->mLines) |
5695 | 0 | { |
5696 | 0 | mLine = aFrame->LinesBegin(); |
5697 | 0 | *aFoundValidLine = FindValidLine(); |
5698 | 0 | } |
5699 | | |
5700 | | void |
5701 | | nsBlockFrame::UpdateFirstLetterStyle(ServoRestyleState& aRestyleState) |
5702 | 0 | { |
5703 | 0 | nsIFrame* letterFrame = GetFirstLetter(); |
5704 | 0 | if (!letterFrame) { |
5705 | 0 | return; |
5706 | 0 | } |
5707 | 0 | |
5708 | 0 | // Figure out what the right style parent is. This needs to match |
5709 | 0 | // nsCSSFrameConstructor::CreateLetterFrame. |
5710 | 0 | nsIFrame* inFlowFrame = letterFrame; |
5711 | 0 | if (inFlowFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) { |
5712 | 0 | inFlowFrame = inFlowFrame->GetPlaceholderFrame(); |
5713 | 0 | } |
5714 | 0 | nsIFrame* styleParent = |
5715 | 0 | CorrectStyleParentFrame(inFlowFrame->GetParent(), |
5716 | 0 | nsCSSPseudoElements::firstLetter()); |
5717 | 0 | ComputedStyle* parentStyle = styleParent->Style(); |
5718 | 0 | RefPtr<ComputedStyle> firstLetterStyle = |
5719 | 0 | aRestyleState.StyleSet() |
5720 | 0 | .ResolvePseudoElementStyle(mContent->AsElement(), |
5721 | 0 | CSSPseudoElementType::firstLetter, |
5722 | 0 | parentStyle, |
5723 | 0 | nullptr); |
5724 | 0 | // Note that we don't need to worry about changehints for the continuation |
5725 | 0 | // styles: those will be handled by the styleParent already. |
5726 | 0 | RefPtr<ComputedStyle> continuationStyle = |
5727 | 0 | aRestyleState.StyleSet().ResolveStyleForFirstLetterContinuation(parentStyle); |
5728 | 0 | UpdateStyleOfOwnedChildFrame(letterFrame, firstLetterStyle, aRestyleState, |
5729 | 0 | Some(continuationStyle.get())); |
5730 | 0 |
|
5731 | 0 | // We also want to update the style on the textframe inside the first-letter. |
5732 | 0 | // We don't need to compute a changehint for this, though, since any changes |
5733 | 0 | // to it are handled by the first-letter anyway. |
5734 | 0 | nsIFrame* textFrame = letterFrame->PrincipalChildList().FirstChild(); |
5735 | 0 | RefPtr<ComputedStyle> firstTextStyle = |
5736 | 0 | aRestyleState.StyleSet().ResolveStyleForText(textFrame->GetContent(), |
5737 | 0 | firstLetterStyle); |
5738 | 0 | textFrame->SetComputedStyle(firstTextStyle); |
5739 | 0 |
|
5740 | 0 | // We don't need to update style for textFrame's continuations: it's already |
5741 | 0 | // set up to inherit from parentStyle, which is what we want. |
5742 | 0 | } |
5743 | | |
5744 | | static nsIFrame* |
5745 | | FindChildContaining(nsBlockFrame* aFrame, nsIFrame* aFindFrame) |
5746 | 0 | { |
5747 | 0 | NS_ASSERTION(aFrame, "must have frame"); |
5748 | 0 | nsIFrame* child; |
5749 | 0 | while (true) { |
5750 | 0 | nsIFrame* block = aFrame; |
5751 | 0 | do { |
5752 | 0 | child = nsLayoutUtils::FindChildContainingDescendant(block, aFindFrame); |
5753 | 0 | if (child) |
5754 | 0 | break; |
5755 | 0 | block = block->GetNextContinuation(); |
5756 | 0 | } while (block); |
5757 | 0 | if (!child) |
5758 | 0 | return nullptr; |
5759 | 0 | if (!(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) |
5760 | 0 | break; |
5761 | 0 | aFindFrame = child->GetPlaceholderFrame(); |
5762 | 0 | } |
5763 | 0 |
|
5764 | 0 | return child; |
5765 | 0 | } |
5766 | | |
5767 | | nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame, |
5768 | | nsIFrame* aFindFrame, bool* aFoundValidLine) |
5769 | | : mFrame(aFrame), mLineList(&aFrame->mLines) |
5770 | 0 | { |
5771 | 0 | *aFoundValidLine = false; |
5772 | 0 |
|
5773 | 0 | nsIFrame* child = FindChildContaining(aFrame, aFindFrame); |
5774 | 0 | if (!child) |
5775 | 0 | return; |
5776 | 0 | |
5777 | 0 | LineIterator line_end = aFrame->LinesEnd(); |
5778 | 0 | mLine = aFrame->LinesBegin(); |
5779 | 0 | if (mLine != line_end && mLine.next() == line_end && |
5780 | 0 | !aFrame->HasOverflowLines()) { |
5781 | 0 | // The block has a single line - that must be it! |
5782 | 0 | *aFoundValidLine = true; |
5783 | 0 | return; |
5784 | 0 | } |
5785 | 0 | |
5786 | 0 | // Try to use the cursor if it exists, otherwise fall back to the first line |
5787 | 0 | if (nsLineBox* const cursor = aFrame->GetLineCursor()) { |
5788 | 0 | mLine = line_end; |
5789 | 0 | // Perform a simultaneous forward and reverse search starting from the |
5790 | 0 | // line cursor. |
5791 | 0 | nsBlockFrame::LineIterator line = aFrame->LinesBeginFrom(cursor); |
5792 | 0 | nsBlockFrame::ReverseLineIterator rline = aFrame->LinesRBeginFrom(cursor); |
5793 | 0 | nsBlockFrame::ReverseLineIterator rline_end = aFrame->LinesREnd(); |
5794 | 0 | // rline is positioned on the line containing 'cursor', so it's not |
5795 | 0 | // rline_end. So we can safely increment it (i.e. move it to one line |
5796 | 0 | // earlier) to start searching there. |
5797 | 0 | ++rline; |
5798 | 0 | while (line != line_end || rline != rline_end) { |
5799 | 0 | if (line != line_end) { |
5800 | 0 | if (line->Contains(child)) { |
5801 | 0 | mLine = line; |
5802 | 0 | break; |
5803 | 0 | } |
5804 | 0 | ++line; |
5805 | 0 | } |
5806 | 0 | if (rline != rline_end) { |
5807 | 0 | if (rline->Contains(child)) { |
5808 | 0 | mLine = rline; |
5809 | 0 | break; |
5810 | 0 | } |
5811 | 0 | ++rline; |
5812 | 0 | } |
5813 | 0 | } |
5814 | 0 | if (mLine != line_end) { |
5815 | 0 | *aFoundValidLine = true; |
5816 | 0 | if (mLine != cursor) { |
5817 | 0 | aFrame->SetProperty(nsBlockFrame::LineCursorProperty(), mLine); |
5818 | 0 | } |
5819 | 0 | return; |
5820 | 0 | } |
5821 | 0 | } else { |
5822 | 0 | for (mLine = aFrame->LinesBegin(); mLine != line_end; ++mLine) { |
5823 | 0 | if (mLine->Contains(child)) { |
5824 | 0 | *aFoundValidLine = true; |
5825 | 0 | return; |
5826 | 0 | } |
5827 | 0 | } |
5828 | 0 | } |
5829 | 0 | // Didn't find the line |
5830 | 0 | MOZ_ASSERT(mLine == line_end, "mLine should be line_end at this point"); |
5831 | 0 |
|
5832 | 0 | // If we reach here, it means that we have not been able to find the |
5833 | 0 | // desired frame in our in-flow lines. So we should start looking at |
5834 | 0 | // our overflow lines. In order to do that, we set mLine to the end |
5835 | 0 | // iterator so that FindValidLine starts to look at overflow lines, |
5836 | 0 | // if any. |
5837 | 0 |
|
5838 | 0 | if (!FindValidLine()) |
5839 | 0 | return; |
5840 | 0 | |
5841 | 0 | do { |
5842 | 0 | if (mLine->Contains(child)) { |
5843 | 0 | *aFoundValidLine = true; |
5844 | 0 | return; |
5845 | 0 | } |
5846 | 0 | } while (Next()); |
5847 | 0 | } |
5848 | | |
5849 | | nsBlockFrame::LineIterator |
5850 | | nsBlockInFlowLineIterator::End() |
5851 | 0 | { |
5852 | 0 | return mLineList->end(); |
5853 | 0 | } |
5854 | | |
5855 | | bool |
5856 | | nsBlockInFlowLineIterator::IsLastLineInList() |
5857 | 0 | { |
5858 | 0 | LineIterator end = End(); |
5859 | 0 | return mLine != end && mLine.next() == end; |
5860 | 0 | } |
5861 | | |
5862 | | bool |
5863 | | nsBlockInFlowLineIterator::Next() |
5864 | 0 | { |
5865 | 0 | ++mLine; |
5866 | 0 | return FindValidLine(); |
5867 | 0 | } |
5868 | | |
5869 | | bool |
5870 | | nsBlockInFlowLineIterator::Prev() |
5871 | 0 | { |
5872 | 0 | LineIterator begin = mLineList->begin(); |
5873 | 0 | if (mLine != begin) { |
5874 | 0 | --mLine; |
5875 | 0 | return true; |
5876 | 0 | } |
5877 | 0 | bool currentlyInOverflowLines = GetInOverflow(); |
5878 | 0 | while (true) { |
5879 | 0 | if (currentlyInOverflowLines) { |
5880 | 0 | mLineList = &mFrame->mLines; |
5881 | 0 | mLine = mLineList->end(); |
5882 | 0 | if (mLine != mLineList->begin()) { |
5883 | 0 | --mLine; |
5884 | 0 | return true; |
5885 | 0 | } |
5886 | 0 | } else { |
5887 | 0 | mFrame = static_cast<nsBlockFrame*>(mFrame->GetPrevInFlow()); |
5888 | 0 | if (!mFrame) |
5889 | 0 | return false; |
5890 | 0 | nsBlockFrame::FrameLines* overflowLines = mFrame->GetOverflowLines(); |
5891 | 0 | if (overflowLines) { |
5892 | 0 | mLineList = &overflowLines->mLines; |
5893 | 0 | mLine = mLineList->end(); |
5894 | 0 | NS_ASSERTION(mLine != mLineList->begin(), "empty overflow line list?"); |
5895 | 0 | --mLine; |
5896 | 0 | return true; |
5897 | 0 | } |
5898 | 0 | } |
5899 | 0 | currentlyInOverflowLines = !currentlyInOverflowLines; |
5900 | 0 | } |
5901 | 0 | } |
5902 | | |
5903 | | bool |
5904 | | nsBlockInFlowLineIterator::FindValidLine() |
5905 | 0 | { |
5906 | 0 | LineIterator end = mLineList->end(); |
5907 | 0 | if (mLine != end) |
5908 | 0 | return true; |
5909 | 0 | bool currentlyInOverflowLines = GetInOverflow(); |
5910 | 0 | while (true) { |
5911 | 0 | if (currentlyInOverflowLines) { |
5912 | 0 | mFrame = static_cast<nsBlockFrame*>(mFrame->GetNextInFlow()); |
5913 | 0 | if (!mFrame) |
5914 | 0 | return false; |
5915 | 0 | mLineList = &mFrame->mLines; |
5916 | 0 | mLine = mLineList->begin(); |
5917 | 0 | if (mLine != mLineList->end()) |
5918 | 0 | return true; |
5919 | 0 | } else { |
5920 | 0 | nsBlockFrame::FrameLines* overflowLines = mFrame->GetOverflowLines(); |
5921 | 0 | if (overflowLines) { |
5922 | 0 | mLineList = &overflowLines->mLines; |
5923 | 0 | mLine = mLineList->begin(); |
5924 | 0 | NS_ASSERTION(mLine != mLineList->end(), "empty overflow line list?"); |
5925 | 0 | return true; |
5926 | 0 | } |
5927 | 0 | } |
5928 | 0 | currentlyInOverflowLines = !currentlyInOverflowLines; |
5929 | 0 | } |
5930 | 0 | } |
5931 | | |
5932 | | // This function removes aDeletedFrame and all its continuations. It |
5933 | | // is optimized for deleting a whole series of frames. The easy |
5934 | | // implementation would invoke itself recursively on |
5935 | | // aDeletedFrame->GetNextContinuation, then locate the line containing |
5936 | | // aDeletedFrame and remove aDeletedFrame from that line. But here we |
5937 | | // start by locating aDeletedFrame and then scanning from that point |
5938 | | // on looking for continuations. |
5939 | | void |
5940 | | nsBlockFrame::DoRemoveFrameInternal(nsIFrame* aDeletedFrame, uint32_t aFlags, |
5941 | | PostDestroyData& aPostDestroyData) |
5942 | 0 | { |
5943 | 0 | // Clear our line cursor, since our lines may change. |
5944 | 0 | ClearLineCursor(); |
5945 | 0 |
|
5946 | 0 | if (aDeletedFrame->GetStateBits() & |
5947 | 0 | (NS_FRAME_OUT_OF_FLOW | NS_FRAME_IS_OVERFLOW_CONTAINER)) { |
5948 | 0 | if (!aDeletedFrame->GetPrevInFlow()) { |
5949 | 0 | NS_ASSERTION(aDeletedFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW, |
5950 | 0 | "Expected out-of-flow frame"); |
5951 | 0 | DoRemoveOutOfFlowFrame(aDeletedFrame); |
5952 | 0 | } |
5953 | 0 | else { |
5954 | 0 | nsContainerFrame::DeleteNextInFlowChild(aDeletedFrame, |
5955 | 0 | (aFlags & FRAMES_ARE_EMPTY) != 0); |
5956 | 0 | } |
5957 | 0 | return; |
5958 | 0 | } |
5959 | 0 |
|
5960 | 0 | // Find the line that contains deletedFrame |
5961 | 0 | nsLineList::iterator line_start = mLines.begin(), |
5962 | 0 | line_end = mLines.end(); |
5963 | 0 | nsLineList::iterator line = line_start; |
5964 | 0 | FrameLines* overflowLines = nullptr; |
5965 | 0 | bool searchingOverflowList = false; |
5966 | 0 | // Make sure we look in the overflow lines even if the normal line |
5967 | 0 | // list is empty |
5968 | 0 | TryAllLines(&line, &line_start, &line_end, &searchingOverflowList, |
5969 | 0 | &overflowLines); |
5970 | 0 | while (line != line_end) { |
5971 | 0 | if (line->Contains(aDeletedFrame)) { |
5972 | 0 | break; |
5973 | 0 | } |
5974 | 0 | ++line; |
5975 | 0 | TryAllLines(&line, &line_start, &line_end, &searchingOverflowList, |
5976 | 0 | &overflowLines); |
5977 | 0 | } |
5978 | 0 |
|
5979 | 0 | if (line == line_end) { |
5980 | 0 | NS_ERROR("can't find deleted frame in lines"); |
5981 | 0 | return; |
5982 | 0 | } |
5983 | 0 |
|
5984 | 0 | if (!(aFlags & FRAMES_ARE_EMPTY)) { |
5985 | 0 | if (line != line_start) { |
5986 | 0 | line.prev()->MarkDirty(); |
5987 | 0 | line.prev()->SetInvalidateTextRuns(true); |
5988 | 0 | } |
5989 | 0 | else if (searchingOverflowList && !mLines.empty()) { |
5990 | 0 | mLines.back()->MarkDirty(); |
5991 | 0 | mLines.back()->SetInvalidateTextRuns(true); |
5992 | 0 | } |
5993 | 0 | } |
5994 | 0 |
|
5995 | 0 | while (line != line_end && aDeletedFrame) { |
5996 | 0 | NS_ASSERTION(this == aDeletedFrame->GetParent(), "messed up delete code"); |
5997 | 0 | NS_ASSERTION(line->Contains(aDeletedFrame), "frame not in line"); |
5998 | 0 |
|
5999 | 0 | if (!(aFlags & FRAMES_ARE_EMPTY)) { |
6000 | 0 | line->MarkDirty(); |
6001 | 0 | line->SetInvalidateTextRuns(true); |
6002 | 0 | } |
6003 | 0 |
|
6004 | 0 | // If the frame being deleted is the last one on the line then |
6005 | 0 | // optimize away the line->Contains(next-in-flow) call below. |
6006 | 0 | bool isLastFrameOnLine = 1 == line->GetChildCount(); |
6007 | 0 | if (!isLastFrameOnLine) { |
6008 | 0 | LineIterator next = line.next(); |
6009 | 0 | nsIFrame* lastFrame = next != line_end ? |
6010 | 0 | next->mFirstChild->GetPrevSibling() : |
6011 | 0 | (searchingOverflowList ? overflowLines->mFrames.LastChild() : |
6012 | 0 | mFrames.LastChild()); |
6013 | 0 | NS_ASSERTION(next == line_end || lastFrame == line->LastChild(), |
6014 | 0 | "unexpected line frames"); |
6015 | 0 | isLastFrameOnLine = lastFrame == aDeletedFrame; |
6016 | 0 | } |
6017 | 0 |
|
6018 | 0 | // Remove aDeletedFrame from the line |
6019 | 0 | if (line->mFirstChild == aDeletedFrame) { |
6020 | 0 | // We should be setting this to null if aDeletedFrame |
6021 | 0 | // is the only frame on the line. HOWEVER in that case |
6022 | 0 | // we will be removing the line anyway, see below. |
6023 | 0 | line->mFirstChild = aDeletedFrame->GetNextSibling(); |
6024 | 0 | } |
6025 | 0 |
|
6026 | 0 | // Hmm, this won't do anything if we're removing a frame in the first |
6027 | 0 | // overflow line... Hopefully doesn't matter |
6028 | 0 | --line; |
6029 | 0 | if (line != line_end && !line->IsBlock()) { |
6030 | 0 | // Since we just removed a frame that follows some inline |
6031 | 0 | // frames, we need to reflow the previous line. |
6032 | 0 | line->MarkDirty(); |
6033 | 0 | } |
6034 | 0 | ++line; |
6035 | 0 |
|
6036 | 0 | // Take aDeletedFrame out of the sibling list. Note that |
6037 | 0 | // prevSibling will only be nullptr when we are deleting the very |
6038 | 0 | // first frame in the main or overflow list. |
6039 | 0 | if (searchingOverflowList) { |
6040 | 0 | overflowLines->mFrames.RemoveFrame(aDeletedFrame); |
6041 | 0 | } else { |
6042 | 0 | mFrames.RemoveFrame(aDeletedFrame); |
6043 | 0 | } |
6044 | 0 |
|
6045 | 0 | // Update the child count of the line to be accurate |
6046 | 0 | line->NoteFrameRemoved(aDeletedFrame); |
6047 | 0 |
|
6048 | 0 | // Destroy frame; capture its next continuation first in case we need |
6049 | 0 | // to destroy that too. |
6050 | 0 | nsIFrame* deletedNextContinuation = (aFlags & REMOVE_FIXED_CONTINUATIONS) ? |
6051 | 0 | aDeletedFrame->GetNextContinuation() : aDeletedFrame->GetNextInFlow(); |
6052 | | #ifdef NOISY_REMOVE_FRAME |
6053 | | printf("DoRemoveFrame: %s line=%p frame=", |
6054 | | searchingOverflowList?"overflow":"normal", line.get()); |
6055 | | nsFrame::ListTag(stdout, aDeletedFrame); |
6056 | | printf(" prevSibling=%p deletedNextContinuation=%p\n", |
6057 | | aDeletedFrame->GetPrevSibling(), deletedNextContinuation); |
6058 | | #endif |
6059 | |
|
6060 | 0 | // If next-in-flow is an overflow container, must remove it first. |
6061 | 0 | if (deletedNextContinuation && |
6062 | 0 | deletedNextContinuation->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER) { |
6063 | 0 | deletedNextContinuation->GetParent()-> |
6064 | 0 | DeleteNextInFlowChild(deletedNextContinuation, false); |
6065 | 0 | deletedNextContinuation = nullptr; |
6066 | 0 | } |
6067 | 0 |
|
6068 | 0 | aDeletedFrame->DestroyFrom(aDeletedFrame, aPostDestroyData); |
6069 | 0 | aDeletedFrame = deletedNextContinuation; |
6070 | 0 |
|
6071 | 0 | bool haveAdvancedToNextLine = false; |
6072 | 0 | // If line is empty, remove it now. |
6073 | 0 | if (0 == line->GetChildCount()) { |
6074 | | #ifdef NOISY_REMOVE_FRAME |
6075 | | printf("DoRemoveFrame: %s line=%p became empty so it will be removed\n", |
6076 | | searchingOverflowList?"overflow":"normal", line.get()); |
6077 | | #endif |
6078 | | nsLineBox *cur = line; |
6079 | 0 | if (!searchingOverflowList) { |
6080 | 0 | line = mLines.erase(line); |
6081 | 0 | // Invalidate the space taken up by the line. |
6082 | 0 | // XXX We need to do this if we're removing a frame as a result of |
6083 | 0 | // a call to RemoveFrame(), but we may not need to do this in all |
6084 | 0 | // cases... |
6085 | | #ifdef NOISY_BLOCK_INVALIDATE |
6086 | | nsRect visOverflow(cur->GetVisualOverflowArea()); |
6087 | | printf("%p invalidate 10 (%d, %d, %d, %d)\n", |
6088 | | this, visOverflow.x, visOverflow.y, |
6089 | | visOverflow.width, visOverflow.height); |
6090 | | #endif |
6091 | 0 | } else { |
6092 | 0 | line = overflowLines->mLines.erase(line); |
6093 | 0 | if (overflowLines->mLines.empty()) { |
6094 | 0 | DestroyOverflowLines(); |
6095 | 0 | overflowLines = nullptr; |
6096 | 0 | // We just invalidated our iterators. Since we were in |
6097 | 0 | // the overflow lines list, which is now empty, set them |
6098 | 0 | // so we're at the end of the regular line list. |
6099 | 0 | line_start = mLines.begin(); |
6100 | 0 | line_end = mLines.end(); |
6101 | 0 | line = line_end; |
6102 | 0 | } |
6103 | 0 | } |
6104 | 0 | FreeLineBox(cur); |
6105 | 0 |
|
6106 | 0 | // If we're removing a line, ReflowDirtyLines isn't going to |
6107 | 0 | // know that it needs to slide lines unless something is marked |
6108 | 0 | // dirty. So mark the previous margin of the next line dirty if |
6109 | 0 | // there is one. |
6110 | 0 | if (line != line_end) { |
6111 | 0 | line->MarkPreviousMarginDirty(); |
6112 | 0 | } |
6113 | 0 | haveAdvancedToNextLine = true; |
6114 | 0 | } else { |
6115 | 0 | // Make the line that just lost a frame dirty, and advance to |
6116 | 0 | // the next line. |
6117 | 0 | if (!deletedNextContinuation || isLastFrameOnLine || |
6118 | 0 | !line->Contains(deletedNextContinuation)) { |
6119 | 0 | line->MarkDirty(); |
6120 | 0 | ++line; |
6121 | 0 | haveAdvancedToNextLine = true; |
6122 | 0 | } |
6123 | 0 | } |
6124 | 0 |
|
6125 | 0 | if (deletedNextContinuation) { |
6126 | 0 | // See if we should keep looking in the current flow's line list. |
6127 | 0 | if (deletedNextContinuation->GetParent() != this) { |
6128 | 0 | // The deceased frames continuation is not a child of the |
6129 | 0 | // current block. So break out of the loop so that we advance |
6130 | 0 | // to the next parent. |
6131 | 0 | // |
6132 | 0 | // If we have a continuation in a different block then all bets are |
6133 | 0 | // off regarding whether we are deleting frames without actual content, |
6134 | 0 | // so don't propagate FRAMES_ARE_EMPTY any further. |
6135 | 0 | aFlags &= ~FRAMES_ARE_EMPTY; |
6136 | 0 | break; |
6137 | 0 | } |
6138 | 0 | |
6139 | 0 | // If we advanced to the next line then check if we should switch to the |
6140 | 0 | // overflow line list. |
6141 | 0 | if (haveAdvancedToNextLine) { |
6142 | 0 | if (line != line_end && !searchingOverflowList && |
6143 | 0 | !line->Contains(deletedNextContinuation)) { |
6144 | 0 | // We have advanced to the next *normal* line but the next-in-flow |
6145 | 0 | // is not there - force a switch to the overflow line list. |
6146 | 0 | line = line_end; |
6147 | 0 | } |
6148 | 0 |
|
6149 | 0 | TryAllLines(&line, &line_start, &line_end, &searchingOverflowList, |
6150 | 0 | &overflowLines); |
6151 | | #ifdef NOISY_REMOVE_FRAME |
6152 | | printf("DoRemoveFrame: now on %s line=%p\n", |
6153 | | searchingOverflowList?"overflow":"normal", line.get()); |
6154 | | #endif |
6155 | | } |
6156 | 0 | } |
6157 | 0 | } |
6158 | 0 |
|
6159 | 0 | if (!(aFlags & FRAMES_ARE_EMPTY) && line.next() != line_end) { |
6160 | 0 | line.next()->MarkDirty(); |
6161 | 0 | line.next()->SetInvalidateTextRuns(true); |
6162 | 0 | } |
6163 | 0 |
|
6164 | | #ifdef DEBUG |
6165 | | VerifyLines(true); |
6166 | | VerifyOverflowSituation(); |
6167 | | #endif |
6168 | |
|
6169 | 0 | // Advance to next flow block if the frame has more continuations. |
6170 | 0 | if (!aDeletedFrame) { |
6171 | 0 | return; |
6172 | 0 | } |
6173 | 0 | nsBlockFrame* nextBlock = nsLayoutUtils::GetAsBlock(aDeletedFrame->GetParent()); |
6174 | 0 | NS_ASSERTION(nextBlock, |
6175 | 0 | "Our child's continuation's parent is not a block?"); |
6176 | 0 | uint32_t flags = (aFlags & REMOVE_FIXED_CONTINUATIONS); |
6177 | 0 | nextBlock->DoRemoveFrameInternal(aDeletedFrame, flags, aPostDestroyData); |
6178 | 0 | } |
6179 | | |
6180 | | static bool |
6181 | | FindBlockLineFor(nsIFrame* aChild, |
6182 | | nsLineList::iterator aBegin, |
6183 | | nsLineList::iterator aEnd, |
6184 | | nsLineList::iterator* aResult) |
6185 | 0 | { |
6186 | 0 | MOZ_ASSERT(aChild->IsBlockOutside()); |
6187 | 0 | for (nsLineList::iterator line = aBegin; line != aEnd; ++line) { |
6188 | 0 | MOZ_ASSERT(line->GetChildCount() > 0); |
6189 | 0 | if (line->IsBlock() && line->mFirstChild == aChild) { |
6190 | 0 | MOZ_ASSERT(line->GetChildCount() == 1); |
6191 | 0 | *aResult = line; |
6192 | 0 | return true; |
6193 | 0 | } |
6194 | 0 | } |
6195 | 0 | return false; |
6196 | 0 | } |
6197 | | |
6198 | | static bool |
6199 | | FindInlineLineFor(nsIFrame* aChild, |
6200 | | const nsFrameList& aFrameList, |
6201 | | nsLineList::iterator aBegin, |
6202 | | nsLineList::iterator aEnd, |
6203 | | nsLineList::iterator* aResult) |
6204 | 0 | { |
6205 | 0 | MOZ_ASSERT(!aChild->IsBlockOutside()); |
6206 | 0 | for (nsLineList::iterator line = aBegin; line != aEnd; ++line) { |
6207 | 0 | MOZ_ASSERT(line->GetChildCount() > 0); |
6208 | 0 | if (!line->IsBlock()) { |
6209 | 0 | // Optimize by comparing the line's last child first. |
6210 | 0 | nsLineList::iterator next = line.next(); |
6211 | 0 | if (aChild == (next == aEnd ? aFrameList.LastChild() |
6212 | 0 | : next->mFirstChild->GetPrevSibling()) || |
6213 | 0 | line->Contains(aChild)) { |
6214 | 0 | *aResult = line; |
6215 | 0 | return true; |
6216 | 0 | } |
6217 | 0 | } |
6218 | 0 | } |
6219 | 0 | return false; |
6220 | 0 | } |
6221 | | |
6222 | | static bool |
6223 | | FindLineFor(nsIFrame* aChild, |
6224 | | const nsFrameList& aFrameList, |
6225 | | nsLineList::iterator aBegin, |
6226 | | nsLineList::iterator aEnd, |
6227 | | nsLineList::iterator* aResult) |
6228 | 0 | { |
6229 | 0 | return aChild->IsBlockOutside() ? |
6230 | 0 | FindBlockLineFor(aChild, aBegin, aEnd, aResult) : |
6231 | 0 | FindInlineLineFor(aChild, aFrameList, aBegin, aEnd, aResult); |
6232 | 0 | } |
6233 | | |
6234 | | nsresult |
6235 | | nsBlockFrame::StealFrame(nsIFrame* aChild) |
6236 | 0 | { |
6237 | 0 | MOZ_ASSERT(aChild->GetParent() == this); |
6238 | 0 |
|
6239 | 0 | if ((aChild->GetStateBits() & NS_FRAME_OUT_OF_FLOW) && |
6240 | 0 | aChild->IsFloating()) { |
6241 | 0 | RemoveFloat(aChild); |
6242 | 0 | return NS_OK; |
6243 | 0 | } |
6244 | 0 | |
6245 | 0 | if (MaybeStealOverflowContainerFrame(aChild)) { |
6246 | 0 | return NS_OK; |
6247 | 0 | } |
6248 | 0 | |
6249 | 0 | MOZ_ASSERT(!(aChild->GetStateBits() & NS_FRAME_OUT_OF_FLOW)); |
6250 | 0 |
|
6251 | 0 | nsLineList::iterator line; |
6252 | 0 | if (FindLineFor(aChild, mFrames, mLines.begin(), mLines.end(), &line)) { |
6253 | 0 | RemoveFrameFromLine(aChild, line, mFrames, mLines); |
6254 | 0 | } else { |
6255 | 0 | FrameLines* overflowLines = GetOverflowLines(); |
6256 | 0 | DebugOnly<bool> found; |
6257 | 0 | found = FindLineFor(aChild, overflowLines->mFrames, |
6258 | 0 | overflowLines->mLines.begin(), |
6259 | 0 | overflowLines->mLines.end(), &line); |
6260 | 0 | MOZ_ASSERT(found); |
6261 | 0 | RemoveFrameFromLine(aChild, line, overflowLines->mFrames, |
6262 | 0 | overflowLines->mLines); |
6263 | 0 | if (overflowLines->mLines.empty()) { |
6264 | 0 | DestroyOverflowLines(); |
6265 | 0 | } |
6266 | 0 | } |
6267 | 0 |
|
6268 | 0 | return NS_OK; |
6269 | 0 | } |
6270 | | |
6271 | | void |
6272 | | nsBlockFrame::RemoveFrameFromLine(nsIFrame* aChild, nsLineList::iterator aLine, |
6273 | | nsFrameList& aFrameList, nsLineList& aLineList) |
6274 | 0 | { |
6275 | 0 | aFrameList.RemoveFrame(aChild); |
6276 | 0 | if (aChild == aLine->mFirstChild) { |
6277 | 0 | aLine->mFirstChild = aChild->GetNextSibling(); |
6278 | 0 | } |
6279 | 0 | aLine->NoteFrameRemoved(aChild); |
6280 | 0 | if (aLine->GetChildCount() > 0) { |
6281 | 0 | aLine->MarkDirty(); |
6282 | 0 | } else { |
6283 | 0 | // The line became empty - destroy it. |
6284 | 0 | nsLineBox* lineBox = aLine; |
6285 | 0 | aLine = aLineList.erase(aLine); |
6286 | 0 | if (aLine != aLineList.end()) { |
6287 | 0 | aLine->MarkPreviousMarginDirty(); |
6288 | 0 | } |
6289 | 0 | FreeLineBox(lineBox); |
6290 | 0 | } |
6291 | 0 | } |
6292 | | |
6293 | | void |
6294 | | nsBlockFrame::DeleteNextInFlowChild(nsIFrame* aNextInFlow, |
6295 | | bool aDeletingEmptyFrames) |
6296 | 0 | { |
6297 | 0 | MOZ_ASSERT(aNextInFlow->GetPrevInFlow(), "bad next-in-flow"); |
6298 | 0 |
|
6299 | 0 | if (aNextInFlow->GetStateBits() & |
6300 | 0 | (NS_FRAME_OUT_OF_FLOW | NS_FRAME_IS_OVERFLOW_CONTAINER)) { |
6301 | 0 | nsContainerFrame::DeleteNextInFlowChild(aNextInFlow, aDeletingEmptyFrames); |
6302 | 0 | } |
6303 | 0 | else { |
6304 | | #ifdef DEBUG |
6305 | | if (aDeletingEmptyFrames) { |
6306 | | nsLayoutUtils::AssertTreeOnlyEmptyNextInFlows(aNextInFlow); |
6307 | | } |
6308 | | #endif |
6309 | | DoRemoveFrame(aNextInFlow, |
6310 | 0 | aDeletingEmptyFrames ? FRAMES_ARE_EMPTY : 0); |
6311 | 0 | } |
6312 | 0 | } |
6313 | | |
6314 | | const nsStyleText* |
6315 | | nsBlockFrame::StyleTextForLineLayout() |
6316 | 0 | { |
6317 | 0 | // Return the pointer to an unmodified style text |
6318 | 0 | return StyleText(); |
6319 | 0 | } |
6320 | | |
6321 | | //////////////////////////////////////////////////////////////////////// |
6322 | | // Float support |
6323 | | |
6324 | | LogicalRect |
6325 | | nsBlockFrame::AdjustFloatAvailableSpace(BlockReflowInput& aState, |
6326 | | const LogicalRect& aFloatAvailableSpace, |
6327 | | nsIFrame* aFloatFrame) |
6328 | 0 | { |
6329 | 0 | // Compute the available inline size. By default, assume the inline |
6330 | 0 | // size of the containing block. |
6331 | 0 | nscoord availISize; |
6332 | 0 | const nsStyleDisplay* floatDisplay = aFloatFrame->StyleDisplay(); |
6333 | 0 | WritingMode wm = aState.mReflowInput.GetWritingMode(); |
6334 | 0 |
|
6335 | 0 | if (mozilla::StyleDisplay::Table != floatDisplay->mDisplay || |
6336 | 0 | eCompatibility_NavQuirks != aState.mPresContext->CompatibilityMode() ) { |
6337 | 0 | availISize = aState.ContentISize(); |
6338 | 0 | } |
6339 | 0 | else { |
6340 | 0 | // This quirk matches the one in BlockReflowInput::FlowAndPlaceFloat |
6341 | 0 | // give tables only the available space |
6342 | 0 | // if they can shrink we may not be constrained to place |
6343 | 0 | // them in the next line |
6344 | 0 | availISize = aFloatAvailableSpace.ISize(wm); |
6345 | 0 | } |
6346 | 0 |
|
6347 | 0 | nscoord availBSize = NS_UNCONSTRAINEDSIZE == aState.ContentBSize() |
6348 | 0 | ? NS_UNCONSTRAINEDSIZE |
6349 | 0 | : std::max(0, aState.ContentBEnd() - aState.mBCoord); |
6350 | 0 |
|
6351 | 0 | if (availBSize != NS_UNCONSTRAINEDSIZE && |
6352 | 0 | !aState.mFlags.mFloatFragmentsInsideColumnEnabled && |
6353 | 0 | nsLayoutUtils::GetClosestFrameOfType(this, LayoutFrameType::ColumnSet)) { |
6354 | 0 | // Tell the float it has unrestricted block-size, so it won't break. |
6355 | 0 | // If the float doesn't actually fit in the column it will fail to be |
6356 | 0 | // placed, and either move to the block-start of the next column or just |
6357 | 0 | // overflow. |
6358 | 0 | availBSize = NS_UNCONSTRAINEDSIZE; |
6359 | 0 | } |
6360 | 0 |
|
6361 | 0 | return LogicalRect(wm, aState.ContentIStart(), aState.ContentBStart(), |
6362 | 0 | availISize, availBSize); |
6363 | 0 | } |
6364 | | |
6365 | | nscoord |
6366 | | nsBlockFrame::ComputeFloatISize(BlockReflowInput& aState, |
6367 | | const LogicalRect& aFloatAvailableSpace, |
6368 | | nsIFrame* aFloat) |
6369 | 0 | { |
6370 | 0 | MOZ_ASSERT(aFloat->GetStateBits() & NS_FRAME_OUT_OF_FLOW, |
6371 | 0 | "aFloat must be an out-of-flow frame"); |
6372 | 0 |
|
6373 | 0 | // Reflow the float. |
6374 | 0 | LogicalRect availSpace = AdjustFloatAvailableSpace(aState, |
6375 | 0 | aFloatAvailableSpace, |
6376 | 0 | aFloat); |
6377 | 0 |
|
6378 | 0 | WritingMode blockWM = aState.mReflowInput.GetWritingMode(); |
6379 | 0 | WritingMode floatWM = aFloat->GetWritingMode(); |
6380 | 0 | ReflowInput |
6381 | 0 | floatRS(aState.mPresContext, aState.mReflowInput, aFloat, |
6382 | 0 | availSpace.Size(blockWM).ConvertTo(floatWM, blockWM)); |
6383 | 0 |
|
6384 | 0 | return floatRS.ComputedSizeWithMarginBorderPadding(blockWM).ISize(blockWM); |
6385 | 0 | } |
6386 | | |
6387 | | void |
6388 | | nsBlockFrame::ReflowFloat(BlockReflowInput& aState, |
6389 | | const LogicalRect& aAdjustedAvailableSpace, |
6390 | | nsIFrame* aFloat, |
6391 | | LogicalMargin& aFloatMargin, |
6392 | | LogicalMargin& aFloatOffsets, |
6393 | | bool aFloatPushedDown, |
6394 | | nsReflowStatus& aReflowStatus) |
6395 | 0 | { |
6396 | 0 | MOZ_ASSERT(aFloat->GetStateBits() & NS_FRAME_OUT_OF_FLOW, |
6397 | 0 | "aFloat must be an out-of-flow frame"); |
6398 | 0 |
|
6399 | 0 | // Reflow the float. |
6400 | 0 | aReflowStatus.Reset(); |
6401 | 0 |
|
6402 | 0 | WritingMode wm = aState.mReflowInput.GetWritingMode(); |
6403 | | #ifdef NOISY_FLOAT |
6404 | | printf("Reflow Float %p in parent %p, availSpace(%d,%d,%d,%d)\n", |
6405 | | aFloat, this, |
6406 | | aAdjustedAvailableSpace.IStart(wm), aAdjustedAvailableSpace.BStart(wm), |
6407 | | aAdjustedAvailableSpace.ISize(wm), aAdjustedAvailableSpace.BSize(wm) |
6408 | | ); |
6409 | | #endif |
6410 | |
|
6411 | 0 | ReflowInput |
6412 | 0 | floatRS(aState.mPresContext, aState.mReflowInput, aFloat, |
6413 | 0 | aAdjustedAvailableSpace.Size(wm).ConvertTo(aFloat->GetWritingMode(), |
6414 | 0 | wm)); |
6415 | 0 |
|
6416 | 0 | // Normally the mIsTopOfPage state is copied from the parent reflow |
6417 | 0 | // state. However, when reflowing a float, if we've placed other |
6418 | 0 | // floats that force this float *down* or *narrower*, we should unset |
6419 | 0 | // the mIsTopOfPage state. |
6420 | 0 | // FIXME: This is somewhat redundant with the |isAdjacentWithTop| |
6421 | 0 | // variable below, which has the exact same effect. Perhaps it should |
6422 | 0 | // be merged into that, except that the test for narrowing here is not |
6423 | 0 | // about adjacency with the top, so it seems misleading. |
6424 | 0 | if (floatRS.mFlags.mIsTopOfPage && |
6425 | 0 | (aFloatPushedDown || |
6426 | 0 | aAdjustedAvailableSpace.ISize(wm) != aState.ContentISize())) { |
6427 | 0 | floatRS.mFlags.mIsTopOfPage = false; |
6428 | 0 | } |
6429 | 0 |
|
6430 | 0 | // Setup a block reflow context to reflow the float. |
6431 | 0 | nsBlockReflowContext brc(aState.mPresContext, aState.mReflowInput); |
6432 | 0 |
|
6433 | 0 | // Reflow the float |
6434 | 0 | bool isAdjacentWithTop = aState.IsAdjacentWithTop(); |
6435 | 0 |
|
6436 | 0 | nsIFrame* clearanceFrame = nullptr; |
6437 | 0 | do { |
6438 | 0 | nsCollapsingMargin margin; |
6439 | 0 | bool mayNeedRetry = false; |
6440 | 0 | floatRS.mDiscoveredClearance = nullptr; |
6441 | 0 | // Only first in flow gets a block-start margin. |
6442 | 0 | if (!aFloat->GetPrevInFlow()) { |
6443 | 0 | brc.ComputeCollapsedBStartMargin(floatRS, &margin, |
6444 | 0 | clearanceFrame, |
6445 | 0 | &mayNeedRetry); |
6446 | 0 |
|
6447 | 0 | if (mayNeedRetry && !clearanceFrame) { |
6448 | 0 | floatRS.mDiscoveredClearance = &clearanceFrame; |
6449 | 0 | // We don't need to push the float manager state because the the block has its own |
6450 | 0 | // float manager that will be destroyed and recreated |
6451 | 0 | } |
6452 | 0 | } |
6453 | 0 |
|
6454 | 0 | brc.ReflowBlock(aAdjustedAvailableSpace, true, margin, |
6455 | 0 | 0, isAdjacentWithTop, |
6456 | 0 | nullptr, floatRS, |
6457 | 0 | aReflowStatus, aState); |
6458 | 0 | } while (clearanceFrame); |
6459 | 0 |
|
6460 | 0 | if (!aReflowStatus.IsFullyComplete() && |
6461 | 0 | ShouldAvoidBreakInside(floatRS)) { |
6462 | 0 | aReflowStatus.SetInlineLineBreakBeforeAndReset(); |
6463 | 0 | } else if (aReflowStatus.IsIncomplete() && |
6464 | 0 | (NS_UNCONSTRAINEDSIZE == aAdjustedAvailableSpace.BSize(wm))) { |
6465 | 0 | // An incomplete reflow status means we should split the float |
6466 | 0 | // if the height is constrained (bug 145305). |
6467 | 0 | aReflowStatus.Reset(); |
6468 | 0 | } |
6469 | 0 |
|
6470 | 0 | if (aReflowStatus.NextInFlowNeedsReflow()) { |
6471 | 0 | aState.mReflowStatus.SetNextInFlowNeedsReflow(); |
6472 | 0 | } |
6473 | 0 |
|
6474 | 0 | if (aFloat->IsLetterFrame()) { |
6475 | 0 | // We never split floating first letters; an incomplete state for |
6476 | 0 | // such frames simply means that there is more content to be |
6477 | 0 | // reflowed on the line. |
6478 | 0 | if (aReflowStatus.IsIncomplete()) |
6479 | 0 | aReflowStatus.Reset(); |
6480 | 0 | } |
6481 | 0 |
|
6482 | 0 | // Capture the margin and offsets information for the caller |
6483 | 0 | aFloatMargin = |
6484 | 0 | // float margins don't collapse |
6485 | 0 | floatRS.ComputedLogicalMargin().ConvertTo(wm, floatRS.GetWritingMode()); |
6486 | 0 | aFloatOffsets = |
6487 | 0 | floatRS.ComputedLogicalOffsets().ConvertTo(wm, floatRS.GetWritingMode()); |
6488 | 0 |
|
6489 | 0 | const ReflowOutput& metrics = brc.GetMetrics(); |
6490 | 0 |
|
6491 | 0 | // Set the rect, make sure the view is properly sized and positioned, |
6492 | 0 | // and tell the frame we're done reflowing it |
6493 | 0 | // XXXldb This seems like the wrong place to be doing this -- shouldn't |
6494 | 0 | // we be doing this in BlockReflowInput::FlowAndPlaceFloat after |
6495 | 0 | // we've positioned the float, and shouldn't we be doing the equivalent |
6496 | 0 | // of |PlaceFrameView| here? |
6497 | 0 | WritingMode metricsWM = metrics.GetWritingMode(); |
6498 | 0 | aFloat->SetSize(metricsWM, metrics.Size(metricsWM)); |
6499 | 0 | if (aFloat->HasView()) { |
6500 | 0 | nsContainerFrame::SyncFrameViewAfterReflow(aState.mPresContext, aFloat, |
6501 | 0 | aFloat->GetView(), |
6502 | 0 | metrics.VisualOverflow(), |
6503 | 0 | NS_FRAME_NO_MOVE_VIEW); |
6504 | 0 | } |
6505 | 0 | // Pass floatRS so the frame hierarchy can be used (redoFloatRS has the same hierarchy) |
6506 | 0 | aFloat->DidReflow(aState.mPresContext, &floatRS); |
6507 | 0 |
|
6508 | | #ifdef NOISY_FLOAT |
6509 | | printf("end ReflowFloat %p, sized to %d,%d\n", |
6510 | | aFloat, metrics.Width(), metrics.Height()); |
6511 | | #endif |
6512 | | } |
6513 | | |
6514 | | StyleClear |
6515 | | nsBlockFrame::FindTrailingClear() |
6516 | 0 | { |
6517 | 0 | // find the break type of the last line |
6518 | 0 | for (nsIFrame* b = this; b; b = b->GetPrevInFlow()) { |
6519 | 0 | nsBlockFrame* block = static_cast<nsBlockFrame*>(b); |
6520 | 0 | LineIterator endLine = block->LinesEnd(); |
6521 | 0 | if (endLine != block->LinesBegin()) { |
6522 | 0 | --endLine; |
6523 | 0 | return endLine->GetBreakTypeAfter(); |
6524 | 0 | } |
6525 | 0 | } |
6526 | 0 | return StyleClear::None; |
6527 | 0 | } |
6528 | | |
6529 | | void |
6530 | | nsBlockFrame::ReflowPushedFloats(BlockReflowInput& aState, |
6531 | | nsOverflowAreas& aOverflowAreas, |
6532 | | nsReflowStatus& aStatus) |
6533 | 0 | { |
6534 | 0 | // Pushed floats live at the start of our float list; see comment |
6535 | 0 | // above nsBlockFrame::DrainPushedFloats. |
6536 | 0 | nsIFrame* f = mFloats.FirstChild(); |
6537 | 0 | nsIFrame* prev = nullptr; |
6538 | 0 | while (f && (f->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT)) { |
6539 | 0 | MOZ_ASSERT(prev == f->GetPrevSibling()); |
6540 | 0 | // When we push a first-continuation float in a non-initial reflow, |
6541 | 0 | // it's possible that we end up with two continuations with the same |
6542 | 0 | // parent. This happens if, on the previous reflow of the block or |
6543 | 0 | // a previous reflow of the line containing the block, the float was |
6544 | 0 | // split between continuations A and B of the parent, but on the |
6545 | 0 | // current reflow, none of the float can fit in A. |
6546 | 0 | // |
6547 | 0 | // When this happens, we might even have the two continuations |
6548 | 0 | // out-of-order due to the management of the pushed floats. In |
6549 | 0 | // particular, if the float's placeholder was in a pushed line that |
6550 | 0 | // we reflowed before it was pushed, and we split the float during |
6551 | 0 | // that reflow, we might have the continuation of the float before |
6552 | 0 | // the float itself. (In the general case, however, it's correct |
6553 | 0 | // for floats in the pushed floats list to come before floats |
6554 | 0 | // anchored in pushed lines; however, in this case it's wrong. We |
6555 | 0 | // should probably find a way to fix it somehow, since it leads to |
6556 | 0 | // incorrect layout in some cases.) |
6557 | 0 | // |
6558 | 0 | // When we have these out-of-order continuations, we might hit the |
6559 | 0 | // next-continuation before the previous-continuation. When that |
6560 | 0 | // happens, just push it. When we reflow the next continuation, |
6561 | 0 | // we'll either pull all of its content back and destroy it (by |
6562 | 0 | // calling DeleteNextInFlowChild), or nsBlockFrame::SplitFloat will |
6563 | 0 | // pull it out of its current position and push it again (and |
6564 | 0 | // potentially repeat this cycle for the next continuation, although |
6565 | 0 | // hopefully then they'll be in the right order). |
6566 | 0 | // |
6567 | 0 | // We should also need this code for the in-order case if the first |
6568 | 0 | // continuation of a float gets moved across more than one |
6569 | 0 | // continuation of the containing block. In this case we'd manage |
6570 | 0 | // to push the second continuation without this check, but not the |
6571 | 0 | // third and later. |
6572 | 0 | nsIFrame *prevContinuation = f->GetPrevContinuation(); |
6573 | 0 | if (prevContinuation && prevContinuation->GetParent() == f->GetParent()) { |
6574 | 0 | mFloats.RemoveFrame(f); |
6575 | 0 | aState.AppendPushedFloatChain(f); |
6576 | 0 | f = !prev ? mFloats.FirstChild() : prev->GetNextSibling(); |
6577 | 0 | continue; |
6578 | 0 | } |
6579 | 0 |
|
6580 | 0 | // Always call FlowAndPlaceFloat; we might need to place this float |
6581 | 0 | // if didn't belong to this block the last time it was reflowed. |
6582 | 0 | aState.FlowAndPlaceFloat(f); |
6583 | 0 | ConsiderChildOverflow(aOverflowAreas, f); |
6584 | 0 |
|
6585 | 0 | nsIFrame* next = !prev ? mFloats.FirstChild() : prev->GetNextSibling(); |
6586 | 0 | if (next == f) { |
6587 | 0 | // We didn't push |f| so its next-sibling is next. |
6588 | 0 | next = f->GetNextSibling(); |
6589 | 0 | prev = f; |
6590 | 0 | } // else: we did push |f| so |prev|'s new next-sibling is next. |
6591 | 0 | f = next; |
6592 | 0 | } |
6593 | 0 |
|
6594 | 0 | // If there are continued floats, then we may need to continue BR clearance |
6595 | 0 | if (0 != aState.ClearFloats(0, StyleClear::Both)) { |
6596 | 0 | nsBlockFrame* prevBlock = static_cast<nsBlockFrame*>(GetPrevInFlow()); |
6597 | 0 | if (prevBlock) { |
6598 | 0 | aState.mFloatBreakType = prevBlock->FindTrailingClear(); |
6599 | 0 | } |
6600 | 0 | } |
6601 | 0 | } |
6602 | | |
6603 | | void |
6604 | | nsBlockFrame::RecoverFloats(nsFloatManager& aFloatManager, WritingMode aWM, |
6605 | | const nsSize& aContainerSize) |
6606 | 0 | { |
6607 | 0 | // Recover our own floats |
6608 | 0 | nsIFrame* stop = nullptr; // Stop before we reach pushed floats that |
6609 | 0 | // belong to our next-in-flow |
6610 | 0 | for (nsIFrame* f = mFloats.FirstChild(); f && f != stop; f = f->GetNextSibling()) { |
6611 | 0 | LogicalRect region = nsFloatManager::GetRegionFor(aWM, f, aContainerSize); |
6612 | 0 | aFloatManager.AddFloat(f, region, aWM, aContainerSize); |
6613 | 0 | if (!stop && f->GetNextInFlow()) |
6614 | 0 | stop = f->GetNextInFlow(); |
6615 | 0 | } |
6616 | 0 |
|
6617 | 0 | // Recurse into our overflow container children |
6618 | 0 | for (nsIFrame* oc = GetChildList(kOverflowContainersList).FirstChild(); |
6619 | 0 | oc; oc = oc->GetNextSibling()) { |
6620 | 0 | RecoverFloatsFor(oc, aFloatManager, aWM, aContainerSize); |
6621 | 0 | } |
6622 | 0 |
|
6623 | 0 | // Recurse into our normal children |
6624 | 0 | for (nsBlockFrame::LineIterator line = LinesBegin(); line != LinesEnd(); ++line) { |
6625 | 0 | if (line->IsBlock()) { |
6626 | 0 | RecoverFloatsFor(line->mFirstChild, aFloatManager, aWM, aContainerSize); |
6627 | 0 | } |
6628 | 0 | } |
6629 | 0 | } |
6630 | | |
6631 | | void |
6632 | | nsBlockFrame::RecoverFloatsFor(nsIFrame* aFrame, |
6633 | | nsFloatManager& aFloatManager, |
6634 | | WritingMode aWM, |
6635 | | const nsSize& aContainerSize) |
6636 | 0 | { |
6637 | 0 | MOZ_ASSERT(aFrame, "null frame"); |
6638 | 0 |
|
6639 | 0 | // Only blocks have floats |
6640 | 0 | nsBlockFrame* block = nsLayoutUtils::GetAsBlock(aFrame); |
6641 | 0 | // Don't recover any state inside a block that has its own float manager |
6642 | 0 | // (we don't currently have any blocks like this, though, thanks to our |
6643 | 0 | // use of extra frames for 'overflow') |
6644 | 0 | if (block && !nsBlockFrame::BlockNeedsFloatManager(block)) { |
6645 | 0 | // If the element is relatively positioned, then adjust x and y |
6646 | 0 | // accordingly so that we consider relatively positioned frames |
6647 | 0 | // at their original position. |
6648 | 0 |
|
6649 | 0 | LogicalRect rect(aWM, block->GetNormalRect(), aContainerSize); |
6650 | 0 | nscoord lineLeft = rect.LineLeft(aWM, aContainerSize); |
6651 | 0 | nscoord blockStart = rect.BStart(aWM); |
6652 | 0 | aFloatManager.Translate(lineLeft, blockStart); |
6653 | 0 | block->RecoverFloats(aFloatManager, aWM, aContainerSize); |
6654 | 0 | aFloatManager.Translate(-lineLeft, -blockStart); |
6655 | 0 | } |
6656 | 0 | } |
6657 | | |
6658 | | ////////////////////////////////////////////////////////////////////// |
6659 | | // Painting, event handling |
6660 | | |
6661 | | #ifdef DEBUG |
6662 | | static void ComputeVisualOverflowArea(nsLineList& aLines, |
6663 | | nscoord aWidth, nscoord aHeight, |
6664 | | nsRect& aResult) |
6665 | | { |
6666 | | nscoord xa = 0, ya = 0, xb = aWidth, yb = aHeight; |
6667 | | for (nsLineList::iterator line = aLines.begin(), line_end = aLines.end(); |
6668 | | line != line_end; |
6669 | | ++line) { |
6670 | | // Compute min and max x/y values for the reflowed frame's |
6671 | | // combined areas |
6672 | | nsRect visOverflow(line->GetVisualOverflowArea()); |
6673 | | nscoord x = visOverflow.x; |
6674 | | nscoord y = visOverflow.y; |
6675 | | nscoord xmost = x + visOverflow.width; |
6676 | | nscoord ymost = y + visOverflow.height; |
6677 | | if (x < xa) { |
6678 | | xa = x; |
6679 | | } |
6680 | | if (xmost > xb) { |
6681 | | xb = xmost; |
6682 | | } |
6683 | | if (y < ya) { |
6684 | | ya = y; |
6685 | | } |
6686 | | if (ymost > yb) { |
6687 | | yb = ymost; |
6688 | | } |
6689 | | } |
6690 | | |
6691 | | aResult.x = xa; |
6692 | | aResult.y = ya; |
6693 | | aResult.width = xb - xa; |
6694 | | aResult.height = yb - ya; |
6695 | | } |
6696 | | #endif |
6697 | | |
6698 | | bool |
6699 | | nsBlockFrame::IsVisibleInSelection(Selection* aSelection) |
6700 | 0 | { |
6701 | 0 | if (mContent->IsAnyOfHTMLElements(nsGkAtoms::html, nsGkAtoms::body)) |
6702 | 0 | return true; |
6703 | 0 | |
6704 | 0 | IgnoredErrorResult rv; |
6705 | 0 | bool visible = aSelection->ContainsNode(*mContent, true, rv); |
6706 | 0 | return !rv.Failed() && visible; |
6707 | 0 | } |
6708 | | |
6709 | | #ifdef DEBUG |
6710 | | static void DebugOutputDrawLine(int32_t aDepth, nsLineBox* aLine, bool aDrawn) { |
6711 | | if (nsBlockFrame::gNoisyDamageRepair) { |
6712 | | nsFrame::IndentBy(stdout, aDepth+1); |
6713 | | nsRect lineArea = aLine->GetVisualOverflowArea(); |
6714 | | printf("%s line=%p bounds=%d,%d,%d,%d ca=%d,%d,%d,%d\n", |
6715 | | aDrawn ? "draw" : "skip", |
6716 | | static_cast<void*>(aLine), |
6717 | | aLine->IStart(), aLine->BStart(), |
6718 | | aLine->ISize(), aLine->BSize(), |
6719 | | lineArea.x, lineArea.y, |
6720 | | lineArea.width, lineArea.height); |
6721 | | } |
6722 | | } |
6723 | | #endif |
6724 | | |
6725 | | static void |
6726 | | DisplayLine(nsDisplayListBuilder* aBuilder, const nsRect& aLineArea, |
6727 | | nsBlockFrame::LineIterator& aLine, |
6728 | | int32_t aDepth, int32_t& aDrawnLines, const nsDisplayListSet& aLists, |
6729 | | nsBlockFrame* aFrame, TextOverflow* aTextOverflow, |
6730 | 0 | uint32_t aLineNumberForTextOverflow) { |
6731 | 0 | // If the line's combined area (which includes child frames that |
6732 | 0 | // stick outside of the line's bounding box or our bounding box) |
6733 | 0 | // intersects the dirty rect then paint the line. |
6734 | 0 | bool intersect = aLineArea.Intersects(aBuilder->GetDirtyRect()); |
6735 | 0 | bool visible = aLineArea.Intersects(aBuilder->GetVisibleRect()); |
6736 | | #ifdef DEBUG |
6737 | | if (nsBlockFrame::gLamePaintMetrics) { |
6738 | | aDrawnLines++; |
6739 | | } |
6740 | | DebugOutputDrawLine(aDepth, aLine.get(), intersect); |
6741 | | #endif |
6742 | | // The line might contain a placeholder for a visible out-of-flow, in which |
6743 | 0 | // case we need to descend into it. If there is such a placeholder, we will |
6744 | 0 | // have NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO set. |
6745 | 0 | // In particular, we really want to check ShouldDescendIntoFrame() |
6746 | 0 | // on all the frames on the line, but that might be expensive. So |
6747 | 0 | // we approximate it by checking it on aFrame; if it's true for any |
6748 | 0 | // frame in the line, it's also true for aFrame. |
6749 | 0 | bool lineInline = aLine->IsInline(); |
6750 | 0 | bool lineMayHaveTextOverflow = aTextOverflow && lineInline; |
6751 | 0 | if (!intersect && !aBuilder->ShouldDescendIntoFrame(aFrame, visible) && |
6752 | 0 | !lineMayHaveTextOverflow) |
6753 | 0 | return; |
6754 | 0 | |
6755 | 0 | // Collect our line's display items in a temporary nsDisplayListCollection, |
6756 | 0 | // so that we can apply any "text-overflow" clipping to the entire collection |
6757 | 0 | // without affecting previous lines. |
6758 | 0 | nsDisplayListCollection collection(aBuilder); |
6759 | 0 |
|
6760 | 0 | // Block-level child backgrounds go on the blockBorderBackgrounds list ... |
6761 | 0 | // Inline-level child backgrounds go on the regular child content list. |
6762 | 0 | nsDisplayListSet childLists(collection, |
6763 | 0 | lineInline ? collection.Content() : collection.BlockBorderBackgrounds()); |
6764 | 0 |
|
6765 | 0 | uint32_t flags = lineInline ? nsIFrame::DISPLAY_CHILD_INLINE : 0; |
6766 | 0 |
|
6767 | 0 | nsIFrame* kid = aLine->mFirstChild; |
6768 | 0 | int32_t n = aLine->GetChildCount(); |
6769 | 0 | while (--n >= 0) { |
6770 | 0 | aFrame->BuildDisplayListForChild(aBuilder, kid, childLists, flags); |
6771 | 0 | kid = kid->GetNextSibling(); |
6772 | 0 | } |
6773 | 0 |
|
6774 | 0 | if (lineMayHaveTextOverflow) { |
6775 | 0 | aTextOverflow->ProcessLine(collection, aLine.get(), aLineNumberForTextOverflow); |
6776 | 0 | } |
6777 | 0 |
|
6778 | 0 | collection.MoveTo(aLists); |
6779 | 0 | } |
6780 | | |
6781 | | void |
6782 | | nsBlockFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, |
6783 | | const nsDisplayListSet& aLists) |
6784 | 0 | { |
6785 | 0 | int32_t drawnLines; // Will only be used if set (gLamePaintMetrics). |
6786 | 0 | int32_t depth = 0; |
6787 | | #ifdef DEBUG |
6788 | | if (gNoisyDamageRepair) { |
6789 | | nsRect dirty = aBuilder->GetDirtyRect(); |
6790 | | depth = GetDepth(); |
6791 | | nsRect ca; |
6792 | | ::ComputeVisualOverflowArea(mLines, mRect.width, mRect.height, ca); |
6793 | | nsFrame::IndentBy(stdout, depth); |
6794 | | ListTag(stdout); |
6795 | | printf(": bounds=%d,%d,%d,%d dirty(absolute)=%d,%d,%d,%d ca=%d,%d,%d,%d\n", |
6796 | | mRect.x, mRect.y, mRect.width, mRect.height, |
6797 | | dirty.x, dirty.y, dirty.width, dirty.height, |
6798 | | ca.x, ca.y, ca.width, ca.height); |
6799 | | } |
6800 | | PRTime start = 0; // Initialize these variables to silence the compiler. |
6801 | | if (gLamePaintMetrics) { |
6802 | | start = PR_Now(); |
6803 | | drawnLines = 0; |
6804 | | } |
6805 | | #endif |
6806 | |
|
6807 | 0 | DisplayBorderBackgroundOutline(aBuilder, aLists); |
6808 | 0 |
|
6809 | 0 | if (GetPrevInFlow()) { |
6810 | 0 | DisplayOverflowContainers(aBuilder, aLists); |
6811 | 0 | for (nsIFrame* f : mFloats) { |
6812 | 0 | if (f->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT) |
6813 | 0 | BuildDisplayListForChild(aBuilder, f, aLists); |
6814 | 0 | } |
6815 | 0 | } |
6816 | 0 |
|
6817 | 0 | aBuilder->MarkFramesForDisplayList(this, mFloats); |
6818 | 0 |
|
6819 | 0 | // Prepare for text-overflow processing. |
6820 | 0 | Maybe<TextOverflow> textOverflow = TextOverflow::WillProcessLines(aBuilder, this); |
6821 | 0 |
|
6822 | 0 | // We'll collect our lines' display items here, & then append this to aLists. |
6823 | 0 | nsDisplayListCollection linesDisplayListCollection(aBuilder); |
6824 | 0 |
|
6825 | 0 | // Don't use the line cursor if we might have a descendant placeholder ... |
6826 | 0 | // it might skip lines that contain placeholders but don't themselves |
6827 | 0 | // intersect with the dirty area. |
6828 | 0 | // In particular, we really want to check ShouldDescendIntoFrame() |
6829 | 0 | // on all our child frames, but that might be expensive. So we |
6830 | 0 | // approximate it by checking it on |this|; if it's true for any |
6831 | 0 | // frame in our child list, it's also true for |this|. |
6832 | 0 | // Also skip the cursor if we're creating text overflow markers, |
6833 | 0 | // since we need to know what line number we're up to in order |
6834 | 0 | // to generate unique display item keys. |
6835 | 0 | nsLineBox* cursor = (aBuilder->ShouldDescendIntoFrame(this, true) || textOverflow.isSome()) ? |
6836 | 0 | nullptr : GetFirstLineContaining(aBuilder->GetDirtyRect().y); |
6837 | 0 | LineIterator line_end = LinesEnd(); |
6838 | 0 |
|
6839 | 0 | if (cursor) { |
6840 | 0 | for (LineIterator line = mLines.begin(cursor); |
6841 | 0 | line != line_end; |
6842 | 0 | ++line) { |
6843 | 0 | nsRect lineArea = line->GetVisualOverflowArea(); |
6844 | 0 | if (!lineArea.IsEmpty()) { |
6845 | 0 | // Because we have a cursor, the combinedArea.ys are non-decreasing. |
6846 | 0 | // Once we've passed aDirtyRect.YMost(), we can never see it again. |
6847 | 0 | if (lineArea.y >= aBuilder->GetDirtyRect().YMost()) { |
6848 | 0 | break; |
6849 | 0 | } |
6850 | 0 | MOZ_ASSERT(textOverflow.isNothing()); |
6851 | 0 | DisplayLine(aBuilder, lineArea, line, depth, drawnLines, |
6852 | 0 | linesDisplayListCollection, this, nullptr, 0); |
6853 | 0 | } |
6854 | 0 | } |
6855 | 0 | } else { |
6856 | 0 | bool nonDecreasingYs = true; |
6857 | 0 | uint32_t lineCount = 0; |
6858 | 0 | nscoord lastY = INT32_MIN; |
6859 | 0 | nscoord lastYMost = INT32_MIN; |
6860 | 0 | for (LineIterator line = LinesBegin(); |
6861 | 0 | line != line_end; |
6862 | 0 | ++line) { |
6863 | 0 | nsRect lineArea = line->GetVisualOverflowArea(); |
6864 | 0 | DisplayLine(aBuilder, lineArea, line, depth, drawnLines, |
6865 | 0 | linesDisplayListCollection, this, textOverflow.ptrOr(nullptr), lineCount); |
6866 | 0 | if (!lineArea.IsEmpty()) { |
6867 | 0 | if (lineArea.y < lastY |
6868 | 0 | || lineArea.YMost() < lastYMost) { |
6869 | 0 | nonDecreasingYs = false; |
6870 | 0 | } |
6871 | 0 | lastY = lineArea.y; |
6872 | 0 | lastYMost = lineArea.YMost(); |
6873 | 0 | } |
6874 | 0 | lineCount++; |
6875 | 0 | } |
6876 | 0 |
|
6877 | 0 | if (nonDecreasingYs && lineCount >= MIN_LINES_NEEDING_CURSOR) { |
6878 | 0 | SetupLineCursor(); |
6879 | 0 | } |
6880 | 0 | } |
6881 | 0 |
|
6882 | 0 | // Pick up the resulting text-overflow markers. We append them to |
6883 | 0 | // PositionedDescendants just before we append the lines' display items, |
6884 | 0 | // so that our text-overflow markers will appear on top of this block's |
6885 | 0 | // normal content but below any of its its' positioned children. |
6886 | 0 | if (textOverflow.isSome()) { |
6887 | 0 | aLists.PositionedDescendants()->AppendToTop(&textOverflow->GetMarkers()); |
6888 | 0 | } |
6889 | 0 | linesDisplayListCollection.MoveTo(aLists); |
6890 | 0 |
|
6891 | 0 | if (HasOutsideBullet()) { |
6892 | 0 | // Display outside bullets manually |
6893 | 0 | nsIFrame* bullet = GetOutsideBullet(); |
6894 | 0 | BuildDisplayListForChild(aBuilder, bullet, aLists); |
6895 | 0 | } |
6896 | 0 |
|
6897 | | #ifdef DEBUG |
6898 | | if (gLamePaintMetrics) { |
6899 | | PRTime end = PR_Now(); |
6900 | | |
6901 | | int32_t numLines = mLines.size(); |
6902 | | if (!numLines) numLines = 1; |
6903 | | PRTime lines, deltaPerLine, delta; |
6904 | | lines = int64_t(numLines); |
6905 | | delta = end - start; |
6906 | | deltaPerLine = delta / lines; |
6907 | | |
6908 | | ListTag(stdout); |
6909 | | char buf[400]; |
6910 | | SprintfLiteral(buf, |
6911 | | ": %" PRId64 " elapsed (%" PRId64 " per line) lines=%d drawn=%d skip=%d", |
6912 | | delta, deltaPerLine, |
6913 | | numLines, drawnLines, numLines - drawnLines); |
6914 | | printf("%s\n", buf); |
6915 | | } |
6916 | | #endif |
6917 | | } |
6918 | | |
6919 | | #ifdef ACCESSIBILITY |
6920 | | a11y::AccType |
6921 | | nsBlockFrame::AccessibleType() |
6922 | 0 | { |
6923 | 0 | if (IsTableCaption()) { |
6924 | 0 | return GetRect().IsEmpty() ? a11y::eNoType : a11y::eHTMLCaptionType; |
6925 | 0 | } |
6926 | 0 |
|
6927 | 0 | // block frame may be for <hr> |
6928 | 0 | if (mContent->IsHTMLElement(nsGkAtoms::hr)) { |
6929 | 0 | return a11y::eHTMLHRType; |
6930 | 0 | } |
6931 | 0 | |
6932 | 0 | if (!HasBullet() || !PresContext()) { |
6933 | 0 | //XXXsmaug What if we're in the shadow dom? |
6934 | 0 | if (!mContent->GetParent()) { |
6935 | 0 | // Don't create accessible objects for the root content node, they are redundant with |
6936 | 0 | // the nsDocAccessible object created with the document node |
6937 | 0 | return a11y::eNoType; |
6938 | 0 | } |
6939 | 0 | |
6940 | 0 | if (mContent == mContent->OwnerDoc()->GetBody()) { |
6941 | 0 | // Don't create accessible objects for the body, they are redundant with |
6942 | 0 | // the nsDocAccessible object created with the document node |
6943 | 0 | return a11y::eNoType; |
6944 | 0 | } |
6945 | 0 | |
6946 | 0 | // Not a bullet, treat as normal HTML container |
6947 | 0 | return a11y::eHyperTextType; |
6948 | 0 | } |
6949 | 0 | |
6950 | 0 | // Create special list bullet accessible |
6951 | 0 | return a11y::eHTMLLiType; |
6952 | 0 | } |
6953 | | #endif |
6954 | | |
6955 | | void nsBlockFrame::ClearLineCursor() |
6956 | 0 | { |
6957 | 0 | if (!(GetStateBits() & NS_BLOCK_HAS_LINE_CURSOR)) { |
6958 | 0 | return; |
6959 | 0 | } |
6960 | 0 | |
6961 | 0 | DeleteProperty(LineCursorProperty()); |
6962 | 0 | RemoveStateBits(NS_BLOCK_HAS_LINE_CURSOR); |
6963 | 0 | } |
6964 | | |
6965 | | void nsBlockFrame::SetupLineCursor() |
6966 | 0 | { |
6967 | 0 | if (GetStateBits() & NS_BLOCK_HAS_LINE_CURSOR |
6968 | 0 | || mLines.empty()) { |
6969 | 0 | return; |
6970 | 0 | } |
6971 | 0 | |
6972 | 0 | SetProperty(LineCursorProperty(), mLines.front()); |
6973 | 0 | AddStateBits(NS_BLOCK_HAS_LINE_CURSOR); |
6974 | 0 | } |
6975 | | |
6976 | | nsLineBox* nsBlockFrame::GetFirstLineContaining(nscoord y) |
6977 | 0 | { |
6978 | 0 | if (!(GetStateBits() & NS_BLOCK_HAS_LINE_CURSOR)) { |
6979 | 0 | return nullptr; |
6980 | 0 | } |
6981 | 0 | |
6982 | 0 | nsLineBox* property = GetProperty(LineCursorProperty()); |
6983 | 0 | LineIterator cursor = mLines.begin(property); |
6984 | 0 | nsRect cursorArea = cursor->GetVisualOverflowArea(); |
6985 | 0 |
|
6986 | 0 | while ((cursorArea.IsEmpty() || cursorArea.YMost() > y) |
6987 | 0 | && cursor != mLines.front()) { |
6988 | 0 | cursor = cursor.prev(); |
6989 | 0 | cursorArea = cursor->GetVisualOverflowArea(); |
6990 | 0 | } |
6991 | 0 | while ((cursorArea.IsEmpty() || cursorArea.YMost() <= y) |
6992 | 0 | && cursor != mLines.back()) { |
6993 | 0 | cursor = cursor.next(); |
6994 | 0 | cursorArea = cursor->GetVisualOverflowArea(); |
6995 | 0 | } |
6996 | 0 |
|
6997 | 0 | if (cursor.get() != property) { |
6998 | 0 | SetProperty(LineCursorProperty(), cursor.get()); |
6999 | 0 | } |
7000 | 0 |
|
7001 | 0 | return cursor.get(); |
7002 | 0 | } |
7003 | | |
7004 | | /* virtual */ void |
7005 | | nsBlockFrame::ChildIsDirty(nsIFrame* aChild) |
7006 | 0 | { |
7007 | 0 | // See if the child is absolutely positioned |
7008 | 0 | if (aChild->GetStateBits() & NS_FRAME_OUT_OF_FLOW && |
7009 | 0 | aChild->IsAbsolutelyPositioned()) { |
7010 | 0 | // do nothing |
7011 | 0 | } else if (aChild == GetOutsideBullet()) { |
7012 | 0 | // The bullet lives in the first line, unless the first line has |
7013 | 0 | // height 0 and there is a second line, in which case it lives |
7014 | 0 | // in the second line. |
7015 | 0 | LineIterator bulletLine = LinesBegin(); |
7016 | 0 | if (bulletLine != LinesEnd() && bulletLine->BSize() == 0 && |
7017 | 0 | bulletLine != mLines.back()) { |
7018 | 0 | bulletLine = bulletLine.next(); |
7019 | 0 | } |
7020 | 0 |
|
7021 | 0 | if (bulletLine != LinesEnd()) { |
7022 | 0 | MarkLineDirty(bulletLine, &mLines); |
7023 | 0 | } |
7024 | 0 | // otherwise we have an empty line list, and ReflowDirtyLines |
7025 | 0 | // will handle reflowing the bullet. |
7026 | 0 | } else { |
7027 | 0 | // Note that we should go through our children to mark lines dirty |
7028 | 0 | // before the next reflow. Doing it now could make things O(N^2) |
7029 | 0 | // since finding the right line is O(N). |
7030 | 0 | // We don't need to worry about marking lines on the overflow list |
7031 | 0 | // as dirty; we're guaranteed to reflow them if we take them off the |
7032 | 0 | // overflow list. |
7033 | 0 | // However, we might have gotten a float, in which case we need to |
7034 | 0 | // reflow the line containing its placeholder. So find the |
7035 | 0 | // ancestor-or-self of the placeholder that's a child of the block, |
7036 | 0 | // and mark it as NS_FRAME_HAS_DIRTY_CHILDREN too, so that we mark |
7037 | 0 | // its line dirty when we handle NS_BLOCK_LOOK_FOR_DIRTY_FRAMES. |
7038 | 0 | // We need to take some care to handle the case where a float is in |
7039 | 0 | // a different continuation than its placeholder, including marking |
7040 | 0 | // an extra block with NS_BLOCK_LOOK_FOR_DIRTY_FRAMES. |
7041 | 0 | if (!(aChild->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) { |
7042 | 0 | AddStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES); |
7043 | 0 | } else { |
7044 | 0 | NS_ASSERTION(aChild->IsFloating(), "should be a float"); |
7045 | 0 | nsIFrame* thisFC = FirstContinuation(); |
7046 | 0 | nsIFrame* placeholderPath = aChild->GetPlaceholderFrame(); |
7047 | 0 | // SVG code sometimes sends FrameNeedsReflow notifications during |
7048 | 0 | // frame destruction, leading to null placeholders, but we're safe |
7049 | 0 | // ignoring those. |
7050 | 0 | if (placeholderPath) { |
7051 | 0 | for (;;) { |
7052 | 0 | nsIFrame *parent = placeholderPath->GetParent(); |
7053 | 0 | if (parent->GetContent() == mContent && |
7054 | 0 | parent->FirstContinuation() == thisFC) { |
7055 | 0 | parent->AddStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES); |
7056 | 0 | break; |
7057 | 0 | } |
7058 | 0 | placeholderPath = parent; |
7059 | 0 | } |
7060 | 0 | placeholderPath->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); |
7061 | 0 | } |
7062 | 0 | } |
7063 | 0 | } |
7064 | 0 |
|
7065 | 0 | nsContainerFrame::ChildIsDirty(aChild); |
7066 | 0 | } |
7067 | | |
7068 | | void |
7069 | | nsBlockFrame::Init(nsIContent* aContent, |
7070 | | nsContainerFrame* aParent, |
7071 | | nsIFrame* aPrevInFlow) |
7072 | 0 | { |
7073 | 0 | if (aPrevInFlow) { |
7074 | 0 | // Copy over the inherited block frame bits from the prev-in-flow. |
7075 | 0 | RemoveStateBits(NS_BLOCK_FLAGS_MASK); |
7076 | 0 | AddStateBits(aPrevInFlow->GetStateBits() & |
7077 | 0 | (NS_BLOCK_FLAGS_MASK & ~NS_BLOCK_FLAGS_NON_INHERITED_MASK)); |
7078 | 0 | } |
7079 | 0 |
|
7080 | 0 | nsContainerFrame::Init(aContent, aParent, aPrevInFlow); |
7081 | 0 |
|
7082 | 0 | if (!aPrevInFlow || |
7083 | 0 | aPrevInFlow->GetStateBits() & NS_BLOCK_NEEDS_BIDI_RESOLUTION) { |
7084 | 0 | AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION); |
7085 | 0 | } |
7086 | 0 |
|
7087 | 0 | // A display:flow-root box establishes a block formatting context. |
7088 | 0 | // If a box has a different block flow direction than its containing block: |
7089 | 0 | // ... |
7090 | 0 | // If the box is a block container, then it establishes a new block |
7091 | 0 | // formatting context. |
7092 | 0 | // (http://dev.w3.org/csswg/css-writing-modes/#block-flow) |
7093 | 0 | // If the box has contain: paint or contain:layout (or contain:strict), |
7094 | 0 | // then it should also establish a formatting context. |
7095 | 0 | if (StyleDisplay()->mDisplay == mozilla::StyleDisplay::FlowRoot || |
7096 | 0 | (GetParent() && StyleVisibility()->mWritingMode != |
7097 | 0 | GetParent()->StyleVisibility()->mWritingMode) || |
7098 | 0 | StyleDisplay()->IsContainPaint() || |
7099 | 0 | StyleDisplay()->IsContainLayout()) { |
7100 | 0 | AddStateBits(NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS); |
7101 | 0 | } |
7102 | 0 |
|
7103 | 0 | if ((GetStateBits() & |
7104 | 0 | (NS_FRAME_FONT_INFLATION_CONTAINER | NS_BLOCK_FLOAT_MGR)) == |
7105 | 0 | (NS_FRAME_FONT_INFLATION_CONTAINER | NS_BLOCK_FLOAT_MGR)) { |
7106 | 0 | AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT); |
7107 | 0 | } |
7108 | 0 | } |
7109 | | |
7110 | | void |
7111 | | nsBlockFrame::SetInitialChildList(ChildListID aListID, |
7112 | | nsFrameList& aChildList) |
7113 | 0 | { |
7114 | 0 | if (kFloatList == aListID) { |
7115 | 0 | mFloats.SetFrames(aChildList); |
7116 | 0 | } else if (kPrincipalList == aListID) { |
7117 | 0 | NS_ASSERTION((GetStateBits() & (NS_BLOCK_FRAME_HAS_INSIDE_BULLET | |
7118 | 0 | NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET)) == 0, |
7119 | 0 | "how can we have a bullet already?"); |
7120 | 0 |
|
7121 | | #ifdef DEBUG |
7122 | | // The only times a block that is an anonymous box is allowed to have a |
7123 | | // first-letter frame are when it's the block inside a non-anonymous cell, |
7124 | | // the block inside a fieldset, button or column set, or a scrolled content |
7125 | | // block, except for <select>. Note that this means that blocks which are |
7126 | | // the anonymous block in {ib} splits do NOT get first-letter frames. |
7127 | | // Note that NS_BLOCK_HAS_FIRST_LETTER_STYLE gets set on all continuations |
7128 | | // of the block. |
7129 | | nsAtom *pseudo = Style()->GetPseudo(); |
7130 | | bool haveFirstLetterStyle = |
7131 | | (!pseudo || |
7132 | | (pseudo == nsCSSAnonBoxes::cellContent() && |
7133 | | GetParent()->Style()->GetPseudo() == nullptr) || |
7134 | | pseudo == nsCSSAnonBoxes::fieldsetContent() || |
7135 | | pseudo == nsCSSAnonBoxes::buttonContent() || |
7136 | | pseudo == nsCSSAnonBoxes::columnContent() || |
7137 | | (pseudo == nsCSSAnonBoxes::scrolledContent() && |
7138 | | !GetParent()->IsListControlFrame()) || |
7139 | | pseudo == nsCSSAnonBoxes::mozSVGText()) && |
7140 | | !IsComboboxControlFrame() && |
7141 | | !IsFrameOfType(eMathML) && |
7142 | | RefPtr<ComputedStyle>(GetFirstLetterStyle(PresContext())) != nullptr; |
7143 | | NS_ASSERTION(haveFirstLetterStyle == |
7144 | | ((mState & NS_BLOCK_HAS_FIRST_LETTER_STYLE) != 0), |
7145 | | "NS_BLOCK_HAS_FIRST_LETTER_STYLE state out of sync"); |
7146 | | #endif |
7147 | |
|
7148 | 0 | AddFrames(aChildList, nullptr); |
7149 | 0 |
|
7150 | 0 | // Create a list bullet if this is a list-item. Note that this is |
7151 | 0 | // done here so that RenumberLists will work (it needs the bullets |
7152 | 0 | // to store the bullet numbers). Also note that due to various |
7153 | 0 | // wrapper frames (scrollframes, columns) we want to use the |
7154 | 0 | // outermost (primary, ideally, but it's not set yet when we get |
7155 | 0 | // here) frame of our content for the display check. On the other |
7156 | 0 | // hand, we look at ourselves for the GetPrevInFlow() check, since |
7157 | 0 | // for a columnset we don't want a bullet per column. Note that |
7158 | 0 | // the outermost frame for the content is the primary frame in |
7159 | 0 | // most cases; the ones when it's not (like tables) can't be |
7160 | 0 | // StyleDisplay::ListItem). |
7161 | 0 | nsIFrame* possibleListItem = this; |
7162 | 0 | while (1) { |
7163 | 0 | nsIFrame* parent = possibleListItem->GetParent(); |
7164 | 0 | if (parent->GetContent() != GetContent()) { |
7165 | 0 | break; |
7166 | 0 | } |
7167 | 0 | possibleListItem = parent; |
7168 | 0 | } |
7169 | 0 | if (mozilla::StyleDisplay::ListItem == |
7170 | 0 | possibleListItem->StyleDisplay()->mDisplay && |
7171 | 0 | !GetPrevInFlow()) { |
7172 | 0 | // Resolve style for the bullet frame |
7173 | 0 | const nsStyleList* styleList = StyleList(); |
7174 | 0 | CounterStyle* style = styleList->mCounterStyle; |
7175 | 0 |
|
7176 | 0 | CreateBulletFrameForListItem( |
7177 | 0 | style->IsBullet(), |
7178 | 0 | styleList->mListStylePosition == NS_STYLE_LIST_STYLE_POSITION_INSIDE); |
7179 | 0 | } |
7180 | 0 | } else { |
7181 | 0 | nsContainerFrame::SetInitialChildList(aListID, aChildList); |
7182 | 0 | } |
7183 | 0 | } |
7184 | | |
7185 | | void |
7186 | | nsBlockFrame::CreateBulletFrameForListItem(bool aCreateBulletList, |
7187 | | bool aListStylePositionInside) |
7188 | 0 | { |
7189 | 0 | nsIPresShell* shell = PresShell(); |
7190 | 0 |
|
7191 | 0 | CSSPseudoElementType pseudoType = aCreateBulletList ? |
7192 | 0 | CSSPseudoElementType::mozListBullet : |
7193 | 0 | CSSPseudoElementType::mozListNumber; |
7194 | 0 |
|
7195 | 0 | RefPtr<ComputedStyle> kidSC = ResolveBulletStyle(pseudoType, |
7196 | 0 | shell->StyleSet()); |
7197 | 0 |
|
7198 | 0 | // Create bullet frame |
7199 | 0 | nsBulletFrame* bullet = new (shell) nsBulletFrame(kidSC); |
7200 | 0 | bullet->Init(mContent, this, nullptr); |
7201 | 0 |
|
7202 | 0 | // If the list bullet frame should be positioned inside then add |
7203 | 0 | // it to the flow now. |
7204 | 0 | if (aListStylePositionInside) { |
7205 | 0 | nsFrameList bulletList(bullet, bullet); |
7206 | 0 | AddFrames(bulletList, nullptr); |
7207 | 0 | SetProperty(InsideBulletProperty(), bullet); |
7208 | 0 | AddStateBits(NS_BLOCK_FRAME_HAS_INSIDE_BULLET); |
7209 | 0 | } else { |
7210 | 0 | nsFrameList* bulletList = new (shell) nsFrameList(bullet, bullet); |
7211 | 0 | SetProperty(OutsideBulletProperty(), bulletList); |
7212 | 0 | AddStateBits(NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET); |
7213 | 0 | } |
7214 | 0 | } |
7215 | | |
7216 | | bool |
7217 | | nsBlockFrame::BulletIsEmpty() const |
7218 | 0 | { |
7219 | 0 | NS_ASSERTION(mContent->GetPrimaryFrame()->StyleDisplay()->mDisplay == |
7220 | 0 | mozilla::StyleDisplay::ListItem && HasOutsideBullet(), |
7221 | 0 | "should only care when we have an outside bullet"); |
7222 | 0 | const nsStyleList* list = StyleList(); |
7223 | 0 | return list->mCounterStyle->IsNone() && |
7224 | 0 | !list->GetListStyleImage(); |
7225 | 0 | } |
7226 | | |
7227 | | void |
7228 | | nsBlockFrame::GetSpokenBulletText(nsAString& aText) const |
7229 | 0 | { |
7230 | 0 | const nsStyleList* myList = StyleList(); |
7231 | 0 | if (myList->GetListStyleImage()) { |
7232 | 0 | aText.Assign(kDiscCharacter); |
7233 | 0 | aText.Append(' '); |
7234 | 0 | } else { |
7235 | 0 | nsBulletFrame* bullet = GetBullet(); |
7236 | 0 | if (bullet) { |
7237 | 0 | bullet->GetSpokenText(aText); |
7238 | 0 | } else { |
7239 | 0 | aText.Truncate(); |
7240 | 0 | } |
7241 | 0 | } |
7242 | 0 | } |
7243 | | |
7244 | | bool |
7245 | | nsBlockFrame::RenumberChildFrames(int32_t* aOrdinal, |
7246 | | int32_t aDepth, |
7247 | | int32_t aIncrement, |
7248 | | bool aForCounting) |
7249 | 0 | { |
7250 | 0 | // Examine each line in the block |
7251 | 0 | bool foundValidLine; |
7252 | 0 | nsBlockInFlowLineIterator bifLineIter(this, &foundValidLine); |
7253 | 0 | if (!foundValidLine) { |
7254 | 0 | return false; |
7255 | 0 | } |
7256 | 0 | |
7257 | 0 | bool renumberedABullet = false; |
7258 | 0 | do { |
7259 | 0 | nsLineList::iterator line = bifLineIter.GetLine(); |
7260 | 0 | nsIFrame* kid = line->mFirstChild; |
7261 | 0 | int32_t n = line->GetChildCount(); |
7262 | 0 | while (--n >= 0) { |
7263 | 0 | bool kidRenumberedABullet = |
7264 | 0 | kid->RenumberFrameAndDescendants(aOrdinal, aDepth, aIncrement, aForCounting); |
7265 | 0 | if (!aForCounting && kidRenumberedABullet) { |
7266 | 0 | line->MarkDirty(); |
7267 | 0 | renumberedABullet = true; |
7268 | 0 | } |
7269 | 0 | kid = kid->GetNextSibling(); |
7270 | 0 | } |
7271 | 0 | } while (bifLineIter.Next()); |
7272 | 0 |
|
7273 | 0 | // We need to set NS_FRAME_HAS_DIRTY_CHILDREN bits up the tree between |
7274 | 0 | // the bullet and the caller of RenumberLists. But the caller itself |
7275 | 0 | // has to be responsible for setting the bit itself, since that caller |
7276 | 0 | // might be making a FrameNeedsReflow call, which requires that the |
7277 | 0 | // bit not be set yet. |
7278 | 0 | if (renumberedABullet && aDepth != 0) { |
7279 | 0 | AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); |
7280 | 0 | } |
7281 | 0 |
|
7282 | 0 | return renumberedABullet; |
7283 | 0 | } |
7284 | | |
7285 | | void |
7286 | | nsBlockFrame::ReflowBullet(nsIFrame* aBulletFrame, |
7287 | | BlockReflowInput& aState, |
7288 | | ReflowOutput& aMetrics, |
7289 | | nscoord aLineTop) |
7290 | 0 | { |
7291 | 0 | const ReflowInput &ri = aState.mReflowInput; |
7292 | 0 |
|
7293 | 0 | // Reflow the bullet now |
7294 | 0 | WritingMode bulletWM = aBulletFrame->GetWritingMode(); |
7295 | 0 | LogicalSize availSize(bulletWM); |
7296 | 0 | // Make up an inline-size since it doesn't really matter (XXX). |
7297 | 0 | availSize.ISize(bulletWM) = aState.ContentISize(); |
7298 | 0 | availSize.BSize(bulletWM) = NS_UNCONSTRAINEDSIZE; |
7299 | 0 |
|
7300 | 0 | // Get the reason right. |
7301 | 0 | // XXXwaterson Should this look just like the logic in |
7302 | 0 | // nsBlockReflowContext::ReflowBlock and nsLineLayout::ReflowFrame? |
7303 | 0 | ReflowInput reflowInput(aState.mPresContext, ri, |
7304 | 0 | aBulletFrame, availSize); |
7305 | 0 | nsReflowStatus status; |
7306 | 0 | aBulletFrame->Reflow(aState.mPresContext, aMetrics, reflowInput, status); |
7307 | 0 |
|
7308 | 0 | // Get the float available space using our saved state from before we |
7309 | 0 | // started reflowing the block, so that we ignore any floats inside |
7310 | 0 | // the block. |
7311 | 0 | // FIXME: aLineTop isn't actually set correctly by some callers, since |
7312 | 0 | // they reposition the line. |
7313 | 0 | LogicalRect floatAvailSpace = |
7314 | 0 | aState.GetFloatAvailableSpaceWithState(aLineTop, ShapeType::ShapeOutside, |
7315 | 0 | &aState.mFloatManagerStateBefore) |
7316 | 0 | .mRect; |
7317 | 0 | // FIXME (bug 25888): need to check the entire region that the first |
7318 | 0 | // line overlaps, not just the top pixel. |
7319 | 0 |
|
7320 | 0 | // Place the bullet now. We want to place the bullet relative to the |
7321 | 0 | // border-box of the associated block (using the right/left margin of |
7322 | 0 | // the bullet frame as separation). However, if a line box would be |
7323 | 0 | // displaced by floats that are *outside* the associated block, we |
7324 | 0 | // want to displace it by the same amount. That is, we act as though |
7325 | 0 | // the edge of the floats is the content-edge of the block, and place |
7326 | 0 | // the bullet at a position offset from there by the block's padding, |
7327 | 0 | // the block's border, and the bullet frame's margin. |
7328 | 0 |
|
7329 | 0 | // IStart from floatAvailSpace gives us the content/float start edge |
7330 | 0 | // in the current writing mode. Then we subtract out the start |
7331 | 0 | // border/padding and the bullet's width and margin to offset the position. |
7332 | 0 | WritingMode wm = ri.GetWritingMode(); |
7333 | 0 | // Get the bullet's margin, converted to our writing mode so that we can |
7334 | 0 | // combine it with other logical values here. |
7335 | 0 | LogicalMargin bulletMargin = |
7336 | 0 | reflowInput.ComputedLogicalMargin().ConvertTo(wm, bulletWM); |
7337 | 0 | nscoord iStart = floatAvailSpace.IStart(wm) - |
7338 | 0 | ri.ComputedLogicalBorderPadding().IStart(wm) - |
7339 | 0 | bulletMargin.IEnd(wm) - |
7340 | 0 | aMetrics.ISize(wm); |
7341 | 0 |
|
7342 | 0 | // Approximate the bullets position; vertical alignment will provide |
7343 | 0 | // the final vertical location. We pass our writing-mode here, because |
7344 | 0 | // it may be different from the bullet frame's mode. |
7345 | 0 | nscoord bStart = floatAvailSpace.BStart(wm); |
7346 | 0 | aBulletFrame->SetRect(wm, LogicalRect(wm, iStart, bStart, |
7347 | 0 | aMetrics.ISize(wm), |
7348 | 0 | aMetrics.BSize(wm)), |
7349 | 0 | aState.ContainerSize()); |
7350 | 0 | aBulletFrame->DidReflow(aState.mPresContext, &aState.mReflowInput); |
7351 | 0 | } |
7352 | | |
7353 | | // This is used to scan frames for any float placeholders, add their |
7354 | | // floats to the list represented by aList, and remove the |
7355 | | // floats from whatever list they might be in. We don't search descendants |
7356 | | // that are float containing blocks. Floats that or not children of 'this' |
7357 | | // are ignored (they are not added to aList). |
7358 | | void |
7359 | | nsBlockFrame::DoCollectFloats(nsIFrame* aFrame, nsFrameList& aList, |
7360 | | bool aCollectSiblings) |
7361 | 0 | { |
7362 | 0 | while (aFrame) { |
7363 | 0 | // Don't descend into float containing blocks. |
7364 | 0 | if (!aFrame->IsFloatContainingBlock()) { |
7365 | 0 | nsIFrame* outOfFlowFrame = |
7366 | 0 | aFrame->IsPlaceholderFrame() |
7367 | 0 | ? nsLayoutUtils::GetFloatFromPlaceholder(aFrame) |
7368 | 0 | : nullptr; |
7369 | 0 | while (outOfFlowFrame && outOfFlowFrame->GetParent() == this) { |
7370 | 0 | RemoveFloat(outOfFlowFrame); |
7371 | 0 | // Remove the IS_PUSHED_FLOAT bit, in case |outOfFlowFrame| came from |
7372 | 0 | // the PushedFloats list. |
7373 | 0 | outOfFlowFrame->RemoveStateBits(NS_FRAME_IS_PUSHED_FLOAT); |
7374 | 0 | aList.AppendFrame(nullptr, outOfFlowFrame); |
7375 | 0 | outOfFlowFrame = outOfFlowFrame->GetNextInFlow(); |
7376 | 0 | // FIXME: By not pulling floats whose parent is one of our |
7377 | 0 | // later siblings, are we risking the pushed floats getting |
7378 | 0 | // out-of-order? |
7379 | 0 | // XXXmats nsInlineFrame's lazy reparenting depends on NOT doing that. |
7380 | 0 | } |
7381 | 0 |
|
7382 | 0 | DoCollectFloats(aFrame->PrincipalChildList().FirstChild(), aList, true); |
7383 | 0 | DoCollectFloats(aFrame->GetChildList(kOverflowList).FirstChild(), aList, true); |
7384 | 0 | } |
7385 | 0 | if (!aCollectSiblings) |
7386 | 0 | break; |
7387 | 0 | aFrame = aFrame->GetNextSibling(); |
7388 | 0 | } |
7389 | 0 | } |
7390 | | |
7391 | | void |
7392 | | nsBlockFrame::CheckFloats(BlockReflowInput& aState) |
7393 | 0 | { |
7394 | | #ifdef DEBUG |
7395 | | // If any line is still dirty, that must mean we're going to reflow this |
7396 | | // block again soon (e.g. because we bailed out after noticing that |
7397 | | // clearance was imposed), so don't worry if the floats are out of sync. |
7398 | | bool anyLineDirty = false; |
7399 | | |
7400 | | // Check that the float list is what we would have built |
7401 | | AutoTArray<nsIFrame*, 8> lineFloats; |
7402 | | for (LineIterator line = LinesBegin(), line_end = LinesEnd(); |
7403 | | line != line_end; ++line) { |
7404 | | if (line->HasFloats()) { |
7405 | | nsFloatCache* fc = line->GetFirstFloat(); |
7406 | | while (fc) { |
7407 | | lineFloats.AppendElement(fc->mFloat); |
7408 | | fc = fc->Next(); |
7409 | | } |
7410 | | } |
7411 | | if (line->IsDirty()) { |
7412 | | anyLineDirty = true; |
7413 | | } |
7414 | | } |
7415 | | |
7416 | | AutoTArray<nsIFrame*, 8> storedFloats; |
7417 | | bool equal = true; |
7418 | | uint32_t i = 0; |
7419 | | for (nsIFrame* f : mFloats) { |
7420 | | if (f->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT) |
7421 | | continue; |
7422 | | storedFloats.AppendElement(f); |
7423 | | if (i < lineFloats.Length() && lineFloats.ElementAt(i) != f) { |
7424 | | equal = false; |
7425 | | } |
7426 | | ++i; |
7427 | | } |
7428 | | |
7429 | | if ((!equal || lineFloats.Length() != storedFloats.Length()) && !anyLineDirty) { |
7430 | | NS_WARNING("nsBlockFrame::CheckFloats: Explicit float list is out of sync with float cache"); |
7431 | | #if defined(DEBUG_roc) |
7432 | | nsFrame::RootFrameList(PresContext(), stdout, 0); |
7433 | | for (i = 0; i < lineFloats.Length(); ++i) { |
7434 | | printf("Line float: %p\n", lineFloats.ElementAt(i)); |
7435 | | } |
7436 | | for (i = 0; i < storedFloats.Length(); ++i) { |
7437 | | printf("Stored float: %p\n", storedFloats.ElementAt(i)); |
7438 | | } |
7439 | | #endif |
7440 | | } |
7441 | | #endif |
7442 | |
|
7443 | 0 | const nsFrameList* oofs = GetOverflowOutOfFlows(); |
7444 | 0 | if (oofs && oofs->NotEmpty()) { |
7445 | 0 | // Floats that were pushed should be removed from our float |
7446 | 0 | // manager. Otherwise the float manager's YMost or XMost might |
7447 | 0 | // be larger than necessary, causing this block to get an |
7448 | 0 | // incorrect desired height (or width). Some of these floats |
7449 | 0 | // may not actually have been added to the float manager because |
7450 | 0 | // they weren't reflowed before being pushed; that's OK, |
7451 | 0 | // RemoveRegions will ignore them. It is safe to do this here |
7452 | 0 | // because we know from here on the float manager will only be |
7453 | 0 | // used for its XMost and YMost, not to place new floats and |
7454 | 0 | // lines. |
7455 | 0 | aState.FloatManager()->RemoveTrailingRegions(oofs->FirstChild()); |
7456 | 0 | } |
7457 | 0 | } |
7458 | | |
7459 | | void |
7460 | | nsBlockFrame::IsMarginRoot(bool* aBStartMarginRoot, bool* aBEndMarginRoot) |
7461 | 0 | { |
7462 | 0 | if (!(GetStateBits() & NS_BLOCK_MARGIN_ROOT)) { |
7463 | 0 | nsIFrame* parent = GetParent(); |
7464 | 0 | if (!parent || parent->IsFloatContainingBlock()) { |
7465 | 0 | *aBStartMarginRoot = false; |
7466 | 0 | *aBEndMarginRoot = false; |
7467 | 0 | return; |
7468 | 0 | } |
7469 | 0 | if (parent->IsColumnSetFrame()) { |
7470 | 0 | *aBStartMarginRoot = GetPrevInFlow() == nullptr; |
7471 | 0 | *aBEndMarginRoot = GetNextInFlow() == nullptr; |
7472 | 0 | return; |
7473 | 0 | } |
7474 | 0 | } |
7475 | 0 | |
7476 | 0 | *aBStartMarginRoot = true; |
7477 | 0 | *aBEndMarginRoot = true; |
7478 | 0 | } |
7479 | | |
7480 | | /* static */ |
7481 | | bool |
7482 | | nsBlockFrame::BlockNeedsFloatManager(nsIFrame* aBlock) |
7483 | 0 | { |
7484 | 0 | MOZ_ASSERT(aBlock, "Must have a frame"); |
7485 | 0 | NS_ASSERTION(nsLayoutUtils::GetAsBlock(aBlock), "aBlock must be a block"); |
7486 | 0 |
|
7487 | 0 | nsIFrame* parent = aBlock->GetParent(); |
7488 | 0 | return (aBlock->GetStateBits() & NS_BLOCK_FLOAT_MGR) || |
7489 | 0 | (parent && !parent->IsFloatContainingBlock()); |
7490 | 0 | } |
7491 | | |
7492 | | /* static */ |
7493 | | bool |
7494 | | nsBlockFrame::BlockCanIntersectFloats(nsIFrame* aFrame) |
7495 | 0 | { |
7496 | 0 | return aFrame->IsFrameOfType(nsIFrame::eBlockFrame) && |
7497 | 0 | !aFrame->IsFrameOfType(nsIFrame::eReplaced) && |
7498 | 0 | !(aFrame->GetStateBits() & NS_BLOCK_FLOAT_MGR); |
7499 | 0 | } |
7500 | | |
7501 | | // Note that this width can vary based on the vertical position. |
7502 | | // However, the cases where it varies are the cases where the width fits |
7503 | | // in the available space given, which means that variation shouldn't |
7504 | | // matter. |
7505 | | /* static */ |
7506 | | nsBlockFrame::ReplacedElementISizeToClear |
7507 | | nsBlockFrame::ISizeToClearPastFloats(const BlockReflowInput& aState, |
7508 | | const LogicalRect& aFloatAvailableSpace, |
7509 | | nsIFrame* aFrame) |
7510 | 0 | { |
7511 | 0 | nscoord inlineStartOffset, inlineEndOffset; |
7512 | 0 | WritingMode wm = aState.mReflowInput.GetWritingMode(); |
7513 | 0 | SizeComputationInput offsetState(aFrame, aState.mReflowInput.mRenderingContext, |
7514 | 0 | wm, aState.mContentArea.ISize(wm)); |
7515 | 0 |
|
7516 | 0 | ReplacedElementISizeToClear result; |
7517 | 0 | aState.ComputeReplacedBlockOffsetsForFloats(aFrame, aFloatAvailableSpace, |
7518 | 0 | inlineStartOffset, |
7519 | 0 | inlineEndOffset); |
7520 | 0 | nscoord availISize = aState.mContentArea.ISize(wm) - |
7521 | 0 | inlineStartOffset - inlineEndOffset; |
7522 | 0 |
|
7523 | 0 | // We actually don't want the min width here; see bug 427782; we only |
7524 | 0 | // want to displace if the width won't compute to a value small enough |
7525 | 0 | // to fit. |
7526 | 0 | // All we really need here is the result of ComputeSize, and we |
7527 | 0 | // could *almost* get that from an SizeComputationInput, except for the |
7528 | 0 | // last argument. |
7529 | 0 | WritingMode frWM = aFrame->GetWritingMode(); |
7530 | 0 | LogicalSize availSpace = LogicalSize(wm, availISize, NS_UNCONSTRAINEDSIZE). |
7531 | 0 | ConvertTo(frWM, wm); |
7532 | 0 | ReflowInput reflowInput(aState.mPresContext, aState.mReflowInput, |
7533 | 0 | aFrame, availSpace); |
7534 | 0 | result.borderBoxISize = |
7535 | 0 | reflowInput.ComputedSizeWithBorderPadding().ConvertTo(wm, frWM).ISize(wm); |
7536 | 0 | // Use the margins from offsetState rather than reflowInput so that |
7537 | 0 | // they aren't reduced by ignoring margins in overconstrained cases. |
7538 | 0 | LogicalMargin computedMargin = |
7539 | 0 | offsetState.ComputedLogicalMargin().ConvertTo(wm, frWM); |
7540 | 0 | result.marginIStart = computedMargin.IStart(wm); |
7541 | 0 | return result; |
7542 | 0 | } |
7543 | | |
7544 | | /* static */ |
7545 | | nsBlockFrame* |
7546 | | nsBlockFrame::GetNearestAncestorBlock(nsIFrame* aCandidate) |
7547 | 0 | { |
7548 | 0 | nsBlockFrame* block = nullptr; |
7549 | 0 | while(aCandidate) { |
7550 | 0 | block = nsLayoutUtils::GetAsBlock(aCandidate); |
7551 | 0 | if (block) { |
7552 | 0 | // yay, candidate is a block! |
7553 | 0 | return block; |
7554 | 0 | } |
7555 | 0 | // Not a block. Check its parent next. |
7556 | 0 | aCandidate = aCandidate->GetParent(); |
7557 | 0 | } |
7558 | 0 | MOZ_ASSERT_UNREACHABLE("Fell off frame tree looking for ancestor block!"); |
7559 | 0 | return nullptr; |
7560 | 0 | } |
7561 | | |
7562 | | void |
7563 | | nsBlockFrame::ComputeFinalBSize(const ReflowInput& aReflowInput, |
7564 | | nsReflowStatus* aStatus, |
7565 | | nscoord aContentBSize, |
7566 | | const LogicalMargin& aBorderPadding, |
7567 | | LogicalSize& aFinalSize, |
7568 | | nscoord aConsumed) |
7569 | 0 | { |
7570 | 0 | WritingMode wm = aReflowInput.GetWritingMode(); |
7571 | 0 | // Figure out how much of the computed height should be |
7572 | 0 | // applied to this frame. |
7573 | 0 | nscoord computedBSizeLeftOver = GetEffectiveComputedBSize(aReflowInput, |
7574 | 0 | aConsumed); |
7575 | 0 | NS_ASSERTION(!( IS_TRUE_OVERFLOW_CONTAINER(this) |
7576 | 0 | && computedBSizeLeftOver ), |
7577 | 0 | "overflow container must not have computedBSizeLeftOver"); |
7578 | 0 |
|
7579 | 0 | aFinalSize.BSize(wm) = |
7580 | 0 | NSCoordSaturatingAdd(NSCoordSaturatingAdd(aBorderPadding.BStart(wm), |
7581 | 0 | computedBSizeLeftOver), |
7582 | 0 | aBorderPadding.BEnd(wm)); |
7583 | 0 |
|
7584 | 0 | if (aStatus->IsIncomplete() && |
7585 | 0 | aFinalSize.BSize(wm) <= aReflowInput.AvailableBSize()) { |
7586 | 0 | // We ran out of height on this page but we're incomplete. |
7587 | 0 | // Set status to complete except for overflow. |
7588 | 0 | aStatus->SetOverflowIncomplete(); |
7589 | 0 | } |
7590 | 0 |
|
7591 | 0 | if (aStatus->IsComplete()) { |
7592 | 0 | if (computedBSizeLeftOver > 0 && |
7593 | 0 | NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableBSize() && |
7594 | 0 | aFinalSize.BSize(wm) > aReflowInput.AvailableBSize()) { |
7595 | 0 | if (ShouldAvoidBreakInside(aReflowInput)) { |
7596 | 0 | aStatus->SetInlineLineBreakBeforeAndReset(); |
7597 | 0 | return; |
7598 | 0 | } |
7599 | 0 | // We don't fit and we consumed some of the computed height, |
7600 | 0 | // so we should consume all the available height and then |
7601 | 0 | // break. If our bottom border/padding straddles the break |
7602 | 0 | // point, then this will increase our height and push the |
7603 | 0 | // border/padding to the next page/column. |
7604 | 0 | aFinalSize.BSize(wm) = std::max(aReflowInput.AvailableBSize(), |
7605 | 0 | aContentBSize); |
7606 | 0 | aStatus->SetIncomplete(); |
7607 | 0 | if (!GetNextInFlow()) |
7608 | 0 | aStatus->SetNextInFlowNeedsReflow(); |
7609 | 0 | } |
7610 | 0 | } |
7611 | 0 | } |
7612 | | |
7613 | | nsresult |
7614 | | nsBlockFrame::ResolveBidi() |
7615 | 0 | { |
7616 | 0 | NS_ASSERTION(!GetPrevInFlow(), |
7617 | 0 | "ResolveBidi called on non-first continuation"); |
7618 | 0 |
|
7619 | 0 | nsPresContext* presContext = PresContext(); |
7620 | 0 | if (!presContext->BidiEnabled()) { |
7621 | 0 | return NS_OK; |
7622 | 0 | } |
7623 | 0 | |
7624 | 0 | return nsBidiPresUtils::Resolve(this); |
7625 | 0 | } |
7626 | | |
7627 | | void |
7628 | | nsBlockFrame::UpdatePseudoElementStyles(ServoRestyleState& aRestyleState) |
7629 | 0 | { |
7630 | 0 | // first-letter needs to be updated before first-line, because first-line can |
7631 | 0 | // change the style of the first-letter. |
7632 | 0 | if (HasFirstLetterChild()) { |
7633 | 0 | UpdateFirstLetterStyle(aRestyleState); |
7634 | 0 | } |
7635 | 0 |
|
7636 | 0 | if (nsBulletFrame* bullet = GetBullet()) { |
7637 | 0 | CSSPseudoElementType type = bullet->Style()->GetPseudoType(); |
7638 | 0 | RefPtr<ComputedStyle> newBulletStyle = |
7639 | 0 | ResolveBulletStyle(type, &aRestyleState.StyleSet()); |
7640 | 0 | UpdateStyleOfOwnedChildFrame(bullet, newBulletStyle, aRestyleState); |
7641 | 0 | } |
7642 | 0 |
|
7643 | 0 | if (nsIFrame* firstLineFrame = GetFirstLineFrame()) { |
7644 | 0 | nsIFrame* styleParent = |
7645 | 0 | CorrectStyleParentFrame(firstLineFrame->GetParent(), |
7646 | 0 | nsCSSPseudoElements::firstLine()); |
7647 | 0 |
|
7648 | 0 | ComputedStyle* parentStyle = styleParent->Style(); |
7649 | 0 | RefPtr<ComputedStyle> firstLineStyle = |
7650 | 0 | aRestyleState.StyleSet() |
7651 | 0 | .ResolvePseudoElementStyle(mContent->AsElement(), |
7652 | 0 | CSSPseudoElementType::firstLine, |
7653 | 0 | parentStyle, |
7654 | 0 | nullptr); |
7655 | 0 |
|
7656 | 0 | // FIXME(bz): Can we make first-line continuations be non-inheriting anon |
7657 | 0 | // boxes? |
7658 | 0 | RefPtr<ComputedStyle> continuationStyle = aRestyleState.StyleSet(). |
7659 | 0 | ResolveInheritingAnonymousBoxStyle(nsCSSAnonBoxes::mozLineFrame(), |
7660 | 0 | parentStyle); |
7661 | 0 |
|
7662 | 0 | UpdateStyleOfOwnedChildFrame(firstLineFrame, firstLineStyle, aRestyleState, |
7663 | 0 | Some(continuationStyle.get())); |
7664 | 0 |
|
7665 | 0 | // We also want to update the styles of the first-line's descendants. We |
7666 | 0 | // don't need to compute a changehint for this, though, since any changes to |
7667 | 0 | // them are handled by the first-line anyway. |
7668 | 0 | RestyleManager* manager = PresContext()->RestyleManager(); |
7669 | 0 | for (nsIFrame* kid : firstLineFrame->PrincipalChildList()) { |
7670 | 0 | manager->ReparentComputedStyleForFirstLine(kid); |
7671 | 0 | } |
7672 | 0 | } |
7673 | 0 | } |
7674 | | |
7675 | | already_AddRefed<ComputedStyle> |
7676 | | nsBlockFrame::ResolveBulletStyle(CSSPseudoElementType aType, |
7677 | | ServoStyleSet* aStyleSet) |
7678 | 0 | { |
7679 | 0 | ComputedStyle* parentStyle = |
7680 | 0 | CorrectStyleParentFrame(this, |
7681 | 0 | nsCSSPseudoElements::GetPseudoAtom(aType))-> |
7682 | 0 | Style(); |
7683 | 0 |
|
7684 | 0 | return aStyleSet->ResolvePseudoElementStyle(mContent->AsElement(), aType, |
7685 | 0 | parentStyle, nullptr); |
7686 | 0 | } |
7687 | | |
7688 | | nsIFrame* |
7689 | | nsBlockFrame::GetFirstLetter() const |
7690 | 0 | { |
7691 | 0 | if (!(GetStateBits() & NS_BLOCK_HAS_FIRST_LETTER_STYLE)) { |
7692 | 0 | // Certainly no first-letter frame. |
7693 | 0 | return nullptr; |
7694 | 0 | } |
7695 | 0 | |
7696 | 0 | return GetProperty(FirstLetterProperty()); |
7697 | 0 | } |
7698 | | |
7699 | | nsIFrame* |
7700 | | nsBlockFrame::GetFirstLineFrame() const |
7701 | 0 | { |
7702 | 0 | // Our ::first-line frame is either the first thing on our principal child |
7703 | 0 | // list, or the second one if we have an inside bullet. |
7704 | 0 | nsIFrame* bullet = GetInsideBullet(); |
7705 | 0 | nsIFrame* maybeFirstLine; |
7706 | 0 | if (bullet) { |
7707 | 0 | maybeFirstLine = bullet->GetNextSibling(); |
7708 | 0 | } else { |
7709 | 0 | maybeFirstLine = PrincipalChildList().FirstChild(); |
7710 | 0 | } |
7711 | 0 |
|
7712 | 0 | if (maybeFirstLine && maybeFirstLine->IsLineFrame()) { |
7713 | 0 | return maybeFirstLine; |
7714 | 0 | } |
7715 | 0 | |
7716 | 0 | return nullptr; |
7717 | 0 | } |
7718 | | |
7719 | | #ifdef DEBUG |
7720 | | void |
7721 | | nsBlockFrame::VerifyLines(bool aFinalCheckOK) |
7722 | | { |
7723 | | if (!gVerifyLines) { |
7724 | | return; |
7725 | | } |
7726 | | if (mLines.empty()) { |
7727 | | return; |
7728 | | } |
7729 | | |
7730 | | nsLineBox* cursor = GetLineCursor(); |
7731 | | |
7732 | | // Add up the counts on each line. Also validate that IsFirstLine is |
7733 | | // set properly. |
7734 | | int32_t count = 0; |
7735 | | LineIterator line, line_end; |
7736 | | for (line = LinesBegin(), line_end = LinesEnd(); |
7737 | | line != line_end; |
7738 | | ++line) { |
7739 | | if (line == cursor) { |
7740 | | cursor = nullptr; |
7741 | | } |
7742 | | if (aFinalCheckOK) { |
7743 | | MOZ_ASSERT(line->GetChildCount(), "empty line"); |
7744 | | if (line->IsBlock()) { |
7745 | | NS_ASSERTION(1 == line->GetChildCount(), "bad first line"); |
7746 | | } |
7747 | | } |
7748 | | count += line->GetChildCount(); |
7749 | | } |
7750 | | |
7751 | | // Then count the frames |
7752 | | int32_t frameCount = 0; |
7753 | | nsIFrame* frame = mLines.front()->mFirstChild; |
7754 | | while (frame) { |
7755 | | frameCount++; |
7756 | | frame = frame->GetNextSibling(); |
7757 | | } |
7758 | | NS_ASSERTION(count == frameCount, "bad line list"); |
7759 | | |
7760 | | // Next: test that each line has right number of frames on it |
7761 | | for (line = LinesBegin(), line_end = LinesEnd(); |
7762 | | line != line_end; |
7763 | | ) { |
7764 | | count = line->GetChildCount(); |
7765 | | frame = line->mFirstChild; |
7766 | | while (--count >= 0) { |
7767 | | frame = frame->GetNextSibling(); |
7768 | | } |
7769 | | ++line; |
7770 | | if ((line != line_end) && (0 != line->GetChildCount())) { |
7771 | | NS_ASSERTION(frame == line->mFirstChild, "bad line list"); |
7772 | | } |
7773 | | } |
7774 | | |
7775 | | if (cursor) { |
7776 | | FrameLines* overflowLines = GetOverflowLines(); |
7777 | | if (overflowLines) { |
7778 | | LineIterator line = overflowLines->mLines.begin(); |
7779 | | LineIterator line_end = overflowLines->mLines.end(); |
7780 | | for (; line != line_end; ++line) { |
7781 | | if (line == cursor) { |
7782 | | cursor = nullptr; |
7783 | | break; |
7784 | | } |
7785 | | } |
7786 | | } |
7787 | | } |
7788 | | NS_ASSERTION(!cursor, "stale LineCursorProperty"); |
7789 | | } |
7790 | | |
7791 | | void |
7792 | | nsBlockFrame::VerifyOverflowSituation() |
7793 | | { |
7794 | | // Overflow out-of-flows must not have a next-in-flow in mFloats or mFrames. |
7795 | | nsFrameList* oofs = GetOverflowOutOfFlows() ; |
7796 | | if (oofs) { |
7797 | | for (nsFrameList::Enumerator e(*oofs); !e.AtEnd(); e.Next()) { |
7798 | | nsIFrame* nif = e.get()->GetNextInFlow(); |
7799 | | MOZ_ASSERT(!nif || (!mFloats.ContainsFrame(nif) && !mFrames.ContainsFrame(nif))); |
7800 | | } |
7801 | | } |
7802 | | |
7803 | | // Pushed floats must not have a next-in-flow in mFloats or mFrames. |
7804 | | oofs = GetPushedFloats(); |
7805 | | if (oofs) { |
7806 | | for (nsFrameList::Enumerator e(*oofs); !e.AtEnd(); e.Next()) { |
7807 | | nsIFrame* nif = e.get()->GetNextInFlow(); |
7808 | | MOZ_ASSERT(!nif || (!mFloats.ContainsFrame(nif) && !mFrames.ContainsFrame(nif))); |
7809 | | } |
7810 | | } |
7811 | | |
7812 | | // A child float next-in-flow's parent must be |this| or a next-in-flow of |this|. |
7813 | | // Later next-in-flows must have the same or later parents. |
7814 | | nsIFrame::ChildListID childLists[] = { nsIFrame::kFloatList, |
7815 | | nsIFrame::kPushedFloatsList }; |
7816 | | for (size_t i = 0; i < ArrayLength(childLists); ++i) { |
7817 | | nsFrameList children(GetChildList(childLists[i])); |
7818 | | for (nsFrameList::Enumerator e(children); !e.AtEnd(); e.Next()) { |
7819 | | nsIFrame* parent = this; |
7820 | | nsIFrame* nif = e.get()->GetNextInFlow(); |
7821 | | for (; nif; nif = nif->GetNextInFlow()) { |
7822 | | bool found = false; |
7823 | | for (nsIFrame* p = parent; p; p = p->GetNextInFlow()) { |
7824 | | if (nif->GetParent() == p) { |
7825 | | parent = p; |
7826 | | found = true; |
7827 | | break; |
7828 | | } |
7829 | | } |
7830 | | MOZ_ASSERT(found, "next-in-flow is a child of parent earlier in the frame tree?"); |
7831 | | } |
7832 | | } |
7833 | | } |
7834 | | |
7835 | | nsBlockFrame* flow = static_cast<nsBlockFrame*>(FirstInFlow()); |
7836 | | while (flow) { |
7837 | | FrameLines* overflowLines = flow->GetOverflowLines(); |
7838 | | if (overflowLines) { |
7839 | | NS_ASSERTION(!overflowLines->mLines.empty(), |
7840 | | "should not be empty if present"); |
7841 | | NS_ASSERTION(overflowLines->mLines.front()->mFirstChild, |
7842 | | "bad overflow lines"); |
7843 | | NS_ASSERTION(overflowLines->mLines.front()->mFirstChild == |
7844 | | overflowLines->mFrames.FirstChild(), |
7845 | | "bad overflow frames / lines"); |
7846 | | } |
7847 | | nsLineBox* cursor = flow->GetLineCursor(); |
7848 | | if (cursor) { |
7849 | | LineIterator line = flow->LinesBegin(); |
7850 | | LineIterator line_end = flow->LinesEnd(); |
7851 | | for (; line != line_end && line != cursor; ++line) |
7852 | | ; |
7853 | | if (line == line_end && overflowLines) { |
7854 | | line = overflowLines->mLines.begin(); |
7855 | | line_end = overflowLines->mLines.end(); |
7856 | | for (; line != line_end && line != cursor; ++line) |
7857 | | ; |
7858 | | } |
7859 | | MOZ_ASSERT(line != line_end, "stale LineCursorProperty"); |
7860 | | } |
7861 | | flow = static_cast<nsBlockFrame*>(flow->GetNextInFlow()); |
7862 | | } |
7863 | | } |
7864 | | |
7865 | | int32_t |
7866 | | nsBlockFrame::GetDepth() const |
7867 | | { |
7868 | | int32_t depth = 0; |
7869 | | nsIFrame* parent = GetParent(); |
7870 | | while (parent) { |
7871 | | parent = parent->GetParent(); |
7872 | | depth++; |
7873 | | } |
7874 | | return depth; |
7875 | | } |
7876 | | |
7877 | | already_AddRefed<ComputedStyle> |
7878 | | nsBlockFrame::GetFirstLetterStyle(nsPresContext* aPresContext) |
7879 | | { |
7880 | | return aPresContext->StyleSet()-> |
7881 | | ProbePseudoElementStyle(*mContent->AsElement(), |
7882 | | CSSPseudoElementType::firstLetter, |
7883 | | Style()); |
7884 | | } |
7885 | | #endif |