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