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