layout/svg/nsSVGIntegrationUtils.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 // Main header first:
michael@0 7 #include "nsSVGIntegrationUtils.h"
michael@0 8
michael@0 9 // Keep others in (case-insensitive) order:
michael@0 10 #include "gfxDrawable.h"
michael@0 11 #include "nsCSSAnonBoxes.h"
michael@0 12 #include "nsDisplayList.h"
michael@0 13 #include "nsFilterInstance.h"
michael@0 14 #include "nsLayoutUtils.h"
michael@0 15 #include "nsRenderingContext.h"
michael@0 16 #include "nsSVGClipPathFrame.h"
michael@0 17 #include "nsSVGEffects.h"
michael@0 18 #include "nsSVGElement.h"
michael@0 19 #include "nsSVGFilterPaintCallback.h"
michael@0 20 #include "nsSVGMaskFrame.h"
michael@0 21 #include "nsSVGPaintServerFrame.h"
michael@0 22 #include "nsSVGUtils.h"
michael@0 23 #include "FrameLayerBuilder.h"
michael@0 24 #include "BasicLayers.h"
michael@0 25 #include "mozilla/gfx/Point.h"
michael@0 26
michael@0 27 using namespace mozilla;
michael@0 28 using namespace mozilla::layers;
michael@0 29
michael@0 30 // ----------------------------------------------------------------------
michael@0 31
michael@0 32 /**
michael@0 33 * This class is used to get the pre-effects visual overflow rect of a frame,
michael@0 34 * or, in the case of a frame with continuations, to collect the union of the
michael@0 35 * pre-effects visual overflow rects of all the continuations. The result is
michael@0 36 * relative to the origin (top left corner of the border box) of the frame, or,
michael@0 37 * if the frame has continuations, the origin of the _first_ continuation.
michael@0 38 */
michael@0 39 class PreEffectsVisualOverflowCollector : public nsLayoutUtils::BoxCallback
michael@0 40 {
michael@0 41 public:
michael@0 42 /**
michael@0 43 * If the pre-effects visual overflow rect of the frame being examined
michael@0 44 * happens to be known, it can be passed in as aCurrentFrame and its
michael@0 45 * pre-effects visual overflow rect can be passed in as
michael@0 46 * aCurrentFrameOverflowArea. This is just an optimization to save a
michael@0 47 * frame property lookup - these arguments are optional.
michael@0 48 */
michael@0 49 PreEffectsVisualOverflowCollector(nsIFrame* aFirstContinuation,
michael@0 50 nsIFrame* aCurrentFrame,
michael@0 51 const nsRect& aCurrentFrameOverflowArea)
michael@0 52 : mFirstContinuation(aFirstContinuation)
michael@0 53 , mCurrentFrame(aCurrentFrame)
michael@0 54 , mCurrentFrameOverflowArea(aCurrentFrameOverflowArea)
michael@0 55 {
michael@0 56 NS_ASSERTION(!mFirstContinuation->GetPrevContinuation(),
michael@0 57 "We want the first continuation here");
michael@0 58 }
michael@0 59
michael@0 60 virtual void AddBox(nsIFrame* aFrame) MOZ_OVERRIDE {
michael@0 61 nsRect overflow = (aFrame == mCurrentFrame) ?
michael@0 62 mCurrentFrameOverflowArea : GetPreEffectsVisualOverflowRect(aFrame);
michael@0 63 mResult.UnionRect(mResult, overflow + aFrame->GetOffsetTo(mFirstContinuation));
michael@0 64 }
michael@0 65
michael@0 66 nsRect GetResult() const {
michael@0 67 return mResult;
michael@0 68 }
michael@0 69
michael@0 70 private:
michael@0 71
michael@0 72 static nsRect GetPreEffectsVisualOverflowRect(nsIFrame* aFrame) {
michael@0 73 nsRect* r = static_cast<nsRect*>
michael@0 74 (aFrame->Properties().Get(nsIFrame::PreEffectsBBoxProperty()));
michael@0 75 if (r) {
michael@0 76 return *r;
michael@0 77 }
michael@0 78 // Despite the fact that we're invoked for frames with SVG effects applied,
michael@0 79 // we can actually get here. All continuations and IB split siblings of a
michael@0 80 // frame with SVG effects applied will have the PreEffectsBBoxProperty
michael@0 81 // property set on them. Therefore, the frames that are passed to us will
michael@0 82 // always have that property set...well, with one exception. If the frames
michael@0 83 // for an element with SVG effects applied have been subject to an "IB
michael@0 84 // split", then the block frame(s) that caused the split will have been
michael@0 85 // wrapped in anonymous, inline-block, nsBlockFrames of pseudo-type
michael@0 86 // nsCSSAnonBoxes::mozAnonymousBlock. These "IB split sibling" anonymous
michael@0 87 // blocks will have the PreEffectsBBoxProperty property set on them, but
michael@0 88 // they will never be passed to us. Instead, we'll be passed the block
michael@0 89 // children that they wrap, which don't have the PreEffectsBBoxProperty
michael@0 90 // property set on them. This is actually okay. What we care about is
michael@0 91 // collecting the _pre_ effects visual overflow rects of the frames to
michael@0 92 // which the SVG effects have been applied. Since the IB split results in
michael@0 93 // any overflow rect adjustments for transforms, effects, etc. taking
michael@0 94 // place on the anonymous block wrappers, the wrapped children are left
michael@0 95 // with their overflow rects unaffected. In other words, calling
michael@0 96 // GetVisualOverflowRect() on the children will return their pre-effects
michael@0 97 // visual overflow rects, just as we need.
michael@0 98 //
michael@0 99 // A couple of tests that demonstrate the IB split and cause us to get here
michael@0 100 // are:
michael@0 101 //
michael@0 102 // * reftests/svg/svg-integration/clipPath-html-06.xhtml
michael@0 103 // * reftests/svg/svg-integration/clipPath-html-06-extref.xhtml
michael@0 104 //
michael@0 105 // If we ever got passed a frame with the PreTransformOverflowAreasProperty
michael@0 106 // property set, that would be bad, since then our GetVisualOverflowRect()
michael@0 107 // call would give us the post-effects, and post-transform, overflow rect.
michael@0 108 //
michael@0 109 NS_ASSERTION(aFrame->GetParent()->StyleContext()->GetPseudo() ==
michael@0 110 nsCSSAnonBoxes::mozAnonymousBlock,
michael@0 111 "How did we getting here, then?");
michael@0 112 NS_ASSERTION(!aFrame->Properties().Get(
michael@0 113 aFrame->PreTransformOverflowAreasProperty()),
michael@0 114 "GetVisualOverflowRect() won't return the pre-effects rect!");
michael@0 115 return aFrame->GetVisualOverflowRect();
michael@0 116 }
michael@0 117
michael@0 118 nsIFrame* mFirstContinuation;
michael@0 119 nsIFrame* mCurrentFrame;
michael@0 120 const nsRect& mCurrentFrameOverflowArea;
michael@0 121 nsRect mResult;
michael@0 122 };
michael@0 123
michael@0 124 /**
michael@0 125 * Gets the union of the pre-effects visual overflow rects of all of a frame's
michael@0 126 * continuations, in "user space".
michael@0 127 */
michael@0 128 static nsRect
michael@0 129 GetPreEffectsVisualOverflowUnion(nsIFrame* aFirstContinuation,
michael@0 130 nsIFrame* aCurrentFrame,
michael@0 131 const nsRect& aCurrentFramePreEffectsOverflow,
michael@0 132 const nsPoint& aFirstContinuationToUserSpace)
michael@0 133 {
michael@0 134 NS_ASSERTION(!aFirstContinuation->GetPrevContinuation(),
michael@0 135 "Need first continuation here");
michael@0 136 PreEffectsVisualOverflowCollector collector(aFirstContinuation,
michael@0 137 aCurrentFrame,
michael@0 138 aCurrentFramePreEffectsOverflow);
michael@0 139 // Compute union of all overflow areas relative to aFirstContinuation:
michael@0 140 nsLayoutUtils::GetAllInFlowBoxes(aFirstContinuation, &collector);
michael@0 141 // Return the result in user space:
michael@0 142 return collector.GetResult() + aFirstContinuationToUserSpace;
michael@0 143 }
michael@0 144
michael@0 145
michael@0 146 bool
michael@0 147 nsSVGIntegrationUtils::UsingEffectsForFrame(const nsIFrame* aFrame)
michael@0 148 {
michael@0 149 // Even when SVG display lists are disabled, returning true for SVG frames
michael@0 150 // does not adversely affect any of our callers. Therefore we don't bother
michael@0 151 // checking the SDL prefs here, since we don't know if we're being called for
michael@0 152 // painting or hit-testing anyway.
michael@0 153 const nsStyleSVGReset *style = aFrame->StyleSVGReset();
michael@0 154 return (style->HasFilters() || style->mClipPath || style->mMask);
michael@0 155 }
michael@0 156
michael@0 157 // For non-SVG frames, this gives the offset to the frame's "user space".
michael@0 158 // For SVG frames, this returns a zero offset.
michael@0 159 static nsPoint
michael@0 160 GetOffsetToBoundingBox(nsIFrame* aFrame)
michael@0 161 {
michael@0 162 if ((aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) {
michael@0 163 // Do NOT call GetAllInFlowRectsUnion for SVG - it will get the
michael@0 164 // covered region relative to the nsSVGOuterSVGFrame, which is absolutely
michael@0 165 // not what we want. SVG frames are always in user space, so they have
michael@0 166 // no offset adjustment to make.
michael@0 167 return nsPoint();
michael@0 168 }
michael@0 169 // We could allow aFrame to be any continuation, but since that would require
michael@0 170 // a GetPrevContinuation() virtual call and conditional returns, and since
michael@0 171 // all our current consumers always pass in the first continuation, we don't
michael@0 172 // currently bother.
michael@0 173 NS_ASSERTION(!aFrame->GetPrevContinuation(), "Not first continuation");
michael@0 174
michael@0 175 // The GetAllInFlowRectsUnion() call gets the union of the frame border-box
michael@0 176 // rects over all continuations, relative to the origin (top-left of the
michael@0 177 // border box) of its second argument (here, aFrame, the first continuation).
michael@0 178 return -nsLayoutUtils::GetAllInFlowRectsUnion(aFrame, aFrame).TopLeft();
michael@0 179 }
michael@0 180
michael@0 181 /* static */ nsSize
michael@0 182 nsSVGIntegrationUtils::GetContinuationUnionSize(nsIFrame* aNonSVGFrame)
michael@0 183 {
michael@0 184 NS_ASSERTION(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG),
michael@0 185 "SVG frames should not get here");
michael@0 186 nsIFrame* firstFrame =
michael@0 187 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame);
michael@0 188 return nsLayoutUtils::GetAllInFlowRectsUnion(firstFrame, firstFrame).Size();
michael@0 189 }
michael@0 190
michael@0 191 /* static */ gfx::Size
michael@0 192 nsSVGIntegrationUtils::GetSVGCoordContextForNonSVGFrame(nsIFrame* aNonSVGFrame)
michael@0 193 {
michael@0 194 NS_ASSERTION(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG),
michael@0 195 "SVG frames should not get here");
michael@0 196 nsIFrame* firstFrame =
michael@0 197 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame);
michael@0 198 nsRect r = nsLayoutUtils::GetAllInFlowRectsUnion(firstFrame, firstFrame);
michael@0 199 nsPresContext* presContext = firstFrame->PresContext();
michael@0 200 return gfx::Size(presContext->AppUnitsToFloatCSSPixels(r.width),
michael@0 201 presContext->AppUnitsToFloatCSSPixels(r.height));
michael@0 202 }
michael@0 203
michael@0 204 gfxRect
michael@0 205 nsSVGIntegrationUtils::GetSVGBBoxForNonSVGFrame(nsIFrame* aNonSVGFrame)
michael@0 206 {
michael@0 207 NS_ASSERTION(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG),
michael@0 208 "SVG frames should not get here");
michael@0 209 nsIFrame* firstFrame =
michael@0 210 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame);
michael@0 211 // 'r' is in "user space":
michael@0 212 nsRect r = GetPreEffectsVisualOverflowUnion(firstFrame, nullptr, nsRect(),
michael@0 213 GetOffsetToBoundingBox(firstFrame));
michael@0 214 return nsLayoutUtils::RectToGfxRect(r,
michael@0 215 aNonSVGFrame->PresContext()->AppUnitsPerCSSPixel());
michael@0 216 }
michael@0 217
michael@0 218 // XXX Since we're called during reflow, this method is broken for frames with
michael@0 219 // continuations. When we're called for a frame with continuations, we're
michael@0 220 // called for each continuation in turn as it's reflowed. However, it isn't
michael@0 221 // until the last continuation is reflowed that this method's
michael@0 222 // GetOffsetToBoundingBox() and GetPreEffectsVisualOverflowUnion() calls will
michael@0 223 // obtain valid border boxes for all the continuations. As a result, we'll
michael@0 224 // end up returning bogus post-filter visual overflow rects for all the prior
michael@0 225 // continuations. Unfortunately, by the time the last continuation is
michael@0 226 // reflowed, it's too late to go back and set and propagate the overflow
michael@0 227 // rects on the previous continuations.
michael@0 228 //
michael@0 229 // The reason that we need to pass an override bbox to
michael@0 230 // GetPreEffectsVisualOverflowUnion rather than just letting it call into our
michael@0 231 // GetSVGBBoxForNonSVGFrame method is because we get called by
michael@0 232 // ComputeEffectsRect when it has been called with
michael@0 233 // aStoreRectProperties set to false. In this case the pre-effects visual
michael@0 234 // overflow rect that it has been passed may be different to that stored on
michael@0 235 // aFrame, resulting in a different bbox.
michael@0 236 //
michael@0 237 // XXXjwatt The pre-effects visual overflow rect passed to
michael@0 238 // ComputeEffectsRect won't include continuation overflows, so
michael@0 239 // for frames with continuation the following filter analysis will likely end
michael@0 240 // up being carried out with a bbox created as if the frame didn't have
michael@0 241 // continuations.
michael@0 242 //
michael@0 243 // XXXjwatt Using aPreEffectsOverflowRect to create the bbox isn't really right
michael@0 244 // for SVG frames, since for SVG frames the SVG spec defines the bbox to be
michael@0 245 // something quite different to the pre-effects visual overflow rect. However,
michael@0 246 // we're essentially calculating an invalidation area here, and using the
michael@0 247 // pre-effects overflow rect will actually overestimate that area which, while
michael@0 248 // being a bit wasteful, isn't otherwise a problem.
michael@0 249 //
michael@0 250 nsRect
michael@0 251 nsSVGIntegrationUtils::
michael@0 252 ComputePostEffectsVisualOverflowRect(nsIFrame* aFrame,
michael@0 253 const nsRect& aPreEffectsOverflowRect)
michael@0 254 {
michael@0 255 NS_ASSERTION(!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT),
michael@0 256 "Don't call this on SVG child frames");
michael@0 257
michael@0 258 nsIFrame* firstFrame =
michael@0 259 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
michael@0 260 nsSVGEffects::EffectProperties effectProperties =
michael@0 261 nsSVGEffects::GetEffectProperties(firstFrame);
michael@0 262 if (!effectProperties.HasValidFilter()) {
michael@0 263 return aPreEffectsOverflowRect;
michael@0 264 }
michael@0 265
michael@0 266 // Create an override bbox - see comment above:
michael@0 267 nsPoint firstFrameToBoundingBox = GetOffsetToBoundingBox(firstFrame);
michael@0 268 // overrideBBox is in "user space", in _CSS_ pixels:
michael@0 269 // XXX Why are we rounding out to pixel boundaries? We don't do that in
michael@0 270 // GetSVGBBoxForNonSVGFrame, and it doesn't appear to be necessary.
michael@0 271 gfxRect overrideBBox =
michael@0 272 nsLayoutUtils::RectToGfxRect(
michael@0 273 GetPreEffectsVisualOverflowUnion(firstFrame, aFrame,
michael@0 274 aPreEffectsOverflowRect,
michael@0 275 firstFrameToBoundingBox),
michael@0 276 aFrame->PresContext()->AppUnitsPerCSSPixel());
michael@0 277 overrideBBox.RoundOut();
michael@0 278
michael@0 279 nsRect overflowRect =
michael@0 280 nsFilterInstance::GetPostFilterBounds(firstFrame, &overrideBBox);
michael@0 281
michael@0 282 // Return overflowRect relative to aFrame, rather than "user space":
michael@0 283 return overflowRect - (aFrame->GetOffsetTo(firstFrame) + firstFrameToBoundingBox);
michael@0 284 }
michael@0 285
michael@0 286 nsIntRegion
michael@0 287 nsSVGIntegrationUtils::AdjustInvalidAreaForSVGEffects(nsIFrame* aFrame,
michael@0 288 const nsPoint& aToReferenceFrame,
michael@0 289 const nsIntRegion& aInvalidRegion)
michael@0 290 {
michael@0 291 if (aInvalidRegion.IsEmpty()) {
michael@0 292 return nsIntRect();
michael@0 293 }
michael@0 294
michael@0 295 // Don't bother calling GetEffectProperties; the filter property should
michael@0 296 // already have been set up during reflow/ComputeFrameEffectsRect
michael@0 297 nsIFrame* firstFrame =
michael@0 298 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
michael@0 299 nsSVGFilterProperty *prop = nsSVGEffects::GetFilterProperty(firstFrame);
michael@0 300 if (!prop || !prop->IsInObserverLists()) {
michael@0 301 return aInvalidRegion;
michael@0 302 }
michael@0 303
michael@0 304 int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
michael@0 305
michael@0 306 if (!prop || !prop->ReferencesValidResources()) {
michael@0 307 // The frame is either not there or not currently available,
michael@0 308 // perhaps because we're in the middle of tearing stuff down.
michael@0 309 // Be conservative, return our visual overflow rect relative
michael@0 310 // to the reference frame.
michael@0 311 nsRect overflow = aFrame->GetVisualOverflowRect() + aToReferenceFrame;
michael@0 312 return overflow.ToOutsidePixels(appUnitsPerDevPixel);
michael@0 313 }
michael@0 314
michael@0 315 // Convert aInvalidRegion into bounding box frame space in app units:
michael@0 316 nsPoint toBoundingBox =
michael@0 317 aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame);
michael@0 318 // The initial rect was relative to the reference frame, so we need to
michael@0 319 // remove that offset to get a rect relative to the current frame.
michael@0 320 toBoundingBox -= aToReferenceFrame;
michael@0 321 nsRegion preEffectsRegion = aInvalidRegion.ToAppUnits(appUnitsPerDevPixel).MovedBy(toBoundingBox);
michael@0 322
michael@0 323 // Adjust the dirty area for effects, and shift it back to being relative to
michael@0 324 // the reference frame.
michael@0 325 nsRegion result = nsFilterInstance::GetPostFilterDirtyArea(firstFrame,
michael@0 326 preEffectsRegion).MovedBy(-toBoundingBox);
michael@0 327 // Return the result, in pixels relative to the reference frame.
michael@0 328 return result.ToOutsidePixels(appUnitsPerDevPixel);
michael@0 329 }
michael@0 330
michael@0 331 nsRect
michael@0 332 nsSVGIntegrationUtils::GetRequiredSourceForInvalidArea(nsIFrame* aFrame,
michael@0 333 const nsRect& aDirtyRect)
michael@0 334 {
michael@0 335 // Don't bother calling GetEffectProperties; the filter property should
michael@0 336 // already have been set up during reflow/ComputeFrameEffectsRect
michael@0 337 nsIFrame* firstFrame =
michael@0 338 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
michael@0 339 nsSVGFilterProperty *prop = nsSVGEffects::GetFilterProperty(firstFrame);
michael@0 340 if (!prop || !prop->ReferencesValidResources()) {
michael@0 341 return aDirtyRect;
michael@0 342 }
michael@0 343
michael@0 344 // Convert aDirtyRect into "user space" in app units:
michael@0 345 nsPoint toUserSpace =
michael@0 346 aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame);
michael@0 347 nsRect postEffectsRect = aDirtyRect + toUserSpace;
michael@0 348
michael@0 349 // Return ther result, relative to aFrame, not in user space:
michael@0 350 return nsFilterInstance::GetPreFilterNeededArea(firstFrame, postEffectsRect).GetBounds()
michael@0 351 - toUserSpace;
michael@0 352 }
michael@0 353
michael@0 354 bool
michael@0 355 nsSVGIntegrationUtils::HitTestFrameForEffects(nsIFrame* aFrame, const nsPoint& aPt)
michael@0 356 {
michael@0 357 nsIFrame* firstFrame =
michael@0 358 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
michael@0 359 // Convert aPt to user space:
michael@0 360 nsPoint toUserSpace;
michael@0 361 if (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) {
michael@0 362 // XXXmstange Isn't this wrong for svg:use and innerSVG frames?
michael@0 363 toUserSpace = aFrame->GetPosition();
michael@0 364 } else {
michael@0 365 toUserSpace =
michael@0 366 aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame);
michael@0 367 }
michael@0 368 nsPoint pt = aPt + toUserSpace;
michael@0 369 return nsSVGUtils::HitTestClip(firstFrame, pt);
michael@0 370 }
michael@0 371
michael@0 372 class RegularFramePaintCallback : public nsSVGFilterPaintCallback
michael@0 373 {
michael@0 374 public:
michael@0 375 RegularFramePaintCallback(nsDisplayListBuilder* aBuilder,
michael@0 376 LayerManager* aManager,
michael@0 377 const nsPoint& aOffset)
michael@0 378 : mBuilder(aBuilder), mLayerManager(aManager),
michael@0 379 mOffset(aOffset) {}
michael@0 380
michael@0 381 virtual void Paint(nsRenderingContext *aContext, nsIFrame *aTarget,
michael@0 382 const nsIntRect* aDirtyRect,
michael@0 383 nsIFrame* aTransformRoot) MOZ_OVERRIDE
michael@0 384 {
michael@0 385 BasicLayerManager* basic = static_cast<BasicLayerManager*>(mLayerManager);
michael@0 386 basic->SetTarget(aContext->ThebesContext());
michael@0 387 nsRenderingContext::AutoPushTranslation push(aContext, -mOffset);
michael@0 388 mLayerManager->EndTransaction(FrameLayerBuilder::DrawThebesLayer, mBuilder);
michael@0 389 }
michael@0 390
michael@0 391 private:
michael@0 392 nsDisplayListBuilder* mBuilder;
michael@0 393 LayerManager* mLayerManager;
michael@0 394 nsPoint mOffset;
michael@0 395 };
michael@0 396
michael@0 397 void
michael@0 398 nsSVGIntegrationUtils::PaintFramesWithEffects(nsRenderingContext* aCtx,
michael@0 399 nsIFrame* aFrame,
michael@0 400 const nsRect& aDirtyRect,
michael@0 401 nsDisplayListBuilder* aBuilder,
michael@0 402 LayerManager *aLayerManager)
michael@0 403 {
michael@0 404 #ifdef DEBUG
michael@0 405 NS_ASSERTION(!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) ||
michael@0 406 (NS_SVGDisplayListPaintingEnabled() &&
michael@0 407 !(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)),
michael@0 408 "Should not use nsSVGIntegrationUtils on this SVG frame");
michael@0 409 #endif
michael@0 410
michael@0 411 /* SVG defines the following rendering model:
michael@0 412 *
michael@0 413 * 1. Render geometry
michael@0 414 * 2. Apply filter
michael@0 415 * 3. Apply clipping, masking, group opacity
michael@0 416 *
michael@0 417 * We follow this, but perform a couple of optimizations:
michael@0 418 *
michael@0 419 * + Use cairo's clipPath when representable natively (single object
michael@0 420 * clip region).
michael@0 421 *
michael@0 422 * + Merge opacity and masking if both used together.
michael@0 423 */
michael@0 424
michael@0 425 const nsIContent* content = aFrame->GetContent();
michael@0 426 bool hasSVGLayout = (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT);
michael@0 427 if (hasSVGLayout) {
michael@0 428 nsISVGChildFrame *svgChildFrame = do_QueryFrame(aFrame);
michael@0 429 if (!svgChildFrame || !aFrame->GetContent()->IsSVG()) {
michael@0 430 NS_ASSERTION(false, "why?");
michael@0 431 return;
michael@0 432 }
michael@0 433 if (!static_cast<const nsSVGElement*>(content)->HasValidDimensions()) {
michael@0 434 return; // The SVG spec says not to draw _anything_
michael@0 435 }
michael@0 436 }
michael@0 437
michael@0 438 float opacity = aFrame->StyleDisplay()->mOpacity;
michael@0 439 if (opacity == 0.0f) {
michael@0 440 return;
michael@0 441 }
michael@0 442 if (opacity != 1.0f &&
michael@0 443 hasSVGLayout && nsSVGUtils::CanOptimizeOpacity(aFrame)) {
michael@0 444 opacity = 1.0f;
michael@0 445 }
michael@0 446
michael@0 447 /* Properties are added lazily and may have been removed by a restyle,
michael@0 448 so make sure all applicable ones are set again. */
michael@0 449 nsIFrame* firstFrame =
michael@0 450 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
michael@0 451 nsSVGEffects::EffectProperties effectProperties =
michael@0 452 nsSVGEffects::GetEffectProperties(firstFrame);
michael@0 453
michael@0 454 bool isOK = effectProperties.HasNoFilterOrHasValidFilter();
michael@0 455 nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame(&isOK);
michael@0 456 nsSVGMaskFrame *maskFrame = effectProperties.GetMaskFrame(&isOK);
michael@0 457 if (!isOK) {
michael@0 458 return; // Some resource is missing. We shouldn't paint anything.
michael@0 459 }
michael@0 460
michael@0 461 bool isTrivialClip = clipPathFrame ? clipPathFrame->IsTrivial() : true;
michael@0 462
michael@0 463 gfxContext* gfx = aCtx->ThebesContext();
michael@0 464 gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(gfx);
michael@0 465
michael@0 466 nsPoint firstFrameOffset = GetOffsetToBoundingBox(firstFrame);
michael@0 467 nsPoint offsetToBoundingBox = aBuilder->ToReferenceFrame(firstFrame) - firstFrameOffset;
michael@0 468 if (!firstFrame->IsFrameOfType(nsIFrame::eSVG)) {
michael@0 469 /* Snap the offset if the reference frame is not a SVG frame,
michael@0 470 * since other frames will be snapped to pixel when rendering. */
michael@0 471 offsetToBoundingBox = nsPoint(
michael@0 472 aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(offsetToBoundingBox.x),
michael@0 473 aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(offsetToBoundingBox.y));
michael@0 474 }
michael@0 475
michael@0 476 // After applying only "offsetToBoundingBox", aCtx would have its origin at
michael@0 477 // the top left corner of aFrame's bounding box (over all continuations).
michael@0 478 // However, SVG painting needs the origin to be located at the origin of the
michael@0 479 // SVG frame's "user space", i.e. the space in which, for example, the
michael@0 480 // frame's BBox lives.
michael@0 481 // SVG geometry frames and foreignObject frames apply their own offsets, so
michael@0 482 // their position is relative to their user space. So for these frame types,
michael@0 483 // if we want aCtx to be in user space, we first need to subtract the
michael@0 484 // frame's position so that SVG painting can later add it again and the
michael@0 485 // frame is painted in the right place.
michael@0 486
michael@0 487 gfxPoint toUserSpaceGfx = nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(aFrame);
michael@0 488 nsPoint toUserSpace(nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.x)),
michael@0 489 nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.y)));
michael@0 490 nsPoint offsetToUserSpace = offsetToBoundingBox - toUserSpace;
michael@0 491
michael@0 492 NS_ASSERTION(hasSVGLayout || offsetToBoundingBox == offsetToUserSpace,
michael@0 493 "For non-SVG frames there shouldn't be any additional offset");
michael@0 494
michael@0 495 aCtx->Translate(offsetToUserSpace);
michael@0 496
michael@0 497 gfxMatrix cssPxToDevPxMatrix = GetCSSPxToDevPxMatrix(aFrame);
michael@0 498
michael@0 499 bool complexEffects = false;
michael@0 500 /* Check if we need to do additional operations on this child's
michael@0 501 * rendering, which necessitates rendering into another surface. */
michael@0 502 if (opacity != 1.0f || maskFrame || (clipPathFrame && !isTrivialClip)
michael@0 503 || aFrame->StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) {
michael@0 504 complexEffects = true;
michael@0 505 gfx->Save();
michael@0 506 aCtx->IntersectClip(aFrame->GetVisualOverflowRectRelativeToSelf() +
michael@0 507 toUserSpace);
michael@0 508 gfx->PushGroup(gfxContentType::COLOR_ALPHA);
michael@0 509 }
michael@0 510
michael@0 511 /* If this frame has only a trivial clipPath, set up cairo's clipping now so
michael@0 512 * we can just do normal painting and get it clipped appropriately.
michael@0 513 */
michael@0 514 if (clipPathFrame && isTrivialClip) {
michael@0 515 gfx->Save();
michael@0 516 clipPathFrame->ClipPaint(aCtx, aFrame, cssPxToDevPxMatrix);
michael@0 517 }
michael@0 518
michael@0 519 /* Paint the child */
michael@0 520 if (effectProperties.HasValidFilter()) {
michael@0 521 RegularFramePaintCallback callback(aBuilder, aLayerManager,
michael@0 522 offsetToUserSpace);
michael@0 523
michael@0 524 nsRegion dirtyRegion = aDirtyRect - offsetToBoundingBox;
michael@0 525 nsFilterInstance::PaintFilteredFrame(aCtx, aFrame, &callback, &dirtyRegion);
michael@0 526 } else {
michael@0 527 gfx->SetMatrix(matrixAutoSaveRestore.Matrix());
michael@0 528 aLayerManager->EndTransaction(FrameLayerBuilder::DrawThebesLayer, aBuilder);
michael@0 529 aCtx->Translate(offsetToUserSpace);
michael@0 530 }
michael@0 531
michael@0 532 if (clipPathFrame && isTrivialClip) {
michael@0 533 gfx->Restore();
michael@0 534 }
michael@0 535
michael@0 536 /* No more effects, we're done. */
michael@0 537 if (!complexEffects) {
michael@0 538 return;
michael@0 539 }
michael@0 540
michael@0 541 gfx->PopGroupToSource();
michael@0 542
michael@0 543 nsRefPtr<gfxPattern> maskSurface =
michael@0 544 maskFrame ? maskFrame->ComputeMaskAlpha(aCtx, aFrame,
michael@0 545 cssPxToDevPxMatrix, opacity) : nullptr;
michael@0 546
michael@0 547 nsRefPtr<gfxPattern> clipMaskSurface;
michael@0 548 if (clipPathFrame && !isTrivialClip) {
michael@0 549 gfx->PushGroup(gfxContentType::COLOR_ALPHA);
michael@0 550
michael@0 551 nsresult rv = clipPathFrame->ClipPaint(aCtx, aFrame, cssPxToDevPxMatrix);
michael@0 552 clipMaskSurface = gfx->PopGroup();
michael@0 553
michael@0 554 if (NS_SUCCEEDED(rv) && clipMaskSurface) {
michael@0 555 // Still more set after clipping, so clip to another surface
michael@0 556 if (maskSurface || opacity != 1.0f) {
michael@0 557 gfx->PushGroup(gfxContentType::COLOR_ALPHA);
michael@0 558 gfx->Mask(clipMaskSurface);
michael@0 559 gfx->PopGroupToSource();
michael@0 560 } else {
michael@0 561 gfx->Mask(clipMaskSurface);
michael@0 562 }
michael@0 563 }
michael@0 564 }
michael@0 565
michael@0 566 if (maskSurface) {
michael@0 567 gfx->Mask(maskSurface);
michael@0 568 } else if (opacity != 1.0f) {
michael@0 569 gfx->Paint(opacity);
michael@0 570 }
michael@0 571
michael@0 572 gfx->Restore();
michael@0 573 }
michael@0 574
michael@0 575 gfxMatrix
michael@0 576 nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(nsIFrame* aNonSVGFrame)
michael@0 577 {
michael@0 578 int32_t appUnitsPerDevPixel = aNonSVGFrame->PresContext()->AppUnitsPerDevPixel();
michael@0 579 float devPxPerCSSPx =
michael@0 580 1 / nsPresContext::AppUnitsToFloatCSSPixels(appUnitsPerDevPixel);
michael@0 581
michael@0 582 return gfxMatrix(devPxPerCSSPx, 0.0,
michael@0 583 0.0, devPxPerCSSPx,
michael@0 584 0.0, 0.0);
michael@0 585 }
michael@0 586
michael@0 587 class PaintFrameCallback : public gfxDrawingCallback {
michael@0 588 public:
michael@0 589 PaintFrameCallback(nsIFrame* aFrame,
michael@0 590 const nsSize aPaintServerSize,
michael@0 591 const gfxIntSize aRenderSize,
michael@0 592 uint32_t aFlags)
michael@0 593 : mFrame(aFrame)
michael@0 594 , mPaintServerSize(aPaintServerSize)
michael@0 595 , mRenderSize(aRenderSize)
michael@0 596 , mFlags (aFlags)
michael@0 597 {}
michael@0 598 virtual bool operator()(gfxContext* aContext,
michael@0 599 const gfxRect& aFillRect,
michael@0 600 const GraphicsFilter& aFilter,
michael@0 601 const gfxMatrix& aTransform) MOZ_OVERRIDE;
michael@0 602 private:
michael@0 603 nsIFrame* mFrame;
michael@0 604 nsSize mPaintServerSize;
michael@0 605 gfxIntSize mRenderSize;
michael@0 606 uint32_t mFlags;
michael@0 607 };
michael@0 608
michael@0 609 bool
michael@0 610 PaintFrameCallback::operator()(gfxContext* aContext,
michael@0 611 const gfxRect& aFillRect,
michael@0 612 const GraphicsFilter& aFilter,
michael@0 613 const gfxMatrix& aTransform)
michael@0 614 {
michael@0 615 if (mFrame->GetStateBits() & NS_FRAME_DRAWING_AS_PAINTSERVER)
michael@0 616 return false;
michael@0 617
michael@0 618 mFrame->AddStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER);
michael@0 619
michael@0 620 nsRefPtr<nsRenderingContext> context(new nsRenderingContext());
michael@0 621 context->Init(mFrame->PresContext()->DeviceContext(), aContext);
michael@0 622 aContext->Save();
michael@0 623
michael@0 624 // Clip to aFillRect so that we don't paint outside.
michael@0 625 aContext->NewPath();
michael@0 626 aContext->Rectangle(aFillRect);
michael@0 627 aContext->Clip();
michael@0 628
michael@0 629 aContext->Multiply(gfxMatrix(aTransform).Invert());
michael@0 630
michael@0 631 // nsLayoutUtils::PaintFrame will anchor its painting at mFrame. But we want
michael@0 632 // to have it anchored at the top left corner of the bounding box of all of
michael@0 633 // mFrame's continuations. So we add a translation transform.
michael@0 634 int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
michael@0 635 nsPoint offset = GetOffsetToBoundingBox(mFrame);
michael@0 636 gfxPoint devPxOffset = gfxPoint(offset.x, offset.y) / appUnitsPerDevPixel;
michael@0 637 aContext->Multiply(gfxMatrix().Translate(devPxOffset));
michael@0 638
michael@0 639 gfxSize paintServerSize =
michael@0 640 gfxSize(mPaintServerSize.width, mPaintServerSize.height) /
michael@0 641 mFrame->PresContext()->AppUnitsPerDevPixel();
michael@0 642
michael@0 643 // nsLayoutUtils::PaintFrame wants to render with paintServerSize, but we
michael@0 644 // want it to render with mRenderSize, so we need to set up a scale transform.
michael@0 645 gfxFloat scaleX = mRenderSize.width / paintServerSize.width;
michael@0 646 gfxFloat scaleY = mRenderSize.height / paintServerSize.height;
michael@0 647 gfxMatrix scaleMatrix = gfxMatrix().Scale(scaleX, scaleY);
michael@0 648 aContext->Multiply(scaleMatrix);
michael@0 649
michael@0 650 // Draw.
michael@0 651 nsRect dirty(-offset.x, -offset.y,
michael@0 652 mPaintServerSize.width, mPaintServerSize.height);
michael@0 653
michael@0 654 uint32_t flags = nsLayoutUtils::PAINT_IN_TRANSFORM |
michael@0 655 nsLayoutUtils::PAINT_ALL_CONTINUATIONS;
michael@0 656 if (mFlags & nsSVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES) {
michael@0 657 flags |= nsLayoutUtils::PAINT_SYNC_DECODE_IMAGES;
michael@0 658 }
michael@0 659 nsLayoutUtils::PaintFrame(context, mFrame,
michael@0 660 dirty, NS_RGBA(0, 0, 0, 0),
michael@0 661 flags);
michael@0 662
michael@0 663 aContext->Restore();
michael@0 664
michael@0 665 mFrame->RemoveStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER);
michael@0 666
michael@0 667 return true;
michael@0 668 }
michael@0 669
michael@0 670 /* static */ already_AddRefed<gfxDrawable>
michael@0 671 nsSVGIntegrationUtils::DrawableFromPaintServer(nsIFrame* aFrame,
michael@0 672 nsIFrame* aTarget,
michael@0 673 const nsSize& aPaintServerSize,
michael@0 674 const gfxIntSize& aRenderSize,
michael@0 675 const gfxMatrix& aContextMatrix,
michael@0 676 uint32_t aFlags)
michael@0 677 {
michael@0 678 // aPaintServerSize is the size that would be filled when using
michael@0 679 // background-repeat:no-repeat and background-size:auto. For normal background
michael@0 680 // images, this would be the intrinsic size of the image; for gradients and
michael@0 681 // patterns this would be the whole target frame fill area.
michael@0 682 // aRenderSize is what we will be actually filling after accounting for
michael@0 683 // background-size.
michael@0 684 if (aFrame->IsFrameOfType(nsIFrame::eSVGPaintServer)) {
michael@0 685 // aFrame is either a pattern or a gradient. These fill the whole target
michael@0 686 // frame by default, so aPaintServerSize is the whole target background fill
michael@0 687 // area.
michael@0 688 nsSVGPaintServerFrame* server =
michael@0 689 static_cast<nsSVGPaintServerFrame*>(aFrame);
michael@0 690
michael@0 691 gfxRect overrideBounds(0, 0,
michael@0 692 aPaintServerSize.width, aPaintServerSize.height);
michael@0 693 overrideBounds.ScaleInverse(aFrame->PresContext()->AppUnitsPerDevPixel());
michael@0 694 nsRefPtr<gfxPattern> pattern =
michael@0 695 server->GetPaintServerPattern(aTarget, aContextMatrix,
michael@0 696 &nsStyleSVG::mFill, 1.0, &overrideBounds);
michael@0 697
michael@0 698 if (!pattern)
michael@0 699 return nullptr;
michael@0 700
michael@0 701 // pattern is now set up to fill aPaintServerSize. But we want it to
michael@0 702 // fill aRenderSize, so we need to add a scaling transform.
michael@0 703 // We couldn't just have set overrideBounds to aRenderSize - it would have
michael@0 704 // worked for gradients, but for patterns it would result in a different
michael@0 705 // pattern size.
michael@0 706 gfxFloat scaleX = overrideBounds.Width() / aRenderSize.width;
michael@0 707 gfxFloat scaleY = overrideBounds.Height() / aRenderSize.height;
michael@0 708 gfxMatrix scaleMatrix = gfxMatrix().Scale(scaleX, scaleY);
michael@0 709 pattern->SetMatrix(scaleMatrix.Multiply(pattern->GetMatrix()));
michael@0 710 nsRefPtr<gfxDrawable> drawable =
michael@0 711 new gfxPatternDrawable(pattern, aRenderSize);
michael@0 712 return drawable.forget();
michael@0 713 }
michael@0 714
michael@0 715 // We don't want to paint into a surface as long as we don't need to, so we
michael@0 716 // set up a drawing callback.
michael@0 717 nsRefPtr<gfxDrawingCallback> cb =
michael@0 718 new PaintFrameCallback(aFrame, aPaintServerSize, aRenderSize, aFlags);
michael@0 719 nsRefPtr<gfxDrawable> drawable = new gfxCallbackDrawable(cb, aRenderSize);
michael@0 720 return drawable.forget();
michael@0 721 }

mercurial