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