CanvasRenderingContext2D.cpp (251594B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "CanvasRenderingContext2D.h" 7 8 #include <algorithm> 9 10 #include "CanvasImageCache.h" 11 #include "CanvasUtils.h" 12 #include "GeckoBindings.h" 13 #include "ImageEncoder.h" 14 #include "ImageRegion.h" 15 #include "LayerUserData.h" 16 #include "Units.h" 17 #include "WindowRenderer.h" 18 #include "gfxBlur.h" 19 #include "gfxContext.h" 20 #include "gfxFont.h" 21 #include "gfxPlatform.h" 22 #include "gfxTextRun.h" 23 #include "gfxUtils.h" 24 #include "js/Array.h" // JS::GetArrayLength 25 #include "js/Conversions.h" 26 #include "js/HeapAPI.h" 27 #include "js/PropertyAndElement.h" // JS_GetElement 28 #include "js/Warnings.h" // JS::WarnASCII 29 #include "js/experimental/TypedData.h" // JS_NewUint8ClampedArray, JS_GetUint8ClampedArrayData 30 #include "jsapi.h" 31 #include "jsfriendapi.h" 32 #include "mozilla/Assertions.h" 33 #include "mozilla/CheckedInt.h" 34 #include "mozilla/CycleCollectedJSRuntime.h" 35 #include "mozilla/DebugOnly.h" 36 #include "mozilla/FilterInstance.h" 37 #include "mozilla/GeckoBindings.h" 38 #include "mozilla/Logging.h" 39 #include "mozilla/MathAlgorithms.h" 40 #include "mozilla/Preferences.h" 41 #include "mozilla/PresShell.h" 42 #include "mozilla/PresShellInlines.h" 43 #include "mozilla/RestyleManager.h" 44 #include "mozilla/SVGContentUtils.h" 45 #include "mozilla/SVGImageContext.h" 46 #include "mozilla/SVGObserverUtils.h" 47 #include "mozilla/ServoBindings.h" 48 #include "mozilla/ServoCSSParser.h" 49 #include "mozilla/ServoStyleSet.h" 50 #include "mozilla/StaticPrefs_browser.h" 51 #include "mozilla/StaticPrefs_gfx.h" 52 #include "mozilla/TimeStamp.h" 53 #include "mozilla/UniquePtr.h" 54 #include "mozilla/dom/CanvasGradient.h" 55 #include "mozilla/dom/CanvasPath.h" 56 #include "mozilla/dom/CanvasPattern.h" 57 #include "mozilla/dom/CanvasRenderingContext2DBinding.h" 58 #include "mozilla/dom/DOMMatrix.h" 59 #include "mozilla/dom/Document.h" 60 #include "mozilla/dom/FontFaceSet.h" 61 #include "mozilla/dom/FontFaceSetImpl.h" 62 #include "mozilla/dom/GeneratePlaceholderCanvasData.h" 63 #include "mozilla/dom/HTMLCanvasElement.h" 64 #include "mozilla/dom/HTMLImageElement.h" 65 #include "mozilla/dom/HTMLVideoElement.h" 66 #include "mozilla/dom/ImageBitmap.h" 67 #include "mozilla/dom/ImageData.h" 68 #include "mozilla/dom/PBrowserParent.h" 69 #include "mozilla/dom/SVGImageElement.h" 70 #include "mozilla/dom/TextMetrics.h" 71 #include "mozilla/dom/ToJSValue.h" 72 #include "mozilla/dom/TypedArray.h" 73 #include "mozilla/dom/VideoFrame.h" 74 #include "mozilla/dom/WorkerPrivate.h" 75 #include "mozilla/gfx/2D.h" 76 #include "mozilla/gfx/CanvasShutdownManager.h" 77 #include "mozilla/gfx/DataSurfaceHelpers.h" 78 #include "mozilla/gfx/Filters.h" 79 #include "mozilla/gfx/Helpers.h" 80 #include "mozilla/gfx/PathHelpers.h" 81 #include "mozilla/gfx/PatternHelpers.h" 82 #include "mozilla/gfx/Swizzle.h" 83 #include "mozilla/gfx/Tools.h" 84 #include "mozilla/intl/BidiEmbeddingLevel.h" 85 #include "mozilla/layers/CanvasClient.h" 86 #include "mozilla/layers/ImageBridgeChild.h" 87 #include "mozilla/layers/PersistentBufferProvider.h" 88 #include "mozilla/layers/WebRenderCanvasRenderer.h" 89 #include "mozilla/layers/WebRenderUserData.h" 90 #include "nsBidiPresUtils.h" 91 #include "nsCCUncollectableMarker.h" 92 #include "nsCSSPseudoElements.h" 93 #include "nsCSSValue.h" 94 #include "nsColor.h" 95 #include "nsComputedDOMStyle.h" 96 #include "nsContentUtils.h" 97 #include "nsDeviceContext.h" 98 #include "nsDisplayList.h" 99 #include "nsError.h" 100 #include "nsFocusManager.h" 101 #include "nsFontMetrics.h" 102 #include "nsFrameLoader.h" 103 #include "nsGfxCIID.h" 104 #include "nsGlobalWindowInner.h" 105 #include "nsIDocShell.h" 106 #include "nsIFrame.h" 107 #include "nsIInterfaceRequestorUtils.h" 108 #include "nsIMemoryReporter.h" 109 #include "nsLayoutUtils.h" 110 #include "nsMathUtils.h" 111 #include "nsPIDOMWindow.h" 112 #include "nsPresContext.h" 113 #include "nsPrintfCString.h" 114 #include "nsRFPService.h" 115 #include "nsReadableUtils.h" 116 #include "nsStyleUtil.h" 117 #include "nsTArray.h" 118 #include "nsWrapperCacheInlines.h" 119 #include "nsXULElement.h" 120 121 #undef free // apparently defined by some windows header, clashing with a 122 // free() method in SkTypes.h 123 124 #ifdef XP_WIN 125 # include "gfxWindowsPlatform.h" 126 #endif 127 128 // windows.h (included by chromium code) defines this, in its infinite wisdom 129 #undef DrawText 130 131 using namespace mozilla; 132 using namespace mozilla::CanvasUtils; 133 using namespace mozilla::css; 134 using namespace mozilla::gfx; 135 using namespace mozilla::image; 136 using namespace mozilla::ipc; 137 using namespace mozilla::layers; 138 139 static mozilla::LazyLogModule gFingerprinterDetection("FingerprinterDetection"); 140 141 namespace mozilla::dom { 142 143 // Cap sigma to avoid overly large temp surfaces. 144 const Float SIGMA_MAX = 100; 145 146 const size_t MAX_STYLE_STACK_SIZE = 1024; 147 148 /* Memory reporter stuff */ 149 static Atomic<int64_t> gCanvasAzureMemoryUsed(0); 150 151 // Adds Save() / Restore() calls to the scope. 152 class MOZ_RAII AutoSaveRestore { 153 public: 154 explicit AutoSaveRestore(CanvasRenderingContext2D* aCtx) : mCtx(aCtx) { 155 mCtx->Save(); 156 } 157 ~AutoSaveRestore() { mCtx->Restore(); } 158 159 private: 160 RefPtr<CanvasRenderingContext2D> mCtx; 161 }; 162 163 // This is KIND_OTHER because it's not always clear where in memory the pixels 164 // of a canvas are stored. Furthermore, this memory will be tracked by the 165 // underlying surface implementations. See bug 655638 for details. 166 class Canvas2dPixelsReporter final : public nsIMemoryReporter { 167 ~Canvas2dPixelsReporter() = default; 168 169 public: 170 NS_DECL_ISUPPORTS 171 172 NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, 173 nsISupports* aData, bool aAnonymize) override { 174 MOZ_COLLECT_REPORT("canvas-2d-pixels", KIND_OTHER, UNITS_BYTES, 175 gCanvasAzureMemoryUsed, 176 "Memory used by 2D canvases. Each canvas requires " 177 "(width * height * 4) bytes."); 178 179 return NS_OK; 180 } 181 }; 182 183 NS_IMPL_ISUPPORTS(Canvas2dPixelsReporter, nsIMemoryReporter) 184 185 class CanvasConicGradient : public CanvasGradient { 186 public: 187 CanvasConicGradient(CanvasRenderingContext2D* aContext, Float aAngle, 188 const Point& aCenter) 189 : CanvasGradient(aContext, Type::CONIC), 190 mAngle(aAngle), 191 mCenter(aCenter) {} 192 193 const Float mAngle; 194 const Point mCenter; 195 }; 196 197 class CanvasRadialGradient : public CanvasGradient { 198 public: 199 CanvasRadialGradient(CanvasRenderingContext2D* aContext, 200 const Point& aBeginOrigin, Float aBeginRadius, 201 const Point& aEndOrigin, Float aEndRadius) 202 : CanvasGradient(aContext, Type::RADIAL), 203 mCenter1(aBeginOrigin), 204 mCenter2(aEndOrigin), 205 mRadius1(aBeginRadius), 206 mRadius2(aEndRadius) {} 207 208 Point mCenter1; 209 Point mCenter2; 210 Float mRadius1; 211 Float mRadius2; 212 }; 213 214 class CanvasLinearGradient : public CanvasGradient { 215 public: 216 CanvasLinearGradient(CanvasRenderingContext2D* aContext, const Point& aBegin, 217 const Point& aEnd) 218 : CanvasGradient(aContext, Type::LINEAR), mBegin(aBegin), mEnd(aEnd) {} 219 220 protected: 221 friend struct CanvasBidiProcessor; 222 friend class CanvasGeneralPattern; 223 224 // Beginning of linear gradient. 225 Point mBegin; 226 // End of linear gradient. 227 Point mEnd; 228 }; 229 230 bool CanvasRenderingContext2D::PatternIsOpaque( 231 CanvasRenderingContext2D::Style aStyle, bool* aIsColor) const { 232 const ContextState& state = CurrentState(); 233 bool opaque = false; 234 bool color = false; 235 if (state.globalAlpha >= 1.0) { 236 if (state.patternStyles[aStyle] && state.patternStyles[aStyle]->mSurface) { 237 opaque = IsOpaque(state.patternStyles[aStyle]->mSurface->GetFormat()); 238 } else if (!state.gradientStyles[aStyle]) { 239 // TODO: for gradient patterns we could check that all stops are opaque 240 // colors. 241 // it's a color pattern. 242 opaque = sRGBColor::FromABGR(state.colorStyles[aStyle]).a >= 1.0; 243 color = true; 244 } 245 } 246 if (aIsColor) { 247 *aIsColor = color; 248 } 249 return opaque; 250 } 251 252 // This class is named 'GeneralCanvasPattern' instead of just 253 // 'GeneralPattern' to keep Windows PGO builds from confusing the 254 // GeneralPattern class in gfxContext.cpp with this one. 255 class CanvasGeneralPattern { 256 public: 257 using Style = CanvasRenderingContext2D::Style; 258 using ContextState = CanvasRenderingContext2D::ContextState; 259 260 Pattern& ForStyle(CanvasRenderingContext2D* aCtx, Style aStyle, 261 DrawTarget* aRT) { 262 // This should only be called once or the mPattern destructor will 263 // not be executed. 264 NS_ASSERTION( 265 !mPattern.GetPattern(), 266 "ForStyle() should only be called once on CanvasGeneralPattern!"); 267 268 const ContextState& state = aCtx->CurrentState(); 269 270 if (state.StyleIsColor(aStyle)) { 271 mPattern.InitColorPattern(ToDeviceColor(state.colorStyles[aStyle])); 272 } else if (state.gradientStyles[aStyle] && 273 state.gradientStyles[aStyle]->GetType() == 274 CanvasGradient::Type::LINEAR) { 275 auto gradient = static_cast<CanvasLinearGradient*>( 276 state.gradientStyles[aStyle].get()); 277 278 mPattern.InitLinearGradientPattern( 279 gradient->mBegin, gradient->mEnd, 280 gradient->GetGradientStopsForTarget(aRT)); 281 } else if (state.gradientStyles[aStyle] && 282 state.gradientStyles[aStyle]->GetType() == 283 CanvasGradient::Type::RADIAL) { 284 auto gradient = static_cast<CanvasRadialGradient*>( 285 state.gradientStyles[aStyle].get()); 286 287 mPattern.InitRadialGradientPattern( 288 gradient->mCenter1, gradient->mCenter2, gradient->mRadius1, 289 gradient->mRadius2, gradient->GetGradientStopsForTarget(aRT)); 290 } else if (state.gradientStyles[aStyle] && 291 state.gradientStyles[aStyle]->GetType() == 292 CanvasGradient::Type::CONIC) { 293 auto gradient = 294 static_cast<CanvasConicGradient*>(state.gradientStyles[aStyle].get()); 295 296 mPattern.InitConicGradientPattern( 297 gradient->mCenter, gradient->mAngle, 0, 1, 298 gradient->GetGradientStopsForTarget(aRT)); 299 } else if (state.patternStyles[aStyle]) { 300 aCtx->DoSecurityCheck(state.patternStyles[aStyle]->mPrincipal, 301 state.patternStyles[aStyle]->mForceWriteOnly, 302 state.patternStyles[aStyle]->mCORSUsed); 303 304 ExtendMode mode; 305 if (state.patternStyles[aStyle]->mRepeat == 306 CanvasPattern::RepeatMode::NOREPEAT) { 307 mode = ExtendMode::CLAMP; 308 } else { 309 mode = ExtendMode::REPEAT; 310 } 311 312 SamplingFilter samplingFilter; 313 if (state.imageSmoothingEnabled) { 314 samplingFilter = SamplingFilter::GOOD; 315 } else { 316 samplingFilter = SamplingFilter::POINT; 317 } 318 319 mPattern.InitSurfacePattern(state.patternStyles[aStyle]->mSurface, mode, 320 state.patternStyles[aStyle]->mTransform, 321 samplingFilter); 322 } 323 324 return *mPattern.GetPattern(); 325 } 326 327 GeneralPattern mPattern; 328 }; 329 330 /* This is an RAII based class that can be used as a drawtarget for 331 * operations that need to have a filter applied to their results. 332 * All coordinates passed to the constructor are in device space. 333 */ 334 class AdjustedTargetForFilter { 335 public: 336 using ContextState = CanvasRenderingContext2D::ContextState; 337 338 AdjustedTargetForFilter(CanvasRenderingContext2D* aCtx, 339 DrawTarget* aFinalTarget, 340 const gfx::IntPoint& aFilterSpaceToTargetOffset, 341 const gfx::IntRect& aPreFilterBounds, 342 const gfx::IntRect& aPostFilterBounds, 343 gfx::CompositionOp aCompositionOp, 344 bool aAllowOptimization = false) 345 : mFinalTarget(aFinalTarget), 346 mCtx(aCtx), 347 mPostFilterBounds(aPostFilterBounds), 348 mOffset(aFilterSpaceToTargetOffset), 349 mCompositionOp(aCompositionOp), 350 mAllowOptimization(aAllowOptimization) { 351 nsIntRegion sourceGraphicNeededRegion; 352 nsIntRegion fillPaintNeededRegion; 353 nsIntRegion strokePaintNeededRegion; 354 355 FilterSupport::ComputeSourceNeededRegions( 356 aCtx->CurrentState().filter, mPostFilterBounds, 357 sourceGraphicNeededRegion, fillPaintNeededRegion, 358 strokePaintNeededRegion); 359 360 mSourceGraphicRect = sourceGraphicNeededRegion.GetBounds(); 361 mFillPaintRect = fillPaintNeededRegion.GetBounds(); 362 mStrokePaintRect = strokePaintNeededRegion.GetBounds(); 363 364 mSourceGraphicRect = mSourceGraphicRect.Intersect(aPreFilterBounds); 365 366 if (mSourceGraphicRect.IsEmpty()) { 367 // The filter might not make any use of the source graphic. We need to 368 // create a DrawTarget that we can return from DT() anyway, so we'll 369 // just use a 1x1-sized one. 370 mSourceGraphicRect.SizeTo(1, 1); 371 } 372 373 if (mAllowOptimization) { 374 return; 375 } 376 377 if (!(mFillPaintRect.IsEmpty() || 378 mFinalTarget->CanCreateSimilarDrawTarget(mFillPaintRect.Size(), 379 SurfaceFormat::B8G8R8A8)) || 380 !(mStrokePaintRect.IsEmpty() || 381 mFinalTarget->CanCreateSimilarDrawTarget(mStrokePaintRect.Size(), 382 SurfaceFormat::B8G8R8A8)) || 383 !mFinalTarget->CanCreateSimilarDrawTarget(mSourceGraphicRect.Size(), 384 SurfaceFormat::B8G8R8A8)) { 385 mTarget = mFinalTarget; 386 mCtx = nullptr; 387 mFinalTarget = nullptr; 388 return; 389 } 390 391 mTarget = mFinalTarget->CreateSimilarDrawTarget(mSourceGraphicRect.Size(), 392 SurfaceFormat::B8G8R8A8); 393 394 if (mTarget) { 395 // See bug 1524554. 396 mTarget->ClearRect(gfx::Rect()); 397 } 398 399 if (!mTarget || !mTarget->IsValid()) { 400 // XXX - Deal with the situation where our temp size is too big to 401 // fit in a texture (bug 1066622). 402 mTarget = mFinalTarget; 403 mCtx = nullptr; 404 mFinalTarget = nullptr; 405 return; 406 } 407 408 mTarget->SetTransform(mFinalTarget->GetTransform().PostTranslate( 409 -mSourceGraphicRect.TopLeft() + mOffset)); 410 } 411 412 void Fill(const Path* aPath, const Pattern& aPattern, 413 const DrawOptions& aOptions) { 414 if (mAllowOptimization) { 415 mDeferInput = mFinalTarget->DeferFilterInput( 416 aPath, aPattern, mSourceGraphicRect, mOffset, aOptions); 417 } else { 418 mTarget->Fill(aPath, aPattern, aOptions); 419 } 420 } 421 422 void FillRect(const Rect& aRect, const Pattern& aPattern, 423 const DrawOptions& aOptions) { 424 if (mAllowOptimization) { 425 RefPtr<Path> path = MakePathForRect(*mFinalTarget, aRect); 426 mDeferInput = mFinalTarget->DeferFilterInput( 427 path, aPattern, mSourceGraphicRect, mOffset, aOptions); 428 } else { 429 mTarget->FillRect(aRect, aPattern, aOptions); 430 } 431 } 432 433 void Stroke(const Path* aPath, const Pattern& aPattern, 434 const StrokeOptions& aStrokeOptions, 435 const DrawOptions& aOptions) { 436 if (mAllowOptimization) { 437 mDeferInput = 438 mFinalTarget->DeferFilterInput(aPath, aPattern, mSourceGraphicRect, 439 mOffset, aOptions, &aStrokeOptions); 440 } else { 441 mTarget->Stroke(aPath, aPattern, aStrokeOptions, aOptions); 442 } 443 } 444 445 void StrokeRect(const Rect& aRect, const Pattern& aPattern, 446 const StrokeOptions& aStrokeOptions, 447 const DrawOptions& aOptions) { 448 if (mAllowOptimization) { 449 RefPtr<Path> path = MakePathForRect(*mFinalTarget, aRect); 450 mDeferInput = 451 mFinalTarget->DeferFilterInput(path, aPattern, mSourceGraphicRect, 452 mOffset, aOptions, &aStrokeOptions); 453 } else { 454 mTarget->StrokeRect(aRect, aPattern, aStrokeOptions, aOptions); 455 } 456 } 457 458 void StrokeLine(const Point& aStart, const Point& aEnd, 459 const Pattern& aPattern, const StrokeOptions& aStrokeOptions, 460 const DrawOptions& aOptions) { 461 if (mAllowOptimization) { 462 RefPtr<PathBuilder> builder = mFinalTarget->CreatePathBuilder(); 463 builder->MoveTo(aStart); 464 builder->LineTo(aEnd); 465 RefPtr<Path> path = builder->Finish(); 466 mDeferInput = 467 mFinalTarget->DeferFilterInput(path, aPattern, mSourceGraphicRect, 468 mOffset, aOptions, &aStrokeOptions); 469 } else { 470 mTarget->StrokeLine(aStart, aEnd, aPattern, aStrokeOptions, aOptions); 471 } 472 } 473 474 void DrawSurface(SourceSurface* aSurface, const Rect& aDest, 475 const Rect& aSource, const DrawSurfaceOptions& aSurfOptions, 476 const DrawOptions& aOptions) { 477 if (mAllowOptimization) { 478 RefPtr<Path> path = MakePathForRect(*mFinalTarget, aSource); 479 SurfacePattern pattern(aSurface, ExtendMode::CLAMP, Matrix(), 480 aSurfOptions.mSamplingFilter); 481 Matrix matrix = Matrix::Scaling(aDest.width / aSource.width, 482 aDest.height / aSource.height); 483 matrix.PreTranslate(-aSource.x, -aSource.y); 484 matrix.PostTranslate(aDest.x, aDest.y); 485 AutoRestoreTransform autoRestoreTransform(mFinalTarget); 486 mFinalTarget->ConcatTransform(matrix); 487 mDeferInput = mFinalTarget->DeferFilterInput( 488 path, pattern, mSourceGraphicRect, mOffset, aOptions); 489 } else { 490 mTarget->DrawSurface(aSurface, aDest, aSource, aSurfOptions, aOptions); 491 } 492 } 493 494 // Return a SourceSurface that contains the FillPaint or StrokePaint source. 495 already_AddRefed<FilterNode> DoSourcePaint( 496 gfx::IntRect& aRect, CanvasRenderingContext2D::Style aStyle) { 497 if (aRect.IsEmpty()) { 498 return nullptr; 499 } 500 501 if (mAllowOptimization) { 502 Matrix transform = mFinalTarget->GetTransform(); 503 RefPtr<Path> path = 504 transform.Invert() 505 ? MakePathForRect(*mFinalTarget, transform.TransformBounds( 506 Rect(aRect - mOffset))) 507 : MakeEmptyPath(*mFinalTarget); 508 return mFinalTarget->DeferFilterInput( 509 path, CanvasGeneralPattern().ForStyle(mCtx, aStyle, mFinalTarget), 510 aRect, mOffset); 511 } 512 513 RefPtr<DrawTarget> dt = mFinalTarget->CreateSimilarDrawTarget( 514 aRect.Size(), SurfaceFormat::B8G8R8A8); 515 516 if (dt) { 517 // See bug 1524554. 518 dt->ClearRect(gfx::Rect()); 519 } 520 521 if (!dt || !dt->IsValid()) { 522 aRect.SetEmpty(); 523 return nullptr; 524 } 525 526 Matrix transform = 527 mFinalTarget->GetTransform().PostTranslate(-aRect.TopLeft() + mOffset); 528 529 dt->SetTransform(transform); 530 531 if (transform.Invert()) { 532 gfx::Rect dtBounds(0, 0, aRect.width, aRect.height); 533 gfx::Rect fillRect = transform.TransformBounds(dtBounds); 534 dt->FillRect(fillRect, CanvasGeneralPattern().ForStyle(mCtx, aStyle, dt)); 535 } 536 537 RefPtr<SourceSurface> snapshot = dt->Snapshot(); 538 if (!snapshot) { 539 return nullptr; 540 } 541 542 return FilterWrappers::ForSurface(mFinalTarget, snapshot, aRect.TopLeft()); 543 } 544 545 ~AdjustedTargetForFilter() { 546 if (!mCtx) { 547 return; 548 } 549 550 RefPtr<FilterNode> sourceGraphic; 551 if (mAllowOptimization) { 552 sourceGraphic = mDeferInput; 553 } else if (RefPtr<SourceSurface> snapshot = mTarget->Snapshot()) { 554 sourceGraphic = FilterWrappers::ForSurface(mFinalTarget, snapshot, 555 mSourceGraphicRect.TopLeft()); 556 } 557 RefPtr<FilterNode> fillPaint = 558 DoSourcePaint(mFillPaintRect, CanvasRenderingContext2D::Style::FILL); 559 RefPtr<FilterNode> strokePaint = DoSourcePaint( 560 mStrokePaintRect, CanvasRenderingContext2D::Style::STROKE); 561 562 AutoRestoreTransform autoRestoreTransform(mFinalTarget); 563 mFinalTarget->SetTransform(Matrix()); 564 565 MOZ_RELEASE_ASSERT(!mCtx->CurrentState().filter.mPrimitives.IsEmpty()); 566 gfx::FilterSupport::RenderFilterDescription( 567 mFinalTarget, mCtx->CurrentState().filter, gfx::Rect(mPostFilterBounds), 568 std::move(sourceGraphic), mSourceGraphicRect, std::move(fillPaint), 569 mFillPaintRect, std::move(strokePaint), mStrokePaintRect, 570 mCtx->CurrentState().filterAdditionalImages, 571 mPostFilterBounds.TopLeft() - mOffset, 572 DrawOptions(1.0f, mCompositionOp)); 573 574 const gfx::FilterDescription& filter = mCtx->CurrentState().filter; 575 MOZ_RELEASE_ASSERT(!filter.mPrimitives.IsEmpty()); 576 if (filter.mPrimitives.LastElement().IsTainted()) { 577 if (mCtx->mCanvasElement) { 578 mCtx->mCanvasElement->SetWriteOnly(); 579 } else if (mCtx->mOffscreenCanvas) { 580 mCtx->mOffscreenCanvas->SetWriteOnly(); 581 } 582 } 583 } 584 585 DrawTarget* DT() { return mTarget; } 586 587 private: 588 RefPtr<DrawTarget> mTarget; 589 RefPtr<DrawTarget> mFinalTarget; 590 CanvasRenderingContext2D* mCtx; 591 gfx::IntRect mSourceGraphicRect; 592 gfx::IntRect mFillPaintRect; 593 gfx::IntRect mStrokePaintRect; 594 gfx::IntRect mPostFilterBounds; 595 gfx::IntPoint mOffset; 596 gfx::CompositionOp mCompositionOp; 597 bool mAllowOptimization; 598 RefPtr<FilterNode> mDeferInput; 599 }; 600 601 /* This is an RAII based class that can be used as a drawtarget for 602 * operations that need to have a shadow applied to their results. 603 * All coordinates passed to the constructor are in device space. 604 */ 605 class AdjustedTargetForShadow { 606 public: 607 using ContextState = CanvasRenderingContext2D::ContextState; 608 609 AdjustedTargetForShadow(CanvasRenderingContext2D* aCtx, 610 DrawTarget* aFinalTarget, const gfx::Rect& aBounds, 611 gfx::CompositionOp aCompositionOp) 612 : mFinalTarget(aFinalTarget), mCtx(aCtx), mCompositionOp(aCompositionOp) { 613 const ContextState& state = mCtx->CurrentState(); 614 mSigma = state.ShadowBlurSigma(); 615 616 // We actually include the bounds of the shadow blur, this makes it 617 // easier to execute the actual blur on hardware, and shouldn't affect 618 // the amount of pixels that need to be touched. 619 gfx::Rect bounds = aBounds; 620 int32_t blurRadius = state.ShadowBlurRadius(); 621 bounds.Inflate(blurRadius); 622 bounds.RoundOut(); 623 if (!bounds.ToIntRect(&mTempRect) || 624 !mFinalTarget->CanCreateSimilarDrawTarget(mTempRect.Size(), 625 SurfaceFormat::B8G8R8A8)) { 626 mTarget = mFinalTarget; 627 mCtx = nullptr; 628 mFinalTarget = nullptr; 629 return; 630 } 631 632 mTarget = mFinalTarget->CreateShadowDrawTarget( 633 mTempRect.Size(), SurfaceFormat::B8G8R8A8, mSigma); 634 635 if (mTarget) { 636 // See bug 1524554. 637 mTarget->ClearRect(gfx::Rect()); 638 } 639 640 if (!mTarget || !mTarget->IsValid()) { 641 // XXX - Deal with the situation where our temp size is too big to 642 // fit in a texture (bug 1066622). 643 mTarget = mFinalTarget; 644 mCtx = nullptr; 645 mFinalTarget = nullptr; 646 } else { 647 mTarget->SetTransform( 648 mFinalTarget->GetTransform().PostTranslate(-mTempRect.TopLeft())); 649 } 650 } 651 652 ~AdjustedTargetForShadow() { 653 if (!mCtx) { 654 return; 655 } 656 657 RefPtr<SourceSurface> snapshot = mTarget->Snapshot(); 658 659 mFinalTarget->DrawSurfaceWithShadow( 660 snapshot, mTempRect.TopLeft(), 661 ShadowOptions(ToDeviceColor(mCtx->CurrentState().shadowColor), 662 mCtx->CurrentState().shadowOffset, mSigma), 663 mCompositionOp); 664 } 665 666 DrawTarget* DT() { return mTarget; } 667 668 gfx::IntPoint OffsetToFinalDT() { return mTempRect.TopLeft(); } 669 670 private: 671 RefPtr<DrawTarget> mTarget; 672 RefPtr<DrawTarget> mFinalTarget; 673 CanvasRenderingContext2D* mCtx; 674 Float mSigma; 675 gfx::IntRect mTempRect; 676 gfx::CompositionOp mCompositionOp; 677 }; 678 679 /* 680 * This is an RAII based class that can be used as a drawtarget for 681 * operations that need a shadow or a filter drawn. It will automatically 682 * provide a temporary target when needed, and if so blend it back with a 683 * shadow, filter, or both. 684 * If both a shadow and a filter are needed, the filter is applied first, 685 * and the shadow is applied to the filtered results. 686 * 687 * aBounds specifies the bounds of the drawing operation that will be 688 * drawn to the target, it is given in device space! If this is nullptr the 689 * drawing operation will be assumed to cover the whole canvas. 690 */ 691 class AdjustedTarget { 692 public: 693 using ContextState = CanvasRenderingContext2D::ContextState; 694 695 explicit AdjustedTarget(CanvasRenderingContext2D* aCtx, 696 const gfx::Rect* aBounds = nullptr, 697 bool aAllowOptimization = false) 698 : mCtx(aCtx), mUsedOperation(aCtx->CurrentState().op) { 699 // All rects in this function are in the device space of ctx->mTarget. 700 701 // In order to keep our temporary surfaces as small as possible, we first 702 // calculate what their maximum required bounds would need to be if we 703 // were to fill the whole canvas. Everything outside those bounds we don't 704 // need to render. 705 gfx::Rect r(0, 0, aCtx->mWidth, aCtx->mHeight); 706 gfx::Rect maxSourceNeededBoundsForShadow = 707 MaxSourceNeededBoundsForShadow(r, aCtx); 708 gfx::Rect maxSourceNeededBoundsForFilter = 709 MaxSourceNeededBoundsForFilter(maxSourceNeededBoundsForShadow, aCtx); 710 if (!aCtx->IsTargetValid()) { 711 return; 712 } 713 714 gfx::Rect bounds = maxSourceNeededBoundsForFilter; 715 if (aBounds) { 716 bounds = bounds.Intersect(*aBounds); 717 } 718 gfx::Rect boundsAfterFilter = BoundsAfterFilter(bounds, aCtx); 719 if (!aCtx->IsTargetValid() || !boundsAfterFilter.IsFinite()) { 720 return; 721 } 722 723 gfx::IntPoint offsetToFinalDT; 724 725 // First set up the shadow draw target, because the shadow goes outside. 726 // It applies to the post-filter results, if both a filter and a shadow 727 // are used. 728 const bool applyFilter = aCtx->NeedToApplyFilter(); 729 if (aCtx->NeedToDrawShadow()) { 730 if (aAllowOptimization && !applyFilter) { 731 // If only drawing a shadow and no filter, then avoid buffering to an 732 // intermediate target while drawing the shadow directly to the final 733 // target. When doing so, we want to use the actual composition op 734 // instead of OP_OVER. 735 mTarget = aCtx->mTarget; 736 if (mTarget && mTarget->IsValid()) { 737 mOptimizeShadow = true; 738 return; 739 } 740 } 741 mShadowTarget = MakeUnique<AdjustedTargetForShadow>( 742 aCtx, aCtx->mTarget, boundsAfterFilter, mUsedOperation); 743 mTarget = mShadowTarget->DT(); 744 offsetToFinalDT = mShadowTarget->OffsetToFinalDT(); 745 746 // If we also have a filter, the filter needs to be drawn with OP_OVER 747 // because shadow drawing already applies op on the result. 748 mUsedOperation = CompositionOp::OP_OVER; 749 } 750 751 // Now set up the filter draw target. 752 if (!aCtx->IsTargetValid()) { 753 return; 754 } 755 if (applyFilter) { 756 bounds.RoundOut(); 757 758 if (!mTarget) { 759 mTarget = aCtx->mTarget; 760 } 761 gfx::IntRect intBounds; 762 if (!bounds.ToIntRect(&intBounds)) { 763 return; 764 } 765 766 // Only allow optimization of filters if no shadow is being drawn. 767 if (aAllowOptimization && !mShadowTarget) { 768 mOptimizeFilter = true; 769 } 770 771 mFilterTarget = MakeUnique<AdjustedTargetForFilter>( 772 aCtx, mTarget, offsetToFinalDT, intBounds, 773 gfx::RoundedToInt(boundsAfterFilter), mUsedOperation, 774 mOptimizeFilter); 775 mTarget = mFilterTarget->DT(); 776 mUsedOperation = CompositionOp::OP_OVER; 777 } 778 if (!mTarget) { 779 mTarget = aCtx->mTarget; 780 } 781 } 782 783 ~AdjustedTarget() { 784 // The order in which the targets are finalized is important. 785 // Filters are inside, any shadow applies to the post-filter results. 786 mFilterTarget.reset(); 787 mShadowTarget.reset(); 788 } 789 790 operator DrawTarget*() { return mTarget; } 791 792 DrawTarget* operator->() MOZ_NO_ADDREF_RELEASE_ON_RETURN { return mTarget; } 793 794 CompositionOp UsedOperation() const { return mUsedOperation; } 795 796 bool UseOptimizeShadow() const { return mOptimizeShadow; } 797 bool UseOptimizeFilter() const { return mOptimizeFilter; } 798 799 ShadowOptions ShadowParams() const { 800 const ContextState& state = mCtx->CurrentState(); 801 return ShadowOptions(ToDeviceColor(state.shadowColor), state.shadowOffset, 802 state.ShadowBlurSigma()); 803 } 804 805 void Fill(const Path* aPath, const Pattern& aPattern, 806 const DrawOptions& aOptions) { 807 if (mOptimizeFilter) { 808 mFilterTarget->Fill(aPath, aPattern, aOptions); 809 return; 810 } 811 if (mOptimizeShadow) { 812 mTarget->DrawShadow(aPath, aPattern, ShadowParams(), aOptions); 813 } 814 mTarget->Fill(aPath, aPattern, aOptions); 815 } 816 817 void FillRect(const Rect& aRect, const Pattern& aPattern, 818 const DrawOptions& aOptions) { 819 if (mOptimizeFilter) { 820 mFilterTarget->FillRect(aRect, aPattern, aOptions); 821 return; 822 } 823 if (mOptimizeShadow) { 824 RefPtr<Path> path = MakePathForRect(*mTarget, aRect); 825 mTarget->DrawShadow(path, aPattern, ShadowParams(), aOptions); 826 } 827 mTarget->FillRect(aRect, aPattern, aOptions); 828 } 829 830 void Stroke(const Path* aPath, const Pattern& aPattern, 831 const StrokeOptions& aStrokeOptions, 832 const DrawOptions& aOptions) { 833 if (mOptimizeFilter) { 834 mFilterTarget->Stroke(aPath, aPattern, aStrokeOptions, aOptions); 835 return; 836 } 837 if (mOptimizeShadow) { 838 mTarget->DrawShadow(aPath, aPattern, ShadowParams(), aOptions, 839 &aStrokeOptions); 840 } 841 mTarget->Stroke(aPath, aPattern, aStrokeOptions, aOptions); 842 } 843 844 void StrokeRect(const Rect& aRect, const Pattern& aPattern, 845 const StrokeOptions& aStrokeOptions, 846 const DrawOptions& aOptions) { 847 if (mOptimizeFilter) { 848 mFilterTarget->StrokeRect(aRect, aPattern, aStrokeOptions, aOptions); 849 return; 850 } 851 if (mOptimizeShadow) { 852 RefPtr<Path> path = MakePathForRect(*mTarget, aRect); 853 mTarget->DrawShadow(path, aPattern, ShadowParams(), aOptions, 854 &aStrokeOptions); 855 } 856 mTarget->StrokeRect(aRect, aPattern, aStrokeOptions, aOptions); 857 } 858 859 void StrokeLine(const Point& aStart, const Point& aEnd, 860 const Pattern& aPattern, const StrokeOptions& aStrokeOptions, 861 const DrawOptions& aOptions) { 862 if (mOptimizeFilter) { 863 mFilterTarget->StrokeLine(aStart, aEnd, aPattern, aStrokeOptions, 864 aOptions); 865 return; 866 } 867 if (mOptimizeShadow) { 868 RefPtr<PathBuilder> builder = mTarget->CreatePathBuilder(); 869 builder->MoveTo(aStart); 870 builder->LineTo(aEnd); 871 RefPtr<Path> path = builder->Finish(); 872 mTarget->DrawShadow(path, aPattern, ShadowParams(), aOptions, 873 &aStrokeOptions); 874 } 875 mTarget->StrokeLine(aStart, aEnd, aPattern, aStrokeOptions, aOptions); 876 } 877 878 void DrawSurface(SourceSurface* aSurface, const Rect& aDest, 879 const Rect& aSource, const DrawSurfaceOptions& aSurfOptions, 880 const DrawOptions& aOptions) { 881 if (mOptimizeFilter) { 882 mFilterTarget->DrawSurface(aSurface, aDest, aSource, aSurfOptions, 883 aOptions); 884 return; 885 } 886 if (mOptimizeShadow) { 887 RefPtr<Path> path = MakePathForRect(*mTarget, aSource); 888 ShadowOptions shadowParams(ShadowParams()); 889 SurfacePattern pattern(aSurface, ExtendMode::CLAMP, Matrix(), 890 shadowParams.BlurRadius() > 1 891 ? SamplingFilter::POINT 892 : aSurfOptions.mSamplingFilter); 893 Matrix matrix = Matrix::Scaling(aDest.width / aSource.width, 894 aDest.height / aSource.height); 895 matrix.PreTranslate(-aSource.x, -aSource.y); 896 matrix.PostTranslate(aDest.x, aDest.y); 897 AutoRestoreTransform autoRestoreTransform(mTarget); 898 mTarget->ConcatTransform(matrix); 899 mTarget->DrawShadow(path, pattern, shadowParams, aOptions); 900 } 901 mTarget->DrawSurface(aSurface, aDest, aSource, aSurfOptions, aOptions); 902 } 903 904 private: 905 gfx::Rect MaxSourceNeededBoundsForFilter(const gfx::Rect& aDestBounds, 906 CanvasRenderingContext2D* aCtx) { 907 const bool applyFilter = aCtx->NeedToApplyFilter(); 908 if (!aCtx->IsTargetValid()) { 909 return aDestBounds; 910 } 911 if (!applyFilter) { 912 return aDestBounds; 913 } 914 915 nsIntRegion sourceGraphicNeededRegion; 916 nsIntRegion fillPaintNeededRegion; 917 nsIntRegion strokePaintNeededRegion; 918 919 FilterSupport::ComputeSourceNeededRegions( 920 aCtx->CurrentState().filter, gfx::RoundedToInt(aDestBounds), 921 sourceGraphicNeededRegion, fillPaintNeededRegion, 922 strokePaintNeededRegion); 923 924 return gfx::Rect(sourceGraphicNeededRegion.GetBounds()); 925 } 926 927 gfx::Rect MaxSourceNeededBoundsForShadow(const gfx::Rect& aDestBounds, 928 CanvasRenderingContext2D* aCtx) { 929 if (!aCtx->NeedToDrawShadow()) { 930 return aDestBounds; 931 } 932 933 const ContextState& state = aCtx->CurrentState(); 934 gfx::Rect sourceBounds = aDestBounds - state.shadowOffset; 935 sourceBounds.Inflate(state.ShadowBlurRadius()); 936 937 // Union the shadow source with the original rect because we're going to 938 // draw both. 939 return sourceBounds.Union(aDestBounds); 940 } 941 942 gfx::Rect BoundsAfterFilter(const gfx::Rect& aBounds, 943 CanvasRenderingContext2D* aCtx) { 944 const bool applyFilter = aCtx->NeedToApplyFilter(); 945 if (!aCtx->IsTargetValid()) { 946 return aBounds; 947 } 948 if (!applyFilter) { 949 return aBounds; 950 } 951 952 gfx::Rect bounds(aBounds); 953 bounds.RoundOut(); 954 955 gfx::IntRect intBounds; 956 if (!bounds.ToIntRect(&intBounds)) { 957 return gfx::Rect(); 958 } 959 960 nsIntRegion extents = gfx::FilterSupport::ComputePostFilterExtents( 961 aCtx->CurrentState().filter, intBounds); 962 return gfx::Rect(extents.GetBounds()); 963 } 964 965 CanvasRenderingContext2D* mCtx; 966 bool mOptimizeShadow = false; 967 bool mOptimizeFilter = false; 968 CompositionOp mUsedOperation; 969 RefPtr<DrawTarget> mTarget; 970 UniquePtr<AdjustedTargetForShadow> mShadowTarget; 971 UniquePtr<AdjustedTargetForFilter> mFilterTarget; 972 }; 973 974 void CanvasPattern::SetTransform(const DOMMatrix2DInit& aInit, 975 ErrorResult& aError) { 976 Matrix matrix2D(DOMMatrixReadOnly::ToValidatedMatrixDouble(aInit, aError)); 977 if (aError.Failed()) { 978 return; 979 } 980 if (!matrix2D.IsFinite()) { 981 return; 982 } 983 mTransform = Matrix(matrix2D); 984 } 985 986 void CanvasGradient::AddColorStop(float aOffset, const nsACString& aColorstr, 987 ErrorResult& aRv) { 988 if (aOffset < 0.0 || aOffset > 1.0) { 989 return aRv.ThrowIndexSizeError("Offset out of 0-1.0 range"); 990 } 991 992 if (!mContext) { 993 return aRv.ThrowSyntaxError("No canvas context"); 994 } 995 996 auto color = mContext->ParseColor( 997 aColorstr, CanvasRenderingContext2D::ResolveCurrentColor::No); 998 if (!color) { 999 return aRv.ThrowSyntaxError("Invalid color"); 1000 } 1001 1002 GradientStop newStop; 1003 1004 newStop.offset = aOffset; 1005 newStop.color = ToDeviceColor(*color); 1006 1007 mRawStops.AppendElement(newStop); 1008 } 1009 1010 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasGradient, mContext) 1011 1012 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasPattern, mContext) 1013 1014 NS_IMPL_CYCLE_COLLECTING_ADDREF(CanvasRenderingContext2D) 1015 NS_IMPL_CYCLE_COLLECTING_RELEASE(CanvasRenderingContext2D) 1016 1017 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(CanvasRenderingContext2D) 1018 1019 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CanvasRenderingContext2D) 1020 // Make sure we remove ourselves from the list of demotable contexts (raw 1021 // pointers), since we're logically destructed at this point. 1022 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCanvasElement) 1023 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOffscreenCanvas) 1024 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocShell) 1025 for (uint32_t i = 0; i < tmp->mStyleStack.Length(); i++) { 1026 ImplCycleCollectionUnlink(tmp->mStyleStack[i].patternStyles[Style::STROKE]); 1027 ImplCycleCollectionUnlink(tmp->mStyleStack[i].patternStyles[Style::FILL]); 1028 ImplCycleCollectionUnlink( 1029 tmp->mStyleStack[i].gradientStyles[Style::STROKE]); 1030 ImplCycleCollectionUnlink(tmp->mStyleStack[i].gradientStyles[Style::FILL]); 1031 if (auto* autoSVGFiltersObserver = 1032 tmp->mStyleStack[i].autoSVGFiltersObserver.get()) { 1033 /* 1034 * XXXjwatt: I don't think this is doing anything useful. All we do under 1035 * this function is clear a raw C-style (i.e. not strong) pointer. That's 1036 * clearly not helping in breaking any cycles. The fact that we MOZ_CRASH 1037 * in OnRenderingChange if that pointer is null indicates that this isn't 1038 * even doing anything useful in terms of preventing further invalidation 1039 * from any observed filters. 1040 */ 1041 autoSVGFiltersObserver->Detach(); 1042 } 1043 ImplCycleCollectionUnlink(tmp->mStyleStack[i].autoSVGFiltersObserver); 1044 } 1045 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER 1046 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR 1047 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 1048 1049 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CanvasRenderingContext2D) 1050 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCanvasElement) 1051 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOffscreenCanvas) 1052 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocShell) 1053 for (uint32_t i = 0; i < tmp->mStyleStack.Length(); i++) { 1054 ImplCycleCollectionTraverse( 1055 cb, tmp->mStyleStack[i].patternStyles[Style::STROKE], 1056 "Stroke CanvasPattern"); 1057 ImplCycleCollectionTraverse(cb, 1058 tmp->mStyleStack[i].patternStyles[Style::FILL], 1059 "Fill CanvasPattern"); 1060 ImplCycleCollectionTraverse( 1061 cb, tmp->mStyleStack[i].gradientStyles[Style::STROKE], 1062 "Stroke CanvasGradient"); 1063 ImplCycleCollectionTraverse(cb, 1064 tmp->mStyleStack[i].gradientStyles[Style::FILL], 1065 "Fill CanvasGradient"); 1066 ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].autoSVGFiltersObserver, 1067 "RAII SVG Filters Observer"); 1068 } 1069 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 1070 1071 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(CanvasRenderingContext2D) 1072 if (nsCCUncollectableMarker::sGeneration && tmp->HasKnownLiveWrapper()) { 1073 dom::Element* canvasElement = tmp->mCanvasElement; 1074 if (canvasElement) { 1075 if (canvasElement->IsPurple()) { 1076 canvasElement->RemovePurple(); 1077 } 1078 dom::Element::MarkNodeChildren(canvasElement); 1079 } 1080 return true; 1081 } 1082 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END 1083 1084 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(CanvasRenderingContext2D) 1085 return nsCCUncollectableMarker::sGeneration && tmp->HasKnownLiveWrapper(); 1086 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END 1087 1088 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(CanvasRenderingContext2D) 1089 return nsCCUncollectableMarker::sGeneration && tmp->HasKnownLiveWrapper(); 1090 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END 1091 1092 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanvasRenderingContext2D) 1093 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 1094 NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal) 1095 NS_INTERFACE_MAP_ENTRY(nsISupports) 1096 NS_INTERFACE_MAP_END 1097 1098 CanvasRenderingContext2D::ContextState::ContextState() = default; 1099 1100 CanvasRenderingContext2D::ContextState::ContextState(const ContextState& aOther) 1101 : fontGroup(aOther.fontGroup), 1102 fontFont(aOther.fontFont), 1103 fontComputedStyle(aOther.fontComputedStyle), 1104 gradientStyles(aOther.gradientStyles), 1105 patternStyles(aOther.patternStyles), 1106 colorStyles(aOther.colorStyles), 1107 font(aOther.font), 1108 textAlign(aOther.textAlign), 1109 textBaseline(aOther.textBaseline), 1110 textDirection(aOther.textDirection), 1111 fontKerning(aOther.fontKerning), 1112 fontStretch(aOther.fontStretch), 1113 fontVariantCaps(aOther.fontVariantCaps), 1114 textRendering(aOther.textRendering), 1115 letterSpacing(aOther.letterSpacing), 1116 wordSpacing(aOther.wordSpacing), 1117 letterSpacingStr(aOther.letterSpacingStr), 1118 wordSpacingStr(aOther.wordSpacingStr), 1119 shadowColor(aOther.shadowColor), 1120 transform(aOther.transform), 1121 shadowOffset(aOther.shadowOffset), 1122 lineWidth(aOther.lineWidth), 1123 miterLimit(aOther.miterLimit), 1124 globalAlpha(aOther.globalAlpha), 1125 shadowBlur(aOther.shadowBlur), 1126 dash(aOther.dash.Clone()), 1127 dashOffset(aOther.dashOffset), 1128 op(aOther.op), 1129 fillRule(aOther.fillRule), 1130 lineCap(aOther.lineCap), 1131 lineJoin(aOther.lineJoin), 1132 filterString(aOther.filterString), 1133 filterChain(aOther.filterChain), 1134 autoSVGFiltersObserver(aOther.autoSVGFiltersObserver), 1135 filter(aOther.filter), 1136 filterAdditionalImages(aOther.filterAdditionalImages.Clone()), 1137 filterSourceGraphicTainted(aOther.filterSourceGraphicTainted), 1138 imageSmoothingEnabled(aOther.imageSmoothingEnabled) {} 1139 1140 CanvasRenderingContext2D::ContextState::~ContextState() = default; 1141 1142 void CanvasRenderingContext2D::ContextState::SetColorStyle(Style aWhichStyle, 1143 nscolor aColor) { 1144 colorStyles[aWhichStyle] = aColor; 1145 gradientStyles[aWhichStyle] = nullptr; 1146 patternStyles[aWhichStyle] = nullptr; 1147 } 1148 1149 void CanvasRenderingContext2D::ContextState::SetPatternStyle( 1150 Style aWhichStyle, CanvasPattern* aPat) { 1151 gradientStyles[aWhichStyle] = nullptr; 1152 patternStyles[aWhichStyle] = aPat; 1153 } 1154 1155 void CanvasRenderingContext2D::ContextState::SetGradientStyle( 1156 Style aWhichStyle, CanvasGradient* aGrad) { 1157 gradientStyles[aWhichStyle] = aGrad; 1158 patternStyles[aWhichStyle] = nullptr; 1159 } 1160 1161 /** 1162 ** CanvasRenderingContext2D impl 1163 **/ 1164 1165 // Initialize our static variables. 1166 MOZ_THREAD_LOCAL(uintptr_t) CanvasRenderingContext2D::sNumLivingContexts; 1167 MOZ_THREAD_LOCAL(DrawTarget*) CanvasRenderingContext2D::sErrorTarget; 1168 1169 // Helpers to map Canvas2D WebIDL enum values to gfx constants for rendering. 1170 static JoinStyle CanvasToGfx(CanvasLineJoin aJoin) { 1171 switch (aJoin) { 1172 case CanvasLineJoin::Round: 1173 return JoinStyle::ROUND; 1174 case CanvasLineJoin::Bevel: 1175 return JoinStyle::BEVEL; 1176 case CanvasLineJoin::Miter: 1177 return JoinStyle::MITER_OR_BEVEL; 1178 default: 1179 MOZ_CRASH("unknown lineJoin!"); 1180 } 1181 } 1182 1183 static CapStyle CanvasToGfx(CanvasLineCap aCap) { 1184 switch (aCap) { 1185 case CanvasLineCap::Butt: 1186 return CapStyle::BUTT; 1187 case CanvasLineCap::Round: 1188 return CapStyle::ROUND; 1189 case CanvasLineCap::Square: 1190 return CapStyle::SQUARE; 1191 default: 1192 MOZ_CRASH("unknown lineCap!"); 1193 } 1194 } 1195 1196 static uint8_t CanvasToGfx(CanvasFontKerning aKerning) { 1197 switch (aKerning) { 1198 case CanvasFontKerning::Auto: 1199 return NS_FONT_KERNING_AUTO; 1200 case CanvasFontKerning::Normal: 1201 return NS_FONT_KERNING_NORMAL; 1202 case CanvasFontKerning::None: 1203 return NS_FONT_KERNING_NONE; 1204 default: 1205 MOZ_CRASH("unknown kerning!"); 1206 } 1207 } 1208 1209 CanvasRenderingContext2D::CanvasRenderingContext2D( 1210 layers::LayersBackend aCompositorBackend) 1211 : // these are the default values from the Canvas spec 1212 mWidth(0), 1213 mHeight(0), 1214 mZero(false), 1215 mOpaqueAttrValue(false), 1216 mContextAttributesHasAlpha(true), 1217 mOpaque(false), 1218 mResetLayer(true), 1219 mIPC(false), 1220 mHasPendingStableStateCallback(false), 1221 mIsEntireFrameInvalid(false), 1222 mPredictManyRedrawCalls(false), 1223 mFrameCaptureState(FrameCaptureState::CLEAN, 1224 "CanvasRenderingContext2D::mFrameCaptureState"), 1225 mInvalidateCount(0), 1226 mWriteOnly(false) { 1227 sNumLivingContexts.infallibleInit(); 1228 sErrorTarget.infallibleInit(); 1229 sNumLivingContexts.set(sNumLivingContexts.get() + 1); 1230 } 1231 1232 CanvasRenderingContext2D::~CanvasRenderingContext2D() { 1233 CanvasImageCache::NotifyCanvasDestroyed(this); 1234 RemovePostRefreshObserver(); 1235 RemoveShutdownObserver(); 1236 ResetBitmap(); 1237 1238 sNumLivingContexts.set(sNumLivingContexts.get() - 1); 1239 if (sNumLivingContexts.get() == 0 && sErrorTarget.get()) { 1240 RefPtr<DrawTarget> target = dont_AddRef(sErrorTarget.get()); 1241 sErrorTarget.set(nullptr); 1242 } 1243 } 1244 1245 nsresult CanvasRenderingContext2D::Initialize() { 1246 if (NS_WARN_IF(!AddShutdownObserver())) { 1247 return NS_ERROR_FAILURE; 1248 } 1249 1250 return NS_OK; 1251 } 1252 1253 JSObject* CanvasRenderingContext2D::WrapObject( 1254 JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { 1255 return CanvasRenderingContext2D_Binding::Wrap(aCx, this, aGivenProto); 1256 } 1257 1258 void CanvasRenderingContext2D::GetContextAttributes( 1259 CanvasRenderingContext2DSettings& aSettings) const { 1260 aSettings = CanvasRenderingContext2DSettings(); 1261 1262 aSettings.mAlpha = mContextAttributesHasAlpha; 1263 aSettings.mWillReadFrequently = mWillReadFrequently; 1264 aSettings.mForceSoftwareRendering = mForceSoftwareRendering; 1265 1266 // We don't support the 'desynchronized' and 'colorSpace' attributes, so 1267 // those just keep their default values. 1268 } 1269 1270 void CanvasRenderingContext2D::GetDebugInfo( 1271 bool aEnsureTarget, CanvasRenderingContext2DDebugInfo& aDebugInfo, 1272 ErrorResult& aError) { 1273 if (aEnsureTarget && !EnsureTarget(aError)) { 1274 return; 1275 } 1276 1277 if (!mBufferProvider) { 1278 aError.ThrowInvalidStateError("No buffer provider available"); 1279 return; 1280 } 1281 1282 if (!mTarget) { 1283 aError.ThrowInvalidStateError("No target available"); 1284 return; 1285 } 1286 1287 aDebugInfo.mIsAccelerated = mBufferProvider->IsAccelerated(); 1288 aDebugInfo.mIsShared = mBufferProvider->IsShared(); 1289 aDebugInfo.mBackendType = static_cast<int8_t>(mTarget->GetBackendType()); 1290 aDebugInfo.mDrawTargetType = static_cast<int8_t>(mTarget->GetType()); 1291 } 1292 1293 CanvasRenderingContext2D::ColorStyleCacheEntry 1294 CanvasRenderingContext2D::ParseColorSlow(const nsACString& aString) { 1295 ColorStyleCacheEntry result{nsCString(aString)}; 1296 Document* document = mCanvasElement ? mCanvasElement->OwnerDoc() : nullptr; 1297 css::Loader* loader = document ? document->GetExistingCSSLoader() : nullptr; 1298 1299 PresShell* presShell = GetPresShell(); 1300 ServoStyleSet* set = presShell ? presShell->StyleSet() : nullptr; 1301 const StylePerDocumentStyleData* data = set ? set->RawData() : nullptr; 1302 bool wasCurrentColor = false; 1303 nscolor color; 1304 if (ServoCSSParser::ComputeColor(data, NS_RGB(0, 0, 0), aString, &color, 1305 &wasCurrentColor, loader)) { 1306 result.mWasCurrentColor = wasCurrentColor; 1307 result.mColor.emplace(color); 1308 } 1309 1310 return result; 1311 } 1312 1313 Maybe<nscolor> CanvasRenderingContext2D::ParseColor( 1314 const nsACString& aString, ResolveCurrentColor aResolveCurrentColor) { 1315 auto entry = mColorStyleCache.Lookup(aString); 1316 if (!entry) { 1317 entry.Set(ParseColorSlow(aString)); 1318 } 1319 1320 const auto& data = entry.Data(); 1321 if (data.mWasCurrentColor && mCanvasElement && 1322 aResolveCurrentColor == ResolveCurrentColor::Yes) { 1323 // If it was currentColor, get the value of the color property, flushing 1324 // style if necessary. 1325 RefPtr<const ComputedStyle> canvasStyle = 1326 nsComputedDOMStyle::GetComputedStyle(mCanvasElement); 1327 if (canvasStyle) { 1328 return Some(canvasStyle->StyleText()->mColor.ToColor()); 1329 } 1330 } 1331 return data.mColor; 1332 } 1333 1334 void CanvasRenderingContext2D::ResetBitmap(bool aFreeBuffer) { 1335 if (mCanvasElement) { 1336 mCanvasElement->InvalidateCanvas(); 1337 } 1338 1339 // only do this for non-docshell created contexts, 1340 // since those are the ones that we created a surface for 1341 if (mTarget && IsTargetValid() && !mDocShell) { 1342 gCanvasAzureMemoryUsed -= mWidth * mHeight * 4; 1343 } 1344 1345 bool forceReset = true; 1346 ReturnTarget(forceReset); 1347 mTarget = nullptr; 1348 if (aFreeBuffer) { 1349 mBufferProvider = nullptr; 1350 } else if (mBufferProvider) { 1351 // Try to keep the buffer around. However, we still need to clear the 1352 // contents as if it was recreated before next use. 1353 mBufferNeedsClear = true; 1354 } 1355 1356 // Since the target changes the backing texture will change, and this will 1357 // no longer be valid. 1358 mIsEntireFrameInvalid = false; 1359 mPredictManyRedrawCalls = false; 1360 mFrameCaptureState = FrameCaptureState::CLEAN; 1361 } 1362 1363 void CanvasRenderingContext2D::OnShutdown() { 1364 RefPtr<PersistentBufferProvider> provider = mBufferProvider; 1365 1366 ResetBitmap(); 1367 1368 if (provider) { 1369 provider->OnShutdown(); 1370 } 1371 1372 if (mOffscreenCanvas) { 1373 mOffscreenCanvas->Destroy(); 1374 } 1375 1376 mHasShutdown = true; 1377 } 1378 1379 bool CanvasRenderingContext2D::AddShutdownObserver() { 1380 auto* const canvasManager = CanvasShutdownManager::Get(); 1381 if (NS_WARN_IF(!canvasManager)) { 1382 mHasShutdown = true; 1383 return false; 1384 } 1385 1386 canvasManager->AddShutdownObserver(this); 1387 return true; 1388 } 1389 1390 void CanvasRenderingContext2D::RemoveShutdownObserver() { 1391 auto* const canvasManager = CanvasShutdownManager::MaybeGet(); 1392 if (!canvasManager) { 1393 return; 1394 } 1395 1396 canvasManager->RemoveShutdownObserver(this); 1397 } 1398 1399 void CanvasRenderingContext2D::OnRemoteCanvasLost() { 1400 // We only lose context / data if we are using remote canvas, which is only 1401 // for accelerated targets. 1402 if (!mBufferProvider || !mBufferProvider->IsAccelerated() || mIsContextLost) { 1403 return; 1404 } 1405 1406 // 2. Set context's context lost to true. 1407 mIsContextLost = mAllowContextRestore = true; 1408 1409 // 3. Reset the rendering context to its default state given context. 1410 ClearTarget(); 1411 1412 // We dispatch because it isn't safe to call into the script event handlers, 1413 // and we don't want to mutate our state in CanvasShutdownManager. 1414 NS_DispatchToCurrentThread(NS_NewCancelableRunnableFunction( 1415 "CanvasRenderingContext2D::OnRemoteCanvasLost", [self = RefPtr{this}] { 1416 // 4. Let shouldRestore be the result of firing an event named 1417 // contextlost at canvas, with the cancelable attribute initialized to 1418 // true. 1419 self->mAllowContextRestore = self->DispatchEvent( 1420 u"contextlost"_ns, CanBubble::eNo, Cancelable::eYes); 1421 gfxCriticalNote << gfx::hexa(self.get()) 1422 << " accel canvas lost, can restore: " 1423 << self->mAllowContextRestore; 1424 })); 1425 } 1426 1427 void CanvasRenderingContext2D::OnRemoteCanvasRestored() { 1428 // We never lost our context if it was not a remote canvas, nor can we restore 1429 // if we have already shutdown. 1430 if (mHasShutdown || !mIsContextLost || !mAllowContextRestore) { 1431 return; 1432 } 1433 1434 // We dispatch because it isn't safe to call into the script event handlers, 1435 // and we don't want to mutate our state in CanvasShutdownManager. 1436 NS_DispatchToCurrentThread(NS_NewCancelableRunnableFunction( 1437 "CanvasRenderingContext2D::OnRemoteCanvasRestored", 1438 [self = RefPtr{this}] { 1439 // 5. If shouldRestore is false, then abort these steps. 1440 if (!self->mHasShutdown && self->mIsContextLost && 1441 self->mAllowContextRestore) { 1442 // 7. Set context's context lost to false. 1443 self->mIsContextLost = false; 1444 1445 // 6. Attempt to restore context by creating a backing storage using 1446 // context's attributes and associating them with context. If this 1447 // fails, then abort these steps. 1448 if (!self->EnsureTarget()) { 1449 gfxCriticalNote << gfx::hexa(self.get()) 1450 << " accel canvas failed to restore"; 1451 self->mIsContextLost = true; 1452 return; 1453 } 1454 1455 // 8. Fire an event named contextrestored at canvas. 1456 self->DispatchEvent(u"contextrestored"_ns, CanBubble::eNo, 1457 Cancelable::eNo); 1458 gfxCriticalNote << gfx::hexa(self.get()) << " accel canvas restored"; 1459 } 1460 })); 1461 } 1462 1463 void CanvasRenderingContext2D::SetStyleFromString(const nsACString& aStr, 1464 Style aWhichStyle) { 1465 MOZ_ASSERT(!aStr.IsVoid()); 1466 1467 Maybe<nscolor> color = ParseColor(aStr); 1468 if (!color) { 1469 return; 1470 } 1471 1472 CurrentState().SetColorStyle(aWhichStyle, *color); 1473 } 1474 1475 void CanvasRenderingContext2D::GetStyleAsUnion( 1476 OwningUTF8StringOrCanvasGradientOrCanvasPattern& aValue, 1477 Style aWhichStyle) { 1478 const ContextState& state = CurrentState(); 1479 if (state.patternStyles[aWhichStyle]) { 1480 aValue.SetAsCanvasPattern() = state.patternStyles[aWhichStyle]; 1481 } else if (state.gradientStyles[aWhichStyle]) { 1482 aValue.SetAsCanvasGradient() = state.gradientStyles[aWhichStyle]; 1483 } else { 1484 StyleColorToString(state.colorStyles[aWhichStyle], 1485 aValue.SetAsUTF8String()); 1486 } 1487 } 1488 1489 // static 1490 void CanvasRenderingContext2D::StyleColorToString(const nscolor& aColor, 1491 nsACString& aStr) { 1492 aStr.Truncate(); 1493 // We can't reuse the normal CSS color stringification code, 1494 // because the spec calls for a different algorithm for canvas. 1495 if (NS_GET_A(aColor) == 255) { 1496 aStr.AppendPrintf("#%02x%02x%02x", NS_GET_R(aColor), NS_GET_G(aColor), 1497 NS_GET_B(aColor)); 1498 } else { 1499 aStr.AppendPrintf("rgba(%d, %d, %d, ", NS_GET_R(aColor), NS_GET_G(aColor), 1500 NS_GET_B(aColor)); 1501 aStr.AppendFloat(nsStyleUtil::ColorComponentToFloat(NS_GET_A(aColor))); 1502 aStr.Append(')'); 1503 } 1504 } 1505 1506 nsresult CanvasRenderingContext2D::Redraw() { 1507 mFrameCaptureState = FrameCaptureState::DIRTY; 1508 1509 if (mIsEntireFrameInvalid) { 1510 return NS_OK; 1511 } 1512 1513 mIsEntireFrameInvalid = true; 1514 1515 if (mCanvasElement) { 1516 SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement); 1517 mCanvasElement->InvalidateCanvasContent(nullptr); 1518 } else if (mOffscreenCanvas) { 1519 mOffscreenCanvas->QueueCommitToCompositor(); 1520 } else { 1521 NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!"); 1522 } 1523 1524 return NS_OK; 1525 } 1526 1527 void CanvasRenderingContext2D::Redraw(const gfx::Rect& aR) { 1528 mFrameCaptureState = FrameCaptureState::DIRTY; 1529 1530 ++mInvalidateCount; 1531 1532 if (mIsEntireFrameInvalid) { 1533 return; 1534 } 1535 1536 if (mPredictManyRedrawCalls || mInvalidateCount > kCanvasMaxInvalidateCount) { 1537 Redraw(); 1538 return; 1539 } 1540 1541 if (mCanvasElement) { 1542 SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement); 1543 mCanvasElement->InvalidateCanvasContent(&aR); 1544 } else if (mOffscreenCanvas) { 1545 mOffscreenCanvas->QueueCommitToCompositor(); 1546 } else { 1547 NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!"); 1548 } 1549 } 1550 1551 void CanvasRenderingContext2D::DidRefresh() {} 1552 1553 void CanvasRenderingContext2D::RedrawUser(const gfxRect& aR) { 1554 mFrameCaptureState = FrameCaptureState::DIRTY; 1555 1556 if (mIsEntireFrameInvalid) { 1557 ++mInvalidateCount; 1558 return; 1559 } 1560 1561 gfx::Rect newr = mTarget->GetTransform().TransformBounds(ToRect(aR)); 1562 Redraw(newr); 1563 } 1564 1565 bool CanvasRenderingContext2D::CopyBufferProvider( 1566 PersistentBufferProvider& aOld, DrawTarget& aTarget, IntRect aCopyRect) { 1567 // Borrowing the snapshot must be done after ReturnTarget. 1568 RefPtr<SourceSurface> snapshot = aOld.BorrowSnapshot(); 1569 1570 if (!snapshot) { 1571 return false; 1572 } 1573 1574 aTarget.CopySurface(snapshot, aCopyRect, IntPoint()); 1575 1576 aOld.ReturnSnapshot(snapshot.forget()); 1577 return true; 1578 } 1579 1580 void CanvasRenderingContext2D::Demote() {} 1581 1582 void CanvasRenderingContext2D::ScheduleStableStateCallback() { 1583 if (mHasPendingStableStateCallback) { 1584 return; 1585 } 1586 mHasPendingStableStateCallback = true; 1587 1588 nsContentUtils::RunInStableState( 1589 NewRunnableMethod("dom::CanvasRenderingContext2D::OnStableState", this, 1590 &CanvasRenderingContext2D::OnStableState)); 1591 } 1592 1593 void CanvasRenderingContext2D::OnStableState() { 1594 if (!mHasPendingStableStateCallback) { 1595 return; 1596 } 1597 1598 ReturnTarget(); 1599 1600 mHasPendingStableStateCallback = false; 1601 } 1602 1603 void CanvasRenderingContext2D::RestoreClipsAndTransformToTarget() { 1604 // Restore clips and transform. 1605 mTarget->SetTransform(Matrix()); 1606 1607 if (mTarget->GetBackendType() == gfx::BackendType::CAIRO) { 1608 // Cairo doesn't play well with huge clips. When given a very big clip it 1609 // will try to allocate big mask surface without taking the target 1610 // size into account which can cause OOM. See bug 1034593. 1611 // This limits the clip extents to the size of the canvas. 1612 // A fix in Cairo would probably be preferable, but requires somewhat 1613 // invasive changes. 1614 mTarget->PushClipRect(gfx::Rect(0, 0, mWidth, mHeight)); 1615 } 1616 1617 for (auto& style : mStyleStack) { 1618 for (auto& clipOrTransform : style.clipsAndTransforms) { 1619 if (clipOrTransform.IsClip()) { 1620 if (clipOrTransform.clip->GetBackendType() != mPathType) { 1621 // We have possibly changed backends, so we need to convert the clips 1622 // in case they are no longer compatible with mTarget. 1623 RefPtr<PathBuilder> pathBuilder = mTarget->CreatePathBuilder(); 1624 clipOrTransform.clip->StreamToSink(pathBuilder); 1625 clipOrTransform.clip = pathBuilder->Finish(); 1626 } 1627 mTarget->PushClip(clipOrTransform.clip); 1628 } else { 1629 mTarget->SetTransform(clipOrTransform.transform); 1630 } 1631 } 1632 } 1633 } 1634 1635 bool CanvasRenderingContext2D::BorrowTarget(const IntRect& aPersistedRect, 1636 bool aNeedsClear) { 1637 // We are attempting to request a DrawTarget from the current 1638 // PersistentBufferProvider. However, if the provider needs to be refreshed, 1639 // or if it is accelerated and the application has requested that we disallow 1640 // acceleration, then we skip trying to use this provider so that it will be 1641 // recreated by EnsureTarget later. 1642 if (!mBufferProvider || mBufferProvider->RequiresRefresh() || 1643 (mBufferProvider->IsAccelerated() && UseSoftwareRendering())) { 1644 return false; 1645 } 1646 mTarget = mBufferProvider->BorrowDrawTarget(aPersistedRect); 1647 if (!mTarget || !mTarget->IsValid()) { 1648 if (mTarget) { 1649 mBufferProvider->ReturnDrawTarget(mTarget.forget()); 1650 } 1651 return false; 1652 } 1653 if (!mBufferProvider->PreservesDrawingState() || mBufferNeedsClear || 1654 mTargetNeedsClipsAndTransforms) { 1655 if (mBufferProvider->PreservesDrawingState()) { 1656 // If the buffer provider preserves the clip and transform state, then 1657 // we must ensure it is cleared before reusing the target. 1658 if (!mTarget->RemoveAllClips()) { 1659 mBufferProvider->ReturnDrawTarget(mTarget.forget()); 1660 return false; 1661 } 1662 mTarget->SetTransform(Matrix()); 1663 } 1664 1665 // If the canvas was reset, then we need to clear the target in case its 1666 // contents was somehow preserved. We only need to clear the target if 1667 // the operation doesn't fill the entire canvas. 1668 if (mBufferNeedsClear && aNeedsClear) { 1669 mTarget->ClearRect(gfx::Rect(mTarget->GetRect())); 1670 } 1671 1672 RestoreClipsAndTransformToTarget(); 1673 1674 mBufferNeedsClear = false; 1675 mTargetNeedsClipsAndTransforms = false; 1676 } 1677 return true; 1678 } 1679 1680 bool CanvasRenderingContext2D::HasAnyClips() const { 1681 for (const auto& style : mStyleStack) { 1682 for (const auto& clipOrTransform : style.clipsAndTransforms) { 1683 if (clipOrTransform.IsClip()) { 1684 return true; 1685 } 1686 } 1687 } 1688 return false; 1689 } 1690 1691 bool CanvasRenderingContext2D::HasErrorState(ErrorResult& aError, 1692 bool aInitProvider) { 1693 // If there is no buffer provider, then attempt to initialize it to flush any 1694 // error state. It also forces any backend resources (such as WebGL contexts) 1695 // in the buffer provider to initialize as soon as possible, so that it may 1696 // speed up initial page loads. 1697 // It is normally beneficial to delay initialization of just the target until 1698 // we encounter a drawing operation, since this may allow us to elide copying 1699 // the old contents of the target to the new target if the drawing operation 1700 // would overwrite the entire framebuffer contents. 1701 // However, if there is no old target to copy from, there is no benefit to 1702 // delaying it. It may incidentally delay creation of the buffer provider, 1703 // which is an expensive operation that benefits from being scheduled as soon 1704 // as possible. Thus, we only want to delay initialization of the target when 1705 // a buffer provider already exists. 1706 if (aInitProvider && !mBufferProvider && !EnsureTarget(aError)) { 1707 return true; 1708 } 1709 1710 if (AlreadyShutDown()) { 1711 gfxCriticalNoteOnce << "Attempt to render into a Canvas2d after shutdown."; 1712 SetErrorState(); 1713 aError.ThrowInvalidStateError( 1714 "Cannot use canvas after shutdown initiated."); 1715 return true; 1716 } 1717 1718 // The spec doesn't say what to do in this case, but Chrome silently fails 1719 // without throwing an error. We should at least throw if the canvas is 1720 // permanently disabled. 1721 if (NS_WARN_IF(mIsContextLost)) { 1722 if (!mAllowContextRestore) { 1723 aError.ThrowInvalidStateError( 1724 "Cannot use canvas as context is lost forever."); 1725 } 1726 return true; 1727 } 1728 1729 if (mTarget && mTarget == sErrorTarget.get()) { 1730 aError.ThrowInvalidStateError("Canvas is already in error state."); 1731 return true; 1732 } 1733 1734 return false; 1735 } 1736 1737 bool CanvasRenderingContext2D::EnsureTarget(ErrorResult& aError, 1738 const gfx::Rect* aCoveredRect, 1739 bool aWillClear, 1740 bool aSkipTransform) { 1741 if (HasErrorState(aError, false)) { 1742 return false; 1743 } 1744 1745 // If there is an existing target and no error state, just reuse it. 1746 if (mTarget) { 1747 return true; 1748 } 1749 1750 // Check that the dimensions are sane 1751 if (mWidth > StaticPrefs::gfx_canvas_max_size() || 1752 mHeight > StaticPrefs::gfx_canvas_max_size()) { 1753 SetErrorState(); 1754 aError.ThrowInvalidStateError("Canvas exceeds max size."); 1755 return false; 1756 } 1757 1758 if (mWidth < 0 || mHeight < 0) { 1759 SetErrorState(); 1760 aError.ThrowInvalidStateError("Canvas has invalid size."); 1761 return false; 1762 } 1763 1764 // If the next drawing command covers the entire canvas, we can skip copying 1765 // from the previous frame and/or clearing the canvas. 1766 // If a clip is active we don't know for sure that the next drawing command 1767 // will really cover the entire canvas. 1768 gfx::Rect canvasRect(0, 0, mWidth, mHeight); 1769 bool canDiscardContent = 1770 aCoveredRect && 1771 (aSkipTransform ? *aCoveredRect 1772 : GetCurrentTransform().TransformBounds(*aCoveredRect)) 1773 .Contains(canvasRect) && 1774 !HasAnyClips(); 1775 1776 ScheduleStableStateCallback(); 1777 1778 IntRect persistedRect = canDiscardContent || mBufferNeedsClear 1779 ? IntRect() 1780 : IntRect(0, 0, mWidth, mHeight); 1781 1782 // Attempt to reuse the existing buffer provider. 1783 if (BorrowTarget(persistedRect, !canDiscardContent)) { 1784 return true; 1785 } 1786 1787 RefPtr<DrawTarget> newTarget; 1788 RefPtr<PersistentBufferProvider> newProvider; 1789 1790 if (!TryAcceleratedTarget(newTarget, newProvider) && 1791 !TrySharedTarget(newTarget, newProvider) && 1792 !TryBasicTarget(newTarget, newProvider, aError)) { 1793 gfxCriticalError( 1794 CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(GetSize()))) 1795 << "Failed borrow shared and basic targets."; 1796 1797 SetErrorState(); 1798 return false; 1799 } 1800 1801 MOZ_ASSERT(newTarget); 1802 MOZ_ASSERT(newProvider); 1803 1804 bool needsClear = 1805 !canDiscardContent || (mBufferProvider && mBufferNeedsClear); 1806 if (newTarget->GetBackendType() == gfx::BackendType::SKIA && 1807 (needsClear || !aWillClear)) { 1808 // Skia expects the unused X channel to contains 0xFF even for opaque 1809 // operations so we can't skip clearing in that case, even if we are going 1810 // to cover the entire canvas in the next drawing operation. 1811 newTarget->ClearRect(canvasRect); 1812 needsClear = false; 1813 } 1814 1815 // Try to copy data from the previous buffer provider if there is one. 1816 if (!canDiscardContent && mBufferProvider && !mBufferNeedsClear && 1817 CopyBufferProvider(*mBufferProvider, *newTarget, persistedRect)) { 1818 needsClear = false; 1819 } 1820 1821 if (needsClear) { 1822 newTarget->ClearRect(canvasRect); 1823 } 1824 1825 // Ensure any Path state is compatible with the type of DrawTarget used. This 1826 // may require making a copy with the correct type if they (rarely) mismatch. 1827 mPathType = newTarget->GetPathType(); 1828 MOZ_ASSERT(mPathType != BackendType::NONE); 1829 if (mPathBuilder && mPathBuilder->GetBackendType() != mPathType) { 1830 RefPtr<Path> path = mPathBuilder->Finish(); 1831 mPathBuilder = newTarget->CreatePathBuilder(path->GetFillRule()); 1832 path->StreamToSink(mPathBuilder); 1833 } 1834 if (mPath && mPath->GetBackendType() != mPathType) { 1835 RefPtr<PathBuilder> builder = 1836 newTarget->CreatePathBuilder(mPath->GetFillRule()); 1837 mPath->StreamToSink(builder); 1838 mPath = builder->Finish(); 1839 } 1840 1841 mTarget = std::move(newTarget); 1842 mBufferProvider = std::move(newProvider); 1843 mBufferNeedsClear = false; 1844 mTargetNeedsClipsAndTransforms = false; 1845 1846 RegisterAllocation(); 1847 AddZoneWaitingForGC(); 1848 1849 RestoreClipsAndTransformToTarget(); 1850 1851 // Force a full layer transaction since we didn't have a layer before 1852 // and now we might need one. 1853 if (mCanvasElement) { 1854 mCanvasElement->InvalidateCanvas(); 1855 } 1856 // EnsureTarget hasn't drawn anything. Preserve mFrameCaptureState. 1857 FrameCaptureState captureState = mFrameCaptureState; 1858 // Calling Redraw() tells our invalidation machinery that the entire 1859 // canvas is already invalid, which can speed up future drawing. 1860 Redraw(); 1861 mFrameCaptureState = captureState; 1862 1863 return true; 1864 } 1865 1866 void CanvasRenderingContext2D::SetInitialState() { 1867 // Set up the initial canvas defaults 1868 mPathBuilder = nullptr; 1869 mPath = nullptr; 1870 mPathPruned = false; 1871 mPathTransform = Matrix(); 1872 mPathTransformDirty = false; 1873 mPathType = 1874 (mTarget ? mTarget : gfxPlatform::ThreadLocalScreenReferenceDrawTarget()) 1875 ->GetPathType(); 1876 MOZ_ASSERT(mPathType != BackendType::NONE); 1877 1878 mStyleStack.Clear(); 1879 ContextState* state = mStyleStack.AppendElement(); 1880 state->globalAlpha = 1.0; 1881 1882 state->colorStyles[Style::FILL] = NS_RGB(0, 0, 0); 1883 state->colorStyles[Style::STROKE] = NS_RGB(0, 0, 0); 1884 state->shadowColor = NS_RGBA(0, 0, 0, 0); 1885 } 1886 1887 void CanvasRenderingContext2D::SetErrorState() { 1888 EnsureErrorTarget(); 1889 1890 if (mTarget && mTarget != sErrorTarget.get()) { 1891 gCanvasAzureMemoryUsed -= mWidth * mHeight * 4; 1892 } 1893 1894 mTarget = sErrorTarget.get(); 1895 mBufferProvider = nullptr; 1896 1897 // clear transforms, clips, etc. 1898 SetInitialState(); 1899 } 1900 1901 void CanvasRenderingContext2D::RegisterAllocation() { 1902 // XXX - It would make more sense to track the allocation in 1903 // PeristentBufferProvider, rather than here. 1904 static bool registered = false; 1905 // FIXME: Disable the reporter for now, see bug 1241865 1906 if (!registered && false) { 1907 registered = true; 1908 RegisterStrongMemoryReporter(new Canvas2dPixelsReporter()); 1909 } 1910 } 1911 1912 void CanvasRenderingContext2D::AddZoneWaitingForGC() { 1913 JSObject* wrapper = GetWrapperPreserveColor(); 1914 if (wrapper) { 1915 CycleCollectedJSRuntime::Get()->AddZoneWaitingForGC( 1916 JS::GetObjectZone(wrapper)); 1917 } 1918 } 1919 1920 static WindowRenderer* WindowRendererFromCanvasElement( 1921 nsINode* aCanvasElement) { 1922 if (!aCanvasElement) { 1923 return nullptr; 1924 } 1925 1926 return nsContentUtils::WindowRendererForDocument(aCanvasElement->OwnerDoc()); 1927 } 1928 1929 bool CanvasRenderingContext2D::TryAcceleratedTarget( 1930 RefPtr<gfx::DrawTarget>& aOutDT, 1931 RefPtr<layers::PersistentBufferProvider>& aOutProvider) { 1932 if (!XRE_IsContentProcess()) { 1933 // Only allow accelerated contexts to be created in a content process to 1934 // ensure it is remoted appropriately and run on the correct parent or 1935 // GPU process threads. 1936 return false; 1937 } 1938 if (mBufferProvider && mBufferProvider->IsAccelerated() && 1939 mBufferProvider->RequiresRefresh()) { 1940 // If there is already a provider and we got here, then the provider needs 1941 // to be refreshed and we should avoid using acceleration in the future. 1942 mAllowAcceleration = false; 1943 } 1944 // Don't try creating an accelerate DrawTarget if either acceleration failed 1945 // previously or if the application expects acceleration to be slow. 1946 if (!mAllowAcceleration || UseSoftwareRendering()) { 1947 return false; 1948 } 1949 1950 if (mCanvasElement) { 1951 MOZ_ASSERT(NS_IsMainThread()); 1952 1953 WindowRenderer* renderer = WindowRendererFromCanvasElement(mCanvasElement); 1954 if (NS_WARN_IF(!renderer)) { 1955 return false; 1956 } 1957 1958 aOutProvider = PersistentBufferProviderAccelerated::Create( 1959 GetSize(), GetSurfaceFormat(), renderer->AsKnowsCompositor()); 1960 } else if (mOffscreenCanvas && 1961 StaticPrefs::gfx_canvas_remote_allow_offscreen()) { 1962 RefPtr<ImageBridgeChild> imageBridge = ImageBridgeChild::GetSingleton(); 1963 if (NS_WARN_IF(!imageBridge)) { 1964 return false; 1965 } 1966 1967 aOutProvider = PersistentBufferProviderAccelerated::Create( 1968 GetSize(), GetSurfaceFormat(), imageBridge); 1969 } 1970 1971 if (!aOutProvider) { 1972 return false; 1973 } 1974 aOutDT = aOutProvider->BorrowDrawTarget(IntRect()); 1975 MOZ_ASSERT(aOutDT); 1976 return !!aOutDT; 1977 } 1978 1979 bool CanvasRenderingContext2D::TrySharedTarget( 1980 RefPtr<gfx::DrawTarget>& aOutDT, 1981 RefPtr<layers::PersistentBufferProvider>& aOutProvider) { 1982 aOutDT = nullptr; 1983 aOutProvider = nullptr; 1984 1985 if (mBufferProvider && mBufferProvider->IsShared()) { 1986 // we are already using a shared buffer provider, we are allocating a new 1987 // one because the current one failed so let's just fall back to the basic 1988 // provider. 1989 return false; 1990 } 1991 1992 if (mCanvasElement) { 1993 MOZ_ASSERT(NS_IsMainThread()); 1994 1995 WindowRenderer* renderer = WindowRendererFromCanvasElement(mCanvasElement); 1996 if (NS_WARN_IF(!renderer)) { 1997 return false; 1998 } 1999 2000 aOutProvider = renderer->CreatePersistentBufferProvider( 2001 GetSize(), GetSurfaceFormat(), 2002 !mAllowAcceleration || UseSoftwareRendering()); 2003 } else if (mOffscreenCanvas) { 2004 if (!StaticPrefs::gfx_offscreencanvas_shared_provider()) { 2005 return false; 2006 } 2007 2008 RefPtr<layers::ImageBridgeChild> imageBridge = 2009 layers::ImageBridgeChild::GetSingleton(); 2010 if (NS_WARN_IF(!imageBridge)) { 2011 return false; 2012 } 2013 2014 aOutProvider = PersistentBufferProviderShared::Create( 2015 GetSize(), GetSurfaceFormat(), imageBridge, 2016 !mAllowAcceleration || UseSoftwareRendering(), 2017 mOffscreenCanvas->GetWindowID()); 2018 } 2019 2020 if (!aOutProvider) { 2021 return false; 2022 } 2023 2024 // We can pass an empty persisted rect since we just created the buffer 2025 // provider (nothing to restore). 2026 aOutDT = aOutProvider->BorrowDrawTarget(IntRect()); 2027 MOZ_ASSERT(aOutDT); 2028 2029 return !!aOutDT; 2030 } 2031 2032 bool CanvasRenderingContext2D::TryBasicTarget( 2033 RefPtr<gfx::DrawTarget>& aOutDT, 2034 RefPtr<layers::PersistentBufferProvider>& aOutProvider, 2035 ErrorResult& aError) { 2036 aOutDT = gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget( 2037 GetSize(), GetSurfaceFormat(), UseSoftwareRendering()); 2038 if (!aOutDT) { 2039 aError.ThrowInvalidStateError("Canvas could not create basic draw target."); 2040 return false; 2041 } 2042 2043 // See Bug 1524554 - this forces DT initialization. 2044 aOutDT->ClearRect(gfx::Rect()); 2045 2046 if (!aOutDT->IsValid()) { 2047 aOutDT = nullptr; 2048 aError.ThrowInvalidStateError("Canvas could not init basic draw target."); 2049 return false; 2050 } 2051 2052 aOutProvider = new PersistentBufferProviderBasic(aOutDT); 2053 return true; 2054 } 2055 2056 PersistentBufferProvider* CanvasRenderingContext2D::GetBufferProvider() { 2057 if (mBufferProvider && mBufferNeedsClear) { 2058 // Force the buffer to clear before it is used. 2059 EnsureTarget(); 2060 } 2061 return mBufferProvider; 2062 } 2063 2064 Maybe<SurfaceDescriptor> CanvasRenderingContext2D::GetFrontBuffer( 2065 WebGLFramebufferJS*, const bool webvr) { 2066 if (auto* provider = GetBufferProvider()) { 2067 return provider->GetFrontBuffer(); 2068 } 2069 return Nothing(); 2070 } 2071 2072 already_AddRefed<layers::FwdTransactionTracker> 2073 CanvasRenderingContext2D::UseCompositableForwarder( 2074 layers::CompositableForwarder* aForwarder) { 2075 if (mBufferProvider) { 2076 return mBufferProvider->UseCompositableForwarder(aForwarder); 2077 } 2078 return nullptr; 2079 } 2080 2081 PresShell* CanvasRenderingContext2D::GetPresShell() { 2082 if (mCanvasElement) { 2083 return mCanvasElement->OwnerDoc()->GetPresShell(); 2084 } 2085 if (mDocShell) { 2086 return mDocShell->GetPresShell(); 2087 } 2088 return nullptr; 2089 } 2090 2091 NS_IMETHODIMP 2092 CanvasRenderingContext2D::SetDimensions(int32_t aWidth, int32_t aHeight) { 2093 // Zero sized surfaces can cause problems. 2094 mZero = false; 2095 if (aHeight == 0) { 2096 aHeight = 1; 2097 mZero = true; 2098 } 2099 if (aWidth == 0) { 2100 aWidth = 1; 2101 mZero = true; 2102 } 2103 2104 ClearTarget(aWidth, aHeight); 2105 2106 return NS_OK; 2107 } 2108 2109 void CanvasRenderingContext2D::AddAssociatedMemory() { 2110 JSObject* wrapper = GetWrapperMaybeDead(); 2111 if (wrapper) { 2112 JS::AddAssociatedMemory(wrapper, BindingJSObjectMallocBytes(this), 2113 JS::MemoryUse::DOMBinding); 2114 } 2115 } 2116 2117 void CanvasRenderingContext2D::RemoveAssociatedMemory() { 2118 JSObject* wrapper = GetWrapperMaybeDead(); 2119 if (wrapper) { 2120 JS::RemoveAssociatedMemory(wrapper, BindingJSObjectMallocBytes(this), 2121 JS::MemoryUse::DOMBinding); 2122 } 2123 } 2124 2125 void CanvasRenderingContext2D::ClearTarget(int32_t aWidth, int32_t aHeight) { 2126 // Only free the buffer provider if the size no longer matches. 2127 bool freeBuffer = aWidth != mWidth || aHeight != mHeight; 2128 ResetBitmap(freeBuffer); 2129 2130 mResetLayer = true; 2131 2132 SetInitialState(); 2133 2134 // Update dimensions only if new (strictly positive) values were passed. 2135 if (aWidth > 0 && aHeight > 0) { 2136 // Update the memory size associated with the wrapper object when we change 2137 // the dimensions. Note that we need to keep updating dying wrappers before 2138 // they are finalized so that the memory accounting balances out. 2139 RemoveAssociatedMemory(); 2140 mWidth = aWidth; 2141 mHeight = aHeight; 2142 AddAssociatedMemory(); 2143 } 2144 2145 if (mOffscreenCanvas) { 2146 OffscreenCanvasDisplayData data; 2147 data.mSize = {mWidth, mHeight}; 2148 data.mIsOpaque = mOpaque; 2149 data.mIsAlphaPremult = true; 2150 data.mDoPaintCallbacks = true; 2151 mOffscreenCanvas->UpdateDisplayData(data); 2152 } 2153 2154 if (!mCanvasElement || !mCanvasElement->IsInComposedDoc()) { 2155 return; 2156 } 2157 2158 // For vertical writing-mode, unless text-orientation is sideways, 2159 // we'll modify the initial value of textBaseline to 'middle'. 2160 RefPtr<const ComputedStyle> canvasStyle = 2161 nsComputedDOMStyle::GetComputedStyle(mCanvasElement); 2162 if (canvasStyle) { 2163 WritingMode wm(canvasStyle); 2164 if (wm.IsVertical() && !wm.IsSideways()) { 2165 CurrentState().textBaseline = CanvasTextBaseline::Middle; 2166 } 2167 } 2168 } 2169 2170 void CanvasRenderingContext2D::ReturnTarget(bool aForceReset) { 2171 if (mTarget && mBufferProvider && mTarget != sErrorTarget.get()) { 2172 CurrentState().transform = mTarget->GetTransform(); 2173 if (aForceReset || !mBufferProvider->PreservesDrawingState()) { 2174 for (const auto& style : mStyleStack) { 2175 for (const auto& clipOrTransform : style.clipsAndTransforms) { 2176 if (clipOrTransform.IsClip()) { 2177 mTarget->PopClip(); 2178 } 2179 } 2180 } 2181 2182 if (mTarget->GetBackendType() == gfx::BackendType::CAIRO) { 2183 // With the cairo backend we pushed an extra clip rect which we have to 2184 // balance out here. See the comment in 2185 // RestoreClipsAndTransformToTarget. 2186 mTarget->PopClip(); 2187 } 2188 2189 mTarget->SetTransform(Matrix()); 2190 } 2191 2192 mBufferProvider->ReturnDrawTarget(mTarget.forget()); 2193 } 2194 } 2195 2196 NS_IMETHODIMP 2197 CanvasRenderingContext2D::InitializeWithDrawTarget( 2198 nsIDocShell* aShell, NotNull<gfx::DrawTarget*> aTarget) { 2199 if (NS_WARN_IF(!AddShutdownObserver())) { 2200 return NS_ERROR_FAILURE; 2201 } 2202 2203 RemovePostRefreshObserver(); 2204 mDocShell = aShell; 2205 AddPostRefreshObserverIfNecessary(); 2206 2207 IntSize size = aTarget->GetSize(); 2208 SetDimensions(size.width, size.height); 2209 2210 mTarget = aTarget; 2211 mBufferProvider = new PersistentBufferProviderBasic(aTarget); 2212 2213 RestoreClipsAndTransformToTarget(); 2214 2215 return NS_OK; 2216 } 2217 2218 void CanvasRenderingContext2D::SetOpaqueValueFromOpaqueAttr( 2219 bool aOpaqueAttrValue) { 2220 if (aOpaqueAttrValue != mOpaqueAttrValue) { 2221 mOpaqueAttrValue = aOpaqueAttrValue; 2222 UpdateIsOpaque(); 2223 } 2224 } 2225 2226 void CanvasRenderingContext2D::UpdateIsOpaque() { 2227 mOpaque = !mContextAttributesHasAlpha || mOpaqueAttrValue; 2228 ClearTarget(); 2229 } 2230 2231 NS_IMETHODIMP 2232 CanvasRenderingContext2D::SetContextOptions(JSContext* aCx, 2233 JS::Handle<JS::Value> aOptions, 2234 ErrorResult& aRvForDictionaryInit) { 2235 if (aOptions.isNullOrUndefined()) { 2236 return NS_OK; 2237 } 2238 2239 // This shouldn't be called before drawing starts, so there should be no 2240 // drawtarget yet 2241 MOZ_ASSERT(!mTarget); 2242 2243 CanvasRenderingContext2DSettings attributes; 2244 if (!attributes.Init(aCx, aOptions)) { 2245 aRvForDictionaryInit.Throw(NS_ERROR_UNEXPECTED); 2246 return NS_ERROR_UNEXPECTED; 2247 } 2248 2249 mWillReadFrequently = attributes.mWillReadFrequently; 2250 mForceSoftwareRendering = attributes.mForceSoftwareRendering; 2251 2252 mContextAttributesHasAlpha = attributes.mAlpha; 2253 UpdateIsOpaque(); 2254 2255 return NS_OK; 2256 } 2257 2258 UniquePtr<uint8_t[]> CanvasRenderingContext2D::GetImageBuffer( 2259 mozilla::CanvasUtils::ImageExtraction aExtractionBehavior, 2260 int32_t* out_format, gfx::IntSize* out_imageSize) { 2261 UniquePtr<uint8_t[]> ret; 2262 2263 *out_format = 0; 2264 *out_imageSize = {}; 2265 2266 if (!GetBufferProvider() && !EnsureTarget()) { 2267 return nullptr; 2268 } 2269 2270 RefPtr<SourceSurface> snapshot = mBufferProvider->BorrowSnapshot(); 2271 if (snapshot) { 2272 RefPtr<DataSourceSurface> data = snapshot->GetDataSurface(); 2273 if (data && data->GetSize() == GetSize()) { 2274 *out_format = imgIEncoder::INPUT_FORMAT_HOSTARGB; 2275 *out_imageSize = data->GetSize(); 2276 ret = SurfaceToPackedBGRA(data); 2277 } 2278 } 2279 2280 mBufferProvider->ReturnSnapshot(snapshot.forget()); 2281 2282 if (ret) { 2283 nsRFPService::PotentiallyDumpImage( 2284 PrincipalOrNull(), ret.get(), out_imageSize->width, 2285 out_imageSize->height, 2286 out_imageSize->width * out_imageSize->height * 4); 2287 if (aExtractionBehavior == CanvasUtils::ImageExtraction::Randomize) { 2288 nsRFPService::RandomizePixels( 2289 GetCookieJarSettings(), PrincipalOrNull(), ret.get(), 2290 out_imageSize->width, out_imageSize->height, 2291 out_imageSize->width * out_imageSize->height * 4, 2292 SurfaceFormat::A8R8G8B8_UINT32); 2293 } 2294 } 2295 2296 return ret; 2297 } 2298 2299 NS_IMETHODIMP 2300 CanvasRenderingContext2D::GetInputStream( 2301 const char* aMimeType, const nsAString& aEncoderOptions, 2302 mozilla::CanvasUtils::ImageExtraction aExtractionBehavior, 2303 const nsACString& aRandomizationKey, nsIInputStream** aStream) { 2304 nsCString enccid("@mozilla.org/image/encoder;2?type="); 2305 enccid += aMimeType; 2306 nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get()); 2307 if (!encoder) { 2308 return NS_ERROR_FAILURE; 2309 } 2310 2311 int32_t format = 0; 2312 gfx::IntSize imageSize = {}; 2313 UniquePtr<uint8_t[]> imageBuffer = 2314 GetImageBuffer(aExtractionBehavior, &format, &imageSize); 2315 if (!imageBuffer) { 2316 return NS_ERROR_FAILURE; 2317 } 2318 2319 return ImageEncoder::GetInputStream( 2320 imageSize.width, imageSize.height, imageBuffer.get(), format, encoder, 2321 aEncoderOptions, aRandomizationKey, aStream); 2322 } 2323 2324 already_AddRefed<mozilla::gfx::SourceSurface> 2325 CanvasRenderingContext2D::GetOptimizedSnapshot(DrawTarget* aTarget, 2326 gfxAlphaType* aOutAlphaType) { 2327 if (aOutAlphaType) { 2328 *aOutAlphaType = (mOpaque ? gfxAlphaType::Opaque : gfxAlphaType::Premult); 2329 } 2330 2331 // For GetSurfaceSnapshot we always call EnsureTarget even if mBufferProvider 2332 // already exists, otherwise we get performance issues. See bug 1567054. 2333 if (!EnsureTarget()) { 2334 MOZ_ASSERT( 2335 mTarget == sErrorTarget.get() || mIsContextLost, 2336 "On EnsureTarget failure mTarget should be set to sErrorTarget."); 2337 // In rare circumstances we may have failed to create an error target. 2338 return mTarget ? mTarget->Snapshot() : nullptr; 2339 } 2340 2341 // The concept of BorrowSnapshot seems a bit broken here, but the original 2342 // code in GetSurfaceSnapshot just returned a snapshot from mTarget, which 2343 // amounts to breaking the concept implicitly. 2344 RefPtr<SourceSurface> snapshot = mBufferProvider->BorrowSnapshot(aTarget); 2345 RefPtr<SourceSurface> retSurface = snapshot; 2346 mBufferProvider->ReturnSnapshot(snapshot.forget()); 2347 return retSurface.forget(); 2348 } 2349 2350 SurfaceFormat CanvasRenderingContext2D::GetSurfaceFormat() const { 2351 return mOpaque ? SurfaceFormat::B8G8R8X8 : SurfaceFormat::B8G8R8A8; 2352 } 2353 2354 // 2355 // state 2356 // 2357 2358 Matrix CanvasRenderingContext2D::GetCurrentTransform() const { 2359 if (IsTargetValid()) { 2360 return mTarget->GetTransform(); 2361 } 2362 for (auto style = mStyleStack.crbegin(); style != mStyleStack.crend(); 2363 ++style) { 2364 const auto& clipsAndTransforms = style->clipsAndTransforms; 2365 auto clipOrTransform = clipsAndTransforms.end(); 2366 while (clipOrTransform != clipsAndTransforms.begin()) { 2367 --clipOrTransform; 2368 if (!clipOrTransform->IsClip()) { 2369 return clipOrTransform->transform; 2370 } 2371 } 2372 } 2373 return Matrix(); 2374 } 2375 2376 void CanvasRenderingContext2D::Save() { 2377 if (MOZ_UNLIKELY(HasErrorState() || mStyleStack.IsEmpty())) { 2378 SetErrorState(); 2379 return; 2380 } 2381 mStyleStack[mStyleStack.Length() - 1].transform = GetCurrentTransform(); 2382 mStyleStack.SetCapacity(mStyleStack.Length() + 1); 2383 mStyleStack.AppendElement(CurrentState()); 2384 2385 if (mStyleStack.Length() > MAX_STYLE_STACK_SIZE) { 2386 // This is not fast, but is better than OOMing and shouldn't be hit by 2387 // reasonable code. 2388 mStyleStack.RemoveElementAt(0); 2389 } 2390 } 2391 2392 void CanvasRenderingContext2D::Restore() { 2393 if (MOZ_UNLIKELY(mStyleStack.Length() < 2 || HasErrorState())) { 2394 return; 2395 } 2396 2397 if (IsTargetValid()) { 2398 for (const auto& clipOrTransform : CurrentState().clipsAndTransforms) { 2399 if (clipOrTransform.IsClip()) { 2400 mTarget->PopClip(); 2401 } 2402 } 2403 mTarget->SetTransform(PreviousState().transform); 2404 } else { 2405 mTargetNeedsClipsAndTransforms = true; 2406 } 2407 2408 mStyleStack.RemoveLastElement(); 2409 2410 mPathTransformDirty = true; 2411 } 2412 2413 // 2414 // transformations 2415 // 2416 2417 void CanvasRenderingContext2D::Scale(double aX, double aY, 2418 ErrorResult& aError) { 2419 if (HasErrorState(aError)) { 2420 return; 2421 } 2422 Matrix newMatrix = GetCurrentTransform(); 2423 newMatrix.PreScale(aX, aY); 2424 SetTransformInternal(newMatrix); 2425 } 2426 2427 void CanvasRenderingContext2D::Rotate(double aAngle, ErrorResult& aError) { 2428 if (HasErrorState(aError)) { 2429 return; 2430 } 2431 Matrix newMatrix = Matrix::Rotation(aAngle) * GetCurrentTransform(); 2432 SetTransformInternal(newMatrix); 2433 } 2434 2435 void CanvasRenderingContext2D::Translate(double aX, double aY, 2436 ErrorResult& aError) { 2437 if (HasErrorState(aError)) { 2438 return; 2439 } 2440 Matrix newMatrix = GetCurrentTransform(); 2441 newMatrix.PreTranslate(aX, aY); 2442 SetTransformInternal(newMatrix); 2443 } 2444 2445 void CanvasRenderingContext2D::Transform(double aM11, double aM12, double aM21, 2446 double aM22, double aDx, double aDy, 2447 ErrorResult& aError) { 2448 if (HasErrorState(aError)) { 2449 return; 2450 } 2451 Matrix newMatrix(aM11, aM12, aM21, aM22, aDx, aDy); 2452 newMatrix *= GetCurrentTransform(); 2453 SetTransformInternal(newMatrix); 2454 } 2455 2456 already_AddRefed<DOMMatrix> CanvasRenderingContext2D::GetTransform( 2457 ErrorResult& aError) { 2458 // If we are silently failing, then we still need to return a transform while 2459 // we are in the process of recovering. 2460 RefPtr<DOMMatrix> matrix = 2461 new DOMMatrix(GetParentObject(), GetCurrentTransform()); 2462 return matrix.forget(); 2463 } 2464 2465 void CanvasRenderingContext2D::SetTransform(double aM11, double aM12, 2466 double aM21, double aM22, 2467 double aDx, double aDy, 2468 ErrorResult& aError) { 2469 if (HasErrorState(aError)) { 2470 return; 2471 } 2472 Matrix newMatrix(aM11, aM12, aM21, aM22, aDx, aDy); 2473 SetTransformInternal(newMatrix); 2474 } 2475 2476 void CanvasRenderingContext2D::SetTransform(const DOMMatrix2DInit& aInit, 2477 ErrorResult& aError) { 2478 if (HasErrorState(aError)) { 2479 return; 2480 } 2481 Matrix matrix2D(DOMMatrixReadOnly::ToValidatedMatrixDouble(aInit, aError)); 2482 if (!aError.Failed()) { 2483 SetTransformInternal(matrix2D); 2484 } 2485 } 2486 2487 void CanvasRenderingContext2D::SetTransformInternal(const Matrix& aTransform) { 2488 if (!aTransform.IsFinite()) { 2489 return; 2490 } 2491 2492 // Save the transform in the clip stack to be able to replay clips properly. 2493 auto& clipsAndTransforms = CurrentState().clipsAndTransforms; 2494 if (clipsAndTransforms.IsEmpty() || 2495 clipsAndTransforms.LastElement().IsClip()) { 2496 clipsAndTransforms.AppendElement(ClipState(aTransform)); 2497 } else { 2498 // If the last item is a transform we can replace it instead of appending 2499 // a new item. 2500 clipsAndTransforms.LastElement().transform = aTransform; 2501 } 2502 2503 if (IsTargetValid()) { 2504 mTarget->SetTransform(aTransform); 2505 } else { 2506 mTargetNeedsClipsAndTransforms = true; 2507 } 2508 mPathTransformDirty = true; 2509 } 2510 2511 void CanvasRenderingContext2D::ResetTransform(ErrorResult& aError) { 2512 SetTransform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0, aError); 2513 } 2514 2515 // 2516 // colors 2517 // 2518 2519 void CanvasRenderingContext2D::SetStyleFromUnion( 2520 const UTF8StringOrCanvasGradientOrCanvasPattern& aValue, 2521 Style aWhichStyle) { 2522 if (aValue.IsUTF8String()) { 2523 SetStyleFromString(aValue.GetAsUTF8String(), aWhichStyle); 2524 return; 2525 } 2526 2527 if (aValue.IsCanvasGradient()) { 2528 SetStyleFromGradient(aValue.GetAsCanvasGradient(), aWhichStyle); 2529 return; 2530 } 2531 2532 if (aValue.IsCanvasPattern()) { 2533 CanvasPattern& pattern = aValue.GetAsCanvasPattern(); 2534 SetStyleFromPattern(pattern, aWhichStyle); 2535 if (pattern.mForceWriteOnly) { 2536 SetWriteOnly(); 2537 } 2538 return; 2539 } 2540 2541 MOZ_ASSERT_UNREACHABLE("Invalid union value"); 2542 } 2543 2544 void CanvasRenderingContext2D::SetFillRule(const nsAString& aString) { 2545 FillRule rule; 2546 2547 if (aString.EqualsLiteral("evenodd")) 2548 rule = FillRule::FILL_EVEN_ODD; 2549 else if (aString.EqualsLiteral("nonzero")) 2550 rule = FillRule::FILL_WINDING; 2551 else 2552 return; 2553 2554 CurrentState().fillRule = rule; 2555 } 2556 2557 void CanvasRenderingContext2D::GetFillRule(nsAString& aString) { 2558 switch (CurrentState().fillRule) { 2559 case FillRule::FILL_WINDING: 2560 aString.AssignLiteral("nonzero"); 2561 break; 2562 case FillRule::FILL_EVEN_ODD: 2563 aString.AssignLiteral("evenodd"); 2564 break; 2565 } 2566 } 2567 // 2568 // gradients and patterns 2569 // 2570 already_AddRefed<CanvasGradient> CanvasRenderingContext2D::CreateLinearGradient( 2571 double aX0, double aY0, double aX1, double aY1) { 2572 RefPtr<CanvasGradient> grad = 2573 new CanvasLinearGradient(this, Point(aX0, aY0), Point(aX1, aY1)); 2574 2575 return grad.forget(); 2576 } 2577 2578 already_AddRefed<CanvasGradient> CanvasRenderingContext2D::CreateRadialGradient( 2579 double aX0, double aY0, double aR0, double aX1, double aY1, double aR1, 2580 ErrorResult& aError) { 2581 if (aR0 < 0.0 || aR1 < 0.0) { 2582 aError.ThrowIndexSizeError("Negative radius"); 2583 return nullptr; 2584 } 2585 2586 RefPtr<CanvasGradient> grad = new CanvasRadialGradient( 2587 this, Point(aX0, aY0), aR0, Point(aX1, aY1), aR1); 2588 2589 return grad.forget(); 2590 } 2591 2592 already_AddRefed<CanvasGradient> CanvasRenderingContext2D::CreateConicGradient( 2593 double aAngle, double aCx, double aCy) { 2594 double adjustedStartAngle = aAngle + M_PI / 2.0; 2595 return MakeAndAddRef<CanvasConicGradient>(this, adjustedStartAngle, 2596 Point(aCx, aCy)); 2597 } 2598 2599 already_AddRefed<CanvasPattern> CanvasRenderingContext2D::CreatePattern( 2600 const CanvasImageSource& aSource, const nsAString& aRepeat, 2601 ErrorResult& aError) { 2602 CanvasPattern::RepeatMode repeatMode = CanvasPattern::RepeatMode::NOREPEAT; 2603 2604 if (aRepeat.IsEmpty() || aRepeat.EqualsLiteral("repeat")) { 2605 repeatMode = CanvasPattern::RepeatMode::REPEAT; 2606 } else if (aRepeat.EqualsLiteral("repeat-x")) { 2607 repeatMode = CanvasPattern::RepeatMode::REPEATX; 2608 } else if (aRepeat.EqualsLiteral("repeat-y")) { 2609 repeatMode = CanvasPattern::RepeatMode::REPEATY; 2610 } else if (aRepeat.EqualsLiteral("no-repeat")) { 2611 repeatMode = CanvasPattern::RepeatMode::NOREPEAT; 2612 } else { 2613 aError.ThrowSyntaxError("Invalid pattern keyword"); 2614 return nullptr; 2615 } 2616 2617 Element* element = nullptr; 2618 OffscreenCanvas* offscreenCanvas = nullptr; 2619 VideoFrame* videoFrame = nullptr; 2620 2621 if (aSource.IsHTMLCanvasElement()) { 2622 HTMLCanvasElement* canvas = &aSource.GetAsHTMLCanvasElement(); 2623 element = canvas; 2624 2625 CSSIntSize size = canvas->GetSize(); 2626 if (size.width == 0) { 2627 aError.ThrowInvalidStateError("Passed-in canvas has width 0"); 2628 return nullptr; 2629 } 2630 2631 if (size.height == 0) { 2632 aError.ThrowInvalidStateError("Passed-in canvas has height 0"); 2633 return nullptr; 2634 } 2635 2636 // Special case for Canvas, which could be an Azure canvas! 2637 nsICanvasRenderingContextInternal* srcCanvas = canvas->GetCurrentContext(); 2638 if (srcCanvas) { 2639 // This might not be an Azure canvas! 2640 RefPtr<SourceSurface> srcSurf = srcCanvas->GetSurfaceSnapshot(); 2641 if (!srcSurf) { 2642 aError.ThrowInvalidStateError( 2643 "CanvasRenderingContext2D.createPattern() failed to snapshot source" 2644 "canvas."); 2645 return nullptr; 2646 } 2647 2648 RefPtr<CanvasPattern> pat = 2649 new CanvasPattern(this, srcSurf, repeatMode, element->NodePrincipal(), 2650 canvas->IsWriteOnly(), false); 2651 2652 return pat.forget(); 2653 } 2654 } else if (aSource.IsHTMLImageElement()) { 2655 HTMLImageElement* img = &aSource.GetAsHTMLImageElement(); 2656 element = img; 2657 } else if (aSource.IsSVGImageElement()) { 2658 SVGImageElement* img = &aSource.GetAsSVGImageElement(); 2659 element = img; 2660 } else if (aSource.IsHTMLVideoElement()) { 2661 auto& video = aSource.GetAsHTMLVideoElement(); 2662 video.LogVisibility( 2663 mozilla::dom::HTMLVideoElement::CallerAPI::CREATE_PATTERN); 2664 element = &video; 2665 } else if (aSource.IsOffscreenCanvas()) { 2666 offscreenCanvas = &aSource.GetAsOffscreenCanvas(); 2667 2668 CSSIntSize size = offscreenCanvas->GetWidthHeight(); 2669 if (size.width == 0) { 2670 aError.ThrowInvalidStateError("Passed-in canvas has width 0"); 2671 return nullptr; 2672 } 2673 2674 if (size.height == 0) { 2675 aError.ThrowInvalidStateError("Passed-in canvas has height 0"); 2676 return nullptr; 2677 } 2678 2679 nsICanvasRenderingContextInternal* srcCanvas = 2680 offscreenCanvas->GetContext(); 2681 if (srcCanvas) { 2682 RefPtr<SourceSurface> srcSurf = srcCanvas->GetSurfaceSnapshot(); 2683 if (!srcSurf) { 2684 aError.ThrowInvalidStateError( 2685 "Passed-in canvas failed to create snapshot"); 2686 return nullptr; 2687 } 2688 2689 RefPtr<CanvasPattern> pat = new CanvasPattern( 2690 this, srcSurf, repeatMode, srcCanvas->PrincipalOrNull(), 2691 offscreenCanvas->IsWriteOnly(), false); 2692 2693 return pat.forget(); 2694 } 2695 } else if (aSource.IsVideoFrame()) { 2696 videoFrame = &aSource.GetAsVideoFrame(); 2697 2698 if (videoFrame->CodedWidth() == 0) { 2699 aError.ThrowInvalidStateError("Passed-in canvas has width 0"); 2700 return nullptr; 2701 } 2702 2703 if (videoFrame->CodedHeight() == 0) { 2704 aError.ThrowInvalidStateError("Passed-in canvas has height 0"); 2705 return nullptr; 2706 } 2707 } else { 2708 // Special case for ImageBitmap 2709 ImageBitmap& imgBitmap = aSource.GetAsImageBitmap(); 2710 if (!EnsureTarget(aError)) { 2711 return nullptr; 2712 } 2713 2714 MOZ_ASSERT(IsTargetValid()); 2715 2716 RefPtr<SourceSurface> srcSurf = imgBitmap.PrepareForDrawTarget(mTarget); 2717 if (!srcSurf) { 2718 aError.ThrowInvalidStateError( 2719 "Passed-in ImageBitmap has been transferred"); 2720 return nullptr; 2721 } 2722 2723 // An ImageBitmap never taints others so we set principalForSecurityCheck to 2724 // nullptr and set CORSUsed to true for passing the security check in 2725 // CanvasUtils::DoDrawImageSecurityCheck(). 2726 RefPtr<CanvasPattern> pat = new CanvasPattern( 2727 this, srcSurf, repeatMode, nullptr, imgBitmap.IsWriteOnly(), true); 2728 2729 return pat.forget(); 2730 } 2731 2732 if (!EnsureTarget(aError)) { 2733 return nullptr; 2734 } 2735 2736 MOZ_ASSERT(IsTargetValid()); 2737 2738 // The canvas spec says that createPattern should use the first frame 2739 // of animated images 2740 auto flags = nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE | 2741 nsLayoutUtils::SFE_EXACT_SIZE_SURFACE; 2742 SurfaceFromElementResult res; 2743 if (offscreenCanvas) { 2744 res = nsLayoutUtils::SurfaceFromOffscreenCanvas(offscreenCanvas, flags, 2745 mTarget); 2746 } else if (videoFrame) { 2747 res = nsLayoutUtils::SurfaceFromVideoFrame(videoFrame, flags, mTarget); 2748 } else { 2749 res = nsLayoutUtils::SurfaceFromElement(element, flags, mTarget); 2750 } 2751 2752 // Per spec, we should throw here for the HTMLImageElement and SVGImageElement 2753 // cases if the image request state is "broken". In terms of the infromation 2754 // in "res", the "broken" state corresponds to not having a size and not being 2755 // still-loading (so there is no size forthcoming). 2756 if (aSource.IsHTMLImageElement() || aSource.IsSVGImageElement()) { 2757 if (!res.mIsStillLoading && !res.mHasSize) { 2758 aError.ThrowInvalidStateError( 2759 "Passed-in image's current request's state is \"broken\""); 2760 return nullptr; 2761 } 2762 2763 if (res.mSize.width == 0 || res.mSize.height == 0) { 2764 return nullptr; 2765 } 2766 2767 // Is the "fully decodable" check already done in SurfaceFromElement? It's 2768 // not clear how to do it from here, exactly. 2769 } 2770 2771 RefPtr<SourceSurface> surface = res.GetSourceSurface(); 2772 if (!surface) { 2773 return nullptr; 2774 } 2775 2776 RefPtr<CanvasPattern> pat = 2777 new CanvasPattern(this, surface, repeatMode, res.mPrincipal, 2778 res.mIsWriteOnly, res.mCORSUsed); 2779 return pat.forget(); 2780 } 2781 2782 // 2783 // shadows 2784 // 2785 void CanvasRenderingContext2D::SetShadowColor(const nsACString& aShadowColor) { 2786 Maybe<nscolor> color = ParseColor(aShadowColor); 2787 if (!color) { 2788 return; 2789 } 2790 2791 CurrentState().shadowColor = *color; 2792 } 2793 2794 // 2795 // filters 2796 // 2797 2798 static already_AddRefed<StyleLockedDeclarationBlock> CreateDeclarationForServo( 2799 NonCustomCSSPropertyId aProperty, const nsACString& aPropertyValue, 2800 Document* aDocument) { 2801 ServoCSSParser::ParsingEnvironment env{aDocument->DefaultStyleAttrURLData(), 2802 aDocument->GetCompatibilityMode(), 2803 // Loader only for error reporting 2804 aDocument->GetExistingCSSLoader()}; 2805 RefPtr<StyleLockedDeclarationBlock> servoDeclarations = 2806 ServoCSSParser::ParseProperty(aProperty, aPropertyValue, env, 2807 StyleParsingMode::DEFAULT); 2808 2809 if (!servoDeclarations) { 2810 // We got a syntax error. The spec says this value must be ignored. 2811 return nullptr; 2812 } 2813 2814 if (aProperty == eCSSProperty_font) { 2815 Servo_DeclarationBlock_SanitizeForCanvas(servoDeclarations); 2816 } 2817 2818 return servoDeclarations.forget(); 2819 } 2820 2821 static already_AddRefed<StyleLockedDeclarationBlock> 2822 CreateFontDeclarationForServo(const nsACString& aFont, Document* aDocument) { 2823 return CreateDeclarationForServo(eCSSProperty_font, aFont, aDocument); 2824 } 2825 2826 static already_AddRefed<const ComputedStyle> GetFontStyleForServo( 2827 Element* aElement, const nsACString& aFont, PresShell* aPresShell, 2828 nsACString& aOutUsedFont, ErrorResult& aError) { 2829 RefPtr<StyleLockedDeclarationBlock> declarations = 2830 CreateFontDeclarationForServo(aFont, aPresShell->GetDocument()); 2831 if (!declarations) { 2832 // We got a syntax error. The spec says this value must be ignored. 2833 return nullptr; 2834 } 2835 2836 // In addition to unparseable values, the spec says we need to reject 2837 // 'inherit' and 'initial'. The easiest way to check for this is to look 2838 // at font-size-adjust, which the font shorthand resets to 'none'. 2839 if (Servo_DeclarationBlock_HasCSSWideKeyword(declarations, 2840 eCSSProperty_font_size_adjust)) { 2841 return nullptr; 2842 } 2843 2844 ServoStyleSet* styleSet = aPresShell->StyleSet(); 2845 2846 // Have to get a parent ComputedStyle for inherit-like relative values (2em, 2847 // bolder, etc.) 2848 RefPtr<const ComputedStyle> parentStyle; 2849 if (aElement) { 2850 parentStyle = nsComputedDOMStyle::GetComputedStyle(aElement); 2851 if (NS_WARN_IF(aPresShell->IsDestroying())) { 2852 // The flush might've killed the shell. 2853 aError.Throw(NS_ERROR_FAILURE); 2854 return nullptr; 2855 } 2856 } 2857 if (!parentStyle) { 2858 RefPtr<StyleLockedDeclarationBlock> declarations = 2859 CreateFontDeclarationForServo("10px sans-serif"_ns, 2860 aPresShell->GetDocument()); 2861 MOZ_ASSERT(declarations); 2862 2863 parentStyle = 2864 aPresShell->StyleSet()->ResolveForDeclarations(nullptr, declarations); 2865 } 2866 2867 MOZ_RELEASE_ASSERT(parentStyle, "Should have a valid parent style"); 2868 2869 MOZ_ASSERT(!aPresShell->IsDestroying(), 2870 "We should have returned an error above if the presshell is " 2871 "being destroyed."); 2872 2873 RefPtr<const ComputedStyle> sc = 2874 styleSet->ResolveForDeclarations(parentStyle, declarations); 2875 2876 // https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-font 2877 // The font-size component must be converted to CSS px for reserialization, 2878 // so we update the declarations with the value from the computed style. 2879 if (!sc->StyleFont()->mFont.family.is_system_font) { 2880 float px = sc->StyleFont()->mFont.size.ToCSSPixels(); 2881 Servo_DeclarationBlock_SetLengthValue(declarations, eCSSProperty_font_size, 2882 px, eCSSUnit_Pixel); 2883 } 2884 2885 // The font getter is required to be reserialized based on what we 2886 // parsed (including having line-height removed). 2887 // If we failed to reserialize, ignore this attempt to set the value. 2888 Servo_SerializeFontValueForCanvas(declarations, &aOutUsedFont); 2889 if (aOutUsedFont.IsEmpty()) { 2890 return nullptr; 2891 } 2892 2893 return sc.forget(); 2894 } 2895 2896 static already_AddRefed<StyleLockedDeclarationBlock> 2897 CreateFilterDeclarationForServo(const nsACString& aFilter, 2898 Document* aDocument) { 2899 return CreateDeclarationForServo(eCSSProperty_filter, aFilter, aDocument); 2900 } 2901 2902 static already_AddRefed<const ComputedStyle> ResolveFilterStyleForServo( 2903 const nsACString& aFilterString, const ComputedStyle* aParentStyle, 2904 PresShell* aPresShell, ErrorResult& aError) { 2905 RefPtr<StyleLockedDeclarationBlock> declarations = 2906 CreateFilterDeclarationForServo(aFilterString, aPresShell->GetDocument()); 2907 if (!declarations) { 2908 // Refuse to accept the filter, but do not throw an error. 2909 return nullptr; 2910 } 2911 2912 // In addition to unparseable values, the spec says we need to reject 2913 // 'inherit' and 'initial'. 2914 if (Servo_DeclarationBlock_HasCSSWideKeyword(declarations, 2915 eCSSProperty_filter)) { 2916 return nullptr; 2917 } 2918 2919 ServoStyleSet* styleSet = aPresShell->StyleSet(); 2920 RefPtr<const ComputedStyle> computedValues = 2921 styleSet->ResolveForDeclarations(aParentStyle, declarations); 2922 2923 return computedValues.forget(); 2924 } 2925 2926 bool CanvasRenderingContext2D::ParseFilter( 2927 const nsACString& aString, StyleOwnedSlice<StyleFilter>& aFilterChain, 2928 ErrorResult& aError) { 2929 RefPtr<PresShell> presShell = GetPresShell(); 2930 if (!presShell) { 2931 nsIGlobalObject* global = GetParentObject(); 2932 FontFaceSet* fontFaceSet = global ? global->GetFonts() : nullptr; 2933 FontFaceSetImpl* fontFaceSetImpl = 2934 fontFaceSet ? fontFaceSet->GetImpl() : nullptr; 2935 RefPtr<URLExtraData> urlExtraData = 2936 fontFaceSetImpl ? fontFaceSetImpl->GetURLExtraData() : nullptr; 2937 2938 if (NS_WARN_IF(!urlExtraData)) { 2939 // Provided we have a FontFaceSetImpl object, this should only happen on 2940 // worker threads, where we failed to initialize the worker before it was 2941 // shutdown. 2942 aError.ThrowInvalidStateError("Missing URLExtraData"); 2943 return false; 2944 } 2945 2946 if (NS_WARN_IF(!Servo_ParseFilters(&aString, /* aIgnoreUrls */ true, 2947 urlExtraData, &aFilterChain))) { 2948 return false; 2949 } 2950 2951 return true; 2952 } 2953 2954 const ComputedStyle* parentStyle = GetCurrentFontComputedStyle(); 2955 if (!parentStyle) { 2956 return false; 2957 } 2958 2959 RefPtr<const ComputedStyle> style = 2960 ResolveFilterStyleForServo(aString, parentStyle, presShell, aError); 2961 if (!style) { 2962 return false; 2963 } 2964 2965 aFilterChain = style->StyleEffects()->mFilters; 2966 return true; 2967 } 2968 2969 void CanvasRenderingContext2D::SetFilter(const nsACString& aFilter, 2970 ErrorResult& aError) { 2971 StyleOwnedSlice<StyleFilter> filterChain; 2972 if (ParseFilter(aFilter, filterChain, aError)) { 2973 CurrentState().filterString = aFilter; 2974 CurrentState().filterChain = std::move(filterChain); 2975 if (mCanvasElement) { 2976 CurrentState().autoSVGFiltersObserver = 2977 SVGObserverUtils::ObserveFiltersForCanvasContext( 2978 this, mCanvasElement, CurrentState().filterChain.AsSpan()); 2979 } 2980 UpdateFilter(/* aFlushIfNeeded = */ true); 2981 } 2982 } 2983 2984 static already_AddRefed<const ComputedStyle> ResolveStyleForServo( 2985 NonCustomCSSPropertyId aProperty, const nsACString& aString, 2986 const ComputedStyle* aParentStyle, PresShell* aPresShell, 2987 ErrorResult& aError) { 2988 RefPtr<StyleLockedDeclarationBlock> declarations = 2989 CreateDeclarationForServo(aProperty, aString, aPresShell->GetDocument()); 2990 if (!declarations) { 2991 return nullptr; 2992 } 2993 2994 // In addition to unparseable values, reject 'inherit' and 'initial'. 2995 if (Servo_DeclarationBlock_HasCSSWideKeyword(declarations, aProperty)) { 2996 return nullptr; 2997 } 2998 2999 ServoStyleSet* styleSet = aPresShell->StyleSet(); 3000 return styleSet->ResolveForDeclarations(aParentStyle, declarations); 3001 } 3002 3003 already_AddRefed<const ComputedStyle> 3004 CanvasRenderingContext2D::ResolveStyleForProperty( 3005 NonCustomCSSPropertyId aProperty, const nsACString& aValue) { 3006 RefPtr<PresShell> presShell = GetPresShell(); 3007 if (NS_WARN_IF(!presShell)) { 3008 return nullptr; 3009 } 3010 3011 const ComputedStyle* parentStyle = GetCurrentFontComputedStyle(); 3012 if (!parentStyle) { 3013 return nullptr; 3014 } 3015 3016 return ResolveStyleForServo(aProperty, aValue, parentStyle, presShell, 3017 IgnoreErrors()); 3018 } 3019 3020 void CanvasRenderingContext2D::GetLetterSpacing(nsACString& aLetterSpacing) { 3021 if (CurrentState().letterSpacingStr.IsEmpty()) { 3022 aLetterSpacing.AssignLiteral("0px"); 3023 } else { 3024 aLetterSpacing = CurrentState().letterSpacingStr; 3025 } 3026 } 3027 3028 void CanvasRenderingContext2D::SetLetterSpacing( 3029 const nsACString& aLetterSpacing) { 3030 ParseSpacing(aLetterSpacing, &CurrentState().letterSpacing, 3031 CurrentState().letterSpacingStr); 3032 } 3033 3034 void CanvasRenderingContext2D::GetWordSpacing(nsACString& aWordSpacing) { 3035 if (CurrentState().wordSpacingStr.IsEmpty()) { 3036 aWordSpacing.AssignLiteral("0px"); 3037 } else { 3038 aWordSpacing = CurrentState().wordSpacingStr; 3039 } 3040 } 3041 3042 void CanvasRenderingContext2D::SetWordSpacing(const nsACString& aWordSpacing) { 3043 ParseSpacing(aWordSpacing, &CurrentState().wordSpacing, 3044 CurrentState().wordSpacingStr); 3045 } 3046 3047 static GeckoFontMetrics GetFontMetricsFromCanvas(void* aContext) { 3048 auto* ctx = static_cast<CanvasRenderingContext2D*>(aContext); 3049 auto* fontGroup = ctx->GetCurrentFontStyle(); 3050 if (!fontGroup) { 3051 // Shouldn't happen, but just in case... return plausible values for a 3052 // 10px font (canvas default size). 3053 return {Length::FromPixels(5.0), 3054 Length::FromPixels(5.0), 3055 Length::FromPixels(8.0), 3056 Length::FromPixels(10.0), 3057 Length::FromPixels(8.0), 3058 Length::FromPixels(10.0), 3059 0.0f, 3060 0.0f}; 3061 } 3062 auto metrics = fontGroup->GetMetricsForCSSUnits( 3063 nsFontMetrics::eHorizontal, StyleQueryFontMetricsFlags::NEEDS_CH | 3064 StyleQueryFontMetricsFlags::NEEDS_IC); 3065 return {Length::FromPixels(metrics.xHeight), 3066 Length::FromPixels(metrics.zeroWidth), 3067 Length::FromPixels(metrics.capHeight), 3068 Length::FromPixels(metrics.ideographicWidth), 3069 Length::FromPixels(metrics.maxAscent), 3070 Length::FromPixels(fontGroup->GetStyle()->size), 3071 0.0f, 3072 0.0f}; 3073 } 3074 3075 void CanvasRenderingContext2D::ParseSpacing(const nsACString& aSpacing, 3076 float* aValue, 3077 nsACString& aNormalized) { 3078 // Normalize whitespace in the string before trying to parse it, as we want 3079 // to store it in normalized form, and this allows a simple check against the 3080 // 'normal' keyword, which is not accepted. 3081 nsAutoCString normalized(aSpacing); 3082 normalized.CompressWhitespace(true, true); 3083 ToLowerCase(normalized); 3084 if (normalized.EqualsLiteral("normal")) { 3085 return; 3086 } 3087 float value; 3088 if (!Servo_ParseLengthWithoutStyleContext(&normalized, &value, 3089 GetFontMetricsFromCanvas, this)) { 3090 if (!GetPresShell()) { 3091 return; 3092 } 3093 // This will parse aSpacing as a <length-percentage>... 3094 RefPtr<const ComputedStyle> style = 3095 ResolveStyleForProperty(eCSSProperty_letter_spacing, aSpacing); 3096 if (!style) { 3097 return; 3098 } 3099 // ...but only <length> is allowed according to the canvas spec. 3100 if (!style->StyleText()->mLetterSpacing.IsLength()) { 3101 return; 3102 } 3103 value = style->StyleText()->mLetterSpacing.AsLength().ToCSSPixels(); 3104 } 3105 aNormalized = normalized; 3106 *aValue = value; 3107 } 3108 3109 class CanvasUserSpaceMetrics final : public UserSpaceMetricsWithSize { 3110 public: 3111 CanvasUserSpaceMetrics(const gfx::IntSize& aSize, const nsFont& aFont, 3112 const StyleLineHeight& aLineHeight, 3113 RefPtr<nsAtom> aFontLanguage, 3114 bool aFontExplicitLanguage, 3115 const ComputedStyle* aCanvasStyle, 3116 nsPresContext* aPresContext) 3117 : mSize(aSize), 3118 mFont(aFont), 3119 mLineHeight(aLineHeight), 3120 mFontLanguage(std::move(aFontLanguage)), 3121 mFontExplicitLanguage(aFontExplicitLanguage), 3122 mCanvasStyle(aCanvasStyle), 3123 mPresContext(aPresContext) {} 3124 3125 float GetZoom() const override { 3126 return mCanvasStyle ? mCanvasStyle->EffectiveZoom().ToFloat() : 1.0f; 3127 } 3128 3129 float GetRootZoom() const override { 3130 return UserSpaceMetrics::GetZoom( 3131 mPresContext->Document()->GetRootElement()); 3132 } 3133 3134 float GetEmLength(Type aType) const override { 3135 switch (aType) { 3136 case Type::This: 3137 return mFont.size.ToCSSPixels(); 3138 case Type::Root: 3139 return SVGContentUtils::GetFontSize( 3140 mPresContext->Document()->GetRootElement()); 3141 default: 3142 MOZ_ASSERT_UNREACHABLE("Was a new value added to the enumeration?"); 3143 return 1.0f; 3144 } 3145 } 3146 gfx::Size GetSize() const override { return Size(mSize); } 3147 3148 CSSSize GetCSSViewportSize() const override { 3149 return GetCSSViewportSizeFromContext(mPresContext); 3150 } 3151 3152 float GetLineHeight(Type aType) const override { 3153 // This is used if a filter is added through `url()`, and if the SVG 3154 // filter being referred to is using line-height units. 3155 switch (aType) { 3156 case Type::This: { 3157 const auto wm = GetWritingModeForType(aType); 3158 const auto lh = ReflowInput::CalcLineHeightForCanvas( 3159 mLineHeight, mFont, mFontLanguage, mFontExplicitLanguage, 3160 mPresContext, wm); 3161 return nsPresContext::AppUnitsToFloatCSSPixels(lh); 3162 } 3163 case Type::Root: { 3164 return SVGContentUtils::GetLineHeight( 3165 mPresContext->Document()->GetRootElement()); 3166 } 3167 } 3168 MOZ_ASSERT_UNREACHABLE("Was a new value added to the enumeration?"); 3169 return 1.0f; 3170 } 3171 3172 private: 3173 GeckoFontMetrics GetFontMetricsForType(Type aType) const override { 3174 switch (aType) { 3175 case Type::This: { 3176 if (!mCanvasStyle) { 3177 return DefaultFontMetrics(); 3178 } 3179 return Gecko_GetFontMetrics( 3180 mPresContext, WritingMode(mCanvasStyle).IsVertical(), 3181 mCanvasStyle->StyleFont(), mCanvasStyle->StyleFont()->mFont.size, 3182 StyleQueryFontMetricsFlags::USE_USER_FONT_SET | 3183 StyleQueryFontMetricsFlags::NEEDS_CH | 3184 StyleQueryFontMetricsFlags::NEEDS_IC); 3185 } 3186 case Type::Root: 3187 return GetFontMetrics(mPresContext->Document()->GetRootElement()); 3188 default: 3189 MOZ_ASSERT_UNREACHABLE("Was a new value added to the enumeration?"); 3190 return DefaultFontMetrics(); 3191 } 3192 } 3193 3194 WritingMode GetWritingModeForType(Type aType) const override { 3195 switch (aType) { 3196 case Type::This: 3197 return mCanvasStyle ? WritingMode(mCanvasStyle) : WritingMode(); 3198 case Type::Root: 3199 return GetWritingMode(mPresContext->Document()->GetRootElement()); 3200 default: 3201 MOZ_ASSERT_UNREACHABLE("Was a new value added to the enumeration?"); 3202 return WritingMode(); 3203 } 3204 } 3205 3206 gfx::IntSize mSize; 3207 const nsFont& mFont; 3208 StyleLineHeight mLineHeight; 3209 RefPtr<nsAtom> mFontLanguage; 3210 bool mFontExplicitLanguage; 3211 RefPtr<const ComputedStyle> mCanvasStyle; 3212 nsPresContext* mPresContext; 3213 }; 3214 3215 // The filter might reference an SVG filter that is declared inside this 3216 // document. Flush frames so that we'll have a SVGFilterFrame to work 3217 // with. 3218 static bool FiltersNeedFrameFlush(Span<const StyleFilter> aFilters) { 3219 for (const auto& filter : aFilters) { 3220 if (filter.IsUrl()) { 3221 return true; 3222 } 3223 } 3224 return false; 3225 } 3226 3227 void CanvasRenderingContext2D::UpdateFilter(bool aFlushIfNeeded) { 3228 const bool writeOnly = IsWriteOnly() || 3229 (mCanvasElement && mCanvasElement->IsWriteOnly()) || 3230 (mOffscreenCanvas && mOffscreenCanvas->IsWriteOnly()); 3231 3232 RefPtr<PresShell> presShell = GetPresShell(); 3233 if (!mOffscreenCanvas && (!presShell || presShell->IsDestroying())) { 3234 // Ensure we set an empty filter and update the state to 3235 // reflect the current "taint" status of the canvas 3236 CurrentState().filter = FilterDescription(); 3237 CurrentState().filterSourceGraphicTainted = writeOnly; 3238 return; 3239 } 3240 3241 // The PresContext is only used with URL filters and we don't allow those to 3242 // be used on worker threads. 3243 nsPresContext* presContext = nullptr; 3244 if (presShell) { 3245 if (aFlushIfNeeded && 3246 FiltersNeedFrameFlush(CurrentState().filterChain.AsSpan())) { 3247 presShell->FlushPendingNotifications(FlushType::Frames); 3248 } 3249 3250 if (MOZ_UNLIKELY(presShell->IsDestroying())) { 3251 return; 3252 } 3253 3254 presContext = presShell->GetPresContext(); 3255 } 3256 // FIXME(emilio): This seems like it should use GetCurrentFontComputedStyle to 3257 // make sure the font is up-to-date, but the old code didn't... Find a 3258 // test-case where it matters? 3259 const ComputedStyle* currentFontStyle = CurrentState().fontComputedStyle; 3260 const RefPtr<const ComputedStyle> canvasStyle = 3261 mCanvasElement 3262 ? nsComputedDOMStyle::GetComputedStyleNoFlush(mCanvasElement) 3263 : nullptr; 3264 3265 MOZ_RELEASE_ASSERT(!mStyleStack.IsEmpty()); 3266 auto& state = CurrentState(); 3267 auto lineHeight = currentFontStyle 3268 ? currentFontStyle->StyleFont()->mLineHeight 3269 : StyleLineHeight::Normal(); 3270 auto* language = currentFontStyle 3271 ? currentFontStyle->StyleFont()->mLanguage.get() 3272 : nullptr; 3273 bool explicitLanguage = 3274 state.fontComputedStyle && 3275 state.fontComputedStyle->StyleFont()->mExplicitLanguage; 3276 state.filter = FilterInstance::GetFilterDescription( 3277 mCanvasElement, state.filterChain.AsSpan(), state.autoSVGFiltersObserver, 3278 writeOnly, 3279 CanvasUserSpaceMetrics(GetSize(), state.fontFont, lineHeight, language, 3280 explicitLanguage, canvasStyle, presContext), 3281 gfxRect(0, 0, mWidth, mHeight), state.filterAdditionalImages); 3282 state.filterSourceGraphicTainted = writeOnly; 3283 } 3284 3285 // 3286 // rects 3287 // 3288 3289 static bool ValidateRect(double& aX, double& aY, double& aWidth, 3290 double& aHeight, bool aIsZeroSizeValid) { 3291 if (!aIsZeroSizeValid && (aWidth == 0.0 || aHeight == 0.0)) { 3292 return false; 3293 } 3294 3295 // bug 1018527 3296 // The values of canvas API input are in double precision, but Moz2D APIs are 3297 // using float precision. Bypass canvas API calls when the input is out of 3298 // float precision to avoid precision problem 3299 if (!std::isfinite((float)aX) || !std::isfinite((float)aY) || 3300 !std::isfinite((float)aWidth) || !std::isfinite((float)aHeight)) { 3301 return false; 3302 } 3303 3304 // bug 1074733 3305 // The canvas spec does not forbid rects with negative w or h, so given 3306 // corners (x, y), (x+w, y), (x+w, y+h), and (x, y+h) we must generate 3307 // the appropriate rect by flipping negative dimensions. This prevents 3308 // draw targets from receiving "empty" rects later on. 3309 if (aWidth < 0) { 3310 aWidth = -aWidth; 3311 aX -= aWidth; 3312 } 3313 if (aHeight < 0) { 3314 aHeight = -aHeight; 3315 aY -= aHeight; 3316 } 3317 return true; 3318 } 3319 3320 void CanvasRenderingContext2D::ClearRect(double aX, double aY, double aW, 3321 double aH) { 3322 // Do not allow zeros - it's a no-op at that point per spec. 3323 if (!ValidateRect(aX, aY, aW, aH, false)) { 3324 return; 3325 } 3326 3327 gfx::Rect clearRect(aX, aY, aW, aH); 3328 3329 EnsureTarget(&clearRect, true); 3330 if (!IsTargetValid()) { 3331 return; 3332 } 3333 3334 mTarget->ClearRect(clearRect); 3335 3336 RedrawUser(gfxRect(aX, aY, aW, aH)); 3337 } 3338 3339 void CanvasRenderingContext2D::FillRect(double aX, double aY, double aW, 3340 double aH) { 3341 mFeatureUsage |= CanvasFeatureUsage::FillRect; 3342 3343 if (!ValidateRect(aX, aY, aW, aH, true)) { 3344 return; 3345 } 3346 3347 const ContextState* state = &CurrentState(); 3348 if (state->patternStyles[Style::FILL]) { 3349 auto& style = state->patternStyles[Style::FILL]; 3350 CanvasPattern::RepeatMode repeat = style->mRepeat; 3351 // In the FillRect case repeat modes are easy to deal with. 3352 bool limitx = repeat == CanvasPattern::RepeatMode::NOREPEAT || 3353 repeat == CanvasPattern::RepeatMode::REPEATY; 3354 bool limity = repeat == CanvasPattern::RepeatMode::NOREPEAT || 3355 repeat == CanvasPattern::RepeatMode::REPEATX; 3356 if ((limitx || limity) && style->mTransform.IsRectilinear()) { 3357 // For rectilinear transforms, we can just get the transformed pattern 3358 // bounds and intersect them with the fill rectangle bounds. 3359 // TODO: If the transform is not rectilinear, then we would need a fully 3360 // general clip path to represent the X and Y clip planes bounding the 3361 // pattern. For such cases, it would be more efficient to rely on Skia's 3362 // Decal tiling mode rather than trying to generate a path. Until then, 3363 // just punt to relying on the default Clamp mode. 3364 gfx::Rect patternBounds(style->mSurface->GetRect()); 3365 patternBounds = style->mTransform.TransformBounds(patternBounds); 3366 if (style->mTransform.HasNonAxisAlignedTransform()) { 3367 // If there is an rotation (90 or 270 degrees), the X axis of the 3368 // pattern projects onto the Y axis of the geometry, and vice versa. 3369 std::swap(limitx, limity); 3370 } 3371 // We always need to execute painting for non-over operators, even if 3372 // we end up with w/h = 0. The default Rect::Intersect can cause both 3373 // dimensions to become empty if either dimension individually fails 3374 // to overlap, which is unsuitable. Instead, we need to independently 3375 // limit the supplied rectangle on each dimension as required. 3376 if (limitx) { 3377 double x2 = aX + aW; 3378 aX = std::max(aX, double(patternBounds.x)); 3379 aW = std::max(std::min(x2, double(patternBounds.XMost())) - aX, 0.0); 3380 } 3381 if (limity) { 3382 double y2 = aY + aH; 3383 aY = std::max(aY, double(patternBounds.y)); 3384 aH = std::max(std::min(y2, double(patternBounds.YMost())) - aY, 0.0); 3385 } 3386 } 3387 } 3388 state = nullptr; 3389 3390 bool isColor; 3391 bool discardContent = PatternIsOpaque(Style::FILL, &isColor) && 3392 (CurrentState().op == CompositionOp::OP_OVER || 3393 CurrentState().op == CompositionOp::OP_SOURCE); 3394 const gfx::Rect fillRect(aX, aY, aW, aH); 3395 EnsureTarget(discardContent ? &fillRect : nullptr, discardContent && isColor); 3396 if (!IsTargetValid()) { 3397 return; 3398 } 3399 3400 gfx::Rect bounds; 3401 const bool needBounds = NeedToCalculateBounds(); 3402 if (!IsTargetValid()) { 3403 return; 3404 } 3405 if (needBounds) { 3406 bounds = mTarget->GetTransform().TransformBounds(fillRect); 3407 } 3408 3409 AntialiasMode antialiasMode = CurrentState().imageSmoothingEnabled 3410 ? AntialiasMode::DEFAULT 3411 : AntialiasMode::NONE; 3412 3413 AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds, true); 3414 CompositionOp op = target.UsedOperation(); 3415 if (!target) { 3416 return; 3417 } 3418 target.FillRect(gfx::Rect(aX, aY, aW, aH), 3419 CanvasGeneralPattern().ForStyle(this, Style::FILL, mTarget), 3420 DrawOptions(CurrentState().globalAlpha, op, antialiasMode)); 3421 3422 RedrawUser(gfxRect(aX, aY, aW, aH)); 3423 } 3424 3425 void CanvasRenderingContext2D::StrokeRect(double aX, double aY, double aW, 3426 double aH) { 3427 if (!aW && !aH) { 3428 return; 3429 } 3430 3431 if (!ValidateRect(aX, aY, aW, aH, true)) { 3432 return; 3433 } 3434 3435 EnsureTarget(); 3436 if (!IsTargetValid()) { 3437 return; 3438 } 3439 3440 const bool needBounds = NeedToCalculateBounds(); 3441 if (!IsTargetValid()) { 3442 return; 3443 } 3444 3445 gfx::Rect bounds; 3446 if (needBounds) { 3447 const ContextState& state = CurrentState(); 3448 bounds = gfx::Rect(aX - state.lineWidth / 2.0f, aY - state.lineWidth / 2.0f, 3449 aW + state.lineWidth, aH + state.lineWidth); 3450 bounds = mTarget->GetTransform().TransformBounds(bounds); 3451 } 3452 3453 if (!IsTargetValid()) { 3454 return; 3455 } 3456 3457 if (!aH) { 3458 CapStyle cap = CapStyle::BUTT; 3459 if (CurrentState().lineJoin == CanvasLineJoin::Round) { 3460 cap = CapStyle::ROUND; 3461 } 3462 AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds, true); 3463 auto op = target.UsedOperation(); 3464 if (!target) { 3465 return; 3466 } 3467 3468 const ContextState& state = CurrentState(); 3469 target.StrokeLine( 3470 Point(aX, aY), Point(aX + aW, aY), 3471 CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget), 3472 StrokeOptions(state.lineWidth, CanvasToGfx(state.lineJoin), cap, 3473 state.miterLimit, state.dash.Length(), 3474 state.dash.Elements(), state.dashOffset), 3475 DrawOptions(state.globalAlpha, op)); 3476 return; 3477 } 3478 3479 if (!aW) { 3480 CapStyle cap = CapStyle::BUTT; 3481 if (CurrentState().lineJoin == CanvasLineJoin::Round) { 3482 cap = CapStyle::ROUND; 3483 } 3484 AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds, true); 3485 auto op = target.UsedOperation(); 3486 if (!target) { 3487 return; 3488 } 3489 3490 const ContextState& state = CurrentState(); 3491 target.StrokeLine( 3492 Point(aX, aY), Point(aX, aY + aH), 3493 CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget), 3494 StrokeOptions(state.lineWidth, CanvasToGfx(state.lineJoin), cap, 3495 state.miterLimit, state.dash.Length(), 3496 state.dash.Elements(), state.dashOffset), 3497 DrawOptions(state.globalAlpha, op)); 3498 return; 3499 } 3500 3501 AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds, true); 3502 auto op = target.UsedOperation(); 3503 if (!target) { 3504 return; 3505 } 3506 3507 const ContextState& state = CurrentState(); 3508 target.StrokeRect( 3509 gfx::Rect(aX, aY, aW, aH), 3510 CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget), 3511 StrokeOptions(state.lineWidth, CanvasToGfx(state.lineJoin), 3512 CanvasToGfx(state.lineCap), state.miterLimit, 3513 state.dash.Length(), state.dash.Elements(), 3514 state.dashOffset), 3515 DrawOptions(state.globalAlpha, op)); 3516 3517 Redraw(); 3518 } 3519 3520 // 3521 // path bits 3522 // 3523 3524 void CanvasRenderingContext2D::BeginPath() { 3525 mPath = nullptr; 3526 mPathBuilder = nullptr; 3527 mPathPruned = false; 3528 } 3529 3530 void CanvasRenderingContext2D::FillImpl(const gfx::Path& aPath) { 3531 MOZ_ASSERT(IsTargetValid()); 3532 if (aPath.IsEmpty()) { 3533 return; 3534 } 3535 3536 const bool needBounds = NeedToCalculateBounds(); 3537 gfx::Rect bounds; 3538 if (needBounds) { 3539 bounds = aPath.GetBounds(mTarget->GetTransform()); 3540 } 3541 3542 AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds, true); 3543 if (!target) { 3544 return; 3545 } 3546 3547 auto op = target.UsedOperation(); 3548 if (!IsTargetValid() || !target) { 3549 return; 3550 } 3551 target.Fill(&aPath, 3552 CanvasGeneralPattern().ForStyle(this, Style::FILL, mTarget), 3553 DrawOptions(CurrentState().globalAlpha, op)); 3554 Redraw(); 3555 } 3556 3557 void CanvasRenderingContext2D::Fill(const CanvasWindingRule& aWinding) { 3558 EnsureTargetAndUserSpacePath(aWinding); 3559 if (!IsTargetValid()) { 3560 return; 3561 } 3562 3563 if (mPath) { 3564 FillImpl(*mPath); 3565 } 3566 } 3567 3568 void CanvasRenderingContext2D::Fill(const CanvasPath& aPath, 3569 const CanvasWindingRule& aWinding) { 3570 EnsureTarget(); 3571 if (!IsTargetValid()) { 3572 return; 3573 } 3574 3575 RefPtr<gfx::Path> gfxpath = aPath.GetPath(aWinding, mTarget); 3576 if (gfxpath) { 3577 FillImpl(*gfxpath); 3578 } 3579 } 3580 3581 void CanvasRenderingContext2D::StrokeImpl(const gfx::Path& aPath) { 3582 MOZ_ASSERT(IsTargetValid()); 3583 if (aPath.IsEmpty()) { 3584 return; 3585 } 3586 3587 const ContextState* state = &CurrentState(); 3588 StrokeOptions strokeOptions(state->lineWidth, CanvasToGfx(state->lineJoin), 3589 CanvasToGfx(state->lineCap), state->miterLimit, 3590 state->dash.Length(), state->dash.Elements(), 3591 state->dashOffset); 3592 state = nullptr; 3593 3594 const bool needBounds = NeedToCalculateBounds(); 3595 if (!IsTargetValid()) { 3596 return; 3597 } 3598 gfx::Rect bounds; 3599 if (needBounds) { 3600 bounds = aPath.GetStrokedBounds(strokeOptions, mTarget->GetTransform()); 3601 } 3602 3603 AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds, true); 3604 if (!target) { 3605 return; 3606 } 3607 3608 auto op = target.UsedOperation(); 3609 if (!IsTargetValid() || !target) { 3610 return; 3611 } 3612 target.Stroke(&aPath, 3613 CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget), 3614 strokeOptions, DrawOptions(CurrentState().globalAlpha, op)); 3615 Redraw(); 3616 } 3617 3618 void CanvasRenderingContext2D::Stroke() { 3619 mFeatureUsage |= CanvasFeatureUsage::Stroke; 3620 3621 EnsureTargetAndUserSpacePath(); 3622 if (!IsTargetValid()) { 3623 return; 3624 } 3625 3626 if (mPath) { 3627 StrokeImpl(*mPath); 3628 } 3629 } 3630 3631 void CanvasRenderingContext2D::Stroke(const CanvasPath& aPath) { 3632 EnsureTarget(); 3633 if (!IsTargetValid()) { 3634 return; 3635 } 3636 RefPtr<gfx::Path> gfxpath = 3637 aPath.GetPath(CanvasWindingRule::Nonzero, mTarget); 3638 if (gfxpath) { 3639 StrokeImpl(*gfxpath); 3640 } 3641 } 3642 3643 void CanvasRenderingContext2D::DrawFocusIfNeeded( 3644 mozilla::dom::Element& aElement, ErrorResult& aRv) { 3645 EnsureTargetAndUserSpacePath(); 3646 if (!mPath) { 3647 return; 3648 } 3649 3650 if (DrawCustomFocusRing(aElement)) { 3651 AutoSaveRestore asr(this); 3652 3653 // set state to conforming focus state 3654 ContextState* state = &CurrentState(); 3655 state->globalAlpha = 1.0; 3656 state->shadowBlur = 0; 3657 state->shadowOffset.x = 0; 3658 state->shadowOffset.y = 0; 3659 state->op = mozilla::gfx::CompositionOp::OP_OVER; 3660 3661 state->lineCap = CanvasLineCap::Butt; 3662 state->lineJoin = CanvasLineJoin::Miter; 3663 state->lineWidth = 1; 3664 state->dash.Clear(); 3665 3666 // color and style of the rings is the same as for image maps 3667 // set the background focus color 3668 state->SetColorStyle(Style::STROKE, NS_RGBA(255, 255, 255, 255)); 3669 state = nullptr; 3670 3671 // draw the focus ring 3672 Stroke(); 3673 if (!mPath) { 3674 return; 3675 } 3676 3677 // set dashing for foreground 3678 nsTArray<mozilla::gfx::Float>& dash = CurrentState().dash; 3679 for (uint32_t i = 0; i < 2; ++i) { 3680 if (!dash.AppendElement(1, fallible)) { 3681 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 3682 return; 3683 } 3684 } 3685 3686 // set the foreground focus color 3687 CurrentState().SetColorStyle(Style::STROKE, NS_RGBA(0, 0, 0, 255)); 3688 // draw the focus ring 3689 Stroke(); 3690 if (!mPath) { 3691 return; 3692 } 3693 } 3694 } 3695 3696 bool CanvasRenderingContext2D::DrawCustomFocusRing(Element& aElement) { 3697 if (!aElement.State().HasState(ElementState::FOCUSRING)) { 3698 return false; 3699 } 3700 3701 HTMLCanvasElement* canvas = GetCanvas(); 3702 if (!canvas || !aElement.IsInclusiveDescendantOf(canvas)) { 3703 return false; 3704 } 3705 3706 EnsureTargetAndUserSpacePath(); 3707 return true; 3708 } 3709 3710 void CanvasRenderingContext2D::Clip(const CanvasWindingRule& aWinding) { 3711 EnsureUserSpacePath(aWinding); 3712 3713 if (!mPath) { 3714 return; 3715 } 3716 3717 if (IsTargetValid()) { 3718 mTarget->PushClip(mPath); 3719 } else { 3720 mTargetNeedsClipsAndTransforms = true; 3721 } 3722 CurrentState().clipsAndTransforms.AppendElement(ClipState(mPath)); 3723 } 3724 3725 void CanvasRenderingContext2D::Clip(const CanvasPath& aPath, 3726 const CanvasWindingRule& aWinding) { 3727 if (!mBufferProvider) { 3728 EnsureTarget(); 3729 if (!IsTargetValid()) { 3730 return; 3731 } 3732 } 3733 3734 RefPtr<gfx::Path> gfxpath = aPath.GetPath(aWinding, mPathType); 3735 3736 if (!gfxpath) { 3737 return; 3738 } 3739 3740 if (IsTargetValid()) { 3741 mTarget->PushClip(gfxpath); 3742 } else { 3743 mTargetNeedsClipsAndTransforms = true; 3744 } 3745 CurrentState().clipsAndTransforms.AppendElement(ClipState(gfxpath)); 3746 } 3747 3748 void CanvasRenderingContext2D::ArcTo(double aX1, double aY1, double aX2, 3749 double aY2, double aRadius, 3750 ErrorResult& aError) { 3751 if (aRadius < 0) { 3752 return aError.ThrowIndexSizeError("Negative radius"); 3753 } 3754 3755 if (!EnsureWritablePath()) { 3756 return; 3757 } 3758 3759 // Current point in user space! 3760 Point p0 = mPathBuilder->CurrentPoint(); 3761 3762 Point p1(aX1, aY1); 3763 Point p2(aX2, aY2); 3764 3765 if (!p1.IsFinite() || !p2.IsFinite() || !std::isfinite(aRadius)) { 3766 return; 3767 } 3768 3769 // Execute these calculations in double precision to avoid cumulative 3770 // rounding errors. 3771 double dir, a2, b2, c2, cosx, sinx, d, anx, any, bnx, bny, x3, y3, x4, y4, cx, 3772 cy, angle0, angle1; 3773 bool anticlockwise; 3774 3775 if (p0 == p1 || p1 == p2 || aRadius == 0) { 3776 LineTo(p1); 3777 return; 3778 } 3779 3780 // Check for colinearity 3781 dir = (p2.x.value - p1.x.value) * (p0.y.value - p1.y.value) + 3782 (p2.y.value - p1.y.value) * (p1.x.value - p0.x.value); 3783 if (dir == 0) { 3784 LineTo(p1); 3785 return; 3786 } 3787 3788 // XXX - Math for this code was already available from the non-azure code 3789 // and would be well tested. Perhaps converting to bezier directly might 3790 // be more efficient longer run. 3791 a2 = (p0.x - aX1) * (p0.x - aX1) + (p0.y - aY1) * (p0.y - aY1); 3792 b2 = (aX1 - aX2) * (aX1 - aX2) + (aY1 - aY2) * (aY1 - aY2); 3793 c2 = (p0.x - aX2) * (p0.x - aX2) + (p0.y - aY2) * (p0.y - aY2); 3794 cosx = (a2 + b2 - c2) / (2 * sqrt(a2 * b2)); 3795 3796 sinx = sqrt(1 - cosx * cosx); 3797 d = aRadius / ((1 - cosx) / sinx); 3798 3799 anx = (aX1 - p0.x) / sqrt(a2); 3800 any = (aY1 - p0.y) / sqrt(a2); 3801 bnx = (aX1 - aX2) / sqrt(b2); 3802 bny = (aY1 - aY2) / sqrt(b2); 3803 x3 = aX1 - anx * d; 3804 y3 = aY1 - any * d; 3805 x4 = aX1 - bnx * d; 3806 y4 = aY1 - bny * d; 3807 anticlockwise = (dir < 0); 3808 cx = x3 + any * aRadius * (anticlockwise ? 1 : -1); 3809 cy = y3 - anx * aRadius * (anticlockwise ? 1 : -1); 3810 angle0 = atan2((y3 - cy), (x3 - cx)); 3811 angle1 = atan2((y4 - cy), (x4 - cx)); 3812 3813 LineTo(x3, y3); 3814 3815 Arc(cx, cy, aRadius, angle0, angle1, anticlockwise, aError); 3816 } 3817 3818 void CanvasRenderingContext2D::Arc(double aX, double aY, double aR, 3819 double aStartAngle, double aEndAngle, 3820 bool aAnticlockwise, ErrorResult& aError) { 3821 if (aR < 0.0) { 3822 return aError.ThrowIndexSizeError("Negative radius"); 3823 } 3824 if (aStartAngle == aEndAngle) { 3825 LineTo(aX + aR * cos(aStartAngle), aY + aR * sin(aStartAngle)); 3826 return; 3827 } 3828 3829 if (!EnsureWritablePath()) { 3830 return; 3831 } 3832 3833 EnsureActivePath(); 3834 3835 mPathBuilder->Arc(Point(aX, aY), aR, aStartAngle, aEndAngle, aAnticlockwise); 3836 mPathPruned = false; 3837 } 3838 3839 void CanvasRenderingContext2D::Rect(double aX, double aY, double aW, 3840 double aH) { 3841 if (!EnsureWritablePath()) { 3842 return; 3843 } 3844 3845 if (!std::isfinite(aX) || !std::isfinite(aY) || !std::isfinite(aW) || 3846 !std::isfinite(aH)) { 3847 return; 3848 } 3849 3850 EnsureCapped(); 3851 mPathBuilder->MoveTo(Point(aX, aY)); 3852 if (aW == 0 && aH == 0) { 3853 return; 3854 } 3855 mPathBuilder->LineTo(Point(aX + aW, aY)); 3856 mPathBuilder->LineTo(Point(aX + aW, aY + aH)); 3857 mPathBuilder->LineTo(Point(aX, aY + aH)); 3858 mPathBuilder->Close(); 3859 } 3860 3861 // https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-roundrect 3862 static void RoundRectImpl( 3863 PathBuilder* aPathBuilder, const Maybe<Matrix>& aTransform, double aX, 3864 double aY, double aW, double aH, 3865 const UnrestrictedDoubleOrDOMPointInitOrUnrestrictedDoubleOrDOMPointInitSequence& 3866 aRadii, 3867 ErrorResult& aError) { 3868 // Step 1. If any of x, y, w, or h are infinite or NaN, then return. 3869 if (!std::isfinite(aX) || !std::isfinite(aY) || !std::isfinite(aW) || 3870 !std::isfinite(aH)) { 3871 return; 3872 } 3873 3874 nsTArray<OwningUnrestrictedDoubleOrDOMPointInit> radii; 3875 // Step 2. If radii is an unrestricted double or DOMPointInit, then set radii 3876 // to « radii ». 3877 if (aRadii.IsUnrestrictedDouble()) { 3878 radii.AppendElement()->SetAsUnrestrictedDouble() = 3879 aRadii.GetAsUnrestrictedDouble(); 3880 } else if (aRadii.IsDOMPointInit()) { 3881 radii.AppendElement()->SetAsDOMPointInit() = aRadii.GetAsDOMPointInit(); 3882 } else { 3883 radii = aRadii.GetAsUnrestrictedDoubleOrDOMPointInitSequence(); 3884 // Step 3. If radii is not a list of size one, two, three, or 3885 // four, then throw a RangeError. 3886 if (radii.Length() < 1 || radii.Length() > 4) { 3887 aError.ThrowRangeError("Can have between 1 and 4 radii"); 3888 return; 3889 } 3890 } 3891 3892 // Step 4. Let normalizedRadii be an empty list. 3893 AutoTArray<Size, 4> normalizedRadii; 3894 3895 // Step 5. For each radius of radii: 3896 for (const auto& radius : radii) { 3897 // Step 5.1. If radius is a DOMPointInit: 3898 if (radius.IsDOMPointInit()) { 3899 const DOMPointInit& point = radius.GetAsDOMPointInit(); 3900 // Step 5.1.1. If radius["x"] or radius["y"] is infinite or NaN, then 3901 // return. 3902 if (!std::isfinite(point.mX) || !std::isfinite(point.mY)) { 3903 return; 3904 } 3905 3906 // Step 5.1.2. If radius["x"] or radius["y"] is negative, then 3907 // throw a RangeError. 3908 if (point.mX < 0 || point.mY < 0) { 3909 aError.ThrowRangeError("Radius can not be negative"); 3910 return; 3911 } 3912 3913 // Step 5.1.3. Otherwise, append radius to 3914 // normalizedRadii. 3915 normalizedRadii.AppendElement( 3916 Size(gfx::Float(point.mX), gfx::Float(point.mY))); 3917 continue; 3918 } 3919 3920 // Step 5.2. If radius is a unrestricted double: 3921 double r = radius.GetAsUnrestrictedDouble(); 3922 // Step 5.2.1. If radius is infinite or NaN, then return. 3923 if (!std::isfinite(r)) { 3924 return; 3925 } 3926 3927 // Step 5.2.2. If radius is negative, then throw a RangeError. 3928 if (r < 0) { 3929 aError.ThrowRangeError("Radius can not be negative"); 3930 return; 3931 } 3932 3933 // Step 5.2.3. Otherwise append «[ "x" → radius, "y" → radius ]» to 3934 // normalizedRadii. 3935 normalizedRadii.AppendElement(Size(gfx::Float(r), gfx::Float(r))); 3936 } 3937 3938 // Step 6. Let upperLeft, upperRight, lowerRight, and lowerLeft be null. 3939 Size upperLeft, upperRight, lowerRight, lowerLeft; 3940 3941 if (normalizedRadii.Length() == 4) { 3942 // Step 7. If normalizedRadii's size is 4, then set upperLeft to 3943 // normalizedRadii[0], set upperRight to normalizedRadii[1], set lowerRight 3944 // to normalizedRadii[2], and set lowerLeft to normalizedRadii[3]. 3945 upperLeft = normalizedRadii[0]; 3946 upperRight = normalizedRadii[1]; 3947 lowerRight = normalizedRadii[2]; 3948 lowerLeft = normalizedRadii[3]; 3949 } else if (normalizedRadii.Length() == 3) { 3950 // Step 8. If normalizedRadii's size is 3, then set upperLeft to 3951 // normalizedRadii[0], set upperRight and lowerLeft to normalizedRadii[1], 3952 // and set lowerRight to normalizedRadii[2]. 3953 upperLeft = normalizedRadii[0]; 3954 upperRight = normalizedRadii[1]; 3955 lowerRight = normalizedRadii[2]; 3956 lowerLeft = normalizedRadii[1]; 3957 } else if (normalizedRadii.Length() == 2) { 3958 // Step 9. If normalizedRadii's size is 2, then set upperLeft and lowerRight 3959 // to normalizedRadii[0] and set upperRight and lowerLeft to 3960 // normalizedRadii[1]. 3961 upperLeft = normalizedRadii[0]; 3962 upperRight = normalizedRadii[1]; 3963 lowerRight = normalizedRadii[0]; 3964 lowerLeft = normalizedRadii[1]; 3965 } else { 3966 // Step 10. If normalizedRadii's size is 1, then set upperLeft, upperRight, 3967 // lowerRight, and lowerLeft to normalizedRadii[0]. 3968 MOZ_ASSERT(normalizedRadii.Length() == 1); 3969 upperLeft = normalizedRadii[0]; 3970 upperRight = normalizedRadii[0]; 3971 lowerRight = normalizedRadii[0]; 3972 lowerLeft = normalizedRadii[0]; 3973 } 3974 3975 // This is not as specified but copied from Chrome. 3976 // XXX Maybe if we implemented Step 12 (the path algorithm) per 3977 // spec this wouldn't be needed? 3978 Float x(aX), y(aY), w(aW), h(aH); 3979 bool clockwise = true; 3980 if (w < 0) { 3981 // Horizontal flip 3982 clockwise = false; 3983 x += w; 3984 w = -w; 3985 std::swap(upperLeft, upperRight); 3986 std::swap(lowerLeft, lowerRight); 3987 } 3988 3989 if (h < 0) { 3990 // Vertical flip 3991 clockwise = !clockwise; 3992 y += h; 3993 h = -h; 3994 std::swap(upperLeft, lowerLeft); 3995 std::swap(upperRight, lowerRight); 3996 } 3997 3998 // Step 11. Corner curves must not overlap. Scale all radii to prevent this: 3999 // Step 11.1. Let top be upperLeft["x"] + upperRight["x"]. 4000 Float top = upperLeft.width + upperRight.width; 4001 // Step 11.2. Let right be upperRight["y"] + lowerRight["y"]. 4002 Float right = upperRight.height + lowerRight.height; 4003 // Step 11.3. Let bottom be lowerRight["x"] + lowerLeft["x"]. 4004 Float bottom = lowerRight.width + lowerLeft.width; 4005 // Step 11.4. Let left be upperLeft["y"] + lowerLeft["y"]. 4006 Float left = upperLeft.height + lowerLeft.height; 4007 // Step 11.5. Let scale be the minimum value of the ratios w / top, h / right, 4008 // w / bottom, h / left. 4009 Float scale = std::min({w / top, h / right, w / bottom, h / left}); 4010 // Step 11.6. If scale is less than 1, then set the x and y members of 4011 // upperLeft, upperRight, lowerLeft, and lowerRight to their current values 4012 // multiplied by scale. 4013 if (scale < 1.0f) { 4014 upperLeft = upperLeft * scale; 4015 upperRight = upperRight * scale; 4016 lowerLeft = lowerLeft * scale; 4017 lowerRight = lowerRight * scale; 4018 } 4019 4020 // Step 12. Create a new subpath: 4021 // Step 13. Mark the subpath as closed. 4022 // Note: Implemented by AppendRoundedRectToPath, which is shared with CSS 4023 // borders etc. 4024 gfx::Rect rect{x, y, w, h}; 4025 RectCornerRadii cornerRadii(upperLeft, upperRight, lowerRight, lowerLeft); 4026 AppendRoundedRectToPath(aPathBuilder, rect, cornerRadii, clockwise, 4027 aTransform); 4028 4029 // Step 14. Create a new subpath with the point (x, y) as the only point in 4030 // the subpath. 4031 // XXX We don't seem to be doing this for ::Rect either? 4032 } 4033 4034 void CanvasRenderingContext2D::RoundRect( 4035 double aX, double aY, double aW, double aH, 4036 const UnrestrictedDoubleOrDOMPointInitOrUnrestrictedDoubleOrDOMPointInitSequence& 4037 aRadii, 4038 ErrorResult& aError) { 4039 if (!EnsureWritablePath()) { 4040 return; 4041 } 4042 4043 PathBuilder* builder = mPathBuilder; 4044 Maybe<Matrix> transform = Nothing(); 4045 4046 EnsureCapped(); 4047 RoundRectImpl(builder, transform, aX, aY, aW, aH, aRadii, aError); 4048 } 4049 4050 void CanvasRenderingContext2D::Ellipse(double aX, double aY, double aRadiusX, 4051 double aRadiusY, double aRotation, 4052 double aStartAngle, double aEndAngle, 4053 bool aAnticlockwise, 4054 ErrorResult& aError) { 4055 if (aRadiusX < 0.0 || aRadiusY < 0.0) { 4056 return aError.ThrowIndexSizeError("Negative radius"); 4057 } 4058 4059 if (!EnsureWritablePath()) { 4060 return; 4061 } 4062 4063 ArcToBezier(this, Point(aX, aY), Size(aRadiusX, aRadiusY), aStartAngle, 4064 aEndAngle, aAnticlockwise, aRotation); 4065 mPathPruned = false; 4066 } 4067 4068 void CanvasRenderingContext2D::FlushPathTransform() { 4069 if (!mPathTransformDirty) { 4070 return; 4071 } 4072 Matrix newTransform = GetCurrentTransform(); 4073 if (mPath || mPathBuilder) { 4074 Matrix inverse = newTransform; 4075 if (!inverse.ExactlyEquals(mPathTransform) && inverse.Invert()) { 4076 TransformCurrentPath(mPathTransform * inverse); 4077 } 4078 } 4079 mPathTransform = newTransform; 4080 mPathTransformDirty = false; 4081 } 4082 4083 bool CanvasRenderingContext2D::EnsureWritablePath() { 4084 if (!mBufferProvider) { 4085 EnsureTarget(); 4086 // NOTE: IsTargetValid() may be false here (mTarget == sErrorTarget) but we 4087 // go ahead and create a path anyway since callers depend on that. 4088 if (!mTarget) { 4089 return false; 4090 } 4091 } 4092 4093 FillRule fillRule = CurrentState().fillRule; 4094 4095 if (mPathTransformDirty) { 4096 FlushPathTransform(); 4097 } 4098 4099 if (mPathBuilder) { 4100 return true; 4101 } 4102 4103 if (!mPath) { 4104 if (mBufferProvider) { 4105 mPathBuilder = Factory::CreatePathBuilder(mPathType, fillRule); 4106 } else { 4107 mPathBuilder = mTarget->CreatePathBuilder(fillRule); 4108 } 4109 } else { 4110 mPathBuilder = Path::ToBuilder(mPath.forget(), fillRule); 4111 } 4112 return true; 4113 } 4114 4115 bool CanvasRenderingContext2D::EnsureBufferProvider() { 4116 if (mBufferProvider) { 4117 return true; 4118 } 4119 EnsureTarget(); 4120 return IsTargetValid(); 4121 } 4122 4123 void CanvasRenderingContext2D::EnsureUserSpacePath( 4124 const CanvasWindingRule& aWinding) { 4125 FillRule fillRule = CurrentState().fillRule; 4126 if (aWinding == CanvasWindingRule::Evenodd) { 4127 fillRule = FillRule::FILL_EVEN_ODD; 4128 } 4129 4130 if (!EnsureBufferProvider()) { 4131 return; 4132 } 4133 4134 if (mPathTransformDirty) { 4135 FlushPathTransform(); 4136 } 4137 4138 if (!mPath && !mPathBuilder) { 4139 mPathBuilder = Factory::CreatePathBuilder(mPathType, fillRule); 4140 } 4141 4142 if (mPathBuilder) { 4143 EnsureCapped(); 4144 mPath = mPathBuilder->Finish(); 4145 mPathBuilder = nullptr; 4146 } 4147 4148 if (mPath && mPath->GetFillRule() != fillRule) { 4149 Path::SetFillRule(mPath, fillRule); 4150 } 4151 4152 NS_ASSERTION(mPath, "mPath should exist"); 4153 } 4154 4155 void CanvasRenderingContext2D::TransformCurrentPath(const Matrix& aTransform) { 4156 if (mPathBuilder) { 4157 mPathBuilder = Path::ToBuilder(mPathBuilder->Finish(), aTransform); 4158 } else if (mPath) { 4159 mPathBuilder = Path::ToBuilder(mPath.forget(), aTransform); 4160 } 4161 } 4162 4163 // 4164 // text 4165 // 4166 4167 void CanvasRenderingContext2D::SetFont(const nsACString& aFont, 4168 ErrorResult& aError) { 4169 mFeatureUsage |= CanvasFeatureUsage::SetFont; 4170 4171 SetFontInternal(aFont, aError); 4172 if (aError.Failed()) { 4173 return; 4174 } 4175 4176 // Setting the font attribute magically resets fontVariantCaps and 4177 // fontStretch to normal. 4178 // (spec unclear, cf. https://github.com/whatwg/html/issues/8103) 4179 SetFontVariantCaps(CanvasFontVariantCaps::Normal); 4180 SetFontStretch(CanvasFontStretch::Normal); 4181 4182 // If letterSpacing or wordSpacing is present, recompute to account for 4183 // changes to font-relative dimensions. 4184 UpdateSpacing(); 4185 } 4186 4187 static float QuantizeFontSize(float aSize) { 4188 // Based on the Veltkamp-Dekker float-splitting algorithm, see e.g. 4189 // https://indico.cern.ch/event/313684/contributions/1687773/attachments/600513/826490/FPArith-Part2.pdf 4190 // A 32-bit float has 24 bits of precision (23 stored, plus an implicit 1 bit 4191 // at the start of the mantissa). 4192 constexpr int bitsToDrop = 17; // leaving 7 bits of precision 4193 constexpr int scale = 1 << bitsToDrop; 4194 float d = aSize * (scale + 1); 4195 float t = d - aSize; 4196 return d - t; 4197 } 4198 4199 bool CanvasRenderingContext2D::SetFontInternal(const nsACString& aFont, 4200 ErrorResult& aError) { 4201 RefPtr<PresShell> presShell = GetPresShell(); 4202 if (!presShell) { 4203 return SetFontInternalDisconnected(aFont, aError); 4204 } 4205 4206 nsPresContext* c = presShell->GetPresContext(); 4207 FontStyleCacheKey key{aFont, c->RestyleManager()->GetRestyleGeneration()}; 4208 auto entry = mFontStyleCache.Lookup(key); 4209 if (!entry) { 4210 FontStyleData newData; 4211 newData.mKey = key; 4212 newData.mStyle = GetFontStyleForServo(mCanvasElement, aFont, presShell, 4213 newData.mUsedFont, aError); 4214 entry.Set(newData); 4215 } 4216 4217 const auto& data = entry.Data(); 4218 if (!data.mStyle) { 4219 return false; 4220 } 4221 4222 const nsStyleFont* fontStyle = data.mStyle->StyleFont(); 4223 4224 // Purposely ignore the font size that respects the user's minimum 4225 // font preference (fontStyle->mFont.size) in favor of the computed 4226 // size (fontStyle->mSize). See 4227 // https://bugzilla.mozilla.org/show_bug.cgi?id=698652. 4228 // FIXME: Nobody initializes mAllowZoom for servo? 4229 // MOZ_ASSERT(!fontStyle->mAllowZoom, 4230 // "expected text zoom to be disabled on this nsStyleFont"); 4231 nsFont resizedFont(fontStyle->mFont); 4232 // Create a font group working in units of CSS pixels instead of the usual 4233 // device pixels, to avoid being affected by page zoom. nsFontMetrics will 4234 // convert nsFont size in app units to device pixels for the font group, so 4235 // here we first apply to the size the equivalent of a conversion from device 4236 // pixels to CSS pixels, to adjust for the difference in expectations from 4237 // other nsFontMetrics clients. 4238 resizedFont.size = 4239 fontStyle->mSize.ScaledBy(1.0f / c->CSSToDevPixelScale().scale); 4240 4241 // Quantize font size to avoid filling caches with thousands of fonts that 4242 // differ by imperceptibly-tiny size deltas. 4243 resizedFont.size = StyleCSSPixelLength::FromPixels( 4244 QuantizeFontSize(resizedFont.size.ToCSSPixels())); 4245 4246 resizedFont.kerning = CanvasToGfx(CurrentState().fontKerning); 4247 4248 // fontStretch handling: if fontStretch is not 'normal', apply it; 4249 // if it is normal, then use whatever the shorthand set. 4250 // XXX(jfkthame) The interaction between the shorthand and the separate attr 4251 // here is not clearly spec'd, and we may want to reconsider it (or revise 4252 // the available values); see https://github.com/whatwg/html/issues/8103. 4253 switch (CurrentState().fontStretch) { 4254 case CanvasFontStretch::Normal: 4255 // Leave whatever the shorthand set. 4256 break; 4257 case CanvasFontStretch::Ultra_condensed: 4258 resizedFont.stretch = StyleFontStretch::ULTRA_CONDENSED; 4259 break; 4260 case CanvasFontStretch::Extra_condensed: 4261 resizedFont.stretch = StyleFontStretch::EXTRA_CONDENSED; 4262 break; 4263 case CanvasFontStretch::Condensed: 4264 resizedFont.stretch = StyleFontStretch::CONDENSED; 4265 break; 4266 case CanvasFontStretch::Semi_condensed: 4267 resizedFont.stretch = StyleFontStretch::SEMI_CONDENSED; 4268 break; 4269 case CanvasFontStretch::Semi_expanded: 4270 resizedFont.stretch = StyleFontStretch::SEMI_EXPANDED; 4271 break; 4272 case CanvasFontStretch::Expanded: 4273 resizedFont.stretch = StyleFontStretch::EXPANDED; 4274 break; 4275 case CanvasFontStretch::Extra_expanded: 4276 resizedFont.stretch = StyleFontStretch::EXTRA_EXPANDED; 4277 break; 4278 case CanvasFontStretch::Ultra_expanded: 4279 resizedFont.stretch = StyleFontStretch::ULTRA_EXPANDED; 4280 break; 4281 default: 4282 MOZ_ASSERT_UNREACHABLE("unknown stretch value"); 4283 break; 4284 } 4285 4286 // fontVariantCaps handling: if fontVariantCaps is not 'normal', apply it; 4287 // if it is, then use the smallCaps boolean from the shorthand. 4288 // XXX(jfkthame) The interaction between the shorthand and the separate attr 4289 // here is not clearly spec'd, and we may want to reconsider it (or revise 4290 // the available values); see https://github.com/whatwg/html/issues/8103. 4291 switch (CurrentState().fontVariantCaps) { 4292 case CanvasFontVariantCaps::Normal: 4293 // Leave whatever the shorthand set. 4294 break; 4295 case CanvasFontVariantCaps::Small_caps: 4296 resizedFont.variantCaps = NS_FONT_VARIANT_CAPS_SMALLCAPS; 4297 break; 4298 case CanvasFontVariantCaps::All_small_caps: 4299 resizedFont.variantCaps = NS_FONT_VARIANT_CAPS_ALLSMALL; 4300 break; 4301 case CanvasFontVariantCaps::Petite_caps: 4302 resizedFont.variantCaps = NS_FONT_VARIANT_CAPS_PETITECAPS; 4303 break; 4304 case CanvasFontVariantCaps::All_petite_caps: 4305 resizedFont.variantCaps = NS_FONT_VARIANT_CAPS_ALLPETITE; 4306 break; 4307 case CanvasFontVariantCaps::Unicase: 4308 resizedFont.variantCaps = NS_FONT_VARIANT_CAPS_UNICASE; 4309 break; 4310 case CanvasFontVariantCaps::Titling_caps: 4311 resizedFont.variantCaps = NS_FONT_VARIANT_CAPS_TITLING; 4312 break; 4313 default: 4314 MOZ_ASSERT_UNREACHABLE("unknown caps value"); 4315 break; 4316 } 4317 4318 c->Document()->FlushUserFontSet(); 4319 4320 nsFontMetrics::Params params; 4321 params.language = fontStyle->mLanguage; 4322 params.explicitLanguage = fontStyle->mExplicitLanguage; 4323 params.userFontSet = c->GetUserFontSet(); 4324 params.textPerf = c->GetTextPerfMetrics(); 4325 #ifdef XP_WIN 4326 params.allowForceGDIClassic = false; 4327 #endif 4328 RefPtr<nsFontMetrics> metrics = c->GetMetricsFor(resizedFont, params); 4329 4330 gfxFontGroup* newFontGroup = metrics->GetThebesFontGroup(); 4331 CurrentState().fontGroup = newFontGroup; 4332 NS_ASSERTION(CurrentState().fontGroup, "Could not get font group"); 4333 CurrentState().font = data.mUsedFont; 4334 CurrentState().fontFont = fontStyle->mFont; 4335 CurrentState().fontFont.size = fontStyle->mSize; 4336 CurrentState().fontComputedStyle = data.mStyle; 4337 4338 return true; 4339 } 4340 4341 static nsAutoCString FamilyListToString( 4342 const StyleFontFamilyList& aFamilyList) { 4343 return StringJoin(", "_ns, aFamilyList.list.AsSpan(), 4344 [](nsACString& dst, const StyleSingleFontFamily& name) { 4345 name.AppendToString(dst); 4346 }); 4347 } 4348 4349 static void SerializeFontForCanvas(const StyleFontFamilyList& aList, 4350 const gfxFontStyle& aStyle, 4351 nsACString& aUsedFont) { 4352 // Re-serialize the font shorthand as required by the canvas spec. 4353 aUsedFont.Truncate(); 4354 4355 if (!aStyle.style.IsNormal()) { 4356 aStyle.style.ToString(aUsedFont); 4357 aUsedFont.Append(" "); 4358 } 4359 4360 // font-weight is serialized as a number 4361 if (!aStyle.weight.IsNormal()) { 4362 aUsedFont.AppendFloat(aStyle.weight.ToFloat()); 4363 aUsedFont.Append(" "); 4364 } 4365 4366 // font-stretch is serialized using CSS Fonts 3 keywords, not percentages. 4367 if (!aStyle.stretch.IsNormal() && 4368 Servo_FontStretch_SerializeKeyword(&aStyle.stretch, &aUsedFont)) { 4369 aUsedFont.Append(" "); 4370 } 4371 4372 if (aStyle.variantCaps == NS_FONT_VARIANT_CAPS_SMALLCAPS) { 4373 aUsedFont.Append("small-caps "); 4374 } 4375 4376 // Serialize the computed (not specified) size, and the family name(s). 4377 aUsedFont.AppendFloat(aStyle.size); 4378 aUsedFont.Append("px "); 4379 aUsedFont.Append(FamilyListToString(aList)); 4380 } 4381 4382 bool CanvasRenderingContext2D::SetFontInternalDisconnected( 4383 const nsACString& aFont, ErrorResult& aError) { 4384 FontFaceSet* fontFaceSet = nullptr; 4385 if (mCanvasElement) { 4386 fontFaceSet = mCanvasElement->OwnerDoc()->Fonts(); 4387 } else { 4388 nsIGlobalObject* global = GetParentObject(); 4389 fontFaceSet = global ? global->GetFonts() : nullptr; 4390 } 4391 4392 FontFaceSetImpl* fontFaceSetImpl = 4393 fontFaceSet ? fontFaceSet->GetImpl() : nullptr; 4394 RefPtr<URLExtraData> urlExtraData = 4395 fontFaceSetImpl ? fontFaceSetImpl->GetURLExtraData() : nullptr; 4396 4397 if (NS_WARN_IF(!urlExtraData)) { 4398 // Provided we have a FontFaceSetImpl object, this should only happen on 4399 // worker threads, where we failed to initialize the worker before it was 4400 // shutdown. 4401 aError.ThrowInvalidStateError("Missing URLExtraData"); 4402 return false; 4403 } 4404 4405 if (fontFaceSetImpl) { 4406 fontFaceSetImpl->FlushUserFontSet(); 4407 } 4408 4409 // In the OffscreenCanvas case we don't have the context necessary to call 4410 // GetFontStyleForServo(), as we do in the main-thread canvas context, so 4411 // instead we borrow ParseFontShorthandForMatching to parse the attribute. 4412 StyleFontFamilyList list; 4413 gfxFontStyle fontStyle; 4414 float size = 0.0f; 4415 bool smallCaps = false; 4416 if (!ServoCSSParser::ParseFontShorthandForMatching( 4417 aFont, urlExtraData, list, fontStyle.style, fontStyle.stretch, 4418 fontStyle.weight, &size, &smallCaps)) { 4419 return false; 4420 } 4421 4422 fontStyle.size = QuantizeFontSize(size); 4423 #ifdef XP_WIN 4424 fontStyle.allowForceGDIClassic = false; 4425 #endif 4426 4427 switch (CurrentState().fontStretch) { 4428 case CanvasFontStretch::Normal: 4429 // Leave whatever the shorthand set. 4430 break; 4431 case CanvasFontStretch::Ultra_condensed: 4432 fontStyle.stretch = StyleFontStretch::ULTRA_CONDENSED; 4433 break; 4434 case CanvasFontStretch::Extra_condensed: 4435 fontStyle.stretch = StyleFontStretch::EXTRA_CONDENSED; 4436 break; 4437 case CanvasFontStretch::Condensed: 4438 fontStyle.stretch = StyleFontStretch::CONDENSED; 4439 break; 4440 case CanvasFontStretch::Semi_condensed: 4441 fontStyle.stretch = StyleFontStretch::SEMI_CONDENSED; 4442 break; 4443 case CanvasFontStretch::Semi_expanded: 4444 fontStyle.stretch = StyleFontStretch::SEMI_EXPANDED; 4445 break; 4446 case CanvasFontStretch::Expanded: 4447 fontStyle.stretch = StyleFontStretch::EXPANDED; 4448 break; 4449 case CanvasFontStretch::Extra_expanded: 4450 fontStyle.stretch = StyleFontStretch::EXTRA_EXPANDED; 4451 break; 4452 case CanvasFontStretch::Ultra_expanded: 4453 fontStyle.stretch = StyleFontStretch::ULTRA_EXPANDED; 4454 break; 4455 default: 4456 MOZ_ASSERT_UNREACHABLE("unknown stretch value"); 4457 break; 4458 } 4459 4460 // fontVariantCaps handling: if fontVariantCaps is not 'normal', apply it; 4461 // if it is, then use the smallCaps boolean from the shorthand. 4462 // XXX(jfkthame) The interaction between the shorthand and the separate attr 4463 // here is not clearly spec'd, and we may want to reconsider it (or revise 4464 // the available values); see https://github.com/whatwg/html/issues/8103. 4465 switch (CurrentState().fontVariantCaps) { 4466 case CanvasFontVariantCaps::Normal: 4467 fontStyle.variantCaps = smallCaps ? NS_FONT_VARIANT_CAPS_SMALLCAPS 4468 : NS_FONT_VARIANT_CAPS_NORMAL; 4469 break; 4470 case CanvasFontVariantCaps::Small_caps: 4471 fontStyle.variantCaps = NS_FONT_VARIANT_CAPS_SMALLCAPS; 4472 break; 4473 case CanvasFontVariantCaps::All_small_caps: 4474 fontStyle.variantCaps = NS_FONT_VARIANT_CAPS_ALLSMALL; 4475 break; 4476 case CanvasFontVariantCaps::Petite_caps: 4477 fontStyle.variantCaps = NS_FONT_VARIANT_CAPS_PETITECAPS; 4478 break; 4479 case CanvasFontVariantCaps::All_petite_caps: 4480 fontStyle.variantCaps = NS_FONT_VARIANT_CAPS_ALLPETITE; 4481 break; 4482 case CanvasFontVariantCaps::Unicase: 4483 fontStyle.variantCaps = NS_FONT_VARIANT_CAPS_UNICASE; 4484 break; 4485 case CanvasFontVariantCaps::Titling_caps: 4486 fontStyle.variantCaps = NS_FONT_VARIANT_CAPS_TITLING; 4487 break; 4488 default: 4489 MOZ_ASSERT_UNREACHABLE("unknown caps value"); 4490 break; 4491 } 4492 // If variantCaps is set, we need to disable a gfxFont fast-path. 4493 fontStyle.noFallbackVariantFeatures = 4494 (fontStyle.variantCaps == NS_FONT_VARIANT_CAPS_NORMAL); 4495 4496 // Set the kerning feature, if required by the fontKerning attribute. 4497 gfxFontFeature setting{TRUETYPE_TAG('k', 'e', 'r', 'n'), 0}; 4498 switch (CurrentState().fontKerning) { 4499 case CanvasFontKerning::None: 4500 setting.mValue = 0; 4501 fontStyle.featureSettings.AppendElement(setting); 4502 break; 4503 case CanvasFontKerning::Normal: 4504 setting.mValue = 1; 4505 fontStyle.featureSettings.AppendElement(setting); 4506 break; 4507 default: 4508 // auto case implies use user agent default 4509 break; 4510 } 4511 4512 // If we have a canvas element, get its lang (if known). 4513 RefPtr<nsAtom> language; 4514 bool explicitLanguage = false; 4515 if (mCanvasElement) { 4516 language = mCanvasElement->FragmentOrElement::GetLang(); 4517 if (language) { 4518 explicitLanguage = true; 4519 } else { 4520 language = mCanvasElement->OwnerDoc()->GetLanguageForStyle(); 4521 } 4522 } else { 4523 // Pass the OS default language, to behave similarly to HTML or canvas- 4524 // element content with no language tag. 4525 language = nsLanguageAtomService::GetService()->GetLocaleLanguage(); 4526 } 4527 4528 // TODO: Cache fontGroups in the Worker (use an nsFontCache?) 4529 gfxFontGroup* fontGroup = 4530 new gfxFontGroup(mOffscreenCanvas, // aFontVisibilityProvider 4531 list, // aFontFamilyList 4532 &fontStyle, // aStyle 4533 language, // aLanguage 4534 explicitLanguage, // aExplicitLanguage 4535 nullptr, // aTextPerf 4536 fontFaceSetImpl, // aUserFontSet 4537 1.0, // aDevToCssSize 4538 StyleFontVariantEmoji::Normal); 4539 auto& state = CurrentState(); 4540 state.fontGroup = fontGroup; 4541 SerializeFontForCanvas(list, fontStyle, state.font); 4542 state.fontFont = nsFont(StyleFontFamily{list, false, false}, 4543 StyleCSSPixelLength::FromPixels(size)); 4544 state.fontFont.variantCaps = fontStyle.variantCaps; 4545 state.fontComputedStyle = nullptr; 4546 return true; 4547 } 4548 4549 void CanvasRenderingContext2D::UpdateSpacing() { 4550 auto state = CurrentState(); 4551 if (!state.letterSpacingStr.IsEmpty()) { 4552 SetLetterSpacing(state.letterSpacingStr); 4553 } 4554 if (!state.wordSpacingStr.IsEmpty()) { 4555 SetWordSpacing(state.wordSpacingStr); 4556 } 4557 } 4558 4559 /* 4560 * Helper function that replaces the whitespace characters in a string 4561 * with U+0020 SPACE. The whitespace characters are defined as U+0020 SPACE, 4562 * U+0009 CHARACTER TABULATION (tab), U+000A LINE FEED (LF), U+000B LINE 4563 * TABULATION, U+000C FORM FEED (FF), and U+000D CARRIAGE RETURN (CR). 4564 * We also replace characters with Bidi type Segment Separator or Block 4565 * Separator. 4566 * @param str The string whose whitespace characters to replace. 4567 */ 4568 static inline void TextReplaceWhitespaceCharacters(nsAutoString& aStr) { 4569 aStr.ReplaceChar(u"\x09\x0A\x0B\x0C\x0D\x1C\x1D\x1E\x1F\x85\x2029", 4570 char16_t(' ')); 4571 } 4572 4573 void CanvasRenderingContext2D::FillText(const nsAString& aText, double aX, 4574 double aY, 4575 const Optional<double>& aMaxWidth, 4576 ErrorResult& aError) { 4577 // We try to match the most commonly observed strings used by canvas 4578 // fingerprinting scripts. 4579 MOZ_LOG(gFingerprinterDetection, LogLevel::Verbose, 4580 ("mFillTextCalls %i FillText: " 4581 "\"%s\"\n", 4582 mFillTextCalls, NS_ConvertUTF16toUTF8(aText).get())); 4583 if (mFillTextCalls <= 5) { 4584 if (aText == u"Cwm fjordbank glyphs vext quiz, 😃"_ns) { 4585 mFeatureUsage |= CanvasFeatureUsage::KnownText_1; 4586 } else if (StringBeginsWith(aText, u"Hel$&?6%"_ns)) { 4587 mFeatureUsage |= CanvasFeatureUsage::KnownText_2; // Imperva 4588 } else if (StringBeginsWith(aText, u"<@nv45. "_ns)) { 4589 mFeatureUsage |= CanvasFeatureUsage::KnownText_3; 4590 } else if (aText == u"Cañvas FP 😎 12345"_ns) { 4591 mFeatureUsage |= CanvasFeatureUsage::KnownText_4; 4592 } else if (StringBeginsWith(aText, u"❤️🤪🎉👋"_ns)) { 4593 mFeatureUsage |= CanvasFeatureUsage::KnownText_5; // hCaptcha 4594 } else if (aText == u"SomeCanvasFingerPrint.65@345876"_ns) { 4595 mFeatureUsage |= CanvasFeatureUsage::KnownText_6; 4596 } else if (aText == u"Browser,Signal <canvas> 2.0"_ns) { 4597 mFeatureUsage |= CanvasFeatureUsage::KnownText_7; 4598 } else if (aText == u"@Browsers~%fingGPRint$&,<canvas>"_ns) { 4599 mFeatureUsage |= CanvasFeatureUsage::KnownText_8; 4600 } else if (aText == u"M"_ns) { 4601 mFeatureUsage |= CanvasFeatureUsage::KnownText_9; 4602 } else if (aText == u"E"_ns) { 4603 mFeatureUsage |= CanvasFeatureUsage::KnownText_10; 4604 } else if (aText == u"g"_ns) { 4605 mFeatureUsage |= CanvasFeatureUsage::KnownText_11; 4606 } else if (aText == u"Soft Ruddy Foothold 2"_ns) { 4607 mFeatureUsage |= CanvasFeatureUsage::KnownText_12; // Akamai 4608 } else if (aText == u"!H71JCaj)]# 1@#"_ns) { 4609 mFeatureUsage |= CanvasFeatureUsage::KnownText_13; // Akamai 4610 } else if (aText == u"oubrg5h56e@!$3t4"_ns) { 4611 mFeatureUsage |= CanvasFeatureUsage::KnownText_14; 4612 } else if (aText == u"Cwm fjordbank glyphs vext quiz,"_ns) { 4613 mFeatureUsage |= CanvasFeatureUsage::KnownText_15; 4614 } else if (aText == u"ClientJS,org <canvas> 1.0"_ns) { 4615 mFeatureUsage |= CanvasFeatureUsage::KnownText_16; 4616 } else if (aText == u"IaID,org <canvas> 1.0"_ns) { 4617 mFeatureUsage |= CanvasFeatureUsage::KnownText_17; 4618 } else if (aText == u"conviva"_ns) { 4619 mFeatureUsage |= CanvasFeatureUsage::KnownText_18; 4620 } else if (aText == u"Random Text WMwmil10Oo"_ns) { 4621 mFeatureUsage |= CanvasFeatureUsage::KnownText_19; 4622 } else if (aText == u"-0.5753861119575491"_ns) { 4623 mFeatureUsage |= CanvasFeatureUsage::KnownText_20; 4624 } else if (aText == u"0.8178819121159085"_ns) { 4625 mFeatureUsage |= CanvasFeatureUsage::KnownText_21; 4626 } else if (StringBeginsWith(aText, u"Cwm fjordbank"_ns)) { 4627 mFeatureUsage |= CanvasFeatureUsage::KnownText_22; 4628 } else if (StringBeginsWith(aText, u"iO0A"_ns)) { 4629 mFeatureUsage |= CanvasFeatureUsage::KnownText_23; 4630 } else if (aText == u"<@nv45. F1n63r,Pr1n71n6!"_ns) { 4631 mFeatureUsage |= CanvasFeatureUsage::KnownText_24; 4632 } else if (aText == u"Cwm fjordbank gly 😃"_ns) { 4633 mFeatureUsage |= CanvasFeatureUsage::KnownText_25; 4634 } else if (aText == u"clientgear.com <canvas> 1.0") { 4635 mFeatureUsage |= CanvasFeatureUsage::KnownText_26; 4636 } else if (aText == u"iO0A🤣💩") { 4637 mFeatureUsage |= CanvasFeatureUsage::KnownText_27; 4638 } else if (aText == u"Ry"_ns) { 4639 mFeatureUsage |= CanvasFeatureUsage::KnownText_28; 4640 } 4641 } 4642 mFillTextCalls++; 4643 4644 DebugOnly<UniquePtr<TextMetrics>> metrics = DrawOrMeasureText( 4645 aText, aX, aY, aMaxWidth, TextDrawOperation::FILL, aError); 4646 MOZ_ASSERT( 4647 !metrics.inspect()); // drawing operation never returns TextMetrics 4648 } 4649 4650 void CanvasRenderingContext2D::StrokeText(const nsAString& aText, double aX, 4651 double aY, 4652 const Optional<double>& aMaxWidth, 4653 ErrorResult& aError) { 4654 DebugOnly<UniquePtr<TextMetrics>> metrics = DrawOrMeasureText( 4655 aText, aX, aY, aMaxWidth, TextDrawOperation::STROKE, aError); 4656 MOZ_ASSERT( 4657 !metrics.inspect()); // drawing operation never returns TextMetrics 4658 } 4659 4660 UniquePtr<TextMetrics> CanvasRenderingContext2D::MeasureText( 4661 const nsAString& aRawText, ErrorResult& aError) { 4662 Optional<double> maxWidth; 4663 return DrawOrMeasureText(aRawText, 0, 0, maxWidth, TextDrawOperation::MEASURE, 4664 aError); 4665 } 4666 4667 /** 4668 * Used for nsBidiPresUtils::ProcessText 4669 */ 4670 struct MOZ_STACK_CLASS CanvasBidiProcessor final 4671 : public nsBidiPresUtils::BidiProcessor { 4672 using Style = CanvasRenderingContext2D::Style; 4673 4674 explicit CanvasBidiProcessor(mozilla::gfx::PaletteCache& aPaletteCache) 4675 : mPaletteCache(aPaletteCache) { 4676 if (StaticPrefs::gfx_missing_fonts_notify()) { 4677 mMissingFonts = MakeUnique<gfxMissingFontRecorder>(); 4678 } 4679 } 4680 4681 ~CanvasBidiProcessor() { 4682 // notify front-end code if we encountered missing glyphs in any script 4683 if (mMissingFonts) { 4684 mMissingFonts->Flush(); 4685 } 4686 } 4687 4688 class PropertyProvider : public gfxTextRun::PropertyProvider { 4689 public: 4690 explicit PropertyProvider(const CanvasBidiProcessor& aProcessor) 4691 : mProcessor(aProcessor) {} 4692 4693 bool GetSpacing(gfxTextRun::Range aRange, 4694 gfxFont::Spacing* aSpacing) const { 4695 for (auto i = aRange.start; i < aRange.end; ++i) { 4696 auto* charGlyphs = mProcessor.mTextRun->GetCharacterGlyphs(); 4697 if (i == mProcessor.mTextRun->GetLength() - 1 || 4698 (charGlyphs[i + 1].IsClusterStart() && 4699 charGlyphs[i + 1].IsLigatureGroupStart())) { 4700 // Currently we add all the letterspacing to the right of the glyph, 4701 // which is similar to Chrome's behavior, though the LTR vs RTL 4702 // asymmetry seems unfortunate. 4703 if (mProcessor.mTextRun->IsRightToLeft()) { 4704 aSpacing->mAfter = 0; 4705 aSpacing->mBefore = NSToCoordRound(mProcessor.mLetterSpacing); 4706 } else { 4707 aSpacing->mBefore = 0; 4708 aSpacing->mAfter = NSToCoordRound(mProcessor.mLetterSpacing); 4709 } 4710 } else { 4711 aSpacing->mBefore = 0; 4712 aSpacing->mAfter = 0; 4713 } 4714 if (charGlyphs[i].CharIsSpace()) { 4715 if (mProcessor.mTextRun->IsRightToLeft()) { 4716 aSpacing->mBefore += NSToCoordRound(mProcessor.mWordSpacing); 4717 } else { 4718 aSpacing->mAfter += NSToCoordRound(mProcessor.mWordSpacing); 4719 } 4720 } 4721 aSpacing++; 4722 } 4723 return mProcessor.mLetterSpacing != 0.0 || mProcessor.mWordSpacing != 0.0; 4724 } 4725 4726 mozilla::StyleHyphens GetHyphensOption() const { 4727 return mozilla::StyleHyphens::None; 4728 } 4729 4730 // Methods only used when hyphenation is active, not relevant to canvas2d: 4731 void GetHyphenationBreaks(gfxTextRun::Range aRange, 4732 gfxTextRun::HyphenType* aBreakBefore) const { 4733 MOZ_ASSERT_UNREACHABLE("no hyphenation in canvas2d text!"); 4734 } 4735 gfxFloat GetHyphenWidth() const { 4736 MOZ_ASSERT_UNREACHABLE("no hyphenation in canvas2d text!"); 4737 return 0.0; 4738 } 4739 already_AddRefed<DrawTarget> GetDrawTarget() const { 4740 MOZ_ASSERT_UNREACHABLE("no hyphenation in canvas2d text!"); 4741 return nullptr; 4742 } 4743 uint32_t GetAppUnitsPerDevUnit() const { 4744 MOZ_ASSERT_UNREACHABLE("no hyphenation in canvas2d text!"); 4745 return 60; 4746 } 4747 gfx::ShapedTextFlags GetShapedTextFlags() const { 4748 MOZ_ASSERT_UNREACHABLE("no hyphenation in canvas2d text!"); 4749 return gfx::ShapedTextFlags(); 4750 } 4751 4752 private: 4753 const CanvasBidiProcessor& mProcessor; 4754 }; 4755 4756 using ContextState = CanvasRenderingContext2D::ContextState; 4757 4758 void SetText(const char16_t* aText, int32_t aLength, 4759 intl::BidiDirection aDirection) override { 4760 if (mIgnoreSetText) { 4761 // We've been told to ignore SetText because the processor is only ever 4762 // handling a single, fixed string. 4763 MOZ_ASSERT(mTextRun && mTextRun->GetLength() == uint32_t(aLength)); 4764 return; 4765 } 4766 mSetTextCount++; 4767 auto* pfl = gfxPlatformFontList::PlatformFontList(); 4768 pfl->Lock(); 4769 mFontgrp->UpdateUserFonts(); // ensure user font generation is current 4770 // adjust flags for current direction run 4771 gfx::ShapedTextFlags flags = mTextRunFlags; 4772 if (aDirection == intl::BidiDirection::RTL) { 4773 flags |= gfx::ShapedTextFlags::TEXT_IS_RTL; 4774 } else { 4775 flags &= ~gfx::ShapedTextFlags::TEXT_IS_RTL; 4776 } 4777 mTextRun = mFontgrp->MakeTextRun( 4778 aText, aLength, mDrawTarget, mAppUnitsPerDevPixel, flags, 4779 nsTextFrameUtils::Flags::DontSkipDrawingForPendingUserFonts, 4780 mMissingFonts.get()); 4781 pfl->Unlock(); 4782 } 4783 4784 nscoord GetWidth() override { 4785 PropertyProvider provider(*this); 4786 gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText( 4787 mDoMeasureBoundingBox ? gfxFont::TIGHT_INK_EXTENTS 4788 : gfxFont::LOOSE_INK_EXTENTS, 4789 mDrawTarget, &provider); 4790 4791 // this only measures the height; the total width is gotten from the 4792 // the return value of ProcessText. 4793 if (mDoMeasureBoundingBox) { 4794 textRunMetrics.mBoundingBox.Scale(1.0 / mAppUnitsPerDevPixel); 4795 mBoundingBox = mBoundingBox.Union(textRunMetrics.mBoundingBox); 4796 } 4797 4798 return NSToCoordRound(textRunMetrics.mAdvanceWidth); 4799 } 4800 4801 already_AddRefed<gfxPattern> GetGradientFor(Style aStyle) { 4802 RefPtr<gfxPattern> pattern; 4803 CanvasGradient* gradient = mCtx->CurrentState().gradientStyles[aStyle]; 4804 CanvasGradient::Type type = gradient->GetType(); 4805 4806 switch (type) { 4807 case CanvasGradient::Type::CONIC: { 4808 auto conic = static_cast<CanvasConicGradient*>(gradient); 4809 pattern = new gfxPattern(conic->mCenter.x, conic->mCenter.y, 4810 conic->mAngle, 0, 1); 4811 break; 4812 } 4813 case CanvasGradient::Type::RADIAL: { 4814 auto radial = static_cast<CanvasRadialGradient*>(gradient); 4815 pattern = new gfxPattern(radial->mCenter1.x, radial->mCenter1.y, 4816 radial->mRadius1, radial->mCenter2.x, 4817 radial->mCenter2.y, radial->mRadius2); 4818 break; 4819 } 4820 case CanvasGradient::Type::LINEAR: { 4821 auto linear = static_cast<CanvasLinearGradient*>(gradient); 4822 pattern = new gfxPattern(linear->mBegin.x, linear->mBegin.y, 4823 linear->mEnd.x, linear->mEnd.y); 4824 break; 4825 } 4826 default: 4827 MOZ_ASSERT(false, "Should be linear, radial or conic gradient."); 4828 return nullptr; 4829 } 4830 4831 for (auto stop : gradient->mRawStops) { 4832 pattern->AddColorStop(stop.offset, stop.color); 4833 } 4834 4835 return pattern.forget(); 4836 } 4837 4838 gfx::ExtendMode CvtCanvasRepeatToGfxRepeat( 4839 CanvasPattern::RepeatMode aRepeatMode) { 4840 switch (aRepeatMode) { 4841 case CanvasPattern::RepeatMode::REPEAT: 4842 return gfx::ExtendMode::REPEAT; 4843 case CanvasPattern::RepeatMode::REPEATX: 4844 return gfx::ExtendMode::REPEAT_X; 4845 case CanvasPattern::RepeatMode::REPEATY: 4846 return gfx::ExtendMode::REPEAT_Y; 4847 case CanvasPattern::RepeatMode::NOREPEAT: 4848 return gfx::ExtendMode::CLAMP; 4849 default: 4850 return gfx::ExtendMode::CLAMP; 4851 } 4852 } 4853 4854 already_AddRefed<gfxPattern> GetPatternFor(Style aStyle) { 4855 const CanvasPattern* pat = mCtx->CurrentState().patternStyles[aStyle]; 4856 RefPtr<gfxPattern> pattern = new gfxPattern(pat->mSurface, pat->mTransform); 4857 pattern->SetExtend(CvtCanvasRepeatToGfxRepeat(pat->mRepeat)); 4858 return pattern.forget(); 4859 } 4860 4861 void DrawText(nscoord aXOffset) override { 4862 gfx::Point point = mPt; 4863 bool rtl = mTextRun->IsRightToLeft(); 4864 bool verticalRun = mTextRun->IsVertical(); 4865 RefPtr<gfxPattern> pattern; 4866 4867 float& inlineCoord = verticalRun ? point.y.value : point.x.value; 4868 inlineCoord += aXOffset; 4869 4870 PropertyProvider provider(*this); 4871 4872 // offset is given in terms of left side of string 4873 if (rtl) { 4874 // Bug 581092 - don't use rounded pixel width to advance to 4875 // right-hand end of run, because this will cause different 4876 // glyph positioning for LTR vs RTL drawing of the same 4877 // glyph string on OS X and DWrite where textrun widths may 4878 // involve fractional pixels. 4879 gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText( 4880 mDoMeasureBoundingBox ? gfxFont::TIGHT_INK_EXTENTS 4881 : gfxFont::LOOSE_INK_EXTENTS, 4882 mDrawTarget, &provider); 4883 inlineCoord += textRunMetrics.mAdvanceWidth; 4884 // old code was: 4885 // point.x += width * mAppUnitsPerDevPixel; 4886 // TODO: restore this if/when we move to fractional coords 4887 // throughout the text layout process 4888 } 4889 4890 mCtx->EnsureTarget(); 4891 if (!mCtx->IsTargetValid()) { 4892 return; 4893 } 4894 4895 // Defer the tasks to gfxTextRun which will handle color/svg-in-ot fonts 4896 // appropriately. 4897 StrokeOptions strokeOpts; 4898 DrawOptions drawOpts; 4899 Style style = (mOp == CanvasRenderingContext2D::TextDrawOperation::FILL) 4900 ? Style::FILL 4901 : Style::STROKE; 4902 const ContextState& state = mCtx->CurrentState(); 4903 4904 gfx::Rect bounds; 4905 if (mCtx->NeedToCalculateBounds()) { 4906 bounds = ToRect(mBoundingBox); 4907 bounds.MoveBy(mPt / mAppUnitsPerDevPixel); 4908 if (style == Style::STROKE) { 4909 bounds.Inflate(state.lineWidth / 2.0); 4910 } 4911 bounds = mDrawTarget->GetTransform().TransformBounds(bounds); 4912 } 4913 4914 AdjustedTarget target(mCtx, bounds.IsEmpty() ? nullptr : &bounds, false); 4915 if (!target) { 4916 return; 4917 } 4918 4919 gfxContext thebes(target, /* aPreserveTransform */ true); 4920 gfxTextRun::DrawParams params(&thebes, mPaletteCache); 4921 4922 params.allowGDI = false; 4923 4924 if (state.StyleIsColor(style)) { // Color 4925 nscolor fontColor = state.colorStyles[style]; 4926 if (style == Style::FILL) { 4927 params.context->SetColor(sRGBColor::FromABGR(fontColor)); 4928 } else { 4929 params.textStrokeColor = fontColor; 4930 } 4931 } else { 4932 if (state.gradientStyles[style]) { // Gradient 4933 pattern = GetGradientFor(style); 4934 } else if (state.patternStyles[style]) { // Pattern 4935 pattern = GetPatternFor(style); 4936 } else { 4937 MOZ_ASSERT(false, "Should never reach here."); 4938 return; 4939 } 4940 MOZ_ASSERT(pattern, "No valid pattern."); 4941 4942 if (style == Style::FILL) { 4943 params.context->SetPattern(pattern); 4944 } else { 4945 params.textStrokePattern = pattern; 4946 } 4947 } 4948 4949 drawOpts.mAlpha = state.globalAlpha; 4950 drawOpts.mCompositionOp = target.UsedOperation(); 4951 if (!mCtx->IsTargetValid()) { 4952 return; 4953 } 4954 4955 params.drawOpts = &drawOpts; 4956 params.provider = &provider; 4957 4958 if (style == Style::STROKE) { 4959 strokeOpts.mLineWidth = state.lineWidth; 4960 strokeOpts.mLineJoin = CanvasToGfx(state.lineJoin); 4961 strokeOpts.mLineCap = CanvasToGfx(state.lineCap); 4962 strokeOpts.mMiterLimit = state.miterLimit; 4963 strokeOpts.mDashLength = state.dash.Length(); 4964 strokeOpts.mDashPattern = 4965 (strokeOpts.mDashLength > 0) ? state.dash.Elements() : 0; 4966 strokeOpts.mDashOffset = state.dashOffset; 4967 4968 params.drawMode = DrawMode::GLYPH_STROKE; 4969 params.strokeOpts = &strokeOpts; 4970 } 4971 4972 mTextRun->Draw(gfxTextRun::Range(mTextRun.get()), point, params); 4973 } 4974 4975 // current text run 4976 RefPtr<gfxTextRun> mTextRun; 4977 4978 // pointer to a screen reference context used to measure text and such 4979 RefPtr<DrawTarget> mDrawTarget; 4980 4981 // Pointer to the draw target we should fill our text to 4982 CanvasRenderingContext2D* mCtx = nullptr; 4983 4984 // position of the left side of the string, alphabetic baseline 4985 gfx::Point mPt; 4986 4987 // current font 4988 gfxFontGroup* mFontgrp = nullptr; 4989 4990 // palette cache for COLR font rendering 4991 mozilla::gfx::PaletteCache& mPaletteCache; 4992 4993 // spacing adjustments to be applied 4994 gfx::Float mLetterSpacing = 0.0f; 4995 gfx::Float mWordSpacing = 0.0f; 4996 4997 // to record any unsupported characters found in the text, 4998 // and notify front-end if it is interested 4999 UniquePtr<gfxMissingFontRecorder> mMissingFonts; 5000 5001 // dev pixel conversion factor 5002 int32_t mAppUnitsPerDevPixel = 0; 5003 5004 // operation (fill or stroke) 5005 CanvasRenderingContext2D::TextDrawOperation mOp = 5006 CanvasRenderingContext2D::TextDrawOperation::FILL; 5007 5008 // union of bounding boxes of all runs, needed for shadows 5009 gfxRect mBoundingBox; 5010 5011 // flags to use when creating textrun, based on CSS style 5012 gfx::ShapedTextFlags mTextRunFlags = gfx::ShapedTextFlags(); 5013 5014 // Count of how many times SetText has been called on this processor. 5015 uint32_t mSetTextCount = 0; 5016 5017 // true iff the bounding box should be measured 5018 bool mDoMeasureBoundingBox = false; 5019 5020 // true if future SetText calls should be ignored 5021 bool mIgnoreSetText = false; 5022 }; 5023 5024 UniquePtr<TextMetrics> CanvasRenderingContext2D::DrawOrMeasureText( 5025 const nsAString& aText, float aX, float aY, 5026 const Optional<double>& aMaxWidth, TextDrawOperation aOp, 5027 ErrorResult& aError) { 5028 RefPtr<gfxFontGroup> currentFontStyle = GetCurrentFontStyle(); 5029 if (NS_WARN_IF(!currentFontStyle)) { 5030 aError = NS_ERROR_FAILURE; 5031 return nullptr; 5032 } 5033 5034 RefPtr<PresShell> presShell = GetPresShell(); 5035 RefPtr<Document> document = presShell ? presShell->GetDocument() : nullptr; 5036 5037 // replace all the whitespace characters with U+0020 SPACE 5038 nsAutoString textToDraw(aText); 5039 TextReplaceWhitespaceCharacters(textToDraw); 5040 5041 // According to spec, the API should return an empty array if maxWidth was 5042 // provided but is less than or equal to zero or equal to NaN. 5043 if (aMaxWidth.WasPassed() && 5044 (aMaxWidth.Value() <= 0 || std::isnan(aMaxWidth.Value()))) { 5045 textToDraw.Truncate(); 5046 } 5047 5048 RefPtr<const ComputedStyle> canvasStyle; 5049 if (mCanvasElement) { 5050 canvasStyle = nsComputedDOMStyle::GetComputedStyle(mCanvasElement); 5051 } 5052 5053 // Get text direction, either from the property or inherited from context. 5054 const ContextState& state = CurrentState(); 5055 bool isRTL; 5056 switch (state.textDirection) { 5057 case CanvasDirection::Ltr: 5058 isRTL = false; 5059 break; 5060 case CanvasDirection::Rtl: 5061 isRTL = true; 5062 break; 5063 case CanvasDirection::Inherit: 5064 if (canvasStyle) { 5065 isRTL = 5066 canvasStyle->StyleVisibility()->mDirection == StyleDirection::Rtl; 5067 } else if (document) { 5068 isRTL = GET_BIDI_OPTION_DIRECTION(document->GetBidiOptions()) == 5069 IBMBIDI_TEXTDIRECTION_RTL; 5070 } else { 5071 isRTL = false; 5072 } 5073 break; 5074 default: 5075 MOZ_CRASH("unknown direction!"); 5076 } 5077 5078 // This is only needed to know if we can know the drawing bounding box easily. 5079 const bool doCalculateBounds = NeedToCalculateBounds(); 5080 if (presShell && presShell->IsDestroying()) { 5081 aError = NS_ERROR_FAILURE; 5082 return nullptr; 5083 } 5084 5085 nsPresContext* presContext = 5086 presShell ? presShell->GetPresContext() : nullptr; 5087 5088 if (presContext) { 5089 // ensure user font set is up to date 5090 presContext->Document()->FlushUserFontSet(); 5091 currentFontStyle->SetUserFontSet(presContext->GetUserFontSet()); 5092 } 5093 5094 if (currentFontStyle->GetStyle()->size == 0.0F) { 5095 aError = NS_OK; 5096 if (aOp == TextDrawOperation::MEASURE) { 5097 return MakeUnique<TextMetrics>(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 5098 0.0, 0.0, 0.0, 0.0); 5099 } 5100 return nullptr; 5101 } 5102 5103 if (!std::isfinite(aX) || !std::isfinite(aY)) { 5104 aError = NS_OK; 5105 // This may not be correct - what should TextMetrics contain in the case of 5106 // infinite width or height? 5107 if (aOp == TextDrawOperation::MEASURE) { 5108 return MakeUnique<TextMetrics>(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 5109 0.0, 0.0, 0.0, 0.0); 5110 } 5111 return nullptr; 5112 } 5113 5114 CanvasBidiProcessor processor(mPaletteCache); 5115 5116 // If we don't have a ComputedStyle, we can't set up vertical-text flags 5117 // (for now, at least; perhaps we need new Canvas API to control this). 5118 processor.mTextRunFlags = 5119 canvasStyle ? nsLayoutUtils::GetTextRunFlagsForStyle( 5120 canvasStyle, presContext, canvasStyle->StyleFont(), 5121 canvasStyle->StyleText(), 0) 5122 : gfx::ShapedTextFlags(); 5123 5124 switch (state.textRendering) { 5125 case CanvasTextRendering::Auto: 5126 if (state.fontFont.size.ToCSSPixels() < 5127 StaticPrefs::browser_display_auto_quality_min_font_size()) { 5128 processor.mTextRunFlags |= gfx::ShapedTextFlags::TEXT_OPTIMIZE_SPEED; 5129 } else { 5130 processor.mTextRunFlags &= ~gfx::ShapedTextFlags::TEXT_OPTIMIZE_SPEED; 5131 } 5132 break; 5133 case CanvasTextRendering::OptimizeSpeed: 5134 processor.mTextRunFlags |= gfx::ShapedTextFlags::TEXT_OPTIMIZE_SPEED; 5135 break; 5136 case CanvasTextRendering::OptimizeLegibility: 5137 case CanvasTextRendering::GeometricPrecision: 5138 processor.mTextRunFlags &= ~gfx::ShapedTextFlags::TEXT_OPTIMIZE_SPEED; 5139 break; 5140 default: 5141 MOZ_CRASH("unknown textRendering!"); 5142 } 5143 5144 GetAppUnitsValues(&processor.mAppUnitsPerDevPixel, nullptr); 5145 processor.mPt = gfx::Point(aX, aY); 5146 processor.mDrawTarget = gfxPlatform::ThreadLocalScreenReferenceDrawTarget(); 5147 5148 // If we don't have a target then we don't have a transform. A target won't 5149 // be needed in the case where we're measuring the text size. This allows 5150 // to avoid creating a target if it's only being used to measure text sizes. 5151 processor.mDrawTarget->SetTransform(GetCurrentTransform()); 5152 processor.mCtx = this; 5153 processor.mOp = aOp; 5154 processor.mBoundingBox = gfxRect(0, 0, 0, 0); 5155 processor.mDoMeasureBoundingBox = doCalculateBounds || 5156 !mIsEntireFrameInvalid || 5157 aOp == TextDrawOperation::MEASURE; 5158 processor.mFontgrp = currentFontStyle; 5159 5160 if (state.letterSpacing != 0.0 || state.wordSpacing != 0.0) { 5161 processor.mLetterSpacing = 5162 state.letterSpacing * processor.mAppUnitsPerDevPixel; 5163 processor.mWordSpacing = state.wordSpacing * processor.mAppUnitsPerDevPixel; 5164 processor.mTextRunFlags |= gfx::ShapedTextFlags::TEXT_ENABLE_SPACING; 5165 if (state.letterSpacing != 0.0) { 5166 processor.mTextRunFlags |= 5167 gfx::ShapedTextFlags::TEXT_DISABLE_OPTIONAL_LIGATURES; 5168 } 5169 } 5170 5171 nscoord totalWidthCoord; 5172 5173 processor.mFontgrp 5174 ->UpdateUserFonts(); // ensure user font generation is current 5175 RefPtr<gfxFont> font = processor.mFontgrp->GetFirstValidFont(); 5176 const gfxFont::Metrics& fontMetrics = 5177 font->GetMetrics(nsFontMetrics::eHorizontal); 5178 5179 // calls bidi algo twice since it needs the full text width and the 5180 // bounding boxes before rendering anything 5181 aError = nsBidiPresUtils::ProcessText( 5182 textToDraw.get(), textToDraw.Length(), 5183 isRTL ? intl::BidiEmbeddingLevel::RTL() : intl::BidiEmbeddingLevel::LTR(), 5184 presContext, processor, nsBidiPresUtils::MODE_MEASURE, nullptr, 0, 5185 &totalWidthCoord, mBidiEngine); 5186 if (aError.Failed()) { 5187 return nullptr; 5188 } 5189 5190 // If ProcessText only called SetText once, we're dealing with a single run, 5191 // and so we don't need to repeat SetText and textRun construction at drawing 5192 // time below; we can just re-use the existing textRun. 5193 if (processor.mSetTextCount == 1) { 5194 processor.mIgnoreSetText = true; 5195 } 5196 5197 float totalWidth = float(totalWidthCoord) / processor.mAppUnitsPerDevPixel; 5198 5199 // offset pt.x based on text align 5200 gfxFloat anchorX; 5201 5202 if (state.textAlign == CanvasTextAlign::Center) { 5203 anchorX = .5; 5204 } else if (state.textAlign == CanvasTextAlign::Left || 5205 (!isRTL && state.textAlign == CanvasTextAlign::Start) || 5206 (isRTL && state.textAlign == CanvasTextAlign::End)) { 5207 anchorX = 0; 5208 } else { 5209 anchorX = 1; 5210 } 5211 5212 float offsetX = anchorX * totalWidth; 5213 processor.mPt.x -= offsetX; 5214 5215 gfx::ShapedTextFlags runOrientation = 5216 (processor.mTextRunFlags & gfx::ShapedTextFlags::TEXT_ORIENT_MASK); 5217 nsFontMetrics::FontOrientation fontOrientation = 5218 (runOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED || 5219 runOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT) 5220 ? nsFontMetrics::eVertical 5221 : nsFontMetrics::eHorizontal; 5222 5223 // offset pt.y (or pt.x, for vertical text) based on text baseline 5224 gfxFloat baselineAnchor; 5225 5226 switch (state.textBaseline) { 5227 case CanvasTextBaseline::Hanging: 5228 baselineAnchor = font->GetBaselines(fontOrientation).mHanging; 5229 break; 5230 case CanvasTextBaseline::Top: 5231 baselineAnchor = fontMetrics.emAscent; 5232 break; 5233 case CanvasTextBaseline::Middle: 5234 baselineAnchor = (fontMetrics.emAscent - fontMetrics.emDescent) * .5f; 5235 break; 5236 case CanvasTextBaseline::Alphabetic: 5237 baselineAnchor = font->GetBaselines(fontOrientation).mAlphabetic; 5238 break; 5239 case CanvasTextBaseline::Ideographic: 5240 baselineAnchor = font->GetBaselines(fontOrientation).mIdeographic; 5241 break; 5242 case CanvasTextBaseline::Bottom: 5243 baselineAnchor = -fontMetrics.emDescent; 5244 break; 5245 default: 5246 MOZ_CRASH("GFX: unexpected TextBaseline"); 5247 } 5248 5249 // We can't query the textRun directly, as it may not have been created yet; 5250 // so instead we check the flags that will be used to initialize it. 5251 if (runOrientation != gfx::ShapedTextFlags::TEXT_ORIENT_HORIZONTAL) { 5252 if (fontOrientation == nsFontMetrics::eVertical) { 5253 // Adjust to account for mTextRun being shaped using center baseline 5254 // rather than alphabetic. 5255 baselineAnchor -= (fontMetrics.emAscent - fontMetrics.emDescent) * .5f; 5256 } 5257 processor.mPt.x -= baselineAnchor; 5258 } else { 5259 processor.mPt.y += baselineAnchor; 5260 } 5261 5262 // if only measuring, don't need to do any more work 5263 if (aOp == TextDrawOperation::MEASURE) { 5264 aError = NS_OK; 5265 // Note that actualBoundingBoxLeft measures the distance in the leftward 5266 // direction, so its sign is reversed from our usual physical coordinates. 5267 double actualBoundingBoxLeft = offsetX - processor.mBoundingBox.X(); 5268 double actualBoundingBoxRight = processor.mBoundingBox.XMost() - offsetX; 5269 double actualBoundingBoxAscent = 5270 -processor.mBoundingBox.Y() - baselineAnchor; 5271 double actualBoundingBoxDescent = 5272 processor.mBoundingBox.YMost() + baselineAnchor; 5273 auto baselines = font->GetBaselines(fontOrientation); 5274 return MakeUnique<TextMetrics>( 5275 totalWidth, actualBoundingBoxLeft, actualBoundingBoxRight, 5276 fontMetrics.maxAscent - baselineAnchor, // fontBBAscent 5277 fontMetrics.maxDescent + baselineAnchor, // fontBBDescent 5278 actualBoundingBoxAscent, actualBoundingBoxDescent, 5279 fontMetrics.emAscent - baselineAnchor, // emHeightAscent 5280 fontMetrics.emDescent + baselineAnchor, // emHeightDescent 5281 baselines.mHanging - baselineAnchor, 5282 baselines.mAlphabetic - baselineAnchor, 5283 baselines.mIdeographic - baselineAnchor); 5284 } 5285 5286 // If we did not actually calculate bounds, set up a simple bounding box 5287 // based on the text position and advance. 5288 if (!doCalculateBounds) { 5289 processor.mBoundingBox.width = totalWidth; 5290 processor.mBoundingBox.MoveBy(gfxPoint(processor.mPt.x, processor.mPt.y)); 5291 } 5292 5293 processor.mPt.x *= processor.mAppUnitsPerDevPixel; 5294 processor.mPt.y *= processor.mAppUnitsPerDevPixel; 5295 5296 if (!EnsureTarget(aError)) { 5297 return nullptr; 5298 } 5299 5300 MOZ_ASSERT(IsTargetValid()); 5301 5302 Matrix oldTransform = mTarget->GetTransform(); 5303 bool restoreTransform = false; 5304 // if text is over aMaxWidth, then scale the text horizontally such that its 5305 // width is precisely aMaxWidth 5306 if (aMaxWidth.WasPassed() && aMaxWidth.Value() > 0 && 5307 totalWidth > aMaxWidth.Value()) { 5308 Matrix newTransform = oldTransform; 5309 5310 // Translate so that the anchor point is at 0,0, then scale and then 5311 // translate back. 5312 newTransform.PreTranslate(aX, 0); 5313 newTransform.PreScale(aMaxWidth.Value() / totalWidth, 1); 5314 newTransform.PreTranslate(-aX, 0); 5315 /* we do this to avoid an ICE in the android compiler */ 5316 Matrix androidCompilerBug = newTransform; 5317 mTarget->SetTransform(androidCompilerBug); 5318 restoreTransform = true; 5319 } 5320 5321 // save the previous bounding box 5322 gfxRect boundingBox = processor.mBoundingBox; 5323 5324 // don't ever need to measure the bounding box twice 5325 processor.mDoMeasureBoundingBox = false; 5326 5327 aError = nsBidiPresUtils::ProcessText( 5328 textToDraw.get(), textToDraw.Length(), 5329 isRTL ? intl::BidiEmbeddingLevel::RTL() : intl::BidiEmbeddingLevel::LTR(), 5330 presContext, processor, nsBidiPresUtils::MODE_DRAW, nullptr, 0, nullptr, 5331 mBidiEngine); 5332 5333 if (aError.Failed()) { 5334 return nullptr; 5335 } 5336 5337 if (restoreTransform) { 5338 mTarget->SetTransform(oldTransform); 5339 } 5340 5341 if (aOp == CanvasRenderingContext2D::TextDrawOperation::FILL && 5342 !doCalculateBounds) { 5343 RedrawUser(boundingBox); 5344 } else { 5345 Redraw(); 5346 } 5347 5348 aError = NS_OK; 5349 return nullptr; 5350 } 5351 5352 gfxFontGroup* CanvasRenderingContext2D::GetCurrentFontStyle() { 5353 // Use lazy (re)initialization for the fontGroup since it's rather expensive. 5354 5355 RefPtr<PresShell> presShell = GetPresShell(); 5356 nsPresContext* presContext = 5357 presShell ? presShell->GetPresContext() : nullptr; 5358 5359 FontVisibilityProvider* visProvider = nullptr; 5360 if (presContext) { 5361 visProvider = presContext; 5362 } else { 5363 visProvider = mOffscreenCanvas; 5364 } 5365 5366 // If we have a cached fontGroup, check that it is valid for the current 5367 // prescontext or canvas; if not, we need to discard and re-create it. 5368 RefPtr<gfxFontGroup>& fontGroup = CurrentState().fontGroup; 5369 if (fontGroup) { 5370 if (fontGroup->GetFontVisibilityProvider() != visProvider) { 5371 fontGroup = nullptr; 5372 } 5373 } 5374 5375 if (!fontGroup) { 5376 ErrorResult err; 5377 constexpr auto kDefaultFontStyle = "10px sans-serif"_ns; 5378 const float kDefaultFontSize = 10.0; 5379 // If the font has already been set, we're re-creating the fontGroup 5380 // and should re-use the existing font attribute; if not, we initialize 5381 // it to the canvas default. 5382 const nsCString& currentFont = CurrentState().font; 5383 bool fontUpdated = SetFontInternal( 5384 currentFont.IsEmpty() ? kDefaultFontStyle : currentFont, err); 5385 if (err.Failed() || !fontUpdated) { 5386 err.SuppressException(); 5387 // XXX Should we get a default lang from the prescontext or something? 5388 nsAtom* language = nsGkAtoms::x_western; 5389 bool explicitLanguage = false; 5390 gfxFontStyle style; 5391 style.size = kDefaultFontSize; 5392 int32_t perDevPixel, perCSSPixel; 5393 GetAppUnitsValues(&perDevPixel, &perCSSPixel); 5394 gfxFloat devToCssSize = gfxFloat(perDevPixel) / gfxFloat(perCSSPixel); 5395 const auto* sans = 5396 Servo_FontFamily_Generic(StyleGenericFontFamily::SansSerif); 5397 fontGroup = new gfxFontGroup( 5398 visProvider, sans->families, &style, language, explicitLanguage, 5399 presContext ? presContext->GetTextPerfMetrics() : nullptr, nullptr, 5400 devToCssSize, StyleFontVariantEmoji::Normal); 5401 if (fontGroup) { 5402 CurrentState().font = kDefaultFontStyle; 5403 } else { 5404 NS_ERROR("Default canvas font is invalid"); 5405 } 5406 } 5407 } 5408 5409 return fontGroup; 5410 } 5411 5412 // 5413 // line dash styles 5414 // 5415 5416 void CanvasRenderingContext2D::SetLineDash(const Sequence<double>& aSegments, 5417 ErrorResult& aRv) { 5418 nsTArray<mozilla::gfx::Float> dash; 5419 5420 for (uint32_t x = 0; x < aSegments.Length(); x++) { 5421 if (aSegments[x] < 0.0) { 5422 // Pattern elements must be finite "numbers" >= 0, with "finite" 5423 // taken care of by WebIDL 5424 return; 5425 } 5426 5427 if (!dash.AppendElement(aSegments[x], fallible)) { 5428 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 5429 return; 5430 } 5431 } 5432 if (aSegments.Length() % 5433 2) { // If the number of elements is odd, concatenate again 5434 for (uint32_t x = 0; x < aSegments.Length(); x++) { 5435 if (!dash.AppendElement(aSegments[x], fallible)) { 5436 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 5437 return; 5438 } 5439 } 5440 } 5441 5442 CurrentState().dash = std::move(dash); 5443 } 5444 5445 void CanvasRenderingContext2D::GetLineDash(nsTArray<double>& aSegments) const { 5446 const nsTArray<mozilla::gfx::Float>& dash = CurrentState().dash; 5447 aSegments.Clear(); 5448 5449 for (uint32_t x = 0; x < dash.Length(); x++) { 5450 aSegments.AppendElement(dash[x]); 5451 } 5452 } 5453 5454 void CanvasRenderingContext2D::SetLineDashOffset(double aOffset) { 5455 CurrentState().dashOffset = aOffset; 5456 } 5457 5458 double CanvasRenderingContext2D::LineDashOffset() const { 5459 return CurrentState().dashOffset; 5460 } 5461 5462 bool CanvasRenderingContext2D::IsPointInPath(JSContext* aCx, double aX, 5463 double aY, 5464 const CanvasWindingRule& aWinding, 5465 nsIPrincipal& aSubjectPrincipal) { 5466 if (!FloatValidate(aX, aY)) { 5467 return false; 5468 } 5469 5470 // Check for site-specific permission and return false if no permission. 5471 if (mCanvasElement) { 5472 nsCOMPtr<Document> ownerDoc = mCanvasElement->OwnerDoc(); 5473 if (!CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx, 5474 &aSubjectPrincipal)) { 5475 return false; 5476 } 5477 } else if (mOffscreenCanvas && mOffscreenCanvas->ShouldResistFingerprinting( 5478 RFPTarget::CanvasImageExtractionPrompt)) { 5479 return false; 5480 } 5481 5482 EnsureUserSpacePath(aWinding); 5483 if (!mPath) { 5484 return false; 5485 } 5486 5487 return mPath->ContainsPoint(Point(aX, aY), GetCurrentTransform()); 5488 } 5489 5490 bool CanvasRenderingContext2D::IsPointInPath(JSContext* aCx, 5491 const CanvasPath& aPath, double aX, 5492 double aY, 5493 const CanvasWindingRule& aWinding, 5494 nsIPrincipal& aSubjectPrincipal) { 5495 if (!FloatValidate(aX, aY)) { 5496 return false; 5497 } 5498 5499 if (!EnsureBufferProvider()) { 5500 return false; 5501 } 5502 5503 RefPtr<gfx::Path> tempPath = aPath.GetPath(aWinding, mPathType); 5504 5505 return tempPath->ContainsPoint(Point(aX, aY), GetCurrentTransform()); 5506 } 5507 5508 bool CanvasRenderingContext2D::IsPointInStroke( 5509 JSContext* aCx, double aX, double aY, nsIPrincipal& aSubjectPrincipal) { 5510 if (!FloatValidate(aX, aY)) { 5511 return false; 5512 } 5513 5514 // Check for site-specific permission and return false if no permission. 5515 if (mCanvasElement) { 5516 nsCOMPtr<Document> ownerDoc = mCanvasElement->OwnerDoc(); 5517 if (!CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx, 5518 &aSubjectPrincipal)) { 5519 return false; 5520 } 5521 } else if (mOffscreenCanvas && mOffscreenCanvas->ShouldResistFingerprinting( 5522 RFPTarget::CanvasImageExtractionPrompt)) { 5523 return false; 5524 } 5525 5526 EnsureUserSpacePath(); 5527 if (!mPath) { 5528 return false; 5529 } 5530 5531 const ContextState& state = CurrentState(); 5532 5533 StrokeOptions strokeOptions(state.lineWidth, CanvasToGfx(state.lineJoin), 5534 CanvasToGfx(state.lineCap), state.miterLimit, 5535 state.dash.Length(), state.dash.Elements(), 5536 state.dashOffset); 5537 5538 return mPath->StrokeContainsPoint(strokeOptions, Point(aX, aY), 5539 GetCurrentTransform()); 5540 } 5541 5542 bool CanvasRenderingContext2D::IsPointInStroke( 5543 JSContext* aCx, const CanvasPath& aPath, double aX, double aY, 5544 nsIPrincipal& aSubjectPrincipal) { 5545 if (!FloatValidate(aX, aY)) { 5546 return false; 5547 } 5548 5549 if (!EnsureBufferProvider()) { 5550 return false; 5551 } 5552 5553 RefPtr<gfx::Path> tempPath = 5554 aPath.GetPath(CanvasWindingRule::Nonzero, mPathType); 5555 5556 const ContextState& state = CurrentState(); 5557 5558 StrokeOptions strokeOptions(state.lineWidth, CanvasToGfx(state.lineJoin), 5559 CanvasToGfx(state.lineCap), state.miterLimit, 5560 state.dash.Length(), state.dash.Elements(), 5561 state.dashOffset); 5562 5563 return tempPath->StrokeContainsPoint(strokeOptions, Point(aX, aY), 5564 GetCurrentTransform()); 5565 } 5566 5567 // Returns a surface that contains only the part needed to draw aSourceRect. 5568 // On entry, aSourceRect is relative to aSurface, and on return aSourceRect is 5569 // relative to the returned surface. 5570 static already_AddRefed<SourceSurface> ExtractSubrect(SourceSurface* aSurface, 5571 gfx::Rect* aSourceRect, 5572 DrawTarget* aTargetDT) { 5573 gfx::Rect roundedOutSourceRect = *aSourceRect; 5574 roundedOutSourceRect.RoundOut(); 5575 gfx::IntRect roundedOutSourceRectInt; 5576 if (!roundedOutSourceRect.ToIntRect(&roundedOutSourceRectInt) || 5577 roundedOutSourceRectInt.IsEmpty()) { 5578 RefPtr<SourceSurface> surface(aSurface); 5579 return surface.forget(); 5580 } 5581 5582 // Try to extract an optimized sub-surface. 5583 if (RefPtr<SourceSurface> surface = 5584 aSurface->ExtractSubrect(roundedOutSourceRectInt)) { 5585 *aSourceRect -= roundedOutSourceRect.TopLeft(); 5586 return surface.forget(); 5587 } 5588 5589 RefPtr<DrawTarget> subrectDT = aTargetDT->CreateSimilarDrawTarget( 5590 roundedOutSourceRectInt.Size(), SurfaceFormat::B8G8R8A8); 5591 5592 if (subrectDT) { 5593 // See bug 1524554. 5594 subrectDT->ClearRect(gfx::Rect()); 5595 } 5596 5597 if (!subrectDT || !subrectDT->IsValid()) { 5598 RefPtr<SourceSurface> surface(aSurface); 5599 return surface.forget(); 5600 } 5601 5602 *aSourceRect -= roundedOutSourceRect.TopLeft(); 5603 5604 subrectDT->CopySurface(aSurface, roundedOutSourceRectInt, IntPoint()); 5605 return subrectDT->Snapshot(); 5606 } 5607 5608 // 5609 // image 5610 // 5611 5612 static void ClipImageDimension(double& aSourceCoord, double& aSourceSize, 5613 double& aClipOriginCoord, double& aClipSize, 5614 double& aDestCoord, double& aDestSize) { 5615 double scale = aDestSize / aSourceSize; 5616 double relativeCoord = aSourceCoord - aClipOriginCoord; 5617 if (relativeCoord < 0.0) { 5618 double destEnd = aDestCoord + aDestSize; 5619 aDestCoord -= relativeCoord * scale; 5620 aDestSize = destEnd - aDestCoord; 5621 aSourceSize += relativeCoord; 5622 aSourceCoord = aClipOriginCoord; 5623 relativeCoord = 0.0; 5624 } 5625 double delta = aClipSize - (relativeCoord + aSourceSize); 5626 if (delta < 0.0) { 5627 aDestSize += delta * scale; 5628 aSourceSize = aClipSize - relativeCoord; 5629 } 5630 } 5631 5632 // Acts like nsLayoutUtils::SurfaceFromElement, but it'll attempt 5633 // to pull a SourceSurface from our cache. This allows us to avoid 5634 // reoptimizing surfaces if content and canvas backends are different. 5635 SurfaceFromElementResult CanvasRenderingContext2D::CachedSurfaceFromElement( 5636 Element* aElement) { 5637 SurfaceFromElementResult res; 5638 nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(aElement); 5639 if (!imageLoader) { 5640 return res; 5641 } 5642 5643 nsCOMPtr<imgIRequest> imgRequest; 5644 imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, 5645 getter_AddRefs(imgRequest)); 5646 if (!imgRequest) { 5647 return res; 5648 } 5649 5650 uint32_t status = 0; 5651 if (NS_FAILED(imgRequest->GetImageStatus(&status)) || 5652 !(status & imgIRequest::STATUS_LOAD_COMPLETE)) { 5653 return res; 5654 } 5655 5656 nsCOMPtr<nsIPrincipal> principal; 5657 if (NS_FAILED(imgRequest->GetImagePrincipal(getter_AddRefs(principal))) || 5658 !principal) { 5659 return res; 5660 } 5661 5662 if (NS_FAILED(imgRequest->GetHadCrossOriginRedirects( 5663 &res.mHadCrossOriginRedirects))) { 5664 return res; 5665 } 5666 5667 res.mSourceSurface = CanvasImageCache::LookupAllCanvas(aElement, mTarget); 5668 if (!res.mSourceSurface) { 5669 return res; 5670 } 5671 5672 res.mCORSUsed = nsLayoutUtils::ImageRequestUsesCORS(imgRequest); 5673 res.mSize = res.mIntrinsicSize = res.mSourceSurface->GetSize(); 5674 res.mPrincipal = std::move(principal); 5675 res.mImageRequest = std::move(imgRequest); 5676 res.mIsWriteOnly = CheckWriteOnlySecurity(res.mCORSUsed, res.mPrincipal, 5677 res.mHadCrossOriginRedirects); 5678 5679 return res; 5680 } 5681 5682 static void SwapScaleWidthHeightForRotation(gfx::Rect& aRect, 5683 VideoRotation aDegrees) { 5684 if (aDegrees == VideoRotation::kDegree_90 || 5685 aDegrees == VideoRotation::kDegree_270) { 5686 std::swap(aRect.width, aRect.height); 5687 } 5688 } 5689 5690 static Matrix ComputeRotationMatrix(gfxFloat aRotatedWidth, 5691 gfxFloat aRotatedHeight, 5692 VideoRotation aDegrees) { 5693 Point shiftVideoCenterToOrigin(-aRotatedWidth / 2.0, -aRotatedHeight / 2.0); 5694 if (aDegrees == VideoRotation::kDegree_90 || 5695 aDegrees == VideoRotation::kDegree_270) { 5696 std::swap(shiftVideoCenterToOrigin.x, shiftVideoCenterToOrigin.y); 5697 } 5698 auto angle = static_cast<double>(aDegrees) / 180.0 * M_PI; 5699 Matrix rotation = Matrix::Rotation(static_cast<gfx::Float>(angle)); 5700 Point shiftLeftTopToOrigin(aRotatedWidth / 2.0, aRotatedHeight / 2.0); 5701 return rotation.PreTranslate(shiftVideoCenterToOrigin) 5702 .PostTranslate(shiftLeftTopToOrigin); 5703 } 5704 5705 // - 5706 5707 bool ValidSurfaceDescriptorForRemoteCanvas2d( 5708 const layers::SurfaceDescriptor& aSd, 5709 Maybe<layers::SurfaceDescriptor>* aResultSd) { 5710 if (aSd.type() != layers::SurfaceDescriptor::TSurfaceDescriptorGPUVideo) { 5711 return false; 5712 } 5713 5714 const auto& sdv = aSd.get_SurfaceDescriptorGPUVideo(); 5715 if (sdv.type() != 5716 layers::SurfaceDescriptorGPUVideo::TSurfaceDescriptorRemoteDecoder) { 5717 return false; 5718 } 5719 const auto& sdrd = sdv.get_SurfaceDescriptorRemoteDecoder(); 5720 const auto& subdesc = sdrd.subdesc(); 5721 switch (subdesc.type()) { 5722 case layers::RemoteDecoderVideoSubDescriptor::Tnull_t: 5723 break; 5724 #ifdef XP_MACOSX 5725 case layers::RemoteDecoderVideoSubDescriptor:: 5726 TSurfaceDescriptorMacIOSurface: { 5727 const auto& ssd = subdesc.get_SurfaceDescriptorMacIOSurface(); 5728 if (ssd.gpuFence()) { 5729 return false; 5730 } 5731 break; 5732 } 5733 #endif 5734 #ifdef XP_WIN 5735 case layers::RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorD3D10: { 5736 if (!StaticPrefs::gfx_canvas_remote_use_draw_image_fast_path_d3d()) { 5737 return false; 5738 } 5739 const auto& ssd = subdesc.get_SurfaceDescriptorD3D10(); 5740 if (aResultSd) { 5741 *aResultSd = Some(aSd); 5742 // Not IPC-able, but it's just an optimization to have this. 5743 aResultSd->ref() 5744 .get_SurfaceDescriptorGPUVideo() 5745 .get_SurfaceDescriptorRemoteDecoder() 5746 .subdesc() 5747 .get_SurfaceDescriptorD3D10() 5748 .handle() = nullptr; 5749 } else if (ssd.handle()) { 5750 return false; 5751 } 5752 return true; 5753 } 5754 #endif 5755 default: 5756 return false; 5757 } 5758 if (aResultSd) { 5759 *aResultSd = Some(aSd); 5760 } 5761 return true; 5762 } 5763 5764 static Maybe<layers::SurfaceDescriptor> 5765 MaybeGetSurfaceDescriptorForRemoteCanvas( 5766 const SurfaceFromElementResult& aResult) { 5767 if (!StaticPrefs::gfx_canvas_remote_use_draw_image_fast_path()) { 5768 return Nothing(); 5769 } 5770 5771 if (!aResult.mLayersImage) { 5772 return Nothing(); 5773 } 5774 5775 if (const auto sd = aResult.mLayersImage->GetDesc()) { 5776 Maybe<layers::SurfaceDescriptor> result; 5777 if (ValidSurfaceDescriptorForRemoteCanvas2d(*sd, &result)) { 5778 return result; 5779 } 5780 } 5781 return Nothing(); 5782 } 5783 5784 // drawImage(in HTMLImageElement image, in float dx, in float dy); 5785 // -- render image from 0,0 at dx,dy top-left coords 5786 // drawImage(in HTMLImageElement image, in float dx, in float dy, in float dw, 5787 // in float dh); 5788 // -- render image from 0,0 at dx,dy top-left coords clipping it to dw,dh 5789 // drawImage(in HTMLImageElement image, in float sx, in float sy, in float sw, 5790 // in float sh, in float dx, in float dy, in float dw, in float dh); 5791 // -- render the region defined by (sx,sy,sw,wh) in image-local space into the 5792 // region (dx,dy,dw,dh) on the canvas 5793 5794 // If only dx and dy are passed in then optional_argc should be 0. If only 5795 // dx, dy, dw and dh are passed in then optional_argc should be 2. The only 5796 // other valid value for optional_argc is 6 if sx, sy, sw, sh, dx, dy, dw and dh 5797 // are all passed in. 5798 5799 void CanvasRenderingContext2D::DrawImage(const CanvasImageSource& aImage, 5800 double aSx, double aSy, double aSw, 5801 double aSh, double aDx, double aDy, 5802 double aDw, double aDh, 5803 uint8_t aOptional_argc, 5804 ErrorResult& aError) { 5805 MOZ_ASSERT(aOptional_argc == 0 || aOptional_argc == 2 || aOptional_argc == 6); 5806 5807 if (!ValidateRect(aDx, aDy, aDw, aDh, true)) { 5808 return; 5809 } 5810 if (aOptional_argc == 6) { 5811 if (!ValidateRect(aSx, aSy, aSw, aSh, true)) { 5812 return; 5813 } 5814 } 5815 5816 RefPtr<SourceSurface> srcSurf; 5817 gfx::IntSize imgSize; 5818 gfx::IntSize intrinsicImgSize; 5819 Maybe<IntRect> cropRect; 5820 Element* element = nullptr; 5821 OffscreenCanvas* offscreenCanvas = nullptr; 5822 VideoFrame* videoFrame = nullptr; 5823 5824 if (!EnsureTarget(aError)) { 5825 return; 5826 } 5827 5828 MOZ_ASSERT(IsTargetValid()); 5829 5830 if (aImage.IsHTMLCanvasElement()) { 5831 HTMLCanvasElement* canvas = &aImage.GetAsHTMLCanvasElement(); 5832 element = canvas; 5833 CSSIntSize size = canvas->GetSize(); 5834 if (size.width == 0 || size.height == 0) { 5835 return aError.ThrowInvalidStateError("Passed-in canvas is empty"); 5836 } 5837 5838 if (canvas->IsWriteOnly()) { 5839 SetWriteOnly(); 5840 } 5841 } else if (aImage.IsOffscreenCanvas()) { 5842 offscreenCanvas = &aImage.GetAsOffscreenCanvas(); 5843 CSSIntSize size = offscreenCanvas->GetWidthHeight(); 5844 if (size.IsEmpty()) { 5845 return aError.ThrowInvalidStateError("Passed-in canvas is empty"); 5846 } 5847 5848 srcSurf = offscreenCanvas->GetSurfaceSnapshot(); 5849 if (srcSurf) { 5850 imgSize = intrinsicImgSize = srcSurf->GetSize(); 5851 } 5852 5853 if (offscreenCanvas->IsWriteOnly()) { 5854 SetWriteOnly(); 5855 } 5856 } else if (aImage.IsImageBitmap()) { 5857 ImageBitmap& imageBitmap = aImage.GetAsImageBitmap(); 5858 srcSurf = imageBitmap.PrepareForDrawTarget(mTarget); 5859 5860 if (!srcSurf) { 5861 if (imageBitmap.IsClosed()) { 5862 aError.ThrowInvalidStateError("Passed-in ImageBitmap is closed"); 5863 } 5864 return; 5865 } 5866 5867 if (imageBitmap.IsWriteOnly()) { 5868 SetWriteOnly(); 5869 } 5870 5871 imgSize = intrinsicImgSize = 5872 gfx::IntSize(imageBitmap.Width(), imageBitmap.Height()); 5873 } else if (aImage.IsVideoFrame()) { 5874 videoFrame = &aImage.GetAsVideoFrame(); 5875 } else { 5876 if (aImage.IsHTMLImageElement()) { 5877 HTMLImageElement* img = &aImage.GetAsHTMLImageElement(); 5878 element = img; 5879 } else if (aImage.IsSVGImageElement()) { 5880 SVGImageElement* img = &aImage.GetAsSVGImageElement(); 5881 element = img; 5882 } else { 5883 HTMLVideoElement* video = &aImage.GetAsHTMLVideoElement(); 5884 video->LogVisibility( 5885 mozilla::dom::HTMLVideoElement::CallerAPI::DRAW_IMAGE); 5886 element = video; 5887 } 5888 5889 srcSurf = CanvasImageCache::LookupCanvas(element, this, mTarget, &imgSize, 5890 &intrinsicImgSize, &cropRect); 5891 } 5892 5893 DirectDrawInfo drawInfo; 5894 Maybe<layers::SurfaceDescriptor> surfaceDescriptor; 5895 SurfaceFromElementResult res; 5896 5897 if (!srcSurf) { 5898 // The canvas spec says that drawImage should draw the first frame 5899 // of animated images. We also don't want to rasterize vector images. 5900 uint32_t sfeFlags = nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE | 5901 nsLayoutUtils::SFE_NO_RASTERIZING_VECTORS | 5902 nsLayoutUtils::SFE_ALLOW_UNCROPPED_UNSCALED; 5903 5904 if (offscreenCanvas) { 5905 res = nsLayoutUtils::SurfaceFromOffscreenCanvas(offscreenCanvas, sfeFlags, 5906 mTarget); 5907 } else if (videoFrame) { 5908 res = nsLayoutUtils::SurfaceFromVideoFrame(videoFrame, sfeFlags, mTarget); 5909 } else { 5910 res = CanvasRenderingContext2D::CachedSurfaceFromElement(element); 5911 if (!res.mSourceSurface) { 5912 HTMLVideoElement* video = HTMLVideoElement::FromNodeOrNull(element); 5913 if (video && mBufferProvider->IsAccelerated() && 5914 mTarget->IsRecording() && 5915 !(!NeedToApplyFilter() && NeedToDrawShadow())) { 5916 res = nsLayoutUtils::SurfaceFromElement( 5917 video, sfeFlags, mTarget, /* aOptimizeSourceSurface */ false); 5918 surfaceDescriptor = MaybeGetSurfaceDescriptorForRemoteCanvas(res); 5919 if (surfaceDescriptor.isNothing() && res.mLayersImage) { 5920 if ((res.mSourceSurface = res.mLayersImage->GetAsSourceSurface())) { 5921 RefPtr<SourceSurface> opt = 5922 mTarget->OptimizeSourceSurface(res.mSourceSurface); 5923 if (opt) { 5924 res.mSourceSurface = opt; 5925 } 5926 } 5927 } 5928 } else { 5929 res = nsLayoutUtils::SurfaceFromElement(element, sfeFlags, mTarget); 5930 } 5931 } 5932 } 5933 5934 if (surfaceDescriptor.isNothing()) { 5935 srcSurf = res.GetSourceSurface(); 5936 } 5937 5938 if (!srcSurf && surfaceDescriptor.isNothing() && 5939 !res.mDrawInfo.mImgContainer) { 5940 // https://html.spec.whatwg.org/#check-the-usability-of-the-image-argument: 5941 // 5942 // Only throw if the request is broken and the element is an 5943 // HTMLImageElement / SVGImageElement. Note that even for those the spec 5944 // says to silently do nothing in the following cases: 5945 // - The element is still loading. 5946 // - The image is bad, but it's not in the broken state (i.e., we could 5947 // decode the headers and get the size). 5948 if (!res.mIsStillLoading && !res.mHasSize && 5949 (aImage.IsHTMLImageElement() || aImage.IsSVGImageElement())) { 5950 aError.ThrowInvalidStateError("Passed-in image is \"broken\""); 5951 } else if (videoFrame) { 5952 aError.ThrowInvalidStateError("Passed-in video frame is \"broken\""); 5953 } 5954 return; 5955 } 5956 5957 imgSize = res.mSize; 5958 intrinsicImgSize = res.mIntrinsicSize; 5959 cropRect = res.mCropRect; 5960 DoSecurityCheck(res.mPrincipal, res.mIsWriteOnly, res.mCORSUsed); 5961 5962 if (srcSurf) { 5963 if (res.mImageRequest) { 5964 CanvasImageCache::NotifyDrawImage(element, this, mTarget, srcSurf, 5965 imgSize, intrinsicImgSize, cropRect); 5966 } 5967 } else { 5968 drawInfo = res.mDrawInfo; 5969 } 5970 } 5971 5972 double clipOriginX, clipOriginY, clipWidth, clipHeight; 5973 if (cropRect) { 5974 clipOriginX = cropRect.ref().X(); 5975 clipOriginY = cropRect.ref().Y(); 5976 clipWidth = cropRect.ref().Width(); 5977 clipHeight = cropRect.ref().Height(); 5978 } else { 5979 clipOriginX = clipOriginY = 0.0; 5980 clipWidth = imgSize.width; 5981 clipHeight = imgSize.height; 5982 } 5983 5984 // Any provided coordinates are in the display space, or the same as the 5985 // intrinsic size. In order to get to the surface coordinate space, we may 5986 // need to adjust for scaling and/or cropping. If no source coordinates are 5987 // provided, then we can just directly use the actual surface size. 5988 if (aOptional_argc == 0) { 5989 aSx = clipOriginX; 5990 aSy = clipOriginY; 5991 aSw = clipWidth; 5992 aSh = clipHeight; 5993 aDw = (double)intrinsicImgSize.width; 5994 aDh = (double)intrinsicImgSize.height; 5995 } else if (aOptional_argc == 2) { 5996 aSx = clipOriginX; 5997 aSy = clipOriginY; 5998 aSw = clipWidth; 5999 aSh = clipHeight; 6000 } else if (cropRect || intrinsicImgSize != imgSize) { 6001 // We need to first scale between the cropped size and the intrinsic size, 6002 // and then adjust for the offset from the crop rect. 6003 double scaleXToCrop = clipWidth / intrinsicImgSize.width; 6004 double scaleYToCrop = clipHeight / intrinsicImgSize.height; 6005 aSx = aSx * scaleXToCrop + clipOriginX; 6006 aSy = aSy * scaleYToCrop + clipOriginY; 6007 aSw = aSw * scaleXToCrop; 6008 aSh = aSh * scaleYToCrop; 6009 } 6010 6011 if (aSw == 0.0 || aSh == 0.0) { 6012 return; 6013 } 6014 6015 ClipImageDimension(aSx, aSw, clipOriginX, clipWidth, aDx, aDw); 6016 ClipImageDimension(aSy, aSh, clipOriginY, clipHeight, aDy, aDh); 6017 6018 if (aSw <= 0.0 || aSh <= 0.0 || aDw <= 0.0 || aDh <= 0.0) { 6019 // source and/or destination are fully clipped, so nothing is painted 6020 return; 6021 } 6022 6023 if (static_cast<float>(aSw) <= 0.0 || static_cast<float>(aSh) <= 0.0 || 6024 static_cast<float>(aDw) <= 0.0 || static_cast<float>(aDh) <= 0.0) { 6025 // When we actually draw we convert to float, so also check the values as 6026 // floats. 6027 return; 6028 } 6029 6030 // Per spec, the smoothing setting applies only to scaling up a bitmap image. 6031 // When down-scaling the user agent is free to choose whether or not to smooth 6032 // the image. Nearest sampling when down-scaling is rarely desirable and 6033 // smoothing when down-scaling matches chromium's behavior. 6034 // If any dimension is up-scaled, we consider the image as being up-scaled. 6035 auto scale = mTarget->GetTransform().ScaleFactors(); 6036 bool isDownScale = 6037 aDw * Abs(scale.xScale) < aSw && aDh * Abs(scale.yScale) < aSh; 6038 6039 SamplingFilter samplingFilter; 6040 AntialiasMode antialiasMode; 6041 6042 if (CurrentState().imageSmoothingEnabled || isDownScale) { 6043 samplingFilter = gfx::SamplingFilter::LINEAR; 6044 antialiasMode = AntialiasMode::DEFAULT; 6045 } else { 6046 samplingFilter = gfx::SamplingFilter::POINT; 6047 antialiasMode = AntialiasMode::NONE; 6048 } 6049 6050 const bool needBounds = NeedToCalculateBounds(); 6051 if (!IsTargetValid()) { 6052 return; 6053 } 6054 gfx::Rect bounds; 6055 if (needBounds) { 6056 bounds = gfx::Rect(aDx, aDy, aDw, aDh); 6057 bounds = mTarget->GetTransform().TransformBounds(bounds); 6058 } 6059 6060 if (!IsTargetValid()) { 6061 aError.Throw(NS_ERROR_FAILURE); 6062 return; 6063 } 6064 6065 if (srcSurf || surfaceDescriptor.isSome()) { 6066 gfx::Rect sourceRect(aSx, aSy, aSw, aSh); 6067 if (srcSurf && ((element && element == mCanvasElement) || 6068 (offscreenCanvas && offscreenCanvas == mOffscreenCanvas))) { 6069 // srcSurf is a snapshot of mTarget. If we draw to mTarget now, we'll 6070 // trigger a COW copy of the whole canvas into srcSurf. That's a huge 6071 // waste if sourceRect doesn't cover the whole canvas. 6072 // We avoid copying the whole canvas by manually copying just the part 6073 // that we need. 6074 srcSurf = ExtractSubrect(srcSurf, &sourceRect, mTarget); 6075 // The SFE result may inadvertently keep the snapshot alive, forcing a 6076 // copy when MarkChanged is called. Clear out possibly the last reference 6077 // to the original snapshot to avoid this. 6078 res.mSourceSurface = nullptr; 6079 } 6080 6081 AdjustedTarget tempTarget(this, bounds.IsEmpty() ? nullptr : &bounds, true); 6082 if (!tempTarget) { 6083 gfxWarning() << "Invalid adjusted target in Canvas2D " 6084 << gfx::hexa((DrawTarget*)mTarget) << ", " 6085 << NeedToDrawShadow() << NeedToApplyFilter(); 6086 return; 6087 } 6088 6089 auto op = tempTarget.UsedOperation(); 6090 if (!IsTargetValid() || !tempTarget) { 6091 return; 6092 } 6093 6094 VideoRotation rotationDeg = VideoRotation::kDegree_0; 6095 if (HTMLVideoElement* video = HTMLVideoElement::FromNodeOrNull(element)) { 6096 rotationDeg = video->RotationDegrees(); 6097 } 6098 6099 gfx::Rect destRect(aDx, aDy, aDw, aDh); 6100 6101 Matrix currentTransform = tempTarget->GetTransform(); 6102 if (rotationDeg != VideoRotation::kDegree_0) { 6103 tempTarget->ConcatTransform( 6104 ComputeRotationMatrix(aDw, aDh, rotationDeg).PostTranslate(aDx, aDy)); 6105 6106 SwapScaleWidthHeightForRotation(destRect, rotationDeg); 6107 // When rotation exists, aDx, aDy is handled by transform, Since aDest.x 6108 // aDest.y handling of DrawSurface() does not care about the rotation. 6109 destRect.x = 0; 6110 destRect.y = 0; 6111 } 6112 6113 if (srcSurf) { 6114 MOZ_ASSERT(surfaceDescriptor.isNothing()); 6115 6116 tempTarget.DrawSurface( 6117 srcSurf, destRect, sourceRect, 6118 DrawSurfaceOptions(samplingFilter, SamplingBounds::UNBOUNDED), 6119 DrawOptions(CurrentState().globalAlpha, op, antialiasMode)); 6120 } else if (surfaceDescriptor.isSome()) { 6121 MOZ_ASSERT(!tempTarget.UseOptimizeShadow()); 6122 MOZ_ASSERT(res.mLayersImage); 6123 6124 mTarget->DrawSurfaceDescriptor( 6125 surfaceDescriptor.ref(), res.mLayersImage, destRect, sourceRect, 6126 DrawSurfaceOptions(samplingFilter, SamplingBounds::UNBOUNDED), 6127 DrawOptions(CurrentState().globalAlpha, op, antialiasMode)); 6128 } else { 6129 MOZ_ASSERT_UNREACHABLE("unexpected to be called"); 6130 } 6131 6132 if (rotationDeg != VideoRotation::kDegree_0) { 6133 tempTarget->SetTransform(currentTransform); 6134 } 6135 6136 } else { 6137 DrawDirectlyToCanvas(drawInfo, &bounds, gfx::Rect(aDx, aDy, aDw, aDh), 6138 gfx::Rect(aSx, aSy, aSw, aSh), imgSize); 6139 } 6140 6141 RedrawUser(gfxRect(aDx, aDy, aDw, aDh)); 6142 } 6143 6144 void CanvasRenderingContext2D::DrawDirectlyToCanvas( 6145 const DirectDrawInfo& aImage, gfx::Rect* aBounds, gfx::Rect aDest, 6146 gfx::Rect aSrc, gfx::IntSize aImgSize) { 6147 MOZ_ASSERT(aSrc.width > 0 && aSrc.height > 0, 6148 "Need positive source width and height"); 6149 6150 AdjustedTarget tempTarget(this, aBounds->IsEmpty() ? nullptr : aBounds); 6151 if (!tempTarget || !tempTarget->IsValid()) { 6152 return; 6153 } 6154 6155 // Get any existing transforms on the context, including transformations used 6156 // for context shadow. 6157 Matrix matrix = tempTarget->GetTransform(); 6158 gfxMatrix contextMatrix = ThebesMatrix(matrix); 6159 MatrixScalesDouble contextScale = contextMatrix.ScaleFactors(); 6160 6161 // Scale the dest rect to include the context scale. 6162 aDest.Scale((float)contextScale.xScale, (float)contextScale.yScale); 6163 6164 // Scale the image size to the dest rect, and adjust the source rect to match. 6165 MatrixScalesDouble scale(aDest.width / aSrc.width, 6166 aDest.height / aSrc.height); 6167 IntSize scaledImageSize = 6168 IntSize::Ceil(static_cast<float>(scale.xScale * aImgSize.width), 6169 static_cast<float>(scale.yScale * aImgSize.height)); 6170 aSrc.Scale(static_cast<float>(scale.xScale), 6171 static_cast<float>(scale.yScale)); 6172 6173 // We're wrapping tempTarget's (our) DrawTarget here, so we need to restore 6174 // the matrix even though this is a temp gfxContext. 6175 AutoRestoreTransform autoRestoreTransform(mTarget); 6176 6177 gfxContext context(tempTarget); 6178 context.SetMatrixDouble( 6179 contextMatrix 6180 .PreScale(1.0 / contextScale.xScale, 1.0 / contextScale.yScale) 6181 .PreTranslate(aDest.x - aSrc.x, aDest.y - aSrc.y)); 6182 6183 context.SetOp(tempTarget.UsedOperation()); 6184 6185 // FLAG_CLAMP is added for increased performance, since we never tile here. 6186 uint32_t modifiedFlags = aImage.mDrawingFlags | imgIContainer::FLAG_CLAMP; 6187 6188 // XXX hmm is scaledImageSize really in CSS pixels? 6189 CSSIntSize sz(scaledImageSize.width, scaledImageSize.height); 6190 SVGImageContext svgContext(Some(sz)); 6191 6192 if (mContextProperties != CanvasContextProperties::None && 6193 aImage.mImgContainer->GetType() == imgIContainer::TYPE_VECTOR) { 6194 SVGEmbeddingContextPaint* contextPaint = 6195 svgContext.GetOrCreateContextPaint(); 6196 const ContextState& state = CurrentState(); 6197 6198 if (mContextProperties != CanvasContextProperties::Fill && 6199 state.StyleIsColor(Style::STROKE)) { 6200 contextPaint->SetStroke(state.colorStyles[Style::STROKE]); 6201 } 6202 6203 if (mContextProperties != CanvasContextProperties::Stroke && 6204 state.StyleIsColor(Style::FILL)) { 6205 contextPaint->SetFill(state.colorStyles[Style::FILL]); 6206 } 6207 } 6208 6209 auto result = aImage.mImgContainer->Draw( 6210 &context, scaledImageSize, 6211 ImageRegion::Create(gfxRect(aSrc.x, aSrc.y, aSrc.width, aSrc.height)), 6212 aImage.mWhichFrame, SamplingFilter::GOOD, svgContext, modifiedFlags, 6213 CurrentState().globalAlpha); 6214 6215 if (result != ImgDrawResult::SUCCESS) { 6216 NS_WARNING("imgIContainer::Draw failed"); 6217 } 6218 } 6219 6220 void CanvasRenderingContext2D::SetGlobalCompositeOperation( 6221 const nsAString& aOp, ErrorResult& aError) { 6222 CompositionOp comp_op; 6223 6224 #define CANVAS_OP_TO_GFX_OP(cvsop, op2d) \ 6225 if (aOp.EqualsLiteral(cvsop)) comp_op = CompositionOp::OP_##op2d; 6226 6227 CANVAS_OP_TO_GFX_OP("clear", CLEAR) 6228 else CANVAS_OP_TO_GFX_OP("copy", SOURCE) 6229 else CANVAS_OP_TO_GFX_OP("source-atop", ATOP) 6230 else CANVAS_OP_TO_GFX_OP("source-in", IN) 6231 else CANVAS_OP_TO_GFX_OP("source-out", OUT) 6232 else CANVAS_OP_TO_GFX_OP("source-over", OVER) 6233 else CANVAS_OP_TO_GFX_OP("destination-in", DEST_IN) 6234 else CANVAS_OP_TO_GFX_OP("destination-out", DEST_OUT) 6235 else CANVAS_OP_TO_GFX_OP("destination-over", DEST_OVER) 6236 else CANVAS_OP_TO_GFX_OP("destination-atop", DEST_ATOP) 6237 else CANVAS_OP_TO_GFX_OP("lighter", ADD) 6238 else CANVAS_OP_TO_GFX_OP("xor", XOR) 6239 else CANVAS_OP_TO_GFX_OP("multiply", MULTIPLY) 6240 else CANVAS_OP_TO_GFX_OP("screen", SCREEN) 6241 else CANVAS_OP_TO_GFX_OP("overlay", OVERLAY) 6242 else CANVAS_OP_TO_GFX_OP("darken", DARKEN) 6243 else CANVAS_OP_TO_GFX_OP("lighten", LIGHTEN) 6244 else CANVAS_OP_TO_GFX_OP("color-dodge", COLOR_DODGE) 6245 else CANVAS_OP_TO_GFX_OP("color-burn", COLOR_BURN) 6246 else CANVAS_OP_TO_GFX_OP("hard-light", HARD_LIGHT) 6247 else CANVAS_OP_TO_GFX_OP("soft-light", SOFT_LIGHT) 6248 else CANVAS_OP_TO_GFX_OP("difference", DIFFERENCE) 6249 else CANVAS_OP_TO_GFX_OP("exclusion", EXCLUSION) 6250 else CANVAS_OP_TO_GFX_OP("hue", HUE) 6251 else CANVAS_OP_TO_GFX_OP("saturation", SATURATION) 6252 else CANVAS_OP_TO_GFX_OP("color", COLOR) 6253 else CANVAS_OP_TO_GFX_OP("luminosity", LUMINOSITY) 6254 // XXX ERRMSG we need to report an error to developers here! (bug 329026) 6255 else return; 6256 6257 #undef CANVAS_OP_TO_GFX_OP 6258 CurrentState().op = comp_op; 6259 } 6260 6261 void CanvasRenderingContext2D::GetGlobalCompositeOperation( 6262 nsAString& aOp, ErrorResult& aError) { 6263 CompositionOp comp_op = CurrentState().op; 6264 6265 #define CANVAS_OP_TO_GFX_OP(cvsop, op2d) \ 6266 if (comp_op == CompositionOp::OP_##op2d) aOp.AssignLiteral(cvsop); 6267 6268 CANVAS_OP_TO_GFX_OP("clear", CLEAR) 6269 else CANVAS_OP_TO_GFX_OP("copy", SOURCE) 6270 else CANVAS_OP_TO_GFX_OP("destination-atop", DEST_ATOP) 6271 else CANVAS_OP_TO_GFX_OP("destination-in", DEST_IN) 6272 else CANVAS_OP_TO_GFX_OP("destination-out", DEST_OUT) 6273 else CANVAS_OP_TO_GFX_OP("destination-over", DEST_OVER) 6274 else CANVAS_OP_TO_GFX_OP("lighter", ADD) 6275 else CANVAS_OP_TO_GFX_OP("source-atop", ATOP) 6276 else CANVAS_OP_TO_GFX_OP("source-in", IN) 6277 else CANVAS_OP_TO_GFX_OP("source-out", OUT) 6278 else CANVAS_OP_TO_GFX_OP("source-over", OVER) 6279 else CANVAS_OP_TO_GFX_OP("xor", XOR) 6280 else CANVAS_OP_TO_GFX_OP("multiply", MULTIPLY) 6281 else CANVAS_OP_TO_GFX_OP("screen", SCREEN) 6282 else CANVAS_OP_TO_GFX_OP("overlay", OVERLAY) 6283 else CANVAS_OP_TO_GFX_OP("darken", DARKEN) 6284 else CANVAS_OP_TO_GFX_OP("lighten", LIGHTEN) 6285 else CANVAS_OP_TO_GFX_OP("color-dodge", COLOR_DODGE) 6286 else CANVAS_OP_TO_GFX_OP("color-burn", COLOR_BURN) 6287 else CANVAS_OP_TO_GFX_OP("hard-light", HARD_LIGHT) 6288 else CANVAS_OP_TO_GFX_OP("soft-light", SOFT_LIGHT) 6289 else CANVAS_OP_TO_GFX_OP("difference", DIFFERENCE) 6290 else CANVAS_OP_TO_GFX_OP("exclusion", EXCLUSION) 6291 else CANVAS_OP_TO_GFX_OP("hue", HUE) 6292 else CANVAS_OP_TO_GFX_OP("saturation", SATURATION) 6293 else CANVAS_OP_TO_GFX_OP("color", COLOR) 6294 else CANVAS_OP_TO_GFX_OP("luminosity", LUMINOSITY) 6295 else { 6296 aError.Throw(NS_ERROR_FAILURE); 6297 } 6298 6299 #undef CANVAS_OP_TO_GFX_OP 6300 } 6301 6302 void CanvasRenderingContext2D::DrawWindow(nsGlobalWindowInner& aWindow, 6303 double aX, double aY, double aW, 6304 double aH, const nsACString& aBgColor, 6305 uint32_t aFlags, 6306 nsIPrincipal& aSubjectPrincipal, 6307 ErrorResult& aError) { 6308 if (int32_t(aW) == 0 || int32_t(aH) == 0) { 6309 return; 6310 } 6311 6312 // protect against too-large surfaces that will cause allocation 6313 // or overflow issues 6314 if (!Factory::CheckSurfaceSize(IntSize(int32_t(aW), int32_t(aH)), 0xffff)) { 6315 aError.Throw(NS_ERROR_FAILURE); 6316 return; 6317 } 6318 6319 Document* doc = aWindow.GetExtantDoc(); 6320 if (doc && aSubjectPrincipal.GetIsAddonOrExpandedAddonPrincipal()) { 6321 doc->WarnOnceAbout( 6322 DeprecatedOperations::eDrawWindowCanvasRenderingContext2D); 6323 } 6324 6325 // Flush layout updates 6326 if (!(aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_DO_NOT_FLUSH)) { 6327 nsContentUtils::FlushLayoutForTree(aWindow.GetOuterWindow()); 6328 } 6329 6330 CompositionOp op = CurrentState().op; 6331 bool discardContent = 6332 GlobalAlpha() == 1.0f && 6333 (op == CompositionOp::OP_OVER || op == CompositionOp::OP_SOURCE); 6334 const gfx::Rect drawRect(aX, aY, aW, aH); 6335 if (!EnsureTarget(aError, discardContent ? &drawRect : nullptr)) { 6336 return; 6337 } 6338 6339 MOZ_ASSERT(IsTargetValid()); 6340 6341 RefPtr<nsPresContext> presContext; 6342 nsIDocShell* docshell = aWindow.GetDocShell(); 6343 if (docshell) { 6344 presContext = docshell->GetPresContext(); 6345 } 6346 if (!presContext) { 6347 aError.Throw(NS_ERROR_FAILURE); 6348 return; 6349 } 6350 6351 Maybe<nscolor> backgroundColor = ParseColor(aBgColor); 6352 if (!backgroundColor) { 6353 aError.Throw(NS_ERROR_FAILURE); 6354 return; 6355 } 6356 6357 nsRect r(nsPresContext::CSSPixelsToAppUnits((float)aX), 6358 nsPresContext::CSSPixelsToAppUnits((float)aY), 6359 nsPresContext::CSSPixelsToAppUnits((float)aW), 6360 nsPresContext::CSSPixelsToAppUnits((float)aH)); 6361 RenderDocumentFlags renderDocFlags = 6362 (RenderDocumentFlags::IgnoreViewportScrolling | 6363 RenderDocumentFlags::DocumentRelative); 6364 if (aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_DRAW_CARET) { 6365 renderDocFlags |= RenderDocumentFlags::DrawCaret; 6366 } 6367 if (aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_DRAW_VIEW) { 6368 renderDocFlags &= ~(RenderDocumentFlags::IgnoreViewportScrolling | 6369 RenderDocumentFlags::DocumentRelative); 6370 } 6371 if (aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_USE_WIDGET_LAYERS) { 6372 renderDocFlags |= RenderDocumentFlags::UseWidgetLayers; 6373 } 6374 if (aFlags & 6375 CanvasRenderingContext2D_Binding::DRAWWINDOW_ASYNC_DECODE_IMAGES) { 6376 renderDocFlags |= RenderDocumentFlags::AsyncDecodeImages; 6377 } 6378 if (aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_DO_NOT_FLUSH) { 6379 renderDocFlags |= RenderDocumentFlags::DrawWindowNotFlushing; 6380 } 6381 6382 // gfxContext-over-Azure may modify the DrawTarget's transform, so 6383 // save and restore it 6384 Matrix matrix = mTarget->GetTransform(); 6385 double sw = matrix._11 * aW; 6386 double sh = matrix._22 * aH; 6387 if (!sw || !sh) { 6388 return; 6389 } 6390 6391 Maybe<gfxContext> thebes; 6392 RefPtr<DrawTarget> drawDT; 6393 // Rendering directly is faster and can be done if mTarget supports Azure 6394 // and does not need alpha blending. 6395 // Since the pre-transaction callback calls ReturnTarget, we can't have a 6396 // gfxContext wrapped around it when using a shared buffer provider because 6397 // the DrawTarget's shared buffer may be unmapped in ReturnTarget. 6398 op = CompositionOp::OP_ADD; 6399 if (gfxPlatform::GetPlatform()->SupportsAzureContentForDrawTarget(mTarget) && 6400 GlobalAlpha() == 1.0f) { 6401 op = CurrentState().op; 6402 if (!IsTargetValid()) { 6403 aError.Throw(NS_ERROR_FAILURE); 6404 return; 6405 } 6406 } 6407 if (op == CompositionOp::OP_OVER && 6408 (!mBufferProvider || !mBufferProvider->IsShared())) { 6409 thebes.emplace(mTarget); 6410 thebes.ref().SetMatrix(matrix); 6411 } else { 6412 IntSize dtSize = IntSize::Ceil(sw, sh); 6413 if (!Factory::AllowedSurfaceSize(dtSize)) { 6414 // attempt to limit the DT to what will actually cover the target 6415 Size limitSize(mTarget->GetSize()); 6416 limitSize.Scale(matrix._11, matrix._22); 6417 dtSize = Min(dtSize, IntSize::Ceil(limitSize)); 6418 // if the DT is still too big, then error 6419 if (!Factory::AllowedSurfaceSize(dtSize)) { 6420 aError.Throw(NS_ERROR_FAILURE); 6421 return; 6422 } 6423 } 6424 drawDT = gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget( 6425 dtSize, SurfaceFormat::B8G8R8A8); 6426 if (!drawDT || !drawDT->IsValid()) { 6427 aError.Throw(NS_ERROR_FAILURE); 6428 return; 6429 } 6430 6431 thebes.emplace(drawDT); 6432 thebes.ref().SetMatrix(Matrix::Scaling(matrix._11, matrix._22)); 6433 } 6434 MOZ_ASSERT(thebes.isSome()); 6435 6436 RefPtr<PresShell> presShell = presContext->PresShell(); 6437 6438 (void)presShell->RenderDocument(r, renderDocFlags, *backgroundColor, 6439 &thebes.ref()); 6440 // If this canvas was contained in the drawn window, the pre-transaction 6441 // callback may have returned its DT. If so, we must reacquire it here. 6442 if (!EnsureTarget(aError, discardContent ? &drawRect : nullptr)) { 6443 return; 6444 } 6445 6446 MOZ_ASSERT(IsTargetValid()); 6447 6448 if (drawDT) { 6449 RefPtr<SourceSurface> snapshot = drawDT->Snapshot(); 6450 if (NS_WARN_IF(!snapshot)) { 6451 aError.Throw(NS_ERROR_FAILURE); 6452 return; 6453 } 6454 6455 op = CurrentState().op; 6456 if (!IsTargetValid()) { 6457 aError.Throw(NS_ERROR_FAILURE); 6458 return; 6459 } 6460 gfx::Rect destRect(0, 0, aW, aH); 6461 gfx::Rect sourceRect(0, 0, sw, sh); 6462 mTarget->DrawSurface(snapshot, destRect, sourceRect, 6463 DrawSurfaceOptions(gfx::SamplingFilter::POINT), 6464 DrawOptions(GlobalAlpha(), op, AntialiasMode::NONE)); 6465 } else { 6466 mTarget->SetTransform(matrix); 6467 } 6468 6469 // note that x and y are coordinates in the document that 6470 // we're drawing; x and y are drawn to 0,0 in current user 6471 // space. 6472 RedrawUser(gfxRect(0, 0, aW, aH)); 6473 } 6474 6475 // 6476 // device pixel getting/setting 6477 // 6478 6479 already_AddRefed<ImageData> CanvasRenderingContext2D::GetImageData( 6480 JSContext* aCx, int32_t aSx, int32_t aSy, int32_t aSw, int32_t aSh, 6481 nsIPrincipal& aSubjectPrincipal, ErrorResult& aError) { 6482 if (!mCanvasElement && !mDocShell && !mOffscreenCanvas) { 6483 NS_ERROR("No canvas element and no docshell in GetImageData!!!"); 6484 aError.Throw(NS_ERROR_DOM_SECURITY_ERR); 6485 return nullptr; 6486 } 6487 6488 // Check only if we have a canvas element; if we were created with a docshell, 6489 // then it's special internal use. 6490 if (IsWriteOnly() || 6491 (mCanvasElement && !mCanvasElement->CallerCanRead(aSubjectPrincipal)) || 6492 (mOffscreenCanvas && 6493 !mOffscreenCanvas->CallerCanRead(aSubjectPrincipal))) { 6494 // XXX ERRMSG we need to report an error to developers here! (bug 329026) 6495 aError.Throw(NS_ERROR_DOM_SECURITY_ERR); 6496 return nullptr; 6497 } 6498 6499 if (!aSw || !aSh) { 6500 aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); 6501 return nullptr; 6502 } 6503 6504 // Handle negative width and height by flipping the rectangle over in the 6505 // relevant direction. 6506 uint32_t w, h; 6507 if (aSw < 0) { 6508 w = uint32_t(-aSw); 6509 aSx -= w; 6510 } else { 6511 w = aSw; 6512 } 6513 if (aSh < 0) { 6514 h = uint32_t(-aSh); 6515 aSy -= h; 6516 } else { 6517 h = aSh; 6518 } 6519 6520 if (w == 0) { 6521 w = 1; 6522 } 6523 if (h == 0) { 6524 h = 1; 6525 } 6526 6527 RecordCanvasUsage(CanvasExtractionAPI::GetImageData, CSSIntSize(w, h)); 6528 6529 JS::Rooted<JSObject*> array(aCx); 6530 aError = GetImageDataArray(aCx, aSx, aSy, w, h, aSubjectPrincipal, 6531 array.address()); 6532 if (aError.Failed()) { 6533 return nullptr; 6534 } 6535 MOZ_ASSERT(array); 6536 return do_AddRef(new ImageData(GetParentObject(), w, h, array)); 6537 } 6538 6539 static IntRect ClipImageDataTransfer(IntRect& aSrc, const IntPoint& aDestOffset, 6540 const IntSize& aDestBounds) { 6541 IntRect dest = aSrc; 6542 dest.SafeMoveBy(aDestOffset); 6543 dest = IntRect(IntPoint(0, 0), aDestBounds).SafeIntersect(dest); 6544 6545 aSrc = aSrc.SafeIntersect(dest - aDestOffset); 6546 return aSrc + aDestOffset; 6547 } 6548 6549 nsresult CanvasRenderingContext2D::GetImageDataArray( 6550 JSContext* aCx, int32_t aX, int32_t aY, uint32_t aWidth, uint32_t aHeight, 6551 nsIPrincipal& aSubjectPrincipal, JSObject** aRetval) { 6552 MOZ_ASSERT(aWidth && aHeight); 6553 6554 // Restrict the typed array length to INT32_MAX because that's all we support. 6555 CheckedInt<uint32_t> len = CheckedInt<uint32_t>(aWidth) * aHeight * 4; 6556 if (!len.isValid() || len.value() > INT32_MAX) { 6557 return NS_ERROR_DOM_INDEX_SIZE_ERR; 6558 } 6559 6560 CheckedInt<int32_t> rightMost = CheckedInt<int32_t>(aX) + aWidth; 6561 CheckedInt<int32_t> bottomMost = CheckedInt<int32_t>(aY) + aHeight; 6562 6563 if (!rightMost.isValid() || !bottomMost.isValid()) { 6564 return NS_ERROR_DOM_SYNTAX_ERR; 6565 } 6566 6567 JS::Rooted<JSObject*> darray(aCx, JS_NewUint8ClampedArray(aCx, len.value())); 6568 if (!darray) { 6569 return NS_ERROR_OUT_OF_MEMORY; 6570 } 6571 6572 if (mZero) { 6573 *aRetval = darray; 6574 return NS_OK; 6575 } 6576 6577 IntRect dstWriteRect(0, 0, aWidth, aHeight); 6578 IntRect srcReadRect = ClipImageDataTransfer(dstWriteRect, IntPoint(aX, aY), 6579 IntSize(mWidth, mHeight)); 6580 if (srcReadRect.IsEmpty()) { 6581 *aRetval = darray; 6582 return NS_OK; 6583 } 6584 6585 if (!GetBufferProvider() && !EnsureTarget()) { 6586 return NS_ERROR_FAILURE; 6587 } 6588 6589 RefPtr<SourceSurface> snapshot = mBufferProvider->BorrowSnapshot(); 6590 if (!snapshot) { 6591 return NS_ERROR_OUT_OF_MEMORY; 6592 } 6593 6594 RefPtr<DataSourceSurface> readback = snapshot->GetDataSurface(); 6595 mBufferProvider->ReturnSnapshot(snapshot.forget()); 6596 6597 // Check for site-specific permission. 6598 CanvasUtils::ImageExtraction extractionBehavior = 6599 CanvasUtils::ImageExtraction::Unrestricted; 6600 if (mCanvasElement) { 6601 extractionBehavior = CanvasUtils::ImageExtractionResult(mCanvasElement, aCx, 6602 &aSubjectPrincipal); 6603 } else if (mOffscreenCanvas) { 6604 extractionBehavior = CanvasUtils::ImageExtractionResult( 6605 mOffscreenCanvas, aCx, &aSubjectPrincipal); 6606 } 6607 6608 // Clone the data source surface if canvas randomization is enabled. We need 6609 // to do this because we don't want to alter the actual image buffer. 6610 // Otherwise, we will provide inconsistent image data with multiple calls. 6611 // 6612 // Note that we don't need to clone if we will use the place holder because 6613 // the place holder doesn't use actual image data. 6614 if (extractionBehavior == CanvasUtils::ImageExtraction::Randomize) { 6615 if (readback) { 6616 readback = CreateDataSourceSurfaceByCloning(readback); 6617 } 6618 } 6619 6620 DataSourceSurface::MappedSurface rawData; 6621 if (!readback || !readback->Map(DataSourceSurface::READ, &rawData)) { 6622 return NS_ERROR_OUT_OF_MEMORY; 6623 } 6624 6625 do { 6626 uint8_t* randomData; 6627 const IntSize size = readback->GetSize(); 6628 nsRFPService::PotentiallyDumpImage(PrincipalOrNull(), rawData.mData, 6629 size.width, size.height, 6630 size.height * size.width * 4); 6631 if (extractionBehavior == CanvasUtils::ImageExtraction::Placeholder) { 6632 // Since we cannot call any GC-able functions (like requesting the RNG 6633 // service) after we call JS_GetUint8ClampedArrayData, we will 6634 // pre-generate the randomness required for GeneratePlaceholderCanvasData. 6635 randomData = TryToGenerateRandomDataForPlaceholderCanvasData(); 6636 } else if (extractionBehavior == CanvasUtils::ImageExtraction::Randomize) { 6637 // Apply the random noises if canvan randomization is enabled. We don't 6638 // need to calculate random noises if we are going to use the place 6639 // holder. 6640 6641 nsRFPService::RandomizePixels(GetCookieJarSettings(), PrincipalOrNull(), 6642 rawData.mData, size.width, size.height, 6643 size.height * size.width * 4, 6644 SurfaceFormat::A8R8G8B8_UINT32); 6645 } 6646 6647 JS::AutoCheckCannotGC nogc; 6648 bool isShared; 6649 uint8_t* data = JS_GetUint8ClampedArrayData(darray, &isShared, nogc); 6650 MOZ_ASSERT(!isShared); // Should not happen, data was created above 6651 6652 if (extractionBehavior == CanvasUtils::ImageExtraction::Placeholder) { 6653 FillPlaceholderCanvas(randomData, len.value(), data); 6654 break; 6655 } 6656 6657 uint32_t srcStride = rawData.mStride; 6658 uint8_t* src = 6659 rawData.mData + srcReadRect.y * srcStride + srcReadRect.x * 4; 6660 6661 uint8_t* dst = data + dstWriteRect.y * (aWidth * 4) + dstWriteRect.x * 4; 6662 6663 if (mOpaque) { 6664 SwizzleData(src, srcStride, SurfaceFormat::X8R8G8B8_UINT32, dst, 6665 aWidth * 4, SurfaceFormat::R8G8B8A8, dstWriteRect.Size()); 6666 } else { 6667 UnpremultiplyData(src, srcStride, SurfaceFormat::A8R8G8B8_UINT32, dst, 6668 aWidth * 4, SurfaceFormat::R8G8B8A8, 6669 dstWriteRect.Size()); 6670 } 6671 } while (false); 6672 6673 readback->Unmap(); 6674 *aRetval = darray; 6675 return NS_OK; 6676 } 6677 6678 void CanvasRenderingContext2D::EnsureErrorTarget() { 6679 if (sErrorTarget.get()) { 6680 return; 6681 } 6682 6683 RefPtr<DrawTarget> errorTarget = 6684 gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget( 6685 IntSize(1, 1), SurfaceFormat::B8G8R8A8); 6686 MOZ_ASSERT(errorTarget, "Failed to allocate the error target!"); 6687 6688 sErrorTarget.set(errorTarget.forget().take()); 6689 } 6690 6691 void CanvasRenderingContext2D::FillRuleChanged() { 6692 if (mPath) { 6693 mPathBuilder = Path::ToBuilder(mPath.forget(), CurrentState().fillRule); 6694 } 6695 } 6696 6697 void CanvasRenderingContext2D::PutImageData(ImageData& aImageData, int32_t aDx, 6698 int32_t aDy, ErrorResult& aRv) { 6699 RootedSpiderMonkeyInterface<Uint8ClampedArray> arr(RootingCx()); 6700 PutImageData_explicit(aDx, aDy, aImageData, false, 0, 0, 0, 0, aRv); 6701 } 6702 6703 void CanvasRenderingContext2D::PutImageData(ImageData& aImageData, int32_t aDx, 6704 int32_t aDy, int32_t aDirtyX, 6705 int32_t aDirtyY, 6706 int32_t aDirtyWidth, 6707 int32_t aDirtyHeight, 6708 ErrorResult& aRv) { 6709 PutImageData_explicit(aDx, aDy, aImageData, true, aDirtyX, aDirtyY, 6710 aDirtyWidth, aDirtyHeight, aRv); 6711 } 6712 6713 void CanvasRenderingContext2D::PutImageData_explicit( 6714 int32_t aX, int32_t aY, ImageData& aImageData, bool aHasDirtyRect, 6715 int32_t aDirtyX, int32_t aDirtyY, int32_t aDirtyWidth, int32_t aDirtyHeight, 6716 ErrorResult& aRv) { 6717 RootedSpiderMonkeyInterface<Uint8ClampedArray> arr(RootingCx()); 6718 if (!arr.Init(aImageData.GetDataObject())) { 6719 return aRv.ThrowInvalidStateError( 6720 "Failed to extract Uint8ClampedArray from ImageData (security check " 6721 "failed?)"); 6722 } 6723 6724 const uint32_t width = aImageData.Width(); 6725 const uint32_t height = aImageData.Height(); 6726 if (width == 0 || height == 0) { 6727 return aRv.ThrowInvalidStateError("Passed-in image is empty"); 6728 } 6729 6730 IntRect dirtyRect; 6731 IntRect imageDataRect(0, 0, width, height); 6732 6733 if (aHasDirtyRect) { 6734 // fix up negative dimensions 6735 if (aDirtyWidth < 0) { 6736 if (aDirtyWidth == INT_MIN) { 6737 return aRv.ThrowInvalidStateError("Dirty width is invalid"); 6738 } 6739 6740 CheckedInt32 checkedDirtyX = CheckedInt32(aDirtyX) + aDirtyWidth; 6741 6742 if (!checkedDirtyX.isValid()) { 6743 return aRv.ThrowInvalidStateError("Dirty width is invalid"); 6744 } 6745 6746 aDirtyX = checkedDirtyX.value(); 6747 aDirtyWidth = -aDirtyWidth; 6748 } 6749 6750 if (aDirtyHeight < 0) { 6751 if (aDirtyHeight == INT_MIN) { 6752 return aRv.ThrowInvalidStateError("Dirty height is invalid"); 6753 } 6754 6755 CheckedInt32 checkedDirtyY = CheckedInt32(aDirtyY) + aDirtyHeight; 6756 6757 if (!checkedDirtyY.isValid()) { 6758 return aRv.ThrowInvalidStateError("Dirty height is invalid"); 6759 } 6760 6761 aDirtyY = checkedDirtyY.value(); 6762 aDirtyHeight = -aDirtyHeight; 6763 } 6764 6765 // bound the dirty rect within the imageData rectangle 6766 dirtyRect = imageDataRect.Intersect( 6767 IntRect(aDirtyX, aDirtyY, aDirtyWidth, aDirtyHeight)); 6768 6769 if (dirtyRect.Width() <= 0 || dirtyRect.Height() <= 0) { 6770 return; 6771 } 6772 } else { 6773 dirtyRect = imageDataRect; 6774 } 6775 6776 IntRect srcRect = dirtyRect; 6777 dirtyRect = ClipImageDataTransfer(srcRect, IntPoint(aX, aY), 6778 IntSize(mWidth, mHeight)); 6779 if (dirtyRect.IsEmpty()) { 6780 return; 6781 } 6782 6783 RefPtr<DataSourceSurface> sourceSurface; 6784 uint8_t* lockedBits = nullptr; 6785 6786 // The canvas spec says that the current path, transformation matrix, 6787 // shadow attributes, global alpha, the clipping region, and global 6788 // composition operator must not affect the getImageData() and 6789 // putImageData() methods. 6790 const gfx::Rect putRect(dirtyRect); 6791 if (!EnsureTarget(aRv, &putRect, true, true)) { 6792 return; 6793 } 6794 6795 MOZ_ASSERT(IsTargetValid()); 6796 6797 DataSourceSurface::MappedSurface map; 6798 uint8_t* dstData; 6799 IntSize dstSize; 6800 int32_t dstStride; 6801 SurfaceFormat dstFormat; 6802 if (mTarget->LockBits(&lockedBits, &dstSize, &dstStride, &dstFormat)) { 6803 dstData = lockedBits + dirtyRect.y * dstStride + dirtyRect.x * 4; 6804 } else { 6805 sourceSurface = Factory::CreateDataSourceSurface( 6806 dirtyRect.Size(), SurfaceFormat::B8G8R8A8, false); 6807 6808 // In certain scenarios, requesting larger than 8k image fails. Bug 6809 // 803568 covers the details of how to run into it, but the full 6810 // detailed investigation hasn't been done to determine the 6811 // underlying cause. We will just handle the failure to allocate 6812 // the surface to avoid a crash. 6813 if (!sourceSurface) { 6814 return aRv.Throw(NS_ERROR_FAILURE); 6815 } 6816 if (!sourceSurface->Map(DataSourceSurface::READ_WRITE, &map)) { 6817 return aRv.Throw(NS_ERROR_FAILURE); 6818 } 6819 6820 dstData = map.mData; 6821 if (!dstData) { 6822 return aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 6823 } 6824 dstStride = map.mStride; 6825 dstFormat = sourceSurface->GetFormat(); 6826 } 6827 6828 arr.ProcessData( 6829 [&](const Span<uint8_t>& aData, JS::AutoCheckCannotGC&& nogc) { 6830 // Verify that the length hasn't changed. 6831 if (aData.Length() != width * height * 4) { 6832 // FIXME Should this call ReleaseBits/Unmap? 6833 return aRv.ThrowInvalidStateError("Invalid width or height"); 6834 } 6835 6836 uint8_t* srcData = 6837 aData.Elements() + srcRect.y * (width * 4) + srcRect.x * 4; 6838 6839 PremultiplyData(srcData, width * 4, SurfaceFormat::R8G8B8A8, dstData, 6840 dstStride, 6841 mOpaque ? SurfaceFormat::X8R8G8B8_UINT32 6842 : SurfaceFormat::A8R8G8B8_UINT32, 6843 dirtyRect.Size()); 6844 }); 6845 6846 // Ensure surfaces unmapped before potential error exit. 6847 if (lockedBits) { 6848 mTarget->ReleaseBits(lockedBits); 6849 } else if (sourceSurface) { 6850 sourceSurface->Unmap(); 6851 } 6852 6853 if (aRv.Failed()) { 6854 return; 6855 } 6856 6857 if (sourceSurface) { 6858 mTarget->CopySurface(sourceSurface, dirtyRect - dirtyRect.TopLeft(), 6859 dirtyRect.TopLeft()); 6860 } 6861 6862 Redraw( 6863 gfx::Rect(dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height)); 6864 } 6865 6866 static already_AddRefed<ImageData> CreateImageData( 6867 JSContext* aCx, CanvasRenderingContext2D* aContext, uint32_t aW, 6868 uint32_t aH, ErrorResult& aError) { 6869 if (aW == 0) aW = 1; 6870 if (aH == 0) aH = 1; 6871 6872 // Restrict the typed array length to INT32_MAX because that's all we support 6873 // in dom::TypedArray::ComputeState. 6874 CheckedInt<uint32_t> len = CheckedInt<uint32_t>(aW) * aH * 4; 6875 if (!len.isValid() || len.value() > INT32_MAX) { 6876 aError.ThrowIndexSizeError("Invalid width or height"); 6877 return nullptr; 6878 } 6879 6880 // Create the fast typed array; it's initialized to 0 by default. 6881 JS::Rooted<JSObject*> darray( 6882 aCx, Uint8ClampedArray::Create(aCx, aContext, len.value(), aError)); 6883 if (aError.Failed()) { 6884 return nullptr; 6885 } 6886 6887 return do_AddRef(new ImageData(aContext->GetParentObject(), aW, aH, darray)); 6888 } 6889 6890 already_AddRefed<ImageData> CanvasRenderingContext2D::CreateImageData( 6891 JSContext* aCx, int32_t aSw, int32_t aSh, ErrorResult& aError) { 6892 if (!aSw || !aSh) { 6893 aError.ThrowIndexSizeError("Invalid width or height"); 6894 return nullptr; 6895 } 6896 6897 uint32_t w = Abs(aSw); 6898 uint32_t h = Abs(aSh); 6899 return dom::CreateImageData(aCx, this, w, h, aError); 6900 } 6901 6902 already_AddRefed<ImageData> CanvasRenderingContext2D::CreateImageData( 6903 JSContext* aCx, ImageData& aImagedata, ErrorResult& aError) { 6904 return dom::CreateImageData(aCx, this, aImagedata.Width(), 6905 aImagedata.Height(), aError); 6906 } 6907 6908 void CanvasRenderingContext2D::OnMemoryPressure() { 6909 if (mBufferProvider) { 6910 mBufferProvider->OnMemoryPressure(); 6911 } 6912 } 6913 6914 void CanvasRenderingContext2D::OnBeforePaintTransaction() { 6915 if (!mTarget) return; 6916 OnStableState(); 6917 } 6918 6919 void CanvasRenderingContext2D::OnDidPaintTransaction() { MarkContextClean(); } 6920 6921 bool CanvasRenderingContext2D::UpdateWebRenderCanvasData( 6922 nsDisplayListBuilder* aBuilder, WebRenderCanvasData* aCanvasData) { 6923 if (mOpaque) { 6924 // If we're opaque then make sure we have a surface so we paint black 6925 // instead of transparent. 6926 EnsureTarget(); 6927 } 6928 6929 // Don't call EnsureTarget() ... if there isn't already a surface, then 6930 // we have nothing to paint and there is no need to create a surface just 6931 // to paint nothing. Also, EnsureTarget() can cause creation of a persistent 6932 // layer manager which must NOT happen during a paint. 6933 if (!mBufferProvider && !IsTargetValid()) { 6934 // No DidTransactionCallback will be received, so mark the context clean 6935 // now so future invalidations will be dispatched. 6936 MarkContextClean(); 6937 // Clear CanvasRenderer of WebRenderCanvasData 6938 aCanvasData->ClearCanvasRenderer(); 6939 return false; 6940 } 6941 6942 auto renderer = aCanvasData->GetCanvasRenderer(); 6943 6944 if (!mResetLayer && renderer) { 6945 CanvasRendererData data; 6946 data.mContext = this; 6947 data.mSize = GetSize(); 6948 6949 if (renderer->IsDataValid(data)) { 6950 return true; 6951 } 6952 } 6953 6954 renderer = aCanvasData->CreateCanvasRenderer(); 6955 if (!InitializeCanvasRenderer(aBuilder, renderer)) { 6956 // Clear CanvasRenderer of WebRenderCanvasData 6957 aCanvasData->ClearCanvasRenderer(); 6958 return false; 6959 } 6960 6961 MOZ_ASSERT(renderer); 6962 mResetLayer = false; 6963 return true; 6964 } 6965 6966 bool CanvasRenderingContext2D::InitializeCanvasRenderer( 6967 nsDisplayListBuilder* aBuilder, CanvasRenderer* aRenderer) { 6968 CanvasRendererData data; 6969 data.mContext = this; 6970 data.mSize = GetSize(); 6971 data.mIsOpaque = mOpaque; 6972 data.mDoPaintCallbacks = true; 6973 6974 if (!mBufferProvider) { 6975 // Force the creation of a buffer provider. 6976 EnsureTarget(); 6977 ReturnTarget(); 6978 if (!mBufferProvider) { 6979 MarkContextClean(); 6980 return false; 6981 } 6982 } 6983 6984 aRenderer->Initialize(data); 6985 aRenderer->SetDirty(); 6986 return true; 6987 } 6988 6989 void CanvasRenderingContext2D::MarkContextClean() { 6990 if (mInvalidateCount > 0) { 6991 mPredictManyRedrawCalls = mInvalidateCount > kCanvasMaxInvalidateCount; 6992 } 6993 mIsEntireFrameInvalid = false; 6994 mInvalidateCount = 0; 6995 } 6996 6997 void CanvasRenderingContext2D::GetAppUnitsValues(int32_t* aPerDevPixel, 6998 int32_t* aPerCSSPixel) { 6999 // If we don't have a canvas element, we just return something generic. 7000 if (aPerDevPixel) { 7001 *aPerDevPixel = 60; 7002 } 7003 if (aPerCSSPixel) { 7004 *aPerCSSPixel = 60; 7005 } 7006 PresShell* presShell = GetPresShell(); 7007 if (!presShell) { 7008 return; 7009 } 7010 nsPresContext* presContext = presShell->GetPresContext(); 7011 if (!presContext) { 7012 return; 7013 } 7014 if (aPerDevPixel) { 7015 *aPerDevPixel = presContext->AppUnitsPerDevPixel(); 7016 } 7017 if (aPerCSSPixel) { 7018 *aPerCSSPixel = AppUnitsPerCSSPixel(); 7019 } 7020 } 7021 7022 void CanvasRenderingContext2D::SetWriteOnly() { 7023 mWriteOnly = true; 7024 if (mCanvasElement) { 7025 mCanvasElement->SetWriteOnly(); 7026 } else if (mOffscreenCanvas) { 7027 mOffscreenCanvas->SetWriteOnly(); 7028 } 7029 } 7030 7031 bool CanvasRenderingContext2D::UseSoftwareRendering() const { 7032 return mWillReadFrequently || mForceSoftwareRendering; 7033 } 7034 7035 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasPath, mParent) 7036 7037 CanvasPath::CanvasPath(nsISupports* aParent) : mParent(aParent) { 7038 mPathBuilder = 7039 gfxPlatform::ThreadLocalScreenReferenceDrawTarget()->CreatePathBuilder(); 7040 } 7041 7042 CanvasPath::CanvasPath(nsISupports* aParent, 7043 already_AddRefed<PathBuilder> aPathBuilder) 7044 : mParent(aParent), mPathBuilder(aPathBuilder) { 7045 if (!mPathBuilder) { 7046 mPathBuilder = gfxPlatform::ThreadLocalScreenReferenceDrawTarget() 7047 ->CreatePathBuilder(); 7048 } 7049 } 7050 7051 JSObject* CanvasPath::WrapObject(JSContext* aCx, 7052 JS::Handle<JSObject*> aGivenProto) { 7053 return Path2D_Binding::Wrap(aCx, this, aGivenProto); 7054 } 7055 7056 already_AddRefed<CanvasPath> CanvasPath::Constructor( 7057 const GlobalObject& aGlobal) { 7058 RefPtr<CanvasPath> path = new CanvasPath(aGlobal.GetAsSupports()); 7059 return path.forget(); 7060 } 7061 7062 already_AddRefed<CanvasPath> CanvasPath::Constructor( 7063 const GlobalObject& aGlobal, CanvasPath& aCanvasPath) { 7064 RefPtr<gfx::DrawTarget> drawTarget = 7065 gfxPlatform::ThreadLocalScreenReferenceDrawTarget(); 7066 RefPtr<gfx::Path> tempPath = 7067 aCanvasPath.GetPath(CanvasWindingRule::Nonzero, drawTarget.get()); 7068 7069 RefPtr<CanvasPath> path = 7070 new CanvasPath(aGlobal.GetAsSupports(), tempPath->CopyToBuilder()); 7071 return path.forget(); 7072 } 7073 7074 already_AddRefed<CanvasPath> CanvasPath::Constructor( 7075 const GlobalObject& aGlobal, const nsACString& aPathString) { 7076 RefPtr<gfx::Path> tempPath = SVGContentUtils::GetPath(aPathString); 7077 if (!tempPath) { 7078 return Constructor(aGlobal); 7079 } 7080 7081 RefPtr<CanvasPath> path = 7082 new CanvasPath(aGlobal.GetAsSupports(), tempPath->CopyToBuilder()); 7083 return path.forget(); 7084 } 7085 7086 void CanvasPath::ClosePath() { 7087 EnsurePathBuilder(); 7088 7089 mPathBuilder->Close(); 7090 mPruned = false; 7091 } 7092 7093 inline void CanvasPath::EnsureCapped() const { 7094 // If there were zero-length segments emitted that were pruned, we need to 7095 // emit a LineTo to ensure that caps are generated for the segment. 7096 if (mPruned) { 7097 mPathBuilder->LineTo(mPathBuilder->CurrentPoint()); 7098 mPruned = false; 7099 } 7100 } 7101 7102 inline void CanvasPath::EnsureActive() const { 7103 // If the path is not active, then adding an op to the path may cause the path 7104 // to add the first point of the op as the initial point instead of the actual 7105 // current point. 7106 if (mPruned && !mPathBuilder->IsActive()) { 7107 mPathBuilder->MoveTo(mPathBuilder->CurrentPoint()); 7108 mPruned = false; 7109 } 7110 } 7111 7112 void CanvasPath::MoveTo(double aX, double aY) { 7113 EnsurePathBuilder(); 7114 7115 Point pos(ToFloat(aX), ToFloat(aY)); 7116 if (!pos.IsFinite()) { 7117 return; 7118 } 7119 7120 EnsureCapped(); 7121 mPathBuilder->MoveTo(pos); 7122 } 7123 7124 void CanvasPath::LineTo(double aX, double aY) { 7125 LineTo(Point(ToFloat(aX), ToFloat(aY))); 7126 } 7127 7128 void CanvasPath::QuadraticCurveTo(double aCpx, double aCpy, double aX, 7129 double aY) { 7130 EnsurePathBuilder(); 7131 7132 Point cp1(ToFloat(aCpx), ToFloat(aCpy)); 7133 Point cp2(ToFloat(aX), ToFloat(aY)); 7134 if (!cp1.IsFinite() || !cp2.IsFinite()) { 7135 return; 7136 } 7137 if (cp1 == mPathBuilder->CurrentPoint() && cp1 == cp2) { 7138 mPruned = true; 7139 return; 7140 } 7141 7142 EnsureActive(); 7143 7144 mPathBuilder->QuadraticBezierTo(cp1, cp2); 7145 mPruned = false; 7146 } 7147 7148 void CanvasPath::BezierCurveTo(double aCp1x, double aCp1y, double aCp2x, 7149 double aCp2y, double aX, double aY) { 7150 BezierTo(gfx::Point(ToFloat(aCp1x), ToFloat(aCp1y)), 7151 gfx::Point(ToFloat(aCp2x), ToFloat(aCp2y)), 7152 gfx::Point(ToFloat(aX), ToFloat(aY))); 7153 } 7154 7155 void CanvasPath::ArcTo(double aX1, double aY1, double aX2, double aY2, 7156 double aRadius, ErrorResult& aError) { 7157 if (aRadius < 0) { 7158 return aError.ThrowIndexSizeError("Negative radius"); 7159 } 7160 7161 EnsurePathBuilder(); 7162 7163 // Current point in user space! 7164 Point p0 = mPathBuilder->CurrentPoint(); 7165 Point p1(aX1, aY1); 7166 Point p2(aX2, aY2); 7167 7168 if (!p1.IsFinite() || !p2.IsFinite() || !std::isfinite(aRadius)) { 7169 return; 7170 } 7171 7172 // Execute these calculations in double precision to avoid cumulative 7173 // rounding errors. 7174 double dir, a2, b2, c2, cosx, sinx, d, anx, any, bnx, bny, x3, y3, x4, y4, cx, 7175 cy, angle0, angle1; 7176 bool anticlockwise; 7177 7178 if (p0 == p1 || p1 == p2 || aRadius == 0) { 7179 LineTo(p1); 7180 return; 7181 } 7182 7183 // Check for colinearity 7184 dir = (p2.x.value - p1.x.value) * (p0.y.value - p1.y.value) + 7185 (p2.y.value - p1.y.value) * (p1.x.value - p0.x.value); 7186 if (dir == 0) { 7187 LineTo(p1); 7188 return; 7189 } 7190 7191 // XXX - Math for this code was already available from the non-azure code 7192 // and would be well tested. Perhaps converting to bezier directly might 7193 // be more efficient longer run. 7194 a2 = (p0.x - aX1) * (p0.x - aX1) + (p0.y - aY1) * (p0.y - aY1); 7195 b2 = (aX1 - aX2) * (aX1 - aX2) + (aY1 - aY2) * (aY1 - aY2); 7196 c2 = (p0.x - aX2) * (p0.x - aX2) + (p0.y - aY2) * (p0.y - aY2); 7197 cosx = (a2 + b2 - c2) / (2 * sqrt(a2 * b2)); 7198 7199 sinx = sqrt(1 - cosx * cosx); 7200 d = aRadius / ((1 - cosx) / sinx); 7201 7202 anx = (aX1 - p0.x) / sqrt(a2); 7203 any = (aY1 - p0.y) / sqrt(a2); 7204 bnx = (aX1 - aX2) / sqrt(b2); 7205 bny = (aY1 - aY2) / sqrt(b2); 7206 x3 = aX1 - anx * d; 7207 y3 = aY1 - any * d; 7208 x4 = aX1 - bnx * d; 7209 y4 = aY1 - bny * d; 7210 anticlockwise = (dir < 0); 7211 cx = x3 + any * aRadius * (anticlockwise ? 1 : -1); 7212 cy = y3 - anx * aRadius * (anticlockwise ? 1 : -1); 7213 angle0 = atan2((y3 - cy), (x3 - cx)); 7214 angle1 = atan2((y4 - cy), (x4 - cx)); 7215 7216 LineTo(x3, y3); 7217 7218 Arc(cx, cy, aRadius, angle0, angle1, anticlockwise, aError); 7219 } 7220 7221 void CanvasPath::Rect(double aX, double aY, double aW, double aH) { 7222 EnsurePathBuilder(); 7223 7224 if (!std::isfinite(aX) || !std::isfinite(aY) || !std::isfinite(aW) || 7225 !std::isfinite(aH)) { 7226 return; 7227 } 7228 7229 MoveTo(aX, aY); 7230 if (aW == 0 && aH == 0) { 7231 return; 7232 } 7233 LineTo(aX + aW, aY); 7234 LineTo(aX + aW, aY + aH); 7235 LineTo(aX, aY + aH); 7236 ClosePath(); 7237 } 7238 7239 void CanvasPath::RoundRect( 7240 double aX, double aY, double aW, double aH, 7241 const UnrestrictedDoubleOrDOMPointInitOrUnrestrictedDoubleOrDOMPointInitSequence& 7242 aRadii, 7243 ErrorResult& aError) { 7244 EnsurePathBuilder(); 7245 7246 EnsureCapped(); 7247 RoundRectImpl(mPathBuilder, Nothing(), aX, aY, aW, aH, aRadii, aError); 7248 } 7249 7250 void CanvasPath::Arc(double aX, double aY, double aRadius, double aStartAngle, 7251 double aEndAngle, bool aAnticlockwise, 7252 ErrorResult& aError) { 7253 if (aRadius < 0.0) { 7254 return aError.ThrowIndexSizeError("Negative radius"); 7255 } 7256 if (aStartAngle == aEndAngle) { 7257 LineTo(aX + aRadius * cos(aStartAngle), aY + aRadius * sin(aStartAngle)); 7258 return; 7259 } 7260 7261 EnsurePathBuilder(); 7262 7263 EnsureActive(); 7264 7265 mPathBuilder->Arc(Point(aX, aY), aRadius, aStartAngle, aEndAngle, 7266 aAnticlockwise); 7267 mPruned = false; 7268 } 7269 7270 void CanvasPath::Ellipse(double x, double y, double radiusX, double radiusY, 7271 double rotation, double startAngle, double endAngle, 7272 bool anticlockwise, ErrorResult& aError) { 7273 if (radiusX < 0.0 || radiusY < 0.0) { 7274 return aError.ThrowIndexSizeError("Negative radius"); 7275 } 7276 7277 EnsurePathBuilder(); 7278 7279 ArcToBezier(this, Point(x, y), Size(radiusX, radiusY), startAngle, endAngle, 7280 anticlockwise, rotation); 7281 mPruned = false; 7282 } 7283 7284 void CanvasPath::LineTo(const gfx::Point& aPoint) { 7285 EnsurePathBuilder(); 7286 7287 if (!aPoint.IsFinite()) { 7288 return; 7289 } 7290 if (aPoint == mPathBuilder->CurrentPoint()) { 7291 mPruned = true; 7292 return; 7293 } 7294 7295 EnsureActive(); 7296 7297 mPathBuilder->LineTo(aPoint); 7298 mPruned = false; 7299 } 7300 7301 void CanvasPath::BezierTo(const gfx::Point& aCP1, const gfx::Point& aCP2, 7302 const gfx::Point& aCP3) { 7303 EnsurePathBuilder(); 7304 7305 if (!aCP1.IsFinite() || !aCP2.IsFinite() || !aCP3.IsFinite()) { 7306 return; 7307 } 7308 if (aCP1 == mPathBuilder->CurrentPoint() && aCP1 == aCP2 && aCP1 == aCP3) { 7309 mPruned = true; 7310 return; 7311 } 7312 7313 EnsureActive(); 7314 7315 mPathBuilder->BezierTo(aCP1, aCP2, aCP3); 7316 mPruned = false; 7317 } 7318 7319 void CanvasPath::AddPath(CanvasPath& aCanvasPath, const DOMMatrix2DInit& aInit, 7320 ErrorResult& aError) { 7321 RefPtr<gfx::DrawTarget> drawTarget = 7322 gfxPlatform::ThreadLocalScreenReferenceDrawTarget(); 7323 RefPtr<gfx::Path> tempPath = 7324 aCanvasPath.GetPath(CanvasWindingRule::Nonzero, drawTarget.get()); 7325 7326 Matrix transform(DOMMatrixReadOnly::ToValidatedMatrixDouble(aInit, aError)); 7327 if (aError.Failed()) { 7328 return; 7329 } 7330 7331 if (!transform.IsFinite()) { 7332 return; 7333 } 7334 7335 if (!transform.IsIdentity()) { 7336 Path::TransformAndSetFillRule(tempPath, transform, FillRule::FILL_WINDING); 7337 } 7338 7339 EnsurePathBuilder(); // in case a path is added to itself 7340 EnsureCapped(); 7341 tempPath->StreamToSink(mPathBuilder); 7342 } 7343 7344 already_AddRefed<gfx::Path> CanvasPath::GetPath( 7345 const CanvasWindingRule& aWinding, BackendType aBackendType) const { 7346 FillRule fillRule = FillRule::FILL_WINDING; 7347 if (aWinding == CanvasWindingRule::Evenodd) { 7348 fillRule = FillRule::FILL_EVEN_ODD; 7349 } 7350 7351 if (mPath && (mPath->GetBackendType() == aBackendType) && 7352 (mPath->GetFillRule() == fillRule)) { 7353 RefPtr<gfx::Path> path(mPath); 7354 return path.forget(); 7355 } 7356 7357 if (!mPath) { 7358 // if there is no path, there must be a pathbuilder 7359 MOZ_ASSERT(mPathBuilder); 7360 EnsureCapped(); 7361 mPath = mPathBuilder->Finish(); 7362 if (!mPath) { 7363 RefPtr<gfx::Path> path(mPath); 7364 return path.forget(); 7365 } 7366 7367 mPathBuilder = nullptr; 7368 } 7369 7370 // retarget our backend if we're used with a different backend 7371 if (mPath->GetBackendType() != aBackendType) { 7372 RefPtr<PathBuilder> tmpPathBuilder = 7373 Factory::CreatePathBuilder(aBackendType, fillRule); 7374 mPath->StreamToSink(tmpPathBuilder); 7375 mPath = tmpPathBuilder->Finish(); 7376 } else if (mPath->GetFillRule() != fillRule) { 7377 Path::SetFillRule(mPath, fillRule); 7378 } 7379 7380 RefPtr<gfx::Path> path(mPath); 7381 return path.forget(); 7382 } 7383 7384 void CanvasPath::EnsurePathBuilder() const { 7385 if (mPathBuilder) { 7386 return; 7387 } 7388 7389 // if there is not pathbuilder, there must be a path 7390 MOZ_ASSERT(mPath); 7391 mPathBuilder = Path::ToBuilder(mPath.forget()); 7392 } 7393 7394 size_t BindingJSObjectMallocBytes(CanvasRenderingContext2D* aContext) { 7395 IntSize size = aContext->GetSize(); 7396 7397 // TODO: Bug 1552137: No memory will be allocated if either dimension is 7398 // greater than gfxPrefs::gfx_canvas_max_size(). We should check this here 7399 // too. 7400 7401 CheckedInt<uint32_t> bytes = 7402 CheckedInt<uint32_t>(size.width) * size.height * 4; 7403 if (!bytes.isValid()) { 7404 return 0; 7405 } 7406 7407 return bytes.value(); 7408 } 7409 7410 } // namespace mozilla::dom