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(&current);
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