tor-browser

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

HTMLCanvasElement.cpp (50243B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "mozilla/dom/HTMLCanvasElement.h"
      8 
      9 #include "ActiveLayerTracker.h"
     10 #include "CanvasUtils.h"
     11 #include "ClientWebGLContext.h"
     12 #include "ImageEncoder.h"
     13 #include "MediaTrackGraph.h"
     14 #include "VRManagerChild.h"
     15 #include "WindowRenderer.h"
     16 #include "jsapi.h"
     17 #include "jsfriendapi.h"
     18 #include "mozilla/Assertions.h"
     19 #include "mozilla/Base64.h"
     20 #include "mozilla/BasePrincipal.h"
     21 #include "mozilla/EventDispatcher.h"
     22 #include "mozilla/MouseEvents.h"
     23 #include "mozilla/Preferences.h"
     24 #include "mozilla/PresShell.h"
     25 #include "mozilla/ProfilerLabels.h"
     26 #include "mozilla/ProfilerMarkers.h"
     27 #include "mozilla/StaticPrefs_privacy.h"
     28 #include "mozilla/dom/BlobImpl.h"
     29 #include "mozilla/dom/CanvasCaptureMediaStream.h"
     30 #include "mozilla/dom/CanvasRenderingContext2D.h"
     31 #include "mozilla/dom/Document.h"
     32 #include "mozilla/dom/Event.h"
     33 #include "mozilla/dom/File.h"
     34 #include "mozilla/dom/GeneratePlaceholderCanvasData.h"
     35 #include "mozilla/dom/HTMLCanvasElementBinding.h"
     36 #include "mozilla/dom/MouseEvent.h"
     37 #include "mozilla/dom/OffscreenCanvas.h"
     38 #include "mozilla/dom/OffscreenCanvasDisplayHelper.h"
     39 #include "mozilla/dom/VideoStreamTrack.h"
     40 #include "mozilla/gfx/Rect.h"
     41 #include "mozilla/layers/CanvasRenderer.h"
     42 #include "mozilla/layers/WebRenderCanvasRenderer.h"
     43 #include "mozilla/layers/WebRenderUserData.h"
     44 #include "mozilla/webgpu/CanvasContext.h"
     45 #include "nsAttrValueInlines.h"
     46 #include "nsContentUtils.h"
     47 #include "nsDOMJSUtils.h"
     48 #include "nsDisplayList.h"
     49 #include "nsITimer.h"
     50 #include "nsJSUtils.h"
     51 #include "nsLayoutUtils.h"
     52 #include "nsMathUtils.h"
     53 #include "nsNetUtil.h"
     54 #include "nsRefreshDriver.h"
     55 #include "nsStreamUtils.h"
     56 
     57 using namespace mozilla::layers;
     58 using namespace mozilla::gfx;
     59 
     60 NS_IMPL_NS_NEW_HTML_ELEMENT(Canvas)
     61 
     62 namespace mozilla::dom {
     63 
     64 class RequestedFrameRefreshObserver : public nsARefreshObserver {
     65  NS_INLINE_DECL_REFCOUNTING(RequestedFrameRefreshObserver, override)
     66 
     67 public:
     68  RequestedFrameRefreshObserver(HTMLCanvasElement* const aOwningElement,
     69                                nsRefreshDriver* aRefreshDriver,
     70                                bool aReturnPlaceholderData)
     71      : mRegistered(false),
     72        mWatching(false),
     73        mReturnPlaceholderData(aReturnPlaceholderData),
     74        mOwningElement(aOwningElement),
     75        mRefreshDriver(aRefreshDriver),
     76        mWatchManager(this, AbstractThread::MainThread()),
     77        mPendingThrottledCapture(false) {
     78    MOZ_ASSERT(mOwningElement);
     79  }
     80 
     81  static already_AddRefed<DataSourceSurface> CopySurface(
     82      const RefPtr<SourceSurface>& aSurface, bool aReturnPlaceholderData) {
     83    RefPtr<DataSourceSurface> data = aSurface->GetDataSurface();
     84    if (!data) {
     85      return nullptr;
     86    }
     87 
     88    DataSourceSurface::ScopedMap read(data, DataSourceSurface::READ);
     89    if (!read.IsMapped()) {
     90      return nullptr;
     91    }
     92 
     93    RefPtr<DataSourceSurface> copy = Factory::CreateDataSourceSurfaceWithStride(
     94        data->GetSize(), data->GetFormat(), read.GetStride());
     95    if (!copy) {
     96      return nullptr;
     97    }
     98 
     99    DataSourceSurface::ScopedMap write(copy, DataSourceSurface::WRITE);
    100    if (!write.IsMapped()) {
    101      return nullptr;
    102    }
    103 
    104    MOZ_ASSERT(read.GetStride() == write.GetStride());
    105    MOZ_ASSERT(data->GetSize() == copy->GetSize());
    106    MOZ_ASSERT(data->GetFormat() == copy->GetFormat());
    107 
    108    if (aReturnPlaceholderData) {
    109      auto size = write.GetStride() * copy->GetSize().height;
    110      auto* data = write.GetData();
    111      GeneratePlaceholderCanvasData(size, data);
    112    } else {
    113      memcpy(write.GetData(), read.GetData(),
    114             write.GetStride() * copy->GetSize().height);
    115    }
    116 
    117    return copy.forget();
    118  }
    119 
    120  void SetReturnPlaceholderData(bool aReturnPlaceholderData) {
    121    mReturnPlaceholderData = aReturnPlaceholderData;
    122  }
    123 
    124  void NotifyCaptureStateChange() {
    125    if (mPendingThrottledCapture) {
    126      return;
    127    }
    128 
    129    if (!mOwningElement) {
    130      return;
    131    }
    132 
    133    Watchable<FrameCaptureState>* captureState =
    134        mOwningElement->GetFrameCaptureState();
    135    if (!captureState) {
    136      PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {},
    137                           "Abort: No capture state"_ns);
    138      return;
    139    }
    140 
    141    if (captureState->Ref() == FrameCaptureState::CLEAN) {
    142      PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {},
    143                           "Abort: CLEAN"_ns);
    144      return;
    145    }
    146 
    147    if (!mRefreshDriver) {
    148      PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {},
    149                           "Abort: no refresh driver"_ns);
    150      return;
    151    }
    152 
    153    if (!mRefreshDriver->IsThrottled()) {
    154      PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {},
    155                           "Abort: not throttled"_ns);
    156      return;
    157    }
    158 
    159    TimeStamp now = TimeStamp::Now();
    160    TimeStamp next =
    161        mLastCaptureTime.IsNull()
    162            ? now
    163            : mLastCaptureTime + TimeDuration::FromMilliseconds(
    164                                     nsRefreshDriver::DefaultInterval());
    165    if (mLastCaptureTime.IsNull() || next <= now) {
    166      AUTO_PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {},
    167                                "CaptureFrame direct while throttled"_ns);
    168      CaptureFrame(now);
    169      return;
    170    }
    171 
    172    nsCString str;
    173    if (profiler_thread_is_being_profiled_for_markers()) {
    174      str.AppendPrintf("Delaying CaptureFrame by %.2fms",
    175                       (next - now).ToMilliseconds());
    176    }
    177    AUTO_PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {}, str);
    178 
    179    mPendingThrottledCapture = true;
    180    AbstractThread::MainThread()->DelayedDispatch(
    181        NS_NewRunnableFunction(
    182            __func__,
    183            [this, self = RefPtr<RequestedFrameRefreshObserver>(this), next] {
    184              mPendingThrottledCapture = false;
    185              AUTO_PROFILER_MARKER_TEXT(
    186                  "Canvas CaptureStream", MEDIA_RT, {},
    187                  "CaptureFrame after delay while throttled"_ns);
    188              CaptureFrame(next);
    189            }),
    190        // next >= now, so this is a guard for (next - now) flooring to 0.
    191        std::max<uint32_t>(
    192            1, static_cast<uint32_t>((next - now).ToMilliseconds())));
    193  }
    194 
    195  void WillRefresh(TimeStamp aTime) override {
    196    AUTO_PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {},
    197                              "CaptureFrame by refresh driver"_ns);
    198 
    199    CaptureFrame(aTime);
    200  }
    201 
    202  void CaptureFrame(TimeStamp aTime) {
    203    MOZ_ASSERT(NS_IsMainThread());
    204 
    205    if (!mOwningElement) {
    206      PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {},
    207                           "Abort: no owning element"_ns);
    208      return;
    209    }
    210 
    211    if (mOwningElement->IsWriteOnly()) {
    212      PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {},
    213                           "Abort: write only"_ns);
    214      return;
    215    }
    216 
    217    if (auto* captureStateWatchable = mOwningElement->GetFrameCaptureState();
    218        captureStateWatchable &&
    219        *captureStateWatchable == FrameCaptureState::CLEAN) {
    220      PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {},
    221                           "Abort: CLEAN"_ns);
    222      return;
    223    }
    224 
    225    // Mark the context already now, since if the frame capture state is DIRTY
    226    // and we catch an early return below (not marking it CLEAN), the next draw
    227    // will not trigger a capture state change from the
    228    // Watchable<FrameCaptureState>.
    229    mOwningElement->MarkContextCleanForFrameCapture();
    230 
    231    mOwningElement->ProcessDestroyedFrameListeners();
    232 
    233    if (!mOwningElement->IsFrameCaptureRequested(aTime)) {
    234      PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {},
    235                           "Abort: no capture requested"_ns);
    236      return;
    237    }
    238 
    239    RefPtr<SourceSurface> snapshot;
    240    {
    241      AUTO_PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {},
    242                                "GetSnapshot"_ns);
    243      snapshot = mOwningElement->GetSurfaceSnapshot(nullptr);
    244      if (!snapshot) {
    245        PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {},
    246                             "Abort: snapshot failed"_ns);
    247        return;
    248      }
    249    }
    250 
    251    RefPtr<DataSourceSurface> copy;
    252    {
    253      AUTO_PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {},
    254                                "CopySurface"_ns);
    255      copy = CopySurface(snapshot, mReturnPlaceholderData);
    256      if (!copy) {
    257        PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {},
    258                             "Abort: copy failed"_ns);
    259        return;
    260      }
    261    }
    262 
    263    nsCString str;
    264    if (profiler_thread_is_being_profiled_for_markers()) {
    265      TimeDuration sinceLast =
    266          aTime - (mLastCaptureTime.IsNull() ? aTime : mLastCaptureTime);
    267      str.AppendPrintf("Forwarding captured frame %.2fms after last",
    268                       sinceLast.ToMilliseconds());
    269    }
    270    AUTO_PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {}, str);
    271 
    272    if (!mLastCaptureTime.IsNull() && aTime <= mLastCaptureTime) {
    273      aTime = mLastCaptureTime + TimeDuration::FromMilliseconds(1);
    274    }
    275    mLastCaptureTime = aTime;
    276 
    277    mOwningElement->SetFrameCapture(copy.forget(), aTime);
    278  }
    279 
    280  void DetachFromRefreshDriver() {
    281    MOZ_ASSERT(mOwningElement);
    282    MOZ_ASSERT(mRefreshDriver);
    283 
    284    Unregister();
    285    mRefreshDriver = nullptr;
    286    mWatchManager.Shutdown();
    287  }
    288 
    289  bool IsRegisteredAndWatching() { return mRegistered && mWatching; }
    290 
    291  void Register() {
    292    if (!mRegistered) {
    293      MOZ_ASSERT(mRefreshDriver);
    294      if (mRefreshDriver) {
    295        mRefreshDriver->AddRefreshObserver(this, FlushType::Display,
    296                                           "Canvas frame capture listeners");
    297        mRegistered = true;
    298      }
    299    }
    300 
    301    if (mWatching) {
    302      return;
    303    }
    304 
    305    if (!mOwningElement) {
    306      return;
    307    }
    308 
    309    if (Watchable<FrameCaptureState>* captureState =
    310            mOwningElement->GetFrameCaptureState()) {
    311      mWatchManager.Watch(
    312          *captureState,
    313          &RequestedFrameRefreshObserver::NotifyCaptureStateChange);
    314      mWatching = true;
    315    }
    316  }
    317 
    318  void Unregister() {
    319    if (mRegistered) {
    320      MOZ_ASSERT(mRefreshDriver);
    321      if (mRefreshDriver) {
    322        mRefreshDriver->RemoveRefreshObserver(this, FlushType::Display);
    323        mRegistered = false;
    324      }
    325    }
    326 
    327    if (!mWatching) {
    328      return;
    329    }
    330 
    331    if (!mOwningElement) {
    332      return;
    333    }
    334 
    335    if (Watchable<FrameCaptureState>* captureState =
    336            mOwningElement->GetFrameCaptureState()) {
    337      mWatchManager.Unwatch(
    338          *captureState,
    339          &RequestedFrameRefreshObserver::NotifyCaptureStateChange);
    340      mWatching = false;
    341    }
    342  }
    343 
    344 private:
    345  virtual ~RequestedFrameRefreshObserver() {
    346    MOZ_ASSERT(!mRefreshDriver);
    347    MOZ_ASSERT(!mRegistered);
    348    MOZ_ASSERT(!mWatching);
    349  }
    350 
    351  bool mRegistered;
    352  bool mWatching;
    353  bool mReturnPlaceholderData;
    354  const WeakPtr<HTMLCanvasElement> mOwningElement;
    355  RefPtr<nsRefreshDriver> mRefreshDriver;
    356  WatchManager<RequestedFrameRefreshObserver> mWatchManager;
    357  TimeStamp mLastCaptureTime;
    358  bool mPendingThrottledCapture;
    359 };
    360 
    361 // ---------------------------------------------------------------------------
    362 
    363 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(HTMLCanvasPrintState, mCanvas, mContext,
    364                                      mCallback)
    365 
    366 HTMLCanvasPrintState::HTMLCanvasPrintState(
    367    HTMLCanvasElement* aCanvas, nsICanvasRenderingContextInternal* aContext,
    368    nsITimerCallback* aCallback)
    369    : mIsDone(false),
    370      mPendingNotify(false),
    371      mCanvas(aCanvas),
    372      mContext(aContext),
    373      mCallback(aCallback) {}
    374 
    375 HTMLCanvasPrintState::~HTMLCanvasPrintState() = default;
    376 
    377 /* virtual */
    378 JSObject* HTMLCanvasPrintState::WrapObject(JSContext* aCx,
    379                                           JS::Handle<JSObject*> aGivenProto) {
    380  return MozCanvasPrintState_Binding::Wrap(aCx, this, aGivenProto);
    381 }
    382 
    383 nsISupports* HTMLCanvasPrintState::Context() const { return mContext; }
    384 
    385 void HTMLCanvasPrintState::Done() {
    386  if (!mPendingNotify && !mIsDone) {
    387    // The canvas needs to be invalidated for printing reftests on linux to
    388    // work.
    389    if (mCanvas) {
    390      mCanvas->InvalidateCanvas();
    391    }
    392    RefPtr<nsRunnableMethod<HTMLCanvasPrintState>> doneEvent =
    393        NewRunnableMethod("dom::HTMLCanvasPrintState::NotifyDone", this,
    394                          &HTMLCanvasPrintState::NotifyDone);
    395    if (NS_SUCCEEDED(NS_DispatchToCurrentThread(doneEvent))) {
    396      mPendingNotify = true;
    397    }
    398  }
    399 }
    400 
    401 void HTMLCanvasPrintState::NotifyDone() {
    402  mIsDone = true;
    403  mPendingNotify = false;
    404  if (mCallback) {
    405    mCallback->Notify(nullptr);
    406  }
    407 }
    408 
    409 // ---------------------------------------------------------------------------
    410 
    411 HTMLCanvasElementObserver::HTMLCanvasElementObserver(
    412    HTMLCanvasElement* aElement)
    413    : mElement(aElement) {
    414  RegisterObserverEvents();
    415 }
    416 
    417 HTMLCanvasElementObserver::~HTMLCanvasElementObserver() { Destroy(); }
    418 
    419 void HTMLCanvasElementObserver::Destroy() {
    420  UnregisterObserverEvents();
    421  mElement = nullptr;
    422 }
    423 
    424 void HTMLCanvasElementObserver::RegisterObserverEvents() {
    425  if (!mElement) {
    426    return;
    427  }
    428 
    429  nsCOMPtr<nsIObserverService> observerService =
    430      mozilla::services::GetObserverService();
    431 
    432  MOZ_ASSERT(observerService);
    433 
    434  if (observerService) {
    435    observerService->AddObserver(this, "memory-pressure", false);
    436    observerService->AddObserver(this, "canvas-device-reset", false);
    437  }
    438 }
    439 
    440 void HTMLCanvasElementObserver::UnregisterObserverEvents() {
    441  if (!mElement) {
    442    return;
    443  }
    444 
    445  nsCOMPtr<nsIObserverService> observerService =
    446      mozilla::services::GetObserverService();
    447 
    448  // Do not assert on observerService here. This might be triggered by
    449  // the cycle collector at a late enough time, that XPCOM services are
    450  // no longer available. See bug 1029504.
    451  if (observerService) {
    452    observerService->RemoveObserver(this, "memory-pressure");
    453    observerService->RemoveObserver(this, "canvas-device-reset");
    454  }
    455 }
    456 
    457 NS_IMETHODIMP
    458 HTMLCanvasElementObserver::Observe(nsISupports*, const char* aTopic,
    459                                   const char16_t*) {
    460  if (!mElement) {
    461    return NS_OK;
    462  }
    463 
    464  if (strcmp(aTopic, "memory-pressure") == 0) {
    465    mElement->OnMemoryPressure();
    466  } else if (strcmp(aTopic, "canvas-device-reset") == 0) {
    467    mElement->OnDeviceReset();
    468  }
    469 
    470  return NS_OK;
    471 }
    472 
    473 NS_IMPL_ISUPPORTS(HTMLCanvasElementObserver, nsIObserver)
    474 
    475 // ---------------------------------------------------------------------------
    476 
    477 HTMLCanvasElement::HTMLCanvasElement(
    478    already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
    479    : nsGenericHTMLElement(std::move(aNodeInfo)),
    480      mResetLayer(true),
    481      mMaybeModified(false),
    482      mWriteOnly(false) {}
    483 
    484 HTMLCanvasElement::~HTMLCanvasElement() { Destroy(); }
    485 
    486 void HTMLCanvasElement::Destroy() {
    487  if (mOffscreenDisplay) {
    488    mOffscreenDisplay->DestroyElement();
    489    mOffscreenDisplay = nullptr;
    490    mImageContainer = nullptr;
    491  }
    492 
    493  if (mContextObserver) {
    494    mContextObserver->Destroy();
    495    mContextObserver = nullptr;
    496  }
    497 
    498  ResetPrintCallback();
    499  if (mRequestedFrameRefreshObserver) {
    500    mRequestedFrameRefreshObserver->DetachFromRefreshDriver();
    501    mRequestedFrameRefreshObserver = nullptr;
    502  }
    503 }
    504 
    505 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLCanvasElement)
    506 
    507 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLCanvasElement,
    508                                                nsGenericHTMLElement)
    509  tmp->Destroy();
    510  NS_IMPL_CYCLE_COLLECTION_UNLINK(mCurrentContext, mPrintCallback, mPrintState,
    511                                  mOriginalCanvas, mOffscreenCanvas)
    512 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
    513 
    514 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLCanvasElement,
    515                                                  nsGenericHTMLElement)
    516  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCurrentContext, mPrintCallback,
    517                                    mPrintState, mOriginalCanvas,
    518                                    mOffscreenCanvas)
    519 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
    520 
    521 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(HTMLCanvasElement,
    522                                               nsGenericHTMLElement)
    523 
    524 NS_IMPL_ELEMENT_CLONE(HTMLCanvasElement)
    525 
    526 /* virtual */
    527 JSObject* HTMLCanvasElement::WrapNode(JSContext* aCx,
    528                                      JS::Handle<JSObject*> aGivenProto) {
    529  return HTMLCanvasElement_Binding::Wrap(aCx, this, aGivenProto);
    530 }
    531 
    532 already_AddRefed<nsICanvasRenderingContextInternal>
    533 HTMLCanvasElement::CreateContext(CanvasContextType aContextType) {
    534  // Note that the compositor backend will be LAYERS_NONE if there is no widget.
    535  RefPtr<nsICanvasRenderingContextInternal> ret =
    536      CreateContextHelper(aContextType, GetCompositorBackendType());
    537  if (NS_WARN_IF(!ret)) {
    538    return nullptr;
    539  }
    540 
    541  // Add Observer for webgl canvas.
    542  if (aContextType == CanvasContextType::WebGL1 ||
    543      aContextType == CanvasContextType::WebGL2 ||
    544      aContextType == CanvasContextType::Canvas2D) {
    545    if (!mContextObserver) {
    546      mContextObserver = new HTMLCanvasElementObserver(this);
    547    }
    548  }
    549 
    550  ret->SetCanvasElement(this);
    551  return ret.forget();
    552 }
    553 
    554 nsresult HTMLCanvasElement::UpdateContext(
    555    JSContext* aCx, JS::Handle<JS::Value> aNewContextOptions,
    556    ErrorResult& aRvForDictionaryInit) {
    557  nsresult rv = CanvasRenderingContextHelper::UpdateContext(
    558      aCx, aNewContextOptions, aRvForDictionaryInit);
    559 
    560  if (NS_FAILED(rv)) {
    561    return rv;
    562  }
    563 
    564  // If we have a mRequestedFrameRefreshObserver that wasn't fully registered,
    565  // retry that now.
    566  if (mRequestedFrameRefreshObserver.get() &&
    567      !mRequestedFrameRefreshObserver->IsRegisteredAndWatching()) {
    568    mRequestedFrameRefreshObserver->Register();
    569  }
    570 
    571  return NS_OK;
    572 }
    573 
    574 CSSIntSize HTMLCanvasElement::GetWidthHeight() {
    575  CSSIntSize size = kFallbackIntrinsicSizeInPixels;
    576  const nsAttrValue* value;
    577 
    578  if ((value = GetParsedAttr(nsGkAtoms::width)) &&
    579      value->Type() == nsAttrValue::eInteger) {
    580    size.width = value->GetIntegerValue();
    581  }
    582 
    583  if ((value = GetParsedAttr(nsGkAtoms::height)) &&
    584      value->Type() == nsAttrValue::eInteger) {
    585    size.height = value->GetIntegerValue();
    586  }
    587 
    588  MOZ_ASSERT(size.width >= 0 && size.height >= 0,
    589             "we should've required <canvas> width/height attrs to be "
    590             "unsigned (non-negative) values");
    591 
    592  return size;
    593 }
    594 
    595 void HTMLCanvasElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName,
    596                                     const nsAttrValue* aValue,
    597                                     const nsAttrValue* aOldValue,
    598                                     nsIPrincipal* aSubjectPrincipal,
    599                                     bool aNotify) {
    600  AfterMaybeChangeAttr(aNamespaceID, aName, aNotify);
    601 
    602  return nsGenericHTMLElement::AfterSetAttr(
    603      aNamespaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify);
    604 }
    605 
    606 void HTMLCanvasElement::OnAttrSetButNotChanged(
    607    int32_t aNamespaceID, nsAtom* aName, const nsAttrValueOrString& aValue,
    608    bool aNotify) {
    609  AfterMaybeChangeAttr(aNamespaceID, aName, aNotify);
    610 
    611  return nsGenericHTMLElement::OnAttrSetButNotChanged(aNamespaceID, aName,
    612                                                      aValue, aNotify);
    613 }
    614 
    615 void HTMLCanvasElement::AfterMaybeChangeAttr(int32_t aNamespaceID,
    616                                             nsAtom* aName, bool aNotify) {
    617  if (mCurrentContext && aNamespaceID == kNameSpaceID_None &&
    618      (aName == nsGkAtoms::width || aName == nsGkAtoms::height ||
    619       aName == nsGkAtoms::moz_opaque)) {
    620    ErrorResult dummy;
    621    UpdateContext(nullptr, JS::NullHandleValue, dummy);
    622  }
    623 }
    624 
    625 void HTMLCanvasElement::HandlePrintCallback(nsPresContext* aPresContext) {
    626  // Only call the print callback here if 1) we're in a print testing mode or
    627  // print preview mode, 2) the canvas has a print callback and 3) the callback
    628  // hasn't already been called. For real printing the callback is handled in
    629  // nsPageSequenceFrame::PrePrintNextSheet.
    630  if ((aPresContext->Type() == nsPresContext::eContext_PageLayout ||
    631       aPresContext->Type() == nsPresContext::eContext_PrintPreview) &&
    632      !mPrintState && GetMozPrintCallback()) {
    633    DispatchPrintCallback(nullptr);
    634  }
    635 }
    636 
    637 nsresult HTMLCanvasElement::DispatchPrintCallback(nsITimerCallback* aCallback) {
    638  // For print reftests the context may not be initialized yet, so get a context
    639  // so mCurrentContext is set.
    640  if (!mCurrentContext) {
    641    nsresult rv;
    642    nsCOMPtr<nsISupports> context;
    643    rv = GetContext(u"2d"_ns, getter_AddRefs(context));
    644    NS_ENSURE_SUCCESS(rv, rv);
    645  }
    646  mPrintState = new HTMLCanvasPrintState(this, mCurrentContext, aCallback);
    647 
    648  RefPtr<nsRunnableMethod<HTMLCanvasElement>> renderEvent =
    649      NewRunnableMethod("dom::HTMLCanvasElement::CallPrintCallback", this,
    650                        &HTMLCanvasElement::CallPrintCallback);
    651  return OwnerDoc()->Dispatch(renderEvent.forget());
    652 }
    653 
    654 void HTMLCanvasElement::CallPrintCallback() {
    655  AUTO_PROFILER_MARKER_TEXT("HTMLCanvasElement Printing", LAYOUT_Printing, {},
    656                            "HTMLCanvasElement::CallPrintCallback"_ns);
    657  if (!mPrintState) {
    658    // `mPrintState` might have been destroyed by cancelling the previous
    659    // printing (especially the canvas frame destruction) during processing
    660    // event loops in the printing.
    661    return;
    662  }
    663  RefPtr<PrintCallback> callback = GetMozPrintCallback();
    664  RefPtr<HTMLCanvasPrintState> state = mPrintState;
    665  callback->Call(*state);
    666 }
    667 
    668 void HTMLCanvasElement::ResetPrintCallback() {
    669  if (mPrintState) {
    670    mPrintState = nullptr;
    671  }
    672 }
    673 
    674 bool HTMLCanvasElement::IsPrintCallbackDone() {
    675  if (mPrintState == nullptr) {
    676    return true;
    677  }
    678 
    679  return mPrintState->mIsDone;
    680 }
    681 
    682 HTMLCanvasElement* HTMLCanvasElement::GetOriginalCanvas() {
    683  return mOriginalCanvas ? mOriginalCanvas.get() : this;
    684 }
    685 
    686 nsresult HTMLCanvasElement::CopyInnerTo(HTMLCanvasElement* aDest) {
    687  nsresult rv = nsGenericHTMLElement::CopyInnerTo(aDest);
    688  NS_ENSURE_SUCCESS(rv, rv);
    689  Document* destDoc = aDest->OwnerDoc();
    690  if (destDoc->IsStaticDocument()) {
    691    // The Firefox print preview code can create a static clone from an
    692    // existing static clone, so we may not be the original 'canvas' element.
    693    aDest->mOriginalCanvas = GetOriginalCanvas();
    694 
    695    if (GetMozPrintCallback()) {
    696      destDoc->SetHasPrintCallbacks();
    697    }
    698 
    699    // We make sure that the canvas is not zero sized since that would cause
    700    // the DrawImage call below to return an error, which would cause printing
    701    // to fail.
    702    CSSIntSize size = GetWidthHeight();
    703    if (size.height > 0 && size.width > 0) {
    704      nsCOMPtr<nsISupports> cxt;
    705      aDest->GetContext(u"2d"_ns, getter_AddRefs(cxt));
    706      RefPtr<CanvasRenderingContext2D> context2d =
    707          static_cast<CanvasRenderingContext2D*>(cxt.get());
    708      if (context2d && !mPrintCallback) {
    709        CanvasImageSource source;
    710        source.SetAsHTMLCanvasElement() = this;
    711        ErrorResult err;
    712        context2d->DrawImage(source, 0.0, 0.0, err);
    713        rv = err.StealNSResult();
    714      }
    715    }
    716  }
    717  return rv;
    718 }
    719 
    720 nsChangeHint HTMLCanvasElement::GetAttributeChangeHint(
    721    const nsAtom* aAttribute, AttrModType aModType) const {
    722  nsChangeHint retval =
    723      nsGenericHTMLElement::GetAttributeChangeHint(aAttribute, aModType);
    724  if (aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height) {
    725    retval |= NS_STYLE_HINT_REFLOW;
    726  } else if (aAttribute == nsGkAtoms::moz_opaque) {
    727    retval |= NS_STYLE_HINT_VISUAL;
    728  }
    729  return retval;
    730 }
    731 
    732 void HTMLCanvasElement::MapAttributesIntoRule(
    733    MappedDeclarationsBuilder& aBuilder) {
    734  MapAspectRatioInto(aBuilder);
    735  MapCommonAttributesInto(aBuilder);
    736 }
    737 
    738 nsMapRuleToAttributesFunc HTMLCanvasElement::GetAttributeMappingFunction()
    739    const {
    740  return &MapAttributesIntoRule;
    741 }
    742 
    743 NS_IMETHODIMP_(bool)
    744 HTMLCanvasElement::IsAttributeMapped(const nsAtom* aAttribute) const {
    745  static const MappedAttributeEntry attributes[] = {
    746      {nsGkAtoms::width}, {nsGkAtoms::height}, {nullptr}};
    747  static const MappedAttributeEntry* const map[] = {attributes,
    748                                                    sCommonAttributeMap};
    749  return FindAttributeDependence(aAttribute, map);
    750 }
    751 
    752 bool HTMLCanvasElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
    753                                       const nsAString& aValue,
    754                                       nsIPrincipal* aMaybeScriptedPrincipal,
    755                                       nsAttrValue& aResult) {
    756  if (aNamespaceID == kNameSpaceID_None &&
    757      (aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height)) {
    758    return aResult.ParseNonNegativeIntValue(aValue);
    759  }
    760 
    761  return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
    762                                              aMaybeScriptedPrincipal, aResult);
    763 }
    764 
    765 void HTMLCanvasElement::ToDataURL(JSContext* aCx, const nsAString& aType,
    766                                  JS::Handle<JS::Value> aParams,
    767                                  nsAString& aDataURL,
    768                                  nsIPrincipal& aSubjectPrincipal,
    769                                  ErrorResult& aRv) {
    770  bool recheckCanRead = mOffscreenDisplay && mOffscreenDisplay->HasWorkerRef();
    771 
    772  if (!CallerCanRead(aSubjectPrincipal)) {
    773    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
    774    return;
    775  }
    776 
    777  nsString dataURL;
    778  nsresult rv = ToDataURLImpl(aCx, aSubjectPrincipal, aType, aParams, dataURL);
    779  if (recheckCanRead && !CallerCanRead(aSubjectPrincipal)) {
    780    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
    781    return;
    782  }
    783 
    784  if (NS_FAILED(rv)) {
    785    aDataURL.Assign(u"data:,"_ns);
    786    return;
    787  }
    788 
    789  aDataURL = std::move(dataURL);
    790 }
    791 
    792 void HTMLCanvasElement::SetMozPrintCallback(PrintCallback* aCallback) {
    793  mPrintCallback = aCallback;
    794 }
    795 
    796 PrintCallback* HTMLCanvasElement::GetMozPrintCallback() const {
    797  if (mOriginalCanvas) {
    798    return mOriginalCanvas->GetMozPrintCallback();
    799  }
    800  return mPrintCallback;
    801 }
    802 
    803 static uint32_t sCaptureSourceId = 0;
    804 class CanvasCaptureTrackSource : public MediaStreamTrackSource {
    805 public:
    806  NS_DECL_ISUPPORTS_INHERITED
    807  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CanvasCaptureTrackSource,
    808                                           MediaStreamTrackSource)
    809 
    810  CanvasCaptureTrackSource(nsIPrincipal* aPrincipal,
    811                           CanvasCaptureMediaStream* aCaptureStream)
    812      : MediaStreamTrackSource(
    813            aPrincipal, nsString(),
    814            TrackingId(TrackingId::Source::Canvas, sCaptureSourceId++,
    815                       TrackingId::TrackAcrossProcesses::Yes)),
    816        mCaptureStream(aCaptureStream) {}
    817 
    818  MediaSourceEnum GetMediaSource() const override {
    819    return MediaSourceEnum::Other;
    820  }
    821 
    822  bool HasAlpha() const override {
    823    if (!mCaptureStream || !mCaptureStream->Canvas()) {
    824      // In cycle-collection
    825      return false;
    826    }
    827    return !mCaptureStream->Canvas()->GetIsOpaque();
    828  }
    829 
    830  void GetSettings(dom::MediaTrackSettings& aResult) override {
    831    aResult.mWidth.Construct(mCaptureStream->Canvas()->Width());
    832    aResult.mHeight.Construct(mCaptureStream->Canvas()->Height());
    833  }
    834 
    835  void Stop() override {
    836    if (!mCaptureStream) {
    837      return;
    838    }
    839 
    840    mCaptureStream->StopCapture();
    841  }
    842 
    843  void Disable() override {}
    844 
    845  void Enable() override {}
    846 
    847 private:
    848  virtual ~CanvasCaptureTrackSource() = default;
    849 
    850  RefPtr<CanvasCaptureMediaStream> mCaptureStream;
    851 };
    852 
    853 NS_IMPL_ADDREF_INHERITED(CanvasCaptureTrackSource, MediaStreamTrackSource)
    854 NS_IMPL_RELEASE_INHERITED(CanvasCaptureTrackSource, MediaStreamTrackSource)
    855 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanvasCaptureTrackSource)
    856 NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSource)
    857 NS_IMPL_CYCLE_COLLECTION_INHERITED(CanvasCaptureTrackSource,
    858                                   MediaStreamTrackSource, mCaptureStream)
    859 
    860 already_AddRefed<CanvasCaptureMediaStream> HTMLCanvasElement::CaptureStream(
    861    const Optional<double>& aFrameRate, nsIPrincipal& aSubjectPrincipal,
    862    ErrorResult& aRv) {
    863  if (IsWriteOnly()) {
    864    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
    865    return nullptr;
    866  }
    867 
    868  nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
    869  if (!window) {
    870    aRv.Throw(NS_ERROR_FAILURE);
    871    return nullptr;
    872  }
    873 
    874  // Check if we transferred the OffscreenCanvas to a DOM worker. This is not
    875  // defined by the spec yet, so it is better to fail now than implement
    876  // something not compliant:
    877  // https://github.com/w3c/mediacapture-fromelement/issues/65
    878  // https://github.com/w3c/mediacapture-extensions/pull/26
    879  // https://github.com/web-platform-tests/wpt/issues/21102
    880  if (mOffscreenDisplay &&
    881      NS_WARN_IF(!mOffscreenDisplay->CanElementCaptureStream())) {
    882    aRv.ThrowNotSupportedError(
    883        "Capture stream not supported when OffscreenCanvas transferred to "
    884        "worker");
    885    return nullptr;
    886  }
    887 
    888  auto stream = MakeRefPtr<CanvasCaptureMediaStream>(window, this);
    889 
    890  nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
    891  nsresult rv = stream->Init(aFrameRate, principal);
    892  if (NS_FAILED(rv)) {
    893    aRv.Throw(rv);
    894    return nullptr;
    895  }
    896 
    897  RefPtr<MediaStreamTrack> track =
    898      new VideoStreamTrack(window, stream->GetSourceStream(),
    899                           new CanvasCaptureTrackSource(principal, stream));
    900  stream->AddTrackInternal(track);
    901 
    902  // Check site-specific permission and display prompt if appropriate.
    903  // If no permission, arrange for the frame capture listener to return
    904  // all-white, opaque image data.
    905  CanvasUtils::ImageExtraction extractionBehaviour =
    906      CanvasUtils::ImageExtractionResult(this, nullptr, &aSubjectPrincipal);
    907 
    908  rv = RegisterFrameCaptureListener(
    909      stream->FrameCaptureListener(),
    910      extractionBehaviour == CanvasUtils::ImageExtraction::Placeholder);
    911  if (NS_FAILED(rv)) {
    912    aRv.Throw(rv);
    913    return nullptr;
    914  }
    915 
    916  return stream.forget();
    917 }
    918 
    919 nsresult HTMLCanvasElement::ExtractData(JSContext* aCx,
    920                                        nsIPrincipal& aSubjectPrincipal,
    921                                        nsAString& aType,
    922                                        const nsAString& aOptions,
    923                                        nsIInputStream** aStream) {
    924  // Check site-specific permission and display prompt if appropriate.
    925  // If no permission, return all-white, opaque image data.
    926  CanvasUtils::ImageExtraction extractionBehaviour =
    927      CanvasUtils::ImageExtractionResult(this, aCx, &aSubjectPrincipal);
    928 
    929  if (extractionBehaviour != CanvasUtils::ImageExtraction::Placeholder) {
    930    auto size = GetWidthHeight();
    931    auto usage = CanvasUsage::CreateUsage(false, GetCurrentContextType(),
    932                                          CanvasExtractionAPI::ToDataURL, size,
    933                                          GetCurrentContext());
    934    OwnerDoc()->RecordCanvasUsage(usage);
    935  }
    936 
    937  nsCString randomizationKey = VoidCString();
    938  if (extractionBehaviour == CanvasUtils::ImageExtraction::EfficientRandomize) {
    939    nsRFPService::GetFingerprintingRandomizationKeyAsString(
    940        GetCookieJarSettings(), randomizationKey);
    941  }
    942 
    943  return ImageEncoder::ExtractData(aType, aOptions, GetSize(),
    944                                   extractionBehaviour, randomizationKey,
    945                                   mCurrentContext, mOffscreenDisplay, aStream);
    946 }
    947 
    948 nsresult HTMLCanvasElement::ToDataURLImpl(JSContext* aCx,
    949                                          nsIPrincipal& aSubjectPrincipal,
    950                                          const nsAString& aMimeType,
    951                                          const JS::Value& aEncoderOptions,
    952                                          nsAString& aDataURL) {
    953  CSSIntSize size = GetWidthHeight();
    954  if (size.height == 0 || size.width == 0) {
    955    aDataURL = u"data:,"_ns;
    956    return NS_OK;
    957  }
    958 
    959  nsAutoString type;
    960  nsContentUtils::ASCIIToLower(aMimeType, type);
    961 
    962  nsAutoString params;
    963  bool usingCustomParseOptions;
    964  nsresult rv =
    965      ParseParams(aCx, type, aEncoderOptions, params, &usingCustomParseOptions);
    966  if (NS_FAILED(rv)) {
    967    return rv;
    968  }
    969 
    970  nsCOMPtr<nsIInputStream> stream;
    971  rv =
    972      ExtractData(aCx, aSubjectPrincipal, type, params, getter_AddRefs(stream));
    973 
    974  // If there are unrecognized custom parse options, we should fall back to
    975  // the default values for the encoder without any options at all.
    976  if (rv == NS_ERROR_INVALID_ARG && usingCustomParseOptions) {
    977    rv = ExtractData(aCx, aSubjectPrincipal, type, u""_ns,
    978                     getter_AddRefs(stream));
    979  }
    980 
    981  NS_ENSURE_SUCCESS(rv, rv);
    982 
    983  // build data URL string
    984  aDataURL = u"data:"_ns + type + u";base64,"_ns;
    985 
    986  uint64_t count;
    987  rv = stream->Available(&count);
    988  NS_ENSURE_SUCCESS(rv, rv);
    989  NS_ENSURE_TRUE(count <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG);
    990 
    991  return Base64EncodeInputStream(stream, aDataURL, (uint32_t)count,
    992                                 aDataURL.Length());
    993 }
    994 
    995 UniquePtr<uint8_t[]> HTMLCanvasElement::GetImageBuffer(
    996    CanvasUtils::ImageExtraction aExtractionBehavior, int32_t* aOutFormat,
    997    gfx::IntSize* aOutImageSize) {
    998  if (mCurrentContext) {
    999    return mCurrentContext->GetImageBuffer(aExtractionBehavior, aOutFormat,
   1000                                           aOutImageSize);
   1001  }
   1002  if (mOffscreenDisplay) {
   1003    return mOffscreenDisplay->GetImageBuffer(aExtractionBehavior, aOutFormat,
   1004                                             aOutImageSize);
   1005  }
   1006  return nullptr;
   1007 }
   1008 
   1009 void HTMLCanvasElement::ToBlob(JSContext* aCx, BlobCallback& aCallback,
   1010                               const nsAString& aType,
   1011                               JS::Handle<JS::Value> aParams,
   1012                               nsIPrincipal& aSubjectPrincipal,
   1013                               ErrorResult& aRv) {
   1014  bool recheckCanRead = mOffscreenDisplay && mOffscreenDisplay->HasWorkerRef();
   1015 
   1016  if (!CallerCanRead(aSubjectPrincipal)) {
   1017    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
   1018    return;
   1019  }
   1020 
   1021  nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
   1022  MOZ_ASSERT(global);
   1023 
   1024  CSSIntSize elemSize = GetWidthHeight();
   1025  if (elemSize.width == 0 || elemSize.height == 0) {
   1026    // According to spec, blob should return null if either its horizontal
   1027    // dimension or its vertical dimension is zero. See link below.
   1028    // https://html.spec.whatwg.org/multipage/scripting.html#dom-canvas-toblob
   1029    OwnerDoc()->Dispatch(NewRunnableMethod<Blob*, const char*>(
   1030        "dom::HTMLCanvasElement::ToBlob", &aCallback,
   1031        static_cast<void (BlobCallback::*)(Blob*, const char*)>(
   1032            &BlobCallback::Call),
   1033        nullptr, nullptr));
   1034    return;
   1035  }
   1036 
   1037  // Check site-specific permission and display prompt if appropriate.
   1038  // If no permission, return all-white, opaque image data.
   1039  CanvasUtils::ImageExtraction extractionBehaviour =
   1040      CanvasUtils::ImageExtractionResult(this, aCx, &aSubjectPrincipal);
   1041 
   1042  // Encoder callback when encoding is complete.
   1043  class EncodeCallback : public EncodeCompleteCallback {
   1044   public:
   1045    EncodeCallback(nsIGlobalObject* aGlobal, BlobCallback* aCallback,
   1046                   OffscreenCanvasDisplayHelper* aOffscreenDisplay,
   1047                   nsIPrincipal* aSubjectPrincipal)
   1048        : mGlobal(aGlobal),
   1049          mBlobCallback(aCallback),
   1050          mOffscreenDisplay(aOffscreenDisplay),
   1051          mSubjectPrincipal(aSubjectPrincipal) {}
   1052 
   1053    // This is called on main thread.
   1054    MOZ_CAN_RUN_SCRIPT
   1055    nsresult ReceiveBlobImpl(already_AddRefed<BlobImpl> aBlobImpl) override {
   1056      MOZ_ASSERT(NS_IsMainThread());
   1057 
   1058      RefPtr<BlobImpl> blobImpl = aBlobImpl;
   1059 
   1060      RefPtr<Blob> blob;
   1061 
   1062      if (blobImpl && (!mOffscreenDisplay ||
   1063                       mOffscreenDisplay->CallerCanRead(*mSubjectPrincipal))) {
   1064        blob = Blob::Create(mGlobal, blobImpl);
   1065      }
   1066 
   1067      RefPtr<BlobCallback> callback(std::move(mBlobCallback));
   1068      ErrorResult rv;
   1069 
   1070      callback->Call(blob, rv);
   1071 
   1072      mGlobal = nullptr;
   1073      MOZ_ASSERT(!mBlobCallback);
   1074 
   1075      return rv.StealNSResult();
   1076    }
   1077 
   1078    bool CanBeDeletedOnAnyThread() override {
   1079      // EncodeCallback is used from the main thread only.
   1080      return false;
   1081    }
   1082 
   1083    nsCOMPtr<nsIGlobalObject> mGlobal;
   1084    RefPtr<BlobCallback> mBlobCallback;
   1085    RefPtr<OffscreenCanvasDisplayHelper> mOffscreenDisplay;
   1086    RefPtr<nsIPrincipal> mSubjectPrincipal;
   1087  };
   1088 
   1089  RefPtr<EncodeCompleteCallback> callback = new EncodeCallback(
   1090      global, &aCallback, recheckCanRead ? mOffscreenDisplay.get() : nullptr,
   1091      recheckCanRead ? &aSubjectPrincipal : nullptr);
   1092 
   1093  auto usage = CanvasUsage::CreateUsage(false, GetCurrentContextType(),
   1094                                        CanvasExtractionAPI::ToBlob,
   1095                                        GetWidthHeight(), GetCurrentContext());
   1096  if (extractionBehaviour != CanvasUtils::ImageExtraction::Placeholder) {
   1097    OwnerDoc()->RecordCanvasUsage(usage);
   1098  }
   1099 
   1100  CanvasRenderingContextHelper::ToBlob(aCx, callback, aType, aParams,
   1101                                       extractionBehaviour, aRv);
   1102 }
   1103 
   1104 OffscreenCanvas* HTMLCanvasElement::TransferControlToOffscreen(
   1105    ErrorResult& aRv) {
   1106  if (mCurrentContext || mOffscreenCanvas) {
   1107    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   1108    return nullptr;
   1109  }
   1110 
   1111  MOZ_ASSERT(!mOffscreenDisplay);
   1112 
   1113  nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow();
   1114  if (!win) {
   1115    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   1116    return nullptr;
   1117  }
   1118 
   1119  LayersBackend backend = LayersBackend::LAYERS_NONE;
   1120  if (nsIWidget* docWidget = nsContentUtils::WidgetForDocument(OwnerDoc())) {
   1121    if (WindowRenderer* renderer = docWidget->GetWindowRenderer()) {
   1122      backend = renderer->GetCompositorBackendType();
   1123    }
   1124  }
   1125 
   1126  CSSIntSize sz = GetWidthHeight();
   1127  mOffscreenDisplay =
   1128      MakeRefPtr<OffscreenCanvasDisplayHelper>(this, sz.width, sz.height);
   1129  mOffscreenCanvas = new OffscreenCanvas(win->AsGlobal(), sz.width, sz.height,
   1130                                         backend, do_AddRef(mOffscreenDisplay));
   1131  if (mWriteOnly) {
   1132    mOffscreenCanvas->SetWriteOnly(mExpandedReader);
   1133  }
   1134 
   1135  if (!mContextObserver) {
   1136    mContextObserver = new HTMLCanvasElementObserver(this);
   1137  }
   1138 
   1139  return mOffscreenCanvas;
   1140 }
   1141 
   1142 nsresult HTMLCanvasElement::GetContext(const nsAString& aContextId,
   1143                                       nsISupports** aContext) {
   1144  ErrorResult rv;
   1145  mMaybeModified = true;  // For FirstContentfulPaint
   1146  *aContext = GetContext(nullptr, aContextId, JS::NullHandleValue, rv).take();
   1147  return rv.StealNSResult();
   1148 }
   1149 
   1150 already_AddRefed<nsISupports> HTMLCanvasElement::GetContext(
   1151    JSContext* aCx, const nsAString& aContextId,
   1152    JS::Handle<JS::Value> aContextOptions, ErrorResult& aRv) {
   1153  if (mOffscreenCanvas) {
   1154    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   1155    return nullptr;
   1156  }
   1157 
   1158  mMaybeModified = true;  // For FirstContentfulPaint
   1159  return CanvasRenderingContextHelper::GetOrCreateContext(
   1160      aCx, aContextId,
   1161      aContextOptions.isObject() ? aContextOptions : JS::NullHandleValue, aRv);
   1162 }
   1163 
   1164 CSSIntSize HTMLCanvasElement::GetSize() { return GetWidthHeight(); }
   1165 
   1166 bool HTMLCanvasElement::IsWriteOnly() const {
   1167  if (mOffscreenDisplay && mOffscreenDisplay->IsWriteOnly()) {
   1168    return true;
   1169  }
   1170  return mWriteOnly;
   1171 }
   1172 
   1173 void HTMLCanvasElement::SetWriteOnly(
   1174    nsIPrincipal* aExpandedReader /* = nullptr */) {
   1175  mExpandedReader = aExpandedReader;
   1176  mWriteOnly = true;
   1177  if (mOffscreenCanvas) {
   1178    mOffscreenCanvas->SetWriteOnly(aExpandedReader);
   1179  }
   1180 }
   1181 
   1182 bool HTMLCanvasElement::CallerCanRead(nsIPrincipal& aPrincipal) const {
   1183  if (mOffscreenDisplay && !mOffscreenDisplay->CallerCanRead(aPrincipal)) {
   1184    return false;
   1185  }
   1186 
   1187  if (!mWriteOnly) {
   1188    return true;
   1189  }
   1190 
   1191  // If mExpandedReader is set, this canvas was tainted only by
   1192  // mExpandedReader's resources. So allow reading if the subject
   1193  // principal subsumes mExpandedReader.
   1194  if (mExpandedReader && aPrincipal.Subsumes(mExpandedReader)) {
   1195    return true;
   1196  }
   1197 
   1198  return nsContentUtils::PrincipalHasPermission(aPrincipal,
   1199                                                nsGkAtoms::all_urlsPermission);
   1200 }
   1201 
   1202 void HTMLCanvasElement::SetWidth(uint32_t aWidth, ErrorResult& aRv) {
   1203  if (mOffscreenCanvas) {
   1204    aRv.ThrowInvalidStateError(
   1205        "Cannot set width of placeholder canvas transferred to "
   1206        "OffscreenCanvas.");
   1207    return;
   1208  }
   1209 
   1210  SetUnsignedIntAttr(nsGkAtoms::width, aWidth, kFallbackIntrinsicWidthInPixels,
   1211                     aRv);
   1212 }
   1213 
   1214 void HTMLCanvasElement::SetHeight(uint32_t aHeight, ErrorResult& aRv) {
   1215  if (mOffscreenCanvas) {
   1216    aRv.ThrowInvalidStateError(
   1217        "Cannot set height of placeholder canvas transferred to "
   1218        "OffscreenCanvas.");
   1219    return;
   1220  }
   1221 
   1222  SetUnsignedIntAttr(nsGkAtoms::height, aHeight,
   1223                     kFallbackIntrinsicHeightInPixels, aRv);
   1224 }
   1225 
   1226 void HTMLCanvasElement::SetSize(const nsIntSize& aSize, ErrorResult& aRv) {
   1227  if (mOffscreenCanvas) {
   1228    aRv.ThrowInvalidStateError(
   1229        "Cannot set width of placeholder canvas transferred to "
   1230        "OffscreenCanvas.");
   1231    return;
   1232  }
   1233 
   1234  if (NS_WARN_IF(aSize.IsEmpty())) {
   1235    aRv.ThrowRangeError("Canvas size is empty, must be non-empty.");
   1236    return;
   1237  }
   1238 
   1239  SetUnsignedIntAttr(nsGkAtoms::width, aSize.width,
   1240                     kFallbackIntrinsicWidthInPixels, aRv);
   1241  MOZ_ASSERT(!aRv.Failed());
   1242  SetUnsignedIntAttr(nsGkAtoms::height, aSize.height,
   1243                     kFallbackIntrinsicHeightInPixels, aRv);
   1244  MOZ_ASSERT(!aRv.Failed());
   1245 }
   1246 
   1247 void HTMLCanvasElement::FlushOffscreenCanvas() {
   1248  if (mOffscreenDisplay) {
   1249    mOffscreenDisplay->FlushForDisplay();
   1250  }
   1251 }
   1252 
   1253 void HTMLCanvasElement::InvalidateCanvasPlaceholder(uint32_t aWidth,
   1254                                                    uint32_t aHeight) {
   1255  ErrorResult rv;
   1256  SetUnsignedIntAttr(nsGkAtoms::width, aWidth, kFallbackIntrinsicWidthInPixels,
   1257                     rv);
   1258  MOZ_ASSERT(!rv.Failed());
   1259  SetUnsignedIntAttr(nsGkAtoms::height, aHeight,
   1260                     kFallbackIntrinsicHeightInPixels, rv);
   1261  MOZ_ASSERT(!rv.Failed());
   1262 }
   1263 
   1264 static bool InvalidateCanvasData(nsIFrame* aFrame, uint32_t aKey) {
   1265  RefPtr data = GetWebRenderUserData<WebRenderCanvasData>(aFrame, aKey);
   1266  if (!data) {
   1267    return false;
   1268  }
   1269  CanvasRenderer* renderer = data->GetCanvasRenderer();
   1270  if (!renderer) {
   1271    return false;
   1272  }
   1273  renderer->SetDirty();
   1274  aFrame->SchedulePaint(nsIFrame::PAINT_COMPOSITE_ONLY);
   1275  return true;
   1276 }
   1277 
   1278 void HTMLCanvasElement::InvalidateCanvasContent(const gfx::Rect* damageRect) {
   1279  // Cache the current ImageContainer to avoid contention on the mutex.
   1280  if (mOffscreenDisplay) {
   1281    mImageContainer = mOffscreenDisplay->GetImageContainer();
   1282  }
   1283 
   1284  // We don't need to flush anything here; if there's no frame or if
   1285  // we plan to reframe we don't need to invalidate it anyway.
   1286  nsIFrame* frame = GetPrimaryFrame();
   1287  if (!frame) {
   1288    return;
   1289  }
   1290 
   1291  // When using layers-free WebRender, we cannot invalidate the layer (because
   1292  // there isn't one). Instead, we mark the CanvasRenderer dirty and scheduling
   1293  // an empty transaction which is effectively equivalent.
   1294  bool invalidated = false;
   1295  for (auto* item : frame->DisplayItems()) {
   1296    if (item->GetType() == DisplayItemType::TYPE_CANVAS) {
   1297      invalidated |= InvalidateCanvasData(frame, item->GetPerFrameKey());
   1298    }
   1299  }
   1300  invalidated =
   1301      invalidated ||
   1302      InvalidateCanvasData(frame, uint32_t(DisplayItemType::TYPE_CANVAS));
   1303  if (!invalidated) {
   1304    if (damageRect) {
   1305      CSSIntSize size = GetWidthHeight();
   1306      if (size.width != 0 && size.height != 0) {
   1307        gfx::IntRect invalRect = gfx::IntRect::Truncate(*damageRect);
   1308        frame->InvalidateLayer(DisplayItemType::TYPE_CANVAS, &invalRect);
   1309      }
   1310    } else {
   1311      frame->InvalidateLayer(DisplayItemType::TYPE_CANVAS);
   1312    }
   1313 
   1314    // This path is taken in two situations:
   1315    // 1) WebRender is enabled and has not yet processed a display list.
   1316    // 2) WebRender is disabled and layer invalidation failed.
   1317    // In both cases, schedule a full paint to properly update canvas.
   1318    frame->SchedulePaint(nsIFrame::PAINT_DEFAULT, false);
   1319  }
   1320 
   1321  /*
   1322   * Treat canvas invalidations as animation activity for JS. Frequently
   1323   * invalidating a canvas will feed into heuristics and cause JIT code to be
   1324   * kept around longer, for smoother animations.
   1325   */
   1326  if (nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow()) {
   1327    if (JSObject* obj = win->AsGlobal()->GetGlobalJSObject()) {
   1328      js::NotifyAnimationActivity(obj);
   1329    }
   1330  }
   1331 }
   1332 
   1333 void HTMLCanvasElement::InvalidateCanvas() {
   1334  // We don't need to flush anything here; if there's no frame or if
   1335  // we plan to reframe we don't need to invalidate it anyway.
   1336  nsIFrame* frame = GetPrimaryFrame();
   1337  if (!frame) return;
   1338 
   1339  frame->InvalidateFrame();
   1340 }
   1341 
   1342 bool HTMLCanvasElement::GetIsOpaque() {
   1343  if (mCurrentContext) {
   1344    return mCurrentContext->GetIsOpaque();
   1345  }
   1346 
   1347  return GetOpaqueAttr();
   1348 }
   1349 
   1350 bool HTMLCanvasElement::GetOpaqueAttr() {
   1351  return HasAttr(nsGkAtoms::moz_opaque);
   1352 }
   1353 
   1354 CanvasContextType HTMLCanvasElement::GetCurrentContextType() {
   1355  if (mCurrentContextType == CanvasContextType::NoContext &&
   1356      mOffscreenDisplay) {
   1357    mCurrentContextType = mOffscreenDisplay->GetContextType();
   1358  }
   1359  return mCurrentContextType;
   1360 }
   1361 
   1362 already_AddRefed<Image> HTMLCanvasElement::GetAsImage() {
   1363  if (mOffscreenDisplay) {
   1364    return mOffscreenDisplay->GetAsImage();
   1365  }
   1366 
   1367  if (mCurrentContext) {
   1368    return mCurrentContext->GetAsImage();
   1369  }
   1370 
   1371  return nullptr;
   1372 }
   1373 
   1374 bool HTMLCanvasElement::UpdateWebRenderCanvasData(
   1375    nsDisplayListBuilder* aBuilder, WebRenderCanvasData* aCanvasData) {
   1376  MOZ_ASSERT(!mOffscreenDisplay);
   1377 
   1378  if (mCurrentContext) {
   1379    return mCurrentContext->UpdateWebRenderCanvasData(aBuilder, aCanvasData);
   1380  }
   1381 
   1382  // Clear CanvasRenderer of WebRenderCanvasData
   1383  aCanvasData->ClearCanvasRenderer();
   1384  return false;
   1385 }
   1386 
   1387 bool HTMLCanvasElement::InitializeCanvasRenderer(nsDisplayListBuilder* aBuilder,
   1388                                                 CanvasRenderer* aRenderer) {
   1389  MOZ_ASSERT(!mOffscreenDisplay);
   1390 
   1391  if (mCurrentContext) {
   1392    return mCurrentContext->InitializeCanvasRenderer(aBuilder, aRenderer);
   1393  }
   1394 
   1395  return false;
   1396 }
   1397 
   1398 void HTMLCanvasElement::MarkContextClean() {
   1399  if (!mCurrentContext) return;
   1400 
   1401  mCurrentContext->MarkContextClean();
   1402 }
   1403 
   1404 void HTMLCanvasElement::MarkContextCleanForFrameCapture() {
   1405  if (!mCurrentContext) return;
   1406 
   1407  mCurrentContext->MarkContextCleanForFrameCapture();
   1408 }
   1409 
   1410 Watchable<FrameCaptureState>* HTMLCanvasElement::GetFrameCaptureState() {
   1411  if (!mCurrentContext) {
   1412    return nullptr;
   1413  }
   1414  return mCurrentContext->GetFrameCaptureState();
   1415 }
   1416 
   1417 nsresult HTMLCanvasElement::RegisterFrameCaptureListener(
   1418    FrameCaptureListener* aListener, bool aReturnPlaceholderData) {
   1419  WeakPtr<FrameCaptureListener> listener = aListener;
   1420 
   1421  if (mRequestedFrameListeners.Contains(listener)) {
   1422    return NS_OK;
   1423  }
   1424 
   1425  if (!mRequestedFrameRefreshObserver) {
   1426    PresShell* shell = nsContentUtils::FindPresShellForDocument(OwnerDoc());
   1427    if (NS_WARN_IF(!shell)) {
   1428      return NS_ERROR_FAILURE;
   1429    }
   1430 
   1431    nsPresContext* context = shell->GetPresContext();
   1432    if (NS_WARN_IF(!context)) {
   1433      return NS_ERROR_FAILURE;
   1434    }
   1435 
   1436    context = context->GetRootPresContext();
   1437    if (NS_WARN_IF(!context)) {
   1438      return NS_ERROR_FAILURE;
   1439    }
   1440 
   1441    nsRefreshDriver* driver = context->RefreshDriver();
   1442    MOZ_ASSERT(driver);
   1443 
   1444    mRequestedFrameRefreshObserver =
   1445        new RequestedFrameRefreshObserver(this, driver, aReturnPlaceholderData);
   1446  } else {
   1447    mRequestedFrameRefreshObserver->SetReturnPlaceholderData(
   1448        aReturnPlaceholderData);
   1449  }
   1450 
   1451  mRequestedFrameListeners.AppendElement(listener);
   1452  mRequestedFrameRefreshObserver->Register();
   1453  return NS_OK;
   1454 }
   1455 
   1456 bool HTMLCanvasElement::IsFrameCaptureRequested(const TimeStamp& aTime) const {
   1457  for (WeakPtr<FrameCaptureListener> listener : mRequestedFrameListeners) {
   1458    if (!listener) {
   1459      continue;
   1460    }
   1461 
   1462    if (listener->FrameCaptureRequested(aTime)) {
   1463      return true;
   1464    }
   1465  }
   1466  return false;
   1467 }
   1468 
   1469 void HTMLCanvasElement::ProcessDestroyedFrameListeners() {
   1470  // Remove destroyed listeners from the list.
   1471  mRequestedFrameListeners.RemoveElementsBy(
   1472      [](const auto& weakListener) { return !weakListener; });
   1473 
   1474  if (mRequestedFrameListeners.IsEmpty()) {
   1475    mRequestedFrameRefreshObserver->Unregister();
   1476  }
   1477 }
   1478 
   1479 void HTMLCanvasElement::SetFrameCapture(
   1480    already_AddRefed<SourceSurface> aSurface, const TimeStamp& aTime) {
   1481  RefPtr<SourceSurface> surface = aSurface;
   1482  RefPtr<SourceSurfaceImage> image =
   1483      new SourceSurfaceImage(surface->GetSize(), surface);
   1484 
   1485  for (WeakPtr<FrameCaptureListener> listener : mRequestedFrameListeners) {
   1486    if (!listener) {
   1487      continue;
   1488    }
   1489 
   1490    RefPtr<Image> imageRefCopy = image.get();
   1491    listener->NewFrame(imageRefCopy.forget(), aTime);
   1492  }
   1493 }
   1494 
   1495 already_AddRefed<SourceSurface> HTMLCanvasElement::GetSurfaceSnapshot(
   1496    gfxAlphaType* const aOutAlphaType, DrawTarget* aTarget) {
   1497  if (mCurrentContext) {
   1498    return mCurrentContext->GetOptimizedSnapshot(aTarget, aOutAlphaType);
   1499  } else if (mOffscreenDisplay) {
   1500    return mOffscreenDisplay->GetSurfaceSnapshot();
   1501  }
   1502  return nullptr;
   1503 }
   1504 
   1505 layers::LayersBackend HTMLCanvasElement::GetCompositorBackendType() const {
   1506  nsIWidget* docWidget = nsContentUtils::WidgetForDocument(OwnerDoc());
   1507  if (docWidget) {
   1508    WindowRenderer* renderer = docWidget->GetWindowRenderer();
   1509    if (renderer) {
   1510      return renderer->GetCompositorBackendType();
   1511    }
   1512  }
   1513 
   1514  return LayersBackend::LAYERS_NONE;
   1515 }
   1516 
   1517 void HTMLCanvasElement::OnMemoryPressure() {
   1518  // FIXME(aosmond): We need to implement memory pressure handling for
   1519  // OffscreenCanvas when it is on worker threads. See bug 1746260.
   1520 
   1521  if (mCurrentContext) {
   1522    mCurrentContext->OnMemoryPressure();
   1523  }
   1524 }
   1525 
   1526 void HTMLCanvasElement::OnDeviceReset() {
   1527  if (!mOffscreenCanvas && mCurrentContext) {
   1528    mCurrentContext->ResetBitmap();
   1529  }
   1530 }
   1531 
   1532 ClientWebGLContext* HTMLCanvasElement::GetWebGLContext() {
   1533  if (GetCurrentContextType() != CanvasContextType::WebGL1 &&
   1534      GetCurrentContextType() != CanvasContextType::WebGL2) {
   1535    return nullptr;
   1536  }
   1537 
   1538  return static_cast<ClientWebGLContext*>(GetCurrentContext());
   1539 }
   1540 
   1541 webgpu::CanvasContext* HTMLCanvasElement::GetWebGPUContext() {
   1542  if (GetCurrentContextType() != CanvasContextType::WebGPU) {
   1543    return nullptr;
   1544  }
   1545 
   1546  return static_cast<webgpu::CanvasContext*>(GetCurrentContext());
   1547 }
   1548 
   1549 }  // namespace mozilla::dom