Bug 867089 - Validate the playbackRate before using it. r=ehsan
[gecko.git] / layout / svg / nsSVGOuterSVGFrame.cpp
blobf15c883f287801a90d53f8466ea8346c3f77a45c
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/. */
6 // Main header first:
7 #include "nsSVGOuterSVGFrame.h"
9 // Keep others in (case-insensitive) order:
10 #include "gfxMatrix.h"
11 #include "nsDisplayList.h"
12 #include "nsIDocument.h"
13 #include "nsIDOMWindow.h"
14 #include "nsIInterfaceRequestorUtils.h"
15 #include "nsIObjectLoadingContent.h"
16 #include "nsRenderingContext.h"
17 #include "nsStubMutationObserver.h"
18 #include "nsSVGIntegrationUtils.h"
19 #include "nsSVGForeignObjectFrame.h"
20 #include "mozilla/dom/SVGSVGElement.h"
21 #include "nsSVGTextFrame.h"
22 #include "mozilla/dom/SVGViewElement.h"
23 #include "nsSubDocumentFrame.h"
25 using namespace mozilla;
26 using namespace mozilla::dom;
28 class nsSVGMutationObserver : public nsStubMutationObserver
30 public:
31 // nsIMutationObserver interface
32 NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
34 // nsISupports interface:
35 NS_IMETHOD QueryInterface(const nsIID& aIID, void** aInstancePtr);
36 private:
37 NS_IMETHOD_(nsrefcnt) AddRef() { return 1; }
38 NS_IMETHOD_(nsrefcnt) Release() { return 1; }
40 static void UpdateTextFragmentTrees(nsIFrame *aFrame);
43 //----------------------------------------------------------------------
44 // nsISupports methods
46 NS_INTERFACE_MAP_BEGIN(nsSVGMutationObserver)
47 NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
48 NS_INTERFACE_MAP_END
50 static nsSVGMutationObserver sSVGMutationObserver;
52 //----------------------------------------------------------------------
53 // nsIMutationObserver methods
55 void
56 nsSVGMutationObserver::AttributeChanged(nsIDocument* aDocument,
57 Element* aElement,
58 int32_t aNameSpaceID,
59 nsIAtom* aAttribute,
60 int32_t aModType)
62 if (aNameSpaceID != kNameSpaceID_XML || aAttribute != nsGkAtoms::space) {
63 return;
66 nsIFrame* frame = aElement->GetPrimaryFrame();
67 if (!frame) {
68 return;
71 // is the content a child of a text element
72 nsSVGTextContainerFrame* containerFrame = do_QueryFrame(frame);
73 if (containerFrame) {
74 containerFrame->NotifyGlyphMetricsChange();
75 return;
77 // if not, are there text elements amongst its descendents
78 UpdateTextFragmentTrees(frame);
81 //----------------------------------------------------------------------
82 // Implementation helpers
84 void
85 nsSVGOuterSVGFrame::RegisterForeignObject(nsSVGForeignObjectFrame* aFrame)
87 NS_ASSERTION(aFrame, "Who on earth is calling us?!");
89 if (!mForeignObjectHash.IsInitialized()) {
90 mForeignObjectHash.Init();
93 NS_ASSERTION(!mForeignObjectHash.GetEntry(aFrame),
94 "nsSVGForeignObjectFrame already registered!");
96 mForeignObjectHash.PutEntry(aFrame);
98 NS_ASSERTION(mForeignObjectHash.GetEntry(aFrame),
99 "Failed to register nsSVGForeignObjectFrame!");
102 void
103 nsSVGOuterSVGFrame::UnregisterForeignObject(nsSVGForeignObjectFrame* aFrame)
105 NS_ASSERTION(aFrame, "Who on earth is calling us?!");
106 NS_ASSERTION(mForeignObjectHash.GetEntry(aFrame),
107 "nsSVGForeignObjectFrame not in registry!");
108 return mForeignObjectHash.RemoveEntry(aFrame);
111 void
112 nsSVGMutationObserver::UpdateTextFragmentTrees(nsIFrame *aFrame)
114 nsIFrame* kid = aFrame->GetFirstPrincipalChild();
115 while (kid) {
116 if (kid->GetType() == nsGkAtoms::svgTextFrame) {
117 nsSVGTextFrame* textFrame = static_cast<nsSVGTextFrame*>(kid);
118 textFrame->NotifyGlyphMetricsChange();
119 } else {
120 UpdateTextFragmentTrees(kid);
122 kid = kid->GetNextSibling();
126 //----------------------------------------------------------------------
127 // Implementation
129 nsIFrame*
130 NS_NewSVGOuterSVGFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
132 return new (aPresShell) nsSVGOuterSVGFrame(aContext);
135 NS_IMPL_FRAMEARENA_HELPERS(nsSVGOuterSVGFrame)
137 nsSVGOuterSVGFrame::nsSVGOuterSVGFrame(nsStyleContext* aContext)
138 : nsSVGOuterSVGFrameBase(aContext)
139 , mFullZoom(aContext->PresContext()->GetFullZoom())
140 , mViewportInitialized(false)
141 , mIsRootContent(false)
143 // Outer-<svg> has CSS layout, so remove this bit:
144 RemoveStateBits(NS_FRAME_SVG_LAYOUT);
147 void
148 nsSVGOuterSVGFrame::Init(nsIContent* aContent,
149 nsIFrame* aParent,
150 nsIFrame* aPrevInFlow)
152 NS_ASSERTION(aContent->IsSVG(nsGkAtoms::svg),
153 "Content is not an SVG 'svg' element!");
155 AddStateBits(NS_STATE_IS_OUTER_SVG |
156 NS_FRAME_FONT_INFLATION_CONTAINER |
157 NS_FRAME_FONT_INFLATION_FLOW_ROOT);
159 // Check for conditional processing attributes here rather than in
160 // nsCSSFrameConstructor::FindSVGData because we want to avoid
161 // simply giving failing outer <svg> elements an nsSVGContainerFrame.
162 // We don't create other SVG frames if PassesConditionalProcessingTests
163 // returns false, but since we do create nsSVGOuterSVGFrame frames we
164 // prevent them from painting by [ab]use NS_STATE_SVG_NONDISPLAY_CHILD. The
165 // frame will be recreated via an nsChangeHint_ReconstructFrame restyle if
166 // the value returned by PassesConditionalProcessingTests changes.
167 SVGSVGElement *svg = static_cast<SVGSVGElement*>(aContent);
168 if (!svg->PassesConditionalProcessingTests()) {
169 AddStateBits(NS_STATE_SVG_NONDISPLAY_CHILD);
172 nsSVGOuterSVGFrameBase::Init(aContent, aParent, aPrevInFlow);
174 nsIDocument* doc = mContent->GetCurrentDoc();
175 if (doc) {
176 // we only care about our content's zoom and pan values if it's the root element
177 if (doc->GetRootElement() == mContent) {
178 mIsRootContent = true;
180 // sSVGMutationObserver has the same lifetime as the document so does
181 // not need to be removed
182 doc->AddMutationObserverUnlessExists(&sSVGMutationObserver);
186 //----------------------------------------------------------------------
187 // nsQueryFrame methods
189 NS_QUERYFRAME_HEAD(nsSVGOuterSVGFrame)
190 NS_QUERYFRAME_ENTRY(nsISVGSVGFrame)
191 NS_QUERYFRAME_TAIL_INHERITING(nsSVGOuterSVGFrameBase)
193 //----------------------------------------------------------------------
194 // nsIFrame methods
196 //----------------------------------------------------------------------
197 // reflowing
199 /* virtual */ nscoord
200 nsSVGOuterSVGFrame::GetMinWidth(nsRenderingContext *aRenderingContext)
202 nscoord result;
203 DISPLAY_MIN_WIDTH(this, result);
205 result = nscoord(0);
207 return result;
210 /* virtual */ nscoord
211 nsSVGOuterSVGFrame::GetPrefWidth(nsRenderingContext *aRenderingContext)
213 nscoord result;
214 DISPLAY_PREF_WIDTH(this, result);
216 SVGSVGElement *svg = static_cast<SVGSVGElement*>(mContent);
217 nsSVGLength2 &width = svg->mLengthAttributes[SVGSVGElement::ATTR_WIDTH];
219 if (width.IsPercentage()) {
220 // It looks like our containing block's width may depend on our width. In
221 // that case our behavior is undefined according to CSS 2.1 section 10.3.2,
222 // so return zero.
223 result = nscoord(0);
224 } else {
225 result = nsPresContext::CSSPixelsToAppUnits(width.GetAnimValue(svg));
226 if (result < 0) {
227 result = nscoord(0);
231 return result;
234 /* virtual */ nsIFrame::IntrinsicSize
235 nsSVGOuterSVGFrame::GetIntrinsicSize()
237 // XXXjwatt Note that here we want to return the CSS width/height if they're
238 // specified and we're embedded inside an nsIObjectLoadingContent.
240 IntrinsicSize intrinsicSize;
242 SVGSVGElement *content = static_cast<SVGSVGElement*>(mContent);
243 nsSVGLength2 &width = content->mLengthAttributes[SVGSVGElement::ATTR_WIDTH];
244 nsSVGLength2 &height = content->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT];
246 if (!width.IsPercentage()) {
247 nscoord val = nsPresContext::CSSPixelsToAppUnits(width.GetAnimValue(content));
248 if (val < 0) val = 0;
249 intrinsicSize.width.SetCoordValue(val);
252 if (!height.IsPercentage()) {
253 nscoord val = nsPresContext::CSSPixelsToAppUnits(height.GetAnimValue(content));
254 if (val < 0) val = 0;
255 intrinsicSize.height.SetCoordValue(val);
258 return intrinsicSize;
261 /* virtual */ nsSize
262 nsSVGOuterSVGFrame::GetIntrinsicRatio()
264 // We only have an intrinsic size/ratio if our width and height attributes
265 // are both specified and set to non-percentage values, or we have a viewBox
266 // rect: http://www.w3.org/TR/SVGMobile12/coords.html#IntrinsicSizing
268 SVGSVGElement *content = static_cast<SVGSVGElement*>(mContent);
269 nsSVGLength2 &width = content->mLengthAttributes[SVGSVGElement::ATTR_WIDTH];
270 nsSVGLength2 &height = content->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT];
272 if (!width.IsPercentage() && !height.IsPercentage()) {
273 nsSize ratio(NSToCoordRoundWithClamp(width.GetAnimValue(content)),
274 NSToCoordRoundWithClamp(height.GetAnimValue(content)));
275 if (ratio.width < 0) {
276 ratio.width = 0;
278 if (ratio.height < 0) {
279 ratio.height = 0;
281 return ratio;
284 SVGViewElement* viewElement = content->GetCurrentViewElement();
285 const nsSVGViewBoxRect* viewbox = nullptr;
287 // The logic here should match HasViewBox().
288 if (viewElement && viewElement->mViewBox.HasRect()) {
289 viewbox = &viewElement->mViewBox.GetAnimValue();
290 } else if (content->mViewBox.HasRect()) {
291 viewbox = &content->mViewBox.GetAnimValue();
294 if (viewbox) {
295 float viewBoxWidth = viewbox->width;
296 float viewBoxHeight = viewbox->height;
298 if (viewBoxWidth < 0.0f) {
299 viewBoxWidth = 0.0f;
301 if (viewBoxHeight < 0.0f) {
302 viewBoxHeight = 0.0f;
304 return nsSize(NSToCoordRoundWithClamp(viewBoxWidth),
305 NSToCoordRoundWithClamp(viewBoxHeight));
308 return nsSVGOuterSVGFrameBase::GetIntrinsicRatio();
311 /* virtual */ nsSize
312 nsSVGOuterSVGFrame::ComputeSize(nsRenderingContext *aRenderingContext,
313 nsSize aCBSize, nscoord aAvailableWidth,
314 nsSize aMargin, nsSize aBorder, nsSize aPadding,
315 uint32_t aFlags)
317 if (IsRootOfImage() || IsRootOfReplacedElementSubDoc()) {
318 // The embedding element has sized itself using the CSS replaced element
319 // sizing rules, using our intrinsic dimensions as necessary. The SVG spec
320 // says that the width and height of embedded SVG is overridden by the
321 // width and height of the embedding element, so we just need to size to
322 // the viewport that the embedding element has established for us.
323 return aCBSize;
326 nsSize cbSize = aCBSize;
327 IntrinsicSize intrinsicSize = GetIntrinsicSize();
329 if (!mContent->GetParent()) {
330 // We're the root of the outermost browsing context, so we need to scale
331 // cbSize by the full-zoom so that SVGs with percentage width/height zoom:
333 NS_ASSERTION(aCBSize.width != NS_AUTOHEIGHT &&
334 aCBSize.height != NS_AUTOHEIGHT,
335 "root should not have auto-width/height containing block");
336 cbSize.width *= PresContext()->GetFullZoom();
337 cbSize.height *= PresContext()->GetFullZoom();
339 // We also need to honour the width and height attributes' default values
340 // of 100% when we're the root of a browsing context. (GetIntrinsicSize()
341 // doesn't report these since there's no such thing as a percentage
342 // intrinsic size. Also note that explicit percentage values are mapped
343 // into style, so the following isn't for them.)
345 SVGSVGElement* content = static_cast<SVGSVGElement*>(mContent);
347 nsSVGLength2 &width =
348 content->mLengthAttributes[SVGSVGElement::ATTR_WIDTH];
349 if (width.IsPercentage()) {
350 NS_ABORT_IF_FALSE(intrinsicSize.width.GetUnit() == eStyleUnit_None,
351 "GetIntrinsicSize should have reported no "
352 "intrinsic width");
353 float val = width.GetAnimValInSpecifiedUnits() / 100.0f;
354 if (val < 0.0f) val = 0.0f;
355 intrinsicSize.width.SetCoordValue(val * cbSize.width);
358 nsSVGLength2 &height =
359 content->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT];
360 NS_ASSERTION(aCBSize.height != NS_AUTOHEIGHT,
361 "root should not have auto-height containing block");
362 if (height.IsPercentage()) {
363 NS_ABORT_IF_FALSE(intrinsicSize.height.GetUnit() == eStyleUnit_None,
364 "GetIntrinsicSize should have reported no "
365 "intrinsic height");
366 float val = height.GetAnimValInSpecifiedUnits() / 100.0f;
367 if (val < 0.0f) val = 0.0f;
368 intrinsicSize.height.SetCoordValue(val * cbSize.height);
370 NS_ABORT_IF_FALSE(intrinsicSize.height.GetUnit() == eStyleUnit_Coord &&
371 intrinsicSize.width.GetUnit() == eStyleUnit_Coord,
372 "We should have just handled the only situation where"
373 "we lack an intrinsic height or width.");
376 return nsLayoutUtils::ComputeSizeWithIntrinsicDimensions(
377 aRenderingContext, this,
378 intrinsicSize, GetIntrinsicRatio(), cbSize,
379 aMargin, aBorder, aPadding);
382 NS_IMETHODIMP
383 nsSVGOuterSVGFrame::Reflow(nsPresContext* aPresContext,
384 nsHTMLReflowMetrics& aDesiredSize,
385 const nsHTMLReflowState& aReflowState,
386 nsReflowStatus& aStatus)
388 DO_GLOBAL_REFLOW_COUNT("nsSVGOuterSVGFrame");
389 DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus);
390 NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
391 ("enter nsSVGOuterSVGFrame::Reflow: availSize=%d,%d",
392 aReflowState.availableWidth, aReflowState.availableHeight));
394 NS_PRECONDITION(mState & NS_FRAME_IN_REFLOW, "frame is not in reflow");
396 aStatus = NS_FRAME_COMPLETE;
398 aDesiredSize.width = aReflowState.ComputedWidth() +
399 aReflowState.mComputedBorderPadding.LeftRight();
400 aDesiredSize.height = aReflowState.ComputedHeight() +
401 aReflowState.mComputedBorderPadding.TopBottom();
403 NS_ASSERTION(!GetPrevInFlow(), "SVG can't currently be broken across pages.");
405 SVGSVGElement *svgElem = static_cast<SVGSVGElement*>(mContent);
407 nsSVGOuterSVGAnonChildFrame *anonKid =
408 static_cast<nsSVGOuterSVGAnonChildFrame*>(GetFirstPrincipalChild());
410 if (mState & NS_FRAME_FIRST_REFLOW) {
411 // Initialize
412 svgElem->UpdateHasChildrenOnlyTransform();
415 // If our SVG viewport has changed, update our content and notify.
416 // http://www.w3.org/TR/SVG11/coords.html#ViewportSpace
418 svgFloatSize newViewportSize(
419 nsPresContext::AppUnitsToFloatCSSPixels(aReflowState.ComputedWidth()),
420 nsPresContext::AppUnitsToFloatCSSPixels(aReflowState.ComputedHeight()));
422 svgFloatSize oldViewportSize = svgElem->GetViewportSize();
424 uint32_t changeBits = 0;
425 if (newViewportSize != oldViewportSize) {
426 if (oldViewportSize.width <= 0.0f || oldViewportSize.height <= 0.0f) {
427 // The overflow rects of our child frames will be empty if we had a
428 // [synthetic] viewBox during our last reflow, since under
429 // FinishAndStoreOverflow() the nsDisplayTransform::TransformRect call
430 // will have ended up calling SVGSVGElement::GetViewBoxTransform()
431 // which will have returned the identity matrix due to our viewport
432 // having been zero-sized. Mark all our child frames as dirty so that we
433 // reflow them below and update their overflow rects:
434 nsIFrame* anonChild = GetFirstPrincipalChild();
435 anonChild->AddStateBits(NS_FRAME_IS_DIRTY);
436 for (nsIFrame* child = anonChild->GetFirstPrincipalChild(); child;
437 child = child->GetNextSibling()) {
438 child->AddStateBits(NS_FRAME_IS_DIRTY);
441 changeBits |= COORD_CONTEXT_CHANGED;
442 svgElem->SetViewportSize(newViewportSize);
444 if (mFullZoom != PresContext()->GetFullZoom()) {
445 changeBits |= FULL_ZOOM_CHANGED;
446 mFullZoom = PresContext()->GetFullZoom();
448 if (changeBits) {
449 NotifyViewportOrTransformChanged(changeBits);
451 mViewportInitialized = true;
453 if (!(GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD)) {
454 // Now that we've marked the necessary children as dirty, call
455 // ReflowSVG() on them:
457 mCallingReflowSVG = true;
459 // Update the mRects and visual overflow rects of all our descendants,
460 // including our anonymous wrapper kid:
461 anonKid->AddStateBits(mState & NS_FRAME_IS_DIRTY);
462 anonKid->ReflowSVG();
463 NS_ABORT_IF_FALSE(!anonKid->GetNextSibling(),
464 "We should have one anonymous child frame wrapping our real children");
466 mCallingReflowSVG = false;
469 // Make sure we scroll if we're too big:
470 // XXX Use the bounding box of our descendants? (See bug 353460 comment 14.)
471 aDesiredSize.SetOverflowAreasToDesiredBounds();
472 FinishAndStoreOverflow(&aDesiredSize);
474 // Set our anonymous kid's offset from our border box:
475 anonKid->SetPosition(GetContentRectRelativeToSelf().TopLeft());
477 NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
478 ("exit nsSVGOuterSVGFrame::Reflow: size=%d,%d",
479 aDesiredSize.width, aDesiredSize.height));
480 NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize);
481 return NS_OK;
484 NS_IMETHODIMP
485 nsSVGOuterSVGFrame::DidReflow(nsPresContext* aPresContext,
486 const nsHTMLReflowState* aReflowState,
487 nsDidReflowStatus aStatus)
489 nsresult rv = nsSVGOuterSVGFrameBase::DidReflow(aPresContext,aReflowState,aStatus);
491 // Make sure elements styled by :hover get updated if script/animation moves
492 // them under or out from under the pointer:
493 PresContext()->PresShell()->SynthesizeMouseMove(false);
495 return rv;
498 //----------------------------------------------------------------------
499 // container methods
502 * Used to paint/hit-test SVG when SVG display lists are disabled.
504 class nsDisplayOuterSVG : public nsDisplayItem {
505 public:
506 nsDisplayOuterSVG(nsDisplayListBuilder* aBuilder,
507 nsSVGOuterSVGFrame* aFrame) :
508 nsDisplayItem(aBuilder, aFrame) {
509 MOZ_COUNT_CTOR(nsDisplayOuterSVG);
511 #ifdef NS_BUILD_REFCNT_LOGGING
512 virtual ~nsDisplayOuterSVG() {
513 MOZ_COUNT_DTOR(nsDisplayOuterSVG);
515 #endif
517 virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
518 HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames);
519 virtual void Paint(nsDisplayListBuilder* aBuilder,
520 nsRenderingContext* aCtx);
522 virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
523 const nsDisplayItemGeometry* aGeometry,
524 nsRegion* aInvalidRegion);
526 NS_DISPLAY_DECL_NAME("SVGOuterSVG", TYPE_SVG_OUTER_SVG)
529 void
530 nsDisplayOuterSVG::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
531 HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames)
533 nsSVGOuterSVGFrame *outerSVGFrame = static_cast<nsSVGOuterSVGFrame*>(mFrame);
534 nsRect rectAtOrigin = aRect - ToReferenceFrame();
535 nsRect thisRect(nsPoint(0,0), outerSVGFrame->GetSize());
536 if (!thisRect.Intersects(rectAtOrigin))
537 return;
539 nsPoint rectCenter(rectAtOrigin.x + rectAtOrigin.width / 2,
540 rectAtOrigin.y + rectAtOrigin.height / 2);
542 nsSVGOuterSVGAnonChildFrame *anonKid =
543 static_cast<nsSVGOuterSVGAnonChildFrame*>(
544 outerSVGFrame->GetFirstPrincipalChild());
545 nsIFrame* frame = nsSVGUtils::HitTestChildren(
546 anonKid, rectCenter + outerSVGFrame->GetPosition() -
547 outerSVGFrame->GetContentRect().TopLeft());
548 if (frame) {
549 aOutFrames->AppendElement(frame);
553 void
554 nsDisplayOuterSVG::Paint(nsDisplayListBuilder* aBuilder,
555 nsRenderingContext* aContext)
557 #if defined(DEBUG) && defined(SVG_DEBUG_PAINT_TIMING)
558 PRTime start = PR_Now();
559 #endif
561 // Create an SVGAutoRenderState so we can call SetPaintingToWindow on
562 // it, but do so without changing the render mode:
563 SVGAutoRenderState state(aContext, SVGAutoRenderState::GetRenderMode(aContext));
565 if (aBuilder->IsPaintingToWindow()) {
566 state.SetPaintingToWindow(true);
569 nsRect viewportRect =
570 mFrame->GetContentRectRelativeToSelf() + ToReferenceFrame();
572 nsRect clipRect = mVisibleRect.Intersect(viewportRect);
574 nsIntRect contentAreaDirtyRect =
575 (clipRect - viewportRect.TopLeft()).
576 ToOutsidePixels(mFrame->PresContext()->AppUnitsPerDevPixel());
578 aContext->PushState();
579 aContext->Translate(viewportRect.TopLeft());
580 nsSVGUtils::PaintFrameWithEffects(aContext, &contentAreaDirtyRect, mFrame);
581 aContext->PopState();
583 NS_ASSERTION(!aContext->ThebesContext()->HasError(), "Cairo in error state");
585 #if defined(DEBUG) && defined(SVG_DEBUG_PAINT_TIMING)
586 PRTime end = PR_Now();
587 printf("SVG Paint Timing: %f ms\n", (end-start)/1000.0);
588 #endif
591 static PLDHashOperator CheckForeignObjectInvalidatedArea(nsPtrHashKey<nsSVGForeignObjectFrame>* aEntry, void* aData)
593 nsRegion* region = static_cast<nsRegion*>(aData);
594 region->Or(*region, aEntry->GetKey()->GetInvalidRegion());
595 return PL_DHASH_NEXT;
598 nsRegion
599 nsSVGOuterSVGFrame::FindInvalidatedForeignObjectFrameChildren(nsIFrame* aFrame)
601 nsRegion result;
602 if (mForeignObjectHash.Count()) {
603 mForeignObjectHash.EnumerateEntries(CheckForeignObjectInvalidatedArea, &result);
605 return result;
608 void
609 nsDisplayOuterSVG::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
610 const nsDisplayItemGeometry* aGeometry,
611 nsRegion* aInvalidRegion)
613 nsSVGOuterSVGFrame *frame = static_cast<nsSVGOuterSVGFrame*>(mFrame);
614 frame->InvalidateSVG(frame->FindInvalidatedForeignObjectFrameChildren(frame));
616 nsRegion result = frame->GetInvalidRegion();
617 result.MoveBy(ToReferenceFrame());
618 frame->ClearInvalidRegion();
620 nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
621 aInvalidRegion->Or(*aInvalidRegion, result);
624 // helper
625 static inline bool
626 DependsOnIntrinsicSize(const nsIFrame* aEmbeddingFrame)
628 const nsStylePosition *pos = aEmbeddingFrame->StylePosition();
629 const nsStyleCoord &width = pos->mWidth;
630 const nsStyleCoord &height = pos->mHeight;
632 // XXX it would be nice to know if the size of aEmbeddingFrame's containing
633 // block depends on aEmbeddingFrame, then we'd know if we can return false
634 // for eStyleUnit_Percent too.
635 return !width.ConvertsToLength() ||
636 !height.ConvertsToLength();
639 NS_IMETHODIMP
640 nsSVGOuterSVGFrame::AttributeChanged(int32_t aNameSpaceID,
641 nsIAtom* aAttribute,
642 int32_t aModType)
644 if (aNameSpaceID == kNameSpaceID_None &&
645 !(GetStateBits() & (NS_FRAME_FIRST_REFLOW | NS_STATE_SVG_NONDISPLAY_CHILD))) {
646 if (aAttribute == nsGkAtoms::viewBox ||
647 aAttribute == nsGkAtoms::preserveAspectRatio ||
648 aAttribute == nsGkAtoms::transform) {
650 // make sure our cached transform matrix gets (lazily) updated
651 mCanvasTM = nullptr;
653 nsSVGUtils::NotifyChildrenOfSVGChange(GetFirstPrincipalChild(),
654 aAttribute == nsGkAtoms::viewBox ?
655 TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED : TRANSFORM_CHANGED);
657 static_cast<SVGSVGElement*>(mContent)->ChildrenOnlyTransformChanged();
659 } else if (aAttribute == nsGkAtoms::width ||
660 aAttribute == nsGkAtoms::height) {
662 // Don't call ChildrenOnlyTransformChanged() here, since we call it
663 // under Reflow if the width/height actually changed.
665 nsIFrame* embeddingFrame;
666 if (IsRootOfReplacedElementSubDoc(&embeddingFrame) && embeddingFrame) {
667 if (DependsOnIntrinsicSize(embeddingFrame)) {
668 // Tell embeddingFrame's presShell it needs to be reflowed (which takes
669 // care of reflowing us too).
670 embeddingFrame->PresContext()->PresShell()->
671 FrameNeedsReflow(embeddingFrame, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
673 // else our width and height is overridden - don't reflow anything
674 } else {
675 // We are not embedded by reference, so our 'width' and 'height'
676 // attributes are not overridden - we need to reflow.
677 PresContext()->PresShell()->
678 FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
683 return NS_OK;
686 //----------------------------------------------------------------------
687 // painting
689 void
690 nsSVGOuterSVGFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
691 const nsRect& aDirtyRect,
692 const nsDisplayListSet& aLists)
694 if (GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD) {
695 return;
698 DisplayBorderBackgroundOutline(aBuilder, aLists);
700 DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox clip(aBuilder, this);
702 if ((aBuilder->IsForEventDelivery() &&
703 NS_SVGDisplayListHitTestingEnabled()) ||
704 NS_SVGDisplayListPaintingEnabled()) {
705 nsDisplayList *contentList = aLists.Content();
706 nsDisplayListSet set(contentList, contentList, contentList,
707 contentList, contentList, contentList);
708 BuildDisplayListForNonBlockChildren(aBuilder, aDirtyRect, set);
709 } else {
710 aLists.Content()->AppendNewToTop(
711 new (aBuilder) nsDisplayOuterSVG(aBuilder, this));
715 nsSplittableType
716 nsSVGOuterSVGFrame::GetSplittableType() const
718 return NS_FRAME_NOT_SPLITTABLE;
721 nsIAtom *
722 nsSVGOuterSVGFrame::GetType() const
724 return nsGkAtoms::svgOuterSVGFrame;
727 //----------------------------------------------------------------------
728 // nsISVGSVGFrame methods:
730 void
731 nsSVGOuterSVGFrame::NotifyViewportOrTransformChanged(uint32_t aFlags)
733 NS_ABORT_IF_FALSE(aFlags &&
734 !(aFlags & ~(COORD_CONTEXT_CHANGED | TRANSFORM_CHANGED |
735 FULL_ZOOM_CHANGED)),
736 "Unexpected aFlags value");
738 // No point in doing anything when were not init'ed yet:
739 if (!mViewportInitialized) {
740 return;
743 SVGSVGElement *content = static_cast<SVGSVGElement*>(mContent);
745 if (aFlags & COORD_CONTEXT_CHANGED) {
746 if (content->HasViewBoxRect()) {
747 // Percentage lengths on children resolve against the viewBox rect so we
748 // don't need to notify them of the viewport change, but the viewBox
749 // transform will have changed, so we need to notify them of that instead.
750 aFlags = TRANSFORM_CHANGED;
752 else if (content->ShouldSynthesizeViewBox()) {
753 // In the case of a synthesized viewBox, the synthetic viewBox's rect
754 // changes as the viewport changes. As a result we need to maintain the
755 // COORD_CONTEXT_CHANGED flag.
756 aFlags |= TRANSFORM_CHANGED;
758 else if (mCanvasTM && mCanvasTM->IsSingular()) {
759 // A width/height of zero will result in us having a singular mCanvasTM
760 // even when we don't have a viewBox. So we also want to recompute our
761 // mCanvasTM for this width/height change even though we don't have a
762 // viewBox.
763 aFlags |= TRANSFORM_CHANGED;
767 bool haveNonFulLZoomTransformChange = (aFlags & TRANSFORM_CHANGED);
769 if (aFlags & FULL_ZOOM_CHANGED) {
770 // Convert FULL_ZOOM_CHANGED to TRANSFORM_CHANGED:
771 aFlags = (aFlags & ~FULL_ZOOM_CHANGED) | TRANSFORM_CHANGED;
774 if (aFlags & TRANSFORM_CHANGED) {
775 // Make sure our canvas transform matrix gets (lazily) recalculated:
776 mCanvasTM = nullptr;
778 if (haveNonFulLZoomTransformChange &&
779 !(mState & NS_STATE_SVG_NONDISPLAY_CHILD)) {
780 uint32_t flags = (mState & NS_FRAME_IN_REFLOW) ?
781 SVGSVGElement::eDuringReflow : 0;
782 content->ChildrenOnlyTransformChanged(flags);
786 nsSVGUtils::NotifyChildrenOfSVGChange(GetFirstPrincipalChild(), aFlags);
789 //----------------------------------------------------------------------
790 // nsISVGChildFrame methods:
792 NS_IMETHODIMP
793 nsSVGOuterSVGFrame::PaintSVG(nsRenderingContext* aContext,
794 const nsIntRect *aDirtyRect)
796 NS_ASSERTION(GetFirstPrincipalChild()->GetType() ==
797 nsGkAtoms::svgOuterSVGAnonChildFrame &&
798 !GetFirstPrincipalChild()->GetNextSibling(),
799 "We should have a single, anonymous, child");
800 nsSVGOuterSVGAnonChildFrame *anonKid =
801 static_cast<nsSVGOuterSVGAnonChildFrame*>(GetFirstPrincipalChild());
802 return anonKid->PaintSVG(aContext, aDirtyRect);
805 SVGBBox
806 nsSVGOuterSVGFrame::GetBBoxContribution(const gfxMatrix &aToBBoxUserspace,
807 uint32_t aFlags)
809 NS_ASSERTION(GetFirstPrincipalChild()->GetType() ==
810 nsGkAtoms::svgOuterSVGAnonChildFrame &&
811 !GetFirstPrincipalChild()->GetNextSibling(),
812 "We should have a single, anonymous, child");
813 // We must defer to our child so that we don't include our
814 // content->PrependLocalTransformsTo() transforms.
815 nsSVGOuterSVGAnonChildFrame *anonKid =
816 static_cast<nsSVGOuterSVGAnonChildFrame*>(GetFirstPrincipalChild());
817 return anonKid->GetBBoxContribution(aToBBoxUserspace, aFlags);
820 //----------------------------------------------------------------------
821 // nsSVGContainerFrame methods:
823 gfxMatrix
824 nsSVGOuterSVGFrame::GetCanvasTM(uint32_t aFor)
826 if (!(GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD)) {
827 if ((aFor == FOR_PAINTING && NS_SVGDisplayListPaintingEnabled()) ||
828 (aFor == FOR_HIT_TESTING && NS_SVGDisplayListHitTestingEnabled())) {
829 return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(this);
832 if (!mCanvasTM) {
833 SVGSVGElement *content = static_cast<SVGSVGElement*>(mContent);
835 float devPxPerCSSPx =
836 1.0f / PresContext()->AppUnitsToFloatCSSPixels(
837 PresContext()->AppUnitsPerDevPixel());
839 gfxMatrix tm = content->PrependLocalTransformsTo(
840 gfxMatrix().Scale(devPxPerCSSPx, devPxPerCSSPx));
841 mCanvasTM = new gfxMatrix(tm);
843 return *mCanvasTM;
846 //----------------------------------------------------------------------
847 // Implementation helpers
849 bool
850 nsSVGOuterSVGFrame::IsRootOfReplacedElementSubDoc(nsIFrame **aEmbeddingFrame)
852 if (!mContent->GetParent()) {
853 // Our content is the document element
854 nsCOMPtr<nsISupports> container = PresContext()->GetContainer();
855 nsCOMPtr<nsIDOMWindow> window = do_GetInterface(container);
856 if (window) {
857 nsCOMPtr<nsIDOMElement> frameElement;
858 window->GetFrameElement(getter_AddRefs(frameElement));
859 nsCOMPtr<nsIObjectLoadingContent> olc = do_QueryInterface(frameElement);
860 if (olc) {
861 // Our document is inside an HTML 'object', 'embed' or 'applet' element
862 if (aEmbeddingFrame) {
863 nsCOMPtr<nsIContent> element = do_QueryInterface(frameElement);
864 *aEmbeddingFrame = element->GetPrimaryFrame();
865 NS_ASSERTION(*aEmbeddingFrame, "Yikes, no embedding frame!");
867 return true;
871 if (aEmbeddingFrame) {
872 *aEmbeddingFrame = nullptr;
874 return false;
877 bool
878 nsSVGOuterSVGFrame::IsRootOfImage()
880 if (!mContent->GetParent()) {
881 // Our content is the document element
882 nsIDocument* doc = mContent->GetCurrentDoc();
883 if (doc && doc->IsBeingUsedAsImage()) {
884 // Our document is being used as an image
885 return true;
889 return false;
892 bool
893 nsSVGOuterSVGFrame::VerticalScrollbarNotNeeded() const
895 nsSVGLength2 &height = static_cast<SVGSVGElement*>(mContent)->
896 mLengthAttributes[SVGSVGElement::ATTR_HEIGHT];
897 return height.IsPercentage() && height.GetBaseValInSpecifiedUnits() <= 100;
901 //----------------------------------------------------------------------
902 // Implementation of nsSVGOuterSVGAnonChildFrame
904 nsIFrame*
905 NS_NewSVGOuterSVGAnonChildFrame(nsIPresShell* aPresShell,
906 nsStyleContext* aContext)
908 return new (aPresShell) nsSVGOuterSVGAnonChildFrame(aContext);
911 NS_IMPL_FRAMEARENA_HELPERS(nsSVGOuterSVGAnonChildFrame)
913 #ifdef DEBUG
914 void
915 nsSVGOuterSVGAnonChildFrame::Init(nsIContent* aContent,
916 nsIFrame* aParent,
917 nsIFrame* aPrevInFlow)
919 NS_ABORT_IF_FALSE(aParent->GetType() == nsGkAtoms::svgOuterSVGFrame,
920 "Unexpected parent");
921 nsSVGOuterSVGAnonChildFrameBase::Init(aContent, aParent, aPrevInFlow);
923 #endif
925 nsIAtom *
926 nsSVGOuterSVGAnonChildFrame::GetType() const
928 return nsGkAtoms::svgOuterSVGAnonChildFrame;
931 bool
932 nsSVGOuterSVGAnonChildFrame::HasChildrenOnlyTransform(gfxMatrix *aTransform) const
934 // We must claim our nsSVGOuterSVGFrame's children-only transforms as our own
935 // so that the children we are used to wrap are transformed properly.
937 SVGSVGElement *content = static_cast<SVGSVGElement*>(mContent);
939 bool hasTransform = content->HasChildrenOnlyTransform();
941 if (hasTransform && aTransform) {
942 // Outer-<svg> doesn't use x/y, so we can pass eChildToUserSpace here.
943 gfxMatrix identity;
944 *aTransform =
945 content->PrependLocalTransformsTo(identity,
946 nsSVGElement::eChildToUserSpace);
949 return hasTransform;