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