Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/canvas/CanvasRenderingContext2D.cpp
Line
Count
Source (jump to first uncovered line)
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 "mozilla/gfx/Helpers.h"
9
#include "nsXULElement.h"
10
11
#include "nsAutoPtr.h"
12
#include "nsIServiceManager.h"
13
#include "nsMathUtils.h"
14
#include "SVGImageContext.h"
15
16
#include "nsContentUtils.h"
17
18
#include "nsIDocument.h"
19
#include "mozilla/dom/HTMLCanvasElement.h"
20
#include "SVGObserverUtils.h"
21
#include "nsPresContext.h"
22
#include "nsIPresShell.h"
23
24
#include "nsIInterfaceRequestorUtils.h"
25
#include "nsIFrame.h"
26
#include "nsError.h"
27
28
#include "nsCSSPseudoElements.h"
29
#include "nsComputedDOMStyle.h"
30
31
#include "nsPrintfCString.h"
32
33
#include "nsReadableUtils.h"
34
35
#include "nsColor.h"
36
#include "nsGfxCIID.h"
37
#include "nsIDocShell.h"
38
#include "nsIDOMWindow.h"
39
#include "nsPIDOMWindow.h"
40
#include "nsDisplayList.h"
41
#include "nsFocusManager.h"
42
#include "nsContentUtils.h"
43
44
#include "nsTArray.h"
45
46
#include "ImageEncoder.h"
47
#include "ImageRegion.h"
48
49
#include "gfxContext.h"
50
#include "gfxPlatform.h"
51
#include "gfxFont.h"
52
#include "gfxBlur.h"
53
#include "gfxPrefs.h"
54
#include "gfxUtils.h"
55
56
#include "nsFrameLoader.h"
57
#include "nsBidiPresUtils.h"
58
#include "Layers.h"
59
#include "LayerUserData.h"
60
#include "CanvasUtils.h"
61
#include "nsIMemoryReporter.h"
62
#include "nsStyleUtil.h"
63
#include "CanvasImageCache.h"
64
65
#include <algorithm>
66
67
#include "jsapi.h"
68
#include "jsfriendapi.h"
69
#include "js/Conversions.h"
70
#include "js/HeapAPI.h"
71
72
#include "mozilla/Alignment.h"
73
#include "mozilla/Assertions.h"
74
#include "mozilla/CheckedInt.h"
75
#include "mozilla/DebugOnly.h"
76
#include "mozilla/dom/ContentParent.h"
77
#include "mozilla/dom/ImageBitmap.h"
78
#include "mozilla/dom/ImageData.h"
79
#include "mozilla/dom/PBrowserParent.h"
80
#include "mozilla/dom/ToJSValue.h"
81
#include "mozilla/dom/TypedArray.h"
82
#include "mozilla/EndianUtils.h"
83
#include "mozilla/gfx/2D.h"
84
#include "mozilla/gfx/Helpers.h"
85
#include "mozilla/gfx/Tools.h"
86
#include "mozilla/gfx/PathHelpers.h"
87
#include "mozilla/gfx/DataSurfaceHelpers.h"
88
#include "mozilla/gfx/PatternHelpers.h"
89
#include "mozilla/gfx/Swizzle.h"
90
#include "mozilla/layers/PersistentBufferProvider.h"
91
#include "mozilla/MathAlgorithms.h"
92
#include "mozilla/Preferences.h"
93
#include "mozilla/ServoBindings.h"
94
#include "mozilla/Telemetry.h"
95
#include "mozilla/TimeStamp.h"
96
#include "mozilla/UniquePtr.h"
97
#include "mozilla/Unused.h"
98
#include "nsCCUncollectableMarker.h"
99
#include "nsWrapperCacheInlines.h"
100
#include "mozilla/dom/CanvasRenderingContext2DBinding.h"
101
#include "mozilla/dom/CanvasPath.h"
102
#include "mozilla/dom/HTMLImageElement.h"
103
#include "mozilla/dom/HTMLVideoElement.h"
104
#include "mozilla/dom/SVGImageElement.h"
105
#include "mozilla/dom/SVGMatrix.h"
106
#include "mozilla/dom/TextMetrics.h"
107
#include "mozilla/dom/SVGMatrix.h"
108
#include "mozilla/FloatingPoint.h"
109
#include "nsGlobalWindow.h"
110
#include "GLContext.h"
111
#include "GLContextProvider.h"
112
#include "SVGContentUtils.h"
113
#include "nsIScreenManager.h"
114
#include "nsFilterInstance.h"
115
#include "nsSVGLength2.h"
116
#include "nsDeviceContext.h"
117
#include "nsFontMetrics.h"
118
#include "Units.h"
119
#include "CanvasUtils.h"
120
#include "mozilla/CycleCollectedJSRuntime.h"
121
#include "mozilla/ServoStyleSet.h"
122
#include "mozilla/layers/CanvasClient.h"
123
#include "mozilla/layers/WebRenderUserData.h"
124
#include "mozilla/layers/WebRenderCanvasRenderer.h"
125
#include "mozilla/ServoCSSParser.h"
126
127
#undef free // apparently defined by some windows header, clashing with a free()
128
            // method in SkTypes.h
129
#include "SkiaGLGlue.h"
130
#ifdef USE_SKIA
131
#include "SurfaceTypes.h"
132
#include "GLBlitHelper.h"
133
#include "ScopedGLHelpers.h"
134
#endif
135
136
using mozilla::gl::GLContext;
137
using mozilla::gl::SkiaGLGlue;
138
using mozilla::gl::GLContextProvider;
139
140
#ifdef XP_WIN
141
#include "gfxWindowsPlatform.h"
142
#endif
143
144
// windows.h (included by chromium code) defines this, in its infinite wisdom
145
#undef DrawText
146
147
using namespace mozilla;
148
using namespace mozilla::CanvasUtils;
149
using namespace mozilla::css;
150
using namespace mozilla::gfx;
151
using namespace mozilla::image;
152
using namespace mozilla::ipc;
153
using namespace mozilla::layers;
154
155
namespace mozilla {
156
namespace dom {
157
158
// Cap sigma to avoid overly large temp surfaces.
159
const Float SIGMA_MAX = 100;
160
161
const size_t MAX_STYLE_STACK_SIZE = 1024;
162
163
/* Memory reporter stuff */
164
static int64_t gCanvasAzureMemoryUsed = 0;
165
166
// Adds Save() / Restore() calls to the scope.
167
class MOZ_RAII AutoSaveRestore
168
{
169
public:
170
  explicit AutoSaveRestore(CanvasRenderingContext2D* aCtx
171
                           MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
172
    : mCtx(aCtx)
173
0
  {
174
0
    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
175
0
    mCtx->Save();
176
0
  }
177
0
  ~AutoSaveRestore() { mCtx->Restore(); }
178
private:
179
  RefPtr<CanvasRenderingContext2D> mCtx;
180
  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
181
};
182
183
// This is KIND_OTHER because it's not always clear where in memory the pixels
184
// of a canvas are stored.  Furthermore, this memory will be tracked by the
185
// underlying surface implementations.  See bug 655638 for details.
186
class Canvas2dPixelsReporter final : public nsIMemoryReporter
187
{
188
0
  ~Canvas2dPixelsReporter() {}
189
public:
190
  NS_DECL_ISUPPORTS
191
192
  NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
193
                            nsISupports* aData, bool aAnonymize) override
194
0
  {
195
0
    MOZ_COLLECT_REPORT(
196
0
      "canvas-2d-pixels", KIND_OTHER, UNITS_BYTES, gCanvasAzureMemoryUsed,
197
0
      "Memory used by 2D canvases. Each canvas requires "
198
0
      "(width * height * 4) bytes.");
199
0
200
0
    return NS_OK;
201
0
  }
202
};
203
204
NS_IMPL_ISUPPORTS(Canvas2dPixelsReporter, nsIMemoryReporter)
205
206
class CanvasRadialGradient : public CanvasGradient
207
{
208
public:
209
  CanvasRadialGradient(CanvasRenderingContext2D* aContext,
210
                       const Point& aBeginOrigin, Float aBeginRadius,
211
                       const Point& aEndOrigin, Float aEndRadius)
212
    : CanvasGradient(aContext, Type::RADIAL)
213
    , mCenter1(aBeginOrigin)
214
    , mCenter2(aEndOrigin)
215
    , mRadius1(aBeginRadius)
216
    , mRadius2(aEndRadius)
217
0
  {
218
0
  }
219
220
  Point mCenter1;
221
  Point mCenter2;
222
  Float mRadius1;
223
  Float mRadius2;
224
};
225
226
class CanvasLinearGradient : public CanvasGradient
227
{
228
public:
229
  CanvasLinearGradient(CanvasRenderingContext2D* aContext,
230
                       const Point& aBegin, const Point& aEnd)
231
    : CanvasGradient(aContext, Type::LINEAR)
232
    , mBegin(aBegin)
233
    , mEnd(aEnd)
234
0
  {
235
0
  }
236
237
protected:
238
  friend struct CanvasBidiProcessor;
239
  friend class CanvasGeneralPattern;
240
241
  // Beginning of linear gradient.
242
  Point mBegin;
243
  // End of linear gradient.
244
  Point mEnd;
245
};
246
247
bool
248
CanvasRenderingContext2D::PatternIsOpaque(CanvasRenderingContext2D::Style aStyle) const
249
0
{
250
0
  const ContextState& state = CurrentState();
251
0
  if (state.globalAlpha < 1.0) {
252
0
    return false;
253
0
  }
254
0
255
0
  if (state.patternStyles[aStyle] && state.patternStyles[aStyle]->mSurface) {
256
0
    return IsOpaqueFormat(state.patternStyles[aStyle]->mSurface->GetFormat());
257
0
  }
258
0
259
0
  // TODO: for gradient patterns we could check that all stops are opaque
260
0
  // colors.
261
0
262
0
  if (!state.gradientStyles[aStyle]) {
263
0
    // it's a color pattern.
264
0
    return Color::FromABGR(state.colorStyles[aStyle]).a >= 1.0;
265
0
  }
266
0
267
0
  return false;
268
0
}
269
270
// This class is named 'GeneralCanvasPattern' instead of just
271
// 'GeneralPattern' to keep Windows PGO builds from confusing the
272
// GeneralPattern class in gfxContext.cpp with this one.
273
class CanvasGeneralPattern
274
{
275
public:
276
  typedef CanvasRenderingContext2D::Style Style;
277
  typedef CanvasRenderingContext2D::ContextState ContextState;
278
279
  Pattern& ForStyle(CanvasRenderingContext2D* aCtx,
280
                    Style aStyle,
281
                    DrawTarget* aRT)
282
0
  {
283
0
    // This should only be called once or the mPattern destructor will
284
0
    // not be executed.
285
0
    NS_ASSERTION(!mPattern.GetPattern(), "ForStyle() should only be called once on CanvasGeneralPattern!");
286
0
287
0
    const ContextState& state = aCtx->CurrentState();
288
0
289
0
    if (state.StyleIsColor(aStyle)) {
290
0
      mPattern.InitColorPattern(ToDeviceColor(state.colorStyles[aStyle]));
291
0
    } else if (state.gradientStyles[aStyle] &&
292
0
               state.gradientStyles[aStyle]->GetType() == CanvasGradient::Type::LINEAR) {
293
0
      auto gradient =
294
0
        static_cast<CanvasLinearGradient*>(state.gradientStyles[aStyle].get());
295
0
296
0
      mPattern.InitLinearGradientPattern(gradient->mBegin, gradient->mEnd,
297
0
                                         gradient->GetGradientStopsForTarget(aRT));
298
0
    } else if (state.gradientStyles[aStyle] &&
299
0
               state.gradientStyles[aStyle]->GetType() == CanvasGradient::Type::RADIAL) {
300
0
      auto gradient =
301
0
        static_cast<CanvasRadialGradient*>(state.gradientStyles[aStyle].get());
302
0
303
0
      mPattern.InitRadialGradientPattern(gradient->mCenter1, gradient->mCenter2,
304
0
                                         gradient->mRadius1, gradient->mRadius2,
305
0
                                         gradient->GetGradientStopsForTarget(aRT));
306
0
    } else if (state.patternStyles[aStyle]) {
307
0
      if (aCtx->mCanvasElement) {
308
0
        CanvasUtils::DoDrawImageSecurityCheck(aCtx->mCanvasElement,
309
0
                                              state.patternStyles[aStyle]->mPrincipal,
310
0
                                              state.patternStyles[aStyle]->mForceWriteOnly,
311
0
                                              state.patternStyles[aStyle]->mCORSUsed);
312
0
      }
313
0
314
0
      ExtendMode mode;
315
0
      if (state.patternStyles[aStyle]->mRepeat == CanvasPattern::RepeatMode::NOREPEAT) {
316
0
        mode = ExtendMode::CLAMP;
317
0
      } else {
318
0
        mode = ExtendMode::REPEAT;
319
0
      }
320
0
321
0
      SamplingFilter samplingFilter;
322
0
      if (state.imageSmoothingEnabled) {
323
0
        samplingFilter = SamplingFilter::GOOD;
324
0
      } else {
325
0
        samplingFilter = SamplingFilter::POINT;
326
0
      }
327
0
328
0
      mPattern.InitSurfacePattern(state.patternStyles[aStyle]->mSurface, mode,
329
0
                                  state.patternStyles[aStyle]->mTransform,
330
0
                                  samplingFilter);
331
0
    }
332
0
333
0
    return *mPattern.GetPattern();
334
0
  }
335
336
  GeneralPattern mPattern;
337
};
338
339
/* This is an RAII based class that can be used as a drawtarget for
340
 * operations that need to have a filter applied to their results.
341
 * All coordinates passed to the constructor are in device space.
342
 */
343
class AdjustedTargetForFilter
344
{
345
public:
346
  typedef CanvasRenderingContext2D::ContextState ContextState;
347
348
  AdjustedTargetForFilter(CanvasRenderingContext2D* aCtx,
349
                          DrawTarget* aFinalTarget,
350
                          const gfx::IntPoint& aFilterSpaceToTargetOffset,
351
                          const gfx::IntRect& aPreFilterBounds,
352
                          const gfx::IntRect& aPostFilterBounds,
353
                          gfx::CompositionOp aCompositionOp)
354
    : mFinalTarget(aFinalTarget)
355
    , mCtx(aCtx)
356
    , mPostFilterBounds(aPostFilterBounds)
357
    , mOffset(aFilterSpaceToTargetOffset)
358
    , mCompositionOp(aCompositionOp)
359
0
  {
360
0
    nsIntRegion sourceGraphicNeededRegion;
361
0
    nsIntRegion fillPaintNeededRegion;
362
0
    nsIntRegion strokePaintNeededRegion;
363
0
364
0
    FilterSupport::ComputeSourceNeededRegions(
365
0
      aCtx->CurrentState().filter, mPostFilterBounds,
366
0
      sourceGraphicNeededRegion, fillPaintNeededRegion,
367
0
      strokePaintNeededRegion);
368
0
369
0
    mSourceGraphicRect = sourceGraphicNeededRegion.GetBounds();
370
0
    mFillPaintRect = fillPaintNeededRegion.GetBounds();
371
0
    mStrokePaintRect = strokePaintNeededRegion.GetBounds();
372
0
373
0
    mSourceGraphicRect = mSourceGraphicRect.Intersect(aPreFilterBounds);
374
0
375
0
    if (mSourceGraphicRect.IsEmpty()) {
376
0
      // The filter might not make any use of the source graphic. We need to
377
0
      // create a DrawTarget that we can return from DT() anyway, so we'll
378
0
      // just use a 1x1-sized one.
379
0
      mSourceGraphicRect.SizeTo(1, 1);
380
0
    }
381
0
382
0
    mTarget = mFinalTarget->CreateSimilarDrawTarget(mSourceGraphicRect.Size(),
383
0
                                                    SurfaceFormat::B8G8R8A8);
384
0
385
0
    if (!mTarget) {
386
0
      // XXX - Deal with the situation where our temp size is too big to
387
0
      // fit in a texture (bug 1066622).
388
0
      mTarget = mFinalTarget;
389
0
      mCtx = nullptr;
390
0
      mFinalTarget = nullptr;
391
0
      return;
392
0
    }
393
0
394
0
    mTarget->SetTransform(
395
0
      mFinalTarget->GetTransform().PostTranslate(-mSourceGraphicRect.TopLeft() + mOffset));
396
0
  }
397
398
  // Return a SourceSurface that contains the FillPaint or StrokePaint source.
399
  already_AddRefed<SourceSurface>
400
  DoSourcePaint(gfx::IntRect& aRect, CanvasRenderingContext2D::Style aStyle)
401
0
  {
402
0
    if (aRect.IsEmpty()) {
403
0
      return nullptr;
404
0
    }
405
0
406
0
    RefPtr<DrawTarget> dt =
407
0
      mFinalTarget->CreateSimilarDrawTarget(aRect.Size(), SurfaceFormat::B8G8R8A8);
408
0
    if (!dt) {
409
0
      aRect.SetEmpty();
410
0
      return nullptr;
411
0
    }
412
0
413
0
    Matrix transform =
414
0
      mFinalTarget->GetTransform().PostTranslate(-aRect.TopLeft() + mOffset);
415
0
416
0
    dt->SetTransform(transform);
417
0
418
0
    if (transform.Invert()) {
419
0
      gfx::Rect dtBounds(0, 0, aRect.width, aRect.height);
420
0
      gfx::Rect fillRect = transform.TransformBounds(dtBounds);
421
0
      dt->FillRect(fillRect, CanvasGeneralPattern().ForStyle(mCtx, aStyle, dt));
422
0
    }
423
0
    return dt->Snapshot();
424
0
  }
425
426
  ~AdjustedTargetForFilter()
427
0
  {
428
0
    if (!mCtx) {
429
0
      return;
430
0
    }
431
0
432
0
    RefPtr<SourceSurface> snapshot = mTarget->Snapshot();
433
0
434
0
    RefPtr<SourceSurface> fillPaint =
435
0
      DoSourcePaint(mFillPaintRect, CanvasRenderingContext2D::Style::FILL);
436
0
    RefPtr<SourceSurface> strokePaint =
437
0
      DoSourcePaint(mStrokePaintRect, CanvasRenderingContext2D::Style::STROKE);
438
0
439
0
    AutoRestoreTransform autoRestoreTransform(mFinalTarget);
440
0
    mFinalTarget->SetTransform(Matrix());
441
0
442
0
    MOZ_RELEASE_ASSERT(!mCtx->CurrentState().filter.mPrimitives.IsEmpty());
443
0
    gfx::FilterSupport::RenderFilterDescription(
444
0
      mFinalTarget, mCtx->CurrentState().filter,
445
0
      gfx::Rect(mPostFilterBounds),
446
0
      snapshot, mSourceGraphicRect,
447
0
      fillPaint, mFillPaintRect,
448
0
      strokePaint, mStrokePaintRect,
449
0
      mCtx->CurrentState().filterAdditionalImages,
450
0
      mPostFilterBounds.TopLeft() - mOffset,
451
0
      DrawOptions(1.0f, mCompositionOp));
452
0
453
0
    const gfx::FilterDescription& filter = mCtx->CurrentState().filter;
454
0
    MOZ_RELEASE_ASSERT(!filter.mPrimitives.IsEmpty());
455
0
    if (filter.mPrimitives.LastElement().IsTainted() && mCtx->mCanvasElement) {
456
0
      mCtx->mCanvasElement->SetWriteOnly();
457
0
    }
458
0
  }
459
460
  DrawTarget* DT()
461
0
  {
462
0
    return mTarget;
463
0
  }
464
465
private:
466
  RefPtr<DrawTarget> mTarget;
467
  RefPtr<DrawTarget> mFinalTarget;
468
  CanvasRenderingContext2D* mCtx;
469
  gfx::IntRect mSourceGraphicRect;
470
  gfx::IntRect mFillPaintRect;
471
  gfx::IntRect mStrokePaintRect;
472
  gfx::IntRect mPostFilterBounds;
473
  gfx::IntPoint mOffset;
474
  gfx::CompositionOp mCompositionOp;
475
};
476
477
/* This is an RAII based class that can be used as a drawtarget for
478
 * operations that need to have a shadow applied to their results.
479
 * All coordinates passed to the constructor are in device space.
480
 */
481
class AdjustedTargetForShadow
482
{
483
public:
484
  typedef CanvasRenderingContext2D::ContextState ContextState;
485
486
  AdjustedTargetForShadow(CanvasRenderingContext2D* aCtx,
487
                          DrawTarget* aFinalTarget,
488
                          const gfx::Rect& aBounds,
489
                          gfx::CompositionOp aCompositionOp)
490
    : mFinalTarget(aFinalTarget)
491
    , mCtx(aCtx)
492
    , mCompositionOp(aCompositionOp)
493
0
  {
494
0
    const ContextState& state = mCtx->CurrentState();
495
0
    mSigma = state.ShadowBlurSigma();
496
0
497
0
    // We actually include the bounds of the shadow blur, this makes it
498
0
    // easier to execute the actual blur on hardware, and shouldn't affect
499
0
    // the amount of pixels that need to be touched.
500
0
    gfx::Rect bounds = aBounds;
501
0
    int32_t blurRadius = state.ShadowBlurRadius();
502
0
    bounds.Inflate(blurRadius);
503
0
    bounds.RoundOut();
504
0
    bounds.ToIntRect(&mTempRect);
505
0
506
0
    mTarget =
507
0
      mFinalTarget->CreateShadowDrawTarget(mTempRect.Size(),
508
0
                                           SurfaceFormat::B8G8R8A8, mSigma);
509
0
510
0
    if (!mTarget) {
511
0
      // XXX - Deal with the situation where our temp size is too big to
512
0
      // fit in a texture (bug 1066622).
513
0
      mTarget = mFinalTarget;
514
0
      mCtx = nullptr;
515
0
      mFinalTarget = nullptr;
516
0
    } else {
517
0
      mTarget->SetTransform(
518
0
        mFinalTarget->GetTransform().PostTranslate(-mTempRect.TopLeft()));
519
0
    }
520
0
  }
521
522
  ~AdjustedTargetForShadow()
523
0
  {
524
0
    if (!mCtx) {
525
0
      return;
526
0
    }
527
0
528
0
    RefPtr<SourceSurface> snapshot = mTarget->Snapshot();
529
0
530
0
    mFinalTarget->DrawSurfaceWithShadow(snapshot, mTempRect.TopLeft(),
531
0
                                        Color::FromABGR(mCtx->CurrentState().shadowColor),
532
0
                                        mCtx->CurrentState().shadowOffset, mSigma,
533
0
                                        mCompositionOp);
534
0
  }
535
536
  DrawTarget* DT()
537
0
  {
538
0
    return mTarget;
539
0
  }
540
541
  gfx::IntPoint OffsetToFinalDT()
542
0
  {
543
0
    return mTempRect.TopLeft();
544
0
  }
545
546
private:
547
  RefPtr<DrawTarget> mTarget;
548
  RefPtr<DrawTarget> mFinalTarget;
549
  CanvasRenderingContext2D* mCtx;
550
  Float mSigma;
551
  gfx::IntRect mTempRect;
552
  gfx::CompositionOp mCompositionOp;
553
};
554
555
/*
556
 * This is an RAII based class that can be used as a drawtarget for
557
 * operations that need a shadow or a filter drawn. It will automatically
558
 * provide a temporary target when needed, and if so blend it back with a
559
 * shadow, filter, or both.
560
 * If both a shadow and a filter are needed, the filter is applied first,
561
 * and the shadow is applied to the filtered results.
562
 *
563
 * aBounds specifies the bounds of the drawing operation that will be
564
 * drawn to the target, it is given in device space! If this is nullptr the
565
 * drawing operation will be assumed to cover the whole canvas.
566
 */
567
class AdjustedTarget
568
{
569
public:
570
  typedef CanvasRenderingContext2D::ContextState ContextState;
571
572
  explicit AdjustedTarget(CanvasRenderingContext2D* aCtx,
573
                          const gfx::Rect* aBounds = nullptr)
574
0
  {
575
0
    // All rects in this function are in the device space of ctx->mTarget.
576
0
577
0
    // In order to keep our temporary surfaces as small as possible, we first
578
0
    // calculate what their maximum required bounds would need to be if we
579
0
    // were to fill the whole canvas. Everything outside those bounds we don't
580
0
    // need to render.
581
0
    gfx::Rect r(0, 0, aCtx->mWidth, aCtx->mHeight);
582
0
    gfx::Rect maxSourceNeededBoundsForShadow =
583
0
      MaxSourceNeededBoundsForShadow(r, aCtx);
584
0
    gfx::Rect maxSourceNeededBoundsForFilter =
585
0
      MaxSourceNeededBoundsForFilter(maxSourceNeededBoundsForShadow, aCtx);
586
0
    if (!aCtx->IsTargetValid()) {
587
0
      return;
588
0
    }
589
0
590
0
    gfx::Rect bounds = maxSourceNeededBoundsForFilter;
591
0
    if (aBounds) {
592
0
      bounds = bounds.Intersect(*aBounds);
593
0
    }
594
0
    gfx::Rect boundsAfterFilter = BoundsAfterFilter(bounds, aCtx);
595
0
    if (!aCtx->IsTargetValid()) {
596
0
      return;
597
0
    }
598
0
599
0
    mozilla::gfx::CompositionOp op = aCtx->CurrentState().op;
600
0
601
0
    gfx::IntPoint offsetToFinalDT;
602
0
603
0
    // First set up the shadow draw target, because the shadow goes outside.
604
0
    // It applies to the post-filter results, if both a filter and a shadow
605
0
    // are used.
606
0
    if (aCtx->NeedToDrawShadow()) {
607
0
      mShadowTarget = MakeUnique<AdjustedTargetForShadow>(
608
0
        aCtx, aCtx->mTarget, boundsAfterFilter, op);
609
0
      mTarget = mShadowTarget->DT();
610
0
      offsetToFinalDT = mShadowTarget->OffsetToFinalDT();
611
0
612
0
      // If we also have a filter, the filter needs to be drawn with OP_OVER
613
0
      // because shadow drawing already applies op on the result.
614
0
      op = gfx::CompositionOp::OP_OVER;
615
0
    }
616
0
617
0
    // Now set up the filter draw target.
618
0
    const bool applyFilter = aCtx->NeedToApplyFilter();
619
0
    if (!aCtx->IsTargetValid()) {
620
0
      return;
621
0
    }
622
0
    if (applyFilter) {
623
0
      bounds.RoundOut();
624
0
625
0
      if (!mTarget) {
626
0
        mTarget = aCtx->mTarget;
627
0
      }
628
0
      gfx::IntRect intBounds;
629
0
      if (!bounds.ToIntRect(&intBounds)) {
630
0
        return;
631
0
      }
632
0
      mFilterTarget = MakeUnique<AdjustedTargetForFilter>(
633
0
        aCtx, mTarget, offsetToFinalDT, intBounds,
634
0
        gfx::RoundedToInt(boundsAfterFilter), op);
635
0
      mTarget = mFilterTarget->DT();
636
0
    }
637
0
    if (!mTarget) {
638
0
      mTarget = aCtx->mTarget;
639
0
    }
640
0
  }
641
642
  ~AdjustedTarget()
643
0
  {
644
0
    // The order in which the targets are finalized is important.
645
0
    // Filters are inside, any shadow applies to the post-filter results.
646
0
    mFilterTarget.reset();
647
0
    mShadowTarget.reset();
648
0
  }
649
650
  operator DrawTarget*()
651
0
  {
652
0
    return mTarget;
653
0
  }
654
655
  DrawTarget* operator->() MOZ_NO_ADDREF_RELEASE_ON_RETURN
656
0
  {
657
0
    return mTarget;
658
0
  }
659
660
private:
661
662
  gfx::Rect
663
  MaxSourceNeededBoundsForFilter(const gfx::Rect& aDestBounds, CanvasRenderingContext2D* aCtx)
664
0
  {
665
0
    const bool applyFilter = aCtx->NeedToApplyFilter();
666
0
    if (!aCtx->IsTargetValid()) {
667
0
      return aDestBounds;
668
0
    }
669
0
    if (!applyFilter) {
670
0
      return aDestBounds;
671
0
    }
672
0
673
0
    nsIntRegion sourceGraphicNeededRegion;
674
0
    nsIntRegion fillPaintNeededRegion;
675
0
    nsIntRegion strokePaintNeededRegion;
676
0
677
0
    FilterSupport::ComputeSourceNeededRegions(
678
0
      aCtx->CurrentState().filter, gfx::RoundedToInt(aDestBounds),
679
0
      sourceGraphicNeededRegion, fillPaintNeededRegion, strokePaintNeededRegion);
680
0
681
0
    return gfx::Rect(sourceGraphicNeededRegion.GetBounds());
682
0
  }
683
684
  gfx::Rect
685
  MaxSourceNeededBoundsForShadow(const gfx::Rect& aDestBounds, CanvasRenderingContext2D* aCtx)
686
0
  {
687
0
    if (!aCtx->NeedToDrawShadow()) {
688
0
      return aDestBounds;
689
0
    }
690
0
691
0
    const ContextState& state = aCtx->CurrentState();
692
0
    gfx::Rect sourceBounds = aDestBounds - state.shadowOffset;
693
0
    sourceBounds.Inflate(state.ShadowBlurRadius());
694
0
695
0
    // Union the shadow source with the original rect because we're going to
696
0
    // draw both.
697
0
    return sourceBounds.Union(aDestBounds);
698
0
  }
699
700
  gfx::Rect
701
  BoundsAfterFilter(const gfx::Rect& aBounds, CanvasRenderingContext2D* aCtx)
702
0
  {
703
0
    const bool applyFilter = aCtx->NeedToApplyFilter();
704
0
    if (!aCtx->IsTargetValid()) {
705
0
      return aBounds;
706
0
    }
707
0
    if (!applyFilter) {
708
0
      return aBounds;
709
0
    }
710
0
711
0
    gfx::Rect bounds(aBounds);
712
0
    bounds.RoundOut();
713
0
714
0
    gfx::IntRect intBounds;
715
0
    if (!bounds.ToIntRect(&intBounds)) {
716
0
      return gfx::Rect();
717
0
    }
718
0
719
0
    nsIntRegion extents =
720
0
      gfx::FilterSupport::ComputePostFilterExtents(aCtx->CurrentState().filter,
721
0
                                                   intBounds);
722
0
    return gfx::Rect(extents.GetBounds());
723
0
  }
724
725
  RefPtr<DrawTarget> mTarget;
726
  UniquePtr<AdjustedTargetForShadow> mShadowTarget;
727
  UniquePtr<AdjustedTargetForFilter> mFilterTarget;
728
};
729
730
void
731
CanvasPattern::SetTransform(SVGMatrix& aMatrix)
732
0
{
733
0
  mTransform = ToMatrix(aMatrix.GetMatrix());
734
0
}
735
736
void
737
CanvasGradient::AddColorStop(float aOffset, const nsAString& aColorstr, ErrorResult& aRv)
738
0
{
739
0
  if (aOffset < 0.0 || aOffset > 1.0) {
740
0
    aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
741
0
    return;
742
0
  }
743
0
744
0
  nsIPresShell* shell = mContext ? mContext->GetPresShell() : nullptr;
745
0
  ServoStyleSet* styleSet = shell ? shell->StyleSet() : nullptr;
746
0
747
0
  nscolor color;
748
0
  bool ok = ServoCSSParser::ComputeColor(styleSet, NS_RGB(0, 0, 0),
749
0
                                         aColorstr, &color);
750
0
751
0
  if (!ok) {
752
0
    aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
753
0
    return;
754
0
  }
755
0
756
0
  mStops = nullptr;
757
0
758
0
  GradientStop newStop;
759
0
760
0
  newStop.offset = aOffset;
761
0
  newStop.color = Color::FromABGR(color);
762
0
763
0
  mRawStops.AppendElement(newStop);
764
0
}
765
766
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CanvasGradient, AddRef)
767
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CanvasGradient, Release)
768
769
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasGradient, mContext)
770
771
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CanvasPattern, AddRef)
772
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CanvasPattern, Release)
773
774
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasPattern, mContext)
775
776
class CanvasShutdownObserver final : public nsIObserver
777
{
778
public:
779
  explicit CanvasShutdownObserver(CanvasRenderingContext2D* aCanvas)
780
  : mCanvas(aCanvas)
781
0
  {}
782
783
  NS_DECL_ISUPPORTS
784
  NS_DECL_NSIOBSERVER
785
private:
786
0
  ~CanvasShutdownObserver() {}
787
788
  CanvasRenderingContext2D* mCanvas;
789
};
790
791
NS_IMPL_ISUPPORTS(CanvasShutdownObserver, nsIObserver)
792
793
NS_IMETHODIMP
794
CanvasShutdownObserver::Observe(nsISupports* aSubject,
795
                                const char* aTopic,
796
                                const char16_t* aData)
797
0
{
798
0
  if (mCanvas && strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
799
0
    mCanvas->OnShutdown();
800
0
    nsContentUtils::UnregisterShutdownObserver(this);
801
0
  }
802
0
803
0
  return NS_OK;
804
0
}
805
806
class CanvasDrawObserver
807
{
808
public:
809
  explicit CanvasDrawObserver(CanvasRenderingContext2D* aCanvasContext);
810
811
  // Only enumerate draw calls that could affect the heuristic
812
  enum DrawCallType {
813
    PutImageData,
814
    GetImageData,
815
    DrawImage
816
  };
817
818
  // This is the one that we call on relevant draw calls and count
819
  // GPU vs. CPU preferrable calls...
820
  void DidDrawCall(DrawCallType aType);
821
822
  // When this returns true, the observer is done making the decisions.
823
  // Right now, we expect to get rid of the observer after the FrameEnd
824
  // returns true, though the decision could eventually change if the
825
  // function calls shift.  If we change to monitor the functions called
826
  // and make decisions to change more than once, we would probably want
827
  // FrameEnd to reset the timer and counters as it returns true.
828
  bool FrameEnd();
829
830
private:
831
  // These values will be picked up from preferences:
832
  int32_t mMinFramesBeforeDecision;
833
  float mMinSecondsBeforeDecision;
834
  int32_t mMinCallsBeforeDecision;
835
836
  CanvasRenderingContext2D* mCanvasContext;
837
  int32_t mSoftwarePreferredCalls;
838
  int32_t mGPUPreferredCalls;
839
  int32_t mFramesRendered;
840
  TimeStamp mCreationTime;
841
};
842
843
// We are not checking for the validity of the preference values.  For example,
844
// negative values will have an effect of a quick exit, so no harm done.
845
CanvasDrawObserver::CanvasDrawObserver(CanvasRenderingContext2D* aCanvasContext)
846
 : mMinFramesBeforeDecision(gfxPrefs::CanvasAutoAccelerateMinFrames())
847
 , mMinSecondsBeforeDecision(gfxPrefs::CanvasAutoAccelerateMinSeconds())
848
 , mMinCallsBeforeDecision(gfxPrefs::CanvasAutoAccelerateMinCalls())
849
 , mCanvasContext(aCanvasContext)
850
 , mSoftwarePreferredCalls(0)
851
 , mGPUPreferredCalls(0)
852
 , mFramesRendered(0)
853
 , mCreationTime(TimeStamp::NowLoRes())
