Bug 1318573 - (Part 13) Decouple filter related features from ContextState. r?mstange draft
authorKevin Chen <kechen@mozilla.com>
Mon, 13 Mar 2017 18:41:00 +0800
changeset 499866 82c292ffa42a4d6ca48d95c8e34265adca55682b
parent 499865 8e7a965eb7d9dd8f8f41d500ba0b2864a390a3d9
child 549492 8bcd70043ea1bb62b0510a1e3d6ac4bb06f62256
push id49564
push userbmo:kechen@mozilla.com
push dateThu, 16 Mar 2017 09:50:15 +0000
reviewersmstange
bugs1318573
milestone54.0a1
Bug 1318573 - (Part 13) Decouple filter related features from ContextState. r?mstange MozReview-Commit-ID: ICn72ob8Dyn
dom/canvas/AdjustedTarget.cpp
dom/canvas/AdjustedTarget.h
dom/canvas/BasicRenderingContext2D.cpp
dom/canvas/BasicRenderingContext2D.h
dom/canvas/CanvasRenderingContext2D.cpp
dom/canvas/CanvasRenderingContext2D.h
--- a/dom/canvas/AdjustedTarget.cpp
+++ b/dom/canvas/AdjustedTarget.cpp
@@ -1,21 +1,22 @@
 /* This is an RAII based class that can be used as a drawtarget for
  * operations that need to have a filter applied to their results.
  * All coordinates passed to the constructor are in device space.
  */
 #include "AdjustedTarget.h"
