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