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/. */
7 #include "mozilla/dom/HTMLCanvasElement.h"
10 #include "ImageEncoder.h"
12 #include "jsfriendapi.h"
14 #include "MediaSegment.h"
15 #include "mozilla/Assertions.h"
16 #include "mozilla/Base64.h"
17 #include "mozilla/CheckedInt.h"
18 #include "mozilla/dom/CanvasCaptureMediaStream.h"
19 #include "mozilla/dom/CanvasRenderingContext2D.h"
20 #include "mozilla/dom/Event.h"
21 #include "mozilla/dom/File.h"
22 #include "mozilla/dom/HTMLCanvasElementBinding.h"
23 #include "mozilla/dom/MediaStreamTrack.h"
24 #include "mozilla/dom/MouseEvent.h"
25 #include "mozilla/dom/OffscreenCanvas.h"
26 #include "mozilla/EventDispatcher.h"
27 #include "mozilla/gfx/Rect.h"
28 #include "mozilla/layers/AsyncCanvasRenderer.h"
29 #include "mozilla/layers/WebRenderCanvasRenderer.h"
30 #include "mozilla/layers/WebRenderUserData.h"
31 #include "mozilla/MouseEvents.h"
32 #include "mozilla/Preferences.h"
33 #include "mozilla/Telemetry.h"
34 #include "nsAttrValueInlines.h"
35 #include "nsContentUtils.h"
36 #include "nsDisplayList.h"
37 #include "nsDOMJSUtils.h"
38 #include "nsIScriptSecurityManager.h"
40 #include "nsIWritablePropertyBag2.h"
41 #include "nsIXPConnect.h"
42 #include "nsJSUtils.h"
43 #include "nsLayoutUtils.h"
44 #include "nsMathUtils.h"
45 #include "nsNetUtil.h"
46 #include "nsRefreshDriver.h"
47 #include "nsStreamUtils.h"
48 #include "ActiveLayerTracker.h"
49 #include "CanvasUtils.h"
50 #include "VRManagerChild.h"
51 #include "WebGL1Context.h"
52 #include "WebGL2Context.h"
54 using namespace mozilla::layers
;
55 using namespace mozilla::gfx
;
57 NS_IMPL_NS_NEW_HTML_ELEMENT(Canvas
)
62 class RequestedFrameRefreshObserver
: public nsARefreshObserver
64 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RequestedFrameRefreshObserver
, override
)
67 RequestedFrameRefreshObserver(HTMLCanvasElement
* const aOwningElement
,
68 nsRefreshDriver
* aRefreshDriver
,
69 bool aReturnPlaceholderData
)
71 mReturnPlaceholderData(aReturnPlaceholderData
),
72 mOwningElement(aOwningElement
),
73 mRefreshDriver(aRefreshDriver
)
75 MOZ_ASSERT(mOwningElement
);
78 static already_AddRefed
<DataSourceSurface
>
79 CopySurface(const RefPtr
<SourceSurface
>& aSurface
,
80 bool aReturnPlaceholderData
)
82 RefPtr
<DataSourceSurface
> data
= aSurface
->GetDataSurface();
87 DataSourceSurface::ScopedMap
read(data
, DataSourceSurface::READ
);
88 if (!read
.IsMapped()) {
92 RefPtr
<DataSourceSurface
> copy
=
93 Factory::CreateDataSourceSurfaceWithStride(data
->GetSize(),
100 DataSourceSurface::ScopedMap
write(copy
, DataSourceSurface::WRITE
);
101 if (!write
.IsMapped()) {
105 MOZ_ASSERT(read
.GetStride() == write
.GetStride());
106 MOZ_ASSERT(data
->GetSize() == copy
->GetSize());
107 MOZ_ASSERT(data
->GetFormat() == copy
->GetFormat());
109 if (aReturnPlaceholderData
) {
110 // If returning placeholder data, fill the frame copy with white pixels.
111 memset(write
.GetData(), 0xFF,
112 write
.GetStride() * copy
->GetSize().height
);
114 memcpy(write
.GetData(), read
.GetData(),
115 write
.GetStride() * copy
->GetSize().height
);
118 return copy
.forget();
121 void SetReturnPlaceholderData(bool aReturnPlaceholderData
)
123 mReturnPlaceholderData
= aReturnPlaceholderData
;
126 void WillRefresh(TimeStamp aTime
) override
128 MOZ_ASSERT(NS_IsMainThread());
130 AUTO_PROFILER_LABEL("RequestedFrameRefreshObserver::WillRefresh", OTHER
);
132 if (!mOwningElement
) {
136 if (mOwningElement
->IsWriteOnly()) {
140 if (mOwningElement
->IsContextCleanForFrameCapture()) {
144 mOwningElement
->ProcessDestroyedFrameListeners();
146 if (!mOwningElement
->IsFrameCaptureRequested()) {
150 RefPtr
<SourceSurface
> snapshot
;
153 "RequestedFrameRefreshObserver::WillRefresh:GetSnapshot", OTHER
);
154 snapshot
= mOwningElement
->GetSurfaceSnapshot(nullptr);
160 RefPtr
<DataSourceSurface
> copy
;
163 "RequestedFrameRefreshObserver::WillRefresh:CopySurface", OTHER
);
164 copy
= CopySurface(snapshot
, mReturnPlaceholderData
);
172 "RequestedFrameRefreshObserver::WillRefresh:SetFrame", OTHER
);
173 mOwningElement
->SetFrameCapture(copy
.forget(), aTime
);
174 mOwningElement
->MarkContextCleanForFrameCapture();
178 void DetachFromRefreshDriver()
180 MOZ_ASSERT(mOwningElement
);
181 MOZ_ASSERT(mRefreshDriver
);
184 mRefreshDriver
= nullptr;
193 MOZ_ASSERT(mRefreshDriver
);
194 if (mRefreshDriver
) {
195 mRefreshDriver
->AddRefreshObserver(this, FlushType::Display
);
206 MOZ_ASSERT(mRefreshDriver
);
207 if (mRefreshDriver
) {
208 mRefreshDriver
->RemoveRefreshObserver(this, FlushType::Display
);
214 virtual ~RequestedFrameRefreshObserver()
216 MOZ_ASSERT(!mRefreshDriver
);
217 MOZ_ASSERT(!mRegistered
);
221 bool mReturnPlaceholderData
;
222 HTMLCanvasElement
* const mOwningElement
;
223 RefPtr
<nsRefreshDriver
> mRefreshDriver
;
226 // ---------------------------------------------------------------------------
228 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(HTMLCanvasPrintState
, mCanvas
,
231 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(HTMLCanvasPrintState
, AddRef
)
232 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(HTMLCanvasPrintState
, Release
)
234 HTMLCanvasPrintState::HTMLCanvasPrintState(HTMLCanvasElement
* aCanvas
,
235 nsICanvasRenderingContextInternal
* aContext
,
236 nsITimerCallback
* aCallback
)
237 : mIsDone(false), mPendingNotify(false), mCanvas(aCanvas
),
238 mContext(aContext
), mCallback(aCallback
)
242 HTMLCanvasPrintState::~HTMLCanvasPrintState()
246 /* virtual */ JSObject
*
247 HTMLCanvasPrintState::WrapObject(JSContext
* aCx
, JS::Handle
<JSObject
*> aGivenProto
)
249 return MozCanvasPrintState_Binding::Wrap(aCx
, this, aGivenProto
);
253 HTMLCanvasPrintState::Context() const
259 HTMLCanvasPrintState::Done()
261 if (!mPendingNotify
&& !mIsDone
) {
262 // The canvas needs to be invalidated for printing reftests on linux to
265 mCanvas
->InvalidateCanvas();
267 RefPtr
<nsRunnableMethod
<HTMLCanvasPrintState
>> doneEvent
=
268 NewRunnableMethod("dom::HTMLCanvasPrintState::NotifyDone",
270 &HTMLCanvasPrintState::NotifyDone
);
271 if (NS_SUCCEEDED(NS_DispatchToCurrentThread(doneEvent
))) {
272 mPendingNotify
= true;
278 HTMLCanvasPrintState::NotifyDone()
281 mPendingNotify
= false;
283 mCallback
->Notify(nullptr);
287 // ---------------------------------------------------------------------------
289 HTMLCanvasElementObserver::HTMLCanvasElementObserver(HTMLCanvasElement
* aElement
)
292 RegisterVisibilityChangeEvent();
293 RegisterMemoryPressureEvent();
296 HTMLCanvasElementObserver::~HTMLCanvasElementObserver()
302 HTMLCanvasElementObserver::Destroy()
304 UnregisterMemoryPressureEvent();
305 UnregisterVisibilityChangeEvent();
310 HTMLCanvasElementObserver::RegisterVisibilityChangeEvent()
316 nsIDocument
* document
= mElement
->OwnerDoc();
317 document
->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"),
322 HTMLCanvasElementObserver::UnregisterVisibilityChangeEvent()
328 nsIDocument
* document
= mElement
->OwnerDoc();
329 document
->RemoveSystemEventListener(NS_LITERAL_STRING("visibilitychange"),
334 HTMLCanvasElementObserver::RegisterMemoryPressureEvent()
340 nsCOMPtr
<nsIObserverService
> observerService
=
341 mozilla::services::GetObserverService();
343 MOZ_ASSERT(observerService
);
346 observerService
->AddObserver(this, "memory-pressure", false);
350 HTMLCanvasElementObserver::UnregisterMemoryPressureEvent()
356 nsCOMPtr
<nsIObserverService
> observerService
=
357 mozilla::services::GetObserverService();
359 // Do not assert on observerService here. This might be triggered by
360 // the cycle collector at a late enough time, that XPCOM services are
361 // no longer available. See bug 1029504.
363 observerService
->RemoveObserver(this, "memory-pressure");
367 HTMLCanvasElementObserver::Observe(nsISupports
*, const char* aTopic
, const char16_t
*)
369 if (!mElement
|| strcmp(aTopic
, "memory-pressure")) {
373 mElement
->OnMemoryPressure();
379 HTMLCanvasElementObserver::HandleEvent(Event
* aEvent
)
382 aEvent
->GetType(type
);
383 if (!mElement
|| !type
.EqualsLiteral("visibilitychange")) {
387 mElement
->OnVisibilityChange();
392 NS_IMPL_ISUPPORTS(HTMLCanvasElementObserver
, nsIObserver
)
394 // ---------------------------------------------------------------------------
396 HTMLCanvasElement::HTMLCanvasElement(already_AddRefed
<mozilla::dom::NodeInfo
>&& aNodeInfo
)
397 : nsGenericHTMLElement(std::move(aNodeInfo
)),
402 HTMLCanvasElement::~HTMLCanvasElement()
404 if (mContextObserver
) {
405 mContextObserver
->Destroy();
406 mContextObserver
= nullptr;
409 ResetPrintCallback();
410 if (mRequestedFrameRefreshObserver
) {
411 mRequestedFrameRefreshObserver
->DetachFromRefreshDriver();
414 if (mAsyncCanvasRenderer
) {
415 mAsyncCanvasRenderer
->mHTMLCanvasElement
= nullptr;
419 NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLCanvasElement
, nsGenericHTMLElement
,
420 mCurrentContext
, mPrintCallback
,
421 mPrintState
, mOriginalCanvas
,
424 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(HTMLCanvasElement
, nsGenericHTMLElement
)
426 NS_IMPL_ELEMENT_CLONE(HTMLCanvasElement
)
428 /* virtual */ JSObject
*
429 HTMLCanvasElement::WrapNode(JSContext
* aCx
, JS::Handle
<JSObject
*> aGivenProto
)
431 return HTMLCanvasElement_Binding::Wrap(aCx
, this, aGivenProto
);
434 already_AddRefed
<nsICanvasRenderingContextInternal
>
435 HTMLCanvasElement::CreateContext(CanvasContextType aContextType
)
437 // Note that the compositor backend will be LAYERS_NONE if there is no widget.
438 RefPtr
<nsICanvasRenderingContextInternal
> ret
=
439 CreateContextHelper(aContextType
, GetCompositorBackendType());
441 // Add Observer for webgl canvas.
442 if (aContextType
== CanvasContextType::WebGL1
||
443 aContextType
== CanvasContextType::WebGL2
) {
444 if (!mContextObserver
) {
445 mContextObserver
= new HTMLCanvasElementObserver(this);
449 ret
->SetCanvasElement(this);
454 HTMLCanvasElement::GetWidthHeight()
456 nsIntSize
size(DEFAULT_CANVAS_WIDTH
, DEFAULT_CANVAS_HEIGHT
);
457 const nsAttrValue
* value
;
459 if ((value
= GetParsedAttr(nsGkAtoms::width
)) &&
460 value
->Type() == nsAttrValue::eInteger
)
462 size
.width
= value
->GetIntegerValue();
465 if ((value
= GetParsedAttr(nsGkAtoms::height
)) &&
466 value
->Type() == nsAttrValue::eInteger
)
468 size
.height
= value
->GetIntegerValue();
471 MOZ_ASSERT(size
.width
>= 0 && size
.height
>= 0,
472 "we should've required <canvas> width/height attrs to be "
473 "unsigned (non-negative) values");
479 HTMLCanvasElement::AfterSetAttr(int32_t aNamespaceID
, nsAtom
* aName
,
480 const nsAttrValue
* aValue
,
481 const nsAttrValue
* aOldValue
,
482 nsIPrincipal
* aSubjectPrincipal
,
485 AfterMaybeChangeAttr(aNamespaceID
, aName
, aNotify
);
487 return nsGenericHTMLElement::AfterSetAttr(aNamespaceID
, aName
, aValue
,
488 aOldValue
, aSubjectPrincipal
, aNotify
);
492 HTMLCanvasElement::OnAttrSetButNotChanged(int32_t aNamespaceID
, nsAtom
* aName
,
493 const nsAttrValueOrString
& aValue
,
496 AfterMaybeChangeAttr(aNamespaceID
, aName
, aNotify
);
498 return nsGenericHTMLElement::OnAttrSetButNotChanged(aNamespaceID
, aName
,
503 HTMLCanvasElement::AfterMaybeChangeAttr(int32_t aNamespaceID
, nsAtom
* aName
,
506 if (mCurrentContext
&& aNamespaceID
== kNameSpaceID_None
&&
507 (aName
== nsGkAtoms::width
|| aName
== nsGkAtoms::height
||
508 aName
== nsGkAtoms::moz_opaque
)) {
510 UpdateContext(nullptr, JS::NullHandleValue
, dummy
);
515 HTMLCanvasElement::HandlePrintCallback(nsPresContext::nsPresContextType aType
)
517 // Only call the print callback here if 1) we're in a print testing mode or
518 // print preview mode, 2) the canvas has a print callback and 3) the callback
519 // hasn't already been called. For real printing the callback is handled in
520 // nsSimplePageSequenceFrame::PrePrintNextPage.
521 if ((aType
== nsPresContext::eContext_PageLayout
||
522 aType
== nsPresContext::eContext_PrintPreview
) &&
523 !mPrintState
&& GetMozPrintCallback()) {
524 DispatchPrintCallback(nullptr);
529 HTMLCanvasElement::DispatchPrintCallback(nsITimerCallback
* aCallback
)
531 // For print reftests the context may not be initialized yet, so get a context
532 // so mCurrentContext is set.
533 if (!mCurrentContext
) {
535 nsCOMPtr
<nsISupports
> context
;
536 rv
= GetContext(NS_LITERAL_STRING("2d"), getter_AddRefs(context
));
537 NS_ENSURE_SUCCESS(rv
, rv
);
539 mPrintState
= new HTMLCanvasPrintState(this, mCurrentContext
, aCallback
);
541 RefPtr
<nsRunnableMethod
<HTMLCanvasElement
>> renderEvent
=
542 NewRunnableMethod("dom::HTMLCanvasElement::CallPrintCallback",
544 &HTMLCanvasElement::CallPrintCallback
);
545 return OwnerDoc()->Dispatch(TaskCategory::Other
, renderEvent
.forget());
549 HTMLCanvasElement::CallPrintCallback()
552 GetMozPrintCallback()->Call(*mPrintState
, rv
);
556 HTMLCanvasElement::ResetPrintCallback()
559 mPrintState
= nullptr;
564 HTMLCanvasElement::IsPrintCallbackDone()
566 if (mPrintState
== nullptr) {
570 return mPrintState
->mIsDone
;
574 HTMLCanvasElement::GetOriginalCanvas()
576 return mOriginalCanvas
? mOriginalCanvas
.get() : this;
580 HTMLCanvasElement::CopyInnerTo(HTMLCanvasElement
* aDest
)
582 nsresult rv
= nsGenericHTMLElement::CopyInnerTo(aDest
);
583 NS_ENSURE_SUCCESS(rv
, rv
);
584 if (aDest
->OwnerDoc()->IsStaticDocument()) {
585 aDest
->mOriginalCanvas
= this;
587 // We make sure that the canvas is not zero sized since that would cause
588 // the DrawImage call below to return an error, which would cause printing
590 nsIntSize size
= GetWidthHeight();
591 if (size
.height
> 0 && size
.width
> 0) {
592 nsCOMPtr
<nsISupports
> cxt
;
593 aDest
->GetContext(NS_LITERAL_STRING("2d"), getter_AddRefs(cxt
));
594 RefPtr
<CanvasRenderingContext2D
> context2d
=
595 static_cast<CanvasRenderingContext2D
*>(cxt
.get());
596 if (context2d
&& !mPrintCallback
) {
597 CanvasImageSource source
;
598 source
.SetAsHTMLCanvasElement() = this;
600 context2d
->DrawImage(source
, 0.0, 0.0, err
);
601 rv
= err
.StealNSResult();
609 HTMLCanvasElement::GetEventTargetParent(EventChainPreVisitor
& aVisitor
)
611 if (aVisitor
.mEvent
->mClass
== eMouseEventClass
) {
612 WidgetMouseEventBase
* evt
= (WidgetMouseEventBase
*)aVisitor
.mEvent
;
613 if (mCurrentContext
) {
614 nsIFrame
*frame
= GetPrimaryFrame();
618 nsPoint ptInRoot
= nsLayoutUtils::GetEventCoordinatesRelativeTo(evt
, frame
);
619 nsRect paddingRect
= frame
->GetContentRectRelativeToSelf();
621 hitpoint
.x
= (ptInRoot
.x
- paddingRect
.x
) / AppUnitsPerCSSPixel();
622 hitpoint
.y
= (ptInRoot
.y
- paddingRect
.y
) / AppUnitsPerCSSPixel();
624 evt
->region
= mCurrentContext
->GetHitRegion(hitpoint
);
625 aVisitor
.mCanHandle
= true;
628 nsGenericHTMLElement::GetEventTargetParent(aVisitor
);
632 HTMLCanvasElement::GetAttributeChangeHint(const nsAtom
* aAttribute
,
633 int32_t aModType
) const
635 nsChangeHint retval
=
636 nsGenericHTMLElement::GetAttributeChangeHint(aAttribute
, aModType
);
637 if (aAttribute
== nsGkAtoms::width
||
638 aAttribute
== nsGkAtoms::height
)
640 retval
|= NS_STYLE_HINT_REFLOW
;
641 } else if (aAttribute
== nsGkAtoms::moz_opaque
)
643 retval
|= NS_STYLE_HINT_VISUAL
;
649 HTMLCanvasElement::ParseAttribute(int32_t aNamespaceID
,
651 const nsAString
& aValue
,
652 nsIPrincipal
* aMaybeScriptedPrincipal
,
653 nsAttrValue
& aResult
)
655 if (aNamespaceID
== kNameSpaceID_None
&&
656 (aAttribute
== nsGkAtoms::width
|| aAttribute
== nsGkAtoms::height
)) {
657 return aResult
.ParseNonNegativeIntValue(aValue
);
660 return nsGenericHTMLElement::ParseAttribute(aNamespaceID
, aAttribute
, aValue
,
661 aMaybeScriptedPrincipal
, aResult
);
667 HTMLCanvasElement::ToDataURL(JSContext
* aCx
, const nsAString
& aType
,
668 JS::Handle
<JS::Value
> aParams
,
670 nsIPrincipal
& aSubjectPrincipal
,
673 // mWriteOnly check is redundant, but optimizes for the common case.
674 if (mWriteOnly
&& !CallerCanRead(aCx
)) {
675 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
679 aRv
= ToDataURLImpl(aCx
, aSubjectPrincipal
, aType
, aParams
, aDataURL
);
683 HTMLCanvasElement::SetMozPrintCallback(PrintCallback
* aCallback
)
685 mPrintCallback
= aCallback
;
689 HTMLCanvasElement::GetMozPrintCallback() const
691 if (mOriginalCanvas
) {
692 return mOriginalCanvas
->GetMozPrintCallback();
694 return mPrintCallback
;
697 class CanvasCaptureTrackSource
: public MediaStreamTrackSource
700 NS_DECL_ISUPPORTS_INHERITED
701 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CanvasCaptureTrackSource
,
702 MediaStreamTrackSource
)
704 CanvasCaptureTrackSource(nsIPrincipal
* aPrincipal
,
705 CanvasCaptureMediaStream
* aCaptureStream
)
706 : MediaStreamTrackSource(aPrincipal
, nsString())
707 , mCaptureStream(aCaptureStream
) {}
709 MediaSourceEnum
GetMediaSource() const override
711 return MediaSourceEnum::Other
;
716 if (!mCaptureStream
) {
717 NS_ERROR("No stream");
721 mCaptureStream
->StopCapture();
724 void Disable() override
728 void Enable() override
733 virtual ~CanvasCaptureTrackSource() {}
735 RefPtr
<CanvasCaptureMediaStream
> mCaptureStream
;
738 NS_IMPL_ADDREF_INHERITED(CanvasCaptureTrackSource
,
739 MediaStreamTrackSource
)
740 NS_IMPL_RELEASE_INHERITED(CanvasCaptureTrackSource
,
741 MediaStreamTrackSource
)
742 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanvasCaptureTrackSource
)
743 NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSource
)
744 NS_IMPL_CYCLE_COLLECTION_INHERITED(CanvasCaptureTrackSource
,
745 MediaStreamTrackSource
,
748 already_AddRefed
<CanvasCaptureMediaStream
>
749 HTMLCanvasElement::CaptureStream(const Optional
<double>& aFrameRate
,
750 nsIPrincipal
& aSubjectPrincipal
,
754 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
758 nsPIDOMWindowInner
* window
= OwnerDoc()->GetInnerWindow();
760 aRv
.Throw(NS_ERROR_FAILURE
);
764 if (!mCurrentContext
) {
765 aRv
.Throw(NS_ERROR_NOT_INITIALIZED
);
769 RefPtr
<CanvasCaptureMediaStream
> stream
=
770 CanvasCaptureMediaStream::CreateSourceStream(window
, this);
772 aRv
.Throw(NS_ERROR_FAILURE
);
776 TrackID videoTrackId
= 1;
777 nsCOMPtr
<nsIPrincipal
> principal
= NodePrincipal();
779 stream
->Init(aFrameRate
, videoTrackId
, principal
);
785 RefPtr
<MediaStreamTrack
> track
=
786 stream
->CreateDOMTrack(videoTrackId
, MediaSegment::VIDEO
,
787 new CanvasCaptureTrackSource(principal
, stream
));
788 stream
->AddTrackInternal(track
);
790 // Check site-specific permission and display prompt if appropriate.
791 // If no permission, arrange for the frame capture listener to return
792 // all-white, opaque image data.
793 bool usePlaceholder
= !CanvasUtils::IsImageExtractionAllowed(
795 nsContentUtils::GetCurrentJSContext(),
798 rv
= RegisterFrameCaptureListener(stream
->FrameCaptureListener(), usePlaceholder
);
804 return stream
.forget();
808 HTMLCanvasElement::ExtractData(JSContext
* aCx
,
809 nsIPrincipal
& aSubjectPrincipal
,
811 const nsAString
& aOptions
,
812 nsIInputStream
** aStream
)
814 // Check site-specific permission and display prompt if appropriate.
815 // If no permission, return all-white, opaque image data.
816 bool usePlaceholder
= !CanvasUtils::IsImageExtractionAllowed(
817 OwnerDoc(), aCx
, aSubjectPrincipal
);
818 return ImageEncoder::ExtractData(aType
,
823 mAsyncCanvasRenderer
,
828 HTMLCanvasElement::ToDataURLImpl(JSContext
* aCx
,
829 nsIPrincipal
& aSubjectPrincipal
,
830 const nsAString
& aMimeType
,
831 const JS::Value
& aEncoderOptions
,
834 nsIntSize size
= GetWidthHeight();
835 if (size
.height
== 0 || size
.width
== 0) {
836 aDataURL
= NS_LITERAL_STRING("data:,");
841 nsContentUtils::ASCIIToLower(aMimeType
, type
);
844 bool usingCustomParseOptions
;
846 ParseParams(aCx
, type
, aEncoderOptions
, params
, &usingCustomParseOptions
);
851 nsCOMPtr
<nsIInputStream
> stream
;
852 rv
= ExtractData(aCx
, aSubjectPrincipal
, type
, params
,
853 getter_AddRefs(stream
));
855 // If there are unrecognized custom parse options, we should fall back to
856 // the default values for the encoder without any options at all.
857 if (rv
== NS_ERROR_INVALID_ARG
&& usingCustomParseOptions
) {
858 rv
= ExtractData(aCx
, aSubjectPrincipal
, type
, EmptyString(),
859 getter_AddRefs(stream
));
862 NS_ENSURE_SUCCESS(rv
, rv
);
864 // build data URL string
865 aDataURL
= NS_LITERAL_STRING("data:") + type
+ NS_LITERAL_STRING(";base64,");
868 rv
= stream
->Available(&count
);
869 NS_ENSURE_SUCCESS(rv
, rv
);
870 NS_ENSURE_TRUE(count
<= UINT32_MAX
, NS_ERROR_FILE_TOO_BIG
);
872 return Base64EncodeInputStream(stream
, aDataURL
, (uint32_t)count
, aDataURL
.Length());
876 HTMLCanvasElement::ToBlob(JSContext
* aCx
,
877 BlobCallback
& aCallback
,
878 const nsAString
& aType
,
879 JS::Handle
<JS::Value
> aParams
,
880 nsIPrincipal
& aSubjectPrincipal
,
883 // mWriteOnly check is redundant, but optimizes for the common case.
884 if (mWriteOnly
&& !CallerCanRead(aCx
)) {
885 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
889 nsCOMPtr
<nsIGlobalObject
> global
= OwnerDoc()->GetScopeObject();
892 nsIntSize elemSize
= GetWidthHeight();
893 if (elemSize
.width
== 0 || elemSize
.height
== 0) {
894 // According to spec, blob should return null if either its horizontal
895 // dimension or its vertical dimension is zero. See link below.
896 // https://html.spec.whatwg.org/multipage/scripting.html#dom-canvas-toblob
897 OwnerDoc()->Dispatch(
899 NewRunnableMethod
<Blob
*, const char*>(
900 "dom::HTMLCanvasElement::ToBlob",
902 static_cast<void (BlobCallback::*)(Blob
*, const char*)>(
903 &BlobCallback::Call
),
909 // Check site-specific permission and display prompt if appropriate.
910 // If no permission, return all-white, opaque image data.
911 bool usePlaceholder
= !CanvasUtils::IsImageExtractionAllowed(
912 OwnerDoc(), aCx
, aSubjectPrincipal
);
913 CanvasRenderingContextHelper::ToBlob(aCx
, global
, aCallback
, aType
,
914 aParams
, usePlaceholder
, aRv
);
919 HTMLCanvasElement::TransferControlToOffscreen(ErrorResult
& aRv
)
921 if (mCurrentContext
) {
922 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
926 if (!mOffscreenCanvas
) {
927 nsIntSize sz
= GetWidthHeight();
928 RefPtr
<AsyncCanvasRenderer
> renderer
= GetAsyncCanvasRenderer();
929 renderer
->SetWidth(sz
.width
);
930 renderer
->SetHeight(sz
.height
);
932 nsPIDOMWindowInner
* win
= OwnerDoc()->GetInnerWindow();
934 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
938 mOffscreenCanvas
= new OffscreenCanvas(win
->AsGlobal(),
941 GetCompositorBackendType(),
944 mOffscreenCanvas
->SetWriteOnly();
947 if (!mContextObserver
) {
948 mContextObserver
= new HTMLCanvasElementObserver(this);
951 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
954 return mOffscreenCanvas
;
957 already_AddRefed
<File
>
958 HTMLCanvasElement::MozGetAsFile(const nsAString
& aName
,
959 const nsAString
& aType
,
960 nsIPrincipal
& aSubjectPrincipal
,
963 OwnerDoc()->WarnOnceAbout(nsIDocument::eMozGetAsFile
);
965 // do a trust check if this is a write-only canvas
966 if (mWriteOnly
&& !nsContentUtils::IsSystemPrincipal(&aSubjectPrincipal
)) {
967 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
973 aRv
= MozGetAsFileImpl(aName
, aType
, aSubjectPrincipal
, getter_AddRefs(file
));
974 if (NS_WARN_IF(aRv
.Failed())) {
977 return file
.forget();
981 HTMLCanvasElement::MozGetAsFileImpl(const nsAString
& aName
,
982 const nsAString
& aType
,
983 nsIPrincipal
& aSubjectPrincipal
,
986 nsCOMPtr
<nsIInputStream
> stream
;
987 nsAutoString
type(aType
);
988 nsresult rv
= ExtractData(nsContentUtils::GetCurrentJSContext(),
989 aSubjectPrincipal
, type
, EmptyString(),
990 getter_AddRefs(stream
));
991 NS_ENSURE_SUCCESS(rv
, rv
);
994 void* imgData
= nullptr;
995 rv
= NS_ReadInputStreamToBuffer(stream
, &imgData
, -1, &imgSize
);
996 NS_ENSURE_SUCCESS(rv
, rv
);
998 nsCOMPtr
<nsPIDOMWindowInner
> win
= do_QueryInterface(OwnerDoc()->GetScopeObject());
1000 // The File takes ownership of the buffer
1002 File::CreateMemoryFile(win
, imgData
, imgSize
, aName
, type
, PR_Now());
1004 file
.forget(aResult
);
1009 HTMLCanvasElement::GetContext(const nsAString
& aContextId
,
1010 nsISupports
** aContext
)
1013 *aContext
= GetContext(nullptr, aContextId
, JS::NullHandleValue
, rv
).take();
1014 return rv
.StealNSResult();
1017 already_AddRefed
<nsISupports
>
1018 HTMLCanvasElement::GetContext(JSContext
* aCx
,
1019 const nsAString
& aContextId
,
1020 JS::Handle
<JS::Value
> aContextOptions
,
1023 if (mOffscreenCanvas
) {
1027 return CanvasRenderingContextHelper::GetContext(aCx
, aContextId
,
1028 aContextOptions
.isObject() ? aContextOptions
: JS::NullHandleValue
,
1032 already_AddRefed
<nsISupports
>
1033 HTMLCanvasElement::MozGetIPCContext(const nsAString
& aContextId
,
1036 // Note that we're a [ChromeOnly] method, so from JS we can only be called by
1039 // We only support 2d shmem contexts for now.
1040 if (!aContextId
.EqualsLiteral("2d")) {
1041 aRv
.Throw(NS_ERROR_INVALID_ARG
);
1045 CanvasContextType contextType
= CanvasContextType::Canvas2D
;
1047 if (!mCurrentContext
) {
1048 // This canvas doesn't have a context yet.
1050 RefPtr
<nsICanvasRenderingContextInternal
> context
;
1051 context
= CreateContext(contextType
);
1056 mCurrentContext
= context
;
1057 mCurrentContext
->SetIsIPC(true);
1058 mCurrentContextType
= contextType
;
1061 nsresult rv
= UpdateContext(nullptr, JS::NullHandleValue
, dummy
);
1062 if (NS_WARN_IF(NS_FAILED(rv
))) {
1067 // We already have a context of some type.
1068 if (contextType
!= mCurrentContextType
) {
1069 aRv
.Throw(NS_ERROR_INVALID_ARG
);
1074 nsCOMPtr
<nsISupports
> context(mCurrentContext
);
1075 return context
.forget();
1080 HTMLCanvasElement::GetSize()
1082 return GetWidthHeight();
1086 HTMLCanvasElement::IsWriteOnly()
1092 HTMLCanvasElement::SetWriteOnly()
1094 mExpandedReader
= nullptr;
1099 HTMLCanvasElement::SetWriteOnly(nsIPrincipal
* aExpandedReader
)
1101 mExpandedReader
= aExpandedReader
;
1106 HTMLCanvasElement::CallerCanRead(JSContext
* aCx
)
1112 nsIPrincipal
* prin
= nsContentUtils::SubjectPrincipal(aCx
);
1114 // If mExpandedReader is set, this canvas was tainted only by
1115 // mExpandedReader's resources. So allow reading if the subject
1116 // principal subsumes mExpandedReader.
1117 if (mExpandedReader
&& prin
->Subsumes(mExpandedReader
)) {
1121 return nsContentUtils::PrincipalHasPermission(prin
, nsGkAtoms::all_urlsPermission
);
1125 HTMLCanvasElement::InvalidateCanvasContent(const gfx::Rect
* damageRect
)
1127 // We don't need to flush anything here; if there's no frame or if
1128 // we plan to reframe we don't need to invalidate it anyway.
1129 nsIFrame
*frame
= GetPrimaryFrame();
1133 ActiveLayerTracker::NotifyContentChange(frame
);
1135 // When using layers-free WebRender, we cannot invalidate the layer (because there isn't one).
1136 // Instead, we mark the CanvasRenderer dirty and scheduling an empty transaction
1137 // which is effectively equivalent.
1138 CanvasRenderer
* renderer
= nullptr;
1139 RefPtr
<WebRenderCanvasData
> data
= GetWebRenderUserData
<WebRenderCanvasData
>(frame
, static_cast<uint32_t>(DisplayItemType::TYPE_CANVAS
));
1141 renderer
= data
->GetCanvasRenderer();
1145 renderer
->SetDirty();
1146 frame
->SchedulePaint(nsIFrame::PAINT_COMPOSITE_ONLY
);
1148 Layer
* layer
= nullptr;
1150 nsIntSize size
= GetWidthHeight();
1151 if (size
.width
!= 0 && size
.height
!= 0) {
1152 gfx::IntRect invalRect
= gfx::IntRect::Truncate(*damageRect
);
1153 layer
= frame
->InvalidateLayer(DisplayItemType::TYPE_CANVAS
, &invalRect
);
1156 layer
= frame
->InvalidateLayer(DisplayItemType::TYPE_CANVAS
);
1160 static_cast<CanvasLayer
*>(layer
)->Updated();
1165 * Treat canvas invalidations as animation activity for JS. Frequently
1166 * invalidating a canvas will feed into heuristics and cause JIT code to be
1167 * kept around longer, for smoother animations.
1169 nsPIDOMWindowInner
* win
= OwnerDoc()->GetInnerWindow();
1172 if (JSObject
*obj
= win
->AsGlobal()->GetGlobalJSObject()) {
1173 js::NotifyAnimationActivity(obj
);
1179 HTMLCanvasElement::InvalidateCanvas()
1181 // We don't need to flush anything here; if there's no frame or if
1182 // we plan to reframe we don't need to invalidate it anyway.
1183 nsIFrame
*frame
= GetPrimaryFrame();
1187 frame
->InvalidateFrame();
1191 HTMLCanvasElement::CountContexts()
1193 if (mCurrentContext
)
1199 nsICanvasRenderingContextInternal
*
1200 HTMLCanvasElement::GetContextAtIndex(int32_t index
)
1202 if (mCurrentContext
&& index
== 0)
1203 return mCurrentContext
;
1209 HTMLCanvasElement::GetIsOpaque()
1211 if (mCurrentContext
) {
1212 return mCurrentContext
->GetIsOpaque();
1215 return GetOpaqueAttr();
1219 HTMLCanvasElement::GetOpaqueAttr()
1221 return HasAttr(kNameSpaceID_None
, nsGkAtoms::moz_opaque
);
1224 already_AddRefed
<Layer
>
1225 HTMLCanvasElement::GetCanvasLayer(nsDisplayListBuilder
* aBuilder
,
1227 LayerManager
*aManager
)
1229 // The address of sOffscreenCanvasLayerUserDataDummy is used as the user
1230 // data key for retained LayerManagers managed by FrameLayerBuilder.
1231 // We don't much care about what value in it, so just assign a dummy
1233 static uint8_t sOffscreenCanvasLayerUserDataDummy
= 0;
1235 if (mCurrentContext
) {
1236 return mCurrentContext
->GetCanvasLayer(aBuilder
, aOldLayer
, aManager
);
1239 if (mOffscreenCanvas
) {
1241 aOldLayer
&& aOldLayer
->HasUserData(&sOffscreenCanvasLayerUserDataDummy
)) {
1242 RefPtr
<Layer
> ret
= aOldLayer
;
1243 return ret
.forget();
1246 RefPtr
<CanvasLayer
> layer
= aManager
->CreateCanvasLayer();
1248 NS_WARNING("CreateCanvasLayer failed!");
1252 LayerUserData
* userData
= nullptr;
1253 layer
->SetUserData(&sOffscreenCanvasLayerUserDataDummy
, userData
);
1255 CanvasRenderer
* canvasRenderer
= layer
->CreateOrGetCanvasRenderer();
1257 if (!InitializeCanvasRenderer(aBuilder
, canvasRenderer
)) {
1262 return layer
.forget();
1269 HTMLCanvasElement::UpdateWebRenderCanvasData(nsDisplayListBuilder
* aBuilder
,
1270 WebRenderCanvasData
* aCanvasData
)
1272 if (mCurrentContext
) {
1273 return mCurrentContext
->UpdateWebRenderCanvasData(aBuilder
, aCanvasData
);
1275 if (mOffscreenCanvas
) {
1276 CanvasRenderer
* renderer
= aCanvasData
->GetCanvasRenderer();
1278 if(!mResetLayer
&& renderer
) {
1282 renderer
= aCanvasData
->CreateCanvasRenderer();
1283 if (!InitializeCanvasRenderer(aBuilder
, renderer
)) {
1284 // Clear CanvasRenderer of WebRenderCanvasData
1285 aCanvasData
->ClearCanvasRenderer();
1289 MOZ_ASSERT(renderer
);
1290 mResetLayer
= false;
1294 // Clear CanvasRenderer of WebRenderCanvasData
1295 aCanvasData
->ClearCanvasRenderer();
1300 HTMLCanvasElement::InitializeCanvasRenderer(nsDisplayListBuilder
* aBuilder
,
1301 CanvasRenderer
* aRenderer
)
1303 if (mCurrentContext
) {
1304 return mCurrentContext
->InitializeCanvasRenderer(aBuilder
, aRenderer
);
1307 if (mOffscreenCanvas
) {
1308 CanvasInitializeData data
;
1309 data
.mRenderer
= GetAsyncCanvasRenderer();
1310 data
.mSize
= GetWidthHeight();
1311 aRenderer
->Initialize(data
);
1319 HTMLCanvasElement::ShouldForceInactiveLayer(LayerManager
* aManager
)
1321 if (mCurrentContext
) {
1322 return mCurrentContext
->ShouldForceInactiveLayer(aManager
);
1325 if (mOffscreenCanvas
) {
1326 // TODO: We should handle offscreen canvas case.
1334 HTMLCanvasElement::MarkContextClean()
1336 if (!mCurrentContext
)
1339 mCurrentContext
->MarkContextClean();
1343 HTMLCanvasElement::MarkContextCleanForFrameCapture()
1345 if (!mCurrentContext
)
1348 mCurrentContext
->MarkContextCleanForFrameCapture();
1352 HTMLCanvasElement::IsContextCleanForFrameCapture()
1354 return mCurrentContext
&& mCurrentContext
->IsContextCleanForFrameCapture();
1358 HTMLCanvasElement::RegisterFrameCaptureListener(FrameCaptureListener
* aListener
,
1359 bool aReturnPlaceholderData
)
1361 WeakPtr
<FrameCaptureListener
> listener
= aListener
;
1363 if (mRequestedFrameListeners
.Contains(listener
)) {
1367 if (!mRequestedFrameRefreshObserver
) {
1368 nsIDocument
* doc
= OwnerDoc();
1370 return NS_ERROR_FAILURE
;
1373 while (doc
->GetParentDocument()) {
1374 doc
= doc
->GetParentDocument();
1377 nsPresContext
* context
= doc
->GetPresContext();
1379 return NS_ERROR_FAILURE
;
1382 context
= context
->GetRootPresContext();
1384 return NS_ERROR_FAILURE
;
1387 nsRefreshDriver
* driver
= context
->RefreshDriver();
1389 return NS_ERROR_FAILURE
;
1392 mRequestedFrameRefreshObserver
=
1393 new RequestedFrameRefreshObserver(this, driver
, aReturnPlaceholderData
);
1395 mRequestedFrameRefreshObserver
->SetReturnPlaceholderData(aReturnPlaceholderData
);
1398 mRequestedFrameListeners
.AppendElement(listener
);
1399 mRequestedFrameRefreshObserver
->Register();
1404 HTMLCanvasElement::IsFrameCaptureRequested() const
1406 for (WeakPtr
<FrameCaptureListener
> listener
: mRequestedFrameListeners
) {
1411 if (listener
->FrameCaptureRequested()) {
1419 HTMLCanvasElement::ProcessDestroyedFrameListeners()
1421 // Loop backwards to allow removing elements in the loop.
1422 for (int i
= mRequestedFrameListeners
.Length() - 1; i
>= 0; --i
) {
1423 WeakPtr
<FrameCaptureListener
> listener
= mRequestedFrameListeners
[i
];
1425 // listener was destroyed. Remove it from the list.
1426 mRequestedFrameListeners
.RemoveElementAt(i
);
1431 if (mRequestedFrameListeners
.IsEmpty()) {
1432 mRequestedFrameRefreshObserver
->Unregister();
1437 HTMLCanvasElement::SetFrameCapture(already_AddRefed
<SourceSurface
> aSurface
,
1438 const TimeStamp
& aTime
)
1440 RefPtr
<SourceSurface
> surface
= aSurface
;
1441 RefPtr
<SourceSurfaceImage
> image
= new SourceSurfaceImage(surface
->GetSize(), surface
);
1443 for (WeakPtr
<FrameCaptureListener
> listener
: mRequestedFrameListeners
) {
1448 RefPtr
<Image
> imageRefCopy
= image
.get();
1449 listener
->NewFrame(imageRefCopy
.forget(), aTime
);
1453 already_AddRefed
<SourceSurface
>
1454 HTMLCanvasElement::GetSurfaceSnapshot(gfxAlphaType
* const aOutAlphaType
)
1456 if (!mCurrentContext
)
1459 return mCurrentContext
->GetSurfaceSnapshot(aOutAlphaType
);
1462 AsyncCanvasRenderer
*
1463 HTMLCanvasElement::GetAsyncCanvasRenderer()
1465 if (!mAsyncCanvasRenderer
) {
1466 mAsyncCanvasRenderer
= new AsyncCanvasRenderer();
1467 mAsyncCanvasRenderer
->mHTMLCanvasElement
= this;
1470 return mAsyncCanvasRenderer
;
1473 layers::LayersBackend
1474 HTMLCanvasElement::GetCompositorBackendType() const
1476 nsIWidget
* docWidget
= nsContentUtils::WidgetForDocument(OwnerDoc());
1478 layers::LayerManager
* layerManager
= docWidget
->GetLayerManager();
1480 return layerManager
->GetCompositorBackendType();
1484 return LayersBackend::LAYERS_NONE
;
1488 HTMLCanvasElement::OnVisibilityChange()
1490 if (OwnerDoc()->Hidden()) {
1494 if (mOffscreenCanvas
) {
1495 class Runnable final
: public CancelableRunnable
1498 explicit Runnable(AsyncCanvasRenderer
* aRenderer
)
1499 : mozilla::CancelableRunnable("Runnable")
1500 , mRenderer(aRenderer
)
1503 NS_IMETHOD
Run() override
1505 if (mRenderer
&& mRenderer
->mContext
) {
1506 mRenderer
->mContext
->OnVisibilityChange();
1513 RefPtr
<AsyncCanvasRenderer
> mRenderer
;
1516 RefPtr
<nsIRunnable
> runnable
= new Runnable(mAsyncCanvasRenderer
);
1517 nsCOMPtr
<nsIEventTarget
> activeTarget
= mAsyncCanvasRenderer
->GetActiveEventTarget();
1519 activeTarget
->Dispatch(runnable
, nsIThread::DISPATCH_NORMAL
);
1524 if (mCurrentContext
) {
1525 mCurrentContext
->OnVisibilityChange();
1530 HTMLCanvasElement::OnMemoryPressure()
1532 if (mOffscreenCanvas
) {
1533 class Runnable final
: public CancelableRunnable
1536 explicit Runnable(AsyncCanvasRenderer
* aRenderer
)
1537 : mozilla::CancelableRunnable("Runnable")
1538 , mRenderer(aRenderer
)
1541 NS_IMETHOD
Run() override
1543 if (mRenderer
&& mRenderer
->mContext
) {
1544 mRenderer
->mContext
->OnMemoryPressure();
1551 RefPtr
<AsyncCanvasRenderer
> mRenderer
;
1554 RefPtr
<nsIRunnable
> runnable
= new Runnable(mAsyncCanvasRenderer
);
1555 nsCOMPtr
<nsIEventTarget
> activeTarget
= mAsyncCanvasRenderer
->GetActiveEventTarget();
1557 activeTarget
->Dispatch(runnable
, nsIThread::DISPATCH_NORMAL
);
1562 if (mCurrentContext
) {
1563 mCurrentContext
->OnMemoryPressure();
1568 HTMLCanvasElement::SetAttrFromAsyncCanvasRenderer(AsyncCanvasRenderer
*aRenderer
)
1570 HTMLCanvasElement
*element
= aRenderer
->mHTMLCanvasElement
;
1575 if (element
->GetWidthHeight() == aRenderer
->GetSize()) {
1579 gfx::IntSize asyncCanvasSize
= aRenderer
->GetSize();
1582 element
->SetUnsignedIntAttr(nsGkAtoms::width
, asyncCanvasSize
.width
,
1583 DEFAULT_CANVAS_WIDTH
, rv
);
1585 NS_WARNING("Failed to set width attribute to a canvas element asynchronously.");
1588 element
->SetUnsignedIntAttr(nsGkAtoms::height
, asyncCanvasSize
.height
,
1589 DEFAULT_CANVAS_HEIGHT
, rv
);
1591 NS_WARNING("Failed to set height attribute to a canvas element asynchronously.");
1594 element
->mResetLayer
= true;
1598 HTMLCanvasElement::InvalidateFromAsyncCanvasRenderer(AsyncCanvasRenderer
*aRenderer
)
1600 HTMLCanvasElement
*element
= aRenderer
->mHTMLCanvasElement
;
1605 element
->InvalidateCanvasContent(nullptr);
1608 already_AddRefed
<layers::SharedSurfaceTextureClient
>
1609 HTMLCanvasElement::GetVRFrame()
1611 if (GetCurrentContextType() != CanvasContextType::WebGL1
&&
1612 GetCurrentContextType() != CanvasContextType::WebGL2
) {
1616 WebGLContext
* webgl
= static_cast<WebGLContext
*>(GetContextAtIndex(0));
1621 return webgl
->GetVRFrame();
1625 } // namespace mozilla