+#include "mozilla/dom/CanvasRenderingContext2D.h"
 #include "mozilla/dom/HTMLCanvasElement.h"
 
 using mozilla::gfx::DrawTarget;
 
 namespace mozilla {
 namespace dom {
 
-AdjustedTargetForFilter::AdjustedTargetForFilter(BasicRenderingContext2D* aCtx,
+AdjustedTargetForFilter::AdjustedTargetForFilter(CanvasRenderingContext2D* aCtx,
                         DrawTarget* aFinalTarget,
                         const gfx::IntPoint& aFilterSpaceToTargetOffset,
                         const gfx::IntRect& aPreFilterBounds,
                         const gfx::IntRect& aPostFilterBounds,
                         gfx::CompositionOp aCompositionOp)
   : mCtx(nullptr)
   , mCompositionOp(aCompositionOp)
 {
@@ -24,17 +25,17 @@ AdjustedTargetForFilter::AdjustedTargetF
   mPostFilterBounds = aPostFilterBounds;
   mOffset = aFilterSpaceToTargetOffset;
 
   nsIntRegion sourceGraphicNeededRegion;
   nsIntRegion fillPaintNeededRegion;
   nsIntRegion strokePaintNeededRegion;
 
   FilterSupport::ComputeSourceNeededRegions(
-    aCtx->CurrentState().filter, mPostFilterBounds,
+    aCtx->CurrentStateForFilter()->filter, mPostFilterBounds,
     sourceGraphicNeededRegion, fillPaintNeededRegion,
     strokePaintNeededRegion);
 
   mSourceGraphicRect = sourceGraphicNeededRegion.GetBounds();
   mFillPaintRect = fillPaintNeededRegion.GetBounds();
   mStrokePaintRect = strokePaintNeededRegion.GetBounds();
 
   mSourceGraphicRect = mSourceGraphicRect.Intersect(aPreFilterBounds);
@@ -101,28 +102,28 @@ AdjustedTargetForFilter::~AdjustedTarget
   RefPtr<SourceSurface> fillPaint =
     DoSourcePaint(mFillPaintRect, BasicRenderingContext2D::Style::FILL);
   RefPtr<SourceSurface> strokePaint =
     DoSourcePaint(mStrokePaintRect, BasicRenderingContext2D::Style::STROKE);
 
   AutoRestoreTransform autoRestoreTransform(mFinalTarget);
   mFinalTarget->SetTransform(Matrix());
 
-  MOZ_RELEASE_ASSERT(!mCtx->CurrentState().filter.mPrimitives.IsEmpty());
+  MOZ_RELEASE_ASSERT(!mCtx->CurrentStateForFilter()->filter.mPrimitives.IsEmpty());
   gfx::FilterSupport::RenderFilterDescription(
-    mFinalTarget, mCtx->CurrentState().filter,
+    mFinalTarget, mCtx->CurrentStateForFilter()->filter,
     gfx::Rect(mPostFilterBounds),
     snapshot, mSourceGraphicRect,
     fillPaint, mFillPaintRect,
     strokePaint, mStrokePaintRect,
-    mCtx->CurrentState().filterAdditionalImages,
+    mCtx->CurrentStateForFilter()->filterAdditionalImages,
     mPostFilterBounds.TopLeft() - mOffset,
     DrawOptions(1.0f, mCompositionOp));
 
-  const gfx::FilterDescription& filter = mCtx->CurrentState().filter;
+  const gfx::FilterDescription& filter = mCtx->CurrentStateForFilter()->filter;
   MOZ_RELEASE_ASSERT(!filter.mPrimitives.IsEmpty());
   if (filter.mPrimitives.LastElement().IsTainted() &&
       mCtx->GetCanvasElement()) {
     mCtx->GetCanvasElement()->SetWriteOnly();
   }
 }
 
 DrawTarget* AdjustedTargetForFilter::DT()
@@ -135,23 +136,23 @@ AdjustedTargetForShadow::AdjustedTargetF
                         const gfx::Rect& aBounds,
                         gfx::CompositionOp aCompositionOp)
   : mCtx(nullptr)
   , mCompositionOp(aCompositionOp)
 {
   mCtx = aCtx;
   mFinalTarget = aFinalTarget;
 
-  const ContextState &state = mCtx->CurrentState();
+  const ContextState* state = mCtx->CurrentState();
 
-  mSigma = state.ShadowBlurSigma();
+  mSigma = state->ShadowBlurSigma();
 
   gfx::Rect bounds = aBounds;
 
-  int32_t blurRadius = state.ShadowBlurRadius();
+  int32_t blurRadius = state->ShadowBlurRadius();
 
   // We actually include the bounds of the shadow blur, this makes it
   // easier to execute the actual blur on hardware, and shouldn't affect
   // the amount of pixels that need to be touched.
   bounds.Inflate(blurRadius);
 
   bounds.RoundOut();
   bounds.ToIntRect(&mTempRect);
@@ -176,18 +177,18 @@ AdjustedTargetForShadow::~AdjustedTarget
 {
   if (!mCtx) {
     return;
   }
 
   RefPtr<SourceSurface> snapshot = mTarget->Snapshot();
 
   mFinalTarget->DrawSurfaceWithShadow(snapshot, mTempRect.TopLeft(),
-                                      Color::FromABGR(mCtx->CurrentState().shadowColor),
-                                      mCtx->CurrentState().shadowOffset, mSigma,
+                                      Color::FromABGR(mCtx->CurrentState()->shadowColor),
+                                      mCtx->CurrentState()->shadowOffset, mSigma,
                                       mCompositionOp);
 }
 
 DrawTarget* AdjustedTargetForShadow::DT()
 {
   return mTarget;
 }
 
@@ -215,17 +216,17 @@ AdjustedTarget::AdjustedTarget(BasicRend
     MaxSourceNeededBoundsForFilter(maxSourceNeededBoundsForShadow, aCtx);
 
   gfx::Rect bounds = maxSourceNeededBoundsForFilter;
   if (aBounds) {
     bounds = bounds.Intersect(*aBounds);
   }
   gfx::Rect boundsAfterFilter = BoundsAfterFilter(bounds, aCtx);
 
-  mozilla::gfx::CompositionOp op = aCtx->CurrentState().op;
+  mozilla::gfx::CompositionOp op = aCtx->CurrentState()->op;
 
   gfx::IntPoint offsetToFinalDT;
 
   // First set up the shadow draw target, because the shadow goes outside.
   // It applies to the post-filter results, if both a filter and a shadow
   // are used.
   if (aCtx->NeedToDrawShadow()) {
     mShadowTarget = MakeUnique<AdjustedTargetForShadow>(
@@ -245,17 +246,17 @@ AdjustedTarget::AdjustedTarget(BasicRend
     if (!mTarget) {
       mTarget = aCtx->mTarget;
     }
     gfx::IntRect intBounds;
     if (!bounds.ToIntRect(&intBounds)) {
       return;
     }
     mFilterTarget = MakeUnique<AdjustedTargetForFilter>(
-      aCtx, mTarget, offsetToFinalDT, intBounds,
+      static_cast<CanvasRenderingContext2D*>(aCtx), mTarget, offsetToFinalDT, intBounds,
       gfx::RoundedToInt(boundsAfterFilter), op);
     mTarget = mFilterTarget->DT();
   }
   if (!mTarget) {
     mTarget = aCtx->mTarget;
   }
 }
 
@@ -278,33 +279,36 @@ AdjustedTarget::MaxSourceNeededBoundsFor
   if (!aCtx->NeedToApplyFilter()) {
     return aDestBounds;
   }
 
   nsIntRegion sourceGraphicNeededRegion;
   nsIntRegion fillPaintNeededRegion;
   nsIntRegion strokePaintNeededRegion;
 
+  // Only CanvasRenderingContext2D supports filter features, others will blocked
+  // by NeedToApplyFilter().
   FilterSupport::ComputeSourceNeededRegions(
-    aCtx->CurrentState().filter, gfx::RoundedToInt(aDestBounds),
+    static_cast<CanvasRenderingContext2D*>(aCtx)->CurrentStateForFilter()->filter,
+    gfx::RoundedToInt(aDestBounds),
     sourceGraphicNeededRegion, fillPaintNeededRegion, strokePaintNeededRegion);
 
   return gfx::Rect(sourceGraphicNeededRegion.GetBounds());
 }
 
 gfx::Rect
 AdjustedTarget::MaxSourceNeededBoundsForShadow(const gfx::Rect& aDestBounds, BasicRenderingContext2D* aCtx)
 {
   if (!aCtx->NeedToDrawShadow()) {
     return aDestBounds;
   }
 
-  const ContextState &state = aCtx->CurrentState();
-  gfx::Rect sourceBounds = aDestBounds - state.shadowOffset;
-  sourceBounds.Inflate(state.ShadowBlurRadius());
+  const ContextState* state = aCtx->CurrentState();
+  gfx::Rect sourceBounds = aDestBounds - state->shadowOffset;
+  sourceBounds.Inflate(state->ShadowBlurRadius());
 
   // Union the shadow source with the original rect because we're going to
   // draw both.
   return sourceBounds.Union(aDestBounds);
 }
 
 gfx::Rect
 AdjustedTarget::BoundsAfterFilter(const gfx::Rect& aBounds, BasicRenderingContext2D* aCtx)
@@ -316,16 +320,19 @@ AdjustedTarget::BoundsAfterFilter(const 
   gfx::Rect bounds(aBounds);
   bounds.RoundOut();
 
   gfx::IntRect intBounds;
   if (!bounds.ToIntRect(&intBounds)) {
     return gfx::Rect();
   }
 
+  // Only CanvasRenderingContext2D supports filter features, others will blocked
+  // by NeedToApplyFilter().
   nsIntRegion extents =
-    gfx::FilterSupport::ComputePostFilterExtents(aCtx->CurrentState().filter,
-                                                 intBounds);
+    gfx::FilterSupport::ComputePostFilterExtents(
+      static_cast<CanvasRenderingContext2D*>(aCtx)->CurrentStateForFilter()->filter,
+      intBounds);
   return gfx::Rect(extents.GetBounds());
 }
 
 } // dom
 } // mozilla
\ No newline at end of file
--- a/dom/canvas/AdjustedTarget.h
+++ b/dom/canvas/AdjustedTarget.h
@@ -25,17 +25,17 @@ namespace dom {
  */
 class AdjustedTargetForFilter
 {
   //typedef BasicRenderingContext2D::RenderingClass RenderingClass;
 
 public:
   typedef BasicRenderingContext2D::ContextState ContextState;
 
-  AdjustedTargetForFilter(BasicRenderingContext2D* aCtx,
+  AdjustedTargetForFilter(CanvasRenderingContext2D* aCtx,
                           DrawTarget* aFinalTarget,
                           const gfx::IntPoint& aFilterSpaceToTargetOffset,
                           const gfx::IntRect& aPreFilterBounds,
                           const gfx::IntRect& aPostFilterBounds,
                           gfx::CompositionOp aCompositionOp);
 
   // Return a SourceSurface that contains the FillPaint or StrokePaint source.
   already_AddRefed<SourceSurface>
@@ -43,17 +43,17 @@ public:
 
   ~AdjustedTargetForFilter();
 
   DrawTarget* DT();
 
 private:
   RefPtr<DrawTarget> mTarget;
   RefPtr<DrawTarget> mFinalTarget;
-  BasicRenderingContext2D *mCtx;
+  CanvasRenderingContext2D *mCtx;
   gfx::IntRect mSourceGraphicRect;
   gfx::IntRect mFillPaintRect;
   gfx::IntRect mStrokePaintRect;
   gfx::IntRect mPostFilterBounds;
   gfx::IntPoint mOffset;
   gfx::CompositionOp mCompositionOp;
 };
 
--- a/dom/canvas/BasicRenderingContext2D.cpp
+++ b/dom/canvas/BasicRenderingContext2D.cpp
@@ -59,16 +59,20 @@ using mozilla::image::DrawResult;
 using mozilla::image::ImageRegion;
 using mozilla::layers::AutoLockImage;
 using mozilla::layers::PersistentBufferProvider;
 using mozilla::layers::PersistentBufferProviderBasic;
 
 namespace mozilla {
 namespace dom{
 
+typedef BasicRenderingContext2D::ContextState ContextState;
+
+NS_IMPL_ISUPPORTS(ContextState, nsISupports)
+
 // This is KIND_OTHER because it's not always clear where in memory the pixels
 // of a canvas are stored.  Furthermore, this memory will be tracked by the
 // underlying surface implementations.  See bug 655638 for details.
 class Canvas2dPixelsReporter final : public nsIMemoryReporter
 {
   ~Canvas2dPixelsReporter() {}
 public:
   NS_DECL_ISUPPORTS
@@ -220,25 +224,25 @@ BasicRenderingContext2D::EnsureTarget(co
     SetErrorState();
     return aRenderingMode;
   }
 
   // If the next drawing command covers the entire canvas, we can skip copying
   // from the previous frame and/or clearing the canvas.
   gfx::Rect canvasRect(0, 0, mWidth, mHeight);
   bool canDiscardContent = aCoveredRect &&
-    CurrentState().transform.TransformBounds(*aCoveredRect).Contains(canvasRect);
+    CurrentState()->transform.TransformBounds(*aCoveredRect).Contains(canvasRect);
 
   // If a clip is active we don't know for sure that the next drawing command
   // will really cover the entire canvas.
   for (const auto& style : mStyleStack) {
     if (!canDiscardContent) {
       break;
     }
-    for (const auto& clipOrTransform : style.clipsAndTransforms) {
+    for (const auto& clipOrTransform : style->clipsAndTransforms) {
       if (clipOrTransform.IsClip()) {
         canDiscardContent = false;
         break;
       }
     }
   }
 
   ScheduleStableStateCallback();
@@ -388,34 +392,34 @@ BasicRenderingContext2D::RestoreClipsAnd
     // size into account which can cause OOM. See bug 1034593.
     // This limits the clip extents to the size of the canvas.
     // A fix in Cairo would probably be preferable, but requires somewhat
     // invasive changes.
     mTarget->PushClipRect(gfx::Rect(0, 0, mWidth, mHeight));
   }
 
   for (const auto& style : mStyleStack) {
-    for (const auto& clipOrTransform : style.clipsAndTransforms) {
+    for (const auto& clipOrTransform : style->clipsAndTransforms) {
       if (clipOrTransform.IsClip()) {
         mTarget->PushClip(clipOrTransform.clip);
       } else {
         mTarget->SetTransform(clipOrTransform.transform);
       }
     }
   }
 }
 
 void
 BasicRenderingContext2D::ReturnTarget(bool aForceReset)
 {
   if (mTarget && mBufferProvider && mTarget != sErrorTarget) {
-    CurrentState().transform = mTarget->GetTransform();
+    CurrentState()->transform = mTarget->GetTransform();
     if (aForceReset || !mBufferProvider->PreservesDrawingState()) {
       for (const auto& style : mStyleStack) {
-        for (const auto& clipOrTransform : style.clipsAndTransforms) {
+        for (const auto& clipOrTransform : style->clipsAndTransforms) {
           if (clipOrTransform.IsClip()) {
             mTarget->PopClip();
           }
         }
       }
 
       if (mTarget->GetBackendType() == gfx::BackendType::CAIRO) {
         // With the cairo backend we pushed an extra clip rect which we have to
@@ -475,22 +479,25 @@ void
 BasicRenderingContext2D::SetInitialState()
 {
   // Set up the initial canvas defaults
   mPathBuilder = nullptr;
   mPath = nullptr;
   mDSPathBuilder = nullptr;
 
   mStyleStack.Clear();
-  ContextState *state = mStyleStack.AppendElement();
+
+  RefPtr<ContextState> state = CreateContextState();
   state->globalAlpha = 1.0;
-
   state->colorStyles[Style::FILL] = NS_RGB(0,0,0);
   state->colorStyles[Style::STROKE] = NS_RGB(0,0,0);
   state->shadowColor = NS_RGBA(0,0,0,0);
+
+  mStyleStack.AppendElement(state);
+
 }
 
 void
 BasicRenderingContext2D::EnsureErrorTarget()
 {
   if (sErrorTarget) {
     return;
   }
@@ -500,62 +507,62 @@ BasicRenderingContext2D::EnsureErrorTarg
 
   sErrorTarget = errorTarget;
   NS_ADDREF(sErrorTarget);
 }
 
 bool
 BasicRenderingContext2D::PatternIsOpaque(BasicRenderingContext2D::Style aStyle) const
 {
-  const ContextState& state = CurrentState();
-  if (state.globalAlpha < 1.0) {
+  const ContextState* state = CurrentState();
+  if (state->globalAlpha < 1.0) {
     return false;
   }
 
-  if (state.patternStyles[aStyle] && state.patternStyles[aStyle]->mSurface) {
-    return IsOpaqueFormat(state.patternStyles[aStyle]->mSurface->GetFormat());
+  if (state->patternStyles[aStyle] && state->patternStyles[aStyle]->mSurface) {
+    return IsOpaqueFormat(state->patternStyles[aStyle]->mSurface->GetFormat());
   }
 
   // TODO: for gradient patterns we could check that all stops are opaque
   // colors.
 
-  if (!state.gradientStyles[aStyle]) {
+  if (!state->gradientStyles[aStyle]) {
     // it's a color pattern.
-    return Color::FromABGR(state.colorStyles[aStyle]).a >= 1.0;
+    return Color::FromABGR(state->colorStyles[aStyle]).a >= 1.0;
   }
 
   return false;
 }
 
 void
 BasicRenderingContext2D::GetStyleAsUnion(OwningStringOrCanvasGradientOrCanvasPattern& aValue,
                                          Style aWhichStyle)
 {
-  const ContextState &state = CurrentState();
-  if (state.patternStyles[aWhichStyle]) {
-    aValue.SetAsCanvasPattern() = state.patternStyles[aWhichStyle];
-  } else if (state.gradientStyles[aWhichStyle]) {
-    aValue.SetAsCanvasGradient() = state.gradientStyles[aWhichStyle];
+  const ContextState* state = CurrentState();
+  if (state->patternStyles[aWhichStyle]) {
+    aValue.SetAsCanvasPattern() = state->patternStyles[aWhichStyle];
+  } else if (state->gradientStyles[aWhichStyle]) {
+    aValue.SetAsCanvasGradient() = state->gradientStyles[aWhichStyle];
   } else {
-    StyleColorToString(state.colorStyles[aWhichStyle], aValue.SetAsString());
+    StyleColorToString(state->colorStyles[aWhichStyle], aValue.SetAsString());
   }
 }
 
 void
 BasicRenderingContext2D::SetStyleFromString(const nsAString& aStr,
                                             Style aWhichStyle)
 {
   MOZ_ASSERT(!aStr.IsVoid());
 
   nscolor color;
   if (!ParseColor(aStr, &color)) {
     return;
   }
 
-  CurrentState().SetColorStyle(aWhichStyle, color);
+  CurrentState()->SetColorStyle(aWhichStyle, color);
 }
 
 // static
 void
 BasicRenderingContext2D::StyleColorToString(const nscolor& aColor, nsAString& aStr)
 {
   // We can't reuse the normal CSS color stringification code,
   // because the spec calls for a different algorithm for canvas.
@@ -579,44 +586,44 @@ BasicRenderingContext2D::StyleColorToStr
 //
 // state
 //
 
 void
 BasicRenderingContext2D::Save()
 {
   EnsureTarget();
-  mStyleStack[mStyleStack.Length() - 1].transform = mTarget->GetTransform();
-  mStyleStack.SetCapacity(mStyleStack.Length() + 1);
-  mStyleStack.AppendElement(CurrentState());
+  mStyleStack[mStyleStack.Length() - 1]->transform = mTarget->GetTransform();
+  RefPtr<ContextState> state = CreateContextState(CurrentState());
+  mStyleStack.AppendElement(state);
 
   if (mStyleStack.Length() > MAX_STYLE_STACK_SIZE) {
     // This is not fast, but is better than OOMing and shouldn't be hit by
     // reasonable code.
     mStyleStack.RemoveElementAt(0);
   }
 }
 
 void
 BasicRenderingContext2D::Restore()
 {
   if (mStyleStack.Length() - 1 == 0)
     return;
 
   TransformWillUpdate();
 
-  for (const auto& clipOrTransform : CurrentState().clipsAndTransforms) {
+  for (const auto& clipOrTransform : CurrentState()->clipsAndTransforms) {
     if (clipOrTransform.IsClip()) {
       mTarget->PopClip();
     }
   }
 
   mStyleStack.RemoveElementAt(mStyleStack.Length() - 1);
 
-  mTarget->SetTransform(CurrentState().transform);
+  mTarget->SetTransform(CurrentState()->transform);
 }
 
 //
 // transformations
 //
 
 void
 BasicRenderingContext2D::Scale(double aX, double aY, ErrorResult& aError)
@@ -697,17 +704,17 @@ BasicRenderingContext2D::SetTransform(do
 void
 BasicRenderingContext2D::SetTransformInternal(const Matrix& aTransform)
 {
   if (!aTransform.IsFinite()) {
     return;
   }
 
   // Save the transform in the clip stack to be able to replay clips properly.
-  auto& clipsAndTransforms = CurrentState().clipsAndTransforms;
+  auto& clipsAndTransforms = CurrentState()->clipsAndTransforms;
   if (clipsAndTransforms.IsEmpty() || clipsAndTransforms.LastElement().IsClip()) {
     clipsAndTransforms.AppendElement(ClipState(aTransform));
   } else {
     // If the last item is a transform we can replace it instead of appending
     // a new item.
     clipsAndTransforms.LastElement().transform = aTransform;
   }
   mTarget->SetTransform(aTransform);
@@ -758,24 +765,24 @@ BasicRenderingContext2D::SetGlobalCompos
   else CANVAS_OP_TO_GFX_OP("hue", HUE)
   else CANVAS_OP_TO_GFX_OP("saturation", SATURATION)
   else CANVAS_OP_TO_GFX_OP("color", COLOR)
   else CANVAS_OP_TO_GFX_OP("luminosity", LUMINOSITY)
   // XXX ERRMSG we need to report an error to developers here! (bug 329026)
   else return;
 
 #undef CANVAS_OP_TO_GFX_OP
-  CurrentState().op = comp_op;
+  CurrentState()->op = comp_op;
 }
 
 void
 BasicRenderingContext2D::GetGlobalCompositeOperation(nsAString& aOp,
                                                      ErrorResult& aError)
 {
-  CompositionOp comp_op = CurrentState().op;
+  CompositionOp comp_op = CurrentState()->op;
 
 #define CANVAS_OP_TO_GFX_OP(cvsop, op2d) \
   if (comp_op == CompositionOp::OP_##op2d) \
     aOp.AssignLiteral(cvsop);
 
   CANVAS_OP_TO_GFX_OP("copy", SOURCE)
   else CANVAS_OP_TO_GFX_OP("destination-atop", DEST_ATOP)
   else CANVAS_OP_TO_GFX_OP("destination-in", DEST_IN)
@@ -981,17 +988,17 @@ BasicRenderingContext2D::SetStyleFromUni
 void
 BasicRenderingContext2D::SetShadowColor(const nsAString& aShadowColor)
 {
   nscolor color;
   if (!ParseColor(aShadowColor, &color)) {
     return;
   }
 
-  CurrentState().shadowColor = color;
+  CurrentState()->shadowColor = color;
 }
 
 //
 // rects
 //
 
 static bool
 ValidateRect(double& aX, double& aY, double& aWidth, double& aHeight, bool aIsZeroSizeValid)
@@ -1042,31 +1049,31 @@ BasicRenderingContext2D::ClearRect(doubl
 
   RedrawUser(gfxRect(aX, aY, aW, aH));
 }
 
 void
 BasicRenderingContext2D::FillRect(double aX, double aY, double aW,
                                   double aH)
 {
-  const ContextState &state = CurrentState();
+  const ContextState* state = CurrentState();
 
   if (!ValidateRect(aX, aY, aW, aH, true)) {
     return;
   }
 
-  if (state.patternStyles[Style::FILL]) {
+  if (state->patternStyles[Style::FILL]) {
     CanvasPattern::RepeatMode repeat =
-      state.patternStyles[Style::FILL]->mRepeat;
+      state->patternStyles[Style::FILL]->mRepeat;
     // In the FillRect case repeat modes are easy to deal with.
     bool limitx = repeat == CanvasPattern::RepeatMode::NOREPEAT || repeat == CanvasPattern::RepeatMode::REPEATY;
     bool limity = repeat == CanvasPattern::RepeatMode::NOREPEAT || repeat == CanvasPattern::RepeatMode::REPEATX;
 
     IntSize patternSize =
-      state.patternStyles[Style::FILL]->mSurface->GetSize();
+      state->patternStyles[Style::FILL]->mSurface->GetSize();
 
     // We always need to execute painting for non-over operators, even if
     // we end up with w/h = 0.
     if (limitx) {
       if (aX < 0) {
         aW += aX;
         if (aW < 0) {
           aW = 0;
@@ -1106,32 +1113,32 @@ BasicRenderingContext2D::FillRect(double
   const gfx::Rect fillRect(aX, aY, aW, aH);
   EnsureTarget(discardContent ? &fillRect : nullptr);
 
   gfx::Rect bounds;
   if (NeedToCalculateBounds()) {
     bounds = mTarget->GetTransform().TransformBounds(fillRect);
   }
 
-  AntialiasMode antialiasMode = CurrentState().imageSmoothingEnabled ?
+  AntialiasMode antialiasMode = CurrentState()->imageSmoothingEnabled ?
                                 AntialiasMode::DEFAULT : AntialiasMode::NONE;
 
   AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)->
     FillRect(gfx::Rect(aX, aY, aW, aH),
              CanvasGeneralPattern().ForStyle(this, Style::FILL, mTarget),
-             DrawOptions(state.globalAlpha, op, antialiasMode));
+             DrawOptions(state->globalAlpha, op, antialiasMode));
 
   RedrawUser(gfxRect(aX, aY, aW, aH));
 }
 
 void
 BasicRenderingContext2D::StrokeRect(double aX, double aY, double aW,
                                     double aH)
 {
-  const ContextState &state = CurrentState();
+  const ContextState* state = CurrentState();
 
   gfx::Rect bounds;
 
   if (!aW && !aH) {
     return;
   }
 
   if (!ValidateRect(aX, aY, aW, aH, true)) {
@@ -1139,64 +1146,64 @@ BasicRenderingContext2D::StrokeRect(doub
   }
 
   EnsureTarget();
   if (!IsTargetValid()) {
     return;
   }
 
   if (NeedToCalculateBounds()) {
-    bounds = gfx::Rect(aX - state.lineWidth / 2.0f, aY - state.lineWidth / 2.0f,
-                       aW + state.lineWidth, aH + state.lineWidth);
+    bounds = gfx::Rect(aX - state->lineWidth / 2.0f, aY - state->lineWidth / 2.0f,
+                       aW + state->lineWidth, aH + state->lineWidth);
     bounds = mTarget->GetTransform().TransformBounds(bounds);
   }
 
   if (!aH) {
     CapStyle cap = CapStyle::BUTT;
-    if (state.lineJoin == JoinStyle::ROUND) {
+    if (state->lineJoin == JoinStyle::ROUND) {
       cap = CapStyle::ROUND;
     }
     AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)->
       StrokeLine(Point(aX, aY), Point(aX + aW, aY),
                   CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget),
-                  StrokeOptions(state.lineWidth, state.lineJoin,
-                                cap, state.miterLimit,
-                                state.dash.Length(),
-                                state.dash.Elements(),
-                                state.dashOffset),
-                  DrawOptions(state.globalAlpha, UsedOperation()));
+                  StrokeOptions(state->lineWidth, state->lineJoin,
+                                cap, state->miterLimit,
+                                state->dash.Length(),
+                                state->dash.Elements(),
+                                state->dashOffset),
+                  DrawOptions(state->globalAlpha, UsedOperation()));
     return;
   }
 
   if (!aW) {
     CapStyle cap = CapStyle::BUTT;
-    if (state.lineJoin == JoinStyle::ROUND) {
+    if (state->lineJoin == JoinStyle::ROUND) {
       cap = CapStyle::ROUND;
     }
     AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)->
       StrokeLine(Point(aX, aY), Point(aX, aY + aH),
                   CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget),
-                  StrokeOptions(state.lineWidth, state.lineJoin,
-                                cap, state.miterLimit,
-                                state.dash.Length(),
-                                state.dash.Elements(),
-                                state.dashOffset),
-                  DrawOptions(state.globalAlpha, UsedOperation()));
+                  StrokeOptions(state->lineWidth, state->lineJoin,
+                                cap, state->miterLimit,
+                                state->dash.Length(),
+                                state->dash.Elements(),
+                                state->dashOffset),
+                  DrawOptions(state->globalAlpha, UsedOperation()));
     return;
   }
 
   AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)->
     StrokeRect(gfx::Rect(aX, aY, aW, aH),
                CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget),
-               StrokeOptions(state.lineWidth, state.lineJoin,
-                             state.lineCap, state.miterLimit,
-                             state.dash.Length(),
-                             state.dash.Elements(),
-                             state.dashOffset),
-               DrawOptions(state.globalAlpha, UsedOperation()));
+               StrokeOptions(state->lineWidth, state->lineJoin,
+                             state->lineCap, state->miterLimit,
+                             state->dash.Length(),
+                             state->dash.Elements(),
+                             state->dashOffset),
+               DrawOptions(state->globalAlpha, UsedOperation()));
 
   Redraw();
 }
 
 //
 // path bits
 //
 
@@ -1221,17 +1228,17 @@ BasicRenderingContext2D::Fill(const Canv
   gfx::Rect bounds;
 
   if (NeedToCalculateBounds()) {
     bounds = mPath->GetBounds(mTarget->GetTransform());
   }
 
   AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)->
     Fill(mPath, CanvasGeneralPattern().ForStyle(this, Style::FILL, mTarget),
-         DrawOptions(CurrentState().globalAlpha, UsedOperation()));
+         DrawOptions(CurrentState()->globalAlpha, UsedOperation()));
 
   Redraw();
 }
 
 void BasicRenderingContext2D::Fill(const CanvasPath& aPath, const CanvasWindingRule& aWinding)
 {
   EnsureTarget();
 
@@ -1244,107 +1251,107 @@ void BasicRenderingContext2D::Fill(const
   gfx::Rect bounds;
 
   if (NeedToCalculateBounds()) {
     bounds = gfxpath->GetBounds(mTarget->GetTransform());
   }
 
   AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)->
     Fill(gfxpath, CanvasGeneralPattern().ForStyle(this, Style::FILL, mTarget),
-         DrawOptions(CurrentState().globalAlpha, UsedOperation()));
+         DrawOptions(CurrentState()->globalAlpha, UsedOperation()));
 
   Redraw();
 }
 
 void
 BasicRenderingContext2D::Stroke()
 {
   EnsureUserSpacePath();
 
   if (!mPath) {
     return;
   }
 
-  const ContextState &state = CurrentState();
+  const ContextState* state = CurrentState();
 
-  StrokeOptions strokeOptions(state.lineWidth, state.lineJoin,
-                              state.lineCap, state.miterLimit,
-                              state.dash.Length(), state.dash.Elements(),
-                              state.dashOffset);
+  StrokeOptions strokeOptions(state->lineWidth, state->lineJoin,
+                              state->lineCap, state->miterLimit,
+                              state->dash.Length(), state->dash.Elements(),
+                              state->dashOffset);
 
   gfx::Rect bounds;
   if (NeedToCalculateBounds()) {
     bounds =
       mPath->GetStrokedBounds(strokeOptions, mTarget->GetTransform());
   }
 
   AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)->
     Stroke(mPath, CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget),
-           strokeOptions, DrawOptions(state.globalAlpha, UsedOperation()));
+           strokeOptions, DrawOptions(state->globalAlpha, UsedOperation()));
 
   Redraw();
 }
 
 void
 BasicRenderingContext2D::Stroke(const CanvasPath& aPath)
 {
   EnsureTarget();
 
   RefPtr<gfx::Path> gfxpath = aPath.GetPath(CanvasWindingRule::Nonzero, mTarget);
 
   if (!gfxpath) {
     return;
   }
 
-  const ContextState &state = CurrentState();
+  const ContextState* state = CurrentState();
 
-  StrokeOptions strokeOptions(state.lineWidth, state.lineJoin,
-                              state.lineCap, state.miterLimit,
-                              state.dash.Length(), state.dash.Elements(),
-                              state.dashOffset);
+  StrokeOptions strokeOptions(state->lineWidth, state->lineJoin,
+                              state->lineCap, state->miterLimit,
+                              state->dash.Length(), state->dash.Elements(),
+                              state->dashOffset);
 
   gfx::Rect bounds;
   if (NeedToCalculateBounds()) {
     bounds =
       gfxpath->GetStrokedBounds(strokeOptions, mTarget->GetTransform());
   }
 
   AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)->
     Stroke(gfxpath, CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget),
-           strokeOptions, DrawOptions(state.globalAlpha, UsedOperation()));
+           strokeOptions, DrawOptions(state->globalAlpha, UsedOperation()));
 
   Redraw();
 }
 
 void
 BasicRenderingContext2D::Clip(const CanvasWindingRule& aWinding)
 {
   EnsureUserSpacePath(aWinding);
 
   if (!mPath) {
     return;
   }
 
   mTarget->PushClip(mPath);
-  CurrentState().clipsAndTransforms.AppendElement(ClipState(mPath));
+  CurrentState()->clipsAndTransforms.AppendElement(ClipState(mPath));
 }
 
 void
 BasicRenderingContext2D::Clip(const CanvasPath& aPath, const CanvasWindingRule& aWinding)
 {
   EnsureTarget();
 
   RefPtr<gfx::Path> gfxpath = aPath.GetPath(aWinding, mTarget);
 
   if (!gfxpath) {
     return;
   }
 
   mTarget->PushClip(gfxpath);
-  CurrentState().clipsAndTransforms.AppendElement(ClipState(gfxpath));
+  CurrentState()->clipsAndTransforms.AppendElement(ClipState(gfxpath));
 }
 
 bool
 BasicRenderingContext2D::IsPointInPath(double aX, double aY, const CanvasWindingRule& aWinding)
 {
   if (!FloatValidate(aX, aY)) {
     return false;
   }
@@ -1380,50 +1387,50 @@ BasicRenderingContext2D::IsPointInStroke
     return false;
   }
 
   EnsureUserSpacePath();
   if (!mPath) {
     return false;
   }
 
-  const ContextState &state = CurrentState();
+  const ContextState* state = CurrentState();
 
-  StrokeOptions strokeOptions(state.lineWidth,
-                              state.lineJoin,
-                              state.lineCap,
-                              state.miterLimit,
-                              state.dash.Length(),
-                              state.dash.Elements(),
-                              state.dashOffset);
+  StrokeOptions strokeOptions(state->lineWidth,
+                              state->lineJoin,
+                              state->lineCap,
+                              state->miterLimit,
+                              state->dash.Length(),
+                              state->dash.Elements(),
+                              state->dashOffset);
 
   if (mPathTransformWillUpdate) {
     return mPath->StrokeContainsPoint(strokeOptions, Point(aX, aY), mPathToDS);
   }
   return mPath->StrokeContainsPoint(strokeOptions, Point(aX, aY), mTarget->GetTransform());
 }
 
 bool BasicRenderingContext2D::IsPointInStroke(const CanvasPath& aPath, double aX, double aY)
 {
   if (!FloatValidate(aX, aY)) {
     return false;
   }
 
   EnsureTarget();
   RefPtr<gfx::Path> tempPath = aPath.GetPath(CanvasWindingRule::Nonzero, mTarget);
 
-  const ContextState &state = CurrentState();
+  const ContextState* state = CurrentState();
 
-  StrokeOptions strokeOptions(state.lineWidth,
-                              state.lineJoin,
-                              state.lineCap,
-                              state.miterLimit,
-                              state.dash.Length(),
-                              state.dash.Elements(),
-                              state.dashOffset);
+  StrokeOptions strokeOptions(state->lineWidth,
+                              state->lineJoin,
+                              state->lineCap,
+                              state->miterLimit,
+                              state->dash.Length(),
+                              state->dash.Elements(),
+                              state->dashOffset);
 
   return tempPath->StrokeContainsPoint(strokeOptions, Point(aX, aY), mTarget->GetTransform());
 }
 
 void
 BasicRenderingContext2D::ArcTo(double aX1, double aY1, double aX2,
                                double aY2, double aRadius,
                                ErrorResult& aError)
@@ -1569,17 +1576,17 @@ BasicRenderingContext2D::TransformWillUp
     }
     mPathTransformWillUpdate = true;
   }
 }
 
 void
 BasicRenderingContext2D::EnsureUserSpacePath(const CanvasWindingRule& aWinding)
 {
-  FillRule fillRule = CurrentState().fillRule;
+  FillRule fillRule = CurrentState()->fillRule;
   if (aWinding == CanvasWindingRule::Evenodd)
     fillRule = FillRule::FILL_EVEN_ODD;
 
   EnsureTarget();
 
   if (!mPath && !mPathBuilder && !mDSPathBuilder) {
     mPathBuilder = mTarget->CreatePathBuilder(fillRule);
   }
@@ -1627,17 +1634,17 @@ void
 BasicRenderingContext2D::EnsureWritablePath()
 {
   EnsureTarget();
 
   if (mDSPathBuilder) {
     return;
   }
 
-  FillRule fillRule = CurrentState().fillRule;
+  FillRule fillRule = CurrentState()->fillRule;
 
   if (mPathBuilder) {
     if (mPathTransformWillUpdate) {
       mPath = mPathBuilder->Finish();
       mDSPathBuilder =
         mPath->TransformedCopyToBuilder(mPathToDS, fillRule);
       mPath = nullptr;
       mPathBuilder = nullptr;
@@ -1673,23 +1680,23 @@ BasicRenderingContext2D::SetLineCap(cons
   } else if (aLinecapStyle.EqualsLiteral("round")) {
     cap = CapStyle::ROUND;
   } else if (aLinecapStyle.EqualsLiteral("square")) {
     cap = CapStyle::SQUARE;
   } else {
     // XXX ERRMSG we need to report an error to developers here! (bug 329026)
     return;
   }
-  CurrentState().lineCap = cap;
+  CurrentState()->lineCap = cap;
 }
 
 void
 BasicRenderingContext2D::GetLineCap(nsAString& aLinecapStyle)
 {
-  switch (CurrentState().lineCap) {
+  switch (CurrentState()->lineCap) {
   case CapStyle::BUTT:
     aLinecapStyle.AssignLiteral("butt");
     break;
   case CapStyle::ROUND:
     aLinecapStyle.AssignLiteral("round");
     break;
   case CapStyle::SQUARE:
     aLinecapStyle.AssignLiteral("square");
@@ -1707,23 +1714,23 @@ BasicRenderingContext2D::SetLineJoin(con
   } else if (aLinejoinStyle.EqualsLiteral("bevel")) {
     j = JoinStyle::BEVEL;
   } else if (aLinejoinStyle.EqualsLiteral("miter")) {
     j = JoinStyle::MITER_OR_BEVEL;
   } else {
     // XXX ERRMSG we need to report an error to developers here! (bug 329026)
     return;
   }
-  CurrentState().lineJoin = j;
+  CurrentState()->lineJoin = j;
 }
 
 void
 BasicRenderingContext2D::GetLineJoin(nsAString& aLinejoinStyle, ErrorResult& aError)
 {
-  switch (CurrentState().lineJoin) {
+  switch (CurrentState()->lineJoin) {
   case JoinStyle::ROUND:
     aLinejoinStyle.AssignLiteral("round");
     break;
   case JoinStyle::BEVEL:
     aLinejoinStyle.AssignLiteral("bevel");
     break;
   case JoinStyle::MITER_OR_BEVEL:
     aLinejoinStyle.AssignLiteral("miter");
@@ -1755,37 +1762,37 @@ BasicRenderingContext2D::SetLineDash(con
     for (uint32_t x = 0; x < aSegments.Length(); x++) {
       if (!dash.AppendElement(aSegments[x], fallible)) {
         aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
         return;
       }
     }
   }
 
-  CurrentState().dash = Move(dash);
+  CurrentState()->dash = Move(dash);
 }
 
 void
 BasicRenderingContext2D::GetLineDash(nsTArray<double>& aSegments) const {
-  const nsTArray<mozilla::gfx::Float>& dash = CurrentState().dash;
+  const nsTArray<mozilla::gfx::Float>& dash = CurrentState()->dash;
   aSegments.Clear();
 
   for (uint32_t x = 0; x < dash.Length(); x++) {
     aSegments.AppendElement(dash[x]);
   }
 }
 
 void
 BasicRenderingContext2D::SetLineDashOffset(double aOffset) {
-  CurrentState().dashOffset = aOffset;
+  CurrentState()->dashOffset = aOffset;
 }
 
 double
 BasicRenderingContext2D::LineDashOffset() const {
-  return CurrentState().dashOffset;
+  return CurrentState()->dashOffset;
 }
 
 // Returns a surface that contains only the part needed to draw aSourceRect.
 // On entry, aSourceRect is relative to aSurface, and on return aSourceRect is
 // relative to the returned surface.
 static already_AddRefed<SourceSurface>
 ExtractSubrect(SourceSurface* aSurface, gfx::Rect* aSourceRect, DrawTarget* aTargetDT)
 {
@@ -1860,17 +1867,17 @@ BasicRenderingContext2D::DrawDirectlyToC
                        Scale(1.0 / contextScale.width,
                              1.0 / contextScale.height).
                        Translate(aDest.x - aSrc.x, aDest.y - aSrc.y));
 
   // FLAG_CLAMP is added for increased performance, since we never tile here.
   uint32_t modifiedFlags = aImage.mDrawingFlags | imgIContainer::FLAG_CLAMP;
 
   CSSIntSize sz(scaledImageSize.width, scaledImageSize.height); // XXX hmm is scaledImageSize really in CSS pixels?
-  SVGImageContext svgContext(sz, Nothing(), CurrentState().globalAlpha);
+  SVGImageContext svgContext(sz, Nothing(), CurrentState()->globalAlpha);
 
   auto result = aImage.mImgContainer->
     Draw(context, scaledImageSize,
          ImageRegion::Create(gfxRect(aSrc.x, aSrc.y, aSrc.width, aSrc.height)),
          aImage.mWhichFrame, SamplingFilter::GOOD, Some(svgContext), modifiedFlags, 1.0);
 
   if (result != DrawResult::SUCCESS) {
     NS_WARNING("imgIContainer::Draw failed");
@@ -2209,17 +2216,17 @@ BasicRenderingContext2D::DrawImage(const
       aDw <= 0.0 || aDh <= 0.0) {
     // source and/or destination are fully clipped, so nothing is painted
     return;
   }
 
   SamplingFilter samplingFilter;
   AntialiasMode antialiasMode;
 
-  if (CurrentState().imageSmoothingEnabled) {
+  if (CurrentState()->imageSmoothingEnabled) {
     samplingFilter = gfx::SamplingFilter::LINEAR;
     antialiasMode = AntialiasMode::DEFAULT;
   } else {
     samplingFilter = gfx::SamplingFilter::POINT;
     antialiasMode = AntialiasMode::NONE;
   }
 
   gfx::Rect bounds;
@@ -2249,17 +2256,17 @@ BasicRenderingContext2D::DrawImage(const
     if (!tempTarget) {
       gfxDevCrash(LogReason::InvalidDrawTarget) << "Invalid adjusted target in Canvas2D " << gfx::hexa((DrawTarget*)mTarget) << ", " << NeedToDrawShadow() << NeedToApplyFilter();
       return;
     }
     tempTarget->DrawSurface(srcSurf,
                   gfx::Rect(aDx, aDy, aDw, aDh),
                   sourceRect,
                   DrawSurfaceOptions(samplingFilter, SamplingBounds::UNBOUNDED),
-                  DrawOptions(CurrentState().globalAlpha, UsedOperation(), antialiasMode));
+                  DrawOptions(CurrentState()->globalAlpha, UsedOperation(), antialiasMode));
   } else {
     DrawDirectlyToCanvas(drawInfo, &bounds,
                          gfx::Rect(aDx, aDy, aDw, aDh),
                          gfx::Rect(aSx, aSy, aSw, aSh),
                          imgSize);
   }
 
   RedrawUser(gfxRect(aDx, aDy, aDw, aDh));
@@ -2269,58 +2276,58 @@ Pattern&
 CanvasGeneralPattern::ForStyle(BasicRenderingContext2D* aCtx,
                                BasicRenderingContext2D::Style aStyle,
                                DrawTarget* aRT)
 {
   // This should only be called once or the mPattern destructor will
   // not be executed.
   NS_ASSERTION(!mPattern.GetPattern(), "ForStyle() should only be called once on CanvasGeneralPattern!");
 
-  const BasicRenderingContext2D::ContextState &state = aCtx->CurrentState();
+  const BasicRenderingContext2D::ContextState* state = aCtx->CurrentState();
 
-  if (state.StyleIsColor(aStyle)) {
-    mPattern.InitColorPattern(ToDeviceColor(state.colorStyles[aStyle]));
-  } else if (state.gradientStyles[aStyle] &&
-             state.gradientStyles[aStyle]->GetType() == CanvasGradient::Type::LINEAR) {
+  if (state->StyleIsColor(aStyle)) {
+    mPattern.InitColorPattern(ToDeviceColor(state->colorStyles[aStyle]));
+  } else if (state->gradientStyles[aStyle] &&
+             state->gradientStyles[aStyle]->GetType() == CanvasGradient::Type::LINEAR) {
     CanvasLinearGradient *gradient =
-      static_cast<CanvasLinearGradient*>(state.gradientStyles[aStyle].get());
+      static_cast<CanvasLinearGradient*>(state->gradientStyles[aStyle].get());
 
     mPattern.InitLinearGradientPattern(gradient->mBegin, gradient->mEnd,
                                        gradient->GetGradientStopsForTarget(aRT));
-  } else if (state.gradientStyles[aStyle] &&
-             state.gradientStyles[aStyle]->GetType() == CanvasGradient::Type::RADIAL) {
+  } else if (state->gradientStyles[aStyle] &&
+             state->gradientStyles[aStyle]->GetType() == CanvasGradient::Type::RADIAL) {
     CanvasRadialGradient *gradient =
-      static_cast<CanvasRadialGradient*>(state.gradientStyles[aStyle].get());
+      static_cast<CanvasRadialGradient*>(state->gradientStyles[aStyle].get());
 
     mPattern.InitRadialGradientPattern(gradient->mCenter1, gradient->mCenter2,
                                        gradient->mRadius1, gradient->mRadius2,
                                        gradient->GetGradientStopsForTarget(aRT));
-  } else if (state.patternStyles[aStyle]) {
+  } else if (state->patternStyles[aStyle]) {
     if (aCtx->GetCanvasElement()) {
       CanvasUtils::DoDrawImageSecurityCheck(aCtx->GetCanvasElement(),
-                                            state.patternStyles[aStyle]->mPrincipal,
-                                            state.patternStyles[aStyle]->mForceWriteOnly,
-                                            state.patternStyles[aStyle]->mCORSUsed);
+                                            state->patternStyles[aStyle]->mPrincipal,
+                                            state->patternStyles[aStyle]->mForceWriteOnly,
+                                            state->patternStyles[aStyle]->mCORSUsed);
     }
     ExtendMode mode;
-    if (state.patternStyles[aStyle]->mRepeat == CanvasPattern::RepeatMode::NOREPEAT) {
+    if (state->patternStyles[aStyle]->mRepeat == CanvasPattern::RepeatMode::NOREPEAT) {
       mode = ExtendMode::CLAMP;
     } else {
       mode = ExtendMode::REPEAT;
     }
 
     SamplingFilter samplingFilter;
-    if (state.imageSmoothingEnabled) {
+    if (state->imageSmoothingEnabled) {
       samplingFilter = SamplingFilter::GOOD;
     } else {
       samplingFilter = SamplingFilter::POINT;
     }
 
-    mPattern.InitSurfacePattern(state.patternStyles[aStyle]->mSurface, mode,
-                                state.patternStyles[aStyle]->mTransform,
+    mPattern.InitSurfacePattern(state->patternStyles[aStyle]->mSurface, mode,
+                                state->patternStyles[aStyle]->mTransform,
                                 samplingFilter);
   }
 
   return *mPattern.GetPattern();
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/canvas/BasicRenderingContext2D.h
+++ b/dom/canvas/BasicRenderingContext2D.h
@@ -1,29 +1,27 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef BasicRenderingContext2D_h
 #define BasicRenderingContext2D_h
 
-#include "FilterSupport.h"
 #include "gfxTextRun.h"
 #include "Layers.h"
 #include "mozilla/dom/CanvasGradient.h"
 #include "mozilla/dom/CanvasPattern.h"
 #include "mozilla/dom/CanvasRenderingContext2DBinding.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/PatternHelpers.h"
 #include "nsStyleStruct.h"
 #include "nsSVGEffects.h"
 
 using mozilla::gfx::CompositionOp;
 using mozilla::gfx::DrawTarget;
-using mozilla::gfx::FilterDescription;
 using mozilla::gfx::Matrix;
 using mozilla::gfx::Pattern;
 using mozilla::gfx::GeneralPattern;
 
 namespace mozilla {
 namespace dom {
 
  #define NS_BASICRENDERINGCONTEXT2D_IID \
@@ -129,45 +127,45 @@ public:
                     double aDx, double aDy, mozilla::ErrorResult& aError);
   void ResetTransform(mozilla::ErrorResult& aError);
 
   //
   // CanvasCompositing
   //
   double GlobalAlpha()
   {
-    return CurrentState().globalAlpha;
+    return CurrentState()->globalAlpha;
   }
 
   // Useful for silencing cast warnings
   static mozilla::gfx::Float ToFloat(double aValue) { return mozilla::gfx::Float(aValue); }
 
   void SetGlobalAlpha(double aGlobalAlpha)
   {
     if (aGlobalAlpha >= 0.0 && aGlobalAlpha <= 1.0) {
-      CurrentState().globalAlpha = ToFloat(aGlobalAlpha);
+      CurrentState()->globalAlpha = ToFloat(aGlobalAlpha);
     }
   }
   void GetGlobalCompositeOperation(nsAString& aOp,
                                    mozilla::ErrorResult& aError);
   void SetGlobalCompositeOperation(const nsAString& aOp,
                                    mozilla::ErrorResult& aError);
 
   //
   // CanvasImageSmoothing
   //
   bool ImageSmoothingEnabled()
   {
-    return CurrentState().imageSmoothingEnabled;
+    return CurrentState()->imageSmoothingEnabled;
   }
 
   void SetImageSmoothingEnabled(bool aImageSmoothingEnabled)
   {
-    if (aImageSmoothingEnabled != CurrentState().imageSmoothingEnabled) {
-      CurrentState().imageSmoothingEnabled = aImageSmoothingEnabled;
+    if (aImageSmoothingEnabled != CurrentState()->imageSmoothingEnabled) {
+      CurrentState()->imageSmoothingEnabled = aImageSmoothingEnabled;
     }
   }
 
 
   //
   // CanvasFillStrokeStyles
   //
   void
@@ -204,49 +202,49 @@ public:
                                                 const nsAString& aRepeat,
                                                 ErrorResult& aError);
 
   //
   // CanvasShadowStyles
   //
   double ShadowOffsetX()
   {
-    return CurrentState().shadowOffset.x;
+    return CurrentState()->shadowOffset.x;
   }
 
   void SetShadowOffsetX(double aShadowOffsetX)
   {
-    CurrentState().shadowOffset.x = ToFloat(aShadowOffsetX);
+    CurrentState()->shadowOffset.x = ToFloat(aShadowOffsetX);
   }
 
   double ShadowOffsetY()
   {
-    return CurrentState().shadowOffset.y;
+    return CurrentState()->shadowOffset.y;
   }
 
   void SetShadowOffsetY(double aShadowOffsetY)
   {
-    CurrentState().shadowOffset.y = ToFloat(aShadowOffsetY);
+    CurrentState()->shadowOffset.y = ToFloat(aShadowOffsetY);
   }
 
   double ShadowBlur()
   {
-    return CurrentState().shadowBlur;
+    return CurrentState()->shadowBlur;
   }
 
   void SetShadowBlur(double aShadowBlur)
   {
     if (aShadowBlur >= 0.0) {
-      CurrentState().shadowBlur = ToFloat(aShadowBlur);
+      CurrentState()->shadowBlur = ToFloat(aShadowBlur);
     }
   }
 
   void GetShadowColor(nsAString& aShadowColor)
   {
-    StyleColorToString(CurrentState().shadowColor, aShadowColor);
+    StyleColorToString(CurrentState()->shadowColor, aShadowColor);
   }
 
   void SetShadowColor(const nsAString& aShadowColor);
 
   //
   // CanvasRect
   //
   void ClearRect(double aX, double aY, double aW, double aH);
@@ -292,40 +290,40 @@ public:
   }
 
 
   //
   // CanvasPathDrawingStyles
   //
   double LineWidth()
   {
-    return CurrentState().lineWidth;
+    return CurrentState()->lineWidth;
   }
 
   void SetLineWidth(double aWidth)
   {
     if (aWidth > 0.0) {
-      CurrentState().lineWidth = ToFloat(aWidth);
+      CurrentState()->lineWidth = ToFloat(aWidth);
     }
   }
   void GetLineCap(nsAString& aLinecapStyle);
   void SetLineCap(const nsAString& aLinecapStyle);
   void GetLineJoin(nsAString& aLinejoinStyle,
                    mozilla::ErrorResult& aError);
   void SetLineJoin(const nsAString& aLinejoinStyle);
 
   double MiterLimit()
   {
-    return CurrentState().miterLimit;
+    return CurrentState()->miterLimit;
   }
 
   void SetMiterLimit(double aMiter)
   {
     if (aMiter > 0.0) {
-      CurrentState().miterLimit = ToFloat(aMiter);
+      CurrentState()->miterLimit = ToFloat(aMiter);
     }
   }
   void SetLineDash(const Sequence<double>& aSegments,
                    mozilla::ErrorResult& aRv);
   void GetLineDash(nsTArray<double>& aSegments) const;
 
   void SetLineDashOffset(double aOffset);
   double LineDashOffset() const;
@@ -439,169 +437,147 @@ protected:
       : transform(aTransform)
     {}
 
     bool IsClip() const { return !!clip; }
 
     RefPtr<mozilla::gfx::Path> clip;
     Matrix transform;
   };
-
+public:
   // state stack handling
-  class ContextState {
+  class ContextState : public nsISupports {
+  protected:
+    virtual ~ContextState() {}
+
   public:
-  ContextState() : textAlign(TextAlign::START),
-                   textBaseline(TextBaseline::ALPHABETIC),
-                   shadowColor(0),
-                   lineWidth(1.0f),
-                   miterLimit(10.0f),
-                   globalAlpha(1.0f),
-                   shadowBlur(0.0),
-                   dashOffset(0.0f),
-                   op(CompositionOp::OP_OVER),
-                   fillRule(mozilla::gfx::FillRule::FILL_WINDING),
-                   lineCap(mozilla::gfx::CapStyle::BUTT),
-                   lineJoin(mozilla::gfx::JoinStyle::MITER_OR_BEVEL),
-                   filterString(u"none"),
-                   filterSourceGraphicTainted(false),
-                   imageSmoothingEnabled(true),
-                   fontExplicitLanguage(false)
-  { }
+    ContextState() : textAlign(TextAlign::START),
+                     textBaseline(TextBaseline::ALPHABETIC),
+                     shadowColor(0),
+                     lineWidth(1.0f),
+                     miterLimit(10.0f),
+                     globalAlpha(1.0f),
+                     shadowBlur(0.0),
+                     dashOffset(0.0f),
+                     op(CompositionOp::OP_OVER),
+                     fillRule(mozilla::gfx::FillRule::FILL_WINDING),
+                     lineCap(mozilla::gfx::CapStyle::BUTT),
+                     lineJoin(mozilla::gfx::JoinStyle::MITER_OR_BEVEL),
+                     imageSmoothingEnabled(true),
+                     fontExplicitLanguage(false)
+    { }
 
-  ContextState(const ContextState& aOther)
-      : fontGroup(aOther.fontGroup),
-        fontLanguage(aOther.fontLanguage),
-        fontFont(aOther.fontFont),
-        gradientStyles(aOther.gradientStyles),
-        patternStyles(aOther.patternStyles),
-        colorStyles(aOther.colorStyles),
-        font(aOther.font),
-        textAlign(aOther.textAlign),
-        textBaseline(aOther.textBaseline),
-        shadowColor(aOther.shadowColor),
-        transform(aOther.transform),
-        shadowOffset(aOther.shadowOffset),
-        lineWidth(aOther.lineWidth),
-        miterLimit(aOther.miterLimit),
-        globalAlpha(aOther.globalAlpha),
-        shadowBlur(aOther.shadowBlur),
-        dash(aOther.dash),
-        dashOffset(aOther.dashOffset),
-        op(aOther.op),
-        fillRule(aOther.fillRule),
-        lineCap(aOther.lineCap),
-        lineJoin(aOther.lineJoin),
-        filterString(aOther.filterString),
-        filterChain(aOther.filterChain),
-        filterChainObserver(aOther.filterChainObserver),
-        filter(aOther.filter),
-        filterAdditionalImages(aOther.filterAdditionalImages),
-        filterSourceGraphicTainted(aOther.filterSourceGraphicTainted),
-        imageSmoothingEnabled(aOther.imageSmoothingEnabled),
-        fontExplicitLanguage(aOther.fontExplicitLanguage)
-  { }
+    explicit ContextState(const ContextState* aOther)
+        : fontGroup(aOther->fontGroup),
+          fontLanguage(aOther->fontLanguage),
+          fontFont(aOther->fontFont),
+          gradientStyles(aOther->gradientStyles),
+          patternStyles(aOther->patternStyles),
+          colorStyles(aOther->colorStyles),
+          font(aOther->font),
+          textAlign(aOther->textAlign),
+          textBaseline(aOther->textBaseline),
+          shadowColor(aOther->shadowColor),
+          transform(aOther->transform),
+          shadowOffset(aOther->shadowOffset),
+          lineWidth(aOther->lineWidth),
+          miterLimit(aOther->miterLimit),
+          globalAlpha(aOther->globalAlpha),
+          shadowBlur(aOther->shadowBlur),
+          dash(aOther->dash),
+          dashOffset(aOther->dashOffset),
+          op(aOther->op),
+          fillRule(aOther->fillRule),
+          lineCap(aOther->lineCap),
+          lineJoin(aOther->lineJoin),
+          imageSmoothingEnabled(aOther->imageSmoothingEnabled),
+          fontExplicitLanguage(aOther->fontExplicitLanguage)
+    { }
 
-  void SetColorStyle(Style aWhichStyle, nscolor aColor)
-  {
-    colorStyles[aWhichStyle] = aColor;
-    gradientStyles[aWhichStyle] = nullptr;
-    patternStyles[aWhichStyle] = nullptr;
-  }
-
-  void SetPatternStyle(Style aWhichStyle, CanvasPattern* aPat)
-  {
-    gradientStyles[aWhichStyle] = nullptr;
-    patternStyles[aWhichStyle] = aPat;
-  }
+    void SetColorStyle(Style aWhichStyle, nscolor aColor)
+    {
+      colorStyles[aWhichStyle] = aColor;
+      gradientStyles[aWhichStyle] = nullptr;
+      patternStyles[aWhichStyle] = nullptr;
+    }
 
-  void SetGradientStyle(Style aWhichStyle, CanvasGradient* aGrad)
-  {
-    gradientStyles[aWhichStyle] = aGrad;
-    patternStyles[aWhichStyle] = nullptr;
-  }
+    void SetPatternStyle(Style aWhichStyle, CanvasPattern* aPat)
+    {
+      gradientStyles[aWhichStyle] = nullptr;
+      patternStyles[aWhichStyle] = aPat;
+    }
 
-  /**
-    * returns true iff the given style is a solid color.
-    */
-  bool StyleIsColor(Style aWhichStyle) const
-  {
-    return !(patternStyles[aWhichStyle] || gradientStyles[aWhichStyle]);
-  }
+    void SetGradientStyle(Style aWhichStyle, CanvasGradient* aGrad)
+    {
+      gradientStyles[aWhichStyle] = aGrad;
+      patternStyles[aWhichStyle] = nullptr;
+    }
 
-  int32_t ShadowBlurRadius() const
-  {
-    static const gfxFloat GAUSSIAN_SCALE_FACTOR = (3 * sqrt(2 * M_PI) / 4) * 1.5;
-    return (int32_t)floor(ShadowBlurSigma() * GAUSSIAN_SCALE_FACTOR + 0.5);
-  }
-
-  mozilla::gfx::Float ShadowBlurSigma() const
-  {
-    return std::min(SIGMA_MAX, shadowBlur / 2.0f);
-  }
+    /**
+      * returns true iff the given style is a solid color.
+      */
+    bool StyleIsColor(Style aWhichStyle) const
+    {
+      return !(patternStyles[aWhichStyle] || gradientStyles[aWhichStyle]);
+    }
 
-  nsTArray<ClipState> clipsAndTransforms;
-
-  RefPtr<gfxFontGroup> fontGroup;
-  nsCOMPtr<nsIAtom> fontLanguage;
-  nsFont fontFont;
+    int32_t ShadowBlurRadius() const
+    {
+      static const gfxFloat GAUSSIAN_SCALE_FACTOR = (3 * sqrt(2 * M_PI) / 4) * 1.5;
+      return (int32_t)floor(ShadowBlurSigma() * GAUSSIAN_SCALE_FACTOR + 0.5);
+    }
 
-  EnumeratedArray<Style, Style::MAX, RefPtr<CanvasGradient>> gradientStyles;
-  EnumeratedArray<Style, Style::MAX, RefPtr<CanvasPattern>> patternStyles;
-  EnumeratedArray<Style, Style::MAX, nscolor> colorStyles;
+    mozilla::gfx::Float ShadowBlurSigma() const
+    {
+      return std::min(SIGMA_MAX, shadowBlur / 2.0f);
+    }
 
-  nsString font;
-  TextAlign textAlign;
-  TextBaseline textBaseline;
+    NS_DECL_ISUPPORTS
 
-  nscolor shadowColor;
+    nsTArray<ClipState> clipsAndTransforms;
+
+    RefPtr<gfxFontGroup> fontGroup;
+    nsCOMPtr<nsIAtom> fontLanguage;
+    nsFont fontFont;
 
-  Matrix transform;
-  mozilla::gfx::Point shadowOffset;
-  mozilla::gfx::Float lineWidth;
-  mozilla::gfx::Float miterLimit;
-  mozilla::gfx::Float globalAlpha;
-  mozilla::gfx::Float shadowBlur;
-  nsTArray<mozilla::gfx::Float> dash;
-  mozilla::gfx::Float dashOffset;
+    EnumeratedArray<Style, Style::MAX, RefPtr<CanvasGradient>> gradientStyles;
+    EnumeratedArray<Style, Style::MAX, RefPtr<CanvasPattern>> patternStyles;
+    EnumeratedArray<Style, Style::MAX, nscolor> colorStyles;
 
-  CompositionOp op;
-  mozilla::gfx::FillRule fillRule;
-  mozilla::gfx::CapStyle lineCap;
-  mozilla::gfx::JoinStyle lineJoin;
+    nsString font;
+    TextAlign textAlign;
+    TextBaseline textBaseline;
+
+    nscolor shadowColor;
 
-  nsString filterString;
-  nsTArray<nsStyleFilter> filterChain;
-  RefPtr<nsSVGFilterChainObserver> filterChainObserver;
-  mozilla::gfx::FilterDescription filter;
-  nsTArray<RefPtr<mozilla::gfx::SourceSurface>> filterAdditionalImages;
+    Matrix transform;
+    mozilla::gfx::Point shadowOffset;
+    mozilla::gfx::Float lineWidth;
+    mozilla::gfx::Float miterLimit;
+    mozilla::gfx::Float globalAlpha;
+    mozilla::gfx::Float shadowBlur;
+    nsTArray<mozilla::gfx::Float> dash;
+    mozilla::gfx::Float dashOffset;
 
-  // This keeps track of whether the canvas was "tainted" or not when
-  // we last used a filter. This is a security measure, whereby the
-  // canvas is flipped to write-only if a cross-origin image is drawn to it.
-  // This is to stop bad actors from reading back data they shouldn't have
-  // access to.
-  //
-  // This also limits what filters we can apply to the context; in particular
-  // feDisplacementMap is restricted.
-  //
-  // We keep track of this to ensure that if this gets out of sync with the
-  // tainted state of the canvas itself, we update our filters accordingly.
-  bool filterSourceGraphicTainted;
+    CompositionOp op;
+    mozilla::gfx::FillRule fillRule;
+    mozilla::gfx::CapStyle lineCap;
+    mozilla::gfx::JoinStyle lineJoin;
 
-  bool imageSmoothingEnabled;
-  bool fontExplicitLanguage;
+    bool imageSmoothingEnabled;
+    bool fontExplicitLanguage;
   };
-
+protected:
   // Member vars
 
   RenderingMode mRenderingMode;
 
   layers::LayersBackend mCompositorBackend;
 
-  AutoTArray<ContextState, 3> mStyleStack;
+  AutoTArray<RefPtr<ContextState>, 3> mStyleStack;
 
   int32_t mWidth, mHeight;
 
   RefPtr<mozilla::layers::PersistentBufferProvider> mBufferProvider;
 
   /**
     * We also have a device space pathbuilder. The reason for this is as
     * follows, when a path is being built, but the transform changes, we
@@ -668,16 +644,20 @@ protected:
   static uint32_t sNumLivingContexts;
   static int64_t sCanvasAzureMemoryUsed;
 
   bool mHasPendingStableStateCallback;
 
   static mozilla::gfx::DrawTarget* sErrorTarget;
 
 protected:
+  virtual ContextState* CreateContextState(const ContextState* aOther = nullptr) {
+    return aOther ? new ContextState(aOther) : new ContextState();
+  }
+
   virtual HTMLCanvasElement* GetCanvasElement() = 0;
 
   NS_IMETHOD Reset();
 
   RefPtr<CanvasShutdownObserver> mShutdownObserver;
   void RemoveShutdownObserver();
   bool AlreadyShutDown() const { return !mShutdownObserver; }
 
@@ -748,22 +728,22 @@ protected:
 
   /**
    * Check if the target is valid after calling EnsureTarget.
    */
   bool IsTargetValid() const {
     return !!mTarget && mTarget != sErrorTarget;
   }
 
-  inline ContextState& CurrentState() {
-    return mStyleStack[mStyleStack.Length() - 1];
+  inline ContextState* CurrentState() {
+    return mStyleStack[mStyleStack.Length() - 1].get();
   }
 
-  inline const ContextState& CurrentState() const {
-    return mStyleStack[mStyleStack.Length() - 1];
+  inline const ContextState* CurrentState() const {
+    return mStyleStack[mStyleStack.Length() - 1].get();
   }
 
   void DrawImage(const CanvasImageSource& aImgElt,
                  double aSx, double aSy, double aSw, double aSh,
                  double aDx, double aDy, double aDw, double aDh,
                  uint8_t aOptional_argc, mozilla::ErrorResult& aError);
 
   /**
@@ -776,22 +756,22 @@ protected:
 
   // Some helpers.  Doesn't modify a color on failure.
   void SetStyleFromUnion(const StringOrCanvasGradientOrCanvasPattern& aValue,
                          Style aWhichStyle);
   void SetStyleFromString(const nsAString& aStr, Style aWhichStyle);
 
   void SetStyleFromGradient(CanvasGradient& aGradient, Style aWhichStyle)
   {
-    CurrentState().SetGradientStyle(aWhichStyle, &aGradient);
+    CurrentState()->SetGradientStyle(aWhichStyle, &aGradient);
   }
 
   void SetStyleFromPattern(CanvasPattern& aPattern, Style aWhichStyle)
   {
-    CurrentState().SetPatternStyle(aWhichStyle, &aPattern);
+    CurrentState()->SetPatternStyle(aWhichStyle, &aPattern);
   }
 
   // Returns whether a color was successfully parsed.
   virtual bool ParseColor(const nsAString& aString, nscolor* aColor) = 0;
 
   static void StyleColorToString(const nscolor& aColor, nsAString& aStr);
 
   void GetStyleAsUnion(OwningStringOrCanvasGradientOrCanvasPattern& aValue,
@@ -806,31 +786,31 @@ protected:
 
   mozilla::gfx::CompositionOp UsedOperation()
   {
     if (NeedToDrawShadow() || NeedToApplyFilter()) {
       // In this case the shadow or filter rendering will use the operator.
       return mozilla::gfx::CompositionOp::OP_OVER;
     }
 
-    return CurrentState().op;
+    return CurrentState()->op;
   }
 
   /**
     * Returns true if a shadow should be drawn along with a
     * drawing operation.
     */
   bool NeedToDrawShadow()
   {
-    const ContextState& state = CurrentState();
+    const ContextState* state = CurrentState();
 
     // The spec says we should not draw shadows if the operator is OVER.
     // If it's over and the alpha value is zero, nothing needs to be drawn.
-    return NS_GET_A(state.shadowColor) != 0 &&
-      (state.shadowBlur != 0.f || state.shadowOffset.x != 0.f || state.shadowOffset.y != 0.f);
+    return NS_GET_A(state->shadowColor) != 0 &&
+      (state->shadowBlur != 0.f || state->shadowOffset.x != 0.f || state->shadowOffset.y != 0.f);
   }
 
   /**
     * Returns true if the result of a drawing operation should be
     * drawn with a filter.
     */
   virtual bool NeedToApplyFilter() = 0;
 
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -147,16 +147,21 @@ using namespace mozilla::css;
 using namespace mozilla::gfx;
 using namespace mozilla::image;
 using namespace mozilla::ipc;
 using namespace mozilla::layers;
 
 namespace mozilla {
 namespace dom {
 
+typedef BasicRenderingContext2D::ContextState ContextState;
+typedef CanvasRenderingContext2D::ContextStateForFilter ContextStateForFilter;
+
+NS_IMPL_ISUPPORTS_INHERITED0(ContextStateForFilter, ContextState)
+
 class CanvasDrawObserver
 {
 public:
   explicit CanvasDrawObserver(CanvasRenderingContext2D* aCanvasContext);
 
   // Only enumerate draw calls that could affect the heuristic
   enum DrawCallType {
     PutImageData,
@@ -321,17 +326,17 @@ public:
   {
   }
 
   virtual void DoUpdate() override
   {
     if (!mContext) {
       MOZ_CRASH("GFX: This should never be called without a context");
     }
-    // Refresh the cached FilterDescription in mContext->CurrentState().filter.
+    // Refresh the cached FilterDescription in mContext->CurrentStateForFilter()->filter.
     // If this filter is not at the top of the state stack, we'll refresh the
     // wrong filter, but that's ok, because we'll refresh the right filter
     // when we pop the state stack in CanvasRenderingContext2D::Restore().
     mContext->UpdateFilter();
   }
 
   void DetachFromContext() { mContext = nullptr; }
 
@@ -345,44 +350,46 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(CanvasR
 NS_IMPL_CYCLE_COLLECTION_CLASS(CanvasRenderingContext2D)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CanvasRenderingContext2D)
   // Make sure we remove ourselves from the list of demotable contexts (raw pointers),
   // since we're logically destructed at this point.
   CanvasRenderingContext2D::RemoveDemotableContext(tmp);
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mCanvasElement)
   for (uint32_t i = 0; i < tmp->mStyleStack.Length(); i++) {
-    ImplCycleCollectionUnlink(tmp->mStyleStack[i].patternStyles[Style::STROKE]);
-    ImplCycleCollectionUnlink(tmp->mStyleStack[i].patternStyles[Style::FILL]);
-    ImplCycleCollectionUnlink(tmp->mStyleStack[i].gradientStyles[Style::STROKE]);
-    ImplCycleCollectionUnlink(tmp->mStyleStack[i].gradientStyles[Style::FILL]);
+    ImplCycleCollectionUnlink(tmp->mStyleStack[i]->patternStyles[Style::STROKE]);
+    ImplCycleCollectionUnlink(tmp->mStyleStack[i]->patternStyles[Style::FILL]);
+    ImplCycleCollectionUnlink(tmp->mStyleStack[i]->gradientStyles[Style::STROKE]);
+    ImplCycleCollectionUnlink(tmp->mStyleStack[i]->gradientStyles[Style::FILL]);
+    ContextStateForFilter* contextForFilter = static_cast<ContextStateForFilter*>(tmp->mStyleStack[i].get());
     CanvasFilterChainObserver *filterChainObserver =
-      static_cast<CanvasFilterChainObserver*>(tmp->mStyleStack[i].filterChainObserver.get());
+      static_cast<CanvasFilterChainObserver*>(contextForFilter->filterChainObserver.get());
     if (filterChainObserver) {
       filterChainObserver->DetachFromContext();
     }
-    ImplCycleCollectionUnlink(tmp->mStyleStack[i].filterChainObserver);
+    ImplCycleCollectionUnlink(contextForFilter->filterChainObserver);
   }
   for (size_t x = 0 ; x < tmp->mHitRegionsOptions.Length(); x++) {
     RegionInfo& info = tmp->mHitRegionsOptions[x];
     if (info.mElement) {
       ImplCycleCollectionUnlink(info.mElement);
     }
   }
   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CanvasRenderingContext2D)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCanvasElement)
   for (uint32_t i = 0; i < tmp->mStyleStack.Length(); i++) {
-    ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].patternStyles[Style::STROKE], "Stroke CanvasPattern");
-    ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].patternStyles[Style::FILL], "Fill CanvasPattern");
-    ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].gradientStyles[Style::STROKE], "Stroke CanvasGradient");
-    ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].gradientStyles[Style::FILL], "Fill CanvasGradient");
-    ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].filterChainObserver, "Filter Chain Observer");
+    ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i]->patternStyles[Style::STROKE], "Stroke CanvasPattern");
+    ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i]->patternStyles[Style::FILL], "Fill CanvasPattern");
+    ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i]->gradientStyles[Style::STROKE], "Stroke CanvasGradient");
+    ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i]->gradientStyles[Style::FILL], "Fill CanvasGradient");
+    ContextStateForFilter* contextForFilter = static_cast<ContextStateForFilter*>(tmp->mStyleStack[i].get());
+    ImplCycleCollectionTraverse(cb, contextForFilter->filterChainObserver, "Filter Chain Observer");
   }
   for (size_t x = 0 ; x < tmp->mHitRegionsOptions.Length(); x++) {
     RegionInfo& info = tmp->mHitRegionsOptions[x];
     if (info.mElement) {
       ImplCycleCollectionTraverse(cb, info.mElement, "Hit region fallback element");
     }
   }
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
@@ -920,35 +927,33 @@ CanvasRenderingContext2D::SetDimensions(
 
   return NS_OK;
 }
 
 void
 CanvasRenderingContext2D::ClearTarget()
 {
   Reset();
-
   mResetLayer = true;
 
   SetInitialState();
-
   // For vertical writing-mode, unless text-orientation is sideways,
   // we'll modify the initial value of textBaseline to 'middle'.
   RefPtr<nsStyleContext> canvasStyle;
   if (mCanvasElement && mCanvasElement->IsInUncomposedDoc()) {
     nsCOMPtr<nsIPresShell> presShell = GetPresShell();
     if (presShell) {
       canvasStyle =
         nsComputedDOMStyle::GetStyleContextForElement(mCanvasElement,
                                                       nullptr,
                                                       presShell);
       if (canvasStyle) {
         WritingMode wm(canvasStyle);
         if (wm.IsVertical() && !wm.IsSideways()) {
-          CurrentState().textBaseline = TextBaseline::MIDDLE;
+          CurrentStateForFilter()->textBaseline = TextBaseline::MIDDLE;
         }
       }
     }
   }
 }
 
 NS_IMETHODIMP
 CanvasRenderingContext2D::InitializeWithDrawTarget(nsIDocShell* aShell,
@@ -1232,23 +1237,23 @@ CanvasRenderingContext2D::SetFillRule(co
 
   if (aString.EqualsLiteral("evenodd"))
     rule = FillRule::FILL_EVEN_ODD;
   else if (aString.EqualsLiteral("nonzero"))
     rule = FillRule::FILL_WINDING;
   else
     return;
 
-  CurrentState().fillRule = rule;
+  CurrentStateForFilter()->fillRule = rule;
 }
 
 void
 CanvasRenderingContext2D::GetFillRule(nsAString& aString)
 {
-  switch (CurrentState().fillRule) {
+  switch (CurrentStateForFilter()->fillRule) {
   case FillRule::FILL_WINDING:
     aString.AssignLiteral("nonzero"); break;
   case FillRule::FILL_EVEN_ODD:
     aString.AssignLiteral("evenodd"); break;
   }
 }
 
 //
@@ -1508,21 +1513,21 @@ CanvasRenderingContext2D::ParseFilter(co
   return true;
 }
 
 void
 CanvasRenderingContext2D::SetFilter(const nsAString& aFilter, ErrorResult& aError)
 {
   nsTArray<nsStyleFilter> filterChain;
   if (ParseFilter(aFilter, filterChain, aError)) {
-    CurrentState().filterString = aFilter;
-    filterChain.SwapElements(CurrentState().filterChain);
+    CurrentStateForFilter()->filterString = aFilter;
+    filterChain.SwapElements(CurrentStateForFilter()->filterChain);
     if (mCanvasElement) {
-      CurrentState().filterChainObserver =
-        new CanvasFilterChainObserver(CurrentState().filterChain,
+      CurrentStateForFilter()->filterChainObserver =
+        new CanvasFilterChainObserver(CurrentStateForFilter()->filterChain,
                                       mCanvasElement, this);
       UpdateFilter();
     }
   }
 }
 
 class CanvasUserSpaceMetrics : public UserSpaceMetricsWithSize
 {
@@ -1569,42 +1574,42 @@ private:
 
 void
 CanvasRenderingContext2D::UpdateFilter()
 {
   nsCOMPtr<nsIPresShell> presShell = GetPresShell();
   if (!presShell || presShell->IsDestroying()) {
     // Ensure we set an empty filter and update the state to
     // reflect the current "taint" status of the canvas
-    CurrentState().filter = FilterDescription();
-    CurrentState().filterSourceGraphicTainted =
+    CurrentStateForFilter()->filter = FilterDescription();
+    CurrentStateForFilter()->filterSourceGraphicTainted =
       (mCanvasElement && mCanvasElement->IsWriteOnly());
     return;
   }
 
   // The filter might reference an SVG filter that is declared inside this
   // document. Flush frames so that we'll have an nsSVGFilterFrame to work
   // with.
   presShell->FlushPendingNotifications(FlushType::Frames);
 
   bool sourceGraphicIsTainted =
     (mCanvasElement && mCanvasElement->IsWriteOnly());
 
-  CurrentState().filter =
+  CurrentStateForFilter()->filter =
     nsFilterInstance::GetFilterDescription(mCanvasElement,
-      CurrentState().filterChain,
+      CurrentStateForFilter()->filterChain,
       sourceGraphicIsTainted,
       CanvasUserSpaceMetrics(GetSize(),
-                             CurrentState().fontFont,
-                             CurrentState().fontLanguage,
-                             CurrentState().fontExplicitLanguage,
+                             CurrentStateForFilter()->fontFont,
+                             CurrentStateForFilter()->fontLanguage,
+                             CurrentStateForFilter()->fontExplicitLanguage,
                              presShell->GetPresContext()),
       gfxRect(0, 0, mWidth, mHeight),
-      CurrentState().filterAdditionalImages);
-  CurrentState().filterSourceGraphicTainted = sourceGraphicIsTainted;
+      CurrentStateForFilter()->filterAdditionalImages);
+  CurrentStateForFilter()->filterSourceGraphicTainted = sourceGraphicIsTainted;
 }
 
 //
 // path bits
 //
 
 void CanvasRenderingContext2D::DrawFocusIfNeeded(mozilla::dom::Element& aElement,
                                                  ErrorResult& aRv)
@@ -1614,45 +1619,45 @@ void CanvasRenderingContext2D::DrawFocus
   if (!mPath) {
     return;
   }
 
   if (DrawCustomFocusRing(aElement)) {
     Save();
 
     // set state to conforming focus state
-    ContextState& state = CurrentState();
-    state.globalAlpha = 1.0;
-    state.shadowBlur = 0;
-    state.shadowOffset.x = 0;
-    state.shadowOffset.y = 0;
-    state.op = mozilla::gfx::CompositionOp::OP_OVER;
-
-    state.lineCap = CapStyle::BUTT;
-    state.lineJoin = mozilla::gfx::JoinStyle::MITER_OR_BEVEL;
-    state.lineWidth = 1;
-    CurrentState().dash.Clear();
+    ContextState* state = CurrentStateForFilter();
+    state->globalAlpha = 1.0;
+    state->shadowBlur = 0;
+    state->shadowOffset.x = 0;
+    state->shadowOffset.y = 0;
+    state->op = mozilla::gfx::CompositionOp::OP_OVER;
+
+    state->lineCap = CapStyle::BUTT;
+    state->lineJoin = mozilla::gfx::JoinStyle::MITER_OR_BEVEL;
+    state->lineWidth = 1;
+    CurrentStateForFilter()->dash.Clear();
 
     // color and style of the rings is the same as for image maps
     // set the background focus color
-    CurrentState().SetColorStyle(Style::STROKE, NS_RGBA(255, 255, 255, 255));
+    CurrentStateForFilter()->SetColorStyle(Style::STROKE, NS_RGBA(255, 255, 255, 255));
     // draw the focus ring
     Stroke();
 
     // set dashing for foreground
-    nsTArray<mozilla::gfx::Float>& dash = CurrentState().dash;
+    nsTArray<mozilla::gfx::Float>& dash = CurrentStateForFilter()->dash;
     for (uint32_t i = 0; i < 2; ++i) {
       if (!dash.AppendElement(1, fallible)) {
         aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
         return;
       }
     }
 
     // set the foreground focus color
-    CurrentState().SetColorStyle(Style::STROKE, NS_RGBA(0,0,0, 255));
+    CurrentStateForFilter()->SetColorStyle(Style::STROKE, NS_RGBA(0,0,0, 255));
     // draw the focus ring
     Stroke();
 
     Restore();
   }
 }
 
 bool CanvasRenderingContext2D::DrawCustomFocusRing(mozilla::dom::Element& aElement)
@@ -1746,46 +1751,46 @@ CanvasRenderingContext2D::SetFontInterna
   params.language = fontStyle->mLanguage;
   params.explicitLanguage = fontStyle->mExplicitLanguage;
   params.userFontSet = c->GetUserFontSet();
   params.textPerf = c->GetTextPerfMetrics();
   RefPtr<nsFontMetrics> metrics =
     c->DeviceContext()->GetMetricsFor(resizedFont, params);
 
   gfxFontGroup* newFontGroup = metrics->GetThebesFontGroup();
-  CurrentState().fontGroup = newFontGroup;
-  NS_ASSERTION(CurrentState().fontGroup, "Could not get font group");
-  CurrentState().font = usedFont;
-  CurrentState().fontFont = fontStyle->mFont;
-  CurrentState().fontFont.size = fontStyle->mSize;
-  CurrentState().fontLanguage = fontStyle->mLanguage;
-  CurrentState().fontExplicitLanguage = fontStyle->mExplicitLanguage;
+  CurrentStateForFilter()->fontGroup = newFontGroup;
+  NS_ASSERTION(CurrentStateForFilter()->fontGroup, "Could not get font group");
+  CurrentStateForFilter()->font = usedFont;
+  CurrentStateForFilter()->fontFont = fontStyle->mFont;
+  CurrentStateForFilter()->fontFont.size = fontStyle->mSize;
+  CurrentStateForFilter()->fontLanguage = fontStyle->mLanguage;
+  CurrentStateForFilter()->fontExplicitLanguage = fontStyle->mExplicitLanguage;
 
   return true;
 }
 
 void
 CanvasRenderingContext2D::SetTextAlign(const nsAString& aTextAlign)
 {
   if (aTextAlign.EqualsLiteral("start"))
-    CurrentState().textAlign = TextAlign::START;
+    CurrentStateForFilter()->textAlign = TextAlign::START;
   else if (aTextAlign.EqualsLiteral("end"))
-    CurrentState().textAlign = TextAlign::END;
+    CurrentStateForFilter()->textAlign = TextAlign::END;
   else if (aTextAlign.EqualsLiteral("left"))
-    CurrentState().textAlign = TextAlign::LEFT;
+    CurrentStateForFilter()->textAlign = TextAlign::LEFT;
   else if (aTextAlign.EqualsLiteral("right"))
-    CurrentState().textAlign = TextAlign::RIGHT;
+    CurrentStateForFilter()->textAlign = TextAlign::RIGHT;
   else if (aTextAlign.EqualsLiteral("center"))
-    CurrentState().textAlign = TextAlign::CENTER;
+    CurrentStateForFilter()->textAlign = TextAlign::CENTER;
 }
 
 void
 CanvasRenderingContext2D::GetTextAlign(nsAString& aTextAlign)
 {
-  switch (CurrentState().textAlign)
+  switch (CurrentStateForFilter()->textAlign)
   {
   case TextAlign::START:
     aTextAlign.AssignLiteral("start");
     break;
   case TextAlign::END:
     aTextAlign.AssignLiteral("end");
     break;
   case TextAlign::LEFT:
@@ -1799,33 +1804,33 @@ CanvasRenderingContext2D::GetTextAlign(n
     break;
   }
 }
 
 void
 CanvasRenderingContext2D::SetTextBaseline(const nsAString& aTextBaseline)
 {
   if (aTextBaseline.EqualsLiteral("top"))
-    CurrentState().textBaseline = TextBaseline::TOP;
+    CurrentStateForFilter()->textBaseline = TextBaseline::TOP;
   else if (aTextBaseline.EqualsLiteral("hanging"))
-    CurrentState().textBaseline = TextBaseline::HANGING;
+    CurrentStateForFilter()->textBaseline = TextBaseline::HANGING;
   else if (aTextBaseline.EqualsLiteral("middle"))
-    CurrentState().textBaseline = TextBaseline::MIDDLE;
+    CurrentStateForFilter()->textBaseline = TextBaseline::MIDDLE;
   else if (aTextBaseline.EqualsLiteral("alphabetic"))
-    CurrentState().textBaseline = TextBaseline::ALPHABETIC;
+    CurrentStateForFilter()->textBaseline = TextBaseline::ALPHABETIC;
   else if (aTextBaseline.EqualsLiteral("ideographic"))
-    CurrentState().textBaseline = TextBaseline::IDEOGRAPHIC;
+    CurrentStateForFilter()->textBaseline = TextBaseline::IDEOGRAPHIC;
   else if (aTextBaseline.EqualsLiteral("bottom"))
-    CurrentState().textBaseline = TextBaseline::BOTTOM;
+    CurrentStateForFilter()->textBaseline = TextBaseline::BOTTOM;
 }
 
 void
 CanvasRenderingContext2D::GetTextBaseline(nsAString& aTextBaseline)
 {
-  switch (CurrentState().textBaseline)
+  switch (CurrentStateForFilter()->textBaseline)
   {
   case TextBaseline::TOP:
     aTextBaseline.AssignLiteral("top");
     break;
   case TextBaseline::HANGING:
     aTextBaseline.AssignLiteral("hanging");
     break;
   case TextBaseline::MIDDLE:
@@ -2304,17 +2309,17 @@ CanvasRenderingContext2D::DrawOrMeasureT
     }
     return NS_OK;
   }
 
   if (!IsFinite(aX) || !IsFinite(aY)) {
     return NS_OK;
   }
 
-  const ContextState &state = CurrentState();
+  const ContextState* state = CurrentStateForFilter();
 
   // This is only needed to know if we can know the drawing bounding box easily.
   bool doCalculateBounds = NeedToCalculateBounds();
 
   CanvasBidiProcessor processor;
 
   // If we don't have a style context, we can't set up vertical-text flags
   // (for now, at least; perhaps we need new Canvas API to control this).
@@ -2334,17 +2339,17 @@ CanvasRenderingContext2D::DrawOrMeasureT
   // to avoid creating a target if it's only being used to measure text sizes.
   if (mTarget) {
     processor.mDrawTarget->SetTransform(mTarget->GetTransform());
   }
   processor.mCtx = this;
   processor.mOp = aOp;
   processor.mBoundingBox = gfxRect(0, 0, 0, 0);
   processor.mDoMeasureBoundingBox = doCalculateBounds || !mIsEntireFrameInvalid;
-  processor.mState = &CurrentState();
+  processor.mState = CurrentStateForFilter();
   processor.mFontgrp = currentFontStyle;
 
   nscoord totalWidthCoord;
 
   // calls bidi algo twice since it needs the full text width and the
   // bounding boxes before rendering anything
   nsBidi bidiEngine;
   rv = nsBidiPresUtils::ProcessText(textToDraw.get(),
@@ -2369,36 +2374,36 @@ CanvasRenderingContext2D::DrawOrMeasureT
   // if only measuring, don't need to do any more work
   if (aOp==TextDrawOperation::MEASURE) {
     return NS_OK;
   }
 
   // offset pt.x based on text align
   gfxFloat anchorX;
 
-  if (state.textAlign == TextAlign::CENTER) {
+  if (state->textAlign == TextAlign::CENTER) {
     anchorX = .5;
-  } else if (state.textAlign == TextAlign::LEFT ||
-            (!isRTL && state.textAlign == TextAlign::START) ||
-            (isRTL && state.textAlign == TextAlign::END)) {
+  } else if (state->textAlign == TextAlign::LEFT ||
+            (!isRTL && state->textAlign == TextAlign::START) ||
+            (isRTL && state->textAlign == TextAlign::END)) {
     anchorX = 0;
   } else {
     anchorX = 1;
   }
 
   processor.mPt.x -= anchorX * totalWidth;
 
   // offset pt.y (or pt.x, for vertical text) based on text baseline
   processor.mFontgrp->UpdateUserFonts(); // ensure user font generation is current
   const gfxFont::Metrics& fontMetrics =
     processor.mFontgrp->GetFirstValidFont()->GetMetrics(gfxFont::eHorizontal);
 
   gfxFloat baselineAnchor;
 
-  switch (state.textBaseline)
+  switch (state->textBaseline)
   {
   case TextBaseline::HANGING:
       // fall through; best we can do with the information available
   case TextBaseline::TOP:
     baselineAnchor = fontMetrics.emAscent;
     break;
   case TextBaseline::MIDDLE:
     baselineAnchor = (fontMetrics.emAscent - fontMetrics.emDescent) * .5f;
@@ -2484,46 +2489,46 @@ CanvasRenderingContext2D::DrawOrMeasureT
 
   Redraw();
   return NS_OK;
 }
 
 gfxFontGroup *CanvasRenderingContext2D::GetCurrentFontStyle()
 {
   // use lazy initilization for the font group since it's rather expensive
-  if (!CurrentState().fontGroup) {
+  if (!CurrentStateForFilter()->fontGroup) {
     ErrorResult err;
     NS_NAMED_LITERAL_STRING(kDefaultFontStyle, "10px sans-serif");
     static float kDefaultFontSize = 10.0;
     nsCOMPtr<nsIPresShell> presShell = GetPresShell();
     bool fontUpdated = SetFontInternal(kDefaultFontStyle, err);
     if (err.Failed() || !fontUpdated) {
       err.SuppressException();
       gfxFontStyle style;
       style.size = kDefaultFontSize;
       gfxTextPerfMetrics* tp = nullptr;
       if (presShell && !presShell->IsDestroying()) {
         tp = presShell->GetPresContext()->GetTextPerfMetrics();
       }
       int32_t perDevPixel, perCSSPixel;
       GetAppUnitsValues(&perDevPixel, &perCSSPixel);
       gfxFloat devToCssSize = gfxFloat(perDevPixel) / gfxFloat(perCSSPixel);
-      CurrentState().fontGroup =
+      CurrentStateForFilter()->fontGroup =
         gfxPlatform::GetPlatform()->CreateFontGroup(FontFamilyList(eFamily_sans_serif),
                                                     &style, tp,
                                                     nullptr, devToCssSize);
-      if (CurrentState().fontGroup) {
-        CurrentState().font = kDefaultFontStyle;
+      if (CurrentStateForFilter()->fontGroup) {
+        CurrentStateForFilter()->font = kDefaultFontStyle;
       } else {
         NS_ERROR("Default canvas font is invalid");
       }
     }
   }
 
-  return CurrentState().fontGroup;
+  return CurrentStateForFilter()->fontGroup;
 }
 
 //
 // image
 //
 void
 CanvasRenderingContext2D::DrawWindow(nsGlobalWindow& aWindow, double aX,
                                      double aY, double aW, double aH,
@@ -2880,17 +2885,17 @@ CanvasRenderingContext2D::GetImageDataAr
   *aRetval = darray;
   return NS_OK;
 }
 
 void
 CanvasRenderingContext2D::FillRuleChanged()
 {
   if (mPath) {
-    mPathBuilder = mPath->CopyToBuilder(CurrentState().fillRule);
+    mPathBuilder = mPath->CopyToBuilder(CurrentStateForFilter()->fillRule);
     mPath = nullptr;
   }
 }
 
 void
 CanvasRenderingContext2D::PutImageData(ImageData& aImageData, double aDx,
                                        double aDy, ErrorResult& aError)
 {
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -1,15 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef CanvasRenderingContext2D_h
 #define CanvasRenderingContext2D_h
 
+#include "FilterSupport.h"
 #include "mozilla/Attributes.h"
 #include <vector>
 #include "nsIDOMCanvasRenderingContext2D.h"
 #include "nsICanvasRenderingContextInternal.h"
 #include "mozilla/RefPtr.h"
 #include "nsColor.h"
 #include "mozilla/dom/HTMLCanvasElement.h"
 #include "mozilla/dom/HTMLVideoElement.h"
@@ -24,16 +25,18 @@
 #include "imgIEncoder.h"
 #include "nsLayoutUtils.h"
 #include "mozilla/EnumeratedArray.h"
 #include "Layers.h"
 
 class nsGlobalWindow;
 class nsXULElement;
 
+using mozilla::gfx::FilterDescription;
+
 namespace mozilla {
 namespace gl {
 class SourceSurface;
 } // namespace gl
 
 namespace dom {
 class ImageData;
 class StringOrCanvasGradientOrCanvasPattern;
@@ -69,17 +72,17 @@ public:
     }
 
     // corresponds to changes to the old bindings made in bug 745025
     return mCanvasElement->GetOriginalCanvas();
   }
 
   void GetFilter(nsAString& aFilter)
   {
-    aFilter = CurrentState().filterString;
+    aFilter = CurrentStateForFilter()->filterString;
   }
 
   void SetFilter(const nsAString& aFilter, mozilla::ErrorResult& aError);
   void DrawFocusIfNeeded(mozilla::dom::Element& aElement, ErrorResult& aRv);
   bool DrawCustomFocusRing(mozilla::dom::Element& aElement);
   void FillText(const nsAString& aText, double aX, double aY,
                 const Optional<double>& aMaxWidth,
                 mozilla::ErrorResult& aError);
@@ -245,17 +248,67 @@ public:
 
   // Given a point, return hit region ID if it exists
   nsString GetHitRegion(const mozilla::gfx::Point& aPoint) override;
 
 
   // return true and fills in the bound rect if element has a hit region.
   bool GetHitRegionRect(Element* aElement, nsRect& aRect) override;
 
+public:
+  // state stack handling
+  class ContextStateForFilter : public ContextState {
+  protected:
+    virtual ~ContextStateForFilter() {}
+
+  public:
+    ContextStateForFilter() : filterString(u"none"),
+                              filterSourceGraphicTainted(false)
+    { }
+
+    explicit ContextStateForFilter(const ContextStateForFilter* aOther)
+        : ContextState(aOther),
+          filterString(aOther->filterString),
+          filterChain(aOther->filterChain),
+          filterChainObserver(aOther->filterChainObserver),
+          filter(aOther->filter),
+          filterAdditionalImages(aOther->filterAdditionalImages),
+          filterSourceGraphicTainted(aOther->filterSourceGraphicTainted)
+    { }
+
+    NS_DECL_ISUPPORTS_INHERITED
+
+    nsString filterString;
+    nsTArray<nsStyleFilter> filterChain;
+    RefPtr<nsSVGFilterChainObserver> filterChainObserver;
+    mozilla::gfx::FilterDescription filter;
+    nsTArray<RefPtr<mozilla::gfx::SourceSurface>> filterAdditionalImages;
+
+    // This keeps track of whether the canvas was "tainted" or not when
+    // we last used a filter. This is a security measure, whereby the
+    // canvas is flipped to write-only if a cross-origin image is drawn to it.
+    // This is to stop bad actors from reading back data they shouldn't have
+    // access to.
+    //
+    // This also limits what filters we can apply to the context; in particular
+    // feDisplacementMap is restricted.
+    //
+    // We keep track of this to ensure that if this gets out of sync with the
+    // tainted state of the canvas itself, we update our filters accordingly.
+    bool filterSourceGraphicTainted;
+  };
+
 protected:
+  ContextState* CreateContextState(const ContextState* aOther = nullptr) override {
+    return CreateContextState((ContextStateForFilter*) aOther);
+  }
+  ContextState* CreateContextState(const ContextStateForFilter* aOther = nullptr) {
+    return aOther ? new ContextStateForFilter(aOther) : new ContextStateForFilter();
+  }
+
   HTMLCanvasElement* GetCanvasElement() override { return mCanvasElement; }
   nsresult GetImageDataArray(JSContext* aCx, int32_t aX, int32_t aY,
                              uint32_t aWidth, uint32_t aHeight,
                              JSObject** aRetval);
 
   nsresult PutImageData_explicit(int32_t aX, int32_t aY, uint32_t aW, uint32_t aH,
                                  dom::Uint8ClampedArray* aArray,
                                  bool aHasDirtyRect, int32_t aDirtyX, int32_t aDirtyY,
@@ -300,28 +353,28 @@ protected:
 
   /**
     * Returns the surface format this canvas should be allocated using. Takes
     * into account mOpaque, platform requirements, etc.
     */
   mozilla::gfx::SurfaceFormat GetSurfaceFormat() const override;
 
   /**
-   * Update CurrentState().filter with the filter description for
-   * CurrentState().filterChain.
+   * Update CurrentState()->filter with the filter description for
+   * CurrentState()->filterChain.
    * Flushes the PresShell, so the world can change if you call this function.
    */
   void UpdateFilter();
 
   nsString& GetFont()
   {
     /* will initilize the value if not set, else does nothing */
     GetCurrentFontStyle();
 
-    return CurrentState().font;
+    return CurrentState()->font;
   }
 
   // This function maintains a list of raw pointers to cycle-collected
   // objects. We need to ensure that no entries persist beyond unlink,
   // since the objects are logically destructed at that point.
   static std::vector<CanvasRenderingContext2D*>& DemotableContexts();
   static void DemoteOldestContextIfNecessary();
 
@@ -405,32 +458,40 @@ protected:
   }
 
   /**
    * Calls UpdateFilter if the canvas's WriteOnly state has changed between the
    * last call to UpdateFilter and now.
    */
   const gfx::FilterDescription& EnsureUpdatedFilter() {
     bool isWriteOnly = mCanvasElement && mCanvasElement->IsWriteOnly();
-    if (CurrentState().filterSourceGraphicTainted != isWriteOnly) {
+    if (CurrentStateForFilter()->filterSourceGraphicTainted != isWriteOnly) {
       UpdateFilter();
       EnsureTarget();
     }
-    MOZ_ASSERT(CurrentState().filterSourceGraphicTainted == isWriteOnly);
-    return CurrentState().filter;
+    MOZ_ASSERT(CurrentStateForFilter()->filterSourceGraphicTainted == isWriteOnly);
+    return CurrentStateForFilter()->filter;
   }
 
 protected:
   enum class TextDrawOperation : uint8_t {
     FILL,
     STROKE,
     MEASURE
   };
 
 protected:
+  inline ContextStateForFilter* CurrentStateForFilter() {
+    return static_cast<ContextStateForFilter*>(mStyleStack[mStyleStack.Length() - 1].get());
+  }
+
+  inline const ContextStateForFilter* CurrentStateForFilterlter() const {
+    return static_cast<ContextStateForFilter*>(mStyleStack[mStyleStack.Length() - 1].get());
+  }
+
   gfxFontGroup *GetCurrentFontStyle();
 
   /**
    * Implementation of the fillText, strokeText, and measure functions with
    * the operation abstracted to a flag.
    */
   nsresult DrawOrMeasureText(const nsAString& aText,
                              float aX,