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