854
0
{}
855
856
void
857
CanvasDrawObserver::DidDrawCall(DrawCallType aType)
858
0
{
859
0
  switch (aType) {
860
0
    case PutImageData:
861
0
    case GetImageData:
862
0
      if (mGPUPreferredCalls == 0 && mSoftwarePreferredCalls == 0) {
863
0
        mCreationTime = TimeStamp::NowLoRes();
864
0
      }
865
0
      mSoftwarePreferredCalls++;
866
0
      break;
867
0
    case DrawImage:
868
0
      if (mGPUPreferredCalls == 0 && mSoftwarePreferredCalls == 0) {
869
0
        mCreationTime = TimeStamp::NowLoRes();
870
0
      }
871
0
      mGPUPreferredCalls++;
872
0
      break;
873
0
  }
874
0
}
875
876
// If we return true, the observer is done making the decisions...
877
bool
878
CanvasDrawObserver::FrameEnd()
879
0
{
880
0
  mFramesRendered++;
881
0
882
0
  // We log the first mMinFramesBeforeDecision frames of any
883
0
  // canvas object then make a call to determine whether it should
884
0
  // be GPU or CPU backed
885
0
  if ((mFramesRendered >= mMinFramesBeforeDecision) ||
886
0
      ((TimeStamp::NowLoRes() - mCreationTime).ToSeconds()) > mMinSecondsBeforeDecision) {
887
0
888
0
    // If we don't have enough data, don't bother changing...
889
0
    if (mGPUPreferredCalls > mMinCallsBeforeDecision ||
890
0
        mSoftwarePreferredCalls > mMinCallsBeforeDecision) {
891
0
      CanvasRenderingContext2D::RenderingMode switchToMode;
892
0
      if (mGPUPreferredCalls >= mSoftwarePreferredCalls) {
893
0
        switchToMode = CanvasRenderingContext2D::RenderingMode::OpenGLBackendMode;
894
0
      } else {
895
0
        switchToMode = CanvasRenderingContext2D::RenderingMode::SoftwareBackendMode;
896
0
      }
897
0
      if (switchToMode != mCanvasContext->mRenderingMode) {
898
0
        if (!mCanvasContext->SwitchRenderingMode(switchToMode)) {
899
0
          gfxDebug() << "Canvas acceleration failed mode switch to " << switchToMode;
900
0
        }
901
0
      }
902
0
    }
903
0
904
0
    // If we ever redesign this class to constantly monitor the functions
905
0
    // and keep making decisions, we would probably want to reset the counters
906
0
    // and the timers here...
907
0
    return true;
908
0
  }
909
0
  return false;
910
0
}
911
912
class CanvasRenderingContext2DUserData : public LayerUserData {
913
public:
914
  explicit CanvasRenderingContext2DUserData(CanvasRenderingContext2D* aContext)
915
    : mContext(aContext)
916
0
  {
917
0
    aContext->mUserDatas.AppendElement(this);
918
0
  }
919
  ~CanvasRenderingContext2DUserData()
920
0
  {
921
0
    if (mContext) {
922
0
      mContext->mUserDatas.RemoveElement(this);
923
0
    }
924
0
  }
925
926
  static void PreTransactionCallback(void* aData)
927
0
  {
928
0
    CanvasRenderingContext2D* context = static_cast<CanvasRenderingContext2D*>(aData);
929
0
    if (!context || !context->mTarget)
930
0
      return;
931
0
932
0
    context->OnStableState();
933
0
  }
934
935
  static void DidTransactionCallback(void* aData)
936
0
  {
937
0
    CanvasRenderingContext2D* context = static_cast<CanvasRenderingContext2D*>(aData);
938
0
    if (context) {
939
0
      context->MarkContextClean();
940
0
      if (context->mDrawObserver) {
941
0
        if (context->mDrawObserver->FrameEnd()) {
942
0
          // Note that this call deletes and nulls out mDrawObserver:
943
0
          context->RemoveDrawObserver();
944
0
        }
945
0
      }
946
0
    }
947
0
  }
948
  bool IsForContext(CanvasRenderingContext2D* aContext)
949
0
  {
950
0
    return mContext == aContext;
951
0
  }
952
  void Forget()
953
0
  {
954
0
    mContext = nullptr;
955
0
  }
956
957
private:
958
  CanvasRenderingContext2D* mContext;
959
};
960
961
class SVGFilterObserverListForCanvas final : public SVGFilterObserverList
962
{
963
public:
964
  SVGFilterObserverListForCanvas(nsTArray<nsStyleFilter>& aFilters,
965
                                 Element* aCanvasElement,
966
                                 CanvasRenderingContext2D* aContext)
967
    : SVGFilterObserverList(aFilters, aCanvasElement)
968
    , mContext(aContext)
969
0
  {
970
0
  }
971
972
  virtual void OnRenderingChange() override
973
0
  {
974
0
    if (!mContext) {
975
0
      MOZ_CRASH("GFX: This should never be called without a context");
976
0
    }
977
0
    // Refresh the cached FilterDescription in mContext->CurrentState().filter.
978
0
    // If this filter is not at the top of the state stack, we'll refresh the
979
0
    // wrong filter, but that's ok, because we'll refresh the right filter
980
0
    // when we pop the state stack in CanvasRenderingContext2D::Restore().
981
0
    RefPtr<CanvasRenderingContext2D> kungFuDeathGrip(mContext);
982
0
    kungFuDeathGrip->UpdateFilter();
983
0
  }
984
985
0
  void DetachFromContext() { mContext = nullptr; }
986
987
private:
988
  CanvasRenderingContext2D* mContext;
989
};
990
991
NS_IMPL_CYCLE_COLLECTING_ADDREF(CanvasRenderingContext2D)
992
NS_IMPL_CYCLE_COLLECTING_RELEASE(CanvasRenderingContext2D)
993
994
NS_IMPL_CYCLE_COLLECTION_CLASS(CanvasRenderingContext2D)
995
996
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CanvasRenderingContext2D)
997
0
  // Make sure we remove ourselves from the list of demotable contexts (raw pointers),
998
0
  // since we're logically destructed at this point.
999
0
  CanvasRenderingContext2D::RemoveDemotableContext(tmp);
1000
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mCanvasElement)
1001
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocShell)
1002
0
  for (uint32_t i = 0; i < tmp->mStyleStack.Length(); i++) {
1003
0
    ImplCycleCollectionUnlink(tmp->mStyleStack[i].patternStyles[Style::STROKE]);
1004
0
    ImplCycleCollectionUnlink(tmp->mStyleStack[i].patternStyles[Style::FILL]);
1005
0
    ImplCycleCollectionUnlink(tmp->mStyleStack[i].gradientStyles[Style::STROKE]);
1006
0
    ImplCycleCollectionUnlink(tmp->mStyleStack[i].gradientStyles[Style::FILL]);
1007
0
    auto filterObserverList =
1008
0
      static_cast<SVGFilterObserverListForCanvas*>(tmp->mStyleStack[i].filterObserverList.get());
1009
0
    if (filterObserverList) {
1010
0
      filterObserverList->DetachFromContext();
1011
0
    }
1012
0
    ImplCycleCollectionUnlink(tmp->mStyleStack[i].filterObserverList);
1013
0
  }
1014
0
  for (size_t x = 0 ; x < tmp->mHitRegionsOptions.Length(); x++) {
1015
0
    RegionInfo& info = tmp->mHitRegionsOptions[x];
1016
0
    if (info.mElement) {
1017
0
      ImplCycleCollectionUnlink(info.mElement);
1018
0
    }
1019
0
  }
1020
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
1021
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1022
1023
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CanvasRenderingContext2D)
1024
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCanvasElement)
1025
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocShell)
1026
0
  for (uint32_t i = 0; i < tmp->mStyleStack.Length(); i++) {
1027
0
    ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].patternStyles[Style::STROKE], "Stroke CanvasPattern");
1028
0
    ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].patternStyles[Style::FILL], "Fill CanvasPattern");
1029
0
    ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].gradientStyles[Style::STROKE], "Stroke CanvasGradient");
1030
0
    ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].gradientStyles[Style::FILL], "Fill CanvasGradient");
1031
0
    ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].filterObserverList, "Filter Observer List");
1032
0
  }
1033
0
  for (size_t x = 0 ; x < tmp->mHitRegionsOptions.Length(); x++) {
1034
0
    RegionInfo& info = tmp->mHitRegionsOptions[x];
1035
0
    if (info.mElement) {
1036
0
      ImplCycleCollectionTraverse(cb, info.mElement, "Hit region fallback element");
1037
0
    }
1038
0
  }
1039
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1040
1041
NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(CanvasRenderingContext2D)
1042
1043
0
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(CanvasRenderingContext2D)
1044
0
 if (nsCCUncollectableMarker::sGeneration && tmp->HasKnownLiveWrapper()) {
1045
0
   dom::Element* canvasElement = tmp->mCanvasElement;
1046
0
    if (canvasElement) {
1047
0
      if (canvasElement->IsPurple()) {
1048
0
        canvasElement->RemovePurple();
1049
0
      }
1050
0
      dom::Element::MarkNodeChildren(canvasElement);
1051
0
    }
1052
0
    return true;
1053
0
  }
1054
0
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
1055
0
1056
0
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(CanvasRenderingContext2D)
1057
0
  return nsCCUncollectableMarker::sGeneration && tmp->HasKnownLiveWrapper();
1058
0
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
1059
1060
0
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(CanvasRenderingContext2D)
1061
0
  return nsCCUncollectableMarker::sGeneration && tmp->HasKnownLiveWrapper();
1062
0
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
1063
1064
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanvasRenderingContext2D)
1065
0
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
1066
0
  NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal)
1067
0
  NS_INTERFACE_MAP_ENTRY(nsISupports)
1068
0
NS_INTERFACE_MAP_END
1069
1070
/**
1071
 ** CanvasRenderingContext2D impl
1072
 **/
1073
1074
1075
// Initialize our static variables.
1076
uintptr_t CanvasRenderingContext2D::sNumLivingContexts = 0;
1077
DrawTarget* CanvasRenderingContext2D::sErrorTarget = nullptr;
1078
static bool sMaxContextsInitialized = false;
1079
static int32_t sMaxContexts = 0;
1080
1081
1082
1083
CanvasRenderingContext2D::CanvasRenderingContext2D(layers::LayersBackend aCompositorBackend)
1084
  : mRenderingMode(RenderingMode::OpenGLBackendMode)
1085
  , mCompositorBackend(aCompositorBackend)
1086
  // these are the default values from the Canvas spec
1087
  , mWidth(0), mHeight(0)
1088
  , mZero(false)
1089
  , mOpaqueAttrValue(false)
1090
  , mContextAttributesHasAlpha(true)
1091
  , mOpaque(false)
1092
  , mResetLayer(true)
1093
  , mIPC(false)
1094
  , mIsSkiaGL(false)
1095
  , mHasPendingStableStateCallback(false)
1096
  , mDrawObserver(nullptr)
1097
  , mIsEntireFrameInvalid(false)
1098
  , mPredictManyRedrawCalls(false)
1099
  , mIsCapturedFrameInvalid(false)
1100
  , mPathTransformWillUpdate(false)
1101
  , mInvalidateCount(0)
1102
0
{
1103
0
  if (!sMaxContextsInitialized) {
1104
0
    sMaxContexts = gfxPrefs::CanvasAzureAcceleratedLimit();
1105
0
    sMaxContextsInitialized = true;
1106
0
  }
1107
0
1108
0
  sNumLivingContexts++;
1109
0
1110
0
  mShutdownObserver = new CanvasShutdownObserver(this);
1111
0
  nsContentUtils::RegisterShutdownObserver(mShutdownObserver);
1112
0
1113
0
  // The default is to use OpenGL mode
1114
0
  if (AllowOpenGLCanvas()) {
1115
0
    mDrawObserver = new CanvasDrawObserver(this);
1116
0
  } else {
1117
0
    mRenderingMode = RenderingMode::SoftwareBackendMode;
1118
0
  }
1119
0
}
1120
1121
CanvasRenderingContext2D::~CanvasRenderingContext2D()
1122
0
{
1123
0
  RemoveDrawObserver();
1124
0
  RemovePostRefreshObserver();
1125
0
  RemoveShutdownObserver();
1126
0
  Reset();
1127
0
  // Drop references from all CanvasRenderingContext2DUserData to this context
1128
0
  for (uint32_t i = 0; i < mUserDatas.Length(); ++i) {
1129
0
    mUserDatas[i]->Forget();
1130
0
  }
1131
0
  sNumLivingContexts--;
1132
0
  if (!sNumLivingContexts) {
1133
0
    NS_IF_RELEASE(sErrorTarget);
1134
0
  }
1135
0
  RemoveDemotableContext(this);
1136
0
}
1137
1138
JSObject*
1139
CanvasRenderingContext2D::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
1140
0
{
1141
0
  return CanvasRenderingContext2D_Binding::Wrap(aCx, this, aGivenProto);
1142
0
}
1143
1144
bool
1145
CanvasRenderingContext2D::ParseColor(const nsAString& aString,
1146
                                     nscolor* aColor)
1147
0
{
1148
0
  nsIDocument* document = mCanvasElement ? mCanvasElement->OwnerDoc() : nullptr;
1149
0
  css::Loader* loader = document ? document->CSSLoader() : nullptr;
1150
0
1151
0
  nsIPresShell* presShell = GetPresShell();
1152
0
  ServoStyleSet* set = presShell ? presShell->StyleSet() : nullptr;
1153
0
1154
0
  // First, try computing the color without handling currentcolor.
1155
0
  bool wasCurrentColor = false;
1156
0
  if (!ServoCSSParser::ComputeColor(set, NS_RGB(0, 0, 0), aString, aColor,
1157
0
                                    &wasCurrentColor, loader)) {
1158
0
    return false;
1159
0
  }
1160
0
1161
0
  if (wasCurrentColor && mCanvasElement) {
1162
0
    // Otherwise, get the value of the color property, flushing style
1163
0
    // if necessary.
1164
0
    RefPtr<ComputedStyle> canvasStyle =
1165
0
      nsComputedDOMStyle::GetComputedStyle(mCanvasElement, nullptr);
1166
0
    if (canvasStyle) {
1167
0
      *aColor = canvasStyle->StyleColor()->mColor;
1168
0
    }
1169
0
    // Beware that the presShell could be gone here.
1170
0
  }
1171
0
  return true;
1172
0
}
1173
1174
nsresult
1175
CanvasRenderingContext2D::Reset()
1176
0
{
1177
0
  if (mCanvasElement) {
1178
0
    mCanvasElement->InvalidateCanvas();
1179
0
  }
1180
0
1181
0
  // only do this for non-docshell created contexts,
1182
0
  // since those are the ones that we created a surface for
1183
0
  if (mTarget && IsTargetValid() && !mDocShell) {
1184
0
    gCanvasAzureMemoryUsed -= mWidth * mHeight * 4;
1185
0
  }
1186
0
1187
0
  bool forceReset = true;
1188
0
  ReturnTarget(forceReset);
1189
0
  mTarget = nullptr;
1190
0
  mBufferProvider = nullptr;
1191
0
1192
0
  // reset hit regions
1193
0
  mHitRegionsOptions.ClearAndRetainStorage();
1194
0
1195
0
  // Since the target changes the backing texture will change, and this will
1196
0
  // no longer be valid.
1197
0
  mIsEntireFrameInvalid = false;
1198
0
  mPredictManyRedrawCalls = false;
1199
0
  mIsCapturedFrameInvalid = false;
1200
0
1201
0
  return NS_OK;
1202
0
}
1203
1204
void
1205
CanvasRenderingContext2D::OnShutdown()
1206
0
{
1207
0
  mShutdownObserver = nullptr;
1208
0
1209
0
  RefPtr<PersistentBufferProvider> provider = mBufferProvider;
1210
0
1211
0
  Reset();
1212
0
1213
0
  if (provider) {
1214
0
    provider->OnShutdown();
1215
0
  }
1216
0
}
1217
1218
void
1219
CanvasRenderingContext2D::RemoveShutdownObserver()
1220
0
{
1221
0
  if (mShutdownObserver) {
1222
0
    nsContentUtils::UnregisterShutdownObserver(mShutdownObserver);
1223
0
    mShutdownObserver = nullptr;
1224
0
  }
1225
0
}
1226
1227
void
1228
CanvasRenderingContext2D::SetStyleFromString(const nsAString& aStr,
1229
                                             Style aWhichStyle)
1230
0
{
1231
0
  MOZ_ASSERT(!aStr.IsVoid());
1232
0
1233
0
  nscolor color;
1234
0
  if (!ParseColor(aStr, &color)) {
1235
0
    return;
1236
0
  }
1237
0
1238
0
  CurrentState().SetColorStyle(aWhichStyle, color);
1239
0
}
1240
1241
void
1242
CanvasRenderingContext2D::GetStyleAsUnion(OwningStringOrCanvasGradientOrCanvasPattern& aValue,
1243
                                          Style aWhichStyle)
1244
0
{
1245
0
  const ContextState& state = CurrentState();
1246
0
  if (state.patternStyles[aWhichStyle]) {
1247
0
    aValue.SetAsCanvasPattern() = state.patternStyles[aWhichStyle];
1248
0
  } else if (state.gradientStyles[aWhichStyle]) {
1249
0
    aValue.SetAsCanvasGradient() = state.gradientStyles[aWhichStyle];
1250
0
  } else {
1251
0
    StyleColorToString(state.colorStyles[aWhichStyle], aValue.SetAsString());
1252
0
  }
1253
0
}
1254
1255
// static
1256
void
1257
CanvasRenderingContext2D::StyleColorToString(const nscolor& aColor, nsAString& aStr)
1258
0
{
1259
0
  // We can't reuse the normal CSS color stringification code,
1260
0
  // because the spec calls for a different algorithm for canvas.
1261
0
  if (NS_GET_A(aColor) == 255) {
1262
0
    CopyUTF8toUTF16(nsPrintfCString("#%02x%02x%02x",
1263
0
                                    NS_GET_R(aColor),
1264
0
                                    NS_GET_G(aColor),
1265
0
                                    NS_GET_B(aColor)),
1266
0
                    aStr);
1267
0
  } else {
1268
0
    CopyUTF8toUTF16(nsPrintfCString("rgba(%d, %d, %d, ",
1269
0
                                    NS_GET_R(aColor),
1270
0
                                    NS_GET_G(aColor),
1271
0
                                    NS_GET_B(aColor)),
1272
0
                    aStr);
1273
0
    aStr.AppendFloat(nsStyleUtil::ColorComponentToFloat(NS_GET_A(aColor)));
1274
0
    aStr.Append(')');
1275
0
  }
1276
0
}
1277
1278
nsresult
1279
CanvasRenderingContext2D::Redraw()
1280
0
{
1281
0
  mIsCapturedFrameInvalid = true;
1282
0
1283
0
  if (mIsEntireFrameInvalid) {
1284
0
    return NS_OK;
1285
0
  }
1286
0
1287
0
  mIsEntireFrameInvalid = true;
1288
0
1289
0
  if (!mCanvasElement) {
1290
0
    NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!");
1291
0
    return NS_OK;
1292
0
  }
1293
0
1294
0
  SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement);
1295
0
1296
0
  mCanvasElement->InvalidateCanvasContent(nullptr);
1297
0
1298
0
  return NS_OK;
1299
0
}
1300
1301
void
1302
CanvasRenderingContext2D::Redraw(const gfx::Rect& aR)
1303
0
{
1304
0
  mIsCapturedFrameInvalid = true;
1305
0
1306
0
  ++mInvalidateCount;
1307
0
1308
0
  if (mIsEntireFrameInvalid) {
1309
0
    return;
1310
0
  }
1311
0
1312
0
  if (mPredictManyRedrawCalls ||
1313
0
    mInvalidateCount > kCanvasMaxInvalidateCount) {
1314
0
    Redraw();
1315
0
    return;
1316
0
  }
1317
0
1318
0
  if (!mCanvasElement) {
1319
0
    NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!");
1320
0
    return;
1321
0
  }
1322
0
1323
0
  SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement);
1324
0
1325
0
  mCanvasElement->InvalidateCanvasContent(&aR);
1326
0
}
1327
1328
void
1329
CanvasRenderingContext2D::DidRefresh()
1330
0
{
1331
0
  if (IsTargetValid() && mIsSkiaGL) {
1332
0
    SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue();
1333
0
    MOZ_ASSERT(glue);
1334
0
1335
0
    auto gl = glue->GetGLContext();
1336
0
    gl->FlushIfHeavyGLCallsSinceLastFlush();
1337
0
  }
1338
0
}
1339
1340
void
1341
CanvasRenderingContext2D::RedrawUser(const gfxRect& aR)
1342
0
{
1343
0
  mIsCapturedFrameInvalid = true;
1344
0
1345
0
  if (mIsEntireFrameInvalid) {
1346
0
    ++mInvalidateCount;
1347
0
    return;
1348
0
  }
1349
0
1350
0
  gfx::Rect newr = mTarget->GetTransform().TransformBounds(ToRect(aR));
1351
0
  Redraw(newr);
1352
0
}
1353
1354
bool
1355
CanvasRenderingContext2D::AllowOpenGLCanvas() const
1356
0
{
1357
0
  // If we somehow didn't have the correct compositor in the constructor,
1358
0
  // we could do something like this to get it:
1359
0
  //
1360
0
  // HTMLCanvasElement* el = GetCanvas();
1361
0
  // if (el) {
1362
0
  //   mCompositorBackend = el->GetCompositorBackendType();
1363
0
  // }
1364
0
  //
1365
0
  // We could have LAYERS_NONE if there was no widget at the time of
1366
0
  // canvas creation, but in that case the
1367
0
  // HTMLCanvasElement::GetCompositorBackendType would return LAYERS_NONE
1368
0
  // as well, so it wouldn't help much.
1369
0
1370
0
  return (mCompositorBackend == LayersBackend::LAYERS_OPENGL ||
1371
0
          mCompositorBackend == LayersBackend::LAYERS_WR) &&
1372
0
    gfxPlatform::GetPlatform()->AllowOpenGLCanvas();
1373
0
}
1374
1375
bool CanvasRenderingContext2D::SwitchRenderingMode(RenderingMode aRenderingMode)
1376
0
{
1377
0
  if (!(IsTargetValid() || mBufferProvider) || mRenderingMode == aRenderingMode) {
1378
0
    return false;
1379
0
  }
1380
0
1381
0
  MOZ_ASSERT(mBufferProvider);
1382
0
1383
0
#ifdef USE_SKIA_GPU
1384
0
  // Do not attempt to switch into GL mode if the platform doesn't allow it.
1385
0
  if ((aRenderingMode == RenderingMode::OpenGLBackendMode) &&
1386
0
      !AllowOpenGLCanvas()) {
1387
0
      return false;
1388
0
  }
1389
0
#endif
1390
0
1391
0
  RefPtr<PersistentBufferProvider> oldBufferProvider = mBufferProvider;
1392
0
1393
0
  // Return the old target to the buffer provider.
1394
0
  // We need to do this before calling EnsureTarget.
1395
0
  ReturnTarget();
1396
0
  mTarget = nullptr;
1397
0
  mBufferProvider = nullptr;
1398
0
  mResetLayer = true;
1399
0
1400
0
  // Recreate mTarget using the new rendering mode
1401
0
  RenderingMode attemptedMode = EnsureTarget(nullptr, aRenderingMode);
1402
0
  if (!IsTargetValid()) {
1403
0
    return false;
1404
0
  }
1405
0
1406
0
  if (oldBufferProvider && mTarget) {
1407
0
    CopyBufferProvider(*oldBufferProvider, *mTarget, IntRect(0, 0, mWidth, mHeight));
1408
0
  }
1409
0
1410
0
  // We succeeded, so update mRenderingMode to reflect reality
1411
0
  mRenderingMode = attemptedMode;
1412
0
1413
0
  return true;
1414
0
}
1415
1416
bool
1417
CanvasRenderingContext2D::CopyBufferProvider(PersistentBufferProvider& aOld,
1418
                                             DrawTarget& aTarget,
1419
                                             IntRect aCopyRect)
1420
0
{
1421
0
  // Borrowing the snapshot must be done after ReturnTarget.
1422
0
  RefPtr<SourceSurface> snapshot = aOld.BorrowSnapshot();
1423
0
1424
0
  if (!snapshot) {
1425
0
    return false;
1426
0
  }
1427
0
1428
0
  aTarget.CopySurface(snapshot, aCopyRect, IntPoint());
1429
0
  aOld.ReturnSnapshot(snapshot.forget());
1430
0
  return true;
1431
0
}
1432
1433
void CanvasRenderingContext2D::Demote()
1434
0
{
1435
0
  if (SwitchRenderingMode(RenderingMode::SoftwareBackendMode)) {
1436
0
    RemoveDemotableContext(this);
1437
0
  }
1438
0
}
1439
1440
std::vector<CanvasRenderingContext2D*>&
1441
CanvasRenderingContext2D::DemotableContexts()
1442
0
{
1443
0
  // This is a list of raw pointers to cycle-collected objects. We need to ensure
1444
0
  // that we remove elements from it during UNLINK (which can happen considerably before
1445
0
  // the actual destructor) since the object is logically destroyed at that point
1446
0
  // and will be in an inconsistant state.
1447
0
  static std::vector<CanvasRenderingContext2D*> contexts;
1448
0
  return contexts;
1449
0
}
1450
1451
void
1452
CanvasRenderingContext2D::DemoteOldestContextIfNecessary()
1453
0
{
1454
0
  MOZ_ASSERT(sMaxContextsInitialized);
1455
0
  if (sMaxContexts <= 0) {
1456
0
    return;
1457
0
  }
1458
0
1459
0
  std::vector<CanvasRenderingContext2D*>& contexts = DemotableContexts();
1460
0
  if (contexts.size() < (size_t)sMaxContexts)
1461
0
    return;
1462
0
1463
0
  CanvasRenderingContext2D* oldest = contexts.front();
1464
0
  if (oldest->SwitchRenderingMode(RenderingMode::SoftwareBackendMode)) {
1465
0
    RemoveDemotableContext(oldest);
1466
0
  }
1467
0
}
1468
1469
void
1470
CanvasRenderingContext2D::AddDemotableContext(CanvasRenderingContext2D* aContext)
1471
0
{
1472
0
  MOZ_ASSERT(sMaxContextsInitialized);
1473
0
  if (sMaxContexts <= 0)
1474
0
    return;
1475
0
1476
0
  std::vector<CanvasRenderingContext2D*>::iterator iter = std::find(DemotableContexts().begin(), DemotableContexts().end(), aContext);
1477
0
  if (iter != DemotableContexts().end())
1478
0
    return;
1479
0
1480
0
  DemotableContexts().push_back(aContext);
1481
0
}
1482
1483
void
1484
CanvasRenderingContext2D::RemoveDemotableContext(CanvasRenderingContext2D* aContext)
1485
0
{
1486
0
  MOZ_ASSERT(sMaxContextsInitialized);
1487
0
  if (sMaxContexts <= 0)
1488
0
    return;
1489
0
1490
0
  std::vector<CanvasRenderingContext2D*>::iterator iter = std::find(DemotableContexts().begin(), DemotableContexts().end(), aContext);
1491
0
  if (iter != DemotableContexts().end())
1492
0
    DemotableContexts().erase(iter);
1493
0
}
1494
1495
0
#define MIN_SKIA_GL_DIMENSION 16
1496
1497
bool
1498
0
CanvasRenderingContext2D::CheckSizeForSkiaGL(IntSize aSize) {
1499
0
  MOZ_ASSERT(NS_IsMainThread());
1500
0
1501
0
  int minsize = Preferences::GetInt("gfx.canvas.min-size-for-skia-gl", 128);
1502
0
  if (aSize.width < MIN_SKIA_GL_DIMENSION || aSize.height < MIN_SKIA_GL_DIMENSION ||
1503
0
      (aSize.width * aSize.height < minsize * minsize)) {
1504
0
    return false;
1505
0
  }
1506
0
1507
0
  // Maximum pref allows 3 different options:
1508
0
  //  0   means unlimited size
1509
0
  //  > 0 means use value as an absolute threshold
1510
0
  //  < 0 means use the number of screen pixels as a threshold
1511
0
  int maxsize = Preferences::GetInt("gfx.canvas.max-size-for-skia-gl", 0);
1512
0
1513
0
  // unlimited max size
1514
0
  if (!maxsize) {
1515
0
    return true;
1516
0
  }
1517
0
1518
0
  // absolute max size threshold
1519
0
  if (maxsize > 0) {
1520
0
    return aSize.width <= maxsize && aSize.height <= maxsize;
1521
0
  }
1522
0
1523
0
  // Cache the number of pixels on the primary screen
1524
0
  static int32_t gScreenPixels = -1;
1525
0
  if (gScreenPixels < 0) {
1526
0
    // Default to historical mobile screen size of 980x480, like FishIEtank.
1527
0
    // In addition, allow skia use up to this size even if the screen is smaller.
1528
0
    // A lot content expects this size to work well.
1529
0
    // See Bug 999841
1530
0
    if (gfxPlatform::GetPlatform()->HasEnoughTotalSystemMemoryForSkiaGL()) {
1531
0
      gScreenPixels = 980 * 480;
1532
0
    }
1533
0
1534
0
    nsCOMPtr<nsIScreenManager> screenManager =
1535
0
      do_GetService("@mozilla.org/gfx/screenmanager;1");
1536
0
    if (screenManager) {
1537
0
      nsCOMPtr<nsIScreen> primaryScreen;
1538
0
      screenManager->GetPrimaryScreen(getter_AddRefs(primaryScreen));
1539
0
      if (primaryScreen) {
1540
0
        int32_t x, y, width, height;
1541
0
        primaryScreen->GetRect(&x, &y, &width, &height);
1542
0
1543
0
        gScreenPixels = std::max(gScreenPixels, width * height);
1544
0
      }
1545
0
    }
1546
0
  }
1547
0
1548
0
  // Just always use a scale of 1.0. It can be changed if a lot of contents need it.
1549
0
  static double gDefaultScale = 1.0;
1550
0
1551
0
  double scale = gDefaultScale > 0 ? gDefaultScale : 1.0;
1552
0
  int32_t threshold = ceil(scale * scale * gScreenPixels);
1553
0
1554
0
  // screen size acts as max threshold
1555
0
  return threshold < 0 || (aSize.width * aSize.height) <= threshold;
1556
0
}
1557
1558
void
1559
CanvasRenderingContext2D::ScheduleStableStateCallback()
1560
0
{
1561
0
  if (mHasPendingStableStateCallback) {
1562
0
    return;
1563
0
  }
1564
0
  mHasPendingStableStateCallback = true;
1565
0
1566
0
  nsContentUtils::RunInStableState(
1567
0
    NewRunnableMethod("dom::CanvasRenderingContext2D::OnStableState",
1568
0
                      this,
1569
0
                      &CanvasRenderingContext2D::OnStableState));
1570
0
}
1571
1572
void
1573
CanvasRenderingContext2D::OnStableState()
1574
0
{
1575
0
  if (!mHasPendingStableStateCallback) {
1576
0
    return;
1577
0
  }
1578
0
1579
0
  ReturnTarget();
1580
0
1581
0
  mHasPendingStableStateCallback = false;
1582
0
}
1583
1584
void
1585
CanvasRenderingContext2D::RestoreClipsAndTransformToTarget()
1586
0
{
1587
0
  // Restore clips and transform.
1588
0
  mTarget->SetTransform(Matrix());
1589
0
1590
0
  if (mTarget->GetBackendType() == gfx::BackendType::CAIRO) {
1591
0
    // Cairo doesn't play well with huge clips. When given a very big clip it
1592
0
    // will try to allocate big mask surface without taking the target
1593
0
    // size into account which can cause OOM. See bug 1034593.
1594
0
    // This limits the clip extents to the size of the canvas.
1595
0
    // A fix in Cairo would probably be preferable, but requires somewhat
1596
0
    // invasive changes.
1597
0
    mTarget->PushClipRect(gfx::Rect(0, 0, mWidth, mHeight));
1598
0
  }
1599
0
1600
0
  for (const auto& style : mStyleStack) {
1601
0
    for (const auto& clipOrTransform : style.clipsAndTransforms) {
1602
0
      if (clipOrTransform.IsClip()) {
1603
0
        mTarget->PushClip(clipOrTransform.clip);
1604
0
      } else {
1605
0
        mTarget->SetTransform(clipOrTransform.transform);
1606
0
      }
1607
0
    }
1608
0
  }
1609
0
}
1610
1611
CanvasRenderingContext2D::RenderingMode
1612
CanvasRenderingContext2D::EnsureTarget(const gfx::Rect* aCoveredRect,
1613
                                       RenderingMode aRenderingMode)
1614
0
{
1615
0
  if (AlreadyShutDown()) {
1616
0
    gfxCriticalError() << "Attempt to render into a Canvas2d after shutdown.";
1617
0
    SetErrorState();
1618
0
    return aRenderingMode;
1619
0
  }
1620
0
1621
0
  // This would make no sense, so make sure we don't get ourselves in a mess
1622
0
  MOZ_ASSERT(mRenderingMode != RenderingMode::DefaultBackendMode);
1623
0
1624
0
  RenderingMode mode = (aRenderingMode == RenderingMode::DefaultBackendMode) ? mRenderingMode : aRenderingMode;
1625
0
1626
0
  if (mTarget && mode == mRenderingMode) {
1627
0
    return mRenderingMode;
1628
0
  }
1629
0
1630
0
  // Check that the dimensions are sane
1631
0
  if (mWidth > gfxPrefs::MaxCanvasSize() ||
1632
0
      mHeight > gfxPrefs::MaxCanvasSize() ||
1633
0
      mWidth < 0 || mHeight < 0) {
1634
0
1635
0
    SetErrorState();
1636
0
    return aRenderingMode;
1637
0
  }
1638
0
1639
0
  // If the next drawing command covers the entire canvas, we can skip copying
1640
0
  // from the previous frame and/or clearing the canvas.
1641
0
  gfx::Rect canvasRect(0, 0, mWidth, mHeight);
1642
0
  bool canDiscardContent = aCoveredRect &&
1643
0
    CurrentState().transform.TransformBounds(*aCoveredRect).Contains(canvasRect);
1644
0
1645
0
  // If a clip is active we don't know for sure that the next drawing command
1646
0
  // will really cover the entire canvas.
1647
0
  for (const auto& style : mStyleStack) {
1648
0
    if (!canDiscardContent) {
1649
0
      break;
1650
0
    }
1651
0
    for (const auto& clipOrTransform : style.clipsAndTransforms) {
1652
0
      if (clipOrTransform.IsClip()) {
1653
0
        canDiscardContent = false;
1654
0
        break;
1655
0
      }
1656
0
    }
1657
0
  }
1658
0
1659
0
  ScheduleStableStateCallback();
1660
0
1661
0
  IntRect persistedRect = canDiscardContent ? IntRect()
1662
0
                                            : IntRect(0, 0, mWidth, mHeight);
1663
0
1664
0
  if (mBufferProvider && mode == mRenderingMode) {
1665
0
    mTarget = mBufferProvider->BorrowDrawTarget(persistedRect);
1666
0
1667
0
    if (mTarget && !mBufferProvider->PreservesDrawingState()) {
1668
0
      RestoreClipsAndTransformToTarget();
1669
0
    }
1670
0
1671
0
    if (mTarget) {
1672
0
      return mode;
1673
0
    }
1674
0
  }
1675
0
1676
0
  RefPtr<DrawTarget> newTarget;
1677
0
  RefPtr<PersistentBufferProvider> newProvider;
1678
0
1679
0
  if (mode == RenderingMode::OpenGLBackendMode &&
1680
0
      !TrySkiaGLTarget(newTarget, newProvider)) {
1681
0
    // Fall back to software.
1682
0
    mode = RenderingMode::SoftwareBackendMode;
1683
0
  }
1684
0
1685
0
  if (mode == RenderingMode::SoftwareBackendMode &&
1686
0
      !TrySharedTarget(newTarget, newProvider) &&
1687
0
      !TryBasicTarget(newTarget, newProvider)) {
1688
0
1689
0
    gfxCriticalError(
1690
0
      CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(GetSize()))
1691
0
    ) << "Failed borrow shared and basic targets.";
1692
0
1693
0
    SetErrorState();
1694
0
    return mode;
1695
0
  }
