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