Bug 591358 - Part 3: Lazy creation of the draw target in order to save memory and improve performance. r=roc
☠☠ backed out by 72c85979e852 ☠ ☠
authorAnthony Jones <ajones@mozilla.com>
Thu, 31 May 2012 12:47:27 +1200
changeset 104343 013743bb609ef214d6bad4e64bd46863a5b0450f
parent 104342 f9922b42205abb3191f5012ba0196f9ef837f393
child 104344 d89a71a8f5aead66d115b573aab0c8a47129ff12
push id14464
push userryanvm@gmail.com
push dateWed, 05 Sep 2012 21:52:38 +0000
treeherdermozilla-inbound@013743bb609e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs591358
milestone18.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 591358 - Part 3: Lazy creation of the draw target in order to save memory and improve performance. r=roc
content/canvas/src/nsCanvasRenderingContext2DAzure.cpp
content/canvas/src/nsCanvasRenderingContext2DAzure.h
--- a/content/canvas/src/nsCanvasRenderingContext2DAzure.cpp
+++ b/content/canvas/src/nsCanvasRenderingContext2DAzure.cpp
@@ -541,16 +541,17 @@ NS_INTERFACE_MAP_END
  ** CanvasRenderingContext2D impl
  **/
 
 
 // Initialize our static variables.
 uint32_t nsCanvasRenderingContext2DAzure::sNumLivingContexts = 0;
 uint8_t (*nsCanvasRenderingContext2DAzure::sUnpremultiplyTable)[256] = nullptr;
 uint8_t (*nsCanvasRenderingContext2DAzure::sPremultiplyTable)[256] = nullptr;
+RefPtr<DrawTarget> nsCanvasRenderingContext2DAzure::sErrorTarget = nullptr;
 
 namespace mozilla {
 namespace dom {
 
 bool
 AzureCanvasEnabled()
 {
   return gfxPlatform::GetPlatform()->SupportsAzureCanvas();
@@ -572,17 +573,17 @@ NS_NewCanvasRenderingContext2DAzure(nsID
   if (!ctx)
     return NS_ERROR_OUT_OF_MEMORY;
 
   *aResult = ctx.forget().get();
   return NS_OK;
 }
 
 nsCanvasRenderingContext2DAzure::nsCanvasRenderingContext2DAzure()
-  : mValid(false), mZero(false), mOpaque(false), mResetLayer(true)
+  : mZero(false), mOpaque(false), mResetLayer(true)
   , mIPC(false)
   , mIsEntireFrameInvalid(false)
   , mPredictManyRedrawCalls(false), mPathTransformWillUpdate(false)
   , mInvalidateCount(0)
 {
   sNumLivingContexts++;
   SetIsDOMBinding();
 }
@@ -595,16 +596,17 @@ nsCanvasRenderingContext2DAzure::~nsCanv
     mUserDatas[i]->Forget();
   }
   sNumLivingContexts--;
   if (!sNumLivingContexts) {
     delete[] sUnpremultiplyTable;
     delete[] sPremultiplyTable;
     sUnpremultiplyTable = nullptr;
     sPremultiplyTable = nullptr;
+    NS_IF_RELEASE(sErrorTarget);
   }
 }
 
 JSObject*
 nsCanvasRenderingContext2DAzure::WrapObject(JSContext *cx, JSObject *scope,
                                             bool *triedToWrap)
 {
   return CanvasRenderingContext2DBinding::Wrap(cx, scope, this, triedToWrap);
@@ -644,26 +646,25 @@ nsresult
 nsCanvasRenderingContext2DAzure::Reset()
 {
   if (mCanvasElement) {
     mCanvasElement->InvalidateCanvas();
   }
 
   // only do this for non-docshell created contexts,
   // since those are the ones that we created a surface for
-  if (mValid && !mDocShell) {
+  if (mTarget && IsTargetValid() && !mDocShell) {
     gCanvasAzureMemoryUsed -= mWidth * mHeight * 4;
   }
 
   mTarget = nullptr;
 
   // Since the target changes the backing texture will change, and this will
   // no longer be valid.
   mThebesSurface = nullptr;
-  mValid = false;
   mIsEntireFrameInvalid = false;
   mPredictManyRedrawCalls = false;
 
   return NS_OK;
 }
 
 static void
 WarnAboutUnexpectedStyle(nsHTMLCanvasElement* canvasElement)
@@ -830,189 +831,149 @@ nsCanvasRenderingContext2DAzure::RedrawU
     return;
   }
 
   mgfx::Rect newr =
     mTarget->GetTransform().TransformBounds(ToRect(r));
   Redraw(newr);
 }
 
-NS_IMETHODIMP
-nsCanvasRenderingContext2DAzure::SetDimensions(int32_t width, int32_t height)
+void
+nsCanvasRenderingContext2DAzure::EnsureTarget()
 {
-  RefPtr<DrawTarget> target;
-
-  // Zero sized surfaces cause issues, so just go with 1x1.
-  if (height == 0 || width == 0) {
-    mZero = true;
-    height = 1;
-    width = 1;
-  } else {
-    mZero = false;
-  }
-
-  // Check that the dimensions are sane
-  IntSize size(width, height);
+  if (mTarget) {
+    return;
+  }
+
+   // Check that the dimensions are sane
+  IntSize size(mWidth, mHeight);
   if (size.width <= 0xFFFF && size.height <= 0xFFFF &&
       size.width >= 0 && size.height >= 0) {
     SurfaceFormat format = GetSurfaceFormat();
     nsIDocument* ownerDoc = nullptr;
     if (mCanvasElement) {
       ownerDoc = mCanvasElement->OwnerDoc();
     }
 
     nsRefPtr<LayerManager> layerManager = nullptr;
 
     if (ownerDoc) {
       layerManager =
         nsContentUtils::PersistentLayerManagerForDocument(ownerDoc);
     }
 
-    if (layerManager) {
-      target = layerManager->CreateDrawTarget(size, format);
-    } else {
-      target = gfxPlatform::GetPlatform()->CreateOffscreenDrawTarget(size, format);
-    }
-  }
-
-  if (target) {
+     if (layerManager) {
+       mTarget = layerManager->CreateDrawTarget(size, format);
+     } else {
+       mTarget = gfxPlatform::GetPlatform()->CreateOffscreenDrawTarget(size, format);
+     }
+  }
+
+  if (mTarget) {
     if (gCanvasAzureMemoryReporter == nullptr) {
         gCanvasAzureMemoryReporter = new NS_MEMORY_REPORTER_NAME(CanvasAzureMemory);
       NS_RegisterMemoryReporter(gCanvasAzureMemoryReporter);
     }
 
-    gCanvasAzureMemoryUsed += width * height * 4;
+    gCanvasAzureMemoryUsed += mWidth * mHeight * 4;
     JSContext* context = nsContentUtils::GetCurrentJSContext();
     if (context) {
-      JS_updateMallocCounter(context, width * height * 4);
+      JS_updateMallocCounter(context, mWidth * mHeight * 4);
     }
-  }
-
-  return InitializeWithTarget(target, width, height);
+  } else {
+    EnsureErrorTarget();
+    mTarget = sErrorTarget;
+  }
 }
 