1696
0
1697
0
1698
0
  MOZ_ASSERT(newTarget);
1699
0
  MOZ_ASSERT(newProvider);
1700
0
1701
0
  bool needsClear = !canDiscardContent;
1702
0
  if (newTarget->GetBackendType() == gfx::BackendType::SKIA) {
1703
0
    // Skia expects the unused X channel to contains 0xFF even for opaque operations
1704
0
    // so we can't skip clearing in that case, even if we are going to cover the
1705
0
    // entire canvas in the next drawing operation.
1706
0
    newTarget->ClearRect(canvasRect);
1707
0
    needsClear = false;
1708
0
  }
1709
0
1710
0
  // Try to copy data from the previous buffer provider if there is one.
1711
0
  if (!canDiscardContent && mBufferProvider && CopyBufferProvider(*mBufferProvider, *newTarget, persistedRect)) {
1712
0
    needsClear = false;
1713
0
  }
1714
0
1715
0
  if (needsClear) {
1716
0
    newTarget->ClearRect(canvasRect);
1717
0
  }
1718
0
1719
0
  mTarget = newTarget.forget();
1720
0
  mBufferProvider = newProvider.forget();
1721
0
1722
0
  RegisterAllocation();
1723
0
1724
0
  RestoreClipsAndTransformToTarget();
1725
0
1726
0
  // Force a full layer transaction since we didn't have a layer before
1727
0
  // and now we might need one.
1728
0
  if (mCanvasElement) {
1729
0
    mCanvasElement->InvalidateCanvas();
1730
0
  }
1731
0
  // Calling Redraw() tells our invalidation machinery that the entire
1732
0
  // canvas is already invalid, which can speed up future drawing.
1733
0
  Redraw();
1734
0
1735
0
  return mode;
1736
0
}
1737
1738
void
1739
CanvasRenderingContext2D::SetInitialState()
1740
0
{
1741
0
  // Set up the initial canvas defaults
1742
0
  mPathBuilder = nullptr;
1743
0
  mPath = nullptr;
1744
0
  mDSPathBuilder = nullptr;
1745
0
  mPathTransformWillUpdate = false;
1746
0
1747
0
  mStyleStack.Clear();
1748
0
  ContextState* state = mStyleStack.AppendElement();
1749
0
  state->globalAlpha = 1.0;
1750
0
1751
0
  state->colorStyles[Style::FILL] = NS_RGB(0,0,0);
1752
0
  state->colorStyles[Style::STROKE] = NS_RGB(0,0,0);
1753
0
  state->shadowColor = NS_RGBA(0,0,0,0);
1754
0
}
1755
1756
void
1757
CanvasRenderingContext2D::SetErrorState()
1758
0
{
1759
0
  EnsureErrorTarget();
1760
0
1761
0
  if (mTarget && mTarget != sErrorTarget) {
1762
0
    gCanvasAzureMemoryUsed -= mWidth * mHeight * 4;
1763
0
  }
1764
0
1765
0
  mTarget = sErrorTarget;
1766
0
  mBufferProvider = nullptr;
1767
0
1768
0
  // clear transforms, clips, etc.
1769
0
  SetInitialState();
1770
0
}
1771
1772
void
1773
CanvasRenderingContext2D::RegisterAllocation()
1774
0
{
1775
0
  // XXX - It would make more sense to track the allocation in
1776
0
  // PeristentBufferProvider, rather than here.
1777
0
  static bool registered = false;
1778
0
  // FIXME: Disable the reporter for now, see bug 1241865
1779
0
  if (!registered && false) {
1780
0
    registered = true;
1781
0
    RegisterStrongMemoryReporter(new Canvas2dPixelsReporter());
1782
0
  }
1783
0
1784
0
  JSObject* wrapper = GetWrapperPreserveColor();
1785
0
  if (wrapper) {
1786
0
    CycleCollectedJSRuntime::Get()->
1787
0
      AddZoneWaitingForGC(JS::GetObjectZone(wrapper));
1788
0
  }
1789
0
}
1790
1791
static already_AddRefed<LayerManager>
1792
LayerManagerFromCanvasElement(nsINode* aCanvasElement)
1793
0
{
1794
0
  if (!aCanvasElement) {
1795
0
    return nullptr;
1796
0
  }
1797
0
1798
0
  return nsContentUtils::PersistentLayerManagerForDocument(aCanvasElement->OwnerDoc());
1799
0
}
1800
1801
bool
1802
CanvasRenderingContext2D::TrySkiaGLTarget(RefPtr<gfx::DrawTarget>& aOutDT,
1803
                                          RefPtr<layers::PersistentBufferProvider>& aOutProvider)
1804
0
{
1805
0
  aOutDT = nullptr;
1806
0
  aOutProvider = nullptr;
1807
0
1808
0
  mIsSkiaGL = false;
1809
0
1810
0
  IntSize size(mWidth, mHeight);
1811
0
  if (!AllowOpenGLCanvas() || !CheckSizeForSkiaGL(size)) {
1812
0
    return false;
1813
0
  }
1814
0
1815
0
1816
0
  RefPtr<LayerManager> layerManager = LayerManagerFromCanvasElement(mCanvasElement);
1817
0
1818
0
  if (!layerManager) {
1819
0
    return false;
1820
0
  }
1821
0
1822
0
  DemoteOldestContextIfNecessary();
1823
0
  mBufferProvider = nullptr;
1824
0
1825
0
#ifdef USE_SKIA_GPU
1826
0
  SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue();
1827
0
  if (!glue || !glue->GetGrContext() || !glue->GetGLContext()) {
1828
0
    return false;
1829
0
  }
1830
0
1831
0
  SurfaceFormat format = GetSurfaceFormat();
1832
0
  aOutDT = Factory::CreateDrawTargetSkiaWithGrContext(glue->GetGrContext(),
1833
0
                                                      size, format);
1834
0
  if (!aOutDT) {
1835
0
    gfxCriticalNote << "Failed to create a SkiaGL DrawTarget, falling back to software\n";
1836
0
    return false;
1837
0
  }
1838
0
1839
0
  MOZ_ASSERT(aOutDT->GetType() == DrawTargetType::HARDWARE_RASTER);
1840
0
1841
0
  AddDemotableContext(this);
1842
0
  aOutProvider = new PersistentBufferProviderBasic(aOutDT);
1843
0
  mIsSkiaGL = true;
1844
0
  // Drop a note in the debug builds if we ever use accelerated Skia canvas.
1845
0
  gfxWarningOnce() << "Using SkiaGL canvas.";
1846
0
#endif
1847
0
1848
0
  // could still be null if USE_SKIA_GPU is not #defined.
1849
0
  return !!aOutDT;
1850
0
}
1851
1852
bool
1853
CanvasRenderingContext2D::TrySharedTarget(RefPtr<gfx::DrawTarget>& aOutDT,
1854
                                          RefPtr<layers::PersistentBufferProvider>& aOutProvider)
1855
0
{
1856
0
  aOutDT = nullptr;
1857
0
  aOutProvider = nullptr;
1858
0
1859
0
  if (!mCanvasElement) {
1860
0
    return false;
1861
0
  }
1862
0
1863
0
  if (mBufferProvider &&
1864
0
      (mBufferProvider->GetType() == LayersBackend::LAYERS_CLIENT ||
1865
0
       mBufferProvider->GetType() == LayersBackend::LAYERS_WR)) {
1866
0
    // we are already using a shared buffer provider, we are allocating a new one
1867
0
    // because the current one failed so let's just fall back to the basic provider.
1868
0
    return false;
1869
0
  }
1870
0
1871
0
  RefPtr<LayerManager> layerManager = LayerManagerFromCanvasElement(mCanvasElement);
1872
0
1873
0
  if (!layerManager) {
1874
0
    return false;
1875
0
  }
1876
0
1877
0
  aOutProvider = layerManager->CreatePersistentBufferProvider(GetSize(), GetSurfaceFormat());
1878
0
1879
0
  if (!aOutProvider) {
1880
0
    return false;
1881
0
  }
1882
0
1883
0
  // We can pass an empty persisted rect since we just created the buffer
1884
0
  // provider (nothing to restore).
1885
0
  aOutDT = aOutProvider->BorrowDrawTarget(IntRect());
1886
0
  MOZ_ASSERT(aOutDT);
1887
0
1888
0
  return !!aOutDT;
1889
0
}
1890
1891
bool
1892
CanvasRenderingContext2D::TryBasicTarget(RefPtr<gfx::DrawTarget>& aOutDT,
1893
                                         RefPtr<layers::PersistentBufferProvider>& aOutProvider)
1894
0
{
1895
0
  aOutDT = gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(GetSize(),
1896
0
                                                                       GetSurfaceFormat());
1897
0
  if (!aOutDT) {
1898
0
    return false;
1899
0
  }
1900
0
1901
0
  aOutProvider = new PersistentBufferProviderBasic(aOutDT);
1902
0
  return true;
1903
0
}
1904
1905
NS_IMETHODIMP
1906
CanvasRenderingContext2D::SetDimensions(int32_t aWidth, int32_t aHeight)
1907
0
{
1908
0
  // Zero sized surfaces can cause problems.
1909
0
  mZero = false;
1910
0
  if (aHeight == 0) {
1911
0
    aHeight = 1;
1912
0
    mZero = true;
1913
0
  }
1914
0
  if (aWidth == 0) {
1915
0
    aWidth = 1;
1916
0
    mZero = true;
1917
0
  }
1918
0
1919
0
  ClearTarget(aWidth, aHeight);
1920
0
1921
0
  return NS_OK;
1922
0
}
1923
1924
void
1925
CanvasRenderingContext2D::ClearTarget(int32_t aWidth, int32_t aHeight)
1926
0
{
1927
0
  Reset();
1928
0
1929
0
  mResetLayer = true;
1930
0
1931
0
  SetInitialState();
1932
0
1933
0
  // Update dimensions only if new (strictly positive) values were passed.
1934
0
  if (aWidth > 0 && aHeight > 0) {
1935
0
    mWidth = aWidth;
1936
0
    mHeight = aHeight;
1937
0
  }
1938
0
1939
0
  if (!mCanvasElement || !mCanvasElement->IsInComposedDoc()) {
1940
0
    return;
1941
0
  }
1942
0
1943
0
  // For vertical writing-mode, unless text-orientation is sideways,
1944
0
  // we'll modify the initial value of textBaseline to 'middle'.
1945
0
  RefPtr<ComputedStyle> canvasStyle =
1946
0
    nsComputedDOMStyle::GetComputedStyle(mCanvasElement, nullptr);
1947
0
  if (canvasStyle) {
1948
0
    WritingMode wm(canvasStyle);
1949
0
    if (wm.IsVertical() && !wm.IsSideways()) {
1950
0
      CurrentState().textBaseline = TextBaseline::MIDDLE;
1951
0
    }
1952
0
  }
1953
0
}
1954
1955
void
1956
CanvasRenderingContext2D::ReturnTarget(bool aForceReset)
1957
0
{
1958
0
  if (mTarget && mBufferProvider && mTarget != sErrorTarget) {
1959
0
    CurrentState().transform = mTarget->GetTransform();
1960
0
    if (aForceReset || !mBufferProvider->PreservesDrawingState()) {
1961
0
      for (const auto& style : mStyleStack) {
1962
0
        for (const auto& clipOrTransform : style.clipsAndTransforms) {
1963
0
          if (clipOrTransform.IsClip()) {
1964
0
            mTarget->PopClip();
1965
0
          }
1966
0
        }
1967
0
      }
1968
0
1969
0
      if (mTarget->GetBackendType() == gfx::BackendType::CAIRO) {
1970
0
        // With the cairo backend we pushed an extra clip rect which we have to
1971
0
        // balance out here. See the comment in RestoreClipsAndTransformToTarget.
1972
0
        mTarget->PopClip();
1973
0
      }
1974
0
1975
0
      mTarget->SetTransform(Matrix());
1976
0
    }
1977
0
1978
0
    mBufferProvider->ReturnDrawTarget(mTarget.forget());
1979
0
  }
1980
0
}
1981
1982
NS_IMETHODIMP
1983
CanvasRenderingContext2D::InitializeWithDrawTarget(nsIDocShell* aShell,
1984
                                                   NotNull<gfx::DrawTarget*> aTarget)
1985
0
{
1986
0
  RemovePostRefreshObserver();
1987
0
  mDocShell = aShell;
1988
0
  AddPostRefreshObserverIfNecessary();
1989
0
1990
0
  IntSize size = aTarget->GetSize();
1991
0
  SetDimensions(size.width, size.height);
1992
0
1993
0
  mTarget = aTarget;
1994
0
  mBufferProvider = new PersistentBufferProviderBasic(aTarget);
1995
0
1996
0
  if (mTarget->GetBackendType() == gfx::BackendType::CAIRO) {
1997
0
    // Cf comment in EnsureTarget
1998
0
    mTarget->PushClipRect(gfx::Rect(Point(0, 0), Size(mWidth, mHeight)));
1999
0
  }
2000
0
2001
0
  return NS_OK;
2002
0
}
2003
2004
void
2005
CanvasRenderingContext2D::SetOpaqueValueFromOpaqueAttr(bool aOpaqueAttrValue)
2006
0
{
2007
0
  if (aOpaqueAttrValue != mOpaqueAttrValue) {
2008
0
    mOpaqueAttrValue = aOpaqueAttrValue;
2009
0
    UpdateIsOpaque();
2010
0
  }
2011
0
}
2012
2013
void
2014
CanvasRenderingContext2D::UpdateIsOpaque()
2015
0
{
2016
0
  mOpaque = !mContextAttributesHasAlpha || mOpaqueAttrValue;
2017
0
  ClearTarget();
2018
0
}
2019
2020
NS_IMETHODIMP
2021
CanvasRenderingContext2D::SetIsIPC(bool aIsIPC)
2022
0
{
2023
0
  if (aIsIPC != mIPC) {
2024
0
    mIPC = aIsIPC;
2025
0
    ClearTarget();
2026
0
  }
2027
0
2028
0
  return NS_OK;
2029
0
}
2030
2031
NS_IMETHODIMP
2032
CanvasRenderingContext2D::SetContextOptions(JSContext* aCx,
2033
                                            JS::Handle<JS::Value> aOptions,
2034
                                            ErrorResult& aRvForDictionaryInit)
2035
0
{
2036
0
  if (aOptions.isNullOrUndefined()) {
2037
0
    return NS_OK;
2038
0
  }
2039
0
2040
0
  // This shouldn't be called before drawing starts, so there should be no drawtarget yet
2041
0
  MOZ_ASSERT(!mTarget);
2042
0
2043
0
  ContextAttributes2D attributes;
2044
0
  if (!attributes.Init(aCx, aOptions)) {
2045
0
    aRvForDictionaryInit.Throw(NS_ERROR_UNEXPECTED);
2046
0
    return NS_ERROR_UNEXPECTED;
2047
0
  }
2048
0
2049
0
  if (Preferences::GetBool("gfx.canvas.willReadFrequently.enable", false)) {
2050
0
    // Use software when there is going to be a lot of readback
2051
0
    if (attributes.mWillReadFrequently) {
2052
0
2053
0
      // We want to lock into software, so remove the observer that
2054
0
      // may potentially change that...
2055
0
      RemoveDrawObserver();
2056
0
      mRenderingMode = RenderingMode::SoftwareBackendMode;
2057
0
    }
2058
0
  }
2059
0
2060
0
  mContextAttributesHasAlpha = attributes.mAlpha;
2061
0
  UpdateIsOpaque();
2062
0
2063
0
  return NS_OK;
2064
0
}
2065
2066
UniquePtr<uint8_t[]>
2067
CanvasRenderingContext2D::GetImageBuffer(int32_t* aFormat)
2068
0
{
2069
0
  UniquePtr<uint8_t[]> ret;
2070
0
2071
0
  *aFormat = 0;
2072
0
2073
0
  RefPtr<SourceSurface> snapshot;
2074
0
  if (mTarget) {
2075
0
    snapshot = mTarget->Snapshot();
2076
0
  } else if (mBufferProvider) {
2077
0
    snapshot = mBufferProvider->BorrowSnapshot();
2078
0
  } else {
2079
0
    EnsureTarget();
2080
0
    if (!IsTargetValid()) {
2081
0
      return nullptr;
2082
0
    }
2083
0
    snapshot = mTarget->Snapshot();
2084
0
  }
2085
0
2086
0
  if (snapshot) {
2087
0
    RefPtr<DataSourceSurface> data = snapshot->GetDataSurface();
2088
0
    if (data && data->GetSize() == GetSize()) {
2089
0
      *aFormat = imgIEncoder::INPUT_FORMAT_HOSTARGB;
2090
0
      ret = SurfaceToPackedBGRA(data);
2091
0
    }
2092
0
  }
2093
0
2094
0
  if (!mTarget && mBufferProvider) {
2095
0
    mBufferProvider->ReturnSnapshot(snapshot.forget());
2096
0
  }
2097
0
2098
0
  return ret;
2099
0
}
2100
2101
nsString CanvasRenderingContext2D::GetHitRegion(const mozilla::gfx::Point& aPoint)
2102
0
{
2103
0
  for (size_t x = 0 ; x < mHitRegionsOptions.Length(); x++) {
2104
0
    RegionInfo& info = mHitRegionsOptions[x];
2105
0
    if (info.mPath->ContainsPoint(aPoint, Matrix())) {
2106
0
      return info.mId;
2107
0
    }
2108
0
  }
2109
0
  return nsString();
2110
0
}
2111
2112
NS_IMETHODIMP
2113
CanvasRenderingContext2D::GetInputStream(const char* aMimeType,
2114
                                         const char16_t* aEncoderOptions,
2115
                                         nsIInputStream** aStream)
2116
0
{
2117
0
  nsCString enccid("@mozilla.org/image/encoder;2?type=");
2118
0
  enccid += aMimeType;
2119
0
  nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get());
2120
0
  if (!encoder) {
2121
0
    return NS_ERROR_FAILURE;
2122
0
  }
2123
0
2124
0
  int32_t format = 0;
2125
0
  UniquePtr<uint8_t[]> imageBuffer = GetImageBuffer(&format);
2126
0
  if (!imageBuffer) {
2127
0
    return NS_ERROR_FAILURE;
2128
0
  }
2129
0
2130
0
  return ImageEncoder::GetInputStream(mWidth, mHeight, imageBuffer.get(),
2131
0
                                      format, encoder, aEncoderOptions,
2132
0
                                      aStream);
2133
0
}
2134
2135
SurfaceFormat
2136
CanvasRenderingContext2D::GetSurfaceFormat() const
2137
0
{
2138
0
  return mOpaque ? SurfaceFormat::B8G8R8X8 : SurfaceFormat::B8G8R8A8;
2139
0
}
2140
2141
//
2142
// state
2143
//
2144
2145
void
2146
CanvasRenderingContext2D::Save()
2147
0
{
2148
0
  EnsureTarget();
2149
0
  if (MOZ_UNLIKELY(!mTarget || mStyleStack.IsEmpty())) {
2150
0
    SetErrorState();
2151
0
    return;
2152
0
  }
2153
0
  mStyleStack[mStyleStack.Length() - 1].transform = mTarget->GetTransform();
2154
0
  mStyleStack.SetCapacity(mStyleStack.Length() + 1);
2155
0
  mStyleStack.AppendElement(CurrentState());
2156
0
2157
0
  if (mStyleStack.Length() > MAX_STYLE_STACK_SIZE) {
2158
0
    // This is not fast, but is better than OOMing and shouldn't be hit by
2159
0
    // reasonable code.
2160
0
    mStyleStack.RemoveElementAt(0);
2161
0
  }
2162
0
}
2163
2164
void
2165
CanvasRenderingContext2D::Restore()
2166
0
{
2167
0
  if (MOZ_UNLIKELY(mStyleStack.Length() < 2)) {
2168
0
    return;
2169
0
  }
2170
0
2171
0
  TransformWillUpdate();
2172
0
  if (!IsTargetValid()) {
2173
0
    return;
2174
0
  }
2175
0
2176
0
  for (const auto& clipOrTransform : CurrentState().clipsAndTransforms) {
2177
0
    if (clipOrTransform.IsClip()) {
2178
0
      mTarget->PopClip();
2179
0
    }
2180
0
  }
2181
0
2182
0
  mStyleStack.RemoveLastElement();
2183
0
2184
0
  mTarget->SetTransform(CurrentState().transform);
2185
0
}
2186
2187
//
2188
// transformations
2189
//
2190
2191
void
2192
CanvasRenderingContext2D::Scale(double aX, double aY, ErrorResult& aError)
2193
0
{
2194
0
  TransformWillUpdate();
2195
0
  if (!IsTargetValid()) {
2196
0
    aError.Throw(NS_ERROR_FAILURE);
2197
0
    return;
2198
0
  }
2199
0
2200
0
  Matrix newMatrix = mTarget->GetTransform();
2201
0
  newMatrix.PreScale(aX, aY);
2202
0
2203
0
  SetTransformInternal(newMatrix);
2204
0
}
2205
2206
void
2207
CanvasRenderingContext2D::Rotate(double aAngle, ErrorResult& aError)
2208
0
{
2209
0
  TransformWillUpdate();
2210
0
  if (!IsTargetValid()) {
2211
0
    aError.Throw(NS_ERROR_FAILURE);
2212
0
    return;
2213
0
  }
2214
0
2215
0
  Matrix newMatrix = Matrix::Rotation(aAngle) * mTarget->GetTransform();
2216
0
2217
0
  SetTransformInternal(newMatrix);
2218
0
}
2219
2220
void
2221
CanvasRenderingContext2D::Translate(double aX, double aY, ErrorResult& aError)
2222
0
{
2223
0
  TransformWillUpdate();
2224
0
  if (!IsTargetValid()) {
2225
0
    aError.Throw(NS_ERROR_FAILURE);
2226
0
    return;
2227
0
  }
2228
0
2229
0
  Matrix newMatrix = mTarget->GetTransform();
2230
0
  newMatrix.PreTranslate(aX, aY);
2231
0
2232
0
  SetTransformInternal(newMatrix);
2233
0
}
2234
2235
void
2236
CanvasRenderingContext2D::Transform(double aM11, double aM12, double aM21,
2237
                                    double aM22, double aDx, double aDy,
2238
                                    ErrorResult& aError)
2239
0
{
2240
0
  TransformWillUpdate();
2241
0
  if (!IsTargetValid()) {
2242
0
    aError.Throw(NS_ERROR_FAILURE);
2243
0
    return;
2244
0
  }
2245
0
2246
0
  Matrix newMatrix(aM11, aM12, aM21, aM22, aDx, aDy);
2247
0
  newMatrix *= mTarget->GetTransform();
2248
0
2249
0
  SetTransformInternal(newMatrix);
2250
0
}
2251
2252
void
2253
CanvasRenderingContext2D::SetTransform(double aM11, double aM12,
2254
                                       double aM21, double aM22,
2255
                                       double aDx, double aDy,
2256
                                       ErrorResult& aError)
2257
0
{
2258
0
  TransformWillUpdate();
2259
0
  if (!IsTargetValid()) {
2260
0
    aError.Throw(NS_ERROR_FAILURE);
2261
0
    return;
2262
0
  }
2263
0
2264
0
  SetTransformInternal(Matrix(aM11, aM12, aM21, aM22, aDx, aDy));
2265
0
}
2266
2267
void
2268
CanvasRenderingContext2D::SetTransformInternal(const Matrix& aTransform)
2269
0
{
2270
0
  if (!aTransform.IsFinite()) {
2271
0
    return;
2272
0
  }
2273
0
2274
0
  // Save the transform in the clip stack to be able to replay clips properly.
2275
0
  auto& clipsAndTransforms = CurrentState().clipsAndTransforms;
2276
0
  if (clipsAndTransforms.IsEmpty() || clipsAndTransforms.LastElement().IsClip()) {
2277
0
    clipsAndTransforms.AppendElement(ClipState(aTransform));
2278
0
  } else {
2279
0
    // If the last item is a transform we can replace it instead of appending
2280
0
    // a new item.
2281
0
    clipsAndTransforms.LastElement().transform = aTransform;
2282
0
  }
2283
0
  mTarget->SetTransform(aTransform);
2284
0
}
2285
2286
void
2287
CanvasRenderingContext2D::ResetTransform(ErrorResult& aError)
2288
0
{
2289
0
  SetTransform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0, aError);
2290
0
}
2291
2292
static void
2293
MatrixToJSObject(JSContext* aCx, const Matrix& aMatrix,
2294
                 JS::MutableHandle<JSObject*> aResult, ErrorResult& aError)
2295
0
{
2296
0
  double elts[6] = { aMatrix._11, aMatrix._12,
2297
0
                     aMatrix._21, aMatrix._22,
2298
0
                     aMatrix._31, aMatrix._32 };
2299
0
2300
0
  // XXX Should we enter GetWrapper()'s compartment?
2301
0
  JS::Rooted<JS::Value> val(aCx);
2302
0
  if (!ToJSValue(aCx, elts, &val)) {
2303
0
    aError.Throw(NS_ERROR_OUT_OF_MEMORY);
2304
0
  } else {
2305
0
    aResult.set(&val.toObject());
2306
0
  }
2307
0
}
2308
2309
static bool
2310
ObjectToMatrix(JSContext* aCx, JS::Handle<JSObject*> aObj, Matrix& aMatrix,
2311
               ErrorResult& aError)
2312
0
{
2313
0
  uint32_t length;
2314
0
  if (!JS_GetArrayLength(aCx, aObj, &length) || length != 6) {
2315
0
    // Not an array-like thing or wrong size
2316
0
    aError.Throw(NS_ERROR_INVALID_ARG);
2317
0
    return false;
2318
0
  }
2319
0
2320
0
  Float* elts[] = { &aMatrix._11, &aMatrix._12, &aMatrix._21, &aMatrix._22,
2321
0
                    &aMatrix._31, &aMatrix._32 };
2322
0
  for (uint32_t i = 0; i < 6; ++i) {
2323
0
    JS::Rooted<JS::Value> elt(aCx);
2324
0
    double d;
2325
0
    if (!JS_GetElement(aCx, aObj, i, &elt)) {
2326
0
      aError.Throw(NS_ERROR_FAILURE);
2327
0
      return false;
2328
0
    }
2329
0
    if (!CoerceDouble(elt, &d)) {
2330
0
      aError.Throw(NS_ERROR_INVALID_ARG);
2331
0
      return false;
2332
0
    }
2333
0
    if (!FloatValidate(d)) {
2334
0
      // This is weird, but it's the behavior of SetTransform()
2335
0
      return false;
2336
0
    }
2337
0
    *elts[i] = Float(d);
2338
0
  }
2339
0
  return true;
2340
0
}
2341
2342
void
2343
CanvasRenderingContext2D::SetMozCurrentTransform(JSContext* aCx,
2344
                                                 JS::Handle<JSObject*> aCurrentTransform,
2345
                                                 ErrorResult& aError)
2346
0
{
2347
0
  EnsureTarget();
2348
0
  if (!IsTargetValid()) {
2349
0
    aError.Throw(NS_ERROR_FAILURE);
2350
0
    return;
2351
0
  }
2352
0
2353
0
  Matrix newCTM;
2354
0
  if (ObjectToMatrix(aCx, aCurrentTransform, newCTM, aError) && newCTM.IsFinite()) {
2355
0
    mTarget->SetTransform(newCTM);
2356
0
  }
2357
0
}
2358
2359
void
2360
CanvasRenderingContext2D::GetMozCurrentTransform(JSContext* aCx,
2361
                                                 JS::MutableHandle<JSObject*> aResult,
2362
                                                 ErrorResult& aError)
2363
0
{
2364
0
  EnsureTarget();
2365
0
2366
0
  MatrixToJSObject(aCx, mTarget ? mTarget->GetTransform() : Matrix(),
2367
0
                   aResult, aError);
2368
0
}
2369
2370
void
2371
CanvasRenderingContext2D::SetMozCurrentTransformInverse(JSContext* aCx,
2372
                                                        JS::Handle<JSObject*> aCurrentTransform,
2373
                                                        ErrorResult& aError)
2374
0
{
2375
0
  EnsureTarget();
2376
0
  if (!IsTargetValid()) {
2377
0
    aError.Throw(NS_ERROR_FAILURE);
2378
0
    return;
2379
0
  }
2380
0
2381
0
  Matrix newCTMInverse;
2382
0
  if (ObjectToMatrix(aCx, aCurrentTransform, newCTMInverse, aError)) {
2383
0
    // XXX ERRMSG we need to report an error to developers here! (bug 329026)
2384
0
    if (newCTMInverse.Invert() && newCTMInverse.IsFinite()) {
2385
0
      mTarget->SetTransform(newCTMInverse);
2386
0
    }
2387
0
  }
2388
0
}
2389
2390
void
2391
CanvasRenderingContext2D::GetMozCurrentTransformInverse(JSContext* aCx,
2392
                                                        JS::MutableHandle<JSObject*> aResult,
2393
                                                        ErrorResult& aError)
2394
0
{
2395
0
  EnsureTarget();
2396
0
2397
0
  if (!mTarget) {
2398
0
    MatrixToJSObject(aCx, Matrix(), aResult, aError);
2399
0
    return;
2400
0
  }
2401
0
2402
0
  Matrix ctm = mTarget->GetTransform();
2403
0
2404
0
  if (!ctm.Invert()) {
2405
0
    double NaN = JS_GetNaNValue(aCx).toDouble();
2406
0
    ctm = Matrix(NaN, NaN, NaN, NaN, NaN, NaN);
2407
0
  }
2408
0
2409
0
  MatrixToJSObject(aCx, ctm, aResult, aError);
2410
0
}
2411
2412
//
2413
// colors
2414
//
2415
2416
void
2417
CanvasRenderingContext2D::SetStyleFromUnion(const StringOrCanvasGradientOrCanvasPattern& aValue,
2418
                                            Style aWhichStyle)
2419
0
{
2420
0
  if (aValue.IsString()) {
2421
0
    SetStyleFromString(aValue.GetAsString(), aWhichStyle);
2422
0
    return;
2423
0
  }
2424
0
2425
0
  if (aValue.IsCanvasGradient()) {
2426
0
    SetStyleFromGradient(aValue.GetAsCanvasGradient(), aWhichStyle);
2427
0
    return;
2428
0
  }
2429
0
2430
0
  if (aValue.IsCanvasPattern()) {
2431
0
    SetStyleFromPattern(aValue.GetAsCanvasPattern(), aWhichStyle);
2432
0
    return;
2433
0
  }
2434
0
2435
0
  MOZ_ASSERT_UNREACHABLE("Invalid union value");
2436
0
}
2437
2438
void
2439
CanvasRenderingContext2D::SetFillRule(const nsAString& aString)
2440
0
{
2441
0
  FillRule rule;
2442
0
2443
0
  if (aString.EqualsLiteral("evenodd"))
2444
0
    rule = FillRule::FILL_EVEN_ODD;
2445
0
  else if (aString.EqualsLiteral("nonzero"))
2446
0
    rule = FillRule::FILL_WINDING;
2447
0
  else
2448
0
    return;
2449
0
2450
0
  CurrentState().fillRule = rule;
2451
0
}
2452
2453
void
2454
CanvasRenderingContext2D::GetFillRule(nsAString& aString)
2455
{
2456
  switch (CurrentState().fillRule) {
2457
  case FillRule::FILL_WINDING:
2458
    aString.AssignLiteral("nonzero"); break;
2459
  case FillRule::FILL_EVEN_ODD:
2460
    aString.AssignLiteral("evenodd"); break;
2461
  }
2462
}
2463
//
2464
// gradients and patterns
2465
//
2466
already_AddRefed<CanvasGradient>
2467
CanvasRenderingContext2D::CreateLinearGradient(double aX0, double aY0, double aX1, double aY1)
2468
0
{
2469
0
  RefPtr<CanvasGradient> grad =
2470
0
    new CanvasLinearGradient(this, Point(aX0, aY0), Point(aX1, aY1));
2471
0
2472
0
  return grad.forget();
2473
0
}
2474
2475
already_AddRefed<CanvasGradient>
2476
CanvasRenderingContext2D::CreateRadialGradient(double aX0, double aY0, double aR0,
2477
                                               double aX1, double aY1, double aR1,
2478
                                               ErrorResult& aError)
2479
0
{
2480
0
  if (aR0 < 0.0 || aR1 < 0.0) {
2481
0
    aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
2482
0
    return nullptr;
2483
0
  }
2484
0
2485
0
  RefPtr<CanvasGradient> grad =
2486
0
    new CanvasRadialGradient(this, Point(aX0, aY0), aR0, Point(aX1, aY1), aR1);
2487
0
2488
0
  return grad.forget();
2489
0
}
2490
2491
already_AddRefed<CanvasPattern>
2492
CanvasRenderingContext2D::CreatePattern(const CanvasImageSource& aSource,
2493
                                        const nsAString& aRepeat,
2494
                                        ErrorResult& aError)
