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/. */
7 #include "SVGContextPaint.h"
9 #include "gfxContext.h"
11 #include "mozilla/gfx/2D.h"
12 #include "mozilla/BasePrincipal.h"
13 #include "mozilla/dom/Document.h"
14 #include "mozilla/dom/SVGDocument.h"
15 #include "mozilla/extensions/WebExtensionPolicy.h"
16 #include "mozilla/StaticPrefs_svg.h"
17 #include "mozilla/SVGObserverUtils.h"
18 #include "mozilla/SVGUtils.h"
19 #include "SVGPaintServerFrame.h"
21 using namespace mozilla::gfx
;
22 using namespace mozilla::image
;
26 using image::imgDrawingParams
;
29 bool SVGContextPaint::IsAllowedForImageFromURI(nsIURI
* aURI
) {
30 if (StaticPrefs::svg_context_properties_content_enabled()) {
34 // Context paint is pref'ed off for Web content. Ideally we'd have some
35 // easy means to determine whether the frame that has linked to the image
36 // is a frame for a content node that originated from Web content.
37 // Unfortunately different types of anonymous content, about: documents
38 // such as about:reader, etc. that are "our" code that we ship are
39 // sometimes hard to distinguish from real Web content. As a result,
40 // instead of trying to figure out what content is "ours" we instead let
41 // any content provide image context paint, but only if the image is
42 // chrome:// or resource:// do we return true. This should be sufficient
43 // to stop the image context paint feature being useful to (and therefore
44 // used by and relied upon by) Web content. (We don't want Web content to
45 // use this feature because we're not sure that image context paint is a
46 // good mechanism for wider use, or suitable for specification.)
48 // Because the default favicon used in the browser UI needs context paint, we
49 // also allow it for page-icon:<page-url>. This exposes context paint to
50 // 3rd-party favicons, but only for history and bookmark items. Other places
51 // such as the tab bar don't use the page-icon protocol to load favicons.
53 // One case that is not covered by chrome:// or resource:// are WebExtensions,
54 // specifically ones that are "ours". WebExtensions are moz-extension://
55 // regardless if the extension is in-tree or not. Since we don't want
56 // extension developers coming to rely on image context paint either, we only
57 // enable context-paint for extensions that are owned by Mozilla
58 // (based on the extension permission "internal:svgContextPropertiesAllowed").
60 // We also allow this for browser UI icons that are served up from
61 // Mozilla-controlled domains listed in the
62 // svg.context-properties.content.allowed-domains pref.
65 if (NS_SUCCEEDED(aURI
->GetScheme(scheme
)) &&
66 (scheme
.EqualsLiteral("chrome") || scheme
.EqualsLiteral("resource") ||
67 scheme
.EqualsLiteral("page-icon"))) {
70 RefPtr
<BasePrincipal
> principal
=
71 BasePrincipal::CreateContentPrincipal(aURI
, OriginAttributes());
73 RefPtr
<extensions::WebExtensionPolicy
> addonPolicy
= principal
->AddonPolicy();
75 // Only allowed for extensions that have the
76 // internal:svgContextPropertiesAllowed permission (added internally from
77 // to Mozilla-owned extensions, see `isMozillaExtension` function
78 // defined in Extension.jsm for the exact criteria).
79 return addonPolicy
->HasPermission(
80 nsGkAtoms::svgContextPropertiesAllowedPermission
);
83 bool isInAllowList
= false;
84 principal
->IsURIInPrefList("svg.context-properties.content.allowed-domains",
90 * Stores in |aTargetPaint| information on how to reconstruct the current
91 * fill or stroke pattern. Will also set the paint opacity to transparent if
92 * the paint is set to "none".
93 * @param aOuterContextPaint pattern information from the outer text context
94 * @param aTargetPaint where to store the current pattern information
95 * @param aFillOrStroke member pointer to the paint we are setting up
97 static void SetupInheritablePaint(const DrawTarget
* aDrawTarget
,
98 const gfxMatrix
& aContextMatrix
,
99 nsIFrame
* aFrame
, float& aOpacity
,
100 SVGContextPaint
* aOuterContextPaint
,
101 SVGContextPaintImpl::Paint
& aTargetPaint
,
102 StyleSVGPaint
nsStyleSVG::*aFillOrStroke
,
103 imgDrawingParams
& aImgParams
) {
104 const nsStyleSVG
* style
= aFrame
->StyleSVG();
105 SVGPaintServerFrame
* ps
=
106 SVGObserverUtils::GetAndObservePaintServer(aFrame
, aFillOrStroke
);
109 RefPtr
<gfxPattern
> pattern
=
110 ps
->GetPaintServerPattern(aFrame
, aDrawTarget
, aContextMatrix
,
111 aFillOrStroke
, aOpacity
, aImgParams
);
114 aTargetPaint
.SetPaintServer(aFrame
, aContextMatrix
, ps
);
119 if (aOuterContextPaint
) {
120 RefPtr
<gfxPattern
> pattern
;
121 auto tag
= SVGContextPaintImpl::Paint::Tag::None
;
122 switch ((style
->*aFillOrStroke
).kind
.tag
) {
123 case StyleSVGPaintKind::Tag::ContextFill
:
124 tag
= SVGContextPaintImpl::Paint::Tag::ContextFill
;
125 pattern
= aOuterContextPaint
->GetFillPattern(
126 aDrawTarget
, aOpacity
, aContextMatrix
, aImgParams
);
128 case StyleSVGPaintKind::Tag::ContextStroke
:
129 tag
= SVGContextPaintImpl::Paint::Tag::ContextStroke
;
130 pattern
= aOuterContextPaint
->GetStrokePattern(
131 aDrawTarget
, aOpacity
, aContextMatrix
, aImgParams
);
136 aTargetPaint
.SetContextPaint(aOuterContextPaint
, tag
);
142 SVGUtils::GetFallbackOrPaintColor(*aFrame
->Style(), aFillOrStroke
);
143 aTargetPaint
.SetColor(color
);
146 DrawMode
SVGContextPaintImpl::Init(const DrawTarget
* aDrawTarget
,
147 const gfxMatrix
& aContextMatrix
,
149 SVGContextPaint
* aOuterContextPaint
,
150 imgDrawingParams
& aImgParams
) {
151 DrawMode toDraw
= DrawMode(0);
153 const nsStyleSVG
* style
= aFrame
->StyleSVG();
156 if (style
->mFill
.kind
.IsNone()) {
157 SetFillOpacity(0.0f
);
160 SVGUtils::GetOpacity(style
->mFillOpacity
, aOuterContextPaint
);
162 SetupInheritablePaint(aDrawTarget
, aContextMatrix
, aFrame
, opacity
,
163 aOuterContextPaint
, mFillPaint
, &nsStyleSVG::mFill
,
166 SetFillOpacity(opacity
);
168 toDraw
|= DrawMode::GLYPH_FILL
;
172 if (style
->mStroke
.kind
.IsNone()) {
173 SetStrokeOpacity(0.0f
);
176 SVGUtils::GetOpacity(style
->mStrokeOpacity
, aOuterContextPaint
);
178 SetupInheritablePaint(aDrawTarget
, aContextMatrix
, aFrame
, opacity
,
179 aOuterContextPaint
, mStrokePaint
,
180 &nsStyleSVG::mStroke
, aImgParams
);
182 SetStrokeOpacity(opacity
);
184 toDraw
|= DrawMode::GLYPH_STROKE
;
190 void SVGContextPaint::InitStrokeGeometry(gfxContext
* aContext
,
191 float devUnitsPerSVGUnit
) {
192 mStrokeWidth
= aContext
->CurrentLineWidth() / devUnitsPerSVGUnit
;
193 aContext
->CurrentDash(mDashes
, &mDashOffset
);
194 for (uint32_t i
= 0; i
< mDashes
.Length(); i
++) {
195 mDashes
[i
] /= devUnitsPerSVGUnit
;
197 mDashOffset
/= devUnitsPerSVGUnit
;
200 SVGContextPaint
* SVGContextPaint::GetContextPaint(nsIContent
* aContent
) {
201 dom::Document
* ownerDoc
= aContent
->OwnerDoc();
202 if (!ownerDoc
->IsSVGDocument()) {
206 const auto* contextPaint
=
207 ownerDoc
->AsSVGDocument()->GetCurrentContextPaint();
208 MOZ_ASSERT_IF(contextPaint
, ownerDoc
->IsBeingUsedAsImage());
210 // XXX The SVGContextPaint that SVGDocument keeps around is const. We could
211 // and should keep that constness to the SVGContextPaint that we get here
212 // (SVGImageContext is never changed after it is initialized).
214 // Unfortunately lazy initialization of SVGContextPaint (which is a member of
215 // SVGImageContext, and also conceptually never changes after construction)
216 // prevents some of SVGContextPaint's conceptually const methods from being
217 // const. Trying to fix SVGContextPaint (perhaps by using |mutable|) is a
218 // bit of a headache so for now we punt on that, don't reapply the constness
219 // to the SVGContextPaint here, and trust that no one will add code that
220 // actually modifies the object.
221 return const_cast<SVGContextPaint
*>(contextPaint
);
224 already_AddRefed
<gfxPattern
> SVGContextPaintImpl::GetFillPattern(
225 const DrawTarget
* aDrawTarget
, float aOpacity
, const gfxMatrix
& aCTM
,
226 imgDrawingParams
& aImgParams
) {
227 return mFillPaint
.GetPattern(aDrawTarget
, aOpacity
, &nsStyleSVG::mFill
, aCTM
,
231 already_AddRefed
<gfxPattern
> SVGContextPaintImpl::GetStrokePattern(
232 const DrawTarget
* aDrawTarget
, float aOpacity
, const gfxMatrix
& aCTM
,
233 imgDrawingParams
& aImgParams
) {
234 return mStrokePaint
.GetPattern(aDrawTarget
, aOpacity
, &nsStyleSVG::mStroke
,
238 already_AddRefed
<gfxPattern
> SVGContextPaintImpl::Paint::GetPattern(
239 const DrawTarget
* aDrawTarget
, float aOpacity
,
240 StyleSVGPaint
nsStyleSVG::*aFillOrStroke
, const gfxMatrix
& aCTM
,
241 imgDrawingParams
& aImgParams
) {
242 RefPtr
<gfxPattern
> pattern
;
243 if (mPatternCache
.Get(aOpacity
, getter_AddRefs(pattern
))) {
244 // Set the pattern matrix just in case it was messed with by a previous
245 // caller. We should get the same matrix each time a pattern is constructed
246 // so this should be fine.
247 pattern
->SetMatrix(aCTM
* mPatternMatrix
);
248 return pattern
.forget();
251 switch (mPaintType
) {
253 pattern
= new gfxPattern(DeviceColor());
254 mPatternMatrix
= gfxMatrix();
257 DeviceColor color
= ToDeviceColor(mPaintDefinition
.mColor
);
259 pattern
= new gfxPattern(color
);
260 mPatternMatrix
= gfxMatrix();
263 case Tag::PaintServer
:
264 pattern
= mPaintDefinition
.mPaintServerFrame
->GetPaintServerPattern(
265 mFrame
, aDrawTarget
, mContextMatrix
, aFillOrStroke
, aOpacity
,
268 // m maps original-user-space to pattern space
269 gfxMatrix m
= pattern
->GetMatrix();
270 gfxMatrix deviceToOriginalUserSpace
= mContextMatrix
;
271 if (!deviceToOriginalUserSpace
.Invert()) {
274 // mPatternMatrix maps device space to pattern space via original user
276 mPatternMatrix
= deviceToOriginalUserSpace
* m
;
278 pattern
->SetMatrix(aCTM
* mPatternMatrix
);
280 case Tag::ContextFill
:
281 pattern
= mPaintDefinition
.mContextPaint
->GetFillPattern(
282 aDrawTarget
, aOpacity
, aCTM
, aImgParams
);
283 // Don't cache this. mContextPaint will have cached it anyway. If we
284 // cache it, we'll have to compute mPatternMatrix, which is annoying.
285 return pattern
.forget();
286 case Tag::ContextStroke
:
287 pattern
= mPaintDefinition
.mContextPaint
->GetStrokePattern(
288 aDrawTarget
, aOpacity
, aCTM
, aImgParams
);
289 // Don't cache this. mContextPaint will have cached it anyway. If we
290 // cache it, we'll have to compute mPatternMatrix, which is annoying.
291 return pattern
.forget();
293 MOZ_ASSERT(false, "invalid paint type");
297 mPatternCache
.InsertOrUpdate(aOpacity
, RefPtr
{pattern
});
298 return pattern
.forget();
301 AutoSetRestoreSVGContextPaint::AutoSetRestoreSVGContextPaint(
302 const SVGContextPaint
& aContextPaint
, dom::SVGDocument
& aSVGDocument
)
303 : mSVGDocument(aSVGDocument
),
304 mOuterContextPaint(aSVGDocument
.GetCurrentContextPaint()) {
305 MOZ_ASSERT(aSVGDocument
.IsBeingUsedAsImage(),
306 "SVGContextPaint::GetContextPaint assumes this");
308 mSVGDocument
.SetCurrentContextPaint(&aContextPaint
);
311 AutoSetRestoreSVGContextPaint::~AutoSetRestoreSVGContextPaint() {
312 mSVGDocument
.SetCurrentContextPaint(mOuterContextPaint
);
315 // SVGEmbeddingContextPaint
317 already_AddRefed
<gfxPattern
> SVGEmbeddingContextPaint::GetFillPattern(
318 const DrawTarget
* aDrawTarget
, float aFillOpacity
, const gfxMatrix
& aCTM
,
319 imgDrawingParams
& aImgParams
) {
323 // The gfxPattern that we create below depends on aFillOpacity, and since
324 // different elements in the SVG image may pass in different values for
325 // fill opacities we don't try to cache the gfxPattern that we create.
326 DeviceColor fill
= *mFill
;
327 fill
.a
*= aFillOpacity
;
328 return do_AddRef(new gfxPattern(fill
));
331 already_AddRefed
<gfxPattern
> SVGEmbeddingContextPaint::GetStrokePattern(
332 const DrawTarget
* aDrawTarget
, float aStrokeOpacity
, const gfxMatrix
& aCTM
,
333 imgDrawingParams
& aImgParams
) {
337 DeviceColor stroke
= *mStroke
;
338 stroke
.a
*= aStrokeOpacity
;
339 return do_AddRef(new gfxPattern(stroke
));
342 uint32_t SVGEmbeddingContextPaint::Hash() const {
346 hash
= HashGeneric(hash
, mFill
->ToABGR());
348 // Arbitrary number, just to avoid trivial hash collisions between pairs of
349 // instances where one embedding context has fill set to the same value as
350 // another context has stroke set to.
355 hash
= HashGeneric(hash
, mStroke
->ToABGR());
358 if (mFillOpacity
!= 1.0f
) {
359 hash
= HashGeneric(hash
, mFillOpacity
);
362 if (mStrokeOpacity
!= 1.0f
) {
363 hash
= HashGeneric(hash
, mStrokeOpacity
);
369 } // namespace mozilla