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(nsIFrame)
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, IntrinsicISizeType::MinISize);
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, IntrinsicISizeType::PrefISize);
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. If this is our first
114 // reflow, and our out-of-flow has already received its first reflow (before
115 // us), complain.
116 //
117 // Popups are an exception though, because their position doesn't depend on
118 // the placeholder, so they don't have this requirement (and this condition
119 // doesn't hold anyways because the default popupgroup goes before than the
120 // default tooltip, for example).
121 if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW) &&
122 !HasAnyStateBits(PLACEHOLDER_FOR_POPUP) &&
123 !mOutOfFlowFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
124 // Unfortunately, this can currently happen when the placeholder is in a
125 // later continuation or later IB-split sibling than its out-of-flow (as
126 // is the case in some of our existing unit tests). So for now, in that
127 // case, we'll warn instead of asserting.
128 bool isInContinuationOrIBSplit = false;
129 nsIFrame* ancestor = this;
130 while ((ancestor = ancestor->GetParent())) {
131 if (nsLayoutUtils::GetPrevContinuationOrIBSplitSibling(ancestor)) {
132 isInContinuationOrIBSplit = true;
133 break;
134 }
135 }
136
137 if (isInContinuationOrIBSplit) {
138 NS_WARNING("Out-of-flow frame got reflowed before its placeholder");
139 } else {
140 NS_ERROR("Out-of-flow frame got reflowed before its placeholder");
141 }
142 }
143 #endif
144
145 MarkInReflow();
146 DO_GLOBAL_REFLOW_COUNT("nsPlaceholderFrame");
147 DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
148 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
149 aDesiredSize.ClearSize();
150
151 NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
152 }
153
ChildListIDForOutOfFlow(nsFrameState aPlaceholderState,const nsIFrame * aChild)154 static nsIFrame::ChildListID ChildListIDForOutOfFlow(
155 nsFrameState aPlaceholderState, const nsIFrame* aChild) {
156 if (aPlaceholderState & PLACEHOLDER_FOR_FLOAT) {
157 return nsIFrame::kFloatList;
158 }
159 if (aPlaceholderState & PLACEHOLDER_FOR_POPUP) {
160 return nsIFrame::kPopupList;
161 }
162 if (aPlaceholderState & PLACEHOLDER_FOR_FIXEDPOS) {
163 return nsLayoutUtils::MayBeReallyFixedPos(aChild) ? nsIFrame::kFixedList
164 : nsIFrame::kAbsoluteList;
165 }
166 if (aPlaceholderState & PLACEHOLDER_FOR_ABSPOS) {
167 return nsIFrame::kAbsoluteList;
168 }
169 MOZ_DIAGNOSTIC_ASSERT(false, "unknown list");
170 return nsIFrame::kFloatList;
171 }
172
DestroyFrom(nsIFrame * aDestructRoot,PostDestroyData & aPostDestroyData)173 void nsPlaceholderFrame::DestroyFrom(nsIFrame* aDestructRoot,
174 PostDestroyData& aPostDestroyData) {
175 nsIFrame* oof = mOutOfFlowFrame;
176 if (oof) {
177 mOutOfFlowFrame = nullptr;
178 oof->RemoveProperty(nsIFrame::PlaceholderFrameProperty());
179
180 // If aDestructRoot is not an ancestor of the out-of-flow frame,
181 // then call RemoveFrame on it here.
182 // Also destroy it here if it's a popup frame. (Bug 96291)
183 if (HasAnyStateBits(PLACEHOLDER_FOR_POPUP) ||
184 !nsLayoutUtils::IsProperAncestorFrame(aDestructRoot, oof)) {
185 ChildListID listId = ChildListIDForOutOfFlow(GetStateBits(), oof);
186 nsFrameManager* fm = PresContext()->FrameConstructor();
187 fm->RemoveFrame(listId, oof);
188 }
189 // else oof will be destroyed by its parent
190 }
191
192 nsIFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
193 }
194
195 /* virtual */
CanContinueTextRun() const196 bool nsPlaceholderFrame::CanContinueTextRun() const {
197 if (!mOutOfFlowFrame) {
198 return false;
199 }
200 // first-letter frames can continue text runs, and placeholders for floated
201 // first-letter frames can too
202 return mOutOfFlowFrame->CanContinueTextRun();
203 }
204
GetParentComputedStyleForOutOfFlow(nsIFrame ** aProviderFrame) const205 ComputedStyle* nsPlaceholderFrame::GetParentComputedStyleForOutOfFlow(
206 nsIFrame** aProviderFrame) const {
207 MOZ_ASSERT(GetParent(), "How can we not have a parent here?");
208
209 Element* parentElement =
210 mContent ? mContent->GetFlattenedTreeParentElement() : nullptr;
211 // See the similar code in nsIFrame::DoGetParentComputedStyle.
212 if (parentElement && MOZ_LIKELY(parentElement->HasServoData()) &&
213 Servo_Element_IsDisplayContents(parentElement)) {
214 RefPtr<ComputedStyle> style =
215 ServoStyleSet::ResolveServoStyle(*parentElement);
216 *aProviderFrame = nullptr;
217 // See the comment in GetParentComputedStyle to see why returning this as a
218 // weak ref is fine.
219 return style;
220 }
221
222 return GetLayoutParentStyleForOutOfFlow(aProviderFrame);
223 }
224
GetLayoutParentStyleForOutOfFlow(nsIFrame ** aProviderFrame) const225 ComputedStyle* nsPlaceholderFrame::GetLayoutParentStyleForOutOfFlow(
226 nsIFrame** aProviderFrame) const {
227 // Lie about our pseudo so we can step out of all anon boxes and
228 // pseudo-elements. The other option would be to reimplement the
229 // {ib} split gunk here.
230 //
231 // See the hack in CorrectStyleParentFrame for why we pass `MAX`.
232 *aProviderFrame = CorrectStyleParentFrame(GetParent(), PseudoStyleType::MAX);
233 return *aProviderFrame ? (*aProviderFrame)->Style() : nullptr;
234 }
235
236 #ifdef DEBUG
PaintDebugPlaceholder(nsIFrame * aFrame,DrawTarget * aDrawTarget,const nsRect & aDirtyRect,nsPoint aPt)237 static void PaintDebugPlaceholder(nsIFrame* aFrame, DrawTarget* aDrawTarget,
238 const nsRect& aDirtyRect, nsPoint aPt) {
239 ColorPattern cyan(ToDeviceColor(sRGBColor(0.f, 1.f, 1.f, 1.f)));
240 int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
241
242 nscoord x = nsPresContext::CSSPixelsToAppUnits(-5);
243 nsRect r(aPt.x + x, aPt.y, nsPresContext::CSSPixelsToAppUnits(13),
244 nsPresContext::CSSPixelsToAppUnits(3));
245 aDrawTarget->FillRect(NSRectToRect(r, appUnitsPerDevPixel), cyan);
246
247 nscoord y = nsPresContext::CSSPixelsToAppUnits(-10);
248 r = nsRect(aPt.x, aPt.y + y, nsPresContext::CSSPixelsToAppUnits(3),
249 nsPresContext::CSSPixelsToAppUnits(10));
250 aDrawTarget->FillRect(NSRectToRect(r, appUnitsPerDevPixel), cyan);
251 }
252 #endif // DEBUG
253
254 #if defined(DEBUG) || (defined(MOZ_REFLOW_PERF_DSP) && defined(MOZ_REFLOW_PERF))
255
BuildDisplayList(nsDisplayListBuilder * aBuilder,const nsDisplayListSet & aLists)256 void nsPlaceholderFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
257 const nsDisplayListSet& aLists) {
258 DO_GLOBAL_REFLOW_COUNT_DSP("nsPlaceholderFrame");
259
260 # ifdef DEBUG
261 if (GetShowFrameBorders()) {
262 aLists.Outlines()->AppendNewToTop<nsDisplayGeneric>(
263 aBuilder, this, PaintDebugPlaceholder, "DebugPlaceholder",
264 DisplayItemType::TYPE_DEBUG_PLACEHOLDER);
265 }
266 # endif
267 }
268 #endif // DEBUG || (MOZ_REFLOW_PERF_DSP && MOZ_REFLOW_PERF)
269
270 #ifdef DEBUG_FRAME_DUMP
GetFrameName(nsAString & aResult) const271 nsresult nsPlaceholderFrame::GetFrameName(nsAString& aResult) const {
272 return MakeFrameName(u"Placeholder"_ns, aResult);
273 }
274
List(FILE * out,const char * aPrefix,ListFlags aFlags) const275 void nsPlaceholderFrame::List(FILE* out, const char* aPrefix,
276 ListFlags aFlags) const {
277 nsCString str;
278 ListGeneric(str, aPrefix, aFlags);
279
280 if (mOutOfFlowFrame) {
281 str += " outOfFlowFrame=";
282 str += mOutOfFlowFrame->ListTag();
283 }
284 fprintf_stderr(out, "%s\n", str.get());
285 }
286 #endif
287