diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /dom/canvas/CanvasRenderingContextHelper.cpp | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/canvas/CanvasRenderingContextHelper.cpp')
-rw-r--r-- | dom/canvas/CanvasRenderingContextHelper.cpp | 321 |
1 files changed, 321 insertions, 0 deletions
diff --git a/dom/canvas/CanvasRenderingContextHelper.cpp b/dom/canvas/CanvasRenderingContextHelper.cpp new file mode 100644 index 0000000000..d45ad8883e --- /dev/null +++ b/dom/canvas/CanvasRenderingContextHelper.cpp @@ -0,0 +1,321 @@ +/* -*- 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 "GLContext.h" +#include "ImageBitmapRenderingContext.h" +#include "ImageEncoder.h" +#include "mozilla/dom/BlobImpl.h" +#include "mozilla/dom/CanvasRenderingContext2D.h" +#include "mozilla/dom/OffscreenCanvasRenderingContext2D.h" +#include "mozilla/GfxMessageUtils.h" +#include "mozilla/Telemetry.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/webgpu/CanvasContext.h" +#include "MozFramebuffer.h" +#include "nsContentUtils.h" +#include "nsDOMJSUtils.h" +#include "nsIScriptContext.h" +#include "nsJSUtils.h" +#include "ClientWebGLContext.h" + +namespace mozilla::dom { + +CanvasRenderingContextHelper::CanvasRenderingContextHelper() + : mCurrentContextType(CanvasContextType::NoContext) {} + +void CanvasRenderingContextHelper::ToBlob( + JSContext* aCx, nsIGlobalObject* aGlobal, BlobCallback& aCallback, + const nsAString& aType, JS::Handle<JS::Value> aParams, bool aUsePlaceholder, + ErrorResult& aRv) { + // Encoder callback when encoding is complete. + class EncodeCallback : public EncodeCompleteCallback { + public: + EncodeCallback(nsIGlobalObject* aGlobal, BlobCallback* aCallback) + : mGlobal(aGlobal), mBlobCallback(aCallback) {} + + // This is called on main thread. + MOZ_CAN_RUN_SCRIPT + nsresult ReceiveBlobImpl(already_AddRefed<BlobImpl> aBlobImpl) override { + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr<BlobImpl> blobImpl = aBlobImpl; + + RefPtr<Blob> blob; + + if (blobImpl) { + blob = Blob::Create(mGlobal, blobImpl); + } + + RefPtr<BlobCallback> callback(std::move(mBlobCallback)); + ErrorResult rv; + + callback->Call(blob, rv); + + mGlobal = nullptr; + MOZ_ASSERT(!mBlobCallback); + + return rv.StealNSResult(); + } + + bool CanBeDeletedOnAnyThread() override { + // EncodeCallback is used from the main thread only. + return false; + } + + nsCOMPtr<nsIGlobalObject> mGlobal; + RefPtr<BlobCallback> mBlobCallback; + }; + + RefPtr<EncodeCompleteCallback> callback = + new EncodeCallback(aGlobal, &aCallback); + + ToBlob(aCx, callback, aType, aParams, aUsePlaceholder, aRv); +} + +void CanvasRenderingContextHelper::ToBlob( + JSContext* aCx, EncodeCompleteCallback* aCallback, const nsAString& aType, + JS::Handle<JS::Value> aParams, bool aUsePlaceholder, ErrorResult& aRv) { + nsAutoString type; + nsContentUtils::ASCIIToLower(aType, type); + + nsAutoString params; + bool usingCustomParseOptions; + aRv = ParseParams(aCx, type, aParams, params, &usingCustomParseOptions); + if (aRv.Failed()) { + return; + } + + ToBlob(aCallback, type, params, usingCustomParseOptions, aUsePlaceholder, + aRv); +} + +void CanvasRenderingContextHelper::ToBlob(EncodeCompleteCallback* aCallback, + nsAString& aType, + const nsAString& aEncodeOptions, + bool aUsingCustomOptions, + bool aUsePlaceholder, + ErrorResult& aRv) { + const nsIntSize elementSize = GetWidthHeight(); + 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. + 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; + auto imageSize = gfx::IntSize{elementSize.width, elementSize.height}; + if (mCurrentContext) { + imageBuffer = mCurrentContext->GetImageBuffer(&format, &imageSize); + } + + RefPtr<EncodeCompleteCallback> callback = aCallback; + + aRv = ImageEncoder::ExtractDataAsync( + aType, aEncodeOptions, aUsingCustomOptions, std::move(imageBuffer), + format, {imageSize.width, imageSize.height}, aUsePlaceholder, callback); +} + +already_AddRefed<nsICanvasRenderingContextInternal> +CanvasRenderingContextHelper::CreateContext(CanvasContextType aContextType) { + return CreateContextHelper(aContextType, layers::LayersBackend::LAYERS_NONE); +} + +already_AddRefed<nsICanvasRenderingContextInternal> +CanvasRenderingContextHelper::CreateContextHelper( + CanvasContextType aContextType, layers::LayersBackend aCompositorBackend) { + MOZ_ASSERT(aContextType != CanvasContextType::NoContext); + RefPtr<nsICanvasRenderingContextInternal> ret; + + switch (aContextType) { + case CanvasContextType::NoContext: + break; + + case CanvasContextType::Canvas2D: + Telemetry::Accumulate(Telemetry::CANVAS_2D_USED, 1); + ret = new CanvasRenderingContext2D(aCompositorBackend); + break; + + case CanvasContextType::OffscreenCanvas2D: + Telemetry::Accumulate(Telemetry::CANVAS_2D_USED, 1); + ret = new OffscreenCanvasRenderingContext2D(aCompositorBackend); + break; + + case CanvasContextType::WebGL1: + Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED, 1); + + ret = new ClientWebGLContext(/*webgl2:*/ false); + + break; + + case CanvasContextType::WebGL2: + Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED, 1); + + ret = new ClientWebGLContext(/*webgl2:*/ true); + + break; + + case CanvasContextType::WebGPU: + // TODO + // Telemetry::Accumulate(Telemetry::CANVAS_WEBGPU_USED, 1); + + ret = new webgpu::CanvasContext(); + + break; + + case CanvasContextType::ImageBitmap: + ret = new ImageBitmapRenderingContext(); + + break; + } + MOZ_ASSERT(ret); + + ret->Initialize(); + return ret.forget(); +} + +already_AddRefed<nsISupports> CanvasRenderingContextHelper::GetOrCreateContext( + JSContext* aCx, const nsAString& aContextId, + JS::Handle<JS::Value> aContextOptions, ErrorResult& aRv) { + CanvasContextType contextType; + if (!CanvasUtils::GetCanvasContextType(aContextId, &contextType)) + return nullptr; + + return GetOrCreateContext(aCx, contextType, aContextOptions, aRv); +} + +already_AddRefed<nsISupports> CanvasRenderingContextHelper::GetOrCreateContext( + JSContext* aCx, CanvasContextType aContextType, + JS::Handle<JS::Value> aContextOptions, ErrorResult& aRv) { + if (!mCurrentContext) { + // This canvas doesn't have a context yet. + RefPtr<nsICanvasRenderingContextInternal> context; + context = CreateContext(aContextType); + if (!context) { + return nullptr; + } + + // Ensure that the context participates in CC. Note that returning a + // CC participant from QI doesn't addref. + nsXPCOMCycleCollectionParticipant* cp = nullptr; + CallQueryInterface(context, &cp); + if (!cp) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + mCurrentContext = std::move(context); + mCurrentContextType = aContextType; + + // https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-getcontext-dev + // Step 1. If options is not an object, then set options to null. + JS::Rooted<JS::Value> options(RootingCx(), aContextOptions); + if (!options.isObject()) { + options.setNull(); + } + + nsresult rv = UpdateContext(aCx, options, aRv); + if (NS_FAILED(rv)) { + // See bug 645792 and bug 1215072. + // We want to throw only if dictionary initialization fails, + // so only in case aRv has been set to some error value. + if (aContextType == CanvasContextType::WebGL1) { + Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_SUCCESS, 0); + } else if (aContextType == CanvasContextType::WebGL2) { + Telemetry::Accumulate(Telemetry::CANVAS_WEBGL2_SUCCESS, 0); + } else if (aContextType == CanvasContextType::WebGPU) { + // Telemetry::Accumulate(Telemetry::CANVAS_WEBGPU_SUCCESS, 0); + } + return nullptr; + } + if (aContextType == CanvasContextType::WebGL1) { + Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_SUCCESS, 1); + } else if (aContextType == CanvasContextType::WebGL2) { + Telemetry::Accumulate(Telemetry::CANVAS_WEBGL2_SUCCESS, 1); + } else if (aContextType == CanvasContextType::WebGPU) { + // Telemetry::Accumulate(Telemetry::CANVAS_WEBGPU_SUCCESS, 1); + } + } else { + // We already have a context of some type. + if (aContextType != mCurrentContextType) return nullptr; + } + + nsCOMPtr<nsICanvasRenderingContextInternal> context = mCurrentContext; + return context.forget(); +} + +nsresult CanvasRenderingContextHelper::UpdateContext( + JSContext* aCx, JS::Handle<JS::Value> aNewContextOptions, + ErrorResult& aRvForDictionaryInit) { + if (!mCurrentContext) return NS_OK; + + nsIntSize sz = GetWidthHeight(); + + nsCOMPtr<nsICanvasRenderingContextInternal> currentContext = mCurrentContext; + + currentContext->SetOpaqueValueFromOpaqueAttr(GetOpaqueAttr()); + + nsresult rv = currentContext->SetContextOptions(aCx, aNewContextOptions, + aRvForDictionaryInit); + if (NS_FAILED(rv)) { + mCurrentContext = nullptr; + return rv; + } + + rv = currentContext->SetDimensions(sz.width, sz.height); + if (NS_FAILED(rv)) { + mCurrentContext = nullptr; + } + + return rv; +} + +nsresult CanvasRenderingContextHelper::ParseParams( + JSContext* aCx, const nsAString& aType, const JS::Value& aEncoderOptions, + nsAString& outParams, bool* const outUsingCustomParseOptions) { + // Quality parameter is only valid for the image/jpeg and image/webp MIME + // types. + if (aType.EqualsLiteral("image/jpeg") || aType.EqualsLiteral("image/webp")) { + if (aEncoderOptions.isNumber()) { + double quality = aEncoderOptions.toNumber(); + // Quality must be between 0.0 and 1.0, inclusive + if (quality >= 0.0 && quality <= 1.0) { + outParams.AppendLiteral("quality="); + outParams.AppendInt(NS_lround(quality * 100.0)); + } + } + } + + // If we haven't parsed the aParams check for proprietary options. + // The proprietary option -moz-parse-options will take a image lib encoder + // parse options string as is and pass it to the encoder. + *outUsingCustomParseOptions = false; + if (outParams.Length() == 0 && aEncoderOptions.isString()) { + constexpr auto mozParseOptions = u"-moz-parse-options:"_ns; + nsAutoJSString paramString; + if (!paramString.init(aCx, aEncoderOptions.toString())) { + return NS_ERROR_FAILURE; + } + if (StringBeginsWith(paramString, mozParseOptions)) { + nsDependentSubstring parseOptions = + Substring(paramString, mozParseOptions.Length(), + paramString.Length() - mozParseOptions.Length()); + outParams.Append(parseOptions); + *outUsingCustomParseOptions = true; + } + } + + return NS_OK; +} + +} // namespace mozilla::dom |