/* -*- 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/BlobImpl.h" #include "mozilla/dom/OffscreenCanvasBinding.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/WorkerPrivate.h" #include "mozilla/dom/WorkerScope.h" #include "mozilla/layers/CanvasRenderer.h" #include "mozilla/layers/CanvasClient.h" #include "mozilla/layers/ImageBridgeChild.h" #include "mozilla/Telemetry.h" #include "CanvasRenderingContext2D.h" #include "CanvasUtils.h" #include "GLContext.h" #include "GLScreenBuffer.h" #include "ImageBitmap.h" namespace mozilla::dom { OffscreenCanvasCloneData::OffscreenCanvasCloneData( layers::CanvasRenderer* 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() = default; OffscreenCanvas::OffscreenCanvas(nsIGlobalObject* aGlobal, uint32_t aWidth, uint32_t aHeight, layers::LayersBackend aCompositorBackend, layers::CanvasRenderer* 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) { 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(); MOZ_CRASH("todo"); // 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::WebGPU || 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) { // mCanvasRenderer->SetContextType(contextType); if (contextType == CanvasContextType::WebGL1 || contextType == CanvasContextType::WebGL2) { MOZ_ASSERT_UNREACHABLE("WebGL OffscreenCanvas not yet supported."); return nullptr; } if (contextType == CanvasContextType::WebGPU) { MOZ_ASSERT_UNREACHABLE("WebGPU OffscreenCanvas not yet supported."); return nullptr; } } return result.forget(); } layers::ImageContainer* OffscreenCanvas::GetImageContainer() { if (!mCanvasRenderer) { return nullptr; } // return mCanvasRenderer->GetImageContainer(); MOZ_CRASH("todo"); } 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; } MOZ_CRASH("todo"); // The attributes has changed, we have to notify main // thread to change canvas size. if (mAttrDirty) { MOZ_CRASH("todo"); // if (mCanvasRenderer) { // mCanvasRenderer->SetWidth(mWidth); // mCanvasRenderer->SetHeight(mHeight); // mCanvasRenderer->NotifyElementAboutAttributesChanged(); //} mAttrDirty = false; } // CanvasContextType contentType = mCanvasRenderer->GetContextType(); // if (mCurrentContext && (contentType == CanvasContextType::WebGL1 || // contentType == CanvasContextType::WebGL2)) { // MOZ_ASSERT_UNREACHABLE("WebGL OffscreenCanvas not yet supported."); // return; //} // if (mCurrentContext && (contentType == CanvasContextType::WebGPU)) { // MOZ_ASSERT_UNREACHABLE("WebGPU OffscreenCanvas not yet supported."); // return; //} // 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 ReceiveBlobImpl(already_AddRefed aBlobImpl) override { RefPtr blobImpl = aBlobImpl; if (mPromise) { RefPtr blob = Blob::Create(mGlobal, blobImpl); if (NS_WARN_IF(!blob)) { mPromise->MaybeReject(NS_ERROR_FAILURE); } else { mPromise->MaybeResolve(blob); } } mGlobal = nullptr; mPromise = nullptr; return NS_OK; } nsCOMPtr mGlobal; RefPtr mPromise; }; RefPtr callback = new EncodeCallback(global, promise); bool usePlaceholder; if (NS_IsMainThread()) { nsCOMPtr window = do_QueryInterface(GetGlobalObject()); Document* doc = window->GetExtantDoc(); usePlaceholder = doc ? nsContentUtils::ShouldResistFingerprinting(doc) : false; } else { dom::WorkerPrivate* workerPrivate = dom::GetCurrentThreadWorkerPrivate(); usePlaceholder = nsContentUtils::ShouldResistFingerprinting(workerPrivate); } 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 StaticPrefs::gfx_offscreencanvas_enabled(); } 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 mozilla::dom