1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
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 "OffscreenCanvas.h"
8
9 #include "mozilla/dom/OffscreenCanvasBinding.h"
10 #include "mozilla/dom/WorkerPrivate.h"
11 #include "mozilla/dom/WorkerScope.h"
12 #include "mozilla/layers/AsyncCanvasRenderer.h"
13 #include "mozilla/layers/CanvasClient.h"
14 #include "mozilla/layers/ImageBridgeChild.h"
15 #include "mozilla/Telemetry.h"
16 #include "CanvasRenderingContext2D.h"
17 #include "CanvasUtils.h"
18 #include "GLContext.h"
19 #include "GLScreenBuffer.h"
20
21 namespace mozilla {
22 namespace dom {
23
OffscreenCanvasCloneData(layers::AsyncCanvasRenderer * aRenderer,uint32_t aWidth,uint32_t aHeight,layers::LayersBackend aCompositorBackend,bool aNeutered,bool aIsWriteOnly)24 OffscreenCanvasCloneData::OffscreenCanvasCloneData(
25 layers::AsyncCanvasRenderer* aRenderer, uint32_t aWidth, uint32_t aHeight,
26 layers::LayersBackend aCompositorBackend, bool aNeutered, bool aIsWriteOnly)
27 : mRenderer(aRenderer),
28 mWidth(aWidth),
29 mHeight(aHeight),
30 mCompositorBackendType(aCompositorBackend),
31 mNeutered(aNeutered),
32 mIsWriteOnly(aIsWriteOnly) {}
33
34 OffscreenCanvasCloneData::~OffscreenCanvasCloneData() = default;
35
OffscreenCanvas(nsIGlobalObject * aGlobal,uint32_t aWidth,uint32_t aHeight,layers::LayersBackend aCompositorBackend,layers::AsyncCanvasRenderer * aRenderer)36 OffscreenCanvas::OffscreenCanvas(nsIGlobalObject* aGlobal, uint32_t aWidth,
37 uint32_t aHeight,
38 layers::LayersBackend aCompositorBackend,
39 layers::AsyncCanvasRenderer* aRenderer)
40 : DOMEventTargetHelper(aGlobal),
41 mAttrDirty(false),
42 mNeutered(false),
43 mIsWriteOnly(false),
44 mWidth(aWidth),
45 mHeight(aHeight),
46 mCompositorBackendType(aCompositorBackend),
47 mCanvasRenderer(aRenderer) {}
48
~OffscreenCanvas()49 OffscreenCanvas::~OffscreenCanvas() { ClearResources(); }
50
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)51 JSObject* OffscreenCanvas::WrapObject(JSContext* aCx,
52 JS::Handle<JSObject*> aGivenProto) {
53 return OffscreenCanvas_Binding::Wrap(aCx, this, aGivenProto);
54 }
55
56 /* static */
Constructor(const GlobalObject & aGlobal,uint32_t aWidth,uint32_t aHeight)57 already_AddRefed<OffscreenCanvas> OffscreenCanvas::Constructor(
58 const GlobalObject& aGlobal, uint32_t aWidth, uint32_t aHeight) {
59 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
60 RefPtr<OffscreenCanvas> offscreenCanvas = new OffscreenCanvas(
61 global, aWidth, aHeight, layers::LayersBackend::LAYERS_NONE, nullptr);
62 return offscreenCanvas.forget();
63 }
64
ClearResources()65 void OffscreenCanvas::ClearResources() {
66 if (mCanvasClient) {
67 mCanvasClient->Clear();
68
69 if (mCanvasRenderer) {
70 nsCOMPtr<nsISerialEventTarget> activeTarget =
71 mCanvasRenderer->GetActiveEventTarget();
72 MOZ_RELEASE_ASSERT(activeTarget,
73 "GFX: failed to get active event target.");
74 bool current;
75 activeTarget->IsOnCurrentThread(¤t);
76 MOZ_RELEASE_ASSERT(current, "GFX: active thread is not current thread.");
77 mCanvasRenderer->SetCanvasClient(nullptr);
78 mCanvasRenderer->mContext = nullptr;
79 mCanvasRenderer->mGLContext = nullptr;
80 mCanvasRenderer->ResetActiveEventTarget();
81 }
82
83 mCanvasClient = nullptr;
84 }
85 }
86
GetContext(JSContext * aCx,const nsAString & aContextId,JS::Handle<JS::Value> aContextOptions,ErrorResult & aRv)87 already_AddRefed<nsISupports> OffscreenCanvas::GetContext(
88 JSContext* aCx, const nsAString& aContextId,
89 JS::Handle<JS::Value> aContextOptions, ErrorResult& aRv) {
90 if (mNeutered) {
91 aRv.Throw(NS_ERROR_FAILURE);
92 return nullptr;
93 }
94
95 // We only support WebGL in workers for now
96 CanvasContextType contextType;
97 if (!CanvasUtils::GetCanvasContextType(aContextId, &contextType)) {
98 aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
99 return nullptr;
100 }
101
102 if (!(contextType == CanvasContextType::WebGL1 ||
103 contextType == CanvasContextType::WebGL2 ||
104 contextType == CanvasContextType::WebGPU ||
105 contextType == CanvasContextType::ImageBitmap)) {
106 aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
107 return nullptr;
108 }
109
110 RefPtr<nsISupports> result = CanvasRenderingContextHelper::GetContext(
111 aCx, aContextId, aContextOptions, aRv);
112
113 if (!mCurrentContext) {
114 return nullptr;
115 }
116
117 if (mCanvasRenderer) {
118 mCanvasRenderer->SetContextType(contextType);
119 if (contextType == CanvasContextType::WebGL1 ||
120 contextType == CanvasContextType::WebGL2) {
121 MOZ_ASSERT_UNREACHABLE("WebGL OffscreenCanvas not yet supported.");
122 return nullptr;
123 }
124 if (contextType == CanvasContextType::WebGPU) {
125 MOZ_ASSERT_UNREACHABLE("WebGPU OffscreenCanvas not yet supported.");
126 return nullptr;
127 }
128 }
129
130 return result.forget();
131 }
132
GetImageContainer()133 ImageContainer* OffscreenCanvas::GetImageContainer() {
134 if (!mCanvasRenderer) {
135 return nullptr;
136 }
137 return mCanvasRenderer->GetImageContainer();
138 }
139
140 already_AddRefed<nsICanvasRenderingContextInternal>
CreateContext(CanvasContextType aContextType)141 OffscreenCanvas::CreateContext(CanvasContextType aContextType) {
142 RefPtr<nsICanvasRenderingContextInternal> ret =
143 CanvasRenderingContextHelper::CreateContext(aContextType);
144
145 ret->SetOffscreenCanvas(this);
146 return ret.forget();
147 }
148
CommitFrameToCompositor()149 void OffscreenCanvas::CommitFrameToCompositor() {
150 if (!mCanvasRenderer) {
151 // This offscreen canvas doesn't associate to any HTML canvas element.
152 // So, just bail out.
153 return;
154 }
155
156 // The attributes has changed, we have to notify main
157 // thread to change canvas size.
158 if (mAttrDirty) {
159 if (mCanvasRenderer) {
160 mCanvasRenderer->SetWidth(mWidth);
161 mCanvasRenderer->SetHeight(mHeight);
162 mCanvasRenderer->NotifyElementAboutAttributesChanged();
163 }
164 mAttrDirty = false;
165 }
166
167 CanvasContextType contentType = mCanvasRenderer->GetContextType();
168 if (mCurrentContext && (contentType == CanvasContextType::WebGL1 ||
169 contentType == CanvasContextType::WebGL2)) {
170 MOZ_ASSERT_UNREACHABLE("WebGL OffscreenCanvas not yet supported.");
171 return;
172 }
173 if (mCurrentContext && (contentType == CanvasContextType::WebGPU)) {
174 MOZ_ASSERT_UNREACHABLE("WebGPU OffscreenCanvas not yet supported.");
175 return;
176 }
177
178 if (mCanvasRenderer && mCanvasRenderer->mGLContext) {
179 mCanvasRenderer->NotifyElementAboutInvalidation();
180 ImageBridgeChild::GetSingleton()->UpdateAsyncCanvasRenderer(
181 mCanvasRenderer);
182 }
183 }
184
ToCloneData()185 OffscreenCanvasCloneData* OffscreenCanvas::ToCloneData() {
186 return new OffscreenCanvasCloneData(mCanvasRenderer, mWidth, mHeight,
187 mCompositorBackendType, mNeutered,
188 mIsWriteOnly);
189 }
190
TransferToImageBitmap(ErrorResult & aRv)191 already_AddRefed<ImageBitmap> OffscreenCanvas::TransferToImageBitmap(
192 ErrorResult& aRv) {
193 nsCOMPtr<nsIGlobalObject> globalObject = GetGlobalObject();
194 RefPtr<ImageBitmap> result =
195 ImageBitmap::CreateFromOffscreenCanvas(globalObject, *this, aRv);
196 if (aRv.Failed()) {
197 return nullptr;
198 }
199
200 // TODO: Clear the content?
201 return result.forget();
202 }
203
ToBlob(JSContext * aCx,const nsAString & aType,JS::Handle<JS::Value> aParams,ErrorResult & aRv)204 already_AddRefed<Promise> OffscreenCanvas::ToBlob(JSContext* aCx,
205 const nsAString& aType,
206 JS::Handle<JS::Value> aParams,
207 ErrorResult& aRv) {
208 // do a trust check if this is a write-only canvas
209 if (mIsWriteOnly) {
210 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
211 return nullptr;
212 }
213
214 nsCOMPtr<nsIGlobalObject> global = GetGlobalObject();
215
216 RefPtr<Promise> promise = Promise::Create(global, aRv);
217 if (aRv.Failed()) {
218 return nullptr;
219 }
220
221 // Encoder callback when encoding is complete.
222 class EncodeCallback : public EncodeCompleteCallback {
223 public:
224 EncodeCallback(nsIGlobalObject* aGlobal, Promise* aPromise)
225 : mGlobal(aGlobal), mPromise(aPromise) {}
226
227 // This is called on main thread.
228 nsresult ReceiveBlobImpl(already_AddRefed<BlobImpl> aBlobImpl) override {
229 RefPtr<BlobImpl> blobImpl = aBlobImpl;
230
231 if (mPromise) {
232 RefPtr<Blob> blob = Blob::Create(mGlobal, blobImpl);
233 if (NS_WARN_IF(!blob)) {
234 mPromise->MaybeReject(NS_ERROR_FAILURE);
235 } else {
236 mPromise->MaybeResolve(blob);
237 }
238 }
239
240 mGlobal = nullptr;
241 mPromise = nullptr;
242
243 return NS_OK;
244 }
245
246 nsCOMPtr<nsIGlobalObject> mGlobal;
247 RefPtr<Promise> mPromise;
248 };
249
250 RefPtr<EncodeCompleteCallback> callback = new EncodeCallback(global, promise);
251
252 bool usePlaceholder;
253 if (NS_IsMainThread()) {
254 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetGlobalObject());
255 Document* doc = window->GetExtantDoc();
256 usePlaceholder =
257 doc ? nsContentUtils::ShouldResistFingerprinting(doc) : false;
258 } else {
259 dom::WorkerPrivate* workerPrivate = dom::GetCurrentThreadWorkerPrivate();
260 usePlaceholder = nsContentUtils::ShouldResistFingerprinting(workerPrivate);
261 }
262 CanvasRenderingContextHelper::ToBlob(aCx, global, callback, aType, aParams,
263 usePlaceholder, aRv);
264
265 return promise.forget();
266 }
267
GetSurfaceSnapshot(gfxAlphaType * const aOutAlphaType)268 already_AddRefed<gfx::SourceSurface> OffscreenCanvas::GetSurfaceSnapshot(
269 gfxAlphaType* const aOutAlphaType) {
270 if (!mCurrentContext) {
271 return nullptr;
272 }
273
274 return mCurrentContext->GetSurfaceSnapshot(aOutAlphaType);
275 }
276
GetGlobalObject()277 nsCOMPtr<nsIGlobalObject> OffscreenCanvas::GetGlobalObject() {
278 if (NS_IsMainThread()) {
279 return GetParentObject();
280 }
281
282 dom::WorkerPrivate* workerPrivate = dom::GetCurrentThreadWorkerPrivate();
283 return workerPrivate->GlobalScope();
284 }
285
286 /* static */
CreateFromCloneData(nsIGlobalObject * aGlobal,OffscreenCanvasCloneData * aData)287 already_AddRefed<OffscreenCanvas> OffscreenCanvas::CreateFromCloneData(
288 nsIGlobalObject* aGlobal, OffscreenCanvasCloneData* aData) {
289 MOZ_ASSERT(aData);
290 RefPtr<OffscreenCanvas> wc =
291 new OffscreenCanvas(aGlobal, aData->mWidth, aData->mHeight,
292 aData->mCompositorBackendType, aData->mRenderer);
293 if (aData->mNeutered) {
294 wc->SetNeutered();
295 }
296 return wc.forget();
297 }
298
299 /* static */
PrefEnabledOnWorkerThread(JSContext * aCx,JSObject * aObj)300 bool OffscreenCanvas::PrefEnabledOnWorkerThread(JSContext* aCx,
301 JSObject* aObj) {
302 if (NS_IsMainThread()) {
303 return true;
304 }
305
306 return StaticPrefs::gfx_offscreencanvas_enabled();
307 }
308
309 NS_IMPL_CYCLE_COLLECTION_INHERITED(OffscreenCanvas, DOMEventTargetHelper,
310 mCurrentContext)
311
312 NS_IMPL_ADDREF_INHERITED(OffscreenCanvas, DOMEventTargetHelper)
313 NS_IMPL_RELEASE_INHERITED(OffscreenCanvas, DOMEventTargetHelper)
314
315 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(OffscreenCanvas)
316 NS_INTERFACE_MAP_ENTRY(nsISupports)
317 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
318
319 } // namespace dom
320 } // namespace mozilla
321