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 #include "SVGContextPaint.h"
8 
9 #include "gfxContext.h"
10 #include "gfxUtils.h"
11 #include "mozilla/gfx/2D.h"
12 #include "mozilla/Preferences.h"
13 #include "nsIDocument.h"
14 #include "nsSVGPaintServerFrame.h"
15 #include "SVGObserverUtils.h"
16 #include "nsSVGPaintServerFrame.h"
17 
18 using namespace mozilla::gfx;
19 using namespace mozilla::image;
20 
21 namespace mozilla {
22 
23 using image::imgDrawingParams;
24 
IsAllowedForImageFromURI(nsIURI * aURI)25 /* static */ bool SVGContextPaint::IsAllowedForImageFromURI(nsIURI* aURI) {
26   static bool sEnabledForContent = false;
27   static bool sEnabledForContentCached = false;
28 
29   if (!sEnabledForContentCached) {
30     Preferences::AddBoolVarCache(
31         &sEnabledForContent, "svg.context-properties.content.enabled", false);
32     sEnabledForContentCached = true;
33   }
34 
35   if (sEnabledForContent) {
36     return true;
37   }
38 
39   // Context paint is pref'ed off for Web content.  Ideally we'd have some
40   // easy means to determine whether the frame that has linked to the image
41   // is a frame for a content node that originated from Web content.
42   // Unfortunately different types of anonymous content, about: documents
43   // such as about:reader, etc. that are "our" code that we ship are
44   // sometimes hard to distinguish from real Web content.  As a result,
45   // instead of trying to figure out what content is "ours" we instead let
46   // any content provide image context paint, but only if the image is
47   // chrome:// or resource:// do we return true.  This should be sufficient
48   // to stop the image context paint feature being useful to (and therefore
49   // used by and relied upon by) Web content.  (We don't want Web content to
50   // use this feature because we're not sure that image context paint is a
51   // good mechanism for wider use, or suitable for specification.)
52   //
53   // Because the default favicon used in the browser UI needs context paint, we
54   // also allow it for page-icon:<page-url>. This exposes context paint to
55   // 3rd-party favicons, but only for history and bookmark items. Other places
56   // such as the tab bar don't use the page-icon protocol to load favicons.
57   //
58   // One case that is not covered by chrome:// or resource:// are WebExtensions,
59   // specifically ones that are "ours". WebExtensions are moz-extension://
60   // regardless if the extension is in-tree or not. Since we don't want
61   // extension developers coming to rely on image context paint either, we only
62   // enable context-paint for extensions that are signed by Mozilla.
63   //
64   nsAutoCString scheme;
65   if (NS_SUCCEEDED(aURI->GetScheme(scheme)) &&
66       (scheme.EqualsLiteral("chrome") || scheme.EqualsLiteral("resource") ||
67        scheme.EqualsLiteral("page-icon"))) {
68     return true;
69   }
70   RefPtr<BasePrincipal> principal =
71       BasePrincipal::CreateCodebasePrincipal(aURI, OriginAttributes());
72   nsString addonId;
73   if (NS_SUCCEEDED(principal->GetAddonId(addonId))) {
74     if (StringEndsWith(addonId, NS_LITERAL_STRING("@mozilla.org")) ||
75         StringEndsWith(addonId, NS_LITERAL_STRING("@mozilla.com")) ||
76         StringBeginsWith(addonId, NS_LITERAL_STRING("@testpilot-"))) {
77       return true;
78     }
79   }
80   return false;
81 }
82 
83 /**
84  * Stores in |aTargetPaint| information on how to reconstruct the current
85  * fill or stroke pattern. Will also set the paint opacity to transparent if
86  * the paint is set to "none".
87  * @param aOuterContextPaint pattern information from the outer text context
88  * @param aTargetPaint where to store the current pattern information
89  * @param aFillOrStroke member pointer to the paint we are setting up
90  * @param aProperty the frame property descriptor of the fill or stroke paint
91  *   server frame
92  */
SetupInheritablePaint(const DrawTarget * aDrawTarget,const gfxMatrix & aContextMatrix,nsIFrame * aFrame,float & aOpacity,SVGContextPaint * aOuterContextPaint,SVGContextPaintImpl::Paint & aTargetPaint,nsStyleSVGPaint nsStyleSVG::* aFillOrStroke,SVGObserverUtils::PaintingPropertyDescriptor aProperty,imgDrawingParams & aImgParams)93 static void SetupInheritablePaint(
94     const DrawTarget* aDrawTarget, const gfxMatrix& aContextMatrix,
95     nsIFrame* aFrame, float& aOpacity, SVGContextPaint* aOuterContextPaint,
96     SVGContextPaintImpl::Paint& aTargetPaint,
97     nsStyleSVGPaint nsStyleSVG::*aFillOrStroke,
98     SVGObserverUtils::PaintingPropertyDescriptor aProperty,
99     imgDrawingParams& aImgParams) {
100   const nsStyleSVG* style = aFrame->StyleSVG();
101   nsSVGPaintServerFrame* ps =
102       SVGObserverUtils::GetPaintServer(aFrame, aFillOrStroke, aProperty);
103 
104   if (ps) {
105     RefPtr<gfxPattern> pattern =
106         ps->GetPaintServerPattern(aFrame, aDrawTarget, aContextMatrix,
107                                   aFillOrStroke, aOpacity, aImgParams);
108 
109     if (pattern) {
110       aTargetPaint.SetPaintServer(aFrame, aContextMatrix, ps);
111       return;
112     }
113   }
114 
115   if (aOuterContextPaint) {
116     RefPtr<gfxPattern> pattern;
117     switch ((style->*aFillOrStroke).Type()) {
118       case eStyleSVGPaintType_ContextFill:
119         pattern = aOuterContextPaint->GetFillPattern(
120             aDrawTarget, aOpacity, aContextMatrix, aImgParams);
121         break;
122       case eStyleSVGPaintType_ContextStroke:
123         pattern = aOuterContextPaint->GetStrokePattern(
124             aDrawTarget, aOpacity, aContextMatrix, aImgParams);
125         break;
126       default:;
127     }
128     if (pattern) {
129       aTargetPaint.SetContextPaint(aOuterContextPaint,
130                                    (style->*aFillOrStroke).Type());
131       return;
132     }
133   }
134 
135   nscolor color = nsSVGUtils::GetFallbackOrPaintColor(aFrame->StyleContext(),
136                                                       aFillOrStroke);
137   aTargetPaint.SetColor(color);
138 }
139 
Init(const DrawTarget * aDrawTarget,const gfxMatrix & aContextMatrix,nsIFrame * aFrame,SVGContextPaint * aOuterContextPaint,imgDrawingParams & aImgParams)140 DrawMode SVGContextPaintImpl::Init(const DrawTarget* aDrawTarget,
141                                    const gfxMatrix& aContextMatrix,
142                                    nsIFrame* aFrame,
143                                    SVGContextPaint* aOuterContextPaint,
144                                    imgDrawingParams& aImgParams) {
145   DrawMode toDraw = DrawMode(0);
146 
147   const nsStyleSVG* style = aFrame->StyleSVG();
148 
149   // fill:
150   if (style->mFill.Type() == eStyleSVGPaintType_None) {
151     SetFillOpacity(0.0f);
152   } else {
153     float opacity = nsSVGUtils::GetOpacity(
154         style->FillOpacitySource(), style->mFillOpacity, aOuterContextPaint);
155 
156     SetupInheritablePaint(aDrawTarget, aContextMatrix, aFrame, opacity,
157                           aOuterContextPaint, mFillPaint, &nsStyleSVG::mFill,
158                           SVGObserverUtils::FillProperty(), aImgParams);
159 
160     SetFillOpacity(opacity);
161 
162     toDraw |= DrawMode::GLYPH_FILL;
163   }
164 
165   // stroke:
166   if (style->mStroke.Type() == eStyleSVGPaintType_None) {
167     SetStrokeOpacity(0.0f);
168   } else {
169     float opacity =
170         nsSVGUtils::GetOpacity(style->StrokeOpacitySource(),
171                                style->mStrokeOpacity, aOuterContextPaint);
172 
173     SetupInheritablePaint(aDrawTarget, aContextMatrix, aFrame, opacity,
174                           aOuterContextPaint, mStrokePaint,
175                           &nsStyleSVG::mStroke,
176                           SVGObserverUtils::StrokeProperty(), aImgParams);
177 
178     SetStrokeOpacity(opacity);
179 
180     toDraw |= DrawMode::GLYPH_STROKE;
181   }
182 
183   return toDraw;
184 }
185 
InitStrokeGeometry(gfxContext * aContext,float devUnitsPerSVGUnit)186 void SVGContextPaint::InitStrokeGeometry(gfxContext* aContext,
187                                          float devUnitsPerSVGUnit) {
188   mStrokeWidth = aContext->CurrentLineWidth() / devUnitsPerSVGUnit;
189   aContext->CurrentDash(mDashes, &mDashOffset);
190   for (uint32_t i = 0; i < mDashes.Length(); i++) {
191     mDashes[i] /= devUnitsPerSVGUnit;
192   }
193   mDashOffset /= devUnitsPerSVGUnit;
194 }
195 
GetContextPaint(nsIContent * aContent)196 /* static */ SVGContextPaint* SVGContextPaint::GetContextPaint(
197     nsIContent* aContent) {
198   nsIDocument* ownerDoc = aContent->OwnerDoc();
199 
200   if (!ownerDoc->IsBeingUsedAsImage()) {
201     return nullptr;
202   }
203 
204   // XXX The SVGContextPaint that was passed to SetProperty was const. Ideally
205   // we could and should re-apply that constness to the SVGContextPaint that
206   // we get here (SVGImageContext is never changed after it is initialized).
207   // Unfortunately lazy initialization of SVGContextPaint (which is a member of
208   // SVGImageContext, and also conceptually never changes after construction)
209   // prevents some of SVGContextPaint's conceptually const methods from being
210   // const.  Trying to fix SVGContextPaint (perhaps by using |mutable|) is a
211   // bit of a headache so for now we punt on that, don't reapply the constness
212   // to the SVGContextPaint here, and trust that no one will add code that
213   // actually modifies the object.
214 
215   return static_cast<SVGContextPaint*>(
216       ownerDoc->GetProperty(nsGkAtoms::svgContextPaint));
217 }
218 
GetFillPattern(const DrawTarget * aDrawTarget,float aOpacity,const gfxMatrix & aCTM,imgDrawingParams & aImgParams)219 already_AddRefed<gfxPattern> SVGContextPaintImpl::GetFillPattern(
220     const DrawTarget* aDrawTarget, float aOpacity, const gfxMatrix& aCTM,
221     imgDrawingParams& aImgParams) {
222   return mFillPaint.GetPattern(aDrawTarget, aOpacity, &nsStyleSVG::mFill, aCTM,
223                                aImgParams);
224 }
225 
GetStrokePattern(const DrawTarget * aDrawTarget,float aOpacity,const gfxMatrix & aCTM,imgDrawingParams & aImgParams)226 already_AddRefed<gfxPattern> SVGContextPaintImpl::GetStrokePattern(
227     const DrawTarget* aDrawTarget, float aOpacity, const gfxMatrix& aCTM,
228     imgDrawingParams& aImgParams) {
229   return mStrokePaint.GetPattern(aDrawTarget, aOpacity, &nsStyleSVG::mStroke,
230                                  aCTM, aImgParams);
231 }
232 
GetPattern(const DrawTarget * aDrawTarget,float aOpacity,nsStyleSVGPaint nsStyleSVG::* aFillOrStroke,const gfxMatrix & aCTM,imgDrawingParams & aImgParams)233 already_AddRefed<gfxPattern> SVGContextPaintImpl::Paint::GetPattern(
234     const DrawTarget* aDrawTarget, float aOpacity,
235     nsStyleSVGPaint nsStyleSVG::*aFillOrStroke, const gfxMatrix& aCTM,
236     imgDrawingParams& aImgParams) {
237   RefPtr<gfxPattern> pattern;
238   if (mPatternCache.Get(aOpacity, getter_AddRefs(pattern))) {
239     // Set the pattern matrix just in case it was messed with by a previous
240     // caller. We should get the same matrix each time a pattern is constructed
241     // so this should be fine.
242     pattern->SetMatrix(aCTM * mPatternMatrix);
243     return pattern.forget();
244   }
245 
246   switch (mPaintType) {
247     case eStyleSVGPaintType_None:
248       pattern = new gfxPattern(Color());
249       mPatternMatrix = gfxMatrix();
250       break;
251     case eStyleSVGPaintType_Color: {
252       Color color = Color::FromABGR(mPaintDefinition.mColor);
253       color.a *= aOpacity;
254       pattern = new gfxPattern(color);
255       mPatternMatrix = gfxMatrix();
256       break;
257     }
258     case eStyleSVGPaintType_Server:
259       pattern = mPaintDefinition.mPaintServerFrame->GetPaintServerPattern(
260           mFrame, aDrawTarget, mContextMatrix, aFillOrStroke, aOpacity,
261           aImgParams);
262       {
263         // m maps original-user-space to pattern space
264         gfxMatrix m = pattern->GetMatrix();
265         gfxMatrix deviceToOriginalUserSpace = mContextMatrix;
266         if (!deviceToOriginalUserSpace.Invert()) {
267           return nullptr;
268         }
269         // mPatternMatrix maps device space to pattern space via original user
270         // space
271         mPatternMatrix = deviceToOriginalUserSpace * m;
272       }
273       pattern->SetMatrix(aCTM * mPatternMatrix);
274       break;
275     case eStyleSVGPaintType_ContextFill:
276       pattern = mPaintDefinition.mContextPaint->GetFillPattern(
277           aDrawTarget, aOpacity, aCTM, aImgParams);
278       // Don't cache this. mContextPaint will have cached it anyway. If we
279       // cache it, we'll have to compute mPatternMatrix, which is annoying.
280       return pattern.forget();
281     case eStyleSVGPaintType_ContextStroke:
282       pattern = mPaintDefinition.mContextPaint->GetStrokePattern(
283           aDrawTarget, aOpacity, aCTM, aImgParams);
284       // Don't cache this. mContextPaint will have cached it anyway. If we
285       // cache it, we'll have to compute mPatternMatrix, which is annoying.
286       return pattern.forget();
287     default:
288       MOZ_ASSERT(false, "invalid paint type");
289       return nullptr;
290   }
291 
292   mPatternCache.Put(aOpacity, pattern);
293   return pattern.forget();
294 }
295 
AutoSetRestoreSVGContextPaint(const SVGContextPaint * aContextPaint,nsIDocument * aSVGDocument)296 AutoSetRestoreSVGContextPaint::AutoSetRestoreSVGContextPaint(
297     const SVGContextPaint* aContextPaint, nsIDocument* aSVGDocument)
298     : mSVGDocument(aSVGDocument),
299       mOuterContextPaint(
300           aSVGDocument->GetProperty(nsGkAtoms::svgContextPaint)) {
301   // The way that we supply context paint is to temporarily set the context
302   // paint on the owner document of the SVG that we're painting while it's
303   // being painted.
304 
305   MOZ_ASSERT(aContextPaint);
306   MOZ_ASSERT(aSVGDocument->IsBeingUsedAsImage(),
307              "SVGContextPaint::GetContextPaint assumes this");
308 
309   if (mOuterContextPaint) {
310     mSVGDocument->UnsetProperty(nsGkAtoms::svgContextPaint);
311   }
312 
313   DebugOnly<nsresult> res = mSVGDocument->SetProperty(
314       nsGkAtoms::svgContextPaint, const_cast<SVGContextPaint*>(aContextPaint));
315 
316   NS_WARNING_ASSERTION(NS_SUCCEEDED(res), "Failed to set context paint");
317 }
318 
~AutoSetRestoreSVGContextPaint()319 AutoSetRestoreSVGContextPaint::~AutoSetRestoreSVGContextPaint() {
320   mSVGDocument->UnsetProperty(nsGkAtoms::svgContextPaint);
321   if (mOuterContextPaint) {
322     DebugOnly<nsresult> res = mSVGDocument->SetProperty(
323         nsGkAtoms::svgContextPaint, mOuterContextPaint);
324 
325     NS_WARNING_ASSERTION(NS_SUCCEEDED(res), "Failed to restore context paint");
326   }
327 }
328 
329 // SVGEmbeddingContextPaint
330 
GetFillPattern(const DrawTarget * aDrawTarget,float aFillOpacity,const gfxMatrix & aCTM,imgDrawingParams & aImgParams)331 already_AddRefed<gfxPattern> SVGEmbeddingContextPaint::GetFillPattern(
332     const DrawTarget* aDrawTarget, float aFillOpacity, const gfxMatrix& aCTM,
333     imgDrawingParams& aImgParams) {
334   if (!mFill) {
335     return nullptr;
336   }
337   // The gfxPattern that we create below depends on aFillOpacity, and since
338   // different elements in the SVG image may pass in different values for
339   // fill opacities we don't try to cache the gfxPattern that we create.
340   Color fill = *mFill;
341   fill.a *= aFillOpacity;
342   return do_AddRef(new gfxPattern(fill));
343 }
344 
GetStrokePattern(const DrawTarget * aDrawTarget,float aStrokeOpacity,const gfxMatrix & aCTM,imgDrawingParams & aImgParams)345 already_AddRefed<gfxPattern> SVGEmbeddingContextPaint::GetStrokePattern(
346     const DrawTarget* aDrawTarget, float aStrokeOpacity, const gfxMatrix& aCTM,
347     imgDrawingParams& aImgParams) {
348   if (!mStroke) {
349     return nullptr;
350   }
351   Color stroke = *mStroke;
352   stroke.a *= aStrokeOpacity;
353   return do_AddRef(new gfxPattern(stroke));
354 }
355 
Hash() const356 uint32_t SVGEmbeddingContextPaint::Hash() const {
357   uint32_t hash = 0;
358 
359   if (mFill) {
360     hash = HashGeneric(hash, mFill->ToABGR());
361   } else {
362     // Arbitrary number, just to avoid trivial hash collisions between pairs of
363     // instances where one embedding context has fill set to the same value as
364     // another context has stroke set to.
365     hash = 1;
366   }
367 
368   if (mStroke) {
369     hash = HashGeneric(hash, mStroke->ToABGR());
370   }
371 
372   if (mFillOpacity != 1.0f) {
373     hash = HashGeneric(hash, mFillOpacity);
374   }
375 
376   if (mStrokeOpacity != 1.0f) {
377     hash = HashGeneric(hash, mStrokeOpacity);
378   }
379 
380   return hash;
381 }
382 
383 }  // namespace mozilla
384