/src/mozilla-central/layout/generic/nsInlineFrame.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 | | /* rendering object for CSS display:inline objects */ |
8 | | |
9 | | #include "nsInlineFrame.h" |
10 | | |
11 | | #include "gfxContext.h" |
12 | | #include "nsLineLayout.h" |
13 | | #include "nsBlockFrame.h" |
14 | | #include "nsPlaceholderFrame.h" |
15 | | #include "nsGkAtoms.h" |
16 | | #include "nsPresContext.h" |
17 | | #include "nsCSSAnonBoxes.h" |
18 | | #include "mozilla/RestyleManager.h" |
19 | | #include "nsDisplayList.h" |
20 | | #include "mozilla/Likely.h" |
21 | | #include "SVGTextFrame.h" |
22 | | #include "nsStyleChangeList.h" |
23 | | #include "mozilla/ComputedStyle.h" |
24 | | #include "mozilla/ServoStyleSet.h" |
25 | | |
26 | | #ifdef DEBUG |
27 | | #undef NOISY_PUSHING |
28 | | #endif |
29 | | |
30 | | using namespace mozilla; |
31 | | using namespace mozilla::layout; |
32 | | |
33 | | |
34 | | ////////////////////////////////////////////////////////////////////// |
35 | | |
36 | | // Basic nsInlineFrame methods |
37 | | |
38 | | nsInlineFrame* |
39 | | NS_NewInlineFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle) |
40 | 0 | { |
41 | 0 | return new (aPresShell) nsInlineFrame(aStyle); |
42 | 0 | } |
43 | | |
44 | | NS_IMPL_FRAMEARENA_HELPERS(nsInlineFrame) |
45 | | |
46 | 0 | NS_QUERYFRAME_HEAD(nsInlineFrame) |
47 | 0 | NS_QUERYFRAME_ENTRY(nsInlineFrame) |
48 | 0 | NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) |
49 | | |
50 | | #ifdef DEBUG_FRAME_DUMP |
51 | | nsresult |
52 | | nsInlineFrame::GetFrameName(nsAString& aResult) const |
53 | | { |
54 | | return MakeFrameName(NS_LITERAL_STRING("Inline"), aResult); |
55 | | } |
56 | | #endif |
57 | | |
58 | | void |
59 | | nsInlineFrame::InvalidateFrame(uint32_t aDisplayItemKey, bool aRebuildDisplayItems) |
60 | 0 | { |
61 | 0 | if (nsSVGUtils::IsInSVGTextSubtree(this)) { |
62 | 0 | nsIFrame* svgTextFrame = nsLayoutUtils::GetClosestFrameOfType( |
63 | 0 | GetParent(), LayoutFrameType::SVGText); |
64 | 0 | svgTextFrame->InvalidateFrame(); |
65 | 0 | return; |
66 | 0 | } |
67 | 0 | nsContainerFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems); |
68 | 0 | } |
69 | | |
70 | | void |
71 | | nsInlineFrame::InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey, bool aRebuildDisplayItems) |
72 | 0 | { |
73 | 0 | if (nsSVGUtils::IsInSVGTextSubtree(this)) { |
74 | 0 | nsIFrame* svgTextFrame = nsLayoutUtils::GetClosestFrameOfType( |
75 | 0 | GetParent(), LayoutFrameType::SVGText); |
76 | 0 | svgTextFrame->InvalidateFrame(); |
77 | 0 | return; |
78 | 0 | } |
79 | 0 | nsContainerFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey, aRebuildDisplayItems); |
80 | 0 | } |
81 | | |
82 | | static inline bool |
83 | | IsMarginZero(const nsStyleCoord &aCoord) |
84 | 0 | { |
85 | 0 | return aCoord.GetUnit() == eStyleUnit_Auto || |
86 | 0 | nsLayoutUtils::IsMarginZero(aCoord); |
87 | 0 | } |
88 | | |
89 | | /* virtual */ bool |
90 | | nsInlineFrame::IsSelfEmpty() |
91 | 0 | { |
92 | | #if 0 |
93 | | // I used to think inline frames worked this way, but it seems they |
94 | | // don't. At least not in our codebase. |
95 | | if (GetPresContext()->CompatibilityMode() == eCompatibility_FullStandards) { |
96 | | return false; |
97 | | } |
98 | | #endif |
99 | | const nsStyleMargin* margin = StyleMargin(); |
100 | 0 | const nsStyleBorder* border = StyleBorder(); |
101 | 0 | const nsStylePadding* padding = StylePadding(); |
102 | 0 | // Block-start and -end ignored, since they shouldn't affect things, but this |
103 | 0 | // doesn't really match with nsLineLayout.cpp's setting of |
104 | 0 | // ZeroEffectiveSpanBox, anymore, so what should this really be? |
105 | 0 | WritingMode wm = GetWritingMode(); |
106 | 0 | bool haveStart, haveEnd; |
107 | 0 | // Initially set up haveStart and haveEnd in terms of visual (LTR/TTB) |
108 | 0 | // coordinates; we'll exchange them later if bidi-RTL is in effect to |
109 | 0 | // get logical start and end flags. |
110 | 0 | if (wm.IsVertical()) { |
111 | 0 | haveStart = |
112 | 0 | border->GetComputedBorderWidth(eSideTop) != 0 || |
113 | 0 | !nsLayoutUtils::IsPaddingZero(padding->mPadding.GetTop()) || |
114 | 0 | !IsMarginZero(margin->mMargin.GetTop()); |
115 | 0 | haveEnd = |
116 | 0 | border->GetComputedBorderWidth(eSideBottom) != 0 || |
117 | 0 | !nsLayoutUtils::IsPaddingZero(padding->mPadding.GetBottom()) || |
118 | 0 | !IsMarginZero(margin->mMargin.GetBottom()); |
119 | 0 | } else { |
120 | 0 | haveStart = |
121 | 0 | border->GetComputedBorderWidth(eSideLeft) != 0 || |
122 | 0 | !nsLayoutUtils::IsPaddingZero(padding->mPadding.GetLeft()) || |
123 | 0 | !IsMarginZero(margin->mMargin.GetLeft()); |
124 | 0 | haveEnd = |
125 | 0 | border->GetComputedBorderWidth(eSideRight) != 0 || |
126 | 0 | !nsLayoutUtils::IsPaddingZero(padding->mPadding.GetRight()) || |
127 | 0 | !IsMarginZero(margin->mMargin.GetRight()); |
128 | 0 | } |
129 | 0 | if (haveStart || haveEnd) { |
130 | 0 | // We skip this block and return false for box-decoration-break:clone since |
131 | 0 | // in that case all the continuations will have the border/padding/margin. |
132 | 0 | if ((GetStateBits() & NS_FRAME_PART_OF_IBSPLIT) && |
133 | 0 | StyleBorder()->mBoxDecorationBreak == StyleBoxDecorationBreak::Slice) { |
134 | 0 | // When direction=rtl, we need to consider logical rather than visual |
135 | 0 | // start and end, so swap the flags. |
136 | 0 | if (!wm.IsBidiLTR()) { |
137 | 0 | Swap(haveStart, haveEnd); |
138 | 0 | } |
139 | 0 | // For ib-split frames, ignore things we know we'll skip in GetSkipSides. |
140 | 0 | // XXXbz should we be doing this for non-ib-split frames too, in a more |
141 | 0 | // general way? |
142 | 0 |
|
143 | 0 | // Get the first continuation eagerly, as a performance optimization, to |
144 | 0 | // avoid having to get it twice.. |
145 | 0 | nsIFrame* firstCont = FirstContinuation(); |
146 | 0 | return |
147 | 0 | (!haveStart || firstCont->FrameIsNonFirstInIBSplit()) && |
148 | 0 | (!haveEnd || firstCont->FrameIsNonLastInIBSplit()); |
149 | 0 | } |
150 | 0 | return false; |
151 | 0 | } |
152 | 0 | return true; |
153 | 0 | } |
154 | | |
155 | | bool |
156 | | nsInlineFrame::IsEmpty() |
157 | 0 | { |
158 | 0 | if (!IsSelfEmpty()) { |
159 | 0 | return false; |
160 | 0 | } |
161 | 0 | |
162 | 0 | for (nsIFrame* kid : mFrames) { |
163 | 0 | if (!kid->IsEmpty()) |
164 | 0 | return false; |
165 | 0 | } |
166 | 0 |
|
167 | 0 | return true; |
168 | 0 | } |
169 | | |
170 | | nsIFrame::FrameSearchResult |
171 | | nsInlineFrame::PeekOffsetCharacter(bool aForward, int32_t* aOffset, |
172 | | PeekOffsetCharacterOptions aOptions) |
173 | 0 | { |
174 | 0 | // Override the implementation in nsFrame, to skip empty inline frames |
175 | 0 | NS_ASSERTION (aOffset && *aOffset <= 1, "aOffset out of range"); |
176 | 0 | int32_t startOffset = *aOffset; |
177 | 0 | if (startOffset < 0) |
178 | 0 | startOffset = 1; |
179 | 0 | if (aForward == (startOffset == 0)) { |
180 | 0 | // We're before the frame and moving forward, or after it and moving backwards: |
181 | 0 | // skip to the other side, but keep going. |
182 | 0 | *aOffset = 1 - startOffset; |
183 | 0 | } |
184 | 0 | return CONTINUE; |
185 | 0 | } |
186 | | |
187 | | void |
188 | | nsInlineFrame::DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData) |
189 | 0 | { |
190 | 0 | nsFrameList* overflowFrames = GetOverflowFrames(); |
191 | 0 | if (overflowFrames) { |
192 | 0 | // Fixup the parent pointers for any child frames on the OverflowList. |
193 | 0 | // nsIFrame::DestroyFrom depends on that to find the sticky scroll |
194 | 0 | // container (an ancestor). |
195 | 0 | overflowFrames->ApplySetParent(this); |
196 | 0 | } |
197 | 0 | nsContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData); |
198 | 0 | } |
199 | | |
200 | | nsresult |
201 | | nsInlineFrame::StealFrame(nsIFrame* aChild) |
202 | 0 | { |
203 | 0 | if (MaybeStealOverflowContainerFrame(aChild)) { |
204 | 0 | return NS_OK; |
205 | 0 | } |
206 | 0 | |
207 | 0 | nsInlineFrame* parent = this; |
208 | 0 | bool removed = false; |
209 | 0 | do { |
210 | 0 | removed = parent->mFrames.StartRemoveFrame(aChild); |
211 | 0 | if (removed) { |
212 | 0 | break; |
213 | 0 | } |
214 | 0 | |
215 | 0 | // We didn't find the child in our principal child list. |
216 | 0 | // Maybe it's on the overflow list? |
217 | 0 | nsFrameList* frameList = parent->GetOverflowFrames(); |
218 | 0 | if (frameList) { |
219 | 0 | removed = frameList->ContinueRemoveFrame(aChild); |
220 | 0 | if (frameList->IsEmpty()) { |
221 | 0 | parent->DestroyOverflowList(); |
222 | 0 | } |
223 | 0 | if (removed) { |
224 | 0 | break; |
225 | 0 | } |
226 | 0 | } |
227 | 0 | |
228 | 0 | // Due to our "lazy reparenting" optimization 'aChild' might not actually |
229 | 0 | // be on any of our child lists, but instead in one of our next-in-flows. |
230 | 0 | parent = static_cast<nsInlineFrame*>(parent->GetNextInFlow()); |
231 | 0 | } while (parent); |
232 | 0 |
|
233 | 0 | MOZ_ASSERT(removed, "nsInlineFrame::StealFrame: can't find aChild"); |
234 | 0 | return removed ? NS_OK : NS_ERROR_UNEXPECTED; |
235 | 0 | } |
236 | | |
237 | | void |
238 | | nsInlineFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, |
239 | | const nsDisplayListSet& aLists) |
240 | 0 | { |
241 | 0 | BuildDisplayListForInline(aBuilder, aLists); |
242 | 0 |
|
243 | 0 | // The sole purpose of this is to trigger display of the selection |
244 | 0 | // window for Named Anchors, which don't have any children and |
245 | 0 | // normally don't have any size, but in Editor we use CSS to display |
246 | 0 | // an image to represent this "hidden" element. |
247 | 0 | if (!mFrames.FirstChild()) { |
248 | 0 | DisplaySelectionOverlay(aBuilder, aLists.Content()); |
249 | 0 | } |
250 | 0 | } |
251 | | |
252 | | ////////////////////////////////////////////////////////////////////// |
253 | | // Reflow methods |
254 | | |
255 | | /* virtual */ void |
256 | | nsInlineFrame::AddInlineMinISize(gfxContext *aRenderingContext, |
257 | | nsIFrame::InlineMinISizeData *aData) |
258 | 0 | { |
259 | 0 | DoInlineIntrinsicISize(aRenderingContext, aData, nsLayoutUtils::MIN_ISIZE); |
260 | 0 | } |
261 | | |
262 | | /* virtual */ void |
263 | | nsInlineFrame::AddInlinePrefISize(gfxContext *aRenderingContext, |
264 | | nsIFrame::InlinePrefISizeData *aData) |
265 | 0 | { |
266 | 0 | DoInlineIntrinsicISize(aRenderingContext, aData, nsLayoutUtils::PREF_ISIZE); |
267 | 0 | aData->mLineIsEmpty = false; |
268 | 0 | } |
269 | | |
270 | | /* virtual */ |
271 | | LogicalSize |
272 | | nsInlineFrame::ComputeSize(gfxContext *aRenderingContext, |
273 | | WritingMode aWM, |
274 | | const LogicalSize& aCBSize, |
275 | | nscoord aAvailableISize, |
276 | | const LogicalSize& aMargin, |
277 | | const LogicalSize& aBorder, |
278 | | const LogicalSize& aPadding, |
279 | | ComputeSizeFlags aFlags) |
280 | 0 | { |
281 | 0 | // Inlines and text don't compute size before reflow. |
282 | 0 | return LogicalSize(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); |
283 | 0 | } |
284 | | |
285 | | nsRect |
286 | | nsInlineFrame::ComputeTightBounds(DrawTarget* aDrawTarget) const |
287 | 0 | { |
288 | 0 | // be conservative |
289 | 0 | if (Style()->HasTextDecorationLines()) { |
290 | 0 | return GetVisualOverflowRect(); |
291 | 0 | } |
292 | 0 | return ComputeSimpleTightBounds(aDrawTarget); |
293 | 0 | } |
294 | | |
295 | | static void |
296 | | ReparentChildListStyle(nsPresContext* aPresContext, |
297 | | const nsFrameList::Slice& aFrames, |
298 | | nsIFrame* aParentFrame) |
299 | 0 | { |
300 | 0 | RestyleManager* restyleManager = aPresContext->RestyleManager(); |
301 | 0 |
|
302 | 0 | for (nsFrameList::Enumerator e(aFrames); !e.AtEnd(); e.Next()) { |
303 | 0 | NS_ASSERTION(e.get()->GetParent() == aParentFrame, "Bogus parentage"); |
304 | 0 | restyleManager->ReparentComputedStyleForFirstLine(e.get()); |
305 | 0 | nsLayoutUtils::MarkDescendantsDirty(e.get()); |
306 | 0 | } |
307 | 0 | } |
308 | | |
309 | | void |
310 | | nsInlineFrame::Reflow(nsPresContext* aPresContext, |
311 | | ReflowOutput& aMetrics, |
312 | | const ReflowInput& aReflowInput, |
313 | | nsReflowStatus& aStatus) |
314 | 0 | { |
315 | 0 | MarkInReflow(); |
316 | 0 | DO_GLOBAL_REFLOW_COUNT("nsInlineFrame"); |
317 | 0 | DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus); |
318 | 0 | MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); |
319 | 0 |
|
320 | 0 | if (nullptr == aReflowInput.mLineLayout) { |
321 | 0 | NS_ERROR("must have non-null aReflowInput.mLineLayout"); |
322 | 0 | return; |
323 | 0 | } |
324 | 0 | if (IsFrameTreeTooDeep(aReflowInput, aMetrics, aStatus)) { |
325 | 0 | return; |
326 | 0 | } |
327 | 0 | |
328 | 0 | bool lazilySetParentPointer = false; |
329 | 0 |
|
330 | 0 | // Check for an overflow list with our prev-in-flow |
331 | 0 | nsInlineFrame* prevInFlow = (nsInlineFrame*)GetPrevInFlow(); |
332 | 0 | if (prevInFlow) { |
333 | 0 | AutoFrameListPtr prevOverflowFrames(aPresContext, |
334 | 0 | prevInFlow->StealOverflowFrames()); |
335 | 0 | if (prevOverflowFrames) { |
336 | 0 | // When pushing and pulling frames we need to check for whether any |
337 | 0 | // views need to be reparented. |
338 | 0 | nsContainerFrame::ReparentFrameViewList(*prevOverflowFrames, prevInFlow, |
339 | 0 | this); |
340 | 0 |
|
341 | 0 | // Check if we should do the lazilySetParentPointer optimization. |
342 | 0 | // Only do it in simple cases where we're being reflowed for the |
343 | 0 | // first time, nothing (e.g. bidi resolution) has already given |
344 | 0 | // us children, and there's no next-in-flow, so all our frames |
345 | 0 | // will be taken from prevOverflowFrames. |
346 | 0 | if ((GetStateBits() & NS_FRAME_FIRST_REFLOW) && mFrames.IsEmpty() && |
347 | 0 | !GetNextInFlow()) { |
348 | 0 | // If our child list is empty, just put the new frames into it. |
349 | 0 | // Note that we don't set the parent pointer for the new frames. Instead wait |
350 | 0 | // to do this until we actually reflow the frame. If the overflow list contains |
351 | 0 | // thousands of frames this is a big performance issue (see bug #5588) |
352 | 0 | mFrames.SetFrames(*prevOverflowFrames); |
353 | 0 | lazilySetParentPointer = true; |
354 | 0 | } else { |
355 | 0 | // Insert the new frames at the beginning of the child list |
356 | 0 | // and set their parent pointer |
357 | 0 | const nsFrameList::Slice& newFrames = |
358 | 0 | mFrames.InsertFrames(this, nullptr, *prevOverflowFrames); |
359 | 0 | // If our prev in flow was under the first continuation of a first-line |
360 | 0 | // frame then we need to reparent the ComputedStyles to remove the |
361 | 0 | // the special first-line styling. In the lazilySetParentPointer case |
362 | 0 | // we reparent the ComputedStyles when we set their parents in |
363 | 0 | // nsInlineFrame::ReflowFrames and nsInlineFrame::ReflowInlineFrame. |
364 | 0 | if (aReflowInput.mLineLayout->GetInFirstLine()) { |
365 | 0 | ReparentChildListStyle(aPresContext, newFrames, this); |
366 | 0 | } |
367 | 0 | } |
368 | 0 | } |
369 | 0 | } |
370 | 0 |
|
371 | 0 | // It's also possible that we have an overflow list for ourselves |
372 | | #ifdef DEBUG |
373 | | if (GetStateBits() & NS_FRAME_FIRST_REFLOW) { |
374 | | // If it's our initial reflow, then we should not have an overflow list. |
375 | | // However, add an assertion in case we get reflowed more than once with |
376 | | // the initial reflow reason |
377 | | nsFrameList* overflowFrames = GetOverflowFrames(); |
378 | | NS_ASSERTION(!overflowFrames || overflowFrames->IsEmpty(), |
379 | | "overflow list is not empty for initial reflow"); |
380 | | } |
381 | | #endif |
382 | 0 | if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) { |
383 | 0 | DrainSelfOverflowListInternal(aReflowInput.mLineLayout->GetInFirstLine()); |
384 | 0 | } |
385 | 0 |
|
386 | 0 | // Set our own reflow state (additional state above and beyond aReflowInput). |
387 | 0 | InlineReflowInput irs; |
388 | 0 | irs.mPrevFrame = nullptr; |
389 | 0 | irs.mLineContainer = aReflowInput.mLineLayout->LineContainerFrame(); |
390 | 0 | irs.mLineLayout = aReflowInput.mLineLayout; |
391 | 0 | irs.mNextInFlow = (nsInlineFrame*) GetNextInFlow(); |
392 | 0 | irs.mSetParentPointer = lazilySetParentPointer; |
393 | 0 |
|
394 | 0 | if (mFrames.IsEmpty()) { |
395 | 0 | // Try to pull over one frame before starting so that we know |
396 | 0 | // whether we have an anonymous block or not. |
397 | 0 | Unused << PullOneFrame(aPresContext, irs); |
398 | 0 | } |
399 | 0 |
|
400 | 0 | ReflowFrames(aPresContext, aReflowInput, irs, aMetrics, aStatus); |
401 | 0 |
|
402 | 0 | ReflowAbsoluteFrames(aPresContext, aMetrics, aReflowInput, aStatus); |
403 | 0 |
|
404 | 0 | // Note: the line layout code will properly compute our |
405 | 0 | // overflow-rect state for us. |
406 | 0 |
|
407 | 0 | NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aMetrics); |
408 | 0 | } |
409 | | |
410 | | nsresult |
411 | | nsInlineFrame::AttributeChanged(int32_t aNameSpaceID, |
412 | | nsAtom* aAttribute, |
413 | | int32_t aModType) |
414 | 0 | { |
415 | 0 | nsresult rv = |
416 | 0 | nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); |
417 | 0 |
|
418 | 0 | if (NS_FAILED(rv)) { |
419 | 0 | return rv; |
420 | 0 | } |
421 | 0 | |
422 | 0 | if (nsSVGUtils::IsInSVGTextSubtree(this)) { |
423 | 0 | SVGTextFrame* f = static_cast<SVGTextFrame*>( |
424 | 0 | nsLayoutUtils::GetClosestFrameOfType(this, LayoutFrameType::SVGText)); |
425 | 0 | f->HandleAttributeChangeInDescendant(mContent->AsElement(), |
426 | 0 | aNameSpaceID, aAttribute); |
427 | 0 | } |
428 | 0 |
|
429 | 0 | return NS_OK; |
430 | 0 | } |
431 | | |
432 | | bool |
433 | | nsInlineFrame::DrainSelfOverflowListInternal(bool aInFirstLine) |
434 | 0 | { |
435 | 0 | AutoFrameListPtr overflowFrames(PresContext(), StealOverflowFrames()); |
436 | 0 | if (!overflowFrames || overflowFrames->IsEmpty()) { |
437 | 0 | return false; |
438 | 0 | } |
439 | 0 | |
440 | 0 | // The frames on our own overflowlist may have been pushed by a |
441 | 0 | // previous lazilySetParentPointer Reflow so we need to ensure the |
442 | 0 | // correct parent pointer. This is sometimes skipped by Reflow. |
443 | 0 | nsIFrame* firstChild = overflowFrames->FirstChild(); |
444 | 0 | RestyleManager* restyleManager = PresContext()->RestyleManager(); |
445 | 0 | for (nsIFrame* f = firstChild; f; f = f->GetNextSibling()) { |
446 | 0 | f->SetParent(this); |
447 | 0 | if (MOZ_UNLIKELY(aInFirstLine)) { |
448 | 0 | restyleManager->ReparentComputedStyleForFirstLine(f); |
449 | 0 | nsLayoutUtils::MarkDescendantsDirty(f); |
450 | 0 | } |
451 | 0 | } |
452 | 0 | mFrames.AppendFrames(nullptr, *overflowFrames); |
453 | 0 | return true; |
454 | 0 | } |
455 | | |
456 | | /* virtual */ bool |
457 | | nsInlineFrame::DrainSelfOverflowList() |
458 | 0 | { |
459 | 0 | nsIFrame* lineContainer = nsLayoutUtils::FindNearestBlockAncestor(this); |
460 | 0 | // Add the eInFirstLine flag if we have a ::first-line ancestor frame. |
461 | 0 | // No need to look further than the nearest line container though. |
462 | 0 | bool inFirstLine = false; |
463 | 0 | for (nsIFrame* p = GetParent(); p != lineContainer; p = p->GetParent()) { |
464 | 0 | if (p->IsLineFrame()) { |
465 | 0 | inFirstLine = true; |
466 | 0 | break; |
467 | 0 | } |
468 | 0 | } |
469 | 0 | return DrainSelfOverflowListInternal(inFirstLine); |
470 | 0 | } |
471 | | |
472 | | /* virtual */ bool |
473 | | nsInlineFrame::CanContinueTextRun() const |
474 | 0 | { |
475 | 0 | // We can continue a text run through an inline frame |
476 | 0 | return true; |
477 | 0 | } |
478 | | |
479 | | /* virtual */ void |
480 | | nsInlineFrame::PullOverflowsFromPrevInFlow() |
481 | 0 | { |
482 | 0 | nsInlineFrame* prevInFlow = static_cast<nsInlineFrame*>(GetPrevInFlow()); |
483 | 0 | if (prevInFlow) { |
484 | 0 | nsPresContext* presContext = PresContext(); |
485 | 0 | AutoFrameListPtr prevOverflowFrames(presContext, |
486 | 0 | prevInFlow->StealOverflowFrames()); |
487 | 0 | if (prevOverflowFrames) { |
488 | 0 | // Assume that our prev-in-flow has the same line container that we do. |
489 | 0 | nsContainerFrame::ReparentFrameViewList(*prevOverflowFrames, prevInFlow, |
490 | 0 | this); |
491 | 0 | mFrames.InsertFrames(this, nullptr, *prevOverflowFrames); |
492 | 0 | } |
493 | 0 | } |
494 | 0 | } |
495 | | |
496 | | void |
497 | | nsInlineFrame::ReflowFrames(nsPresContext* aPresContext, |
498 | | const ReflowInput& aReflowInput, |
499 | | InlineReflowInput& irs, |
500 | | ReflowOutput& aMetrics, |
501 | | nsReflowStatus& aStatus) |
502 | 0 | { |
503 | 0 | MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); |
504 | 0 |
|
505 | 0 | nsLineLayout* lineLayout = aReflowInput.mLineLayout; |
506 | 0 | bool inFirstLine = aReflowInput.mLineLayout->GetInFirstLine(); |
507 | 0 | RestyleManager* restyleManager = aPresContext->RestyleManager(); |
508 | 0 | WritingMode frameWM = aReflowInput.GetWritingMode(); |
509 | 0 | WritingMode lineWM = aReflowInput.mLineLayout->mRootSpan->mWritingMode; |
510 | 0 | LogicalMargin framePadding = aReflowInput.ComputedLogicalBorderPadding(); |
511 | 0 | nscoord startEdge = 0; |
512 | 0 | const bool boxDecorationBreakClone = |
513 | 0 | MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak == |
514 | 0 | StyleBoxDecorationBreak::Clone); |
515 | 0 | // Don't offset by our start borderpadding if we have a prev continuation or |
516 | 0 | // if we're in a part of an {ib} split other than the first one. For |
517 | 0 | // box-decoration-break:clone we always offset our start since all |
518 | 0 | // continuations have border/padding. |
519 | 0 | if ((!GetPrevContinuation() && !FrameIsNonFirstInIBSplit()) || |
520 | 0 | boxDecorationBreakClone) { |
521 | 0 | startEdge = framePadding.IStart(frameWM); |
522 | 0 | } |
523 | 0 | nscoord availableISize = aReflowInput.AvailableISize(); |
524 | 0 | NS_ASSERTION(availableISize != NS_UNCONSTRAINEDSIZE, |
525 | 0 | "should no longer use available widths"); |
526 | 0 | // Subtract off inline axis border+padding from availableISize |
527 | 0 | availableISize -= startEdge; |
528 | 0 | availableISize -= framePadding.IEnd(frameWM); |
529 | 0 | lineLayout->BeginSpan(this, &aReflowInput, startEdge, |
530 | 0 | startEdge + availableISize, &mBaseline); |
531 | 0 |
|
532 | 0 | // First reflow our principal children. |
533 | 0 | nsIFrame* frame = mFrames.FirstChild(); |
534 | 0 | bool done = false; |
535 | 0 | while (frame) { |
536 | 0 | // Check if we should lazily set the child frame's parent pointer. |
537 | 0 | if (irs.mSetParentPointer) { |
538 | 0 | nsIFrame* child = frame; |
539 | 0 | do { |
540 | 0 | child->SetParent(this); |
541 | 0 | if (inFirstLine) { |
542 | 0 | restyleManager->ReparentComputedStyleForFirstLine(child); |
543 | 0 | nsLayoutUtils::MarkDescendantsDirty(child); |
544 | 0 | } |
545 | 0 | // We also need to do the same for |frame|'s next-in-flows that are in |
546 | 0 | // the sibling list. Otherwise, if we reflow |frame| and it's complete |
547 | 0 | // we'll crash when trying to delete its next-in-flow. |
548 | 0 | // This scenario doesn't happen often, but it can happen. |
549 | 0 | nsIFrame* nextSibling = child->GetNextSibling(); |
550 | 0 | child = child->GetNextInFlow(); |
551 | 0 | if (MOZ_UNLIKELY(child)) { |
552 | 0 | while (child != nextSibling && nextSibling) { |
553 | 0 | nextSibling = nextSibling->GetNextSibling(); |
554 | 0 | } |
555 | 0 | if (!nextSibling) { |
556 | 0 | child = nullptr; |
557 | 0 | } |
558 | 0 | } |
559 | 0 | MOZ_ASSERT(!child || mFrames.ContainsFrame(child)); |
560 | 0 | } while (child); |
561 | 0 |
|
562 | 0 | // Fix the parent pointer for ::first-letter child frame next-in-flows, |
563 | 0 | // so nsFirstLetterFrame::Reflow can destroy them safely (bug 401042). |
564 | 0 | nsIFrame* realFrame = nsPlaceholderFrame::GetRealFrameFor(frame); |
565 | 0 | if (realFrame->IsLetterFrame()) { |
566 | 0 | nsIFrame* child = realFrame->PrincipalChildList().FirstChild(); |
567 | 0 | if (child) { |
568 | 0 | NS_ASSERTION(child->IsTextFrame(), "unexpected frame type"); |
569 | 0 | nsIFrame* nextInFlow = child->GetNextInFlow(); |
570 | 0 | for ( ; nextInFlow; nextInFlow = nextInFlow->GetNextInFlow()) { |
571 | 0 | NS_ASSERTION(nextInFlow->IsTextFrame(), "unexpected frame type"); |
572 | 0 | if (mFrames.ContainsFrame(nextInFlow)) { |
573 | 0 | nextInFlow->SetParent(this); |
574 | 0 | if (inFirstLine) { |
575 | 0 | restyleManager->ReparentComputedStyleForFirstLine(nextInFlow); |
576 | 0 | nsLayoutUtils::MarkDescendantsDirty(nextInFlow); |
577 | 0 | } |
578 | 0 | } |
579 | 0 | else { |
580 | | #ifdef DEBUG |
581 | | // Once we find a next-in-flow that isn't ours none of the |
582 | | // remaining next-in-flows should be either. |
583 | | for ( ; nextInFlow; nextInFlow = nextInFlow->GetNextInFlow()) { |
584 | | NS_ASSERTION(!mFrames.ContainsFrame(nextInFlow), |
585 | | "unexpected letter frame flow"); |
586 | | } |
587 | | #endif |
588 | | break; |
589 | 0 | } |
590 | 0 | } |
591 | 0 | } |
592 | 0 | } |
593 | 0 | } |
594 | 0 | MOZ_ASSERT(frame->GetParent() == this); |
595 | 0 |
|
596 | 0 | if (!done) { |
597 | 0 | bool reflowingFirstLetter = lineLayout->GetFirstLetterStyleOK(); |
598 | 0 | ReflowInlineFrame(aPresContext, aReflowInput, irs, frame, aStatus); |
599 | 0 | done = aStatus.IsInlineBreak() || |
600 | 0 | (!reflowingFirstLetter && aStatus.IsIncomplete()); |
601 | 0 | if (done) { |
602 | 0 | if (!irs.mSetParentPointer) { |
603 | 0 | break; |
604 | 0 | } |
605 | 0 | // Keep reparenting the remaining siblings, but don't reflow them. |
606 | 0 | nsFrameList* pushedFrames = GetOverflowFrames(); |
607 | 0 | if (pushedFrames && pushedFrames->FirstChild() == frame) { |
608 | 0 | // Don't bother if |frame| was pushed to our overflow list. |
609 | 0 | break; |
610 | 0 | } |
611 | 0 | } else { |
612 | 0 | irs.mPrevFrame = frame; |
613 | 0 | } |
614 | 0 | } |
615 | 0 | frame = frame->GetNextSibling(); |
616 | 0 | } |
617 | 0 |
|
618 | 0 | // Attempt to pull frames from our next-in-flow until we can't |
619 | 0 | if (!done && GetNextInFlow()) { |
620 | 0 | while (true) { |
621 | 0 | bool reflowingFirstLetter = lineLayout->GetFirstLetterStyleOK(); |
622 | 0 | if (!frame) { // Could be non-null if we pulled a first-letter frame and |
623 | 0 | // it created a continuation, since we don't push those. |
624 | 0 | frame = PullOneFrame(aPresContext, irs); |
625 | 0 | } |
626 | | #ifdef NOISY_PUSHING |
627 | | printf("%p pulled up %p\n", this, frame); |
628 | | #endif |
629 | 0 | if (!frame) { |
630 | 0 | break; |
631 | 0 | } |
632 | 0 | ReflowInlineFrame(aPresContext, aReflowInput, irs, frame, aStatus); |
633 | 0 | if (aStatus.IsInlineBreak() || |
634 | 0 | (!reflowingFirstLetter && aStatus.IsIncomplete())) { |
635 | 0 | break; |
636 | 0 | } |
637 | 0 | irs.mPrevFrame = frame; |
638 | 0 | frame = frame->GetNextSibling(); |
639 | 0 | } |
640 | 0 | } |
641 | 0 |
|
642 | 0 | NS_ASSERTION(!aStatus.IsComplete() || !GetOverflowFrames(), |
643 | 0 | "We can't be complete AND have overflow frames!"); |
644 | 0 |
|
645 | 0 | // If after reflowing our children they take up no area then make |
646 | 0 | // sure that we don't either. |
647 | 0 | // |
648 | 0 | // Note: CSS demands that empty inline elements still affect the |
649 | 0 | // line-height calculations. However, continuations of an inline |
650 | 0 | // that are empty we force to empty so that things like collapsed |
651 | 0 | // whitespace in an inline element don't affect the line-height. |
652 | 0 | aMetrics.ISize(lineWM) = lineLayout->EndSpan(this); |
653 | 0 |
|
654 | 0 | // Compute final width. |
655 | 0 |
|
656 | 0 | // XXX Note that that the padding start and end are in the frame's |
657 | 0 | // writing mode, but the metrics' inline-size is in the line's |
658 | 0 | // writing mode. This makes sense if the line and frame are both |
659 | 0 | // vertical or both horizontal, but what should happen with |
660 | 0 | // orthogonal inlines? |
661 | 0 |
|
662 | 0 | // Make sure to not include our start border and padding if we have a prev |
663 | 0 | // continuation or if we're in a part of an {ib} split other than the first |
664 | 0 | // one. For box-decoration-break:clone we always include our start border |
665 | 0 | // and padding since all continuations have them. |
666 | 0 | if ((!GetPrevContinuation() && !FrameIsNonFirstInIBSplit()) || |
667 | 0 | boxDecorationBreakClone) { |
668 | 0 | aMetrics.ISize(lineWM) += framePadding.IStart(frameWM); |
669 | 0 | } |
670 | 0 |
|
671 | 0 | /* |
672 | 0 | * We want to only apply the end border and padding if we're the last |
673 | 0 | * continuation and either not in an {ib} split or the last part of it. To |
674 | 0 | * be the last continuation we have to be complete (so that we won't get a |
675 | 0 | * next-in-flow) and have no non-fluid continuations on our continuation |
676 | 0 | * chain. For box-decoration-break:clone we always apply the end border and |
677 | 0 | * padding since all continuations have them. |
678 | 0 | */ |
679 | 0 | if ((aStatus.IsComplete() && |
680 | 0 | !LastInFlow()->GetNextContinuation() && |
681 | 0 | !FrameIsNonLastInIBSplit()) || |
682 | 0 | boxDecorationBreakClone) { |
683 | 0 | aMetrics.ISize(lineWM) += framePadding.IEnd(frameWM); |
684 | 0 | } |
685 | 0 |
|
686 | 0 | nsLayoutUtils::SetBSizeFromFontMetrics(this, aMetrics, |
687 | 0 | framePadding, lineWM, frameWM); |
688 | 0 |
|
689 | 0 | // For now our overflow area is zero. The real value will be |
690 | 0 | // computed in |nsLineLayout::RelativePositionFrames|. |
691 | 0 | aMetrics.mOverflowAreas.Clear(); |
692 | 0 |
|
693 | | #ifdef NOISY_FINAL_SIZE |
694 | | ListTag(stdout); |
695 | | printf(": metrics=%d,%d ascent=%d\n", |
696 | | aMetrics.Width(), aMetrics.Height(), aMetrics.TopAscent()); |
697 | | #endif |
698 | | } |
699 | | |
700 | | // Returns whether there's any remaining frame to pull. |
701 | | /* static */ bool |
702 | | nsInlineFrame::HasFramesToPull(nsInlineFrame* aNextInFlow) |
703 | 0 | { |
704 | 0 | while (aNextInFlow) { |
705 | 0 | if (!aNextInFlow->mFrames.IsEmpty()) { |
706 | 0 | return true; |
707 | 0 | } |
708 | 0 | if (const nsFrameList* overflow = aNextInFlow->GetOverflowFrames()) { |
709 | 0 | if (!overflow->IsEmpty()) { |
710 | 0 | return true; |
711 | 0 | } |
712 | 0 | } |
713 | 0 | aNextInFlow = static_cast<nsInlineFrame*>(aNextInFlow->GetNextInFlow()); |
714 | 0 | } |
715 | 0 | return false; |
716 | 0 | } |
717 | | |
718 | | void |
719 | | nsInlineFrame::ReflowInlineFrame(nsPresContext* aPresContext, |
720 | | const ReflowInput& aReflowInput, |
721 | | InlineReflowInput& irs, |
722 | | nsIFrame* aFrame, |
723 | | nsReflowStatus& aStatus) |
724 | 0 | { |
725 | 0 | nsLineLayout* lineLayout = aReflowInput.mLineLayout; |
726 | 0 | bool reflowingFirstLetter = lineLayout->GetFirstLetterStyleOK(); |
727 | 0 | bool pushedFrame; |
728 | 0 | aStatus.Reset(); |
729 | 0 | lineLayout->ReflowFrame(aFrame, aStatus, nullptr, pushedFrame); |
730 | 0 |
|
731 | 0 | if (aStatus.IsInlineBreakBefore()) { |
732 | 0 | if (aFrame != mFrames.FirstChild()) { |
733 | 0 | // Change break-before status into break-after since we have |
734 | 0 | // already placed at least one child frame. This preserves the |
735 | 0 | // break-type so that it can be propagated upward. |
736 | 0 | StyleClear oldBreakType = aStatus.BreakType(); |
737 | 0 | aStatus.Reset(); |
738 | 0 | aStatus.SetIncomplete(); |
739 | 0 | aStatus.SetInlineLineBreakAfter(oldBreakType); |
740 | 0 | PushFrames(aPresContext, aFrame, irs.mPrevFrame, irs); |
741 | 0 | } |
742 | 0 | else { |
743 | 0 | // Preserve reflow status when breaking-before our first child |
744 | 0 | // and propagate it upward without modification. |
745 | 0 | } |
746 | 0 | return; |
747 | 0 | } |
748 | 0 |
|
749 | 0 | // Create a next-in-flow if needed. |
750 | 0 | if (!aStatus.IsFullyComplete()) { |
751 | 0 | CreateNextInFlow(aFrame); |
752 | 0 | } |
753 | 0 |
|
754 | 0 | if (aStatus.IsInlineBreakAfter()) { |
755 | 0 | nsIFrame* nextFrame = aFrame->GetNextSibling(); |
756 | 0 | if (nextFrame) { |
757 | 0 | aStatus.SetIncomplete(); |
758 | 0 | PushFrames(aPresContext, nextFrame, aFrame, irs); |
759 | 0 | } else { |
760 | 0 | // We must return an incomplete status if there are more child |
761 | 0 | // frames remaining in a next-in-flow that follows this frame. |
762 | 0 | if (HasFramesToPull(static_cast<nsInlineFrame*>(GetNextInFlow()))) { |
763 | 0 | aStatus.SetIncomplete(); |
764 | 0 | } |
765 | 0 | } |
766 | 0 | return; |
767 | 0 | } |
768 | 0 |
|
769 | 0 | if (!aStatus.IsFullyComplete() && !reflowingFirstLetter) { |
770 | 0 | nsIFrame* nextFrame = aFrame->GetNextSibling(); |
771 | 0 | if (nextFrame) { |
772 | 0 | PushFrames(aPresContext, nextFrame, aFrame, irs); |
773 | 0 | } |
774 | 0 | } |
775 | 0 | } |
776 | | |
777 | | nsIFrame* |
778 | | nsInlineFrame::PullOneFrame(nsPresContext* aPresContext, InlineReflowInput& irs) |
779 | 0 | { |
780 | 0 | nsIFrame* frame = nullptr; |
781 | 0 | nsInlineFrame* nextInFlow = irs.mNextInFlow; |
782 | 0 |
|
783 | | #ifdef DEBUG |
784 | | bool willPull = HasFramesToPull(nextInFlow); |
785 | | #endif |
786 | |
|
787 | 0 | while (nextInFlow) { |
788 | 0 | frame = nextInFlow->mFrames.FirstChild(); |
789 | 0 | if (!frame) { |
790 | 0 | // The nextInFlow's principal list has no frames, try its overflow list. |
791 | 0 | nsFrameList* overflowFrames = nextInFlow->GetOverflowFrames(); |
792 | 0 | if (overflowFrames) { |
793 | 0 | frame = overflowFrames->RemoveFirstChild(); |
794 | 0 | if (overflowFrames->IsEmpty()) { |
795 | 0 | // We're stealing the only frame - delete the overflow list. |
796 | 0 | nextInFlow->DestroyOverflowList(); |
797 | 0 | } else { |
798 | 0 | // We leave the remaining frames on the overflow list (rather than |
799 | 0 | // putting them on nextInFlow's principal list) so we don't have to |
800 | 0 | // set up the parent for them. |
801 | 0 | } |
802 | 0 | // ReparentFloatsForInlineChild needs it to be on a child list - |
803 | 0 | // we remove it again below. |
804 | 0 | nextInFlow->mFrames.SetFrames(frame); |
805 | 0 | } |
806 | 0 | } |
807 | 0 |
|
808 | 0 | if (frame) { |
809 | 0 | // If our block has no next continuation, then any floats belonging to |
810 | 0 | // the pulled frame must belong to our block already. This check ensures |
811 | 0 | // we do no extra work in the common non-vertical-breaking case. |
812 | 0 | if (irs.mLineContainer && irs.mLineContainer->GetNextContinuation()) { |
813 | 0 | // The blockChildren.ContainsFrame check performed by |
814 | 0 | // ReparentFloatsForInlineChild will be fast because frame's ancestor |
815 | 0 | // will be the first child of its containing block. |
816 | 0 | ReparentFloatsForInlineChild(irs.mLineContainer, frame, false, |
817 | 0 | ReparentingDirection::Backwards); |
818 | 0 | } |
819 | 0 | nextInFlow->mFrames.RemoveFirstChild(); |
820 | 0 | // nsFirstLineFrame::PullOneFrame calls ReparentComputedStyle. |
821 | 0 |
|
822 | 0 | mFrames.InsertFrame(this, irs.mPrevFrame, frame); |
823 | 0 | if (irs.mLineLayout) { |
824 | 0 | irs.mLineLayout->SetDirtyNextLine(); |
825 | 0 | } |
826 | 0 | nsContainerFrame::ReparentFrameView(frame, nextInFlow, this); |
827 | 0 | break; |
828 | 0 | } |
829 | 0 | nextInFlow = static_cast<nsInlineFrame*>(nextInFlow->GetNextInFlow()); |
830 | 0 | irs.mNextInFlow = nextInFlow; |
831 | 0 | } |
832 | 0 |
|
833 | 0 | MOZ_ASSERT(!!frame == willPull); |
834 | 0 | return frame; |
835 | 0 | } |
836 | | |
837 | | void |
838 | | nsInlineFrame::PushFrames(nsPresContext* aPresContext, |
839 | | nsIFrame* aFromChild, |
840 | | nsIFrame* aPrevSibling, |
841 | | InlineReflowInput& aState) |
842 | 0 | { |
843 | | #ifdef NOISY_PUSHING |
844 | | printf("%p pushing aFromChild %p, disconnecting from prev sib %p\n", |
845 | | this, aFromChild, aPrevSibling); |
846 | | #endif |
847 | |
|
848 | 0 | PushChildrenToOverflow(aFromChild, aPrevSibling); |
849 | 0 | if (aState.mLineLayout) { |
850 | 0 | aState.mLineLayout->SetDirtyNextLine(); |
851 | 0 | } |
852 | 0 | } |
853 | | |
854 | | |
855 | | ////////////////////////////////////////////////////////////////////// |
856 | | |
857 | | nsIFrame::LogicalSides |
858 | | nsInlineFrame::GetLogicalSkipSides(const ReflowInput* aReflowInput) const |
859 | 0 | { |
860 | 0 | if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak == |
861 | 0 | StyleBoxDecorationBreak::Clone)) { |
862 | 0 | return LogicalSides(); |
863 | 0 | } |
864 | 0 | |
865 | 0 | LogicalSides skip; |
866 | 0 | if (!IsFirst()) { |
867 | 0 | nsInlineFrame* prev = (nsInlineFrame*) GetPrevContinuation(); |
868 | 0 | if ((GetStateBits() & NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET) || |
869 | 0 | (prev && (prev->mRect.height || prev->mRect.width))) { |
870 | 0 | // Prev continuation is not empty therefore we don't render our start |
871 | 0 | // border edge. |
872 | 0 | skip |= eLogicalSideBitsIStart; |
873 | 0 | } |
874 | 0 | else { |
875 | 0 | // If the prev continuation is empty, then go ahead and let our start |
876 | 0 | // edge border render. |
877 | 0 | } |
878 | 0 | } |
879 | 0 | if (!IsLast()) { |
880 | 0 | nsInlineFrame* next = (nsInlineFrame*) GetNextContinuation(); |
881 | 0 | if ((GetStateBits() & NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET) || |
882 | 0 | (next && (next->mRect.height || next->mRect.width))) { |
883 | 0 | // Next continuation is not empty therefore we don't render our end |
884 | 0 | // border edge. |
885 | 0 | skip |= eLogicalSideBitsIEnd; |
886 | 0 | } |
887 | 0 | else { |
888 | 0 | // If the next continuation is empty, then go ahead and let our end |
889 | 0 | // edge border render. |
890 | 0 | } |
891 | 0 | } |
892 | 0 |
|
893 | 0 | if (GetStateBits() & NS_FRAME_PART_OF_IBSPLIT) { |
894 | 0 | // All but the last part of an {ib} split should skip the "end" side (as |
895 | 0 | // determined by this frame's direction) and all but the first part of such |
896 | 0 | // a split should skip the "start" side. But figuring out which part of |
897 | 0 | // the split we are involves getting our first continuation, which might be |
898 | 0 | // expensive. So don't bother if we already have the relevant bits set. |
899 | 0 | if (skip != LogicalSides(eLogicalSideBitsIBoth)) { |
900 | 0 | // We're missing one of the skip bits, so check whether we need to set it. |
901 | 0 | // Only get the first continuation once, as an optimization. |
902 | 0 | nsIFrame* firstContinuation = FirstContinuation(); |
903 | 0 | if (firstContinuation->FrameIsNonLastInIBSplit()) { |
904 | 0 | skip |= eLogicalSideBitsIEnd; |
905 | 0 | } |
906 | 0 | if (firstContinuation->FrameIsNonFirstInIBSplit()) { |
907 | 0 | skip |= eLogicalSideBitsIStart; |
908 | 0 | } |
909 | 0 | } |
910 | 0 | } |
911 | 0 |
|
912 | 0 | return skip; |
913 | 0 | } |
914 | | |
915 | | nscoord |
916 | | nsInlineFrame::GetLogicalBaseline(mozilla::WritingMode aWritingMode) const |
917 | 0 | { |
918 | 0 | return mBaseline; |
919 | 0 | } |
920 | | |
921 | | #ifdef ACCESSIBILITY |
922 | | a11y::AccType |
923 | | nsInlineFrame::AccessibleType() |
924 | 0 | { |
925 | 0 | // Broken image accessibles are created here, because layout |
926 | 0 | // replaces the image or image control frame with an inline frame |
927 | 0 | if (mContent->IsHTMLElement(nsGkAtoms::input)) // Broken <input type=image ... /> |
928 | 0 | return a11y::eHTMLButtonType; |
929 | 0 | if (mContent->IsHTMLElement(nsGkAtoms::img)) // Create accessible for broken <img> |
930 | 0 | return a11y::eHyperTextType; |
931 | 0 | |
932 | 0 | return a11y::eNoType; |
933 | 0 | } |
934 | | #endif |
935 | | |
936 | | void |
937 | | nsInlineFrame::UpdateStyleOfOwnedAnonBoxesForIBSplit( |
938 | | ServoRestyleState& aRestyleState) |
939 | 0 | { |
940 | 0 | MOZ_ASSERT(GetStateBits() & NS_FRAME_OWNS_ANON_BOXES, |
941 | 0 | "Why did we get called?"); |
942 | 0 | MOZ_ASSERT(GetStateBits() & NS_FRAME_PART_OF_IBSPLIT, |
943 | 0 | "Why did we have the NS_FRAME_OWNS_ANON_BOXES bit set?"); |
944 | 0 | // Note: this assert _looks_ expensive, but it's cheap in all the cases when |
945 | 0 | // it passes! |
946 | 0 | MOZ_ASSERT(nsLayoutUtils::FirstContinuationOrIBSplitSibling(this) == this, |
947 | 0 | "Only the primary frame of the inline in a block-inside-inline " |
948 | 0 | "split should have NS_FRAME_OWNS_ANON_BOXES"); |
949 | 0 | MOZ_ASSERT(mContent->GetPrimaryFrame() == this, |
950 | 0 | "We should be the primary frame for our element"); |
951 | 0 |
|
952 | 0 | nsIFrame* blockFrame = GetProperty(nsIFrame::IBSplitSibling()); |
953 | 0 | MOZ_ASSERT(blockFrame, "Why did we have an IB split?"); |
954 | 0 |
|
955 | 0 | // The later inlines need to get our style. |
956 | 0 | ComputedStyle* ourStyle = Style(); |
957 | 0 |
|
958 | 0 | // The anonymous block's style inherits from ours, and we already have our new |
959 | 0 | // ComputedStyle. |
960 | 0 | RefPtr<ComputedStyle> newContext = |
961 | 0 | aRestyleState.StyleSet().ResolveInheritingAnonymousBoxStyle( |
962 | 0 | nsCSSAnonBoxes::mozBlockInsideInlineWrapper(), ourStyle); |
963 | 0 |
|
964 | 0 | // We're guaranteed that newContext only differs from the old ComputedStyle on |
965 | 0 | // the block in things they might inherit from us. And changehint processing |
966 | 0 | // guarantees walking the continuation and ib-sibling chains, so our existing |
967 | 0 | // changehint being in aChangeList is good enough. So we don't need to touch |
968 | 0 | // aChangeList at all here. |
969 | 0 |
|
970 | 0 | while (blockFrame) { |
971 | 0 | MOZ_ASSERT(!blockFrame->GetPrevContinuation(), |
972 | 0 | "Must be first continuation"); |
973 | 0 |
|
974 | 0 | MOZ_ASSERT(blockFrame->Style()->GetPseudo() == |
975 | 0 | nsCSSAnonBoxes::mozBlockInsideInlineWrapper(), |
976 | 0 | "Unexpected kind of ComputedStyle"); |
977 | 0 |
|
978 | 0 | // We don't want to just walk through using GetNextContinuationWithSameStyle |
979 | 0 | // here, because we want to set updated ComputedStyles on both our |
980 | 0 | // ib-sibling blocks and inlines. |
981 | 0 | for (nsIFrame* cont = blockFrame; cont; cont = cont->GetNextContinuation()) { |
982 | 0 | cont->SetComputedStyle(newContext); |
983 | 0 | } |
984 | 0 |
|
985 | 0 | nsIFrame* nextInline = blockFrame->GetProperty(nsIFrame::IBSplitSibling()); |
986 | 0 |
|
987 | 0 | // This check is here due to bug 1431232. Please remove it once |
988 | 0 | // that bug is fixed. |
989 | 0 | if (MOZ_UNLIKELY(!nextInline)) { |
990 | 0 | break; |
991 | 0 | } |
992 | 0 | |
993 | 0 | MOZ_ASSERT(nextInline, "There is always a trailing inline in an IB split"); |
994 | 0 |
|
995 | 0 | for (nsIFrame* cont = nextInline; cont; cont = cont->GetNextContinuation()) { |
996 | 0 | cont->SetComputedStyle(ourStyle); |
997 | 0 | } |
998 | 0 | blockFrame = nextInline->GetProperty(nsIFrame::IBSplitSibling()); |
999 | 0 | } |
1000 | 0 | } |
1001 | | |
1002 | | ////////////////////////////////////////////////////////////////////// |
1003 | | |
1004 | | // nsLineFrame implementation |
1005 | | |
1006 | | nsFirstLineFrame* |
1007 | | NS_NewFirstLineFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle) |
1008 | 0 | { |
1009 | 0 | return new (aPresShell) nsFirstLineFrame(aStyle); |
1010 | 0 | } |
1011 | | |
1012 | | NS_IMPL_FRAMEARENA_HELPERS(nsFirstLineFrame) |
1013 | | |
1014 | | void |
1015 | | nsFirstLineFrame::Init(nsIContent* aContent, |
1016 | | nsContainerFrame* aParent, |
1017 | | nsIFrame* aPrevInFlow) |
1018 | 0 | { |
1019 | 0 | nsInlineFrame::Init(aContent, aParent, aPrevInFlow); |
1020 | 0 | if (!aPrevInFlow) { |
1021 | 0 | MOZ_ASSERT(Style()->GetPseudo() == nsCSSPseudoElements::firstLine()); |
1022 | 0 | return; |
1023 | 0 | } |
1024 | 0 |
|
1025 | 0 | // This frame is a continuation - fixup the computed style if aPrevInFlow |
1026 | 0 | // is the first-in-flow (the only one with a ::first-line pseudo). |
1027 | 0 | if (aPrevInFlow->Style()->GetPseudo() == nsCSSPseudoElements::firstLine()) { |
1028 | 0 | MOZ_ASSERT(FirstInFlow() == aPrevInFlow); |
1029 | 0 | // Create a new ComputedStyle that is a child of the parent |
1030 | 0 | // ComputedStyle thus removing the ::first-line style. This way |
1031 | 0 | // we behave as if an anonymous (unstyled) span was the child |
1032 | 0 | // of the parent frame. |
1033 | 0 | ComputedStyle* parentContext = aParent->Style(); |
1034 | 0 | RefPtr<ComputedStyle> newSC = PresContext()->StyleSet()-> |
1035 | 0 | ResolveInheritingAnonymousBoxStyle(nsCSSAnonBoxes::mozLineFrame(), |
1036 | 0 | parentContext); |
1037 | 0 | SetComputedStyle(newSC); |
1038 | 0 | } else { |
1039 | 0 | MOZ_ASSERT(FirstInFlow() != aPrevInFlow); |
1040 | 0 | MOZ_ASSERT(aPrevInFlow->Style()->GetPseudo() == |
1041 | 0 | nsCSSAnonBoxes::mozLineFrame()); |
1042 | 0 | } |
1043 | 0 | } |
1044 | | |
1045 | | #ifdef DEBUG_FRAME_DUMP |
1046 | | nsresult |
1047 | | nsFirstLineFrame::GetFrameName(nsAString& aResult) const |
1048 | | { |
1049 | | return MakeFrameName(NS_LITERAL_STRING("Line"), aResult); |
1050 | | } |
1051 | | #endif |
1052 | | |
1053 | | nsIFrame* |
1054 | | nsFirstLineFrame::PullOneFrame(nsPresContext* aPresContext, |
1055 | | InlineReflowInput& irs) |
1056 | 0 | { |
1057 | 0 | nsIFrame* frame = nsInlineFrame::PullOneFrame(aPresContext, irs); |
1058 | 0 | if (frame && !GetPrevInFlow()) { |
1059 | 0 | // We are a first-line frame. Fixup the child frames |
1060 | 0 | // style-context that we just pulled. |
1061 | 0 | NS_ASSERTION(frame->GetParent() == this, "Incorrect parent?"); |
1062 | 0 | aPresContext->RestyleManager()->ReparentComputedStyleForFirstLine(frame); |
1063 | 0 | nsLayoutUtils::MarkDescendantsDirty(frame); |
1064 | 0 | } |
1065 | 0 | return frame; |
1066 | 0 | } |
1067 | | |
1068 | | void |
1069 | | nsFirstLineFrame::Reflow(nsPresContext* aPresContext, |
1070 | | ReflowOutput& aMetrics, |
1071 | | const ReflowInput& aReflowInput, |
1072 | | nsReflowStatus& aStatus) |
1073 | 0 | { |
1074 | 0 | MarkInReflow(); |
1075 | 0 | MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); |
1076 | 0 |
|
1077 | 0 | if (nullptr == aReflowInput.mLineLayout) { |
1078 | 0 | return; // XXX does this happen? why? |
1079 | 0 | } |
1080 | 0 | |
1081 | 0 | // Check for an overflow list with our prev-in-flow |
1082 | 0 | nsFirstLineFrame* prevInFlow = (nsFirstLineFrame*)GetPrevInFlow(); |
1083 | 0 | if (prevInFlow) { |
1084 | 0 | AutoFrameListPtr prevOverflowFrames(aPresContext, |
1085 | 0 | prevInFlow->StealOverflowFrames()); |
1086 | 0 | if (prevOverflowFrames) { |
1087 | 0 | // Reparent the new frames and their ComputedStyles. |
1088 | 0 | const nsFrameList::Slice& newFrames = |
1089 | 0 | mFrames.InsertFrames(this, nullptr, *prevOverflowFrames); |
1090 | 0 | ReparentChildListStyle(aPresContext, newFrames, this); |
1091 | 0 | } |
1092 | 0 | } |
1093 | 0 |
|
1094 | 0 | // It's also possible that we have an overflow list for ourselves. |
1095 | 0 | DrainSelfOverflowList(); |
1096 | 0 |
|
1097 | 0 | // Set our own reflow state (additional state above and beyond aReflowInput). |
1098 | 0 | InlineReflowInput irs; |
1099 | 0 | irs.mPrevFrame = nullptr; |
1100 | 0 | irs.mLineContainer = aReflowInput.mLineLayout->LineContainerFrame(); |
1101 | 0 | irs.mLineLayout = aReflowInput.mLineLayout; |
1102 | 0 | irs.mNextInFlow = (nsInlineFrame*) GetNextInFlow(); |
1103 | 0 |
|
1104 | 0 | bool wasEmpty = mFrames.IsEmpty(); |
1105 | 0 | if (wasEmpty) { |
1106 | 0 | // Try to pull over one frame before starting so that we know |
1107 | 0 | // whether we have an anonymous block or not. |
1108 | 0 | PullOneFrame(aPresContext, irs); |
1109 | 0 | } |
1110 | 0 |
|
1111 | 0 | if (nullptr == GetPrevInFlow()) { |
1112 | 0 | // XXX This is pretty sick, but what we do here is to pull-up, in |
1113 | 0 | // advance, all of the next-in-flows children. We re-resolve their |
1114 | 0 | // style while we are at at it so that when we reflow they have |
1115 | 0 | // the right style. |
1116 | 0 | // |
1117 | 0 | // All of this is so that text-runs reflow properly. |
1118 | 0 | irs.mPrevFrame = mFrames.LastChild(); |
1119 | 0 | for (;;) { |
1120 | 0 | nsIFrame* frame = PullOneFrame(aPresContext, irs); |
1121 | 0 | if (!frame) { |
1122 | 0 | break; |
1123 | 0 | } |
1124 | 0 | irs.mPrevFrame = frame; |
1125 | 0 | } |
1126 | 0 | irs.mPrevFrame = nullptr; |
1127 | 0 | } |
1128 | 0 |
|
1129 | 0 | NS_ASSERTION(!aReflowInput.mLineLayout->GetInFirstLine(), |
1130 | 0 | "Nested first-line frames? BOGUS"); |
1131 | 0 | aReflowInput.mLineLayout->SetInFirstLine(true); |
1132 | 0 | ReflowFrames(aPresContext, aReflowInput, irs, aMetrics, aStatus); |
1133 | 0 | aReflowInput.mLineLayout->SetInFirstLine(false); |
1134 | 0 |
|
1135 | 0 | ReflowAbsoluteFrames(aPresContext, aMetrics, aReflowInput, aStatus); |
1136 | 0 |
|
1137 | 0 | // Note: the line layout code will properly compute our overflow state for us |
1138 | 0 | } |
1139 | | |
1140 | | /* virtual */ void |
1141 | | nsFirstLineFrame::PullOverflowsFromPrevInFlow() |
1142 | 0 | { |
1143 | 0 | nsFirstLineFrame* prevInFlow = static_cast<nsFirstLineFrame*>(GetPrevInFlow()); |
1144 | 0 | if (prevInFlow) { |
1145 | 0 | nsPresContext* presContext = PresContext(); |
1146 | 0 | AutoFrameListPtr prevOverflowFrames(presContext, |
1147 | 0 | prevInFlow->StealOverflowFrames()); |
1148 | 0 | if (prevOverflowFrames) { |
1149 | 0 | // Assume that our prev-in-flow has the same line container that we do. |
1150 | 0 | const nsFrameList::Slice& newFrames = |
1151 | 0 | mFrames.InsertFrames(this, nullptr, *prevOverflowFrames); |
1152 | 0 | ReparentChildListStyle(presContext, newFrames, this); |
1153 | 0 | } |
1154 | 0 | } |
1155 | 0 | } |
1156 | | |
1157 | | /* virtual */ bool |
1158 | | nsFirstLineFrame::DrainSelfOverflowList() |
1159 | 0 | { |
1160 | 0 | AutoFrameListPtr overflowFrames(PresContext(), StealOverflowFrames()); |
1161 | 0 | if (overflowFrames) { |
1162 | 0 | bool result = !overflowFrames->IsEmpty(); |
1163 | 0 | const nsFrameList::Slice& newFrames = |
1164 | 0 | mFrames.AppendFrames(nullptr, *overflowFrames); |
1165 | 0 | ReparentChildListStyle(PresContext(), newFrames, this); |
1166 | 0 | return result; |
1167 | 0 | } |
1168 | 0 | return false; |
1169 | 0 | } |