summaryrefslogtreecommitdiffstats
path: root/dom/canvas/CanvasRenderingContextHelper.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/canvas/CanvasRenderingContextHelper.cpp')
-rw-r--r--dom/canvas/CanvasRenderingContextHelper.cpp321
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