merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 18 Dec 2015 15:23:39 +0100
changeset 276928 c5cb194cc9cb56d742fb3a7a826f0080b0404edc
parent 276799 8e54ec9a12f2af42dca48b957a88d2ce13e11d09 (current diff)
parent 276927 fbb48f4efd215160f71372122752aad6ace8441a (diff)
child 276929 546ed49f5a403909faee1a0d0eec8324d419e381
child 276939 718acb0e060cecd1d630436d303fdacc0e665431
child 276978 ce75ab3d79c631b2880e7894f9fd05d468aee1a5
child 277003 34efffa32475625460d15ba93fa3c67ee3c8c486
push id29810
push usercbook@mozilla.com
push dateFri, 18 Dec 2015 14:24:54 +0000
treeherdermozilla-central@c5cb194cc9cb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone46.0a1
first release with
nightly linux32
c5cb194cc9cb / 46.0a1 / 20151219030215 / files
nightly linux64
c5cb194cc9cb / 46.0a1 / 20151219030215 / files
nightly mac
c5cb194cc9cb / 46.0a1 / 20151219030215 / files
nightly win32
c5cb194cc9cb / 46.0a1 / 20151219030215 / files
nightly win64
c5cb194cc9cb / 46.0a1 / 20151219030215 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central a=merge
gfx/skia/skia/include/core/SkAdvancedTypefaceMetrics.h
gfx/skia/skia/include/core/SkColorShader.h
gfx/skia/skia/include/core/SkDeviceProperties.h
gfx/skia/skia/include/core/SkDrawPictureCallback.h
gfx/skia/skia/include/core/SkDynamicAnnotations.h
gfx/skia/skia/include/core/SkEndian.h
gfx/skia/skia/include/core/SkFlattenableBuffers.h
gfx/skia/skia/include/core/SkFloatBits.h
gfx/skia/skia/include/core/SkFloatingPoint.h
gfx/skia/skia/include/core/SkInstCnt.h
gfx/skia/skia/include/core/SkOnce.h
gfx/skia/skia/include/core/SkPaintOptionsAndroid.h
gfx/skia/skia/include/core/SkPatch.h
gfx/skia/skia/include/core/SkReadBuffer.h
gfx/skia/skia/include/core/SkReader32.h
gfx/skia/skia/include/core/SkTemplates.h
gfx/skia/skia/include/core/SkThread.h
gfx/skia/skia/include/core/SkWeakRefCnt.h
gfx/skia/skia/include/effects/SkAvoidXfermode.h
gfx/skia/skia/include/effects/SkBitmapSource.h
gfx/skia/skia/include/effects/SkMatrixImageFilter.h
gfx/skia/skia/include/effects/SkPorterDuff.h
gfx/skia/skia/include/effects/SkStippleMaskFilter.h
gfx/skia/skia/include/effects/SkTransparentShader.h
gfx/skia/skia/include/gpu/GrBackendEffectFactory.h
gfx/skia/skia/include/gpu/GrClipData.h
gfx/skia/skia/include/gpu/GrContextFactory.h
gfx/skia/skia/include/gpu/GrDrawEffect.h
gfx/skia/skia/include/gpu/GrEffect.h
gfx/skia/skia/include/gpu/GrEffectStage.h
gfx/skia/skia/include/gpu/GrEffectUnitTest.h
gfx/skia/skia/include/gpu/GrFontScaler.h
gfx/skia/skia/include/gpu/GrGlyph.h
gfx/skia/skia/include/gpu/GrPathRendererChain.h
gfx/skia/skia/include/gpu/GrTBackendEffectFactory.h
gfx/skia/skia/include/gpu/GrUserConfig.h
gfx/skia/skia/include/gpu/SkGpuDevice.h
gfx/skia/skia/include/gpu/gl/SkANGLEGLContext.h
gfx/skia/skia/include/gpu/gl/SkDebugGLContext.h
gfx/skia/skia/include/gpu/gl/SkGLContextHelper.h
gfx/skia/skia/include/gpu/gl/SkMesaGLContext.h
gfx/skia/skia/include/gpu/gl/SkNativeGLContext.h
gfx/skia/skia/include/pdf/SkPDFDevice.h
gfx/skia/skia/include/pdf/SkPDFDocument.h
gfx/skia/skia/include/ports/SkFontStyle.h
gfx/skia/skia/include/ports/SkTypeface_android.h
gfx/skia/skia/include/record/SkRecording.h
gfx/skia/skia/include/svg/SkSVGAttribute.h
gfx/skia/skia/include/svg/SkSVGBase.h
gfx/skia/skia/include/svg/SkSVGPaintState.h
gfx/skia/skia/include/svg/SkSVGParser.h
gfx/skia/skia/include/svg/SkSVGTypes.h
gfx/skia/skia/include/utils/SkCondVar.h
gfx/skia/skia/include/utils/SkDeferredCanvas.h
gfx/skia/skia/include/utils/SkPathUtils.h
gfx/skia/skia/include/utils/SkProxyCanvas.h
gfx/skia/skia/include/utils/SkRunnable.h
gfx/skia/skia/include/utils/SkThreadPool.h
gfx/skia/skia/include/utils/SkWGL.h
gfx/skia/skia/include/utils/ios/SkStream_NSData.h
gfx/skia/skia/include/views/SkOSWindow_NaCl.h
gfx/skia/skia/include/views/SkTextBox.h
gfx/skia/skia/include/views/android/AndroidKeyToSkKey.h
gfx/skia/skia/include/views/unix/XkeysToSkKeys.h
gfx/skia/skia/include/views/unix/keysym2ucs.h
gfx/skia/skia/include/xml/SkJS.h
gfx/skia/skia/src/animator/SkDrawTransparentShader.cpp
gfx/skia/skia/src/animator/SkDrawTransparentShader.h
gfx/skia/skia/src/animator/SkDrawable.cpp
gfx/skia/skia/src/animator/SkDrawable.h
gfx/skia/skia/src/core/SkBBoxHierarchyRecord.cpp
gfx/skia/skia/src/core/SkBBoxHierarchyRecord.h
gfx/skia/skia/src/core/SkBBoxRecord.cpp
gfx/skia/skia/src/core/SkBBoxRecord.h
gfx/skia/skia/src/core/SkBitmap_scroll.cpp
gfx/skia/skia/src/core/SkChecksum.h
gfx/skia/skia/src/core/SkDeviceImageFilterProxy.h
gfx/skia/skia/src/core/SkFlate.cpp
gfx/skia/skia/src/core/SkFlate.h
gfx/skia/skia/src/core/SkFloat.cpp
gfx/skia/skia/src/core/SkFloat.h
gfx/skia/skia/src/core/SkInstCnt.cpp
gfx/skia/skia/src/core/SkLazyFnPtr.h
gfx/skia/skia/src/core/SkLazyPtr.h
gfx/skia/skia/src/core/SkMatrixClipStateMgr.cpp
gfx/skia/skia/src/core/SkMatrixClipStateMgr.h
gfx/skia/skia/src/core/SkPaintOptionsAndroid.cpp
gfx/skia/skia/src/core/SkPatch.cpp
gfx/skia/skia/src/core/SkPathHeap.cpp
gfx/skia/skia/src/core/SkPathHeap.h
gfx/skia/skia/src/core/SkPictureRangePlayback.cpp
gfx/skia/skia/src/core/SkPictureRangePlayback.h
gfx/skia/skia/src/core/SkPictureReplacementPlayback.cpp
gfx/skia/skia/src/core/SkPictureReplacementPlayback.h
gfx/skia/skia/src/core/SkPictureStateTree.cpp
gfx/skia/skia/src/core/SkPictureStateTree.h
gfx/skia/skia/src/core/SkProcSpriteBlitter.cpp
gfx/skia/skia/src/core/SkQuadTree.cpp
gfx/skia/skia/src/core/SkQuadTree.h
gfx/skia/skia/src/core/SkRecordAnalysis.cpp
gfx/skia/skia/src/core/SkRecordAnalysis.h
gfx/skia/skia/src/core/SkRecording.cpp
gfx/skia/skia/src/core/SkRecords.h
gfx/skia/skia/src/core/SkScaledImageCache.cpp
gfx/skia/skia/src/core/SkScaledImageCache.h
gfx/skia/skia/src/core/SkSinTable.h
gfx/skia/skia/src/core/SkTInternalSList.h
gfx/skia/skia/src/core/SkTObjectPool.h
gfx/skia/skia/src/core/SkTRefArray.h
gfx/skia/skia/src/core/SkThreadPriv.h
gfx/skia/skia/src/core/SkTileGrid.cpp
gfx/skia/skia/src/core/SkTileGrid.h
gfx/skia/skia/src/effects/SkAvoidXfermode.cpp
gfx/skia/skia/src/effects/SkBitmapSource.cpp
gfx/skia/skia/src/effects/SkMatrixImageFilter.cpp
gfx/skia/skia/src/effects/SkPorterDuff.cpp
gfx/skia/skia/src/effects/SkStippleMaskFilter.cpp
gfx/skia/skia/src/effects/SkTransparentShader.cpp
gfx/skia/skia/src/effects/gradients/SkBitmapCache.cpp
gfx/skia/skia/src/effects/gradients/SkBitmapCache.h
gfx/skia/skia/src/effects/gradients/SkTwoPointRadialGradient.cpp
gfx/skia/skia/src/effects/gradients/SkTwoPointRadialGradient.h
gfx/skia/skia/src/gpu/GrAAConvexPathRenderer.cpp
gfx/skia/skia/src/gpu/GrAAConvexPathRenderer.h
gfx/skia/skia/src/gpu/GrAAHairLinePathRenderer.cpp
gfx/skia/skia/src/gpu/GrAAHairLinePathRenderer.h
gfx/skia/skia/src/gpu/GrAARectRenderer.cpp
gfx/skia/skia/src/gpu/GrAARectRenderer.h
gfx/skia/skia/src/gpu/GrAddPathRenderers_default.cpp
gfx/skia/skia/src/gpu/GrAllocPool.cpp
gfx/skia/skia/src/gpu/GrAllocPool.h
gfx/skia/skia/src/gpu/GrAtlas.cpp
gfx/skia/skia/src/gpu/GrAtlas.h
gfx/skia/skia/src/gpu/GrBinHashKey.h
gfx/skia/skia/src/gpu/GrBitmapTextContext.cpp
gfx/skia/skia/src/gpu/GrBitmapTextContext.h
gfx/skia/skia/src/gpu/GrBlend.h
gfx/skia/skia/src/gpu/GrCacheID.cpp
gfx/skia/skia/src/gpu/GrClipData.cpp
gfx/skia/skia/src/gpu/GrClipMaskCache.cpp
gfx/skia/skia/src/gpu/GrClipMaskCache.h
gfx/skia/skia/src/gpu/GrDefaultPathRenderer.cpp
gfx/skia/skia/src/gpu/GrDefaultPathRenderer.h
gfx/skia/skia/src/gpu/GrDistanceFieldTextContext.cpp
gfx/skia/skia/src/gpu/GrDistanceFieldTextContext.h
gfx/skia/skia/src/gpu/GrDrawState.cpp
gfx/skia/skia/src/gpu/GrDrawState.h
gfx/skia/skia/src/gpu/GrDrawTargetCaps.h
gfx/skia/skia/src/gpu/GrEffect.cpp
gfx/skia/skia/src/gpu/GrInOrderDrawBuffer.cpp
gfx/skia/skia/src/gpu/GrInOrderDrawBuffer.h
gfx/skia/skia/src/gpu/GrOrderedSet.h
gfx/skia/skia/src/gpu/GrPictureUtils.cpp
gfx/skia/skia/src/gpu/GrPictureUtils.h
gfx/skia/skia/src/gpu/GrPlotMgr.h
gfx/skia/skia/src/gpu/GrRedBlackTree.h
gfx/skia/skia/src/gpu/GrStencilAndCoverPathRenderer.cpp
gfx/skia/skia/src/gpu/GrStencilAndCoverPathRenderer.h
gfx/skia/skia/src/gpu/GrStencilBuffer.cpp
gfx/skia/skia/src/gpu/GrStencilBuffer.h
gfx/skia/skia/src/gpu/GrTBSearch.h
gfx/skia/skia/src/gpu/GrTHashTable.h
gfx/skia/skia/src/gpu/GrTemplates.h
gfx/skia/skia/src/gpu/GrTextStrike.cpp
gfx/skia/skia/src/gpu/GrTextStrike.h
gfx/skia/skia/src/gpu/GrTextStrike_impl.h
gfx/skia/skia/src/gpu/effects/GrCustomCoordsTextureEffect.cpp
gfx/skia/skia/src/gpu/effects/GrCustomCoordsTextureEffect.h
gfx/skia/skia/src/gpu/effects/GrDistanceFieldTextureEffect.cpp
gfx/skia/skia/src/gpu/effects/GrDistanceFieldTextureEffect.h
gfx/skia/skia/src/gpu/effects/GrVertexEffect.h
gfx/skia/skia/src/gpu/gl/GrGLAssembleGLESInterface.h
gfx/skia/skia/src/gpu/gl/GrGLEffect.h
gfx/skia/skia/src/gpu/gl/GrGLProgramEffects.cpp
gfx/skia/skia/src/gpu/gl/GrGLProgramEffects.h
gfx/skia/skia/src/gpu/gl/GrGLSL.cpp
gfx/skia/skia/src/gpu/gl/GrGLSL.h
gfx/skia/skia/src/gpu/gl/GrGLSL_impl.h
gfx/skia/skia/src/gpu/gl/GrGLShaderBuilder.cpp
gfx/skia/skia/src/gpu/gl/GrGLShaderBuilder.h
gfx/skia/skia/src/gpu/gl/GrGLShaderVar.h
gfx/skia/skia/src/gpu/gl/GrGLStencilBuffer.cpp
gfx/skia/skia/src/gpu/gl/GrGLStencilBuffer.h
gfx/skia/skia/src/gpu/gl/GrGLUniformHandle.h
gfx/skia/skia/src/gpu/gl/GrGLUniformManager.cpp
gfx/skia/skia/src/gpu/gl/GrGLUniformManager.h
gfx/skia/skia/src/gpu/gl/GrGLVertexEffect.h
gfx/skia/skia/src/gpu/gl/GrGpuGL.cpp
gfx/skia/skia/src/gpu/gl/GrGpuGL.h
gfx/skia/skia/src/gpu/gl/GrGpuGL_program.cpp
gfx/skia/skia/src/gpu/gl/SkGLContextHelper.cpp
gfx/skia/skia/src/gpu/gl/android/SkNativeGLContext_android.cpp
gfx/skia/skia/src/gpu/gl/iOS/SkNativeGLContext_iOS.mm
gfx/skia/skia/src/gpu/gl/mac/SkNativeGLContext_mac.cpp
gfx/skia/skia/src/gpu/gl/nacl/SkNativeGLContext_nacl.cpp
gfx/skia/skia/src/gpu/gl/unix/GrGLCreateNativeInterface_unix.cpp
gfx/skia/skia/src/gpu/gl/unix/SkNativeGLContext_unix.cpp
gfx/skia/skia/src/gpu/gl/win/SkNativeGLContext_win.cpp
gfx/skia/skia/src/image/SkImagePriv.cpp
gfx/skia/skia/src/image/SkImage_Codec.cpp
gfx/skia/skia/src/lazy/SkCachingPixelRef.cpp
gfx/skia/skia/src/lazy/SkCachingPixelRef.h
gfx/skia/skia/src/opts/SkBlitRect_opts_SSE2.cpp
gfx/skia/skia/src/opts/SkBlitRect_opts_SSE2.h
gfx/skia/skia/src/opts/SkBlitRow_opts_SSE4_asm.S
gfx/skia/skia/src/opts/SkBlitRow_opts_SSE4_x64_asm.S
gfx/skia/skia/src/opts/SkBlurImage_opts.h
gfx/skia/skia/src/opts/SkBlurImage_opts_SSE2.cpp
gfx/skia/skia/src/opts/SkBlurImage_opts_SSE2.h
gfx/skia/skia/src/opts/SkBlurImage_opts_SSE4.cpp
gfx/skia/skia/src/opts/SkBlurImage_opts_SSE4.h
gfx/skia/skia/src/opts/SkBlurImage_opts_arm.cpp
gfx/skia/skia/src/opts/SkBlurImage_opts_neon.cpp
gfx/skia/skia/src/opts/SkBlurImage_opts_neon.h
gfx/skia/skia/src/opts/SkBlurImage_opts_none.cpp
gfx/skia/skia/src/opts/SkMath_opts_SSE2.h
gfx/skia/skia/src/opts/SkMorphology_opts.h
gfx/skia/skia/src/opts/SkMorphology_opts_SSE2.cpp
gfx/skia/skia/src/opts/SkMorphology_opts_SSE2.h
gfx/skia/skia/src/opts/SkMorphology_opts_arm.cpp
gfx/skia/skia/src/opts/SkMorphology_opts_neon.cpp
gfx/skia/skia/src/opts/SkMorphology_opts_neon.h
gfx/skia/skia/src/opts/SkMorphology_opts_none.cpp
gfx/skia/skia/src/opts/SkTextureCompression_opts.h
gfx/skia/skia/src/opts/SkTextureCompression_opts_arm.cpp
gfx/skia/skia/src/opts/SkTextureCompression_opts_neon.cpp
gfx/skia/skia/src/opts/SkTextureCompression_opts_neon.h
gfx/skia/skia/src/opts/SkTextureCompression_opts_none.cpp
gfx/skia/skia/src/opts/SkUtils_opts_SSE2.cpp
gfx/skia/skia/src/opts/SkUtils_opts_SSE2.h
gfx/skia/skia/src/opts/SkUtils_opts_arm.cpp
gfx/skia/skia/src/opts/SkUtils_opts_none.cpp
gfx/skia/skia/src/opts/SkXfermode_opts_SSE2.cpp
gfx/skia/skia/src/opts/SkXfermode_opts_SSE2.h
gfx/skia/skia/src/opts/SkXfermode_opts_arm.cpp
gfx/skia/skia/src/opts/SkXfermode_opts_arm_neon.cpp
gfx/skia/skia/src/opts/SkXfermode_opts_arm_neon.h
gfx/skia/skia/src/opts/SkXfermode_opts_none.cpp
gfx/skia/skia/src/opts/memset.arm.S
gfx/skia/skia/src/opts/memset16_neon.S
gfx/skia/skia/src/opts/memset32_neon.S
gfx/skia/skia/src/pathops/SkDCubicIntersection.cpp
gfx/skia/skia/src/pathops/SkDQuadImplicit.cpp
gfx/skia/skia/src/pathops/SkDQuadImplicit.h
gfx/skia/skia/src/pathops/SkDQuadIntersection.cpp
gfx/skia/skia/src/pathops/SkPathOpsBounds.cpp
gfx/skia/skia/src/pathops/SkPathOpsTriangle.cpp
gfx/skia/skia/src/pathops/SkPathOpsTriangle.h
gfx/skia/skia/src/pathops/SkQuarticRoot.cpp
gfx/skia/skia/src/pathops/SkQuarticRoot.h
gfx/skia/skia/src/pdf/SkPDFCatalog.cpp
gfx/skia/skia/src/pdf/SkPDFCatalog.h
gfx/skia/skia/src/pdf/SkPDFDeviceFlattener.cpp
gfx/skia/skia/src/pdf/SkPDFDeviceFlattener.h
gfx/skia/skia/src/pdf/SkPDFDocument.cpp
gfx/skia/skia/src/pdf/SkPDFImage.cpp
gfx/skia/skia/src/pdf/SkPDFImage.h
gfx/skia/skia/src/pdf/SkPDFPage.cpp
gfx/skia/skia/src/pdf/SkPDFPage.h
gfx/skia/skia/src/pdf/SkTSet.h
gfx/skia/skia/src/ports/SkAtomics_sync.h
gfx/skia/skia/src/ports/SkAtomics_win.h
gfx/skia/skia/src/ports/SkBarriers_arm.h
gfx/skia/skia/src/ports/SkBarriers_tsan.h
gfx/skia/skia/src/ports/SkBarriers_x86.h
gfx/skia/skia/src/ports/SkDebug_nacl.cpp
gfx/skia/skia/src/ports/SkFontConfigInterface_android.cpp
gfx/skia/skia/src/ports/SkFontConfigParser_android.cpp
gfx/skia/skia/src/ports/SkFontConfigParser_android.h
gfx/skia/skia/src/ports/SkFontHost_android_old.cpp
gfx/skia/skia/src/ports/SkFontHost_linux.cpp
gfx/skia/skia/src/ports/SkFontHost_none.cpp
gfx/skia/skia/src/ports/SkFontMgr_default_dw.cpp
gfx/skia/skia/src/ports/SkFontMgr_default_gdi.cpp
gfx/skia/skia/src/ports/SkMutex_pthread.h
gfx/skia/skia/src/ports/SkMutex_win.h
gfx/skia/skia/src/ports/SkOSFile_none.cpp
gfx/skia/skia/src/ports/SkXMLParser_empty.cpp
gfx/skia/skia/src/svg/SkSVG.cpp
gfx/skia/skia/src/svg/SkSVGCircle.cpp
gfx/skia/skia/src/svg/SkSVGCircle.h
gfx/skia/skia/src/svg/SkSVGClipPath.cpp
gfx/skia/skia/src/svg/SkSVGClipPath.h
gfx/skia/skia/src/svg/SkSVGDefs.cpp
gfx/skia/skia/src/svg/SkSVGDefs.h
gfx/skia/skia/src/svg/SkSVGElements.cpp
gfx/skia/skia/src/svg/SkSVGElements.h
gfx/skia/skia/src/svg/SkSVGEllipse.cpp
gfx/skia/skia/src/svg/SkSVGEllipse.h
gfx/skia/skia/src/svg/SkSVGFeColorMatrix.cpp
gfx/skia/skia/src/svg/SkSVGFeColorMatrix.h
gfx/skia/skia/src/svg/SkSVGFilter.cpp
gfx/skia/skia/src/svg/SkSVGFilter.h
gfx/skia/skia/src/svg/SkSVGG.cpp
gfx/skia/skia/src/svg/SkSVGG.h
gfx/skia/skia/src/svg/SkSVGGradient.cpp
gfx/skia/skia/src/svg/SkSVGGradient.h
gfx/skia/skia/src/svg/SkSVGGroup.cpp
gfx/skia/skia/src/svg/SkSVGGroup.h
gfx/skia/skia/src/svg/SkSVGImage.cpp
gfx/skia/skia/src/svg/SkSVGImage.h
gfx/skia/skia/src/svg/SkSVGLine.cpp
gfx/skia/skia/src/svg/SkSVGLine.h
gfx/skia/skia/src/svg/SkSVGLinearGradient.cpp
gfx/skia/skia/src/svg/SkSVGLinearGradient.h
gfx/skia/skia/src/svg/SkSVGMask.cpp
gfx/skia/skia/src/svg/SkSVGMask.h
gfx/skia/skia/src/svg/SkSVGMetadata.cpp
gfx/skia/skia/src/svg/SkSVGMetadata.h
gfx/skia/skia/src/svg/SkSVGPaintState.cpp
gfx/skia/skia/src/svg/SkSVGParser.cpp
gfx/skia/skia/src/svg/SkSVGPath.cpp
gfx/skia/skia/src/svg/SkSVGPath.h
gfx/skia/skia/src/svg/SkSVGPolygon.cpp
gfx/skia/skia/src/svg/SkSVGPolygon.h
gfx/skia/skia/src/svg/SkSVGPolyline.cpp
gfx/skia/skia/src/svg/SkSVGPolyline.h
gfx/skia/skia/src/svg/SkSVGRadialGradient.cpp
gfx/skia/skia/src/svg/SkSVGRadialGradient.h
gfx/skia/skia/src/svg/SkSVGRect.cpp
gfx/skia/skia/src/svg/SkSVGRect.h
gfx/skia/skia/src/svg/SkSVGSVG.cpp
gfx/skia/skia/src/svg/SkSVGSVG.h
gfx/skia/skia/src/svg/SkSVGStop.cpp
gfx/skia/skia/src/svg/SkSVGStop.h
gfx/skia/skia/src/svg/SkSVGSymbol.cpp
gfx/skia/skia/src/svg/SkSVGSymbol.h
gfx/skia/skia/src/svg/SkSVGText.cpp
gfx/skia/skia/src/svg/SkSVGText.h
gfx/skia/skia/src/svg/SkSVGUse.cpp
gfx/skia/skia/src/svg/SkSVGUse.h
gfx/skia/skia/src/utils/SkCondVar.cpp
gfx/skia/skia/src/utils/SkDeferredCanvas.cpp
gfx/skia/skia/src/utils/SkGatherPixelRefsAndRects.cpp
gfx/skia/skia/src/utils/SkGatherPixelRefsAndRects.h
gfx/skia/skia/src/utils/SkPDFRasterizer.cpp
gfx/skia/skia/src/utils/SkPDFRasterizer.h
gfx/skia/skia/src/utils/SkPathUtils.cpp
gfx/skia/skia/src/utils/SkPictureUtils.cpp
gfx/skia/skia/src/utils/SkProxyCanvas.cpp
gfx/skia/skia/src/utils/SkTLogic.h
gfx/skia/skia/src/utils/SkThreadUtils_pthread_linux.cpp
gfx/skia/skia/src/utils/SkThreadUtils_pthread_mach.cpp
gfx/skia/skia/src/utils/SkThreadUtils_pthread_other.cpp
gfx/skia/skia/src/utils/ios/SkFontHost_iOS.mm
gfx/skia/skia/src/utils/ios/SkImageDecoder_iOS.mm
gfx/skia/skia/src/utils/ios/SkOSFile_iOS.mm
gfx/skia/skia/src/utils/ios/SkStream_NSData.mm
gfx/skia/skia/src/views/SkTextBox.cpp
gfx/skia/skia/src/views/mac/SampleApp-Info.plist
gfx/skia/skia/src/views/mac/SampleApp.xib
gfx/skia/skia/src/views/mac/SampleAppDelegate.h
gfx/skia/skia/src/views/mac/SampleAppDelegate.mm
gfx/skia/skia/src/views/mac/SkOSWindow_Mac.cpp
gfx/skia/skia/src/xml/SkJS.cpp
gfx/skia/skia/src/xml/SkJSDisplayable.cpp
--- a/b2g/dev/config/tooltool-manifests/linux64/releng.manifest
+++ b/b2g/dev/config/tooltool-manifests/linux64/releng.manifest
@@ -10,18 +10,18 @@
 "size": 12057960,
 "digest": "6105d6432943141cffb40020dc5ba3a793650bdeb3af9bd5e56d3796c5f03df9962a73e521646cd71fbfb5e266c1e74716ad722fb6055589dfb7d35175bca89e",
 "algorithm": "sha512",
 "filename": "gtk3.tar.xz",
 "setup": "setup.sh",
 "unpack": true
 },
 {
-"size": 73029932,
-"digest": "ef1818acf065838dcb72554e521f9fd7098f0a3690cb6a3106d7bf18f46c342bfdd5a2b7d86e92ee3ddb9e478380343e58ecf8fd242807b8881a2d53fbec5ab3",
+"size": 193213220,
+"digest": "58b8ebd8de923117831dcbba71172a53e26c25bd16c2b2bb363a1254f2cd4e87f95e2c5f41e2dce10e18e43a17e0a395637c9ddcbf1e27673582792f9095c62e",
 "algorithm": "sha512",
 "filename": "rustc.tar.xz",
 "unpack": true
 },
 {
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
--- a/browser/config/tooltool-manifests/linux64/releng.manifest
+++ b/browser/config/tooltool-manifests/linux64/releng.manifest
@@ -9,22 +9,22 @@
 {
 "size": 12057960,
 "digest": "6105d6432943141cffb40020dc5ba3a793650bdeb3af9bd5e56d3796c5f03df9962a73e521646cd71fbfb5e266c1e74716ad722fb6055589dfb7d35175bca89e",
 "algorithm": "sha512",
 "filename": "gtk3.tar.xz",
 "unpack": true
 },
 {
+"size": 193213220,
+"digest": "58b8ebd8de923117831dcbba71172a53e26c25bd16c2b2bb363a1254f2cd4e87f95e2c5f41e2dce10e18e43a17e0a395637c9ddcbf1e27673582792f9095c62e",
+"algorithm": "sha512",
+"filename": "rustc.tar.xz",
+"unpack": true
+},
+{
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
 "filename": "sccache.tar.bz2",
 "unpack": true
-},
-{
-"size": 73029932,
-"digest": "ef1818acf065838dcb72554e521f9fd7098f0a3690cb6a3106d7bf18f46c342bfdd5a2b7d86e92ee3ddb9e478380343e58ecf8fd242807b8881a2d53fbec5ab3",
-"algorithm": "sha512",
-"filename": "rustc.tar.xz",
-"unpack": true
 }
 ]