2495
0
{
2496
0
  CanvasPattern::RepeatMode repeatMode =
2497
0
    CanvasPattern::RepeatMode::NOREPEAT;
2498
0
2499
0
  if (aRepeat.IsEmpty() || aRepeat.EqualsLiteral("repeat")) {
2500
0
    repeatMode = CanvasPattern::RepeatMode::REPEAT;
2501
0
  } else if (aRepeat.EqualsLiteral("repeat-x")) {
2502
0
    repeatMode = CanvasPattern::RepeatMode::REPEATX;
2503
0
  } else if (aRepeat.EqualsLiteral("repeat-y")) {
2504
0
    repeatMode = CanvasPattern::RepeatMode::REPEATY;
2505
0
  } else if (aRepeat.EqualsLiteral("no-repeat")) {
2506
0
    repeatMode = CanvasPattern::RepeatMode::NOREPEAT;
2507
0
  } else {
2508
0
    aError.Throw(NS_ERROR_DOM_SYNTAX_ERR);
2509
0
    return nullptr;
2510
0
  }
2511
0
2512
0
  Element* element;
2513
0
  if (aSource.IsHTMLCanvasElement()) {
2514
0
    HTMLCanvasElement* canvas = &aSource.GetAsHTMLCanvasElement();
2515
0
    element = canvas;
2516
0
2517
0
    nsIntSize size = canvas->GetSize();
2518
0
    if (size.width == 0 || size.height == 0) {
2519
0
      aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
2520
0
      return nullptr;
2521
0
    }
2522
0
2523
0
    // Special case for Canvas, which could be an Azure canvas!
2524
0
    nsICanvasRenderingContextInternal* srcCanvas = canvas->GetContextAtIndex(0);
2525
0
    if (srcCanvas) {
2526
0
      // This might not be an Azure canvas!
2527
0
      RefPtr<SourceSurface> srcSurf = srcCanvas->GetSurfaceSnapshot();
2528
0
      if (!srcSurf) {
2529
0
        JSContext* context = nsContentUtils::GetCurrentJSContext();
2530
0
        if (context) {
2531
0
          JS_ReportWarningASCII(context,
2532
0
                                "CanvasRenderingContext2D.createPattern()"
2533
0
                                " failed to snapshot source canvas.");
2534
0
        }
2535
0
        aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
2536
0
        return nullptr;
2537
0
      }
2538
0
2539
0
      RefPtr<CanvasPattern> pat =
2540
0
        new CanvasPattern(this, srcSurf, repeatMode, element->NodePrincipal(), canvas->IsWriteOnly(), false);
2541
0
2542
0
      return pat.forget();
2543
0
    }
2544
0
  } else if (aSource.IsHTMLImageElement()) {
2545
0
    HTMLImageElement* img = &aSource.GetAsHTMLImageElement();
2546
0
    if (img->IntrinsicState().HasState(NS_EVENT_STATE_BROKEN)) {
2547
0
      aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
2548
0
      return nullptr;
2549
0
    }
2550
0
2551
0
    element = img;
2552
0
  } else if (aSource.IsSVGImageElement()) {
2553
0
    SVGImageElement* img = &aSource.GetAsSVGImageElement();
2554
0
    if (img->IntrinsicState().HasState(NS_EVENT_STATE_BROKEN)) {
2555
0
      aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
2556
0
      return nullptr;
2557
0
    }
2558
0
2559
0
    element = img;
2560
0
  } else if (aSource.IsHTMLVideoElement()) {
2561
0
    auto& video = aSource.GetAsHTMLVideoElement();
2562
0
    video.MarkAsContentSource(mozilla::dom::HTMLVideoElement::CallerAPI::CREATE_PATTERN);
2563
0
    element = &video;
2564
0
  } else {
2565
0
    // Special case for ImageBitmap
2566
0
    ImageBitmap& imgBitmap = aSource.GetAsImageBitmap();
2567
0
    EnsureTarget();
2568
0
    if (!IsTargetValid()) {
2569
0
      aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
2570
0
      return nullptr;
2571
0
    }
2572
0
    RefPtr<SourceSurface> srcSurf = imgBitmap.PrepareForDrawTarget(mTarget);
2573
0
    if (!srcSurf) {
2574
0
      JSContext* context = nsContentUtils::GetCurrentJSContext();
2575
0
      if (context) {
2576
0
        JS_ReportWarningASCII(context,
2577
0
                              "CanvasRenderingContext2D.createPattern()"
2578
0
                              " failed to prepare source ImageBitmap.");
2579
0
      }
2580
0
      aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
2581
0
      return nullptr;
2582
0
    }
2583
0
2584
0
    // An ImageBitmap never taints others so we set principalForSecurityCheck to
2585
0
    // nullptr and set CORSUsed to true for passing the security check in
2586
0
    // CanvasUtils::DoDrawImageSecurityCheck().
2587
0
    RefPtr<CanvasPattern> pat =
2588
0
      new CanvasPattern(this, srcSurf, repeatMode, nullptr, false, true);
2589
0
2590
0
    return pat.forget();
2591
0
  }
2592
0
2593
0
  EnsureTarget();
2594
0
  if (!IsTargetValid()) {
2595
0
    aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
2596
0
    return nullptr;
2597
0
  }
2598
0
2599
0
  // The canvas spec says that createPattern should use the first frame
2600
0
  // of animated images
2601
0
  nsLayoutUtils::SurfaceFromElementResult res =
2602
0
    nsLayoutUtils::SurfaceFromElement(element,
2603
0
      nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE, mTarget);
2604
0
2605
0
  if (!res.GetSourceSurface()) {
2606
0
    return nullptr;
2607
0
  }
2608
0
2609
0
  RefPtr<CanvasPattern> pat = new CanvasPattern(this, res.GetSourceSurface(), repeatMode,
2610
0
                                                res.mPrincipal, res.mIsWriteOnly,
2611
0
                                                res.mCORSUsed);
2612
0
  return pat.forget();
2613
0
}
2614
2615
//
2616
// shadows
2617
//
2618
void
2619
CanvasRenderingContext2D::SetShadowColor(const nsAString& aShadowColor)
2620
0
{
2621
0
  nscolor color;
2622
0
  if (!ParseColor(aShadowColor, &color)) {
2623
0
    return;
2624
0
  }
2625
0
2626
0
  CurrentState().shadowColor = color;
2627
0
}
2628
2629
//
2630
// filters
2631
//
2632
2633
2634
static already_AddRefed<RawServoDeclarationBlock>
2635
CreateDeclarationForServo(nsCSSPropertyID aProperty,
2636
                          const nsAString& aPropertyValue,
2637
                          nsIDocument* aDocument)
2638
0
{
2639
0
  RefPtr<URLExtraData> data =
2640
0
    new URLExtraData(aDocument->GetDocBaseURI(),
2641
0
                     aDocument->GetDocumentURI(),
2642
0
                     aDocument->NodePrincipal(),
2643
0
                     aDocument->GetReferrerPolicy());
2644
0
2645
0
  ServoCSSParser::ParsingEnvironment env(data,
2646
0
                                         aDocument->GetCompatibilityMode(),
2647
0
                                         aDocument->CSSLoader());
2648
0
  RefPtr<RawServoDeclarationBlock> servoDeclarations =
2649
0
    ServoCSSParser::ParseProperty(aProperty, aPropertyValue, env);
2650
0
2651
0
  if (!servoDeclarations) {
2652
0
    // We got a syntax error.  The spec says this value must be ignored.
2653
0
    return nullptr;
2654
0
  }
2655
0
2656
0
  // From canvas spec, force to set line-height property to 'normal' font
2657
0
  // property.
2658
0
  if (aProperty == eCSSProperty_font) {
2659
0
    const nsCString normalString = NS_LITERAL_CSTRING("normal");
2660
0
    Servo_DeclarationBlock_SetPropertyById(servoDeclarations,
2661
0
                                           eCSSProperty_line_height,
2662
0
                                           &normalString,
2663
0
                                           false,
2664
0
                                           data,
2665
0
                                           ParsingMode::Default,
2666
0
                                           aDocument->GetCompatibilityMode(),
2667
0
                                           aDocument->CSSLoader(),
2668
0
                                           { });
2669
0
  }
2670
0
2671
0
  return servoDeclarations.forget();
2672
0
}
2673
2674
static already_AddRefed<RawServoDeclarationBlock>
2675
CreateFontDeclarationForServo(const nsAString& aFont,
2676
                              nsIDocument* aDocument)
2677
0
{
2678
0
  return CreateDeclarationForServo(eCSSProperty_font, aFont, aDocument);
2679
0
}
2680
2681
static already_AddRefed<ComputedStyle>
2682
GetFontStyleForServo(Element* aElement, const nsAString& aFont,
2683
                     nsIPresShell* aPresShell,
2684
                     nsAString& aOutUsedFont,
2685
                     ErrorResult& aError)
2686
0
{
2687
0
  RefPtr<RawServoDeclarationBlock> declarations =
2688
0
    CreateFontDeclarationForServo(aFont, aPresShell->GetDocument());
2689
0
  if (!declarations) {
2690
0
    // We got a syntax error.  The spec says this value must be ignored.
2691
0
    return nullptr;
2692
0
  }
2693
0
2694
0
  // In addition to unparseable values, the spec says we need to reject
2695
0
  // 'inherit' and 'initial'. The easiest way to check for this is to look
2696
0
  // at font-size-adjust, which the font shorthand resets to 'none'.
2697
0
  if (Servo_DeclarationBlock_HasCSSWideKeyword(declarations,
2698
0
                                               eCSSProperty_font_size_adjust)) {
2699
0
    return nullptr;
2700
0
  }
2701
0
2702
0
  ServoStyleSet* styleSet = aPresShell->StyleSet();
2703
0
2704
0
  RefPtr<ComputedStyle> parentStyle;
2705
0
  // have to get a parent ComputedStyle for inherit-like relative
2706
0
  // values (2em, bolder, etc.)
2707
0
  if (aElement && aElement->IsInComposedDoc()) {
2708
0
    parentStyle = nsComputedDOMStyle::GetComputedStyle(aElement, nullptr);
2709
0
    if (!parentStyle) {
2710
0
      // The flush killed the shell, so we couldn't get any meaningful style
2711
0
      // back.
2712
0
      aError.Throw(NS_ERROR_FAILURE);
2713
0
      return nullptr;
2714
0
    }
2715
0
  } else {
2716
0
    RefPtr<RawServoDeclarationBlock> declarations =
2717
0
      CreateFontDeclarationForServo(NS_LITERAL_STRING("10px sans-serif"),
2718
0
                                    aPresShell->GetDocument());
2719
0
    MOZ_ASSERT(declarations);
2720
0
2721
0
    parentStyle = aPresShell->StyleSet()->
2722
0
      ResolveForDeclarations(nullptr, declarations);
2723
0
  }
2724
0
2725
0
  MOZ_RELEASE_ASSERT(parentStyle, "Should have a valid parent style");
2726
0
2727
0
  MOZ_ASSERT(!aPresShell->IsDestroying(),
2728
0
             "We should have returned an error above if the presshell is "
2729
0
             "being destroyed.");
2730
0
2731
0
  RefPtr<ComputedStyle> sc =
2732
0
    styleSet->ResolveForDeclarations(parentStyle, declarations);
2733
0
2734
0
  // The font getter is required to be reserialized based on what we
2735
0
  // parsed (including having line-height removed).  (Older drafts of
2736
0
  // the spec required font sizes be converted to pixels, but that no
2737
0
  // longer seems to be required.)
2738
0
  Servo_SerializeFontValueForCanvas(declarations, &aOutUsedFont);
2739
0
  return sc.forget();
2740
0
}
2741
2742
2743
static already_AddRefed<RawServoDeclarationBlock>
2744
CreateFilterDeclarationForServo(const nsAString& aFilter,
2745
                                nsIDocument* aDocument)
2746
0
{
2747
0
  return CreateDeclarationForServo(eCSSProperty_filter, aFilter, aDocument);
2748
0
}
2749
2750
static already_AddRefed<ComputedStyle>
2751
ResolveFilterStyleForServo(const nsAString& aFilterString,
2752
                           const ComputedStyle* aParentStyle,
2753
                           nsIPresShell* aPresShell,
2754
                           ErrorResult& aError)
2755
0
{
2756
0
  RefPtr<RawServoDeclarationBlock> declarations =
2757
0
    CreateFilterDeclarationForServo(aFilterString, aPresShell->GetDocument());
2758
0
  if (!declarations) {
2759
0
    // Refuse to accept the filter, but do not throw an error.
2760
0
    return nullptr;
2761
0
  }
2762
0
2763
0
  // In addition to unparseable values, the spec says we need to reject
2764
0
  // 'inherit' and 'initial'.
2765
0
  if (Servo_DeclarationBlock_HasCSSWideKeyword(declarations,
2766
0
                                               eCSSProperty_filter)) {
2767
0
    return nullptr;
2768
0
  }
2769
0
2770
0
  ServoStyleSet* styleSet = aPresShell->StyleSet();
2771
0
  RefPtr<ComputedStyle> computedValues =
2772
0
    styleSet->ResolveForDeclarations(aParentStyle, declarations);
2773
0
2774
0
  return computedValues.forget();
2775
0
}
2776
2777
bool
2778
CanvasRenderingContext2D::ParseFilter(const nsAString& aString,
2779
                                      nsTArray<nsStyleFilter>& aFilterChain,
2780
                                      ErrorResult& aError)
2781
0
{
2782
0
  if (!mCanvasElement && !mDocShell) {
2783
0
    NS_WARNING("Canvas element must be non-null or a docshell must be provided");
2784
0
    aError.Throw(NS_ERROR_FAILURE);
2785
0
    return false;
2786
0
  }
2787
0
2788
0
  nsCOMPtr<nsIPresShell> presShell = GetPresShell();
2789
0
  if (!presShell) {
2790
0
    aError.Throw(NS_ERROR_FAILURE);
2791
0
    return false;
2792
0
  }
2793
0
2794
0
  nsString usedFont; // unused
2795
0
2796
0
  RefPtr<ComputedStyle> parentStyle =
2797
0
    GetFontStyleForServo(mCanvasElement,
2798
0
                         GetFont(),
2799
0
                         presShell,
2800
0
                         usedFont,
2801
0
                         aError);
2802
0
  if (!parentStyle) {
2803
0
    return false;
2804
0
  }
2805
0
2806
0
  RefPtr<ComputedStyle> computedValues =
2807
0
    ResolveFilterStyleForServo(aString,
2808
0
                               parentStyle,
2809
0
                               presShell,
2810
0
                               aError);
2811
0
  if (!computedValues) {
2812
0
    return false;
2813
0
  }
2814
0
2815
0
  const nsStyleEffects* effects = computedValues->ComputedData()->GetStyleEffects();
2816
0
  // XXX: This mFilters is a one shot object, we probably could avoid copying.
2817
0
  aFilterChain = effects->mFilters;
2818
0
  return true;
2819
0
}
2820
2821
void
2822
CanvasRenderingContext2D::SetFilter(const nsAString& aFilter, ErrorResult& aError)
2823
0
{
2824
0
  nsTArray<nsStyleFilter> filterChain;
2825
0
  if (ParseFilter(aFilter, filterChain, aError)) {
2826
0
    CurrentState().filterString = aFilter;
2827
0
    filterChain.SwapElements(CurrentState().filterChain);
2828
0
    if (mCanvasElement) {
2829
0
      CurrentState().filterObserverList =
2830
0
        new SVGFilterObserverListForCanvas(CurrentState().filterChain,
2831
0
                                           mCanvasElement, this);
2832
0
      UpdateFilter();
2833
0
    }
2834
0
  }
2835
0
}
2836
2837
class CanvasUserSpaceMetrics : public UserSpaceMetricsWithSize
2838
{
2839
public:
2840
  CanvasUserSpaceMetrics(const gfx::IntSize& aSize, const nsFont& aFont,
2841
                         nsAtom* aFontLanguage, bool aExplicitLanguage,
2842
                         nsPresContext* aPresContext)
2843
    : mSize(aSize)
2844
    , mFont(aFont)
2845
    , mFontLanguage(aFontLanguage)
2846
    , mExplicitLanguage(aExplicitLanguage)
2847
    , mPresContext(aPresContext)
2848
0
  {
2849
0
  }
2850
2851
  virtual float GetEmLength() const override
2852
0
  {
2853
0
    return NSAppUnitsToFloatPixels(mFont.size,
2854
0
                                   AppUnitsPerCSSPixel());
2855
0
  }
2856
2857
  virtual float GetExLength() const override
2858
0
  {
2859
0
    nsDeviceContext* dc = mPresContext->DeviceContext();
2860
0
    nsFontMetrics::Params params;
2861
0
    params.language = mFontLanguage;
2862
0
    params.explicitLanguage = mExplicitLanguage;
2863
0
    params.textPerf = mPresContext->GetTextPerfMetrics();
2864
0
    RefPtr<nsFontMetrics> fontMetrics = dc->GetMetricsFor(mFont, params);
2865
0
    return NSAppUnitsToFloatPixels(fontMetrics->XHeight(),
2866
0
                                   AppUnitsPerCSSPixel());
2867
0
  }
2868
2869
  virtual gfx::Size GetSize() const override
2870
0
  { return Size(mSize); }
2871
2872
private:
2873
  gfx::IntSize mSize;
2874
  const nsFont& mFont;
2875
  nsAtom* mFontLanguage;
2876
  bool mExplicitLanguage;
2877
  nsPresContext* mPresContext;
2878
};
2879
2880
void
2881
CanvasRenderingContext2D::UpdateFilter()
2882
0
{
2883
0
  nsCOMPtr<nsIPresShell> presShell = GetPresShell();
2884
0
  if (!presShell || presShell->IsDestroying()) {
2885
0
    // Ensure we set an empty filter and update the state to
2886
0
    // reflect the current "taint" status of the canvas
2887
0
    CurrentState().filter = FilterDescription();
2888
0
    CurrentState().filterSourceGraphicTainted =
2889
0
      (mCanvasElement && mCanvasElement->IsWriteOnly());
2890
0
    return;
2891
0
  }
2892
0
2893
0
  // The filter might reference an SVG filter that is declared inside this
2894
0
  // document. Flush frames so that we'll have an nsSVGFilterFrame to work
2895
0
  // with.
2896
0
  presShell->FlushPendingNotifications(FlushType::Frames);
2897
0
  MOZ_RELEASE_ASSERT(!mStyleStack.IsEmpty());
2898
0
  if (MOZ_UNLIKELY(presShell->IsDestroying())) {
2899
0
    return;
2900
0
  }
2901
0
2902
0
  bool sourceGraphicIsTainted =
2903
0
    (mCanvasElement && mCanvasElement->IsWriteOnly());
2904
0
2905
0
  CurrentState().filter =
2906
0
    nsFilterInstance::GetFilterDescription(mCanvasElement,
2907
0
      CurrentState().filterChain,
2908
0
      sourceGraphicIsTainted,
2909
0
      CanvasUserSpaceMetrics(GetSize(),
2910
0
                             CurrentState().fontFont,
2911
0
                             CurrentState().fontLanguage,
2912
0
                             CurrentState().fontExplicitLanguage,
2913
0
                             presShell->GetPresContext()),
2914
0
      gfxRect(0, 0, mWidth, mHeight),
2915
0
      CurrentState().filterAdditionalImages);
2916
0
  CurrentState().filterSourceGraphicTainted = sourceGraphicIsTainted;
2917
0
}
2918
2919
//
2920
// rects
2921
//
2922
2923
static bool
2924
ValidateRect(double& aX, double& aY, double& aWidth, double& aHeight, bool aIsZeroSizeValid)
2925
0
{
2926
0
  if (!aIsZeroSizeValid && (aWidth == 0.0 || aHeight == 0.0)) {
2927
0
    return false;
2928
0
  }
2929
0
2930
0
  // bug 1018527
2931
0
  // The values of canvas API input are in double precision, but Moz2D APIs are
2932
0
  // using float precision. Bypass canvas API calls when the input is out of
2933
0
  // float precision to avoid precision problem
2934
0
  if (!std::isfinite((float)aX) | !std::isfinite((float)aY) |
2935
0
      !std::isfinite((float)aWidth) | !std::isfinite((float)aHeight)) {
2936
0
    return false;
2937
0
  }
2938
0
2939
0
  // bug 1074733
2940
0
  // The canvas spec does not forbid rects with negative w or h, so given
2941
0
  // corners (x, y), (x+w, y), (x+w, y+h), and (x, y+h) we must generate
2942
0
  // the appropriate rect by flipping negative dimensions. This prevents
2943
0
  // draw targets from receiving "empty" rects later on.
2944
0
  if (aWidth < 0) {
2945
0
    aWidth = -aWidth;
2946
0
    aX -= aWidth;
2947
0
  }
2948
0
  if (aHeight < 0) {
2949
0
    aHeight = -aHeight;
2950
0
    aY -= aHeight;
2951
0
  }
2952
0
  return true;
2953
0
}
2954
2955
void
2956
CanvasRenderingContext2D::ClearRect(double aX, double aY, double aW,
2957
                                    double aH)
2958
0
{
2959
0
  // Do not allow zeros - it's a no-op at that point per spec.
2960
0
  if (!ValidateRect(aX, aY, aW, aH, false)) {
2961
0
    return;
2962
0
  }
2963
0
2964
0
  gfx::Rect clearRect(aX, aY, aW, aH);
2965
0
2966
0
  EnsureTarget(&clearRect);
2967
0
  if (!IsTargetValid()) {
2968
0
    return;
2969
0
  }
2970
0
2971
0
  mTarget->ClearRect(clearRect);
2972
0
2973
0
  RedrawUser(gfxRect(aX, aY, aW, aH));
2974
0
}
2975
2976
void
2977
CanvasRenderingContext2D::FillRect(double aX, double aY, double aW, double aH)
2978
0
{
2979
0
  if (!ValidateRect(aX, aY, aW, aH, true)) {
2980
0
    return;
2981
0
  }
2982
0
2983
0
  const ContextState* state = &CurrentState();
2984
0
  if (state->patternStyles[Style::FILL]) {
2985
0
    CanvasPattern::RepeatMode repeat =
2986
0
      state->patternStyles[Style::FILL]->mRepeat;
2987
0
    // In the FillRect case repeat modes are easy to deal with.
2988
0
    bool limitx = repeat == CanvasPattern::RepeatMode::NOREPEAT ||
2989
0
                  repeat == CanvasPattern::RepeatMode::REPEATY;
2990
0
    bool limity = repeat == CanvasPattern::RepeatMode::NOREPEAT ||
2991
0
                  repeat == CanvasPattern::RepeatMode::REPEATX;
2992
0
2993
0
    IntSize patternSize =
2994
0
      state->patternStyles[Style::FILL]->mSurface->GetSize();
2995
0
2996
0
    // We always need to execute painting for non-over operators, even if
2997
0
    // we end up with w/h = 0.
2998
0
    if (limitx) {
2999
0
      if (aX < 0) {
3000
0
        aW += aX;
3001
0
        if (aW < 0) {
3002
0
          aW = 0;
3003
0
        }
3004
0
3005
0
        aX = 0;
3006
0
      }
3007
0
      if (aX + aW > patternSize.width) {
3008
0
        aW = patternSize.width - aX;
3009
0
        if (aW < 0) {
3010
0
          aW = 0;
3011
0
        }
3012
0
      }
3013
0
    }
3014
0
    if (limity) {
3015
0
      if (aY < 0) {
3016
0
        aH += aY;
3017
0
        if (aH < 0) {
3018
0
          aH = 0;
3019
0
        }
3020
0
3021
0
        aY = 0;
3022
0
      }
3023
0
      if (aY + aH > patternSize.height) {
3024
0
        aH = patternSize.height - aY;
3025
0
        if (aH < 0) {
3026
0
          aH = 0;
3027
0
        }
3028
0
      }
3029
0
    }
3030
0
  }
3031
0
  state = nullptr;
3032
0
3033
0
  CompositionOp op = UsedOperation();
3034
0
  bool discardContent = PatternIsOpaque(Style::FILL)
3035
0
    && (op == CompositionOp::OP_OVER || op == CompositionOp::OP_SOURCE);
3036
0
  const gfx::Rect fillRect(aX, aY, aW, aH);
3037
0
  EnsureTarget(discardContent ? &fillRect : nullptr);
3038
0
  if (!IsTargetValid()) {
3039
0
    return;
3040
0
  }
3041
0
3042
0
  gfx::Rect bounds;
3043
0
  const bool needBounds = NeedToCalculateBounds();
3044
0
  if (!IsTargetValid()) {
3045
0
    return;
3046
0
  }
3047
0
  if (needBounds) {
3048
0
    bounds = mTarget->GetTransform().TransformBounds(fillRect);
3049
0
  }
3050
0
3051
0
  AntialiasMode antialiasMode = CurrentState().imageSmoothingEnabled ?
3052
0
                                AntialiasMode::DEFAULT : AntialiasMode::NONE;
3053
0
3054
0
  AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds);
3055
0
  if (!target) {
3056
0
    return;
3057
0
  }
3058
0
  target->FillRect(gfx::Rect(aX, aY, aW, aH),
3059
0
                   CanvasGeneralPattern().ForStyle(this, Style::FILL, mTarget),
3060
0
                   DrawOptions(CurrentState().globalAlpha, op, antialiasMode));
3061
0
3062
0
  RedrawUser(gfxRect(aX, aY, aW, aH));
3063
0
}
3064
3065
void
3066
CanvasRenderingContext2D::StrokeRect(double aX, double aY, double aW, double aH)
3067
0
{
3068
0
  if (!aW && !aH) {
3069
0
    return;
3070
0
  }
3071
0
3072
0
  if (!ValidateRect(aX, aY, aW, aH, true)) {
3073
0
    return;
3074
0
  }
3075
0
3076
0
  EnsureTarget();
3077
0
  if (!IsTargetValid()) {
3078
0
    return;
3079
0
  }
3080
0
3081
0
  const bool needBounds = NeedToCalculateBounds();
3082
0
  if (!IsTargetValid()) {
3083
0
    return;
3084
0
  }
3085
0
3086
0
  gfx::Rect bounds;
3087
0
  if (needBounds) {
3088
0
    const ContextState& state = CurrentState();
3089
0
    bounds = gfx::Rect(aX - state.lineWidth / 2.0f, aY - state.lineWidth / 2.0f,
3090
0
                       aW + state.lineWidth, aH + state.lineWidth);
3091
0
    bounds = mTarget->GetTransform().TransformBounds(bounds);
3092
0
  }
3093
0
3094
0
  auto op = UsedOperation();
3095
0
  if (!IsTargetValid()) {
3096
0
    return;
3097
0
  }
3098
0
3099
0
  if (!aH) {
3100
0
    CapStyle cap = CapStyle::BUTT;
3101
0
    if (CurrentState().lineJoin == JoinStyle::ROUND) {
3102
0
      cap = CapStyle::ROUND;
3103
0
    }
3104
0
    AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds);
3105
0
    if (!target) {
3106
0
      return;
3107
0
    }
3108
0
3109
0
    const ContextState& state = CurrentState();
3110
0
    target->
3111
0
      StrokeLine(Point(aX, aY), Point(aX + aW, aY),
3112
0
                  CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget),
3113
0
                  StrokeOptions(state.lineWidth, state.lineJoin,
3114
0
                                cap, state.miterLimit,
3115
0
                                state.dash.Length(),
3116
0
                                state.dash.Elements(),
3117
0
                                state.dashOffset),
3118
0
                  DrawOptions(state.globalAlpha, op));
3119
0
    return;
3120
0
  }
3121
0
3122
0
  if (!aW) {
3123
0
    CapStyle cap = CapStyle::BUTT;
3124
0
    if (CurrentState().lineJoin == JoinStyle::ROUND) {
3125
0
      cap = CapStyle::ROUND;
3126
0
    }
3127
0
    AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds);
3128
0
    if (!target) {
3129
0
      return;
3130
0
    }
3131
0
3132
0
    const ContextState& state = CurrentState();
3133
0
    target->
3134
0
      StrokeLine(Point(aX, aY), Point(aX, aY + aH),
3135
0
                  CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget),
3136
0
                  StrokeOptions(state.lineWidth, state.lineJoin,
3137
0
                                cap, state.miterLimit,
3138
0
                                state.dash.Length(),
3139
0
                                state.dash.Elements(),
3140
0
                                state.dashOffset),
3141
0
                  DrawOptions(state.globalAlpha, op));
3142
0
    return;
3143
0
  }
3144
0
3145
0
  AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds);
3146
0
  if (!target) {
3147
0
    return;
3148
0
  }
3149
0
3150
0
  const ContextState& state = CurrentState();
3151
0
  target->
3152
0
    StrokeRect(gfx::Rect(aX, aY, aW, aH),
3153
0
               CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget),
3154
0
               StrokeOptions(state.lineWidth, state.lineJoin,
3155
0
                             state.lineCap, state.miterLimit,
3156
0
                             state.dash.Length(),
3157
0
                             state.dash.Elements(),
3158
0
                             state.dashOffset),
3159
0
               DrawOptions(state.globalAlpha, op));
3160
0
3161
0
  Redraw();
3162
0
}
3163
3164
//
3165
// path bits
3166
//
3167
3168
void
3169
CanvasRenderingContext2D::BeginPath()
3170
0
{
3171
0
  mPath = nullptr;
3172
0
  mPathBuilder = nullptr;
3173
0
  mDSPathBuilder = nullptr;
3174
0
  mPathTransformWillUpdate = false;
3175
0
}
3176
3177
void
3178
CanvasRenderingContext2D::Fill(const CanvasWindingRule& aWinding)
3179
0
{
3180
0
  EnsureUserSpacePath(aWinding);
3181
0
3182
0
  if (!mPath) {
3183
0
    return;
3184
0
  }
3185
0
3186
0
  const bool needBounds = NeedToCalculateBounds();
3187
0
  if (!IsTargetValid()) {
3188
0
    return;
3189
0
  }
3190
0
  gfx::Rect bounds;
3191
0
  if (needBounds) {
3192
0
    bounds = mPath->GetBounds(mTarget->GetTransform());
3193
0
  }
3194
0
3195
0
  AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds);
3196
0
  if (!target) {
3197
0
    return;
3198
0
  }
3199
0
3200
0
  auto op = UsedOperation();
3201
0
  if (!IsTargetValid() || !target) {
3202
0
    return;
3203
0
  }
3204
0
  target->Fill(mPath,
3205
0
               CanvasGeneralPattern().ForStyle(this, Style::FILL, mTarget),
3206
0
               DrawOptions(CurrentState().globalAlpha, op));
3207
0
  Redraw();
3208
0
}
3209
3210
void CanvasRenderingContext2D::Fill(const CanvasPath& aPath, const CanvasWindingRule& aWinding)
3211
0
{
3212
0
  EnsureTarget();
3213
0
  if (!IsTargetValid()) {
3214
0
    return;
3215
0
  }
3216
0
3217
0
  RefPtr<gfx::Path> gfxpath = aPath.GetPath(aWinding, mTarget);
3218
0
  if (!gfxpath) {
3219
0
    return;
3220
0
  }
3221
0
3222
0
  const bool needBounds = NeedToCalculateBounds();
3223
0
  if (!IsTargetValid()) {
3224
0
    return;
3225
0
  }
3226
0
  gfx::Rect bounds;
3227
0
  if (needBounds) {
3228
0
    bounds = gfxpath->GetBounds(mTarget->GetTransform());
3229
0
  }
3230
0
3231
0
  AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds);
3232
0
  if (!target) {
3233
0
    return;
3234
0
  }
3235
0
3236
0
  auto op = UsedOperation();
3237
0
  if (!IsTargetValid() || !target) {
3238
0
    return;
3239
0
  }
3240
0
  target->Fill(gfxpath,
3241
0
               CanvasGeneralPattern().ForStyle(this, Style::FILL, mTarget),
3242
0
               DrawOptions(CurrentState().globalAlpha, op));
3243
0
  Redraw();
3244
0
}
3245
3246
void
3247
CanvasRenderingContext2D::Stroke()
3248
0
{
3249
0
  EnsureUserSpacePath();
3250
0
3251
0
  if (!mPath) {
3252
0
    return;
3253
0
  }
3254
0
3255
0
  const ContextState* state = &CurrentState();
3256
0
  StrokeOptions strokeOptions(state->lineWidth, state->lineJoin,
3257
0
                              state->lineCap, state->miterLimit,
3258
0
                              state->dash.Length(), state->dash.Elements(),
3259
0
                              state->dashOffset);
3260
0
  state = nullptr;
3261
0
3262
0
  const bool needBounds = NeedToCalculateBounds();
3263
0
  if (!IsTargetValid()) {
3264
0
    return;
3265
0
  }
3266
0
  gfx::Rect bounds;
3267
0
  if (needBounds) {
3268
0
    bounds =
3269
0
      mPath->GetStrokedBounds(strokeOptions, mTarget->GetTransform());
3270
0
  }
3271
0
3272
0
  AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds);
3273
0
  if (!target) {
3274
0
    return;
3275
0
  }
3276
0
3277
0
  auto op = UsedOperation();
3278
0
  if (!IsTargetValid() || !target) {
3279
0
    return;
3280
0
  }
3281
0
  target->Stroke(mPath,
3282
0
                 CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget),
3283
0
                 strokeOptions, DrawOptions(CurrentState().globalAlpha, op));
3284
0
  Redraw();
3285
0
}
3286
3287
void
3288
CanvasRenderingContext2D::Stroke(const CanvasPath& aPath)
3289
0
{
3290
0
  EnsureTarget();
3291
0
  if (!IsTargetValid()) {
3292
0
    return;
3293
0
  }
3294
0
3295
0
  RefPtr<gfx::Path> gfxpath = aPath.GetPath(CanvasWindingRule::Nonzero, mTarget);
3296
0
3297
0
  if (!gfxpath) {
3298
0
    return;
3299
0
  }
3300
0
3301
0
  const ContextState* state = &CurrentState();
3302
0
  StrokeOptions strokeOptions(state->lineWidth, state->lineJoin,
3303
0
                              state->lineCap, state->miterLimit,
3304
0
                              state->dash.Length(), state->dash.Elements(),
3305
0
                              state->dashOffset);
3306
0
  state = nullptr;
3307
0
3308
0
  const bool needBounds = NeedToCalculateBounds();
3309
0
  if (!IsTargetValid()) {
3310
0
    return;
3311
0
  }
3312
0
  gfx::Rect bounds;
3313
0
  if (needBounds) {
3314
0
    bounds =
3315
0
      gfxpath->GetStrokedBounds(strokeOptions, mTarget->GetTransform());
3316
0
  }
3317
0
3318
0
  AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds);
3319
0
  if (!target) {
3320
0
    return;
3321
0
  }
3322
0
3323
0
  auto op = UsedOperation();
3324
0
  if (!IsTargetValid() || !target) {
3325
0
    return;
3326
0
  }
3327
0
  target->Stroke(gfxpath,
3328
0
                 CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget),
3329
0
                 strokeOptions, DrawOptions(CurrentState().globalAlpha, op));
3330
0
  Redraw();
3331
0
}
3332
3333
void CanvasRenderingContext2D::DrawFocusIfNeeded(mozilla::dom::Element& aElement,
3334
                                                 ErrorResult& aRv)