-nsresult
-nsCanvasRenderingContext2DAzure::Initialize(int32_t width, int32_t height)
+NS_IMETHODIMP
+nsCanvasRenderingContext2DAzure::SetDimensions(int32_t width, int32_t height)
 {
-  mWidth = width;
-  mHeight = height;
-
-  if (!mValid) {
-    // Create a dummy target in the hopes that it will help us deal with users
-    // calling into us after having changed the size where the size resulted
-    // in an inability to create a correct DrawTarget.
-    mTarget = gfxPlatform::GetPlatform()->CreateOffscreenDrawTarget(IntSize(1, 1), FORMAT_B8G8R8A8);
-  }
+  ClearTarget();
+
+  // Zero sized surfaces cause issues, so just go with 1x1.
+  if (height == 0 || width == 0) {
+    mZero = true;
+    mWidth = 1;
+    mHeight = 1;
+  } else {
+    mZero = false;
+    mWidth = width;
+    mHeight = height;
+  }
+
+  return NS_OK;
+}
+
+void
+nsCanvasRenderingContext2DAzure::ClearTarget()
+{
+  Reset();
 
   mResetLayer = true;
 
   // set up the initial canvas defaults
   mStyleStack.Clear();
   mPathBuilder = nullptr;
   mPath = nullptr;
   mDSPathBuilder = nullptr;
 
   ContextState *state = mStyleStack.AppendElement();
   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);
