Thu, 15 Jan 2015 21:03:48 +0100
Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)
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/. */
6 #include "gfxImageSurface.h"
7 #include "ImageEncoder.h"
8 #include "mozilla/dom/CanvasRenderingContext2D.h"
10 namespace mozilla {
11 namespace dom {
13 class EncodingCompleteEvent : public nsRunnable
14 {
15 public:
16 NS_DECL_THREADSAFE_ISUPPORTS
18 EncodingCompleteEvent(nsIScriptContext* aScriptContext,
19 nsIThread* aEncoderThread,
20 FileCallback& aCallback)
21 : mImgSize(0)
22 , mType()
23 , mImgData(nullptr)
24 , mScriptContext(aScriptContext)
25 , mEncoderThread(aEncoderThread)
26 , mCallback(&aCallback)
27 , mFailed(false)
28 {}
29 virtual ~EncodingCompleteEvent() {}
31 NS_IMETHOD Run()
32 {
33 MOZ_ASSERT(NS_IsMainThread());
35 mozilla::ErrorResult rv;
37 if (!mFailed) {
38 nsRefPtr<nsDOMMemoryFile> blob =
39 new nsDOMMemoryFile(mImgData, mImgSize, mType);
41 if (mScriptContext) {
42 JSContext* jsContext = mScriptContext->GetNativeContext();
43 if (jsContext) {
44 JS_updateMallocCounter(jsContext, mImgSize);
45 }
46 }
48 mCallback->Call(blob, rv);
49 }
51 // These members aren't thread-safe. We're making sure that they're being
52 // released on the main thread here. Otherwise, they could be getting
53 // released by EncodingRunnable's destructor on the encoding thread
54 // (bug 916128).
55 mScriptContext = nullptr;
56 mCallback = nullptr;
58 mEncoderThread->Shutdown();
59 return rv.ErrorCode();
60 }
62 void SetMembers(void* aImgData, uint64_t aImgSize, const nsAutoString& aType)
63 {
64 mImgData = aImgData;
65 mImgSize = aImgSize;
66 mType = aType;
67 }
69 void SetFailed()
70 {
71 mFailed = true;
72 }
74 private:
75 uint64_t mImgSize;
76 nsAutoString mType;
77 void* mImgData;
78 nsCOMPtr<nsIScriptContext> mScriptContext;
79 nsCOMPtr<nsIThread> mEncoderThread;
80 nsRefPtr<FileCallback> mCallback;
81 bool mFailed;
82 };
84 NS_IMPL_ISUPPORTS(EncodingCompleteEvent, nsIRunnable);
86 class EncodingRunnable : public nsRunnable
87 {
88 public:
89 NS_DECL_THREADSAFE_ISUPPORTS
91 EncodingRunnable(const nsAString& aType,
92 const nsAString& aOptions,
93 uint8_t* aImageBuffer,
94 imgIEncoder* aEncoder,
95 EncodingCompleteEvent* aEncodingCompleteEvent,
96 int32_t aFormat,
97 const nsIntSize aSize,
98 bool aUsePlaceholder,
99 bool aUsingCustomOptions)
100 : mType(aType)
101 , mOptions(aOptions)
102 , mImageBuffer(aImageBuffer)
103 , mEncoder(aEncoder)
104 , mEncodingCompleteEvent(aEncodingCompleteEvent)
105 , mFormat(aFormat)
106 , mSize(aSize)
107 , mUsePlaceholder(aUsePlaceholder)
108 , mUsingCustomOptions(aUsingCustomOptions)
109 {}
110 virtual ~EncodingRunnable() {}
112 nsresult ProcessImageData(uint64_t* aImgSize, void** aImgData)
113 {
114 nsCOMPtr<nsIInputStream> stream;
115 nsresult rv = ImageEncoder::ExtractDataInternal(mType,
116 mOptions,
117 mImageBuffer,
118 mFormat,
119 mSize,
120 mUsePlaceholder,
121 nullptr,
122 getter_AddRefs(stream),
123 mEncoder);
125 // If there are unrecognized custom parse options, we should fall back to
126 // the default values for the encoder without any options at all.
127 if (rv == NS_ERROR_INVALID_ARG && mUsingCustomOptions) {
128 rv = ImageEncoder::ExtractDataInternal(mType,
129 EmptyString(),
130 mImageBuffer,
131 mFormat,
132 mSize,
133 mUsePlaceholder,
134 nullptr,
135 getter_AddRefs(stream),
136 mEncoder);
137 }
138 NS_ENSURE_SUCCESS(rv, rv);
140 rv = stream->Available(aImgSize);
141 NS_ENSURE_SUCCESS(rv, rv);
142 NS_ENSURE_TRUE(*aImgSize <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG);
144 rv = NS_ReadInputStreamToBuffer(stream, aImgData, *aImgSize);
145 NS_ENSURE_SUCCESS(rv, rv);
147 return rv;
148 }
150 NS_IMETHOD Run()
151 {
152 uint64_t imgSize;
153 void* imgData = nullptr;
155 nsresult rv = ProcessImageData(&imgSize, &imgData);
156 if (NS_FAILED(rv)) {
157 mEncodingCompleteEvent->SetFailed();
158 } else {
159 mEncodingCompleteEvent->SetMembers(imgData, imgSize, mType);
160 }
161 rv = NS_DispatchToMainThread(mEncodingCompleteEvent, NS_DISPATCH_NORMAL);
162 if (NS_FAILED(rv)) {
163 // Better to leak than to crash.
164 mEncodingCompleteEvent.forget();
165 return rv;
166 }
168 return rv;
169 }
171 private:
172 nsAutoString mType;
173 nsAutoString mOptions;
174 nsAutoArrayPtr<uint8_t> mImageBuffer;
175 nsCOMPtr<imgIEncoder> mEncoder;
176 nsRefPtr<EncodingCompleteEvent> mEncodingCompleteEvent;
177 int32_t mFormat;
178 const nsIntSize mSize;
179 bool mUsePlaceholder;
180 bool mUsingCustomOptions;
181 };
183 NS_IMPL_ISUPPORTS(EncodingRunnable, nsIRunnable)
185 /* static */
186 nsresult
187 ImageEncoder::ExtractData(nsAString& aType,
188 const nsAString& aOptions,
189 const nsIntSize aSize,
190 bool aUsePlaceholder,
191 nsICanvasRenderingContextInternal* aContext,
192 nsIInputStream** aStream)
193 {
194 nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType);
195 if (!encoder) {
196 return NS_IMAGELIB_ERROR_NO_ENCODER;
197 }
199 return ExtractDataInternal(aType, aOptions, nullptr, 0, aSize,
200 aUsePlaceholder, aContext, aStream, encoder);
201 }
203 /* static */
204 nsresult
205 ImageEncoder::ExtractDataAsync(nsAString& aType,
206 const nsAString& aOptions,
207 bool aUsingCustomOptions,
208 uint8_t* aImageBuffer,
209 int32_t aFormat,
210 const nsIntSize aSize,
211 bool aUsePlaceholder,
212 nsICanvasRenderingContextInternal* aContext,
213 nsIScriptContext* aScriptContext,
214 FileCallback& aCallback)
215 {
216 nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType);
217 if (!encoder) {
218 return NS_IMAGELIB_ERROR_NO_ENCODER;
219 }
221 nsCOMPtr<nsIThread> encoderThread;
222 nsresult rv = NS_NewThread(getter_AddRefs(encoderThread), nullptr);
223 NS_ENSURE_SUCCESS(rv, rv);
225 nsRefPtr<EncodingCompleteEvent> completeEvent =
226 new EncodingCompleteEvent(aScriptContext, encoderThread, aCallback);
228 nsCOMPtr<nsIRunnable> event = new EncodingRunnable(aType,
229 aOptions,
230 aImageBuffer,
231 encoder,
232 completeEvent,
233 aFormat,
234 aSize,
235 aUsePlaceholder,
236 aUsingCustomOptions);
237 return encoderThread->Dispatch(event, NS_DISPATCH_NORMAL);
238 }
240 /*static*/ nsresult
241 ImageEncoder::GetInputStream(int32_t aWidth,
242 int32_t aHeight,
243 uint8_t* aImageBuffer,
244 int32_t aFormat,
245 imgIEncoder* aEncoder,
246 const char16_t* aEncoderOptions,
247 nsIInputStream** aStream)
248 {
249 nsresult rv =
250 aEncoder->InitFromData(aImageBuffer,
251 aWidth * aHeight * 4, aWidth, aHeight, aWidth * 4,
252 aFormat,
253 nsDependentString(aEncoderOptions));
254 NS_ENSURE_SUCCESS(rv, rv);
256 return CallQueryInterface(aEncoder, aStream);
257 }
259 /* static */
260 nsresult
261 ImageEncoder::ExtractDataInternal(const nsAString& aType,
262 const nsAString& aOptions,
263 uint8_t* aImageBuffer,
264 int32_t aFormat,
265 const nsIntSize aSize,
266 bool aUsePlaceholder,
267 nsICanvasRenderingContextInternal* aContext,
268 nsIInputStream** aStream,
269 imgIEncoder* aEncoder)
270 {
271 nsCOMPtr<nsIInputStream> imgStream;
273 // get image bytes
274 nsresult rv;
275 if (aImageBuffer && !aUsePlaceholder) {
276 rv = ImageEncoder::GetInputStream(
277 aSize.width,
278 aSize.height,
279 aImageBuffer,
280 aFormat,
281 aEncoder,
282 nsPromiseFlatString(aOptions).get(),
283 getter_AddRefs(imgStream));
284 } else if (aContext && !aUsePlaceholder) {
285 NS_ConvertUTF16toUTF8 encoderType(aType);
286 rv = aContext->GetInputStream(encoderType.get(),
287 nsPromiseFlatString(aOptions).get(),
288 getter_AddRefs(imgStream));
289 } else {
290 // If placeholder data requested or no context, encode an empty image.
291 // note that if we didn't have a current context, the spec says we're
292 // supposed to just return transparent black pixels of the canvas
293 // dimensions.
294 nsRefPtr<gfxImageSurface> emptyCanvas =
295 new gfxImageSurface(gfxIntSize(aSize.width, aSize.height),
296 gfxImageFormat::ARGB32);
297 if (emptyCanvas->CairoStatus()) {
298 return NS_ERROR_INVALID_ARG;
299 }
300 if (aUsePlaceholder) {
301 // If placeholder data was requested, return all-white, opaque image data.
302 int32_t dataSize = emptyCanvas->GetDataSize();
303 memset(emptyCanvas->Data(), 0xFF, dataSize);
304 }
305 rv = aEncoder->InitFromData(emptyCanvas->Data(),
306 aSize.width * aSize.height * 4,
307 aSize.width,
308 aSize.height,
309 aSize.width * 4,
310 imgIEncoder::INPUT_FORMAT_HOSTARGB,
311 aOptions);
312 if (NS_SUCCEEDED(rv)) {
313 imgStream = do_QueryInterface(aEncoder);
314 }
315 }
316 NS_ENSURE_SUCCESS(rv, rv);
318 imgStream.forget(aStream);
319 return rv;
320 }
322 /* static */
323 already_AddRefed<imgIEncoder>
324 ImageEncoder::GetImageEncoder(nsAString& aType)
325 {
326 // Get an image encoder for the media type.
327 nsCString encoderCID("@mozilla.org/image/encoder;2?type=");
328 NS_ConvertUTF16toUTF8 encoderType(aType);
329 encoderCID += encoderType;
330 nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(encoderCID.get());
332 if (!encoder && aType != NS_LITERAL_STRING("image/png")) {
333 // Unable to create an encoder instance of the specified type. Falling back
334 // to PNG.
335 aType.AssignLiteral("image/png");
336 nsCString PNGEncoderCID("@mozilla.org/image/encoder;2?type=image/png");
337 encoder = do_CreateInstance(PNGEncoderCID.get());
338 }
340 return encoder.forget();
341 }
343 } // namespace dom
344 } // namespace mozilla