|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 /* |
|
7 * rendering object that is the root of the frame tree, which contains |
|
8 * the document's scrollbars and contains fixed-positioned elements |
|
9 */ |
|
10 |
|
11 #include "nsViewportFrame.h" |
|
12 #include "nsGkAtoms.h" |
|
13 #include "nsIScrollableFrame.h" |
|
14 #include "nsSubDocumentFrame.h" |
|
15 #include "nsAbsoluteContainingBlock.h" |
|
16 #include "GeckoProfiler.h" |
|
17 |
|
18 using namespace mozilla; |
|
19 |
|
20 nsIFrame* |
|
21 NS_NewViewportFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) |
|
22 { |
|
23 return new (aPresShell) ViewportFrame(aContext); |
|
24 } |
|
25 |
|
26 NS_IMPL_FRAMEARENA_HELPERS(ViewportFrame) |
|
27 NS_QUERYFRAME_HEAD(ViewportFrame) |
|
28 NS_QUERYFRAME_ENTRY(ViewportFrame) |
|
29 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) |
|
30 |
|
31 void |
|
32 ViewportFrame::Init(nsIContent* aContent, |
|
33 nsIFrame* aParent, |
|
34 nsIFrame* aPrevInFlow) |
|
35 { |
|
36 Super::Init(aContent, aParent, aPrevInFlow); |
|
37 |
|
38 nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrame(this); |
|
39 if (parent) { |
|
40 nsFrameState state = parent->GetStateBits(); |
|
41 |
|
42 mState |= state & (NS_FRAME_IN_POPUP); |
|
43 } |
|
44 } |
|
45 |
|
46 nsresult |
|
47 ViewportFrame::SetInitialChildList(ChildListID aListID, |
|
48 nsFrameList& aChildList) |
|
49 { |
|
50 // See which child list to add the frames to |
|
51 #ifdef DEBUG |
|
52 nsFrame::VerifyDirtyBitSet(aChildList); |
|
53 #endif |
|
54 return nsContainerFrame::SetInitialChildList(aListID, aChildList); |
|
55 } |
|
56 |
|
57 void |
|
58 ViewportFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, |
|
59 const nsRect& aDirtyRect, |
|
60 const nsDisplayListSet& aLists) |
|
61 { |
|
62 PROFILER_LABEL("ViewportFrame", "BuildDisplayList"); |
|
63 nsIFrame* kid = mFrames.FirstChild(); |
|
64 if (!kid) |
|
65 return; |
|
66 |
|
67 // make the kid's BorderBackground our own. This ensures that the canvas |
|
68 // frame's background becomes our own background and therefore appears |
|
69 // below negative z-index elements. |
|
70 BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists); |
|
71 } |
|
72 |
|
73 nsresult |
|
74 ViewportFrame::AppendFrames(ChildListID aListID, |
|
75 nsFrameList& aFrameList) |
|
76 { |
|
77 NS_ASSERTION(aListID == kPrincipalList || |
|
78 aListID == GetAbsoluteListID(), "unexpected child list"); |
|
79 NS_ASSERTION(aListID != GetAbsoluteListID() || |
|
80 GetChildList(aListID).IsEmpty(), "Shouldn't have any kids!"); |
|
81 return nsContainerFrame::AppendFrames(aListID, aFrameList); |
|
82 } |
|
83 |
|
84 nsresult |
|
85 ViewportFrame::InsertFrames(ChildListID aListID, |
|
86 nsIFrame* aPrevFrame, |
|
87 nsFrameList& aFrameList) |
|
88 { |
|
89 NS_ASSERTION(aListID == kPrincipalList || |
|
90 aListID == GetAbsoluteListID(), "unexpected child list"); |
|
91 NS_ASSERTION(aListID != GetAbsoluteListID() || |
|
92 GetChildList(aListID).IsEmpty(), "Shouldn't have any kids!"); |
|
93 return nsContainerFrame::InsertFrames(aListID, aPrevFrame, aFrameList); |
|
94 } |
|
95 |
|
96 nsresult |
|
97 ViewportFrame::RemoveFrame(ChildListID aListID, |
|
98 nsIFrame* aOldFrame) |
|
99 { |
|
100 NS_ASSERTION(aListID == kPrincipalList || |
|
101 aListID == GetAbsoluteListID(), "unexpected child list"); |
|
102 return nsContainerFrame::RemoveFrame(aListID, aOldFrame); |
|
103 } |
|
104 |
|
105 /* virtual */ nscoord |
|
106 ViewportFrame::GetMinWidth(nsRenderingContext *aRenderingContext) |
|
107 { |
|
108 nscoord result; |
|
109 DISPLAY_MIN_WIDTH(this, result); |
|
110 if (mFrames.IsEmpty()) |
|
111 result = 0; |
|
112 else |
|
113 result = mFrames.FirstChild()->GetMinWidth(aRenderingContext); |
|
114 |
|
115 return result; |
|
116 } |
|
117 |
|
118 /* virtual */ nscoord |
|
119 ViewportFrame::GetPrefWidth(nsRenderingContext *aRenderingContext) |
|
120 { |
|
121 nscoord result; |
|
122 DISPLAY_PREF_WIDTH(this, result); |
|
123 if (mFrames.IsEmpty()) |
|
124 result = 0; |
|
125 else |
|
126 result = mFrames.FirstChild()->GetPrefWidth(aRenderingContext); |
|
127 |
|
128 return result; |
|
129 } |
|
130 |
|
131 nsPoint |
|
132 ViewportFrame::AdjustReflowStateForScrollbars(nsHTMLReflowState* aReflowState) const |
|
133 { |
|
134 // Get our prinicpal child frame and see if we're scrollable |
|
135 nsIFrame* kidFrame = mFrames.FirstChild(); |
|
136 nsIScrollableFrame* scrollingFrame = do_QueryFrame(kidFrame); |
|
137 |
|
138 if (scrollingFrame) { |
|
139 nsMargin scrollbars = scrollingFrame->GetActualScrollbarSizes(); |
|
140 aReflowState->SetComputedWidth(aReflowState->ComputedWidth() - |
|
141 scrollbars.LeftRight()); |
|
142 aReflowState->AvailableWidth() -= scrollbars.LeftRight(); |
|
143 aReflowState->SetComputedHeightWithoutResettingResizeFlags( |
|
144 aReflowState->ComputedHeight() - scrollbars.TopBottom()); |
|
145 return nsPoint(scrollbars.left, scrollbars.top); |
|
146 } |
|
147 return nsPoint(0, 0); |
|
148 } |
|
149 |
|
150 nsRect |
|
151 ViewportFrame::AdjustReflowStateAsContainingBlock(nsHTMLReflowState* aReflowState) const |
|
152 { |
|
153 #ifdef DEBUG |
|
154 nsPoint offset = |
|
155 #endif |
|
156 AdjustReflowStateForScrollbars(aReflowState); |
|
157 |
|
158 NS_ASSERTION(GetAbsoluteContainingBlock()->GetChildList().IsEmpty() || |
|
159 (offset.x == 0 && offset.y == 0), |
|
160 "We don't handle correct positioning of fixed frames with " |
|
161 "scrollbars in odd positions"); |
|
162 |
|
163 // If a scroll position clamping scroll-port size has been set, layout |
|
164 // fixed position elements to this size instead of the computed size. |
|
165 nsRect rect(0, 0, aReflowState->ComputedWidth(), aReflowState->ComputedHeight()); |
|
166 nsIPresShell* ps = PresContext()->PresShell(); |
|
167 if (ps->IsScrollPositionClampingScrollPortSizeSet()) { |
|
168 rect.SizeTo(ps->GetScrollPositionClampingScrollPortSize()); |
|
169 } |
|
170 |
|
171 // Make sure content document fixed-position margins are respected. |
|
172 rect.Deflate(ps->GetContentDocumentFixedPositionMargins()); |
|
173 return rect; |
|
174 } |
|
175 |
|
176 nsresult |
|
177 ViewportFrame::Reflow(nsPresContext* aPresContext, |
|
178 nsHTMLReflowMetrics& aDesiredSize, |
|
179 const nsHTMLReflowState& aReflowState, |
|
180 nsReflowStatus& aStatus) |
|
181 { |
|
182 DO_GLOBAL_REFLOW_COUNT("ViewportFrame"); |
|
183 DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus); |
|
184 NS_FRAME_TRACE_REFLOW_IN("ViewportFrame::Reflow"); |
|
185 |
|
186 // Initialize OUT parameters |
|
187 aStatus = NS_FRAME_COMPLETE; |
|
188 |
|
189 // Because |Reflow| sets mComputedHeight on the child to |
|
190 // availableHeight. |
|
191 AddStateBits(NS_FRAME_CONTAINS_RELATIVE_HEIGHT); |
|
192 |
|
193 // Set our size up front, since some parts of reflow depend on it |
|
194 // being already set. Note that the computed height may be |
|
195 // unconstrained; that's ok. Consumers should watch out for that. |
|
196 SetSize(nsSize(aReflowState.ComputedWidth(), aReflowState.ComputedHeight())); |
|
197 |
|
198 // Reflow the main content first so that the placeholders of the |
|
199 // fixed-position frames will be in the right places on an initial |
|
200 // reflow. |
|
201 nscoord kidHeight = 0; |
|
202 |
|
203 nsresult rv = NS_OK; |
|
204 |
|
205 if (mFrames.NotEmpty()) { |
|
206 // Deal with a non-incremental reflow or an incremental reflow |
|
207 // targeted at our one-and-only principal child frame. |
|
208 if (aReflowState.ShouldReflowAllKids() || |
|
209 aReflowState.mFlags.mVResize || |
|
210 NS_SUBTREE_DIRTY(mFrames.FirstChild())) { |
|
211 // Reflow our one-and-only principal child frame |
|
212 nsIFrame* kidFrame = mFrames.FirstChild(); |
|
213 nsHTMLReflowMetrics kidDesiredSize(aReflowState); |
|
214 nsSize availableSpace(aReflowState.AvailableWidth(), |
|
215 aReflowState.AvailableHeight()); |
|
216 nsHTMLReflowState kidReflowState(aPresContext, aReflowState, |
|
217 kidFrame, availableSpace); |
|
218 |
|
219 // Reflow the frame |
|
220 kidReflowState.SetComputedHeight(aReflowState.ComputedHeight()); |
|
221 rv = ReflowChild(kidFrame, aPresContext, kidDesiredSize, kidReflowState, |
|
222 0, 0, 0, aStatus); |
|
223 kidHeight = kidDesiredSize.Height(); |
|
224 |
|
225 FinishReflowChild(kidFrame, aPresContext, kidDesiredSize, nullptr, 0, 0, 0); |
|
226 } else { |
|
227 kidHeight = mFrames.FirstChild()->GetSize().height; |
|
228 } |
|
229 } |
|
230 |
|
231 NS_ASSERTION(aReflowState.AvailableWidth() != NS_UNCONSTRAINEDSIZE, |
|
232 "shouldn't happen anymore"); |
|
233 |
|
234 // Return the max size as our desired size |
|
235 aDesiredSize.Width() = aReflowState.AvailableWidth(); |
|
236 // Being flowed initially at an unconstrained height means we should |
|
237 // return our child's intrinsic size. |
|
238 aDesiredSize.Height() = aReflowState.ComputedHeight() != NS_UNCONSTRAINEDSIZE |
|
239 ? aReflowState.ComputedHeight() |
|
240 : kidHeight; |
|
241 aDesiredSize.SetOverflowAreasToDesiredBounds(); |
|
242 |
|
243 if (mFrames.NotEmpty()) { |
|
244 ConsiderChildOverflow(aDesiredSize.mOverflowAreas, mFrames.FirstChild()); |
|
245 } |
|
246 |
|
247 if (IsAbsoluteContainer()) { |
|
248 // Make a copy of the reflow state and change the computed width and height |
|
249 // to reflect the available space for the fixed items |
|
250 nsHTMLReflowState reflowState(aReflowState); |
|
251 |
|
252 if (reflowState.AvailableHeight() == NS_UNCONSTRAINEDSIZE) { |
|
253 // We have an intrinsic-height document with abs-pos/fixed-pos children. |
|
254 // Set the available height and mComputedHeight to our chosen height. |
|
255 reflowState.AvailableHeight() = aDesiredSize.Height(); |
|
256 // Not having border/padding simplifies things |
|
257 NS_ASSERTION(reflowState.ComputedPhysicalBorderPadding() == nsMargin(0,0,0,0), |
|
258 "Viewports can't have border/padding"); |
|
259 reflowState.SetComputedHeight(aDesiredSize.Height()); |
|
260 } |
|
261 |
|
262 nsRect rect = AdjustReflowStateAsContainingBlock(&reflowState); |
|
263 |
|
264 // Just reflow all the fixed-pos frames. |
|
265 rv = GetAbsoluteContainingBlock()->Reflow(this, aPresContext, reflowState, aStatus, |
|
266 rect, |
|
267 false, true, true, // XXX could be optimized |
|
268 &aDesiredSize.mOverflowAreas); |
|
269 } |
|
270 |
|
271 // If we were dirty then do a repaint |
|
272 if (GetStateBits() & NS_FRAME_IS_DIRTY) { |
|
273 InvalidateFrame(); |
|
274 } |
|
275 |
|
276 // Clipping is handled by the document container (e.g., nsSubDocumentFrame), |
|
277 // so we don't need to change our overflow areas. |
|
278 bool overflowChanged = FinishAndStoreOverflow(&aDesiredSize); |
|
279 if (overflowChanged) { |
|
280 // We may need to alert our container to get it to pick up the |
|
281 // overflow change. |
|
282 nsSubDocumentFrame* container = static_cast<nsSubDocumentFrame*> |
|
283 (nsLayoutUtils::GetCrossDocParentFrame(this)); |
|
284 if (container && !container->ShouldClipSubdocument()) { |
|
285 container->PresContext()->PresShell()-> |
|
286 FrameNeedsReflow(container, nsIPresShell::eResize, NS_FRAME_IS_DIRTY); |
|
287 } |
|
288 } |
|
289 |
|
290 NS_FRAME_TRACE_REFLOW_OUT("ViewportFrame::Reflow", aStatus); |
|
291 NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize); |
|
292 return rv; |
|
293 } |
|
294 |
|
295 nsIAtom* |
|
296 ViewportFrame::GetType() const |
|
297 { |
|
298 return nsGkAtoms::viewportFrame; |
|
299 } |
|
300 |
|
301 #ifdef DEBUG_FRAME_DUMP |
|
302 nsresult |
|
303 ViewportFrame::GetFrameName(nsAString& aResult) const |
|
304 { |
|
305 return MakeFrameName(NS_LITERAL_STRING("Viewport"), aResult); |
|
306 } |
|
307 #endif |