tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

CanvasRenderingContextHelper.cpp (10765B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include "CanvasRenderingContextHelper.h"
      7 
      8 #include "ClientWebGLContext.h"
      9 #include "GLContext.h"
     10 #include "ImageBitmapRenderingContext.h"
     11 #include "ImageEncoder.h"
     12 #include "MozFramebuffer.h"
     13 #include "mozilla/GfxMessageUtils.h"
     14 #include "mozilla/UniquePtr.h"
     15 #include "mozilla/dom/CanvasRenderingContext2D.h"
     16 #include "mozilla/dom/OffscreenCanvasRenderingContext2D.h"
     17 #include "mozilla/glean/DomCanvasMetrics.h"
     18 #include "mozilla/webgpu/CanvasContext.h"
     19 #include "nsContentUtils.h"
     20 #include "nsDOMJSUtils.h"
     21 #include "nsIScriptContext.h"
     22 #include "nsJSUtils.h"
     23 
     24 namespace mozilla::dom {
     25 
     26 CanvasRenderingContextHelper::CanvasRenderingContextHelper()
     27    : mCurrentContextType(CanvasContextType::NoContext) {}
     28 
     29 void CanvasRenderingContextHelper::ToBlob(
     30    JSContext* aCx, EncodeCompleteCallback* aCallback, const nsAString& aType,
     31    JS::Handle<JS::Value> aParams,
     32    CanvasUtils::ImageExtraction aExtractionBehavior, ErrorResult& aRv) {
     33  nsAutoString type;
     34  nsContentUtils::ASCIIToLower(aType, type);
     35 
     36  nsAutoString params;
     37  bool usingCustomParseOptions;
     38  aRv = ParseParams(aCx, type, aParams, params, &usingCustomParseOptions);
     39  if (aRv.Failed()) {
     40    return;
     41  }
     42 
     43  ToBlob(aCallback, type, params, usingCustomParseOptions, aExtractionBehavior,
     44         aRv);
     45 }
     46 
     47 void CanvasRenderingContextHelper::ToBlob(
     48    EncodeCompleteCallback* aCallback, nsAString& aType,
     49    const nsAString& aEncodeOptions, bool aUsingCustomOptions,
     50    CanvasUtils::ImageExtraction aExtractionBehavior, ErrorResult& aRv) {
     51  const CSSIntSize elementSize = GetWidthHeight();
     52  if (mCurrentContext) {
     53    // We disallow canvases of width or height zero, and set them to 1, so
     54    // we will have a discrepancy with the sizes of the canvas and the context.
     55    // That discrepancy is OK, the rest are not.
     56    if ((elementSize.width != mCurrentContext->GetWidth() &&
     57         (elementSize.width != 0 || mCurrentContext->GetWidth() != 1)) ||
     58        (elementSize.height != mCurrentContext->GetHeight() &&
     59         (elementSize.height != 0 || mCurrentContext->GetHeight() != 1))) {
     60      aRv.Throw(NS_ERROR_FAILURE);
     61      return;
     62    }
     63  }
     64 
     65  nsCString randomizationKeyStr = VoidCString();
     66  if (aExtractionBehavior == CanvasUtils::ImageExtraction::EfficientRandomize) {
     67    nsRFPService::GetFingerprintingRandomizationKeyAsString(
     68        GetCookieJarSettings(), randomizationKeyStr);
     69  }
     70 
     71  int32_t format = 0;
     72  auto imageSize = gfx::IntSize{elementSize.width, elementSize.height};
     73  UniquePtr<uint8_t[]> imageBuffer =
     74      GetImageBuffer(aExtractionBehavior, &format, &imageSize);
     75  RefPtr<EncodeCompleteCallback> callback = aCallback;
     76 
     77  aRv = ImageEncoder::ExtractDataAsync(
     78      aType, aEncodeOptions, aUsingCustomOptions, std::move(imageBuffer),
     79      format, CSSIntSize::FromUnknownSize(imageSize), aExtractionBehavior,
     80      randomizationKeyStr, callback);
     81 }
     82 
     83 UniquePtr<uint8_t[]> CanvasRenderingContextHelper::GetImageBuffer(
     84    CanvasUtils::ImageExtraction aExtractionBehavior, int32_t* aOutFormat,
     85    gfx::IntSize* aOutImageSize) {
     86  if (mCurrentContext) {
     87    return mCurrentContext->GetImageBuffer(aExtractionBehavior, aOutFormat,
     88                                           aOutImageSize);
     89  }
     90  return nullptr;
     91 }
     92 
     93 nsICookieJarSettings* CanvasRenderingContextHelper::GetCookieJarSettings()
     94    const {
     95  if (mCurrentContext) {
     96    return mCurrentContext->GetCookieJarSettings();
     97  }
     98  return nullptr;
     99 }
    100 
    101 already_AddRefed<nsICanvasRenderingContextInternal>
    102 CanvasRenderingContextHelper::CreateContext(CanvasContextType aContextType) {
    103  return CreateContextHelper(aContextType, layers::LayersBackend::LAYERS_NONE);
    104 }
    105 
    106 already_AddRefed<nsICanvasRenderingContextInternal>
    107 CanvasRenderingContextHelper::CreateContextHelper(
    108    CanvasContextType aContextType, layers::LayersBackend aCompositorBackend) {
    109  MOZ_ASSERT(aContextType != CanvasContextType::NoContext);
    110  RefPtr<nsICanvasRenderingContextInternal> ret;
    111 
    112  switch (aContextType) {
    113    case CanvasContextType::NoContext:
    114      break;
    115 
    116    case CanvasContextType::Canvas2D:
    117      glean::canvas::used_2d.EnumGet(glean::canvas::Used2dLabel::eTrue).Add();
    118      ret = new CanvasRenderingContext2D(aCompositorBackend);
    119      break;
    120 
    121    case CanvasContextType::OffscreenCanvas2D:
    122      glean::canvas::used_2d.EnumGet(glean::canvas::Used2dLabel::eTrue).Add();
    123      ret = new OffscreenCanvasRenderingContext2D(aCompositorBackend);
    124      break;
    125 
    126    case CanvasContextType::WebGL1:
    127      glean::canvas::webgl_used.EnumGet(glean::canvas::WebglUsedLabel::eTrue)
    128          .Add();
    129 
    130      ret = new ClientWebGLContext(/*webgl2:*/ false);
    131 
    132      break;
    133 
    134    case CanvasContextType::WebGL2:
    135      glean::canvas::webgl_used.EnumGet(glean::canvas::WebglUsedLabel::eTrue)
    136          .Add();
    137 
    138      ret = new ClientWebGLContext(/*webgl2:*/ true);
    139 
    140      break;
    141 
    142    case CanvasContextType::WebGPU:
    143      // TODO
    144      // Telemetry::Accumulate(Telemetry::CANVAS_WEBGPU_USED, 1);
    145 
    146      ret = new webgpu::CanvasContext();
    147 
    148      break;
    149 
    150    case CanvasContextType::ImageBitmap:
    151      ret = new ImageBitmapRenderingContext();
    152 
    153      break;
    154  }
    155  MOZ_ASSERT(ret);
    156 
    157  if (NS_WARN_IF(NS_FAILED(ret->Initialize()))) {
    158    return nullptr;
    159  }
    160  return ret.forget();
    161 }
    162 
    163 already_AddRefed<nsISupports> CanvasRenderingContextHelper::GetOrCreateContext(
    164    JSContext* aCx, const nsAString& aContextId,
    165    JS::Handle<JS::Value> aContextOptions, ErrorResult& aRv) {
    166  CanvasContextType contextType;
    167  if (!CanvasUtils::GetCanvasContextType(aContextId, &contextType))
    168    return nullptr;
    169 
    170  return GetOrCreateContext(aCx, contextType, aContextOptions, aRv);
    171 }
    172 
    173 already_AddRefed<nsISupports> CanvasRenderingContextHelper::GetOrCreateContext(
    174    JSContext* aCx, CanvasContextType aContextType,
    175    JS::Handle<JS::Value> aContextOptions, ErrorResult& aRv) {
    176  if (!mCurrentContext) {
    177    // This canvas doesn't have a context yet.
    178    RefPtr<nsICanvasRenderingContextInternal> context;
    179    context = CreateContext(aContextType);
    180    if (!context) {
    181      aRv.ThrowUnknownError("Failed to create context");
    182      return nullptr;
    183    }
    184 
    185    // Ensure that the context participates in CC.  Note that returning a
    186    // CC participant from QI doesn't addref.
    187    nsXPCOMCycleCollectionParticipant* cp = nullptr;
    188    CallQueryInterface(context, &cp);
    189    if (!cp) {
    190      aRv.Throw(NS_ERROR_FAILURE);
    191      return nullptr;
    192    }
    193 
    194    mCurrentContext = std::move(context);
    195    mCurrentContextType = aContextType;
    196 
    197    // https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-getcontext-dev
    198    // Step 1. If options is not an object, then set options to null.
    199    JS::Rooted<JS::Value> options(RootingCx(), aContextOptions);
    200    if (!options.isObject()) {
    201      options.setNull();
    202    }
    203 
    204    nsresult rv = UpdateContext(aCx, options, aRv);
    205    if (NS_FAILED(rv)) {
    206      // See bug 645792 and bug 1215072.
    207      // We want to throw only if dictionary initialization fails,
    208      // so only in case aRv has been set to some error value.
    209      if (aContextType == CanvasContextType::WebGL1) {
    210        glean::canvas::webgl_success
    211            .EnumGet(glean::canvas::WebglSuccessLabel::eFalse)
    212            .Add();
    213      } else if (aContextType == CanvasContextType::WebGL2) {
    214        glean::canvas::webgl2_success
    215            .EnumGet(glean::canvas::Webgl2SuccessLabel::eFalse)
    216            .Add();
    217      } else if (aContextType == CanvasContextType::WebGPU) {
    218        // Telemetry::Accumulate(Telemetry::CANVAS_WEBGPU_SUCCESS, 0);
    219      }
    220      return nullptr;
    221    }
    222    if (aContextType == CanvasContextType::WebGL1) {
    223      glean::canvas::webgl_success
    224          .EnumGet(glean::canvas::WebglSuccessLabel::eTrue)
    225          .Add();
    226    } else if (aContextType == CanvasContextType::WebGL2) {
    227      glean::canvas::webgl2_success
    228          .EnumGet(glean::canvas::Webgl2SuccessLabel::eTrue)
    229          .Add();
    230    } else if (aContextType == CanvasContextType::WebGPU) {
    231      // Telemetry::Accumulate(Telemetry::CANVAS_WEBGPU_SUCCESS, 1);
    232    }
    233  } else {
    234    // We already have a context of some type.
    235    if (aContextType != mCurrentContextType) return nullptr;
    236  }
    237 
    238  nsCOMPtr<nsICanvasRenderingContextInternal> context = mCurrentContext;
    239  return context.forget();
    240 }
    241 
    242 nsresult CanvasRenderingContextHelper::UpdateContext(
    243    JSContext* aCx, JS::Handle<JS::Value> aNewContextOptions,
    244    ErrorResult& aRvForDictionaryInit) {
    245  if (!mCurrentContext) return NS_OK;
    246 
    247  CSSIntSize sz = GetWidthHeight();
    248 
    249  nsCOMPtr<nsICanvasRenderingContextInternal> currentContext = mCurrentContext;
    250 
    251  currentContext->SetOpaqueValueFromOpaqueAttr(GetOpaqueAttr());
    252 
    253  nsresult rv = currentContext->SetContextOptions(aCx, aNewContextOptions,
    254                                                  aRvForDictionaryInit);
    255  if (NS_FAILED(rv)) {
    256    mCurrentContext = nullptr;
    257    return rv;
    258  }
    259 
    260  rv = currentContext->SetDimensions(sz.width, sz.height);
    261  if (NS_FAILED(rv)) {
    262    mCurrentContext = nullptr;
    263  }
    264 
    265  return rv;
    266 }
    267 
    268 nsresult CanvasRenderingContextHelper::ParseParams(
    269    JSContext* aCx, const nsAString& aType, const JS::Value& aEncoderOptions,
    270    nsAString& outParams, bool* const outUsingCustomParseOptions) {
    271  // Quality parameter is only valid for the image/jpeg and image/webp MIME
    272  // types.
    273  if (aType.EqualsLiteral("image/jpeg") || aType.EqualsLiteral("image/webp")) {
    274    if (aEncoderOptions.isNumber()) {
    275      double quality = aEncoderOptions.toNumber();
    276      // Quality must be between 0.0 and 1.0, inclusive
    277      if (quality >= 0.0 && quality <= 1.0) {
    278        outParams.AppendLiteral("quality=");
    279        outParams.AppendInt(NS_lround(quality * 100.0));
    280      }
    281    }
    282  }
    283 
    284  // If we haven't parsed the aParams check for proprietary options.
    285  // The proprietary option -moz-parse-options will take a image lib encoder
    286  // parse options string as is and pass it to the encoder.
    287  *outUsingCustomParseOptions = false;
    288  if (outParams.Length() == 0 && aEncoderOptions.isString()) {
    289    constexpr auto mozParseOptions = u"-moz-parse-options:"_ns;
    290    nsAutoJSString paramString;
    291    if (!paramString.init(aCx, aEncoderOptions.toString())) {
    292      return NS_ERROR_FAILURE;
    293    }
    294    if (StringBeginsWith(paramString, mozParseOptions)) {
    295      nsDependentSubstring parseOptions =
    296          Substring(paramString, mozParseOptions.Length(),
    297                    paramString.Length() - mozParseOptions.Length());
    298      outParams.Append(parseOptions);
    299      *outUsingCustomParseOptions = true;
    300    }
    301  }
    302 
    303  return NS_OK;
    304 }
    305 
    306 }  // namespace mozilla::dom