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