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