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