3335
0
{
3336
0
  EnsureUserSpacePath();
3337
0
  if (!mPath) {
3338
0
    return;
3339
0
  }
3340
0
3341
0
  if (DrawCustomFocusRing(aElement)) {
3342
0
    AutoSaveRestore asr(this);
3343
0
3344
0
    // set state to conforming focus state
3345
0
    ContextState* state = &CurrentState();
3346
0
    state->globalAlpha = 1.0;
3347
0
    state->shadowBlur = 0;
3348
0
    state->shadowOffset.x = 0;
3349
0
    state->shadowOffset.y = 0;
3350
0
    state->op = mozilla::gfx::CompositionOp::OP_OVER;
3351
0
3352
0
    state->lineCap = CapStyle::BUTT;
3353
0
    state->lineJoin = mozilla::gfx::JoinStyle::MITER_OR_BEVEL;
3354
0
    state->lineWidth = 1;
3355
0
    state->dash.Clear();
3356
0
3357
0
    // color and style of the rings is the same as for image maps
3358
0
    // set the background focus color
3359
0
    state->SetColorStyle(Style::STROKE, NS_RGBA(255, 255, 255, 255));
3360
0
    state = nullptr;
3361
0
3362
0
    // draw the focus ring
3363
0
    Stroke();
3364
0
    if (!mPath) {
3365
0
      return;
3366
0
    }
3367
0
3368
0
    // set dashing for foreground
3369
0
    nsTArray<mozilla::gfx::Float>& dash = CurrentState().dash;
3370
0
    for (uint32_t i = 0; i < 2; ++i) {
3371
0
      if (!dash.AppendElement(1, fallible)) {
3372
0
        aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
3373
0
        return;
3374
0
      }
3375
0
    }
3376
0
3377
0
    // set the foreground focus color
3378
0
    CurrentState().SetColorStyle(Style::STROKE, NS_RGBA(0,0,0, 255));
3379
0
    // draw the focus ring
3380
0
    Stroke();
3381
0
    if (!mPath) {
3382
0
      return;
3383
0
    }
3384
0
  }
3385
0
}
3386
3387
bool CanvasRenderingContext2D::DrawCustomFocusRing(mozilla::dom::Element& aElement)
3388
0
{
3389
0
  EnsureUserSpacePath();
3390
0
3391
0
  HTMLCanvasElement* canvas = GetCanvas();
3392
0
3393
0
  if (!canvas|| !nsContentUtils::ContentIsDescendantOf(&aElement, canvas)) {
3394
0
    return false;
3395
0
  }
3396
0
3397
0
  nsFocusManager* fm = nsFocusManager::GetFocusManager();
3398
0
  if (fm) {
3399
0
    // check that the element is focused
3400
0
    if (&aElement == fm->GetFocusedElement()) {
3401
0
      if (nsPIDOMWindowOuter* window = aElement.OwnerDoc()->GetWindow()) {
3402
0
        return window->ShouldShowFocusRing();
3403
0
      }
3404
0
    }
3405
0
  }
3406
0
3407
0
  return false;
3408
0
}
3409
3410
void
3411
CanvasRenderingContext2D::Clip(const CanvasWindingRule& aWinding)
3412
0
{
3413
0
  EnsureUserSpacePath(aWinding);
3414
0
3415
0
  if (!mPath) {
3416
0
    return;
3417
0
  }
3418
0
3419
0
  mTarget->PushClip(mPath);
3420
0
  CurrentState().clipsAndTransforms.AppendElement(ClipState(mPath));
3421
0
}
3422
3423
void
3424
CanvasRenderingContext2D::Clip(const CanvasPath& aPath, const CanvasWindingRule& aWinding)
3425
0
{
3426
0
  EnsureTarget();
3427
0
  if (!IsTargetValid()) {
3428
0
    return;
3429
0
  }
3430
0
3431
0
  RefPtr<gfx::Path> gfxpath = aPath.GetPath(aWinding, mTarget);
3432
0
3433
0
  if (!gfxpath) {
3434
0
    return;
3435
0
  }
3436
0
3437
0
  mTarget->PushClip(gfxpath);
3438
0
  CurrentState().clipsAndTransforms.AppendElement(ClipState(gfxpath));
3439
0
}
3440
3441
void
3442
CanvasRenderingContext2D::ArcTo(double aX1, double aY1, double aX2,
3443
                                double aY2, double aRadius,
3444
                                ErrorResult& aError)
3445
0
{
3446
0
  if (aRadius < 0) {
3447
0
    aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
3448
0
    return;
3449
0
  }
3450
0
3451
0
  EnsureWritablePath();
3452
0
3453
0
  // Current point in user space!
3454
0
  Point p0;
3455
0
  if (mPathBuilder) {
3456
0
    p0 = mPathBuilder->CurrentPoint();
3457
0
  } else {
3458
0
    Matrix invTransform = mTarget->GetTransform();
3459
0
    if (!invTransform.Invert()) {
3460
0
      return;
3461
0
    }
3462
0
3463
0
    p0 = invTransform.TransformPoint(mDSPathBuilder->CurrentPoint());
3464
0
  }
3465
0
3466
0
  Point p1(aX1, aY1);
3467
0
  Point p2(aX2, aY2);
3468
0
3469
0
  // Execute these calculations in double precision to avoid cumulative
3470
0
  // rounding errors.
3471
0
  double dir, a2, b2, c2, cosx, sinx, d, anx, any,
3472
0
         bnx, bny, x3, y3, x4, y4, cx, cy, angle0, angle1;
3473
0
  bool anticlockwise;
3474
0
3475
0
  if (p0 == p1 || p1 == p2 || aRadius == 0) {
3476
0
    LineTo(p1.x, p1.y);
3477
0
    return;
3478
0
  }
3479
0
3480
0
  // Check for colinearity
3481
0
  dir = (p2.x - p1.x) * (p0.y - p1.y) + (p2.y - p1.y) * (p1.x - p0.x);
3482
0
  if (dir == 0) {
3483
0
    LineTo(p1.x, p1.y);
3484
0
    return;
3485
0
  }
3486
0
3487
0
3488
0
  // XXX - Math for this code was already available from the non-azure code
3489
0
  // and would be well tested. Perhaps converting to bezier directly might
3490
0
  // be more efficient longer run.
3491
0
  a2 = (p0.x-aX1)*(p0.x-aX1) + (p0.y-aY1)*(p0.y-aY1);
3492
0
  b2 = (aX1-aX2)*(aX1-aX2) + (aY1-aY2)*(aY1-aY2);
3493
0
  c2 = (p0.x-aX2)*(p0.x-aX2) + (p0.y-aY2)*(p0.y-aY2);
3494
0
  cosx = (a2+b2-c2)/(2*sqrt(a2*b2));
3495
0
3496
0
  sinx = sqrt(1 - cosx*cosx);
3497
0
  d = aRadius / ((1 - cosx) / sinx);
3498
0
3499
0
  anx = (aX1-p0.x) / sqrt(a2);
3500
0
  any = (aY1-p0.y) / sqrt(a2);
3501
0
  bnx = (aX1-aX2) / sqrt(b2);
3502
0
  bny = (aY1-aY2) / sqrt(b2);
3503
0
  x3 = aX1 - anx*d;
3504
0
  y3 = aY1 - any*d;
3505
0
  x4 = aX1 - bnx*d;
3506
0
  y4 = aY1 - bny*d;
3507
0
  anticlockwise = (dir < 0);
3508
0
  cx = x3 + any*aRadius*(anticlockwise ? 1 : -1);
3509
0
  cy = y3 - anx*aRadius*(anticlockwise ? 1 : -1);
3510
0
  angle0 = atan2((y3-cy), (x3-cx));
3511
0
  angle1 = atan2((y4-cy), (x4-cx));
3512
0
3513
0
3514
0
  LineTo(x3, y3);
3515
0
3516
0
  Arc(cx, cy, aRadius, angle0, angle1, anticlockwise, aError);
3517
0
}
3518
3519
void
3520
CanvasRenderingContext2D::Arc(double aX, double aY, double aR,
3521
                              double aStartAngle, double aEndAngle,
3522
                              bool aAnticlockwise, ErrorResult& aError)
3523
0
{
3524
0
  if (aR < 0.0) {
3525
0
    aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
3526
0
    return;
3527
0
  }
3528
0
3529
0
  EnsureWritablePath();
3530
0
3531
0
  ArcToBezier(this, Point(aX, aY), Size(aR, aR), aStartAngle, aEndAngle, aAnticlockwise);
3532
0
}
3533
3534
void
3535
CanvasRenderingContext2D::Rect(double aX, double aY, double aW, double aH)
3536
0
{
3537
0
  EnsureWritablePath();
3538
0
3539
0
  if (mPathBuilder) {
3540
0
    mPathBuilder->MoveTo(Point(aX, aY));
3541
0
    mPathBuilder->LineTo(Point(aX + aW, aY));
3542
0
    mPathBuilder->LineTo(Point(aX + aW, aY + aH));
3543
0
    mPathBuilder->LineTo(Point(aX, aY + aH));
3544
0
    mPathBuilder->Close();
3545
0
  } else {
3546
0
    mDSPathBuilder->MoveTo(mTarget->GetTransform().TransformPoint(Point(aX, aY)));
3547
0
    mDSPathBuilder->LineTo(mTarget->GetTransform().TransformPoint(Point(aX + aW, aY)));
3548
0
    mDSPathBuilder->LineTo(mTarget->GetTransform().TransformPoint(Point(aX + aW, aY + aH)));
3549
0
    mDSPathBuilder->LineTo(mTarget->GetTransform().TransformPoint(Point(aX, aY + aH)));
3550
0
    mDSPathBuilder->Close();
3551
0
  }
3552
0
}
3553
3554
void
3555
CanvasRenderingContext2D::Ellipse(double aX, double aY, double aRadiusX, double aRadiusY,
3556
                                  double aRotation, double aStartAngle, double aEndAngle,
3557
                                  bool aAnticlockwise, ErrorResult& aError)
3558
0
{
3559
0
  if (aRadiusX < 0.0 || aRadiusY < 0.0) {
3560
0
    aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
3561
0
    return;
3562
0
  }
3563
0
3564
0
  EnsureWritablePath();
3565
0
3566
0
  ArcToBezier(this, Point(aX, aY), Size(aRadiusX, aRadiusY), aStartAngle, aEndAngle,
3567
0
              aAnticlockwise, aRotation);
3568
0
}
3569
3570
void
3571
CanvasRenderingContext2D::EnsureWritablePath()
3572
0
{
3573
0
  EnsureTarget();
3574
0
  // NOTE: IsTargetValid() may be false here (mTarget == sErrorTarget) but we
3575
0
  // go ahead and create a path anyway since callers depend on that.
3576
0
3577
0
  if (mDSPathBuilder) {
3578
0
    return;
3579
0
  }
3580
0
3581
0
  FillRule fillRule = CurrentState().fillRule;
3582
0
3583
0
  if (mPathBuilder) {
3584
0
    if (mPathTransformWillUpdate) {
3585
0
      mPath = mPathBuilder->Finish();
3586
0
      mDSPathBuilder =
3587
0
        mPath->TransformedCopyToBuilder(mPathToDS, fillRule);
3588
0
      mPath = nullptr;
3589
0
      mPathBuilder = nullptr;
3590
0
      mPathTransformWillUpdate = false;
3591
0
    }
3592
0
    return;
3593
0
  }
3594
0
3595
0
  if (!mPath) {
3596
0
    NS_ASSERTION(!mPathTransformWillUpdate, "mPathTransformWillUpdate should be false, if all paths are null");
3597
0
    mPathBuilder = mTarget->CreatePathBuilder(fillRule);
3598
0
  } else if (!mPathTransformWillUpdate) {
3599
0
    mPathBuilder = mPath->CopyToBuilder(fillRule);
3600
0
  } else {
3601
0
    mDSPathBuilder =
3602
0
      mPath->TransformedCopyToBuilder(mPathToDS, fillRule);
3603
0
    mPathTransformWillUpdate = false;
3604
0
    mPath = nullptr;
3605
0
  }
3606
0
}
3607
3608
void
3609
CanvasRenderingContext2D::EnsureUserSpacePath(const CanvasWindingRule& aWinding)
3610
0
{
3611
0
  FillRule fillRule = CurrentState().fillRule;
3612
0
  if (aWinding == CanvasWindingRule::Evenodd)
3613
0
    fillRule = FillRule::FILL_EVEN_ODD;
3614
0
3615
0
  EnsureTarget();
3616
0
  if (!IsTargetValid()) {
3617
0
    return;
3618
0
  }
3619
0
3620
0
  if (!mPath && !mPathBuilder && !mDSPathBuilder) {
3621
0
    mPathBuilder = mTarget->CreatePathBuilder(fillRule);
3622
0
  }
3623
0
3624
0
  if (mPathBuilder) {
3625
0
    mPath = mPathBuilder->Finish();
3626
0
    mPathBuilder = nullptr;
3627
0
  }
3628
0
3629
0
  if (mPath &&
3630
0
      mPathTransformWillUpdate) {
3631
0
    mDSPathBuilder =
3632
0
      mPath->TransformedCopyToBuilder(mPathToDS, fillRule);
3633
0
    mPath = nullptr;
3634
0
    mPathTransformWillUpdate = false;
3635
0
  }
3636
0
3637
0
  if (mDSPathBuilder) {
3638
0
    RefPtr<Path> dsPath;
3639
0
    dsPath = mDSPathBuilder->Finish();
3640
0
    mDSPathBuilder = nullptr;
3641
0
3642
0
    Matrix inverse = mTarget->GetTransform();
3643
0
    if (!inverse.Invert()) {
3644
0
      NS_WARNING("Could not invert transform");
3645
0
      return;
3646
0
    }
3647
0
3648
0
    mPathBuilder =
3649
0
      dsPath->TransformedCopyToBuilder(inverse, fillRule);
3650
0
    mPath = mPathBuilder->Finish();
3651
0
    mPathBuilder = nullptr;
3652
0
  }
3653
0
3654
0
  if (mPath && mPath->GetFillRule() != fillRule) {
3655
0
    mPathBuilder = mPath->CopyToBuilder(fillRule);
3656
0
    mPath = mPathBuilder->Finish();
3657
0
    mPathBuilder = nullptr;
3658
0
  }
3659
0
3660
0
  NS_ASSERTION(mPath, "mPath should exist");
3661
0
}
3662
3663
void
3664
CanvasRenderingContext2D::TransformWillUpdate()
3665
0
{
3666
0
  EnsureTarget();
3667
0
  if (!IsTargetValid()) {
3668
0
    return;
3669
0
  }
3670
0
3671
0
  // Store the matrix that would transform the current path to device
3672
0
  // space.
3673
0
  if (mPath || mPathBuilder) {
3674
0
    if (!mPathTransformWillUpdate) {
3675
0
      // If the transform has already been updated, but a device space builder
3676
0
      // has not been created yet mPathToDS contains the right transform to
3677
0
      // transform the current mPath into device space.
3678
0
      // We should leave it alone.
3679
0
      mPathToDS = mTarget->GetTransform();
3680
0
    }
3681
0
    mPathTransformWillUpdate = true;
3682
0
  }
3683
0
}
3684
3685
//
3686
// text
3687
//
3688
3689
void
3690
CanvasRenderingContext2D::SetFont(const nsAString& aFont,
3691
                                  ErrorResult& aError)
3692
0
{
3693
0
  SetFontInternal(aFont, aError);
3694
0
}
3695
3696
bool
3697
CanvasRenderingContext2D::SetFontInternal(const nsAString& aFont,
3698
                                          ErrorResult& aError)
3699
0
{
3700
0
  /*
3701
0
    * If font is defined with relative units (e.g. ems) and the parent
3702
0
    * ComputedStyle changes in between calls, setting the font to the
3703
0
    * same value as previous could result in a different computed value,
3704
0
    * so we cannot have the optimization where we check if the new font
3705
0
    * string is equal to the old one.
3706
0
    */
3707
0
3708
0
  if (!mCanvasElement && !mDocShell) {
3709
0
    NS_WARNING("Canvas element must be non-null or a docshell must be provided");
3710
0
    aError.Throw(NS_ERROR_FAILURE);
3711
0
    return false;
3712
0
  }
3713
0
3714
0
  nsCOMPtr<nsIPresShell> presShell = GetPresShell();
3715
0
  if (!presShell) {
3716
0
    aError.Throw(NS_ERROR_FAILURE);
3717
0
    return false;
3718
0
  }
3719
0
3720
0
  nsString usedFont;
3721
0
  RefPtr<ComputedStyle> sc =
3722
0
    GetFontStyleForServo(mCanvasElement, aFont, presShell, usedFont, aError);
3723
0
  if (!sc) {
3724
0
    return false;
3725
0
  }
3726
0
3727
0
  const nsStyleFont* fontStyle = sc->StyleFont();
3728
0
  nsPresContext* c = presShell->GetPresContext();
3729
0
3730
0
  // Purposely ignore the font size that respects the user's minimum
3731
0
  // font preference (fontStyle->mFont.size) in favor of the computed
3732
0
  // size (fontStyle->mSize).  See
3733
0
  // https://bugzilla.mozilla.org/show_bug.cgi?id=698652.
3734
0
  // FIXME: Nobody initializes mAllowZoom for servo?
3735
0
  //MOZ_ASSERT(!fontStyle->mAllowZoom,
3736
0
  //           "expected text zoom to be disabled on this nsStyleFont");
3737
0
  nsFont resizedFont(fontStyle->mFont);
3738
0
  // Create a font group working in units of CSS pixels instead of the usual
3739
0
  // device pixels, to avoid being affected by page zoom. nsFontMetrics will
3740
0
  // convert nsFont size in app units to device pixels for the font group, so
3741
0
  // here we first apply to the size the equivalent of a conversion from device
3742
0
  // pixels to CSS pixels, to adjust for the difference in expectations from
3743
0
  // other nsFontMetrics clients.
3744
0
  resizedFont.size =
3745
0
    (fontStyle->mSize * c->AppUnitsPerDevPixel()) / AppUnitsPerCSSPixel();
3746
0
3747
0
  nsFontMetrics::Params params;
3748
0
  params.language = fontStyle->mLanguage;
3749
0
  params.explicitLanguage = fontStyle->mExplicitLanguage;
3750
0
  params.userFontSet = c->GetUserFontSet();
3751
0
  params.textPerf = c->GetTextPerfMetrics();
3752
0
  RefPtr<nsFontMetrics> metrics =
3753
0
    c->DeviceContext()->GetMetricsFor(resizedFont, params);
3754
0
3755
0
  gfxFontGroup* newFontGroup = metrics->GetThebesFontGroup();
3756
0
  CurrentState().fontGroup = newFontGroup;
3757
0
  NS_ASSERTION(CurrentState().fontGroup, "Could not get font group");
3758
0
  CurrentState().font = usedFont;
3759
0
  CurrentState().fontFont = fontStyle->mFont;
3760
0
  CurrentState().fontFont.size = fontStyle->mSize;
3761
0
  CurrentState().fontLanguage = fontStyle->mLanguage;
3762
0
  CurrentState().fontExplicitLanguage = fontStyle->mExplicitLanguage;
3763
0
3764
0
  return true;
3765
0
}
3766
3767
void
3768
CanvasRenderingContext2D::SetTextAlign(const nsAString& aTextAlign)
3769
0
{
3770
0
  if (aTextAlign.EqualsLiteral("start"))
3771
0
    CurrentState().textAlign = TextAlign::START;
3772
0
  else if (aTextAlign.EqualsLiteral("end"))
3773
0
    CurrentState().textAlign = TextAlign::END;
3774
0
  else if (aTextAlign.EqualsLiteral("left"))
3775
0
    CurrentState().textAlign = TextAlign::LEFT;
3776
0
  else if (aTextAlign.EqualsLiteral("right"))
3777
0
    CurrentState().textAlign = TextAlign::RIGHT;
3778
0
  else if (aTextAlign.EqualsLiteral("center"))
3779
0
    CurrentState().textAlign = TextAlign::CENTER;
3780
0
}
3781
3782
void
3783
CanvasRenderingContext2D::GetTextAlign(nsAString& aTextAlign)
3784
{
3785
  switch (CurrentState().textAlign)
3786
  {
3787
  case TextAlign::START:
3788
    aTextAlign.AssignLiteral("start");
3789
    break;
3790
  case TextAlign::END:
3791
    aTextAlign.AssignLiteral("end");
3792
    break;
3793
  case TextAlign::LEFT:
3794
    aTextAlign.AssignLiteral("left");
3795
    break;
3796
  case TextAlign::RIGHT:
3797
    aTextAlign.AssignLiteral("right");
3798
    break;
3799
  case TextAlign::CENTER:
3800
    aTextAlign.AssignLiteral("center");
3801
    break;
3802
  }
3803
}
3804
3805
void
3806
CanvasRenderingContext2D::SetTextBaseline(const nsAString& aTextBaseline)
3807
0
{
3808
0
  if (aTextBaseline.EqualsLiteral("top"))
3809
0
    CurrentState().textBaseline = TextBaseline::TOP;
3810
0
  else if (aTextBaseline.EqualsLiteral("hanging"))
3811
0
    CurrentState().textBaseline = TextBaseline::HANGING;
3812
0
  else if (aTextBaseline.EqualsLiteral("middle"))
3813
0
    CurrentState().textBaseline = TextBaseline::MIDDLE;
3814
0
  else if (aTextBaseline.EqualsLiteral("alphabetic"))
3815
0
    CurrentState().textBaseline = TextBaseline::ALPHABETIC;
3816
0
  else if (aTextBaseline.EqualsLiteral("ideographic"))
3817
0
    CurrentState().textBaseline = TextBaseline::IDEOGRAPHIC;
3818
0
  else if (aTextBaseline.EqualsLiteral("bottom"))
3819
0
    CurrentState().textBaseline = TextBaseline::BOTTOM;
3820
0
}
3821
3822
void
3823
CanvasRenderingContext2D::GetTextBaseline(nsAString& aTextBaseline)
3824
{
3825
  switch (CurrentState().textBaseline)
3826
  {
3827
  case TextBaseline::TOP:
3828
    aTextBaseline.AssignLiteral("top");
3829
    break;
3830
  case TextBaseline::HANGING:
3831
    aTextBaseline.AssignLiteral("hanging");
3832
    break;
3833
  case TextBaseline::MIDDLE:
3834
    aTextBaseline.AssignLiteral("middle");
3835
    break;
3836
  case TextBaseline::ALPHABETIC:
3837
    aTextBaseline.AssignLiteral("alphabetic");
3838
    break;
3839
  case TextBaseline::IDEOGRAPHIC:
3840
    aTextBaseline.AssignLiteral("ideographic");
3841
    break;
3842
  case TextBaseline::BOTTOM:
3843
    aTextBaseline.AssignLiteral("bottom");
3844
    break;
3845
  }
3846
}
3847
3848
/*
3849
 * Helper function that replaces the whitespace characters in a string
3850
 * with U+0020 SPACE. The whitespace characters are defined as U+0020 SPACE,
3851
 * U+0009 CHARACTER TABULATION (tab), U+000A LINE FEED (LF), U+000B LINE
3852
 * TABULATION, U+000C FORM FEED (FF), and U+000D CARRIAGE RETURN (CR).
3853
 * @param str The string whose whitespace characters to replace.
3854
 */
3855
static inline void
3856
TextReplaceWhitespaceCharacters(nsAutoString& aStr)
3857
0
{
3858
0
  aStr.ReplaceChar("\x09\x0A\x0B\x0C\x0D", char16_t(' '));
3859
0
}
3860
3861
void
3862
CanvasRenderingContext2D::FillText(const nsAString& aText, double aX,
3863
                                   double aY,
3864
                                   const Optional<double>& aMaxWidth,
3865
                                   ErrorResult& aError)
3866
0
{
3867
0
  aError = DrawOrMeasureText(aText, aX, aY, aMaxWidth, TextDrawOperation::FILL, nullptr);
3868
0
}
3869
3870
void
3871
CanvasRenderingContext2D::StrokeText(const nsAString& aText, double aX,
3872
                                     double aY,
3873
                                     const Optional<double>& aMaxWidth,
3874
                                     ErrorResult& aError)
3875
0
{
3876
0
  aError = DrawOrMeasureText(aText, aX, aY, aMaxWidth, TextDrawOperation::STROKE, nullptr);
3877
0
}
3878
3879
TextMetrics*
3880
CanvasRenderingContext2D::MeasureText(const nsAString& aRawText,
3881
                                      ErrorResult& aError)
3882
0
{
3883
0
  float width;
3884
0
  Optional<double> maxWidth;
3885
0
  aError = DrawOrMeasureText(aRawText, 0, 0, maxWidth, TextDrawOperation::MEASURE, &width);
3886
0
  if (aError.Failed()) {
3887
0
    return nullptr;
3888
0
  }
3889
0
3890
0
  return new TextMetrics(width);
3891
0
}
3892
3893
void
3894
CanvasRenderingContext2D::AddHitRegion(const HitRegionOptions& aOptions, ErrorResult& aError)
3895
0
{
3896
0
  RefPtr<gfx::Path> path;
3897
0
  if (aOptions.mPath) {
3898
0
    EnsureTarget();
3899
0
    if (!IsTargetValid()) {
3900
0
      return;
3901
0
    }
3902
0
    path = aOptions.mPath->GetPath(CanvasWindingRule::Nonzero, mTarget);
3903
0
  }
3904
0
3905
0
  if (!path) {
3906
0
    // check if the path is valid
3907
0
    EnsureUserSpacePath(CanvasWindingRule::Nonzero);
3908
0
    path = mPath;
3909
0
  }
3910
0
3911
0
  if (!path) {
3912
0
    aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
3913
0
    return;
3914
0
  }
3915
0
3916
0
  // get the bounds of the current path. They are relative to the canvas
3917
0
  gfx::Rect bounds(path->GetBounds(mTarget->GetTransform()));
3918
0
  if ((bounds.width == 0) || (bounds.height == 0) || !bounds.IsFinite()) {
3919
0
    // The specified region has no pixels.
3920
0
    aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
3921
0
    return;
3922
0
  }
3923
0
3924
0
  // remove old hit region first
3925
0
  RemoveHitRegion(aOptions.mId);
3926
0
3927
0
  if (aOptions.mControl) {
3928
0
    // also remove regions with this control
3929
0
    for (size_t x = 0; x < mHitRegionsOptions.Length(); x++) {
3930
0
      RegionInfo& info = mHitRegionsOptions[x];
3931
0
      if (info.mElement == aOptions.mControl) {
3932
0
        mHitRegionsOptions.RemoveElementAt(x);
3933
0
        break;
3934
0
      }
3935
0
    }
3936
0
#ifdef ACCESSIBILITY
3937
0
  aOptions.mControl->SetProperty(nsGkAtoms::hitregion, new bool(true),
3938
0
                                nsINode::DeleteProperty<bool>);
3939
0
#endif
3940
0
  }
3941
0
3942
0
  // finally, add the region to the list
3943
0
  RegionInfo info;
3944
0
  info.mId = aOptions.mId;
3945
0
  info.mElement = aOptions.mControl;
3946
0
  RefPtr<PathBuilder> pathBuilder = path->TransformedCopyToBuilder(mTarget->GetTransform());
3947
0
  info.mPath = pathBuilder->Finish();
3948
0
3949
0
  mHitRegionsOptions.InsertElementAt(0, info);
3950
0
}
3951
3952
void
3953
CanvasRenderingContext2D::RemoveHitRegion(const nsAString& aId)
3954
0
{
3955
0
  if (aId.Length() == 0) {
3956
0
     return;
3957
0
   }
3958
0
3959
0
  for (size_t x = 0; x < mHitRegionsOptions.Length(); x++) {
3960
0
    RegionInfo& info = mHitRegionsOptions[x];
3961
0
    if (info.mId == aId) {
3962
0
      mHitRegionsOptions.RemoveElementAt(x);
3963
0
3964
0
      return;
3965
0
    }
3966
0
  }
3967
0
}
3968
3969
void
3970
CanvasRenderingContext2D::ClearHitRegions()
3971
0
{
3972
0
  mHitRegionsOptions.Clear();
3973
0
}
3974
3975
bool
3976
CanvasRenderingContext2D::GetHitRegionRect(Element* aElement, nsRect& aRect)
3977
0
{
3978
0
  for (unsigned int x = 0; x < mHitRegionsOptions.Length(); x++) {
3979
0
    RegionInfo& info = mHitRegionsOptions[x];
3980
0
    if (info.mElement == aElement) {
3981
0
      gfx::Rect bounds(info.mPath->GetBounds());
3982
0
      gfxRect rect(bounds.x, bounds.y, bounds.width, bounds.height);
3983
0
      aRect = nsLayoutUtils::RoundGfxRectToAppRect(rect, AppUnitsPerCSSPixel());
3984
0
3985
0
      return true;
3986
0
    }
3987
0
  }
3988
0
3989
0
  return false;
3990
0
}
3991
3992
/**
3993
 * Used for nsBidiPresUtils::ProcessText
3994
 */
3995
struct MOZ_STACK_CLASS CanvasBidiProcessor : public nsBidiPresUtils::BidiProcessor
3996
{
3997
  typedef CanvasRenderingContext2D::Style Style;
3998
3999
  CanvasBidiProcessor()
4000
    : nsBidiPresUtils::BidiProcessor()
4001
    , mCtx(nullptr)
4002
    , mFontgrp(nullptr)
4003
    , mAppUnitsPerDevPixel(0)
4004
    , mOp(CanvasRenderingContext2D::TextDrawOperation::FILL)
4005
    , mTextRunFlags()
4006
    , mDoMeasureBoundingBox(false)
4007
0
  {
4008
0
    if (Preferences::GetBool(GFX_MISSING_FONTS_NOTIFY_PREF)) {
4009
0
      mMissingFonts = new gfxMissingFontRecorder();
4010
0
    }
4011
0
  }
4012
4013
  ~CanvasBidiProcessor()
4014
0
  {
4015
0
    // notify front-end code if we encountered missing glyphs in any script
4016
0
    if (mMissingFonts) {
4017
0
      mMissingFonts->Flush();
4018
0
    }
4019
0
  }
4020
4021
  typedef CanvasRenderingContext2D::ContextState ContextState;
4022
4023
  virtual void SetText(const char16_t* aText, int32_t aLength, nsBidiDirection aDirection) override
4024
0
  {
4025
0
    mFontgrp->UpdateUserFonts(); // ensure user font generation is current
4026
0
    // adjust flags for current direction run
4027
0
    gfx::ShapedTextFlags flags = mTextRunFlags;
4028
0
    if (aDirection == NSBIDI_RTL) {
4029
0
      flags |= gfx::ShapedTextFlags::TEXT_IS_RTL;
4030
0
    } else {
4031
0
      flags &= ~gfx::ShapedTextFlags::TEXT_IS_RTL;
4032
0
    }
4033
0
    mTextRun = mFontgrp->MakeTextRun(aText,
4034
0
                                     aLength,
4035
0
                                     mDrawTarget,
4036
0
                                     mAppUnitsPerDevPixel,
4037
0
                                     flags,
4038
0
                                     nsTextFrameUtils::Flags(),
4039
0
                                     mMissingFonts);
4040
0
  }
4041
4042
  virtual nscoord GetWidth() override
4043
0
  {
4044
0
    gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText(
4045
0
        mDoMeasureBoundingBox ? gfxFont::TIGHT_INK_EXTENTS
4046
0
                              : gfxFont::LOOSE_INK_EXTENTS, mDrawTarget);
4047
0
4048
0
    // this only measures the height; the total width is gotten from the
4049
0
    // the return value of ProcessText.
4050
0
    if (mDoMeasureBoundingBox) {
4051
0
      textRunMetrics.mBoundingBox.Scale(1.0 / mAppUnitsPerDevPixel);
4052
0
      mBoundingBox = mBoundingBox.Union(textRunMetrics.mBoundingBox);
4053
0
    }
4054
0
4055
0
    return NSToCoordRound(textRunMetrics.mAdvanceWidth);
4056
0
  }
4057
4058
  already_AddRefed<gfxPattern> GetGradientFor(Style aStyle)
4059
0
  {
4060
0
    RefPtr<gfxPattern> pattern;
4061
0
    CanvasGradient* gradient = mCtx->CurrentState().gradientStyles[aStyle];
4062
0
    CanvasGradient::Type type = gradient->GetType();
4063
0
4064
0
    switch (type) {
4065
0
    case CanvasGradient::Type::RADIAL: {
4066
0
      auto radial = static_cast<CanvasRadialGradient*>(gradient);
4067
0
      pattern = new gfxPattern(radial->mCenter1.x, radial->mCenter1.y,
4068
0
                               radial->mRadius1, radial->mCenter2.x,
4069
0
                               radial->mCenter2.y, radial->mRadius2);
4070
0
      break;
4071
0
    }
4072
0
    case CanvasGradient::Type::LINEAR: {
4073
0
      auto linear = static_cast<CanvasLinearGradient*>(gradient);
4074
0
      pattern = new gfxPattern(linear->mBegin.x, linear->mBegin.y,
4075
0
                               linear->mEnd.x, linear->mEnd.y);
4076
0
      break;
4077
0
    }
4078
0
    default:
4079
0
      MOZ_ASSERT(false, "Should be linear or radial gradient.");
4080
0
      return nullptr;
4081
0
    }
4082
0
4083
0
    for (auto stop : gradient->mRawStops) {
4084
0
      pattern->AddColorStop(stop.offset, stop.color);
4085
0
    }
4086
0
4087
0
    return pattern.forget();
4088
0
  }
4089
4090
  gfx::ExtendMode CvtCanvasRepeatToGfxRepeat(
4091
    CanvasPattern::RepeatMode aRepeatMode)
4092
  {
4093
    switch (aRepeatMode) {
4094
    case CanvasPattern::RepeatMode::REPEAT:
4095
      return gfx::ExtendMode::REPEAT;
4096
    case CanvasPattern::RepeatMode::REPEATX:
4097
      return gfx::ExtendMode::REPEAT_X;
4098
    case CanvasPattern::RepeatMode::REPEATY:
4099
      return gfx::ExtendMode::REPEAT_Y;
4100
    case CanvasPattern::RepeatMode::NOREPEAT:
4101
      return gfx::ExtendMode::CLAMP;
4102
    default:
4103
      return gfx::ExtendMode::CLAMP;
4104
    }
4105
  }
4106
4107
  already_AddRefed<gfxPattern> GetPatternFor(Style aStyle)
4108
0
  {
4109
0
    const CanvasPattern* pat = mCtx->CurrentState().patternStyles[aStyle];
4110
0
    RefPtr<gfxPattern> pattern = new gfxPattern(pat->mSurface, pat->mTransform);
4111
0
    pattern->SetExtend(CvtCanvasRepeatToGfxRepeat(pat->mRepeat));
4112
0
    return pattern.forget();
4113
0
  }
4114
4115
  virtual void DrawText(nscoord aXOffset, nscoord aWidth) override
4116
0
  {
4117
0
    gfx::Point point = mPt;
4118
0
    bool rtl = mTextRun->IsRightToLeft();
4119
0
    bool verticalRun = mTextRun->IsVertical();
4120
0
    RefPtr<gfxPattern> pattern;
4121
0
4122
0
    float& inlineCoord = verticalRun ? point.y : point.x;
4123
0
    inlineCoord += aXOffset;
4124
0
4125
0
    // offset is given in terms of left side of string
4126
0
    if (rtl) {
4127
0
      // Bug 581092 - don't use rounded pixel width to advance to
4128
0
      // right-hand end of run, because this will cause different
4129
0
      // glyph positioning for LTR vs RTL drawing of the same
4130
0
      // glyph string on OS X and DWrite where textrun widths may
4131
0
      // involve fractional pixels.
4132
0
      gfxTextRun::Metrics textRunMetrics =
4133
0
        mTextRun->MeasureText(mDoMeasureBoundingBox ?
4134
0
                                gfxFont::TIGHT_INK_EXTENTS :
4135
0
                                gfxFont::LOOSE_INK_EXTENTS,
4136
0
                              mDrawTarget);
4137
0
      inlineCoord += textRunMetrics.mAdvanceWidth;
4138
0
      // old code was:
4139
0
      //   point.x += width * mAppUnitsPerDevPixel;
4140
0
      // TODO: restore this if/when we move to fractional coords
4141
0
      // throughout the text layout process
4142
0
    }
4143
0
4144
0
    mCtx->EnsureTarget();
4145
0
    if (!mCtx->IsTargetValid()) {
4146
0
      return;
4147
0
    }
4148
0
4149
0
    // Defer the tasks to gfxTextRun which will handle color/svg-in-ot fonts
4150
0
    // appropriately.
4151
0
    StrokeOptions strokeOpts;
4152
0
    DrawOptions drawOpts;
4153
0
    Style style = (mOp == CanvasRenderingContext2D::TextDrawOperation::FILL)
4154
0
                    ? Style::FILL
4155
0
                    : Style::STROKE;
4156
0
4157
0
    AdjustedTarget target(mCtx);
4158
0
    if (!target) {
4159
0
      return;
4160
0
    }
4161
0
4162
0
    RefPtr<gfxContext> thebes =
4163
0
      gfxContext::CreatePreservingTransformOrNull(target);
4164
0
    if (!thebes) {
4165
0
      // If CreatePreservingTransformOrNull returns null, it will also have
4166
0
      // issued a gfxCriticalNote already, so here we'll just bail out.
4167
0
      return;
4168
0
    }
4169
0
    gfxTextRun::DrawParams params(thebes);
4170
0
4171
0
    const ContextState* state = &mCtx->CurrentState();
4172
0
    if (state->StyleIsColor(style)) { // Color
4173
0
      nscolor fontColor = state->colorStyles[style];
4174
0
      if (style == Style::FILL) {
4175
0
        params.context->SetColor(Color::FromABGR(fontColor));
4176
0
      } else {
4177
0
        params.textStrokeColor = fontColor;
4178
0
      }
4179
0
    } else {
4180
0
      if (state->gradientStyles[style]) { // Gradient
4181
0
        pattern = GetGradientFor(style);
4182
0
      } else if (state->patternStyles[style]) { // Pattern
4183
0
        pattern = GetPatternFor(style);
4184
0
      } else {
4185
0
        MOZ_ASSERT(false, "Should never reach here.");
4186
0
        return;
4187
0
      }
4188
0
      MOZ_ASSERT(pattern, "No valid pattern.");
4189
0
4190
0
      if (style == Style::FILL) {
4191
0
        params.context->SetPattern(pattern);
4192
0
      } else {
4193
0
        params.textStrokePattern = pattern;
4194
0
      }
4195
0
    }
4196
0
4197
0
    drawOpts.mAlpha = state->globalAlpha;
4198
0
    drawOpts.mCompositionOp = mCtx->UsedOperation();
4199
0
    if (!mCtx->IsTargetValid()) {
4200
0
      return;
4201
0
    }
4202
0
    state = &mCtx->CurrentState();
4203
0
    params.drawOpts = &drawOpts;
4204
0
4205
0
    if (style == Style::STROKE) {
4206
0
      strokeOpts.mLineWidth = state->lineWidth;
4207
0
      strokeOpts.mLineJoin = state->lineJoin;
4208
0
      strokeOpts.mLineCap = state->lineCap;
4209
0
      strokeOpts.mMiterLimit = state->miterLimit;
4210
0
      strokeOpts.mDashLength = state->dash.Length();
4211
0
      strokeOpts.mDashPattern =
4212
0
        (strokeOpts.mDashLength > 0) ? state->dash.Elements() : 0;
4213
0
      strokeOpts.mDashOffset = state->dashOffset;
4214
0
4215
0
      params.drawMode = DrawMode::GLYPH_STROKE;
4216
0
      params.strokeOpts = &strokeOpts;
4217
0
    }
4218
0
4219
0
    mTextRun->Draw(gfxTextRun::Range(mTextRun.get()), point, params);
4220
0
  }
4221
4222
  // current text run
4223
  RefPtr<gfxTextRun> mTextRun;
4224
4225
  // pointer to a screen reference context used to measure text and such
4226
  RefPtr<DrawTarget> mDrawTarget;
4227
4228
  // Pointer to the draw target we should fill our text to
4229
  CanvasRenderingContext2D* mCtx;
4230
4231
  // position of the left side of the string, alphabetic baseline
4232
  gfx::Point mPt;
4233
4234
  // current font
4235
  gfxFontGroup* mFontgrp;
4236
4237
  // to record any unsupported characters found in the text,
4238
  // and notify front-end if it is interested
4239
  nsAutoPtr<gfxMissingFontRecorder> mMissingFonts;
4240
4241
  // dev pixel conversion factor
4242
  int32_t mAppUnitsPerDevPixel;
4243
4244
  // operation (fill or stroke)
4245
  CanvasRenderingContext2D::TextDrawOperation mOp;
4246
4247
  // union of bounding boxes of all runs, needed for shadows
4248
  gfxRect mBoundingBox;
4249
4250
  // flags to use when creating textrun, based on CSS style
4251
  gfx::ShapedTextFlags mTextRunFlags;
4252
4253
  // true iff the bounding box should be measured
4254
  bool mDoMeasureBoundingBox;
4255
};
4256
4257
nsresult
4258
CanvasRenderingContext2D::DrawOrMeasureText(const nsAString& aRawText,
4259
                                            float aX,
4260
                                            float aY,
4261
                                            const Optional<double>& aMaxWidth,
4262
                                            TextDrawOperation aOp,
4263
                                            float* aWidth)
