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: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 "nsImageClipboard.h"
8 #include "gfxUtils.h"
9 #include "mozilla/gfx/2D.h"
10 #include "mozilla/gfx/DataSurfaceHelpers.h"
11 #include "mozilla/RefPtr.h"
12 #include "nsITransferable.h"
13 #include "nsGfxCIID.h"
14 #include "nsMemory.h"
15 #include "prmem.h"
16 #include "imgIEncoder.h"
17 #include "nsLiteralString.h"
18 #include "nsComponentManagerUtils.h"
20 #define BFH_LENGTH 14
22 using namespace mozilla;
23 using namespace mozilla::gfx;
25 /* Things To Do 11/8/00
27 Check image metrics, can we support them? Do we need to?
28 Any other render format? HTML?
30 */
33 //
34 // nsImageToClipboard ctor
35 //
36 // Given an imgIContainer, convert it to a DIB that is ready to go on the win32 clipboard
37 //
38 nsImageToClipboard::nsImageToClipboard(imgIContainer* aInImage, bool aWantDIBV5)
39 : mImage(aInImage)
40 , mWantDIBV5(aWantDIBV5)
41 {
42 // nothing to do here
43 }
46 //
47 // nsImageToClipboard dtor
48 //
49 // Clean up after ourselves. We know that we have created the bitmap
50 // successfully if we still have a pointer to the header.
51 //
52 nsImageToClipboard::~nsImageToClipboard()
53 {
54 }
57 //
58 // GetPicture
59 //
60 // Call to get the actual bits that go on the clipboard. If an error
61 // ocurred during conversion, |outBits| will be null.
62 //
63 // NOTE: The caller owns the handle and must delete it with ::GlobalRelease()
64 //
65 nsresult
66 nsImageToClipboard :: GetPicture ( HANDLE* outBits )
67 {
68 NS_ASSERTION ( outBits, "Bad parameter" );
70 return CreateFromImage ( mImage, outBits );
72 } // GetPicture
75 //
76 // CalcSize
77 //
78 // Computes # of bytes needed by a bitmap with the specified attributes.
79 //
80 int32_t
81 nsImageToClipboard :: CalcSize ( int32_t aHeight, int32_t aColors, WORD aBitsPerPixel, int32_t aSpanBytes )
82 {
83 int32_t HeaderMem = sizeof(BITMAPINFOHEADER);
85 // add size of pallette to header size
86 if (aBitsPerPixel < 16)
87 HeaderMem += aColors * sizeof(RGBQUAD);
89 if (aHeight < 0)
90 aHeight = -aHeight;
92 return (HeaderMem + (aHeight * aSpanBytes));
93 }
96 //
97 // CalcSpanLength
98 //
99 // Computes the span bytes for determining the overall size of the image
100 //
101 int32_t
102 nsImageToClipboard::CalcSpanLength(uint32_t aWidth, uint32_t aBitCount)
103 {
104 int32_t spanBytes = (aWidth * aBitCount) >> 5;
106 if ((aWidth * aBitCount) & 0x1F)
107 spanBytes++;
108 spanBytes <<= 2;
110 return spanBytes;
111 }
114 //
115 // CreateFromImage
116 //
117 // Do the work to setup the bitmap header and copy the bits out of the
118 // image.
119 //
120 nsresult
121 nsImageToClipboard::CreateFromImage ( imgIContainer* inImage, HANDLE* outBitmap )
122 {
123 nsresult rv;
124 *outBitmap = nullptr;
126 RefPtr<SourceSurface> surface =
127 inImage->GetFrame(imgIContainer::FRAME_CURRENT,
128 imgIContainer::FLAG_SYNC_DECODE);
129 NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE);
131 MOZ_ASSERT(surface->GetFormat() == SurfaceFormat::B8G8R8A8 ||
132 surface->GetFormat() == SurfaceFormat::B8G8R8X8);
134 RefPtr<DataSourceSurface> dataSurface;
135 if (surface->GetFormat() == SurfaceFormat::B8G8R8A8) {
136 dataSurface = surface->GetDataSurface();
137 } else {
138 // XXXjwatt Bug 995923 - get rid of this copy and handle B8G8R8X8
139 // directly below once bug 995807 is fixed.
140 dataSurface = gfxUtils::
141 CopySurfaceToDataSourceSurfaceWithFormat(surface,
142 SurfaceFormat::B8G8R8A8);
143 }
144 NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
146 nsCOMPtr<imgIEncoder> encoder = do_CreateInstance("@mozilla.org/image/encoder;2?type=image/bmp", &rv);
147 NS_ENSURE_SUCCESS(rv, rv);
149 uint32_t format;
150 nsAutoString options;
151 if (mWantDIBV5) {
152 options.AppendLiteral("version=5;bpp=");
153 } else {
154 options.AppendLiteral("version=3;bpp=");
155 }
156 switch (dataSurface->GetFormat()) {
157 case SurfaceFormat::B8G8R8A8:
158 format = imgIEncoder::INPUT_FORMAT_HOSTARGB;
159 options.AppendInt(32);
160 break;
161 #if 0
162 // XXXjwatt Bug 995923 - fix |format| and reenable once bug 995807 is fixed.
163 case SurfaceFormat::B8G8R8X8:
164 format = imgIEncoder::INPUT_FORMAT_RGB;
165 options.AppendInt(24);
166 break;
167 #endif
168 default:
169 NS_NOTREACHED("Unexpected surface format");
170 return NS_ERROR_INVALID_ARG;
171 }
173 DataSourceSurface::MappedSurface map;
174 bool mappedOK = dataSurface->Map(DataSourceSurface::MapType::READ, &map);
175 NS_ENSURE_TRUE(mappedOK, NS_ERROR_FAILURE);
177 rv = encoder->InitFromData(map.mData, 0,
178 dataSurface->GetSize().width,
179 dataSurface->GetSize().height,
180 map.mStride,
181 format, options);
182 dataSurface->Unmap();
183 NS_ENSURE_SUCCESS(rv, rv);
185 uint32_t size;
186 encoder->GetImageBufferUsed(&size);
187 NS_ENSURE_TRUE(size > BFH_LENGTH, NS_ERROR_FAILURE);
188 HGLOBAL glob = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE | GMEM_ZEROINIT,
189 size - BFH_LENGTH);
190 if (!glob)
191 return NS_ERROR_OUT_OF_MEMORY;
193 char *dst = (char*) ::GlobalLock(glob);
194 char *src;
195 rv = encoder->GetImageBuffer(&src);
196 NS_ENSURE_SUCCESS(rv, rv);
198 ::CopyMemory(dst, src + BFH_LENGTH, size - BFH_LENGTH);
199 ::GlobalUnlock(glob);
201 *outBitmap = (HANDLE)glob;
202 return NS_OK;
203 }
205 nsImageFromClipboard :: nsImageFromClipboard ()
206 {
207 // nothing to do here
208 }
210 nsImageFromClipboard :: ~nsImageFromClipboard ( )
211 {
212 }
214 //
215 // GetEncodedImageStream
216 //
217 // Take the raw clipboard image data and convert it to aMIMEFormat in the form of a nsIInputStream
218 //
219 nsresult
220 nsImageFromClipboard ::GetEncodedImageStream (unsigned char * aClipboardData, const char * aMIMEFormat, nsIInputStream** aInputStream )
221 {
222 NS_ENSURE_ARG_POINTER (aInputStream);
223 NS_ENSURE_ARG_POINTER (aMIMEFormat);
224 nsresult rv;
225 *aInputStream = nullptr;
227 // pull the size information out of the BITMAPINFO header and
228 // initialize the image
229 BITMAPINFO* header = (BITMAPINFO *) aClipboardData;
230 int32_t width = header->bmiHeader.biWidth;
231 int32_t height = header->bmiHeader.biHeight;
232 // neg. heights mean the Y axis is inverted and we don't handle that case
233 NS_ENSURE_TRUE(height > 0, NS_ERROR_FAILURE);
235 unsigned char * rgbData = new unsigned char[width * height * 3 /* RGB */];
237 if (rgbData) {
238 BYTE * pGlobal = (BYTE *) aClipboardData;
239 // Convert the clipboard image into RGB packed pixel data
240 rv = ConvertColorBitMap((unsigned char *) (pGlobal + header->bmiHeader.biSize), header, rgbData);
241 // if that succeeded, encode the bitmap as aMIMEFormat data. Don't return early or we risk leaking rgbData
242 if (NS_SUCCEEDED(rv)) {
243 nsAutoCString encoderCID(NS_LITERAL_CSTRING("@mozilla.org/image/encoder;2?type="));
245 // Map image/jpg to image/jpeg (which is how the encoder is registered).
246 if (strcmp(aMIMEFormat, kJPGImageMime) == 0)
247 encoderCID.Append("image/jpeg");
248 else
249 encoderCID.Append(aMIMEFormat);
250 nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(encoderCID.get(), &rv);
251 if (NS_SUCCEEDED(rv)){
252 rv = encoder->InitFromData(rgbData, 0, width, height, 3 * width /* RGB * # pixels in a row */,
253 imgIEncoder::INPUT_FORMAT_RGB, EmptyString());
254 if (NS_SUCCEEDED(rv))
255 encoder->QueryInterface(NS_GET_IID(nsIInputStream), (void **) aInputStream);
256 }
257 }
258 delete [] rgbData;
259 }
260 else
261 rv = NS_ERROR_OUT_OF_MEMORY;
263 return rv;
264 } // GetImage
266 //
267 // InvertRows
268 //
269 // Take the image data from the clipboard and invert the rows. Modifying aInitialBuffer in place.
270 //
271 void
272 nsImageFromClipboard::InvertRows(unsigned char * aInitialBuffer, uint32_t aSizeOfBuffer, uint32_t aNumBytesPerRow)
273 {
274 if (!aNumBytesPerRow)
275 return;
277 uint32_t numRows = aSizeOfBuffer / aNumBytesPerRow;
278 unsigned char * row = new unsigned char[aNumBytesPerRow];
280 uint32_t currentRow = 0;
281 uint32_t lastRow = (numRows - 1) * aNumBytesPerRow;
282 while (currentRow < lastRow)
283 {
284 // store the current row into a temporary buffer
285 memcpy(row, &aInitialBuffer[currentRow], aNumBytesPerRow);
286 memcpy(&aInitialBuffer[currentRow], &aInitialBuffer[lastRow], aNumBytesPerRow);
287 memcpy(&aInitialBuffer[lastRow], row, aNumBytesPerRow);
288 lastRow -= aNumBytesPerRow;
289 currentRow += aNumBytesPerRow;
290 }
292 delete[] row;
293 }
295 //
296 // ConvertColorBitMap
297 //
298 // Takes the clipboard bitmap and converts it into a RGB packed pixel values.
299 //
300 nsresult
301 nsImageFromClipboard::ConvertColorBitMap(unsigned char * aInputBuffer, PBITMAPINFO pBitMapInfo, unsigned char * aOutBuffer)
302 {
303 uint8_t bitCount = pBitMapInfo->bmiHeader.biBitCount;
304 uint32_t imageSize = pBitMapInfo->bmiHeader.biSizeImage; // may be zero for BI_RGB bitmaps which means we need to calculate by hand
305 uint32_t bytesPerPixel = bitCount / 8;
307 if (bitCount <= 4)
308 bytesPerPixel = 1;
310 // rows are DWORD aligned. Calculate how many real bytes are in each row in the bitmap. This number won't
311 // correspond to biWidth.
312 uint32_t rowSize = (bitCount * pBitMapInfo->bmiHeader.biWidth + 7) / 8; // +7 to round up
313 if (rowSize % 4)
314 rowSize += (4 - (rowSize % 4)); // Pad to DWORD Boundary
316 // if our buffer includes a color map, skip over it
317 if (bitCount <= 8)
318 {
319 int32_t bytesToSkip = (pBitMapInfo->bmiHeader.biClrUsed ? pBitMapInfo->bmiHeader.biClrUsed : (1 << bitCount) ) * sizeof(RGBQUAD);
320 aInputBuffer += bytesToSkip;
321 }
323 bitFields colorMasks; // only used if biCompression == BI_BITFIELDS
325 if (pBitMapInfo->bmiHeader.biCompression == BI_BITFIELDS)
326 {
327 // color table consists of 3 DWORDS containing the color masks...
328 colorMasks.red = (*((uint32_t*)&(pBitMapInfo->bmiColors[0])));
329 colorMasks.green = (*((uint32_t*)&(pBitMapInfo->bmiColors[1])));
330 colorMasks.blue = (*((uint32_t*)&(pBitMapInfo->bmiColors[2])));
331 CalcBitShift(&colorMasks);
332 aInputBuffer += 3 * sizeof(DWORD);
333 }
334 else if (pBitMapInfo->bmiHeader.biCompression == BI_RGB && !imageSize) // BI_RGB can have a size of zero which means we figure it out
335 {
336 // XXX: note use rowSize here and not biWidth. rowSize accounts for the DWORD padding for each row
337 imageSize = rowSize * pBitMapInfo->bmiHeader.biHeight;
338 }
340 // The windows clipboard image format inverts the rows
341 InvertRows(aInputBuffer, imageSize, rowSize);
343 if (!pBitMapInfo->bmiHeader.biCompression || pBitMapInfo->bmiHeader.biCompression == BI_BITFIELDS)
344 {
345 uint32_t index = 0;
346 uint32_t writeIndex = 0;
348 unsigned char redValue, greenValue, blueValue;
349 uint8_t colorTableEntry = 0;
350 int8_t bit; // used for grayscale bitmaps where each bit is a pixel
351 uint32_t numPixelsLeftInRow = pBitMapInfo->bmiHeader.biWidth; // how many more pixels do we still need to read for the current row
352 uint32_t pos = 0;
354 while (index < imageSize)
355 {
356 switch (bitCount)
357 {
358 case 1:
359 for (bit = 7; bit >= 0 && numPixelsLeftInRow; bit--)
360 {
361 colorTableEntry = (aInputBuffer[index] >> bit) & 1;
362 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbRed;
363 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbGreen;
364 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbBlue;
365 numPixelsLeftInRow--;
366 }
367 pos += 1;
368 break;
369 case 4:
370 {
371 // each aInputBuffer[index] entry contains data for two pixels.
372 // read the first pixel
373 colorTableEntry = aInputBuffer[index] >> 4;
374 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbRed;
375 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbGreen;
376 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbBlue;
377 numPixelsLeftInRow--;
379 if (numPixelsLeftInRow) // now read the second pixel
380 {
381 colorTableEntry = aInputBuffer[index] & 0xF;
382 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbRed;
383 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbGreen;
384 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbBlue;
385 numPixelsLeftInRow--;
386 }
387 pos += 1;
388 }
389 break;
390 case 8:
391 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[aInputBuffer[index]].rgbRed;
392 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[aInputBuffer[index]].rgbGreen;
393 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[aInputBuffer[index]].rgbBlue;
394 numPixelsLeftInRow--;
395 pos += 1;
396 break;
397 case 16:
398 {
399 uint16_t num = 0;
400 num = (uint8_t) aInputBuffer[index+1];
401 num <<= 8;
402 num |= (uint8_t) aInputBuffer[index];
404 redValue = ((uint32_t) (((float)(num & 0xf800) / 0xf800) * 0xFF0000) & 0xFF0000)>> 16;
405 greenValue = ((uint32_t)(((float)(num & 0x07E0) / 0x07E0) * 0x00FF00) & 0x00FF00)>> 8;
406 blueValue = ((uint32_t)(((float)(num & 0x001F) / 0x001F) * 0x0000FF) & 0x0000FF);
408 // now we have the right RGB values...
409 aOutBuffer[writeIndex++] = redValue;
410 aOutBuffer[writeIndex++] = greenValue;
411 aOutBuffer[writeIndex++] = blueValue;
412 numPixelsLeftInRow--;
413 pos += 2;
414 }
415 break;
416 case 32:
417 case 24:
418 if (pBitMapInfo->bmiHeader.biCompression == BI_BITFIELDS)
419 {
420 uint32_t val = *((uint32_t*) (aInputBuffer + index) );
421 aOutBuffer[writeIndex++] = (val & colorMasks.red) >> colorMasks.redRightShift << colorMasks.redLeftShift;
422 aOutBuffer[writeIndex++] = (val & colorMasks.green) >> colorMasks.greenRightShift << colorMasks.greenLeftShift;
423 aOutBuffer[writeIndex++] = (val & colorMasks.blue) >> colorMasks.blueRightShift << colorMasks.blueLeftShift;
424 numPixelsLeftInRow--;
425 pos += 4; // we read in 4 bytes of data in order to process this pixel
426 }
427 else
428 {
429 aOutBuffer[writeIndex++] = aInputBuffer[index+2];
430 aOutBuffer[writeIndex++] = aInputBuffer[index+1];
431 aOutBuffer[writeIndex++] = aInputBuffer[index];
432 numPixelsLeftInRow--;
433 pos += bytesPerPixel; // 3 bytes for 24 bit data, 4 bytes for 32 bit data (we skip over the 4th byte)...
434 }
435 break;
436 default:
437 // This is probably the wrong place to check this...
438 return NS_ERROR_FAILURE;
439 }
441 index += bytesPerPixel; // increment our loop counter
443 if (!numPixelsLeftInRow)
444 {
445 if (rowSize != pos)
446 {
447 // advance index to skip over remaining padding bytes
448 index += (rowSize - pos);
449 }
450 numPixelsLeftInRow = pBitMapInfo->bmiHeader.biWidth;
451 pos = 0;
452 }
454 } // while we still have bytes to process
455 }
457 return NS_OK;
458 }
460 void nsImageFromClipboard::CalcBitmask(uint32_t aMask, uint8_t& aBegin, uint8_t& aLength)
461 {
462 // find the rightmost 1
463 uint8_t pos;
464 bool started = false;
465 aBegin = aLength = 0;
466 for (pos = 0; pos <= 31; pos++)
467 {
468 if (!started && (aMask & (1 << pos)))
469 {
470 aBegin = pos;
471 started = true;
472 }
473 else if (started && !(aMask & (1 << pos)))
474 {
475 aLength = pos - aBegin;
476 break;
477 }
478 }
479 }
481 void nsImageFromClipboard::CalcBitShift(bitFields * aColorMask)
482 {
483 uint8_t begin, length;
484 // red
485 CalcBitmask(aColorMask->red, begin, length);
486 aColorMask->redRightShift = begin;
487 aColorMask->redLeftShift = 8 - length;
488 // green
489 CalcBitmask(aColorMask->green, begin, length);
490 aColorMask->greenRightShift = begin;
491 aColorMask->greenLeftShift = 8 - length;
492 // blue
493 CalcBitmask(aColorMask->blue, begin, length);
494 aColorMask->blueRightShift = begin;
495 aColorMask->blueLeftShift = 8 - length;
496 }