/* -*- 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/DOMPrefs.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 aGivenProto) { return OffscreenCanvas_Binding::Wrap(aCx, this, aGivenProto); } /* static */ already_AddRefed OffscreenCanvas::Constructor( const GlobalObject& aGlobal, uint32_t aWidth, uint32_t aHeight, ErrorResult& aRv) { nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); RefPtr offscreenCanvas = new OffscreenCanvas( global, aWidth, aHeight, layers::LayersBackend::LAYERS_NONE, nullptr); return offscreenCanvas.forget(); } void OffscreenCanvas::ClearResources() { if (mCanvasClient) { mCanvasClient->Clear(); if (mCanvasRenderer) { nsCOMPtr activeTarget = mCanvasRenderer->GetActiveEventTarget(); MOZ_RELEASE_ASSERT(activeTarget, "GFX: failed to get active event target."); bool current; activeTarget->IsOnCurrentThread(¤t); MOZ_RELEASE_ASSERT(current, "GFX: active thread is not current thread."); mCanvasRenderer->SetCanvasClient(nullptr); mCanvasRenderer->mContext = nullptr; mCanvasRenderer->mGLContext = nullptr; mCanvasRenderer->ResetActiveEventTarget(); } mCanvasClient = nullptr; } } already_AddRefed OffscreenCanvas::GetContext( JSContext* aCx, const nsAString& aContextId, JS::Handle 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; } RefPtr result = CanvasRenderingContextHelper::GetContext( aCx, aContextId, aContextOptions, aRv); if (!mCurrentContext) { return nullptr; } if (mCanvasRenderer) { if (contextType == CanvasContextType::WebGL1 || contextType == CanvasContextType::WebGL2) { WebGLContext* webGL = static_cast(mCurrentContext.get()); gl::GLContext* gl = webGL->GL(); mCanvasRenderer->mContext = mCurrentContext; mCanvasRenderer->SetActiveEventTarget(); mCanvasRenderer->mGLContext = gl; mCanvasRenderer->SetIsAlphaPremultiplied(webGL->IsPremultAlpha() || !gl->Caps().alpha); if (RefPtr 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 factory = gl::GLScreenBuffer::CreateFactory(gl, caps, forwarder, flags); if (factory) screen->Morph(std::move(factory)); } } } return result.forget(); } already_AddRefed OffscreenCanvas::CreateContext(CanvasContextType aContextType) { RefPtr 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(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 OffscreenCanvas::TransferToImageBitmap( ErrorResult& aRv) { nsCOMPtr globalObject = GetGlobalObject(); RefPtr result = ImageBitmap::CreateFromOffscreenCanvas(globalObject, *this, aRv); if (aRv.Failed()) { return nullptr; } // TODO: Clear the content? return result.forget(); } already_AddRefed OffscreenCanvas::ToBlob(JSContext* aCx, const nsAString& aType, JS::Handle 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 global = GetGlobalObject(); RefPtr 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 aBlob) override { RefPtr blob = aBlob; if (mPromise) { RefPtr newBlob = Blob::Create(mGlobal, blob->Impl()); mPromise->MaybeResolve(newBlob); } mGlobal = nullptr; mPromise = nullptr; return NS_OK; } nsCOMPtr mGlobal; RefPtr mPromise; }; RefPtr callback = new EncodeCallback(global, promise); // TODO: Can we obtain the context and document here somehow // so that we can decide when usePlaceholder should be true/false? // See https://trac.torproject.org/18599 // For now, we always return a placeholder if fingerprinting resistance is on. bool usePlaceholder = nsContentUtils::ShouldResistFingerprinting(); CanvasRenderingContextHelper::ToBlob(aCx, global, callback, aType, aParams, usePlaceholder, aRv); return promise.forget(); } already_AddRefed OffscreenCanvas::GetSurfaceSnapshot( gfxAlphaType* const aOutAlphaType) { if (!mCurrentContext) { return nullptr; } return mCurrentContext->GetSurfaceSnapshot(aOutAlphaType); } nsCOMPtr OffscreenCanvas::GetGlobalObject() { if (NS_IsMainThread()) { return GetParentObject(); } dom::WorkerPrivate* workerPrivate = dom::GetCurrentThreadWorkerPrivate(); return workerPrivate->GlobalScope(); } /* static */ already_AddRefed OffscreenCanvas::CreateFromCloneData(nsIGlobalObject* aGlobal, OffscreenCanvasCloneData* aData) { MOZ_ASSERT(aData); RefPtr wc = new OffscreenCanvas(aGlobal, aData->mWidth, aData->mHeight, aData->mCompositorBackendType, aData->mRenderer); if (aData->mNeutered) { wc->SetNeutered(); } return wc.forget(); } /* static */ bool OffscreenCanvas::PrefEnabledOnWorkerThread(JSContext* aCx, JSObject* aObj) { if (NS_IsMainThread()) { return true; } return DOMPrefs::gfx_offscreencanvas_enabled(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(OffscreenCanvas) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) } // namespace dom } // namespace mozilla