4264
0
{
4265
0
  nsresult rv;
4266
0
4267
0
  if (!mCanvasElement && !mDocShell) {
4268
0
    NS_WARNING("Canvas element must be non-null or a docshell must be provided");
4269
0
    return NS_ERROR_FAILURE;
4270
0
  }
4271
0
4272
0
  nsCOMPtr<nsIPresShell> presShell = GetPresShell();
4273
0
  if (!presShell)
4274
0
    return NS_ERROR_FAILURE;
4275
0
4276
0
  nsIDocument* document = presShell->GetDocument();
4277
0
4278
0
  // replace all the whitespace characters with U+0020 SPACE
4279
0
  nsAutoString textToDraw(aRawText);
4280
0
  TextReplaceWhitespaceCharacters(textToDraw);
4281
0
4282
0
  // According to spec, the API should return an empty array if maxWidth was provided
4283
0
  // but is less than or equal to zero or equal to NaN.
4284
0
  if (aMaxWidth.WasPassed() && (aMaxWidth.Value() <= 0 || IsNaN(aMaxWidth.Value()))) {
4285
0
    textToDraw.Truncate();
4286
0
  }
4287
0
4288
0
  // for now, default to ltr if not in doc
4289
0
  bool isRTL = false;
4290
0
4291
0
  RefPtr<ComputedStyle> canvasStyle;
4292
0
  if (mCanvasElement && mCanvasElement->IsInComposedDoc()) {
4293
0
    // try to find the closest context
4294
0
    canvasStyle =
4295
0
      nsComputedDOMStyle::GetComputedStyle(mCanvasElement, nullptr);
4296
0
    if (!canvasStyle) {
4297
0
      return NS_ERROR_FAILURE;
4298
0
    }
4299
0
4300
0
    isRTL = canvasStyle->StyleVisibility()->mDirection ==
4301
0
      NS_STYLE_DIRECTION_RTL;
4302
0
  } else {
4303
0
    isRTL = GET_BIDI_OPTION_DIRECTION(document->GetBidiOptions()) == IBMBIDI_TEXTDIRECTION_RTL;
4304
0
  }
4305
0
4306
0
  // This is only needed to know if we can know the drawing bounding box easily.
4307
0
  const bool doCalculateBounds = NeedToCalculateBounds();
4308
0
  if (presShell->IsDestroying()) {
4309
0
    return NS_ERROR_FAILURE;
4310
0
  }
4311
0
4312
0
  gfxFontGroup* currentFontStyle = GetCurrentFontStyle();
4313
0
  if (!currentFontStyle) {
4314
0
    return NS_ERROR_FAILURE;
4315
0
  }
4316
0
4317
0
  MOZ_ASSERT(!presShell->IsDestroying(),
4318
0
             "GetCurrentFontStyle() should have returned null if the presshell is being destroyed");
4319
0
4320
0
  nsPresContext* presContext = presShell->GetPresContext();
4321
0
4322
0
  // ensure user font set is up to date
4323
0
  currentFontStyle->SetUserFontSet(presContext->GetUserFontSet());
4324
0
4325
0
  if (currentFontStyle->GetStyle()->size == 0.0F) {
4326
0
    if (aWidth) {
4327
0
      *aWidth = 0;
4328
0
    }
4329
0
    return NS_OK;
4330
0
  }
4331
0
4332
0
  if (!IsFinite(aX) || !IsFinite(aY)) {
4333
0
    return NS_OK;
4334
0
  }
4335
0
4336
0
  CanvasBidiProcessor processor;
4337
0
4338
0
  // If we don't have a ComputedStyle, we can't set up vertical-text flags
4339
0
  // (for now, at least; perhaps we need new Canvas API to control this).
4340
0
  processor.mTextRunFlags = canvasStyle
4341
0
    ? nsLayoutUtils::GetTextRunFlagsForStyle(canvasStyle,
4342
0
                                             presContext,
4343
0
                                             canvasStyle->StyleFont(),
4344
0
                                             canvasStyle->StyleText(),
4345
0
                                             0)
4346
0
    : gfx::ShapedTextFlags();
4347
0
4348
0
  GetAppUnitsValues(&processor.mAppUnitsPerDevPixel, nullptr);
4349
0
  processor.mPt = gfx::Point(aX, aY);
4350
0
  processor.mDrawTarget =
4351
0
    gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
4352
0
4353
0
  // If we don't have a target then we don't have a transform. A target won't
4354
0
  // be needed in the case where we're measuring the text size. This allows
4355
0
  // to avoid creating a target if it's only being used to measure text sizes.
4356
0
  if (mTarget) {
4357
0
    processor.mDrawTarget->SetTransform(mTarget->GetTransform());
4358
0
  }
4359
0
  processor.mCtx = this;
4360
0
  processor.mOp = aOp;
4361
0
  processor.mBoundingBox = gfxRect(0, 0, 0, 0);
4362
0
  processor.mDoMeasureBoundingBox = doCalculateBounds || !mIsEntireFrameInvalid;
4363
0
  processor.mFontgrp = currentFontStyle;
4364
0
4365
0
  nscoord totalWidthCoord;
4366
0
4367
0
  // calls bidi algo twice since it needs the full text width and the
4368
0
  // bounding boxes before rendering anything
4369
0
  rv = nsBidiPresUtils::ProcessText(textToDraw.get(),
4370
0
                                    textToDraw.Length(),
4371
0
                                    isRTL ? NSBIDI_RTL : NSBIDI_LTR,
4372
0
                                    presShell->GetPresContext(),
4373
0
                                    processor,
4374
0
                                    nsBidiPresUtils::MODE_MEASURE,
4375
0
                                    nullptr,
4376
0
                                    0,
4377
0
                                    &totalWidthCoord,
4378
0
                                    &mBidiEngine);
4379
0
  if (NS_FAILED(rv)) {
4380
0
    return rv;
4381
0
  }
4382
0
4383
0
  float totalWidth = float(totalWidthCoord) / processor.mAppUnitsPerDevPixel;
4384
0
  if (aWidth) {
4385
0
    *aWidth = totalWidth;
4386
0
  }
4387
0
4388
0
  // if only measuring, don't need to do any more work
4389
0
  if (aOp==TextDrawOperation::MEASURE) {
4390
0
    return NS_OK;
4391
0
  }
4392
0
4393
0
  // offset pt.x based on text align
4394
0
  gfxFloat anchorX;
4395
0
4396
0
  const ContextState& state = CurrentState();
4397
0
  if (state.textAlign == TextAlign::CENTER) {
4398
0
    anchorX = .5;
4399
0
  } else if (state.textAlign == TextAlign::LEFT ||
4400
0
            (!isRTL && state.textAlign == TextAlign::START) ||
4401
0
            (isRTL && state.textAlign == TextAlign::END)) {
4402
0
    anchorX = 0;
4403
0
  } else {
4404
0
    anchorX = 1;
4405
0
  }
4406
0
4407
0
  processor.mPt.x -= anchorX * totalWidth;
4408
0
4409
0
  // offset pt.y (or pt.x, for vertical text) based on text baseline
4410
0
  processor.mFontgrp->UpdateUserFonts(); // ensure user font generation is current
4411
0
  const gfxFont::Metrics& fontMetrics =
4412
0
    processor.mFontgrp->GetFirstValidFont()->GetMetrics(gfxFont::eHorizontal);
4413
0
4414
0
  gfxFloat baselineAnchor;
4415
0
4416
0
  switch (state.textBaseline)
4417
0
  {
4418
0
  case TextBaseline::HANGING:
4419
0
      // fall through; best we can do with the information available
4420
0
  case TextBaseline::TOP:
4421
0
    baselineAnchor = fontMetrics.emAscent;
4422
0
    break;
4423
0
  case TextBaseline::MIDDLE:
4424
0
    baselineAnchor = (fontMetrics.emAscent - fontMetrics.emDescent) * .5f;
4425
0
    break;
4426
0
  case TextBaseline::IDEOGRAPHIC:
4427
0
    // fall through; best we can do with the information available
4428
0
  case TextBaseline::ALPHABETIC:
4429
0
    baselineAnchor = 0;
4430
0
    break;
4431
0
  case TextBaseline::BOTTOM:
4432
0
    baselineAnchor = -fontMetrics.emDescent;
4433
0
    break;
4434
0
  default:
4435
0
    MOZ_CRASH("GFX: unexpected TextBaseline");
4436
0
  }
4437
0
4438
0
  // We can't query the textRun directly, as it may not have been created yet;
4439
0
  // so instead we check the flags that will be used to initialize it.
4440
0
  gfx::ShapedTextFlags runOrientation =
4441
0
    (processor.mTextRunFlags & gfx::ShapedTextFlags::TEXT_ORIENT_MASK);
4442
0
  if (runOrientation != gfx::ShapedTextFlags::TEXT_ORIENT_HORIZONTAL) {
4443
0
    if (runOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED ||
4444
0
        runOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT) {
4445
0
      // Adjust to account for mTextRun being shaped using center baseline
4446
0
      // rather than alphabetic.
4447
0
      baselineAnchor -= (fontMetrics.emAscent - fontMetrics.emDescent) * .5f;
4448
0
    }
4449
0
    processor.mPt.x -= baselineAnchor;
4450
0
  } else {
4451
0
    processor.mPt.y += baselineAnchor;
4452
0
  }
4453
0
4454
0
  // correct bounding box to get it to be the correct size/position
4455
0
  processor.mBoundingBox.width = totalWidth;
4456
0
  processor.mBoundingBox.MoveBy(gfxPoint(processor.mPt.x, processor.mPt.y));
4457
0
4458
0
  processor.mPt.x *= processor.mAppUnitsPerDevPixel;
4459
0
  processor.mPt.y *= processor.mAppUnitsPerDevPixel;
4460
0
4461
0
  EnsureTarget();
4462
0
  if (!IsTargetValid()) {
4463
0
    return NS_ERROR_FAILURE;
4464
0
  }
4465
0
4466
0
  Matrix oldTransform = mTarget->GetTransform();
4467
0
  // if text is over aMaxWidth, then scale the text horizontally such that its
4468
0
  // width is precisely aMaxWidth
4469
0
  if (aMaxWidth.WasPassed() && aMaxWidth.Value() > 0 &&
4470
0
      totalWidth > aMaxWidth.Value()) {
4471
0
    Matrix newTransform = oldTransform;
4472
0
4473
0
    // Translate so that the anchor point is at 0,0, then scale and then
4474
0
    // translate back.
4475
0
    newTransform.PreTranslate(aX, 0);
4476
0
    newTransform.PreScale(aMaxWidth.Value() / totalWidth, 1);
4477
0
    newTransform.PreTranslate(-aX, 0);
4478
0
    /* we do this to avoid an ICE in the android compiler */
4479
0
    Matrix androidCompilerBug = newTransform;
4480
0
    mTarget->SetTransform(androidCompilerBug);
4481
0
  }
4482
0
4483
0
  // save the previous bounding box
4484
0
  gfxRect boundingBox = processor.mBoundingBox;
4485
0
4486
0
  // don't ever need to measure the bounding box twice
4487
0
  processor.mDoMeasureBoundingBox = false;
4488
0
4489
0
  rv = nsBidiPresUtils::ProcessText(textToDraw.get(),
4490
0
                                    textToDraw.Length(),
4491
0
                                    isRTL ? NSBIDI_RTL : NSBIDI_LTR,
4492
0
                                    presShell->GetPresContext(),
4493
0
                                    processor,
4494
0
                                    nsBidiPresUtils::MODE_DRAW,
4495
0
                                    nullptr,
4496
0
                                    0,
4497
0
                                    nullptr,
4498
0
                                    &mBidiEngine);
4499
0
4500
0
4501
0
  mTarget->SetTransform(oldTransform);
4502
0
4503
0
  if (aOp == CanvasRenderingContext2D::TextDrawOperation::FILL &&
4504
0
      !doCalculateBounds) {
4505
0
    RedrawUser(boundingBox);
4506
0
    return NS_OK;
4507
0
  }
4508
0
4509
0
  Redraw();
4510
0
  return NS_OK;
4511
0
}
4512
4513
gfxFontGroup*
4514
CanvasRenderingContext2D::GetCurrentFontStyle()
4515
0
{
4516
0
  // use lazy initilization for the font group since it's rather expensive
4517
0
  if (!CurrentState().fontGroup) {
4518
0
    ErrorResult err;
4519
0
    NS_NAMED_LITERAL_STRING(kDefaultFontStyle, "10px sans-serif");
4520
0
    static float kDefaultFontSize = 10.0;
4521
0
    nsCOMPtr<nsIPresShell> presShell = GetPresShell();
4522
0
    bool fontUpdated = SetFontInternal(kDefaultFontStyle, err);
4523
0
    if (err.Failed() || !fontUpdated) {
4524
0
      err.SuppressException();
4525
0
      gfxFontStyle style;
4526
0
      style.size = kDefaultFontSize;
4527
0
      gfxTextPerfMetrics* tp = nullptr;
4528
0
      if (presShell && !presShell->IsDestroying()) {
4529
0
        tp = presShell->GetPresContext()->GetTextPerfMetrics();
4530
0
      }
4531
0
      int32_t perDevPixel, perCSSPixel;
4532
0
      GetAppUnitsValues(&perDevPixel, &perCSSPixel);
4533
0
      gfxFloat devToCssSize = gfxFloat(perDevPixel) / gfxFloat(perCSSPixel);
4534
0
      CurrentState().fontGroup =
4535
0
        gfxPlatform::GetPlatform()->CreateFontGroup(FontFamilyList(eFamily_sans_serif),
4536
0
                                                    &style, tp,
4537
0
                                                    nullptr, devToCssSize);
4538
0
      if (CurrentState().fontGroup) {
4539
0
        CurrentState().font = kDefaultFontStyle;
4540
0
      } else {
4541
0
        NS_ERROR("Default canvas font is invalid");
4542
0
      }
4543
0
    }
4544
0
  }
4545
0
4546
0
  return CurrentState().fontGroup;
4547
0
}
4548
4549
//
4550
// line caps/joins
4551
//
4552
4553
void
4554
CanvasRenderingContext2D::SetLineCap(const nsAString& aLinecapStyle)
4555
0
{
4556
0
  CapStyle cap;
4557
0
4558
0
  if (aLinecapStyle.EqualsLiteral("butt")) {
4559
0
    cap = CapStyle::BUTT;
4560
0
  } else if (aLinecapStyle.EqualsLiteral("round")) {
4561
0
    cap = CapStyle::ROUND;
4562
0
  } else if (aLinecapStyle.EqualsLiteral("square")) {
4563
0
    cap = CapStyle::SQUARE;
4564
0
  } else {
4565
0
    // XXX ERRMSG we need to report an error to developers here! (bug 329026)
4566
0
    return;
4567
0
  }
4568
0
4569
0
  CurrentState().lineCap = cap;
4570
0
}
4571
4572
void
4573
CanvasRenderingContext2D::GetLineCap(nsAString& aLinecapStyle)
4574
{
4575
  switch (CurrentState().lineCap) {
4576
  case CapStyle::BUTT:
4577
    aLinecapStyle.AssignLiteral("butt");
4578
    break;
4579
  case CapStyle::ROUND:
4580
    aLinecapStyle.AssignLiteral("round");
4581
    break;
4582
  case CapStyle::SQUARE:
4583
    aLinecapStyle.AssignLiteral("square");
4584
    break;
4585
  }
4586
}
4587
4588
void
4589
CanvasRenderingContext2D::SetLineJoin(const nsAString& aLinejoinStyle)
4590
0
{
4591
0
  JoinStyle j;
4592
0
4593
0
  if (aLinejoinStyle.EqualsLiteral("round")) {
4594
0
    j = JoinStyle::ROUND;
4595
0
  } else if (aLinejoinStyle.EqualsLiteral("bevel")) {
4596
0
    j = JoinStyle::BEVEL;
4597
0
  } else if (aLinejoinStyle.EqualsLiteral("miter")) {
4598
0
    j = JoinStyle::MITER_OR_BEVEL;
4599
0
  } else {
4600
0
    // XXX ERRMSG we need to report an error to developers here! (bug 329026)
4601
0
    return;
4602
0
  }
4603
0
4604
0
  CurrentState().lineJoin = j;
4605
0
}
4606
4607
void
4608
CanvasRenderingContext2D::GetLineJoin(nsAString& aLinejoinStyle, ErrorResult& aError)
4609
{
4610
  switch (CurrentState().lineJoin) {
4611
  case JoinStyle::ROUND:
4612
    aLinejoinStyle.AssignLiteral("round");
4613
    break;
4614
  case JoinStyle::BEVEL:
4615
    aLinejoinStyle.AssignLiteral("bevel");
4616
    break;
4617
  case JoinStyle::MITER_OR_BEVEL:
4618
    aLinejoinStyle.AssignLiteral("miter");
4619
    break;
4620
  default:
4621
    aError.Throw(NS_ERROR_FAILURE);
4622
  }
4623
}
4624
4625
void
4626
CanvasRenderingContext2D::SetLineDash(const Sequence<double>& aSegments,
4627
                                      ErrorResult& aRv)
4628
0
{
4629
0
  nsTArray<mozilla::gfx::Float> dash;
4630
0
4631
0
  for (uint32_t x = 0; x < aSegments.Length(); x++) {
4632
0
    if (aSegments[x] < 0.0) {
4633
0
      // Pattern elements must be finite "numbers" >= 0, with "finite"
4634
0
      // taken care of by WebIDL
4635
0
      return;
4636
0
    }
4637
0
4638
0
    if (!dash.AppendElement(aSegments[x], fallible)) {
4639
0
      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
4640
0
      return;
4641
0
    }
4642
0
  }
4643
0
  if (aSegments.Length() % 2) { // If the number of elements is odd, concatenate again
4644
0
    for (uint32_t x = 0; x < aSegments.Length(); x++) {
4645
0
      if (!dash.AppendElement(aSegments[x], fallible)) {
4646
0
        aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
4647
0
        return;
4648
0
      }
4649
0
    }
4650
0
  }
4651
0
4652
0
  CurrentState().dash = std::move(dash);
4653
0
}
4654
4655
void
4656
0
CanvasRenderingContext2D::GetLineDash(nsTArray<double>& aSegments) const {
4657
0
  const nsTArray<mozilla::gfx::Float>& dash = CurrentState().dash;
4658
0
  aSegments.Clear();
4659
0
4660
0
  for (uint32_t x = 0; x < dash.Length(); x++) {
4661
0
    aSegments.AppendElement(dash[x]);
4662
0
  }
4663
0
}
4664
4665
void
4666
0
CanvasRenderingContext2D::SetLineDashOffset(double aOffset) {
4667
0
  CurrentState().dashOffset = aOffset;
4668
0
}
4669
4670
double
4671
0
CanvasRenderingContext2D::LineDashOffset() const {
4672
0
  return CurrentState().dashOffset;
4673
0
}
4674
4675
bool
4676
CanvasRenderingContext2D::IsPointInPath(JSContext* aCx, double aX, double aY,
4677
                                        const CanvasWindingRule& aWinding,
4678
                                        nsIPrincipal& aSubjectPrincipal)
4679
0
{
4680
0
  if (!FloatValidate(aX, aY)) {
4681
0
    return false;
4682
0
  }
4683
0
4684
0
  // Check for site-specific permission and return false if no permission.
4685
0
  if (mCanvasElement) {
4686
0
    nsCOMPtr<nsIDocument> ownerDoc = mCanvasElement->OwnerDoc();
4687
0
    if (!CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx, aSubjectPrincipal)) {
4688
0
      return false;
4689
0
    }
4690
0
  }
4691
0
4692
0
  EnsureUserSpacePath(aWinding);
4693
0
  if (!mPath) {
4694
0
    return false;
4695
0
  }
4696
0
4697
0
  if (mPathTransformWillUpdate) {
4698
0
    return mPath->ContainsPoint(Point(aX, aY), mPathToDS);
4699
0
  }
4700
0
4701
0
  return mPath->ContainsPoint(Point(aX, aY), mTarget->GetTransform());
4702
0
}
4703
4704
bool
4705
CanvasRenderingContext2D::IsPointInPath(JSContext* aCx, const CanvasPath& aPath,
4706
                                        double aX, double aY,
4707
                                        const CanvasWindingRule& aWinding,
4708
                                        nsIPrincipal&)
4709
0
{
4710
0
  if (!FloatValidate(aX, aY)) {
4711
0
    return false;
4712
0
  }
4713
0
4714
0
  EnsureTarget();
4715
0
  if (!IsTargetValid()) {
4716
0
    return false;
4717
0
  }
4718
0
4719
0
  RefPtr<gfx::Path> tempPath = aPath.GetPath(aWinding, mTarget);
4720
0
4721
0
  return tempPath->ContainsPoint(Point(aX, aY), mTarget->GetTransform());
4722
0
}
4723
4724
bool
4725
CanvasRenderingContext2D::IsPointInStroke(JSContext* aCx, double aX, double aY,
4726
                                          nsIPrincipal& aSubjectPrincipal)
4727
0
{
4728
0
  if (!FloatValidate(aX, aY)) {
4729
0
    return false;
4730
0
  }
4731
0
4732
0
  // Check for site-specific permission and return false if no permission.
4733
0
  if (mCanvasElement) {
4734
0
    nsCOMPtr<nsIDocument> ownerDoc = mCanvasElement->OwnerDoc();
4735
0
    if (!CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx, aSubjectPrincipal)) {
4736
0
      return false;
4737
0
    }
4738
0
  }
4739
0
4740
0
  EnsureUserSpacePath();
4741
0
  if (!mPath) {
4742
0
    return false;
4743
0
  }
4744
0
4745
0
  const ContextState &state = CurrentState();
4746
0
4747
0
  StrokeOptions strokeOptions(state.lineWidth,
4748
0
                              state.lineJoin,
4749
0
                              state.lineCap,
4750
0
                              state.miterLimit,
4751
0
                              state.dash.Length(),
4752
0
                              state.dash.Elements(),
4753
0
                              state.dashOffset);
4754
0
4755
0
  if (mPathTransformWillUpdate) {
4756
0
    return mPath->StrokeContainsPoint(strokeOptions, Point(aX, aY), mPathToDS);
4757
0
  }
4758
0
  return mPath->StrokeContainsPoint(strokeOptions, Point(aX, aY), mTarget->GetTransform());
4759
0
}
4760
4761
bool
4762
CanvasRenderingContext2D::IsPointInStroke(JSContext* aCx, const CanvasPath& aPath,
4763
                                          double aX, double aY, nsIPrincipal&)
4764
0
{
4765
0
  if (!FloatValidate(aX, aY)) {
4766
0
    return false;
4767
0
  }
4768
0
4769
0
  EnsureTarget();
4770
0
  if (!IsTargetValid()) {
4771
0
    return false;
4772
0
  }
4773
0
4774
0
  RefPtr<gfx::Path> tempPath = aPath.GetPath(CanvasWindingRule::Nonzero, mTarget);
4775
0
4776
0
  const ContextState &state = CurrentState();
4777
0
4778
0
  StrokeOptions strokeOptions(state.lineWidth,
4779
0
                              state.lineJoin,
4780
0
                              state.lineCap,
4781
0
                              state.miterLimit,
4782
0
                              state.dash.Length(),
4783
0
                              state.dash.Elements(),
4784
0
                              state.dashOffset);
4785
0
4786
0
  return tempPath->StrokeContainsPoint(strokeOptions, Point(aX, aY), mTarget->GetTransform());
4787
0
}
4788
4789
// Returns a surface that contains only the part needed to draw aSourceRect.
4790
// On entry, aSourceRect is relative to aSurface, and on return aSourceRect is
4791
// relative to the returned surface.
4792
static already_AddRefed<SourceSurface>
4793
ExtractSubrect(SourceSurface* aSurface, gfx::Rect* aSourceRect, DrawTarget* aTargetDT)
4794
0
{
4795
0
  gfx::Rect roundedOutSourceRect = *aSourceRect;
4796
0
  roundedOutSourceRect.RoundOut();
4797
0
  gfx::IntRect roundedOutSourceRectInt;
4798
0
  if (!roundedOutSourceRect.ToIntRect(&roundedOutSourceRectInt)) {
4799
0
    RefPtr<SourceSurface> surface(aSurface);
4800
0
    return surface.forget();
4801
0
  }
4802
0
4803
0
  RefPtr<DrawTarget> subrectDT =
4804
0
    aTargetDT->CreateSimilarDrawTarget(roundedOutSourceRectInt.Size(), SurfaceFormat::B8G8R8A8);
4805
0
4806
0
  if (!subrectDT) {
4807
0
    RefPtr<SourceSurface> surface(aSurface);
4808
0
    return surface.forget();
4809
0
  }
4810
0
4811
0
  *aSourceRect -= roundedOutSourceRect.TopLeft();
4812
0
4813
0
  subrectDT->CopySurface(aSurface, roundedOutSourceRectInt, IntPoint());
4814
0
  return subrectDT->Snapshot();
4815
0
}
4816
4817
//
4818
// image
4819
//
4820
4821
static void
4822
ClipImageDimension(double& aSourceCoord, double& aSourceSize, int32_t aImageSize,
4823
                   double& aDestCoord, double& aDestSize)
4824
0
{
4825
0
  double scale = aDestSize / aSourceSize;
4826
0
  if (aSourceCoord < 0.0) {
4827
0
    double destEnd = aDestCoord + aDestSize;
4828
0
    aDestCoord -= aSourceCoord * scale;
4829
0
    aDestSize = destEnd - aDestCoord;
4830
0
    aSourceSize += aSourceCoord;
4831
0
    aSourceCoord = 0.0;
4832
0
  }
4833
0
  double delta = aImageSize - (aSourceCoord + aSourceSize);
4834
0
  if (delta < 0.0) {
4835
0
    aDestSize += delta * scale;
4836
0
    aSourceSize = aImageSize - aSourceCoord;
4837
0
  }
4838
0
}
4839
4840
// Acts like nsLayoutUtils::SurfaceFromElement, but it'll attempt
4841
// to pull a SourceSurface from our cache. This allows us to avoid
4842
// reoptimizing surfaces if content and canvas backends are different.
4843
nsLayoutUtils::SurfaceFromElementResult
4844
CanvasRenderingContext2D::CachedSurfaceFromElement(Element* aElement)
4845
0
{
4846
0
  nsLayoutUtils::SurfaceFromElementResult res;
4847
0
  nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(aElement);
4848
0
  if (!imageLoader) {
4849
0
    return res;
4850
0
  }
4851
0
4852
0
  nsCOMPtr<imgIRequest> imgRequest;
4853
0
  imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
4854
0
                          getter_AddRefs(imgRequest));
4855
0
  if (!imgRequest) {
4856
0
    return res;
4857
0
  }
4858
0
4859
0
  uint32_t status = 0;
4860
0
  if (NS_FAILED(imgRequest->GetImageStatus(&status)) ||
4861
0
      !(status & imgIRequest::STATUS_LOAD_COMPLETE)) {
4862
0
    return res;
4863
0
  }
4864
0
4865
0
  nsCOMPtr<nsIPrincipal> principal;
4866
0
  if (NS_FAILED(imgRequest->GetImagePrincipal(getter_AddRefs(principal))) ||
4867
0
      !principal) {
4868
0
    return res;
4869
0
  }
4870
0
4871
0
  res.mSourceSurface =
4872
0
    CanvasImageCache::LookupAllCanvas(aElement, mIsSkiaGL);
4873
0
  if (!res.mSourceSurface) {
4874
0
    return res;
4875
0
  }
4876
0
4877
0
  int32_t corsmode = imgIRequest::CORS_NONE;
4878
0
  if (NS_SUCCEEDED(imgRequest->GetCORSMode(&corsmode))) {
4879
0
    res.mCORSUsed = corsmode != imgIRequest::CORS_NONE;
4880
0
  }
4881
0
4882
0
  res.mSize = res.mSourceSurface->GetSize();
4883
0
  res.mPrincipal = principal.forget();
4884
0
  res.mIsWriteOnly = false;
4885
0
  res.mImageRequest = imgRequest.forget();
4886
0
4887
0
  return res;
4888
0
}
4889
4890
// drawImage(in HTMLImageElement image, in float dx, in float dy);
4891
//   -- render image from 0,0 at dx,dy top-left coords
4892
// drawImage(in HTMLImageElement image, in float dx, in float dy, in float dw, in float dh);
4893
//   -- render image from 0,0 at dx,dy top-left coords clipping it to dw,dh
4894
// drawImage(in HTMLImageElement image, in float sx, in float sy, in float sw, in float sh, in float dx, in float dy, in float dw, in float dh);
4895
//   -- render the region defined by (sx,sy,sw,wh) in image-local space into the region (dx,dy,dw,dh) on the canvas
4896
4897
// If only dx and dy are passed in then optional_argc should be 0. If only
4898
// dx, dy, dw and dh are passed in then optional_argc should be 2. The only
4899
// other valid value for optional_argc is 6 if sx, sy, sw, sh, dx, dy, dw and dh
4900
// are all passed in.
4901
4902
void
4903
CanvasRenderingContext2D::DrawImage(const CanvasImageSource& aImage,
4904
                                    double aSx, double aSy, double aSw,
4905
                                    double aSh, double aDx, double aDy,
4906
                                    double aDw, double aDh,
4907
                                    uint8_t aOptional_argc,
4908
                                    ErrorResult& aError)
