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