/src/mozilla-central/layout/svg/nsSVGForeignObjectFrame.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 "nsSVGForeignObjectFrame.h" |
9 | | |
10 | | // Keep others in (case-insensitive) order: |
11 | | #include "ImgDrawResult.h" |
12 | | #include "gfxContext.h" |
13 | | #include "nsDisplayList.h" |
14 | | #include "nsGkAtoms.h" |
15 | | #include "nsNameSpaceManager.h" |
16 | | #include "nsLayoutUtils.h" |
17 | | #include "nsRegion.h" |
18 | | #include "nsSVGContainerFrame.h" |
19 | | #include "SVGObserverUtils.h" |
20 | | #include "mozilla/dom/SVGForeignObjectElement.h" |
21 | | #include "nsSVGIntegrationUtils.h" |
22 | | #include "nsSVGOuterSVGFrame.h" |
23 | | #include "nsSVGUtils.h" |
24 | | #include "mozilla/AutoRestore.h" |
25 | | |
26 | | using namespace mozilla; |
27 | | using namespace mozilla::dom; |
28 | | using namespace mozilla::image; |
29 | | |
30 | | //---------------------------------------------------------------------- |
31 | | // Implementation |
32 | | |
33 | | nsContainerFrame* |
34 | | NS_NewSVGForeignObjectFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle) |
35 | 0 | { |
36 | 0 | return new (aPresShell) nsSVGForeignObjectFrame(aStyle); |
37 | 0 | } |
38 | | |
39 | | NS_IMPL_FRAMEARENA_HELPERS(nsSVGForeignObjectFrame) |
40 | | |
41 | | nsSVGForeignObjectFrame::nsSVGForeignObjectFrame(ComputedStyle* aStyle) |
42 | | : nsContainerFrame(aStyle, kClassID) |
43 | | , mInReflow(false) |
44 | 0 | { |
45 | 0 | AddStateBits(NS_FRAME_REFLOW_ROOT | NS_FRAME_MAY_BE_TRANSFORMED | |
46 | 0 | NS_FRAME_SVG_LAYOUT); |
47 | 0 | } |
48 | | |
49 | | //---------------------------------------------------------------------- |
50 | | // nsIFrame methods |
51 | | |
52 | 0 | NS_QUERYFRAME_HEAD(nsSVGForeignObjectFrame) |
53 | 0 | NS_QUERYFRAME_ENTRY(nsSVGDisplayableFrame) |
54 | 0 | NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) |
55 | | |
56 | | void |
57 | | nsSVGForeignObjectFrame::Init(nsIContent* aContent, |
58 | | nsContainerFrame* aParent, |
59 | | nsIFrame* aPrevInFlow) |
60 | 0 | { |
61 | 0 | NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::foreignObject), |
62 | 0 | "Content is not an SVG foreignObject!"); |
63 | 0 |
|
64 | 0 | nsContainerFrame::Init(aContent, aParent, aPrevInFlow); |
65 | 0 | AddStateBits(aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD); |
66 | 0 | AddStateBits(NS_FRAME_FONT_INFLATION_CONTAINER | |
67 | 0 | NS_FRAME_FONT_INFLATION_FLOW_ROOT); |
68 | 0 | if (!(mState & NS_FRAME_IS_NONDISPLAY)) { |
69 | 0 | nsSVGUtils::GetOuterSVGFrame(this)->RegisterForeignObject(this); |
70 | 0 | } |
71 | 0 | } |
72 | | |
73 | | void nsSVGForeignObjectFrame::DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData) |
74 | 0 | { |
75 | 0 | // Only unregister if we registered in the first place: |
76 | 0 | if (!(mState & NS_FRAME_IS_NONDISPLAY)) { |
77 | 0 | nsSVGUtils::GetOuterSVGFrame(this)->UnregisterForeignObject(this); |
78 | 0 | } |
79 | 0 | nsContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData); |
80 | 0 | } |
81 | | |
82 | | nsresult |
83 | | nsSVGForeignObjectFrame::AttributeChanged(int32_t aNameSpaceID, |
84 | | nsAtom *aAttribute, |
85 | | int32_t aModType) |
86 | 0 | { |
87 | 0 | if (aNameSpaceID == kNameSpaceID_None) { |
88 | 0 | if (aAttribute == nsGkAtoms::width || |
89 | 0 | aAttribute == nsGkAtoms::height) { |
90 | 0 | nsLayoutUtils::PostRestyleEvent( |
91 | 0 | mContent->AsElement(), nsRestyleHint(0), |
92 | 0 | nsChangeHint_InvalidateRenderingObservers); |
93 | 0 | nsSVGUtils::ScheduleReflowSVG(this); |
94 | 0 | // XXXjwatt: why mark intrinsic widths dirty? can't we just use eResize? |
95 | 0 | RequestReflow(nsIPresShell::eStyleChange); |
96 | 0 | } else if (aAttribute == nsGkAtoms::x || |
97 | 0 | aAttribute == nsGkAtoms::y) { |
98 | 0 | // make sure our cached transform matrix gets (lazily) updated |
99 | 0 | mCanvasTM = nullptr; |
100 | 0 | nsLayoutUtils::PostRestyleEvent( |
101 | 0 | mContent->AsElement(), nsRestyleHint(0), |
102 | 0 | nsChangeHint_InvalidateRenderingObservers); |
103 | 0 | nsSVGUtils::ScheduleReflowSVG(this); |
104 | 0 | } else if (aAttribute == nsGkAtoms::transform) { |
105 | 0 | // We don't invalidate for transform changes (the layers code does that). |
106 | 0 | // Also note that SVGTransformableElement::GetAttributeChangeHint will |
107 | 0 | // return nsChangeHint_UpdateOverflow for "transform" attribute changes |
108 | 0 | // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call. |
109 | 0 | mCanvasTM = nullptr; |
110 | 0 | } else if (aAttribute == nsGkAtoms::viewBox || |
111 | 0 | aAttribute == nsGkAtoms::preserveAspectRatio) { |
112 | 0 | nsLayoutUtils::PostRestyleEvent( |
113 | 0 | mContent->AsElement(), nsRestyleHint(0), |
114 | 0 | nsChangeHint_InvalidateRenderingObservers); |
115 | 0 | } |
116 | 0 | } |
117 | 0 |
|
118 | 0 | return NS_OK; |
119 | 0 | } |
120 | | |
121 | | void |
122 | | nsSVGForeignObjectFrame::Reflow(nsPresContext* aPresContext, |
123 | | ReflowOutput& aDesiredSize, |
124 | | const ReflowInput& aReflowInput, |
125 | | nsReflowStatus& aStatus) |
126 | 0 | { |
127 | 0 | MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); |
128 | 0 | MOZ_ASSERT(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY), |
129 | 0 | "Should not have been called"); |
130 | 0 |
|
131 | 0 | // Only InvalidateAndScheduleBoundsUpdate marks us with NS_FRAME_IS_DIRTY, |
132 | 0 | // so if that bit is still set we still have a resize pending. If we hit |
133 | 0 | // this assertion, then we should get the presShell to skip reflow roots |
134 | 0 | // that have a dirty parent since a reflow is going to come via the |
135 | 0 | // reflow root's parent anyway. |
136 | 0 | NS_ASSERTION(!(GetStateBits() & NS_FRAME_IS_DIRTY), |
137 | 0 | "Reflowing while a resize is pending is wasteful"); |
138 | 0 |
|
139 | 0 | // ReflowSVG makes sure mRect is up to date before we're called. |
140 | 0 |
|
141 | 0 | NS_ASSERTION(!aReflowInput.mParentReflowInput, |
142 | 0 | "should only get reflow from being reflow root"); |
143 | 0 | NS_ASSERTION(aReflowInput.ComputedWidth() == GetSize().width && |
144 | 0 | aReflowInput.ComputedHeight() == GetSize().height, |
145 | 0 | "reflow roots should be reflowed at existing size and " |
146 | 0 | "svg.css should ensure we have no padding/border/margin"); |
147 | 0 |
|
148 | 0 | DoReflow(); |
149 | 0 |
|
150 | 0 | WritingMode wm = aReflowInput.GetWritingMode(); |
151 | 0 | LogicalSize finalSize(wm, aReflowInput.ComputedISize(), |
152 | 0 | aReflowInput.ComputedBSize()); |
153 | 0 | aDesiredSize.SetSize(wm, finalSize); |
154 | 0 | aDesiredSize.SetOverflowAreasToDesiredBounds(); |
155 | 0 | } |
156 | | |
157 | | void |
158 | | nsSVGForeignObjectFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, |
159 | | const nsDisplayListSet& aLists) |
160 | 0 | { |
161 | 0 | if (!static_cast<const nsSVGElement*>(GetContent())->HasValidDimensions()) { |
162 | 0 | return; |
163 | 0 | } |
164 | 0 | nsDisplayList newList; |
165 | 0 | nsDisplayListSet set(&newList, &newList, &newList, |
166 | 0 | &newList, &newList, &newList); |
167 | 0 | DisplayOutline(aBuilder, set); |
168 | 0 | BuildDisplayListForNonBlockChildren(aBuilder, set); |
169 | 0 | aLists.Content()->AppendToTop(MakeDisplayItem<nsDisplayForeignObject>(aBuilder, this, &newList)); |
170 | 0 | } |
171 | | |
172 | | bool |
173 | | nsSVGForeignObjectFrame::IsSVGTransformed(Matrix *aOwnTransform, |
174 | | Matrix *aFromParentTransform) const |
175 | 0 | { |
176 | 0 | bool foundTransform = false; |
177 | 0 |
|
178 | 0 | // Check if our parent has children-only transforms: |
179 | 0 | nsIFrame *parent = GetParent(); |
180 | 0 | if (parent && |
181 | 0 | parent->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) { |
182 | 0 | foundTransform = static_cast<nsSVGContainerFrame*>(parent)-> |
183 | 0 | HasChildrenOnlyTransform(aFromParentTransform); |
184 | 0 | } |
185 | 0 |
|
186 | 0 | nsSVGElement *content = static_cast<nsSVGElement*>(GetContent()); |
187 | 0 | nsSVGAnimatedTransformList* transformList = |
188 | 0 | content->GetAnimatedTransformList(); |
189 | 0 | if ((transformList && transformList->HasTransform()) || |
190 | 0 | content->GetAnimateMotionTransform()) { |
191 | 0 | if (aOwnTransform) { |
192 | 0 | *aOwnTransform = gfx::ToMatrix(content->PrependLocalTransformsTo( |
193 | 0 | gfxMatrix(), |
194 | 0 | eUserSpaceToParent)); |
195 | 0 | } |
196 | 0 | foundTransform = true; |
197 | 0 | } |
198 | 0 | return foundTransform; |
199 | 0 | } |
200 | | |
201 | | void |
202 | | nsSVGForeignObjectFrame::PaintSVG(gfxContext& aContext, |
203 | | const gfxMatrix& aTransform, |
204 | | imgDrawingParams& aImgParams, |
205 | | const nsIntRect* aDirtyRect) |
206 | 0 | { |
207 | 0 | NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() || |
208 | 0 | (mState & NS_FRAME_IS_NONDISPLAY), |
209 | 0 | "If display lists are enabled, only painting of non-display " |
210 | 0 | "SVG should take this code path"); |
211 | 0 |
|
212 | 0 | if (IsDisabled()) { |
213 | 0 | return; |
214 | 0 | } |
215 | 0 | |
216 | 0 | nsIFrame* kid = PrincipalChildList().FirstChild(); |
217 | 0 | if (!kid) { |
218 | 0 | return; |
219 | 0 | } |
220 | 0 | |
221 | 0 | if (aTransform.IsSingular()) { |
222 | 0 | NS_WARNING("Can't render foreignObject element!"); |
223 | 0 | return; |
224 | 0 | } |
225 | 0 |
|
226 | 0 | nsRect kidDirtyRect = kid->GetVisualOverflowRect(); |
227 | 0 |
|
228 | 0 | /* Check if we need to draw anything. */ |
229 | 0 | if (aDirtyRect) { |
230 | 0 | NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() || |
231 | 0 | (mState & NS_FRAME_IS_NONDISPLAY), |
232 | 0 | "Display lists handle dirty rect intersection test"); |
233 | 0 | // Transform the dirty rect into app units in our userspace. |
234 | 0 | gfxMatrix invmatrix = aTransform; |
235 | 0 | DebugOnly<bool> ok = invmatrix.Invert(); |
236 | 0 | NS_ASSERTION(ok, "inverse of non-singular matrix should be non-singular"); |
237 | 0 |
|
238 | 0 | gfxRect transDirtyRect = gfxRect(aDirtyRect->x, aDirtyRect->y, |
239 | 0 | aDirtyRect->width, aDirtyRect->height); |
240 | 0 | transDirtyRect = invmatrix.TransformBounds(transDirtyRect); |
241 | 0 |
|
242 | 0 | kidDirtyRect.IntersectRect(kidDirtyRect, |
243 | 0 | nsLayoutUtils::RoundGfxRectToAppRect(transDirtyRect, |
244 | 0 | AppUnitsPerCSSPixel())); |
245 | 0 |
|
246 | 0 | // XXX after bug 614732 is fixed, we will compare mRect with aDirtyRect, |
247 | 0 | // not with kidDirtyRect. I.e. |
248 | 0 | // int32_t appUnitsPerDevPx = PresContext()->AppUnitsPerDevPixel(); |
249 | 0 | // mRect.ToOutsidePixels(appUnitsPerDevPx).Intersects(*aDirtyRect) |
250 | 0 | if (kidDirtyRect.IsEmpty()) |
251 | 0 | return; |
252 | 0 | } |
253 | 0 | |
254 | 0 | aContext.Save(); |
255 | 0 |
|
256 | 0 | if (StyleDisplay()->IsScrollableOverflow()) { |
257 | 0 | float x, y, width, height; |
258 | 0 | static_cast<nsSVGElement*>(GetContent())-> |
259 | 0 | GetAnimatedLengthValues(&x, &y, &width, &height, nullptr); |
260 | 0 |
|
261 | 0 | gfxRect clipRect = |
262 | 0 | nsSVGUtils::GetClipRectForFrame(this, 0.0f, 0.0f, width, height); |
263 | 0 | nsSVGUtils::SetClipRect(&aContext, aTransform, clipRect); |
264 | 0 | } |
265 | 0 |
|
266 | 0 | // SVG paints in CSS px, but normally frames paint in dev pixels. Here we |
267 | 0 | // multiply a CSS-px-to-dev-pixel factor onto aTransform so our children |
268 | 0 | // paint correctly. |
269 | 0 | float cssPxPerDevPx = PresContext()-> |
270 | 0 | AppUnitsToFloatCSSPixels(PresContext()->AppUnitsPerDevPixel()); |
271 | 0 | gfxMatrix canvasTMForChildren = aTransform; |
272 | 0 | canvasTMForChildren.PreScale(cssPxPerDevPx, cssPxPerDevPx); |
273 | 0 |
|
274 | 0 | aContext.Multiply(canvasTMForChildren); |
275 | 0 |
|
276 | 0 | using PaintFrameFlags = nsLayoutUtils::PaintFrameFlags; |
277 | 0 | PaintFrameFlags flags = PaintFrameFlags::PAINT_IN_TRANSFORM; |
278 | 0 | if (SVGAutoRenderState::IsPaintingToWindow(aContext.GetDrawTarget())) { |
279 | 0 | flags |= PaintFrameFlags::PAINT_TO_WINDOW; |
280 | 0 | } |
281 | 0 | if (aImgParams.imageFlags & imgIContainer::FLAG_SYNC_DECODE) { |
282 | 0 | flags |= PaintFrameFlags::PAINT_SYNC_DECODE_IMAGES; |
283 | 0 | } |
284 | 0 | Unused << nsLayoutUtils::PaintFrame(&aContext, kid, nsRegion(kidDirtyRect), |
285 | 0 | NS_RGBA(0,0,0,0), |
286 | 0 | nsDisplayListBuilderMode::PAINTING, |
287 | 0 | flags); |
288 | 0 |
|
289 | 0 | aContext.Restore(); |
290 | 0 | } |
291 | | |
292 | | nsIFrame* |
293 | | nsSVGForeignObjectFrame::GetFrameForPoint(const gfxPoint& aPoint) |
294 | 0 | { |
295 | 0 | NS_ASSERTION(!NS_SVGDisplayListHitTestingEnabled() || |
296 | 0 | (mState & NS_FRAME_IS_NONDISPLAY), |
297 | 0 | "If display lists are enabled, only hit-testing of a " |
298 | 0 | "clipPath's contents should take this code path"); |
299 | 0 |
|
300 | 0 | if (IsDisabled() || (GetStateBits() & NS_FRAME_IS_NONDISPLAY)) |
301 | 0 | return nullptr; |
302 | 0 | |
303 | 0 | nsIFrame* kid = PrincipalChildList().FirstChild(); |
304 | 0 | if (!kid) |
305 | 0 | return nullptr; |
306 | 0 | |
307 | 0 | float x, y, width, height; |
308 | 0 | static_cast<nsSVGElement*>(GetContent())-> |
309 | 0 | GetAnimatedLengthValues(&x, &y, &width, &height, nullptr); |
310 | 0 |
|
311 | 0 | if (!gfxRect(x, y, width, height).Contains(aPoint) || |
312 | 0 | !nsSVGUtils::HitTestClip(this, aPoint)) { |
313 | 0 | return nullptr; |
314 | 0 | } |
315 | 0 | |
316 | 0 | // Convert the point to app units relative to the top-left corner of the |
317 | 0 | // viewport that's established by the foreignObject element: |
318 | 0 | |
319 | 0 | gfxPoint pt = (aPoint + gfxPoint(x, y)) * AppUnitsPerCSSPixel(); |
320 | 0 | nsPoint point = nsPoint(NSToIntRound(pt.x), NSToIntRound(pt.y)); |
321 | 0 |
|
322 | 0 | return nsLayoutUtils::GetFrameForPoint(kid, point); |
323 | 0 | } |
324 | | |
325 | | void |
326 | | nsSVGForeignObjectFrame::ReflowSVG() |
327 | 0 | { |
328 | 0 | NS_ASSERTION(nsSVGUtils::OuterSVGIsCallingReflowSVG(this), |
329 | 0 | "This call is probably a wasteful mistake"); |
330 | 0 |
|
331 | 0 | MOZ_ASSERT(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY), |
332 | 0 | "ReflowSVG mechanism not designed for this"); |
333 | 0 |
|
334 | 0 | if (!nsSVGUtils::NeedsReflowSVG(this)) { |
335 | 0 | return; |
336 | 0 | } |
337 | 0 | |
338 | 0 | // We update mRect before the DoReflow call so that DoReflow uses the |
339 | 0 | // correct dimensions: |
340 | 0 | |
341 | 0 | float x, y, w, h; |
342 | 0 | static_cast<SVGForeignObjectElement*>(GetContent())-> |
343 | 0 | GetAnimatedLengthValues(&x, &y, &w, &h, nullptr); |
344 | 0 |
|
345 | 0 | // If mRect's width or height are negative, reflow blows up! We must clamp! |
346 | 0 | if (w < 0.0f) w = 0.0f; |
347 | 0 | if (h < 0.0f) h = 0.0f; |
348 | 0 |
|
349 | 0 | mRect = nsLayoutUtils::RoundGfxRectToAppRect( |
350 | 0 | gfxRect(x, y, w, h), |
351 | 0 | AppUnitsPerCSSPixel()); |
352 | 0 |
|
353 | 0 | // Fully mark our kid dirty so that it gets resized if necessary |
354 | 0 | // (NS_FRAME_HAS_DIRTY_CHILDREN isn't enough in that case): |
355 | 0 | nsIFrame* kid = PrincipalChildList().FirstChild(); |
356 | 0 | kid->AddStateBits(NS_FRAME_IS_DIRTY); |
357 | 0 |
|
358 | 0 | // Make sure to not allow interrupts if we're not being reflown as a root: |
359 | 0 | nsPresContext::InterruptPreventer noInterrupts(PresContext()); |
360 | 0 |
|
361 | 0 | DoReflow(); |
362 | 0 |
|
363 | 0 | if (mState & NS_FRAME_FIRST_REFLOW) { |
364 | 0 | // Make sure we have our filter property (if any) before calling |
365 | 0 | // FinishAndStoreOverflow (subsequent filter changes are handled off |
366 | 0 | // nsChangeHint_UpdateEffects): |
367 | 0 | SVGObserverUtils::UpdateEffects(this); |
368 | 0 | } |
369 | 0 |
|
370 | 0 | // If we have a filter, we need to invalidate ourselves because filter |
371 | 0 | // output can change even if none of our descendants need repainting. |
372 | 0 | if (StyleEffects()->HasFilters()) { |
373 | 0 | InvalidateFrame(); |
374 | 0 | } |
375 | 0 |
|
376 | 0 | // TODO: once we support |overflow:visible| on foreignObject, then we will |
377 | 0 | // need to take account of our descendants here. |
378 | 0 | nsRect overflow = nsRect(nsPoint(0,0), mRect.Size()); |
379 | 0 | nsOverflowAreas overflowAreas(overflow, overflow); |
380 | 0 | FinishAndStoreOverflow(overflowAreas, mRect.Size()); |
381 | 0 |
|
382 | 0 | // Now unset the various reflow bits: |
383 | 0 | RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | |
384 | 0 | NS_FRAME_HAS_DIRTY_CHILDREN); |
385 | 0 | } |
386 | | |
387 | | void |
388 | | nsSVGForeignObjectFrame::NotifySVGChanged(uint32_t aFlags) |
389 | 0 | { |
390 | 0 | MOZ_ASSERT(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED), |
391 | 0 | "Invalidation logic may need adjusting"); |
392 | 0 |
|
393 | 0 | bool needNewBounds = false; // i.e. mRect or visual overflow rect |
394 | 0 | bool needReflow = false; |
395 | 0 | bool needNewCanvasTM = false; |
396 | 0 |
|
397 | 0 | if (aFlags & COORD_CONTEXT_CHANGED) { |
398 | 0 | SVGForeignObjectElement *fO = |
399 | 0 | static_cast<SVGForeignObjectElement*>(GetContent()); |
400 | 0 | // Coordinate context changes affect mCanvasTM if we have a |
401 | 0 | // percentage 'x' or 'y' |
402 | 0 | if (fO->mLengthAttributes[SVGForeignObjectElement::ATTR_X].IsPercentage() || |
403 | 0 | fO->mLengthAttributes[SVGForeignObjectElement::ATTR_Y].IsPercentage()) { |
404 | 0 | needNewBounds = true; |
405 | 0 | needNewCanvasTM = true; |
406 | 0 | } |
407 | 0 | // Our coordinate context's width/height has changed. If we have a |
408 | 0 | // percentage width/height our dimensions will change so we must reflow. |
409 | 0 | if (fO->mLengthAttributes[SVGForeignObjectElement::ATTR_WIDTH].IsPercentage() || |
410 | 0 | fO->mLengthAttributes[SVGForeignObjectElement::ATTR_HEIGHT].IsPercentage()) { |
411 | 0 | needNewBounds = true; |
412 | 0 | needReflow = true; |
413 | 0 | } |
414 | 0 | } |
415 | 0 |
|
416 | 0 | if (aFlags & TRANSFORM_CHANGED) { |
417 | 0 | if (mCanvasTM && mCanvasTM->IsSingular()) { |
418 | 0 | needNewBounds = true; // old bounds are bogus |
419 | 0 | } |
420 | 0 | needNewCanvasTM = true; |
421 | 0 | // In an ideal world we would reflow when our CTM changes. This is because |
422 | 0 | // glyph metrics do not necessarily scale uniformly with change in scale |
423 | 0 | // and, as a result, CTM changes may require text to break at different |
424 | 0 | // points. The problem would be how to keep performance acceptable when |
425 | 0 | // e.g. the transform of an ancestor is animated. |
426 | 0 | // We also seem to get some sort of infinite loop post bug 421584 if we |
427 | 0 | // reflow. |
428 | 0 | } |
429 | 0 |
|
430 | 0 | if (needNewBounds) { |
431 | 0 | // Ancestor changes can't affect how we render from the perspective of |
432 | 0 | // any rendering observers that we may have, so we don't need to |
433 | 0 | // invalidate them. We also don't need to invalidate ourself, since our |
434 | 0 | // changed ancestor will have invalidated its entire area, which includes |
435 | 0 | // our area. |
436 | 0 | nsSVGUtils::ScheduleReflowSVG(this); |
437 | 0 | } |
438 | 0 |
|
439 | 0 | // If we're called while the PresShell is handling reflow events then we |
440 | 0 | // must have been called as a result of the NotifyViewportChange() call in |
441 | 0 | // our nsSVGOuterSVGFrame's Reflow() method. We must not call RequestReflow |
442 | 0 | // at this point (i.e. during reflow) because it could confuse the |
443 | 0 | // PresShell and prevent it from reflowing us properly in future. Besides |
444 | 0 | // that, nsSVGOuterSVGFrame::DidReflow will take care of reflowing us |
445 | 0 | // synchronously, so there's no need. |
446 | 0 | if (needReflow && !PresShell()->IsReflowLocked()) { |
447 | 0 | RequestReflow(nsIPresShell::eResize); |
448 | 0 | } |
449 | 0 |
|
450 | 0 | if (needNewCanvasTM) { |
451 | 0 | // Do this after calling InvalidateAndScheduleBoundsUpdate in case we |
452 | 0 | // change the code and it needs to use it. |
453 | 0 | mCanvasTM = nullptr; |
454 | 0 | } |
455 | 0 | } |
456 | | |
457 | | SVGBBox |
458 | | nsSVGForeignObjectFrame::GetBBoxContribution(const Matrix &aToBBoxUserspace, |
459 | | uint32_t aFlags) |
460 | 0 | { |
461 | 0 | SVGForeignObjectElement *content = |
462 | 0 | static_cast<SVGForeignObjectElement*>(GetContent()); |
463 | 0 |
|
464 | 0 | float x, y, w, h; |
465 | 0 | content->GetAnimatedLengthValues(&x, &y, &w, &h, nullptr); |
466 | 0 |
|
467 | 0 | if (w < 0.0f) w = 0.0f; |
468 | 0 | if (h < 0.0f) h = 0.0f; |
469 | 0 |
|
470 | 0 | if (aToBBoxUserspace.IsSingular()) { |
471 | 0 | // XXX ReportToConsole |
472 | 0 | return SVGBBox(); |
473 | 0 | } |
474 | 0 | return aToBBoxUserspace.TransformBounds(gfx::Rect(0.0, 0.0, w, h)); |
475 | 0 | } |
476 | | |
477 | | //---------------------------------------------------------------------- |
478 | | |
479 | | gfxMatrix |
480 | | nsSVGForeignObjectFrame::GetCanvasTM() |
481 | 0 | { |
482 | 0 | if (!mCanvasTM) { |
483 | 0 | NS_ASSERTION(GetParent(), "null parent"); |
484 | 0 |
|
485 | 0 | nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(GetParent()); |
486 | 0 | SVGForeignObjectElement *content = |
487 | 0 | static_cast<SVGForeignObjectElement*>(GetContent()); |
488 | 0 |
|
489 | 0 | gfxMatrix tm = content->PrependLocalTransformsTo(parent->GetCanvasTM()); |
490 | 0 |
|
491 | 0 | mCanvasTM = new gfxMatrix(tm); |
492 | 0 | } |
493 | 0 | return *mCanvasTM; |
494 | 0 | } |
495 | | |
496 | | //---------------------------------------------------------------------- |
497 | | // Implementation helpers |
498 | | |
499 | | void nsSVGForeignObjectFrame::RequestReflow(nsIPresShell::IntrinsicDirty aType) |
500 | 0 | { |
501 | 0 | if (GetStateBits() & NS_FRAME_FIRST_REFLOW) |
502 | 0 | // If we haven't had a ReflowSVG() yet, nothing to do. |
503 | 0 | return; |
504 | 0 | |
505 | 0 | nsIFrame* kid = PrincipalChildList().FirstChild(); |
506 | 0 | if (!kid) |
507 | 0 | return; |
508 | 0 | |
509 | 0 | PresShell()->FrameNeedsReflow(kid, aType, NS_FRAME_IS_DIRTY); |
510 | 0 | } |
511 | | |
512 | | void |
513 | | nsSVGForeignObjectFrame::DoReflow() |
514 | 0 | { |
515 | 0 | MarkInReflow(); |
516 | 0 | // Skip reflow if we're zero-sized, unless this is our first reflow. |
517 | 0 | if (IsDisabled() && |
518 | 0 | !(GetStateBits() & NS_FRAME_FIRST_REFLOW)) |
519 | 0 | return; |
520 | 0 | |
521 | 0 | nsPresContext *presContext = PresContext(); |
522 | 0 | nsIFrame* kid = PrincipalChildList().FirstChild(); |
523 | 0 | if (!kid) |
524 | 0 | return; |
525 | 0 | |
526 | 0 | // initiate a synchronous reflow here and now: |
527 | 0 | RefPtr<gfxContext> renderingContext = |
528 | 0 | presContext->PresShell()->CreateReferenceRenderingContext(); |
529 | 0 |
|
530 | 0 | mInReflow = true; |
531 | 0 |
|
532 | 0 | WritingMode wm = kid->GetWritingMode(); |
533 | 0 | ReflowInput reflowInput(presContext, kid, |
534 | 0 | renderingContext, |
535 | 0 | LogicalSize(wm, ISize(wm), |
536 | 0 | NS_UNCONSTRAINEDSIZE)); |
537 | 0 | ReflowOutput desiredSize(reflowInput); |
538 | 0 | nsReflowStatus status; |
539 | 0 |
|
540 | 0 | // We don't use mRect.height above because that tells the child to do |
541 | 0 | // page/column breaking at that height. |
542 | 0 | NS_ASSERTION(reflowInput.ComputedPhysicalBorderPadding() == nsMargin(0, 0, 0, 0) && |
543 | 0 | reflowInput.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0), |
544 | 0 | "style system should ensure that :-moz-svg-foreign-content " |
545 | 0 | "does not get styled"); |
546 | 0 | NS_ASSERTION(reflowInput.ComputedISize() == ISize(wm), |
547 | 0 | "reflow state made child wrong size"); |
548 | 0 | reflowInput.SetComputedBSize(BSize(wm)); |
549 | 0 |
|
550 | 0 | ReflowChild(kid, presContext, desiredSize, reflowInput, 0, 0, |
551 | 0 | NS_FRAME_NO_MOVE_FRAME, status); |
552 | 0 | NS_ASSERTION(mRect.width == desiredSize.Width() && |
553 | 0 | mRect.height == desiredSize.Height(), "unexpected size"); |
554 | 0 | FinishReflowChild(kid, presContext, desiredSize, &reflowInput, 0, 0, |
555 | 0 | NS_FRAME_NO_MOVE_FRAME); |
556 | 0 |
|
557 | 0 | mInReflow = false; |
558 | 0 | } |
559 | | |
560 | | nsRect |
561 | | nsSVGForeignObjectFrame::GetInvalidRegion() |
562 | 0 | { |
563 | 0 | MOZ_ASSERT(!NS_SVGDisplayListPaintingEnabled(), |
564 | 0 | "Only called by nsDisplayOuterSVG code"); |
565 | 0 |
|
566 | 0 | nsIFrame* kid = PrincipalChildList().FirstChild(); |
567 | 0 | if (kid->HasInvalidFrameInSubtree()) { |
568 | 0 | gfxRect r(mRect.x, mRect.y, mRect.width, mRect.height); |
569 | 0 | r.Scale(1.0 / AppUnitsPerCSSPixel()); |
570 | 0 | nsRect rect = nsSVGUtils::ToCanvasBounds(r, GetCanvasTM(), PresContext()); |
571 | 0 | rect = nsSVGUtils::GetPostFilterVisualOverflowRect(this, rect); |
572 | 0 | return rect; |
573 | 0 | } |
574 | 0 | return nsRect(); |
575 | 0 | } |
576 | | |
577 | | void |
578 | | nsSVGForeignObjectFrame::AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) |
579 | 0 | { |
580 | 0 | MOZ_ASSERT(PrincipalChildList().FirstChild(), "Must have our anon box"); |
581 | 0 | aResult.AppendElement(OwnedAnonBox(PrincipalChildList().FirstChild())); |
582 | 0 | } |