4909
0
{
4910
0
  if (mDrawObserver) {
4911
0
    mDrawObserver->DidDrawCall(CanvasDrawObserver::DrawCallType::DrawImage);
4912
0
  }
4913
0
4914
0
  MOZ_ASSERT(aOptional_argc == 0 || aOptional_argc == 2 || aOptional_argc == 6);
4915
0
4916
0
  if (!ValidateRect(aDx, aDy, aDw, aDh, true)) {
4917
0
    return;
4918
0
  }
4919
0
  if (aOptional_argc == 6) {
4920
0
    if (!ValidateRect(aSx, aSy, aSw, aSh, true)) {
4921
0
      return;
4922
0
    }
4923
0
  }
4924
0
4925
0
  RefPtr<SourceSurface> srcSurf;
4926
0
  gfx::IntSize imgSize;
4927
0
4928
0
  Element* element = nullptr;
4929
0
4930
0
  EnsureTarget();
4931
0
  if (!IsTargetValid()) {
4932
0
    return;
4933
0
  }
4934
0
4935
0
  if (aImage.IsHTMLCanvasElement()) {
4936
0
    HTMLCanvasElement* canvas = &aImage.GetAsHTMLCanvasElement();
4937
0
    element = canvas;
4938
0
    nsIntSize size = canvas->GetSize();
4939
0
    if (size.width == 0 || size.height == 0) {
4940
0
      aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
4941
0
      return;
4942
0
    }
4943
0
  } else if (aImage.IsImageBitmap()) {
4944
0
    ImageBitmap& imageBitmap = aImage.GetAsImageBitmap();
4945
0
    srcSurf = imageBitmap.PrepareForDrawTarget(mTarget);
4946
0
4947
0
    if (!srcSurf) {
4948
0
      return;
4949
0
    }
4950
0
4951
0
    imgSize = gfx::IntSize(imageBitmap.Width(), imageBitmap.Height());
4952
0
  }
4953
0
  else {
4954
0
    if (aImage.IsHTMLImageElement()) {
4955
0
      HTMLImageElement* img = &aImage.GetAsHTMLImageElement();
4956
0
      element = img;
4957
0
    } else if (aImage.IsSVGImageElement()) {
4958
0
      SVGImageElement* img = &aImage.GetAsSVGImageElement();
4959
0
      element = img;
4960
0
    } else {
4961
0
      HTMLVideoElement* video = &aImage.GetAsHTMLVideoElement();
4962
0
      video->MarkAsContentSource(mozilla::dom::HTMLVideoElement::CallerAPI::DRAW_IMAGE);
4963
0
      element = video;
4964
0
    }
4965
0
4966
0
    srcSurf =
4967
0
     CanvasImageCache::LookupCanvas(element, mCanvasElement, &imgSize, mIsSkiaGL);
4968
0
  }
4969
0
4970
0
  nsLayoutUtils::DirectDrawInfo drawInfo;
4971
0
4972
0
  if (!srcSurf) {
4973
0
    // The canvas spec says that drawImage should draw the first frame
4974
0
    // of animated images. We also don't want to rasterize vector images.
4975
0
    uint32_t sfeFlags = nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE |
4976
0
                        nsLayoutUtils::SFE_NO_RASTERIZING_VECTORS;
4977
0
4978
0
    nsLayoutUtils::SurfaceFromElementResult res =
4979
0
      CanvasRenderingContext2D::CachedSurfaceFromElement(element);
4980
0
4981
0
    if (!res.mSourceSurface) {
4982
0
      res = nsLayoutUtils::SurfaceFromElement(element, sfeFlags, mTarget);
4983
0
    }
4984
0
4985
0
    if (!res.mSourceSurface && !res.mDrawInfo.mImgContainer) {
4986
0
      // The spec says to silently do nothing in the following cases:
4987
0
      //   - The element is still loading.
4988
0
      //   - The image is bad, but it's not in the broken state (i.e., we could
4989
0
      //     decode the headers and get the size).
4990
0
      if (!res.mIsStillLoading && !res.mHasSize) {
4991
0
        aError.Throw(NS_ERROR_NOT_AVAILABLE);
4992
0
      }
4993
0
      return;
4994
0
    }
4995
0
4996
0
    imgSize = res.mSize;
4997
0
4998
0
    // Scale sw/sh based on aspect ratio
4999
0
    if (aImage.IsHTMLVideoElement()) {
5000
0
      HTMLVideoElement* video = &aImage.GetAsHTMLVideoElement();
5001
0
      int32_t displayWidth = video->VideoWidth();
5002
0
      int32_t displayHeight = video->VideoHeight();
5003
0
      if (displayWidth == 0 || displayHeight == 0) {
5004
0
        return;
5005
0
      }
5006
0
      aSw *= (double)imgSize.width / (double)displayWidth;
5007
0
      aSh *= (double)imgSize.height / (double)displayHeight;
5008
0
    }
5009
0
5010
0
    if (mCanvasElement) {
5011
0
      CanvasUtils::DoDrawImageSecurityCheck(mCanvasElement,
5012
0
                                            res.mPrincipal, res.mIsWriteOnly,
5013
0
                                            res.mCORSUsed);
5014
0
    }
5015
0
5016
0
    if (res.mSourceSurface) {
5017
0
      if (res.mImageRequest) {
5018
0
        CanvasImageCache::NotifyDrawImage(element, mCanvasElement, res.mSourceSurface, imgSize, mIsSkiaGL);
5019
0
      }
5020
0
      srcSurf = res.mSourceSurface;
5021
0
    } else {
5022
0
      drawInfo = res.mDrawInfo;
5023
0
    }
5024
0
  }
5025
0
5026
0
  if (aOptional_argc == 0) {
5027
0
    aSx = aSy = 0.0;
5028
0
    aDw = aSw = (double) imgSize.width;
5029
0
    aDh = aSh = (double) imgSize.height;
5030
0
  } else if (aOptional_argc == 2) {
5031
0
    aSx = aSy = 0.0;
5032
0
    aSw = (double) imgSize.width;
5033
0
    aSh = (double) imgSize.height;
5034
0
  }
5035
0
5036
0
  if (aSw == 0.0 || aSh == 0.0) {
5037
0
    aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
5038
0
    return;
5039
0
  }
5040
0
5041
0
  ClipImageDimension(aSx, aSw, imgSize.width, aDx, aDw);
5042
0
  ClipImageDimension(aSy, aSh, imgSize.height, aDy, aDh);
5043
0
5044
0
  if (aSw <= 0.0 || aSh <= 0.0 ||
5045
0
      aDw <= 0.0 || aDh <= 0.0) {
5046
0
    // source and/or destination are fully clipped, so nothing is painted
5047
0
    return;
5048
0
  }
5049
0
5050
0
  // Per spec, the smoothing setting applies only to scaling up a bitmap image.
5051
0
  // When down-scaling the user agent is free to choose whether or not to smooth
5052
0
  // the image. Nearest sampling when down-scaling is rarely desirable and
5053
0
  // smoothing when down-scaling matches chromium's behavior.
5054
0
  // If any dimension is up-scaled, we consider the image as being up-scaled.
5055
0
  auto scale = mTarget->GetTransform().ScaleFactors(true);
5056
0
  bool isDownScale = aDw * Abs(scale.width) < aSw && aDh * Abs(scale.height) < aSh;
5057
0
5058
0
  SamplingFilter samplingFilter;
5059
0
  AntialiasMode antialiasMode;
5060
0
5061
0
  if (CurrentState().imageSmoothingEnabled || isDownScale) {
5062
0
    samplingFilter = gfx::SamplingFilter::LINEAR;
5063
0
    antialiasMode = AntialiasMode::DEFAULT;
5064
0
  } else {
5065
0
    samplingFilter = gfx::SamplingFilter::POINT;
5066
0
    antialiasMode = AntialiasMode::NONE;
5067
0
  }
5068
0
5069
0
  const bool needBounds = NeedToCalculateBounds();
5070
0
  if (!IsTargetValid()) {
5071
0
    return;
5072
0
  }
5073
0
  gfx::Rect bounds;
5074
0
  if (needBounds) {
5075
0
    bounds = gfx::Rect(aDx, aDy, aDw, aDh);
5076
0
    bounds = mTarget->GetTransform().TransformBounds(bounds);
5077
0
  }
5078
0
5079
0
  if (!IsTargetValid()) {
5080
0
    aError.Throw(NS_ERROR_FAILURE);
5081
0
    return;
5082
0
  }
5083
0
5084
0
  if (srcSurf) {
5085
0
    gfx::Rect sourceRect(aSx, aSy, aSw, aSh);
5086
0
    if (element == mCanvasElement) {
5087
0
      // srcSurf is a snapshot of mTarget. If we draw to mTarget now, we'll
5088
0
      // trigger a COW copy of the whole canvas into srcSurf. That's a huge
5089
0
      // waste if sourceRect doesn't cover the whole canvas.
5090
0
      // We avoid copying the whole canvas by manually copying just the part
5091
0
      // that we need.
5092
0
      srcSurf = ExtractSubrect(srcSurf, &sourceRect, mTarget);
5093
0
    }
5094
0
5095
0
    AdjustedTarget tempTarget(this, bounds.IsEmpty() ? nullptr : &bounds);
5096
0
    if (!tempTarget) {
5097
0
      gfxDevCrash(LogReason::InvalidDrawTarget) << "Invalid adjusted target in Canvas2D " << gfx::hexa((DrawTarget*)mTarget) << ", " << NeedToDrawShadow() << NeedToApplyFilter();
5098
0
      return;
5099
0
    }
5100
0
5101
0
    auto op = UsedOperation();
5102
0
    if (!IsTargetValid() || !tempTarget) {
5103
0
      return;
5104
0
    }
5105
0
    tempTarget->DrawSurface(srcSurf,
5106
0
                  gfx::Rect(aDx, aDy, aDw, aDh),
5107
0
                  sourceRect,
5108
0
                  DrawSurfaceOptions(samplingFilter, SamplingBounds::UNBOUNDED),
5109
0
                  DrawOptions(CurrentState().globalAlpha, op, antialiasMode));
5110
0
  } else {
5111
0
    DrawDirectlyToCanvas(drawInfo, &bounds,
5112
0
                         gfx::Rect(aDx, aDy, aDw, aDh),
5113
0
                         gfx::Rect(aSx, aSy, aSw, aSh),
5114
0
                         imgSize);
5115
0
  }
5116
0
5117
0
  RedrawUser(gfxRect(aDx, aDy, aDw, aDh));
5118
0
}
5119
5120
void
5121
CanvasRenderingContext2D::DrawDirectlyToCanvas(
5122
                          const nsLayoutUtils::DirectDrawInfo& aImage,
5123
                          gfx::Rect* aBounds,
5124
                          gfx::Rect aDest,
5125
                          gfx::Rect aSrc,
5126
                          gfx::IntSize aImgSize)
5127
0
{
5128
0
  MOZ_ASSERT(aSrc.width > 0 && aSrc.height > 0,
5129
0
             "Need positive source width and height");
5130
0
5131
0
  AdjustedTarget tempTarget(this, aBounds->IsEmpty() ? nullptr: aBounds);
5132
0
  if (!tempTarget) {
5133
0
    return;
5134
0
  }
5135
0
5136
0
  // Get any existing transforms on the context, including transformations used
5137
0
  // for context shadow.
5138
0
  Matrix matrix = tempTarget->GetTransform();
5139
0
  gfxMatrix contextMatrix = ThebesMatrix(matrix);
5140
0
  gfxSize contextScale(contextMatrix.ScaleFactors(true));
5141
0
5142
0
  // Scale the dest rect to include the context scale.
5143
0
  aDest.Scale(contextScale.width, contextScale.height);
5144
0
5145
0
  // Scale the image size to the dest rect, and adjust the source rect to match.
5146
0
  gfxSize scale(aDest.width / aSrc.width, aDest.height / aSrc.height);
5147
0
  IntSize scaledImageSize = IntSize::Ceil(aImgSize.width * scale.width,
5148
0
                                          aImgSize.height * scale.height);
5149
0
  aSrc.Scale(scale.width, scale.height);
5150
0
5151
0
  // We're wrapping tempTarget's (our) DrawTarget here, so we need to restore
5152
0
  // the matrix even though this is a temp gfxContext.
5153
0
  AutoRestoreTransform autoRestoreTransform(mTarget);
5154
0
5155
0
  RefPtr<gfxContext> context = gfxContext::CreateOrNull(tempTarget);
5156
0
  if (!context) {
5157
0
    gfxDevCrash(LogReason::InvalidContext) << "Canvas context problem";
5158
0
    return;
5159
0
  }
5160
0
  context->SetMatrixDouble(contextMatrix.
5161
0
                           PreScale(1.0 / contextScale.width,
5162
0
                                    1.0 / contextScale.height).
5163
0
                           PreTranslate(aDest.x - aSrc.x, aDest.y - aSrc.y));
5164
0
5165
0
  // FLAG_CLAMP is added for increased performance, since we never tile here.
5166
0
  uint32_t modifiedFlags = aImage.mDrawingFlags | imgIContainer::FLAG_CLAMP;
5167
0
5168
0
  CSSIntSize sz(scaledImageSize.width, scaledImageSize.height); // XXX hmm is scaledImageSize really in CSS pixels?
5169
0
  SVGImageContext svgContext(Some(sz));
5170
0
5171
0
  auto result = aImage.mImgContainer->
5172
0
    Draw(context, scaledImageSize,
5173
0
         ImageRegion::Create(gfxRect(aSrc.x, aSrc.y, aSrc.width, aSrc.height)),
5174
0
         aImage.mWhichFrame, SamplingFilter::GOOD, Some(svgContext), modifiedFlags, CurrentState().globalAlpha);
5175
0
5176
0
  if (result != ImgDrawResult::SUCCESS) {
5177
0
    NS_WARNING("imgIContainer::Draw failed");
5178
0
  }
5179
0
}
5180
5181
void
5182
CanvasRenderingContext2D::SetGlobalCompositeOperation(const nsAString& aOp,
5183
                                                      ErrorResult& aError)
5184
0
{
5185
0
  CompositionOp comp_op;
5186
0
5187
0
#define CANVAS_OP_TO_GFX_OP(cvsop, op2d) \
5188
0
  if (aOp.EqualsLiteral(cvsop))   \
5189
0
    comp_op = CompositionOp::OP_##op2d;
5190
0
5191
0
  CANVAS_OP_TO_GFX_OP("copy", SOURCE)
5192
0
  else CANVAS_OP_TO_GFX_OP("source-atop", ATOP)
5193
0
  else CANVAS_OP_TO_GFX_OP("source-in", IN)
5194
0
  else CANVAS_OP_TO_GFX_OP("source-out", OUT)
5195
0
  else CANVAS_OP_TO_GFX_OP("source-over", OVER)
5196
0
  else CANVAS_OP_TO_GFX_OP("destination-in", DEST_IN)
5197
0
  else CANVAS_OP_TO_GFX_OP("destination-out", DEST_OUT)
5198
0
  else CANVAS_OP_TO_GFX_OP("destination-over", DEST_OVER)
5199
0
  else CANVAS_OP_TO_GFX_OP("destination-atop", DEST_ATOP)
5200
0
  else CANVAS_OP_TO_GFX_OP("lighter", ADD)
5201
0
  else CANVAS_OP_TO_GFX_OP("xor", XOR)
5202
0
  else CANVAS_OP_TO_GFX_OP("multiply", MULTIPLY)
5203
0
  else CANVAS_OP_TO_GFX_OP("screen", SCREEN)
5204
0
  else CANVAS_OP_TO_GFX_OP("overlay", OVERLAY)
5205
0
  else CANVAS_OP_TO_GFX_OP("darken", DARKEN)
5206
0
  else CANVAS_OP_TO_GFX_OP("lighten", LIGHTEN)
5207
0
  else CANVAS_OP_TO_GFX_OP("color-dodge", COLOR_DODGE)
5208
0
  else CANVAS_OP_TO_GFX_OP("color-burn", COLOR_BURN)
5209
0
  else CANVAS_OP_TO_GFX_OP("hard-light", HARD_LIGHT)
5210
0
  else CANVAS_OP_TO_GFX_OP("soft-light", SOFT_LIGHT)
5211
0
  else CANVAS_OP_TO_GFX_OP("difference", DIFFERENCE)
5212
0
  else CANVAS_OP_TO_GFX_OP("exclusion", EXCLUSION)
5213
0
  else CANVAS_OP_TO_GFX_OP("hue", HUE)
5214
0
  else CANVAS_OP_TO_GFX_OP("saturation", SATURATION)
5215
0
  else CANVAS_OP_TO_GFX_OP("color", COLOR)
5216
0
  else CANVAS_OP_TO_GFX_OP("luminosity", LUMINOSITY)
5217
0
  // XXX ERRMSG we need to report an error to developers here! (bug 329026)
5218
0
  else return;
5219
0
5220
0
#undef CANVAS_OP_TO_GFX_OP
5221
0
  CurrentState().op = comp_op;
5222
0
}
5223
5224
void
5225
CanvasRenderingContext2D::GetGlobalCompositeOperation(nsAString& aOp,
5226
                                                      ErrorResult& aError)
5227
0
{
5228
0
  CompositionOp comp_op = CurrentState().op;
5229
0
5230
0
#define CANVAS_OP_TO_GFX_OP(cvsop, op2d) \
5231
0
  if (comp_op == CompositionOp::OP_##op2d) \
5232
0
    aOp.AssignLiteral(cvsop);
5233
0
5234
0
  CANVAS_OP_TO_GFX_OP("copy", SOURCE)
5235
0
  else CANVAS_OP_TO_GFX_OP("destination-atop", DEST_ATOP)
5236
0
  else CANVAS_OP_TO_GFX_OP("destination-in", DEST_IN)
5237
0
  else CANVAS_OP_TO_GFX_OP("destination-out", DEST_OUT)
5238
0
  else CANVAS_OP_TO_GFX_OP("destination-over", DEST_OVER)
5239
0
  else CANVAS_OP_TO_GFX_OP("lighter", ADD)
5240
0
  else CANVAS_OP_TO_GFX_OP("source-atop", ATOP)
5241
0
  else CANVAS_OP_TO_GFX_OP("source-in", IN)
5242
0
  else CANVAS_OP_TO_GFX_OP("source-out", OUT)
5243
0
  else CANVAS_OP_TO_GFX_OP("source-over", OVER)
5244
0
  else CANVAS_OP_TO_GFX_OP("xor", XOR)
5245
0
  else CANVAS_OP_TO_GFX_OP("multiply", MULTIPLY)
5246
0
  else CANVAS_OP_TO_GFX_OP("screen", SCREEN)
5247
0
  else CANVAS_OP_TO_GFX_OP("overlay", OVERLAY)
5248
0
  else CANVAS_OP_TO_GFX_OP("darken", DARKEN)
5249
0
  else CANVAS_OP_TO_GFX_OP("lighten", LIGHTEN)
5250
0
  else CANVAS_OP_TO_GFX_OP("color-dodge", COLOR_DODGE)
5251
0
  else CANVAS_OP_TO_GFX_OP("color-burn", COLOR_BURN)
5252
0
  else CANVAS_OP_TO_GFX_OP("hard-light", HARD_LIGHT)
5253
0
  else CANVAS_OP_TO_GFX_OP("soft-light", SOFT_LIGHT)
5254
0
  else CANVAS_OP_TO_GFX_OP("difference", DIFFERENCE)
5255
0
  else CANVAS_OP_TO_GFX_OP("exclusion", EXCLUSION)
5256
0
  else CANVAS_OP_TO_GFX_OP("hue", HUE)
5257
0
  else CANVAS_OP_TO_GFX_OP("saturation", SATURATION)
5258
0
  else CANVAS_OP_TO_GFX_OP("color", COLOR)
5259
0
  else CANVAS_OP_TO_GFX_OP("luminosity", LUMINOSITY)
5260
0
  else {
5261
0
    aError.Throw(NS_ERROR_FAILURE);
5262
0
  }
5263
0
5264
0
#undef CANVAS_OP_TO_GFX_OP
5265
0
}
5266
5267
void
5268
CanvasRenderingContext2D::DrawWindow(nsGlobalWindowInner& aWindow, double aX,
5269
                                     double aY, double aW, double aH,
5270
                                     const nsAString& aBgColor,
5271
                                     uint32_t aFlags, ErrorResult& aError)
5272
0
{
5273
0
  if (int32_t(aW) == 0 || int32_t(aH) == 0) {
5274
0
    return;
5275
0
  }
5276
0
5277
0
  // protect against too-large surfaces that will cause allocation
5278
0
  // or overflow issues
5279
0
  if (!Factory::CheckSurfaceSize(IntSize(int32_t(aW), int32_t(aH)), 0xffff)) {
5280
0
    aError.Throw(NS_ERROR_FAILURE);
5281
0
    return;
5282
0
  }
5283
0
5284
0
  // Flush layout updates
5285
0
  if (!(aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_DO_NOT_FLUSH)) {
5286
0
    nsContentUtils::FlushLayoutForTree(aWindow.AsInner()->GetOuterWindow());
5287
0
  }
5288
0
5289
0
  CompositionOp op = UsedOperation();
5290
0
  bool discardContent = GlobalAlpha() == 1.0f
5291
0
    && (op == CompositionOp::OP_OVER || op == CompositionOp::OP_SOURCE);
5292
0
  const gfx::Rect drawRect(aX, aY, aW, aH);
5293
0
  EnsureTarget(discardContent ? &drawRect : nullptr);
5294
0
  if (!IsTargetValid()) {
5295
0
    return;
5296
0
  }
5297
0
5298
0
  RefPtr<nsPresContext> presContext;
5299
0
  nsIDocShell* docshell = aWindow.GetDocShell();
5300
0
  if (docshell) {
5301
0
    docshell->GetPresContext(getter_AddRefs(presContext));
5302
0
  }
5303
0
  if (!presContext) {
5304
0
    aError.Throw(NS_ERROR_FAILURE);
5305
0
    return;
5306
0
  }
5307
0
5308
0
  nscolor backgroundColor;
5309
0
  if (!ParseColor(aBgColor, &backgroundColor)) {
5310
0
    aError.Throw(NS_ERROR_FAILURE);
5311
0
    return;
5312
0
  }
5313
0
5314
0
  nsRect r(nsPresContext::CSSPixelsToAppUnits((float)aX),
5315
0
           nsPresContext::CSSPixelsToAppUnits((float)aY),
5316
0
           nsPresContext::CSSPixelsToAppUnits((float)aW),
5317
0
           nsPresContext::CSSPixelsToAppUnits((float)aH));
5318
0
  uint32_t renderDocFlags = (nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING |
5319
0
                             nsIPresShell::RENDER_DOCUMENT_RELATIVE);
5320
0
  if (aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_DRAW_CARET) {
5321
0
    renderDocFlags |= nsIPresShell::RENDER_CARET;
5322
0
  }
5323
0
  if (aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_DRAW_VIEW) {
5324
0
    renderDocFlags &= ~(nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING |
5325
0
                        nsIPresShell::RENDER_DOCUMENT_RELATIVE);
5326
0
  }
5327
0
  if (aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_USE_WIDGET_LAYERS) {
5328
0
    renderDocFlags |= nsIPresShell::RENDER_USE_WIDGET_LAYERS;
5329
0
  }
5330
0
  if (aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_ASYNC_DECODE_IMAGES) {
5331
0
    renderDocFlags |= nsIPresShell::RENDER_ASYNC_DECODE_IMAGES;
5332
0
  }
5333
0
  if (aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_DO_NOT_FLUSH) {
5334
0
    renderDocFlags |= nsIPresShell::RENDER_DRAWWINDOW_NOT_FLUSHING;
5335
0
  }
5336
0
5337
0
  // gfxContext-over-Azure may modify the DrawTarget's transform, so
5338
0
  // save and restore it
5339
0
  Matrix matrix = mTarget->GetTransform();
5340
0
  double sw = matrix._11 * aW;
5341
0
  double sh = matrix._22 * aH;
5342
0
  if (!sw || !sh) {
5343
0
    return;
5344
0
  }
5345
0
5346
0
  RefPtr<gfxContext> thebes;
5347
0
  RefPtr<DrawTarget> drawDT;
5348
0
  // Rendering directly is faster and can be done if mTarget supports Azure
5349
0
  // and does not need alpha blending.
5350
0
  // Since the pre-transaction callback calls ReturnTarget, we can't have a
5351
0
  // gfxContext wrapped around it when using a shared buffer provider because
5352
0
  // the DrawTarget's shared buffer may be unmapped in ReturnTarget.
5353
0
  op = CompositionOp::OP_ADD;
5354
0
  if (gfxPlatform::GetPlatform()->SupportsAzureContentForDrawTarget(mTarget) &&
5355
0
      GlobalAlpha() == 1.0f) {
5356
0
    op = UsedOperation();
5357
0
    if (!IsTargetValid()) {
5358
0
      aError.Throw(NS_ERROR_FAILURE);
5359
0
      return;
5360
0
    }
5361
0
  }
5362
0
  if (op == CompositionOp::OP_OVER &&
5363
0
      (!mBufferProvider ||
5364
0
       (mBufferProvider->GetType() != LayersBackend::LAYERS_CLIENT &&
5365
0
        mBufferProvider->GetType() != LayersBackend::LAYERS_WR)))
5366
0
  {
5367
0
    thebes = gfxContext::CreateOrNull(mTarget);
5368
0
    MOZ_ASSERT(thebes); // already checked the draw target above
5369
0
                        // (in SupportsAzureContentForDrawTarget)
5370
0
    thebes->SetMatrix(matrix);
5371
0
  } else {
5372
0
    IntSize dtSize = IntSize::Ceil(sw, sh);
5373
0
    if (!Factory::AllowedSurfaceSize(dtSize)) {
5374
0
      aError.Throw(NS_ERROR_FAILURE);
5375
0
      return;
5376
0
    }
5377
0
    drawDT =
5378
0
      gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(dtSize,
5379
0
                                                                   SurfaceFormat::B8G8R8A8);
5380
0
    if (!drawDT || !drawDT->IsValid()) {
5381
0
      aError.Throw(NS_ERROR_FAILURE);
5382
0
      return;
5383
0
    }
5384
0
5385
0
    thebes = gfxContext::CreateOrNull(drawDT);
5386
0
    MOZ_ASSERT(thebes); // alrady checked the draw target above
5387
0
    thebes->SetMatrix(Matrix::Scaling(matrix._11, matrix._22));
5388
0
  }
5389
0
5390
0
  nsCOMPtr<nsIPresShell> shell = presContext->PresShell();
5391
0
5392
0
  Unused << shell->RenderDocument(r, renderDocFlags, backgroundColor, thebes);
5393
0
  // If this canvas was contained in the drawn window, the pre-transaction callback
5394
0
  // may have returned its DT. If so, we must reacquire it here.
5395
0
  EnsureTarget(discardContent ? &drawRect : nullptr);
5396
0
5397
0
  if (drawDT) {
5398
0
    RefPtr<SourceSurface> snapshot = drawDT->Snapshot();
5399
0
    if (NS_WARN_IF(!snapshot)) {
5400
0
      aError.Throw(NS_ERROR_FAILURE);
5401
0
      return;
5402
0
    }
5403
0
    RefPtr<DataSourceSurface> data = snapshot->GetDataSurface();
5404
0
    if (!data || !Factory::AllowedSurfaceSize(data->GetSize())) {
5405
0
      gfxCriticalError() << "Unexpected invalid data source surface " <<
5406
0
        (data ? data->GetSize() : IntSize(0,0));
5407
0
      aError.Throw(NS_ERROR_FAILURE);
5408
0
      return;
5409
0
    }
5410
0
5411
0
    DataSourceSurface::MappedSurface rawData;
5412
0
    if (NS_WARN_IF(!data->Map(DataSourceSurface::READ, &rawData))) {
5413
0
        aError.Throw(NS_ERROR_FAILURE);
5414
0
        return;
5415
0
    }
5416
0
    RefPtr<SourceSurface> source =
5417
0
      mTarget->CreateSourceSurfaceFromData(rawData.mData,
5418
0
                                           data->GetSize(),
5419
0
                                           rawData.mStride,
5420
0
                                           data->GetFormat());
5421
0
    data->Unmap();
5422
0
5423
0
    if (!source) {
5424
0
      aError.Throw(NS_ERROR_FAILURE);
5425
0
      return;
5426
0
    }
5427
0
5428
0
    op = UsedOperation();
5429
0
    if (!IsTargetValid()) {
5430
0
      aError.Throw(NS_ERROR_FAILURE);
5431
0
      return;
5432
0
    }
5433
0
    gfx::Rect destRect(0, 0, aW, aH);
5434
0
    gfx::Rect sourceRect(0, 0, sw, sh);
5435
0
    mTarget->DrawSurface(source, destRect, sourceRect,
5436
0
                         DrawSurfaceOptions(gfx::SamplingFilter::POINT),
5437
0
                         DrawOptions(GlobalAlpha(), op,
5438
0
                                     AntialiasMode::NONE));
5439
0
  } else {
5440
0
    mTarget->SetTransform(matrix);
5441
0
  }
5442
0
5443
0
  // note that x and y are coordinates in the document that
5444
0
  // we're drawing; x and y are drawn to 0,0 in current user
5445
0
  // space.
5446
0
  RedrawUser(gfxRect(0, 0, aW, aH));
5447
0
}
5448
5449
//
5450
// device pixel getting/setting
5451
//
5452
5453
already_AddRefed<ImageData>
5454
CanvasRenderingContext2D::GetImageData(JSContext* aCx, double aSx,
5455
                                       double aSy, double aSw, double aSh,
5456
                                       nsIPrincipal& aSubjectPrincipal,
5457
                                       ErrorResult& aError)
5458
0
{
5459
0
  if (mDrawObserver) {
5460
0
    mDrawObserver->DidDrawCall(CanvasDrawObserver::DrawCallType::GetImageData);
5461
0
  }
5462
0
5463
0
  if (!mCanvasElement && !mDocShell) {
5464
0
    NS_ERROR("No canvas element and no docshell in GetImageData!!!");
5465
0
    aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
5466
0
    return nullptr;
5467
0
  }
5468
0
5469
0
  // Check only if we have a canvas element; if we were created with a docshell,
5470
0
  // then it's special internal use.
5471
0
  if (mCanvasElement && mCanvasElement->IsWriteOnly() &&
5472
0
      // We could ask bindings for the caller type, but they already hand us a
5473
0
      // JSContext, and we're at least _somewhat_ perf-sensitive (so may not
5474
0
      // want to compute the caller type in the common non-write-only case), so
5475
0
      // let's just use what we have.
5476
0
      !nsContentUtils::CallerHasPermission(aCx, nsGkAtoms::all_urlsPermission))
5477
0
  {
5478
0
    // XXX ERRMSG we need to report an error to developers here! (bug 329026)
5479
0
    aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
5480
0
    return nullptr;
5481
0
  }
5482
0
5483
0
  if (!IsFinite(aSx) || !IsFinite(aSy) ||
5484
0
      !IsFinite(aSw) || !IsFinite(aSh)) {
5485
0
    aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
5486
0
    return nullptr;
5487
0
  }
5488
0
5489
0
  if (!aSw || !aSh) {
5490
0
    aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
5491
0
    return nullptr;
5492
0
  }
5493
0
5494
0
  int32_t x = JS::ToInt32(aSx);
5495
0
  int32_t y = JS::ToInt32(aSy);
5496
0
  int32_t wi = JS::ToInt32(aSw);
5497
0
  int32_t hi = JS::ToInt32(aSh);
5498
0
5499
0
  // Handle negative width and height by flipping the rectangle over in the
5500
0
  // relevant direction.
5501
0
  uint32_t w, h;
5502
0
  if (aSw < 0) {
5503
0
    w = -wi;
5504
0
    x -= w;
5505
0
  } else {
5506
0
    w = wi;
5507
0
  }
5508
0
  if (aSh < 0) {
5509
0
    h = -hi;
5510
0
    y -= h;
5511
0
  } else {
5512
0
    h = hi;
5513
0
  }
5514
0
5515
0
  if (w == 0) {
5516
0
    w = 1;
5517
0
  }
5518
0
  if (h == 0) {
5519
0
    h = 1;
5520
0
  }
5521
0
5522
0
  JS::Rooted<JSObject*> array(aCx);
5523
0
  aError = GetImageDataArray(aCx, x, y, w, h, aSubjectPrincipal, array.address());
5524
0
  if (aError.Failed()) {
5525
0
    return nullptr;
5526
0
  }
5527
0
  MOZ_ASSERT(array);
5528
0
5529
0
  RefPtr<ImageData> imageData = new ImageData(w, h, *array);
5530
0
  return imageData.forget();
5531
0
}
5532
5533
nsresult
5534
CanvasRenderingContext2D::GetImageDataArray(JSContext* aCx,
5535
                                            int32_t aX,
5536
                                            int32_t aY,
5537
                                            uint32_t aWidth,
5538
                                            uint32_t aHeight,
5539
                                            nsIPrincipal& aSubjectPrincipal,
5540
                                            JSObject** aRetval)
5541
0
{
5542
0
  if (mDrawObserver) {
5543
0
    mDrawObserver->DidDrawCall(CanvasDrawObserver::DrawCallType::GetImageData);
5544
0
  }
5545
0
5546
0
  MOZ_ASSERT(aWidth && aHeight);
5547
0
5548
0
  CheckedInt<uint32_t> len = CheckedInt<uint32_t>(aWidth) * aHeight * 4;
5549
0
  if (!len.isValid()) {
5550
0
    return NS_ERROR_DOM_INDEX_SIZE_ERR;
5551
0
  }
5552
0
5553
0
  CheckedInt<int32_t> rightMost = CheckedInt<int32_t>(aX) + aWidth;
5554
0
  CheckedInt<int32_t> bottomMost = CheckedInt<int32_t>(aY) + aHeight;
5555
0
5556
0
  if (!rightMost.isValid() || !bottomMost.isValid()) {
5557
0
    return NS_ERROR_DOM_SYNTAX_ERR;
5558
0
  }
5559
0
5560
0
  JS::Rooted<JSObject*> darray(aCx, JS_NewUint8ClampedArray(aCx, len.value()));
5561
0
  if (!darray) {
5562
0
    return NS_ERROR_OUT_OF_MEMORY;
5563
0
  }
5564
0
5565
0
  if (mZero) {
5566
0
    *aRetval = darray;
5567
0
    return NS_OK;
5568
0
  }
5569
0
5570
0
  IntRect srcRect(0, 0, mWidth, mHeight);
5571
0
  IntRect destRect(aX, aY, aWidth, aHeight);
5572
0
  IntRect srcReadRect = srcRect.Intersect(destRect);
5573
0
  if (srcReadRect.IsEmpty()) {
5574
0
    *aRetval = darray;
5575
0
    return NS_OK;
5576
0
  }
5577
0
5578
0
  RefPtr<DataSourceSurface> readback;
5579
0
  DataSourceSurface::MappedSurface rawData;
5580
0
  RefPtr<SourceSurface> snapshot;
5581
0
  if (!mTarget && mBufferProvider) {
5582
0
    snapshot = mBufferProvider->BorrowSnapshot();
5583
0
  } else {
5584
0
    EnsureTarget();
5585
0
    if (!IsTargetValid()) {
5586
0
      return NS_ERROR_FAILURE;
5587
0
    }
5588
0
    snapshot = mTarget->Snapshot();
5589
0
  }
5590
0
5591
0
  if (snapshot) {
5592
0
    readback = snapshot->GetDataSurface();
5593
0
  }
5594
0
5595
0
  if (!mTarget && mBufferProvider) {
5596
0
    mBufferProvider->ReturnSnapshot(snapshot.forget());
5597
0
  }
5598
0
5599
0
  if (!readback || !readback->Map(DataSourceSurface::READ, &rawData)) {
5600
0
    return NS_ERROR_OUT_OF_MEMORY;
5601
0
  }
5602
0
5603
0
  IntRect dstWriteRect = srcReadRect;
5604
0
  dstWriteRect.MoveBy(-aX, -aY);
5605
0
5606
0
  // Check for site-specific permission.  This check is not needed if the
5607
0
  // canvas was created with a docshell (that is only done for special
5608
0
  // internal uses).
5609
0
  bool usePlaceholder = false;
5610
0
  if (mCanvasElement) {
5611
0
    nsCOMPtr<nsIDocument> ownerDoc = mCanvasElement->OwnerDoc();
5612
0
    usePlaceholder = !CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx, aSubjectPrincipal);
5613
0
  }
5614
0
5615
0
  do {
5616
0
    JS::AutoCheckCannotGC nogc;
5617
0
    bool isShared;
5618
0
    uint8_t* data = JS_GetUint8ClampedArrayData(darray, &isShared, nogc);
5619
0
    MOZ_ASSERT(!isShared);        // Should not happen, data was created above
5620
0
5621
0
    uint32_t srcStride = rawData.mStride;
5622
0
    uint8_t* src = rawData.mData + srcReadRect.y * srcStride + srcReadRect.x * 4;
5623
0
5624
0
    // Return all-white, opaque pixel data if no permission.
5625
0
    if (usePlaceholder) {
5626
0
      memset(data, 0xFF, len.value());
5627
0
      break;
5628
0
    }
5629
0
5630
0
    uint8_t* dst = data + dstWriteRect.y * (aWidth * 4) + dstWriteRect.x * 4;
5631
0
5632
0
    if (mOpaque) {
5633
0
      SwizzleData(src, srcStride, SurfaceFormat::X8R8G8B8_UINT32,
5634
0
                  dst, aWidth * 4, SurfaceFormat::R8G8B8A8,
5635
0
                  dstWriteRect.Size());
5636
0
    } else {
5637
0
      UnpremultiplyData(src, srcStride, SurfaceFormat::A8R8G8B8_UINT32,
5638
0
                        dst, aWidth * 4, SurfaceFormat::R8G8B8A8,
5639
0
                        dstWriteRect.Size());
5640
0
    }
5641
0
  } while (false);
5642
0
5643
0
  readback->Unmap();
5644
0
  *aRetval = darray;
5645
0
  return NS_OK;
5646
0
}
5647
5648
void
5649
CanvasRenderingContext2D::EnsureErrorTarget()
5650
0
{
5651
0
  if (sErrorTarget) {
5652
0
    return;
5653
0
  }
5654
0
5655
0
  RefPtr<DrawTarget> errorTarget = gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(IntSize(1, 1), SurfaceFormat::B8G8R8A8);
5656
0
  MOZ_ASSERT(errorTarget, "Failed to allocate the error target!");
5657
0
5658
0
  sErrorTarget = errorTarget;
5659
0
  NS_ADDREF(sErrorTarget);
5660
0
}
5661
5662
void
5663
CanvasRenderingContext2D::FillRuleChanged()
5664
0
{
5665
0
  if (mPath) {
5666
0
    mPathBuilder = mPath->CopyToBuilder(CurrentState().fillRule);
5667
0
    mPath = nullptr;
5668
0
  }
5669
0
}
5670
5671
void
5672
CanvasRenderingContext2D::PutImageData(ImageData& aImageData, double aDx,
5673
                                       double aDy, ErrorResult& aError)
