|
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 #include "nsProgressFrame.h" |
|
7 |
|
8 #include "nsIContent.h" |
|
9 #include "nsPresContext.h" |
|
10 #include "nsGkAtoms.h" |
|
11 #include "nsNameSpaceManager.h" |
|
12 #include "nsIDocument.h" |
|
13 #include "nsIPresShell.h" |
|
14 #include "nsNodeInfoManager.h" |
|
15 #include "nsINodeInfo.h" |
|
16 #include "nsContentCreatorFunctions.h" |
|
17 #include "nsContentUtils.h" |
|
18 #include "nsFormControlFrame.h" |
|
19 #include "nsFontMetrics.h" |
|
20 #include "mozilla/dom/Element.h" |
|
21 #include "mozilla/dom/HTMLProgressElement.h" |
|
22 #include "nsContentList.h" |
|
23 #include "nsStyleSet.h" |
|
24 #include "nsThemeConstants.h" |
|
25 #include <algorithm> |
|
26 |
|
27 using namespace mozilla::dom; |
|
28 |
|
29 nsIFrame* |
|
30 NS_NewProgressFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) |
|
31 { |
|
32 return new (aPresShell) nsProgressFrame(aContext); |
|
33 } |
|
34 |
|
35 NS_IMPL_FRAMEARENA_HELPERS(nsProgressFrame) |
|
36 |
|
37 nsProgressFrame::nsProgressFrame(nsStyleContext* aContext) |
|
38 : nsContainerFrame(aContext) |
|
39 , mBarDiv(nullptr) |
|
40 { |
|
41 } |
|
42 |
|
43 nsProgressFrame::~nsProgressFrame() |
|
44 { |
|
45 } |
|
46 |
|
47 void |
|
48 nsProgressFrame::DestroyFrom(nsIFrame* aDestructRoot) |
|
49 { |
|
50 NS_ASSERTION(!GetPrevContinuation(), |
|
51 "nsProgressFrame should not have continuations; if it does we " |
|
52 "need to call RegUnregAccessKey only for the first."); |
|
53 nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false); |
|
54 nsContentUtils::DestroyAnonymousContent(&mBarDiv); |
|
55 nsContainerFrame::DestroyFrom(aDestructRoot); |
|
56 } |
|
57 |
|
58 nsresult |
|
59 nsProgressFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements) |
|
60 { |
|
61 // Create the progress bar div. |
|
62 nsCOMPtr<nsIDocument> doc = mContent->GetDocument(); |
|
63 mBarDiv = doc->CreateHTMLElement(nsGkAtoms::div); |
|
64 |
|
65 // Associate ::-moz-progress-bar pseudo-element to the anonymous child. |
|
66 nsCSSPseudoElements::Type pseudoType = nsCSSPseudoElements::ePseudo_mozProgressBar; |
|
67 nsRefPtr<nsStyleContext> newStyleContext = PresContext()->StyleSet()-> |
|
68 ResolvePseudoElementStyle(mContent->AsElement(), pseudoType, |
|
69 StyleContext(), mBarDiv->AsElement()); |
|
70 |
|
71 if (!aElements.AppendElement(ContentInfo(mBarDiv, newStyleContext))) { |
|
72 return NS_ERROR_OUT_OF_MEMORY; |
|
73 } |
|
74 |
|
75 return NS_OK; |
|
76 } |
|
77 |
|
78 void |
|
79 nsProgressFrame::AppendAnonymousContentTo(nsBaseContentList& aElements, |
|
80 uint32_t aFilter) |
|
81 { |
|
82 aElements.MaybeAppendElement(mBarDiv); |
|
83 } |
|
84 |
|
85 NS_QUERYFRAME_HEAD(nsProgressFrame) |
|
86 NS_QUERYFRAME_ENTRY(nsProgressFrame) |
|
87 NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator) |
|
88 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) |
|
89 |
|
90 |
|
91 void |
|
92 nsProgressFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, |
|
93 const nsRect& aDirtyRect, |
|
94 const nsDisplayListSet& aLists) |
|
95 { |
|
96 BuildDisplayListForInline(aBuilder, aDirtyRect, aLists); |
|
97 } |
|
98 |
|
99 nsresult nsProgressFrame::Reflow(nsPresContext* aPresContext, |
|
100 nsHTMLReflowMetrics& aDesiredSize, |
|
101 const nsHTMLReflowState& aReflowState, |
|
102 nsReflowStatus& aStatus) |
|
103 { |
|
104 DO_GLOBAL_REFLOW_COUNT("nsProgressFrame"); |
|
105 DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus); |
|
106 |
|
107 NS_ASSERTION(mBarDiv, "Progress bar div must exist!"); |
|
108 NS_ASSERTION(!GetPrevContinuation(), |
|
109 "nsProgressFrame should not have continuations; if it does we " |
|
110 "need to call RegUnregAccessKey only for the first."); |
|
111 |
|
112 if (mState & NS_FRAME_FIRST_REFLOW) { |
|
113 nsFormControlFrame::RegUnRegAccessKey(this, true); |
|
114 } |
|
115 |
|
116 nsIFrame* barFrame = mBarDiv->GetPrimaryFrame(); |
|
117 NS_ASSERTION(barFrame, "The progress frame should have a child with a frame!"); |
|
118 |
|
119 ReflowBarFrame(barFrame, aPresContext, aReflowState, aStatus); |
|
120 |
|
121 aDesiredSize.Width() = aReflowState.ComputedWidth() + |
|
122 aReflowState.ComputedPhysicalBorderPadding().LeftRight(); |
|
123 aDesiredSize.Height() = aReflowState.ComputedHeight() + |
|
124 aReflowState.ComputedPhysicalBorderPadding().TopBottom(); |
|
125 |
|
126 aDesiredSize.SetOverflowAreasToDesiredBounds(); |
|
127 ConsiderChildOverflow(aDesiredSize.mOverflowAreas, barFrame); |
|
128 FinishAndStoreOverflow(&aDesiredSize); |
|
129 |
|
130 aStatus = NS_FRAME_COMPLETE; |
|
131 |
|
132 NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize); |
|
133 |
|
134 return NS_OK; |
|
135 } |
|
136 |
|
137 void |
|
138 nsProgressFrame::ReflowBarFrame(nsIFrame* aBarFrame, |
|
139 nsPresContext* aPresContext, |
|
140 const nsHTMLReflowState& aReflowState, |
|
141 nsReflowStatus& aStatus) |
|
142 { |
|
143 bool vertical = StyleDisplay()->mOrient == NS_STYLE_ORIENT_VERTICAL; |
|
144 nsHTMLReflowState reflowState(aPresContext, aReflowState, aBarFrame, |
|
145 nsSize(aReflowState.ComputedWidth(), |
|
146 NS_UNCONSTRAINEDSIZE)); |
|
147 nscoord size = vertical ? aReflowState.ComputedHeight() |
|
148 : aReflowState.ComputedWidth(); |
|
149 nscoord xoffset = aReflowState.ComputedPhysicalBorderPadding().left; |
|
150 nscoord yoffset = aReflowState.ComputedPhysicalBorderPadding().top; |
|
151 |
|
152 double position = static_cast<HTMLProgressElement*>(mContent)->Position(); |
|
153 |
|
154 // Force the bar's size to match the current progress. |
|
155 // When indeterminate, the progress' size will be 100%. |
|
156 if (position >= 0.0) { |
|
157 size *= position; |
|
158 } |
|
159 |
|
160 if (!vertical && StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) { |
|
161 xoffset += aReflowState.ComputedWidth() - size; |
|
162 } |
|
163 |
|
164 // The bar size is fixed in these cases: |
|
165 // - the progress position is determined: the bar size is fixed according |
|
166 // to it's value. |
|
167 // - the progress position is indeterminate and the bar appearance should be |
|
168 // shown as native: the bar size is forced to 100%. |
|
169 // Otherwise (when the progress is indeterminate and the bar appearance isn't |
|
170 // native), the bar size isn't fixed and can be set by the author. |
|
171 if (position != -1 || ShouldUseNativeStyle()) { |
|
172 if (vertical) { |
|
173 // We want the bar to begin at the bottom. |
|
174 yoffset += aReflowState.ComputedHeight() - size; |
|
175 |
|
176 size -= reflowState.ComputedPhysicalMargin().TopBottom() + |
|
177 reflowState.ComputedPhysicalBorderPadding().TopBottom(); |
|
178 size = std::max(size, 0); |
|
179 reflowState.SetComputedHeight(size); |
|
180 } else { |
|
181 size -= reflowState.ComputedPhysicalMargin().LeftRight() + |
|
182 reflowState.ComputedPhysicalBorderPadding().LeftRight(); |
|
183 size = std::max(size, 0); |
|
184 reflowState.SetComputedWidth(size); |
|
185 } |
|
186 } else if (vertical) { |
|
187 // For vertical progress bars, we need to position the bar specificly when |
|
188 // the width isn't constrained (position == -1 and !ShouldUseNativeStyle()) |
|
189 // because aReflowState.ComputedHeight() - size == 0. |
|
190 yoffset += aReflowState.ComputedHeight() - reflowState.ComputedHeight(); |
|
191 } |
|
192 |
|
193 xoffset += reflowState.ComputedPhysicalMargin().left; |
|
194 yoffset += reflowState.ComputedPhysicalMargin().top; |
|
195 |
|
196 nsHTMLReflowMetrics barDesiredSize(aReflowState); |
|
197 ReflowChild(aBarFrame, aPresContext, barDesiredSize, reflowState, xoffset, |
|
198 yoffset, 0, aStatus); |
|
199 FinishReflowChild(aBarFrame, aPresContext, barDesiredSize, &reflowState, |
|
200 xoffset, yoffset, 0); |
|
201 } |
|
202 |
|
203 nsresult |
|
204 nsProgressFrame::AttributeChanged(int32_t aNameSpaceID, |
|
205 nsIAtom* aAttribute, |
|
206 int32_t aModType) |
|
207 { |
|
208 NS_ASSERTION(mBarDiv, "Progress bar div must exist!"); |
|
209 |
|
210 if (aNameSpaceID == kNameSpaceID_None && |
|
211 (aAttribute == nsGkAtoms::value || aAttribute == nsGkAtoms::max)) { |
|
212 nsIFrame* barFrame = mBarDiv->GetPrimaryFrame(); |
|
213 NS_ASSERTION(barFrame, "The progress frame should have a child with a frame!"); |
|
214 PresContext()->PresShell()->FrameNeedsReflow(barFrame, nsIPresShell::eResize, |
|
215 NS_FRAME_IS_DIRTY); |
|
216 InvalidateFrame(); |
|
217 } |
|
218 |
|
219 return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); |
|
220 } |
|
221 |
|
222 nsSize |
|
223 nsProgressFrame::ComputeAutoSize(nsRenderingContext *aRenderingContext, |
|
224 nsSize aCBSize, nscoord aAvailableWidth, |
|
225 nsSize aMargin, nsSize aBorder, |
|
226 nsSize aPadding, bool aShrinkWrap) |
|
227 { |
|
228 nsSize autoSize; |
|
229 autoSize.height = autoSize.width = |
|
230 NSToCoordRound(StyleFont()->mFont.size * |
|
231 nsLayoutUtils::FontSizeInflationFor(this)); // 1em |
|
232 |
|
233 if (StyleDisplay()->mOrient == NS_STYLE_ORIENT_VERTICAL) { |
|
234 autoSize.height *= 10; // 10em |
|
235 } else { |
|
236 autoSize.width *= 10; // 10em |
|
237 } |
|
238 |
|
239 return autoSize; |
|
240 } |
|
241 |
|
242 nscoord |
|
243 nsProgressFrame::GetMinWidth(nsRenderingContext *aRenderingContext) |
|
244 { |
|
245 nsRefPtr<nsFontMetrics> fontMet; |
|
246 NS_ENSURE_SUCCESS( |
|
247 nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fontMet)), 0); |
|
248 |
|
249 nscoord minWidth = fontMet->Font().size; // 1em |
|
250 |
|
251 if (StyleDisplay()->mOrient == NS_STYLE_ORIENT_AUTO || |
|
252 StyleDisplay()->mOrient == NS_STYLE_ORIENT_HORIZONTAL) { |
|
253 // The orientation is horizontal |
|
254 minWidth *= 10; // 10em |
|
255 } |
|
256 |
|
257 return minWidth; |
|
258 } |
|
259 |
|
260 nscoord |
|
261 nsProgressFrame::GetPrefWidth(nsRenderingContext *aRenderingContext) |
|
262 { |
|
263 return GetMinWidth(aRenderingContext); |
|
264 } |
|
265 |
|
266 bool |
|
267 nsProgressFrame::ShouldUseNativeStyle() const |
|
268 { |
|
269 // Use the native style if these conditions are satisfied: |
|
270 // - both frames use the native appearance; |
|
271 // - neither frame has author specified rules setting the border or the |
|
272 // background. |
|
273 return StyleDisplay()->mAppearance == NS_THEME_PROGRESSBAR && |
|
274 mBarDiv->GetPrimaryFrame()->StyleDisplay()->mAppearance == NS_THEME_PROGRESSBAR_CHUNK && |
|
275 !PresContext()->HasAuthorSpecifiedRules(const_cast<nsProgressFrame*>(this), |
|
276 NS_AUTHOR_SPECIFIED_BORDER | NS_AUTHOR_SPECIFIED_BACKGROUND) && |
|
277 !PresContext()->HasAuthorSpecifiedRules(mBarDiv->GetPrimaryFrame(), |
|
278 NS_AUTHOR_SPECIFIED_BORDER | NS_AUTHOR_SPECIFIED_BACKGROUND); |
|
279 } |
|
280 |
|
281 Element* |
|
282 nsProgressFrame::GetPseudoElement(nsCSSPseudoElements::Type aType) |
|
283 { |
|
284 if (aType == nsCSSPseudoElements::ePseudo_mozProgressBar) { |
|
285 return mBarDiv; |
|
286 } |
|
287 |
|
288 return nsContainerFrame::GetPseudoElement(aType); |
|
289 } |