/src/mozilla-central/layout/svg/nsSVGOuterSVGFrame.cpp
Line | Count | Source (jump to first uncovered line) |
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 | | // Main header first: |
8 | | #include "nsSVGOuterSVGFrame.h" |
9 | | |
10 | | // Keep others in (case-insensitive) order: |
11 | | #include "gfxContext.h" |
12 | | #include "nsDisplayList.h" |
13 | | #include "nsIDocument.h" |
14 | | #include "nsIInterfaceRequestorUtils.h" |
15 | | #include "nsIObjectLoadingContent.h" |
16 | | #include "nsSVGIntegrationUtils.h" |
17 | | #include "nsSVGForeignObjectFrame.h" |
18 | | #include "mozilla/dom/Element.h" |
19 | | #include "mozilla/dom/SVGSVGElement.h" |
20 | | #include "mozilla/dom/SVGViewElement.h" |
21 | | #include "nsSubDocumentFrame.h" |
22 | | |
23 | | using namespace mozilla; |
24 | | using namespace mozilla::dom; |
25 | | using namespace mozilla::image; |
26 | | |
27 | | //---------------------------------------------------------------------- |
28 | | // Implementation helpers |
29 | | |
30 | | void |
31 | | nsSVGOuterSVGFrame::RegisterForeignObject(nsSVGForeignObjectFrame* aFrame) |
32 | 0 | { |
33 | 0 | NS_ASSERTION(aFrame, "Who on earth is calling us?!"); |
34 | 0 |
|
35 | 0 | if (!mForeignObjectHash) { |
36 | 0 | mForeignObjectHash = new nsTHashtable<nsPtrHashKey<nsSVGForeignObjectFrame> >(); |
37 | 0 | } |
38 | 0 |
|
39 | 0 | NS_ASSERTION(!mForeignObjectHash->GetEntry(aFrame), |
40 | 0 | "nsSVGForeignObjectFrame already registered!"); |
41 | 0 |
|
42 | 0 | mForeignObjectHash->PutEntry(aFrame); |
43 | 0 |
|
44 | 0 | NS_ASSERTION(mForeignObjectHash->GetEntry(aFrame), |
45 | 0 | "Failed to register nsSVGForeignObjectFrame!"); |
46 | 0 | } |
47 | | |
48 | | void |
49 | | nsSVGOuterSVGFrame::UnregisterForeignObject(nsSVGForeignObjectFrame* aFrame) |
50 | 0 | { |
51 | 0 | NS_ASSERTION(aFrame, "Who on earth is calling us?!"); |
52 | 0 | NS_ASSERTION(mForeignObjectHash && mForeignObjectHash->GetEntry(aFrame), |
53 | 0 | "nsSVGForeignObjectFrame not in registry!"); |
54 | 0 | return mForeignObjectHash->RemoveEntry(aFrame); |
55 | 0 | } |
56 | | |
57 | | //---------------------------------------------------------------------- |
58 | | // Implementation |
59 | | |
60 | | nsContainerFrame* |
61 | | NS_NewSVGOuterSVGFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle) |
62 | 0 | { |
63 | 0 | return new (aPresShell) nsSVGOuterSVGFrame(aStyle); |
64 | 0 | } |
65 | | |
66 | | NS_IMPL_FRAMEARENA_HELPERS(nsSVGOuterSVGFrame) |
67 | | |
68 | | nsSVGOuterSVGFrame::nsSVGOuterSVGFrame(ComputedStyle* aStyle) |
69 | | : nsSVGDisplayContainerFrame(aStyle, kClassID) |
70 | | , mCallingReflowSVG(false) |
71 | | , mFullZoom(PresContext()->GetFullZoom()) |
72 | | , mViewportInitialized(false) |
73 | | , mIsRootContent(false) |
74 | 0 | { |
75 | 0 | // Outer-<svg> has CSS layout, so remove this bit: |
76 | 0 | RemoveStateBits(NS_FRAME_SVG_LAYOUT); |
77 | 0 | } |
78 | | |
79 | | // helper |
80 | | static inline bool |
81 | | DependsOnIntrinsicSize(const nsIFrame* aEmbeddingFrame) |
82 | 0 | { |
83 | 0 | const nsStylePosition *pos = aEmbeddingFrame->StylePosition(); |
84 | 0 | const nsStyleCoord &width = pos->mWidth; |
85 | 0 | const nsStyleCoord &height = pos->mHeight; |
86 | 0 |
|
87 | 0 | // XXX it would be nice to know if the size of aEmbeddingFrame's containing |
88 | 0 | // block depends on aEmbeddingFrame, then we'd know if we can return false |
89 | 0 | // for eStyleUnit_Percent too. |
90 | 0 | return !width.ConvertsToLength() || |
91 | 0 | !height.ConvertsToLength(); |
92 | 0 | } |
93 | | |
94 | | void |
95 | | nsSVGOuterSVGFrame::Init(nsIContent* aContent, |
96 | | nsContainerFrame* aParent, |
97 | | nsIFrame* aPrevInFlow) |
98 | 0 | { |
99 | 0 | NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::svg), |
100 | 0 | "Content is not an SVG 'svg' element!"); |
101 | 0 |
|
102 | 0 | AddStateBits(NS_STATE_IS_OUTER_SVG | |
103 | 0 | NS_FRAME_FONT_INFLATION_CONTAINER | |
104 | 0 | NS_FRAME_FONT_INFLATION_FLOW_ROOT); |
105 | 0 |
|
106 | 0 | // Check for conditional processing attributes here rather than in |
107 | 0 | // nsCSSFrameConstructor::FindSVGData because we want to avoid |
108 | 0 | // simply giving failing outer <svg> elements an nsSVGContainerFrame. |
109 | 0 | // We don't create other SVG frames if PassesConditionalProcessingTests |
110 | 0 | // returns false, but since we do create nsSVGOuterSVGFrame frames we |
111 | 0 | // prevent them from painting by [ab]use NS_FRAME_IS_NONDISPLAY. The |
112 | 0 | // frame will be recreated via an nsChangeHint_ReconstructFrame restyle if |
113 | 0 | // the value returned by PassesConditionalProcessingTests changes. |
114 | 0 | SVGSVGElement *svg = static_cast<SVGSVGElement*>(aContent); |
115 | 0 | if (!svg->PassesConditionalProcessingTests()) { |
116 | 0 | AddStateBits(NS_FRAME_IS_NONDISPLAY); |
117 | 0 | } |
118 | 0 |
|
119 | 0 | nsSVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow); |
120 | 0 |
|
121 | 0 | nsIDocument* doc = mContent->GetUncomposedDoc(); |
122 | 0 | if (doc) { |
123 | 0 | // we only care about our content's zoom and pan values if it's the root element |
124 | 0 | if (doc->GetRootElement() == mContent) { |
125 | 0 | mIsRootContent = true; |
126 | 0 |
|
127 | 0 | nsIFrame* embeddingFrame; |
128 | 0 | if (IsRootOfReplacedElementSubDoc(&embeddingFrame) && embeddingFrame) { |
129 | 0 | if (MOZ_UNLIKELY(!embeddingFrame->HasAllStateBits(NS_FRAME_IS_DIRTY)) && |
130 | 0 | DependsOnIntrinsicSize(embeddingFrame)) { |
131 | 0 | // Looks like this document is loading after the embedding element |
132 | 0 | // has had its first reflow, and that its size depends on our |
133 | 0 | // intrinsic size. We need it to resize itself to use our (now |
134 | 0 | // available) intrinsic size: |
135 | 0 | embeddingFrame->PresShell()-> |
136 | 0 | FrameNeedsReflow(embeddingFrame, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY); |
137 | 0 | } |
138 | 0 | } |
139 | 0 | } |
140 | 0 | } |
141 | 0 | } |
142 | | |
143 | | //---------------------------------------------------------------------- |
144 | | // nsQueryFrame methods |
145 | | |
146 | 0 | NS_QUERYFRAME_HEAD(nsSVGOuterSVGFrame) |
147 | 0 | NS_QUERYFRAME_ENTRY(nsISVGSVGFrame) |
148 | 0 | NS_QUERYFRAME_TAIL_INHERITING(nsSVGDisplayContainerFrame) |
149 | | |
150 | | //---------------------------------------------------------------------- |
151 | | // nsIFrame methods |
152 | | //---------------------------------------------------------------------- |
153 | | // reflowing |
154 | | |
155 | | /* virtual */ nscoord |
156 | | nsSVGOuterSVGFrame::GetMinISize(gfxContext *aRenderingContext) |
157 | 0 | { |
158 | 0 | nscoord result; |
159 | 0 | DISPLAY_MIN_INLINE_SIZE(this, result); |
160 | 0 |
|
161 | 0 | result = nscoord(0); |
162 | 0 |
|
163 | 0 | return result; |
164 | 0 | } |
165 | | |
166 | | /* virtual */ nscoord |
167 | | nsSVGOuterSVGFrame::GetPrefISize(gfxContext *aRenderingContext) |
168 | 0 | { |
169 | 0 | nscoord result; |
170 | 0 | DISPLAY_PREF_INLINE_SIZE(this, result); |
171 | 0 |
|
172 | 0 | SVGSVGElement *svg = static_cast<SVGSVGElement*>(GetContent()); |
173 | 0 | WritingMode wm = GetWritingMode(); |
174 | 0 | const nsSVGLength2& isize = wm.IsVertical() |
175 | 0 | ? svg->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT] |
176 | 0 | : svg->mLengthAttributes[SVGSVGElement::ATTR_WIDTH]; |
177 | 0 |
|
178 | 0 | if (isize.IsPercentage()) { |
179 | 0 | // It looks like our containing block's isize may depend on our isize. In |
180 | 0 | // that case our behavior is undefined according to CSS 2.1 section 10.3.2. |
181 | 0 | // As a last resort, we'll fall back to returning zero. |
182 | 0 | result = nscoord(0); |
183 | 0 |
|
184 | 0 | // Returning zero may be unhelpful, however, as it leads to unexpected |
185 | 0 | // disappearance of %-sized SVGs in orthogonal contexts, where our |
186 | 0 | // containing block wants to shrink-wrap. So let's look for an ancestor |
187 | 0 | // with non-zero size in this dimension, and use that as a (somewhat |
188 | 0 | // arbitrary) result instead. |
189 | 0 | nsIFrame *parent = GetParent(); |
190 | 0 | while (parent) { |
191 | 0 | nscoord parentISize = parent->GetLogicalSize(wm).ISize(wm); |
192 | 0 | if (parentISize > 0 && parentISize != NS_UNCONSTRAINEDSIZE) { |
193 | 0 | result = parentISize; |
194 | 0 | break; |
195 | 0 | } |
196 | 0 | parent = parent->GetParent(); |
197 | 0 | } |
198 | 0 | } else { |
199 | 0 | result = nsPresContext::CSSPixelsToAppUnits(isize.GetAnimValue(svg)); |
200 | 0 | if (result < 0) { |
201 | 0 | result = nscoord(0); |
202 | 0 | } |
203 | 0 | } |
204 | 0 |
|
205 | 0 | return result; |
206 | 0 | } |
207 | | |
208 | | /* virtual */ IntrinsicSize |
209 | | nsSVGOuterSVGFrame::GetIntrinsicSize() |
210 | 0 | { |
211 | 0 | // XXXjwatt Note that here we want to return the CSS width/height if they're |
212 | 0 | // specified and we're embedded inside an nsIObjectLoadingContent. |
213 | 0 |
|
214 | 0 | IntrinsicSize intrinsicSize; |
215 | 0 |
|
216 | 0 | SVGSVGElement *content = static_cast<SVGSVGElement*>(GetContent()); |
217 | 0 | const nsSVGLength2& width = |
218 | 0 | content->mLengthAttributes[SVGSVGElement::ATTR_WIDTH]; |
219 | 0 | const nsSVGLength2& height = |
220 | 0 | content->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT]; |
221 | 0 |
|
222 | 0 | if (!width.IsPercentage()) { |
223 | 0 | nscoord val = nsPresContext::CSSPixelsToAppUnits(width.GetAnimValue(content)); |
224 | 0 | if (val < 0) val = 0; |
225 | 0 | intrinsicSize.width.SetCoordValue(val); |
226 | 0 | } |
227 | 0 |
|
228 | 0 | if (!height.IsPercentage()) { |
229 | 0 | nscoord val = nsPresContext::CSSPixelsToAppUnits(height.GetAnimValue(content)); |
230 | 0 | if (val < 0) val = 0; |
231 | 0 | intrinsicSize.height.SetCoordValue(val); |
232 | 0 | } |
233 | 0 |
|
234 | 0 | return intrinsicSize; |
235 | 0 | } |
236 | | |
237 | | /* virtual */ nsSize |
238 | | nsSVGOuterSVGFrame::GetIntrinsicRatio() |
239 | 0 | { |
240 | 0 | // We only have an intrinsic size/ratio if our width and height attributes |
241 | 0 | // are both specified and set to non-percentage values, or we have a viewBox |
242 | 0 | // rect: http://www.w3.org/TR/SVGMobile12/coords.html#IntrinsicSizing |
243 | 0 | // Unfortunately we have to return the ratio as two nscoords whereas what |
244 | 0 | // we have are two floats. Using app units allows for some floating point |
245 | 0 | // values to work but really small or large numbers will fail. |
246 | 0 |
|
247 | 0 | SVGSVGElement *content = static_cast<SVGSVGElement*>(GetContent()); |
248 | 0 | const nsSVGLength2& width = |
249 | 0 | content->mLengthAttributes[SVGSVGElement::ATTR_WIDTH]; |
250 | 0 | const nsSVGLength2& height = |
251 | 0 | content->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT]; |
252 | 0 |
|
253 | 0 | if (!width.IsPercentage() && !height.IsPercentage()) { |
254 | 0 | nsSize ratio( |
255 | 0 | nsPresContext::CSSPixelsToAppUnits(width.GetAnimValue(content)), |
256 | 0 | nsPresContext::CSSPixelsToAppUnits(height.GetAnimValue(content))); |
257 | 0 | if (ratio.width < 0) { |
258 | 0 | ratio.width = 0; |
259 | 0 | } |
260 | 0 | if (ratio.height < 0) { |
261 | 0 | ratio.height = 0; |
262 | 0 | } |
263 | 0 | return ratio; |
264 | 0 | } |
265 | 0 |
|
266 | 0 | SVGViewElement* viewElement = content->GetCurrentViewElement(); |
267 | 0 | const nsSVGViewBoxRect* viewbox = nullptr; |
268 | 0 |
|
269 | 0 | // The logic here should match HasViewBox(). |
270 | 0 | if (viewElement && viewElement->mViewBox.HasRect()) { |
271 | 0 | viewbox = &viewElement->mViewBox.GetAnimValue(); |
272 | 0 | } else if (content->mViewBox.HasRect()) { |
273 | 0 | viewbox = &content->mViewBox.GetAnimValue(); |
274 | 0 | } |
275 | 0 |
|
276 | 0 | if (viewbox) { |
277 | 0 | float viewBoxWidth = viewbox->width; |
278 | 0 | float viewBoxHeight = viewbox->height; |
279 | 0 |
|
280 | 0 | if (viewBoxWidth < 0.0f) { |
281 | 0 | viewBoxWidth = 0.0f; |
282 | 0 | } |
283 | 0 | if (viewBoxHeight < 0.0f) { |
284 | 0 | viewBoxHeight = 0.0f; |
285 | 0 | } |
286 | 0 | return nsSize(nsPresContext::CSSPixelsToAppUnits(viewBoxWidth), |
287 | 0 | nsPresContext::CSSPixelsToAppUnits(viewBoxHeight)); |
288 | 0 | } |
289 | 0 |
|
290 | 0 | return nsSVGDisplayContainerFrame::GetIntrinsicRatio(); |
291 | 0 | } |
292 | | |
293 | | /* virtual */ |
294 | | LogicalSize |
295 | | nsSVGOuterSVGFrame::ComputeSize(gfxContext *aRenderingContext, |
296 | | WritingMode aWM, |
297 | | const LogicalSize& aCBSize, |
298 | | nscoord aAvailableISize, |
299 | | const LogicalSize& aMargin, |
300 | | const LogicalSize& aBorder, |
301 | | const LogicalSize& aPadding, |
302 | | ComputeSizeFlags aFlags) |
303 | 0 | { |
304 | 0 | if (IsRootOfImage() || IsRootOfReplacedElementSubDoc()) { |
305 | 0 | // The embedding element has sized itself using the CSS replaced element |
306 | 0 | // sizing rules, using our intrinsic dimensions as necessary. The SVG spec |
307 | 0 | // says that the width and height of embedded SVG is overridden by the |
308 | 0 | // width and height of the embedding element, so we just need to size to |
309 | 0 | // the viewport that the embedding element has established for us. |
310 | 0 | return aCBSize; |
311 | 0 | } |
312 | 0 | |
313 | 0 | LogicalSize cbSize = aCBSize; |
314 | 0 | IntrinsicSize intrinsicSize = GetIntrinsicSize(); |
315 | 0 |
|
316 | 0 | if (!mContent->GetParent()) { |
317 | 0 | // We're the root of the outermost browsing context, so we need to scale |
318 | 0 | // cbSize by the full-zoom so that SVGs with percentage width/height zoom: |
319 | 0 |
|
320 | 0 | NS_ASSERTION(aCBSize.ISize(aWM) != NS_AUTOHEIGHT && |
321 | 0 | aCBSize.BSize(aWM) != NS_AUTOHEIGHT, |
322 | 0 | "root should not have auto-width/height containing block"); |
323 | 0 | cbSize.ISize(aWM) *= PresContext()->GetFullZoom(); |
324 | 0 | cbSize.BSize(aWM) *= PresContext()->GetFullZoom(); |
325 | 0 |
|
326 | 0 | // We also need to honour the width and height attributes' default values |
327 | 0 | // of 100% when we're the root of a browsing context. (GetIntrinsicSize() |
328 | 0 | // doesn't report these since there's no such thing as a percentage |
329 | 0 | // intrinsic size. Also note that explicit percentage values are mapped |
330 | 0 | // into style, so the following isn't for them.) |
331 | 0 |
|
332 | 0 | SVGSVGElement* content = static_cast<SVGSVGElement*>(GetContent()); |
333 | 0 |
|
334 | 0 | const nsSVGLength2& width = |
335 | 0 | content->mLengthAttributes[SVGSVGElement::ATTR_WIDTH]; |
336 | 0 | if (width.IsPercentage()) { |
337 | 0 | MOZ_ASSERT(intrinsicSize.width.GetUnit() == eStyleUnit_None, |
338 | 0 | "GetIntrinsicSize should have reported no intrinsic width"); |
339 | 0 | float val = width.GetAnimValInSpecifiedUnits() / 100.0f; |
340 | 0 | if (val < 0.0f) val = 0.0f; |
341 | 0 | intrinsicSize.width.SetCoordValue(val * cbSize.Width(aWM)); |
342 | 0 | } |
343 | 0 |
|
344 | 0 | const nsSVGLength2& height = |
345 | 0 | content->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT]; |
346 | 0 | NS_ASSERTION(aCBSize.BSize(aWM) != NS_AUTOHEIGHT, |
347 | 0 | "root should not have auto-height containing block"); |
348 | 0 | if (height.IsPercentage()) { |
349 | 0 | MOZ_ASSERT(intrinsicSize.height.GetUnit() == eStyleUnit_None, |
350 | 0 | "GetIntrinsicSize should have reported no intrinsic height"); |
351 | 0 | float val = height.GetAnimValInSpecifiedUnits() / 100.0f; |
352 | 0 | if (val < 0.0f) val = 0.0f; |
353 | 0 | intrinsicSize.height.SetCoordValue(val * cbSize.Height(aWM)); |
354 | 0 | } |
355 | 0 | MOZ_ASSERT(intrinsicSize.height.GetUnit() == eStyleUnit_Coord && |
356 | 0 | intrinsicSize.width.GetUnit() == eStyleUnit_Coord, |
357 | 0 | "We should have just handled the only situation where" |
358 | 0 | "we lack an intrinsic height or width."); |
359 | 0 | } |
360 | 0 |
|
361 | 0 | return ComputeSizeWithIntrinsicDimensions(aRenderingContext, aWM, |
362 | 0 | intrinsicSize, GetIntrinsicRatio(), |
363 | 0 | cbSize, aMargin, aBorder, aPadding, |
364 | 0 | aFlags); |
365 | 0 | } |
366 | | |
367 | | void |
368 | | nsSVGOuterSVGFrame::Reflow(nsPresContext* aPresContext, |
369 | | ReflowOutput& aDesiredSize, |
370 | | const ReflowInput& aReflowInput, |
371 | | nsReflowStatus& aStatus) |
372 | 0 | { |
373 | 0 | MarkInReflow(); |
374 | 0 | DO_GLOBAL_REFLOW_COUNT("nsSVGOuterSVGFrame"); |
375 | 0 | DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus); |
376 | 0 | MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); |
377 | 0 | NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, |
378 | 0 | ("enter nsSVGOuterSVGFrame::Reflow: availSize=%d,%d", |
379 | 0 | aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight())); |
380 | 0 |
|
381 | 0 | MOZ_ASSERT(mState & NS_FRAME_IN_REFLOW, "frame is not in reflow"); |
382 | 0 |
|
383 | 0 | aDesiredSize.Width() = aReflowInput.ComputedWidth() + |
384 | 0 | aReflowInput.ComputedPhysicalBorderPadding().LeftRight(); |
385 | 0 | aDesiredSize.Height() = aReflowInput.ComputedHeight() + |
386 | 0 | aReflowInput.ComputedPhysicalBorderPadding().TopBottom(); |
387 | 0 |
|
388 | 0 | NS_ASSERTION(!GetPrevInFlow(), "SVG can't currently be broken across pages."); |
389 | 0 |
|
390 | 0 | SVGSVGElement *svgElem = static_cast<SVGSVGElement*>(GetContent()); |
391 | 0 |
|
392 | 0 | nsSVGOuterSVGAnonChildFrame *anonKid = |
393 | 0 | static_cast<nsSVGOuterSVGAnonChildFrame*>(PrincipalChildList().FirstChild()); |
394 | 0 |
|
395 | 0 | if (mState & NS_FRAME_FIRST_REFLOW) { |
396 | 0 | // Initialize |
397 | 0 | svgElem->UpdateHasChildrenOnlyTransform(); |
398 | 0 | } |
399 | 0 |
|
400 | 0 | // If our SVG viewport has changed, update our content and notify. |
401 | 0 | // http://www.w3.org/TR/SVG11/coords.html#ViewportSpace |
402 | 0 |
|
403 | 0 | svgFloatSize newViewportSize( |
404 | 0 | nsPresContext::AppUnitsToFloatCSSPixels(aReflowInput.ComputedWidth()), |
405 | 0 | nsPresContext::AppUnitsToFloatCSSPixels(aReflowInput.ComputedHeight())); |
406 | 0 |
|
407 | 0 | svgFloatSize oldViewportSize = svgElem->GetViewportSize(); |
408 | 0 |
|
409 | 0 | uint32_t changeBits = 0; |
410 | 0 | if (newViewportSize != oldViewportSize) { |
411 | 0 | // When our viewport size changes, we may need to update the overflow rects |
412 | 0 | // of our child frames. This is the case if: |
413 | 0 | // |
414 | 0 | // * We have a real/synthetic viewBox (a children-only transform), since |
415 | 0 | // the viewBox transform will change as the viewport dimensions change. |
416 | 0 | // |
417 | 0 | // * We do not have a real/synthetic viewBox, but the last time we |
418 | 0 | // reflowed (or the last time UpdateOverflow() was called) we did. |
419 | 0 | // |
420 | 0 | // We only handle the former case here, in which case we mark all our child |
421 | 0 | // frames as dirty so that we reflow them below and update their overflow |
422 | 0 | // rects. |
423 | 0 | // |
424 | 0 | // In the latter case, updating of overflow rects is handled for removal of |
425 | 0 | // real viewBox (the viewBox attribute) in AttributeChanged. Synthetic |
426 | 0 | // viewBox "removal" (e.g. a document references the same SVG via both an |
427 | 0 | // <svg:image> and then as a CSS background image (a synthetic viewBox is |
428 | 0 | // used when painting the former, but not when painting the latter)) is |
429 | 0 | // handled in SVGSVGElement::FlushImageTransformInvalidation. |
430 | 0 | // |
431 | 0 | if (svgElem->HasViewBoxOrSyntheticViewBox()) { |
432 | 0 | nsIFrame* anonChild = PrincipalChildList().FirstChild(); |
433 | 0 | anonChild->AddStateBits(NS_FRAME_IS_DIRTY); |
434 | 0 | for (nsIFrame* child : anonChild->PrincipalChildList()) { |
435 | 0 | child->AddStateBits(NS_FRAME_IS_DIRTY); |
436 | 0 | } |
437 | 0 | } |
438 | 0 | changeBits |= COORD_CONTEXT_CHANGED; |
439 | 0 | svgElem->SetViewportSize(newViewportSize); |
440 | 0 | } |
441 | 0 | if (mFullZoom != PresContext()->GetFullZoom()) { |
442 | 0 | changeBits |= FULL_ZOOM_CHANGED; |
443 | 0 | mFullZoom = PresContext()->GetFullZoom(); |
444 | 0 | } |
445 | 0 | if (changeBits) { |
446 | 0 | NotifyViewportOrTransformChanged(changeBits); |
447 | 0 | } |
448 | 0 | mViewportInitialized = true; |
449 | 0 |
|
450 | 0 | // Now that we've marked the necessary children as dirty, call |
451 | 0 | // ReflowSVG() or ReflowSVGNonDisplayText() on them, depending |
452 | 0 | // on whether we are non-display. |
453 | 0 | mCallingReflowSVG = true; |
454 | 0 | if (GetStateBits() & NS_FRAME_IS_NONDISPLAY) { |
455 | 0 | ReflowSVGNonDisplayText(this); |
456 | 0 | } else { |
457 | 0 | // Update the mRects and visual overflow rects of all our descendants, |
458 | 0 | // including our anonymous wrapper kid: |
459 | 0 | anonKid->AddStateBits(mState & NS_FRAME_IS_DIRTY); |
460 | 0 | anonKid->ReflowSVG(); |
461 | 0 | MOZ_ASSERT(!anonKid->GetNextSibling(), |
462 | 0 | "We should have one anonymous child frame wrapping our real " |
463 | 0 | "children"); |
464 | 0 | } |
465 | 0 | mCallingReflowSVG = false; |
466 | 0 |
|
467 | 0 | // Set our anonymous kid's offset from our border box: |
468 | 0 | anonKid->SetPosition(GetContentRectRelativeToSelf().TopLeft()); |
469 | 0 |
|
470 | 0 | // Including our size in our overflow rects regardless of the value of |
471 | 0 | // 'background', 'border', etc. makes sure that we usually (when we clip to |
472 | 0 | // our content area) don't have to keep changing our overflow rects as our |
473 | 0 | // descendants move about (see perf comment below). Including our size in our |
474 | 0 | // scrollable overflow rect also makes sure that we scroll if we're too big |
475 | 0 | // for our viewport. |
476 | 0 | // |
477 | 0 | // <svg> never allows scrolling to anything outside its mRect (only panning), |
478 | 0 | // so we must always keep our scrollable overflow set to our size. |
479 | 0 | // |
480 | 0 | // With regards to visual overflow, we always clip root-<svg> (see our |
481 | 0 | // BuildDisplayList method) regardless of the value of the 'overflow' |
482 | 0 | // property since that is per-spec, even for the initial 'visible' value. For |
483 | 0 | // that reason there's no point in adding descendant visual overflow to our |
484 | 0 | // own when this frame is for a root-<svg>. That said, there's also a very |
485 | 0 | // good performance reason for us wanting to avoid doing so. If we did, then |
486 | 0 | // the frame's overflow would often change as descendants that are partially |
487 | 0 | // or fully outside its rect moved (think animation on/off screen), and that |
488 | 0 | // would cause us to do a full NS_FRAME_IS_DIRTY reflow and repaint of the |
489 | 0 | // entire document tree each such move (see bug 875175). |
490 | 0 | // |
491 | 0 | // So it's only non-root outer-<svg> that has the visual overflow of its |
492 | 0 | // descendants added to its own. (Note that the default user-agent style |
493 | 0 | // sheet makes 'hidden' the default value for :not(root(svg)), so usually |
494 | 0 | // FinishAndStoreOverflow will still clip this back to the frame's rect.) |
495 | 0 | // |
496 | 0 | // WARNING!! Keep UpdateBounds below in sync with whatever we do for our |
497 | 0 | // overflow rects here! (Again, see bug 875175.) |
498 | 0 | // |
499 | 0 | aDesiredSize.SetOverflowAreasToDesiredBounds(); |
500 | 0 | if (!mIsRootContent) { |
501 | 0 | aDesiredSize.mOverflowAreas.VisualOverflow().UnionRect( |
502 | 0 | aDesiredSize.mOverflowAreas.VisualOverflow(), |
503 | 0 | anonKid->GetVisualOverflowRect() + anonKid->GetPosition()); |
504 | 0 | } |
505 | 0 | FinishAndStoreOverflow(&aDesiredSize); |
506 | 0 |
|
507 | 0 | NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, |
508 | 0 | ("exit nsSVGOuterSVGFrame::Reflow: size=%d,%d", |
509 | 0 | aDesiredSize.Width(), aDesiredSize.Height())); |
510 | 0 | NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize); |
511 | 0 | } |
512 | | |
513 | | void |
514 | | nsSVGOuterSVGFrame::DidReflow(nsPresContext* aPresContext, |
515 | | const ReflowInput* aReflowInput) |
516 | 0 | { |
517 | 0 | nsSVGDisplayContainerFrame::DidReflow(aPresContext,aReflowInput); |
518 | 0 |
|
519 | 0 | // Make sure elements styled by :hover get updated if script/animation moves |
520 | 0 | // them under or out from under the pointer: |
521 | 0 | PresShell()->SynthesizeMouseMove(false); |
522 | 0 | } |
523 | | |
524 | | /* virtual */ void |
525 | | nsSVGOuterSVGFrame::UnionChildOverflow(nsOverflowAreas& aOverflowAreas) |
526 | 0 | { |
527 | 0 | // See the comments in Reflow above. |
528 | 0 |
|
529 | 0 | // WARNING!! Keep this in sync with Reflow above! |
530 | 0 |
|
531 | 0 | if (!mIsRootContent) { |
532 | 0 | nsIFrame *anonKid = PrincipalChildList().FirstChild(); |
533 | 0 | aOverflowAreas.VisualOverflow().UnionRect( |
534 | 0 | aOverflowAreas.VisualOverflow(), |
535 | 0 | anonKid->GetVisualOverflowRect() + anonKid->GetPosition()); |
536 | 0 | } |
537 | 0 | } |
538 | | |
539 | | |
540 | | //---------------------------------------------------------------------- |
541 | | // container methods |
542 | | |
543 | | /** |
544 | | * Used to paint/hit-test SVG when SVG display lists are disabled. |
545 | | */ |
546 | | class nsDisplayOuterSVG final : public nsDisplayItem |
547 | | { |
548 | | public: |
549 | | nsDisplayOuterSVG(nsDisplayListBuilder* aBuilder, |
550 | | nsSVGOuterSVGFrame* aFrame) : |
551 | 0 | nsDisplayItem(aBuilder, aFrame) { |
552 | 0 | MOZ_COUNT_CTOR(nsDisplayOuterSVG); |
553 | 0 | } |
554 | | #ifdef NS_BUILD_REFCNT_LOGGING |
555 | | virtual ~nsDisplayOuterSVG() { |
556 | | MOZ_COUNT_DTOR(nsDisplayOuterSVG); |
557 | | } |
558 | | #endif |
559 | | |
560 | | virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, |
561 | | HitTestState* aState, |
562 | | nsTArray<nsIFrame*> *aOutFrames) override; |
563 | | virtual void Paint(nsDisplayListBuilder* aBuilder, |
564 | | gfxContext* aCtx) override; |
565 | | |
566 | | virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, |
567 | | const nsDisplayItemGeometry* aGeometry, |
568 | | nsRegion* aInvalidRegion) const override; |
569 | | |
570 | | nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override |
571 | 0 | { |
572 | 0 | return new nsDisplayItemGenericImageGeometry(this, aBuilder); |
573 | 0 | } |
574 | | |
575 | | NS_DISPLAY_DECL_NAME("SVGOuterSVG", TYPE_SVG_OUTER_SVG) |
576 | | }; |
577 | | |
578 | | void |
579 | | nsDisplayOuterSVG::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, |
580 | | HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) |
581 | 0 | { |
582 | 0 | nsSVGOuterSVGFrame *outerSVGFrame = static_cast<nsSVGOuterSVGFrame*>(mFrame); |
583 | 0 |
|
584 | 0 | nsPoint refFrameToContentBox = |
585 | 0 | ToReferenceFrame() + outerSVGFrame->GetContentRectRelativeToSelf().TopLeft(); |
586 | 0 |
|
587 | 0 | nsPoint pointRelativeToContentBox = |
588 | 0 | nsPoint(aRect.x + aRect.width / 2, aRect.y + aRect.height / 2) - |
589 | 0 | refFrameToContentBox; |
590 | 0 |
|
591 | 0 | gfxPoint svgViewportRelativePoint = |
592 | 0 | gfxPoint(pointRelativeToContentBox.x, pointRelativeToContentBox.y) / |
593 | 0 | AppUnitsPerCSSPixel(); |
594 | 0 |
|
595 | 0 | nsSVGOuterSVGAnonChildFrame *anonKid = |
596 | 0 | static_cast<nsSVGOuterSVGAnonChildFrame*>( |
597 | 0 | outerSVGFrame->PrincipalChildList().FirstChild()); |
598 | 0 |
|
599 | 0 | nsIFrame* frame = |
600 | 0 | nsSVGUtils::HitTestChildren(anonKid, svgViewportRelativePoint); |
601 | 0 | if (frame) { |
602 | 0 | aOutFrames->AppendElement(frame); |
603 | 0 | } |
604 | 0 | } |
605 | | |
606 | | void |
607 | | nsDisplayOuterSVG::Paint(nsDisplayListBuilder* aBuilder, |
608 | | gfxContext* aContext) |
609 | 0 | { |
610 | | #if defined(DEBUG) && defined(SVG_DEBUG_PAINT_TIMING) |
611 | | PRTime start = PR_Now(); |
612 | | #endif |
613 | |
|
614 | 0 | // Create an SVGAutoRenderState so we can call SetPaintingToWindow on it. |
615 | 0 | SVGAutoRenderState state(aContext->GetDrawTarget()); |
616 | 0 |
|
617 | 0 | if (aBuilder->IsPaintingToWindow()) { |
618 | 0 | state.SetPaintingToWindow(true); |
619 | 0 | } |
620 | 0 |
|
621 | 0 | nsRect viewportRect = |
622 | 0 | mFrame->GetContentRectRelativeToSelf() + ToReferenceFrame(); |
623 | 0 |
|
624 | 0 | nsRect clipRect = GetPaintRect().Intersect(viewportRect); |
625 | 0 |
|
626 | 0 | uint32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); |
627 | 0 |
|
628 | 0 | nsIntRect contentAreaDirtyRect = |
629 | 0 | (clipRect - viewportRect.TopLeft()). |
630 | 0 | ToOutsidePixels(appUnitsPerDevPixel); |
631 | 0 |
|
632 | 0 | gfxPoint devPixelOffset = |
633 | 0 | nsLayoutUtils::PointToGfxPoint(viewportRect.TopLeft(), appUnitsPerDevPixel); |
634 | 0 |
|
635 | 0 | aContext->Save(); |
636 | 0 | imgDrawingParams imgParams(aBuilder->ShouldSyncDecodeImages() |
637 | 0 | ? imgIContainer::FLAG_SYNC_DECODE |
638 | 0 | : imgIContainer::FLAG_SYNC_DECODE_IF_FAST); |
639 | 0 | // We include the offset of our frame and a scale from device pixels to user |
640 | 0 | // units (i.e. CSS px) in the matrix that we pass to our children): |
641 | 0 | gfxMatrix tm = nsSVGUtils::GetCSSPxToDevPxMatrix(mFrame) * |
642 | 0 | gfxMatrix::Translation(devPixelOffset); |
643 | 0 | nsSVGUtils::PaintFrameWithEffects(mFrame, *aContext, tm, |
644 | 0 | imgParams, &contentAreaDirtyRect); |
645 | 0 | nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, imgParams.result); |
646 | 0 | aContext->Restore(); |
647 | 0 |
|
648 | | #if defined(DEBUG) && defined(SVG_DEBUG_PAINT_TIMING) |
649 | | PRTime end = PR_Now(); |
650 | | printf("SVG Paint Timing: %f ms\n", (end-start)/1000.0); |
651 | | #endif |
652 | | } |
653 | | |
654 | | nsRegion |
655 | | nsSVGOuterSVGFrame::FindInvalidatedForeignObjectFrameChildren(nsIFrame* aFrame) |
656 | 0 | { |
657 | 0 | nsRegion result; |
658 | 0 | if (mForeignObjectHash && mForeignObjectHash->Count()) { |
659 | 0 | for (auto it = mForeignObjectHash->Iter(); !it.Done(); it.Next()) { |
660 | 0 | result.Or(result, it.Get()->GetKey()->GetInvalidRegion()); |
661 | 0 | } |
662 | 0 | } |
663 | 0 | return result; |
664 | 0 | } |
665 | | |
666 | | void |
667 | | nsDisplayOuterSVG::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, |
668 | | const nsDisplayItemGeometry* aGeometry, |
669 | | nsRegion* aInvalidRegion) const |
670 | 0 | { |
671 | 0 | nsSVGOuterSVGFrame *frame = static_cast<nsSVGOuterSVGFrame*>(mFrame); |
672 | 0 | frame->InvalidateSVG(frame->FindInvalidatedForeignObjectFrameChildren(frame)); |
673 | 0 |
|
674 | 0 | nsRegion result = frame->GetInvalidRegion(); |
675 | 0 | result.MoveBy(ToReferenceFrame()); |
676 | 0 | frame->ClearInvalidRegion(); |
677 | 0 |
|
678 | 0 | nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion); |
679 | 0 | aInvalidRegion->Or(*aInvalidRegion, result); |
680 | 0 |
|
681 | 0 | auto geometry = |
682 | 0 | static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry); |
683 | 0 |
|
684 | 0 | if (aBuilder->ShouldSyncDecodeImages() && |
685 | 0 | geometry->ShouldInvalidateToSyncDecodeImages()) { |
686 | 0 | bool snap; |
687 | 0 | aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap)); |
688 | 0 | } |
689 | 0 | } |
690 | | |
691 | | nsresult |
692 | | nsSVGOuterSVGFrame::AttributeChanged(int32_t aNameSpaceID, |
693 | | nsAtom* aAttribute, |
694 | | int32_t aModType) |
695 | 0 | { |
696 | 0 | if (aNameSpaceID == kNameSpaceID_None && |
697 | 0 | !(GetStateBits() & (NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_NONDISPLAY))) { |
698 | 0 | if (aAttribute == nsGkAtoms::viewBox || |
699 | 0 | aAttribute == nsGkAtoms::preserveAspectRatio || |
700 | 0 | aAttribute == nsGkAtoms::transform) { |
701 | 0 |
|
702 | 0 | // make sure our cached transform matrix gets (lazily) updated |
703 | 0 | mCanvasTM = nullptr; |
704 | 0 |
|
705 | 0 | nsSVGUtils::NotifyChildrenOfSVGChange(PrincipalChildList().FirstChild(), |
706 | 0 | aAttribute == nsGkAtoms::viewBox ? |
707 | 0 | TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED : TRANSFORM_CHANGED); |
708 | 0 |
|
709 | 0 | if (aAttribute != nsGkAtoms::transform) { |
710 | 0 | static_cast<SVGSVGElement*>(GetContent())->ChildrenOnlyTransformChanged(); |
711 | 0 | } |
712 | 0 |
|
713 | 0 | } else if (aAttribute == nsGkAtoms::width || |
714 | 0 | aAttribute == nsGkAtoms::height) { |
715 | 0 |
|
716 | 0 | // Don't call ChildrenOnlyTransformChanged() here, since we call it |
717 | 0 | // under Reflow if the width/height actually changed. |
718 | 0 |
|
719 | 0 | nsIFrame* embeddingFrame; |
720 | 0 | if (IsRootOfReplacedElementSubDoc(&embeddingFrame) && embeddingFrame) { |
721 | 0 | if (DependsOnIntrinsicSize(embeddingFrame)) { |
722 | 0 | // Tell embeddingFrame's presShell it needs to be reflowed (which takes |
723 | 0 | // care of reflowing us too). |
724 | 0 | embeddingFrame->PresShell()-> |
725 | 0 | FrameNeedsReflow(embeddingFrame, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY); |
726 | 0 | } |
727 | 0 | // else our width and height is overridden - don't reflow anything |
728 | 0 | } else { |
729 | 0 | // We are not embedded by reference, so our 'width' and 'height' |
730 | 0 | // attributes are not overridden - we need to reflow. |
731 | 0 | PresShell()-> |
732 | 0 | FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY); |
733 | 0 | } |
734 | 0 | } |
735 | 0 | } |
736 | 0 |
|
737 | 0 | return NS_OK; |
738 | 0 | } |
739 | | |
740 | | bool |
741 | | nsSVGOuterSVGFrame::IsSVGTransformed(Matrix* aOwnTransform, |
742 | | Matrix* aFromParentTransform) const |
743 | 0 | { |
744 | 0 | // Our anonymous child's HasChildrenOnlyTransform() implementation makes sure |
745 | 0 | // our children-only transforms are applied to our children. We only care |
746 | 0 | // about transforms that transform our own frame here. |
747 | 0 |
|
748 | 0 | bool foundTransform = false; |
749 | 0 |
|
750 | 0 | SVGSVGElement *content = static_cast<SVGSVGElement*>(GetContent()); |
751 | 0 | nsSVGAnimatedTransformList* transformList = |
752 | 0 | content->GetAnimatedTransformList(); |
753 | 0 | if ((transformList && transformList->HasTransform()) || |
754 | 0 | content->GetAnimateMotionTransform()) { |
755 | 0 | if (aOwnTransform) { |
756 | 0 | *aOwnTransform = gfx::ToMatrix( |
757 | 0 | content->PrependLocalTransformsTo( |
758 | 0 | gfxMatrix(), eUserSpaceToParent)); |
759 | 0 | } |
760 | 0 | foundTransform = true; |
761 | 0 | } |
762 | 0 |
|
763 | 0 | return foundTransform; |
764 | 0 | } |
765 | | |
766 | | //---------------------------------------------------------------------- |
767 | | // painting |
768 | | |
769 | | void |
770 | | nsSVGOuterSVGFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, |
771 | | const nsDisplayListSet& aLists) |
772 | 0 | { |
773 | 0 | if (GetStateBits() & NS_FRAME_IS_NONDISPLAY) { |
774 | 0 | return; |
775 | 0 | } |
776 | 0 | |
777 | 0 | DisplayBorderBackgroundOutline(aBuilder, aLists); |
778 | 0 |
|
779 | 0 | // Per-spec, we always clip root-<svg> even when 'overflow' has its initial |
780 | 0 | // value of 'visible'. See also the "visual overflow" comments in Reflow. |
781 | 0 | DisplayListClipState::AutoSaveRestore autoSR(aBuilder); |
782 | 0 | if (mIsRootContent || |
783 | 0 | StyleDisplay()->IsScrollableOverflow()) { |
784 | 0 | autoSR.ClipContainingBlockDescendantsToContentBox(aBuilder, this); |
785 | 0 | } |
786 | 0 |
|
787 | 0 | if ((aBuilder->IsForEventDelivery() && |
788 | 0 | NS_SVGDisplayListHitTestingEnabled()) || |
789 | 0 | (!aBuilder->IsForEventDelivery() && |
790 | 0 | NS_SVGDisplayListPaintingEnabled())) { |
791 | 0 | nsDisplayList* contentList = aLists.Content(); |
792 | 0 | nsDisplayListSet set(contentList, contentList, contentList, |
793 | 0 | contentList, contentList, contentList); |
794 | 0 | BuildDisplayListForNonBlockChildren(aBuilder, set); |
795 | 0 | } else if (IsVisibleForPainting(aBuilder) || !aBuilder->IsForPainting()) { |
796 | 0 | aLists.Content()->AppendToTop( |
797 | 0 | MakeDisplayItem<nsDisplayOuterSVG>(aBuilder, this)); |
798 | 0 | } |
799 | 0 | } |
800 | | |
801 | | nsSplittableType |
802 | | nsSVGOuterSVGFrame::GetSplittableType() const |
803 | 0 | { |
804 | 0 | return NS_FRAME_NOT_SPLITTABLE; |
805 | 0 | } |
806 | | |
807 | | //---------------------------------------------------------------------- |
808 | | // nsISVGSVGFrame methods: |
809 | | |
810 | | void |
811 | | nsSVGOuterSVGFrame::NotifyViewportOrTransformChanged(uint32_t aFlags) |
812 | 0 | { |
813 | 0 | MOZ_ASSERT(aFlags && |
814 | 0 | !(aFlags & ~(COORD_CONTEXT_CHANGED | TRANSFORM_CHANGED | |
815 | 0 | FULL_ZOOM_CHANGED)), |
816 | 0 | "Unexpected aFlags value"); |
817 | 0 |
|
818 | 0 | // No point in doing anything when were not init'ed yet: |
819 | 0 | if (!mViewportInitialized) { |
820 | 0 | return; |
821 | 0 | } |
822 | 0 | |
823 | 0 | SVGSVGElement *content = static_cast<SVGSVGElement*>(GetContent()); |
824 | 0 |
|
825 | 0 | if (aFlags & COORD_CONTEXT_CHANGED) { |
826 | 0 | if (content->HasViewBoxRect()) { |
827 | 0 | // Percentage lengths on children resolve against the viewBox rect so we |
828 | 0 | // don't need to notify them of the viewport change, but the viewBox |
829 | 0 | // transform will have changed, so we need to notify them of that instead. |
830 | 0 | aFlags = TRANSFORM_CHANGED; |
831 | 0 | } |
832 | 0 | else if (content->ShouldSynthesizeViewBox()) { |
833 | 0 | // In the case of a synthesized viewBox, the synthetic viewBox's rect |
834 | 0 | // changes as the viewport changes. As a result we need to maintain the |
835 | 0 | // COORD_CONTEXT_CHANGED flag. |
836 | 0 | aFlags |= TRANSFORM_CHANGED; |
837 | 0 | } |
838 | 0 | else if (mCanvasTM && mCanvasTM->IsSingular()) { |
839 | 0 | // A width/height of zero will result in us having a singular mCanvasTM |
840 | 0 | // even when we don't have a viewBox. So we also want to recompute our |
841 | 0 | // mCanvasTM for this width/height change even though we don't have a |
842 | 0 | // viewBox. |
843 | 0 | aFlags |= TRANSFORM_CHANGED; |
844 | 0 | } |
845 | 0 | } |
846 | 0 |
|
847 | 0 | bool haveNonFulLZoomTransformChange = (aFlags & TRANSFORM_CHANGED); |
848 | 0 |
|
849 | 0 | if (aFlags & FULL_ZOOM_CHANGED) { |
850 | 0 | // Convert FULL_ZOOM_CHANGED to TRANSFORM_CHANGED: |
851 | 0 | aFlags = (aFlags & ~FULL_ZOOM_CHANGED) | TRANSFORM_CHANGED; |
852 | 0 | } |
853 | 0 |
|
854 | 0 | if (aFlags & TRANSFORM_CHANGED) { |
855 | 0 | // Make sure our canvas transform matrix gets (lazily) recalculated: |
856 | 0 | mCanvasTM = nullptr; |
857 | 0 |
|
858 | 0 | if (haveNonFulLZoomTransformChange && |
859 | 0 | !(mState & NS_FRAME_IS_NONDISPLAY)) { |
860 | 0 | uint32_t flags = (mState & NS_FRAME_IN_REFLOW) ? |
861 | 0 | SVGSVGElement::eDuringReflow : 0; |
862 | 0 | content->ChildrenOnlyTransformChanged(flags); |
863 | 0 | } |
864 | 0 | } |
865 | 0 |
|
866 | 0 | nsSVGUtils::NotifyChildrenOfSVGChange(PrincipalChildList().FirstChild(), aFlags); |
867 | 0 | } |
868 | | |
869 | | //---------------------------------------------------------------------- |
870 | | // nsSVGDisplayableFrame methods: |
871 | | |
872 | | void |
873 | | nsSVGOuterSVGFrame::PaintSVG(gfxContext& aContext, |
874 | | const gfxMatrix& aTransform, |
875 | | imgDrawingParams& aImgParams, |
876 | | const nsIntRect* aDirtyRect) |
877 | 0 | { |
878 | 0 | NS_ASSERTION(PrincipalChildList().FirstChild()->IsSVGOuterSVGAnonChildFrame() && |
879 | 0 | !PrincipalChildList().FirstChild()->GetNextSibling(), |
880 | 0 | "We should have a single, anonymous, child"); |
881 | 0 | nsSVGOuterSVGAnonChildFrame *anonKid = |
882 | 0 | static_cast<nsSVGOuterSVGAnonChildFrame*>(PrincipalChildList().FirstChild()); |
883 | 0 | anonKid->PaintSVG(aContext, aTransform, aImgParams, aDirtyRect); |
884 | 0 | } |
885 | | |
886 | | SVGBBox |
887 | | nsSVGOuterSVGFrame::GetBBoxContribution(const gfx::Matrix &aToBBoxUserspace, |
888 | | uint32_t aFlags) |
889 | 0 | { |
890 | 0 | NS_ASSERTION(PrincipalChildList().FirstChild()->IsSVGOuterSVGAnonChildFrame() && |
891 | 0 | !PrincipalChildList().FirstChild()->GetNextSibling(), |
892 | 0 | "We should have a single, anonymous, child"); |
893 | 0 | // We must defer to our child so that we don't include our |
894 | 0 | // content->PrependLocalTransformsTo() transforms. |
895 | 0 | nsSVGOuterSVGAnonChildFrame *anonKid = |
896 | 0 | static_cast<nsSVGOuterSVGAnonChildFrame*>(PrincipalChildList().FirstChild()); |
897 | 0 | return anonKid->GetBBoxContribution(aToBBoxUserspace, aFlags); |
898 | 0 | } |
899 | | |
900 | | //---------------------------------------------------------------------- |
901 | | // nsSVGContainerFrame methods: |
902 | | |
903 | | gfxMatrix |
904 | | nsSVGOuterSVGFrame::GetCanvasTM() |
905 | 0 | { |
906 | 0 | if (!mCanvasTM) { |
907 | 0 | SVGSVGElement *content = static_cast<SVGSVGElement*>(GetContent()); |
908 | 0 |
|
909 | 0 | float devPxPerCSSPx = |
910 | 0 | 1.0f / PresContext()->AppUnitsToFloatCSSPixels( |
911 | 0 | PresContext()->AppUnitsPerDevPixel()); |
912 | 0 |
|
913 | 0 | gfxMatrix tm = content->PrependLocalTransformsTo( |
914 | 0 | gfxMatrix::Scaling(devPxPerCSSPx, devPxPerCSSPx)); |
915 | 0 | mCanvasTM = new gfxMatrix(tm); |
916 | 0 | } |
917 | 0 | return *mCanvasTM; |
918 | 0 | } |
919 | | |
920 | | //---------------------------------------------------------------------- |
921 | | // Implementation helpers |
922 | | |
923 | | bool |
924 | | nsSVGOuterSVGFrame::IsRootOfReplacedElementSubDoc(nsIFrame **aEmbeddingFrame) |
925 | 0 | { |
926 | 0 | if (!mContent->GetParent()) { |
927 | 0 | // Our content is the document element |
928 | 0 | nsCOMPtr<nsIDocShell> docShell = PresContext()->GetDocShell(); |
929 | 0 | nsCOMPtr<nsPIDOMWindowOuter> window; |
930 | 0 | if (docShell) { |
931 | 0 | window = docShell->GetWindow(); |
932 | 0 | } |
933 | 0 |
|
934 | 0 | if (window) { |
935 | 0 | RefPtr<Element> frameElement = window->GetFrameElement(); |
936 | 0 | if (frameElement && |
937 | 0 | frameElement->IsAnyOfHTMLElements(nsGkAtoms::object, |
938 | 0 | nsGkAtoms::embed, |
939 | 0 | nsGkAtoms::iframe)) { |
940 | 0 | // Our document is inside an HTML 'object', 'embed' or 'iframe' element |
941 | 0 | if (aEmbeddingFrame) { |
942 | 0 | *aEmbeddingFrame = frameElement->GetPrimaryFrame(); |
943 | 0 | NS_ASSERTION(*aEmbeddingFrame, "Yikes, no embedding frame!"); |
944 | 0 | } |
945 | 0 | return true; |
946 | 0 | } |
947 | 0 | } |
948 | 0 | } |
949 | 0 | if (aEmbeddingFrame) { |
950 | 0 | *aEmbeddingFrame = nullptr; |
951 | 0 | } |
952 | 0 | return false; |
953 | 0 | } |
954 | | |
955 | | bool |
956 | | nsSVGOuterSVGFrame::IsRootOfImage() |
957 | 0 | { |
958 | 0 | if (!mContent->GetParent()) { |
959 | 0 | // Our content is the document element |
960 | 0 | nsIDocument* doc = mContent->GetUncomposedDoc(); |
961 | 0 | if (doc && doc->IsBeingUsedAsImage()) { |
962 | 0 | // Our document is being used as an image |
963 | 0 | return true; |
964 | 0 | } |
965 | 0 | } |
966 | 0 | |
967 | 0 | return false; |
968 | 0 | } |
969 | | |
970 | | bool |
971 | | nsSVGOuterSVGFrame::VerticalScrollbarNotNeeded() const |
972 | 0 | { |
973 | 0 | const nsSVGLength2& height = static_cast<SVGSVGElement*>(GetContent())-> |
974 | 0 | mLengthAttributes[SVGSVGElement::ATTR_HEIGHT]; |
975 | 0 | return height.IsPercentage() && height.GetBaseValInSpecifiedUnits() <= 100; |
976 | 0 | } |
977 | | |
978 | | void |
979 | | nsSVGOuterSVGFrame::AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) |
980 | 0 | { |
981 | 0 | nsIFrame* anonKid = PrincipalChildList().FirstChild(); |
982 | 0 | MOZ_ASSERT(anonKid->IsSVGOuterSVGAnonChildFrame()); |
983 | 0 | aResult.AppendElement(OwnedAnonBox(anonKid)); |
984 | 0 | } |
985 | | |
986 | | //---------------------------------------------------------------------- |
987 | | // Implementation of nsSVGOuterSVGAnonChildFrame |
988 | | |
989 | | nsContainerFrame* |
990 | | NS_NewSVGOuterSVGAnonChildFrame(nsIPresShell* aPresShell, |
991 | | ComputedStyle* aStyle) |
992 | 0 | { |
993 | 0 | return new (aPresShell) nsSVGOuterSVGAnonChildFrame(aStyle); |
994 | 0 | } |
995 | | |
996 | | NS_IMPL_FRAMEARENA_HELPERS(nsSVGOuterSVGAnonChildFrame) |
997 | | |
998 | | #ifdef DEBUG |
999 | | void |
1000 | | nsSVGOuterSVGAnonChildFrame::Init(nsIContent* aContent, |
1001 | | nsContainerFrame* aParent, |
1002 | | nsIFrame* aPrevInFlow) |
1003 | | { |
1004 | | MOZ_ASSERT(aParent->IsSVGOuterSVGFrame(), "Unexpected parent"); |
1005 | | nsSVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow); |
1006 | | } |
1007 | | #endif |
1008 | | |
1009 | | void |
1010 | | nsSVGOuterSVGAnonChildFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, |
1011 | | const nsDisplayListSet& aLists) |
1012 | 0 | { |
1013 | 0 | // Wrap our contents into an nsDisplaySVGWrapper. |
1014 | 0 | // We wrap this frame instead of the nsSVGOuterSVGFrame so that the wrapper |
1015 | 0 | // doesn't contain the <svg> element's CSS styles, like backgrounds or borders. |
1016 | 0 | // Creating the nsDisplaySVGWrapper here also means that it'll be inside the |
1017 | 0 | // nsDisplayTransform for our viewbox transform. |
1018 | 0 | // The nsDisplaySVGWrapper's reference frame is this frame, because this frame |
1019 | 0 | // always returns true from IsSVGTransformed. |
1020 | 0 | nsDisplayList newList; |
1021 | 0 | nsDisplayListSet set(&newList, &newList, &newList, |
1022 | 0 | &newList, &newList, &newList); |
1023 | 0 | BuildDisplayListForNonBlockChildren(aBuilder, set); |
1024 | 0 | aLists.Content()->AppendToTop(MakeDisplayItem<nsDisplaySVGWrapper>(aBuilder, this, &newList)); |
1025 | 0 | } |
1026 | | |
1027 | | static Matrix |
1028 | | ComputeOuterSVGAnonChildFrameTransform(const nsSVGOuterSVGAnonChildFrame* aFrame) |
1029 | 0 | { |
1030 | 0 | // Our elements 'transform' attribute is applied to our nsSVGOuterSVGFrame |
1031 | 0 | // parent, and the element's children-only transforms are applied to us, the |
1032 | 0 | // anonymous child frame. Since we are the child frame, we apply the |
1033 | 0 | // children-only transforms as if they are our own transform. |
1034 | 0 | SVGSVGElement* content = static_cast<SVGSVGElement*>(aFrame->GetContent()); |
1035 | 0 |
|
1036 | 0 | if (!content->HasChildrenOnlyTransform()) { |
1037 | 0 | return Matrix(); |
1038 | 0 | } |
1039 | 0 | |
1040 | 0 | // Outer-<svg> doesn't use x/y, so we can pass eChildToUserSpace here. |
1041 | 0 | gfxMatrix ownMatrix = |
1042 | 0 | content->PrependLocalTransformsTo(gfxMatrix(), eChildToUserSpace); |
1043 | 0 |
|
1044 | 0 | if (ownMatrix.HasNonTranslation()) { |
1045 | 0 | // viewBox, currentScale and currentTranslate should only produce a |
1046 | 0 | // rectilinear transform. |
1047 | 0 | MOZ_ASSERT(ownMatrix.IsRectilinear(), |
1048 | 0 | "Non-rectilinear transform will break the following logic"); |
1049 | 0 |
|
1050 | 0 | // The nsDisplayTransform code will apply this transform to our frame, |
1051 | 0 | // including to our frame position. We don't want our frame position to |
1052 | 0 | // be scaled though, so we need to correct for that in the transform. |
1053 | 0 | // XXX Yeah, this is a bit hacky. |
1054 | 0 | CSSPoint pos = CSSPixel::FromAppUnits(aFrame->GetPosition()); |
1055 | 0 | CSSPoint scaledPos = CSSPoint(ownMatrix._11 * pos.x, ownMatrix._22 * pos.y); |
1056 | 0 | CSSPoint deltaPos = scaledPos - pos; |
1057 | 0 | ownMatrix *= gfxMatrix::Translation(-deltaPos.x, -deltaPos.y); |
1058 | 0 | } |
1059 | 0 |
|
1060 | 0 | return gfx::ToMatrix(ownMatrix); |
1061 | 0 | } |
1062 | | |
1063 | | // We want this frame to be a reference frame. An easy way to achieve that is |
1064 | | // to always return true from this method, even for identity transforms. |
1065 | | // This frame being a reference frame ensures that the offset between this |
1066 | | // <svg> element and the parent reference frame is completely absorbed by the |
1067 | | // nsDisplayTransform that's created for this frame, and that this offset does |
1068 | | // not affect our descendants' transforms. Consequently, if the <svg> element |
1069 | | // moves, e.g. during scrolling, the transform matrices of our contents are |
1070 | | // unaffected. This simplifies invalidation. |
1071 | | bool |
1072 | | nsSVGOuterSVGAnonChildFrame::IsSVGTransformed(Matrix* aOwnTransform, |
1073 | | Matrix* aFromParentTransform) const |
1074 | 0 | { |
1075 | 0 | if (aOwnTransform) { |
1076 | 0 | *aOwnTransform = ComputeOuterSVGAnonChildFrameTransform(this); |
1077 | 0 | } |
1078 | 0 |
|
1079 | 0 | return true; |
1080 | 0 | } |