5674
0
{
5675
0
  RootedSpiderMonkeyInterface<Uint8ClampedArray> arr(RootingCx());
5676
0
  DebugOnly<bool> inited = arr.Init(aImageData.GetDataObject());
5677
0
  MOZ_ASSERT(inited);
5678
0
5679
0
  aError = PutImageData_explicit(JS::ToInt32(aDx), JS::ToInt32(aDy),
5680
0
                                aImageData.Width(), aImageData.Height(),
5681
0
                                &arr, false, 0, 0, 0, 0);
5682
0
}
5683
5684
void
5685
CanvasRenderingContext2D::PutImageData(ImageData& aImageData, double aDx,
5686
                                       double aDy, double aDirtyX,
5687
                                       double aDirtyY, double aDirtyWidth,
5688
                                       double aDirtyHeight,
5689
                                       ErrorResult& aError)
5690
0
{
5691
0
  RootedSpiderMonkeyInterface<Uint8ClampedArray> arr(RootingCx());
5692
0
  DebugOnly<bool> inited = arr.Init(aImageData.GetDataObject());
5693
0
  MOZ_ASSERT(inited);
5694
0
5695
0
  aError = PutImageData_explicit(JS::ToInt32(aDx), JS::ToInt32(aDy),
5696
0
                                aImageData.Width(), aImageData.Height(),
5697
0
                                &arr, true,
5698
0
                                JS::ToInt32(aDirtyX),
5699
0
                                JS::ToInt32(aDirtyY),
5700
0
                                JS::ToInt32(aDirtyWidth),
5701
0
                                JS::ToInt32(aDirtyHeight));
5702
0
}
5703
5704
nsresult
5705
CanvasRenderingContext2D::PutImageData_explicit(int32_t aX, int32_t aY, uint32_t aW, uint32_t aH,
5706
                                                dom::Uint8ClampedArray* aArray,
5707
                                                bool aHasDirtyRect, int32_t aDirtyX, int32_t aDirtyY,
5708
                                                int32_t aDirtyWidth, int32_t aDirtyHeight)
5709
0
{
5710
0
  if (mDrawObserver) {
5711
0
    mDrawObserver->DidDrawCall(CanvasDrawObserver::DrawCallType::PutImageData);
5712
0
  }
5713
0
5714
0
  if (aW == 0 || aH == 0) {
5715
0
    return NS_ERROR_DOM_INVALID_STATE_ERR;
5716
0
  }
5717
0
5718
0
  IntRect dirtyRect;
5719
0
  IntRect imageDataRect(0, 0, aW, aH);
5720
0
5721
0
  if (aHasDirtyRect) {
5722
0
    // fix up negative dimensions
5723
0
    if (aDirtyWidth < 0) {
5724
0
      NS_ENSURE_TRUE(aDirtyWidth != INT_MIN, NS_ERROR_DOM_INDEX_SIZE_ERR);
5725
0
5726
0
      CheckedInt32 checkedDirtyX = CheckedInt32(aDirtyX) + aDirtyWidth;
5727
0
5728
0
      if (!checkedDirtyX.isValid())
5729
0
        return NS_ERROR_DOM_INDEX_SIZE_ERR;
5730
0
5731
0
      aDirtyX = checkedDirtyX.value();
5732
0
      aDirtyWidth = -aDirtyWidth;
5733
0
    }
5734
0
5735
0
    if (aDirtyHeight < 0) {
5736
0
      NS_ENSURE_TRUE(aDirtyHeight != INT_MIN, NS_ERROR_DOM_INDEX_SIZE_ERR);
5737
0
5738
0
      CheckedInt32 checkedDirtyY = CheckedInt32(aDirtyY) + aDirtyHeight;
5739
0
5740
0
      if (!checkedDirtyY.isValid())
5741
0
        return NS_ERROR_DOM_INDEX_SIZE_ERR;
5742
0
5743
0
      aDirtyY = checkedDirtyY.value();
5744
0
      aDirtyHeight = -aDirtyHeight;
5745
0
    }
5746
0
5747
0
    // bound the dirty rect within the imageData rectangle
5748
0
    dirtyRect = imageDataRect.Intersect(IntRect(aDirtyX, aDirtyY, aDirtyWidth, aDirtyHeight));
5749
0
5750
0
    if (dirtyRect.Width() <= 0 || dirtyRect.Height() <= 0)
5751
0
      return NS_OK;
5752
0
  } else {
5753
0
    dirtyRect = imageDataRect;
5754
0
  }
5755
0
5756
0
  dirtyRect.MoveBy(IntPoint(aX, aY));
5757
0
  dirtyRect = IntRect(0, 0, mWidth, mHeight).Intersect(dirtyRect);
5758
0
5759
0
  if (dirtyRect.Width() <= 0 || dirtyRect.Height() <= 0) {
5760
0
    return NS_OK;
5761
0
  }
5762
0
5763
0
  aArray->ComputeLengthAndData();
5764
0
5765
0
  uint32_t dataLen = aArray->Length();
5766
0
5767
0
  uint32_t len = aW * aH * 4;
5768
0
  if (dataLen != len) {
5769
0
    return NS_ERROR_DOM_INVALID_STATE_ERR;
5770
0
  }
5771
0
5772
0
  // The canvas spec says that the current path, transformation matrix, shadow attributes,
5773
0
  // global alpha, the clipping region, and global composition operator must not affect the
5774
0
  // getImageData() and putImageData() methods.
5775
0
  const gfx::Rect putRect(dirtyRect);
5776
0
  EnsureTarget(&putRect);
5777
0
5778
0
  if (!IsTargetValid()) {
5779
0
    return NS_ERROR_FAILURE;
5780
0
  }
5781
0
5782
0
  DataSourceSurface::MappedSurface map;
5783
0
  RefPtr<DataSourceSurface> sourceSurface;
5784
0
  uint8_t* lockedBits = nullptr;
5785
0
  uint8_t* dstData;
5786
0
  IntSize dstSize;
5787
0
  int32_t dstStride;
5788
0
  SurfaceFormat dstFormat;
5789
0
  if (mTarget->LockBits(&lockedBits, &dstSize, &dstStride, &dstFormat)) {
5790
0
    dstData = lockedBits + dirtyRect.y * dstStride + dirtyRect.x * 4;
5791
0
  } else {
5792
0
    sourceSurface =
5793
0
      Factory::CreateDataSourceSurface(dirtyRect.Size(),
5794
0
                                       SurfaceFormat::B8G8R8A8,
5795
0
                                       false);
5796
0
5797
0
    // In certain scenarios, requesting larger than 8k image fails.  Bug 803568
5798
0
    // covers the details of how to run into it, but the full detailed
5799
0
    // investigation hasn't been done to determine the underlying cause.  We
5800
0
    // will just handle the failure to allocate the surface to avoid a crash.
5801
0
    if (!sourceSurface) {
5802
0
      return NS_ERROR_FAILURE;
5803
0
    }
5804
0
    if (!sourceSurface->Map(DataSourceSurface::READ_WRITE, &map)) {
5805
0
      return NS_ERROR_FAILURE;
5806
0
    }
5807
0
5808
0
    dstData = map.mData;
5809
0
    if (!dstData) {
5810
0
      return NS_ERROR_OUT_OF_MEMORY;
5811
0
    }
5812
0
    dstStride = map.mStride;
5813
0
    dstFormat = sourceSurface->GetFormat();
5814
0
  }
5815
0
5816
0
  IntRect srcRect = dirtyRect - IntPoint(aX, aY);
5817
0
  uint8_t* srcData = aArray->Data() + srcRect.y * (aW * 4) + srcRect.x * 4;
5818
0
5819
0
  PremultiplyData(srcData, aW * 4, SurfaceFormat::R8G8B8A8,
5820
0
                  dstData, dstStride,
5821
0
                  mOpaque ? SurfaceFormat::X8R8G8B8_UINT32 : SurfaceFormat::A8R8G8B8_UINT32,
5822
0
                  dirtyRect.Size());
5823
0
5824
0
  if (lockedBits) {
5825
0
    mTarget->ReleaseBits(lockedBits);
5826
0
  } else if (sourceSurface) {
5827
0
    sourceSurface->Unmap();
5828
0
    mTarget->CopySurface(sourceSurface, dirtyRect - dirtyRect.TopLeft(), dirtyRect.TopLeft());
5829
0
  }
5830
0
5831
0
  Redraw(gfx::Rect(dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height));
5832
0
5833
0
  return NS_OK;
5834
0
}
5835
5836
static already_AddRefed<ImageData>
5837
CreateImageData(JSContext* aCx, CanvasRenderingContext2D* aContext,
5838
                uint32_t aW, uint32_t aH, ErrorResult& aError)
5839
0
{
5840
0
  if (aW == 0)
5841
0
      aW = 1;
5842
0
  if (aH == 0)
5843
0
      aH = 1;
5844
0
5845
0
  CheckedInt<uint32_t> len = CheckedInt<uint32_t>(aW) * aH * 4;
5846
0
  if (!len.isValid()) {
5847
0
    aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
5848
0
    return nullptr;
5849
0
  }
5850
0
5851
0
  // Create the fast typed array; it's initialized to 0 by default.
5852
0
  JSObject* darray = Uint8ClampedArray::Create(aCx, aContext, len.value());
5853
0
  if (!darray) {
5854
0
    aError.Throw(NS_ERROR_OUT_OF_MEMORY);
5855
0
    return nullptr;
5856
0
  }
5857
0
5858
0
  RefPtr<mozilla::dom::ImageData> imageData =
5859
0
    new mozilla::dom::ImageData(aW, aH, *darray);
5860
0
  return imageData.forget();
5861
0
}
5862
5863
already_AddRefed<ImageData>
5864
CanvasRenderingContext2D::CreateImageData(JSContext* aCx, double aSw,
5865
                                          double aSh, ErrorResult& aError)
5866
0
{
5867
0
  if (!aSw || !aSh) {
5868
0
    aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
5869
0
    return nullptr;
5870
0
  }
5871
0
5872
0
  int32_t wi = JS::ToInt32(aSw);
5873
0
  int32_t hi = JS::ToInt32(aSh);
5874
0
5875
0
  uint32_t w = Abs(wi);
5876
0
  uint32_t h = Abs(hi);
5877
0
  return mozilla::dom::CreateImageData(aCx, this, w, h, aError);
5878
0
}
5879
5880
already_AddRefed<ImageData>
5881
CanvasRenderingContext2D::CreateImageData(JSContext* aCx,
5882
                                          ImageData& aImagedata,
5883
                                          ErrorResult& aError)
5884
0
{
5885
0
  return mozilla::dom::CreateImageData(aCx, this, aImagedata.Width(),
5886
0
                                       aImagedata.Height(), aError);
5887
0
}
5888
5889
static uint8_t g2DContextLayerUserData;
5890
5891
5892
uint32_t
5893
CanvasRenderingContext2D::SkiaGLTex() const
5894
0
{
5895
0
  if (!mTarget) {
5896
0
    return 0;
5897
0
  }
5898
0
  MOZ_ASSERT(IsTargetValid());
5899
0
  return (uint32_t)(uintptr_t)mTarget->GetNativeSurface(NativeSurfaceType::OPENGL_TEXTURE);
5900
0
}
5901
5902
void CanvasRenderingContext2D::RemoveDrawObserver()
5903
0
{
5904
0
  if (mDrawObserver) {
5905
0
    delete mDrawObserver;
5906
0
    mDrawObserver = nullptr;
5907
0
  }
5908
0
}
5909
5910
already_AddRefed<Layer>
5911
CanvasRenderingContext2D::GetCanvasLayer(nsDisplayListBuilder* aBuilder,
5912
                                         Layer* aOldLayer,
5913
                                         LayerManager* aManager)
5914
0
{
5915
0
  if (mOpaque || mIsSkiaGL) {
5916
0
    // If we're opaque then make sure we have a surface so we paint black
5917
0
    // instead of transparent.
5918
0
    // If we're using SkiaGL, then SkiaGLTex() below needs the target to
5919
0
    // be accessible.
5920
0
    EnsureTarget();
5921
0
  }
5922
0
5923
0
  // Don't call EnsureTarget() ... if there isn't already a surface, then
5924
0
  // we have nothing to paint and there is no need to create a surface just
5925
0
  // to paint nothing. Also, EnsureTarget() can cause creation of a persistent
5926
0
  // layer manager which must NOT happen during a paint.
5927
0
  if (!mBufferProvider && !IsTargetValid()) {
5928
0
    // No DidTransactionCallback will be received, so mark the context clean
5929
0
    // now so future invalidations will be dispatched.
5930
0
    MarkContextClean();
5931
0
    return nullptr;
5932
0
  }
5933
0
5934
0
  if (!mResetLayer && aOldLayer) {
5935
0
    auto userData =
5936
0
      static_cast<CanvasRenderingContext2DUserData*>(
5937
0
        aOldLayer->GetUserData(&g2DContextLayerUserData));
5938
0
5939
0
    CanvasInitializeData data;
5940
0
5941
0
    if (mIsSkiaGL) {
5942
0
      GLuint skiaGLTex = SkiaGLTex();
5943
0
      if (skiaGLTex) {
5944
0
        SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue();
5945
0
        MOZ_ASSERT(glue);
5946
0
        data.mGLContext = glue->GetGLContext();
5947
0
        data.mFrontbufferGLTex = skiaGLTex;
5948
0
      }
5949
0
    }
5950
0
5951
0
    data.mBufferProvider = mBufferProvider;
5952
0
5953
0
    if (userData &&
5954
0
        userData->IsForContext(this) &&
5955
0
        static_cast<CanvasLayer*>(aOldLayer)->CreateOrGetCanvasRenderer()->IsDataValid(data)) {
5956
0
      RefPtr<Layer> ret = aOldLayer;
5957
0
      return ret.forget();
5958
0
    }
5959
0
  }
5960
0
5961
0
  RefPtr<CanvasLayer> canvasLayer = aManager->CreateCanvasLayer();
5962
0
  if (!canvasLayer) {
5963
0
    NS_WARNING("CreateCanvasLayer returned null!");
5964
0
    // No DidTransactionCallback will be received, so mark the context clean
5965
0
    // now so future invalidations will be dispatched.
5966
0
    MarkContextClean();
5967
0
    return nullptr;
5968
0
  }
5969
0
  CanvasRenderingContext2DUserData* userData = nullptr;
5970
0
  // Make the layer tell us whenever a transaction finishes (including
5971
0
  // the current transaction), so we can clear our invalidation state and
5972
0
  // start invalidating again. We need to do this for all layers since
5973
0
  // callers of DrawWindow may be expecting to receive normal invalidation
5974
0
  // notifications after this paint.
5975
0
5976
0
  // The layer will be destroyed when we tear down the presentation
5977
0
  // (at the latest), at which time this userData will be destroyed,
5978
0
  // releasing the reference to the element.
5979
0
  // The userData will receive DidTransactionCallbacks, which flush the
5980
0
  // the invalidation state to indicate that the canvas is up to date.
5981
0
  userData = new CanvasRenderingContext2DUserData(this);
5982
0
  canvasLayer->SetUserData(&g2DContextLayerUserData, userData);
5983
0
5984
0
  CanvasRenderer* canvasRenderer = canvasLayer->CreateOrGetCanvasRenderer();
5985
0
  InitializeCanvasRenderer(aBuilder, canvasRenderer);
5986
0
  uint32_t flags = mOpaque ? Layer::CONTENT_OPAQUE : 0;
5987
0
  canvasLayer->SetContentFlags(flags);
5988
0
5989
0
  mResetLayer = false;
5990
0
5991
0
  return canvasLayer.forget();
5992
0
}
5993
5994
bool
5995
CanvasRenderingContext2D::UpdateWebRenderCanvasData(nsDisplayListBuilder* aBuilder,
5996
                                                    WebRenderCanvasData* aCanvasData)
5997
0
{
5998
0
  if (mOpaque || mIsSkiaGL) {
5999
0
    // If we're opaque then make sure we have a surface so we paint black
6000
0
    // instead of transparent.
6001
0
    // If we're using SkiaGL, then SkiaGLTex() below needs the target to
6002
0
    // be accessible.
6003
0
    EnsureTarget();
6004
0
  }
6005
0
6006
0
  // Don't call EnsureTarget() ... if there isn't already a surface, then
6007
0
  // we have nothing to paint and there is no need to create a surface just
6008
0
  // to paint nothing. Also, EnsureTarget() can cause creation of a persistent
6009
0
  // layer manager which must NOT happen during a paint.
6010
0
  if (!mBufferProvider && !IsTargetValid()) {
6011
0
    // No DidTransactionCallback will be received, so mark the context clean
6012
0
    // now so future invalidations will be dispatched.
6013
0
    MarkContextClean();
6014
0
    // Clear CanvasRenderer of WebRenderCanvasData
6015
0
    aCanvasData->ClearCanvasRenderer();
6016
0
    return false;
6017
0
  }
6018
0
6019
0
  CanvasRenderer* renderer = aCanvasData->GetCanvasRenderer();
6020
0
6021
0
  if(!mResetLayer && renderer) {
6022
0
    CanvasInitializeData data;
6023
0
6024
0
    if (mIsSkiaGL) {
6025
0
      GLuint skiaGLTex = SkiaGLTex();
6026
0
      if (skiaGLTex) {
6027
0
        SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue();
6028
0
        MOZ_ASSERT(glue);
6029
0
        data.mGLContext = glue->GetGLContext();
6030
0
        data.mFrontbufferGLTex = skiaGLTex;
6031
0
      }
6032
0
    }
6033
0
    data.mBufferProvider = mBufferProvider;
6034
0
6035
0
    if (renderer->IsDataValid(data)) {
6036
0
      return true;
6037
0
    }
6038
0
  }
6039
0
6040
0
  renderer = aCanvasData->CreateCanvasRenderer();
6041
0
  if (!InitializeCanvasRenderer(aBuilder, renderer)) {
6042
0
    // Clear CanvasRenderer of WebRenderCanvasData
6043
0
    aCanvasData->ClearCanvasRenderer();
6044
0
    return false;
6045
0
  }
6046
0
6047
0
  MOZ_ASSERT(renderer);
6048
0
  mResetLayer = false;
6049
0
  return true;
6050
0
}
6051
6052
bool
6053
CanvasRenderingContext2D::InitializeCanvasRenderer(nsDisplayListBuilder* aBuilder,
6054
                                                   CanvasRenderer* aRenderer)
6055
0
{
6056
0
  CanvasInitializeData data;
6057
0
  data.mSize = GetSize();
6058
0
  data.mHasAlpha = !mOpaque;
6059
0
  data.mPreTransCallback = CanvasRenderingContext2DUserData::PreTransactionCallback;
6060
0
  data.mPreTransCallbackData = this;
6061
0
  data.mDidTransCallback = CanvasRenderingContext2DUserData::DidTransactionCallback;
6062
0
  data.mDidTransCallbackData = this;
6063
0
6064
0
  if (!mBufferProvider) {
6065
0
    // Force the creation of a buffer provider.
6066
0
    EnsureTarget();
6067
0
    ReturnTarget();
6068
0
    if (!mBufferProvider) {
6069
0
      MarkContextClean();
6070
0
      return false;
6071
0
    }
6072
0
  }
6073
0
6074
0
  if (mIsSkiaGL) {
6075
0
      GLuint skiaGLTex = SkiaGLTex();
6076
0
      if (skiaGLTex) {
6077
0
        SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue();
6078
0
        MOZ_ASSERT(glue);
6079
0
        data.mGLContext = glue->GetGLContext();
6080
0
        data.mFrontbufferGLTex = skiaGLTex;
6081
0
      }
6082
0
  }
6083
0
6084
0
  data.mBufferProvider = mBufferProvider;
6085
0
6086
0
  aRenderer->Initialize(data);
6087
0
  aRenderer->SetDirty();
6088
0
  return true;
6089
0
}
6090
6091
void
6092
CanvasRenderingContext2D::MarkContextClean()
6093
0
{
6094
0
  if (mInvalidateCount > 0) {
6095
0
    mPredictManyRedrawCalls = mInvalidateCount > kCanvasMaxInvalidateCount;
6096
0
  }
6097
0
  mIsEntireFrameInvalid = false;
6098
0
  mInvalidateCount = 0;
6099
0
}
6100
6101
void
6102
CanvasRenderingContext2D::MarkContextCleanForFrameCapture()
6103
0
{
6104
0
  mIsCapturedFrameInvalid = false;
6105
0
}
6106
6107
bool
6108
CanvasRenderingContext2D::IsContextCleanForFrameCapture()
6109
0
{
6110
0
  return !mIsCapturedFrameInvalid;
6111
0
}
6112
6113
bool
6114
CanvasRenderingContext2D::ShouldForceInactiveLayer(LayerManager* aManager)
6115
0
{
6116
0
  return !aManager->CanUseCanvasLayerForSize(GetSize());
6117
0
}
6118
6119
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CanvasPath, AddRef)
6120
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CanvasPath, Release)
6121
6122
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasPath, mParent)
6123
6124
CanvasPath::CanvasPath(nsISupports* aParent)
6125
  : mParent(aParent)
6126
0
{
6127
0
  mPathBuilder = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget()->CreatePathBuilder();
6128
0
}
6129
6130
CanvasPath::CanvasPath(nsISupports* aParent, already_AddRefed<PathBuilder> aPathBuilder)
6131
  : mParent(aParent), mPathBuilder(aPathBuilder)
6132
0
{
6133
0
  if (!mPathBuilder) {
6134
0
    mPathBuilder = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget()->CreatePathBuilder();
6135
0
  }
6136
0
}
6137
6138
JSObject*
6139
CanvasPath::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
6140
0
{
6141
0
  return Path2D_Binding::Wrap(aCx, this, aGivenProto);
6142
0
}
6143
6144
already_AddRefed<CanvasPath>
6145
CanvasPath::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv)
6146
0
{
6147
0
  RefPtr<CanvasPath> path = new CanvasPath(aGlobal.GetAsSupports());
6148
0
  return path.forget();
6149
0
}
6150
6151
already_AddRefed<CanvasPath>
6152
CanvasPath::Constructor(const GlobalObject& aGlobal, CanvasPath& aCanvasPath, ErrorResult& aRv)
6153
0
{
6154
0
  RefPtr<gfx::Path> tempPath = aCanvasPath.GetPath(
6155
0
    CanvasWindingRule::Nonzero,
6156
0
    gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget().get());
6157
0
6158
0
  RefPtr<CanvasPath> path = new CanvasPath(aGlobal.GetAsSupports(), tempPath->CopyToBuilder());
6159
0
  return path.forget();
6160
0
}
6161
6162
already_AddRefed<CanvasPath>
6163
CanvasPath::Constructor(const GlobalObject& aGlobal, const nsAString& aPathString, ErrorResult& aRv)
6164
0
{
6165
0
  RefPtr<gfx::Path> tempPath = SVGContentUtils::GetPath(aPathString);
6166
0
  if (!tempPath) {
6167
0
    return Constructor(aGlobal, aRv);
6168
0
  }
6169
0
6170
0
  RefPtr<CanvasPath> path = new CanvasPath(aGlobal.GetAsSupports(), tempPath->CopyToBuilder());
6171
0
  return path.forget();
6172
0
}
6173
6174
void
6175
CanvasPath::ClosePath()
6176
0
{
6177
0
  EnsurePathBuilder();
6178
0
6179
0
  mPathBuilder->Close();
6180
0
}
6181
6182
void
6183
CanvasPath::MoveTo(double aX, double aY)
6184
0
{
6185
0
  EnsurePathBuilder();
6186
0
6187
0
  mPathBuilder->MoveTo(Point(ToFloat(aX), ToFloat(aY)));
6188
0
}
6189
6190
void
6191
CanvasPath::LineTo(double aX, double aY)
6192
0
{
6193
0
  EnsurePathBuilder();
6194
0
6195
0
  mPathBuilder->LineTo(Point(ToFloat(aX), ToFloat(aY)));
6196
0
}
6197
6198
void
6199
CanvasPath::QuadraticCurveTo(double aCpx, double aCpy, double aX, double aY)
6200
0
{
6201
0
  EnsurePathBuilder();
6202
0
6203
0
  mPathBuilder->QuadraticBezierTo(gfx::Point(ToFloat(aCpx), ToFloat(aCpy)),
6204
0
                                  gfx::Point(ToFloat(aX), ToFloat(aY)));
6205
0
}
6206
6207
void
6208
CanvasPath::BezierCurveTo(double aCp1x, double aCp1y,
6209
                          double aCp2x, double aCp2y,
6210
                          double aX, double aY)
6211
0
{
6212
0
  BezierTo(gfx::Point(ToFloat(aCp1x), ToFloat(aCp1y)),
6213
0
             gfx::Point(ToFloat(aCp2x), ToFloat(aCp2y)),
6214
0
             gfx::Point(ToFloat(aX), ToFloat(aY)));
6215
0
}
6216
6217
void
6218
CanvasPath::ArcTo(double aX1, double aY1, double aX2, double aY2, double aRadius,
6219
                  ErrorResult& aError)
6220
0
{
6221
0
  if (aRadius < 0) {
6222
0
    aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
6223
0
    return;
6224
0
  }
6225
0
6226
0
  EnsurePathBuilder();
6227
0
6228
0
  // Current point in user space!
6229
0
  Point p0 = mPathBuilder->CurrentPoint();
6230
0
  Point p1(aX1, aY1);
6231
0
  Point p2(aX2, aY2);
6232
0
6233
0
  // Execute these calculations in double precision to avoid cumulative
6234
0
  // rounding errors.
6235
0
  double dir, a2, b2, c2, cosx, sinx, d, anx, any,
6236
0
         bnx, bny, x3, y3, x4, y4, cx, cy, angle0, angle1;
6237
0
  bool anticlockwise;
6238
0
6239
0
  if (p0 == p1 || p1 == p2 || aRadius == 0) {
6240
0
    LineTo(p1.x, p1.y);
6241
0
    return;
6242
0
  }
6243
0
6244
0
  // Check for colinearity
6245
0
  dir = (p2.x - p1.x) * (p0.y - p1.y) + (p2.y - p1.y) * (p1.x - p0.x);
6246
0
  if (dir == 0) {
6247
0
    LineTo(p1.x, p1.y);
6248
0
    return;
6249
0
  }
6250
0
6251
0
6252
0
  // XXX - Math for this code was already available from the non-azure code
6253
0
  // and would be well tested. Perhaps converting to bezier directly might
6254
0
  // be more efficient longer run.
6255
0
  a2 = (p0.x-aX1)*(p0.x-aX1) + (p0.y-aY1)*(p0.y-aY1);
6256
0
  b2 = (aX1-aX2)*(aX1-aX2) + (aY1-aY2)*(aY1-aY2);
6257
0
  c2 = (p0.x-aX2)*(p0.x-aX2) + (p0.y-aY2)*(p0.y-aY2);
6258
0
  cosx = (a2+b2-c2)/(2*sqrt(a2*b2));
6259
0
6260
0
  sinx = sqrt(1 - cosx*cosx);
6261
0
  d = aRadius / ((1 - cosx) / sinx);
6262
0
6263
0
  anx = (aX1-p0.x) / sqrt(a2);
6264
0
  any = (aY1-p0.y) / sqrt(a2);
6265
0
  bnx = (aX1-aX2) / sqrt(b2);
6266
0
  bny = (aY1-aY2) / sqrt(b2);
6267
0
  x3 = aX1 - anx*d;
6268
0
  y3 = aY1 - any*d;
6269
0
  x4 = aX1 - bnx*d;
6270
0
  y4 = aY1 - bny*d;
6271
0
  anticlockwise = (dir < 0);
6272
0
  cx = x3 + any*aRadius*(anticlockwise ? 1 : -1);
6273
0
  cy = y3 - anx*aRadius*(anticlockwise ? 1 : -1);
6274
0
  angle0 = atan2((y3-cy), (x3-cx));
6275
0
  angle1 = atan2((y4-cy), (x4-cx));
6276
0
6277
0
6278
0
  LineTo(x3, y3);
6279
0
6280
0
  Arc(cx, cy, aRadius, angle0, angle1, anticlockwise, aError);
6281
0
}
6282
6283
void
6284
CanvasPath::Rect(double aX, double aY, double aW, double aH)
6285
0
{
6286
0
  MoveTo(aX, aY);
6287
0
  LineTo(aX + aW, aY);
6288
0
  LineTo(aX + aW, aY + aH);
6289
0
  LineTo(aX, aY + aH);
6290
0
  ClosePath();
6291
0
}
6292
6293
void
6294
CanvasPath::Arc(double aX, double aY, double aRadius,
6295
                double aStartAngle, double aEndAngle, bool aAnticlockwise,
6296
                ErrorResult& aError)
6297
0
{
6298
0
  if (aRadius < 0.0) {
6299
0
    aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
6300
0
    return;
6301
0
  }
6302
0
6303
0
  EnsurePathBuilder();
6304
0
6305
0
  ArcToBezier(this, Point(aX, aY), Size(aRadius, aRadius), aStartAngle, aEndAngle, aAnticlockwise);
6306
0
}
6307
6308
void
6309
CanvasPath::Ellipse(double x, double y, double radiusX, double radiusY,
6310
                    double rotation, double startAngle, double endAngle,
6311
                    bool anticlockwise, ErrorResult& error)
6312
0
{
6313
0
  if (radiusX < 0.0 || radiusY < 0.0) {
6314
0
    error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
6315
0
    return;
6316
0
  }
6317
0
6318
0
  EnsurePathBuilder();
6319
0
6320
0
  ArcToBezier(this, Point(x, y), Size(radiusX, radiusY), startAngle, endAngle,
6321
0
              anticlockwise, rotation);
6322
0
}
6323
6324
void
6325
CanvasPath::LineTo(const gfx::Point& aPoint)
6326
0
{
6327
0
  EnsurePathBuilder();
6328
0
6329
0
  mPathBuilder->LineTo(aPoint);
6330
0
}
6331
6332
void
6333
CanvasPath::BezierTo(const gfx::Point& aCP1,
6334
                     const gfx::Point& aCP2,
6335
                     const gfx::Point& aCP3)
6336
0
{
6337
0
  EnsurePathBuilder();
6338
0
6339
0
  mPathBuilder->BezierTo(aCP1, aCP2, aCP3);
6340
0
}
6341
6342
void
6343
CanvasPath::AddPath(CanvasPath& aCanvasPath, const Optional<NonNull<SVGMatrix>>& aMatrix)
6344
0
{
6345
0
  RefPtr<gfx::Path> tempPath = aCanvasPath.GetPath(
6346
0
    CanvasWindingRule::Nonzero,
6347
0
    gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget().get());
6348
0
6349
0
  if (aMatrix.WasPassed()) {
6350
0
    const SVGMatrix& m = aMatrix.Value();
6351
0
    Matrix transform(m.A(), m.B(), m.C(), m.D(), m.E(), m.F());
6352
0
6353
0
    if (!transform.IsIdentity()) {
6354
0
      RefPtr<PathBuilder> tempBuilder = tempPath->TransformedCopyToBuilder(transform, FillRule::FILL_WINDING);
6355
0
      tempPath = tempBuilder->Finish();
6356
0
    }
6357
0
  }
6358
0
6359
0
  EnsurePathBuilder(); // in case a path is added to itself
6360
0
  tempPath->StreamToSink(mPathBuilder);
6361
0
}
6362
6363
already_AddRefed<gfx::Path>
6364
CanvasPath::GetPath(const CanvasWindingRule& aWinding, const DrawTarget* aTarget) const
6365
0
{
6366
0
  FillRule fillRule = FillRule::FILL_WINDING;
6367
0
  if (aWinding == CanvasWindingRule::Evenodd) {
6368
0
    fillRule = FillRule::FILL_EVEN_ODD;
6369
0
  }
6370
0
6371
0
  if (mPath &&
6372
0
      (mPath->GetBackendType() == aTarget->GetBackendType()) &&
6373
0
      (mPath->GetFillRule() == fillRule)) {
6374
0
    RefPtr<gfx::Path> path(mPath);
6375
0
    return path.forget();
6376
0
  }
6377
0
6378
0
  if (!mPath) {
6379
0
    // if there is no path, there must be a pathbuilder
6380
0
    MOZ_ASSERT(mPathBuilder);
6381
0
    mPath = mPathBuilder->Finish();
6382
0
    if (!mPath) {
6383
0
      RefPtr<gfx::Path> path(mPath);
6384
0
      return path.forget();
6385
0
    }
6386
0
6387
0
    mPathBuilder = nullptr;
6388
0
  }
6389
0
6390
0
  // retarget our backend if we're used with a different backend
6391
0
  if (mPath->GetBackendType() != aTarget->GetBackendType()) {
6392
0
    RefPtr<PathBuilder> tmpPathBuilder = aTarget->CreatePathBuilder(fillRule);
6393
0
    mPath->StreamToSink(tmpPathBuilder);
6394
0
    mPath = tmpPathBuilder->Finish();
6395
0
  } else if (mPath->GetFillRule() != fillRule) {
6396
0
    RefPtr<PathBuilder> tmpPathBuilder = mPath->CopyToBuilder(fillRule);
6397
0
    mPath = tmpPathBuilder->Finish();
6398
0
  }
6399
0
6400
0
  RefPtr<gfx::Path> path(mPath);
6401
0
  return path.forget();
6402
0
}
6403
6404
void
6405
CanvasPath::EnsurePathBuilder() const
6406
0
{
6407
0
  if (mPathBuilder) {
6408
0
    return;
6409
0
  }
6410
0
6411
0
  // if there is not pathbuilder, there must be a path
6412
0
  MOZ_ASSERT(mPath);
6413
0
  mPathBuilder = mPath->CopyToBuilder();
6414
0
  mPath = nullptr;
6415
0
}
6416
6417
size_t
6418
BindingJSObjectMallocBytes(CanvasRenderingContext2D* aContext)
6419
0
{
6420
0
  return aContext->GetWidth() * aContext->GetHeight() * 4;
6421
0
}
6422
6423
} // namespace dom
6424
} // namespace mozilla