1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include "CanvasRenderingContextHelper.h"
7 #include "ImageBitmapRenderingContext.h"
8 #include "ImageEncoder.h"
9 #include "mozilla/dom/CanvasRenderingContext2D.h"
10 #include "mozilla/GfxMessageUtils.h"
11 #include "mozilla/Telemetry.h"
12 #include "mozilla/UniquePtr.h"
13 #include "mozilla/webgpu/CanvasContext.h"
14 #include "MozFramebuffer.h"
15 #include "nsContentUtils.h"
16 #include "nsDOMJSUtils.h"
17 #include "nsIScriptContext.h"
18 #include "nsJSUtils.h"
19 #include "ClientWebGLContext.h"
20
21 namespace mozilla {
22 namespace dom {
23
CanvasRenderingContextHelper()24 CanvasRenderingContextHelper::CanvasRenderingContextHelper()
25 : mCurrentContextType(CanvasContextType::NoContext) {}
26
ToBlob(JSContext * aCx,nsIGlobalObject * aGlobal,BlobCallback & aCallback,const nsAString & aType,JS::Handle<JS::Value> aParams,bool aUsePlaceholder,ErrorResult & aRv)27 void CanvasRenderingContextHelper::ToBlob(
28 JSContext* aCx, nsIGlobalObject* aGlobal, BlobCallback& aCallback,
29 const nsAString& aType, JS::Handle<JS::Value> aParams, bool aUsePlaceholder,
30 ErrorResult& aRv) {
31 // Encoder callback when encoding is complete.
32 class EncodeCallback : public EncodeCompleteCallback {
33 public:
34 EncodeCallback(nsIGlobalObject* aGlobal, BlobCallback* aCallback)
35 : mGlobal(aGlobal), mBlobCallback(aCallback) {}
36
37 // This is called on main thread.
38 MOZ_CAN_RUN_SCRIPT
39 nsresult ReceiveBlobImpl(already_AddRefed<BlobImpl> aBlobImpl) override {
40 RefPtr<BlobImpl> blobImpl = aBlobImpl;
41
42 RefPtr<Blob> blob;
43
44 if (blobImpl) {
45 blob = Blob::Create(mGlobal, blobImpl);
46 }
47
48 RefPtr<BlobCallback> callback(std::move(mBlobCallback));
49 ErrorResult rv;
50
51 callback->Call(blob, rv);
52
53 mGlobal = nullptr;
54 MOZ_ASSERT(!mBlobCallback);
55
56 return rv.StealNSResult();
57 }
58
59 nsCOMPtr<nsIGlobalObject> mGlobal;
60 RefPtr<BlobCallback> mBlobCallback;
61 };
62
63 RefPtr<EncodeCompleteCallback> callback =
64 new EncodeCallback(aGlobal, &aCallback);
65
66 ToBlob(aCx, aGlobal, callback, aType, aParams, aUsePlaceholder, aRv);
67 }
68
ToBlob(JSContext * aCx,nsIGlobalObject * aGlobal,EncodeCompleteCallback * aCallback,const nsAString & aType,JS::Handle<JS::Value> aParams,bool aUsePlaceholder,ErrorResult & aRv)69 void CanvasRenderingContextHelper::ToBlob(
70 JSContext* aCx, nsIGlobalObject* aGlobal, EncodeCompleteCallback* aCallback,
71 const nsAString& aType, JS::Handle<JS::Value> aParams, bool aUsePlaceholder,
72 ErrorResult& aRv) {
73 nsAutoString type;
74 nsContentUtils::ASCIIToLower(aType, type);
75
76 nsAutoString params;
77 bool usingCustomParseOptions;
78 aRv = ParseParams(aCx, type, aParams, params, &usingCustomParseOptions);
79 if (aRv.Failed()) {
80 return;
81 }
82
83 if (mCurrentContext) {
84 // We disallow canvases of width or height zero, and set them to 1, so
85 // we will have a discrepancy with the sizes of the canvas and the context.
86 // That discrepancy is OK, the rest are not.
87 nsIntSize elementSize = GetWidthHeight();
88 if ((elementSize.width != mCurrentContext->GetWidth() &&
89 (elementSize.width != 0 || mCurrentContext->GetWidth() != 1)) ||
90 (elementSize.height != mCurrentContext->GetHeight() &&
91 (elementSize.height != 0 || mCurrentContext->GetHeight() != 1))) {
92 aRv.Throw(NS_ERROR_FAILURE);
93 return;
94 }
95 }
96
97 UniquePtr<uint8_t[]> imageBuffer;
98 int32_t format = 0;
99 if (mCurrentContext) {
100 imageBuffer = mCurrentContext->GetImageBuffer(&format);
101 }
102
103 RefPtr<EncodeCompleteCallback> callback = aCallback;
104
105 aRv = ImageEncoder::ExtractDataAsync(
106 type, params, usingCustomParseOptions, std::move(imageBuffer), format,
107 GetWidthHeight(), aUsePlaceholder, callback);
108 }
109
110 already_AddRefed<nsICanvasRenderingContextInternal>
CreateContext(CanvasContextType aContextType)111 CanvasRenderingContextHelper::CreateContext(CanvasContextType aContextType) {
112 return CreateContextHelper(aContextType, layers::LayersBackend::LAYERS_NONE);
113 }
114
115 already_AddRefed<nsICanvasRenderingContextInternal>
CreateContextHelper(CanvasContextType aContextType,layers::LayersBackend aCompositorBackend)116 CanvasRenderingContextHelper::CreateContextHelper(
117 CanvasContextType aContextType, layers::LayersBackend aCompositorBackend) {
118 MOZ_ASSERT(aContextType != CanvasContextType::NoContext);
119 RefPtr<nsICanvasRenderingContextInternal> ret;
120
121 switch (aContextType) {
122 case CanvasContextType::NoContext:
123 break;
124
125 case CanvasContextType::Canvas2D:
126 Telemetry::Accumulate(Telemetry::CANVAS_2D_USED, 1);
127 ret = new CanvasRenderingContext2D(aCompositorBackend);
128 break;
129
130 case CanvasContextType::WebGL1:
131 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED, 1);
132
133 ret = new ClientWebGLContext(/*webgl2:*/ false);
134 if (!ret) return nullptr;
135
136 break;
137
138 case CanvasContextType::WebGL2:
139 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED, 1);
140
141 ret = new ClientWebGLContext(/*webgl2:*/ true);
142 if (!ret) return nullptr;
143
144 break;
145
146 case CanvasContextType::WebGPU:
147 // TODO
148 // Telemetry::Accumulate(Telemetry::CANVAS_WEBGPU_USED, 1);
149
150 ret = new webgpu::CanvasContext();
151 if (!ret) return nullptr;
152
153 break;
154
155 case CanvasContextType::ImageBitmap:
156 ret = new ImageBitmapRenderingContext();
157
158 break;
159 }
160 MOZ_ASSERT(ret);
161
162 return ret.forget();
163 }
164
GetContext(JSContext * aCx,const nsAString & aContextId,JS::Handle<JS::Value> aContextOptions,ErrorResult & aRv)165 already_AddRefed<nsISupports> CanvasRenderingContextHelper::GetContext(
166 JSContext* aCx, const nsAString& aContextId,
167 JS::Handle<JS::Value> aContextOptions, ErrorResult& aRv) {
168 CanvasContextType contextType;
169 if (!CanvasUtils::GetCanvasContextType(aContextId, &contextType))
170 return nullptr;
171
172 if (!mCurrentContext) {
173 // This canvas doesn't have a context yet.
174 RefPtr<nsICanvasRenderingContextInternal> context;
175 context = CreateContext(contextType);
176 if (!context) {
177 return nullptr;
178 }
179
180 // Ensure that the context participates in CC. Note that returning a
181 // CC participant from QI doesn't addref.
182 nsXPCOMCycleCollectionParticipant* cp = nullptr;
183 CallQueryInterface(context, &cp);
184 if (!cp) {
185 aRv.Throw(NS_ERROR_FAILURE);
186 return nullptr;
187 }
188
189 mCurrentContext = std::move(context);
190 mCurrentContextType = contextType;
191
192 nsresult rv = UpdateContext(aCx, aContextOptions, aRv);
193 if (NS_FAILED(rv)) {
194 // See bug 645792 and bug 1215072.
195 // We want to throw only if dictionary initialization fails,
196 // so only in case aRv has been set to some error value.
197 if (contextType == CanvasContextType::WebGL1)
198 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_SUCCESS, 0);
199 else if (contextType == CanvasContextType::WebGL2)
200 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL2_SUCCESS, 0);
201 else if (contextType == CanvasContextType::WebGPU) {
202 // Telemetry::Accumulate(Telemetry::CANVAS_WEBGPU_SUCCESS, 0);
203 }
204 return nullptr;
205 }
206 if (contextType == CanvasContextType::WebGL1)
207 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_SUCCESS, 1);
208 else if (contextType == CanvasContextType::WebGL2)
209 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL2_SUCCESS, 1);
210 else if (contextType == CanvasContextType::WebGPU) {
211 // Telemetry::Accumulate(Telemetry::CANVAS_WEBGPU_SUCCESS, 1);
212 }
213 } else {
214 // We already have a context of some type.
215 if (contextType != mCurrentContextType) return nullptr;
216 }
217
218 nsCOMPtr<nsICanvasRenderingContextInternal> context = mCurrentContext;
219 return context.forget();
220 }
221
UpdateContext(JSContext * aCx,JS::Handle<JS::Value> aNewContextOptions,ErrorResult & aRvForDictionaryInit)222 nsresult CanvasRenderingContextHelper::UpdateContext(
223 JSContext* aCx, JS::Handle<JS::Value> aNewContextOptions,
224 ErrorResult& aRvForDictionaryInit) {
225 if (!mCurrentContext) return NS_OK;
226
227 nsIntSize sz = GetWidthHeight();
228
229 nsCOMPtr<nsICanvasRenderingContextInternal> currentContext = mCurrentContext;
230
231 currentContext->SetOpaqueValueFromOpaqueAttr(GetOpaqueAttr());
232
233 nsresult rv = currentContext->SetContextOptions(aCx, aNewContextOptions,
234 aRvForDictionaryInit);
235 if (NS_FAILED(rv)) {
236 mCurrentContext = nullptr;
237 return rv;
238 }
239
240 rv = currentContext->SetDimensions(sz.width, sz.height);
241 if (NS_FAILED(rv)) {
242 mCurrentContext = nullptr;
243 }
244
245 return rv;
246 }
247
ParseParams(JSContext * aCx,const nsAString & aType,const JS::Value & aEncoderOptions,nsAString & outParams,bool * const outUsingCustomParseOptions)248 nsresult CanvasRenderingContextHelper::ParseParams(
249 JSContext* aCx, const nsAString& aType, const JS::Value& aEncoderOptions,
250 nsAString& outParams, bool* const outUsingCustomParseOptions) {
251 // Quality parameter is only valid for the image/jpeg MIME type
252 if (aType.EqualsLiteral("image/jpeg")) {
253 if (aEncoderOptions.isNumber()) {
254 double quality = aEncoderOptions.toNumber();
255 // Quality must be between 0.0 and 1.0, inclusive
256 if (quality >= 0.0 && quality <= 1.0) {
257 outParams.AppendLiteral("quality=");
258 outParams.AppendInt(NS_lround(quality * 100.0));
259 }
260 }
261 }
262
263 // If we haven't parsed the aParams check for proprietary options.
264 // The proprietary option -moz-parse-options will take a image lib encoder
265 // parse options string as is and pass it to the encoder.
266 *outUsingCustomParseOptions = false;
267 if (outParams.Length() == 0 && aEncoderOptions.isString()) {
268 NS_NAMED_LITERAL_STRING(mozParseOptions, "-moz-parse-options:");
269 nsAutoJSString paramString;
270 if (!paramString.init(aCx, aEncoderOptions.toString())) {
271 return NS_ERROR_FAILURE;
272 }
273 if (StringBeginsWith(paramString, mozParseOptions)) {
274 nsDependentSubstring parseOptions =
275 Substring(paramString, mozParseOptions.Length(),
276 paramString.Length() - mozParseOptions.Length());
277 outParams.Append(parseOptions);
278 *outUsingCustomParseOptions = true;
279 }
280 }
281
282 return NS_OK;
283 }
284
285 } // namespace dom
286 } // namespace mozilla
287