tor-browser

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

CanvasUtils.cpp (28174B)


      1 /* -*- Mode: C++; tab-width: 20; 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 "CanvasUtils.h"
      7 
      8 #include <stdlib.h>
      9 
     10 #include "WebGL2Context.h"
     11 #include "jsapi.h"
     12 #include "mozIThirdPartyUtil.h"
     13 #include "mozilla/BasePrincipal.h"
     14 #include "mozilla/Services.h"
     15 #include "mozilla/StaticPrefs_gfx.h"
     16 #include "mozilla/StaticPrefs_privacy.h"
     17 #include "mozilla/StaticPrefs_webgl.h"
     18 #include "mozilla/dom/BrowserChild.h"
     19 #include "mozilla/dom/Document.h"
     20 #include "mozilla/dom/HTMLCanvasElement.h"
     21 #include "mozilla/dom/OffscreenCanvas.h"
     22 #include "mozilla/dom/UserActivation.h"
     23 #include "mozilla/dom/WindowGlobalParent.h"
     24 #include "mozilla/dom/WorkerCommon.h"
     25 #include "mozilla/dom/WorkerPrivate.h"
     26 #include "mozilla/dom/WorkerRunnable.h"
     27 #include "mozilla/gfx/Matrix.h"
     28 #include "mozilla/gfx/gfxVars.h"
     29 #include "nsContentUtils.h"
     30 #include "nsGfxCIID.h"
     31 #include "nsICanvasRenderingContextInternal.h"
     32 #include "nsIHTMLCollection.h"
     33 #include "nsIObserverService.h"
     34 #include "nsIPermissionManager.h"
     35 #include "nsIPrincipal.h"
     36 #include "nsIScriptError.h"
     37 #include "nsIScriptObjectPrincipal.h"
     38 #include "nsPrintfCString.h"
     39 #include "nsTArray.h"
     40 #include "nsUnicharUtils.h"
     41 
     42 #define TOPIC_CANVAS_PERMISSIONS_PROMPT "canvas-permissions-prompt"
     43 #define TOPIC_CANVAS_PERMISSIONS_PROMPT_HIDE_DOORHANGER \
     44  "canvas-permissions-prompt-hide-doorhanger"
     45 #define PERMISSION_CANVAS_EXTRACT_DATA "canvas"_ns
     46 
     47 using namespace mozilla::gfx;
     48 
     49 static bool IsUnrestrictedPrincipal(nsIPrincipal* aPrincipal) {
     50  if (!aPrincipal) {
     51    return false;
     52  }
     53 
     54  // The system principal can always extract canvas data.
     55  if (aPrincipal->IsSystemPrincipal()) {
     56    return true;
     57  }
     58 
     59  // Allow chrome: and resource: (this especially includes PDF.js)
     60  if (aPrincipal->SchemeIs("chrome") || aPrincipal->SchemeIs("resource")) {
     61    return true;
     62  }
     63 
     64  // Allow extension principals.
     65  return aPrincipal->GetIsAddonOrExpandedAddonPrincipal();
     66 }
     67 
     68 namespace mozilla::CanvasUtils {
     69 
     70 class OffscreenCanvasPermissionRunnable final
     71    : public dom::WorkerMainThreadRunnable {
     72 public:
     73  OffscreenCanvasPermissionRunnable(dom::WorkerPrivate* aWorkerPrivate,
     74                                    nsIPrincipal* aPrincipal)
     75      : WorkerMainThreadRunnable(aWorkerPrivate,
     76                                 "OffscreenCanvasPermissionRunnable"_ns),
     77        mPrincipal(aPrincipal) {
     78    MOZ_ASSERT(aWorkerPrivate);
     79    aWorkerPrivate->AssertIsOnWorkerThread();
     80  }
     81 
     82  bool MainThreadRun() override {
     83    AssertIsOnMainThread();
     84 
     85    mResult = GetCanvasExtractDataPermission(mPrincipal);
     86    return true;
     87  }
     88 
     89  uint32_t GetResult() const { return mResult; }
     90 
     91 private:
     92  nsCOMPtr<nsIPrincipal> mPrincipal;
     93  uint32_t mResult = nsIPermissionManager::UNKNOWN_ACTION;
     94 };
     95 
     96 uint32_t GetCanvasExtractDataPermission(nsIPrincipal* aPrincipal) {
     97  if (!aPrincipal) {
     98    return nsIPermissionManager::UNKNOWN_ACTION;
     99  }
    100 
    101  if (IsUnrestrictedPrincipal(aPrincipal)) {
    102    return true;
    103  }
    104 
    105  if (NS_IsMainThread()) {
    106    nsresult rv;
    107    nsCOMPtr<nsIPermissionManager> permissionManager =
    108        do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
    109    NS_ENSURE_SUCCESS(rv, nsIPermissionManager::UNKNOWN_ACTION);
    110 
    111    uint32_t permission;
    112    rv = permissionManager->TestPermissionFromPrincipal(
    113        aPrincipal, PERMISSION_CANVAS_EXTRACT_DATA, &permission);
    114    NS_ENSURE_SUCCESS(rv, nsIPermissionManager::UNKNOWN_ACTION);
    115 
    116    return permission;
    117  }
    118  if (auto* workerPrivate = dom::GetCurrentThreadWorkerPrivate()) {
    119    RefPtr<OffscreenCanvasPermissionRunnable> runnable =
    120        new OffscreenCanvasPermissionRunnable(workerPrivate, aPrincipal);
    121    ErrorResult rv;
    122    runnable->Dispatch(workerPrivate, dom::WorkerStatus::Canceling, rv);
    123    if (rv.Failed()) {
    124      return nsIPermissionManager::UNKNOWN_ACTION;
    125    }
    126    return runnable->GetResult();
    127  }
    128  return nsIPermissionManager::UNKNOWN_ACTION;
    129 }
    130 
    131 /*
    132 ┌──────────────────────────────────────────────────────────────────────────┐
    133 │IsImageExtractionAllowed(dom::OffscreenCanvas*, JSContext*, nsIPrincipal*)│
    134 └────────────────────────────────────┬─────────────────────────────────────┘
    135 
    136                   ┌─────────────────▼────────────────────┐
    137 ┌─────No──────────│Any prompt RFP target enabled? See [1]│
    138 ▼                 └─────────────────┬────────────────────┘
    139 │                                   │Yes
    140 │                 ┌─────────────────▼────────┐
    141 ├─────Yes─────────┤Is unrestricted principal?│
    142 ▼                 └─────────────────┬────────┘
    143 │                                   │No
    144 │                 ┌─────────────────▼────────┐
    145 │          ┌──No──┤Are third parties blocked?│
    146 │          │      └─────────────────┬────────┘
    147 │          │                        │Yes
    148 │          │      ┌─────────────────▼─────────────┐
    149 │          │      │Are we in a third-party window?├───────Yes──────────┐
    150 │          │      └─────────────────┬─────────────┘                    ▼
    151 │          │                        │No                                │
    152 │          │      ┌─────────────────▼──┐                               │
    153 │          └──────►Do we show a prompt?├────────────Yes─┐              │
    154 │                 └─────────────────┬──┘                ▼              │
    155 │                                   │No                 │              │
    156 │                 ┌─────────────────▼─────────────┐     │              │
    157 │                 │Do we allow reading canvas data│     │              │
    158 │                 │in response to user input?     ├─No──┤              │
    159 │                 └─────────────────┬─────────────┘     ▼              │
    160 │                                   │Yes                │              │
    161 │                 ┌─────────────────▼─────────┐         │              │
    162 ├─────Yes─────────┼Are we handling user input?│         │              │
    163 ▼                 └─────────────────┬─────────┘         │              │
    164 │                                   │No                 │              │
    165 │                 ┌─────────────────▼─────────────┐     │              │
    166 ┌▼─────┐           │Show Permission Prompt (either ◄─────┘          ┌───▼──┐
    167 │return│           │w/ doorhanger, or w/o depending│                │return│
    168 │true  │           │on User Input)                 ├────────────────►false │
    169 └──────┘           └───────────────────────────────┘                └──────┘
    170 [1]: CanvasImageExtractionPrompt, CanvasExtractionBeforeUserInputIsBlocked,
    171     CanvasExtractionFromThirdPartiesIsBlocked are the RFP targets mentioned.
    172 */
    173 bool IsImageExtractionAllowed_impl(
    174    bool aCanvasImageExtractionPrompt,
    175    bool aCanvasExtractionBeforeUserInputIsBlocked,
    176    bool aCanvasExtractionFromThirdPartiesIsBlocked, JSContext* aCx,
    177    nsIPrincipal* aPrincipal,
    178    const std::function<bool()>& aGetIsThirdPartyWindow,
    179    const std::function<void(const nsAutoString&)>& aReportToConsole,
    180    const std::function<void(bool)>& aTryPrompt) {
    181  /*
    182   * There are three RFPTargets that change the behavior here, and they can be
    183   * in any combination
    184   * - CanvasImageExtractionPrompt - whether or not to prompt the user for
    185   * canvas extraction. If enabled, before canvas is extracted we will ensure
    186   * the user has granted permission.
    187   * - CanvasExtractionBeforeUserInputIsBlocked - if enabled, canvas extraction
    188   * before user input has occurred is always blocked, regardless of any other
    189   * Target behavior
    190   * - CanvasExtractionFromThirdPartiesIsBlocked - if enabled, canvas extraction
    191   * by third parties is always blocked, regardless of any other Target behavior
    192   *
    193   * There are two odd cases:
    194   * 1) When CanvasImageExtractionPrompt=false but
    195   *    CanvasExtractionBeforeUserInputIsBlocked=true Conceptually this is
    196   *    "Always allow canvas extraction in response to user input, and never
    197   *     allow it otherwise"
    198   *
    199   *    That's fine as a concept, but it might be a little confusing, so we
    200   *    still want to show the permission icon in the address bar, but never
    201   *    the permission doorhanger.
    202   * 2) When CanvasExtractionFromThirdPartiesIsBlocked=false - we will prompt
    203   *    the user for permission _for the frame_ (maybe with the doorhanger,
    204   *    maybe not).  The prompt shows the frame's origin, but it's easy to
    205   *    mistake that for the origin of the top-level page and grant it when you
    206   *    don't mean to.  This combination isn't likely to be used by anyone
    207   *    except those opting in, so that's alright.
    208   */
    209 
    210  if (!aCanvasImageExtractionPrompt &&
    211      !aCanvasExtractionBeforeUserInputIsBlocked &&
    212      !aCanvasExtractionFromThirdPartiesIsBlocked) {
    213    return true;
    214  }
    215 
    216  // Don't proceed if we don't have a document or JavaScript context.
    217  if (!aCx) {
    218    return false;
    219  }
    220 
    221  if (IsUnrestrictedPrincipal(aPrincipal)) {
    222    return true;
    223  }
    224 
    225  Maybe<nsAutoCString> origin = Nothing();
    226  auto getOrigin = [&]() {
    227    if (origin.isSome()) {
    228      return origin->IsEmpty();
    229    }
    230 
    231    nsAutoCString originResult;
    232    nsresult rv = NS_ERROR_FAILURE;
    233    if (aPrincipal) {
    234      rv = aPrincipal->GetOrigin(originResult);
    235    }
    236    origin = NS_SUCCEEDED(rv) ? Some(originResult) : Some(""_ns);
    237 
    238    return NS_SUCCEEDED(rv);
    239  };
    240 
    241  if (aCanvasExtractionFromThirdPartiesIsBlocked) {
    242    if (aGetIsThirdPartyWindow()) {
    243      nsAutoString message;
    244      message.AppendPrintf(
    245          "Blocked %s third party from extracting canvas data.",
    246          getOrigin() ? origin->get() : "unknown");
    247      aReportToConsole(message);
    248      return false;
    249    }
    250  }
    251 
    252  if (!aCanvasImageExtractionPrompt &&
    253      !aCanvasExtractionBeforeUserInputIsBlocked) {
    254    return true;
    255  }
    256 
    257  // -------------------------------------------------------------------
    258  // Check a site's permission
    259 
    260  // If the user has previously granted or not granted permission, we can return
    261  // immediately. Load Permission Manager service.
    262  uint64_t permission = GetCanvasExtractDataPermission(aPrincipal);
    263  switch (permission) {
    264    case nsIPermissionManager::ALLOW_ACTION:
    265      return true;
    266    case nsIPermissionManager::DENY_ACTION:
    267      return false;
    268    default:
    269      break;
    270  }
    271 
    272  // -------------------------------------------------------------------
    273  // At this point, there's only one way to return true: if we are always
    274  // allowing canvas in response to user input, and not prompting
    275  bool hidePermissionDoorhanger = false;
    276  if (!aCanvasImageExtractionPrompt &&
    277      aCanvasExtractionBeforeUserInputIsBlocked) {
    278    // If so, see if this is in response to user input.
    279    if (NS_IsMainThread() && dom::UserActivation::IsHandlingUserInput()) {
    280      return true;
    281    }
    282 
    283    hidePermissionDoorhanger = true;
    284  }
    285 
    286  // -------------------------------------------------------------------
    287  // Now we know we're going to block it, and log something to the console,
    288  // and show some sort of prompt maybe with the doorhanger, maybe not
    289 
    290  hidePermissionDoorhanger |=
    291      aCanvasExtractionBeforeUserInputIsBlocked &&
    292      (!NS_IsMainThread() || !dom::UserActivation::IsHandlingUserInput());
    293 
    294  nsAutoString message;
    295  message.AppendPrintf("Blocked %s from extracting canvas data",
    296                       getOrigin() ? origin->get() : "unknown");
    297  message.AppendPrintf(hidePermissionDoorhanger
    298                           ? " because no user input was detected"
    299                           : " but prompting the user.");
    300  aReportToConsole(message);
    301 
    302  aTryPrompt(hidePermissionDoorhanger);
    303 
    304  return false;
    305 }
    306 
    307 bool IsImageExtractionAllowed(dom::Document* aDocument, JSContext* aCx,
    308                              nsIPrincipal* aPrincipal) {
    309  if (NS_WARN_IF(!aDocument)) {
    310    return false;
    311  }
    312 
    313  bool canvasImageExtractionPrompt = aDocument->ShouldResistFingerprinting(
    314      RFPTarget::CanvasImageExtractionPrompt);
    315  bool canvasExtractionBeforeUserInputIsBlocked =
    316      aDocument->ShouldResistFingerprinting(
    317          RFPTarget::CanvasExtractionBeforeUserInputIsBlocked);
    318  bool canvasExtractionFromThirdPartiesIsBlocked =
    319      aDocument->ShouldResistFingerprinting(
    320          RFPTarget::CanvasExtractionFromThirdPartiesIsBlocked);
    321 
    322  // This part is duplicate but it helps us return faster
    323  // before we create bunch of lambdas
    324  if (!canvasImageExtractionPrompt &&
    325      !canvasExtractionBeforeUserInputIsBlocked &&
    326      !canvasExtractionFromThirdPartiesIsBlocked) {
    327    return true;
    328  }
    329 
    330  auto getIsThirdPartyWindow = [&]() {
    331    return aDocument->GetWindowContext()
    332               ? aDocument->GetWindowContext()->GetIsThirdPartyWindow()
    333               : false;
    334  };
    335 
    336  auto reportToConsole = [&](const nsAutoString& message) {
    337    nsContentUtils::ReportToConsoleNonLocalized(
    338        message, nsIScriptError::warningFlag, "Security"_ns, aDocument);
    339  };
    340 
    341  auto prompt = [&](bool hidePermissionDoorhanger) {
    342    if (!aPrincipal) {
    343      return;
    344    }
    345 
    346    nsAutoCString origin;
    347    nsresult rv = aPrincipal->GetOrigin(origin);
    348    if (NS_FAILED(rv)) {
    349      return;
    350    }
    351 
    352    if (!XRE_IsContentProcess()) {
    353      MOZ_ASSERT_UNREACHABLE(
    354          "Who's calling this from the parent process without a chrome window "
    355          "(it would have been exempt from the RFP targets)?");
    356      return;
    357    }
    358 
    359    nsPIDOMWindowOuter* win = aDocument->GetWindow();
    360    if (RefPtr<dom::BrowserChild> browserChild =
    361            dom::BrowserChild::GetFrom(win)) {
    362      browserChild->SendShowCanvasPermissionPrompt(origin,
    363                                                   hidePermissionDoorhanger);
    364    }
    365  };
    366 
    367  return IsImageExtractionAllowed_impl(
    368      canvasImageExtractionPrompt, canvasExtractionBeforeUserInputIsBlocked,
    369      canvasExtractionFromThirdPartiesIsBlocked, aCx, aPrincipal,
    370      getIsThirdPartyWindow, reportToConsole, prompt);
    371 }
    372 
    373 ImageExtraction ImageExtractionResult(dom::HTMLCanvasElement* aCanvasElement,
    374                                      JSContext* aCx,
    375                                      nsIPrincipal* aPrincipal) {
    376  if (IsUnrestrictedPrincipal(aPrincipal)) {
    377    return ImageExtraction::Unrestricted;
    378  }
    379 
    380  nsCOMPtr<dom::Document> ownerDoc = aCanvasElement->OwnerDoc();
    381  if (!IsImageExtractionAllowed(ownerDoc, aCx, aPrincipal)) {
    382    return ImageExtraction::Placeholder;
    383  }
    384 
    385  if (ownerDoc->ShouldResistFingerprinting(
    386          RFPTarget::EfficientCanvasRandomization) &&
    387      GetCanvasExtractDataPermission(aPrincipal) !=
    388          nsIPermissionManager::ALLOW_ACTION) {
    389    return ImageExtraction::EfficientRandomize;
    390  }
    391 
    392  if ((ownerDoc->ShouldResistFingerprinting(RFPTarget::CanvasRandomization) ||
    393       ownerDoc->ShouldResistFingerprinting(RFPTarget::WebGLRandomization)) &&
    394      GetCanvasExtractDataPermission(aPrincipal) !=
    395          nsIPermissionManager::ALLOW_ACTION) {
    396    return ImageExtraction::Randomize;
    397  }
    398 
    399  return ImageExtraction::Unrestricted;
    400 }
    401 
    402 bool IsImageExtractionAllowed(dom::OffscreenCanvas* aOffscreenCanvas,
    403                              JSContext* aCx, nsIPrincipal* aPrincipal) {
    404  if (!aOffscreenCanvas) {
    405    return false;
    406  }
    407 
    408  bool canvasImageExtractionPrompt =
    409      aOffscreenCanvas->ShouldResistFingerprinting(
    410          RFPTarget::CanvasImageExtractionPrompt);
    411  bool canvasExtractionBeforeUserInputIsBlocked =
    412      aOffscreenCanvas->ShouldResistFingerprinting(
    413          RFPTarget::CanvasExtractionBeforeUserInputIsBlocked);
    414  bool canvasExtractionFromThirdPartiesIsBlocked =
    415      aOffscreenCanvas->ShouldResistFingerprinting(
    416          RFPTarget::CanvasExtractionFromThirdPartiesIsBlocked);
    417 
    418  // This part is duplicate but it helps us return faster
    419  // before we create bunch of lambdas
    420  if (!canvasImageExtractionPrompt &&
    421      !canvasExtractionBeforeUserInputIsBlocked &&
    422      !canvasExtractionFromThirdPartiesIsBlocked) {
    423    return true;
    424  }
    425 
    426  Maybe<uint64_t> winId = aOffscreenCanvas->GetWindowID();
    427  if (winId.isSome() && *winId == UINT64_MAX) {
    428    // Workers with no window return UINT64_MAX as their window ID.
    429    winId = Nothing();
    430  }
    431 
    432  auto getIsThirdPartyWindow = [&]() {
    433    if (winId.isNothing()) {
    434      return false;
    435    }
    436 
    437    if (NS_IsMainThread()) {
    438      if (RefPtr<dom::WindowContext> win =
    439              dom::WindowGlobalParent::GetById(*winId)) {
    440        return win->GetIsThirdPartyWindow();
    441      }
    442    } else if (auto* workerPrivate = dom::GetCurrentThreadWorkerPrivate()) {
    443      return workerPrivate->IsThirdPartyContext();
    444    }
    445 
    446    return false;
    447  };
    448 
    449  auto reportToConsole = [&](const nsAutoString& message) {
    450    if (winId.isNothing()) {
    451      return;
    452    }
    453 
    454    nsContentUtils::ReportToConsoleByWindowID(
    455        message, nsIScriptError::warningFlag, "Security"_ns, *winId);
    456  };
    457 
    458  nsAutoCString origin;
    459  if (!aPrincipal || NS_FAILED(aPrincipal->GetOrigin(origin))) {
    460    origin = ""_ns;
    461  }
    462 
    463  RefPtr<dom::OffscreenCanvas> canvasRef = aOffscreenCanvas;
    464  auto prompt = [=](bool hidePermissionDoorhanger) {
    465    if (origin.IsEmpty()) {
    466      return;
    467    }
    468 
    469    if (!XRE_IsContentProcess()) {
    470      MOZ_ASSERT_UNREACHABLE(
    471          "Who's calling this from the parent process without a chrome "
    472          "window "
    473          "(it would have been exempt from the RFP targets)?");
    474      return;
    475    }
    476 
    477    if (NS_IsMainThread()) {
    478      nsCOMPtr<nsIGlobalObject> global = canvasRef->GetOwnerGlobal();
    479      NS_ENSURE_TRUE_VOID(global);
    480 
    481      RefPtr<nsPIDOMWindowInner> window = global->GetAsInnerWindow();
    482      NS_ENSURE_TRUE_VOID(window);
    483 
    484      RefPtr<dom::BrowserChild> browserChild =
    485          dom::BrowserChild::GetFrom(window);
    486      NS_ENSURE_TRUE_VOID(browserChild);
    487 
    488      browserChild->SendShowCanvasPermissionPrompt(origin,
    489                                                   hidePermissionDoorhanger);
    490      return;
    491    }
    492 
    493    class OffscreenCanvasPromptRunnable
    494        : public dom::WorkerProxyToMainThreadRunnable {
    495     public:
    496      explicit OffscreenCanvasPromptRunnable(const nsCString& aOrigin,
    497                                             bool aHidePermissionDoorhanger)
    498          : mOrigin(aOrigin),
    499            mHidePermissionDoorhanger(aHidePermissionDoorhanger) {}
    500 
    501      // Runnables don't support MOZ_CAN_RUN_SCRIPT, bug 1535398
    502      MOZ_CAN_RUN_SCRIPT_BOUNDARY void RunOnMainThread(
    503          dom::WorkerPrivate* aWorkerPrivate) override {
    504        MOZ_ASSERT(aWorkerPrivate);
    505        AssertIsOnMainThread();
    506 
    507        RefPtr<nsPIDOMWindowInner> inner = aWorkerPrivate->GetAncestorWindow();
    508        RefPtr<dom::BrowserChild> win = dom::BrowserChild::GetFrom(inner);
    509        NS_ENSURE_TRUE_VOID(win);
    510 
    511        win->SendShowCanvasPermissionPrompt(mOrigin, mHidePermissionDoorhanger);
    512      }
    513 
    514      void RunBackOnWorkerThreadForCleanup(
    515          dom::WorkerPrivate* aWorkerPrivate) override {
    516        MOZ_ASSERT(aWorkerPrivate);
    517        aWorkerPrivate->AssertIsOnWorkerThread();
    518      }
    519 
    520      nsCString mOrigin;
    521      bool mHidePermissionDoorhanger;
    522    };
    523 
    524    if (auto* workerPrivate = dom::GetCurrentThreadWorkerPrivate()) {
    525      RefPtr<OffscreenCanvasPromptRunnable> runnable =
    526          new OffscreenCanvasPromptRunnable(origin, hidePermissionDoorhanger);
    527      runnable->Dispatch(workerPrivate);
    528      return;
    529    }
    530  };
    531 
    532  return IsImageExtractionAllowed_impl(
    533      canvasImageExtractionPrompt, canvasExtractionBeforeUserInputIsBlocked,
    534      canvasExtractionFromThirdPartiesIsBlocked, aCx, aPrincipal,
    535      getIsThirdPartyWindow, reportToConsole, prompt);
    536 }
    537 
    538 ImageExtraction ImageExtractionResult(dom::OffscreenCanvas* aOffscreenCanvas,
    539                                      JSContext* aCx,
    540                                      nsIPrincipal* aPrincipal) {
    541  if (IsUnrestrictedPrincipal(aPrincipal)) {
    542    return ImageExtraction::Unrestricted;
    543  }
    544 
    545  if (!IsImageExtractionAllowed(aOffscreenCanvas, aCx, aPrincipal)) {
    546    return ImageExtraction::Placeholder;
    547  }
    548 
    549  if (aOffscreenCanvas->ShouldResistFingerprinting(
    550          RFPTarget::CanvasRandomization) ||
    551      aOffscreenCanvas->ShouldResistFingerprinting(
    552          RFPTarget::WebGLRandomization)) {
    553    if (GetCanvasExtractDataPermission(aPrincipal) ==
    554        nsIPermissionManager::ALLOW_ACTION) {
    555      return ImageExtraction::Unrestricted;
    556    }
    557    return ImageExtraction::Randomize;
    558  }
    559 
    560  return ImageExtraction::Unrestricted;
    561 }
    562 
    563 bool GetCanvasContextType(const nsAString& str,
    564                          dom::CanvasContextType* const out_type) {
    565  if (str.EqualsLiteral("2d")) {
    566    *out_type = dom::CanvasContextType::Canvas2D;
    567    return true;
    568  }
    569 
    570  if (str.EqualsLiteral("webgl") || str.EqualsLiteral("experimental-webgl")) {
    571    *out_type = dom::CanvasContextType::WebGL1;
    572    return true;
    573  }
    574 
    575  if (StaticPrefs::webgl_enable_webgl2()) {
    576    if (str.EqualsLiteral("webgl2")) {
    577      *out_type = dom::CanvasContextType::WebGL2;
    578      return true;
    579    }
    580  }
    581 
    582  if (gfxVars::AllowWebGPU()) {
    583    if (str.EqualsLiteral("webgpu")) {
    584      *out_type = dom::CanvasContextType::WebGPU;
    585      return true;
    586    }
    587  }
    588 
    589  if (str.EqualsLiteral("bitmaprenderer")) {
    590    *out_type = dom::CanvasContextType::ImageBitmap;
    591    return true;
    592  }
    593 
    594  return false;
    595 }
    596 
    597 /**
    598 * This security check utility might be called from an source that never
    599 * taints others. For example, while painting a CanvasPattern, which is
    600 * created from an ImageBitmap, onto a canvas. In this case, the caller could
    601 * set the CORSUsed true in order to pass this check and leave the aPrincipal
    602 * to be a nullptr since the aPrincipal is not going to be used.
    603 */
    604 void DoDrawImageSecurityCheck(dom::HTMLCanvasElement* aCanvasElement,
    605                              nsIPrincipal* aPrincipal, bool forceWriteOnly,
    606                              bool CORSUsed) {
    607  // Callers should ensure that mCanvasElement is non-null before calling this
    608  if (!aCanvasElement) {
    609    NS_WARNING("DoDrawImageSecurityCheck called without canvas element!");
    610    return;
    611  }
    612 
    613  if (aCanvasElement->IsWriteOnly() && !aCanvasElement->mExpandedReader) {
    614    return;
    615  }
    616 
    617  // If we explicitly set WriteOnly just do it and get out
    618  if (forceWriteOnly) {
    619    aCanvasElement->SetWriteOnly();
    620    return;
    621  }
    622 
    623  // No need to do a security check if the image used CORS for the load
    624  if (CORSUsed) return;
    625 
    626  if (NS_WARN_IF(!aPrincipal)) {
    627    MOZ_ASSERT_UNREACHABLE("Must have a principal here");
    628    aCanvasElement->SetWriteOnly();
    629    return;
    630  }
    631 
    632  if (aCanvasElement->NodePrincipal()->Subsumes(aPrincipal)) {
    633    // This canvas has access to that image anyway
    634    return;
    635  }
    636 
    637  if (BasePrincipal::Cast(aPrincipal)->AddonPolicy()) {
    638    // This is a resource from an extension content script principal.
    639 
    640    if (aCanvasElement->mExpandedReader &&
    641        aCanvasElement->mExpandedReader->Subsumes(aPrincipal)) {
    642      // This canvas already allows reading from this principal.
    643      return;
    644    }
    645 
    646    if (!aCanvasElement->mExpandedReader) {
    647      // Allow future reads from this same princial only.
    648      aCanvasElement->SetWriteOnly(aPrincipal);
    649      return;
    650    }
    651 
    652    // If we got here, this must be the *second* extension tainting
    653    // the canvas.  Fall through to mark it WriteOnly for everyone.
    654  }
    655 
    656  aCanvasElement->SetWriteOnly();
    657 }
    658 
    659 /**
    660 * This security check utility might be called from an source that never
    661 * taints others. For example, while painting a CanvasPattern, which is
    662 * created from an ImageBitmap, onto a canvas. In this case, the caller could
    663 * set the aCORSUsed true in order to pass this check and leave the aPrincipal
    664 * to be a nullptr since the aPrincipal is not going to be used.
    665 */
    666 void DoDrawImageSecurityCheck(dom::OffscreenCanvas* aOffscreenCanvas,
    667                              nsIPrincipal* aPrincipal, bool aForceWriteOnly,
    668                              bool aCORSUsed) {
    669  // Callers should ensure that mCanvasElement is non-null before calling this
    670  if (NS_WARN_IF(!aOffscreenCanvas)) {
    671    return;
    672  }
    673 
    674  nsIPrincipal* expandedReader = aOffscreenCanvas->GetExpandedReader();
    675  if (aOffscreenCanvas->IsWriteOnly() && !expandedReader) {
    676    return;
    677  }
    678 
    679  // If we explicitly set WriteOnly just do it and get out
    680  if (aForceWriteOnly) {
    681    aOffscreenCanvas->SetWriteOnly();
    682    return;
    683  }
    684 
    685  // No need to do a security check if the image used CORS for the load
    686  if (aCORSUsed) {
    687    return;
    688  }
    689 
    690  // If we are on a worker thread, we might not have any principals at all.
    691  nsIGlobalObject* global = aOffscreenCanvas->GetOwnerGlobal();
    692  nsIPrincipal* canvasPrincipal = global ? global->PrincipalOrNull() : nullptr;
    693  if (!aPrincipal || !canvasPrincipal) {
    694    aOffscreenCanvas->SetWriteOnly();
    695    return;
    696  }
    697 
    698  if (canvasPrincipal->Subsumes(aPrincipal)) {
    699    // This canvas has access to that image anyway
    700    return;
    701  }
    702 
    703  if (BasePrincipal::Cast(aPrincipal)->AddonPolicy()) {
    704    // This is a resource from an extension content script principal.
    705 
    706    if (expandedReader && expandedReader->Subsumes(aPrincipal)) {
    707      // This canvas already allows reading from this principal.
    708      return;
    709    }
    710 
    711    if (!expandedReader) {
    712      // Allow future reads from this same princial only.
    713      aOffscreenCanvas->SetWriteOnly(aPrincipal);
    714      return;
    715    }
    716 
    717    // If we got here, this must be the *second* extension tainting
    718    // the canvas.  Fall through to mark it WriteOnly for everyone.
    719  }
    720 
    721  aOffscreenCanvas->SetWriteOnly();
    722 }
    723 
    724 bool CoerceDouble(const JS::Value& v, double* d) {
    725  if (v.isDouble()) {
    726    *d = v.toDouble();
    727  } else if (v.isInt32()) {
    728    *d = double(v.toInt32());
    729  } else if (v.isUndefined()) {
    730    *d = 0.0;
    731  } else {
    732    return false;
    733  }
    734  return true;
    735 }
    736 
    737 bool HasDrawWindowPrivilege(JSContext* aCx, JSObject* /* unused */) {
    738  return nsContentUtils::CallerHasPermission(aCx,
    739                                             nsGkAtoms::all_urlsPermission);
    740 }
    741 
    742 bool CheckWriteOnlySecurity(bool aCORSUsed, nsIPrincipal* aPrincipal,
    743                            bool aHadCrossOriginRedirects) {
    744  if (!aPrincipal) {
    745    return true;
    746  }
    747 
    748  if (!aCORSUsed) {
    749    if (aHadCrossOriginRedirects) {
    750      return true;
    751    }
    752 
    753    nsIGlobalObject* incumbentSettingsObject = dom::GetIncumbentGlobal();
    754    if (!incumbentSettingsObject) {
    755      return true;
    756    }
    757 
    758    nsIPrincipal* principal = incumbentSettingsObject->PrincipalOrNull();
    759    if (NS_WARN_IF(!principal) || !(principal->Subsumes(aPrincipal))) {
    760      return true;
    761    }
    762  }
    763 
    764  return false;
    765 }
    766 
    767 }  // namespace mozilla::CanvasUtils