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