--- a/browser/config/tooltool-manifests/macosx64/releng.manifest
+++ b/browser/config/tooltool-manifests/macosx64/releng.manifest
@@ -5,18 +5,18 @@
 {
 "size": 121389802,
 "digest": "2be6b42cfa1e92de4b49a57123f54043fec2d3cf8385276516dc6aaed99c88768ac4aebd7ce2e007ab074163523da29223436a4d1aef82f0f750f08f1b14cd71",
 "algorithm": "sha512",
 "filename": "clang.tar.bz2",
 "unpack": true
 },
 {
-"size": 128301120,
-"digest": "d2d71103a6cec84b150b8f08bfef2682aa713c2d6eadce584f79836ef27ba4380ffb444165d999b79605f18ad165641a7a8cc0e04a201675ad5f655a59adbda9",
+"size": 215952362,
+"digest": "5e9825dbe83b2a157879076da70fc5c989a1638e30d3b14a9901b166db09013c356a9dc4eaf6c16209a1832d9cb1c67ca869e9b9003cab8355a7f03b3dc08775",
 "algorithm": "sha512",
 "filename": "rustc.tar.bz2",
 "unpack": true
 },
 {
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
--- a/browser/locales/Makefile.in
+++ b/browser/locales/Makefile.in
@@ -67,17 +67,17 @@ STUB_HOOK = $(NSINSTALL) -D '$(_ABS_DIST
     chmod 0755 '$(_ABS_DIST)/$(PKG_INST_PATH)$(PKG_STUB_BASENAME).exe'; \
     $(NULL)
 endif
 
 SEARCHPLUGINS_NAMES = $(shell cat $(call MERGE_FILE,/searchplugins/list.txt)) ddg
 SEARCHPLUGINS_FILENAMES = $(subst :hidden,,$(SEARCHPLUGINS_NAMES))
 SEARCHPLUGINS_PATH := .deps/generated_$(AB_CD)
 SEARCHPLUGINS_TARGET := libs searchplugins
-SEARCHPLUGINS := $(foreach plugin,$(addsuffix .xml,$(SEARCHPLUGINS_FILENAMES)),$(or $(wildcard $(call EN_US_OR_L10N_FILE,searchplugins/$(plugin))),$(info Missing searchplugin: $(plugin))))
+SEARCHPLUGINS := $(foreach plugin,$(addsuffix .xml,$(SEARCHPLUGINS_FILENAMES)),$(or $(wildcard $(call EN_US_OR_L10N_FILE,searchplugins/$(plugin))),$(warning Missing searchplugin: $(plugin))))
 # Some locale-specific search plugins may have preprocessor directives, but the
 # default en-US ones do not.
 SEARCHPLUGINS_FLAGS := --silence-missing-directive-warnings
 PP_TARGETS += SEARCHPLUGINS
 
 list-txt = $(SEARCHPLUGINS_PATH)/list.txt
 GARBAGE += $(list-txt)
 
--- a/build/autoconf/rust.m4
+++ b/build/autoconf/rust.m4
@@ -20,17 +20,17 @@ AC_DEFUN([MOZ_RUST_SUPPORT], [
                       [MOZ_RUST= ])
   if test -z "$RUSTC" -a -n "$MOZ_RUST"; then
     AC_MSG_ERROR([Rust compiler not found.
       To compile rust language sources, you must have 'rustc' in your path.
       See http://www.rust-lang.org/ for more information.])
   fi
   if test -n "$MOZ_RUST" && test -z "$_RUSTC_MAJOR_VERSION" -o \
     "$_RUSTC_MAJOR_VERSION" -lt 1 -o \
-    \( "$_RUSTC_MAJOR_VERSION" -eq 1 -a "$_RUSTC_MINOR_VERSION" -lt 4 \); then
+    \( "$_RUSTC_MAJOR_VERSION" -eq 1 -a "$_RUSTC_MINOR_VERSION" -lt 5 \); then
     AC_MSG_ERROR([Rust compiler ${RUSTC_VERSION} is too old.
       To compile Rust language sources please install at least
-      version 1.4 of the 'rustc' toolchain and make sure it is
+      version 1.5 of the 'rustc' toolchain and make sure it is
       first in your path.
       You can verify this by typing 'rustc --version'.])
   fi
   AC_SUBST(MOZ_RUST)
 ])
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -940,17 +940,17 @@ ifdef ASFILES
 	$(AS) $(ASOUTOPTION)$@ $(ASFLAGS) $($(notdir $<)_FLAGS) $(AS_DASH_C_FLAG) $(_VPATH_SRCS)
 endif
 
 ifdef MOZ_RUST
 # Assume any system libraries rustc links against are already
 # in the target's LIBS.
 $(RSOBJS):
 	$(REPORT_BUILD)
-	$(RUSTC) $(RUSTFLAGS) --crate-type staticlib -o $(call mk_libname,$<) $(_VPATH_SRCS)
+	$(RUSTC) $(RUSTFLAGS) --crate-type staticlib --emit dep-info=$(MDDEPDIR)/$(call mk_libname,$<).pp,link=$(call mk_libname,$<) $(_VPATH_SRCS)
 endif
 
 $(SOBJS):
 	$(REPORT_BUILD)
 	$(AS) -o $@ $(ASFLAGS) $($(notdir $<)_FLAGS) $(LOCAL_INCLUDES) $(TARGET_LOCAL_INCLUDES) -c $<
 
 $(CPPOBJS):
 	$(REPORT_BUILD)
--- a/configure.in
+++ b/configure.in
@@ -6401,24 +6401,16 @@ if test -z "$TAR"; then
     AC_MSG_ERROR([no tar archiver found in \$PATH])
 fi
 AC_SUBST(TAR)
 
 AC_CHECK_PROGS(WGET, wget, "")
 AC_SUBST(WGET)
 
 dnl ========================================================
-dnl Signing
-dnl ========================================================
-
-if test -n "$MOZ_SIGN_CMD"; then
-    AC_DEFINE(MOZ_SIGNING)
-fi
-
-dnl ========================================================
 dnl Maintenance Service
 dnl ========================================================
 
 MOZ_ARG_ENABLE_BOOL(maintenance-service,
 [  --enable-maintenance-service       Enable building of maintenanceservice],
     MOZ_MAINTENANCE_SERVICE=1,
     MOZ_MAINTENANCE_SERVICE= )
 
--- a/devtools/shared/heapsnapshot/DominatorTree.cpp
+++ b/devtools/shared/heapsnapshot/DominatorTree.cpp
@@ -1,42 +1,28 @@
 /* -*-  Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
 /* 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/. */
 
 #include "mozilla/devtools/DominatorTree.h"
-#include "js/Debug.h"
-#include "mozilla/CycleCollectedJSRuntime.h"
 #include "mozilla/dom/DominatorTreeBinding.h"
 
 namespace mozilla {
 namespace devtools {
 
-static MallocSizeOf
-getCurrentThreadDebuggerMallocSizeOf()
-{
-  auto ccrt = CycleCollectedJSRuntime::Get();
-  MOZ_ASSERT(ccrt);
-  auto rt = ccrt->Runtime();
-  MOZ_ASSERT(rt);
-  auto mallocSizeOf = JS::dbg::GetDebuggerMallocSizeOf(rt);
-  MOZ_ASSERT(mallocSizeOf);
-  return mallocSizeOf;
-}
-
 dom::Nullable<uint64_t>
 DominatorTree::GetRetainedSize(uint64_t aNodeId, ErrorResult& aRv)
 {
   JS::ubi::Node::Id id(aNodeId);
   auto node = mHeapSnapshot->getNodeById(id);
   if (node.isNothing())
     return dom::Nullable<uint64_t>();
 
-  auto mallocSizeOf = getCurrentThreadDebuggerMallocSizeOf();
+  auto mallocSizeOf = GetCurrentThreadDebuggerMallocSizeOf();
   JS::ubi::Node::Size size = 0;
   if (!mDominatorTree.getRetainedSize(*node, mallocSizeOf, size)) {
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return dom::Nullable<uint64_t>();
   }
 
   MOZ_ASSERT(size != 0,
              "The node should not have been unknown since we got it from the heap snapshot.");
@@ -78,17 +64,17 @@ DominatorTree::GetImmediatelyDominated(u
   MOZ_ASSERT(aOutResult.IsNull());
 
   JS::ubi::Node::Id id(aNodeId);
   Maybe<JS::ubi::Node> node = mHeapSnapshot->getNodeById(id);
   if (node.isNothing())
     return;
 
   // Get all immediately dominated nodes and their retained sizes.
-  MallocSizeOf mallocSizeOf = getCurrentThreadDebuggerMallocSizeOf();
+  MallocSizeOf mallocSizeOf = GetCurrentThreadDebuggerMallocSizeOf();
   Maybe<JS::ubi::DominatorTree::DominatedSetRange> range = mDominatorTree.getDominatedSet(*node);
   MOZ_ASSERT(range.isSome(), "The node should be known, since we got it from the heap snapshot.");
   size_t length = range->length();
   nsTArray<NodeAndRetainedSize> dominatedNodes(length);
   for (const JS::ubi::Node& dominatedNode : *range) {
     JS::ubi::Node::Size retainedSize = 0;
     if (NS_WARN_IF(!mDominatorTree.getRetainedSize(dominatedNode, mallocSizeOf, retainedSize))) {
       aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
--- a/devtools/shared/heapsnapshot/HeapSnapshot.cpp
+++ b/devtools/shared/heapsnapshot/HeapSnapshot.cpp
@@ -50,16 +50,28 @@ using namespace dom;
 
 using ::google::protobuf::io::ArrayInputStream;
 using ::google::protobuf::io::CodedInputStream;
 using ::google::protobuf::io::GzipInputStream;
 using ::google::protobuf::io::ZeroCopyInputStream;
 
 using JS::ubi::AtomOrTwoByteChars;
 
+MallocSizeOf
+GetCurrentThreadDebuggerMallocSizeOf()
+{
+  auto ccrt = CycleCollectedJSRuntime::Get();
+  MOZ_ASSERT(ccrt);
+  auto rt = ccrt->Runtime();
+  MOZ_ASSERT(rt);
+  auto mallocSizeOf = JS::dbg::GetDebuggerMallocSizeOf(rt);
+  MOZ_ASSERT(mallocSizeOf);
+  return mallocSizeOf;
+}
+
 /*** Cycle Collection Boilerplate *****************************************************************/
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(HeapSnapshot, mParent)
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(HeapSnapshot)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(HeapSnapshot)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(HeapSnapshot)
@@ -477,17 +489,17 @@ HeapSnapshot::TakeCensus(JSContext* cx, 
   }
 
   JS::ubi::RootedCount rootCount(cx, rootType->makeCount());
   if (NS_WARN_IF(!rootCount)) {
     rv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return;
   }
 
-  JS::ubi::CensusHandler handler(census, rootCount);
+  JS::ubi::CensusHandler handler(census, rootCount, GetCurrentThreadDebuggerMallocSizeOf());
 
   {
     JS::AutoCheckCannotGC nogc;
 
     JS::ubi::CensusTraversal traversal(JS_GetRuntime(cx), handler, nogc);
     if (NS_WARN_IF(!traversal.init())) {
       rv.Throw(NS_ERROR_OUT_OF_MEMORY);
       return;
@@ -499,17 +511,17 @@ HeapSnapshot::TakeCensus(JSContext* cx, 
     }
 
     if (NS_WARN_IF(!traversal.traverse())) {
       rv.Throw(NS_ERROR_UNEXPECTED);
       return;
     }
   }
 
-  if (NS_WARN_IF(!handler.report(rval))) {
+  if (NS_WARN_IF(!handler.report(cx, rval))) {
     rv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return;
   }
 }
 
 already_AddRefed<DominatorTree>
 HeapSnapshot::ComputeDominatorTree(ErrorResult& rv)
 {
--- a/devtools/shared/heapsnapshot/HeapSnapshot.h
+++ b/devtools/shared/heapsnapshot/HeapSnapshot.h
@@ -216,12 +216,15 @@ WriteHeapGraph(JSContext* cx,
                JS::AutoCheckCannotGC& noGC)
 {
   uint32_t ignoreNodeCount;
   uint32_t ignoreEdgeCount;
   return WriteHeapGraph(cx, node, writer, wantNames, zones, noGC,
                         ignoreNodeCount, ignoreEdgeCount);
 }
 
+// Get the mozilla::MallocSizeOf for the current thread's JSRuntime.
+MallocSizeOf GetCurrentThreadDebuggerMallocSizeOf();
+
 } // namespace devtools
 } // namespace mozilla
 
 #endif // mozilla_devtools_HeapSnapshot__
--- a/dom/base/FileReader.cpp
+++ b/dom/base/FileReader.cpp
@@ -1,44 +1,44 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
 #include "FileReader.h"
 
-#include "nsContentCID.h"
-#include "nsContentUtils.h"
-#include "nsDOMClassInfoID.h"
-#include "nsError.h"
-#include "nsIFile.h"
-#include "nsNetCID.h"
-#include "nsNetUtil.h"
+#include "nsIEventTarget.h"
+#include "nsIGlobalObject.h"
+#include "nsITimer.h"
+#include "nsITransport.h"
+#include "nsIStreamTransportService.h"
 
-#include "nsXPCOM.h"
-#include "nsIDOMEventListener.h"
-#include "nsJSEnvironment.h"
-#include "nsCycleCollectionParticipant.h"
 #include "mozilla/Base64.h"
+#include "mozilla/dom/DOMError.h"
 #include "mozilla/dom/EncodingUtils.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/FileReaderBinding.h"
 #include "mozilla/dom/ProgressEvent.h"
+#include "nsContentUtils.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDOMJSUtils.h"
+#include "nsError.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
 #include "xpcpublic.h"
-#include "nsDOMJSUtils.h"
 
-#include "jsfriendapi.h"
-
-#include "nsITransport.h"
-#include "nsIStreamTransportService.h"
+#include "WorkerPrivate.h"
+#include "WorkerScope.h"
 
 namespace mozilla {
 namespace dom {
 
+using namespace workers;
+
 #define ABORT_STR "abort"
 #define LOAD_STR "load"
 #define LOADSTART_STR "loadstart"
 #define LOADEND_STR "loadend"
 #define ERROR_STR "error"
 #define PROGRESS_STR "progress"
 
 const uint64_t kUnknownSize = uint64_t(-1);
@@ -51,17 +51,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
                                                   DOMEventTargetHelper)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBlob)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mProgressNotifier)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mError)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FileReader,
                                                 DOMEventTargetHelper)
-  tmp->mResultArrayBuffer = nullptr;
+  tmp->Shutdown();
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mBlob)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mProgressNotifier)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mError)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(FileReader,
                                                DOMEventTargetHelper)
   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResultArrayBuffer)
@@ -71,54 +71,81 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_
   NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
   NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(FileReader, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(FileReader, DOMEventTargetHelper)
 
+class MOZ_RAII FileReaderDecreaseBusyCounter
+{
+  RefPtr<FileReader> mFileReader;
+public:
+  explicit FileReaderDecreaseBusyCounter(FileReader* aFileReader)
+    : mFileReader(aFileReader)
+  {}
+
+  ~FileReaderDecreaseBusyCounter()
+  {
+    mFileReader->DecreaseBusyCounter();
+  }
+};
+
 void
 FileReader::RootResultArrayBuffer()
 {
   mozilla::HoldJSObjects(this);
 }
 
 //FileReader constructors/initializers
 
-FileReader::FileReader(nsPIDOMWindow* aWindow)
+FileReader::FileReader(nsPIDOMWindow* aWindow,
+                       WorkerPrivate* aWorkerPrivate)
   : DOMEventTargetHelper(aWindow)
   , mFileData(nullptr)
   , mDataLen(0)
   , mDataFormat(FILE_AS_BINARY)
   , mResultArrayBuffer(nullptr)
   , mProgressEventWasDelayed(false)
   , mTimerIsActive(false)
   , mReadyState(EMPTY)
   , mTotal(0)
   , mTransferred(0)
+  , mTarget(do_GetCurrentThread())
+  , mBusyCount(0)
+  , mWorkerPrivate(aWorkerPrivate)
 {
+  MOZ_ASSERT_IF(!NS_IsMainThread(), mWorkerPrivate && !aWindow);
+  MOZ_ASSERT_IF(NS_IsMainThread(), !mWorkerPrivate);
   SetDOMStringToNull(mResult);
 }
 
 FileReader::~FileReader()
 {
-  FreeFileData();
-  mResultArrayBuffer = nullptr;
+  Shutdown();
   DropJSObjects(this);
 }
 
 /* static */ already_AddRefed<FileReader>
 FileReader::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv)
 {
   // The owner can be null when this object is used by chrome code.
   nsCOMPtr<nsPIDOMWindow> owner = do_QueryInterface(aGlobal.GetAsSupports());
-  RefPtr<FileReader> fileReader = new FileReader(owner);
+  WorkerPrivate* workerPrivate = nullptr;
 
-  if (!owner && nsContentUtils::IsCallerChrome()) {
+  if (!NS_IsMainThread()) {
+    JSContext* cx = aGlobal.Context();
+    workerPrivate = GetWorkerPrivateFromContext(cx);
+    MOZ_ASSERT(workerPrivate);
+  }
+
+  RefPtr<FileReader> fileReader = new FileReader(owner, workerPrivate);
+
+  if (!owner && nsContentUtils::ThreadsafeIsCallerChrome()) {
     // Instead of grabbing some random global from the context stack,
     // let's use the default one (junk scope) for now.
     // We should move away from this Init...
     fileReader->BindToOwner(xpc::NativeGlobal(xpc::PrivilegedJunkScope()));
   }
 
   return fileReader.forget();
 }
@@ -210,17 +237,27 @@ FileReader::DoOnLoadEnd(nsresult aStatus
 
   aSuccessEvent = NS_LITERAL_STRING(LOAD_STR);
   aTerminationEvent = NS_LITERAL_STRING(LOADEND_STR);
 
   nsresult rv = NS_OK;
   switch (mDataFormat) {
     case FILE_AS_ARRAYBUFFER: {
       AutoJSAPI jsapi;
-      if (NS_WARN_IF(!jsapi.Init(DOMEventTargetHelper::GetParentObject()))) {
+      nsCOMPtr<nsIGlobalObject> globalObject;
+
+      if (NS_IsMainThread()) {
+        globalObject = do_QueryInterface(GetParentObject());
+      } else {
+        MOZ_ASSERT(mWorkerPrivate);
+        MOZ_ASSERT(mBusyCount);
+        globalObject = mWorkerPrivate->GlobalScope();
+      }
+
+      if (!globalObject || !jsapi.Init(globalObject)) {
         FreeFileData();
         return NS_ERROR_FAILURE;
       }
 
       RootResultArrayBuffer();
       mResultArrayBuffer = JS_NewArrayBufferWithContents(jsapi.cx(), mDataLen, mFileData);
       if (!mResultArrayBuffer) {
         JS_ClearPendingException(jsapi.cx());
@@ -251,48 +288,68 @@ FileReader::DoOnLoadEnd(nsresult aStatus
   mResult.SetIsVoid(false);
 
   FreeFileData();
 
   return rv;
 }
 
 nsresult
-FileReader::DoReadData(nsIAsyncInputStream* aStream, uint64_t aCount)
+FileReader::DoAsyncWait()
 {
-  MOZ_ASSERT(aStream);
+  nsresult rv = IncreaseBusyCounter();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = mAsyncStream->AsyncWait(this,
+                               /* aFlags*/ 0,
+                               /* aRequestedCount */ 0,
+                               mTarget);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    DecreaseBusyCounter();
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+FileReader::DoReadData(uint64_t aCount)
+{
+  MOZ_ASSERT(mAsyncStream);
 
   if (mDataFormat == FILE_AS_BINARY) {
     //Continuously update our binary string as data comes in
     uint32_t oldLen = mResult.Length();
     NS_ASSERTION(mResult.Length() == mDataLen, "unexpected mResult length");
     if (uint64_t(oldLen) + aCount > UINT32_MAX)
       return NS_ERROR_OUT_OF_MEMORY;
     char16_t *buf = nullptr;
     mResult.GetMutableData(&buf, oldLen + aCount, fallible);
     NS_ENSURE_TRUE(buf, NS_ERROR_OUT_OF_MEMORY);
 
     uint32_t bytesRead = 0;
-    aStream->ReadSegments(ReadFuncBinaryString, buf + oldLen, aCount,
-                          &bytesRead);
+    mAsyncStream->ReadSegments(ReadFuncBinaryString, buf + oldLen, aCount,
+                               &bytesRead);
     NS_ASSERTION(bytesRead == aCount, "failed to read data");
   }
   else {
     //Update memory buffer to reflect the contents of the file
     if (mDataLen + aCount > UINT32_MAX) {
       // PR_Realloc doesn't support over 4GB memory size even if 64-bit OS
       return NS_ERROR_OUT_OF_MEMORY;
     }
     if (mDataFormat != FILE_AS_ARRAYBUFFER) {
       mFileData = (char *) realloc(mFileData, mDataLen + aCount);
       NS_ENSURE_TRUE(mFileData, NS_ERROR_OUT_OF_MEMORY);
     }
 
     uint32_t bytesRead = 0;
-    aStream->Read(mFileData + mDataLen, aCount, &bytesRead);
+    mAsyncStream->Read(mFileData + mDataLen, aCount, &bytesRead);
     NS_ASSERTION(bytesRead == aCount, "failed to read data");
   }
 
   mDataLen += aCount;
   return NS_OK;
 }
 
 // Helper methods
@@ -356,20 +413,17 @@ FileReader::ReadFileContent(Blob& aBlob,
   mAsyncStream = do_QueryInterface(wrapper);
   MOZ_ASSERT(mAsyncStream);
 
   mTotal = mBlob->GetSize(aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
-  aRv = mAsyncStream->AsyncWait(this,
-                                /* aFlags*/ 0,
-                                /* aRequestedCount */ 0,
-                                NS_GetCurrentThread());
+  aRv = DoAsyncWait();
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   //FileReader should be in loading state here
   mReadyState = LOADING;
   DispatchProgressEvent(NS_LITERAL_STRING(LOADSTART_STR));
 
@@ -462,16 +516,17 @@ FileReader::StartProgressEventTimer()
   if (!mProgressNotifier) {
     mProgressNotifier = do_CreateInstance(NS_TIMER_CONTRACTID);
   }
 
   if (mProgressNotifier) {
     mProgressEventWasDelayed = false;
     mTimerIsActive = true;
     mProgressNotifier->Cancel();
+    mProgressNotifier->SetTarget(mTarget);
     mProgressNotifier->InitWithCallback(this, NS_PROGRESS_EVENT_INTERVAL,
                                         nsITimer::TYPE_ONE_SHOT);
   }
 }
 
 void
 FileReader::ClearProgressEventTimer()
 {
@@ -545,28 +600,30 @@ FileReader::Notify(nsITimer* aTimer)
 // InputStreamCallback
 NS_IMETHODIMP
 FileReader::OnInputStreamReady(nsIAsyncInputStream* aStream)
 {
   if (mReadyState != LOADING || aStream != mAsyncStream) {
     return NS_OK;
   }
 
+  // We use this class to decrease the busy counter at the end of this method.
+  // In theory we can do it immediatelly but, for debugging reasons, we want to
+  // be 100% sure we have a feature when OnLoadEnd() is called.
+  FileReaderDecreaseBusyCounter RAII(this);
+
   uint64_t aCount;
   nsresult rv = aStream->Available(&aCount);
 
   if (NS_SUCCEEDED(rv) && aCount) {
-    rv = DoReadData(aStream, aCount);
+    rv = DoReadData(aCount);
   }
 
   if (NS_SUCCEEDED(rv)) {
-    rv = aStream->AsyncWait(this,
-                            /* aFlags*/ 0,
-                            /* aRequestedCount */ 0,
-                            NS_GetCurrentThread());
+    rv = DoAsyncWait();
   }
 
   if (NS_FAILED(rv) || !aCount) {
     if (rv == NS_BASE_STREAM_CLOSED) {
       rv = NS_OK;
     }
     return OnLoadEnd(rv);
   }
@@ -638,10 +695,61 @@ FileReader::Abort(ErrorResult& aRv)
   //Clean up memory buffer
   FreeFileData();
 
   // Dispatch the events
   DispatchProgressEvent(NS_LITERAL_STRING(ABORT_STR));
   DispatchProgressEvent(NS_LITERAL_STRING(LOADEND_STR));
 }
 
+nsresult
+FileReader::IncreaseBusyCounter()
+{
+  if (mWorkerPrivate && mBusyCount++ == 0 &&
+      !mWorkerPrivate->AddFeature(mWorkerPrivate->GetJSContext(), this)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+void
+FileReader::DecreaseBusyCounter()
+{
+  MOZ_ASSERT_IF(mWorkerPrivate, mBusyCount);
+  if (mWorkerPrivate && --mBusyCount == 0) {
+    mWorkerPrivate->RemoveFeature(mWorkerPrivate->GetJSContext(), this);
+  }
+}
+
+bool
+FileReader::Notify(JSContext* aCx, Status aStatus)
+{
+  MOZ_ASSERT(mWorkerPrivate);
+  mWorkerPrivate->AssertIsOnWorkerThread();
+
+  if (aStatus > Running) {
+    Shutdown();
+  }
+
+  return true;
+}
+
+void
+FileReader::Shutdown()
+{
+  FreeFileData();
+  mResultArrayBuffer = nullptr;
+
+  if (mAsyncStream) {
+    mAsyncStream->Close();
+    mAsyncStream = nullptr;
+  }
+
+  if (mWorkerPrivate && mBusyCount != 0) {
+    mWorkerPrivate->RemoveFeature(mWorkerPrivate->GetJSContext(), this);
+    mWorkerPrivate = nullptr;
+    mBusyCount = 0;
+  }
+}
+
 } // dom namespace
 } // mozilla namespace
--- a/dom/base/FileReader.h
+++ b/dom/base/FileReader.h
@@ -4,57 +4,67 @@
  * 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 mozilla_dom_FileReader_h
 #define mozilla_dom_FileReader_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/DOMEventTargetHelper.h"
-#include "mozilla/dom/DOMError.h"
 
-#include "nsCOMPtr.h"
 #include "nsIAsyncInputStream.h"
-#include "nsIStreamListener.h"
-#include "nsISupportsUtils.h"
 #include "nsIInterfaceRequestor.h"
-#include "nsITimer.h"
-#include "nsJSUtils.h"
+#include "nsCOMPtr.h"
 #include "nsString.h"
-#include "nsTArray.h"
 #include "nsWeakReference.h"
-#include "prtime.h"
+#include "WorkerFeature.h"
 
 #define NS_PROGRESS_EVENT_INTERVAL 50
 
+class nsITimer;
+class nsIEventTarget;
+
 namespace mozilla {
 namespace dom {
 
 class Blob;
+class DOMError;
+
+namespace workers {
+class WorkerPrivate;
+}
 
 extern const uint64_t kUnknownSize;
 
+class FileReaderDecreaseBusyCounter;
+
 class FileReader final : public DOMEventTargetHelper,
                          public nsIInterfaceRequestor,
                          public nsSupportsWeakReference,
                          public nsIInputStreamCallback,
-                         public nsITimerCallback
+                         public nsITimerCallback,
+                         public workers::WorkerFeature
 {
+  friend class FileReaderDecreaseBusyCounter;
+
 public:
-  explicit FileReader(nsPIDOMWindow* aWindow);
+  FileReader(nsPIDOMWindow* aWindow,
+             workers::WorkerPrivate* aWorkerPrivate);
 
   NS_DECL_ISUPPORTS_INHERITED
 
   NS_DECL_NSITIMERCALLBACK
   NS_DECL_NSIINPUTSTREAMCALLBACK
   NS_DECL_NSIINTERFACEREQUESTOR
 
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(FileReader, DOMEventTargetHelper)
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(FileReader,
+                                                         DOMEventTargetHelper)
 
-  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+  virtual JSObject* WrapObject(JSContext* aCx,
+                               JS::Handle<JSObject*> aGivenProto) override;
 
   // WebIDL
   static already_AddRefed<FileReader>
   Constructor(const GlobalObject& aGlobal, ErrorResult& aRv);
   void ReadAsArrayBuffer(JSContext* aCx, Blob& aBlob, ErrorResult& aRv)
   {
     ReadFileContent(aBlob, EmptyString(), FILE_AS_ARRAYBUFFER, aRv);
   }
@@ -91,16 +101,19 @@ public:
   IMPL_EVENT_HANDLER(error)
   IMPL_EVENT_HANDLER(loadend)
 
   void ReadAsBinaryString(Blob& aBlob, ErrorResult& aRv)
   {
     ReadFileContent(aBlob, EmptyString(), FILE_AS_BINARY, aRv);
   }
 
+  // WorkerFeature
+  bool Notify(JSContext* aCx, workers::Status) override;
+
 private:
   virtual ~FileReader();
 
   // This must be in sync with dom/webidl/FileReader.webidl
   enum eReadyState {
     EMPTY = 0,
     LOADING = 1,
     DONE = 2
@@ -126,28 +139,34 @@ private:
 
   nsresult OnLoadEnd(nsresult aStatus);
 
   void StartProgressEventTimer();
   void ClearProgressEventTimer();
   void DispatchError(nsresult rv, nsAString& finalEvent);
   nsresult DispatchProgressEvent(const nsAString& aType);
 
-  nsresult DoReadData(nsIAsyncInputStream* aStream, uint64_t aCount);
+  nsresult DoAsyncWait();
+  nsresult DoReadData(uint64_t aCount);
 
   nsresult DoOnLoadEnd(nsresult aStatus, nsAString& aSuccessEvent,
                        nsAString& aTerminationEvent);
 
   void FreeFileData()
   {
     free(mFileData);
     mFileData = nullptr;
     mDataLen = 0;
   }
 
+  nsresult IncreaseBusyCounter();
+  void DecreaseBusyCounter();
+
+  void Shutdown();
+
   char *mFileData;
   RefPtr<Blob> mBlob;
   nsCString mCharset;
   uint32_t mDataLen;
 
   eDataFormat mDataFormat;
 
   nsString mResult;
@@ -161,14 +180,21 @@ private:
   nsCOMPtr<nsIAsyncInputStream> mAsyncStream;
 
   RefPtr<DOMError> mError;
 
   eReadyState mReadyState;
 
   uint64_t mTotal;
   uint64_t mTransferred;
+
+  nsCOMPtr<nsIEventTarget> mTarget;
+
+  uint64_t mBusyCount;
+
+  // Kept alive with a WorkerFeature.
+  workers::WorkerPrivate* mWorkerPrivate;
 };
 
 } // dom namespace
 } // mozilla namespace
 
 #endif // mozilla_dom_FileReader_h
--- a/dom/base/ImageEncoder.cpp
+++ b/dom/base/ImageEncoder.cpp
@@ -1,24 +1,28 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
 #include "ImageEncoder.h"
+#include "mozilla/ClearOnShutdown.h"
 #include "mozilla/dom/CanvasRenderingContext2D.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/DataSurfaceHelpers.h"
 #include "mozilla/layers/AsyncCanvasRenderer.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/SyncRunnable.h"
 #include "mozilla/unused.h"
 #include "gfxUtils.h"
+#include "nsIThreadPool.h"
 #include "nsNetUtil.h"
+#include "nsXPCOMCIDInternal.h"
+#include "WorkerPrivate.h"
 
 using namespace mozilla::gfx;
 
 namespace mozilla {
 namespace dom {
 
 // This class should be placed inside GetBRGADataSourceSurfaceSync(). However,
 // due to B2G ICS uses old complier (C++98/03) which forbids local class as
@@ -64,76 +68,79 @@ private:
 // main thread.
 already_AddRefed<DataSourceSurface>
 GetBRGADataSourceSurfaceSync(already_AddRefed<layers::Image> aImage)
 {
   RefPtr<SurfaceHelper> helper = new SurfaceHelper(Move(aImage));
   return helper->GetDataSurfaceSafe();
 }
 
-class EncodingCompleteEvent : public nsRunnable
+class EncodingCompleteEvent : public nsCancelableRunnable
 {
   virtual ~EncodingCompleteEvent() {}
 
 public:
-  NS_DECL_ISUPPORTS_INHERITED
-
-  EncodingCompleteEvent(nsIThread* aEncoderThread,
-                        EncodeCompleteCallback* aEncodeCompleteCallback)
+  explicit EncodingCompleteEvent(EncodeCompleteCallback* aEncodeCompleteCallback)
     : mImgSize(0)
     , mType()
     , mImgData(nullptr)
-    , mEncoderThread(aEncoderThread)
     , mEncodeCompleteCallback(aEncodeCompleteCallback)
     , mFailed(false)
-  {}
+  {
+    if (!NS_IsMainThread() && workers::GetCurrentThreadWorkerPrivate()) {
+      mCreationThread = NS_GetCurrentThread();
+    } else {
+      NS_GetMainThread(getter_AddRefs(mCreationThread));
+    }
+  }
 
   NS_IMETHOD Run() override
   {
     nsresult rv = NS_OK;
-    MOZ_ASSERT(NS_IsMainThread());
 
     if (!mFailed) {
       // The correct parentObject has to be set by the mEncodeCompleteCallback.
       RefPtr<Blob> blob =
         Blob::CreateMemoryBlob(nullptr, mImgData, mImgSize, mType);
       MOZ_ASSERT(blob);
 
       rv = mEncodeCompleteCallback->ReceiveBlob(blob.forget());
     }
 
     mEncodeCompleteCallback = nullptr;
 
-    mEncoderThread->Shutdown();
     return rv;
   }
 
   void SetMembers(void* aImgData, uint64_t aImgSize, const nsAutoString& aType)
   {
     mImgData = aImgData;
     mImgSize = aImgSize;
     mType = aType;
   }
 
   void SetFailed()
   {
     mFailed = true;
   }
 
+  nsIThread* GetCreationThread()
+  {
+    return mCreationThread;
+  }
+
 private:
   uint64_t mImgSize;
   nsAutoString mType;
   void* mImgData;
-  nsCOMPtr<nsIThread> mEncoderThread;
+  nsCOMPtr<nsIThread> mCreationThread;
   RefPtr<EncodeCompleteCallback> mEncodeCompleteCallback;
   bool mFailed;
 };
 
-NS_IMPL_ISUPPORTS_INHERITED0(EncodingCompleteEvent, nsRunnable);
-
 class EncodingRunnable : public nsRunnable
 {
   virtual ~EncodingRunnable() {}
 
 public:
   NS_DECL_ISUPPORTS_INHERITED
 
   EncodingRunnable(const nsAString& aType,
@@ -202,17 +209,18 @@ public:
     void* imgData = nullptr;
 
     nsresult rv = ProcessImageData(&imgSize, &imgData);
     if (NS_FAILED(rv)) {
       mEncodingCompleteEvent->SetFailed();
     } else {
       mEncodingCompleteEvent->SetMembers(imgData, imgSize, mType);
     }
-    rv = NS_DispatchToMainThread(mEncodingCompleteEvent);
+    rv = mEncodingCompleteEvent->GetCreationThread()->
+      Dispatch(mEncodingCompleteEvent, nsIThread::DISPATCH_NORMAL);
     if (NS_FAILED(rv)) {
       // Better to leak than to crash.
       Unused << mEncodingCompleteEvent.forget();
       return rv;
     }
 
     return rv;
   }
@@ -226,16 +234,18 @@ private:
   RefPtr<EncodingCompleteEvent> mEncodingCompleteEvent;
   int32_t mFormat;
   const nsIntSize mSize;
   bool mUsingCustomOptions;
 };
 
 NS_IMPL_ISUPPORTS_INHERITED0(EncodingRunnable, nsRunnable);
 
+StaticRefPtr<nsIThreadPool> ImageEncoder::sThreadPool;
+
 /* static */
 nsresult
 ImageEncoder::ExtractData(nsAString& aType,
                           const nsAString& aOptions,
                           const nsIntSize aSize,
                           nsICanvasRenderingContextInternal* aContext,
                           layers::AsyncCanvasRenderer* aRenderer,
                           nsIInputStream** aStream)
@@ -257,34 +267,35 @@ ImageEncoder::ExtractDataFromLayersImage
                                               layers::Image* aImage,
                                               EncodeCompleteCallback* aEncodeCallback)
 {
   nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType);
   if (!encoder) {
     return NS_IMAGELIB_ERROR_NO_ENCODER;
   }
 
-  nsCOMPtr<nsIThread> encoderThread;
-  nsresult rv = NS_NewThread(getter_AddRefs(encoderThread), nullptr);
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsresult rv = EnsureThreadPool();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
 
   RefPtr<EncodingCompleteEvent> completeEvent =
-    new EncodingCompleteEvent(encoderThread, aEncodeCallback);
+    new EncodingCompleteEvent(aEncodeCallback);
 
   nsIntSize size(aImage->GetSize().width, aImage->GetSize().height);
   nsCOMPtr<nsIRunnable> event = new EncodingRunnable(aType,
                                                      aOptions,
                                                      nullptr,
                                                      aImage,
                                                      encoder,
                                                      completeEvent,
                                                      imgIEncoder::INPUT_FORMAT_HOSTARGB,
                                                      size,
                                                      aUsingCustomOptions);
-  return encoderThread->Dispatch(event, NS_DISPATCH_NORMAL);
+  return sThreadPool->Dispatch(event, NS_DISPATCH_NORMAL);
 }
 
 /* static */
 nsresult
 ImageEncoder::ExtractDataAsync(nsAString& aType,
                                const nsAString& aOptions,
                                bool aUsingCustomOptions,
                                UniquePtr<uint8_t[]> aImageBuffer,
@@ -292,33 +303,34 @@ ImageEncoder::ExtractDataAsync(nsAString
                                const nsIntSize aSize,
                                EncodeCompleteCallback* aEncodeCallback)
 {
   nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType);
   if (!encoder) {
     return NS_IMAGELIB_ERROR_NO_ENCODER;
   }
 
-  nsCOMPtr<nsIThread> encoderThread;
-  nsresult rv = NS_NewThread(getter_AddRefs(encoderThread), nullptr);
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsresult rv = EnsureThreadPool();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
 
   RefPtr<EncodingCompleteEvent> completeEvent =
-    new EncodingCompleteEvent(encoderThread, aEncodeCallback);
+    new EncodingCompleteEvent(aEncodeCallback);
 
   nsCOMPtr<nsIRunnable> event = new EncodingRunnable(aType,
                                                      aOptions,
                                                      Move(aImageBuffer),
                                                      nullptr,
                                                      encoder,
                                                      completeEvent,
                                                      aFormat,
                                                      aSize,
                                                      aUsingCustomOptions);
-  return encoderThread->Dispatch(event, NS_DISPATCH_NORMAL);
+  return sThreadPool->Dispatch(event, NS_DISPATCH_NORMAL);
 }
 
 /*static*/ nsresult
 ImageEncoder::GetInputStream(int32_t aWidth,
                              int32_t aHeight,
                              uint8_t* aImageBuffer,
                              int32_t aFormat,
                              imgIEncoder* aEncoder,
@@ -474,10 +486,53 @@ ImageEncoder::GetImageEncoder(nsAString&
     aType.AssignLiteral("image/png");
     nsCString PNGEncoderCID("@mozilla.org/image/encoder;2?type=image/png");
     encoder = do_CreateInstance(PNGEncoderCID.get());
   }
 
   return encoder.forget();
 }
 
+/* static */
+nsresult
+ImageEncoder::EnsureThreadPool()
+{
+  if (!sThreadPool) {
+    nsCOMPtr<nsIThreadPool> threadPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID);
+    sThreadPool = threadPool;
+    if (!NS_IsMainThread()) {
+      NS_DispatchToMainThread(NS_NewRunnableFunction([]() -> void {
+        ClearOnShutdown(&sThreadPool);
+      }));
+    } else {
+      ClearOnShutdown(&sThreadPool);
+    }
+
+    const uint32_t kThreadLimit = 2;
+    const uint32_t kIdleThreadLimit = 1;
+    const uint32_t kIdleThreadTimeoutMs = 30000;
+
+    nsresult rv = sThreadPool->SetName(NS_LITERAL_CSTRING("EncodingRunnable"));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    rv = sThreadPool->SetThreadLimit(kThreadLimit);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    rv = sThreadPool->SetIdleThreadLimit(kIdleThreadLimit);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    rv = sThreadPool->SetIdleThreadTimeout(kIdleThreadTimeoutMs);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  return NS_OK;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/ImageEncoder.h
+++ b/dom/base/ImageEncoder.h
@@ -11,16 +11,17 @@
 #include "nsError.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/HTMLCanvasElementBinding.h"
 #include "mozilla/UniquePtr.h"
 #include "nsLayoutUtils.h"
 #include "nsSize.h"
 
 class nsICanvasRenderingContextInternal;
+class nsIThreadPool;
 
 namespace mozilla {
 
 namespace layers {
 class AsyncCanvasRenderer;
 class Image;
 } // namespace layers
 
@@ -102,16 +103,21 @@ private:
 
   // Creates and returns an encoder instance of the type specified in aType.
   // aType may change to "image/png" if no instance of the original type could
   // be created and we had to fall back to a PNG encoder. A null return value
   // should be interpreted as NS_IMAGELIB_ERROR_NO_ENCODER and aType is
   // undefined in this case.
   static already_AddRefed<imgIEncoder> GetImageEncoder(nsAString& aType);
 
+  static nsresult EnsureThreadPool();
+
+  // Thread pool for dispatching EncodingRunnable.
+  static StaticRefPtr<nsIThreadPool> sThreadPool;
+
   friend class EncodingRunnable;
 };
 
 /**
  *  The callback interface of ExtractDataAsync and ExtractDataFromLayersImageAsync.
  *  ReceiveBlob() is called on main thread when encoding is complete.
  */
 class EncodeCompleteCallback
--- a/dom/base/StructuredCloneHolder.cpp
+++ b/dom/base/StructuredCloneHolder.cpp
@@ -23,16 +23,17 @@
 #include "mozilla/dom/MessagePortBinding.h"
 #include "mozilla/dom/OffscreenCanvas.h"
 #include "mozilla/dom/OffscreenCanvasBinding.h"
 #include "mozilla/dom/PMessagePort.h"
 #include "mozilla/dom/StructuredCloneTags.h"
 #include "mozilla/dom/SubtleCryptoBinding.h"
 #include "mozilla/dom/ToJSValue.h"
 #include "mozilla/dom/WebCryptoCommon.h"
+#include "mozilla/gfx/2D.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/BackgroundUtils.h"
 #include "mozilla/ipc/PBackgroundSharedTypes.h"
 #include "MultipartBlobImpl.h"
 #include "nsFormData.h"
 #include "nsIRemoteBlob.h"
 #include "nsQueryObject.h"
 
@@ -301,17 +302,17 @@ StructuredCloneHolder::Read(nsISupports*
   if (!StructuredCloneHolderBase::Read(aCx, aValue)) {
     JS_ClearPendingException(aCx);
     aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
   }
 
   // If we are tranferring something, we cannot call 'Read()' more than once.
   if (mSupportsTransferring) {
     mBlobImplArray.Clear();
-    mClonedImages.Clear();
+    mClonedSurfaces.Clear();
     Clear();
   }
 }
 
 void
 StructuredCloneHolder::ReadFromBuffer(nsISupports* aParent,
                                       JSContext* aCx,
                                       uint64_t* aBuffer,
@@ -971,17 +972,17 @@ StructuredCloneHolder::CustomReadHandler
     MOZ_ASSERT(mSupportedContext == SameProcessSameThread ||
                mSupportedContext == SameProcessDifferentThread);
 
     // Get the current global object.
     // This can be null.
     nsCOMPtr<nsIGlobalObject> parent = do_QueryInterface(mParent);
     // aIndex is the index of the cloned image.
     return ImageBitmap::ReadStructuredClone(aCx, aReader,
-                                            parent, GetImages(), aIndex);
+                                            parent, GetSurfaces(), aIndex);
    }
 
   return ReadFullySerializableObjects(aCx, aReader, aTag);
 }
 
 bool
 StructuredCloneHolder::CustomWriteHandler(JSContext* aCx,
                                           JSStructuredCloneWriter* aWriter,
@@ -1016,17 +1017,17 @@ StructuredCloneHolder::CustomWriteHandle
   }
 
   // See if this is an ImageBitmap object.
   if (mSupportedContext == SameProcessSameThread ||
       mSupportedContext == SameProcessDifferentThread) {
     ImageBitmap* imageBitmap = nullptr;
     if (NS_SUCCEEDED(UNWRAP_OBJECT(ImageBitmap, aObj, imageBitmap))) {
       return ImageBitmap::WriteStructuredClone(aWriter,
-                                               GetImages(),
+                                               GetSurfaces(),
                                                imageBitmap);
     }
   }
 
   return WriteFullySerializableObjects(aCx, aWriter, aObj);
 }
 
 bool
@@ -1067,29 +1068,50 @@ StructuredCloneHolder::CustomReadTransfe
   }
 
   if (aTag == SCTAG_DOM_CANVAS) {
     MOZ_ASSERT(mSupportedContext == SameProcessSameThread ||
                mSupportedContext == SameProcessDifferentThread);
     MOZ_ASSERT(aContent);
     OffscreenCanvasCloneData* data =
       static_cast<OffscreenCanvasCloneData*>(aContent);
-    RefPtr<OffscreenCanvas> canvas = OffscreenCanvas::CreateFromCloneData(data);
+    nsCOMPtr<nsIGlobalObject> parent = do_QueryInterface(mParent);
+    RefPtr<OffscreenCanvas> canvas = OffscreenCanvas::CreateFromCloneData(parent, data);
     delete data;
 
     JS::Rooted<JS::Value> value(aCx);
     if (!GetOrCreateDOMReflector(aCx, canvas, &value)) {
       JS_ClearPendingException(aCx);
       return false;
     }
 
     aReturnObject.set(&value.toObject());
     return true;
   }
 
+  if (aTag == SCTAG_DOM_IMAGEBITMAP) {
+    MOZ_ASSERT(mSupportedContext == SameProcessSameThread ||
+               mSupportedContext == SameProcessDifferentThread);
+    MOZ_ASSERT(aContent);
+    ImageBitmapCloneData* data =
+      static_cast<ImageBitmapCloneData*>(aContent);
+    nsCOMPtr<nsIGlobalObject> parent = do_QueryInterface(mParent);
+    RefPtr<ImageBitmap> bitmap = ImageBitmap::CreateFromCloneData(parent, data);
+    delete data;
+
+    JS::Rooted<JS::Value> value(aCx);
+    if (!GetOrCreateDOMReflector(aCx, bitmap, &value)) {
+      JS_ClearPendingException(aCx);
+      return false;
+    }
+
+    aReturnObject.set(&value.toObject());
+    return true;
+  }
+
   return false;
 }
 
 bool
 StructuredCloneHolder::CustomWriteTransferHandler(JSContext* aCx,
                                                   JS::Handle<JSObject*> aObj,
                                                   uint32_t* aTag,
                                                   JS::TransferableOwnership* aOwnership,
@@ -1128,16 +1150,31 @@ StructuredCloneHolder::CustomWriteTransf
         *aTag = SCTAG_DOM_CANVAS;
         *aOwnership = JS::SCTAG_TMO_CUSTOM;
         *aContent = canvas->ToCloneData();
         MOZ_ASSERT(*aContent);
         canvas->SetNeutered();
 
         return true;
       }
+
+      ImageBitmap* bitmap = nullptr;
+      rv = UNWRAP_OBJECT(ImageBitmap, aObj, bitmap);
+      if (NS_SUCCEEDED(rv)) {
+        MOZ_ASSERT(bitmap);
+
+        *aExtraData = 0;
+        *aTag = SCTAG_DOM_IMAGEBITMAP;
+        *aOwnership = JS::SCTAG_TMO_CUSTOM;
+        *aContent = bitmap->ToCloneData();
+        MOZ_ASSERT(*aContent);
+        bitmap->Close();
+
+        return true;
+      }
     }
   }
 
   return false;
 }
 
 void
 StructuredCloneHolder::CustomFreeTransferHandler(uint32_t aTag,
@@ -1158,12 +1195,22 @@ StructuredCloneHolder::CustomFreeTransfe
     MOZ_ASSERT(mSupportedContext == SameProcessSameThread ||
                mSupportedContext == SameProcessDifferentThread);
     MOZ_ASSERT(aContent);
     OffscreenCanvasCloneData* data =
       static_cast<OffscreenCanvasCloneData*>(aContent);
     delete data;
     return;
   }
+
+  if (aTag == SCTAG_DOM_IMAGEBITMAP) {
+    MOZ_ASSERT(mSupportedContext == SameProcessSameThread ||
+               mSupportedContext == SameProcessDifferentThread);
+    MOZ_ASSERT(aContent);
+    ImageBitmapCloneData* data =
+      static_cast<ImageBitmapCloneData*>(aContent);
+    delete data;
+    return;
+  }
 }
 
 } // dom namespace
 } // mozilla namespace
--- a/dom/base/StructuredCloneHolder.h
+++ b/dom/base/StructuredCloneHolder.h
@@ -17,16 +17,20 @@
 #endif
 
 namespace mozilla {
 class ErrorResult;
 namespace layers {
 class Image;
 }
 
+namespace gfx {
+class DataSourceSurface;
+}
+
 namespace dom {
 
 class StructuredCloneHolderBase
 {
 public:
   StructuredCloneHolderBase();
   virtual ~StructuredCloneHolderBase();
 
@@ -176,17 +180,17 @@ public:
   // You should free this buffer with StructuredCloneHolder::FreeBuffer().
   void MoveBufferDataToArray(FallibleTArray<uint8_t>& aArray,
                              ErrorResult& aRv);
 
   // Call this method to know if this object is keeping some DOM object alive.
   bool HasClonedDOMObjects() const
   {
     return !mBlobImplArray.IsEmpty() ||
-           !mClonedImages.IsEmpty();
+           !mClonedSurfaces.IsEmpty();
   }
 
   nsTArray<RefPtr<BlobImpl>>& BlobImpls()
   {
     MOZ_ASSERT(mSupportsCloning, "Blobs cannot be taken/set if cloning is not supported.");
     return mBlobImplArray;
   }
 
@@ -207,19 +211,19 @@ public:
   }
 
   nsTArray<MessagePortIdentifier>& PortIdentifiers()
   {
     MOZ_ASSERT(mSupportsTransferring);
     return mPortIdentifiers;
   }
 
-  nsTArray<RefPtr<layers::Image>>& GetImages()
+  nsTArray<RefPtr<gfx::DataSourceSurface>>& GetSurfaces()
   {
-    return mClonedImages;
+    return mClonedSurfaces;
   }
 
   // Implementations of the virtual methods to allow cloning of objects which
   // JS engine itself doesn't clone.
 
   virtual JSObject* CustomReadHandler(JSContext* aCx,
                                       JSStructuredCloneReader* aReader,
                                       uint32_t aTag,
@@ -286,20 +290,20 @@ protected:
   bool mSupportsCloning;
   bool mSupportsTransferring;
   ContextSupport mSupportedContext;
 
   // Used for cloning blobs in the structured cloning algorithm.
   nsTArray<RefPtr<BlobImpl>> mBlobImplArray;
 
   // This is used for sharing the backend of ImageBitmaps.
-  // The layers::Image object must be thread-safely reference-counted.
-  // The layers::Image object will not be written ever via any ImageBitmap
+  // The DataSourceSurface object must be thread-safely reference-counted.
+  // The DataSourceSurface object will not be written ever via any ImageBitmap
   // instance, so no race condition will occur.
-  nsTArray<RefPtr<layers::Image>> mClonedImages;
+  nsTArray<RefPtr<gfx::DataSourceSurface>> mClonedSurfaces;
 
   // This raw pointer is only set within ::Read() and is unset by the end.
   nsISupports* MOZ_NON_OWNING_REF mParent;
 
   // This array contains the ports once we've finished the reading. It's
   // generated from the mPortIdentifiers array.
   nsTArray<RefPtr<MessagePort>> mTransferredPorts;
 
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -6770,87 +6770,47 @@ nsContentUtils::HaveEqualPrincipals(nsID
   if (!aDoc1 || !aDoc2) {
     return false;
   }
   bool principalsEqual = false;
   aDoc1->NodePrincipal()->Equals(aDoc2->NodePrincipal(), &principalsEqual);
   return principalsEqual;
 }
 
-static void
-CheckForWindowedPlugins(nsISupports* aSupports, void* aResult)
-{
-  nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
-  if (!content || !content->IsInDoc()) {
-    return;
-  }
-  nsCOMPtr<nsIObjectLoadingContent> olc(do_QueryInterface(content));
-  if (!olc) {
-    return;
-  }
-  RefPtr<nsNPAPIPluginInstance> plugin;
-  olc->GetPluginInstance(getter_AddRefs(plugin));
-  if (!plugin) {
-    return;
-  }
-  bool isWindowless = false;
-  nsresult res = plugin->IsWindowless(&isWindowless);
-  if (NS_SUCCEEDED(res) && !isWindowless) {
-    *static_cast<bool*>(aResult) = true;
-  }
-}
-
-static bool
-DocTreeContainsWindowedPlugins(nsIDocument* aDoc, void* aResult)
-{
-  if (!nsContentUtils::IsChromeDoc(aDoc)) {
-    aDoc->EnumerateActivityObservers(CheckForWindowedPlugins, aResult);
-  }
-  if (*static_cast<bool*>(aResult)) {
-    // Return false to stop iteration, we found a windowed plugin.
-    return false;
-  }
-  aDoc->EnumerateSubDocuments(DocTreeContainsWindowedPlugins, aResult);
-  // Return false to stop iteration if we found a windowed plugin in
-  // the sub documents.
-  return !*static_cast<bool*>(aResult);
-}
-
-/* static */
-bool
-nsContentUtils::HasPluginWithUncontrolledEventDispatch(nsIDocument* aDoc)
-{
-#ifdef XP_MACOSX
-  // We control dispatch to all mac plugins.
-  return false;
-#endif
-  bool result = false;
-  
-  // Find the top of the document's branch, the child of the chrome document.
-  nsIDocument* doc = aDoc;
-  nsIDocument* parent = nullptr;
-  while (doc && (parent = doc->GetParentDocument()) && !IsChromeDoc(parent)) {
-    doc = parent;
-  }
-
-  DocTreeContainsWindowedPlugins(doc, &result);
-  return result;
-}
-
 /* static */
 bool
 nsContentUtils::HasPluginWithUncontrolledEventDispatch(nsIContent* aContent)
 {
 #ifdef XP_MACOSX
   // We control dispatch to all mac plugins.
   return false;
+#else
+  if (!aContent || !aContent->IsInDoc()) {
+    return false;
+  }
+
+  nsCOMPtr<nsIObjectLoadingContent> olc = do_QueryInterface(aContent);
+  if (!olc) {
+    return false;
+  }
+
+  RefPtr<nsNPAPIPluginInstance> plugin;
+  olc->GetPluginInstance(getter_AddRefs(plugin));
+  if (!plugin) {
+    return false;
+  }
+
+  bool isWindowless = false;
+  nsresult res = plugin->IsWindowless(&isWindowless);
+  if (NS_FAILED(res)) {
+    return false;
+  }
+
+  return !isWindowless;
 #endif
-  bool result = false;
-  CheckForWindowedPlugins(aContent, &result);
-  return result;
 }
 
 /* static */
 void
 nsContentUtils::FireMutationEventsForDirectParsing(nsIDocument* aDoc,
                                                    nsIContent* aDest,
                                                    int32_t aOldChildCount)
 {
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -2055,24 +2055,16 @@ public:
    * be used to "fingerprint" and track the user across websites.
    */
   static bool ResistFingerprinting()
   {
     return sPrivacyResistFingerprinting;
   }
 
   /**
-   * Returns true if the doc tree branch which contains aDoc contains any
-   * plugins which we don't control event dispatch for, i.e. do any plugins
-   * in the same tab as this document receive key events outside of our
-   * control? This always returns false on MacOSX.
-   */
-  static bool HasPluginWithUncontrolledEventDispatch(nsIDocument* aDoc);
-
-  /**
    * Return true if this doc is controlled by a ServiceWorker.
    */
   static bool IsControlledByServiceWorker(nsIDocument* aDocument);
 
   /**
    * Fire mutation events for changes caused by parsing directly into a
    * context node.
    *
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -7663,17 +7663,16 @@ nsGlobalWindow::Open(const nsAString& aU
   nsCOMPtr<nsIDOMWindow> window;
   nsresult rv = OpenInternal(aUrl, aName, aOptions,
                              false,          // aDialog
                              false,          // aContentModal
                              true,           // aCalledNoScript
                              false,          // aDoJSFixups
                              true,           // aNavigate
                              nullptr, nullptr,  // No args
-                             GetPrincipal(),    // aCalleePrincipal
                              nullptr,           // aJSCallerContext
                              getter_AddRefs(window));
   if (NS_SUCCEEDED(rv) && window) {
     return CallQueryInterface(window, _retval);
   }
   return rv;
 }
 
@@ -7684,17 +7683,16 @@ nsGlobalWindow::OpenJS(const nsAString& 
   MOZ_ASSERT(IsOuterWindow());
   return OpenInternal(aUrl, aName, aOptions,
                       false,          // aDialog
                       false,          // aContentModal
                       false,          // aCalledNoScript
                       true,           // aDoJSFixups
                       true,           // aNavigate
                       nullptr, nullptr,  // No args
-                      GetPrincipal(),    // aCalleePrincipal
                       nsContentUtils::GetCurrentJSContext(), // aJSCallerContext
                       _retval);
 }
 
 // like Open, but attaches to the new window any extra parameters past
 // [features] as a JS property named "arguments"
 nsresult
 nsGlobalWindow::OpenDialog(const nsAString& aUrl, const nsAString& aName,
@@ -7704,17 +7702,16 @@ nsGlobalWindow::OpenDialog(const nsAStri
   MOZ_ASSERT(IsOuterWindow());
   return OpenInternal(aUrl, aName, aOptions,
                       true,                    // aDialog
                       false,                   // aContentModal
                       true,                    // aCalledNoScript
                       false,                   // aDoJSFixups
                       true,                    // aNavigate
                       nullptr, aExtraArgument,    // Arguments
-                      GetPrincipal(),             // aCalleePrincipal
                       nullptr,                    // aJSCallerContext
                       _retval);
 }
 
 // Like Open, but passes aNavigate=false.
 /* virtual */ nsresult
 nsGlobalWindow::OpenNoNavigate(const nsAString& aUrl,
                                const nsAString& aName,
@@ -7724,17 +7721,16 @@ nsGlobalWindow::OpenNoNavigate(const nsA
   MOZ_ASSERT(IsOuterWindow());
   return OpenInternal(aUrl, aName, aOptions,
                       false,          // aDialog
                       false,          // aContentModal
                       true,           // aCalledNoScript
                       false,          // aDoJSFixups
                       false,          // aNavigate
                       nullptr, nullptr,  // No args
-                      GetPrincipal(),    // aCalleePrincipal
                       nullptr,           // aJSCallerContext
                       _retval);
 
 }
 
 already_AddRefed<nsIDOMWindow>
 nsGlobalWindow::OpenDialogOuter(JSContext* aCx, const nsAString& aUrl,
                                 const nsAString& aName, const nsAString& aOptions,
@@ -7754,17 +7750,16 @@ nsGlobalWindow::OpenDialogOuter(JSContex
   nsCOMPtr<nsIDOMWindow> dialog;
   aError = OpenInternal(aUrl, aName, aOptions,
                         true,             // aDialog
                         false,            // aContentModal
                         false,            // aCalledNoScript
                         false,            // aDoJSFixups
                         true,                // aNavigate
                         argvArray, nullptr,  // Arguments
-                        GetPrincipal(),      // aCalleePrincipal
                         aCx,                 // aJSCallerContext
                         getter_AddRefs(dialog));
   return dialog.forget();
 }
 
 already_AddRefed<nsIDOMWindow>
 nsGlobalWindow::OpenDialog(JSContext* aCx, const nsAString& aUrl,
                            const nsAString& aName, const nsAString& aOptions,
@@ -8834,17 +8829,16 @@ nsGlobalWindow::ShowModalDialogOuter(con
   nsContentUtils::SetMicroTaskLevel(0);
   aError = OpenInternal(aUrl, EmptyString(), options,
                         false,          // aDialog
                         true,           // aContentModal
                         true,           // aCalledNoScript
                         true,           // aDoJSFixups
                         true,           // aNavigate
                         nullptr, argHolder, // args
-                        GetPrincipal(),     // aCalleePrincipal
                         nullptr,            // aJSCallerContext
                         getter_AddRefs(dlgWin));
   nsContentUtils::SetMicroTaskLevel(oldMicroTaskLevel);
   LeaveModalState();
   if (aError.Failed()) {
     return nullptr;
   }
 
@@ -11265,17 +11259,16 @@ public:
 
 nsresult
 nsGlobalWindow::OpenInternal(const nsAString& aUrl, const nsAString& aName,
                              const nsAString& aOptions, bool aDialog,
                              bool aContentModal, bool aCalledNoScript,
                              bool aDoJSFixups, bool aNavigate,
                              nsIArray *argv,
                              nsISupports *aExtraArgument,
-                             nsIPrincipal *aCalleePrincipal,
                              JSContext *aJSCallerContext,
                              nsIDOMWindow **aReturn)
 {
   MOZ_ASSERT(IsOuterWindow());
 
 #ifdef DEBUG
   uint32_t argc = 0;
   if (argv)
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -1387,17 +1387,16 @@ private:
                                     const nsAString& aOptions,
                                     bool aDialog,
                                     bool aContentModal,
                                     bool aCalledNoScript,
                                     bool aDoJSFixups,
                                     bool aNavigate,
                                     nsIArray *argv,
                                     nsISupports *aExtraArgument,
-                                    nsIPrincipal *aCalleePrincipal,
                                     JSContext *aJSCallerContext,
                                     nsIDOMWindow **aReturn);
 
 public:
   // Timeout Functions
   // Language agnostic timeout function (all args passed).
   // |interval| is in milliseconds.
   nsresult SetTimeoutOrInterval(nsIScriptTimeoutHandler *aHandler,
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -5214,29 +5214,27 @@ CanvasRenderingContext2D::GetImageDataAr
     if (!readback || !readback->Map(DataSourceSurface::READ, &rawData)) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
   }
 
   IntRect dstWriteRect = srcReadRect;
   dstWriteRect.MoveBy(-aX, -aY);
 
-  uint8_t* src;
-  uint32_t srcStride;
-
-  if (readback) {
-    srcStride = rawData.mStride;
-    src = rawData.mData + srcReadRect.y * srcStride + srcReadRect.x * 4;
-  }
-
   JS::AutoCheckCannotGC nogc;
   bool isShared;
   uint8_t* data = JS_GetUint8ClampedArrayData(darray, &isShared, nogc);
   MOZ_ASSERT(!isShared);        // Should not happen, data was created above
-  if (!readback) {
+
+  uint8_t* src;
+  uint32_t srcStride;
+  if (readback) {
+    srcStride = rawData.mStride;
+    src = rawData.mData + srcReadRect.y * srcStride + srcReadRect.x * 4;
+  } else {
     src = data;
     srcStride = aWidth * 4;
   }
 
   uint8_t* dst = data + dstWriteRect.y * (aWidth * 4) + dstWriteRect.x * 4;
 
   if (mOpaque) {
     for (int32_t j = 0; j < dstWriteRect.height; ++j) {
@@ -5574,19 +5572,19 @@ CanvasRenderingContext2D::GetBufferProvi
     return nullptr;
   }
 
   mBufferProvider = new PersistentBufferProviderBasic(mTarget);
 
   return mBufferProvider;
 }
 
-already_AddRefed<CanvasLayer>
+already_AddRefed<Layer>
 CanvasRenderingContext2D::GetCanvasLayer(nsDisplayListBuilder* aBuilder,
-                                         CanvasLayer *aOldLayer,
+                                         Layer *aOldLayer,
                                          LayerManager *aManager)
 {
   if (mOpaque || mIsSkiaGL) {
     // If we're opaque then make sure we have a surface so we paint black
     // instead of transparent.
     // If we're using SkiaGL, then SkiaGLTex() below needs the target to
     // be accessible.
     EnsureTarget();
@@ -5619,18 +5617,20 @@ CanvasRenderingContext2D::GetCanvasLayer
       data.mFrontbufferGLTex = skiaGLTex;
       PersistentBufferProvider *provider = GetBufferProvider(aManager);
       data.mBufferProvider = provider;
     } else {
       PersistentBufferProvider *provider = GetBufferProvider(aManager);
       data.mBufferProvider = provider;
     }
 
-    if (userData && userData->IsForContext(this) && aOldLayer->IsDataValid(data)) {
-      RefPtr<CanvasLayer> ret = aOldLayer;
+    if (userData &&
+        userData->IsForContext(this) &&
+        static_cast<CanvasLayer*>(aOldLayer)->IsDataValid(data)) {
+      RefPtr<Layer> ret = aOldLayer;
       return ret.forget();
     }
   }
 
   RefPtr<CanvasLayer> canvasLayer = aManager->CreateCanvasLayer();
   if (!canvasLayer) {
     NS_WARNING("CreateCanvasLayer returned null!");
     // No DidTransactionCallback will be received, so mark the context clean
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -454,19 +454,19 @@ public:
     }
     return mTarget->Snapshot();
   }
 
   NS_IMETHOD SetIsOpaque(bool isOpaque) override;
   bool GetIsOpaque() override { return mOpaque; }
   NS_IMETHOD Reset() override;
   mozilla::layers::PersistentBufferProvider* GetBufferProvider(mozilla::layers::LayerManager* aManager);
-  already_AddRefed<CanvasLayer> GetCanvasLayer(nsDisplayListBuilder* aBuilder,
-                                               CanvasLayer *aOldLayer,
-                                               LayerManager *aManager) override;
+  already_AddRefed<Layer> GetCanvasLayer(nsDisplayListBuilder* aBuilder,
+                                         Layer *aOldLayer,
+                                         LayerManager *aManager) override;
   virtual bool ShouldForceInactiveLayer(LayerManager *aManager) override;
   void MarkContextClean() override;
   void MarkContextCleanForFrameCapture() override;
   bool IsContextCleanForFrameCapture() override;
   NS_IMETHOD SetIsIPC(bool isIPC) override;
   // this rect is in canvas device space
   void Redraw(const mozilla::gfx::Rect &r);
   NS_IMETHOD Redraw(const gfxRect &r) override { Redraw(ToRect(r)); return NS_OK; }
--- a/dom/canvas/CanvasRenderingContextHelper.cpp
+++ b/dom/canvas/CanvasRenderingContextHelper.cpp
@@ -1,14 +1,15 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/. */
 
 #include "CanvasRenderingContextHelper.h"
+#include "ImageBitmapRenderingContext.h"
 #include "ImageEncoder.h"
 #include "mozilla/dom/CanvasRenderingContext2D.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/UniquePtr.h"
 #include "nsContentUtils.h"
 #include "nsDOMJSUtils.h"
 #include "nsIScriptContext.h"
 #include "nsJSUtils.h"
@@ -21,46 +22,16 @@ namespace dom {
 void
 CanvasRenderingContextHelper::ToBlob(JSContext* aCx,
                                      nsIGlobalObject* aGlobal,
                                      FileCallback& aCallback,
                                      const nsAString& aType,
                                      JS::Handle<JS::Value> aParams,
                                      ErrorResult& aRv)
 {
-  nsAutoString type;
-  nsContentUtils::ASCIIToLower(aType, type);
-
-  nsAutoString params;
-  bool usingCustomParseOptions;
-  aRv = ParseParams(aCx, type, aParams, params, &usingCustomParseOptions);
-  if (aRv.Failed()) {
-    return;
-  }
-
-  if (mCurrentContext) {
-    // We disallow canvases of width or height zero, and set them to 1, so
-    // we will have a discrepancy with the sizes of the canvas and the context.
-    // That discrepancy is OK, the rest are not.
-    nsIntSize elementSize = GetWidthHeight();
-    if ((elementSize.width != mCurrentContext->GetWidth() &&
-         (elementSize.width != 0 || mCurrentContext->GetWidth() != 1)) ||
-        (elementSize.height != mCurrentContext->GetHeight() &&
-         (elementSize.height != 0 || mCurrentContext->GetHeight() != 1))) {
-      aRv.Throw(NS_ERROR_FAILURE);
-      return;
-    }
-  }
-
-  UniquePtr<uint8_t[]> imageBuffer;
-  int32_t format = 0;
-  if (mCurrentContext) {
-    imageBuffer = mCurrentContext->GetImageBuffer(&format);
-  }
-
   // Encoder callback when encoding is complete.
   class EncodeCallback : public EncodeCompleteCallback
   {
   public:
     EncodeCallback(nsIGlobalObject* aGlobal, FileCallback* aCallback)
       : mGlobal(aGlobal)
       , mFileCallback(aCallback) {}
 
@@ -92,16 +63,59 @@ CanvasRenderingContextHelper::ToBlob(JSC
 
     nsCOMPtr<nsIGlobalObject> mGlobal;
     RefPtr<FileCallback> mFileCallback;
   };
 
   RefPtr<EncodeCompleteCallback> callback =
     new EncodeCallback(aGlobal, &aCallback);
 
+  ToBlob(aCx, aGlobal, callback, aType, aParams, aRv);
+}
+
+void
+CanvasRenderingContextHelper::ToBlob(JSContext* aCx,
+                                     nsIGlobalObject* aGlobal,
+                                     EncodeCompleteCallback* aCallback,
+                                     const nsAString& aType,
+                                     JS::Handle<JS::Value> aParams,
+                                     ErrorResult& aRv)
+{
+  nsAutoString type;
+  nsContentUtils::ASCIIToLower(aType, type);
+
+  nsAutoString params;
+  bool usingCustomParseOptions;
+  aRv = ParseParams(aCx, type, aParams, params, &usingCustomParseOptions);
+  if (aRv.Failed()) {
+    return;
+  }
+
+  if (mCurrentContext) {
+    // We disallow canvases of width or height zero, and set them to 1, so
+    // we will have a discrepancy with the sizes of the canvas and the context.
+    // That discrepancy is OK, the rest are not.
+    nsIntSize elementSize = GetWidthHeight();
+    if ((elementSize.width != mCurrentContext->GetWidth() &&
+         (elementSize.width != 0 || mCurrentContext->GetWidth() != 1)) ||
+        (elementSize.height != mCurrentContext->GetHeight() &&
+         (elementSize.height != 0 || mCurrentContext->GetHeight() != 1))) {
+      aRv.Throw(NS_ERROR_FAILURE);
+      return;
+    }
+  }
+
+  UniquePtr<uint8_t[]> imageBuffer;
+  int32_t format = 0;
+  if (mCurrentContext) {
+    imageBuffer = mCurrentContext->GetImageBuffer(&format);
+  }
+
+  RefPtr<EncodeCompleteCallback> callback = aCallback;
+
   aRv = ImageEncoder::ExtractDataAsync(type,
                                        params,
                                        usingCustomParseOptions,
                                        Move(imageBuffer),
                                        format,
                                        GetWidthHeight(),
                                        callback);
 }
@@ -133,16 +147,21 @@ CanvasRenderingContextHelper::CreateCont
   case CanvasContextType::WebGL2:
     Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED, 1);
 
     ret = WebGL2Context::Create();
     if (!ret)
       return nullptr;
 
     break;
+
+  case CanvasContextType::ImageBitmap:
+    ret = new ImageBitmapRenderingContext();
+
+    break;
   }
   MOZ_ASSERT(ret);
 
   return ret.forget();
 }
 
 already_AddRefed<nsISupports>
 CanvasRenderingContextHelper::GetContext(JSContext* aCx,
--- a/dom/canvas/CanvasRenderingContextHelper.h
+++ b/dom/canvas/CanvasRenderingContextHelper.h
@@ -13,23 +13,25 @@ class nsICanvasRenderingContextInternal;
 class nsIGlobalObject;
 
 namespace mozilla {
 
 class ErrorResult;
 
 namespace dom {
 
+class EncodeCompleteCallback;
 class FileCallback;
 
 enum class CanvasContextType : uint8_t {
   NoContext,
   Canvas2D,
   WebGL1,
-  WebGL2
+  WebGL2,
+  ImageBitmap
 };
 
 /**
  * Povides common RenderingContext functionality used by both OffscreenCanvas
  * and HTMLCanvasElement.
  */
 class CanvasRenderingContextHelper
 {
@@ -52,16 +54,20 @@ protected:
                                const JS::Value& aEncoderOptions,
                                nsAString& outParams,
                                bool* const outCustomParseOptions);
 
   void ToBlob(JSContext* aCx, nsIGlobalObject* global, FileCallback& aCallback,
               const nsAString& aType, JS::Handle<JS::Value> aParams,
               ErrorResult& aRv);
 
+  void ToBlob(JSContext* aCx, nsIGlobalObject* aGlobal, EncodeCompleteCallback* aCallback,
+              const nsAString& aType, JS::Handle<JS::Value> aParams,
+              ErrorResult& aRv);
+
   virtual already_AddRefed<nsICanvasRenderingContextInternal>
   CreateContext(CanvasContextType aContextType);
 
   virtual nsIntSize GetWidthHeight() = 0;
 
   CanvasContextType mCurrentContextType;
   nsCOMPtr<nsICanvasRenderingContextInternal> mCurrentContext;
 };
--- a/dom/canvas/CanvasUtils.cpp
+++ b/dom/canvas/CanvasUtils.cpp
@@ -56,16 +56,21 @@ GetCanvasContextType(const nsAString& st
 
   if (WebGL2Context::IsSupported()) {
     if (str.EqualsLiteral("webgl2")) {
       *out_type = dom::CanvasContextType::WebGL2;
       return true;
     }
   }
 
+  if (str.EqualsLiteral("bitmaprenderer")) {
+    *out_type = dom::CanvasContextType::ImageBitmap;
+    return true;
+  }
+
   return false;
 }
 
 /**
  * This security check utility might be called from an source that never taints
  * others. For example, while painting a CanvasPattern, which is created from an
  * ImageBitmap, onto a canvas. In this case, the caller could set the CORSUsed
  * true in order to pass this check and leave the aPrincipal to be a nullptr
--- a/dom/canvas/ImageBitmap.cpp
+++ b/dom/canvas/ImageBitmap.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/. */
 
 #include "mozilla/dom/ImageBitmap.h"
+
 #include "mozilla/dom/ImageBitmapBinding.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/StructuredCloneTags.h"
 #include "mozilla/dom/WorkerPrivate.h"
 #include "mozilla/dom/WorkerRunnable.h"
 #include "mozilla/gfx/2D.h"
 #include "imgTools.h"
 #include "libyuv.h"
@@ -140,25 +141,24 @@ CropAndCopyDataSourceSurface(DataSourceS
       dstBufferPtr += dstMap.GetStride();
     }
   }
 
   return dstDataSurface.forget();
 }
 
 /*
- * Encapsulate the given _aSurface_ into a layers::CairoImage.
+ * Encapsulate the given _aSurface_ into a layers::SourceSurfaceImage.
  */
 static already_AddRefed<layers::Image>
 CreateImageFromSurface(SourceSurface* aSurface)
 {
   MOZ_ASSERT(aSurface);
-  RefPtr<layers::CairoImage> image =
-    new layers::CairoImage(aSurface->GetSize(), aSurface);
-
+  RefPtr<layers::SourceSurfaceImage> image =
+    new layers::SourceSurfaceImage(aSurface->GetSize(), aSurface);
   return image.forget();
 }
 
 /*
  * CreateImageFromRawData(), CreateSurfaceFromRawData() and
  * CreateImageFromRawDataInMainThreadSyncTask are helpers for
  * create-from-ImageData case
  */
@@ -245,23 +245,23 @@ CreateImageFromRawData(const gfx::IntSiz
     return nullptr;
   }
 
   return image.forget();
 }
 
 /*
  * This is a synchronous task.
- * This class is used to create a layers::CairoImage from raw data in the main
+ * This class is used to create a layers::SourceSurfaceImage from raw data in the main
  * thread. While creating an ImageBitmap from an ImageData, we need to create
  * a SouceSurface from the ImageData's raw data and then set the SourceSurface
- * into a layers::CairoImage. However, the layers::CairoImage asserts the
+ * into a layers::SourceSurfaceImage. However, the layers::SourceSurfaceImage asserts the
  * setting operation in the main thread, so if we are going to create an
  * ImageBitmap from an ImageData off the main thread, we post an event to the
- * main thread to create a layers::CairoImage from an ImageData's raw data.
+ * main thread to create a layers::SourceSurfaceImage from an ImageData's raw data.
  */
 class CreateImageFromRawDataInMainThreadSyncTask final :
   public WorkerMainThreadRunnable
 {
 public:
   CreateImageFromRawDataInMainThreadSyncTask(uint8_t* aBuffer,
                                              uint32_t aBufferLength,
                                              uint32_t aStride,
@@ -404,26 +404,38 @@ ImageBitmap::~ImageBitmap()
 
 JSObject*
 ImageBitmap::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return ImageBitmapBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
+ImageBitmap::Close()
+{
+  mData = nullptr;
+  mSurface = nullptr;
+  mPictureRect.SetEmpty();
+}
+
+void
 ImageBitmap::SetPictureRect(const IntRect& aRect, ErrorResult& aRv)
 {
   mPictureRect = FixUpNegativeDimension(aRect, aRv);
 }
 
 already_AddRefed<SourceSurface>
 ImageBitmap::PrepareForDrawTarget(gfx::DrawTarget* aTarget)
 {
   MOZ_ASSERT(aTarget);
 
+  if (!mData) {
+    return nullptr;
+  }
+
   if (!mSurface) {
     mSurface = mData->GetAsSourceSurface();
   }
 
   if (!mSurface) {
     return nullptr;
   }
 
@@ -488,16 +500,78 @@ ImageBitmap::PrepareForDrawTarget(gfx::D
   // to, under the assumption it'll likely be drawn again to that target.
   // This call should be a no-op for already-optimized surfaces
   mSurface = target->OptimizeSourceSurface(mSurface);
 
   RefPtr<gfx::SourceSurface> surface(mSurface);
   return surface.forget();
 }
 
+already_AddRefed<layers::Image>
+ImageBitmap::TransferAsImage()
+{
+  RefPtr<layers::Image> image = mData;
+  Close();
+  return image.forget();
+}
+
+ImageBitmapCloneData*
+ImageBitmap::ToCloneData()
+{
+  ImageBitmapCloneData* result = new ImageBitmapCloneData();
+  result->mPictureRect = mPictureRect;
+  RefPtr<SourceSurface> surface = mData->GetAsSourceSurface();
+  result->mSurface = surface->GetDataSurface();
+  MOZ_ASSERT(result->mSurface);
+
+  return result;
+}
+
+/* static */ already_AddRefed<ImageBitmap>
+ImageBitmap::CreateFromCloneData(nsIGlobalObject* aGlobal,
+                                 ImageBitmapCloneData* aData)
+{
+  RefPtr<layers::Image> data =
+    CreateImageFromSurface(aData->mSurface);
+
+  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data);
+  ErrorResult rv;
+  ret->SetPictureRect(aData->mPictureRect, rv);
+  return ret.forget();
+}
+
+/* static */ already_AddRefed<ImageBitmap>
+ImageBitmap::CreateFromOffscreenCanvas(nsIGlobalObject* aGlobal,
+                                       OffscreenCanvas& aOffscreenCanvas,
+                                       ErrorResult& aRv)
+{
+  // Check origin-clean.
+  if (aOffscreenCanvas.IsWriteOnly()) {
+    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+    return nullptr;
+  }
+
+  nsLayoutUtils::SurfaceFromElementResult res =
+    nsLayoutUtils::SurfaceFromOffscreenCanvas(&aOffscreenCanvas,
+                                              nsLayoutUtils::SFE_WANT_FIRST_FRAME);
+
+  RefPtr<SourceSurface> surface = res.GetSourceSurface();
+
+  if (NS_WARN_IF(!surface)) {
+    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+    return nullptr;
+  }
+
+  RefPtr<layers::Image> data =
+    CreateImageFromSurface(surface);
+
+  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data);
+  return ret.forget();
+}
+
 /* static */ already_AddRefed<ImageBitmap>
 ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, HTMLImageElement& aImageEl,
                             const Maybe<IntRect>& aCropRect, ErrorResult& aRv)
 {
   // Check if the image element is completely available or not.
   if (!aImageEl.Complete()) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
@@ -1149,17 +1223,17 @@ ImageBitmap::Create(nsIGlobalObject* aGl
 
   return promise.forget();
 }
 
 /*static*/ JSObject*
 ImageBitmap::ReadStructuredClone(JSContext* aCx,
                                  JSStructuredCloneReader* aReader,
                                  nsIGlobalObject* aParent,
-                                 const nsTArray<RefPtr<layers::Image>>& aClonedImages,
+                                 const nsTArray<RefPtr<DataSourceSurface>>& aClonedSurfaces,
                                  uint32_t aIndex)
 {
   MOZ_ASSERT(aCx);
   MOZ_ASSERT(aReader);
   // aParent might be null.
 
   uint32_t picRectX_;
   uint32_t picRectY_;
@@ -1172,28 +1246,29 @@ ImageBitmap::ReadStructuredClone(JSConte
   }
 
   int32_t picRectX = BitwiseCast<int32_t>(picRectX_);
   int32_t picRectY = BitwiseCast<int32_t>(picRectY_);
   int32_t picRectWidth = BitwiseCast<int32_t>(picRectWidth_);
   int32_t picRectHeight = BitwiseCast<int32_t>(picRectHeight_);
 
   // Create a new ImageBitmap.
-  MOZ_ASSERT(!aClonedImages.IsEmpty());
-  MOZ_ASSERT(aIndex < aClonedImages.Length());
+  MOZ_ASSERT(!aClonedSurfaces.IsEmpty());
+  MOZ_ASSERT(aIndex < aClonedSurfaces.Length());
 
   // RefPtr<ImageBitmap> needs to go out of scope before toObjectOrNull() is
   // called because the static analysis thinks dereferencing XPCOM objects
   // can GC (because in some cases it can!), and a return statement with a
   // JSObject* type means that JSObject* is on the stack as a raw pointer
   // while destructors are running.
   JS::Rooted<JS::Value> value(aCx);
   {
+    RefPtr<layers::Image> img = CreateImageFromSurface(aClonedSurfaces[aIndex]);
     RefPtr<ImageBitmap> imageBitmap =
-      new ImageBitmap(aParent, aClonedImages[aIndex]);
+      new ImageBitmap(aParent, img);
 
     ErrorResult error;
     imageBitmap->SetPictureRect(IntRect(picRectX, picRectY,
                                         picRectWidth, picRectHeight), error);
     if (NS_WARN_IF(error.Failed())) {
       error.SuppressException();
       return nullptr;
     }
@@ -1203,35 +1278,51 @@ ImageBitmap::ReadStructuredClone(JSConte
     }
   }
 
   return &(value.toObject());
 }
 
 /*static*/ bool
 ImageBitmap::WriteStructuredClone(JSStructuredCloneWriter* aWriter,
-                                  nsTArray<RefPtr<layers::Image>>& aClonedImages,
+                                  nsTArray<RefPtr<DataSourceSurface>>& aClonedSurfaces,
                                   ImageBitmap* aImageBitmap)
 {
   MOZ_ASSERT(aWriter);
   MOZ_ASSERT(aImageBitmap);
 
   const uint32_t picRectX = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.x);
   const uint32_t picRectY = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.y);
   const uint32_t picRectWidth = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.width);
   const uint32_t picRectHeight = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.height);
 
-  // Indexing the cloned images and send the index to the receiver.
-  uint32_t index = aClonedImages.Length();
+  // Indexing the cloned surfaces and send the index to the receiver.
+  uint32_t index = aClonedSurfaces.Length();
 
   if (NS_WARN_IF(!JS_WriteUint32Pair(aWriter, SCTAG_DOM_IMAGEBITMAP, index)) ||
       NS_WARN_IF(!JS_WriteUint32Pair(aWriter, picRectX, picRectY)) ||
       NS_WARN_IF(!JS_WriteUint32Pair(aWriter, picRectWidth, picRectHeight))) {
     return false;
   }
 
-  aClonedImages.AppendElement(aImageBitmap->mData);
-
+  RefPtr<SourceSurface> surface =
+    aImageBitmap->mData->GetAsSourceSurface();
+  RefPtr<DataSourceSurface> snapshot = surface->GetDataSurface();
+  RefPtr<DataSourceSurface> dstDataSurface;
+  {
+    // DataSourceSurfaceD2D1::GetStride() will call EnsureMapped implicitly and
+    // won't Unmap after exiting function. So instead calling GetStride()
+    // directly, using ScopedMap to get stride.
+    DataSourceSurface::ScopedMap map(snapshot, DataSourceSurface::READ);
+    dstDataSurface =
+      Factory::CreateDataSourceSurfaceWithStride(snapshot->GetSize(),
+                                                 snapshot->GetFormat(),
+                                                 map.GetStride(),
+                                                 true);
+  }
+  MOZ_ASSERT(dstDataSurface);
+  Factory::CopyDataSourceSurface(snapshot, dstDataSurface);
+  aClonedSurfaces.AppendElement(dstDataSurface);
   return true;
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/canvas/ImageBitmap.h
+++ b/dom/canvas/ImageBitmap.h
@@ -20,24 +20,26 @@ struct JSStructuredCloneWriter;
 
 class nsIGlobalObject;
 
 namespace mozilla {
 
 class ErrorResult;
 
 namespace gfx {
+class DataSourceSurface;
 class SourceSurface;
 }
 
 namespace layers {
 class Image;
 }
 
 namespace dom {
+class OffscreenCanvas;
 
 namespace workers {
 class WorkerStructuredCloneClosure;
 }
 
 class CanvasRenderingContext2D;
 class File;
 class HTMLCanvasElement;
@@ -45,16 +47,22 @@ class HTMLImageElement;
 class HTMLVideoElement;
 class ImageData;
 class Promise;
 class PostMessageEvent; // For StructuredClone between windows.
 class CreateImageBitmapFromBlob;
 class CreateImageBitmapFromBlobTask;
 class CreateImageBitmapFromBlobWorkerTask;
 
+struct ImageBitmapCloneData final
+{
+  RefPtr<gfx::DataSourceSurface> mSurface;
+  gfx::IntRect mPictureRect;
+};
+
 /*
  * ImageBitmap is an opaque handler to several kinds of image-like objects from
  * HTMLImageElement, HTMLVideoElement, HTMLCanvasElement, ImageData to
  * CanvasRenderingContext2D and Image Blob.
  *
  * An ImageBitmap could be painted to a canvas element.
  *
  * Generally, an ImageBitmap only keeps a reference to its source object's
@@ -78,37 +86,57 @@ public:
     return mPictureRect.Width();
   }
 
   uint32_t Height() const
   {
     return mPictureRect.Height();
   }
 
+  void Close();
+
   /*
    * The PrepareForDrawTarget() might return null if the mPictureRect does not
    * intersect with the size of mData.
    */
   already_AddRefed<gfx::SourceSurface>
   PrepareForDrawTarget(gfx::DrawTarget* aTarget);
 
+  /*
+   * Transfer ownership of buffer to caller. So this function call
+   * Close() implicitly.
+   */
+  already_AddRefed<layers::Image>
+  TransferAsImage();
+
+  ImageBitmapCloneData*
+  ToCloneData();
+
+  static already_AddRefed<ImageBitmap>
+  CreateFromCloneData(nsIGlobalObject* aGlobal, ImageBitmapCloneData* aData);
+
+  static already_AddRefed<ImageBitmap>
+  CreateFromOffscreenCanvas(nsIGlobalObject* aGlobal,
+                            OffscreenCanvas& aOffscreenCanvas,
+                            ErrorResult& aRv);
+
   static already_AddRefed<Promise>
   Create(nsIGlobalObject* aGlobal, const ImageBitmapSource& aSrc,
          const Maybe<gfx::IntRect>& aCropRect, ErrorResult& aRv);
 
   static JSObject*
   ReadStructuredClone(JSContext* aCx,
                       JSStructuredCloneReader* aReader,
                       nsIGlobalObject* aParent,
-                      const nsTArray<RefPtr<layers::Image>>& aClonedImages,
+                      const nsTArray<RefPtr<gfx::DataSourceSurface>>& aClonedSurfaces,
                       uint32_t aIndex);
 
   static bool
   WriteStructuredClone(JSStructuredCloneWriter* aWriter,
-                       nsTArray<RefPtr<layers::Image>>& aClonedImages,
+                       nsTArray<RefPtr<gfx::DataSourceSurface>>& aClonedSurfaces,
                        ImageBitmap* aImageBitmap);
 
   friend CreateImageBitmapFromBlob;
   friend CreateImageBitmapFromBlobTask;
   friend CreateImageBitmapFromBlobWorkerTask;
 
 protected:
 
new file mode 100644
--- /dev/null
+++ b/dom/canvas/ImageBitmapRenderingContext.cpp
@@ -0,0 +1,303 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * 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/. */
+
+#include "ImageBitmapRenderingContext.h"
+#include "mozilla/dom/ImageBitmapRenderingContextBinding.h"
+#include "ImageContainer.h"
+#include "ImageLayers.h"
+
+namespace mozilla {
+namespace dom {
+
+ImageBitmapRenderingContext::ImageBitmapRenderingContext()
+  : mWidth(0)
+  , mHeight(0)
+{
+}
+
+ImageBitmapRenderingContext::~ImageBitmapRenderingContext()
+{
+  RemovePostRefreshObserver();
+}
+
+JSObject*
+ImageBitmapRenderingContext::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return ImageBitmapRenderingContextBinding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<layers::Image>
+ImageBitmapRenderingContext::ClipToIntrinsicSize()
+{
+  if (!mImage) {
+    return nullptr;
+  }
+
+  // If image is larger than canvas intrinsic size, clip it to the intrinsic size.
+  RefPtr<gfx::SourceSurface> surface;
+  RefPtr<layers::Image> result;
+  if (mWidth < mImage->GetSize().width ||
+      mHeight < mImage->GetSize().height) {
+    surface = MatchWithIntrinsicSize();
+  } else {
+    surface = mImage->GetAsSourceSurface();
+  }
+  result = new layers::SourceSurfaceImage(gfx::IntSize(mWidth, mHeight), surface);
+  return result.forget();
+}
+
+void
+ImageBitmapRenderingContext::TransferImageBitmap(ImageBitmap& aImageBitmap)
+{
+  Reset();
+  mImage = aImageBitmap.TransferAsImage();
+
+  if (!mImage) {
+    return;
+  }
+
+  Redraw(gfxRect(0, 0, mWidth, mHeight));
+}
+
+int32_t
+ImageBitmapRenderingContext::GetWidth() const
+{
+  return mWidth;
+}
+
+int32_t
+ImageBitmapRenderingContext::GetHeight() const
+{
+  return mHeight;
+}
+
+NS_IMETHODIMP
+ImageBitmapRenderingContext::SetDimensions(int32_t aWidth, int32_t aHeight)
+{
+  mWidth = aWidth;
+  mHeight = aHeight;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ImageBitmapRenderingContext::InitializeWithSurface(nsIDocShell* aDocShell,
+                                                   gfxASurface* aSurface,
+                                                   int32_t aWidth,
+                                                   int32_t aHeight)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+already_AddRefed<DataSourceSurface>
+ImageBitmapRenderingContext::MatchWithIntrinsicSize()
+{
+  RefPtr<SourceSurface> surface = mImage->GetAsSourceSurface();
+  RefPtr<DataSourceSurface> temp =
+    Factory::CreateDataSourceSurface(IntSize(mWidth, mHeight), surface->GetFormat());
+  if (!temp) {
+    return nullptr;
+  }
+
+  DataSourceSurface::ScopedMap map(temp, DataSourceSurface::READ_WRITE);
+  if (!map.IsMapped()) {
+    return nullptr;
+  }
+
+  RefPtr<DrawTarget> dt =
+    Factory::CreateDrawTargetForData(BackendType::CAIRO,
+                                     map.GetData(),
+                                     temp->GetSize(),
+                                     map.GetStride(),
+                                     temp->GetFormat());
+  if (!dt) {
+    return nullptr;
+  }
+
+
+  dt->ClearRect(Rect(0, 0, mWidth, mHeight));
+  dt->CopySurface(surface,
+                  IntRect(0, 0, surface->GetSize().width,
+                                surface->GetSize().height),
+                  IntPoint(0, 0));
+
+  return temp.forget();
+}
+
+mozilla::UniquePtr<uint8_t[]>
+ImageBitmapRenderingContext::GetImageBuffer(int32_t* aFormat)
+{
+  *aFormat = 0;
+
+  if (!mImage) {
+    return nullptr;
+  }
+
+  RefPtr<SourceSurface> surface = mImage->GetAsSourceSurface();
+  RefPtr<DataSourceSurface> data = surface->GetDataSurface();
+  if (!data) {
+    return nullptr;
+  }
+
+  if (data->GetSize() != IntSize(mWidth, mHeight)) {
+    data = MatchWithIntrinsicSize();
+  }
+
+  *aFormat = imgIEncoder::INPUT_FORMAT_HOSTARGB;
+  return SurfaceToPackedBGRA(data);
+}
+
+NS_IMETHODIMP
+ImageBitmapRenderingContext::GetInputStream(const char* aMimeType,
+                                            const char16_t* aEncoderOptions,
+                                            nsIInputStream** aStream)
+{
+  nsCString enccid("@mozilla.org/image/encoder;2?type=");
+  enccid += aMimeType;
+  nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get());
+  if (!encoder) {
+    return NS_ERROR_FAILURE;
+  }
+
+  int32_t format = 0;
+  UniquePtr<uint8_t[]> imageBuffer = GetImageBuffer(&format);
+  if (!imageBuffer) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return ImageEncoder::GetInputStream(mWidth, mHeight, imageBuffer.get(), format,
+                                      encoder, aEncoderOptions, aStream);
+}
+
+already_AddRefed<mozilla::gfx::SourceSurface>
+ImageBitmapRenderingContext::GetSurfaceSnapshot(bool* aPremultAlpha)
+{
+  if (!mImage) {
+    return nullptr;
+  }
+
+  if (aPremultAlpha) {
+    *aPremultAlpha = true;
+  }
+
+  RefPtr<SourceSurface> surface = mImage->GetAsSourceSurface();
+  if (surface->GetSize() != IntSize(mWidth, mHeight)) {
+    return MatchWithIntrinsicSize();
+  }
+
+  return surface.forget();
+}
+
+NS_IMETHODIMP
+ImageBitmapRenderingContext::SetIsOpaque(bool aIsOpaque)
+{
+  return NS_OK;
+}
+
+bool
+ImageBitmapRenderingContext::GetIsOpaque()
+{
+  return false;
+}
+
+NS_IMETHODIMP
+ImageBitmapRenderingContext::Reset()
+{
+  if (mCanvasElement) {
+    mCanvasElement->InvalidateCanvas();
+  }
+
+  mImage = nullptr;
+
+  return NS_OK;
+}
+
+already_AddRefed<Layer>
+ImageBitmapRenderingContext::GetCanvasLayer(nsDisplayListBuilder* aBuilder,
+                                            Layer* aOldLayer,
+                                            LayerManager* aManager)
+{
+  if (!mImage) {
+    // No DidTransactionCallback will be received, so mark the context clean
+    // now so future invalidations will be dispatched.
+    MarkContextClean();
+    return nullptr;
+  }
+
+  RefPtr<ImageLayer> imageLayer;
+
+  if (aOldLayer) {
+    imageLayer = static_cast<ImageLayer*>(aOldLayer);
+  } else {
+    imageLayer = aManager->CreateImageLayer();
+  }
+
+  RefPtr<ImageContainer> imageContainer = imageLayer->GetContainer();
+  if (!imageContainer) {
+    imageContainer = aManager->CreateImageContainer();
+    imageLayer->SetContainer(imageContainer);
+  }
+
+  nsAutoTArray<ImageContainer::NonOwningImage, 1> imageList;
+  RefPtr<layers::Image> image = ClipToIntrinsicSize();
+  imageList.AppendElement(ImageContainer::NonOwningImage(image));
+  imageContainer->SetCurrentImages(imageList);
+
+  return imageLayer.forget();
+}
+
+void
+ImageBitmapRenderingContext::MarkContextClean()
+{
+}
+
+NS_IMETHODIMP
+ImageBitmapRenderingContext::Redraw(const gfxRect& aDirty)
+{
+  if (!mCanvasElement) {
+    return NS_OK;
+  }
+
+  mozilla::gfx::Rect rect = ToRect(aDirty);
+  mCanvasElement->InvalidateCanvasContent(&rect);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ImageBitmapRenderingContext::SetIsIPC(bool aIsIPC)
+{
+  return NS_OK;
+}
+
+void
+ImageBitmapRenderingContext::DidRefresh()
+{
+}
+
+void
+ImageBitmapRenderingContext::MarkContextCleanForFrameCapture()
+{
+}
+
+bool
+ImageBitmapRenderingContext::IsContextCleanForFrameCapture()
+{
+  return true;
+}
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ImageBitmapRenderingContext)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ImageBitmapRenderingContext)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ImageBitmapRenderingContext,
+  mCanvasElement,
+  mOffscreenCanvas)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ImageBitmapRenderingContext)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+}
+}
new file mode 100644
--- /dev/null
+++ b/dom/canvas/ImageBitmapRenderingContext.h
@@ -0,0 +1,97 @@
+/* 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 ImageBitmapRenderingContext_h
+#define ImageBitmapRenderingContext_h
+
+#include "nsICanvasRenderingContextInternal.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+
+namespace gfx {
+class DataSourceSurface;
+class SourceSurface;
+}
+
+namespace layers {
+class Image;
+class ImageContainer;
+}
+
+namespace dom {
+
+/**
+ * The purpose of ImageBitmapRenderingContext is to provide a faster and efficient
+ * way to display ImageBitmap. Simply call TransferImageBitmap() then we'll transfer
+ * the surface of ImageBitmap to this context and then to use it to display.
+ *
+ * See more details in spec: https://wiki.whatwg.org/wiki/OffscreenCanvas
+ */
+class ImageBitmapRenderingContext final :
+  public nsICanvasRenderingContextInternal,
+  public nsWrapperCache
+{
+  virtual ~ImageBitmapRenderingContext();
+
+public:
+  ImageBitmapRenderingContext();
+
+  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  // nsISupports interface + CC
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ImageBitmapRenderingContext)
+
+  void TransferImageBitmap(ImageBitmap& aImageBitmap);
+
+  // nsICanvasRenderingContextInternal
+  virtual int32_t GetWidth() const override;
+  virtual int32_t GetHeight() const override;
+
+  NS_IMETHOD SetDimensions(int32_t aWidth, int32_t aHeight) override;
+
+  NS_IMETHOD InitializeWithSurface(nsIDocShell* aDocShell,
+                                   gfxASurface* aSurface,
+                                   int32_t aWidth,
+                                   int32_t aHeight) override;
+
+  virtual mozilla::UniquePtr<uint8_t[]> GetImageBuffer(int32_t* aFormat) override;
+  NS_IMETHOD GetInputStream(const char* aMimeType,
+                            const char16_t* aEncoderOptions,
+                            nsIInputStream** aStream) override;
+
+  virtual already_AddRefed<mozilla::gfx::SourceSurface>
+  GetSurfaceSnapshot(bool* aPremultAlpha = nullptr) override;
+
+  NS_IMETHOD SetIsOpaque(bool aIsOpaque) override;
+  virtual bool GetIsOpaque() override;
+  NS_IMETHOD Reset() override;
+  virtual already_AddRefed<Layer> GetCanvasLayer(nsDisplayListBuilder* aBuilder,
+                                                 Layer* aOldLayer,
+                                                 LayerManager* aManager) override;
+  virtual void MarkContextClean() override;
+
+  NS_IMETHOD Redraw(const gfxRect& aDirty) override;
+  NS_IMETHOD SetIsIPC(bool aIsIPC) override;
+
+  virtual void DidRefresh() override;
+
+  virtual void MarkContextCleanForFrameCapture() override;
+  virtual bool IsContextCleanForFrameCapture() override;
+
+protected:
+  already_AddRefed<gfx::DataSourceSurface> MatchWithIntrinsicSize();
+  already_AddRefed<layers::Image> ClipToIntrinsicSize();
+  int32_t mWidth;
+  int32_t mHeight;
+
+  RefPtr<layers::Image> mImage;
+};
+
+}
+}
+
+#endif /* ImageBitmapRenderingContext_h */
--- a/dom/canvas/OffscreenCanvas.cpp
+++ b/dom/canvas/OffscreenCanvas.cpp
@@ -3,88 +3,102 @@
 /* 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/. */
 
 #include "OffscreenCanvas.h"
 
 #include "mozilla/dom/OffscreenCanvasBinding.h"
 #include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerScope.h"
 #include "mozilla/layers/AsyncCanvasRenderer.h"
 #include "mozilla/layers/CanvasClient.h"
 #include "mozilla/layers/ImageBridgeChild.h"
 #include "mozilla/Telemetry.h"
 #include "CanvasRenderingContext2D.h"
 #include "CanvasUtils.h"
 #include "GLScreenBuffer.h"
 #include "WebGL1Context.h"
 #include "WebGL2Context.h"
 
 namespace mozilla {
 namespace dom {
 
 OffscreenCanvasCloneData::OffscreenCanvasCloneData(layers::AsyncCanvasRenderer* aRenderer,
                                                    uint32_t aWidth, uint32_t aHeight,
                                                    layers::LayersBackend aCompositorBackend,
-                                                   bool aNeutered)
+                                                   bool aNeutered, bool aIsWriteOnly)
   : mRenderer(aRenderer)
   , mWidth(aWidth)
   , mHeight(aHeight)
   , mCompositorBackendType(aCompositorBackend)
   , mNeutered(aNeutered)
+  , mIsWriteOnly(aIsWriteOnly)
 {
 }
 
 OffscreenCanvasCloneData::~OffscreenCanvasCloneData()
 {
 }
 
-OffscreenCanvas::OffscreenCanvas(uint32_t aWidth,
+OffscreenCanvas::OffscreenCanvas(nsIGlobalObject* aGlobal,
+                                 uint32_t aWidth,
                                  uint32_t aHeight,
                                  layers::LayersBackend aCompositorBackend,
                                  layers::AsyncCanvasRenderer* aRenderer)
-  : mAttrDirty(false)
+  : DOMEventTargetHelper(aGlobal)
+  , mAttrDirty(false)
   , mNeutered(false)
+  , mIsWriteOnly(false)
   , mWidth(aWidth)
   , mHeight(aHeight)
   , mCompositorBackendType(aCompositorBackend)
   , mCanvasClient(nullptr)
   , mCanvasRenderer(aRenderer)
 {}
 
 OffscreenCanvas::~OffscreenCanvas()
 {
   ClearResources();
 }
 
-OffscreenCanvas*
-OffscreenCanvas::GetParentObject() const
-{
-  return nullptr;
-}
-
 JSObject*
 OffscreenCanvas::WrapObject(JSContext* aCx,
                             JS::Handle<JSObject*> aGivenProto)
 {
   return OffscreenCanvasBinding::Wrap(aCx, this, aGivenProto);
 }
 
+/* static */ already_AddRefed<OffscreenCanvas>
+OffscreenCanvas::Constructor(const GlobalObject& aGlobal,
+                             uint32_t aWidth,
+                             uint32_t aHeight,
+                             ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+  RefPtr<OffscreenCanvas> offscreenCanvas =
+    new OffscreenCanvas(global, aWidth, aHeight,
+                        layers::LayersBackend::LAYERS_NONE, nullptr);
+  return offscreenCanvas.forget();
+}
+
 void
 OffscreenCanvas::ClearResources()
 {
   if (mCanvasClient) {
     mCanvasClient->Clear();
     ImageBridgeChild::DispatchReleaseCanvasClient(mCanvasClient);
     mCanvasClient = nullptr;
 
     if (mCanvasRenderer) {
       nsCOMPtr<nsIThread> activeThread = mCanvasRenderer->GetActiveThread();
       MOZ_RELEASE_ASSERT(activeThread);
-      MOZ_RELEASE_ASSERT(activeThread == NS_GetCurrentThread());
+      bool current;
+      activeThread->IsOnCurrentThread(&current);
+      MOZ_RELEASE_ASSERT(current);
       mCanvasRenderer->SetCanvasClient(nullptr);
       mCanvasRenderer->mContext = nullptr;
       mCanvasRenderer->mGLContext = nullptr;
       mCanvasRenderer->ResetActiveThread();
     }
   }
 }
 
@@ -102,55 +116,59 @@ OffscreenCanvas::GetContext(JSContext* a
   // We only support WebGL in workers for now
   CanvasContextType contextType;
   if (!CanvasUtils::GetCanvasContextType(aContextId, &contextType)) {
     aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
     return nullptr;
   }
 
   if (!(contextType == CanvasContextType::WebGL1 ||
-        contextType == CanvasContextType::WebGL2))
+        contextType == CanvasContextType::WebGL2 ||
+        contextType == CanvasContextType::ImageBitmap))
   {
     aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
     return nullptr;
   }
 
   already_AddRefed<nsISupports> result =
     CanvasRenderingContextHelper::GetContext(aCx,
                                              aContextId,
                                              aContextOptions,
                                              aRv);
 
   if (!mCurrentContext) {
     return nullptr;
   }
 
   if (mCanvasRenderer) {
-    WebGLContext* webGL = static_cast<WebGLContext*>(mCurrentContext.get());
-    gl::GLContext* gl = webGL->GL();
-    mCanvasRenderer->mContext = mCurrentContext;
-    mCanvasRenderer->SetActiveThread();
-    mCanvasRenderer->mGLContext = gl;
-    mCanvasRenderer->SetIsAlphaPremultiplied(webGL->IsPremultAlpha() || !gl->Caps().alpha);
+    if (contextType == CanvasContextType::WebGL1 ||
+        contextType == CanvasContextType::WebGL2) {
+      WebGLContext* webGL = static_cast<WebGLContext*>(mCurrentContext.get());
+      gl::GLContext* gl = webGL->GL();
+      mCanvasRenderer->mContext = mCurrentContext;
+      mCanvasRenderer->SetActiveThread();
+      mCanvasRenderer->mGLContext = gl;
+      mCanvasRenderer->SetIsAlphaPremultiplied(webGL->IsPremultAlpha() || !gl->Caps().alpha);
 
-    if (ImageBridgeChild::IsCreated()) {
-      TextureFlags flags = TextureFlags::ORIGIN_BOTTOM_LEFT;
-      mCanvasClient = ImageBridgeChild::GetSingleton()->
-        CreateCanvasClient(CanvasClient::CanvasClientTypeShSurf, flags).take();
-      mCanvasRenderer->SetCanvasClient(mCanvasClient);
+      if (ImageBridgeChild::IsCreated()) {
+        TextureFlags flags = TextureFlags::ORIGIN_BOTTOM_LEFT;
+        mCanvasClient = ImageBridgeChild::GetSingleton()->
+          CreateCanvasClient(CanvasClient::CanvasClientTypeShSurf, flags).take();
+        mCanvasRenderer->SetCanvasClient(mCanvasClient);
 
-      gl::GLScreenBuffer* screen = gl->Screen();
-      gl::SurfaceCaps caps = screen->mCaps;
-      auto forwarder = mCanvasClient->GetForwarder();
+        gl::GLScreenBuffer* screen = gl->Screen();
+        gl::SurfaceCaps caps = screen->mCaps;
+        auto forwarder = mCanvasClient->GetForwarder();
 
-      UniquePtr<gl::SurfaceFactory> factory =
-        gl::GLScreenBuffer::CreateFactory(gl, caps, forwarder, flags);
+        UniquePtr<gl::SurfaceFactory> factory =
+          gl::GLScreenBuffer::CreateFactory(gl, caps, forwarder, flags);
 
-      if (factory)
-        screen->Morph(Move(factory));
+        if (factory)
+          screen->Morph(Move(factory));
+      }
     }
   }
 
   return result;
 }
 
 already_AddRefed<nsICanvasRenderingContextInternal>
 OffscreenCanvas::CreateContext(CanvasContextType aContextType)
@@ -160,16 +178,22 @@ OffscreenCanvas::CreateContext(CanvasCon
 
   ret->SetOffscreenCanvas(this);
   return ret.forget();
 }
 
 void
 OffscreenCanvas::CommitFrameToCompositor()
 {
+  if (!mCanvasRenderer) {
+    // This offscreen canvas doesn't associate to any HTML canvas element.
+    // So, just bail out.
+    return;
+  }
+
   // The attributes has changed, we have to notify main
   // thread to change canvas size.
   if (mAttrDirty) {
     if (mCanvasRenderer) {
       mCanvasRenderer->SetWidth(mWidth);
       mCanvasRenderer->SetHeight(mHeight);
       mCanvasRenderer->NotifyElementAboutAttributesChanged();
     }
@@ -186,25 +210,131 @@ OffscreenCanvas::CommitFrameToCompositor
       UpdateAsyncCanvasRenderer(mCanvasRenderer);
   }
 }
 
 OffscreenCanvasCloneData*
 OffscreenCanvas::ToCloneData()
 {
   return new OffscreenCanvasCloneData(mCanvasRenderer, mWidth, mHeight,
-                                      mCompositorBackendType, mNeutered);
+                                      mCompositorBackendType, mNeutered, mIsWriteOnly);
+}
+
+already_AddRefed<ImageBitmap>
+OffscreenCanvas::TransferToImageBitmap()
+{
+  ErrorResult rv;
+  RefPtr<ImageBitmap> result = ImageBitmap::CreateFromOffscreenCanvas(GetGlobalObject(), *this, rv);
+
+  // Clear the content.
+  if ((mCurrentContextType == CanvasContextType::WebGL1 ||
+       mCurrentContextType == CanvasContextType::WebGL2))
+  {
+    WebGLContext* webGL = static_cast<WebGLContext*>(mCurrentContext.get());
+    webGL->ClearScreen();
+  }
+
+  return result.forget();
+}
+
+already_AddRefed<Promise>
+OffscreenCanvas::ToBlob(JSContext* aCx,
+                        const nsAString& aType,
+                        JS::Handle<JS::Value> aParams,
+                        ErrorResult& aRv)
+{
+  // do a trust check if this is a write-only canvas
+  if (mIsWriteOnly) {
+    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIGlobalObject> global = GetGlobalObject();
+
+  RefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  // Encoder callback when encoding is complete.
+  class EncodeCallback : public EncodeCompleteCallback
+  {
+  public:
+    EncodeCallback(nsIGlobalObject* aGlobal, Promise* aPromise)
+      : mGlobal(aGlobal)
+      , mPromise(aPromise) {}
+
+    // This is called on main thread.
+    nsresult ReceiveBlob(already_AddRefed<Blob> aBlob)
+    {
+      RefPtr<Blob> blob = aBlob;
+
+      ErrorResult rv;
+      uint64_t size = blob->GetSize(rv);
+      if (rv.Failed()) {
+        rv.SuppressException();
+      } else {
+        AutoJSAPI jsapi;
+        if (jsapi.Init(mGlobal)) {
+          JS_updateMallocCounter(jsapi.cx(), size);
+        }
+      }
+
+      if (mPromise) {
+        RefPtr<Blob> newBlob = Blob::Create(mGlobal, blob->Impl());
+        mPromise->MaybeResolve(newBlob);
+      }
+
+      mGlobal = nullptr;
+      mPromise = nullptr;
+
+      return rv.StealNSResult();
+    }
+
+    nsCOMPtr<nsIGlobalObject> mGlobal;
+    RefPtr<Promise> mPromise;
+  };
+
+  RefPtr<EncodeCompleteCallback> callback =
+    new EncodeCallback(global, promise);
+
+  CanvasRenderingContextHelper::ToBlob(aCx, global,
+                                       callback, aType, aParams, aRv);
+
+  return promise.forget();
+}
+
+already_AddRefed<gfx::SourceSurface>
+OffscreenCanvas::GetSurfaceSnapshot(bool* aPremultAlpha)
+{
+  if (!mCurrentContext) {
+    return nullptr;
+  }
+
+  return mCurrentContext->GetSurfaceSnapshot(aPremultAlpha);
+}
+
+nsCOMPtr<nsIGlobalObject>
+OffscreenCanvas::GetGlobalObject()
+{
+  if (NS_IsMainThread()) {
+    return GetParentObject();
+  }
+
+  dom::workers::WorkerPrivate* workerPrivate =
+    dom::workers::GetCurrentThreadWorkerPrivate();
+  return workerPrivate->GlobalScope();
 }
 
 /* static */ already_AddRefed<OffscreenCanvas>
-OffscreenCanvas::CreateFromCloneData(OffscreenCanvasCloneData* aData)
+OffscreenCanvas::CreateFromCloneData(nsIGlobalObject* aGlobal, OffscreenCanvasCloneData* aData)
 {
   MOZ_ASSERT(aData);
   RefPtr<OffscreenCanvas> wc =
-    new OffscreenCanvas(aData->mWidth, aData->mHeight,
+    new OffscreenCanvas(aGlobal, aData->mWidth, aData->mHeight,
                         aData->mCompositorBackendType, aData->mRenderer);
   if (aData->mNeutered) {
     wc->SetNeutered();
   }
   return wc.forget();
 }
 
 /* static */ bool
--- a/dom/canvas/OffscreenCanvas.h
+++ b/dom/canvas/OffscreenCanvas.h
@@ -20,53 +20,63 @@ namespace mozilla {
 class ErrorResult;
 
 namespace layers {
 class AsyncCanvasRenderer;
 class CanvasClient;
 } // namespace layers
 
 namespace dom {
+class Blob;
+class ImageBitmap;
 
 // This is helper class for transferring OffscreenCanvas to worker thread.
 // Because OffscreenCanvas is not thread-safe. So we cannot pass Offscreen-
 // Canvas to worker thread directly. Thus, we create this helper class and
 // store necessary data in it then pass it to worker thread.
 struct OffscreenCanvasCloneData final
 {
   OffscreenCanvasCloneData(layers::AsyncCanvasRenderer* aRenderer,
                            uint32_t aWidth, uint32_t aHeight,
                            layers::LayersBackend aCompositorBackend,
-                           bool aNeutered);
+                           bool aNeutered, bool aIsWriteOnly);
   ~OffscreenCanvasCloneData();
 
   RefPtr<layers::AsyncCanvasRenderer> mRenderer;
   uint32_t mWidth;
   uint32_t mHeight;
   layers::LayersBackend mCompositorBackendType;
   bool mNeutered;
+  bool mIsWriteOnly;
 };
 
 class OffscreenCanvas final : public DOMEventTargetHelper
                             , public CanvasRenderingContextHelper
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(OffscreenCanvas, DOMEventTargetHelper)
 
-  OffscreenCanvas(uint32_t aWidth,
+  OffscreenCanvas(nsIGlobalObject* aGlobal,
+                  uint32_t aWidth,
                   uint32_t aHeight,
                   layers::LayersBackend aCompositorBackend,
                   layers::AsyncCanvasRenderer* aRenderer);
 
-  OffscreenCanvas* GetParentObject() const;
+  nsCOMPtr<nsIGlobalObject> GetParentObject() const { return GetOwnerGlobal(); }
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
+  static already_AddRefed<OffscreenCanvas>
+  Constructor(const GlobalObject& aGlobal,
+              uint32_t aWidth,
+              uint32_t aHeight,
+              ErrorResult& aRv);
+
   void ClearResources();
 
   uint32_t Width() const
   {
     return mWidth;
   }
 
   uint32_t Height() const
@@ -95,23 +105,34 @@ public:
     }
 
     if (mHeight != aHeight) {
       mHeight = aHeight;
       CanvasAttrChanged();
     }
   }
 
+  already_AddRefed<ImageBitmap>
+  TransferToImageBitmap();
+
+  already_AddRefed<Promise>
+  ToBlob(JSContext* aCx,
+         const nsAString& aType,
+         JS::Handle<JS::Value> aParams,
+         ErrorResult& aRv);
+
   nsICanvasRenderingContextInternal* GetContext() const
   {
     return mCurrentContext;
   }
 
+  already_AddRefed<gfx::SourceSurface> GetSurfaceSnapshot(bool* aPremultAlpha = nullptr);
+
   static already_AddRefed<OffscreenCanvas>
-  CreateFromCloneData(OffscreenCanvasCloneData* aData);
+  CreateFromCloneData(nsIGlobalObject* aGlobal, OffscreenCanvasCloneData* aData);
 
   static bool PrefEnabled(JSContext* aCx, JSObject* aObj);
 
   // Return true on main-thread, and return gfx.offscreencanvas.enabled
   // on worker thread.
   static bool PrefEnabledOnWorkerThread(JSContext* aCx, JSObject* aObj);
 
   OffscreenCanvasCloneData* ToCloneData();
@@ -142,33 +163,46 @@ public:
     mNeutered = true;
   }
 
   bool IsNeutered() const
   {
     return mNeutered;
   }
 
+  void SetWriteOnly()
+  {
+    mIsWriteOnly = true;
+  }
+
+  bool IsWriteOnly() const
+  {
+    return mIsWriteOnly;
+  }
+
   layers::LayersBackend GetCompositorBackendType() const
   {
     return mCompositorBackendType;
   }
 
 private:
   ~OffscreenCanvas();
 
+  nsCOMPtr<nsIGlobalObject> GetGlobalObject();
+
   void CanvasAttrChanged()
   {
     mAttrDirty = true;
     ErrorResult dummy;
     UpdateContext(nullptr, JS::NullHandleValue, dummy);
   }
 
   bool mAttrDirty;
   bool mNeutered;
+  bool mIsWriteOnly;
 
   uint32_t mWidth;
   uint32_t mHeight;
 
   layers::LayersBackend mCompositorBackendType;
 
   layers::CanvasClient* mCanvasClient;
   RefPtr<layers::AsyncCanvasRenderer> mCanvasRenderer;
--- a/dom/canvas/WebGL2Context.cpp
+++ b/dom/canvas/WebGL2Context.cpp
@@ -1,19 +1,19 @@
 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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/. */
 
 #include "WebGL2Context.h"
 
+#include "gfxPrefs.h"
 #include "GLContext.h"
 #include "mozilla/dom/WebGL2RenderingContextBinding.h"
 #include "mozilla/ArrayUtils.h"
-#include "mozilla/Preferences.h"
 #include "mozilla/Telemetry.h"
 #include "WebGLBuffer.h"
 #include "WebGLFormats.h"
 #include "WebGLTransformFeedback.h"
 
 namespace mozilla {
 
 WebGL2Context::WebGL2Context()
@@ -32,17 +32,17 @@ UniquePtr<webgl::FormatUsageAuthority>
 WebGL2Context::CreateFormatUsage(gl::GLContext* gl) const
 {
     return webgl::FormatUsageAuthority::CreateForWebGL2(gl);
 }
 
 /*static*/ bool
 WebGL2Context::IsSupported()
 {
-    return Preferences::GetBool("webgl.enable-prototype-webgl2", false);
+    return gfxPrefs::WebGL2Enabled();
 }
 
 /*static*/ WebGL2Context*
 WebGL2Context::Create()
 {
     return new WebGL2Context();
 }
 
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -1126,27 +1126,27 @@ public:
 
         webgl->UpdateLastUseIndex();
     }
 
 private:
     RefPtr<HTMLCanvasElement> mCanvas;
 };
 
-already_AddRefed<layers::CanvasLayer>
+already_AddRefed<layers::Layer>
 WebGLContext::GetCanvasLayer(nsDisplayListBuilder* builder,
-                             CanvasLayer* oldLayer,
+                             Layer* oldLayer,
                              LayerManager* manager)
 {
     if (IsContextLost())
         return nullptr;
 
     if (!mResetLayer && oldLayer &&
         oldLayer->HasUserData(&gWebGLLayerUserData)) {
-        RefPtr<layers::CanvasLayer> ret = oldLayer;
+        RefPtr<layers::Layer> ret = oldLayer;
         return ret.forget();
     }
 
     RefPtr<CanvasLayer> canvasLayer = manager->CreateCanvasLayer();
     if (!canvasLayer) {
         NS_WARNING("CreateCanvasLayer returned null!");
         return nullptr;
     }
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -326,18 +326,18 @@ public:
      */
     WebGLTexture*
     ActiveBoundTextureForTexImageTarget(const TexImageTarget texImgTarget) const
     {
         const TexTarget texTarget = TexImageTargetToTexTarget(texImgTarget);
         return ActiveBoundTextureForTarget(texTarget);
     }
 
-    already_AddRefed<CanvasLayer>
-    GetCanvasLayer(nsDisplayListBuilder* builder, CanvasLayer* oldLayer,
+    already_AddRefed<Layer>
+    GetCanvasLayer(nsDisplayListBuilder* builder, Layer* oldLayer,
                    LayerManager* manager) override;
 
     // Note that 'clean' here refers to its invalidation state, not the
     // contents of the buffer.
     void MarkContextClean() override { mInvalidated = false; }
 
     void MarkContextCleanForFrameCapture() override { mCapturedFrameInvalidated = false; }
 
--- a/dom/canvas/WebGLFormats.cpp
+++ b/dom/canvas/WebGLFormats.cpp
@@ -534,21 +534,24 @@ FormatUsageAuthority::CreateForWebGL1(gl
         usage->isRenderable = isRenderable;
         usage->isFilterable = isFilterable;
     };
 
     // GLES 2.0.25, p117, Table 4.5
     // RGBA8 is made renderable in WebGL 1.0, "Framebuffer Object Attachments"
     //                              render filter
     //                              able   able
-    fnSet(EffectiveFormat::RGBA8  , true , true);
-    fnSet(EffectiveFormat::RGBA4  , true , true);
-    fnSet(EffectiveFormat::RGB5_A1, true , true);
-    fnSet(EffectiveFormat::RGB8   , false, true);
-    fnSet(EffectiveFormat::RGB565 , true , true);
+    fnSet(EffectiveFormat::RGBA8  , true, true);
+    fnSet(EffectiveFormat::RGBA4  , true, true);
+    fnSet(EffectiveFormat::RGB5_A1, true, true);
+    fnSet(EffectiveFormat::RGB565 , true, true);
+
+    // RGB8 is not guaranteed to be renderable, but we should allow it for web-compat.
+    // Min-capability mode should mark this as non-renderable.
+    fnSet(EffectiveFormat::RGB8, true, true);
 
     fnSet(EffectiveFormat::Luminance8Alpha8, false, true);
     fnSet(EffectiveFormat::Luminance8      , false, true);
     fnSet(EffectiveFormat::Alpha8          , false, true);
 
     fnSet(EffectiveFormat::DEPTH_COMPONENT16, true, false);
     fnSet(EffectiveFormat::STENCIL_INDEX8   , true, false);
 
--- a/dom/canvas/moz.build
+++ b/dom/canvas/moz.build
@@ -2,17 +2,17 @@
 # vim: set filetype=python:
 # 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/.
 
 TEST_DIRS += ['compiledtest']
 
 # Number changes to this file to avoid bug 1081323 (clobber after changing a manifest):
-# 1
+# 2
 
 MOCHITEST_MANIFESTS += [
     'test/crossorigin/mochitest.ini',
     'test/mochitest-subsuite-webgl.ini',
     'test/mochitest.ini',
 ]
 
 MOCHITEST_CHROME_MANIFESTS += ['test/chrome/chrome.ini']
@@ -29,32 +29,34 @@ EXPORTS.mozilla.ipc += [
 EXPORTS.mozilla.dom += [
     'CanvasGradient.h',
     'CanvasPath.h',
     'CanvasPattern.h',
     'CanvasRenderingContext2D.h',
     'CanvasRenderingContextHelper.h',
     'CanvasUtils.h',
     'ImageBitmap.h',
+    'ImageBitmapRenderingContext.h',
     'ImageBitmapSource.h',
     'ImageData.h',
     'OffscreenCanvas.h',
     'TextMetrics.h',
     'WebGLVertexArrayObject.h',
 ]
 
 # Canvas 2D and common sources
 UNIFIED_SOURCES += [
     'CanvasImageCache.cpp',
     'CanvasRenderingContext2D.cpp',
     'CanvasRenderingContextHelper.cpp',
     'CanvasUtils.cpp',
     'DocumentRendererChild.cpp',
     'DocumentRendererParent.cpp',
     'ImageBitmap.cpp',
+    'ImageBitmapRenderingContext.cpp',
     'ImageData.cpp',
     'OffscreenCanvas.cpp',
 ]
 
 # WebGL Sources
 UNIFIED_SOURCES += [
     'MurmurHash3.cpp',
     'TexUnpackBlob.cpp',
--- a/dom/canvas/nsICanvasRenderingContextInternal.h
+++ b/dom/canvas/nsICanvasRenderingContextInternal.h
@@ -21,29 +21,31 @@
   { 0xbd, 0xfb, 0x85, 0x57, 0x8a, 0xc2, 0xb4, 0x4b } }
 
 class gfxASurface;
 class nsDisplayListBuilder;
 
 namespace mozilla {
 namespace layers {
 class CanvasLayer;
+class Layer;
 class LayerManager;
 } // namespace layers
 namespace gfx {
 class SourceSurface;
 } // namespace gfx
 } // namespace mozilla
 
 class nsICanvasRenderingContextInternal :
   public nsISupports,
   public nsAPostRefreshObserver
 {
 public:
   typedef mozilla::layers::CanvasLayer CanvasLayer;
+  typedef mozilla::layers::Layer Layer;
   typedef mozilla::layers::LayerManager LayerManager;
 
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICANVASRENDERINGCONTEXTINTERNAL_IID)
 
   void SetCanvasElement(mozilla::dom::HTMLCanvasElement* parentCanvas)
   {
     RemovePostRefreshObserver();
     mCanvasElement = parentCanvas;
@@ -124,19 +126,19 @@ public:
   virtual bool GetIsOpaque() = 0;
 
   // Invalidate this context and release any held resources, in preperation
   // for possibly reinitializing with SetDimensions/InitializeWithSurface.
   NS_IMETHOD Reset() = 0;
 
   // Return the CanvasLayer for this context, creating
   // one for the given layer manager if not available.
-  virtual already_AddRefed<CanvasLayer> GetCanvasLayer(nsDisplayListBuilder* builder,
-                                                       CanvasLayer *oldLayer,
-                                                       LayerManager *manager) = 0;
+  virtual already_AddRefed<Layer> GetCanvasLayer(nsDisplayListBuilder* builder,
+                                                 Layer *oldLayer,
+                                                 LayerManager *manager) = 0;
 
   // Return true if the canvas should be forced to be "inactive" to ensure
   // it can be drawn to the screen even if it's too large to be blitted by
   // an accelerated CanvasLayer.
   virtual bool ShouldForceInactiveLayer(LayerManager *manager) { return false; }
 
   virtual void MarkContextClean() = 0;
 
--- a/dom/canvas/test/mochitest.ini
+++ b/dom/canvas/test/mochitest.ini
@@ -213,16 +213,17 @@ skip-if = toolkit == 'cocoa'
 [test_2d.path.arc.shape.3.html]
 skip-if = toolkit != 'cocoa'
 [test_2d.path.rect.selfintersect.html]
 skip-if = toolkit != 'cocoa'
 # This test is bogus according to the spec; see bug 407107
 [test_2d.path.rect.zero.6.html]
 disabled = bug 407107
 [test_2d.strokeRect.zero.5.html]
+[test_bitmaprenderer.html]
 [test_bug232227.html]
 [test_bug613794.html]
 [test_bug753758.html]
 [test_bug764125.html]
 [test_bug856472.html]
 [test_bug866575.html]
 skip-if = (toolkit == 'gonk' && debug) #bug 1045153
 [test_bug902651.html]
@@ -241,37 +242,43 @@ skip-if = os == "android" || appname == 
 support-files = captureStream_common.js
 [test_drawImageIncomplete.html]
 [test_drawImage_document_domain.html]
 [test_drawImage_edge_cases.html]
 [test_drawWindow.html]
 support-files = file_drawWindow_source.html file_drawWindow_common.js
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk')
 [test_imagebitmap.html]
+[test_imagebitmap_close.html]
 [test_imagebitmap_cropping.html]
 [test_imagebitmap_on_worker.html]
 [test_imagebitmap_structuredclone.html]
 [test_imagebitmap_structuredclone_iframe.html]
 [test_imagebitmap_structuredclone_window.html]
+[test_imagebitmap_transfer.html]
 [test_ImageData_ctor.html]
 [test_isPointInStroke.html]
 [test_mozDashOffset.html]
 [test_mozGetAsFile.html]
 [test_strokeText_throw.html]
 [test_toBlob.html]
 [test_toDataURL_alpha.html]
 [test_toDataURL_lowercase_ascii.html]
 [test_toDataURL_parameters.html]
 [test_windingRuleUndefined.html]
 [test_2d.fillText.gradient.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # bug 1040965
 [test_2d_composite_canvaspattern_setTransform.html]
 [test_createPattern_broken.html]
 [test_setlinedash.html]
 [test_filter.html]
+[test_offscreencanvas_toblob.html]
+tags = offscreencanvas
+[test_offscreencanvas_toimagebitmap.html]
+tags = offscreencanvas
 [test_offscreencanvas_basic_webgl.html]
 tags = offscreencanvas
 [test_offscreencanvas_dynamic_fallback.html]
 tags = offscreencanvas
 [test_offscreencanvas_sharedworker.html]
 tags = offscreencanvas
 [test_offscreencanvas_serviceworker.html]
 tags = offscreencanvas
--- a/dom/canvas/test/offscreencanvas.js
+++ b/dom/canvas/test/offscreencanvas.js
@@ -1,32 +1,54 @@
 /* WebWorker for test_offscreencanvas_*.html */
+(function(){
+
 var port = null;
 
+function isInWorker() {
+  try {
+    return !(self instanceof Window);
+  } catch (e) {
+    return true;
+  }
+}
+
+function postMessageGeneral(data) {
+  if (isInWorker()) {
+    if (port) {
+      port.postMessage(data);
+    } else {
+      postMessage(data);
+    }
+  } else {
+    postMessage(data, "*");
+  }
+}
+
 function ok(expect, msg) {
-  if (port) {
-    port.postMessage({type: "test", result: !!expect, name: msg});
-  } else {
-    postMessage({type: "test", result: !!expect, name: msg});
-  }
+  postMessageGeneral({type: "test", result: !!expect, name: msg});
 }
 
 function finish() {
-  if (port) {
-    port.postMessage({type: "finish"});
-  } else {
-    postMessage({type: "finish"});
-  }
+  postMessageGeneral({type: "finish"});
 }
 
 function drawCount(count) {
+  postMessageGeneral({type: "draw", count: count});
+}
+
+function sendBlob(blob) {
+  postMessageGeneral({type: "blob", blob: blob});
+}
+
+function sendImageBitmap(img) {
   if (port) {
-    port.postMessage({type: "draw", count: count});
+    port.postMessage({type: "imagebitmap", bitmap: img});
   } else {
-    postMessage({type: "draw", count: count});
+    postMessage({type: "imagebitmap", bitmap: img});
   }
 }
 
 //--------------------------------------------------------------------
 // WebGL Drawing Functions
 //--------------------------------------------------------------------
 function createDrawFunc(canvas) {
   var gl;
@@ -135,37 +157,42 @@ function createDrawFunc(canvas) {
 
   gl.useProgram(program);
   gl.enableVertexAttribArray(program.position);
   gl.vertexAttribPointer(program.position, 2, gl.FLOAT, false, 0, 0);
 
   // Start drawing
   checkGLError('after setup');
 
-  return function(prefix) {
+  return function(prefix, needCommitFrame) {
     if (prefix) {
       prefix = "[" + prefix + "] ";
     } else {
       prefix = "";
     }
 
     gl.viewport(0, 0, canvas.width, canvas.height);
 
     preDraw(prefix);
     gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
     postDraw(prefix);
-    gl.commit();
+    if (needCommitFrame) {
+      gl.commit();
+    }
     checkGLError(prefix);
   };
 }
 
 /* entry point */
 function entryFunction(testStr, subtests, offscreenCanvas) {
   var test = testStr;
   var canvas = offscreenCanvas;
+  if (test == "webgl_imagebitmap") {
+    canvas = new OffscreenCanvas(64, 64);
+  }
 
   if (test != "subworker") {
     ok(canvas, "Canvas successfully transfered to worker");
     ok(canvas.getContext, "Canvas has getContext");
 
     ok(canvas.width == 64, "OffscreenCanvas width should be 64");
     ok(canvas.height == 64, "OffscreenCanvas height should be 64");
   }
@@ -185,61 +212,88 @@ function entryFunction(testStr, subtests
     var count = 0;
     var iid = setInterval(function() {
       if (count++ > 20) {
         clearInterval(iid);
         ok(true, "Worker is done");
         finish();
         return;
       }
-      draw("loop " +count);
+      draw("loop " +count, true);
     }, 0);
   }
   //------------------------------------------------------------------------
   // Test dynamic fallback
   //------------------------------------------------------------------------
   else if (test == "webgl_fallback") {
     draw = createDrawFunc(canvas);
     if (!draw) {
       return;
     }
 
     var count = 0;
     var iid = setInterval(function() {
       ++count;
-      draw("loop " + count);
+      draw("loop " + count, true);
       drawCount(count);
     }, 0);
   }
   //------------------------------------------------------------------------
+  // Test toBlob
+  //------------------------------------------------------------------------
+  else if (test == "webgl_toblob") {
+    draw = createDrawFunc(canvas);
+    if (!draw) {
+      return;
+    }
+
+    draw("", false);
+    canvas.toBlob().then(function(blob) {
+      sendBlob(blob);
+    });
+  }
+  //------------------------------------------------------------------------
+  // Test toImageBitmap
+  //------------------------------------------------------------------------
+  else if (test == "webgl_imagebitmap") {
+    draw = createDrawFunc(canvas);
+    if (!draw) {
+      return;
+    }
+
+    draw("", false);
+    var imgBitmap = canvas.transferToImageBitmap();
+    sendImageBitmap(imgBitmap);
+  }
+  //------------------------------------------------------------------------
   // Canvas Size Change from Worker
   //------------------------------------------------------------------------
   else if (test == "webgl_changesize") {
     draw = createDrawFunc(canvas);
     if (!draw) {
       finish();
       return;
     }
 
-    draw("64x64");
+    draw("64x64", true);
 
     setTimeout(function() {
       canvas.width = 128;
       canvas.height = 128;
-      draw("Increased to 128x128");
+      draw("Increased to 128x128", true);
 
       setTimeout(function() {
         canvas.width = 32;
         canvas.width = 32;
-        draw("Decreased to 32x32");
+        draw("Decreased to 32x32", true);
 
         setTimeout(function() {
           canvas.width = 64;
           canvas.height = 64;
-          draw("Increased to 64x64");
+          draw("Increased to 64x64", true);
 
           ok(true, "Worker is done");
           finish();
         }, 0);
       }, 0);
     }, 0);
   }
   //------------------------------------------------------------------------
@@ -292,8 +346,14 @@ onconnect = function(evt) {
   port = evt.ports[0];
 
   port.addEventListener('message', function(evt) {
     entryFunction(evt.data.test, evt.data.subtests, evt.data.canvas);
   });
 
   port.start();
 };
+
+if (!isInWorker()) {
+  window.entryFunction = entryFunction;
+}
+
+})();
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/test_bitmaprenderer.html
@@ -0,0 +1,163 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>WebGL in OffscreenCanvas</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<script type="text/js-worker">
+function ok(expect, msg) {
+  postMessage({"type": "status", status: !!expect, msg: msg});
+}
+
+onmessage = function(event) {
+  var bitmap = event.data.bitmap;
+  ok(!!bitmap, "Get the ImageBitmap from the main script.");
+
+  var offscreenCanvas = new OffscreenCanvas(64, 64);
+  var ctx = offscreenCanvas.getContext('bitmaprenderer');
+  ok(!!ctx, "Get bitmaprenderer context on worker.");
+
+  ctx.transferImageBitmap(bitmap);
+  var resultBitmap = offscreenCanvas.transferToImageBitmap();
+  postMessage({"type": "bitmap", bitmap: resultBitmap}, [resultBitmap]);
+}
+</script>
+<script>
+
+SimpleTest.waitForExplicitFinish();
+
+function createCanvas(width, height) {
+  var htmlCanvas = document.createElement('canvas');
+  htmlCanvas.width = width;
+  htmlCanvas.height = height;
+  document.body.appendChild(htmlCanvas);
+  return htmlCanvas;
+}
+
+function runTest(canvasWidth, canvasHeight, nextTest) {
+  var canvas1 = createCanvas(canvasWidth, canvasHeight);
+  var ctx = canvas1.getContext("2d");
+  ctx.fillStyle = "#00FF00";
+  ctx.fillRect(0, 0, canvasWidth, canvasHeight);
+
+  var canvasRef = createCanvas(90, 90);
+  var ctx = canvasRef.getContext("2d");
+  // Clear with black transparent first
+  ctx.fillStyle = "rgba(0, 0, 0, 0)";
+  ctx.fillRect(0, 0, 90, 90);
+
+  ctx.fillStyle = "#00FF00";
+  ctx.fillRect(0, 0, canvasWidth, canvasHeight);
+
+  createImageBitmap(canvas1).then(function(bmp) {
+    document.body.removeChild(canvas1);
+
+    var canvas2 = createCanvas(90, 90);
+    var ctx2 = canvas2.getContext("bitmaprenderer");
+    ctx2.transferImageBitmap(bmp);
+
+    ok(canvasRef.toDataURL() == canvas2.toDataURL(), "toDataURL should return same result.");
+
+    // Exam render result
+    canvasRef.style.display = "none";
+    canvas2.style.display = "block";
+    var snapshot = snapshotWindow(window);
+
+    canvasRef.style.display = "block";
+    canvas2.style.display = "none";
+    var snapshotRef = snapshotWindow(window);
+
+    var results = compareSnapshots(snapshot, snapshotRef, true);
+    ok(results[0], "Screenshots should be the same");
+
+    document.body.removeChild(canvasRef);
+    document.body.removeChild(canvas2);
+
+    nextTest();
+  });
+}
+
+function scaleTest() {
+  var canvas1 = createCanvas(64, 64);
+  var ctx = canvas1.getContext("2d");
+  ctx.fillStyle = "#00FF00";
+  ctx.fillRect(0, 0, 64, 64);
+
+  var canvas2 = createCanvas(64, 64);
+  var ctx2 = canvas2.getContext("2d");
+  ctx2.fillStyle = "#00FF00";
+  ctx2.fillRect(0, 0, 64, 64);
+
+  var p1 = createImageBitmap(canvas1);
+  var p2 = createImageBitmap(canvas2);
+  Promise.all([p1, p2]).then(function(bitmaps) {
+    document.body.removeChild(canvas1);
+    document.body.removeChild(canvas2);
+
+    // Create a large canvas then shrink.
+    var canvas3 = createCanvas(128, 128);
+    var ctx3 = canvas3.getContext("bitmaprenderer");
+    ctx3.transferImageBitmap(bitmaps[0]);
+    var snapshotLargeRef = snapshotWindow(window);
+
+    canvas3.width = 32;
+    canvas3.height = 32;
+    var snapshotSmall = snapshotWindow(window);
+    document.body.removeChild(canvas3);
+
+    // Create a small canvas then grow.
+    var canvas4 = createCanvas(32, 32);
+    var ctx4 = canvas4.getContext("bitmaprenderer");
+    ctx4.transferImageBitmap(bitmaps[1]);
+    var snapshotSmallRef = snapshotWindow(window);
+
+    canvas4.width = 128;
+    canvas4.height = 128;
+    var snapshotLarge = snapshotWindow(window);
+    document.body.removeChild(canvas4);
+
+    var resultsLarge = compareSnapshots(snapshotLarge, snapshotLargeRef, true);
+    ok(resultsLarge[0], "Screenshots should be the same");
+
+    var resultsSmall = compareSnapshots(snapshotSmall, snapshotSmallRef, true);
+    ok(resultsSmall[0], "Screenshots should be the same");
+    runTestOnWorker();
+  });
+}
+
+function runTestOnWorker() {
+  var canvas1 = createCanvas(64, 64);
+  var ctx = canvas1.getContext("2d");
+  ctx.fillStyle = "#00FF00";
+  ctx.fillRect(0, 0, 64, 64);
+
+  var blob = new Blob(Array.prototype.map.call(document.querySelectorAll("script[type=\"text\/js-worker\"]"), function (oScript) { return oScript.textContent; }),{type: "text/javascript"});
+
+  var worker = new Worker(window.URL.createObjectURL(blob));
+
+  createImageBitmap(canvas1).then(function(bmp) {
+    worker.postMessage({bitmap: bmp}, [bmp]);
+    worker.onmessage = function(event) {
+      if (event.data.type == "status") {
+        ok(event.data.status, event.data.msg);
+      } else if (event.data.type == "bitmap") {
+        var canvas2 = createCanvas(64, 64);
+        var ctx2 = canvas2.getContext('bitmaprenderer');
+        ctx2.transferImageBitmap(event.data.bitmap);
+        ok(canvas1.toDataURL() == canvas2.toDataURL(), 'toDataURL should be the same');
+        SimpleTest.finish();
+      }
+    }
+  });
+}
+
+SpecialPowers.pushPrefEnv({'set': [
+  ['gfx.offscreencanvas.enabled', true],
+]}, runTest.bind(this, 64, 64, runTest.bind(this, 128, 128, scaleTest)));
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/test_imagebitmap_close.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>WebGL in OffscreenCanvas</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<script type="text/js-worker">
+function ok(expect, msg) {
+  postMessage({"type": "status", status: !!expect, msg: msg});
+}
+
+onmessage = function(event) {
+  var bitmap = event.data.bitmap;
+  ok(!!bitmap, "Get the ImageBitmap from the main script.");
+  bitmap.close();
+  ok(bitmap.width == 0 && bitmap.height == 0, "After close(), width and height should return 0");
+  postMessage({"type": "finish"});
+}
+</script>
+<script>
+
+SimpleTest.waitForExplicitFinish();
+
+function createCanvas() {
+  var htmlCanvas = document.createElement('canvas');
+  htmlCanvas.width = 64;
+  htmlCanvas.height = 64;
+  document.body.appendChild(htmlCanvas);
+  return htmlCanvas;
+}
+
+function runTest() {
+  var canvas1 = createCanvas();
+  var ctx = canvas1.getContext("2d");
+  ctx.fillStyle = "#00FF00";
+  ctx.fillRect(0, 0, 64, 64);
+
+  var canvasRef = createCanvas();
+  var ctx = canvasRef.getContext("2d");
+  ctx.fillStyle = "#00FF00";
+  ctx.fillRect(0, 0, 64, 64);
+
+  createImageBitmap(canvas1).then(function(bmp) {
+    var canvas2 = createCanvas();
+    var ctx2 = canvas2.getContext("2d");
+    ctx2.drawImage(bmp, 0, 0);
+
+    ok(canvasRef.toDataURL() == canvas2.toDataURL(), "toDataURL should return same result.");
+    document.body.removeChild(canvas2);
+
+    bmp.close();
+    ok(bmp.width == 0 && bmp.height == 0, "After close(), width and height should return 0");
+    var canvas2 = createCanvas();
+    var ctx2 = canvas2.getContext("2d");
+    var beforeDrawImageDataURL = canvas2.toDataURL();
+    ctx2.drawImage(bmp, 0, 0);
+    var afterDrawImageDataURL = canvas2.toDataURL();
+    ok(beforeDrawImageDataURL == afterDrawImageDataURL,
+       "Drawing operations with a closed ImageBitmap should do nothing.");
+    runTestOnWorker();
+  });
+}
+
+function runTestOnWorker() {
+  var canvas1 = createCanvas();
+  var ctx = canvas1.getContext("2d");
+  ctx.fillStyle = "#00FF00";
+  ctx.fillRect(0, 0, 64, 64);
+
+  var blob = new Blob(Array.prototype.map.call(document.querySelectorAll("script[type=\"text\/js-worker\"]"), function (oScript) { return oScript.textContent; }),{type: "text/javascript"});
+
+  var worker = new Worker(window.URL.createObjectURL(blob));
+
+  createImageBitmap(canvas1).then(function(bmp) {
+    worker.postMessage({bitmap: bmp}, [bmp]);
+    worker.onmessage = function(event) {
+      if (event.data.type == "status") {
+        ok(event.data.status, event.data.msg);
+      } else if (event.data.type == "finish") {
+        SimpleTest.finish();
+      }
+    }
+  });
+}
+
+runTest();
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/test_imagebitmap_transfer.html
@@ -0,0 +1,111 @@
+<!DOCTYPE HTML>
+<title>Test ImageBitmap : Transfer</title>
+<meta charset="utf-8">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<body>
+<script type="text/javascript">
+
+var gImage1;
+var gImage2;
+var gImageBitmap1;
+var gImageBitmap2;
+
+function isPixel(ctx1, ctx2, x, y) {
+  var pixel1 = ctx1.getImageData(x, y, 1, 1);
+  var pixel2 = ctx2.getImageData(x, y, 1, 1);
+  ok(pixel1.data[0] == pixel2.data[0] &&
+     pixel1.data[1] == pixel2.data[1] &&
+     pixel1.data[2] == pixel2.data[2] &&
+     pixel1.data[3] == pixel2.data[3],
+    "Color(" + pixel1.data[0] + ", " + pixel1.data[1] + ", " + pixel1.data[2] + ", " + pixel1.data[3] + ") should qual to Color(" + pixel2.data[0] + ", " + pixel2.data[1] + ", " + pixel2.data[2] + ", " + pixel2.data[3] +  ")");
+}
+
+function compareImageBitmapWithImageElement(imageBitmap, imageElement) {
+  var canvas1 = document.createElement('canvas');
+  var canvas2 = document.createElement('canvas');
+
+  canvas1.width  = imageElement.naturalWidth;
+  canvas1.height = imageElement.naturalHeight;
+  canvas2.width  = imageElement.naturalWidth;
+  canvas2.height = imageElement.naturalHeight;
+
+  var ctx1 = canvas1.getContext('2d');
+  var ctx2 = canvas2.getContext('2d');
+
+  ctx1.drawImage(imageElement, 0, 0);
+  ctx2.drawImage(imageBitmap, 0, 0);
+
+  document.body.appendChild(canvas1);
+  document.body.appendChild(canvas2);
+
+  for (var t = 0; t < 20; ++t) {
+    // check one random pixel
+    var randomX = Math.floor(Math.random() * imageElement.naturalWidth);
+    var randomY = Math.floor(Math.random() * imageElement.naturalHeight);
+    isPixel(ctx1, ctx2, randomX, randomY);
+  }
+}
+
+var worker = new Worker("imagebitmap_structuredclone.js");
+worker.onmessage = function(event) {
+
+  if (event.data.type == "status") {
+    ok(event.data.status, event.data.msg);
+  } else if (event.data.type == "finish") {
+    SimpleTest.finish();
+  } else if (event.data.type == "bitmap1") {
+    compareImageBitmapWithImageElement(event.data.bitmap, gImage1);
+  } else if (event.data.type == "bitmap2") {
+    compareImageBitmapWithImageElement(event.data.bitmap, gImage2);
+  }
+}
+
+function prepareTwoImageBitmap() {
+  gImage1 = document.createElement('img');
+  gImage2 = document.createElement('img');
+  gImage1.src = "image_rgrg-256x256.png";
+  gImage2.src = "image_yellow.png";
+
+  var p1 = new Promise(function(resolve, reject) {
+    gImage1.onload = function() {
+      var promise = createImageBitmap(gImage1);
+      promise.then(function(bitmap) {
+        gImageBitmap1 = bitmap;
+        resolve(true);
+      });
+    }
+  });
+
+  var p2 = new Promise(function(resolve, reject) {
+    gImage2.onload = function() {
+      var promise = createImageBitmap(gImage2);
+      promise.then(function(bitmap) {
+        gImageBitmap2 = bitmap;
+        resolve(true);
+      });
+    }
+  });
+
+  return Promise.all([p1, p2]);
+}
+
+function runTests() {
+  ok(worker, "Worker created successfully.");
+
+  prepareTwoImageBitmap().then(function(){
+    worker.postMessage({"bitmap1":gImageBitmap1, "bitmap2":gImageBitmap2},
+                       [gImageBitmap1, gImageBitmap2]);
+
+    ok(gImageBitmap1.width == 0 && gImageBitmap1.height == 0,
+       "After transfer, ImageBitmap become neutered");
+    ok(gImageBitmap2.width == 0 && gImageBitmap2.height == 0,
+       "After transfer, ImageBitmap become neutered");
+  });
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTests);
+
+</script>
+</body>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/test_offscreencanvas_toblob.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>WebGL in OffscreenCanvas</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="offscreencanvas.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<canvas id="c" width="64" height="64"></canvas>
+<canvas id="c-mt" width="64" height="64"></canvas>
+<canvas id="c-ref" width="64" height="64"></canvas>
+<script>
+
+SimpleTest.waitForExplicitFinish();
+
+function testBlob(blob, callback) {
+  // testing toBlob
+  // Fill c-ref with green color.
+  var c = document.getElementById("c-ref");
+  var ctx = c.getContext("2d");
+  ctx.rect(0, 0, 64, 64);
+  ctx.fillStyle = "#00FF00";
+  ctx.fill();
+  var reader = new FileReader();
+  reader.onload = function(e) {
+    ok(c.toDataURL() == e.target.result, "toBlob should return a 64x64 green square");
+    callback();
+  };
+  reader.readAsDataURL(blob);
+}
+
+function runTestOnMainThread() {
+  var htmlCanvas = document.getElementById("c-mt");
+  ok(htmlCanvas, "Should have HTML canvas element");
+
+  window.onmessage = function(evt) {
+    var msg = evt.data || {};
+    if (msg.type == "test") {
+      ok(msg.result, msg.name);
+    }
+    if (msg.type == "blob") {
+      testBlob(msg.blob, SimpleTest.finish);
+    }
+  }
+
+  ok(htmlCanvas.transferControlToOffscreen, "HTMLCanvasElement has transferControlToOffscreen function");
+
+  var offscreenCanvas = htmlCanvas.transferControlToOffscreen();
+  ok(offscreenCanvas, "Expected transferControlToOffscreen to succeed");
+
+  entryFunction('webgl_toblob', '', offscreenCanvas);
+}
+
+function runTest() {
+
+  var htmlCanvas = document.getElementById("c");
+  var worker = new Worker("offscreencanvas.js");
+
+  ok(htmlCanvas, "Should have HTML canvas element");
+  ok(worker, "Web worker successfully created");
+
+  worker.onmessage = function(evt) {
+    var msg = evt.data || {};
+    if (msg.type == "test") {
+      ok(msg.result, msg.name);
+    }
+    if (msg.type == "blob") {
+      testBlob(msg.blob, function() {
+        worker.terminate();
+        runTestOnMainThread();
+      });
+    }
+  }
+
+  ok(htmlCanvas.transferControlToOffscreen, "HTMLCanvasElement has transferControlToOffscreen function");
+
+  var offscreenCanvas = htmlCanvas.transferControlToOffscreen();
+  ok(offscreenCanvas, "Expected transferControlToOffscreen to succeed");
+
+  worker.postMessage({test: 'webgl_toblob', canvas: offscreenCanvas}, [offscreenCanvas]);
+}
+
+SpecialPowers.pushPrefEnv({'set': [
+  ['gfx.offscreencanvas.enabled', true],
+  ['webgl.force-enabled', true],
+]}, runTest);
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/test_offscreencanvas_toimagebitmap.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>WebGL in OffscreenCanvas</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<canvas id="c" width="64" height="64"></canvas>
+<canvas id="c2" width="64" height="64"></canvas>
+<canvas id="c-ref" width="64" height="64"></canvas>
+<script>
+
+SimpleTest.waitForExplicitFinish();
+
+function runTest() {
+
+  var worker = new Worker("offscreencanvas.js");
+
+  ok(worker, "Web worker successfully created");
+
+  worker.onmessage = function(evt) {
+    var msg = evt.data || {};
+    if (msg.type == "test") {
+      ok(msg.result, msg.name);
+    }
+    if (msg.type == "imagebitmap") {
+      // testing toBlob
+      // Fill c-ref with green color.
+      var c = document.getElementById("c-ref");
+      var ctx = c.getContext("2d");
+      ctx.rect(0, 0, 64, 64);
+      ctx.fillStyle = "#00FF00";
+      ctx.fill();
+
+      var htmlCanvas = document.getElementById("c");
+      var bitmapRenderer = htmlCanvas.getContext("bitmaprenderer");
+      bitmapRenderer.transferImageBitmap(msg.bitmap);
+
+      ok(c.toDataURL() == htmlCanvas.toDataURL(),
+         "imagebitmap should return a 64x64 green square");
+
+      // The ownership of msg.bitmap should be transferred to canvas "c" when
+      // we called transferImageBitmap. So we test if the ownership is actually
+      // transferred here.
+      var htmlCanvas = document.getElementById("c2");
+      var bitmapRenderer = htmlCanvas.getContext("bitmaprenderer");
+      bitmapRenderer.transferImageBitmap(msg.bitmap);
+
+      SimpleTest.doesThrow(
+        function() { c2.toDataURL(); },
+        "ImageBitmap has been transferred, toDataURL will throw.");
+
+      worker.terminate();
+      SimpleTest.finish();
+    }
+  }
+
+  worker.postMessage({test: 'webgl_imagebitmap'});
+}
+
+SpecialPowers.pushPrefEnv({'set': [
+  ['gfx.offscreencanvas.enabled', true],
+  ['webgl.force-enabled', true],
+]}, runTest);
+
+</script>
+</body>
+</html>
--- a/dom/canvas/test/webgl-mochitest.ini
+++ b/dom/canvas/test/webgl-mochitest.ini
@@ -8,17 +8,16 @@ support-files =
 
 [webgl-mochitest/test_backbuffer_channels.html]
 fail-if = (os == 'b2g')
 [webgl-mochitest/test_depth_readpixels.html]
 [webgl-mochitest/test_capture.html]
 support-files = captureStream_common.js
 [webgl-mochitest/test_cubemap_must_be_square.html]
 [webgl-mochitest/test_depth_tex_lazy_clear.html]
-skip-if = 1
 [webgl-mochitest/test_draw.html]
 [webgl-mochitest/test_fb_param.html]
 [webgl-mochitest/test_fb_param_crash.html]
 [webgl-mochitest/test_hidden_alpha.html]
 skip-if = (os == 'b2g') || buildapp == 'mulet' # Mulet - bug 1093639 (crashes in libLLVM-3.0.so)
 [webgl-mochitest/test_implicit_color_buffer_float.html]
 [webgl-mochitest/test_highp_fs.html]
 [webgl-mochitest/test_no_arr_points.html]
--- a/dom/html/HTMLCanvasElement.cpp
+++ b/dom/html/HTMLCanvasElement.cpp
@@ -369,17 +369,18 @@ HTMLCanvasElement::~HTMLCanvasElement()
 
   if (mAsyncCanvasRenderer) {
     mAsyncCanvasRenderer->mHTMLCanvasElement = nullptr;
   }
 }
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLCanvasElement, nsGenericHTMLElement,
                                    mCurrentContext, mPrintCallback,
-                                   mPrintState, mOriginalCanvas)
+                                   mPrintState, mOriginalCanvas,
+                                   mOffscreenCanvas)
 
 NS_IMPL_ADDREF_INHERITED(HTMLCanvasElement, Element)
 NS_IMPL_RELEASE_INHERITED(HTMLCanvasElement, Element)
 
 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLCanvasElement)
   NS_INTERFACE_TABLE_INHERITED(HTMLCanvasElement, nsIDOMHTMLCanvasElement)
 NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLElement)
 
@@ -774,20 +775,27 @@ HTMLCanvasElement::TransferControlToOffs
   }
 
   if (!mOffscreenCanvas) {
     nsIntSize sz = GetWidthHeight();
     RefPtr<AsyncCanvasRenderer> renderer = GetAsyncCanvasRenderer();
     renderer->SetWidth(sz.width);
     renderer->SetHeight(sz.height);
 
-    mOffscreenCanvas = new OffscreenCanvas(sz.width,
+    nsCOMPtr<nsIGlobalObject> global =
+      do_QueryInterface(OwnerDoc()->GetInnerWindow());
+    mOffscreenCanvas = new OffscreenCanvas(global,
+                                           sz.width,
                                            sz.height,
                                            GetCompositorBackendType(),
                                            renderer);
+    if (mWriteOnly) {
+      mOffscreenCanvas->SetWriteOnly();
+    }
+
     if (!mContextObserver) {
       mContextObserver = new HTMLCanvasElementObserver(this);
     }
   } else {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   }
 
   return mOffscreenCanvas;
@@ -1033,35 +1041,35 @@ HTMLCanvasElement::GetIsOpaque()
 }
 
 bool
 HTMLCanvasElement::GetOpaqueAttr()
 {
   return HasAttr(kNameSpaceID_None, nsGkAtoms::moz_opaque);
 }
 
-already_AddRefed<CanvasLayer>
+already_AddRefed<Layer>
 HTMLCanvasElement::GetCanvasLayer(nsDisplayListBuilder* aBuilder,
-                                  CanvasLayer *aOldLayer,
+                                  Layer *aOldLayer,
                                   LayerManager *aManager)
 {
   // The address of sOffscreenCanvasLayerUserDataDummy is used as the user
   // data key for retained LayerManagers managed by FrameLayerBuilder.
   // We don't much care about what value in it, so just assign a dummy
   // value for it.
   static uint8_t sOffscreenCanvasLayerUserDataDummy = 0;
 
   if (mCurrentContext) {
     return mCurrentContext->GetCanvasLayer(aBuilder, aOldLayer, aManager);
   }
 
   if (mOffscreenCanvas) {
     if (!mResetLayer &&
         aOldLayer && aOldLayer->HasUserData(&sOffscreenCanvasLayerUserDataDummy)) {
-      RefPtr<CanvasLayer> ret = aOldLayer;
+      RefPtr<Layer> ret = aOldLayer;
       return ret.forget();
     }
 
     RefPtr<CanvasLayer> layer = aManager->CreateCanvasLayer();
     if (!layer) {
       NS_WARNING("CreateCanvasLayer failed!");
       return nullptr;
     }
@@ -1168,17 +1176,17 @@ HTMLCanvasElement::IsFrameCaptureRequest
   }
   return false;
 }
 
 void
 HTMLCanvasElement::SetFrameCapture(already_AddRefed<SourceSurface> aSurface)
 {
   RefPtr<SourceSurface> surface = aSurface;
-  RefPtr<CairoImage> image = new CairoImage(surface->GetSize(), surface);
+  RefPtr<SourceSurfaceImage> image = new SourceSurfaceImage(surface->GetSize(), surface);
 
   // Loop backwards to allow removing elements in the loop.
   for (int i = mRequestedFrameListeners.Length() - 1; i >= 0; --i) {
     WeakPtr<FrameCaptureListener> listener = mRequestedFrameListeners[i];
     if (!listener) {
       // listener was destroyed. Remove it from the list.
       mRequestedFrameListeners.RemoveElementAt(i);
       continue;
--- a/dom/html/HTMLCanvasElement.h
+++ b/dom/html/HTMLCanvasElement.h
@@ -26,16 +26,17 @@ class nsITimerCallback;
 namespace mozilla {
 
 class WebGLContext;
 
 namespace layers {
 class AsyncCanvasRenderer;
 class CanvasLayer;
 class Image;
+class Layer;
 class LayerManager;
 } // namespace layers
 namespace gfx {
 class SourceSurface;
 } // namespace gfx
 
 namespace dom {
 class CanvasCaptureMediaStream;
@@ -115,16 +116,17 @@ class HTMLCanvasElement final : public n
 {
   enum {
     DEFAULT_CANVAS_WIDTH = 300,
     DEFAULT_CANVAS_HEIGHT = 150
   };
 
   typedef layers::AsyncCanvasRenderer AsyncCanvasRenderer;
   typedef layers::CanvasLayer CanvasLayer;
+  typedef layers::Layer Layer;
   typedef layers::LayerManager LayerManager;
 
 public:
   explicit HTMLCanvasElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
 
   NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLCanvasElement, canvas)
 
   // nsISupports
@@ -303,19 +305,19 @@ public:
   nsresult CopyInnerTo(mozilla::dom::Element* aDest);
 
   virtual nsresult PreHandleEvent(mozilla::EventChainPreVisitor& aVisitor) override;
 
   /*
    * Helpers called by various users of Canvas
    */
 
-  already_AddRefed<CanvasLayer> GetCanvasLayer(nsDisplayListBuilder* aBuilder,
-                                               CanvasLayer *aOldLayer,
-                                               LayerManager *aManager);
+  already_AddRefed<Layer> GetCanvasLayer(nsDisplayListBuilder* aBuilder,
+                                         Layer *aOldLayer,
+                                         LayerManager *aManager);
   // Should return true if the canvas layer should always be marked inactive.
   // We should return true here if we can't do accelerated compositing with
   // a non-BasicCanvasLayer.
   bool ShouldForceInactiveLayer(LayerManager *aManager);
 
   // Call this whenever we need future changes to the canvas
   // to trigger fresh invalidation requests. This needs to be called
   // whenever we render the canvas contents to the screen, or whenever we
--- a/dom/html/HTMLImageElement.cpp
+++ b/dom/html/HTMLImageElement.cpp
@@ -991,17 +991,17 @@ HTMLImageElement::PictureSourceSrcsetCha
     mResponsiveSelector ? mResponsiveSelector->Content() : nullptr;
 
   if (aSourceNode == currentSrc) {
     // We're currently using this node as our responsive selector
     // source.
     mResponsiveSelector->SetCandidatesFromSourceSet(aNewValue);
   }
 
-  if (!mInDocResponsiveContent) {
+  if (!mInDocResponsiveContent && IsInComposedDoc()) {
     nsIDocument* doc = GetOurOwnerDoc();
     if (doc) {
       doc->AddResponsiveContent(this);
       mInDocResponsiveContent = true;
     }
   }
 
   // This always triggers the image update steps per the spec, even if
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -290,30 +290,35 @@ HTMLInputElement::nsFilePickerShownCallb
 {
 }
 
 NS_IMPL_ISUPPORTS(UploadLastDir::ContentPrefCallback, nsIContentPrefCallback2)
 
 NS_IMETHODIMP
 UploadLastDir::ContentPrefCallback::HandleCompletion(uint16_t aReason)
 {
-  nsCOMPtr<nsIFile> localFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
-  NS_ENSURE_STATE(localFile);
-
-  if (aReason == nsIContentPrefCallback2::COMPLETE_ERROR ||
-      !mResult) {
-    // Default to "desktop" directory for each platform
-    nsCOMPtr<nsIFile> homeDir;
-    NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(homeDir));
-    localFile = do_QueryInterface(homeDir);
-  } else {
-    nsAutoString prefStr;
-    nsCOMPtr<nsIVariant> pref;
-    mResult->GetValue(getter_AddRefs(pref));
-    pref->GetAsAString(prefStr);
+  nsCOMPtr<nsIFile> localFile;
+  nsAutoString prefStr;
+
+  if (aReason == nsIContentPrefCallback2::COMPLETE_ERROR || !mResult) {
+    prefStr = Preferences::GetString("dom.input.fallbackUploadDir");
+    if (prefStr.IsEmpty()) {
+      // If no custom directory was set through the pref, default to
+      // "desktop" directory for each platform.
+      NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(localFile));
+    }
+  }
+
+  if (!localFile) {
+    if (prefStr.IsEmpty() && mResult) {
+      nsCOMPtr<nsIVariant> pref;
+      mResult->GetValue(getter_AddRefs(pref));
+      pref->GetAsAString(prefStr);
+    }
+    localFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
     localFile->InitWithPath(prefStr);
   }
 
   mFilePicker->SetDisplayDirectory(localFile);
   mFilePicker->Open(mFpCallback);
   return NS_OK;
 }
 
--- a/dom/html/test/mochitest.ini
+++ b/dom/html/test/mochitest.ini
@@ -595,8 +595,10 @@ support-files = file_bug871161-1.html fi
 [test_window_open_close.html]
 skip-if = buildapp == 'b2g' # bug 1129014
 [test_img_complete.html]
 [test_viewport_resize.html]
 [test_extapp.html]
 [test_image_clone_load.html]
 [test_bug1203668.html]
 [test_bug1166138.html]
+[test_filepicker_default_directory.html]
+skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android'
new file mode 100644
--- /dev/null
+++ b/dom/html/test/test_filepicker_default_directory.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1194893
+-->
+<head>
+  <title>Test for filepicker default directory</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1194893">Mozilla Bug 1194893</a>
+<div id="content">
+  <input type="file" id="f">
+</div>
+<pre id="text">
+<script class="testbody" type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+const { Cc: Cc, Ci: Ci } = SpecialPowers;
+
+// Platform-independent directory names are #define'd in xpcom/io/nsDirectoryServiceDefs.h
+var defaultUploadDirectory = Cc["@mozilla.org/file/directory_service;1"]
+                            .getService(Ci.nsIDirectoryService)
+                            .QueryInterface(Ci.nsIProperties)
+                            .get("Desk", Ci.nsIFile);
+
+// When we want to test an upload directory other than the default, we need to
+// get a valid directory in a platform-independent way. Since NS_OS_DESKTOP_DIR
+// may fallback to NS_OS_HOME_DIR, let's use NS_OS_TMP_DIR.
+var customUploadDirectory = Cc["@mozilla.org/file/directory_service;1"]
+                            .getService(Ci.nsIDirectoryService)
+                            .QueryInterface(Ci.nsIProperties)
+                            .get("TmpD", Ci.nsIFile);
+
+// Useful for debugging
+//info("defaultUploadDirectory" + defaultUploadDirectory.path);
+//info("customUploadDirectory" + customUploadDirectory.path);
+
+var MockFilePicker = SpecialPowers.MockFilePicker;
+MockFilePicker.init(window);
+
+// need to show the MockFilePicker so .displayDirectory gets set
+var f = document.getElementById("f");
+f.focus();
+
+var testIndex = 0;
+var tests = [
+  ["", defaultUploadDirectory.path],
+  [customUploadDirectory.path, customUploadDirectory.path]
+]
+
+MockFilePicker.showCallback = function(filepicker) {
+  info(SpecialPowers.wrap(MockFilePicker).displayDirectory.path);
+
+  is(SpecialPowers.wrap(MockFilePicker).displayDirectory.path,
+  tests[testIndex][1]);
+
+  if (++testIndex == tests.length) {
+    MockFilePicker.cleanup();
+    SimpleTest.finish();
+  } else {
+    launchNextTest();
+  }
+}
+
+function launchNextTest() {
+  SpecialPowers.pushPrefEnv(
+    { 'set': [
+      ['dom.input.fallbackUploadDir', tests[testIndex][0]],
+    ]},
+  function () {
+    f.click();
+  });
+}
+
+launchNextTest();
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/ipc/StructuredCloneData.cpp
+++ b/dom/ipc/StructuredCloneData.cpp
@@ -39,17 +39,17 @@ StructuredCloneData::Copy(const Structur
       SharedJSAllocatedData::CreateFromExternalData(aData.Data(),
                                                     aData.DataLength());
     NS_ENSURE_TRUE(mSharedData, false);
   }
 
   MOZ_ASSERT(BlobImpls().IsEmpty());
   BlobImpls().AppendElements(aData.BlobImpls());
 
-  MOZ_ASSERT(GetImages().IsEmpty());
+  MOZ_ASSERT(GetSurfaces().IsEmpty());
 
   return true;
 }
 
 void
 StructuredCloneData::Read(JSContext* aCx,
                           JS::MutableHandle<JS::Value> aValue,
                           ErrorResult &aRv)
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -785,16 +785,17 @@ MediaDecoder::Play()
 nsresult
 MediaDecoder::Seek(double aTime, SeekTarget::Type aSeekType)
 {
   MOZ_ASSERT(NS_IsMainThread());
   NS_ENSURE_TRUE(!mShuttingDown, NS_ERROR_FAILURE);
 
   UpdateDormantState(false /* aDormantTimeout */, true /* aActivity */);
 
+  MOZ_ASSERT(!mIsDormant, "should be out of dormant by now");
   MOZ_ASSERT(aTime >= 0.0, "Cannot seek to a negative value.");
 
   int64_t timeUsecs = 0;
   nsresult rv = SecondsToUsecs(aTime, timeUsecs);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mLogicalPosition = aTime;
   mWasEndedWhenEnteredDormant = false;
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -777,16 +777,27 @@ MediaDecoderStateMachine::OnNotDecoded(M
              [self] (MediaData::Type aType) -> void {
                self->WaitRequestRef(aType).Complete();
                self->DispatchDecodeTasksIfNeeded();
              },
              [self] (WaitForDataRejectValue aRejection) -> void {
                self->WaitRequestRef(aRejection.mType).Complete();
              }));
 
+    // We are out of data to decode and will enter buffering mode soon.
+    // We want to play the frames we have already decoded, so we stop pre-rolling
+    // and ensure that loadeddata is fired as required.
+    if (isAudio) {
+      StopPrerollingAudio();
+    } else {
+      StopPrerollingVideo();
+    }
+    if (mState == DECODER_STATE_BUFFERING || mState == DECODER_STATE_DECODING) {
+        MaybeFinishDecodeFirstFrame();
+    }
     return;
   }
 
   if (aReason == MediaDecoderReader::CANCELED) {
     DispatchDecodeTasksIfNeeded();
     return;
   }
 
@@ -1476,17 +1487,17 @@ MediaDecoderStateMachine::Seek(SeekTarge
 
   // We need to be able to seek both at a transport level and at a media level
   // to seek.
   if (!mMediaSeekable) {
     DECODER_WARN("Seek() function should not be called on a non-seekable state machine");
     return MediaDecoder::SeekPromise::CreateAndReject(/* aIgnored = */ true, __func__);
   }
 
-  NS_ASSERTION(mState > DECODER_STATE_DECODING_METADATA,
+  MOZ_ASSERT(mState > DECODER_STATE_DECODING_METADATA,
                "We should have got duration already");
 
   if (mState < DECODER_STATE_DECODING ||
       (IsDecodingFirstFrame() && !mReader->ForceZeroStartTime())) {
     DECODER_LOG("Seek() Not Enough Data to continue at this stage, queuing seek");
     mQueuedSeek.RejectIfExists(__func__);
     mQueuedSeek.mTarget = aTarget;
     return mQueuedSeek.mPromise.Ensure(__func__);
--- a/dom/media/MediaEventSource.h
+++ b/dom/media/MediaEventSource.h
@@ -4,16 +4,17 @@
  * 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 MediaEventSource_h_
 #define MediaEventSource_h_
 
 #include "mozilla/AbstractThread.h"
 #include "mozilla/Atomics.h"
+#include "mozilla/IndexSequence.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/Tuple.h"
 #include "mozilla/TypeTraits.h"
 #include "mozilla/UniquePtr.h"
 
 #include "nsISupportsImpl.h"
 #include "nsTArray.h"
 #include "nsThreadUtils.h"
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -541,16 +541,19 @@ MediaFormatReader::OnDemuxFailed(TrackTy
   switch (aFailure) {
     case DemuxerFailureReason::END_OF_STREAM:
       NotifyEndOfStream(aTrack);
       break;
     case DemuxerFailureReason::DEMUXER_ERROR:
       NotifyError(aTrack);
       break;
     case DemuxerFailureReason::WAITING_FOR_DATA:
+      if (!decoder.mWaitingForData) {
+        decoder.mNeedDraining = true;
+      }
       NotifyWaitingForData(aTrack);
       break;
     case DemuxerFailureReason::CANCELED:
     case DemuxerFailureReason::SHUTDOWN:
       if (decoder.HasPromise()) {
         decoder.RejectPromise(CANCELED, __func__);
       }
       break;
@@ -686,16 +689,19 @@ MediaFormatReader::NotifyError(TrackType
 }
 
 void
 MediaFormatReader::NotifyWaitingForData(TrackType aTrack)
 {
   MOZ_ASSERT(OnTaskQueue());
   auto& decoder = GetDecoderData(aTrack);
   decoder.mWaitingForData = true;
+  if (decoder.mTimeThreshold) {
+    decoder.mTimeThreshold.ref().mWaiting = true;
+  }
   ScheduleUpdate(aTrack);
 }
 
 void
 MediaFormatReader::NotifyEndOfStream(TrackType aTrack)
 {
   MOZ_ASSERT(OnTaskQueue());
   auto& decoder = GetDecoderData(aTrack);
@@ -755,28 +761,42 @@ bool
 MediaFormatReader::UpdateReceivedNewData(TrackType aTrack)
 {
   MOZ_ASSERT(OnTaskQueue());
   auto& decoder = GetDecoderData(aTrack);
 
   if (!decoder.mReceivedNewData) {
     return false;
   }
-  decoder.mReceivedNewData = false;
-  decoder.mWaitingForData = false;
-  bool hasLastEnd;
-  media::TimeUnit lastEnd = decoder.mTimeRanges.GetEnd(&hasLastEnd);
+
   // Update our cached TimeRange.
   decoder.mTimeRanges = decoder.mTrackDemuxer->GetBuffered();
-  if (decoder.mTimeRanges.Length() &&
-      (!hasLastEnd || decoder.mTimeRanges.GetEnd() > lastEnd)) {
-    // New data was added after our previous end, we can clear the EOS flag.
-    decoder.mDemuxEOS = false;
+
+  if (decoder.mDrainComplete || decoder.mDraining) {
+    // We do not want to clear mWaitingForData or mDemuxEOS while
+    // a drain is in progress in order to properly complete the operation.
+    return false;
   }
 
+  bool hasLastEnd;
+  media::TimeUnit lastEnd = decoder.mTimeRanges.GetEnd(&hasLastEnd);
+  if (hasLastEnd) {
+    if (decoder.mLastTimeRangesEnd && decoder.mLastTimeRangesEnd.ref() > lastEnd) {
+      // New data was added after our previous end, we can clear the EOS flag.
+      decoder.mDemuxEOS = false;
+    }
+    decoder.mLastTimeRangesEnd = Some(lastEnd);
+  }
+
+  decoder.mReceivedNewData = false;
+  if (decoder.mTimeThreshold) {
+    decoder.mTimeThreshold.ref().mWaiting = false;
+  }
+  decoder.mWaitingForData = false;
+
   if (decoder.mError) {
     return false;
   }
   if (decoder.HasWaitingPromise()) {
     MOZ_ASSERT(!decoder.HasPromise());
     LOG("We have new data. Resolving WaitingPromise");
     decoder.mWaitingPromise.Resolve(decoder.mType, __func__);
     return true;
@@ -803,16 +823,18 @@ MediaFormatReader::RequestDemuxSamples(T
 
   if (!decoder.mQueuedSamples.IsEmpty()) {
     // No need to demux new samples.
     return;
   }
 
   if (decoder.mDemuxEOS) {
     // Nothing left to demux.
+    // We do not want to attempt to demux while in waiting for data mode
+    // as it would retrigger an unecessary drain.
     return;
   }
 
   LOGV("Requesting extra demux %s", TrackTypeToStr(aTrack));
   if (aTrack == TrackInfo::kVideoTrack) {
     DoDemuxVideo();
   } else {
     DoDemuxAudio();
@@ -878,57 +900,31 @@ MediaFormatReader::HandleDemuxedSamples(
         return;
       }
 
       LOG("%s stream id has changed from:%d to:%d, recreating decoder.",
           TrackTypeToStr(aTrack), decoder.mLastStreamSourceID,
           info->GetID());
       decoder.mInfo = info;
       decoder.mLastStreamSourceID = info->GetID();
+      decoder.mNextStreamSourceID.reset();
       // Flush will clear our array of queued samples. So make a copy now.
       nsTArray<RefPtr<MediaRawData>> samples{decoder.mQueuedSamples};
       Flush(aTrack);
       decoder.mDecoder->Shutdown();
       decoder.mDecoder = nullptr;
       if (sample->mKeyframe) {
         decoder.mQueuedSamples.AppendElements(Move(samples));
         NotifyDecodingRequested(aTrack);
       } else {
-        MOZ_ASSERT(decoder.mTimeThreshold.isNothing());
+        SeekTarget seekTarget =
+          decoder.mTimeThreshold.refOr(SeekTarget(TimeUnit::FromMicroseconds(sample->mTime), false));
         LOG("Stream change occurred on a non-keyframe. Seeking to:%lld",
-            sample->mTime);
-        decoder.mTimeThreshold = Some(TimeUnit::FromMicroseconds(sample->mTime));
-        RefPtr<MediaFormatReader> self = this;
-        decoder.ResetDemuxer();
-        decoder.mSeekRequest.Begin(decoder.mTrackDemuxer->Seek(decoder.mTimeThreshold.ref())
-                   ->Then(OwnerThread(), __func__,
-                          [self, aTrack] (media::TimeUnit aTime) {
-                            auto& decoder = self->GetDecoderData(aTrack);
-                            decoder.mSeekRequest.Complete();
-                            self->NotifyDecodingRequested(aTrack);
-                          },
-                          [self, aTrack] (DemuxerFailureReason aResult) {
-                            auto& decoder = self->GetDecoderData(aTrack);
-                            decoder.mSeekRequest.Complete();
-                            switch (aResult) {
-                              case DemuxerFailureReason::WAITING_FOR_DATA:
-                                self->NotifyWaitingForData(aTrack);
-                                break;
-                              case DemuxerFailureReason::END_OF_STREAM:
-                                self->NotifyEndOfStream(aTrack);
-                                break;
-                              case DemuxerFailureReason::CANCELED:
-                              case DemuxerFailureReason::SHUTDOWN:
-                                break;
-                              default:
-                                self->NotifyError(aTrack);
-                                break;
-                            }
-                            decoder.mTimeThreshold.reset();
-                          }));
+            seekTarget.mTime.ToMicroseconds());
+        InternalSeek(aTrack, seekTarget);
       }
       return;
     }
 
     LOGV("Input:%lld (dts:%lld kf:%d)",
          sample->mTime, sample->mTimecode, sample->mKeyframe);
     decoder.mOutputRequested = true;
     decoder.mNumSamplesInput++;
@@ -953,16 +949,52 @@ MediaFormatReader::HandleDemuxedSamples(
     samplesPending = true;
   }
 
   // We have serviced the decoder's request for more data.
   decoder.mInputExhausted = false;
 }
 
 void
+MediaFormatReader::InternalSeek(TrackType aTrack, const SeekTarget& aTarget)
+{
+  MOZ_ASSERT(OnTaskQueue());
+  auto& decoder = GetDecoderData(aTrack);
+  decoder.mTimeThreshold = Some(aTarget);
+  RefPtr<MediaFormatReader> self = this;
+  decoder.ResetDemuxer();
+  decoder.mSeekRequest.Begin(decoder.mTrackDemuxer->Seek(decoder.mTimeThreshold.ref().mTime)
+             ->Then(OwnerThread(), __func__,
+                    [self, aTrack] (media::TimeUnit aTime) {
+                      auto& decoder = self->GetDecoderData(aTrack);
+                      decoder.mSeekRequest.Complete();
+                      self->NotifyDecodingRequested(aTrack);
+                    },
+                    [self, aTrack] (DemuxerFailureReason aResult) {
+                      auto& decoder = self->GetDecoderData(aTrack);
+                      decoder.mSeekRequest.Complete();
+                      switch (aResult) {
+                        case DemuxerFailureReason::WAITING_FOR_DATA:
+                          self->NotifyWaitingForData(aTrack);
+                          break;
+                        case DemuxerFailureReason::END_OF_STREAM:
+                          self->NotifyEndOfStream(aTrack);
+                          break;
+                        case DemuxerFailureReason::CANCELED:
+                        case DemuxerFailureReason::SHUTDOWN:
+                          break;
+                        default:
+                          self->NotifyError(aTrack);
+                          break;
+                      }
+                      decoder.mTimeThreshold.reset();
+                    }));
+}
+
+void
 MediaFormatReader::DrainDecoder(TrackType aTrack)
 {
   MOZ_ASSERT(OnTaskQueue());
 
   auto& decoder = GetDecoderData(aTrack);
   if (!decoder.mNeedDraining || decoder.mDraining) {
     return;
   }
@@ -987,111 +1019,131 @@ MediaFormatReader::Update(TrackType aTra
   MOZ_ASSERT(OnTaskQueue());
 
   if (mShutdown) {
     return;
   }
 
   LOGV("Processing update for %s", TrackTypeToStr(aTrack));
 
-  bool needInput = false;
   bool needOutput = false;
   auto& decoder = GetDecoderData(aTrack);
   decoder.mUpdateScheduled = false;
 
   if (!mInitDone) {
     return;
   }
 
   if (UpdateReceivedNewData(aTrack)) {
     LOGV("Nothing more to do");
     return;
   }
 
-  if (!decoder.HasPromise() && decoder.mWaitingForData) {
-    // Nothing more we can do at present.
-    LOGV("Still waiting for data.");
-    return;
-  }
-
   // Record number of frames decoded and parsed. Automatically update the
   // stats counters using the AutoNotifyDecoded stack-based class.
   AbstractMediaDecoder::AutoNotifyDecoded a(mDecoder);
 
-  if (aTrack == TrackInfo::kVideoTrack) {
-    uint64_t delta =
-      decoder.mNumSamplesOutputTotal - mLastReportedNumDecodedFrames;
-    a.mDecoded = static_cast<uint32_t>(delta);
-    mLastReportedNumDecodedFrames = decoder.mNumSamplesOutputTotal;
+  // Drop any frames found prior our internal seek target.
+  while (decoder.mTimeThreshold && decoder.mOutput.Length()) {
+    RefPtr<MediaData>& output = decoder.mOutput[0];
+    SeekTarget target = decoder.mTimeThreshold.ref();
+    media::TimeUnit time = media::TimeUnit::FromMicroseconds(output->mTime);
+    if (time >= target.mTime) {
+      // We have reached our internal seek target.
+      decoder.mTimeThreshold.reset();
+    }
+    if (time < target.mTime || target.mDropTarget) {
+      LOGV("Internal Seeking: Dropping %s frame time:%f wanted:%f (kf:%d)",
+           TrackTypeToStr(aTrack),
+           media::TimeUnit::FromMicroseconds(output->mTime).ToSeconds(),
+           target.mTime.ToSeconds(),
+           output->mKeyframe);
+      decoder.mOutput.RemoveElementAt(0);
+    }
   }
 
   if (decoder.HasPromise()) {
     needOutput = true;
-    if (!decoder.mOutput.IsEmpty()) {
+    if (decoder.mOutput.Length()) {
       // We have a decoded sample ready to be returned.
       if (aTrack == TrackType::kVideoTrack) {
+        uint64_t delta =
+          decoder.mNumSamplesOutputTotal - mLastReportedNumDecodedFrames;
+        a.mDecoded = static_cast<uint32_t>(delta);
+        mLastReportedNumDecodedFrames = decoder.mNumSamplesOutputTotal;
         nsCString error;
         mVideo.mIsHardwareAccelerated =
           mVideo.mDecoder && mVideo.mDecoder->IsHardwareAccelerated(error);
       }
-      while (decoder.mOutput.Length()) {
-        RefPtr<MediaData> output = decoder.mOutput[0];
-        decoder.mOutput.RemoveElementAt(0);
-        decoder.mSizeOfQueue -= 1;
-        if (decoder.mTimeThreshold.isNothing() ||
-            media::TimeUnit::FromMicroseconds(output->mTime) >= decoder.mTimeThreshold.ref()) {
-          ReturnOutput(output, aTrack);
-          decoder.mTimeThreshold.reset();
-          break;
-        } else {
-          LOGV("Internal Seeking: Dropping frame time:%f wanted:%f (kf:%d)",
-               media::TimeUnit::FromMicroseconds(output->mTime).ToSeconds(),
-               decoder.mTimeThreshold.ref().ToSeconds(),
-               output->mKeyframe);
-        }
-      }
+      RefPtr<MediaData> output = decoder.mOutput[0];
+      decoder.mOutput.RemoveElementAt(0);
+      decoder.mSizeOfQueue -= 1;
+      decoder.mLastSampleTime =
+        Some(media::TimeUnit::FromMicroseconds(output->mTime));
+      ReturnOutput(output, aTrack);
+    } else if (decoder.mError) {
+      LOG("Rejecting %s promise: DECODE_ERROR", TrackTypeToStr(aTrack));
+      decoder.RejectPromise(DECODE_ERROR, __func__);
+      return;
     } else if (decoder.mDrainComplete) {
+      bool wasDraining = decoder.mDraining;
       decoder.mDrainComplete = false;
       decoder.mDraining = false;
-      if (decoder.mError) {
-        LOG("Decoding Error");
-        decoder.RejectPromise(DECODE_ERROR, __func__);
-        return;
-      } else if (decoder.mDemuxEOS) {
+      if (decoder.mDemuxEOS) {
+        LOG("Rejecting %s promise: EOS", TrackTypeToStr(aTrack));
         decoder.RejectPromise(END_OF_STREAM, __func__);
+      } else if (decoder.mWaitingForData) {
+        if (wasDraining && decoder.mLastSampleTime &&
+            !decoder.mNextStreamSourceID) {
+          // We have completed draining the decoder following WaitingForData.
+          // Set up the internal seek machinery to be able to resume from the
+          // last sample decoded.
+          LOG("Seeking to last sample time: %lld",
+              decoder.mLastSampleTime.ref().ToMicroseconds());
+          InternalSeek(aTrack, SeekTarget(decoder.mLastSampleTime.ref(), true));
+        }
+        LOG("Rejecting %s promise: WAITING_FOR_DATA", TrackTypeToStr(aTrack));
+        decoder.RejectPromise(WAITING_FOR_DATA, __func__);
       }
-    } else if (decoder.mError) {
-      decoder.RejectPromise(DECODE_ERROR, __func__);
-      return;
-    } else if (decoder.mWaitingForData) {
-      LOG("Waiting For Data");
-      decoder.RejectPromise(WAITING_FOR_DATA, __func__);
-      return;
+      // Now that draining has completed, we check if we have received
+      // new data again as the result may now be different from the earlier
+      // run.
+      if (UpdateReceivedNewData(aTrack)) {
+        LOGV("Nothing more to do");
+        return;
+      }
     }
   }
 
   if (decoder.mNeedDraining) {
     DrainDecoder(aTrack);
     return;
   }
 
-  if (!NeedInput(decoder)) {
+  bool needInput = NeedInput(decoder);
+
+  LOGV("Update(%s) ni=%d no=%d ie=%d, in:%llu out:%llu qs=%u pending:%u waiting:%d ahead:%d sid:%u",
+       TrackTypeToStr(aTrack), needInput, needOutput, decoder.mInputExhausted,
+       decoder.mNumSamplesInput, decoder.mNumSamplesOutput,
+       uint32_t(size_t(decoder.mSizeOfQueue)), uint32_t(decoder.mOutput.Length()),
+       decoder.mWaitingForData, !decoder.HasPromise(), decoder.mLastStreamSourceID);
+
+  if (decoder.mWaitingForData &&
+      (!decoder.mTimeThreshold || decoder.mTimeThreshold.ref().mWaiting)) {
+    // Nothing more we can do at present.
+    LOGV("Still waiting for data.");
+    return;
+  }
+
+  if (!needInput) {
     LOGV("No need for additional input (pending:%u)",
          uint32_t(decoder.mOutput.Length()));
     return;
   }
 
-  needInput = true;
-
-  LOGV("Update(%s) ni=%d no=%d ie=%d, in:%llu out:%llu qs=%u pending:%u ahead:%d sid:%u",
-       TrackTypeToStr(aTrack), needInput, needOutput, decoder.mInputExhausted,
-       decoder.mNumSamplesInput, decoder.mNumSamplesOutput,
-       uint32_t(size_t(decoder.mSizeOfQueue)), uint32_t(decoder.mOutput.Length()),
-       !decoder.HasPromise(), decoder.mLastStreamSourceID);
-
   // Demux samples if we don't have some.
   RequestDemuxSamples(aTrack);
 
   HandleDemuxedSamples(aTrack, a);
 }
 
 void
 MediaFormatReader::ReturnOutput(MediaData* aData, TrackType aTrack)
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -122,16 +122,32 @@ private:
   // Called when new samples need to be demuxed.
   void RequestDemuxSamples(TrackType aTrack);
   // Handle demuxed samples by the input behavior.
   void HandleDemuxedSamples(TrackType aTrack,
                             AbstractMediaDecoder::AutoNotifyDecoded& aA);
   // Decode any pending already demuxed samples.
   bool DecodeDemuxedSamples(TrackType aTrack,
                             MediaRawData* aSample);
+
+  struct SeekTarget {
+    SeekTarget(const media::TimeUnit& aTime, bool aDropTarget)
+      : mTime(aTime)
+      , mDropTarget(aDropTarget)
+      , mWaiting(false)
+    {}
+
+    media::TimeUnit mTime;
+    bool mDropTarget;
+    bool mWaiting;
+  };
+  // Perform an internal seek to aTime. If aDropTarget is true then
+  // the first sample past the target will be dropped.
+  void InternalSeek(TrackType aTrack, const SeekTarget& aTarget);
+
   // Drain the current decoder.
   void DrainDecoder(TrackType aTrack);
   void NotifyNewOutput(TrackType aTrack, MediaData* aSample);
   void NotifyInputExhausted(TrackType aTrack);
   void NotifyDrainComplete(TrackType aTrack);
   void NotifyError(TrackType aTrack);
   void NotifyWaitingForData(TrackType aTrack);
   void NotifyEndOfStream(TrackType aTrack);
@@ -256,18 +272,21 @@ private:
     bool mDecodingRequested;
     bool mOutputRequested;
     bool mInputExhausted;
     bool mError;
     bool mNeedDraining;
     bool mDraining;
     bool mDrainComplete;
     // If set, all decoded samples prior mTimeThreshold will be dropped.
-    // Used for internal seeking when a change of stream is detected.
-    Maybe<media::TimeUnit> mTimeThreshold;
+    // Used for internal seeking when a change of stream is detected or when
+    // encountering data discontinuity.
+    Maybe<SeekTarget> mTimeThreshold;
+    // Time of last sample returned.
+    Maybe<media::TimeUnit> mLastSampleTime;
 
     // Decoded samples returned my mDecoder awaiting being returned to
     // state machine upon request.
     nsTArray<RefPtr<MediaData>> mOutput;
     uint64_t mNumSamplesInput;
     uint64_t mNumSamplesOutput;
     uint64_t mNumSamplesOutputTotal;
 
@@ -295,32 +314,34 @@ private:
       mQueuedSamples.Clear();
       mDecodingRequested = false;
       mOutputRequested = false;
       mInputExhausted = false;
       mNeedDraining = false;
       mDraining = false;
       mDrainComplete = false;
       mTimeThreshold.reset();
+      mLastSampleTime.reset();
       mOutput.Clear();
       mNumSamplesInput = 0;
       mNumSamplesOutput = 0;
       mSizeOfQueue = 0;
       mNextStreamSourceID.reset();
     }
 
     // Used by the MDSM for logging purposes.
     Atomic<size_t> mSizeOfQueue;
     // Used by the MDSM to determine if video decoding is hardware accelerated.
     // This value is updated after a frame is successfully decoded.
     Atomic<bool> mIsHardwareAccelerated;
     // Sample format monitoring.
     uint32_t mLastStreamSourceID;
     Maybe<uint32_t> mNextStreamSourceID;
     media::TimeIntervals mTimeRanges;
+    Maybe<media::TimeUnit> mLastTimeRangesEnd;
     RefPtr<SharedTrackInfo> mInfo;
   };
 
   template<typename PromiseType>
   struct DecoderDataWithPromise : public DecoderData {
     DecoderDataWithPromise(MediaFormatReader* aOwner,
                            MediaData::Type aType,
                            uint32_t aDecodeAhead) :
--- a/dom/media/mediasink/DecodedStream.cpp
+++ b/dom/media/mediasink/DecodedStream.cpp
@@ -475,17 +475,20 @@ DecodedStream::CreateData(MozPromiseHold
       // properly on the main thread.
       if (mData) {
         DecodedStreamData* data = mData.release();
         RefPtr<DecodedStream> self = mThis.forget();
         nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
           self->mOutputStreamManager.Disconnect();
           delete data;
         });
-        AbstractThread::MainThread()->Dispatch(r.forget());
+        // We are in tail dispatching phase. Don't call
+        // AbstractThread::MainThread()->Dispatch() to avoid reentrant
+        // AutoTaskDispatcher.
+        NS_DispatchToMainThread(r.forget());
       }
     }
     RefPtr<DecodedStream> mThis;
     Method mMethod;
     UniquePtr<DecodedStreamData> mData;
   };
 
   // Post a message to ensure |mData| is only updated on the worker thread.
--- a/dom/media/mediasink/VideoSink.cpp
+++ b/dom/media/mediasink/VideoSink.cpp
@@ -1,14 +1,15 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
+#include "MediaQueue.h"
 #include "VideoSink.h"
 
 namespace mozilla {
 
 extern LazyLogModule gMediaDecoderLog;
 #define VSINK_LOG(msg, ...) \
   MOZ_LOG(gMediaDecoderLog, LogLevel::Debug, \
     ("VideoSink=%p " msg, this, ##__VA_ARGS__))
--- a/dom/media/mediasource/MediaSourceDemuxer.cpp
+++ b/dom/media/mediasource/MediaSourceDemuxer.cpp
@@ -290,17 +290,17 @@ MediaSourceDemuxer::GetMozDebugReaderDat
 
 MediaSourceTrackDemuxer::MediaSourceTrackDemuxer(MediaSourceDemuxer* aParent,
                                                  TrackInfo::TrackType aType,
                                                  TrackBuffersManager* aManager)
   : mParent(aParent)
   , mManager(aManager)
   , mType(aType)
   , mMonitor("MediaSourceTrackDemuxer")
-  , mLastSeek(Some(TimeUnit()))
+  , mReset(true)
 {
 }
 
 UniquePtr<TrackInfo>
 MediaSourceTrackDemuxer::GetInfo() const
 {
   return mParent->GetTrackInfo(mType)->Clone();
 }
@@ -323,17 +323,18 @@ MediaSourceTrackDemuxer::GetSamples(int3
 
 void
 MediaSourceTrackDemuxer::Reset()
 {
   MOZ_ASSERT(mParent, "Called after BreackCycle()");
   RefPtr<MediaSourceTrackDemuxer> self = this;
   nsCOMPtr<nsIRunnable> task =
     NS_NewRunnableFunction([self] () {
-      self->mLastSeek = Some(TimeUnit());
+      self->mNextSample.reset();
+      self->mReset = true;
       self->mManager->Seek(self->mType, TimeUnit(), TimeUnit());
       {
         MonitorAutoLock mon(self->mMonitor);
         self->mNextRandomAccessPoint =
           self->mManager->GetNextRandomAccessPoint(self->mType);
       }
     });
   mParent->GetTaskQueue()->Dispatch(task.forget());
@@ -375,59 +376,68 @@ MediaSourceTrackDemuxer::BreakCycles()
 
 RefPtr<MediaSourceTrackDemuxer::SeekPromise>
 MediaSourceTrackDemuxer::DoSeek(media::TimeUnit aTime)
 {
   TimeIntervals buffered = mManager->Buffered(mType);
   buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ);
 
   if (!buffered.Contains(aTime)) {
-    mLastSeek = Some(aTime);
     // We don't have the data to seek to.
     return SeekPromise::CreateAndReject(DemuxerFailureReason::WAITING_FOR_DATA,
                                         __func__);
   }
   TimeUnit seekTime =
     mManager->Seek(mType, aTime, MediaSourceDemuxer::EOS_FUZZ);
+  bool error;
+  RefPtr<MediaRawData> sample =
+    mManager->GetSample(mType,
+                        media::TimeUnit(),
+                        error);
+  MOZ_ASSERT(!error && sample);
+  mNextSample = Some(sample);
+  mReset = false;
   {
     MonitorAutoLock mon(mMonitor);
     mNextRandomAccessPoint = mManager->GetNextRandomAccessPoint(mType);
   }
-  mLastSeek = Some(aTime);
   return SeekPromise::CreateAndResolve(seekTime, __func__);
 }
 
 RefPtr<MediaSourceTrackDemuxer::SamplesPromise>
 MediaSourceTrackDemuxer::DoGetSamples(int32_t aNumSamples)
 {
-  if (mLastSeek) {
+  if (mReset) {
     // If a seek (or reset) was recently performed, we ensure that the data
     // we are about to retrieve is still available.
     TimeIntervals buffered = mManager->Buffered(mType);
     buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ);
 
-    if (!buffered.Contains(mLastSeek.ref())) {
+    if (!buffered.Contains(TimeUnit::FromMicroseconds(0))) {
       return SamplesPromise::CreateAndReject(
         mManager->IsEnded() ? DemuxerFailureReason::END_OF_STREAM :
                               DemuxerFailureReason::WAITING_FOR_DATA, __func__);
     }
-    mLastSeek.reset();
+    mReset = false;
   }
-  bool error;
-  RefPtr<MediaRawData> sample =
-    mManager->GetSample(mType,
-                        MediaSourceDemuxer::EOS_FUZZ,
-                        error);
-  if (!sample) {
-    if (error) {
-      return SamplesPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__);
+  bool error = false;
+  RefPtr<MediaRawData> sample;
+  if (mNextSample) {
+    sample = mNextSample.ref();
+    mNextSample.reset();
+  } else {
+    sample = mManager->GetSample(mType, MediaSourceDemuxer::EOS_FUZZ, error);
+    if (!sample) {
+      if (error) {
+        return SamplesPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__);
+      }
+      return SamplesPromise::CreateAndReject(
+        mManager->IsEnded() ? DemuxerFailureReason::END_OF_STREAM :
+                              DemuxerFailureReason::WAITING_FOR_DATA, __func__);
     }
-    return SamplesPromise::CreateAndReject(
-      mManager->IsEnded() ? DemuxerFailureReason::END_OF_STREAM :
-                            DemuxerFailureReason::WAITING_FOR_DATA, __func__);
   }
   RefPtr<SamplesHolder> samples = new SamplesHolder;
   samples->mSamples.AppendElement(sample);
   if (mNextRandomAccessPoint.ToMicroseconds() <= sample->mTime) {
     MonitorAutoLock mon(mMonitor);
     mNextRandomAccessPoint = mManager->GetNextRandomAccessPoint(mType);
   }
   return SamplesPromise::CreateAndResolve(samples, __func__);
--- a/dom/media/mediasource/MediaSourceDemuxer.h
+++ b/dom/media/mediasource/MediaSourceDemuxer.h
@@ -124,14 +124,17 @@ private:
   media::TimeUnit GetNextRandomAccessPoint();
 
   RefPtr<MediaSourceDemuxer> mParent;
   RefPtr<TrackBuffersManager> mManager;
   TrackInfo::TrackType mType;
   // Monitor protecting members below accessed from multiple threads.
   Monitor mMonitor;
   media::TimeUnit mNextRandomAccessPoint;
-  Maybe<media::TimeUnit> mLastSeek;
+  Maybe<RefPtr<MediaRawData>> mNextSample;
+  // Set to true following a reset. Ensure that the next sample demuxed
+  // is available at position 0.
+  bool mReset;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/mediasource/test/mochitest.ini
+++ b/dom/media/mediasource/test/mochitest.ini
@@ -35,28 +35,30 @@ support-files =
   bipbop/bipbop13.m4s^headers^ bipbop/bipbop_video13.m4s^headers^
 
 [test_BufferedSeek.html]
 [test_BufferedSeek_mp4.html]
 skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac")) # Only supported on osx and vista+
 [test_BufferingWait.html]
 skip-if = toolkit == 'android' #timeout android bug 1199531
 [test_BufferingWait_mp4.html]
-skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac") || (os == "win" && os_version == "6.1")) # Only supported on osx and vista+, disabling on win7 bug 1191138
+skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac")) # Only supported on osx and vista+
+[test_DrainOnMissingData_mp4.html]
+skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac")) # Only supported on osx and vista+
 [test_EndOfStream.html]
 skip-if = (true || toolkit == 'android' || buildapp == 'mulet') #timeout android/mulet only bug 1101187 and bug 1182946
 [test_EndOfStream_mp4.html]
 skip-if = (toolkit == 'android' || buildapp == 'mulet') || ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac")) # Only supported on osx and vista+
 [test_DurationUpdated.html]
 [test_DurationUpdated_mp4.html]
 skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac")) # Only supported on osx and vista+
 [test_FrameSelection.html]
 [test_HaveMetadataUnbufferedSeek.html]
 [test_HaveMetadataUnbufferedSeek_mp4.html]
-skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac") || (os == "win" && os_version == "6.1")) # Only supported on osx and vista+, disabling on win7 bug 1191138
+skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac")) # Only supported on osx and vista+
 [test_LoadedDataFired_mp4.html]
 skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac")) # Only supported on osx and vista+
 [test_LoadedMetadataFired.html]
 [test_LoadedMetadataFired_mp4.html]
 skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac")) # Only supported on osx and vista+
 [test_MediaSource.html]
 [test_MediaSource_memory_reporting.html]
 [test_MediaSource_mp4.html]
@@ -93,16 +95,16 @@ skip-if = ((os == "win" && os_version ==
 skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac")) # Only supported on osx and vista+
 [test_SplitAppend.html]
 [test_SplitAppend_mp4.html]
 skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac")) # Only supported on osx and vista+
 [test_TimestampOffset_mp4.html]
 skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac")) # Only supported on osx and vista+
 [test_TruncatedDuration.html]
 [test_TruncatedDuration_mp4.html]
-skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac") || (os == "win" && os_version == "6.1")) # Only supported on osx and vista+, disabling on win7 bug 1191138
+skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac")) # Only supported on osx and vista+
 [test_WaitingOnMissingData.html]
 skip-if = true # Disabled due to bug 1124493 and friends. WebM MSE is deprioritized.
 [test_WaitingOnMissingData_mp4.html]
-skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac") || (os == "win" && os_version == "6.1")) # Only supported on osx and vista+, disabling on win7 bug 1191138
+skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac")) # Only supported on osx and vista+
 [test_WaitingToEndedTransition_mp4.html]
-skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac") || (os == "win" && os_version == "6.1")) # Only supported on osx and vista+, disabling on win7 bug 1191138
+skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac")) # Only supported on osx and vista+
 
--- a/dom/media/mediasource/test/test_BufferingWait.html
+++ b/dom/media/mediasource/test/test_BufferingWait.html
@@ -36,24 +36,25 @@ runWithMSE(function(ms, v) {
     fetchWithXHR("seek.webm", function(arrayBuffer) {
       sb.addEventListener('error', (e) => { ok(false, "Got Error: " + e); SimpleTest.finish(); });
       loadSegment.bind(null, sb, new Uint8Array(arrayBuffer, 0, 318))().then(
       loadSegment.bind(null, sb, new Uint8Array(arrayBuffer, 318, 25523-318))).then(
       loadSegment.bind(null, sb, new Uint8Array(arrayBuffer, 25523, 46712-25523))).then(
       /* Note - Missing |46712, 67833 - 46712| segment here corresponding to (0.8, 1.2] */
       /* Note - Missing |67833, 88966 - 67833| segment here corresponding to (1.2, 1.6]  */
       loadSegment.bind(null, sb, new Uint8Array(arrayBuffer, 88966))).then(function() {
-        var promise = waitUntilTime(0.27);
+        // 0.767 is the time of the last video sample +- 40ms.
+        var promise = waitUntilTime(.767-0.04);
         info("Playing video. It should play for a bit, then fire 'waiting'");
         v.play();
         return promise;
       }).then(function() {
         window.firstStop = Date.now();
         loadSegment(sb, new Uint8Array(arrayBuffer, 46712, 67833 - 46712));
-        return waitUntilTime(0.66);
+        return waitUntilTime(1.167-0.04);
       }).then(function() {
         var waitDuration = (Date.now() - window.firstStop) / 1000;
         ok(waitDuration < 15, "Should not spend an inordinate amount of time buffering: " + waitDuration);
         SimpleTest.finish();
         /* If we allow the rest of the stream to be played, we get stuck at
            around 2s. See bug 1093133.
         once(v, 'ended', SimpleTest.finish.bind(SimpleTest));
         return loadSegment(sb, new Uint8Array(arrayBuffer, 67833, 88966 - 67833));
--- a/dom/media/mediasource/test/test_BufferingWait_mp4.html
+++ b/dom/media/mediasource/test/test_BufferingWait_mp4.html
@@ -36,28 +36,26 @@ runWithMSE(function(ms, v) {
     sb.addEventListener('error', (e) => { ok(false, "Got Error: " + e); SimpleTest.finish(); });
     fetchAndLoad(sb, 'bipbop/bipbop', ['init'], '.mp4')
     .then(fetchAndLoad.bind(null, sb, 'bipbop/bipbop', ['1'], '.m4s'))
     .then(fetchAndLoad.bind(null, sb, 'bipbop/bipbop', ['2'], '.m4s'))
     /* Note - Missing |bipbop3| segment here corresponding to (1.62, 2.41] */
     /* Note - Missing |bipbop4| segment here corresponding to (2.41, 3.20]  */
     .then(fetchAndLoad.bind(null, sb, 'bipbop/bipbop', ['5'], '.m4s'))
     .then(function() {
-        // Some decoders (Windows in particular) may keep up to 25 frames queued
-        // before returning a sample. 0.7 is 1.62s - 25 * 0.03333
-        var promise = waitUntilTime(0.7);
+        // last audio sample has a start time of 1.578956s
+        var promise = waitUntilTime(1.57895);
         info("Playing video. It should play for a bit, then fire 'waiting'");
         v.play();
         return promise;
       }).then(function() {
         window.firstStop = Date.now();
         fetchAndLoad(sb, 'bipbop/bipbop', ['3'], '.m4s');
-        // Some decoders (Windows in particular) may keep up to 25 frames queued
-        // before returning a sample. 1.5 is 2.41s - 25 * 0.03333
-        return waitUntilTime(1.5);
+        // last audio sample has a start time of 2.368435
+        return waitUntilTime(2.36843);
       }).then(function() {
         var waitDuration = (Date.now() - window.firstStop) / 1000;
         ok(waitDuration < 15, "Should not spend an inordinate amount of time buffering: " + waitDuration);
         once(v, 'ended', SimpleTest.finish.bind(SimpleTest));
         return fetchAndLoad(sb, 'bipbop/bipbop', ['4'], '.m4s');
       }).then(function() {
         ms.endOfStream();
       });;
new file mode 100644
--- /dev/null
+++ b/dom/media/mediasource/test/test_DrainOnMissingData_mp4.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html><head>
+<meta http-equiv="content-type" content="text/html; charset=windows-1252">
+  <title>MSE: |waiting| event when source data is missing</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="mediasource.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test"><script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+runWithMSE(function(ms, el) {
+  el.controls = true;
+  once(ms, 'sourceopen').then(function() {
+    ok(true, "Receive a sourceopen event");
+    var videosb = ms.addSourceBuffer("video/mp4");
+    fetchAndLoad(videosb, 'bipbop/bipbop_video', ['init'], '.mp4')
+    .then(function() {
+      // Set appendWindowEnd to ensure we only have about 6 frames worth.
+      // We must feed at least 6 frames to pass the MDSM pre-roll.
+      videosb.appendWindowEnd = .4;
+      return fetchAndLoad(videosb, 'bipbop/bipbop_video', ['1'], '.m4s');
+    })
+    .then(function() {
+      info("Invoking play()");
+      var promises = [];
+      promises.push(once(el, 'playing'));
+      el.play();
+      return Promise.all(promises);
+    })
+    .then(function() {
+      info("got playing");
+      return once(el, 'waiting');
+    }).then(function() {
+      info("got waiting");
+      info("Loading more data");
+      // Waiting will be fired on the last frame +- 40ms.
+      isfuzzy(el.currentTime, videosb.buffered.end(0) - 1/30,
+              0.04, "Got a waiting event at " + el.currentTime);
+      videosb.appendWindowEnd = 1;
+      var p = once(el, 'ended');
+      var loads = fetchAndLoad(videosb, 'bipbop/bipbop_video', [1], '.m4s');
+      loads.then(() => ms.endOfStream());
+      return p;
+    }).then(function() {
+      // These fuzz factors are bigger than they should be. We should investigate
+      // and fix them in bug 1137574.
+      is(el.duration, 0.801666, "Video has correct duration: " + el.duration);
+      is(el.currentTime, el.duration, "Video has correct currentTime.");
+      SimpleTest.finish();
+    });
+  });
+});
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/media/mediasource/test/test_PlayEvents.html
+++ b/dom/media/mediasource/test/test_PlayEvents.html
@@ -19,32 +19,41 @@ SimpleTest.waitForExplicitFinish();
 // 4. Load 1.6s of data at the seek position and ensure that canplay is fired and that readyState is now HAVE_FUTURE_DATA
 // 5. Start playing video and check that once it reaches a position with no data, readyState goes back to HAVE_CURRENT_DATA and waiting event is fired.
 // 6. Add 1.6s of data once video element fired waiting, that canplay is fired once readyState is HAVE_FUTURE_DATA.
 // 7. Finally load data to the end and ensure that canplaythrough is fired and that readyState is now HAVE_ENOUGH_DATA
 
 runWithMSE(function(ms, el) {
   el.controls = true;
   once(ms, 'sourceopen').then(function() {
+    // Log events for debugging.
+    var events = ["suspend", "play", "canplay", "canplaythrough", "loadstart", "loadedmetadata",
+                  "loadeddata", "playing", "ended", "error", "stalled", "emptied", "abort",
+                  "waiting", "pause", "durationchange", "seeking", "seeked"];
+    function logEvent(e) {
+      var v = e.target;
+      info("got " + e.type + " event");
+    }
+    events.forEach(function(e) {
+      el.addEventListener(e, logEvent, false);
+    });
+
     ok(true, "Receive a sourceopen event");
     var videosb = ms.addSourceBuffer("video/mp4");
     el.addEventListener("error", function(e) {
       ok(false, "should not fire '" + e + "' event");
     });
     is(el.readyState, el.HAVE_NOTHING, "readyState is HAVE_NOTHING");
     fetchAndLoad(videosb, 'bipbop/bipbop_video', ['init'], '.mp4')
     .then(once.bind(null, el, 'loadedmetadata'))
     .then(function() {
        ok(true, "got loadedmetadata event");
        var promises = [];
        promises.push(once(el, 'loadeddata'));
        promises.push(once(el, 'canplay'));
-       // Load [0, 1.601666). We must ensure that we load over 25 frames as the
-       // windows H264 decoder will not produce a sample until then
-       // (bug 1191138).
        promises.push(fetchAndLoad(videosb, 'bipbop/bipbop_video', range(1, 3), '.m4s'));
        return Promise.all(promises);
     })
     .then(function() {
       ok(true, "got canplay event");
       // set element duration to 3.203333s. We do so in order to guarantee that
       // the end of the buffered range will be equal to duration, causing
       // canplaythrough to be fired later.
--- a/dom/media/mediasource/test/test_WaitingOnMissingData_mp4.html
+++ b/dom/media/mediasource/test/test_WaitingOnMissingData_mp4.html
@@ -35,19 +35,19 @@ runWithMSE(function(ms, el) {
       ok(true, "Video playing. It should play for a bit, then fire 'waiting'");
       var p = once(el, 'waiting');
       el.play();
       return p;
     }).then(function() {
       // currentTime is based on the current video frame, so if the audio ends just before
       // the next video frame, currentTime can be up to 1 frame's worth earlier than
       // min(audioEnd, videoEnd).
-      // Some decoders (Windows in particular) may keep up to 25 frames queued.
-      isfuzzy(el.currentTime, Math.min(audiosb.buffered.end(0), videosb.buffered.end(0)) - 1/60,
-              25 * 1/30, "Got a waiting event at " + el.currentTime);
+      // 0.0465 is the length of the last audio frame.
+      ok(el.currentTime >= (Math.min(audiosb.buffered.end(0), videosb.buffered.end(0)) - 0.0465),
+         "Got a waiting event at " + el.currentTime);
       info("Loading more data");
       var p = once(el, 'ended');
       var loads = Promise.all([fetchAndLoad(audiosb, 'bipbop/bipbop_audio', [5], '.m4s'),
                                fetchAndLoad(videosb, 'bipbop/bipbop_video', [6], '.m4s')]);
       loads.then(() => ms.endOfStream());
       return p;
     }).then(function() {
       // These fuzz factors are bigger than they should be. We should investigate
--- a/dom/media/mediasource/test/test_WaitingToEndedTransition_mp4.html
+++ b/dom/media/mediasource/test/test_WaitingToEndedTransition_mp4.html
@@ -12,16 +12,20 @@
 SimpleTest.waitForExplicitFinish();
 
 runWithMSE(function(ms, el) {
   el.controls = true;
   once(ms, 'sourceopen').then(function() {
     ok(true, "Receive a sourceopen event");
     var audiosb = ms.addSourceBuffer("audio/mp4");
     var videosb = ms.addSourceBuffer("video/mp4");
+    // ensure tracks end at approximately the same time to ensure ended event is
+    // always fired (bug 1233639).
+    audiosb.appendWindowEnd = 3.9;
+    videosb.appendWindowEnd = 3.9;
     fetchAndLoad(audiosb, 'bipbop/bipbop_audio', ['init'], '.mp4')
     .then(fetchAndLoad.bind(null, videosb, 'bipbop/bipbop_video', ['init'], '.mp4'))
     .then(fetchAndLoad.bind(null, audiosb, 'bipbop/bipbop_audio', range(1, 5), '.m4s'))
     .then(fetchAndLoad.bind(null, videosb, 'bipbop/bipbop_video', range(1, 6), '.m4s'))
     .then(function() {
       // HTMLMediaElement fires 'waiting' if somebody invokes |play()| before the MDSM
       // has notified it of available data. Make sure that we get 'playing' before
       // we starting waiting for 'waiting'.
@@ -30,28 +34,21 @@ runWithMSE(function(ms, el) {
       el.play();
       return p;
     }).then(function() {
       ok(true, "Video playing. It should play for a bit, then fire 'waiting'");
       var p = once(el, 'waiting');
       el.play();
       return p;
     }).then(function() {
-      // currentTime is based on the current video frame, so if the audio ends just before
-      // the next video frame, currentTime can be up to 1 frame's worth earlier than
-      // min(audioEnd, videoEnd).
-      // Some decoders (Windows in particular) may keep up to 25 frames queued.
-      isfuzzy(el.currentTime, Math.min(audiosb.buffered.end(0), videosb.buffered.end(0)) - 1/60,
-              25 * 1/30, "Got a waiting event at " + el.currentTime);
-    }).then(function() {
       var p = once(el, 'ended');
       ms.endOfStream();
       return p;
     }).then(function() {
-      is(el.duration, 4.005, "Video has correct duration: " + el.duration);
+      is(el.duration, 3.854512, "Video has correct duration: " + el.duration);
       is(el.currentTime, el.duration, "Video has correct currentTime.");
       SimpleTest.finish();
     });
   });
 });
 
 </script>
 </pre>
--- a/dom/media/platforms/omx/OmxDataDecoder.cpp
+++ b/dom/media/platforms/omx/OmxDataDecoder.cpp
@@ -221,43 +221,54 @@ OmxDataDecoder::DoAsyncShutdown()
 {
   LOG("(%p)", this);
   MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn());
   MOZ_ASSERT(mFlushing);
 
   mWatchManager.Unwatch(mOmxState, &OmxDataDecoder::OmxStateRunner);
   mWatchManager.Unwatch(mPortSettingsChanged, &OmxDataDecoder::PortSettingsChanged);
 
-  // Do flush so all port can be returned to client.
+  // Flush to all ports, so all buffers can be returned from component.
   RefPtr<OmxDataDecoder> self = this;
   mOmxLayer->SendCommand(OMX_CommandFlush, OMX_ALL, nullptr)
     ->Then(mOmxTaskQueue, __func__,
            [self] () -> RefPtr<OmxCommandPromise> {
-             LOG("DoAsyncShutdown: flush complete, collecting buffers...");
-             self->CollectBufferPromises(OMX_DirMax)
-               ->Then(self->mOmxTaskQueue, __func__,
-                   [self] () {
-                     LOG("DoAsyncShutdown: releasing all buffers.");
-                     self->ReleaseBuffers(OMX_DirInput);
-                     self->ReleaseBuffers(OMX_DirOutput);
-                   },
-                   [self] () {
-                     self->mOmxLayer->Shutdown();
-                   });
-
+             LOG("DoAsyncShutdown: flush complete");
              return self->mOmxLayer->SendCommand(OMX_CommandStateSet, OMX_StateIdle, nullptr);
            },
            [self] () {
              self->mOmxLayer->Shutdown();
            })
     ->CompletionPromise()
     ->Then(mOmxTaskQueue, __func__,
            [self] () -> RefPtr<OmxCommandPromise> {
-             LOG("DoAsyncShutdown: OMX_StateIdle");
-             return self->mOmxLayer->SendCommand(OMX_CommandStateSet, OMX_StateLoaded, nullptr);
+             RefPtr<OmxCommandPromise> p =
+               self->mOmxLayer->SendCommand(OMX_CommandStateSet, OMX_StateLoaded, nullptr);
+
+             LOG("DoAsyncShutdown: collecting buffers...");
+             self->CollectBufferPromises(OMX_DirMax)
+               ->Then(self->mOmxTaskQueue, __func__,
+                   [self] () {
+                   // According to spec 3.1.1.2.2.1:
+                   // OMX_StateLoaded needs to be sent before releasing buffers.
+                   // And state transition from OMX_StateIdle to OMX_StateLoaded
+                   // is completed when all of the buffers have been removed
+                   // from the component.
+                   // Here the buffer promises are not resolved due to displaying
+                   // in layer, it needs to wait before the layer returns the
+                   // buffers.
+                   LOG("DoAsyncShutdown: all buffers collected, releasing buffers...");
+                   self->ReleaseBuffers(OMX_DirInput);
+                   self->ReleaseBuffers(OMX_DirOutput);
+                   },
+                   [self] () {
+                     self->mOmxLayer->Shutdown();
+                   });
+
+             return p;
            },
            [self] () {
              self->mOmxLayer->Shutdown();
            })
     ->CompletionPromise()
     ->Then(mOmxTaskQueue, __func__,
            [self] () {
              LOG("DoAsyncShutdown: OMX_StateLoaded, it is safe to shutdown omx");
--- a/dom/media/webrtc/MediaEngineTabVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineTabVideoSource.cpp
@@ -191,17 +191,17 @@ void
 MediaEngineTabVideoSource::NotifyPull(MediaStreamGraph*,
                                       SourceMediaStream* aSource,
                                       TrackID aID, StreamTime aDesiredTime)
 {
   VideoSegment segment;
   MonitorAutoLock mon(mMonitor);
 
   // Note: we're not giving up mImage here
-  RefPtr<layers::CairoImage> image = mImage;
+  RefPtr<layers::SourceSurfaceImage> image = mImage;
   StreamTime delta = aDesiredTime - aSource->GetEndOfAppendedData(aID);
   if (delta > 0) {
     // nullptr images are allowed
     gfx::IntSize size = image ? image->GetSize() : IntSize(0, 0);
     segment.AppendFrame(image.forget().downcast<layers::Image>(), delta, size);
     // This can fail if either a) we haven't added the track yet, or b)
     // we've removed or finished the track.
     aSource->AppendToTrack(aID, &(segment));
@@ -293,17 +293,17 @@ MediaEngineTabVideoSource::Draw() {
 
   NS_ENSURE_SUCCESS_VOID(presShell->RenderDocument(r, renderDocFlags, bgColor, context));
 
   RefPtr<SourceSurface> surface = dt->Snapshot();
   if (!surface) {
     return;
   }
 
-  RefPtr<layers::CairoImage> image = new layers::CairoImage(size, surface);
+  RefPtr<layers::SourceSurfaceImage> image = new layers::SourceSurfaceImage(size, surface);
 
   MonitorAutoLock mon(mMonitor);
   mImage = image;
 }
 
 nsresult
 MediaEngineTabVideoSource::Stop(mozilla::SourceMediaStream*, mozilla::TrackID)
 {
--- a/dom/media/webrtc/MediaEngineTabVideoSource.h
+++ b/dom/media/webrtc/MediaEngineTabVideoSource.h
@@ -83,14 +83,14 @@ private:
     int32_t mViewportOffsetX;
     int32_t mViewportOffsetY;
     int32_t mViewportWidth;
     int32_t mViewportHeight;
     int32_t mTimePerFrame;
     ScopedFreePtr<unsigned char> mData;
     size_t mDataSize;
     nsCOMPtr<nsIDOMWindow> mWindow;
-    RefPtr<layers::CairoImage> mImage;
+    RefPtr<layers::SourceSurfaceImage> mImage;
     nsCOMPtr<nsITimer> mTimer;
     Monitor mMonitor;
     nsCOMPtr<nsITabSource> mTabSource;
   };
 }
--- a/dom/plugins/ipc/PluginInstanceParent.cpp
+++ b/dom/plugins/ipc/PluginInstanceParent.cpp
@@ -952,17 +952,17 @@ PluginInstanceParent::RecvShow(const NPR
         // its back.
         gfxRect ur(updatedRect.left, updatedRect.top,
                    updatedRect.right - updatedRect.left,
                    updatedRect.bottom - updatedRect.top);
         surface->MarkDirty(ur);
 
         RefPtr<gfx::SourceSurface> sourceSurface =
             gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(nullptr, surface);
-        RefPtr<CairoImage> image = new CairoImage(surface->GetSize(), sourceSurface);
+        RefPtr<SourceSurfaceImage> image = new SourceSurfaceImage(surface->GetSize(), sourceSurface);
 
         nsAutoTArray<ImageContainer::NonOwningImage,1> imageList;
         imageList.AppendElement(
             ImageContainer::NonOwningImage(image));
 
         ImageContainer *container = GetImageContainer();
         container->SetCurrentImages(imageList);
     }
--- a/dom/security/nsCSPService.cpp
+++ b/dom/security/nsCSPService.cpp
@@ -134,72 +134,16 @@ CSPService::ShouldLoad(uint32_t aContent
   // TYPE_REFRESH    -- never passed to ShouldLoad (see nsIContentPolicy.idl)
   // TYPE_DOCUMENT   -- used for frame-ancestors
   if (aContentType == nsIContentPolicy::TYPE_CSP_REPORT ||
     aContentType == nsIContentPolicy::TYPE_REFRESH ||
     aContentType == nsIContentPolicy::TYPE_DOCUMENT) {
     return NS_OK;
   }
 
-  // ----- THIS IS A TEMPORARY FAST PATH FOR CERTIFIED APPS. -----
-  // ----- PLEASE REMOVE ONCE bug 925004 LANDS.              -----
-
-  // Cache the app status for this origin.
-  uint16_t status = nsIPrincipal::APP_STATUS_NOT_INSTALLED;
-  nsAutoCString sourceOrigin;
-  if (aRequestPrincipal && aRequestOrigin) {
-    aRequestOrigin->GetPrePath(sourceOrigin);
-    if (!mAppStatusCache.Get(sourceOrigin, &status)) {
-      aRequestPrincipal->GetAppStatus(&status);
-      mAppStatusCache.Put(sourceOrigin, status);
-    }
-  }
-
-  if (status == nsIPrincipal::APP_STATUS_CERTIFIED) {
-    // The CSP for certified apps is :
-    // "default-src * data: blob:; script-src 'self'; object-src 'none'; style-src 'self' app://theme.gaiamobile.org:*"
-    // That means we can optimize for this case by:
-    // - loading same origin scripts and stylesheets, and stylesheets from the
-    //   theme url space.
-    // - never loading objects.
-    // - accepting everything else.
-
-    switch (aContentType) {
-      case nsIContentPolicy::TYPE_SCRIPT:
-      case nsIContentPolicy::TYPE_STYLESHEET:
-        {
-          // Whitelist the theme resources.
-          auto themeOrigin = Preferences::GetCString("b2g.theme.origin");
-          nsAutoCString contentOrigin;
-          aContentLocation->GetPrePath(contentOrigin);
-
-          if (!(sourceOrigin.Equals(contentOrigin) ||
-                (themeOrigin && themeOrigin.Equals(contentOrigin)))) {
-            *aDecision = nsIContentPolicy::REJECT_SERVER;
-          }
-        }
-        break;
-
-      case nsIContentPolicy::TYPE_OBJECT:
-        *aDecision = nsIContentPolicy::REJECT_SERVER;
-        break;
-
-      default:
-        *aDecision = nsIContentPolicy::ACCEPT;
-    }
-
-    // Only cache and return if we are successful. If not, we want the error
-    // to be reported, and thus fallback to the slow path.
-    if (*aDecision == nsIContentPolicy::ACCEPT) {
-      return NS_OK;
-    }
-  }
-
-  // ----- END OF TEMPORARY FAST PATH FOR CERTIFIED APPS. -----
-
   // query the principal of the document; if no document is passed, then
   // fall back to using the requestPrincipal (e.g. service workers do not
   // pass a document).
   nsCOMPtr<nsINode> node(do_QueryInterface(aRequestContext));
   nsCOMPtr<nsIPrincipal> principal = node ? node->NodePrincipal()
                                           : aRequestPrincipal;
   if (!principal) {
     // if we can't query a principal, then there is nothing to do.
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -699,16 +699,18 @@ var interfaceNamesInGlobalScope =
     "IDBTransaction",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "IDBVersionChangeEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Image",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "ImageBitmap",
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    "ImageBitmapRenderingContext",
+// IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "ImageCapture", disabled: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "ImageCaptureErrorEvent", disabled: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "ImageData",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "InputEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
--- a/dom/webidl/FileReader.webidl
+++ b/dom/webidl/FileReader.webidl
@@ -6,17 +6,17 @@
  * The origin of this IDL file is
  * http://dev.w3.org/2006/webapi/FileAPI/#dfn-filereader
  *
  * Copyright © 2013 W3C® (MIT, ERCIM, Keio, Beihang), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
 [Constructor,
- Exposed=(Window,System)]
+ Exposed=(Window,Worker,System)]
 interface FileReader : EventTarget {
   // async read methods
   [Throws]
   void readAsArrayBuffer(Blob blob);
   [Throws]
   void readAsText(Blob blob, optional DOMString label = "");
   [Throws]
   void readAsDataURL(Blob blob);
--- a/dom/webidl/ImageBitmap.webidl
+++ b/dom/webidl/ImageBitmap.webidl
@@ -18,15 +18,27 @@ typedef (HTMLImageElement or
 [Exposed=(Window,Worker)]
 interface ImageBitmap {
   [Constant]
   readonly attribute unsigned long width;
   [Constant]
   readonly attribute unsigned long height;
 };
 
+// It's crucial that there be a way to explicitly dispose of ImageBitmaps
+// since they refer to potentially large graphics resources. Some uses
+// of this API proposal will result in repeated allocations of ImageBitmaps,
+// and garbage collection will not reliably reclaim them quickly enough.
+// Here we reuse close(), which also exists on another Transferable type,
+// MessagePort. Potentially, all Transferable types should inherit from a
+// new interface type "Closeable".
+partial interface ImageBitmap {
+  // Dispose of all graphical resources associated with this ImageBitmap.
+  void close();
+};
+
 [NoInterfaceObject, Exposed=(Window,Worker)]
 interface ImageBitmapFactories {
   [Throws]
   Promise<ImageBitmap> createImageBitmap(ImageBitmapSource aImage);
   [Throws]
   Promise<ImageBitmap> createImageBitmap(ImageBitmapSource aImage, long aSx, long aSy, long aSw, long aSh);
 };
new file mode 100644
--- /dev/null
+++ b/dom/webidl/ImageBitmapRenderingContext.webidl
@@ -0,0 +1,36 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ *
+ * The origin of this IDL file is
+ * https://wiki.whatwg.org/wiki/OffscreenCanvas
+ *
+ * © Copyright 2004-2011 Apple Computer, Inc., Mozilla Foundation, and
+ * Opera Software ASA. You are granted a license to use, reproduce
+ * and create derivative works of this document.
+ */
+
+// The new ImageBitmapRenderingContext is a canvas rendering context
+// which only provides the functionality to replace the canvas's
+// contents with the given ImageBitmap. Its context id (the first argument
+// to getContext) is "bitmaprenderer".
+[Exposed=(Window,Worker)]
+interface ImageBitmapRenderingContext {
+  // Displays the given ImageBitmap in the canvas associated with this
+  // rendering context. Ownership of the ImageBitmap is transferred to
+  // the canvas. The caller may not use its reference to the ImageBitmap
+  // after making this call. (This semantic is crucial to enable prompt
+  // reclamation of expensive graphics resources, rather than relying on
+  // garbage collection to do so.)
+  //
+  // The ImageBitmap conceptually replaces the canvas's bitmap, but
+  // it does not change the canvas's intrinsic width or height.
+  //
+  // The ImageBitmap, when displayed, is clipped to the rectangle
+  // defined by the canvas's instrinsic width and height. Pixels that
+  // would be covered by the canvas's bitmap which are not covered by
+  // the supplied ImageBitmap are rendered transparent black. Any CSS
+  // styles affecting the display of the canvas are applied as usual.
+  void transferImageBitmap(ImageBitmap bitmap);
+};
--- a/dom/webidl/OffscreenCanvas.webidl
+++ b/dom/webidl/OffscreenCanvas.webidl
@@ -1,28 +1,29 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/.
  *
  * For more information on this interface, please see
  * https://wiki.whatwg.org/wiki/OffscreenCanvas
- *
- * Current implementation focus on transfer canvas from main thread to worker.
- * So there are some spec doesn't implement, such as [Constructor], toBlob() and
- * transferToImageBitmap in OffscreenCanvas. Bug 1172796 will implement
- * remaining spec.
  */
 
-[Exposed=(Window,Worker),
+[Constructor(unsigned long width, unsigned long height),
+ Exposed=(Window,Worker),
  Func="mozilla::dom::OffscreenCanvas::PrefEnabled"]
 interface OffscreenCanvas : EventTarget {
   [Pure, SetterThrows]
   attribute unsigned long width;
   [Pure, SetterThrows]
   attribute unsigned long height;
 
   [Throws]
   nsISupports? getContext(DOMString contextId,
                           optional any contextOptions = null);
+
+  ImageBitmap transferToImageBitmap();
+  [Throws]
+  Promise<Blob> toBlob(optional DOMString type = "",
+                       optional any encoderOptions);
 };
 
 // OffscreenCanvas implements Transferable;
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -260,16 +260,17 @@ WEBIDL_FILES = [
     'IDBKeyRange.webidl',
     'IDBMutableFile.webidl',
     'IDBObjectStore.webidl',
     'IDBOpenDBRequest.webidl',
     'IDBRequest.webidl',
     'IDBTransaction.webidl',
     'IDBVersionChangeEvent.webidl',
     'ImageBitmap.webidl',
+    'ImageBitmapRenderingContext.webidl',
     'ImageCapture.webidl',
     'ImageData.webidl',
     'ImageDocument.webidl',
     'InputEvent.webidl',
     'InputMethod.webidl',
     'InputPort.webidl',
     'InputPortManager.webidl',
     'InspectorUtils.webidl',
--- a/dom/workers/ScriptLoader.cpp
+++ b/dom/workers/ScriptLoader.cpp
@@ -624,16 +624,17 @@ private:
   OnStartRequest(nsIRequest* aRequest, uint32_t aIndex)
   {
     AssertIsOnMainThread();
     MOZ_ASSERT(aIndex < mLoadInfos.Length());
 
     // If one load info cancels or hits an error, it can race with the start
     // callback coming from another load info.
     if (mCanceledMainThread || !mCacheCreator) {
+      aRequest->Cancel(NS_ERROR_FAILURE);
       return NS_ERROR_FAILURE;
     }
 
     ScriptLoadInfo& loadInfo = mLoadInfos[aIndex];
 
     nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
     MOZ_ASSERT(channel == loadInfo.mChannel);
 
copy from dom/base/test/fileapi_chromeScript.js
copy to dom/workers/test/fileapi_chromeScript.js
--- a/dom/workers/test/mochitest.ini
+++ b/dom/workers/test/mochitest.ini
@@ -114,16 +114,18 @@ support-files =
   sharedworker_performance_user_timing.js
   referrer.sjs
   performance_observer.html
   sharedWorker_ports.js
   sharedWorker_lifetime.js
   worker_referrer.js
   websocket_https.html
   websocket_https_worker.js
+  worker_fileReader.js
+  fileapi_chromeScript.js
 
 [test_404.html]
 [test_atob.html]
 [test_blobConstructor.html]
 [test_blobWorkers.html]
 [test_bug949946.html]
 [test_bug978260.html]
 [test_bug998474.html]
@@ -231,8 +233,9 @@ skip-if = buildapp == 'b2g'
 [test_xhr_system.html]
 skip-if = buildapp == 'b2g'
 [test_xhr_timeout.html]
 skip-if = (os == "win") || (os == "mac") || toolkit == 'android' #bug 798220
 [test_xhrAbort.html]
 [test_referrer.html]
 [test_sharedWorker_ports.html]
 [test_sharedWorker_lifetime.html]
+[test_fileReader.html]
--- a/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js
+++ b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js
@@ -110,16 +110,18 @@ var interfaceNamesInGlobalScope =
     "ExtendableEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "ExtendableMessageEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "FetchEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "File",
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    "FileReader",
+// IMPORTANT: Do not change this list without review from a DOM peer!
     "FileReaderSync",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "FormData",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Headers",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "IDBCursor",
 // IMPORTANT: Do not change this list without review from a DOM peer!
@@ -140,16 +142,18 @@ var interfaceNamesInGlobalScope =
     "IDBRequest",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "IDBTransaction",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "IDBVersionChangeEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "ImageBitmap",
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    "ImageBitmapRenderingContext",
+// IMPORTANT: Do not change this list without review from a DOM peer!
     "ImageData",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MessageChannel",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MessageEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MessagePort",
 // IMPORTANT: Do not change this list without review from a DOM peer!
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/test_fileReader.html
@@ -0,0 +1,100 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for FileReader in workers</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+<script type="text/javascript;version=1.7">
+
+const minFileSize = 20000;
+SimpleTest.waitForExplicitFinish();
+
+// Create strings containing data we'll test with. We'll want long
+// strings to ensure they span multiple buffers while loading
+var testTextData = "asd b\tlah\u1234w\u00a0r";
+while (testTextData.length < minFileSize) {
+  testTextData = testTextData + testTextData;
+}
+
+var testASCIIData = "abcdef 123456\n";
+while (testASCIIData.length < minFileSize) {
+  testASCIIData = testASCIIData + testASCIIData;
+}
+
+var testBinaryData = "";
+for (var i = 0; i < 256; i++) {
+  testBinaryData += String.fromCharCode(i);
+}
+while (testBinaryData.length < minFileSize) {
+  testBinaryData = testBinaryData + testBinaryData;
+}
+
+var dataurldata0 = testBinaryData.substr(0, testBinaryData.length -
+					 testBinaryData.length % 3);
+var dataurldata1 = testBinaryData.substr(0, testBinaryData.length - 2 -
+					 testBinaryData.length % 3);
+var dataurldata2 = testBinaryData.substr(0, testBinaryData.length - 1 -
+					 testBinaryData.length % 3);
+
+
+//Set up files for testing
+var openerURL = SimpleTest.getTestFileURL("fileapi_chromeScript.js");
+var opener = SpecialPowers.loadChromeScript(openerURL);
+opener.addMessageListener("files.opened", onFilesOpened);
+opener.sendAsyncMessage("files.open", [
+  testASCIIData,
+  testBinaryData,
+  null,
+  convertToUTF8(testTextData),
+  convertToUTF16(testTextData),
+  "",
+  dataurldata0,
+  dataurldata1,
+  dataurldata2,
+]);
+
+function onFilesOpened(message) {
+  var worker = new Worker('worker_fileReader.js');
+  worker.postMessage({ blobs: message,
+                       testTextData: testTextData,
+                       testASCIIData: testASCIIData,
+                       testBinaryData: testBinaryData,
+                       dataurldata0: dataurldata0,
+                       dataurldata1: dataurldata1,
+                       dataurldata2: dataurldata2 });
+
+  worker.onmessage = function(e) {
+    var msg = e.data;
+    if (msg.type == 'finish') {
+      SimpleTest.finish();
+      return;
+    }
+
+    if (msg.type == 'check') {
+      ok(msg.status, msg.msg);
+      return;
+    }
+
+    ok(false, "Unknown message.");
+  }
+}
+
+function convertToUTF16(s) {
+  res = "";
+  for (var i = 0; i < s.length; ++i) {
+    c = s.charCodeAt(i);
+    res += String.fromCharCode(c & 255, c >>> 8);
+  }
+  return res;
+}
+
+function convertToUTF8(s) {
+  return unescape(encodeURIComponent(s));
+}
+
+</script>
+</body>
+</html>
--- a/dom/workers/test/test_worker_interfaces.js
+++ b/dom/workers/test/test_worker_interfaces.js
@@ -102,16 +102,18 @@ var interfaceNamesInGlobalScope =
     "DOMStringList",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Event",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "EventTarget",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "File",
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    "FileReader",
+// IMPORTANT: Do not change this list without review from a DOM peer!
     "FileReaderSync",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "FormData",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Headers",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "IDBCursor",
 // IMPORTANT: Do not change this list without review from a DOM peer!
@@ -132,16 +134,18 @@ var interfaceNamesInGlobalScope =
     "IDBRequest",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "IDBTransaction",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "IDBVersionChangeEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "ImageBitmap",
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    "ImageBitmapRenderingContext",
+// IMPORTANT: Do not change this list without review from a DOM peer!
     "ImageData",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MessageChannel",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MessageEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MessagePort",
 // IMPORTANT: Do not change this list without review from a DOM peer!
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/worker_fileReader.js
@@ -0,0 +1,417 @@
+var testRanCounter = 0;
+var expectedTestCount = 0;
+var testSetupFinished = false;
+
+function ok(a, msg) {
+  postMessage({type: 'check', status: !!a, msg: msg });
+}
+
+function is(a, b, msg) {
+  ok(a === b, msg);
+}
+
+function finish() {
+  postMessage({type: 'finish'});
+}
+
+function convertToUTF16(s) {
+  res = "";
+  for (var i = 0; i < s.length; ++i) {
+    c = s.charCodeAt(i);
+    res += String.fromCharCode(c & 255, c >>> 8);
+  }
+  return res;
+}
+
+function convertToUTF8(s) {
+  return unescape(encodeURIComponent(s));
+}
+
+function convertToDataURL(s) {
+  return "data:application/octet-stream;base64," + btoa(s);
+}
+
+onmessage = function(message) {
+  is(FileReader.EMPTY, 0, "correct EMPTY value");
+  is(FileReader.LOADING, 1, "correct LOADING value");
+  is(FileReader.DONE, 2, "correct DONE value");
+
+  // List of blobs.
+  var asciiFile = message.data.blobs.shift();
+  var binaryFile = message.data.blobs.shift();
+  var nonExistingFile = message.data.blobs.shift();
+  var utf8TextFile = message.data.blobs.shift();
+  var utf16TextFile = message.data.blobs.shift();
+  var emptyFile = message.data.blobs.shift();
+  var dataUrlFile0 = message.data.blobs.shift();
+  var dataUrlFile1 = message.data.blobs.shift();
+  var dataUrlFile2 = message.data.blobs.shift();
+
+  // List of buffers for testing.
+  var testTextData = message.data.testTextData;
+  var testASCIIData = message.data.testASCIIData;
+  var testBinaryData = message.data.testBinaryData;
+  var dataurldata0 = message.data.dataurldata0;
+  var dataurldata1 = message.data.dataurldata1;
+  var dataurldata2 = message.data.dataurldata2;
+
+  // Test that plain reading works and fires events as expected, both
+  // for text and binary reading
+
+  var onloadHasRunText = false;
+  var onloadStartHasRunText = false;
+  r = new FileReader();
+  is(r.readyState, FileReader.EMPTY, "correct initial text readyState");
+  r.onload = getLoadHandler(testASCIIData, testASCIIData.length, "plain reading");
+  r.addEventListener("load", function() { onloadHasRunText = true }, false);
+  r.addEventListener("loadstart", function() { onloadStartHasRunText = true }, false);
+  r.readAsText(asciiFile);
+  is(r.readyState, FileReader.LOADING, "correct loading text readyState");
+  is(onloadHasRunText, false, "text loading must be async");
+  is(onloadStartHasRunText, true, "text loadstart should fire sync");
+  expectedTestCount++;
+
+  var onloadHasRunBinary = false;
+  var onloadStartHasRunBinary = false;
+  r = new FileReader();
+  is(r.readyState, FileReader.EMPTY, "correct initial binary readyState");
+  r.addEventListener("load", function() { onloadHasRunBinary = true }, false);
+  r.addEventListener("loadstart", function() { onloadStartHasRunBinary = true }, false);
+  r.readAsBinaryString(binaryFile);
+  r.onload = getLoadHandler(testBinaryData, testBinaryData.length, "binary reading");
+  is(r.readyState, FileReader.LOADING, "correct loading binary readyState");
+  is(onloadHasRunBinary, false, "binary loading must be async");
+  is(onloadStartHasRunBinary, true, "binary loadstart should fire sync");
+  expectedTestCount++;
+
+  var onloadHasRunArrayBuffer = false;
+  var onloadStartHasRunArrayBuffer = false;
+  r = new FileReader();
+  is(r.readyState, FileReader.EMPTY, "correct initial arrayBuffer readyState");
+  r.addEventListener("load", function() { onloadHasRunArrayBuffer = true }, false);
+  r.addEventListener("loadstart", function() { onloadStartHasRunArrayBuffer = true }, false);
+  r.readAsArrayBuffer(binaryFile);
+  r.onload = getLoadHandlerForArrayBuffer(testBinaryData, testBinaryData.length, "array buffer reading");
+  is(r.readyState, FileReader.LOADING, "correct loading arrayBuffer readyState");
+  is(onloadHasRunArrayBuffer, false, "arrayBuffer loading must be async");
+  is(onloadStartHasRunArrayBuffer, true, "arrayBuffer loadstart should fire sync");
+  expectedTestCount++;
+
+  // Test a variety of encodings, and make sure they work properly
+  r = new FileReader();
+  r.onload = getLoadHandler(testASCIIData, testASCIIData.length, "no encoding reading");
+  r.readAsText(asciiFile, "");
+  expectedTestCount++;
+
+  r = new FileReader();
+  r.onload = getLoadHandler(testASCIIData, testASCIIData.length, "iso8859 reading");
+  r.readAsText(asciiFile, "iso-8859-1");
+  expectedTestCount++;
+
+  r = new FileReader();
+  r.onload = getLoadHandler(testTextData,
+                            convertToUTF8(testTextData).length,
+                            "utf8 reading");
+  r.readAsText(utf8TextFile, "utf8");
+  expectedTestCount++;
+
+  r = new FileReader();
+  r.readAsText(utf16TextFile, "utf-16");
+  r.onload = getLoadHandler(testTextData,
+                            convertToUTF16(testTextData).length,
+                            "utf16 reading");
+  expectedTestCount++;
+
+  // Test get result without reading
+  r = new FileReader();
+  is(r.readyState, FileReader.EMPTY,
+     "readyState in test reader get result without reading");
+  is(r.error, null,
+     "no error in test reader get result without reading");
+  is(r.result, null,
+     "result in test reader get result without reading");
+
+  // Test loading an empty file works (and doesn't crash!)
+  r = new FileReader();
+  r.onload = getLoadHandler("", 0, "empty no encoding reading");
+  r.readAsText(emptyFile, "");
+  expectedTestCount++;
+
+  r = new FileReader();
+  r.onload = getLoadHandler("", 0, "empty utf8 reading");
+  r.readAsText(emptyFile, "utf8");
+  expectedTestCount++;
+
+  r = new FileReader();
+  r.onload = getLoadHandler("", 0, "empty utf16 reading");
+  r.readAsText(emptyFile, "utf-16");
+  expectedTestCount++;
+
+  r = new FileReader();
+  r.onload = getLoadHandler("", 0, "empty binary string reading");
+  r.readAsBinaryString(emptyFile);
+  expectedTestCount++;
+
+  r = new FileReader();
+  r.onload = getLoadHandlerForArrayBuffer("", 0, "empty array buffer reading");
+  r.readAsArrayBuffer(emptyFile);
+  expectedTestCount++;
+
+  r = new FileReader();
+  r.onload = getLoadHandler(convertToDataURL(""), 0, "empt binary string reading");
+  r.readAsDataURL(emptyFile);
+  expectedTestCount++;
+
+  // Test reusing a FileReader to read multiple times
+  r = new FileReader();
+  r.onload = getLoadHandler(testASCIIData,
+                            testASCIIData.length,
+                            "to-be-reused reading text")
+  var makeAnotherReadListener = function(event) {
+    r = event.target;
+    r.removeEventListener("load", makeAnotherReadListener, false);
+    r.onload = getLoadHandler(testASCIIData,
+                              testASCIIData.length,
+                              "reused reading text");
+    r.readAsText(asciiFile);
+  };
+  r.addEventListener("load", makeAnotherReadListener, false);
+  r.readAsText(asciiFile);
+  expectedTestCount += 2;
+
+  r = new FileReader();
+  r.onload = getLoadHandler(testBinaryData,
+                            testBinaryData.length,
+                            "to-be-reused reading binary")
+  var makeAnotherReadListener2 = function(event) {
+    r = event.target;
+    r.removeEventListener("load", makeAnotherReadListener2, false);
+    r.onload = getLoadHandler(testBinaryData,
+                              testBinaryData.length,
+                              "reused reading binary");
+    r.readAsBinaryString(binaryFile);
+  };
+  r.addEventListener("load", makeAnotherReadListener2, false);
+  r.readAsBinaryString(binaryFile);
+  expectedTestCount += 2;
+
+  r = new FileReader();
+  r.onload = getLoadHandler(convertToDataURL(testBinaryData),
+                            testBinaryData.length,
+                            "to-be-reused reading data url")
+  var makeAnotherReadListener3 = function(event) {
+    r = event.target;
+    r.removeEventListener("load", makeAnotherReadListener3, false);
+    r.onload = getLoadHandler(convertToDataURL(testBinaryData),
+                              testBinaryData.length,
+                              "reused reading data url");
+    r.readAsDataURL(binaryFile);
+  };
+  r.addEventListener("load", makeAnotherReadListener3, false);
+  r.readAsDataURL(binaryFile);
+  expectedTestCount += 2;
+
+  r = new FileReader();
+  r.onload = getLoadHandlerForArrayBuffer(testBinaryData,
+                                          testBinaryData.length,
+                                          "to-be-reused reading arrayBuffer")
+  var makeAnotherReadListener4 = function(event) {
+    r = event.target;
+    r.removeEventListener("load", makeAnotherReadListener4, false);
+    r.onload = getLoadHandlerForArrayBuffer(testBinaryData,
+                                            testBinaryData.length,
+                                            "reused reading arrayBuffer");
+    r.readAsArrayBuffer(binaryFile);
+  };
+  r.addEventListener("load", makeAnotherReadListener4, false);
+  r.readAsArrayBuffer(binaryFile);
+  expectedTestCount += 2;
+
+  // Test first reading as ArrayBuffer then read as something else
+  // (BinaryString) and doesn't crash
+  r = new FileReader();
+  r.onload = getLoadHandlerForArrayBuffer(testBinaryData,
+                                          testBinaryData.length,
+                                          "to-be-reused reading arrayBuffer")
+  var makeAnotherReadListener5 = function(event) {
+    r = event.target;
+    r.removeEventListener("load", makeAnotherReadListener5, false);
+    r.onload = getLoadHandler(testBinaryData,
+                              testBinaryData.length,
+                              "reused reading binary string");
+    r.readAsBinaryString(binaryFile);
+  };
+  r.addEventListener("load", makeAnotherReadListener5, false);
+  r.readAsArrayBuffer(binaryFile);
+  expectedTestCount += 2;
+
+  //Test data-URI encoding on differing file sizes
+  is(dataurldata0.length % 3, 0, "Want to test data with length % 3 == 0");
+  r = new FileReader();
+  r.onload = getLoadHandler(convertToDataURL(dataurldata0),
+                            dataurldata0.length,
+                            "dataurl reading, %3 = 0");
+  r.readAsDataURL(dataUrlFile0);
+  expectedTestCount++;
+
+  is(dataurldata1.length % 3, 1, "Want to test data with length % 3 == 1");
+  r = new FileReader();
+  r.onload = getLoadHandler(convertToDataURL(dataurldata1),
+                            dataurldata1.length,
+                            "dataurl reading, %3 = 1");
+  r.readAsDataURL(dataUrlFile1);
+  expectedTestCount++;
+
+  is(dataurldata2.length % 3, 2, "Want to test data with length % 3 == 2");
+  r = new FileReader();
+  r.onload = getLoadHandler(convertToDataURL(dataurldata2),
+                            dataurldata2.length,
+                            "dataurl reading, %3 = 2");
+  r.readAsDataURL(dataUrlFile2),
+  expectedTestCount++;
+
+
+  // Test abort()
+  var abortHasRun = false;
+  var loadEndHasRun = false;
+  r = new FileReader();
+  r.onabort = function (event) {
+    is(abortHasRun, false, "abort should only fire once");
+    is(loadEndHasRun, false, "loadend shouldn't have fired yet");
+    abortHasRun = true;
+    is(event.target.readyState, FileReader.DONE, "should be DONE while firing onabort");
+    is(event.target.error.name, "AbortError", "error set to AbortError for aborted reads");
+    is(event.target.result, null, "file data should be null on aborted reads");
+  }
+  r.onloadend = function (event) {
+    is(abortHasRun, true, "abort should fire before loadend");
+    is(loadEndHasRun, false, "loadend should only fire once");
+    loadEndHasRun = true;
+    is(event.target.readyState, FileReader.DONE, "should be DONE while firing onabort");
+    is(event.target.error.name, "AbortError", "error set to AbortError for aborted reads");
+    is(event.target.result, null, "file data should be null on aborted reads");
+  }
+  r.onload = function() { ok(false, "load should not fire for aborted reads") };
+  r.onerror = function() { ok(false, "error should not fire for aborted reads") };
+  r.onprogress = function() { ok(false, "progress should not fire for aborted reads") };
+  var abortThrew = false;
+  try {
+    r.abort();
+  } catch(e) {
+    abortThrew = true;
+  }
+  is(abortThrew, true, "abort() must throw if not loading");
+  is(abortHasRun, false, "abort() is a no-op unless loading");
+  r.readAsText(asciiFile);
+  r.abort();
+  is(abortHasRun, true, "abort should fire sync");
+  is(loadEndHasRun, true, "loadend should fire sync");
+
+  // Test calling readAsX to cause abort()
+  var reuseAbortHasRun = false;
+  r = new FileReader();
+  r.onabort = function (event) {
+    is(reuseAbortHasRun, false, "abort should only fire once");
+    reuseAbortHasRun = true;
+    is(event.target.readyState, FileReader.DONE, "should be DONE while firing onabort");
+    is(event.target.error.name, "AbortError", "error set to AbortError for aborted reads");
+    is(event.target.result, null, "file data should be null on aborted reads");
+  }
+  r.onload = function() { ok(false, "load should not fire for aborted reads") };
+  var abortThrew = false;
+  try {
+    r.abort();
+  } catch(e) {
+    abortThrew = true;
+  }
+  is(abortThrew, true, "abort() must throw if not loading");
+  is(reuseAbortHasRun, false, "abort() is a no-op unless loading");
+  r.readAsText(asciiFile);
+  r.readAsText(asciiFile);
+  is(reuseAbortHasRun, true, "abort should fire sync");
+  r.onload = getLoadHandler(testASCIIData, testASCIIData.length, "reuse-as-abort reading");
+  expectedTestCount++;
+
+
+  // Test reading from nonexistent files
+  r = new FileReader();
+  var didThrow = false;
+  r.onerror = function (event) {
+    is(event.target.readyState, FileReader.DONE, "should be DONE while firing onerror");
+    is(event.target.error.name, "NotFoundError", "error set to NotFoundError for nonexistent files");
+    is(event.target.result, null, "file data should be null on aborted reads");
+    testHasRun();
+  };
+  r.onload = function (event) {
+    is(false, "nonexistent file shouldn't load! (FIXME: bug 1122788)");
+    testHasRun();
+  };
+  try {
+    r.readAsDataURL(nonExistingFile);
+    expectedTestCount++;
+  } catch(ex) {
+    didThrow = true;
+  }
+  // Once this test passes, we should test that onerror gets called and
+  // that the FileReader object is in the right state during that call.
+  is(didThrow, false, "shouldn't throw when opening nonexistent file, should fire error instead");
+
+
+  function getLoadHandler(expectedResult, expectedLength, testName) {
+    return function (event) {
+      is(event.target.readyState, FileReader.DONE,
+         "readyState in test " + testName);
+      is(event.target.error, null,
+         "no error in test " + testName);
+      is(event.target.result, expectedResult,
+         "result in test " + testName);
+      is(event.lengthComputable, true,
+         "lengthComputable in test " + testName);
+      is(event.loaded, expectedLength,
+         "loaded in test " + testName);
+      is(event.total, expectedLength,
+         "total in test " + testName);
+      testHasRun();
+    }
+  }
+
+  function getLoadHandlerForArrayBuffer(expectedResult, expectedLength, testName) {
+    return function (event) {
+      is(event.target.readyState, FileReader.DONE,
+         "readyState in test " + testName);
+      is(event.target.error, null,
+         "no error in test " +  testName);
+      is(event.lengthComputable, true,
+         "lengthComputable in test " + testName);
+      is(event.loaded, expectedLength,
+         "loaded in test " + testName);
+      is(event.total, expectedLength,
+         "total in test " + testName);
+      is(event.target.result.byteLength, expectedLength,
+         "array buffer size in test " + testName);
+      var u8v = new Uint8Array(event.target.result);
+      is(String.fromCharCode.apply(String, u8v), expectedResult,
+         "array buffer contents in test " + testName);
+      u8v = null;
+      is(event.target.result.byteLength, expectedLength,
+         "array buffer size after gc in test " + testName);
+      u8v = new Uint8Array(event.target.result);
+      is(String.fromCharCode.apply(String, u8v), expectedResult,
+         "array buffer contents after gc in test " + testName);
+      testHasRun();
+    }
+  }
+
+  function testHasRun() {
+    //alert(testRanCounter);
+    ++testRanCounter;
+    if (testRanCounter == expectedTestCount) {
+      is(testSetupFinished, true, "test setup should have finished; check for exceptions");
+      is(onloadHasRunText, true, "onload text should have fired by now");
+      is(onloadHasRunBinary, true, "onload binary should have fired by now");
+      finish();
+    }
+  }
+
+  testSetupFinished = true;
+}
--- a/gfx/2d/2D.h
+++ b/gfx/2d/2D.h
@@ -312,18 +312,23 @@ public:
 
 class StoredPattern;
 class DrawTargetCaptureImpl;
 
 /**
  * This is the base class for source surfaces. These objects are surfaces
  * which may be used as a source in a SurfacePattern or a DrawSurface call.
  * They cannot be drawn to directly.
+ *
+ * Although SourceSurface has thread-safe refcount, some SourceSurface cannot
+ * be used on random threads at the same time. Only DataSourceSurface can be
+ * used on random threads now. This will be fixed in the future. Eventually
+ * all SourceSurface should be thread-safe.
  */
-class SourceSurface : public RefCounted<SourceSurface>
+class SourceSurface : public external::AtomicRefCounted<SourceSurface>
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurface)
   virtual ~SourceSurface() {}
 
   virtual SurfaceType GetType() const = 0;
   virtual IntSize GetSize() const = 0;
   virtual SurfaceFormat GetFormat() const = 0;
@@ -1222,16 +1227,21 @@ public:
    * wrap this data and the data for this source surface. The caller is
    * responsible for deallocating the memory only after destruction of the
    * surface.
    */
   static already_AddRefed<DataSourceSurface>
     CreateWrappingDataSourceSurface(uint8_t *aData, int32_t aStride,
                                     const IntSize &aSize, SurfaceFormat aFormat);
 
+  static void
+    CopyDataSourceSurface(DataSourceSurface* aSource,
+                          DataSourceSurface* aDest);
+
+
   static already_AddRefed<DrawEventRecorder>
     CreateEventRecorderForFile(const char *aFilename);
 
   static void SetGlobalEventRecorder(DrawEventRecorder *aRecorder);
 
   // This is a little hacky at the moment, but we want to have this data. Bug 1068613.
   static void SetLogForwarder(LogForwarder* aLogFwd);
 
--- a/gfx/2d/Factory.cpp
+++ b/gfx/2d/Factory.cpp
@@ -914,16 +914,107 @@ Factory::CreateDataSourceSurfaceWithStri
   if (newSurf->InitWithStride(aSize, aFormat, aStride, aZero)) {
     return newSurf.forget();
   }
 
   gfxCriticalError(LoggerOptionsBasedOnSize(aSize)) << "CreateDataSourceSurfaceWithStride failed to initialize " << aSize << ", " << aFormat << ", " << aStride << ", " << aZero;
   return nullptr;
 }
 
+static uint16_t
+PackRGB565(uint8_t r, uint8_t g, uint8_t b)
+{
+  uint16_t pixel = ((r << 11) & 0xf800) |
+                   ((g <<  5) & 0x07e0) |
+                   ((b      ) & 0x001f);
+
+  return pixel;
+}
+
+void
+Factory::CopyDataSourceSurface(DataSourceSurface* aSource,
+                               DataSourceSurface* aDest)
+{
+  // Don't worry too much about speed.
+  MOZ_ASSERT(aSource->GetSize() == aDest->GetSize());
+  MOZ_ASSERT(aSource->GetFormat() == SurfaceFormat::R8G8B8A8 ||
+             aSource->GetFormat() == SurfaceFormat::R8G8B8X8 ||
+             aSource->GetFormat() == SurfaceFormat::B8G8R8A8 ||
+             aSource->GetFormat() == SurfaceFormat::B8G8R8X8);
+  MOZ_ASSERT(aDest->GetFormat() == SurfaceFormat::R8G8B8A8 ||
+             aDest->GetFormat() == SurfaceFormat::R8G8B8X8 ||
+             aDest->GetFormat() == SurfaceFormat::B8G8R8A8 ||
+             aDest->GetFormat() == SurfaceFormat::B8G8R8X8 ||
+             aDest->GetFormat() == SurfaceFormat::R5G6B5_UINT16);
+
+  const bool isSrcBGR = aSource->GetFormat() == SurfaceFormat::B8G8R8A8 ||
+                        aSource->GetFormat() == SurfaceFormat::B8G8R8X8;
+  const bool isDestBGR = aDest->GetFormat() == SurfaceFormat::B8G8R8A8 ||
+                         aDest->GetFormat() == SurfaceFormat::B8G8R8X8;
+  const bool needsSwap02 = isSrcBGR != isDestBGR;
+
+  const bool srcHasAlpha = aSource->GetFormat() == SurfaceFormat::R8G8B8A8 ||
+                           aSource->GetFormat() == SurfaceFormat::B8G8R8A8;
+  const bool destHasAlpha = aDest->GetFormat() == SurfaceFormat::R8G8B8A8 ||
+                            aDest->GetFormat() == SurfaceFormat::B8G8R8A8;
+  const bool needsAlphaMask = !srcHasAlpha && destHasAlpha;
+
+  const bool needsConvertTo16Bits = aDest->GetFormat() == SurfaceFormat::R5G6B5_UINT16;
+
+  DataSourceSurface::MappedSurface srcMap;
+  DataSourceSurface::MappedSurface destMap;
+  if (!aSource->Map(DataSourceSurface::MapType::READ, &srcMap) ||
+    !aDest->Map(DataSourceSurface::MapType::WRITE, &destMap)) {
+    MOZ_ASSERT(false, "CopyDataSourceSurface: Failed to map surface.");
+    return;
+  }
+
+  MOZ_ASSERT(srcMap.mStride >= 0);
+  MOZ_ASSERT(destMap.mStride >= 0);
+
+  const size_t srcBPP = BytesPerPixel(aSource->GetFormat());
+  const size_t srcRowBytes = aSource->GetSize().width * srcBPP;
+  const size_t srcRowHole = srcMap.mStride - srcRowBytes;
+
+  const size_t destBPP = BytesPerPixel(aDest->GetFormat());
+  const size_t destRowBytes = aDest->GetSize().width * destBPP;
+  const size_t destRowHole = destMap.mStride - destRowBytes;
+
+  uint8_t* srcRow = srcMap.mData;
+  uint8_t* destRow = destMap.mData;
+  const size_t rows = aSource->GetSize().height;
+  for (size_t i = 0; i < rows; i++) {
+    const uint8_t* srcRowEnd = srcRow + srcRowBytes;
+
+    while (srcRow != srcRowEnd) {
+      uint8_t d0 = needsSwap02 ? srcRow[2] : srcRow[0];
+      uint8_t d1 = srcRow[1];
+      uint8_t d2 = needsSwap02 ? srcRow[0] : srcRow[2];
+      uint8_t d3 = needsAlphaMask ? 0xff : srcRow[3];
+
+      if (needsConvertTo16Bits) {
+        *(uint16_t*)destRow = PackRGB565(d0, d1, d2);
+      } else {
+        destRow[0] = d0;
+        destRow[1] = d1;
+        destRow[2] = d2;
+        destRow[3] = d3;
+      }
+      srcRow += srcBPP;
+      destRow += destBPP;
+    }
+
+    srcRow += srcRowHole;
+    destRow += destRowHole;
+  }
+
+  aSource->Unmap();
+  aDest->Unmap();
+}
+
 already_AddRefed<DrawEventRecorder>
 Factory::CreateEventRecorderForFile(const char *aFilename)
 {
   return MakeAndAddRef<DrawEventRecorderFile>(aFilename);
 }
 
 void
 Factory::SetGlobalEventRecorder(DrawEventRecorder *aRecorder)
--- a/gfx/gl/GLReadTexImageHelper.cpp
+++ b/gfx/gl/GLReadTexImageHelper.cpp
@@ -245,106 +245,16 @@ SwapRAndBComponents(DataSourceSurface* s
         }
 
         row += rowHole;
     }
 
     surf->Unmap();
 }
 
-static uint16_t
-PackRGB565(uint8_t r, uint8_t g, uint8_t b)
-{
-    uint16_t pixel = ((r << 11) & 0xf800) |
-                     ((g <<  5) & 0x07e0) |
-                     ((b      ) & 0x001f);
-
-    return pixel;
-}
-
-static void
-CopyDataSourceSurface(DataSourceSurface* aSource,
-                      DataSourceSurface* aDest)
-{
-    // Don't worry too much about speed.
-    MOZ_ASSERT(aSource->GetSize() == aDest->GetSize());
-    MOZ_ASSERT(aSource->GetFormat() == SurfaceFormat::R8G8B8A8 ||
-               aSource->GetFormat() == SurfaceFormat::R8G8B8X8 ||
-               aSource->GetFormat() == SurfaceFormat::B8G8R8A8 ||
-               aSource->GetFormat() == SurfaceFormat::B8G8R8X8);
-    MOZ_ASSERT(aDest->GetFormat() == SurfaceFormat::R8G8B8A8 ||
-               aDest->GetFormat() == SurfaceFormat::R8G8B8X8 ||
-               aDest->GetFormat() == SurfaceFormat::B8G8R8A8 ||
-               aDest->GetFormat() == SurfaceFormat::B8G8R8X8 ||
-               aDest->GetFormat() == SurfaceFormat::R5G6B5_UINT16);
-
-    const bool isSrcBGR = aSource->GetFormat() == SurfaceFormat::B8G8R8A8 ||
-                          aSource->GetFormat() == SurfaceFormat::B8G8R8X8;
-    const bool isDestBGR = aDest->GetFormat() == SurfaceFormat::B8G8R8A8 ||
-                           aDest->GetFormat() == SurfaceFormat::B8G8R8X8;
-    const bool needsSwap02 = isSrcBGR != isDestBGR;
-
-    const bool srcHasAlpha = aSource->GetFormat() == SurfaceFormat::R8G8B8A8 ||
-                             aSource->GetFormat() == SurfaceFormat::B8G8R8A8;
-    const bool destHasAlpha = aDest->GetFormat() == SurfaceFormat::R8G8B8A8 ||
-                              aDest->GetFormat() == SurfaceFormat::B8G8R8A8;
-    const bool needsAlphaMask = !srcHasAlpha && destHasAlpha;
-
-    const bool needsConvertTo16Bits = aDest->GetFormat() == SurfaceFormat::R5G6B5_UINT16;
-
-    DataSourceSurface::MappedSurface srcMap;
-    DataSourceSurface::MappedSurface destMap;
-    if (!aSource->Map(DataSourceSurface::MapType::READ, &srcMap) ||
-        !aDest->Map(DataSourceSurface::MapType::WRITE, &destMap)) {
-        MOZ_ASSERT(false, "CopyDataSourceSurface: Failed to map surface.");
-        return;
-    }
-    MOZ_ASSERT(srcMap.mStride >= 0);
-    MOZ_ASSERT(destMap.mStride >= 0);
-
-    const size_t srcBPP = BytesPerPixel(aSource->GetFormat());
-    const size_t srcRowBytes = aSource->GetSize().width * srcBPP;
-    const size_t srcRowHole = srcMap.mStride - srcRowBytes;
-
-    const size_t destBPP = BytesPerPixel(aDest->GetFormat());
-    const size_t destRowBytes = aDest->GetSize().width * destBPP;
-    const size_t destRowHole = destMap.mStride - destRowBytes;
-
-    uint8_t* srcRow = srcMap.mData;
-    uint8_t* destRow = destMap.mData;
-    const size_t rows = aSource->GetSize().height;
-    for (size_t i = 0; i < rows; i++) {
-        const uint8_t* srcRowEnd = srcRow + srcRowBytes;
-
-        while (srcRow != srcRowEnd) {
-            uint8_t d0 = needsSwap02 ? srcRow[2] : srcRow[0];
-            uint8_t d1 = srcRow[1];
-            uint8_t d2 = needsSwap02 ? srcRow[0] : srcRow[2];
-            uint8_t d3 = needsAlphaMask ? 0xff : srcRow[3];
-
-            if (needsConvertTo16Bits) {
-                *(uint16_t*)destRow = PackRGB565(d0, d1, d2);
-            } else {
-                destRow[0] = d0;
-                destRow[1] = d1;
-                destRow[2] = d2;
-                destRow[3] = d3;
-            }
-            srcRow += srcBPP;
-            destRow += destBPP;
-        }
-
-        srcRow += srcRowHole;
-        destRow += destRowHole;
-    }
-
-    aSource->Unmap();
-    aDest->Unmap();
-}
-
 static int
 CalcRowStride(int width, int pixelSize, int alignment)
 {
     MOZ_ASSERT(alignment);
 
     int rowStride = width * pixelSize;
     if (rowStride % alignment) { // Extra at the end of the line?
         int alignmentCount = rowStride / alignment;
@@ -495,17 +405,17 @@ ReadPixelsIntoDataSurface(GLContext* gl,
                     readSurf->GetData());
 
     if (currentPackAlignment != readAlignment)
         gl->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, currentPackAlignment);
 
     if (readSurf != dest) {
         MOZ_ASSERT(readFormat == LOCAL_GL_RGBA);
         MOZ_ASSERT(readType == LOCAL_GL_UNSIGNED_BYTE);
-        CopyDataSourceSurface(readSurf, dest);
+        gfx::Factory::CopyDataSourceSurface(readSurf, dest);
     }
 
     // Check if GL is giving back 1.0 alpha for
     // RGBA reads to RGBA images from no-alpha buffers.
 #ifdef XP_MACOSX
     if (gl->WorkAroundDriverBugs() &&
         gl->Vendor() == gl::GLVendor::NVIDIA &&
         hasAlpha &&
--- a/gfx/layers/ImageContainer.cpp
+++ b/gfx/layers/ImageContainer.cpp
@@ -544,28 +544,28 @@ PlanarYCbCrImage::GetAsSourceSurface()
 
   gfx::ConvertYCbCrToRGB(mData, format, size, mapping.GetData(), mapping.GetStride());
 
   mSourceSurface = surface;
 
   return surface.forget();
 }
 
-CairoImage::CairoImage(const gfx::IntSize& aSize, gfx::SourceSurface* aSourceSurface)
+SourceSurfaceImage::SourceSurfaceImage(const gfx::IntSize& aSize, gfx::SourceSurface* aSourceSurface)
   : Image(nullptr, ImageFormat::CAIRO_SURFACE),
     mSize(aSize),
     mSourceSurface(aSourceSurface)
 {}
 
-CairoImage::~CairoImage()
+SourceSurfaceImage::~SourceSurfaceImage()
 {
 }
 
 TextureClient*
-CairoImage::GetTextureClient(CompositableClient *aClient)
+SourceSurfaceImage::GetTextureClient(CompositableClient *aClient)
 {
   if (!aClient) {
     return nullptr;
   }
 
   CompositableForwarder* forwarder = aClient->GetForwarder();
   RefPtr<TextureClient> textureClient = mTextureClients.Get(forwarder->GetSerial());
   if (textureClient) {
--- a/gfx/layers/ImageContainer.h
+++ b/gfx/layers/ImageContainer.h
@@ -78,16 +78,60 @@ public:
   static void AddRef(RawRef aRawRef)
   {
     NS_ASSERTION(NS_IsMainThread(),
                  "Can only add a reference on the main thread");
     aRawRef->AddRef();
   }
 };
 
+class nsOwningThreadSourceSurfaceRef;
+
+template <>
+class nsAutoRefTraits<nsOwningThreadSourceSurfaceRef> {
+public:
+  typedef mozilla::gfx::SourceSurface* RawRef;
+
+  /**
+   * The XPCOM event that will do the actual release on the creation thread.
+   */
+  class SurfaceReleaser : public nsRunnable {
+  public:
+    explicit SurfaceReleaser(RawRef aRef) : mRef(aRef) {}
+    NS_IMETHOD Run() {
+      mRef->Release();
+      return NS_OK;
+    }
+    RawRef mRef;
+  };
+
+  static RawRef Void() { return nullptr; }
+  void Release(RawRef aRawRef)
+  {
+    MOZ_ASSERT(mOwningThread);<