Thu, 15 Jan 2015 15:59:08 +0100
Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
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 "nsJPEGEncoder.h"
7 #include "prprf.h"
8 #include "nsString.h"
9 #include "nsStreamUtils.h"
10 #include "gfxColor.h"
12 #include <setjmp.h>
13 #include "jerror.h"
15 using namespace mozilla;
17 NS_IMPL_ISUPPORTS(nsJPEGEncoder, imgIEncoder, nsIInputStream, nsIAsyncInputStream)
19 // used to pass error info through the JPEG library
20 struct encoder_error_mgr {
21 jpeg_error_mgr pub;
22 jmp_buf setjmp_buffer;
23 };
25 nsJPEGEncoder::nsJPEGEncoder() : mFinished(false),
26 mImageBuffer(nullptr), mImageBufferSize(0),
27 mImageBufferUsed(0), mImageBufferReadPoint(0),
28 mCallback(nullptr),
29 mCallbackTarget(nullptr), mNotifyThreshold(0),
30 mReentrantMonitor("nsJPEGEncoder.mReentrantMonitor")
31 {
32 }
34 nsJPEGEncoder::~nsJPEGEncoder()
35 {
36 if (mImageBuffer) {
37 moz_free(mImageBuffer);
38 mImageBuffer = nullptr;
39 }
40 }
43 // nsJPEGEncoder::InitFromData
44 //
45 // One output option is supported: "quality=X" where X is an integer in the
46 // range 0-100. Higher values for X give better quality.
47 //
48 // Transparency is always discarded.
50 NS_IMETHODIMP nsJPEGEncoder::InitFromData(const uint8_t* aData,
51 uint32_t aLength, // (unused, req'd by JS)
52 uint32_t aWidth,
53 uint32_t aHeight,
54 uint32_t aStride,
55 uint32_t aInputFormat,
56 const nsAString& aOutputOptions)
57 {
58 NS_ENSURE_ARG(aData);
60 // validate input format
61 if (aInputFormat != INPUT_FORMAT_RGB &&
62 aInputFormat != INPUT_FORMAT_RGBA &&
63 aInputFormat != INPUT_FORMAT_HOSTARGB)
64 return NS_ERROR_INVALID_ARG;
66 // Stride is the padded width of each row, so it better be longer (I'm afraid
67 // people will not understand what stride means, so check it well)
68 if ((aInputFormat == INPUT_FORMAT_RGB &&
69 aStride < aWidth * 3) ||
70 ((aInputFormat == INPUT_FORMAT_RGBA || aInputFormat == INPUT_FORMAT_HOSTARGB) &&
71 aStride < aWidth * 4)) {
72 NS_WARNING("Invalid stride for InitFromData");
73 return NS_ERROR_INVALID_ARG;
74 }
76 // can't initialize more than once
77 if (mImageBuffer != nullptr)
78 return NS_ERROR_ALREADY_INITIALIZED;
80 // options: we only have one option so this is easy
81 int quality = 92;
82 if (aOutputOptions.Length() > 0) {
83 // have options string
84 const nsString qualityPrefix(NS_LITERAL_STRING("quality="));
85 if (aOutputOptions.Length() > qualityPrefix.Length() &&
86 StringBeginsWith(aOutputOptions, qualityPrefix)) {
87 // have quality string
88 nsCString value = NS_ConvertUTF16toUTF8(Substring(aOutputOptions,
89 qualityPrefix.Length()));
90 int newquality = -1;
91 if (PR_sscanf(value.get(), "%d", &newquality) == 1) {
92 if (newquality >= 0 && newquality <= 100) {
93 quality = newquality;
94 } else {
95 NS_WARNING("Quality value out of range, should be 0-100, using default");
96 }
97 } else {
98 NS_WARNING("Quality value invalid, should be integer 0-100, using default");
99 }
100 }
101 else {
102 return NS_ERROR_INVALID_ARG;
103 }
104 }
106 jpeg_compress_struct cinfo;
108 // We set up the normal JPEG error routines, then override error_exit.
109 // This must be done before the call to create_compress
110 encoder_error_mgr errmgr;
111 cinfo.err = jpeg_std_error(&errmgr.pub);
112 errmgr.pub.error_exit = errorExit;
113 // Establish the setjmp return context for my_error_exit to use.
114 if (setjmp(errmgr.setjmp_buffer)) {
115 // If we get here, the JPEG code has signaled an error.
116 // We need to clean up the JPEG object, close the input file, and return.
117 return NS_ERROR_FAILURE;
118 }
120 jpeg_create_compress(&cinfo);
121 cinfo.image_width = aWidth;
122 cinfo.image_height = aHeight;
123 cinfo.input_components = 3;
124 cinfo.in_color_space = JCS_RGB;
125 cinfo.data_precision = 8;
127 jpeg_set_defaults(&cinfo);
128 jpeg_set_quality(&cinfo, quality, 1); // quality here is 0-100
129 if (quality >= 90) {
130 int i;
131 for (i=0; i < MAX_COMPONENTS; i++) {
132 cinfo.comp_info[i].h_samp_factor=1;
133 cinfo.comp_info[i].v_samp_factor=1;
134 }
135 }
137 // set up the destination manager
138 jpeg_destination_mgr destmgr;
139 destmgr.init_destination = initDestination;
140 destmgr.empty_output_buffer = emptyOutputBuffer;
141 destmgr.term_destination = termDestination;
142 cinfo.dest = &destmgr;
143 cinfo.client_data = this;
145 jpeg_start_compress(&cinfo, 1);
147 // feed it the rows
148 if (aInputFormat == INPUT_FORMAT_RGB) {
149 while (cinfo.next_scanline < cinfo.image_height) {
150 const uint8_t* row = &aData[cinfo.next_scanline * aStride];
151 jpeg_write_scanlines(&cinfo, const_cast<uint8_t**>(&row), 1);
152 }
153 } else if (aInputFormat == INPUT_FORMAT_RGBA) {
154 uint8_t* row = new uint8_t[aWidth * 3];
155 while (cinfo.next_scanline < cinfo.image_height) {
156 ConvertRGBARow(&aData[cinfo.next_scanline * aStride], row, aWidth);
157 jpeg_write_scanlines(&cinfo, &row, 1);
158 }
159 delete[] row;
160 } else if (aInputFormat == INPUT_FORMAT_HOSTARGB) {
161 uint8_t* row = new uint8_t[aWidth * 3];
162 while (cinfo.next_scanline < cinfo.image_height) {
163 ConvertHostARGBRow(&aData[cinfo.next_scanline * aStride], row, aWidth);
164 jpeg_write_scanlines(&cinfo, &row, 1);
165 }
166 delete[] row;
167 }
169 jpeg_finish_compress(&cinfo);
170 jpeg_destroy_compress(&cinfo);
172 mFinished = true;
173 NotifyListener();
175 // if output callback can't get enough memory, it will free our buffer
176 if (!mImageBuffer)
177 return NS_ERROR_OUT_OF_MEMORY;
179 return NS_OK;
180 }
183 NS_IMETHODIMP nsJPEGEncoder::StartImageEncode(uint32_t aWidth,
184 uint32_t aHeight,
185 uint32_t aInputFormat,
186 const nsAString& aOutputOptions)
187 {
188 return NS_ERROR_NOT_IMPLEMENTED;
189 }
191 // Returns the number of bytes in the image buffer used.
192 NS_IMETHODIMP nsJPEGEncoder::GetImageBufferUsed(uint32_t *aOutputSize)
193 {
194 NS_ENSURE_ARG_POINTER(aOutputSize);
195 *aOutputSize = mImageBufferUsed;
196 return NS_OK;
197 }
199 // Returns a pointer to the start of the image buffer
200 NS_IMETHODIMP nsJPEGEncoder::GetImageBuffer(char **aOutputBuffer)
201 {
202 NS_ENSURE_ARG_POINTER(aOutputBuffer);
203 *aOutputBuffer = reinterpret_cast<char*>(mImageBuffer);
204 return NS_OK;
205 }
207 NS_IMETHODIMP nsJPEGEncoder::AddImageFrame(const uint8_t* aData,
208 uint32_t aLength,
209 uint32_t aWidth,
210 uint32_t aHeight,
211 uint32_t aStride,
212 uint32_t aFrameFormat,
213 const nsAString& aFrameOptions)
214 {
215 return NS_ERROR_NOT_IMPLEMENTED;
216 }
218 NS_IMETHODIMP nsJPEGEncoder::EndImageEncode()
219 {
220 return NS_ERROR_NOT_IMPLEMENTED;
221 }
224 /* void close (); */
225 NS_IMETHODIMP nsJPEGEncoder::Close()
226 {
227 if (mImageBuffer != nullptr) {
228 moz_free(mImageBuffer);
229 mImageBuffer = nullptr;
230 mImageBufferSize = 0;
231 mImageBufferUsed = 0;
232 mImageBufferReadPoint = 0;
233 }
234 return NS_OK;
235 }
237 /* unsigned long available (); */
238 NS_IMETHODIMP nsJPEGEncoder::Available(uint64_t *_retval)
239 {
240 if (!mImageBuffer)
241 return NS_BASE_STREAM_CLOSED;
243 *_retval = mImageBufferUsed - mImageBufferReadPoint;
244 return NS_OK;
245 }
247 /* [noscript] unsigned long read (in charPtr aBuf, in unsigned long aCount); */
248 NS_IMETHODIMP nsJPEGEncoder::Read(char * aBuf, uint32_t aCount,
249 uint32_t *_retval)
250 {
251 return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval);
252 }
254 /* [noscript] unsigned long readSegments (in nsWriteSegmentFun aWriter, in voidPtr aClosure, in unsigned long aCount); */
255 NS_IMETHODIMP nsJPEGEncoder::ReadSegments(nsWriteSegmentFun aWriter, void *aClosure, uint32_t aCount, uint32_t *_retval)
256 {
257 // Avoid another thread reallocing the buffer underneath us
258 ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor);
260 uint32_t maxCount = mImageBufferUsed - mImageBufferReadPoint;
261 if (maxCount == 0) {
262 *_retval = 0;
263 return mFinished ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
264 }
266 if (aCount > maxCount)
267 aCount = maxCount;
268 nsresult rv = aWriter(this, aClosure,
269 reinterpret_cast<const char*>(mImageBuffer+mImageBufferReadPoint),
270 0, aCount, _retval);
271 if (NS_SUCCEEDED(rv)) {
272 NS_ASSERTION(*_retval <= aCount, "bad write count");
273 mImageBufferReadPoint += *_retval;
274 }
276 // errors returned from the writer end here!
277 return NS_OK;
278 }
280 /* boolean isNonBlocking (); */
281 NS_IMETHODIMP nsJPEGEncoder::IsNonBlocking(bool *_retval)
282 {
283 *_retval = true;
284 return NS_OK;
285 }
287 NS_IMETHODIMP nsJPEGEncoder::AsyncWait(nsIInputStreamCallback *aCallback,
288 uint32_t aFlags,
289 uint32_t aRequestedCount,
290 nsIEventTarget *aTarget)
291 {
292 if (aFlags != 0)
293 return NS_ERROR_NOT_IMPLEMENTED;
295 if (mCallback || mCallbackTarget)
296 return NS_ERROR_UNEXPECTED;
298 mCallbackTarget = aTarget;
299 // 0 means "any number of bytes except 0"
300 mNotifyThreshold = aRequestedCount;
301 if (!aRequestedCount)
302 mNotifyThreshold = 1024; // 1 KB seems good. We don't want to notify incessantly
304 // We set the callback absolutely last, because NotifyListener uses it to
305 // determine if someone needs to be notified. If we don't set it last,
306 // NotifyListener might try to fire off a notification to a null target
307 // which will generally cause non-threadsafe objects to be used off the main thread
308 mCallback = aCallback;
310 // What we are being asked for may be present already
311 NotifyListener();
312 return NS_OK;
313 }
315 NS_IMETHODIMP nsJPEGEncoder::CloseWithStatus(nsresult aStatus)
316 {
317 return Close();
318 }
322 // nsJPEGEncoder::ConvertHostARGBRow
323 //
324 // Our colors are stored with premultiplied alphas, but we need
325 // an output with no alpha in machine-independent byte order.
326 //
327 // See gfx/cairo/cairo/src/cairo-png.c
328 void
329 nsJPEGEncoder::ConvertHostARGBRow(const uint8_t* aSrc, uint8_t* aDest,
330 uint32_t aPixelWidth)
331 {
332 for (uint32_t x = 0; x < aPixelWidth; x++) {
333 const uint32_t& pixelIn = ((const uint32_t*)(aSrc))[x];
334 uint8_t *pixelOut = &aDest[x * 3];
336 pixelOut[0] = (pixelIn & 0xff0000) >> 16;
337 pixelOut[1] = (pixelIn & 0x00ff00) >> 8;
338 pixelOut[2] = (pixelIn & 0x0000ff) >> 0;
339 }
340 }
342 /**
343 * nsJPEGEncoder::ConvertRGBARow
344 *
345 * Input is RGBA, output is RGB, so we should alpha-premultiply.
346 */
347 void
348 nsJPEGEncoder::ConvertRGBARow(const uint8_t* aSrc, uint8_t* aDest,
349 uint32_t aPixelWidth)
350 {
351 for (uint32_t x = 0; x < aPixelWidth; x++) {
352 const uint8_t* pixelIn = &aSrc[x * 4];
353 uint8_t* pixelOut = &aDest[x * 3];
355 uint8_t alpha = pixelIn[3];
356 pixelOut[0] = gfxPreMultiply(pixelIn[0], alpha);
357 pixelOut[1] = gfxPreMultiply(pixelIn[1], alpha);
358 pixelOut[2] = gfxPreMultiply(pixelIn[2], alpha);
359 }
360 }
362 // nsJPEGEncoder::initDestination
363 //
364 // Initialize destination. This is called by jpeg_start_compress() before
365 // any data is actually written. It must initialize next_output_byte and
366 // free_in_buffer. free_in_buffer must be initialized to a positive value.
368 void // static
369 nsJPEGEncoder::initDestination(jpeg_compress_struct* cinfo)
370 {
371 nsJPEGEncoder* that = static_cast<nsJPEGEncoder*>(cinfo->client_data);
372 NS_ASSERTION(! that->mImageBuffer, "Image buffer already initialized");
374 that->mImageBufferSize = 8192;
375 that->mImageBuffer = (uint8_t*)moz_malloc(that->mImageBufferSize);
376 that->mImageBufferUsed = 0;
378 cinfo->dest->next_output_byte = that->mImageBuffer;
379 cinfo->dest->free_in_buffer = that->mImageBufferSize;
380 }
383 // nsJPEGEncoder::emptyOutputBuffer
384 //
385 // This is called whenever the buffer has filled (free_in_buffer reaches
386 // zero). In typical applications, it should write out the *entire* buffer
387 // (use the saved start address and buffer length; ignore the current state
388 // of next_output_byte and free_in_buffer). Then reset the pointer & count
389 // to the start of the buffer, and return TRUE indicating that the buffer
390 // has been dumped. free_in_buffer must be set to a positive value when
391 // TRUE is returned. A FALSE return should only be used when I/O suspension
392 // is desired (this operating mode is discussed in the next section).
394 boolean // static
395 nsJPEGEncoder::emptyOutputBuffer(jpeg_compress_struct* cinfo)
396 {
397 nsJPEGEncoder* that = static_cast<nsJPEGEncoder*>(cinfo->client_data);
398 NS_ASSERTION(that->mImageBuffer, "No buffer to empty!");
400 // When we're reallocing the buffer we need to take the lock to ensure
401 // that nobody is trying to read from the buffer we are destroying
402 ReentrantMonitorAutoEnter autoEnter(that->mReentrantMonitor);
404 that->mImageBufferUsed = that->mImageBufferSize;
406 // expand buffer, just double size each time
407 that->mImageBufferSize *= 2;
409 uint8_t* newBuf = (uint8_t*)moz_realloc(that->mImageBuffer,
410 that->mImageBufferSize);
411 if (! newBuf) {
412 // can't resize, just zero (this will keep us from writing more)
413 moz_free(that->mImageBuffer);
414 that->mImageBuffer = nullptr;
415 that->mImageBufferSize = 0;
416 that->mImageBufferUsed = 0;
418 // This seems to be the only way to do errors through the JPEG library. We
419 // pass an nsresult masquerading as an int, which works because the
420 // setjmp() caller casts it back.
421 longjmp(((encoder_error_mgr*)(cinfo->err))->setjmp_buffer,
422 static_cast<int>(NS_ERROR_OUT_OF_MEMORY));
423 }
424 that->mImageBuffer = newBuf;
426 cinfo->dest->next_output_byte = &that->mImageBuffer[that->mImageBufferUsed];
427 cinfo->dest->free_in_buffer = that->mImageBufferSize - that->mImageBufferUsed;
428 return 1;
429 }
432 // nsJPEGEncoder::termDestination
433 //
434 // Terminate destination --- called by jpeg_finish_compress() after all data
435 // has been written. In most applications, this must flush any data
436 // remaining in the buffer. Use either next_output_byte or free_in_buffer
437 // to determine how much data is in the buffer.
439 void // static
440 nsJPEGEncoder::termDestination(jpeg_compress_struct* cinfo)
441 {
442 nsJPEGEncoder* that = static_cast<nsJPEGEncoder*>(cinfo->client_data);
443 if (! that->mImageBuffer)
444 return;
445 that->mImageBufferUsed = cinfo->dest->next_output_byte - that->mImageBuffer;
446 NS_ASSERTION(that->mImageBufferUsed < that->mImageBufferSize,
447 "JPEG library busted, got a bad image buffer size");
448 that->NotifyListener();
449 }
452 // nsJPEGEncoder::errorExit
453 //
454 // Override the standard error method in the IJG JPEG decoder code. This
455 // was mostly copied from nsJPEGDecoder.cpp
457 void // static
458 nsJPEGEncoder::errorExit(jpeg_common_struct* cinfo)
459 {
460 nsresult error_code;
461 encoder_error_mgr *err = (encoder_error_mgr *) cinfo->err;
463 // Convert error to a browser error code
464 switch (cinfo->err->msg_code) {
465 case JERR_OUT_OF_MEMORY:
466 error_code = NS_ERROR_OUT_OF_MEMORY;
467 break;
468 default:
469 error_code = NS_ERROR_FAILURE;
470 }
472 // Return control to the setjmp point. We pass an nsresult masquerading as
473 // an int, which works because the setjmp() caller casts it back.
474 longjmp(err->setjmp_buffer, static_cast<int>(error_code));
475 }
477 void
478 nsJPEGEncoder::NotifyListener()
479 {
480 // We might call this function on multiple threads (any threads that call
481 // AsyncWait and any that do encoding) so we lock to avoid notifying the
482 // listener twice about the same data (which generally leads to a truncated
483 // image).
484 ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor);
486 if (mCallback &&
487 (mImageBufferUsed - mImageBufferReadPoint >= mNotifyThreshold ||
488 mFinished)) {
489 nsCOMPtr<nsIInputStreamCallback> callback;
490 if (mCallbackTarget) {
491 callback = NS_NewInputStreamReadyEvent(mCallback, mCallbackTarget);
492 } else {
493 callback = mCallback;
494 }
496 NS_ASSERTION(callback, "Shouldn't fail to make the callback");
497 // Null the callback first because OnInputStreamReady could reenter
498 // AsyncWait
499 mCallback = nullptr;
500 mCallbackTarget = nullptr;
501 mNotifyThreshold = 0;
503 callback->OnInputStreamReady(this);
504 }
505 }