-
-  if (mTarget) {
-    mTarget->ClearRect(mgfx::Rect(Point(0, 0), Size(mWidth, mHeight)));
-    // always force a redraw, because if the surface dimensions were reset
-    // then the surface became cleared, and we need to redraw everything.
-    Redraw();
-  }
-
-  return mValid ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
-}
-
-nsresult
-nsCanvasRenderingContext2DAzure::InitializeWithTarget(DrawTarget *target, int32_t width, int32_t height)
-{
-  Reset();
-
-  NS_ASSERTION(mCanvasElement, "Must have a canvas element!");
-  mDocShell = nullptr;
-
-  // This first time this is called on this object is via
-  // nsHTMLCanvasElement::GetContext. If target was non-null then mTarget is
-  // non-null, otherwise we'll return an error here and GetContext won't
-  // return this context object and we'll never enter this code again.
-  // All other times this method is called, if target is null then
-  // mTarget won't be changed, i.e. it will remain non-null, or else it
-  // will be set to non-null.
-  // In all cases, any usable canvas context will have non-null mTarget.
-
-  if (target) {
-    mValid = true;
-    mTarget = target;
-  } else {
-    mValid = false;
-  }
-
-  return Initialize(width, height);
 }
 
 NS_IMETHODIMP
 nsCanvasRenderingContext2DAzure::InitializeWithSurface(nsIDocShell *shell, gfxASurface *surface, int32_t width, int32_t height)
 {
   mDocShell = shell;
   mThebesSurface = surface;
 
+  SetDimensions(width, height);
   mTarget = gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(surface, IntSize(width, height));
-  mValid = mTarget != nullptr;
-
-  return Initialize(width, height);
+  if (!mTarget) {
+    EnsureErrorTarget();
+    mTarget = sErrorTarget;
+  }
+
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsCanvasRenderingContext2DAzure::SetIsOpaque(bool isOpaque)
 {
-  if (isOpaque == mOpaque)
-    return NS_OK;
-
-  mOpaque = isOpaque;
-
-  if (mValid) {
-    /* If we've already been created, let SetDimensions take care of
-      * recreating our surface
-      */
-    return SetDimensions(mWidth, mHeight);
+  if (isOpaque != mOpaque) {
+    mOpaque = isOpaque;
+    ClearTarget();
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsCanvasRenderingContext2DAzure::SetIsIPC(bool isIPC)
 {
-  if (isIPC == mIPC)
-      return NS_OK;
-
-  mIPC = isIPC;
-
-  if (mValid) {
-    /* If we've already been created, let SetDimensions take care of
-      * recreating our surface
-      */
-    return SetDimensions(mWidth, mHeight);
+  if (isIPC != mIPC) {
+    mIPC = isIPC;
+    ClearTarget();
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsCanvasRenderingContext2DAzure::Render(gfxContext *ctx, gfxPattern::GraphicsFilter aFilter, uint32_t aFlags)
 {
   nsresult rv = NS_OK;
 
-  if (!mValid || !mTarget) {
+  EnsureTarget();
+  if (!IsTargetValid()) {
     return NS_ERROR_FAILURE;
   }
 
   nsRefPtr<gfxASurface> surface;
   
   if (NS_FAILED(GetThebesSurface(getter_AddRefs(surface)))) {
     return NS_ERROR_FAILURE;
   }
@@ -1046,17 +1007,18 @@ nsCanvasRenderingContext2DAzure::Render(
   return rv;
 }
 
 NS_IMETHODIMP
 nsCanvasRenderingContext2DAzure::GetInputStream(const char *aMimeType,
                                                 const PRUnichar *aEncoderOptions,
                                                 nsIInputStream **aStream)
 {
-  if (!mValid || !mTarget) {
+  EnsureTarget();
+  if (!IsTargetValid()) {
     return NS_ERROR_FAILURE;
   }
 
   nsRefPtr<gfxASurface> surface;
 
   if (NS_FAILED(GetThebesSurface(getter_AddRefs(surface)))) {
     return NS_ERROR_FAILURE;
   }
@@ -1133,16 +1095,17 @@ nsCanvasRenderingContext2DAzure::GetCanv
 
 //
 // state
 //
 
 void
 nsCanvasRenderingContext2DAzure::Save()
 {
+  EnsureTarget();
   mStyleStack[mStyleStack.Length() - 1].transform = mTarget->GetTransform();
   mStyleStack.SetCapacity(mStyleStack.Length() + 1);
   mStyleStack.AppendElement(CurrentState());
 }
 
 NS_IMETHODIMP
 nsCanvasRenderingContext2DAzure::MozSave()
 {
@@ -1151,24 +1114,24 @@ nsCanvasRenderingContext2DAzure::MozSave
 }
 
 void
 nsCanvasRenderingContext2DAzure::Restore()
 {
   if (mStyleStack.Length() - 1 == 0)
     return;
 
+  TransformWillUpdate();
+
   for (uint32_t i = 0; i < CurrentState().clipsPushed.size(); i++) {
     mTarget->PopClip();
   }
 
   mStyleStack.RemoveElementAt(mStyleStack.Length() - 1);
 
-  TransformWillUpdate();
-
   mTarget->SetTransform(CurrentState().transform);
 }
 
 NS_IMETHODIMP
 nsCanvasRenderingContext2DAzure::MozRestore()
 {
   Restore();
   return NS_OK;
@@ -1176,78 +1139,76 @@ nsCanvasRenderingContext2DAzure::MozRest
 
 //
 // transformations
 //
 
 void
 nsCanvasRenderingContext2DAzure::Scale(double x, double y, ErrorResult& error)
 {
-  if (!mTarget) {
-    error.Throw(NS_ERROR_FAILURE);
-    return;
-  }
-
   if (!FloatValidate(x,y)) {
     return;
   }
 
   TransformWillUpdate();
+  if (!IsTargetValid()) {
+    error.Throw(NS_ERROR_FAILURE);
+    return;
+  }
 
   Matrix newMatrix = mTarget->GetTransform();
   mTarget->SetTransform(newMatrix.Scale(x, y));
 }
 
 NS_IMETHODIMP
 nsCanvasRenderingContext2DAzure::Scale(float x, float y)
 {
   ErrorResult rv;
   Scale((double)x, (double)y, rv);
   return rv.ErrorCode();
 }
 
 void
 nsCanvasRenderingContext2DAzure::Rotate(double angle, ErrorResult& error)
 {
-  if (!mTarget) {
-    error.Throw(NS_ERROR_FAILURE);
-    return;
-  }
-
   if (!FloatValidate(angle)) {
     return;
   }
 
   TransformWillUpdate();
+  if (!IsTargetValid()) {
+    error.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
 
   Matrix rotation = Matrix::Rotation(angle);
   mTarget->SetTransform(rotation * mTarget->GetTransform());
 }
 
 NS_IMETHODIMP
 nsCanvasRenderingContext2DAzure::Rotate(float angle)
 {
   ErrorResult rv;
   Rotate((double)angle, rv);
   return rv.ErrorCode();
 }
 
 void
 nsCanvasRenderingContext2DAzure::Translate(double x, double y, ErrorResult& error)
 {
-  if (!mTarget) {
-    error.Throw(NS_ERROR_FAILURE);
-    return;
-  }
-
   if (!FloatValidate(x,y)) {
     return;
   }
 
   TransformWillUpdate();
+  if (!IsTargetValid()) {
+    error.Throw(NS_ERROR_FAILURE);
+    return;
+  }
 
   Matrix newMatrix = mTarget->GetTransform();
   mTarget->SetTransform(newMatrix.Translate(x, y));
 }
 
 NS_IMETHODIMP
 nsCanvasRenderingContext2DAzure::Translate(float x, float y)
 {
@@ -1256,26 +1217,25 @@ nsCanvasRenderingContext2DAzure::Transla
   return rv.ErrorCode();
 }
 
 void
 nsCanvasRenderingContext2DAzure::Transform(double m11, double m12, double m21,
                                            double m22, double dx, double dy,
                                            ErrorResult& error)
 {
-  if (!mTarget) {
-    error.Throw(NS_ERROR_FAILURE);
-    return;
-  }
-
   if (!FloatValidate(m11,m12,m21,m22,dx,dy)) {
     return;
   }
 
   TransformWillUpdate();
+  if (!IsTargetValid()) {
+    error.Throw(NS_ERROR_FAILURE);
+    return;
+  }
 
   Matrix matrix(m11, m12, m21, m22, dx, dy);
   mTarget->SetTransform(matrix * mTarget->GetTransform());
 }
 
 NS_IMETHODIMP
 nsCanvasRenderingContext2DAzure::Transform(float m11, float m12, float m21, float m22, float dx, float dy)
 {
@@ -1286,26 +1246,25 @@ nsCanvasRenderingContext2DAzure::Transfo
 }
 
 void
 nsCanvasRenderingContext2DAzure::SetTransform(double m11, double m12,
                                               double m21, double m22,
                                               double dx, double dy,
                                               ErrorResult& error)
 {
-  if (!mTarget) {
-    error.Throw(NS_ERROR_FAILURE);
-    return;
-  }
-
   if (!FloatValidate(m11,m12,m21,m22,dx,dy)) {
     return;
   }
 
   TransformWillUpdate();
+  if (!IsTargetValid()) {
+    error.Throw(NS_ERROR_FAILURE);
+    return;
+  }
 
   Matrix matrix(m11, m12, m21, m22, dx, dy);
   mTarget->SetTransform(matrix);
 }
 
 NS_IMETHODIMP
 nsCanvasRenderingContext2DAzure::SetTransform(float m11, float m12, float m21, float m22, float dx, float dy)
 {
@@ -1364,17 +1323,18 @@ ObjectToMatrix(JSContext* cx, JSObject& 
   return true;
 }
 
 void
 nsCanvasRenderingContext2DAzure::SetMozCurrentTransform(JSContext* cx,
                                                         JSObject& currentTransform,
                                                         ErrorResult& error)
 {
-  if (!mTarget) {
+  EnsureTarget();
+  if (!IsTargetValid()) {
     error.Throw(NS_ERROR_FAILURE);
     return;
   }
 
   Matrix newCTM;
   if (ObjectToMatrix(cx, currentTransform, newCTM, error)) {
     mTarget->SetTransform(newCTM);
   }
@@ -1392,22 +1352,17 @@ nsCanvasRenderingContext2DAzure::SetMozC
   SetMozCurrentTransform(cx, matrix.toObject(), rv);
   return rv.ErrorCode();
 }
 
 JSObject*
 nsCanvasRenderingContext2DAzure::GetMozCurrentTransform(JSContext* cx,
                                                         ErrorResult& error) const
 {
-  if (!mTarget) {
-    error.Throw(NS_ERROR_FAILURE);
-    return NULL;
-  }
-
-  return MatrixToJSObject(cx, mTarget->GetTransform(), error);
+  return MatrixToJSObject(cx, mTarget ? mTarget->GetTransform() : Matrix(), error);
 }
 
 NS_IMETHODIMP
 nsCanvasRenderingContext2DAzure::GetMozCurrentTransform(JSContext* cx,
                                                         jsval* matrix)
 {
   ErrorResult rv;
   JSObject* obj = GetMozCurrentTransform(cx, rv);
@@ -1417,17 +1372,18 @@ nsCanvasRenderingContext2DAzure::GetMozC
   return rv.ErrorCode();
 }
 
 void
 nsCanvasRenderingContext2DAzure::SetMozCurrentTransformInverse(JSContext* cx,
                                                                JSObject& currentTransform,
                                                                ErrorResult& error)
 {
-  if (!mTarget) {
+  EnsureTarget();
+  if (!IsTargetValid()) {
     error.Throw(NS_ERROR_FAILURE);
     return;
   }
 
   Matrix newCTMInverse;
   if (ObjectToMatrix(cx, currentTransform, newCTMInverse, error)) {
     // XXX ERRMSG we need to report an error to developers here! (bug 329026)
     if (newCTMInverse.Invert()) {
@@ -1449,18 +1405,17 @@ nsCanvasRenderingContext2DAzure::SetMozC
   return rv.ErrorCode();
 }
 
 JSObject*
 nsCanvasRenderingContext2DAzure::GetMozCurrentTransformInverse(JSContext* cx,
                                                                ErrorResult& error) const
 {
   if (!mTarget) {
-    error.Throw(NS_ERROR_FAILURE);
-    return NULL;
+    return MatrixToJSObject(cx, Matrix(), error);
   }
 
   Matrix ctm = mTarget->GetTransform();
 
   if (!ctm.Invert()) {
     double NaN = JSVAL_TO_DOUBLE(JS_GetNaNValue(cx));
     ctm = Matrix(NaN, NaN, NaN, NaN, NaN, NaN);
   }
@@ -1897,16 +1852,17 @@ nsCanvasRenderingContext2DAzure::CreateP
     return NULL;
   }
 
   // Ignore nullptr cairo surfaces! See bug 666312.
   if (!res.mSurface->CairoSurface() || res.mSurface->CairoStatus()) {
     return NULL;
   }
 
+  EnsureTarget();
   RefPtr<SourceSurface> srcSurf =
     gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mTarget, res.mSurface);
 
   nsRefPtr<nsCanvasPatternAzure> pat =
     new nsCanvasPatternAzure(srcSurf, repeatMode, res.mPrincipal,
                              res.mIsWriteOnly, res.mCORSUsed);
 
   return pat.forget();
@@ -2000,17 +1956,17 @@ nsCanvasRenderingContext2DAzure::GetMozS
 //
 // rects
 //
 
 void
 nsCanvasRenderingContext2DAzure::ClearRect(double x, double y, double w,
                                            double h)
 {
-  if (!FloatValidate(x,y,w,h)) {
+  if (!FloatValidate(x,y,w,h) || !mTarget) {
     return;
   }
  
   mTarget->ClearRect(mgfx::Rect(x, y, w, h));
 
   RedrawUser(gfxRect(x, y, w, h));
 }
 
@@ -2074,16 +2030,17 @@ nsCanvasRenderingContext2DAzure::FillRec
           h = 0;
         }
       }
     }
   }
 
   mgfx::Rect bounds;
   
+  EnsureTarget();
   if (NeedToDrawShadow()) {
     bounds = mgfx::Rect(x, y, w, h);
     bounds = mTarget->GetTransform().TransformBounds(bounds);
   }
 
   AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)->
     FillRect(mgfx::Rect(x, y, w, h),
              CanvasGeneralPattern().ForStyle(this, STYLE_FILL, mTarget),
@@ -2105,27 +2062,32 @@ nsCanvasRenderingContext2DAzure::StrokeR
 {
   if (!FloatValidate(x,y,w,h)) {
     return;
   }
 
   const ContextState &state = CurrentState();
 
   mgfx::Rect bounds;
-  
+
+  if (!w && !h) {
+    return;
+  }
+
+  EnsureTarget();
+  if (!IsTargetValid()) {
+    return;
+  }
+
   if (NeedToDrawShadow()) {
     bounds = mgfx::Rect(x - state.lineWidth / 2.0f, y - state.lineWidth / 2.0f,
                         w + state.lineWidth, h + state.lineWidth);
     bounds = mTarget->GetTransform().TransformBounds(bounds);
   }
 
-  if (!w && !h) {
-    return;
-  }
-
   if (!h) {
     CapStyle cap = CAP_BUTT;
     if (state.lineJoin == JOIN_ROUND) {
       cap = CAP_ROUND;
     }
     AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)->
       StrokeLine(Point(x, y), Point(x + w, y),
                   CanvasGeneralPattern().ForStyle(this, STYLE_STROKE, mTarget),
@@ -2486,16 +2448,17 @@ nsCanvasRenderingContext2DAzure::EnsureW
         mPath->TransformedCopyToBuilder(mPathToDS, fillRule);
       mPath = nullptr;
       mPathBuilder = nullptr;
       mPathTransformWillUpdate = false;
     }
     return;
   }
 
+  EnsureTarget();
   if (!mPath) {
     NS_ASSERTION(!mPathTransformWillUpdate, "mPathTransformWillUpdate should be false, if all paths are null");
     mPathBuilder = mTarget->CreatePathBuilder(fillRule);
   } else if (!mPathTransformWillUpdate) {
     mPathBuilder = mPath->CopyToBuilder(fillRule);
   } else {
     mDSPathBuilder =
       mPath->TransformedCopyToBuilder(mPathToDS, fillRule);
@@ -2504,16 +2467,17 @@ nsCanvasRenderingContext2DAzure::EnsureW
 }
 
 void
 nsCanvasRenderingContext2DAzure::EnsureUserSpacePath(bool aCommitTransform /* = true */)
 {
   FillRule fillRule = CurrentState().fillRule;
 
   if (!mPath && !mPathBuilder && !mDSPathBuilder) {
+    EnsureTarget();
     mPathBuilder = mTarget->CreatePathBuilder(fillRule);
   }
 
   if (mPathBuilder) {
     mPath = mPathBuilder->Finish();
     mPathBuilder = nullptr;
   }
 
@@ -2549,16 +2513,18 @@ nsCanvasRenderingContext2DAzure::EnsureU
   }
 
   NS_ASSERTION(mPath, "mPath should exist");
 }
 
 void
 nsCanvasRenderingContext2DAzure::TransformWillUpdate()
 {
+  EnsureTarget();
+
   // Store the matrix that would transform the current path to device
   // space.
   if (mPath || mPathBuilder) {
     if (!mPathTransformWillUpdate) {
       // If the transform has already been updated, but a device space builder
       // has not been created yet mPathToDS contains the right transform to
       // transform the current mPath into device space.
       // We should leave it alone.
@@ -3032,16 +2998,17 @@ struct NS_STACK_CLASS nsCanvasBidiProces
     const gfxTextRun::GlyphRun *runs = mTextRun->GetGlyphRuns(&numRuns);
     const uint32_t appUnitsPerDevUnit = mAppUnitsPerDevPixel;
     const double devUnitsPerAppUnit = 1.0/double(appUnitsPerDevUnit);
     Point baselineOrigin =
       Point(point.x * devUnitsPerAppUnit, point.y * devUnitsPerAppUnit);
 
     float advanceSum = 0;
 
+    mCtx->EnsureTarget();
     for (uint32_t c = 0; c < numRuns; c++) {
       gfxFont *font = runs[c].mFont;
       uint32_t endRun = 0;
       if (c + 1 < numRuns) {
         endRun = runs[c + 1].mCharacterOffset;
       } else {
         endRun = mTextRun->GetLength();
       }
@@ -3232,18 +3199,24 @@ nsCanvasRenderingContext2DAzure::DrawOrM
   bool doDrawShadow = aOp == TEXT_DRAW_OPERATION_FILL && NeedToDrawShadow();
 
   nsCanvasBidiProcessorAzure processor;
 
   GetAppUnitsValues(&processor.mAppUnitsPerDevPixel, nullptr);
   processor.mPt = gfxPoint(aX, aY);
   processor.mThebes =
     new gfxContext(gfxPlatform::GetPlatform()->ScreenReferenceSurface());
-  Matrix matrix = mTarget->GetTransform();
-  processor.mThebes->SetMatrix(gfxMatrix(matrix._11, matrix._12, matrix._21, matrix._22, matrix._31, matrix._32));
+
+  // If we don't have a target then we don't have a transform. A target won't
+  // be needed in the case where we're measuring the text size. This allows
+  // to avoid creating a target if it's only being used to measure text sizes.
+  if (mTarget) {
+    Matrix matrix = mTarget->GetTransform();
+    processor.mThebes->SetMatrix(gfxMatrix(matrix._11, matrix._12, matrix._21, matrix._22, matrix._31, matrix._32));
+  }
   processor.mCtx = this;
   processor.mOp = aOp;
   processor.mBoundingBox = gfxRect(0, 0, 0, 0);
   processor.mDoMeasureBoundingBox = doDrawShadow || !mIsEntireFrameInvalid;
   processor.mState = &CurrentState();
   processor.mFontgrp = currentFontStyle;
     
   nscoord totalWidthCoord;
@@ -3323,16 +3296,17 @@ nsCanvasRenderingContext2DAzure::DrawOrM
 
   // correct bounding box to get it to be the correct size/position
   processor.mBoundingBox.width = totalWidth;
   processor.mBoundingBox.MoveBy(processor.mPt);
 
   processor.mPt.x *= processor.mAppUnitsPerDevPixel;
   processor.mPt.y *= processor.mAppUnitsPerDevPixel;
 
+  EnsureTarget();
   Matrix oldTransform = mTarget->GetTransform();
   // if text is over aMaxWidth, then scale the text horizontally such that its
   // width is precisely aMaxWidth
   if (aMaxWidth.WasPassed() && aMaxWidth.Value() > 0 &&
       totalWidth > aMaxWidth.Value()) {
     Matrix newTransform = oldTransform;
 
     // Translate so that the anchor point is at 0,0, then scale and then
@@ -3662,16 +3636,18 @@ nsCanvasRenderingContext2DAzure::DrawIma
                                            ErrorResult& error)
 {
   MOZ_ASSERT(optional_argc == 0 || optional_argc == 2 || optional_argc == 6);
 
   RefPtr<SourceSurface> srcSurf;
   gfxIntSize imgSize;
 
   Element* element;
+
+  EnsureTarget();
   if (image.IsHTMLCanvasElement()) {
     nsHTMLCanvasElement* canvas = image.GetAsHTMLCanvasElement();
     element = canvas;
     nsIntSize size = canvas->GetSize();
     if (size.width == 0 || size.height == 0) {
       error.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
       return;
     }
@@ -3923,16 +3899,17 @@ nsCanvasRenderingContext2DAzure::DrawWin
     return;
   }
 
   nsRefPtr<gfxASurface> drawSurf;
   GetThebesSurface(getter_AddRefs(drawSurf));
 
   nsRefPtr<gfxContext> thebes = new gfxContext(drawSurf);
 
+  EnsureTarget();
   Matrix matrix = mTarget->GetTransform();
   thebes->SetMatrix(gfxMatrix(matrix._11, matrix._12, matrix._21,
                               matrix._22, matrix._31, matrix._32));
 
   // We can't allow web apps to call this until we fix at least the
   // following potential security issues:
   // -- rendering cross-domain IFRAMEs and then extracting the results
   // -- rendering the user's theme and then extracting the results
@@ -4141,17 +4118,18 @@ nsCanvasRenderingContext2DAzure::EnsureU
 }
 
 
 already_AddRefed<ImageData>
 nsCanvasRenderingContext2DAzure::GetImageData(JSContext* aCx, double aSx,
                                               double aSy, double aSw,
                                               double aSh, ErrorResult& error)
 {
-  if (!mValid) {
+  EnsureTarget();
+  if (!IsTargetValid()) {
     error.Throw(NS_ERROR_FAILURE);
     return NULL;
   }
 
   if (!mCanvasElement && !mDocShell) {
     NS_ERROR("No canvas element and no docshell in GetImageData!!!");
     error.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return NULL;
@@ -4335,16 +4313,29 @@ nsCanvasRenderingContext2DAzure::EnsureP
   for (int a = 0; a <= 255; a++) {
     for (int c = 0; c <= 255; c++) {
       sPremultiplyTable[a][c] = (a * c + 254) / 255;
     }
   }
 }
 
 void
+nsCanvasRenderingContext2DAzure::EnsureErrorTarget()
+{
+  if (sErrorTarget) {
+    return;
+  }
+
+  sErrorTarget = gfxPlatform::GetPlatform()->CreateOffscreenDrawTarget(IntSize(1, 1), FORMAT_B8G8R8A8);
+  NS_ABORT_IF_FALSE(sErrorTarget, "Failed to allocate the error target!");
+
+  NS_ADDREF(sErrorTarget);
+}
+
+void
 nsCanvasRenderingContext2DAzure::FillRuleChanged()
 {
   if (mPath) {
     mPathBuilder = mPath->CopyToBuilder(CurrentState().fillRule);
     mPath = nullptr;
   }
 }
 
@@ -4402,20 +4393,16 @@ nsCanvasRenderingContext2DAzure::PutImag
 }
 
 NS_IMETHODIMP
 nsCanvasRenderingContext2DAzure::PutImageData_explicit(int32_t x, int32_t y, uint32_t w, uint32_t h,
                                                        unsigned char *aData, uint32_t aDataLen,
                                                        bool hasDirtyRect, int32_t dirtyX, int32_t dirtyY,
                                                        int32_t dirtyWidth, int32_t dirtyHeight)
 {
-  if (!mValid) {
-    return NS_ERROR_FAILURE;
-  }
-
   if (w == 0 || h == 0) {
     return NS_ERROR_DOM_SYNTAX_ERR;
   }
 
   IntRect dirtyRect;
   IntRect imageDataRect(0, 0, w, h);
 
   if (hasDirtyRect) {
@@ -4494,16 +4481,21 @@ nsCanvasRenderingContext2DAzure::PutImag
       *dst++ = a;
       *dst++ = sPremultiplyTable[a][r];
       *dst++ = sPremultiplyTable[a][g];
       *dst++ = sPremultiplyTable[a][b];
 #endif
     }
   }
 
+  EnsureTarget();
+  if (!IsTargetValid()) {
+    return NS_ERROR_FAILURE;
+  }
+
   RefPtr<SourceSurface> sourceSurface =
     mTarget->CreateSourceSurfaceFromData(imgsurf->Data(), IntSize(w, h), imgsurf->Stride(), FORMAT_B8G8R8A8);
 
 
   mTarget->CopySurface(sourceSurface,
                        IntRect(dirtyRect.x - x, dirtyRect.y - y,
                                dirtyRect.width, dirtyRect.height),
                        IntPoint(dirtyRect.x, dirtyRect.y));
@@ -4511,23 +4503,17 @@ nsCanvasRenderingContext2DAzure::PutImag
   Redraw(mgfx::Rect(dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height));
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsCanvasRenderingContext2DAzure::GetThebesSurface(gfxASurface **surface)
 {
-  if (!mTarget) {
-    nsRefPtr<gfxASurface> tmpSurf =
-      gfxPlatform::GetPlatform()->CreateOffscreenSurface(gfxIntSize(1, 1), gfxASurface::CONTENT_COLOR_ALPHA);
-    *surface = tmpSurf.forget().get();
-    return NS_OK;
-  }
-
+  EnsureTarget();
   if (!mThebesSurface) {
     mThebesSurface =
       gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(mTarget);    
 
     if (!mThebesSurface) {
       return NS_ERROR_FAILURE;
     }
   } else {
@@ -4627,26 +4613,25 @@ nsCanvasRenderingContext2DAzure::SetMozI
 
 static uint8_t g2DContextLayerUserData;
 
 already_AddRefed<CanvasLayer>
 nsCanvasRenderingContext2DAzure::GetCanvasLayer(nsDisplayListBuilder* aBuilder,
                                                 CanvasLayer *aOldLayer,
                                                 LayerManager *aManager)
 {
-  if (!mValid) {
+  EnsureTarget();
+  if (!IsTargetValid()) {
     // No DidTransactionCallback will be received, so mark the context clean
     // now so future invalidations will be dispatched.
     MarkContextClean();
     return nullptr;
   }
 
-  if (mTarget) {
-    mTarget->Flush();
-  }
+  mTarget->Flush();
 
   if (!mResetLayer && aOldLayer) {
       CanvasRenderingContext2DUserDataAzure* userData =
       static_cast<CanvasRenderingContext2DUserDataAzure*>(
         aOldLayer->GetUserData(&g2DContextLayerUserData));
     if (userData && userData->IsForContext(this)) {
       NS_ADDREF(aOldLayer);
       return aOldLayer;
@@ -4702,10 +4687,10 @@ nsCanvasRenderingContext2DAzure::MarkCon
   mIsEntireFrameInvalid = false;
   mInvalidateCount = 0;
 }
 
 
 bool
 nsCanvasRenderingContext2DAzure::ShouldForceInactiveLayer(LayerManager *aManager)
 {
-    return !aManager->CanUseCanvasLayerForSize(gfxIntSize(mWidth, mHeight));
+  return !aManager->CanUseCanvasLayerForSize(gfxIntSize(mWidth, mHeight));
 }
--- a/content/canvas/src/nsCanvasRenderingContext2DAzure.h
+++ b/content/canvas/src/nsCanvasRenderingContext2DAzure.h
@@ -561,16 +561,18 @@ protected:
     */
   static uint8_t (*sUnpremultiplyTable)[256];
 
   /**
     * Lookup table used to speed up PutImageData().
     */
   static uint8_t (*sPremultiplyTable)[256];
 
+  static mozilla::RefPtr<mozilla::gfx::DrawTarget> sErrorTarget;
+
   // Some helpers.  Doesn't modify a color on failure.
   void SetStyleFromJSValue(JSContext* cx, JS::Value& value, Style whichStyle);
   void SetStyleFromString(const nsAString& str, Style whichStyle);
 
   void SetStyleFromGradient(nsCanvasGradientAzure *gradient, Style whichStyle)
   {
     CurrentState().SetGradientStyle(whichStyle, gradient);
   }
@@ -593,33 +595,60 @@ protected:
     */
   void EnsureUnpremultiplyTable();
 
   /**
     * Creates the premultiply lookup table, if it doesn't exist.
     */
   void EnsurePremultiplyTable();
 
+  /**
+   * Creates the error target, if it doesn't exist
+   */
+  static void EnsureErrorTarget();
+
   /* This function ensures there is a writable pathbuilder available, this
    * pathbuilder may be working in user space or in device space or
    * device space.
    * After calling this function mPathTransformWillUpdate will be false
    */
   void EnsureWritablePath();
 
   // Ensures a path in UserSpace is available.
   // If aCommitTransform is true, then any transform on the context will be
   // used for the path.
   void EnsureUserSpacePath(bool aCommitTransform = true);
 
+  /**
+   * Needs to be called before updating the transform. This makes a call to
+   * EnsureTarget() so you don't have to.
+   */
   void TransformWillUpdate();
 
   // Report the fillRule has changed.
   void FillRuleChanged();
 
+   /**
+   * Create the backing surfacing, if it doesn't exist. If there is an error
+   * in creating the target then it will put sErrorTarget in place. If there
+   * is in turn an error in creating the sErrorTarget then they would both
+   * be null so IsTargetValid() would still return null.
+   */
+  void EnsureTarget();
+
+  /*
+   * Disposes an old target and prepares to lazily create a new target.
+   */
+  void ClearTarget();
+
+  /**
+   * Check if the target is valid after calling EnsureTarget.
+   */
+  bool IsTargetValid() { return mTarget != sErrorTarget; }
+
   /**
     * Returns the surface format this canvas should be allocated using. Takes
     * into account mOpaque, platform requirements, etc.
     */
   mozilla::gfx::SurfaceFormat GetSurfaceFormat() const;
 
   void DrawImage(const HTMLImageOrCanvasOrVideoElement &imgElt,
                  double sx, double sy, double sw, double sh,
@@ -658,20 +687,16 @@ protected:
     }
 
     return false;
   }
 
   // Member vars
   int32_t mWidth, mHeight;
 
-  // This is true when the canvas is valid, false otherwise, this occurs when
-  // for some reason initialization of the drawtarget fails. If the canvas
-  // is invalid certain behavior is expected.
-  bool mValid;
   // This is true when the canvas is valid, but of zero size, this requires
   // specific behavior on some operations.
   bool mZero;
 
   bool mOpaque;
 
   // This is true when the next time our layer is retrieved we need to
   // recreate it (i.e. our backing surface changed)
@@ -679,17 +704,19 @@ protected:
   // This is needed for drawing in drawAsyncXULElement
   bool mIPC;
 
   nsTArray<CanvasRenderingContext2DUserDataAzure*> mUserDatas;
 
   // If mCanvasElement is not provided, then a docshell is
   nsCOMPtr<nsIDocShell> mDocShell;
 
-  // our drawing surfaces, contexts, and layers
+  // This is created lazily so it is necessary to call EnsureTarget before
+  // accessing it. In the event of an error it will be equal to
+  // sErrorTarget.
   mozilla::RefPtr<mozilla::gfx::DrawTarget> mTarget;
 
   /**
     * Flag to avoid duplicate calls to InvalidateFrame. Set to true whenever
     * Redraw is called, reset to false when Render is called.
     */
   bool mIsEntireFrameInvalid;
   /**