diff options
Diffstat (limited to 'dom/canvas/OffscreenCanvas.cpp')
-rw-r--r-- | dom/canvas/OffscreenCanvas.cpp | 371 |
1 files changed, 371 insertions, 0 deletions
diff --git a/dom/canvas/OffscreenCanvas.cpp b/dom/canvas/OffscreenCanvas.cpp new file mode 100644 index 0000000000..0d188c24e3 --- /dev/null +++ b/dom/canvas/OffscreenCanvas.cpp @@ -0,0 +1,371 @@ +/* -*- 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 "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 aIsWriteOnly) + : mRenderer(aRenderer) + , mWidth(aWidth) + , mHeight(aHeight) + , mCompositorBackendType(aCompositorBackend) + , mNeutered(aNeutered) + , mIsWriteOnly(aIsWriteOnly) +{ +} + +OffscreenCanvasCloneData::~OffscreenCanvasCloneData() +{ +} + +OffscreenCanvas::OffscreenCanvas(nsIGlobalObject* aGlobal, + uint32_t aWidth, + uint32_t aHeight, + layers::LayersBackend aCompositorBackend, + layers::AsyncCanvasRenderer* aRenderer) + : DOMEventTargetHelper(aGlobal) + , mAttrDirty(false) + , mNeutered(false) + , mIsWriteOnly(false) + , mWidth(aWidth) + , mHeight(aHeight) + , mCompositorBackendType(aCompositorBackend) + , mCanvasRenderer(aRenderer) +{} + +OffscreenCanvas::~OffscreenCanvas() +{ + ClearResources(); +} + +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(); + + if (mCanvasRenderer) { + nsCOMPtr<nsIThread> activeThread = mCanvasRenderer->GetActiveThread(); + MOZ_RELEASE_ASSERT(activeThread, "GFX: failed to get active thread."); + bool current; + activeThread->IsOnCurrentThread(¤t); + MOZ_RELEASE_ASSERT(current, "GFX: active thread is not current thread."); + mCanvasRenderer->SetCanvasClient(nullptr); + mCanvasRenderer->mContext = nullptr; + mCanvasRenderer->mGLContext = nullptr; + mCanvasRenderer->ResetActiveThread(); + } + + mCanvasClient = nullptr; + } +} + +already_AddRefed<nsISupports> +OffscreenCanvas::GetContext(JSContext* aCx, + const nsAString& aContextId, + JS::Handle<JS::Value> aContextOptions, + ErrorResult& aRv) +{ + if (mNeutered) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + // 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::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) { + 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 (RefPtr<ImageBridgeChild> imageBridge = ImageBridgeChild::GetSingleton()) { + TextureFlags flags = TextureFlags::ORIGIN_BOTTOM_LEFT; + mCanvasClient = imageBridge->CreateCanvasClient(CanvasClient::CanvasClientTypeShSurf, flags); + mCanvasRenderer->SetCanvasClient(mCanvasClient); + + 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); + + if (factory) + screen->Morph(Move(factory)); + } + } + } + + return result; +} + +already_AddRefed<nsICanvasRenderingContextInternal> +OffscreenCanvas::CreateContext(CanvasContextType aContextType) +{ + RefPtr<nsICanvasRenderingContextInternal> ret = + CanvasRenderingContextHelper::CreateContext(aContextType); + + 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(); + } + mAttrDirty = false; + } + + if (mCurrentContext) { + static_cast<WebGLContext*>(mCurrentContext.get())->PresentScreenBuffer(); + } + + if (mCanvasRenderer && mCanvasRenderer->mGLContext) { + mCanvasRenderer->NotifyElementAboutInvalidation(); + ImageBridgeChild::GetSingleton()-> + UpdateAsyncCanvasRenderer(mCanvasRenderer); + } +} + +OffscreenCanvasCloneData* +OffscreenCanvas::ToCloneData() +{ + return new OffscreenCanvasCloneData(mCanvasRenderer, mWidth, mHeight, + mCompositorBackendType, mNeutered, mIsWriteOnly); +} + +already_AddRefed<ImageBitmap> +OffscreenCanvas::TransferToImageBitmap() +{ + ErrorResult rv; + nsCOMPtr<nsIGlobalObject> globalObject = GetGlobalObject(); + RefPtr<ImageBitmap> result = ImageBitmap::CreateFromOffscreenCanvas(globalObject, *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(nsIGlobalObject* aGlobal, OffscreenCanvasCloneData* aData) +{ + MOZ_ASSERT(aData); + RefPtr<OffscreenCanvas> wc = + new OffscreenCanvas(aGlobal, aData->mWidth, aData->mHeight, + aData->mCompositorBackendType, aData->mRenderer); + if (aData->mNeutered) { + wc->SetNeutered(); + } + return wc.forget(); +} + +/* static */ bool +OffscreenCanvas::PrefEnabled(JSContext* aCx, JSObject* aObj) +{ + if (NS_IsMainThread()) { + return Preferences::GetBool("gfx.offscreencanvas.enabled"); + } else { + WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx); + MOZ_ASSERT(workerPrivate); + return workerPrivate->OffscreenCanvasEnabled(); + } +} + +/* static */ bool +OffscreenCanvas::PrefEnabledOnWorkerThread(JSContext* aCx, JSObject* aObj) +{ + if (NS_IsMainThread()) { + return true; + } + + return PrefEnabled(aCx, aObj); +} + +NS_IMPL_CYCLE_COLLECTION_INHERITED(OffscreenCanvas, DOMEventTargetHelper, mCurrentContext) + +NS_IMPL_ADDREF_INHERITED(OffscreenCanvas, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(OffscreenCanvas, DOMEventTargetHelper) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(OffscreenCanvas) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +} // namespace dom +} // namespace mozilla |