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