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 the point that anchors out-of-flow rendering
9 * objects such as floats and absolutely positioned elements
10 */
11
12 #include "nsPlaceholderFrame.h"
13
14 #include "gfxContext.h"
15 #include "gfxUtils.h"
16 #include "mozilla/dom/ElementInlines.h"
17 #include "mozilla/gfx/2D.h"
18 #include "mozilla/PresShell.h"
19 #include "mozilla/PresShellInlines.h"
20 #include "mozilla/ServoStyleSetInlines.h"
21 #include "nsCSSFrameConstructor.h"
22 #include "nsDisplayList.h"
23 #include "nsLayoutUtils.h"
24 #include "nsPresContext.h"
25 #include "nsIFrameInlines.h"
26 #include "nsIContentInlines.h"
27
28 using namespace mozilla;
29 using namespace mozilla::dom;
30 using namespace mozilla::gfx;
31
NS_NewPlaceholderFrame(PresShell * aPresShell,ComputedStyle * aStyle,nsFrameState aTypeBits)32 nsPlaceholderFrame* NS_NewPlaceholderFrame(PresShell* aPresShell,
33 ComputedStyle* aStyle,
34 nsFrameState aTypeBits) {
35 return new (aPresShell)
36 nsPlaceholderFrame(aStyle, aPresShell->GetPresContext(), aTypeBits);
37 }
38
39 NS_IMPL_FRAMEARENA_HELPERS(nsPlaceholderFrame)
40
41 #ifdef DEBUG
NS_QUERYFRAME_HEAD(nsPlaceholderFrame)42 NS_QUERYFRAME_HEAD(nsPlaceholderFrame)
43 NS_QUERYFRAME_ENTRY(nsPlaceholderFrame)
44 NS_QUERYFRAME_TAIL_INHERITING(nsFrame)
45 #endif
46
47 /* virtual */
48 nsSize nsPlaceholderFrame::GetXULMinSize(nsBoxLayoutState& aBoxLayoutState) {
49 nsSize size(0, 0);
50 DISPLAY_MIN_SIZE(this, size);
51 return size;
52 }
53
54 /* virtual */
GetXULPrefSize(nsBoxLayoutState & aBoxLayoutState)55 nsSize nsPlaceholderFrame::GetXULPrefSize(nsBoxLayoutState& aBoxLayoutState) {
56 nsSize size(0, 0);
57 DISPLAY_PREF_SIZE(this, size);
58 return size;
59 }
60
61 /* virtual */
GetXULMaxSize(nsBoxLayoutState & aBoxLayoutState)62 nsSize nsPlaceholderFrame::GetXULMaxSize(nsBoxLayoutState& aBoxLayoutState) {
63 nsSize size(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
64 DISPLAY_MAX_SIZE(this, size);
65 return size;
66 }
67
68 /* virtual */
AddInlineMinISize(gfxContext * aRenderingContext,nsIFrame::InlineMinISizeData * aData)69 void nsPlaceholderFrame::AddInlineMinISize(
70 gfxContext* aRenderingContext, nsIFrame::InlineMinISizeData* aData) {
71 // Override AddInlineMinWith so that *nothing* happens. In
72 // particular, we don't want to zero out |aData->mTrailingWhitespace|,
73 // since nsLineLayout skips placeholders when trimming trailing
74 // whitespace, and we don't want to set aData->mSkipWhitespace to
75 // false.
76
77 // ...but push floats onto the list
78 if (mOutOfFlowFrame->IsFloating()) {
79 nscoord floatWidth = nsLayoutUtils::IntrinsicForContainer(
80 aRenderingContext, mOutOfFlowFrame, nsLayoutUtils::MIN_ISIZE);
81 aData->mFloats.AppendElement(
82 InlineIntrinsicISizeData::FloatInfo(mOutOfFlowFrame, floatWidth));
83 }
84 }
85
86 /* virtual */
AddInlinePrefISize(gfxContext * aRenderingContext,nsIFrame::InlinePrefISizeData * aData)87 void nsPlaceholderFrame::AddInlinePrefISize(
88 gfxContext* aRenderingContext, nsIFrame::InlinePrefISizeData* aData) {
89 // Override AddInlinePrefWith so that *nothing* happens. In
90 // particular, we don't want to zero out |aData->mTrailingWhitespace|,
91 // since nsLineLayout skips placeholders when trimming trailing
92 // whitespace, and we don't want to set aData->mSkipWhitespace to
93 // false.
94
95 // ...but push floats onto the list
96 if (mOutOfFlowFrame->IsFloating()) {
97 nscoord floatWidth = nsLayoutUtils::IntrinsicForContainer(
98 aRenderingContext, mOutOfFlowFrame, nsLayoutUtils::PREF_ISIZE);
99 aData->mFloats.AppendElement(
100 InlineIntrinsicISizeData::FloatInfo(mOutOfFlowFrame, floatWidth));
101 }
102 }
103
Reflow(nsPresContext * aPresContext,ReflowOutput & aDesiredSize,const ReflowInput & aReflowInput,nsReflowStatus & aStatus)104 void nsPlaceholderFrame::Reflow(nsPresContext* aPresContext,
105 ReflowOutput& aDesiredSize,
106 const ReflowInput& aReflowInput,
107 nsReflowStatus& aStatus) {
108 // NOTE that the ReflowInput passed to this method is not fully initialized,
109 // on the grounds that reflowing a placeholder is a rather trivial operation.
110 // (See bug 1367711.)
111
112 #ifdef DEBUG
113 // We should be getting reflowed before our out-of-flow.
114 // If this is our first reflow, and our out-of-flow has already received its
115 // first reflow (before us), complain.
116 if ((GetStateBits() & NS_FRAME_FIRST_REFLOW) &&
117 !(mOutOfFlowFrame->GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
118 // Unfortunately, this can currently happen when the placeholder is in a
119 // later continuation or later IB-split sibling than its out-of-flow (as
120 // is the case in some of our existing unit tests). So for now, in that
121 // case, we'll warn instead of asserting.
122 bool isInContinuationOrIBSplit = false;
123 nsIFrame* ancestor = this;
124 while ((ancestor = ancestor->GetParent())) {
125 if (nsLayoutUtils::GetPrevContinuationOrIBSplitSibling(ancestor)) {
126 isInContinuationOrIBSplit = true;
127 break;
128 }
129 }
130
131 if (isInContinuationOrIBSplit) {
132 NS_WARNING("Out-of-flow frame got reflowed before its placeholder");
133 } else {
134 NS_ERROR("Out-of-flow frame got reflowed before its placeholder");
135 }
136 }
137 #endif
138
139 MarkInReflow();
140 DO_GLOBAL_REFLOW_COUNT("nsPlaceholderFrame");
141 DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
142 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
143 aDesiredSize.ClearSize();
144
145 NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
146 }
147
ChildListIDForOutOfFlow(nsFrameState aPlaceholderState,const nsIFrame * aChild)148 static nsIFrame::ChildListID ChildListIDForOutOfFlow(
149 nsFrameState aPlaceholderState, const nsIFrame* aChild) {
150 if (aPlaceholderState & PLACEHOLDER_FOR_FLOAT) {
151 return nsIFrame::kFloatList;
152 }
153 if (aPlaceholderState & PLACEHOLDER_FOR_POPUP) {
154 return nsIFrame::kPopupList;
155 }
156 if (aPlaceholderState & PLACEHOLDER_FOR_FIXEDPOS) {
157 return nsLayoutUtils::MayBeReallyFixedPos(aChild) ? nsIFrame::kFixedList
158 : nsIFrame::kAbsoluteList;
159 }
160 if (aPlaceholderState & PLACEHOLDER_FOR_ABSPOS) {
161 return nsIFrame::kAbsoluteList;
162 }
163 MOZ_DIAGNOSTIC_ASSERT(false, "unknown list");
164 return nsIFrame::kFloatList;
165 }
166
DestroyFrom(nsIFrame * aDestructRoot,PostDestroyData & aPostDestroyData)167 void nsPlaceholderFrame::DestroyFrom(nsIFrame* aDestructRoot,
168 PostDestroyData& aPostDestroyData) {
169 nsIFrame* oof = mOutOfFlowFrame;
170 if (oof) {
171 mOutOfFlowFrame = nullptr;
172 oof->RemoveProperty(nsIFrame::PlaceholderFrameProperty());
173
174 // If aDestructRoot is not an ancestor of the out-of-flow frame,
175 // then call RemoveFrame on it here.
176 // Also destroy it here if it's a popup frame. (Bug 96291)
177 if ((GetStateBits() & PLACEHOLDER_FOR_POPUP) ||
178 !nsLayoutUtils::IsProperAncestorFrame(aDestructRoot, oof)) {
179 ChildListID listId = ChildListIDForOutOfFlow(GetStateBits(), oof);
180 nsFrameManager* fm = PresContext()->FrameConstructor();
181 fm->RemoveFrame(listId, oof);
182 }
183 // else oof will be destroyed by its parent
184 }
185
186 nsFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
187 }
188
189 /* virtual */
CanContinueTextRun() const190 bool nsPlaceholderFrame::CanContinueTextRun() const {
191 if (!mOutOfFlowFrame) {
192 return false;
193 }
194 // first-letter frames can continue text runs, and placeholders for floated
195 // first-letter frames can too
196 return mOutOfFlowFrame->CanContinueTextRun();
197 }
198
GetParentComputedStyleForOutOfFlow(nsIFrame ** aProviderFrame) const199 ComputedStyle* nsPlaceholderFrame::GetParentComputedStyleForOutOfFlow(
200 nsIFrame** aProviderFrame) const {
201 MOZ_ASSERT(GetParent(), "How can we not have a parent here?");
202
203 Element* parentElement =
204 mContent ? mContent->GetFlattenedTreeParentElement() : nullptr;
205 if (parentElement && Servo_Element_IsDisplayContents(parentElement)) {
206 RefPtr<ComputedStyle> style =
207 ServoStyleSet::ResolveServoStyle(*parentElement);
208 *aProviderFrame = nullptr;
209 // See the comment in GetParentComputedStyle to see why returning this as a
210 // weak ref is fine.
211 return style;
212 }
213
214 return GetLayoutParentStyleForOutOfFlow(aProviderFrame);
215 }
216
GetLayoutParentStyleForOutOfFlow(nsIFrame ** aProviderFrame) const217 ComputedStyle* nsPlaceholderFrame::GetLayoutParentStyleForOutOfFlow(
218 nsIFrame** aProviderFrame) const {
219 // Lie about our pseudo so we can step out of all anon boxes and
220 // pseudo-elements. The other option would be to reimplement the
221 // {ib} split gunk here.
222 //
223 // See the hack in CorrectStyleParentFrame for why we pass `MAX`.
224 *aProviderFrame = CorrectStyleParentFrame(GetParent(), PseudoStyleType::MAX);
225 return *aProviderFrame ? (*aProviderFrame)->Style() : nullptr;
226 }
227
228 #ifdef DEBUG
PaintDebugPlaceholder(nsIFrame * aFrame,DrawTarget * aDrawTarget,const nsRect & aDirtyRect,nsPoint aPt)229 static void PaintDebugPlaceholder(nsIFrame* aFrame, DrawTarget* aDrawTarget,
230 const nsRect& aDirtyRect, nsPoint aPt) {
231 ColorPattern cyan(ToDeviceColor(sRGBColor(0.f, 1.f, 1.f, 1.f)));
232 int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
233
234 nscoord x = nsPresContext::CSSPixelsToAppUnits(-5);
235 nsRect r(aPt.x + x, aPt.y, nsPresContext::CSSPixelsToAppUnits(13),
236 nsPresContext::CSSPixelsToAppUnits(3));
237 aDrawTarget->FillRect(NSRectToRect(r, appUnitsPerDevPixel), cyan);
238
239 nscoord y = nsPresContext::CSSPixelsToAppUnits(-10);
240 r = nsRect(aPt.x, aPt.y + y, nsPresContext::CSSPixelsToAppUnits(3),
241 nsPresContext::CSSPixelsToAppUnits(10));
242 aDrawTarget->FillRect(NSRectToRect(r, appUnitsPerDevPixel), cyan);
243 }
244 #endif // DEBUG
245
246 #if defined(DEBUG) || (defined(MOZ_REFLOW_PERF_DSP) && defined(MOZ_REFLOW_PERF))
247
BuildDisplayList(nsDisplayListBuilder * aBuilder,const nsDisplayListSet & aLists)248 void nsPlaceholderFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
249 const nsDisplayListSet& aLists) {
250 DO_GLOBAL_REFLOW_COUNT_DSP("nsPlaceholderFrame");
251
252 # ifdef DEBUG
253 if (GetShowFrameBorders()) {
254 aLists.Outlines()->AppendNewToTop<nsDisplayGeneric>(
255 aBuilder, this, PaintDebugPlaceholder, "DebugPlaceholder",
256 DisplayItemType::TYPE_DEBUG_PLACEHOLDER);
257 }
258 # endif
259 }
260 #endif // DEBUG || (MOZ_REFLOW_PERF_DSP && MOZ_REFLOW_PERF)
261
262 #ifdef DEBUG_FRAME_DUMP
GetFrameName(nsAString & aResult) const263 nsresult nsPlaceholderFrame::GetFrameName(nsAString& aResult) const {
264 return MakeFrameName(NS_LITERAL_STRING("Placeholder"), aResult);
265 }
266
List(FILE * out,const char * aPrefix,ListFlags aFlags) const267 void nsPlaceholderFrame::List(FILE* out, const char* aPrefix,
268 ListFlags aFlags) const {
269 nsCString str;
270 ListGeneric(str, aPrefix, aFlags);
271
272 if (mOutOfFlowFrame) {
273 str += " outOfFlowFrame=";
274 str += mOutOfFlowFrame->ListTag();
275 }
276 fprintf_stderr(out, "%s\n", str.get());
277 }
278 #endif
279