summaryrefslogtreecommitdiffstats
path: root/image/decoders
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /image/decoders
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'image/decoders')
-rw-r--r--image/decoders/EXIF.cpp331
-rw-r--r--image/decoders/EXIF.h75
-rw-r--r--image/decoders/GIF2.h65
-rw-r--r--image/decoders/iccjpeg.c195
-rw-r--r--image/decoders/iccjpeg.h67
-rw-r--r--image/decoders/icon/android/moz.build13
-rw-r--r--image/decoders/icon/android/nsIconChannel.cpp145
-rw-r--r--image/decoders/icon/android/nsIconChannel.h46
-rw-r--r--image/decoders/icon/gtk/moz.build16
-rw-r--r--image/decoders/icon/gtk/nsIconChannel.cpp427
-rw-r--r--image/decoders/icon/gtk/nsIconChannel.h43
-rw-r--r--image/decoders/icon/mac/moz.build11
-rw-r--r--image/decoders/icon/mac/nsIconChannel.h59
-rw-r--r--image/decoders/icon/mac/nsIconChannelCocoa.mm565
-rw-r--r--image/decoders/icon/moz.build32
-rw-r--r--image/decoders/icon/nsIconModule.cpp60
-rw-r--r--image/decoders/icon/nsIconProtocolHandler.cpp126
-rw-r--r--image/decoders/icon/nsIconProtocolHandler.h26
-rw-r--r--image/decoders/icon/nsIconURI.cpp699
-rw-r--r--image/decoders/icon/nsIconURI.h63
-rw-r--r--image/decoders/icon/win/moz.build11
-rw-r--r--image/decoders/icon/win/nsIconChannel.cpp841
-rw-r--r--image/decoders/icon/win/nsIconChannel.h66
-rw-r--r--image/decoders/moz.build47
-rw-r--r--image/decoders/nsBMPDecoder.cpp1067
-rw-r--r--image/decoders/nsBMPDecoder.h235
-rw-r--r--image/decoders/nsGIFDecoder2.cpp1085
-rw-r--r--image/decoders/nsGIFDecoder2.h152
-rw-r--r--image/decoders/nsICODecoder.cpp673
-rw-r--r--image/decoders/nsICODecoder.h137
-rw-r--r--image/decoders/nsIconDecoder.cpp128
-rw-r--r--image/decoders/nsIconDecoder.h67
-rw-r--r--image/decoders/nsJPEGDecoder.cpp1006
-rw-r--r--image/decoders/nsJPEGDecoder.h125
-rw-r--r--image/decoders/nsPNGDecoder.cpp1113
-rw-r--r--image/decoders/nsPNGDecoder.h158
36 files changed, 9975 insertions, 0 deletions
diff --git a/image/decoders/EXIF.cpp b/image/decoders/EXIF.cpp
new file mode 100644
index 000000000..8197c886c
--- /dev/null
+++ b/image/decoders/EXIF.cpp
@@ -0,0 +1,331 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "EXIF.h"
+
+#include "mozilla/EndianUtils.h"
+
+namespace mozilla {
+namespace image {
+
+// Section references in this file refer to the EXIF v2.3 standard, also known
+// as CIPA DC-008-Translation-2010.
+
+// See Section 4.6.4, Table 4.
+// Typesafe enums are intentionally not used here since we're comparing to raw
+// integers produced by parsing.
+enum EXIFTag
+{
+ OrientationTag = 0x112,
+};
+
+// See Section 4.6.2.
+enum EXIFType
+{
+ ByteType = 1,
+ ASCIIType = 2,
+ ShortType = 3,
+ LongType = 4,
+ RationalType = 5,
+ UndefinedType = 7,
+ SignedLongType = 9,
+ SignedRational = 10,
+};
+
+static const char* EXIFHeader = "Exif\0\0";
+static const uint32_t EXIFHeaderLength = 6;
+
+/////////////////////////////////////////////////////////////
+// Parse EXIF data, typically found in a JPEG's APP1 segment.
+/////////////////////////////////////////////////////////////
+EXIFData
+EXIFParser::ParseEXIF(const uint8_t* aData, const uint32_t aLength)
+{
+ if (!Initialize(aData, aLength)) {
+ return EXIFData();
+ }
+
+ if (!ParseEXIFHeader()) {
+ return EXIFData();
+ }
+
+ uint32_t offsetIFD;
+ if (!ParseTIFFHeader(offsetIFD)) {
+ return EXIFData();
+ }
+
+ JumpTo(offsetIFD);
+
+ Orientation orientation;
+ if (!ParseIFD0(orientation)) {
+ return EXIFData();
+ }
+
+ // We only care about orientation at this point, so we don't bother with the
+ // other IFDs. If we got this far we're done.
+ return EXIFData(orientation);
+}
+
+/////////////////////////////////////////////////////////
+// Parse the EXIF header. (Section 4.7.2, Figure 30)
+/////////////////////////////////////////////////////////
+bool
+EXIFParser::ParseEXIFHeader()
+{
+ return MatchString(EXIFHeader, EXIFHeaderLength);
+}
+
+/////////////////////////////////////////////////////////
+// Parse the TIFF header. (Section 4.5.2, Table 1)
+/////////////////////////////////////////////////////////
+bool
+EXIFParser::ParseTIFFHeader(uint32_t& aIFD0OffsetOut)
+{
+ // Determine byte order.
+ if (MatchString("MM\0*", 4)) {
+ mByteOrder = ByteOrder::BigEndian;
+ } else if (MatchString("II*\0", 4)) {
+ mByteOrder = ByteOrder::LittleEndian;
+ } else {
+ return false;
+ }
+
+ // Determine offset of the 0th IFD. (It shouldn't be greater than 64k, which
+ // is the maximum size of the entry APP1 segment.)
+ uint32_t ifd0Offset;
+ if (!ReadUInt32(ifd0Offset) || ifd0Offset > 64 * 1024) {
+ return false;
+ }
+
+ // The IFD offset is relative to the beginning of the TIFF header, which
+ // begins after the EXIF header, so we need to increase the offset
+ // appropriately.
+ aIFD0OffsetOut = ifd0Offset + EXIFHeaderLength;
+ return true;
+}
+
+/////////////////////////////////////////////////////////
+// Parse the entries in IFD0. (Section 4.6.2)
+/////////////////////////////////////////////////////////
+bool
+EXIFParser::ParseIFD0(Orientation& aOrientationOut)
+{
+ uint16_t entryCount;
+ if (!ReadUInt16(entryCount)) {
+ return false;
+ }
+
+ for (uint16_t entry = 0 ; entry < entryCount ; ++entry) {
+ // Read the fields of the entry.
+ uint16_t tag;
+ if (!ReadUInt16(tag)) {
+ return false;
+ }
+
+ // Right now, we only care about orientation, so we immediately skip to the
+ // next entry if we find anything else.
+ if (tag != OrientationTag) {
+ Advance(10);
+ continue;
+ }
+
+ uint16_t type;
+ if (!ReadUInt16(type)) {
+ return false;
+ }
+
+ uint32_t count;
+ if (!ReadUInt32(count)) {
+ return false;
+ }
+
+ // We should have an orientation value here; go ahead and parse it.
+ if (!ParseOrientation(type, count, aOrientationOut)) {
+ return false;
+ }
+
+ // Since the orientation is all we care about, we're done.
+ return true;
+ }
+
+ // We didn't find an orientation field in the IFD. That's OK; we assume the
+ // default orientation in that case.
+ aOrientationOut = Orientation();
+ return true;
+}
+
+bool
+EXIFParser::ParseOrientation(uint16_t aType, uint32_t aCount, Orientation& aOut)
+{
+ // Sanity check the type and count.
+ if (aType != ShortType || aCount != 1) {
+ return false;
+ }
+
+ uint16_t value;
+ if (!ReadUInt16(value)) {
+ return false;
+ }
+
+ switch (value) {
+ case 1: aOut = Orientation(Angle::D0, Flip::Unflipped); break;
+ case 2: aOut = Orientation(Angle::D0, Flip::Horizontal); break;
+ case 3: aOut = Orientation(Angle::D180, Flip::Unflipped); break;
+ case 4: aOut = Orientation(Angle::D180, Flip::Horizontal); break;
+ case 5: aOut = Orientation(Angle::D90, Flip::Horizontal); break;
+ case 6: aOut = Orientation(Angle::D90, Flip::Unflipped); break;
+ case 7: aOut = Orientation(Angle::D270, Flip::Horizontal); break;
+ case 8: aOut = Orientation(Angle::D270, Flip::Unflipped); break;
+ default: return false;
+ }
+
+ // This is a 32-bit field, but the orientation value only occupies the first
+ // 16 bits. We need to advance another 16 bits to consume the entire field.
+ Advance(2);
+ return true;
+}
+
+bool
+EXIFParser::Initialize(const uint8_t* aData, const uint32_t aLength)
+{
+ if (aData == nullptr) {
+ return false;
+ }
+
+ // An APP1 segment larger than 64k violates the JPEG standard.
+ if (aLength > 64 * 1024) {
+ return false;
+ }
+
+ mStart = mCurrent = aData;
+ mLength = mRemainingLength = aLength;
+ mByteOrder = ByteOrder::Unknown;
+ return true;
+}
+
+void
+EXIFParser::Advance(const uint32_t aDistance)
+{
+ if (mRemainingLength >= aDistance) {
+ mCurrent += aDistance;
+ mRemainingLength -= aDistance;
+ } else {
+ mCurrent = mStart;
+ mRemainingLength = 0;
+ }
+}
+
+void
+EXIFParser::JumpTo(const uint32_t aOffset)
+{
+ if (mLength >= aOffset) {
+ mCurrent = mStart + aOffset;
+ mRemainingLength = mLength - aOffset;
+ } else {
+ mCurrent = mStart;
+ mRemainingLength = 0;
+ }
+}
+
+bool
+EXIFParser::MatchString(const char* aString, const uint32_t aLength)
+{
+ if (mRemainingLength < aLength) {
+ return false;
+ }
+
+ for (uint32_t i = 0 ; i < aLength ; ++i) {
+ if (mCurrent[i] != aString[i]) {
+ return false;
+ }
+ }
+
+ Advance(aLength);
+ return true;
+}
+
+bool
+EXIFParser::MatchUInt16(const uint16_t aValue)
+{
+ if (mRemainingLength < 2) {
+ return false;
+ }
+
+ bool matched;
+ switch (mByteOrder) {
+ case ByteOrder::LittleEndian:
+ matched = LittleEndian::readUint16(mCurrent) == aValue;
+ break;
+ case ByteOrder::BigEndian:
+ matched = BigEndian::readUint16(mCurrent) == aValue;
+ break;
+ default:
+ NS_NOTREACHED("Should know the byte order by now");
+ matched = false;
+ }
+
+ if (matched) {
+ Advance(2);
+ }
+
+ return matched;
+}
+
+bool
+EXIFParser::ReadUInt16(uint16_t& aValue)
+{
+ if (mRemainingLength < 2) {
+ return false;
+ }
+
+ bool matched = true;
+ switch (mByteOrder) {
+ case ByteOrder::LittleEndian:
+ aValue = LittleEndian::readUint16(mCurrent);
+ break;
+ case ByteOrder::BigEndian:
+ aValue = BigEndian::readUint16(mCurrent);
+ break;
+ default:
+ NS_NOTREACHED("Should know the byte order by now");
+ matched = false;
+ }
+
+ if (matched) {
+ Advance(2);
+ }
+
+ return matched;
+}
+
+bool
+EXIFParser::ReadUInt32(uint32_t& aValue)
+{
+ if (mRemainingLength < 4) {
+ return false;
+ }
+
+ bool matched = true;
+ switch (mByteOrder) {
+ case ByteOrder::LittleEndian:
+ aValue = LittleEndian::readUint32(mCurrent);
+ break;
+ case ByteOrder::BigEndian:
+ aValue = BigEndian::readUint32(mCurrent);
+ break;
+ default:
+ NS_NOTREACHED("Should know the byte order by now");
+ matched = false;
+ }
+
+ if (matched) {
+ Advance(4);
+ }
+
+ return matched;
+}
+
+} // namespace image
+} // namespace mozilla
diff --git a/image/decoders/EXIF.h b/image/decoders/EXIF.h
new file mode 100644
index 000000000..9028f2cf8
--- /dev/null
+++ b/image/decoders/EXIF.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_image_decoders_EXIF_h
+#define mozilla_image_decoders_EXIF_h
+
+#include <stdint.h>
+#include "nsDebug.h"
+
+#include "Orientation.h"
+
+namespace mozilla {
+namespace image {
+
+enum class ByteOrder : uint8_t {
+ Unknown,
+ LittleEndian,
+ BigEndian
+};
+
+struct EXIFData
+{
+ EXIFData() { }
+ explicit EXIFData(Orientation aOrientation) : orientation(aOrientation) { }
+
+ const Orientation orientation;
+};
+
+class EXIFParser
+{
+public:
+ static EXIFData
+ Parse(const uint8_t* aData, const uint32_t aLength)
+ {
+ EXIFParser parser;
+ return parser.ParseEXIF(aData, aLength);
+ }
+
+private:
+ EXIFParser()
+ : mStart(nullptr)
+ , mCurrent(nullptr)
+ , mLength(0)
+ , mRemainingLength(0)
+ , mByteOrder(ByteOrder::Unknown)
+ { }
+
+ EXIFData ParseEXIF(const uint8_t* aData, const uint32_t aLength);
+ bool ParseEXIFHeader();
+ bool ParseTIFFHeader(uint32_t& aIFD0OffsetOut);
+ bool ParseIFD0(Orientation& aOrientationOut);
+ bool ParseOrientation(uint16_t aType, uint32_t aCount, Orientation& aOut);
+
+ bool Initialize(const uint8_t* aData, const uint32_t aLength);
+ void Advance(const uint32_t aDistance);
+ void JumpTo(const uint32_t aOffset);
+
+ bool MatchString(const char* aString, const uint32_t aLength);
+ bool MatchUInt16(const uint16_t aValue);
+ bool ReadUInt16(uint16_t& aOut);
+ bool ReadUInt32(uint32_t& aOut);
+
+ const uint8_t* mStart;
+ const uint8_t* mCurrent;
+ uint32_t mLength;
+ uint32_t mRemainingLength;
+ ByteOrder mByteOrder;
+};
+
+} // namespace image
+} // namespace mozilla
+
+#endif // mozilla_image_decoders_EXIF_h
diff --git a/image/decoders/GIF2.h b/image/decoders/GIF2.h
new file mode 100644
index 000000000..2521667d4
--- /dev/null
+++ b/image/decoders/GIF2.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_image_decoders_GIF2_H
+#define mozilla_image_decoders_GIF2_H
+
+#define MAX_LZW_BITS 12
+#define MAX_BITS 4097 // 2^MAX_LZW_BITS+1
+#define MAX_COLORS 256
+#define MIN_HOLD_SIZE 256
+
+enum { GIF_TRAILER = 0x3B }; // ';'
+enum { GIF_IMAGE_SEPARATOR = 0x2C }; // ','
+enum { GIF_EXTENSION_INTRODUCER = 0x21 }; // '!'
+enum { GIF_GRAPHIC_CONTROL_LABEL = 0xF9 };
+enum { GIF_APPLICATION_EXTENSION_LABEL = 0xFF };
+
+// A GIF decoder's state
+typedef struct gif_struct {
+ // LZW decoder state machine
+ uint8_t* stackp; // Current stack pointer
+ int datasize;
+ int codesize;
+ int codemask;
+ int avail; // Index of next available slot in dictionary
+ int oldcode;
+ uint8_t firstchar;
+ int bits; // Number of unread bits in "datum"
+ int32_t datum; // 32-bit input buffer
+
+ // Output state machine
+ int64_t pixels_remaining; // Pixels remaining to be output.
+
+ // Parameters for image frame currently being decoded
+ int tpixel; // Index of transparent pixel
+ int32_t disposal_method; // Restore to background, leave in place, etc.
+ uint32_t* local_colormap; // Per-image colormap
+ int local_colormap_size; // Size of local colormap array.
+ uint32_t delay_time; // Display time, in milliseconds,
+ // for this image in a multi-image GIF
+
+ // Global (multi-image) state
+ int version; // Either 89 for GIF89 or 87 for GIF87
+ int32_t screen_width; // Logical screen width & height
+ int32_t screen_height;
+ uint8_t global_colormap_depth; // Depth of global colormap array
+ uint16_t global_colormap_count; // Number of colors in global colormap
+ int images_decoded; // Counts images for multi-part GIFs
+ int loop_count; // Netscape specific extension block to control
+ // the number of animation loops a GIF
+ // renders.
+
+ bool is_transparent; // TRUE, if tpixel is valid
+
+ uint16_t prefix[MAX_BITS]; // LZW decoding tables
+ uint32_t global_colormap[MAX_COLORS]; // Default colormap if local not
+ // supplied
+ uint8_t suffix[MAX_BITS]; // LZW decoding tables
+ uint8_t stack[MAX_BITS]; // Base of LZW decoder stack
+
+} gif_struct;
+
+#endif // mozilla_image_decoders_GIF2_H
diff --git a/image/decoders/iccjpeg.c b/image/decoders/iccjpeg.c
new file mode 100644
index 000000000..af014cf6b
--- /dev/null
+++ b/image/decoders/iccjpeg.c
@@ -0,0 +1,195 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * iccjpeg.c
+ *
+ * This file provides code to read and write International Color Consortium
+ * (ICC) device profiles embedded in JFIF JPEG image files. The ICC has
+ * defined a standard format for including such data in JPEG "APP2" markers.
+ * The code given here does not know anything about the internal structure
+ * of the ICC profile data; it just knows how to put the profile data into
+ * a JPEG file being written, or get it back out when reading.
+ *
+ * This code depends on new features added to the IJG JPEG library as of
+ * IJG release 6b; it will not compile or work with older IJG versions.
+ *
+ * NOTE: this code would need surgery to work on 16-bit-int machines
+ * with ICC profiles exceeding 64K bytes in size. If you need to do that,
+ * change all the "unsigned int" variables to "INT32". You'll also need
+ * to find a malloc() replacement that can allocate more than 64K.
+ */
+
+#include "iccjpeg.h"
+#include <stdlib.h> /* define malloc() */
+
+
+/*
+ * Since an ICC profile can be larger than the maximum size of a JPEG marker
+ * (64K), we need provisions to split it into multiple markers. The format
+ * defined by the ICC specifies one or more APP2 markers containing the
+ * following data:
+ * Identifying string ASCII "ICC_PROFILE\0" (12 bytes)
+ * Marker sequence number 1 for first APP2, 2 for next, etc (1 byte)
+ * Number of markers Total number of APP2's used (1 byte)
+ * Profile data (remainder of APP2 data)
+ * Decoders should use the marker sequence numbers to reassemble the profile,
+ * rather than assuming that the APP2 markers appear in the correct sequence.
+ */
+
+#define ICC_MARKER (JPEG_APP0 + 2) /* JPEG marker code for ICC */
+#define ICC_OVERHEAD_LEN 14 /* size of non-profile data in APP2 */
+#define MAX_BYTES_IN_MARKER 65533 /* maximum data len of a JPEG marker */
+#define MAX_DATA_BYTES_IN_MARKER (MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN)
+
+/*
+ * Prepare for reading an ICC profile
+ */
+
+void
+setup_read_icc_profile (j_decompress_ptr cinfo)
+{
+ /* Tell the library to keep any APP2 data it may find */
+ jpeg_save_markers(cinfo, ICC_MARKER, 0xFFFF);
+}
+
+
+/*
+ * Handy subroutine to test whether a saved marker is an ICC profile marker.
+ */
+
+static boolean
+marker_is_icc (jpeg_saved_marker_ptr marker)
+{
+ return
+ marker->marker == ICC_MARKER &&
+ marker->data_length >= ICC_OVERHEAD_LEN &&
+ /* verify the identifying string */
+ GETJOCTET(marker->data[0]) == 0x49 &&
+ GETJOCTET(marker->data[1]) == 0x43 &&
+ GETJOCTET(marker->data[2]) == 0x43 &&
+ GETJOCTET(marker->data[3]) == 0x5F &&
+ GETJOCTET(marker->data[4]) == 0x50 &&
+ GETJOCTET(marker->data[5]) == 0x52 &&
+ GETJOCTET(marker->data[6]) == 0x4F &&
+ GETJOCTET(marker->data[7]) == 0x46 &&
+ GETJOCTET(marker->data[8]) == 0x49 &&
+ GETJOCTET(marker->data[9]) == 0x4C &&
+ GETJOCTET(marker->data[10]) == 0x45 &&
+ GETJOCTET(marker->data[11]) == 0x0;
+}
+
+
+/*
+ * See if there was an ICC profile in the JPEG file being read;
+ * if so, reassemble and return the profile data.
+ *
+ * TRUE is returned if an ICC profile was found, FALSE if not.
+ * If TRUE is returned, *icc_data_ptr is set to point to the
+ * returned data, and *icc_data_len is set to its length.
+ *
+ * IMPORTANT: the data at **icc_data_ptr has been allocated with malloc()
+ * and must be freed by the caller with free() when the caller no longer
+ * needs it. (Alternatively, we could write this routine to use the
+ * IJG library's memory allocator, so that the data would be freed implicitly
+ * at jpeg_finish_decompress() time. But it seems likely that many apps
+ * will prefer to have the data stick around after decompression finishes.)
+ *
+ * NOTE: if the file contains invalid ICC APP2 markers, we just silently
+ * return FALSE. You might want to issue an error message instead.
+ */
+
+boolean
+read_icc_profile (j_decompress_ptr cinfo,
+ JOCTET** icc_data_ptr,
+ unsigned int* icc_data_len)
+{
+ jpeg_saved_marker_ptr marker;
+ int num_markers = 0;
+ int seq_no;
+ JOCTET* icc_data;
+ unsigned int total_length;
+#define MAX_SEQ_NO 255 /* sufficient since marker numbers are bytes */
+ char marker_present[MAX_SEQ_NO+1]; /* 1 if marker found */
+ unsigned int data_length[MAX_SEQ_NO+1]; /* size of profile data in marker */
+ unsigned int data_offset[MAX_SEQ_NO+1]; /* offset for data in marker */
+
+ *icc_data_ptr = NULL; /* avoid confusion if FALSE return */
+ *icc_data_len = 0;
+
+ /* This first pass over the saved markers discovers whether there are
+ * any ICC markers and verifies the consistency of the marker numbering.
+ */
+
+ for (seq_no = 1; seq_no <= MAX_SEQ_NO; seq_no++) {
+ marker_present[seq_no] = 0;
+ }
+
+ for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) {
+ if (marker_is_icc(marker)) {
+ if (num_markers == 0) {
+ num_markers = GETJOCTET(marker->data[13]);
+ } else if (num_markers != GETJOCTET(marker->data[13])) {
+ return FALSE; /* inconsistent num_markers fields */
+ }
+ seq_no = GETJOCTET(marker->data[12]);
+ if (seq_no <= 0 || seq_no > num_markers) {
+ return FALSE; /* bogus sequence number */
+ }
+ if (marker_present[seq_no]) {
+ return FALSE; /* duplicate sequence numbers */
+ }
+ marker_present[seq_no] = 1;
+ data_length[seq_no] = marker->data_length - ICC_OVERHEAD_LEN;
+ }
+ }
+
+ if (num_markers == 0) {
+ return FALSE;
+ }
+
+ /* Check for missing markers, count total space needed,
+ * compute offset of each marker's part of the data.
+ */
+
+ total_length = 0;
+ for (seq_no = 1; seq_no <= num_markers; seq_no++) {
+ if (marker_present[seq_no] == 0) {
+ return FALSE; /* missing sequence number */
+ }
+ data_offset[seq_no] = total_length;
+ total_length += data_length[seq_no];
+ }
+
+ if (total_length <= 0) {
+ return FALSE; /* found only empty markers? */
+ }
+
+ /* Allocate space for assembled data */
+ icc_data = (JOCTET*) malloc(total_length * sizeof(JOCTET));
+ if (icc_data == NULL) {
+ return FALSE; /* oops, out of memory */
+ }
+
+ /* and fill it in */
+ for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) {
+ if (marker_is_icc(marker)) {
+ JOCTET FAR* src_ptr;
+ JOCTET* dst_ptr;
+ unsigned int length;
+ seq_no = GETJOCTET(marker->data[12]);
+ dst_ptr = icc_data + data_offset[seq_no];
+ src_ptr = marker->data + ICC_OVERHEAD_LEN;
+ length = data_length[seq_no];
+ while (length--) {
+ *dst_ptr++ = *src_ptr++;
+ }
+ }
+ }
+
+ *icc_data_ptr = icc_data;
+ *icc_data_len = total_length;
+
+ return TRUE;
+}
diff --git a/image/decoders/iccjpeg.h b/image/decoders/iccjpeg.h
new file mode 100644
index 000000000..474df719c
--- /dev/null
+++ b/image/decoders/iccjpeg.h
@@ -0,0 +1,67 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * iccjpeg.h
+ *
+ * This file provides code to read and write International Color Consortium
+ * (ICC) device profiles embedded in JFIF JPEG image files. The ICC has
+ * defined a standard format for including such data in JPEG "APP2" markers.
+ * The code given here does not know anything about the internal structure
+ * of the ICC profile data; it just knows how to put the profile data into
+ * a JPEG file being written, or get it back out when reading.
+ *
+ * This code depends on new features added to the IJG JPEG library as of
+ * IJG release 6b; it will not compile or work with older IJG versions.
+ *
+ * NOTE: this code would need surgery to work on 16-bit-int machines
+ * with ICC profiles exceeding 64K bytes in size. See iccprofile.c
+ * for details.
+ */
+
+#ifndef mozilla_image_decoders_iccjpeg_h
+#define mozilla_image_decoders_iccjpeg_h
+
+#include <stdio.h> /* needed to define "FILE", "NULL" */
+#include "jpeglib.h"
+
+/*
+ * Reading a JPEG file that may contain an ICC profile requires two steps:
+ *
+ * 1. After jpeg_create_decompress() but before jpeg_read_header(),
+ * call setup_read_icc_profile(). This routine tells the IJG library
+ * to save in memory any APP2 markers it may find in the file.
+ *
+ * 2. After jpeg_read_header(), call read_icc_profile() to find out
+ * whether there was a profile and obtain it if so.
+ */
+
+
+/*
+ * Prepare for reading an ICC profile
+ */
+
+extern void setup_read_icc_profile JPP((j_decompress_ptr cinfo));
+
+
+/*
+ * See if there was an ICC profile in the JPEG file being read;
+ * if so, reassemble and return the profile data.
+ *
+ * TRUE is returned if an ICC profile was found, FALSE if not.
+ * If TRUE is returned, *icc_data_ptr is set to point to the
+ * returned data, and *icc_data_len is set to its length.
+ *
+ * IMPORTANT: the data at **icc_data_ptr has been allocated with malloc()
+ * and must be freed by the caller with free() when the caller no longer
+ * needs it. (Alternatively, we could write this routine to use the
+ * IJG library's memory allocator, so that the data would be freed implicitly
+ * at jpeg_finish_decompress() time. But it seems likely that many apps
+ * will prefer to have the data stick around after decompression finishes.)
+ */
+
+extern boolean read_icc_profile JPP((j_decompress_ptr cinfo,
+ JOCTET** icc_data_ptr,
+ unsigned int* icc_data_len));
+#endif // mozilla_image_decoders_iccjpeg_h
diff --git a/image/decoders/icon/android/moz.build b/image/decoders/icon/android/moz.build
new file mode 100644
index 000000000..5e58ff0b6
--- /dev/null
+++ b/image/decoders/icon/android/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+ 'nsIconChannel.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
diff --git a/image/decoders/icon/android/nsIconChannel.cpp b/image/decoders/icon/android/nsIconChannel.cpp
new file mode 100644
index 000000000..5670bf2f9
--- /dev/null
+++ b/image/decoders/icon/android/nsIconChannel.cpp
@@ -0,0 +1,145 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <stdlib.h>
+#include "mozilla/dom/ContentChild.h"
+#include "nsMimeTypes.h"
+#include "nsIURL.h"
+#include "nsXULAppAPI.h"
+#include "AndroidBridge.h"
+#include "nsIconChannel.h"
+#include "nsIStringStream.h"
+#include "nsNetUtil.h"
+#include "nsComponentManagerUtils.h"
+#include "nsNullPrincipal.h"
+
+NS_IMPL_ISUPPORTS(nsIconChannel,
+ nsIRequest,
+ nsIChannel)
+
+using namespace mozilla;
+using mozilla::dom::ContentChild;
+
+static nsresult
+GetIconForExtension(const nsACString& aFileExt, uint32_t aIconSize,
+ uint8_t* const aBuf)
+{
+ if (!AndroidBridge::Bridge()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ AndroidBridge::Bridge()->GetIconForExtension(aFileExt, aIconSize, aBuf);
+
+ return NS_OK;
+}
+
+static nsresult
+CallRemoteGetIconForExtension(const nsACString& aFileExt, uint32_t aIconSize,
+ uint8_t* const aBuf)
+{
+ NS_ENSURE_TRUE(aBuf != nullptr, NS_ERROR_NULL_POINTER);
+
+ // An array has to be used to get data from remote process
+ InfallibleTArray<uint8_t> bits;
+ uint32_t bufSize = aIconSize * aIconSize * 4;
+
+ if (!ContentChild::GetSingleton()->SendGetIconForExtension(
+ PromiseFlatCString(aFileExt), aIconSize, &bits)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_ASSERTION(bits.Length() == bufSize, "Pixels array is incomplete");
+ if (bits.Length() != bufSize) {
+ return NS_ERROR_FAILURE;
+ }
+
+ memcpy(aBuf, bits.Elements(), bufSize);
+
+ return NS_OK;
+}
+
+static nsresult
+moz_icon_to_channel(nsIURI* aURI, const nsACString& aFileExt,
+ uint32_t aIconSize, nsIChannel** aChannel)
+{
+ NS_ENSURE_TRUE(aIconSize < 256 && aIconSize > 0, NS_ERROR_UNEXPECTED);
+
+ int width = aIconSize;
+ int height = aIconSize;
+
+ // moz-icon data should have two bytes for the size,
+ // then the ARGB pixel values with pre-multiplied Alpha
+ const int channels = 4;
+ long int buf_size = 2 + channels * height * width;
+ uint8_t* const buf = (uint8_t*)moz_xmalloc(buf_size);
+ NS_ENSURE_TRUE(buf, NS_ERROR_OUT_OF_MEMORY);
+ uint8_t* out = buf;
+
+ *(out++) = width;
+ *(out++) = height;
+
+ nsresult rv;
+ if (XRE_IsParentProcess()) {
+ rv = GetIconForExtension(aFileExt, aIconSize, out);
+ } else {
+ rv = CallRemoteGetIconForExtension(aFileExt, aIconSize, out);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Encode the RGBA data
+ const uint8_t* in = out;
+ for (int y = 0; y < height; ++y) {
+ for (int x = 0; x < width; ++x) {
+ uint8_t r = *(in++);
+ uint8_t g = *(in++);
+ uint8_t b = *(in++);
+ uint8_t a = *(in++);
+#define DO_PREMULTIPLY(c_) uint8_t(uint16_t(c_) * uint16_t(a) / uint16_t(255))
+ *(out++) = DO_PREMULTIPLY(b);
+ *(out++) = DO_PREMULTIPLY(g);
+ *(out++) = DO_PREMULTIPLY(r);
+ *(out++) = a;
+#undef DO_PREMULTIPLY
+ }
+ }
+
+ nsCOMPtr<nsIStringInputStream> stream =
+ do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stream->AdoptData((char*)buf, buf_size);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // nsIconProtocolHandler::NewChannel2 will provide the correct loadInfo for
+ // this iconChannel. Use the most restrictive security settings for the
+ // temporary loadInfo to make sure the channel can not be openend.
+ nsCOMPtr<nsIPrincipal> nullPrincipal = nsNullPrincipal::Create();
+ return NS_NewInputStreamChannel(aChannel,
+ aURI,
+ stream,
+ nullPrincipal,
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE,
+ NS_LITERAL_CSTRING(IMAGE_ICON_MS));
+}
+
+nsresult
+nsIconChannel::Init(nsIURI* aURI)
+{
+ nsCOMPtr<nsIMozIconURI> iconURI = do_QueryInterface(aURI);
+ NS_ASSERTION(iconURI, "URI is not an nsIMozIconURI");
+
+ nsAutoCString stockIcon;
+ iconURI->GetStockIcon(stockIcon);
+
+ uint32_t desiredImageSize;
+ iconURI->GetImageSize(&desiredImageSize);
+
+ nsAutoCString iconFileExt;
+ iconURI->GetFileExtension(iconFileExt);
+
+ return moz_icon_to_channel(iconURI, iconFileExt, desiredImageSize,
+ getter_AddRefs(mRealChannel));
+}
diff --git a/image/decoders/icon/android/nsIconChannel.h b/image/decoders/icon/android/nsIconChannel.h
new file mode 100644
index 000000000..be5542998
--- /dev/null
+++ b/image/decoders/icon/android/nsIconChannel.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_image_decoders_icon_android_nsIconChannel_h
+#define mozilla_image_decoders_icon_android_nsIconChannel_h
+
+#include "mozilla/Attributes.h"
+
+#include "nsIChannel.h"
+#include "nsIURI.h"
+#include "nsIIconURI.h"
+#include "nsCOMPtr.h"
+
+/**
+ * This class is the Android implementation of nsIconChannel.
+ * It asks Android for an icon, and creates a new channel for
+ * that file to which all calls will be proxied.
+ */
+class nsIconChannel final : public nsIChannel {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_FORWARD_NSIREQUEST(mRealChannel->)
+ NS_FORWARD_NSICHANNEL(mRealChannel->)
+
+ nsIconChannel() { }
+
+ /**
+ * Called by nsIconProtocolHandler after it creates this channel.
+ * Must be called before calling any other function on this object.
+ * If this method fails, no other function must be called on this object.
+ */
+ nsresult Init(nsIURI* aURI);
+
+ private:
+ ~nsIconChannel() { }
+
+ /**
+ * The channel to the temp icon file (e.g. to /tmp/2qy9wjqw.html).
+ * Will always be non-null after a successful Init.
+ */
+ nsCOMPtr<nsIChannel> mRealChannel;
+};
+
+#endif // mozilla_image_decoders_icon_android_nsIconChannel_h
diff --git a/image/decoders/icon/gtk/moz.build b/image/decoders/icon/gtk/moz.build
new file mode 100644
index 000000000..a8116ca87
--- /dev/null
+++ b/image/decoders/icon/gtk/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+ 'nsIconChannel.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
+
+if CONFIG['MOZ_ENABLE_GNOMEUI']:
+ CXXFLAGS += CONFIG['MOZ_GNOMEUI_CFLAGS']
+else:
+ CXXFLAGS += CONFIG['TK_CFLAGS']
diff --git a/image/decoders/icon/gtk/nsIconChannel.cpp b/image/decoders/icon/gtk/nsIconChannel.cpp
new file mode 100644
index 000000000..b014e4c04
--- /dev/null
+++ b/image/decoders/icon/gtk/nsIconChannel.cpp
@@ -0,0 +1,427 @@
+/* vim:set ts=2 sw=2 sts=2 cin et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIconChannel.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/EndianUtils.h"
+#include <algorithm>
+
+#ifdef MOZ_ENABLE_GIO
+#include <gio/gio.h>
+#endif
+
+#include <gtk/gtk.h>
+
+#include "nsMimeTypes.h"
+#include "nsIMIMEService.h"
+
+#include "nsServiceManagerUtils.h"
+
+#include "nsNetUtil.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIStringStream.h"
+#include "nsServiceManagerUtils.h"
+#include "nsNullPrincipal.h"
+#include "nsIURL.h"
+#include "prlink.h"
+
+NS_IMPL_ISUPPORTS(nsIconChannel,
+ nsIRequest,
+ nsIChannel)
+
+static nsresult
+moz_gdk_pixbuf_to_channel(GdkPixbuf* aPixbuf, nsIURI* aURI,
+ nsIChannel** aChannel)
+{
+ int width = gdk_pixbuf_get_width(aPixbuf);
+ int height = gdk_pixbuf_get_height(aPixbuf);
+ NS_ENSURE_TRUE(height < 256 && width < 256 && height > 0 && width > 0 &&
+ gdk_pixbuf_get_colorspace(aPixbuf) == GDK_COLORSPACE_RGB &&
+ gdk_pixbuf_get_bits_per_sample(aPixbuf) == 8 &&
+ gdk_pixbuf_get_has_alpha(aPixbuf) &&
+ gdk_pixbuf_get_n_channels(aPixbuf) == 4,
+ NS_ERROR_UNEXPECTED);
+
+ const int n_channels = 4;
+ gsize buf_size = 2 + n_channels * height * width;
+ uint8_t* const buf = (uint8_t*)moz_xmalloc(buf_size);
+ NS_ENSURE_TRUE(buf, NS_ERROR_OUT_OF_MEMORY);
+ uint8_t* out = buf;
+
+ *(out++) = width;
+ *(out++) = height;
+
+ const guchar* const pixels = gdk_pixbuf_get_pixels(aPixbuf);
+ int rowextra = gdk_pixbuf_get_rowstride(aPixbuf) - width * n_channels;
+
+ // encode the RGB data and the A data
+ const guchar* in = pixels;
+ for (int y = 0; y < height; ++y, in += rowextra) {
+ for (int x = 0; x < width; ++x) {
+ uint8_t r = *(in++);
+ uint8_t g = *(in++);
+ uint8_t b = *(in++);
+ uint8_t a = *(in++);
+#define DO_PREMULTIPLY(c_) uint8_t(uint16_t(c_) * uint16_t(a) / uint16_t(255))
+#if MOZ_LITTLE_ENDIAN
+ *(out++) = DO_PREMULTIPLY(b);
+ *(out++) = DO_PREMULTIPLY(g);
+ *(out++) = DO_PREMULTIPLY(r);
+ *(out++) = a;
+#else
+ *(out++) = a;
+ *(out++) = DO_PREMULTIPLY(r);
+ *(out++) = DO_PREMULTIPLY(g);
+ *(out++) = DO_PREMULTIPLY(b);
+#endif
+#undef DO_PREMULTIPLY
+ }
+ }
+
+ NS_ASSERTION(out == buf + buf_size, "size miscalculation");
+
+ nsresult rv;
+ nsCOMPtr<nsIStringInputStream> stream =
+ do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
+
+ // Prevent the leaking of buf
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ free(buf);
+ return rv;
+ }
+
+ // stream takes ownership of buf and will free it on destruction.
+ // This function cannot fail.
+ rv = stream->AdoptData((char*)buf, buf_size);
+
+ // If this no longer holds then re-examine buf's lifetime.
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // nsIconProtocolHandler::NewChannel2 will provide the correct loadInfo for
+ // this iconChannel. Use the most restrictive security settings for the
+ // temporary loadInfo to make sure the channel can not be openend.
+ nsCOMPtr<nsIPrincipal> nullPrincipal = nsNullPrincipal::Create();
+ return NS_NewInputStreamChannel(aChannel,
+ aURI,
+ stream,
+ nullPrincipal,
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE,
+ NS_LITERAL_CSTRING(IMAGE_ICON_MS));
+}
+
+static GtkWidget* gProtoWindow = nullptr;
+static GtkWidget* gStockImageWidget = nullptr;
+
+static void
+ensure_stock_image_widget()
+{
+ // Only the style of the GtkImage needs to be used, but the widget is kept
+ // to track dynamic style changes.
+ if (!gProtoWindow) {
+ gProtoWindow = gtk_window_new(GTK_WINDOW_POPUP);
+ GtkWidget* protoLayout = gtk_fixed_new();
+ gtk_container_add(GTK_CONTAINER(gProtoWindow), protoLayout);
+
+ gStockImageWidget = gtk_image_new();
+ gtk_container_add(GTK_CONTAINER(protoLayout), gStockImageWidget);
+
+ gtk_widget_ensure_style(gStockImageWidget);
+ }
+}
+
+static GtkIconSize
+moz_gtk_icon_size(const char* name)
+{
+ if (strcmp(name, "button") == 0) {
+ return GTK_ICON_SIZE_BUTTON;
+ }
+
+ if (strcmp(name, "menu") == 0) {
+ return GTK_ICON_SIZE_MENU;
+ }
+
+ if (strcmp(name, "toolbar") == 0) {
+ return GTK_ICON_SIZE_LARGE_TOOLBAR;
+ }
+
+ if (strcmp(name, "toolbarsmall") == 0) {
+ return GTK_ICON_SIZE_SMALL_TOOLBAR;
+ }
+
+ if (strcmp(name, "dnd") == 0) {
+ return GTK_ICON_SIZE_DND;
+ }
+
+ if (strcmp(name, "dialog") == 0) {
+ return GTK_ICON_SIZE_DIALOG;
+ }
+
+ return GTK_ICON_SIZE_MENU;
+}
+
+#ifdef MOZ_ENABLE_GIO
+static int32_t
+GetIconSize(nsIMozIconURI* aIconURI)
+{
+ nsAutoCString iconSizeString;
+
+ aIconURI->GetIconSize(iconSizeString);
+ if (iconSizeString.IsEmpty()) {
+ uint32_t size;
+ mozilla::DebugOnly<nsresult> rv = aIconURI->GetImageSize(&size);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "GetImageSize failed");
+ return size;
+ } else {
+ int size;
+
+ GtkIconSize icon_size = moz_gtk_icon_size(iconSizeString.get());
+ gtk_icon_size_lookup(icon_size, &size, nullptr);
+ return size;
+ }
+}
+
+/* Scale icon buffer to preferred size */
+static nsresult
+ScaleIconBuf(GdkPixbuf** aBuf, int32_t iconSize)
+{
+ // Scale buffer only if width or height differ from preferred size
+ if (gdk_pixbuf_get_width(*aBuf) != iconSize &&
+ gdk_pixbuf_get_height(*aBuf) != iconSize) {
+ GdkPixbuf* scaled = gdk_pixbuf_scale_simple(*aBuf, iconSize, iconSize,
+ GDK_INTERP_BILINEAR);
+ // replace original buffer by scaled
+ g_object_unref(*aBuf);
+ *aBuf = scaled;
+ if (!scaled) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+nsIconChannel::InitWithGIO(nsIMozIconURI* aIconURI)
+{
+ GIcon *icon = nullptr;
+ nsCOMPtr<nsIURL> fileURI;
+
+ // Read icon content
+ aIconURI->GetIconURL(getter_AddRefs(fileURI));
+
+ // Get icon for file specified by URI
+ if (fileURI) {
+ bool isFile;
+ nsAutoCString spec;
+ fileURI->GetAsciiSpec(spec);
+ if (NS_SUCCEEDED(fileURI->SchemeIs("file", &isFile)) && isFile) {
+ GFile* file = g_file_new_for_uri(spec.get());
+ GFileInfo* fileInfo = g_file_query_info(file,
+ G_FILE_ATTRIBUTE_STANDARD_ICON,
+ G_FILE_QUERY_INFO_NONE,
+ nullptr, nullptr);
+ g_object_unref(file);
+ if (fileInfo) {
+ // icon from g_content_type_get_icon doesn't need unref
+ icon = g_file_info_get_icon(fileInfo);
+ if (icon) {
+ g_object_ref(icon);
+ }
+ g_object_unref(fileInfo);
+ }
+ }
+ }
+
+ // Try to get icon by using MIME type
+ if (!icon) {
+ nsAutoCString type;
+ aIconURI->GetContentType(type);
+ // Try to get MIME type from file extension by using nsIMIMEService
+ if (type.IsEmpty()) {
+ nsCOMPtr<nsIMIMEService> ms(do_GetService("@mozilla.org/mime;1"));
+ if (ms) {
+ nsAutoCString fileExt;
+ aIconURI->GetFileExtension(fileExt);
+ ms->GetTypeFromExtension(fileExt, type);
+ }
+ }
+ char* ctype = nullptr; // character representation of content type
+ if (!type.IsEmpty()) {
+ ctype = g_content_type_from_mime_type(type.get());
+ }
+ if (ctype) {
+ icon = g_content_type_get_icon(ctype);
+ g_free(ctype);
+ }
+ }
+
+ // Get default icon theme
+ GtkIconTheme* iconTheme = gtk_icon_theme_get_default();
+ GtkIconInfo* iconInfo = nullptr;
+ // Get icon size
+ int32_t iconSize = GetIconSize(aIconURI);
+
+ if (icon) {
+ // Use icon and theme to get GtkIconInfo
+ iconInfo = gtk_icon_theme_lookup_by_gicon(iconTheme,
+ icon, iconSize,
+ (GtkIconLookupFlags)0);
+ g_object_unref(icon);
+ }
+
+ if (!iconInfo) {
+ // Mozilla's mimetype lookup failed. Try the "unknown" icon.
+ iconInfo = gtk_icon_theme_lookup_icon(iconTheme,
+ "unknown", iconSize,
+ (GtkIconLookupFlags)0);
+ if (!iconInfo) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ // Create a GdkPixbuf buffer containing icon and scale it
+ GdkPixbuf* buf = gtk_icon_info_load_icon(iconInfo, nullptr);
+ gtk_icon_info_free(iconInfo);
+ if (!buf) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv = ScaleIconBuf(&buf, iconSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = moz_gdk_pixbuf_to_channel(buf, aIconURI,
+ getter_AddRefs(mRealChannel));
+ g_object_unref(buf);
+ return rv;
+}
+#endif // MOZ_ENABLE_GIO
+
+nsresult
+nsIconChannel::Init(nsIURI* aURI)
+{
+ nsCOMPtr<nsIMozIconURI> iconURI = do_QueryInterface(aURI);
+ NS_ASSERTION(iconURI, "URI is not an nsIMozIconURI");
+
+ nsAutoCString stockIcon;
+ iconURI->GetStockIcon(stockIcon);
+ if (stockIcon.IsEmpty()) {
+#ifdef MOZ_ENABLE_GIO
+ return InitWithGIO(iconURI);
+#else
+ return NS_ERROR_NOT_AVAILABLE;
+#endif
+ }
+
+ // Search for stockIcon
+ nsAutoCString iconSizeString;
+ iconURI->GetIconSize(iconSizeString);
+
+ nsAutoCString iconStateString;
+ iconURI->GetIconState(iconStateString);
+
+ GtkIconSize icon_size = moz_gtk_icon_size(iconSizeString.get());
+ GtkStateType state = iconStateString.EqualsLiteral("disabled") ?
+ GTK_STATE_INSENSITIVE : GTK_STATE_NORMAL;
+
+ // First lookup the icon by stock id and text direction.
+ GtkTextDirection direction = GTK_TEXT_DIR_NONE;
+ if (StringEndsWith(stockIcon, NS_LITERAL_CSTRING("-ltr"))) {
+ direction = GTK_TEXT_DIR_LTR;
+ } else if (StringEndsWith(stockIcon, NS_LITERAL_CSTRING("-rtl"))) {
+ direction = GTK_TEXT_DIR_RTL;
+ }
+
+ bool forceDirection = direction != GTK_TEXT_DIR_NONE;
+ nsAutoCString stockID;
+ bool useIconName = false;
+ if (!forceDirection) {
+ direction = gtk_widget_get_default_direction();
+ stockID = stockIcon;
+ } else {
+ // GTK versions < 2.22 use icon names from concatenating stock id with
+ // -(rtl|ltr), which is how the moz-icon stock name is interpreted here.
+ stockID = Substring(stockIcon, 0, stockIcon.Length() - 4);
+ // However, if we lookup bidi icons by the stock name, then GTK versions
+ // >= 2.22 will use a bidi lookup convention that most icon themes do not
+ // yet follow. Therefore, we first check to see if the theme supports the
+ // old icon name as this will have bidi support (if found).
+ GtkIconTheme* icon_theme = gtk_icon_theme_get_default();
+ // Micking what gtk_icon_set_render_icon does with sizes, though it's not
+ // critical as icons will be scaled to suit size. It just means we follow
+ // the same pathes and so share caches.
+ gint width, height;
+ if (gtk_icon_size_lookup(icon_size, &width, &height)) {
+ gint size = std::min(width, height);
+ // We use gtk_icon_theme_lookup_icon() without
+ // GTK_ICON_LOOKUP_USE_BUILTIN instead of gtk_icon_theme_has_icon() so
+ // we don't pick up fallback icons added by distributions for backward
+ // compatibility.
+ GtkIconInfo* icon =
+ gtk_icon_theme_lookup_icon(icon_theme, stockIcon.get(),
+ size, (GtkIconLookupFlags)0);
+ if (icon) {
+ useIconName = true;
+ gtk_icon_info_free(icon);
+ }
+ }
+ }
+
+ ensure_stock_image_widget();
+ GtkStyle* style = gtk_widget_get_style(gStockImageWidget);
+ GtkIconSet* icon_set = nullptr;
+ if (!useIconName) {
+ icon_set = gtk_style_lookup_icon_set(style, stockID.get());
+ }
+
+ if (!icon_set) {
+ // Either we have choosen icon-name lookup for a bidi icon, or stockIcon is
+ // not a stock id so we assume it is an icon name.
+ useIconName = true;
+ // Creating a GtkIconSet is a convenient way to allow the style to
+ // render the icon, possibly with variations suitable for insensitive
+ // states.
+ icon_set = gtk_icon_set_new();
+ GtkIconSource* icon_source = gtk_icon_source_new();
+
+ gtk_icon_source_set_icon_name(icon_source, stockIcon.get());
+ gtk_icon_set_add_source(icon_set, icon_source);
+ gtk_icon_source_free(icon_source);
+ }
+
+ GdkPixbuf* icon =
+ gtk_icon_set_render_icon(icon_set, style, direction, state,
+ icon_size, gStockImageWidget, nullptr);
+ if (useIconName) {
+ gtk_icon_set_unref(icon_set);
+ }
+
+ // According to documentation, gtk_icon_set_render_icon() never returns
+ // nullptr, but it does return nullptr when we have the problem reported
+ // here: https://bugzilla.gnome.org/show_bug.cgi?id=629878#c13
+ if (!icon) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = moz_gdk_pixbuf_to_channel(icon, iconURI,
+ getter_AddRefs(mRealChannel));
+
+ g_object_unref(icon);
+
+ return rv;
+}
+
+void
+nsIconChannel::Shutdown() {
+ if (gProtoWindow) {
+ gtk_widget_destroy(gProtoWindow);
+ gProtoWindow = nullptr;
+ gStockImageWidget = nullptr;
+ }
+}
diff --git a/image/decoders/icon/gtk/nsIconChannel.h b/image/decoders/icon/gtk/nsIconChannel.h
new file mode 100644
index 000000000..5d59272d5
--- /dev/null
+++ b/image/decoders/icon/gtk/nsIconChannel.h
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_image_decoders_icon_gtk_nsIconChannel_h
+#define mozilla_image_decoders_icon_gtk_nsIconChannel_h
+
+#include "mozilla/Attributes.h"
+
+#include "nsIChannel.h"
+#include "nsIStreamListener.h"
+#include "nsIURI.h"
+#include "nsIIconURI.h"
+#include "nsCOMPtr.h"
+
+/// This class is the gnome implementation of nsIconChannel. It basically asks
+/// gtk/gnome for an icon, saves it as a tmp icon, and creates a new channel for
+/// that file to which all calls will be proxied.
+class nsIconChannel final : public nsIChannel
+{
+ public:
+ NS_DECL_ISUPPORTS
+ NS_FORWARD_NSIREQUEST(mRealChannel->)
+ NS_FORWARD_NSICHANNEL(mRealChannel->)
+
+ nsIconChannel() { }
+
+ static void Shutdown();
+
+ /// Called by nsIconProtocolHandler after it creates this channel.
+ /// Must be called before calling any other function on this object.
+ /// If this method fails, no other function must be called on this object.
+ nsresult Init(nsIURI* aURI);
+ private:
+ ~nsIconChannel() { }
+ /// The channel to the temp icon file (e.g. to /tmp/2qy9wjqw.html).
+ /// Will always be non-null after a successful Init.
+ nsCOMPtr<nsIChannel> mRealChannel;
+
+ nsresult InitWithGIO(nsIMozIconURI* aIconURI);
+};
+
+#endif // mozilla_image_decoders_icon_gtk_nsIconChannel_h
diff --git a/image/decoders/icon/mac/moz.build b/image/decoders/icon/mac/moz.build
new file mode 100644
index 000000000..158c326ea
--- /dev/null
+++ b/image/decoders/icon/mac/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+ 'nsIconChannelCocoa.mm',
+]
+
+FINAL_LIBRARY = 'xul'
diff --git a/image/decoders/icon/mac/nsIconChannel.h b/image/decoders/icon/mac/nsIconChannel.h
new file mode 100644
index 000000000..9fef17119
--- /dev/null
+++ b/image/decoders/icon/mac/nsIconChannel.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_image_encoders_icon_mac_nsIconChannel_h
+#define mozilla_image_encoders_icon_mac_nsIconChannel_h
+
+#include "mozilla/Attributes.h"
+
+#include "nsCOMPtr.h"
+#include "nsXPIDLString.h"
+#include "nsIChannel.h"
+#include "nsILoadGroup.h"
+#include "nsILoadInfo.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIInputStreamPump.h"
+#include "nsIStreamListener.h"
+#include "nsIURI.h"
+
+class nsIFile;
+
+class nsIconChannel final : public nsIChannel, public nsIStreamListener
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ nsIconChannel();
+
+ nsresult Init(nsIURI* uri);
+
+protected:
+ virtual ~nsIconChannel();
+
+ nsCOMPtr<nsIURI> mUrl;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsISupports> mOwner;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+
+ nsCOMPtr<nsIInputStreamPump> mPump;
+ nsCOMPtr<nsIStreamListener> mListener;
+
+ nsresult MakeInputStream(nsIInputStream** _retval, bool nonBlocking);
+
+ nsresult ExtractIconInfoFromUrl(nsIFile** aLocalFile,
+ uint32_t* aDesiredImageSize,
+ nsACString& aContentType,
+ nsACString& aFileExtension);
+};
+
+#endif // mozilla_image_encoders_icon_mac_nsIconChannel_h
diff --git a/image/decoders/icon/mac/nsIconChannelCocoa.mm b/image/decoders/icon/mac/nsIconChannelCocoa.mm
new file mode 100644
index 000000000..9c2686cdd
--- /dev/null
+++ b/image/decoders/icon/mac/nsIconChannelCocoa.mm
@@ -0,0 +1,565 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsContentUtils.h"
+#include "nsIconChannel.h"
+#include "mozilla/EndianUtils.h"
+#include "nsIIconURI.h"
+#include "nsIServiceManager.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsXPIDLString.h"
+#include "nsMimeTypes.h"
+#include "nsMemory.h"
+#include "nsIStringStream.h"
+#include "nsIURL.h"
+#include "nsNetCID.h"
+#include "nsIPipe.h"
+#include "nsIOutputStream.h"
+#include "nsIMIMEService.h"
+#include "nsCExternalHandlerService.h"
+#include "nsILocalFileMac.h"
+#include "nsIFileURL.h"
+#include "nsTArray.h"
+#include "nsObjCExceptions.h"
+#include "nsProxyRelease.h"
+#include "nsContentSecurityManager.h"
+
+#include <Cocoa/Cocoa.h>
+
+// nsIconChannel methods
+nsIconChannel::nsIconChannel()
+{
+}
+
+nsIconChannel::~nsIconChannel()
+{
+ if (mLoadInfo) {
+ NS_ReleaseOnMainThread(mLoadInfo.forget());
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsIconChannel,
+ nsIChannel,
+ nsIRequest,
+ nsIRequestObserver,
+ nsIStreamListener)
+
+nsresult
+nsIconChannel::Init(nsIURI* uri)
+{
+ NS_ASSERTION(uri, "no uri");
+ mUrl = uri;
+ mOriginalURI = uri;
+ nsresult rv;
+ mPump = do_CreateInstance(NS_INPUTSTREAMPUMP_CONTRACTID, &rv);
+ return rv;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIRequest methods:
+
+NS_IMETHODIMP
+nsIconChannel::GetName(nsACString& result)
+{
+ return mUrl->GetSpec(result);
+}
+
+NS_IMETHODIMP
+nsIconChannel::IsPending(bool* result)
+{
+ return mPump->IsPending(result);
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetStatus(nsresult* status)
+{
+ return mPump->GetStatus(status);
+}
+
+NS_IMETHODIMP
+nsIconChannel::Cancel(nsresult status)
+{
+ return mPump->Cancel(status);
+}
+
+NS_IMETHODIMP
+nsIconChannel::Suspend(void)
+{
+ return mPump->Suspend();
+}
+
+NS_IMETHODIMP
+nsIconChannel::Resume(void)
+{
+ return mPump->Resume();
+}
+
+// nsIRequestObserver methods
+NS_IMETHODIMP
+nsIconChannel::OnStartRequest(nsIRequest* aRequest,
+ nsISupports* aContext)
+{
+ if (mListener) {
+ return mListener->OnStartRequest(this, aContext);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::OnStopRequest(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsresult aStatus)
+{
+ if (mListener) {
+ mListener->OnStopRequest(this, aContext, aStatus);
+ mListener = nullptr;
+ }
+
+ // Remove from load group
+ if (mLoadGroup) {
+ mLoadGroup->RemoveRequest(this, nullptr, aStatus);
+ }
+
+ return NS_OK;
+}
+
+// nsIStreamListener methods
+NS_IMETHODIMP
+nsIconChannel::OnDataAvailable(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsIInputStream* aStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ if (mListener) {
+ return mListener->OnDataAvailable(this, aContext, aStream, aOffset, aCount);
+ }
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIChannel methods:
+
+NS_IMETHODIMP
+nsIconChannel::GetOriginalURI(nsIURI** aURI)
+{
+ *aURI = mOriginalURI;
+ NS_ADDREF(*aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::SetOriginalURI(nsIURI* aURI)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+ mOriginalURI = aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetURI(nsIURI** aURI)
+{
+ *aURI = mUrl;
+ NS_IF_ADDREF(*aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::Open(nsIInputStream** _retval)
+{
+ return MakeInputStream(_retval, false);
+}
+
+NS_IMETHODIMP
+nsIconChannel::Open2(nsIInputStream** aStream)
+{
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return Open(aStream);
+}
+
+nsresult
+nsIconChannel::ExtractIconInfoFromUrl(nsIFile** aLocalFile,
+ uint32_t* aDesiredImageSize,
+ nsACString& aContentType,
+ nsACString& aFileExtension)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMozIconURI> iconURI (do_QueryInterface(mUrl, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ iconURI->GetImageSize(aDesiredImageSize);
+ iconURI->GetContentType(aContentType);
+ iconURI->GetFileExtension(aFileExtension);
+
+ nsCOMPtr<nsIURL> url;
+ rv = iconURI->GetIconURL(getter_AddRefs(url));
+ if (NS_FAILED(rv) || !url) return NS_OK;
+
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(url, &rv);
+ if (NS_FAILED(rv) || !fileURL) return NS_OK;
+
+ nsCOMPtr<nsIFile> file;
+ rv = fileURL->GetFile(getter_AddRefs(file));
+ if (NS_FAILED(rv) || !file) return NS_OK;
+
+ nsCOMPtr<nsILocalFileMac> localFileMac (do_QueryInterface(file, &rv));
+ if (NS_FAILED(rv) || !localFileMac) return NS_OK;
+
+ *aLocalFile = file;
+ NS_IF_ADDREF(*aLocalFile);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::AsyncOpen(nsIStreamListener* aListener,
+ nsISupports* ctxt)
+{
+ MOZ_ASSERT(!mLoadInfo ||
+ mLoadInfo->GetSecurityMode() == 0 ||
+ mLoadInfo->GetInitialSecurityCheckDone() ||
+ (mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
+ nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())),
+ "security flags in loadInfo but asyncOpen2() not called");
+
+ nsCOMPtr<nsIInputStream> inStream;
+ nsresult rv = MakeInputStream(getter_AddRefs(inStream), true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Init our stream pump
+ rv = mPump->Init(inStream, int64_t(-1), int64_t(-1), 0, 0, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mPump->AsyncRead(this, ctxt);
+ if (NS_SUCCEEDED(rv)) {
+ // Store our real listener
+ mListener = aListener;
+ // Add ourself to the load group, if available
+ if (mLoadGroup) {
+ mLoadGroup->AddRequest(this, nullptr);
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsIconChannel::AsyncOpen2(nsIStreamListener* aListener)
+{
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return AsyncOpen(listener, nullptr);
+}
+
+nsresult
+nsIconChannel::MakeInputStream(nsIInputStream** _retval,
+ bool aNonBlocking)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsXPIDLCString contentType;
+ nsAutoCString fileExt;
+ nsCOMPtr<nsIFile> fileloc; // file we want an icon for
+ uint32_t desiredImageSize;
+ nsresult rv = ExtractIconInfoFromUrl(getter_AddRefs(fileloc),
+ &desiredImageSize, contentType, fileExt);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool fileExists = false;
+ if (fileloc) {
+ // ensure that we DO NOT resolve aliases, very important for file views
+ fileloc->SetFollowLinks(false);
+ fileloc->Exists(&fileExists);
+ }
+
+ NSImage* iconImage = nil;
+
+ // first try to get the icon from the file if it exists
+ if (fileExists) {
+ nsCOMPtr<nsILocalFileMac> localFileMac(do_QueryInterface(fileloc, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CFURLRef macURL;
+ if (NS_SUCCEEDED(localFileMac->GetCFURL(&macURL))) {
+ iconImage = [[NSWorkspace sharedWorkspace]
+ iconForFile:[(NSURL*)macURL path]];
+ ::CFRelease(macURL);
+ }
+ }
+
+ // if we don't have an icon yet try to get one by extension
+ if (!iconImage && !fileExt.IsEmpty()) {
+ NSString* fileExtension = [NSString stringWithUTF8String:fileExt.get()];
+ iconImage = [[NSWorkspace sharedWorkspace] iconForFileType:fileExtension];
+ }
+
+ // If we still don't have an icon, get the generic document icon.
+ if (!iconImage) {
+ iconImage = [[NSWorkspace sharedWorkspace]
+ iconForFileType:NSFileTypeUnknown];
+ }
+
+ if (!iconImage) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // we have an icon now, size it
+ NSRect desiredSizeRect = NSMakeRect(0, 0, desiredImageSize, desiredImageSize);
+ [iconImage setSize:desiredSizeRect.size];
+
+ [iconImage lockFocus];
+ NSBitmapImageRep* bitmapRep = [[[NSBitmapImageRep alloc]
+ initWithFocusedViewRect:desiredSizeRect]
+ autorelease];
+ [iconImage unlockFocus];
+
+ // we expect the following things to be true about our bitmapRep
+ NS_ENSURE_TRUE(![bitmapRep isPlanar] &&
+ // Not necessarily: on a HiDPI-capable system, we'll get
+ // a 2x bitmap
+ // (unsigned int)[bitmapRep bytesPerPlane] ==
+ // desiredImageSize * desiredImageSize * 4 &&
+ [bitmapRep bitsPerPixel] == 32 &&
+ [bitmapRep samplesPerPixel] == 4 &&
+ [bitmapRep hasAlpha] == YES,
+ NS_ERROR_UNEXPECTED);
+
+ // check what size we actually got, and ensure it isn't too big to return
+ uint32_t actualImageSize = [bitmapRep bytesPerRow] / 4;
+ NS_ENSURE_TRUE(actualImageSize < 256, NS_ERROR_UNEXPECTED);
+
+ // now we can validate the amount of data
+ NS_ENSURE_TRUE((unsigned int)[bitmapRep bytesPerPlane] ==
+ actualImageSize * actualImageSize * 4,
+ NS_ERROR_UNEXPECTED);
+
+ // rgba, pre-multiplied data
+ uint8_t* bitmapRepData = (uint8_t*)[bitmapRep bitmapData];
+
+ // create our buffer
+ int32_t bufferCapacity = 2 + [bitmapRep bytesPerPlane];
+ AutoTArray<uint8_t, 3 + 16 * 16 * 5> iconBuffer; // initial size is for
+ // 16x16
+ iconBuffer.SetLength(bufferCapacity);
+
+ uint8_t* iconBufferPtr = iconBuffer.Elements();
+
+ // write header data into buffer
+ *iconBufferPtr++ = actualImageSize;
+ *iconBufferPtr++ = actualImageSize;
+
+ uint32_t dataCount = [bitmapRep bytesPerPlane];
+ uint32_t index = 0;
+ while (index < dataCount) {
+ // get data from the bitmap
+ uint8_t r = bitmapRepData[index++];
+ uint8_t g = bitmapRepData[index++];
+ uint8_t b = bitmapRepData[index++];
+ uint8_t a = bitmapRepData[index++];
+
+ // write data out to our buffer
+ // non-cairo uses native image format, but the A channel is ignored.
+ // cairo uses ARGB (highest to lowest bits)
+#if MOZ_LITTLE_ENDIAN
+ *iconBufferPtr++ = b;
+ *iconBufferPtr++ = g;
+ *iconBufferPtr++ = r;
+ *iconBufferPtr++ = a;
+#else
+ *iconBufferPtr++ = a;
+ *iconBufferPtr++ = r;
+ *iconBufferPtr++ = g;
+ *iconBufferPtr++ = b;
+#endif
+ }
+
+ NS_ASSERTION(iconBufferPtr == iconBuffer.Elements() + bufferCapacity,
+ "buffer size miscalculation");
+
+ // Now, create a pipe and stuff our data into it
+ nsCOMPtr<nsIInputStream> inStream;
+ nsCOMPtr<nsIOutputStream> outStream;
+ rv = NS_NewPipe(getter_AddRefs(inStream), getter_AddRefs(outStream),
+ bufferCapacity, bufferCapacity, aNonBlocking);
+
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t written;
+ rv = outStream->Write((char*)iconBuffer.Elements(), bufferCapacity,
+ &written);
+ if (NS_SUCCEEDED(rv)) {
+ NS_IF_ADDREF(*_retval = inStream);
+ }
+ }
+
+ // Drop notification callbacks to prevent cycles.
+ mCallbacks = nullptr;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetLoadFlags(uint32_t* aLoadAttributes)
+{
+ return mPump->GetLoadFlags(aLoadAttributes);
+}
+
+NS_IMETHODIMP
+nsIconChannel::SetLoadFlags(uint32_t aLoadAttributes)
+{
+ return mPump->SetLoadFlags(aLoadAttributes);
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetContentType(nsACString& aContentType)
+{
+ aContentType.AssignLiteral(IMAGE_ICON_MS);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::SetContentType(const nsACString& aContentType)
+{
+ //It doesn't make sense to set the content-type on this type
+ // of channel...
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetContentCharset(nsACString& aContentCharset)
+{
+ aContentCharset.AssignLiteral(IMAGE_ICON_MS);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::SetContentCharset(const nsACString& aContentCharset)
+{
+ //It doesn't make sense to set the content-type on this type
+ // of channel...
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetContentDisposition(uint32_t* aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsIconChannel::SetContentDisposition(uint32_t aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsIconChannel::
+ GetContentDispositionFilename(nsAString& aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsIconChannel::
+ SetContentDispositionFilename(const nsAString& aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsIconChannel::
+ GetContentDispositionHeader(nsACString& aContentDispositionHeader)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetContentLength(int64_t* aContentLength)
+{
+ *aContentLength = 0;
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsIconChannel::SetContentLength(int64_t aContentLength)
+{
+ NS_NOTREACHED("nsIconChannel::SetContentLength");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetLoadGroup(nsILoadGroup** aLoadGroup)
+{
+ *aLoadGroup = mLoadGroup;
+ NS_IF_ADDREF(*aLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::SetLoadGroup(nsILoadGroup* aLoadGroup)
+{
+ mLoadGroup = aLoadGroup;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetOwner(nsISupports** aOwner)
+{
+ *aOwner = mOwner.get();
+ NS_IF_ADDREF(*aOwner);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::SetOwner(nsISupports* aOwner)
+{
+ mOwner = aOwner;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetLoadInfo(nsILoadInfo** aLoadInfo)
+{
+ NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::SetLoadInfo(nsILoadInfo* aLoadInfo)
+{
+ mLoadInfo = aLoadInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::
+ GetNotificationCallbacks(nsIInterfaceRequestor** aNotificationCallbacks)
+{
+ *aNotificationCallbacks = mCallbacks.get();
+ NS_IF_ADDREF(*aNotificationCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::
+ SetNotificationCallbacks(nsIInterfaceRequestor* aNotificationCallbacks)
+{
+ mCallbacks = aNotificationCallbacks;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetSecurityInfo(nsISupports** aSecurityInfo)
+{
+ *aSecurityInfo = nullptr;
+ return NS_OK;
+}
+
diff --git a/image/decoders/icon/moz.build b/image/decoders/icon/moz.build
new file mode 100644
index 000000000..9c6106fa7
--- /dev/null
+++ b/image/decoders/icon/moz.build
@@ -0,0 +1,32 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+UNIFIED_SOURCES += [
+ 'nsIconModule.cpp',
+ 'nsIconProtocolHandler.cpp',
+ 'nsIconURI.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+platform = None
+
+if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+ platform = 'gtk'
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ platform = 'win'
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ platform = 'mac'
+
+if CONFIG['OS_TARGET'] == 'Android':
+ platform = 'android'
+
+if platform:
+ LOCAL_INCLUDES += [platform]
diff --git a/image/decoders/icon/nsIconModule.cpp b/image/decoders/icon/nsIconModule.cpp
new file mode 100644
index 000000000..36225374d
--- /dev/null
+++ b/image/decoders/icon/nsIconModule.cpp
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ModuleUtils.h"
+#include "nsServiceManagerUtils.h"
+
+#include "nsIconProtocolHandler.h"
+#include "nsIconURI.h"
+#include "nsIconChannel.h"
+
+// objects that just require generic constructors
+//*****************************************************************************
+// Protocol CIDs
+
+#define NS_ICONPROTOCOL_CID { 0xd0f9db12, 0x249c, 0x11d5, \
+ { 0x99, 0x5, 0x0, 0x10, 0x83, 0x1, 0xe, 0x9b } }
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsIconProtocolHandler)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMozIconURI)
+
+NS_DEFINE_NAMED_CID(NS_ICONPROTOCOL_CID);
+NS_DEFINE_NAMED_CID(NS_MOZICONURI_CID);
+
+static const mozilla::Module::CIDEntry kIconCIDs[] = {
+ { &kNS_ICONPROTOCOL_CID, false, nullptr, nsIconProtocolHandlerConstructor },
+ { &kNS_MOZICONURI_CID, false, nullptr, nsMozIconURIConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kIconContracts[] = {
+ { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "moz-icon", &kNS_ICONPROTOCOL_CID },
+ { nullptr }
+};
+
+static const mozilla::Module::CategoryEntry kIconCategories[] = {
+ { nullptr }
+};
+
+static void
+IconDecoderModuleDtor()
+{
+#if (MOZ_WIDGET_GTK == 2)
+ nsIconChannel::Shutdown();
+#endif
+}
+
+static const mozilla::Module kIconModule = {
+ mozilla::Module::kVersion,
+ kIconCIDs,
+ kIconContracts,
+ kIconCategories,
+ nullptr,
+ nullptr,
+ IconDecoderModuleDtor
+};
+
+NSMODULE_DEFN(nsIconDecoderModule) = &kIconModule;
diff --git a/image/decoders/icon/nsIconProtocolHandler.cpp b/image/decoders/icon/nsIconProtocolHandler.cpp
new file mode 100644
index 000000000..03aaa2a14
--- /dev/null
+++ b/image/decoders/icon/nsIconProtocolHandler.cpp
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIconProtocolHandler.h"
+
+#include "nsIconChannel.h"
+#include "nsIconURI.h"
+#include "nsIURL.h"
+#include "nsCRT.h"
+#include "nsCOMPtr.h"
+#include "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+#include "nsNetCID.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+nsIconProtocolHandler::nsIconProtocolHandler()
+{ }
+
+nsIconProtocolHandler::~nsIconProtocolHandler()
+{ }
+
+NS_IMPL_ISUPPORTS(nsIconProtocolHandler, nsIProtocolHandler,
+ nsISupportsWeakReference)
+
+
+///////////////////////////////////////////////////////////////////////////////
+// nsIProtocolHandler methods:
+
+NS_IMETHODIMP
+nsIconProtocolHandler::GetScheme(nsACString& result)
+{
+ result = "moz-icon";
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconProtocolHandler::GetDefaultPort(int32_t* result)
+{
+ *result = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconProtocolHandler::AllowPort(int32_t port,
+ const char* scheme,
+ bool* _retval)
+{
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconProtocolHandler::GetProtocolFlags(uint32_t* result)
+{
+ *result = URI_NORELATIVE | URI_NOAUTH | URI_IS_UI_RESOURCE |
+ URI_IS_LOCAL_RESOURCE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconProtocolHandler::NewURI(const nsACString& aSpec,
+ const char* aOriginCharset, // ignored
+ nsIURI* aBaseURI,
+ nsIURI** result)
+{
+ nsCOMPtr<nsIMozIconURI> uri = new nsMozIconURI();
+ if (!uri) return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = uri->SetSpec(aSpec);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIURL> iconURL;
+ uri->GetIconURL(getter_AddRefs(iconURL));
+ if (iconURL) {
+ uri = new nsNestedMozIconURI();
+ rv = uri->SetSpec(aSpec);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ NS_ADDREF(*result = uri);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconProtocolHandler::NewChannel2(nsIURI* url,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ NS_ENSURE_ARG_POINTER(url);
+ nsIconChannel* channel = new nsIconChannel;
+ if (!channel) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ NS_ADDREF(channel);
+
+ nsresult rv = channel->Init(url);
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(channel);
+ return rv;
+ }
+
+ // set the loadInfo on the new channel
+ rv = channel->SetLoadInfo(aLoadInfo);
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(channel);
+ return rv;
+ }
+
+ *result = channel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconProtocolHandler::NewChannel(nsIURI* url, nsIChannel** result)
+{
+ return NewChannel2(url, nullptr, result);
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/image/decoders/icon/nsIconProtocolHandler.h b/image/decoders/icon/nsIconProtocolHandler.h
new file mode 100644
index 000000000..9653d36f6
--- /dev/null
+++ b/image/decoders/icon/nsIconProtocolHandler.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_image_decoders_icon_nsIconProtocolHandler_h
+#define mozilla_image_decoders_icon_nsIconProtocolHandler_h
+
+#include "nsWeakReference.h"
+#include "nsIProtocolHandler.h"
+
+class nsIconProtocolHandler : public nsIProtocolHandler,
+ public nsSupportsWeakReference
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+
+ // nsIconProtocolHandler methods:
+ nsIconProtocolHandler();
+
+protected:
+ virtual ~nsIconProtocolHandler();
+};
+
+#endif // mozilla_image_decoders_icon_nsIconProtocolHandler_h
diff --git a/image/decoders/icon/nsIconURI.cpp b/image/decoders/icon/nsIconURI.cpp
new file mode 100644
index 000000000..2c2788c8f
--- /dev/null
+++ b/image/decoders/icon/nsIconURI.cpp
@@ -0,0 +1,699 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set sw=2 sts=2 ts=2 et tw=80:
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIconURI.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/Sprintf.h"
+
+#include "nsIIOService.h"
+#include "nsIURL.h"
+#include "nsNetUtil.h"
+#include "plstr.h"
+#include <stdlib.h>
+
+using namespace mozilla;
+using namespace mozilla::ipc;
+
+#define DEFAULT_IMAGE_SIZE 16
+
+#if defined(MAX_PATH)
+#define SANE_FILE_NAME_LEN MAX_PATH
+#elif defined(PATH_MAX)
+#define SANE_FILE_NAME_LEN PATH_MAX
+#else
+#define SANE_FILE_NAME_LEN 1024
+#endif
+
+// helper function for parsing out attributes like size, and contentType
+// from the icon url.
+static void extractAttributeValue(const char* aSearchString,
+ const char* aAttributeName,
+ nsCString& aResult);
+
+static const char* kSizeStrings[] =
+{
+ "button",
+ "toolbar",
+ "toolbarsmall",
+ "menu",
+ "dnd",
+ "dialog"
+};
+
+static const char* kStateStrings[] =
+{
+ "normal",
+ "disabled"
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsMozIconURI::nsMozIconURI()
+ : mSize(DEFAULT_IMAGE_SIZE),
+ mIconSize(-1),
+ mIconState(-1)
+{ }
+
+nsMozIconURI::~nsMozIconURI()
+{ }
+
+NS_IMPL_ISUPPORTS(nsMozIconURI, nsIMozIconURI, nsIURI, nsIIPCSerializableURI)
+
+#define MOZICON_SCHEME "moz-icon:"
+#define MOZICON_SCHEME_LEN (sizeof(MOZICON_SCHEME) - 1)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIURI methods:
+
+NS_IMETHODIMP
+nsMozIconURI::GetSpec(nsACString& aSpec)
+{
+ aSpec = MOZICON_SCHEME;
+
+ if (mIconURL) {
+ nsAutoCString fileIconSpec;
+ nsresult rv = mIconURL->GetSpec(fileIconSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aSpec += fileIconSpec;
+ } else if (!mStockIcon.IsEmpty()) {
+ aSpec += "//stock/";
+ aSpec += mStockIcon;
+ } else {
+ aSpec += "//";
+ aSpec += mFileName;
+ }
+
+ aSpec += "?size=";
+ if (mIconSize >= 0) {
+ aSpec += kSizeStrings[mIconSize];
+ } else {
+ char buf[20];
+ SprintfLiteral(buf, "%d", mSize);
+ aSpec.Append(buf);
+ }
+
+ if (mIconState >= 0) {
+ aSpec += "&state=";
+ aSpec += kStateStrings[mIconState];
+ }
+
+ if (!mContentType.IsEmpty()) {
+ aSpec += "&contentType=";
+ aSpec += mContentType.get();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetSpecIgnoringRef(nsACString& result)
+{
+ return GetSpec(result);
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetHasRef(bool* result)
+{
+ *result = false;
+ return NS_OK;
+}
+
+// takes a string like ?size=32&contentType=text/html and returns a new string
+// containing just the attribute value. i.e you could pass in this string with
+// an attribute name of 'size=', this will return 32
+// Assumption: attribute pairs in the string are separated by '&'.
+void
+extractAttributeValue(const char* aSearchString,
+ const char* aAttributeName,
+ nsCString& aResult)
+{
+ //NS_ENSURE_ARG_POINTER(extractAttributeValue);
+
+ aResult.Truncate();
+
+ if (aSearchString && aAttributeName) {
+ // search the string for attributeName
+ uint32_t attributeNameSize = strlen(aAttributeName);
+ const char* startOfAttribute = PL_strcasestr(aSearchString, aAttributeName);
+ if (startOfAttribute &&
+ ( *(startOfAttribute-1) == '?' || *(startOfAttribute-1) == '&') ) {
+ startOfAttribute += attributeNameSize; // skip over the attributeName
+ // is there something after the attribute name
+ if (*startOfAttribute) {
+ const char* endofAttribute = strchr(startOfAttribute, '&');
+ if (endofAttribute) {
+ aResult.Assign(Substring(startOfAttribute, endofAttribute));
+ } else {
+ aResult.Assign(startOfAttribute);
+ }
+ } // if we have a attribute value
+ } // if we have a attribute name
+ } // if we got non-null search string and attribute name values
+}
+
+NS_IMETHODIMP
+nsMozIconURI::SetSpec(const nsACString& aSpec)
+{
+ // Reset everything to default values.
+ mIconURL = nullptr;
+ mSize = DEFAULT_IMAGE_SIZE;
+ mContentType.Truncate();
+ mFileName.Truncate();
+ mStockIcon.Truncate();
+ mIconSize = -1;
+ mIconState = -1;
+
+ nsAutoCString iconSpec(aSpec);
+ if (!Substring(iconSpec, 0,
+ MOZICON_SCHEME_LEN).EqualsLiteral(MOZICON_SCHEME)) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ int32_t questionMarkPos = iconSpec.Find("?");
+ if (questionMarkPos != -1 &&
+ static_cast<int32_t>(iconSpec.Length()) > (questionMarkPos + 1)) {
+ extractAttributeValue(iconSpec.get(), "contentType=", mContentType);
+
+ nsAutoCString sizeString;
+ extractAttributeValue(iconSpec.get(), "size=", sizeString);
+ if (!sizeString.IsEmpty()) {
+ const char* sizeStr = sizeString.get();
+ for (uint32_t i = 0; i < ArrayLength(kSizeStrings); i++) {
+ if (PL_strcasecmp(sizeStr, kSizeStrings[i]) == 0) {
+ mIconSize = i;
+ break;
+ }
+ }
+
+ int32_t sizeValue = atoi(sizeString.get());
+ if (sizeValue > 0) {
+ mSize = sizeValue;
+ }
+ }
+
+ nsAutoCString stateString;
+ extractAttributeValue(iconSpec.get(), "state=", stateString);
+ if (!stateString.IsEmpty()) {
+ const char* stateStr = stateString.get();
+ for (uint32_t i = 0; i < ArrayLength(kStateStrings); i++) {
+ if (PL_strcasecmp(stateStr, kStateStrings[i]) == 0) {
+ mIconState = i;
+ break;
+ }
+ }
+ }
+ }
+
+ int32_t pathLength = iconSpec.Length() - MOZICON_SCHEME_LEN;
+ if (questionMarkPos != -1) {
+ pathLength = questionMarkPos - MOZICON_SCHEME_LEN;
+ }
+ if (pathLength < 3) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ nsAutoCString iconPath(Substring(iconSpec, MOZICON_SCHEME_LEN, pathLength));
+
+ // Icon URI path can have three forms:
+ // (1) //stock/<icon-identifier>
+ // (2) //<some dummy file with an extension>
+ // (3) a valid URL
+
+ if (!strncmp("//stock/", iconPath.get(), 8)) {
+ mStockIcon.Assign(Substring(iconPath, 8));
+ // An icon identifier must always be specified.
+ if (mStockIcon.IsEmpty()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+ return NS_OK;
+ }
+
+ if (StringBeginsWith(iconPath, NS_LITERAL_CSTRING("//"))) {
+ // Sanity check this supposed dummy file name.
+ if (iconPath.Length() > SANE_FILE_NAME_LEN) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+ iconPath.Cut(0, 2);
+ mFileName.Assign(iconPath);
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIIOService> ioService(do_GetService(NS_IOSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> uri;
+ ioService->NewURI(iconPath, nullptr, nullptr, getter_AddRefs(uri));
+ mIconURL = do_QueryInterface(uri);
+ if (mIconURL) {
+ mFileName.Truncate();
+ } else if (mFileName.IsEmpty()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetPrePath(nsACString& prePath)
+{
+ prePath = MOZICON_SCHEME;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetScheme(nsACString& aScheme)
+{
+ aScheme = "moz-icon";
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::SetScheme(const nsACString& aScheme)
+{
+ // doesn't make sense to set the scheme of a moz-icon URL
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetUsername(nsACString& aUsername)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::SetUsername(const nsACString& aUsername)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetPassword(nsACString& aPassword)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::SetPassword(const nsACString& aPassword)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetUserPass(nsACString& aUserPass)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::SetUserPass(const nsACString& aUserPass)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetHostPort(nsACString& aHostPort)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::SetHostPort(const nsACString& aHostPort)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::SetHostAndPort(const nsACString& aHostPort)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetHost(nsACString& aHost)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::SetHost(const nsACString& aHost)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetPort(int32_t* aPort)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::SetPort(int32_t aPort)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetPath(nsACString& aPath)
+{
+ aPath.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::SetPath(const nsACString& aPath)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetRef(nsACString& aRef)
+{
+ aRef.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::SetRef(const nsACString& aRef)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::Equals(nsIURI* other, bool* result)
+{
+ *result = false;
+ NS_ENSURE_ARG_POINTER(other);
+ NS_PRECONDITION(result, "null pointer");
+
+ nsAutoCString spec1;
+ nsAutoCString spec2;
+
+ nsresult rv = GetSpec(spec1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = other->GetSpec(spec2);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!PL_strcasecmp(spec1.get(), spec2.get())) {
+ *result = true;
+ } else {
+ *result = false;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::EqualsExceptRef(nsIURI* other, bool* result)
+{
+ // GetRef/SetRef not supported by nsMozIconURI, so
+ // EqualsExceptRef() is the same as Equals().
+ return Equals(other, result);
+}
+
+NS_IMETHODIMP
+nsMozIconURI::SchemeIs(const char* aScheme, bool* aEquals)
+{
+ NS_ENSURE_ARG_POINTER(aEquals);
+ if (!aScheme) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *aEquals = PL_strcasecmp("moz-icon", aScheme) ? false : true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::Clone(nsIURI** result)
+{
+ nsCOMPtr<nsIURL> newIconURL;
+ if (mIconURL) {
+ nsCOMPtr<nsIURI> newURI;
+ nsresult rv = mIconURL->Clone(getter_AddRefs(newURI));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ newIconURL = do_QueryInterface(newURI, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ nsMozIconURI* uri = new nsMozIconURI();
+ newIconURL.swap(uri->mIconURL);
+ uri->mSize = mSize;
+ uri->mContentType = mContentType;
+ uri->mFileName = mFileName;
+ uri->mStockIcon = mStockIcon;
+ uri->mIconSize = mIconSize;
+ uri->mIconState = mIconState;
+ NS_ADDREF(*result = uri);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::CloneIgnoringRef(nsIURI** result)
+{
+ // GetRef/SetRef not supported by nsMozIconURI, so
+ // CloneIgnoringRef() is the same as Clone().
+ return Clone(result);
+}
+
+NS_IMETHODIMP
+nsMozIconURI::CloneWithNewRef(const nsACString& newRef, nsIURI** result)
+{
+ // GetRef/SetRef not supported by nsMozIconURI, so
+ // CloneWithNewRef() is the same as Clone().
+ return Clone(result);
+}
+
+
+NS_IMETHODIMP
+nsMozIconURI::Resolve(const nsACString& relativePath, nsACString& result)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetAsciiSpec(nsACString& aSpecA)
+{
+ return GetSpec(aSpecA);
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetAsciiHostPort(nsACString& aHostPortA)
+{
+ return GetHostPort(aHostPortA);
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetAsciiHost(nsACString& aHostA)
+{
+ return GetHost(aHostA);
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetOriginCharset(nsACString& result)
+{
+ result.Truncate();
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIIconUri methods:
+
+NS_IMETHODIMP
+nsMozIconURI::GetIconURL(nsIURL** aFileUrl)
+{
+ *aFileUrl = mIconURL;
+ NS_IF_ADDREF(*aFileUrl);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::SetIconURL(nsIURL* aFileUrl)
+{
+ // this isn't called anywhere, needs to go through SetSpec parsing
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetImageSize(uint32_t* aImageSize)
+ // measured by # of pixels in a row. defaults to 16.
+{
+ *aImageSize = mSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::SetImageSize(uint32_t aImageSize)
+ // measured by # of pixels in a row. defaults to 16.
+{
+ mSize = aImageSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetContentType(nsACString& aContentType)
+{
+ aContentType = mContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::SetContentType(const nsACString& aContentType)
+{
+ mContentType = aContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetFileExtension(nsACString& aFileExtension)
+{
+ // First, try to get the extension from mIconURL if we have one
+ if (mIconURL) {
+ nsAutoCString fileExt;
+ if (NS_SUCCEEDED(mIconURL->GetFileExtension(fileExt))) {
+ if (!fileExt.IsEmpty()) {
+ // unfortunately, this code doesn't give us the required '.' in
+ // front of the extension so we have to do it ourselves.
+ aFileExtension.Assign('.');
+ aFileExtension.Append(fileExt);
+ }
+ }
+ return NS_OK;
+ }
+
+ if (!mFileName.IsEmpty()) {
+ // truncate the extension out of the file path...
+ const char* chFileName = mFileName.get(); // get the underlying buffer
+ const char* fileExt = strrchr(chFileName, '.');
+ if (!fileExt) {
+ return NS_OK;
+ }
+ aFileExtension = fileExt;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetStockIcon(nsACString& aStockIcon)
+{
+ aStockIcon = mStockIcon;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetIconSize(nsACString& aSize)
+{
+ if (mIconSize >= 0) {
+ aSize = kSizeStrings[mIconSize];
+ } else {
+ aSize.Truncate();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetIconState(nsACString& aState)
+{
+ if (mIconState >= 0) {
+ aState = kStateStrings[mIconState];
+ } else {
+ aState.Truncate();
+ }
+ return NS_OK;
+}
+////////////////////////////////////////////////////////////////////////////////
+// nsIIPCSerializableURI methods:
+
+void
+nsMozIconURI::Serialize(URIParams& aParams)
+{
+ IconURIParams params;
+
+ if (mIconURL) {
+ URIParams iconURLParams;
+ SerializeURI(mIconURL, iconURLParams);
+ if (iconURLParams.type() == URIParams::T__None) {
+ // Serialization failed, bail.
+ return;
+ }
+
+ params.uri() = iconURLParams;
+ } else {
+ params.uri() = void_t();
+ }
+
+ params.size() = mSize;
+ params.fileName() = mFileName;
+ params.stockIcon() = mStockIcon;
+ params.iconSize() = mIconSize;
+ params.iconState() = mIconState;
+
+ aParams = params;
+}
+
+bool
+nsMozIconURI::Deserialize(const URIParams& aParams)
+{
+ if (aParams.type() != URIParams::TIconURIParams) {
+ MOZ_ASSERT_UNREACHABLE("Received unknown URI from other process!");
+ return false;
+ }
+
+ const IconURIParams& params = aParams.get_IconURIParams();
+ if (params.uri().type() != OptionalURIParams::Tvoid_t) {
+ nsCOMPtr<nsIURI> uri = DeserializeURI(params.uri().get_URIParams());
+ mIconURL = do_QueryInterface(uri);
+ if (!mIconURL) {
+ MOZ_ASSERT_UNREACHABLE("bad nsIURI passed");
+ return false;
+ }
+ }
+
+ mSize = params.size();
+ mContentType = params.contentType();
+ mFileName = params.fileName();
+ mStockIcon = params.stockIcon();
+ mIconSize = params.iconSize();
+ mIconState = params.iconState();
+
+ return true;
+}
+
+////////////////////////////////////////////////////////////
+// Nested version of nsIconURI
+
+nsNestedMozIconURI::nsNestedMozIconURI()
+{ }
+
+nsNestedMozIconURI::~nsNestedMozIconURI()
+{ }
+
+NS_IMPL_ISUPPORTS_INHERITED(nsNestedMozIconURI, nsMozIconURI, nsINestedURI)
+
+NS_IMETHODIMP
+nsNestedMozIconURI::GetInnerURI(nsIURI** aURI)
+{
+ nsCOMPtr<nsIURI> iconURL = do_QueryInterface(mIconURL);
+ if (iconURL) {
+ iconURL.forget(aURI);
+ } else {
+ *aURI = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNestedMozIconURI::GetInnermostURI(nsIURI** aURI)
+{
+ return NS_ImplGetInnermostURI(this, aURI);
+}
+
diff --git a/image/decoders/icon/nsIconURI.h b/image/decoders/icon/nsIconURI.h
new file mode 100644
index 000000000..1c0310bec
--- /dev/null
+++ b/image/decoders/icon/nsIconURI.h
@@ -0,0 +1,63 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_image_decoders_icon_nsIconURI_h
+#define mozilla_image_decoders_icon_nsIconURI_h
+
+#include "nsIIconURI.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsIIPCSerializableURI.h"
+#include "nsINestedURI.h"
+
+class nsMozIconURI : public nsIMozIconURI
+ , public nsIIPCSerializableURI
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIURI
+ NS_DECL_NSIMOZICONURI
+ NS_DECL_NSIIPCSERIALIZABLEURI
+
+ // nsMozIconURI
+ nsMozIconURI();
+
+protected:
+ virtual ~nsMozIconURI();
+ nsCOMPtr<nsIURL> mIconURL; // a URL that we want the icon for
+ uint32_t mSize; // the # of pixels in a row that we want for this image.
+ // Typically 16, 32, 128, etc.
+ nsCString mContentType; // optional field explicitly specifying the content
+ // type
+ nsCString mFileName; // for if we don't have an actual file path, we're just
+ // given a filename with an extension
+ nsCString mStockIcon;
+ int32_t mIconSize; // -1 if not specified, otherwise index into
+ // kSizeStrings
+ int32_t mIconState; // -1 if not specified, otherwise index into
+ // kStateStrings
+};
+
+// For moz-icon URIs that point to an actual file on disk and are
+// therefore nested URIs
+class nsNestedMozIconURI final : public nsMozIconURI
+ , public nsINestedURI
+{
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_FORWARD_NSIURI(nsMozIconURI::)
+ NS_FORWARD_NSIMOZICONURI(nsMozIconURI::)
+ NS_FORWARD_NSIIPCSERIALIZABLEURI(nsMozIconURI::)
+
+ NS_DECL_NSINESTEDURI
+
+ nsNestedMozIconURI();
+
+protected:
+ virtual ~nsNestedMozIconURI();
+
+};
+
+#endif // mozilla_image_decoders_icon_nsIconURI_h
diff --git a/image/decoders/icon/win/moz.build b/image/decoders/icon/win/moz.build
new file mode 100644
index 000000000..6f763d953
--- /dev/null
+++ b/image/decoders/icon/win/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+ 'nsIconChannel.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
diff --git a/image/decoders/icon/win/nsIconChannel.cpp b/image/decoders/icon/win/nsIconChannel.cpp
new file mode 100644
index 000000000..9ddcbbc48
--- /dev/null
+++ b/image/decoders/icon/win/nsIconChannel.cpp
@@ -0,0 +1,841 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsIconChannel.h"
+#include "nsIIconURI.h"
+#include "nsIServiceManager.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsXPIDLString.h"
+#include "nsReadableUtils.h"
+#include "nsMimeTypes.h"
+#include "nsMemory.h"
+#include "nsIStringStream.h"
+#include "nsIURL.h"
+#include "nsIOutputStream.h"
+#include "nsIPipe.h"
+#include "nsNetCID.h"
+#include "nsIFile.h"
+#include "nsIFileURL.h"
+#include "nsIMIMEService.h"
+#include "nsCExternalHandlerService.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsProxyRelease.h"
+#include "nsContentSecurityManager.h"
+#include "nsContentUtils.h"
+
+#ifdef _WIN32_WINNT
+#undef _WIN32_WINNT
+#endif
+#define _WIN32_WINNT 0x0600
+
+// we need windows.h to read out registry information...
+#include <windows.h>
+#include <shellapi.h>
+#include <shlobj.h>
+#include <objbase.h>
+#include <wchar.h>
+
+using namespace mozilla;
+
+struct ICONFILEHEADER {
+ uint16_t ifhReserved;
+ uint16_t ifhType;
+ uint16_t ifhCount;
+};
+
+struct ICONENTRY {
+ int8_t ieWidth;
+ int8_t ieHeight;
+ uint8_t ieColors;
+ uint8_t ieReserved;
+ uint16_t iePlanes;
+ uint16_t ieBitCount;
+ uint32_t ieSizeImage;
+ uint32_t ieFileOffset;
+};
+
+// Match stock icons with names
+static SHSTOCKICONID
+GetStockIconIDForName(const nsACString& aStockName)
+{
+ return aStockName.EqualsLiteral("uac-shield") ? SIID_SHIELD :
+ SIID_INVALID;
+}
+
+// nsIconChannel methods
+nsIconChannel::nsIconChannel()
+{
+}
+
+nsIconChannel::~nsIconChannel()
+{
+ if (mLoadInfo) {
+ NS_ReleaseOnMainThread(mLoadInfo.forget());
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsIconChannel,
+ nsIChannel,
+ nsIRequest,
+ nsIRequestObserver,
+ nsIStreamListener)
+
+nsresult
+nsIconChannel::Init(nsIURI* uri)
+{
+ NS_ASSERTION(uri, "no uri");
+ mUrl = uri;
+ mOriginalURI = uri;
+ nsresult rv;
+ mPump = do_CreateInstance(NS_INPUTSTREAMPUMP_CONTRACTID, &rv);
+ return rv;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIRequest methods:
+
+NS_IMETHODIMP
+nsIconChannel::GetName(nsACString& result)
+{
+ return mUrl->GetSpec(result);
+}
+
+NS_IMETHODIMP
+nsIconChannel::IsPending(bool* result)
+{
+ return mPump->IsPending(result);
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetStatus(nsresult* status)
+{
+ return mPump->GetStatus(status);
+}
+
+NS_IMETHODIMP
+nsIconChannel::Cancel(nsresult status)
+{
+ return mPump->Cancel(status);
+}
+
+NS_IMETHODIMP
+nsIconChannel::Suspend(void)
+{
+ return mPump->Suspend();
+}
+
+NS_IMETHODIMP
+nsIconChannel::Resume(void)
+{
+ return mPump->Resume();
+}
+NS_IMETHODIMP
+nsIconChannel::GetLoadGroup(nsILoadGroup** aLoadGroup)
+{
+ *aLoadGroup = mLoadGroup;
+ NS_IF_ADDREF(*aLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::SetLoadGroup(nsILoadGroup* aLoadGroup)
+{
+ mLoadGroup = aLoadGroup;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetLoadFlags(uint32_t* aLoadAttributes)
+{
+ return mPump->GetLoadFlags(aLoadAttributes);
+}
+
+NS_IMETHODIMP
+nsIconChannel::SetLoadFlags(uint32_t aLoadAttributes)
+{
+ return mPump->SetLoadFlags(aLoadAttributes);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIChannel methods:
+
+NS_IMETHODIMP
+nsIconChannel::GetOriginalURI(nsIURI** aURI)
+{
+ *aURI = mOriginalURI;
+ NS_ADDREF(*aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::SetOriginalURI(nsIURI* aURI)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+ mOriginalURI = aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetURI(nsIURI** aURI)
+{
+ *aURI = mUrl;
+ NS_IF_ADDREF(*aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::Open(nsIInputStream** _retval)
+{
+ return MakeInputStream(_retval, false);
+}
+
+NS_IMETHODIMP
+nsIconChannel::Open2(nsIInputStream** aStream)
+{
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return Open(aStream);
+}
+
+nsresult
+nsIconChannel::ExtractIconInfoFromUrl(nsIFile** aLocalFile,
+ uint32_t* aDesiredImageSize, nsCString& aContentType,
+ nsCString& aFileExtension)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMozIconURI> iconURI (do_QueryInterface(mUrl, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ iconURI->GetImageSize(aDesiredImageSize);
+ iconURI->GetContentType(aContentType);
+ iconURI->GetFileExtension(aFileExtension);
+
+ nsCOMPtr<nsIURL> url;
+ rv = iconURI->GetIconURL(getter_AddRefs(url));
+ if (NS_FAILED(rv) || !url) return NS_OK;
+
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(url, &rv);
+ if (NS_FAILED(rv) || !fileURL) return NS_OK;
+
+ nsCOMPtr<nsIFile> file;
+ rv = fileURL->GetFile(getter_AddRefs(file));
+ if (NS_FAILED(rv) || !file) return NS_OK;
+
+ return file->Clone(aLocalFile);
+}
+
+NS_IMETHODIMP
+nsIconChannel::AsyncOpen(nsIStreamListener* aListener,
+ nsISupports* ctxt)
+{
+ MOZ_ASSERT(!mLoadInfo ||
+ mLoadInfo->GetSecurityMode() == 0 ||
+ mLoadInfo->GetInitialSecurityCheckDone() ||
+ (mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
+ nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())),
+ "security flags in loadInfo but asyncOpen2() not called");
+
+ nsCOMPtr<nsIInputStream> inStream;
+ nsresult rv = MakeInputStream(getter_AddRefs(inStream), true);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Init our streampump
+ rv = mPump->Init(inStream, int64_t(-1), int64_t(-1), 0, 0, false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = mPump->AsyncRead(this, ctxt);
+ if (NS_SUCCEEDED(rv)) {
+ // Store our real listener
+ mListener = aListener;
+ // Add ourself to the load group, if available
+ if (mLoadGroup) {
+ mLoadGroup->AddRequest(this, nullptr);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsIconChannel::AsyncOpen2(nsIStreamListener* aListener)
+{
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return AsyncOpen(listener, nullptr);
+}
+
+static DWORD
+GetSpecialFolderIcon(nsIFile* aFile, int aFolder,
+ SHFILEINFOW* aSFI, UINT aInfoFlags)
+{
+ DWORD shellResult = 0;
+
+ if (!aFile) {
+ return shellResult;
+ }
+
+ wchar_t fileNativePath[MAX_PATH];
+ nsAutoString fileNativePathStr;
+ aFile->GetPath(fileNativePathStr);
+ ::GetShortPathNameW(fileNativePathStr.get(), fileNativePath,
+ ArrayLength(fileNativePath));
+
+ LPITEMIDLIST idList;
+ HRESULT hr = ::SHGetSpecialFolderLocation(nullptr, aFolder, &idList);
+ if (SUCCEEDED(hr)) {
+ wchar_t specialNativePath[MAX_PATH];
+ ::SHGetPathFromIDListW(idList, specialNativePath);
+ ::GetShortPathNameW(specialNativePath, specialNativePath,
+ ArrayLength(specialNativePath));
+
+ if (!wcsicmp(fileNativePath, specialNativePath)) {
+ aInfoFlags |= (SHGFI_PIDL | SHGFI_SYSICONINDEX);
+ shellResult = ::SHGetFileInfoW((LPCWSTR)(LPCITEMIDLIST)idList, 0,
+ aSFI,
+ sizeof(*aSFI), aInfoFlags);
+ }
+ }
+ CoTaskMemFree(idList);
+ return shellResult;
+}
+
+static UINT
+GetSizeInfoFlag(uint32_t aDesiredImageSize)
+{
+ return
+ (UINT) (aDesiredImageSize > 16 ? SHGFI_SHELLICONSIZE : SHGFI_SMALLICON);
+}
+
+nsresult
+nsIconChannel::GetHIconFromFile(HICON* hIcon)
+{
+ nsXPIDLCString contentType;
+ nsCString fileExt;
+ nsCOMPtr<nsIFile> localFile; // file we want an icon for
+ uint32_t desiredImageSize;
+ nsresult rv = ExtractIconInfoFromUrl(getter_AddRefs(localFile),
+ &desiredImageSize, contentType,
+ fileExt);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if the file exists, we are going to use it's real attributes...
+ // otherwise we only want to use it for it's extension...
+ SHFILEINFOW sfi;
+ UINT infoFlags = SHGFI_ICON;
+
+ bool fileExists = false;
+
+ nsAutoString filePath;
+ CopyASCIItoUTF16(fileExt, filePath);
+ if (localFile) {
+ rv = localFile->Normalize();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ localFile->GetPath(filePath);
+ if (filePath.Length() < 2 || filePath[1] != ':') {
+ return NS_ERROR_MALFORMED_URI; // UNC
+ }
+
+ if (filePath.Last() == ':') {
+ filePath.Append('\\');
+ } else {
+ localFile->Exists(&fileExists);
+ if (!fileExists) {
+ localFile->GetLeafName(filePath);
+ }
+ }
+ }
+
+ if (!fileExists) {
+ infoFlags |= SHGFI_USEFILEATTRIBUTES;
+ }
+
+ infoFlags |= GetSizeInfoFlag(desiredImageSize);
+
+ // if we have a content type... then use it! but for existing files,
+ // we want to show their real icon.
+ if (!fileExists && !contentType.IsEmpty()) {
+ nsCOMPtr<nsIMIMEService> mimeService
+ (do_GetService(NS_MIMESERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString defFileExt;
+ mimeService->GetPrimaryExtension(contentType, fileExt, defFileExt);
+ // If the mime service does not know about this mime type, we show
+ // the generic icon.
+ // In any case, we need to insert a '.' before the extension.
+ filePath = NS_LITERAL_STRING(".") +
+ NS_ConvertUTF8toUTF16(defFileExt);
+ }
+
+ // Is this the "Desktop" folder?
+ DWORD shellResult = GetSpecialFolderIcon(localFile, CSIDL_DESKTOP,
+ &sfi, infoFlags);
+ if (!shellResult) {
+ // Is this the "My Documents" folder?
+ shellResult = GetSpecialFolderIcon(localFile, CSIDL_PERSONAL,
+ &sfi, infoFlags);
+ }
+
+ // There are other "Special Folders" and Namespace entities that we
+ // are not fetching icons for, see:
+ // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/
+ // shellcc/platform/shell/reference/enums/csidl.asp
+ // If we ever need to get them, code to do so would be inserted here.
+
+ // Not a special folder, or something else failed above.
+ if (!shellResult) {
+ shellResult = ::SHGetFileInfoW(filePath.get(),
+ FILE_ATTRIBUTE_ARCHIVE,
+ &sfi, sizeof(sfi), infoFlags);
+ }
+
+ if (shellResult && sfi.hIcon) {
+ *hIcon = sfi.hIcon;
+ } else {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return rv;
+}
+
+nsresult
+nsIconChannel::GetStockHIcon(nsIMozIconURI* aIconURI,
+ HICON* hIcon)
+{
+ nsresult rv = NS_OK;
+
+ // We can only do this on Vista or above
+ HMODULE hShellDLL = ::LoadLibraryW(L"shell32.dll");
+ decltype(SHGetStockIconInfo)* pSHGetStockIconInfo =
+ (decltype(SHGetStockIconInfo)*) ::GetProcAddress(hShellDLL,
+ "SHGetStockIconInfo");
+
+ if (pSHGetStockIconInfo) {
+ uint32_t desiredImageSize;
+ aIconURI->GetImageSize(&desiredImageSize);
+ nsAutoCString stockIcon;
+ aIconURI->GetStockIcon(stockIcon);
+
+ SHSTOCKICONID stockIconID = GetStockIconIDForName(stockIcon);
+ if (stockIconID == SIID_INVALID) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ UINT infoFlags = SHGSI_ICON;
+ infoFlags |= GetSizeInfoFlag(desiredImageSize);
+
+ SHSTOCKICONINFO sii = {0};
+ sii.cbSize = sizeof(sii);
+ HRESULT hr = pSHGetStockIconInfo(stockIconID, infoFlags, &sii);
+
+ if (SUCCEEDED(hr)) {
+ *hIcon = sii.hIcon;
+ } else {
+ rv = NS_ERROR_FAILURE;
+ }
+ } else {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (hShellDLL) {
+ ::FreeLibrary(hShellDLL);
+ }
+
+ return rv;
+}
+
+// Given a BITMAPINFOHEADER, returns the size of the color table.
+static int
+GetColorTableSize(BITMAPINFOHEADER* aHeader)
+{
+ int colorTableSize = -1;
+
+ // http://msdn.microsoft.com/en-us/library/dd183376%28v=VS.85%29.aspx
+ switch (aHeader->biBitCount) {
+ case 0:
+ colorTableSize = 0;
+ break;
+ case 1:
+ colorTableSize = 2 * sizeof(RGBQUAD);
+ break;
+ case 4:
+ case 8: {
+ // The maximum possible size for the color table is 2**bpp, so check for
+ // that and fail if we're not in those bounds
+ unsigned int maxEntries = 1 << (aHeader->biBitCount);
+ if (aHeader->biClrUsed > 0 && aHeader->biClrUsed <= maxEntries) {
+ colorTableSize = aHeader->biClrUsed * sizeof(RGBQUAD);
+ } else if (aHeader->biClrUsed == 0) {
+ colorTableSize = maxEntries * sizeof(RGBQUAD);
+ }
+ break;
+ }
+ case 16:
+ case 32:
+ // If we have BI_BITFIELDS compression, we would normally need 3 DWORDS for
+ // the bitfields mask which would be stored in the color table; However,
+ // we instead force the bitmap to request data of type BI_RGB so the color
+ // table should be of size 0.
+ // Setting aHeader->biCompression = BI_RGB forces the later call to
+ // GetDIBits to return to us BI_RGB data.
+ if (aHeader->biCompression == BI_BITFIELDS) {
+ aHeader->biCompression = BI_RGB;
+ }
+ colorTableSize = 0;
+ break;
+ case 24:
+ colorTableSize = 0;
+ break;
+ }
+
+ if (colorTableSize < 0) {
+ NS_WARNING("Unable to figure out the color table size for this bitmap");
+ }
+
+ return colorTableSize;
+}
+
+// Given a header and a size, creates a freshly allocated BITMAPINFO structure.
+// It is the caller's responsibility to null-check and delete the structure.
+static BITMAPINFO*
+CreateBitmapInfo(BITMAPINFOHEADER* aHeader, size_t aColorTableSize)
+{
+ BITMAPINFO* bmi = (BITMAPINFO*) ::operator new(sizeof(BITMAPINFOHEADER) +
+ aColorTableSize,
+ mozilla::fallible);
+ if (bmi) {
+ memcpy(bmi, aHeader, sizeof(BITMAPINFOHEADER));
+ memset(bmi->bmiColors, 0, aColorTableSize);
+ }
+ return bmi;
+}
+
+nsresult
+nsIconChannel::MakeInputStream(nsIInputStream** _retval, bool aNonBlocking)
+{
+ // Check whether the icon requested's a file icon or a stock icon
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+
+ // GetDIBits does not exist on windows mobile.
+ HICON hIcon = nullptr;
+
+ nsCOMPtr<nsIMozIconURI> iconURI(do_QueryInterface(mUrl, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString stockIcon;
+ iconURI->GetStockIcon(stockIcon);
+ if (!stockIcon.IsEmpty()) {
+ rv = GetStockHIcon(iconURI, &hIcon);
+ } else {
+ rv = GetHIconFromFile(&hIcon);
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (hIcon) {
+ // we got a handle to an icon. Now we want to get a bitmap for the icon
+ // using GetIconInfo....
+ ICONINFO iconInfo;
+ if (GetIconInfo(hIcon, &iconInfo)) {
+ // we got the bitmaps, first find out their size
+ HDC hDC = CreateCompatibleDC(nullptr); // get a device context for
+ // the screen.
+ BITMAPINFOHEADER maskHeader = {sizeof(BITMAPINFOHEADER)};
+ BITMAPINFOHEADER colorHeader = {sizeof(BITMAPINFOHEADER)};
+ int colorTableSize, maskTableSize;
+ if (GetDIBits(hDC, iconInfo.hbmMask, 0, 0, nullptr,
+ (BITMAPINFO*)&maskHeader, DIB_RGB_COLORS) &&
+ GetDIBits(hDC, iconInfo.hbmColor, 0, 0, nullptr,
+ (BITMAPINFO*)&colorHeader, DIB_RGB_COLORS) &&
+ maskHeader.biHeight == colorHeader.biHeight &&
+ maskHeader.biWidth == colorHeader.biWidth &&
+ colorHeader.biBitCount > 8 &&
+ colorHeader.biSizeImage > 0 &&
+ colorHeader.biWidth >= 0 && colorHeader.biWidth <= 255 &&
+ colorHeader.biHeight >= 0 && colorHeader.biHeight <= 255 &&
+ maskHeader.biSizeImage > 0 &&
+ (colorTableSize = GetColorTableSize(&colorHeader)) >= 0 &&
+ (maskTableSize = GetColorTableSize(&maskHeader)) >= 0) {
+ uint32_t iconSize = sizeof(ICONFILEHEADER) +
+ sizeof(ICONENTRY) +
+ sizeof(BITMAPINFOHEADER) +
+ colorHeader.biSizeImage +
+ maskHeader.biSizeImage;
+
+ UniquePtr<char[]> buffer = MakeUnique<char[]>(iconSize);
+ if (!buffer) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ } else {
+ char* whereTo = buffer.get();
+ int howMuch;
+
+ // the data starts with an icon file header
+ ICONFILEHEADER iconHeader;
+ iconHeader.ifhReserved = 0;
+ iconHeader.ifhType = 1;
+ iconHeader.ifhCount = 1;
+ howMuch = sizeof(ICONFILEHEADER);
+ memcpy(whereTo, &iconHeader, howMuch);
+ whereTo += howMuch;
+
+ // followed by the single icon entry
+ ICONENTRY iconEntry;
+ iconEntry.ieWidth = static_cast<int8_t>(colorHeader.biWidth);
+ iconEntry.ieHeight = static_cast<int8_t>(colorHeader.biHeight);
+ iconEntry.ieColors = 0;
+ iconEntry.ieReserved = 0;
+ iconEntry.iePlanes = 1;
+ iconEntry.ieBitCount = colorHeader.biBitCount;
+ iconEntry.ieSizeImage = sizeof(BITMAPINFOHEADER) +
+ colorHeader.biSizeImage +
+ maskHeader.biSizeImage;
+ iconEntry.ieFileOffset = sizeof(ICONFILEHEADER) + sizeof(ICONENTRY);
+ howMuch = sizeof(ICONENTRY);
+ memcpy(whereTo, &iconEntry, howMuch);
+ whereTo += howMuch;
+
+ // followed by the bitmap info header
+ // (doubling the height because icons have two bitmaps)
+ colorHeader.biHeight *= 2;
+ colorHeader.biSizeImage += maskHeader.biSizeImage;
+ howMuch = sizeof(BITMAPINFOHEADER);
+ memcpy(whereTo, &colorHeader, howMuch);
+ whereTo += howMuch;
+ colorHeader.biHeight /= 2;
+ colorHeader.biSizeImage -= maskHeader.biSizeImage;
+
+ // followed by the XOR bitmap data (colorHeader)
+ // (you'd expect the color table to come here, but it apparently
+ // doesn't)
+ BITMAPINFO* colorInfo = CreateBitmapInfo(&colorHeader,
+ colorTableSize);
+ if (colorInfo && GetDIBits(hDC, iconInfo.hbmColor, 0,
+ colorHeader.biHeight, whereTo, colorInfo,
+ DIB_RGB_COLORS)) {
+ whereTo += colorHeader.biSizeImage;
+
+ // and finally the AND bitmap data (maskHeader)
+ BITMAPINFO* maskInfo = CreateBitmapInfo(&maskHeader, maskTableSize);
+ if (maskInfo && GetDIBits(hDC, iconInfo.hbmMask, 0,
+ maskHeader.biHeight, whereTo, maskInfo,
+ DIB_RGB_COLORS)) {
+ // Now, create a pipe and stuff our data into it
+ nsCOMPtr<nsIInputStream> inStream;
+ nsCOMPtr<nsIOutputStream> outStream;
+ rv = NS_NewPipe(getter_AddRefs(inStream),
+ getter_AddRefs(outStream),
+ iconSize, iconSize, aNonBlocking);
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t written;
+ rv = outStream->Write(buffer.get(), iconSize, &written);
+ if (NS_SUCCEEDED(rv)) {
+ NS_ADDREF(*_retval = inStream);
+ }
+ }
+
+ } // if we got bitmap bits
+ delete maskInfo;
+ } // if we got mask bits
+ delete colorInfo;
+ } // if we allocated the buffer
+ } // if we got mask size
+
+ DeleteDC(hDC);
+ DeleteObject(iconInfo.hbmColor);
+ DeleteObject(iconInfo.hbmMask);
+ } // if we got icon info
+ DestroyIcon(hIcon);
+ } // if we got an hIcon
+
+ // If we didn't make a stream, then fail.
+ if (!*_retval && NS_SUCCEEDED(rv)) {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetContentType(nsACString& aContentType)
+{
+ aContentType.AssignLiteral(IMAGE_ICO);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::SetContentType(const nsACString& aContentType)
+{
+ // It doesn't make sense to set the content-type on this type
+ // of channel...
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsIconChannel::GetContentCharset(nsACString& aContentCharset)
+{
+ aContentCharset.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::SetContentCharset(const nsACString& aContentCharset)
+{
+ // It doesn't make sense to set the content-charset on this type
+ // of channel...
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetContentDisposition(uint32_t* aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsIconChannel::SetContentDisposition(uint32_t aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsIconChannel::
+ GetContentDispositionFilename(nsAString& aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsIconChannel::
+ SetContentDispositionFilename(const nsAString& aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsIconChannel::
+ GetContentDispositionHeader(nsACString& aContentDispositionHeader)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetContentLength(int64_t* aContentLength)
+{
+ *aContentLength = 0;
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsIconChannel::SetContentLength(int64_t aContentLength)
+{
+ NS_NOTREACHED("nsIconChannel::SetContentLength");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetOwner(nsISupports** aOwner)
+{
+ *aOwner = mOwner.get();
+ NS_IF_ADDREF(*aOwner);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::SetOwner(nsISupports* aOwner)
+{
+ mOwner = aOwner;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetLoadInfo(nsILoadInfo** aLoadInfo)
+{
+ NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::SetLoadInfo(nsILoadInfo* aLoadInfo)
+{
+ mLoadInfo = aLoadInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::
+ GetNotificationCallbacks(nsIInterfaceRequestor** aNotificationCallbacks)
+{
+ *aNotificationCallbacks = mCallbacks.get();
+ NS_IF_ADDREF(*aNotificationCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::
+ SetNotificationCallbacks(nsIInterfaceRequestor* aNotificationCallbacks)
+{
+ mCallbacks = aNotificationCallbacks;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetSecurityInfo(nsISupports** aSecurityInfo)
+{
+ *aSecurityInfo = nullptr;
+ return NS_OK;
+}
+
+// nsIRequestObserver methods
+NS_IMETHODIMP nsIconChannel::OnStartRequest(nsIRequest* aRequest,
+ nsISupports* aContext)
+{
+ if (mListener) {
+ return mListener->OnStartRequest(this, aContext);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::OnStopRequest(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsresult aStatus)
+{
+ if (mListener) {
+ mListener->OnStopRequest(this, aContext, aStatus);
+ mListener = nullptr;
+ }
+
+ // Remove from load group
+ if (mLoadGroup) {
+ mLoadGroup->RemoveRequest(this, nullptr, aStatus);
+ }
+
+ // Drop notification callbacks to prevent cycles.
+ mCallbacks = nullptr;
+
+ return NS_OK;
+}
+
+// nsIStreamListener methods
+NS_IMETHODIMP
+nsIconChannel::OnDataAvailable(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsIInputStream* aStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ if (mListener) {
+ return mListener->OnDataAvailable(this, aContext, aStream, aOffset, aCount);
+ }
+ return NS_OK;
+}
diff --git a/image/decoders/icon/win/nsIconChannel.h b/image/decoders/icon/win/nsIconChannel.h
new file mode 100644
index 000000000..94df5a52e
--- /dev/null
+++ b/image/decoders/icon/win/nsIconChannel.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_image_encoders_icon_win_nsIconChannel_h
+#define mozilla_image_encoders_icon_win_nsIconChannel_h
+
+#include "mozilla/Attributes.h"
+
+#include "nsCOMPtr.h"
+#include "nsXPIDLString.h"
+#include "nsIChannel.h"
+#include "nsILoadGroup.h"
+#include "nsILoadInfo.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIURI.h"
+#include "nsIInputStreamPump.h"
+#include "nsIStreamListener.h"
+#include "nsIIconURI.h"
+
+#include <windows.h>
+
+class nsIFile;
+
+class nsIconChannel final : public nsIChannel, public nsIStreamListener
+{
+ ~nsIconChannel();
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ nsIconChannel();
+
+ nsresult Init(nsIURI* uri);
+
+protected:
+ nsCOMPtr<nsIURI> mUrl;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsISupports> mOwner;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+
+ nsCOMPtr<nsIInputStreamPump> mPump;
+ nsCOMPtr<nsIStreamListener> mListener;
+
+ nsresult ExtractIconInfoFromUrl(nsIFile** aLocalFile,
+ uint32_t* aDesiredImageSize,
+ nsCString& aContentType,
+ nsCString& aFileExtension);
+ nsresult GetHIconFromFile(HICON* hIcon);
+ nsresult MakeInputStream(nsIInputStream** _retval, bool nonBlocking);
+
+ // Functions specific to Vista and above
+protected:
+ nsresult GetStockHIcon(nsIMozIconURI* aIconURI, HICON* hIcon);
+};
+
+#endif // mozilla_image_encoders_icon_win_nsIconChannel_h
diff --git a/image/decoders/moz.build b/image/decoders/moz.build
new file mode 100644
index 000000000..775c3eaa5
--- /dev/null
+++ b/image/decoders/moz.build
@@ -0,0 +1,47 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+toolkit = CONFIG['MOZ_WIDGET_TOOLKIT']
+
+# The Icon Channel stuff really shouldn't live in decoders/icon, but we'll
+# fix that another time.
+if 'gtk' in toolkit:
+ DIRS += ['icon/gtk', 'icon']
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ DIRS += ['icon/win', 'icon']
+
+if toolkit == 'cocoa':
+ DIRS += ['icon/mac', 'icon']
+elif toolkit == 'android':
+ DIRS += ['icon/android', 'icon']
+
+UNIFIED_SOURCES += [
+ 'EXIF.cpp',
+ 'iccjpeg.c',
+ 'nsBMPDecoder.cpp',
+ 'nsGIFDecoder2.cpp',
+ 'nsICODecoder.cpp',
+ 'nsIconDecoder.cpp',
+ 'nsJPEGDecoder.cpp',
+ 'nsPNGDecoder.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+LOCAL_INCLUDES += [
+ # Access to Skia headers for Downscaler.
+ '/gfx/2d',
+ # Decoders need ImageLib headers.
+ '/image',
+]
+
+LOCAL_INCLUDES += CONFIG['SKIA_INCLUDES']
+
+FINAL_LIBRARY = 'xul'
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/image/decoders/nsBMPDecoder.cpp b/image/decoders/nsBMPDecoder.cpp
new file mode 100644
index 000000000..1f0449e4e
--- /dev/null
+++ b/image/decoders/nsBMPDecoder.cpp
@@ -0,0 +1,1067 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This is a cross-platform BMP Decoder, which should work everywhere,
+// including big-endian machines like the PowerPC.
+//
+// BMP is a format that has been extended multiple times. To understand the
+// decoder you need to understand this history. The summary of the history
+// below was determined from the following documents.
+//
+// - http://www.fileformat.info/format/bmp/egff.htm
+// - http://www.fileformat.info/format/os2bmp/egff.htm
+// - http://fileformats.archiveteam.org/wiki/BMP
+// - http://fileformats.archiveteam.org/wiki/OS/2_BMP
+// - https://en.wikipedia.org/wiki/BMP_file_format
+// - https://upload.wikimedia.org/wikipedia/commons/c/c4/BMPfileFormat.png
+//
+// WINDOWS VERSIONS OF THE BMP FORMAT
+// ----------------------------------
+// WinBMPv1.
+// - This version is no longer used and can be ignored.
+//
+// WinBMPv2.
+// - First is a 14 byte file header that includes: the magic number ("BM"),
+// file size, and offset to the pixel data (|mDataOffset|).
+// - Next is a 12 byte info header which includes: the info header size
+// (mBIHSize), width, height, number of color planes, and bits-per-pixel
+// (|mBpp|) which must be 1, 4, 8 or 24.
+// - Next is the semi-optional color table, which has length 2^|mBpp| and has 3
+// bytes per value (BGR). The color table is required if |mBpp| is 1, 4, or 8.
+// - Next is an optional gap.
+// - Next is the pixel data, which is pointed to by |mDataOffset|.
+//
+// WinBMPv3. This is the most widely used version.
+// - It changed the info header to 40 bytes by taking the WinBMPv2 info
+// header, enlargening its width and height fields, and adding more fields
+// including: a compression type (|mCompression|) and number of colors
+// (|mNumColors|).
+// - The semi-optional color table is now 4 bytes per value (BGR0), and its
+// length is |mNumColors|, or 2^|mBpp| if |mNumColors| is zero.
+// - |mCompression| can be RGB (i.e. no compression), RLE4 (if |mBpp|==4) or
+// RLE8 (if |mBpp|==8) values.
+//
+// WinBMPv3-NT. A variant of WinBMPv3.
+// - It did not change the info header layout from WinBMPv3.
+// - |mBpp| can now be 16 or 32, in which case |mCompression| can be RGB or the
+// new BITFIELDS value; in the latter case an additional 12 bytes of color
+// bitfields follow the info header.
+//
+// WinBMPv4.
+// - It extended the info header to 108 bytes, including the 12 bytes of color
+// mask data from WinBMPv3-NT, plus alpha mask data, and also color-space and
+// gamma correction fields.
+//
+// WinBMPv5.
+// - It extended the info header to 124 bytes, adding color profile data.
+// - It also added an optional color profile table after the pixel data (and
+// another optional gap).
+//
+// WinBMPv3-ICO. This is a variant of WinBMPv3.
+// - It's the BMP format used for BMP images within ICO files.
+// - The only difference with WinBMPv3 is that if an image is 32bpp and has no
+// compression, then instead of treating the pixel data as 0RGB it is treated
+// as ARGB, but only if one or more of the A values are non-zero.
+//
+// OS/2 VERSIONS OF THE BMP FORMAT
+// -------------------------------
+// OS2-BMPv1.
+// - Almost identical to WinBMPv2; the differences are basically ignorable.
+//
+// OS2-BMPv2.
+// - Similar to WinBMPv3.
+// - The info header is 64 bytes but can be reduced to as little as 16; any
+// omitted fields are treated as zero. The first 40 bytes of these fields are
+// nearly identical to the WinBMPv3 info header; the remaining 24 bytes are
+// different.
+// - Also adds compression types "Huffman 1D" and "RLE24", which we don't
+// support.
+// - We treat OS2-BMPv2 files as if they are WinBMPv3 (i.e. ignore the extra 24
+// bytes in the info header), which in practice is good enough.
+
+#include "ImageLogging.h"
+#include "nsBMPDecoder.h"
+
+#include <stdlib.h>
+
+#include "mozilla/Attributes.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/Likely.h"
+
+#include "nsIInputStream.h"
+#include "RasterImage.h"
+#include <algorithm>
+
+using namespace mozilla::gfx;
+
+namespace mozilla {
+namespace image {
+namespace bmp {
+
+struct Compression {
+ enum {
+ RGB = 0,
+ RLE8 = 1,
+ RLE4 = 2,
+ BITFIELDS = 3
+ };
+};
+
+// RLE escape codes and constants.
+struct RLE {
+ enum {
+ ESCAPE = 0,
+ ESCAPE_EOL = 0,
+ ESCAPE_EOF = 1,
+ ESCAPE_DELTA = 2,
+
+ SEGMENT_LENGTH = 2,
+ DELTA_LENGTH = 2
+ };
+};
+
+} // namespace bmp
+
+using namespace bmp;
+
+/// Sets the pixel data in aDecoded to the given values.
+/// @param aDecoded pointer to pixel to be set, will be incremented to point to
+/// the next pixel.
+static void
+SetPixel(uint32_t*& aDecoded, uint8_t aRed, uint8_t aGreen,
+ uint8_t aBlue, uint8_t aAlpha = 0xFF)
+{
+ *aDecoded++ = gfxPackedPixel(aAlpha, aRed, aGreen, aBlue);
+}
+
+static void
+SetPixel(uint32_t*& aDecoded, uint8_t idx,
+ const UniquePtr<ColorTableEntry[]>& aColors)
+{
+ SetPixel(aDecoded,
+ aColors[idx].mRed, aColors[idx].mGreen, aColors[idx].mBlue);
+}
+
+/// Sets two (or one if aCount = 1) pixels
+/// @param aDecoded where the data is stored. Will be moved 4 resp 8 bytes
+/// depending on whether one or two pixels are written.
+/// @param aData The values for the two pixels
+/// @param aCount Current count. Is decremented by one or two.
+static void
+Set4BitPixel(uint32_t*& aDecoded, uint8_t aData, uint32_t& aCount,
+ const UniquePtr<ColorTableEntry[]>& aColors)
+{
+ uint8_t idx = aData >> 4;
+ SetPixel(aDecoded, idx, aColors);
+ if (--aCount > 0) {
+ idx = aData & 0xF;
+ SetPixel(aDecoded, idx, aColors);
+ --aCount;
+ }
+}
+
+static mozilla::LazyLogModule sBMPLog("BMPDecoder");
+
+// The length of the mBIHSize field in the info header.
+static const uint32_t BIHSIZE_FIELD_LENGTH = 4;
+
+nsBMPDecoder::nsBMPDecoder(RasterImage* aImage, State aState, size_t aLength)
+ : Decoder(aImage)
+ , mLexer(Transition::To(aState, aLength), Transition::TerminateSuccess())
+ , mIsWithinICO(false)
+ , mMayHaveTransparency(false)
+ , mDoesHaveTransparency(false)
+ , mNumColors(0)
+ , mColors(nullptr)
+ , mBytesPerColor(0)
+ , mPreGapLength(0)
+ , mPixelRowSize(0)
+ , mCurrentRow(0)
+ , mCurrentPos(0)
+ , mAbsoluteModeNumPixels(0)
+{
+}
+
+// Constructor for normal BMP files.
+nsBMPDecoder::nsBMPDecoder(RasterImage* aImage)
+ : nsBMPDecoder(aImage, State::FILE_HEADER, FILE_HEADER_LENGTH)
+{
+}
+
+// Constructor used for WinBMPv3-ICO files, which lack a file header.
+nsBMPDecoder::nsBMPDecoder(RasterImage* aImage, uint32_t aDataOffset)
+ : nsBMPDecoder(aImage, State::INFO_HEADER_SIZE, BIHSIZE_FIELD_LENGTH)
+{
+ SetIsWithinICO();
+
+ // Even though the file header isn't present in this case, the dataOffset
+ // field is set as if it is, and so we must increment mPreGapLength
+ // accordingly.
+ mPreGapLength += FILE_HEADER_LENGTH;
+
+ // This is the one piece of data we normally get from a BMP file header, so
+ // it must be provided via an argument.
+ mH.mDataOffset = aDataOffset;
+}
+
+nsBMPDecoder::~nsBMPDecoder()
+{
+}
+
+// Obtains the size of the compressed image resource.
+int32_t
+nsBMPDecoder::GetCompressedImageSize() const
+{
+ // In the RGB case mImageSize might not be set, so compute it manually.
+ MOZ_ASSERT(mPixelRowSize != 0);
+ return mH.mCompression == Compression::RGB
+ ? mPixelRowSize * AbsoluteHeight()
+ : mH.mImageSize;
+}
+
+nsresult
+nsBMPDecoder::BeforeFinishInternal()
+{
+ if (!IsMetadataDecode() && !mImageData) {
+ return NS_ERROR_FAILURE; // No image; something went wrong.
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsBMPDecoder::FinishInternal()
+{
+ // We shouldn't be called in error cases.
+ MOZ_ASSERT(!HasError(), "Can't call FinishInternal on error!");
+
+ // We should never make multiple frames.
+ MOZ_ASSERT(GetFrameCount() <= 1, "Multiple BMP frames?");
+
+ // Send notifications if appropriate.
+ if (!IsMetadataDecode() && HasSize()) {
+
+ // We should have image data.
+ MOZ_ASSERT(mImageData);
+
+ // If it was truncated, fill in the missing pixels as black.
+ while (mCurrentRow > 0) {
+ uint32_t* dst = RowBuffer();
+ while (mCurrentPos < mH.mWidth) {
+ SetPixel(dst, 0, 0, 0);
+ mCurrentPos++;
+ }
+ mCurrentPos = 0;
+ FinishRow();
+ }
+
+ // Invalidate.
+ nsIntRect r(0, 0, mH.mWidth, AbsoluteHeight());
+ PostInvalidation(r);
+
+ MOZ_ASSERT_IF(mDoesHaveTransparency, mMayHaveTransparency);
+
+ // We have transparency if we either detected some in the image itself
+ // (i.e., |mDoesHaveTransparency| is true) or we're in an ICO, which could
+ // mean we have an AND mask that provides transparency (i.e., |mIsWithinICO|
+ // is true).
+ // XXX(seth): We can tell when we create the decoder if the AND mask is
+ // present, so we could be more precise about this.
+ const Opacity opacity = mDoesHaveTransparency || mIsWithinICO
+ ? Opacity::SOME_TRANSPARENCY
+ : Opacity::FULLY_OPAQUE;
+
+ PostFrameStop(opacity);
+ PostDecodeDone();
+ }
+
+ return NS_OK;
+}
+
+// ----------------------------------------
+// Actual Data Processing
+// ----------------------------------------
+
+void
+BitFields::Value::Set(uint32_t aMask)
+{
+ mMask = aMask;
+
+ // Handle this exceptional case first. The chosen values don't matter
+ // (because a mask of zero will always give a value of zero) except that
+ // mBitWidth:
+ // - shouldn't be zero, because that would cause an infinite loop in Get();
+ // - shouldn't be 5 or 8, because that could cause a false positive match in
+ // IsR5G5B5() or IsR8G8B8().
+ if (mMask == 0x0) {
+ mRightShift = 0;
+ mBitWidth = 1;
+ return;
+ }
+
+ // Find the rightmost 1.
+ uint8_t i;
+ for (i = 0; i < 32; i++) {
+ if (mMask & (1 << i)) {
+ break;
+ }
+ }
+ mRightShift = i;
+
+ // Now find the leftmost 1 in the same run of 1s. (If there are multiple runs
+ // of 1s -- which isn't valid -- we'll behave as if only the lowest run was
+ // present, which seems reasonable.)
+ for (i = i + 1; i < 32; i++) {
+ if (!(mMask & (1 << i))) {
+ break;
+ }
+ }
+ mBitWidth = i - mRightShift;
+}
+
+MOZ_ALWAYS_INLINE uint8_t
+BitFields::Value::Get(uint32_t aValue) const
+{
+ // Extract the unscaled value.
+ uint32_t v = (aValue & mMask) >> mRightShift;
+
+ // Idea: to upscale v precisely we need to duplicate its bits, possibly
+ // repeatedly, possibly partially in the last case, from bit 7 down to bit 0
+ // in v2. For example:
+ //
+ // - mBitWidth=1: v2 = v<<7 | v<<6 | ... | v<<1 | v>>0 k -> kkkkkkkk
+ // - mBitWidth=2: v2 = v<<6 | v<<4 | v<<2 | v>>0 jk -> jkjkjkjk
+ // - mBitWidth=3: v2 = v<<5 | v<<2 | v>>1 ijk -> ijkijkij
+ // - mBitWidth=4: v2 = v<<4 | v>>0 hijk -> hijkhijk
+ // - mBitWidth=5: v2 = v<<3 | v>>2 ghijk -> ghijkghi
+ // - mBitWidth=6: v2 = v<<2 | v>>4 fghijk -> fghijkfg
+ // - mBitWidth=7: v2 = v<<1 | v>>6 efghijk -> efghijke
+ // - mBitWidth=8: v2 = v>>0 defghijk -> defghijk
+ // - mBitWidth=9: v2 = v>>1 cdefghijk -> cdefghij
+ // - mBitWidth=10: v2 = v>>2 bcdefghijk -> bcdefghi
+ // - mBitWidth=11: v2 = v>>3 abcdefghijk -> abcdefgh
+ // - etc.
+ //
+ uint8_t v2 = 0;
+ int32_t i; // must be a signed integer
+ for (i = 8 - mBitWidth; i > 0; i -= mBitWidth) {
+ v2 |= v << uint32_t(i);
+ }
+ v2 |= v >> uint32_t(-i);
+ return v2;
+}
+
+MOZ_ALWAYS_INLINE uint8_t
+BitFields::Value::GetAlpha(uint32_t aValue, bool& aHasAlphaOut) const
+{
+ if (mMask == 0x0) {
+ return 0xff;
+ }
+ aHasAlphaOut = true;
+ return Get(aValue);
+}
+
+MOZ_ALWAYS_INLINE uint8_t
+BitFields::Value::Get5(uint32_t aValue) const
+{
+ MOZ_ASSERT(mBitWidth == 5);
+ uint32_t v = (aValue & mMask) >> mRightShift;
+ return (v << 3u) | (v >> 2u);
+}
+
+MOZ_ALWAYS_INLINE uint8_t
+BitFields::Value::Get8(uint32_t aValue) const
+{
+ MOZ_ASSERT(mBitWidth == 8);
+ uint32_t v = (aValue & mMask) >> mRightShift;
+ return v;
+}
+
+void
+BitFields::SetR5G5B5()
+{
+ mRed.Set(0x7c00);
+ mGreen.Set(0x03e0);
+ mBlue.Set(0x001f);
+}
+
+void
+BitFields::SetR8G8B8()
+{
+ mRed.Set(0xff0000);
+ mGreen.Set(0xff00);
+ mBlue.Set(0x00ff);
+}
+
+bool
+BitFields::IsR5G5B5() const
+{
+ return mRed.mBitWidth == 5 &&
+ mGreen.mBitWidth == 5 &&
+ mBlue.mBitWidth == 5 &&
+ mAlpha.mMask == 0x0;
+}
+
+bool
+BitFields::IsR8G8B8() const
+{
+ return mRed.mBitWidth == 8 &&
+ mGreen.mBitWidth == 8 &&
+ mBlue.mBitWidth == 8 &&
+ mAlpha.mMask == 0x0;
+}
+
+uint32_t*
+nsBMPDecoder::RowBuffer()
+{
+ if (mDownscaler) {
+ return reinterpret_cast<uint32_t*>(mDownscaler->RowBuffer()) + mCurrentPos;
+ }
+
+ // Convert from row (1..mHeight) to absolute line (0..mHeight-1).
+ int32_t line = (mH.mHeight < 0)
+ ? -mH.mHeight - mCurrentRow
+ : mCurrentRow - 1;
+ int32_t offset = line * mH.mWidth + mCurrentPos;
+ return reinterpret_cast<uint32_t*>(mImageData) + offset;
+}
+
+void
+nsBMPDecoder::FinishRow()
+{
+ if (mDownscaler) {
+ mDownscaler->CommitRow();
+
+ if (mDownscaler->HasInvalidation()) {
+ DownscalerInvalidRect invalidRect = mDownscaler->TakeInvalidRect();
+ PostInvalidation(invalidRect.mOriginalSizeRect,
+ Some(invalidRect.mTargetSizeRect));
+ }
+ } else {
+ PostInvalidation(IntRect(0, mCurrentRow, mH.mWidth, 1));
+ }
+ mCurrentRow--;
+}
+
+LexerResult
+nsBMPDecoder::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume)
+{
+ MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!");
+
+ return mLexer.Lex(aIterator, aOnResume,
+ [=](State aState, const char* aData, size_t aLength) {
+ switch (aState) {
+ case State::FILE_HEADER: return ReadFileHeader(aData, aLength);
+ case State::INFO_HEADER_SIZE: return ReadInfoHeaderSize(aData, aLength);
+ case State::INFO_HEADER_REST: return ReadInfoHeaderRest(aData, aLength);
+ case State::BITFIELDS: return ReadBitfields(aData, aLength);
+ case State::COLOR_TABLE: return ReadColorTable(aData, aLength);
+ case State::GAP: return SkipGap();
+ case State::AFTER_GAP: return AfterGap();
+ case State::PIXEL_ROW: return ReadPixelRow(aData);
+ case State::RLE_SEGMENT: return ReadRLESegment(aData);
+ case State::RLE_DELTA: return ReadRLEDelta(aData);
+ case State::RLE_ABSOLUTE: return ReadRLEAbsolute(aData, aLength);
+ default:
+ MOZ_CRASH("Unknown State");
+ }
+ });
+}
+
+LexerTransition<nsBMPDecoder::State>
+nsBMPDecoder::ReadFileHeader(const char* aData, size_t aLength)
+{
+ mPreGapLength += aLength;
+
+ bool signatureOk = aData[0] == 'B' && aData[1] == 'M';
+ if (!signatureOk) {
+ return Transition::TerminateFailure();
+ }
+
+ // We ignore the filesize (aData + 2) and reserved (aData + 6) fields.
+
+ mH.mDataOffset = LittleEndian::readUint32(aData + 10);
+
+ return Transition::To(State::INFO_HEADER_SIZE, BIHSIZE_FIELD_LENGTH);
+}
+
+// We read the info header in two steps: (a) read the mBIHSize field to
+// determine how long the header is; (b) read the rest of the header.
+LexerTransition<nsBMPDecoder::State>
+nsBMPDecoder::ReadInfoHeaderSize(const char* aData, size_t aLength)
+{
+ mPreGapLength += aLength;
+
+ mH.mBIHSize = LittleEndian::readUint32(aData);
+
+ bool bihSizeOk = mH.mBIHSize == InfoHeaderLength::WIN_V2 ||
+ mH.mBIHSize == InfoHeaderLength::WIN_V3 ||
+ mH.mBIHSize == InfoHeaderLength::WIN_V4 ||
+ mH.mBIHSize == InfoHeaderLength::WIN_V5 ||
+ (mH.mBIHSize >= InfoHeaderLength::OS2_V2_MIN &&
+ mH.mBIHSize <= InfoHeaderLength::OS2_V2_MAX);
+ if (!bihSizeOk) {
+ return Transition::TerminateFailure();
+ }
+ // ICO BMPs must have a WinBMPv3 header. nsICODecoder should have already
+ // terminated decoding if this isn't the case.
+ MOZ_ASSERT_IF(mIsWithinICO, mH.mBIHSize == InfoHeaderLength::WIN_V3);
+
+ return Transition::To(State::INFO_HEADER_REST,
+ mH.mBIHSize - BIHSIZE_FIELD_LENGTH);
+}
+
+LexerTransition<nsBMPDecoder::State>
+nsBMPDecoder::ReadInfoHeaderRest(const char* aData, size_t aLength)
+{
+ mPreGapLength += aLength;
+
+ // |mWidth| and |mHeight| may be signed (Windows) or unsigned (OS/2). We just
+ // read as unsigned because in practice that's good enough.
+ if (mH.mBIHSize == InfoHeaderLength::WIN_V2) {
+ mH.mWidth = LittleEndian::readUint16(aData + 0);
+ mH.mHeight = LittleEndian::readUint16(aData + 2);
+ // We ignore the planes (aData + 4) field; it should always be 1.
+ mH.mBpp = LittleEndian::readUint16(aData + 6);
+ } else {
+ mH.mWidth = LittleEndian::readUint32(aData + 0);
+ mH.mHeight = LittleEndian::readUint32(aData + 4);
+ // We ignore the planes (aData + 4) field; it should always be 1.
+ mH.mBpp = LittleEndian::readUint16(aData + 10);
+
+ // For OS2-BMPv2 the info header may be as little as 16 bytes, so be
+ // careful for these fields.
+ mH.mCompression = aLength >= 16 ? LittleEndian::readUint32(aData + 12) : 0;
+ mH.mImageSize = aLength >= 20 ? LittleEndian::readUint32(aData + 16) : 0;
+ // We ignore the xppm (aData + 20) and yppm (aData + 24) fields.
+ mH.mNumColors = aLength >= 32 ? LittleEndian::readUint32(aData + 28) : 0;
+ // We ignore the important_colors (aData + 36) field.
+
+ // For WinBMPv4, WinBMPv5 and (possibly) OS2-BMPv2 there are additional
+ // fields in the info header which we ignore, with the possible exception
+ // of the color bitfields (see below).
+ }
+
+ // Run with MOZ_LOG=BMPDecoder:5 set to see this output.
+ MOZ_LOG(sBMPLog, LogLevel::Debug,
+ ("BMP: bihsize=%u, %d x %d, bpp=%u, compression=%u, colors=%u\n",
+ mH.mBIHSize, mH.mWidth, mH.mHeight, uint32_t(mH.mBpp),
+ mH.mCompression, mH.mNumColors));
+
+ // BMPs with negative width are invalid. Also, reject extremely wide images
+ // to keep the math sane. And reject INT_MIN as a height because you can't
+ // get its absolute value (because -INT_MIN is one more than INT_MAX).
+ const int32_t k64KWidth = 0x0000FFFF;
+ bool sizeOk = 0 <= mH.mWidth && mH.mWidth <= k64KWidth &&
+ mH.mHeight != INT_MIN;
+ if (!sizeOk) {
+ return Transition::TerminateFailure();
+ }
+
+ // Check mBpp and mCompression.
+ bool bppCompressionOk =
+ (mH.mCompression == Compression::RGB &&
+ (mH.mBpp == 1 || mH.mBpp == 4 || mH.mBpp == 8 ||
+ mH.mBpp == 16 || mH.mBpp == 24 || mH.mBpp == 32)) ||
+ (mH.mCompression == Compression::RLE8 && mH.mBpp == 8) ||
+ (mH.mCompression == Compression::RLE4 && mH.mBpp == 4) ||
+ (mH.mCompression == Compression::BITFIELDS &&
+ // For BITFIELDS compression we require an exact match for one of the
+ // WinBMP BIH sizes; this clearly isn't an OS2 BMP.
+ (mH.mBIHSize == InfoHeaderLength::WIN_V3 ||
+ mH.mBIHSize == InfoHeaderLength::WIN_V4 ||
+ mH.mBIHSize == InfoHeaderLength::WIN_V5) &&
+ (mH.mBpp == 16 || mH.mBpp == 32));
+ if (!bppCompressionOk) {
+ return Transition::TerminateFailure();
+ }
+
+ // Initialize our current row to the top of the image.
+ mCurrentRow = AbsoluteHeight();
+
+ // Round it up to the nearest byte count, then pad to 4-byte boundary.
+ // Compute this even for a metadate decode because GetCompressedImageSize()
+ // relies on it.
+ mPixelRowSize = (mH.mBpp * mH.mWidth + 7) / 8;
+ uint32_t surplus = mPixelRowSize % 4;
+ if (surplus != 0) {
+ mPixelRowSize += 4 - surplus;
+ }
+
+ size_t bitFieldsLengthStillToRead = 0;
+ if (mH.mCompression == Compression::BITFIELDS) {
+ // Need to read bitfields.
+ if (mH.mBIHSize >= InfoHeaderLength::WIN_V4) {
+ // Bitfields are present in the info header, so we can read them
+ // immediately.
+ mBitFields.ReadFromHeader(aData + 36, /* aReadAlpha = */ true);
+ } else {
+ // Bitfields are present after the info header, so we will read them in
+ // ReadBitfields().
+ bitFieldsLengthStillToRead = BitFields::LENGTH;
+ }
+ } else if (mH.mBpp == 16) {
+ // No bitfields specified; use the default 5-5-5 values.
+ mBitFields.SetR5G5B5();
+ } else if (mH.mBpp == 32) {
+ // No bitfields specified; use the default 8-8-8 values.
+ mBitFields.SetR8G8B8();
+ }
+
+ return Transition::To(State::BITFIELDS, bitFieldsLengthStillToRead);
+}
+
+void
+BitFields::ReadFromHeader(const char* aData, bool aReadAlpha)
+{
+ mRed.Set (LittleEndian::readUint32(aData + 0));
+ mGreen.Set(LittleEndian::readUint32(aData + 4));
+ mBlue.Set (LittleEndian::readUint32(aData + 8));
+ if (aReadAlpha) {
+ mAlpha.Set(LittleEndian::readUint32(aData + 12));
+ }
+}
+
+LexerTransition<nsBMPDecoder::State>
+nsBMPDecoder::ReadBitfields(const char* aData, size_t aLength)
+{
+ mPreGapLength += aLength;
+
+ // If aLength is zero there are no bitfields to read, or we already read them
+ // in ReadInfoHeader().
+ if (aLength != 0) {
+ mBitFields.ReadFromHeader(aData, /* aReadAlpha = */ false);
+ }
+
+ // Note that RLE-encoded BMPs might be transparent because the 'delta' mode
+ // can skip pixels and cause implicit transparency.
+ mMayHaveTransparency =
+ mIsWithinICO ||
+ mH.mCompression == Compression::RLE8 ||
+ mH.mCompression == Compression::RLE4 ||
+ (mH.mCompression == Compression::BITFIELDS &&
+ mBitFields.mAlpha.IsPresent());
+ if (mMayHaveTransparency) {
+ PostHasTransparency();
+ }
+
+ // Post our size to the superclass.
+ PostSize(mH.mWidth, AbsoluteHeight());
+
+ // We've now read all the headers. If we're doing a metadata decode, we're
+ // done.
+ if (IsMetadataDecode()) {
+ return Transition::TerminateSuccess();
+ }
+
+ // Set up the color table, if present; it'll be filled in by ReadColorTable().
+ if (mH.mBpp <= 8) {
+ mNumColors = 1 << mH.mBpp;
+ if (0 < mH.mNumColors && mH.mNumColors < mNumColors) {
+ mNumColors = mH.mNumColors;
+ }
+
+ // Always allocate and zero 256 entries, even though mNumColors might be
+ // smaller, because the file might erroneously index past mNumColors.
+ mColors = MakeUnique<ColorTableEntry[]>(256);
+ memset(mColors.get(), 0, 256 * sizeof(ColorTableEntry));
+
+ // OS/2 Bitmaps have no padding byte.
+ mBytesPerColor = (mH.mBIHSize == InfoHeaderLength::WIN_V2) ? 3 : 4;
+ }
+
+ MOZ_ASSERT(!mImageData, "Already have a buffer allocated?");
+ nsresult rv = AllocateFrame(/* aFrameNum = */ 0, OutputSize(),
+ FullOutputFrame(),
+ mMayHaveTransparency ? SurfaceFormat::B8G8R8A8
+ : SurfaceFormat::B8G8R8X8);
+ if (NS_FAILED(rv)) {
+ return Transition::TerminateFailure();
+ }
+ MOZ_ASSERT(mImageData, "Should have a buffer now");
+
+ if (mDownscaler) {
+ // BMPs store their rows in reverse order, so the downscaler needs to
+ // reverse them again when writing its output. Unless the height is
+ // negative!
+ rv = mDownscaler->BeginFrame(Size(), Nothing(),
+ mImageData, mMayHaveTransparency,
+ /* aFlipVertically = */ mH.mHeight >= 0);
+ if (NS_FAILED(rv)) {
+ return Transition::TerminateFailure();
+ }
+ }
+
+ return Transition::To(State::COLOR_TABLE, mNumColors * mBytesPerColor);
+}
+
+LexerTransition<nsBMPDecoder::State>
+nsBMPDecoder::ReadColorTable(const char* aData, size_t aLength)
+{
+ MOZ_ASSERT_IF(aLength != 0, mNumColors > 0 && mColors);
+
+ mPreGapLength += aLength;
+
+ for (uint32_t i = 0; i < mNumColors; i++) {
+ // The format is BGR or BGR0.
+ mColors[i].mBlue = uint8_t(aData[0]);
+ mColors[i].mGreen = uint8_t(aData[1]);
+ mColors[i].mRed = uint8_t(aData[2]);
+ aData += mBytesPerColor;
+ }
+
+ // We know how many bytes we've read so far (mPreGapLength) and we know the
+ // offset of the pixel data (mH.mDataOffset), so we can determine the length
+ // of the gap (possibly zero) between the color table and the pixel data.
+ //
+ // If the gap is negative the file must be malformed (e.g. mH.mDataOffset
+ // points into the middle of the color palette instead of past the end) and
+ // we give up.
+ if (mPreGapLength > mH.mDataOffset) {
+ return Transition::TerminateFailure();
+ }
+
+ uint32_t gapLength = mH.mDataOffset - mPreGapLength;
+ return Transition::ToUnbuffered(State::AFTER_GAP, State::GAP, gapLength);
+}
+
+LexerTransition<nsBMPDecoder::State>
+nsBMPDecoder::SkipGap()
+{
+ return Transition::ContinueUnbuffered(State::GAP);
+}
+
+LexerTransition<nsBMPDecoder::State>
+nsBMPDecoder::AfterGap()
+{
+ // If there are no pixels we can stop.
+ //
+ // XXX: normally, if there are no pixels we will have stopped decoding before
+ // now, outside of this decoder. However, if the BMP is within an ICO file,
+ // it's possible that the ICO claimed the image had a non-zero size while the
+ // BMP claims otherwise. This test is to catch that awkward case. If we ever
+ // come up with a more general solution to this ICO-and-BMP-disagree-on-size
+ // problem, this test can be removed.
+ if (mH.mWidth == 0 || mH.mHeight == 0) {
+ return Transition::TerminateSuccess();
+ }
+
+ bool hasRLE = mH.mCompression == Compression::RLE8 ||
+ mH.mCompression == Compression::RLE4;
+ return hasRLE
+ ? Transition::To(State::RLE_SEGMENT, RLE::SEGMENT_LENGTH)
+ : Transition::To(State::PIXEL_ROW, mPixelRowSize);
+}
+
+LexerTransition<nsBMPDecoder::State>
+nsBMPDecoder::ReadPixelRow(const char* aData)
+{
+ MOZ_ASSERT(mCurrentRow > 0);
+ MOZ_ASSERT(mCurrentPos == 0);
+
+ const uint8_t* src = reinterpret_cast<const uint8_t*>(aData);
+ uint32_t* dst = RowBuffer();
+ uint32_t lpos = mH.mWidth;
+ switch (mH.mBpp) {
+ case 1:
+ while (lpos > 0) {
+ int8_t bit;
+ uint8_t idx;
+ for (bit = 7; bit >= 0 && lpos > 0; bit--) {
+ idx = (*src >> bit) & 1;
+ SetPixel(dst, idx, mColors);
+ --lpos;
+ }
+ ++src;
+ }
+ break;
+
+ case 4:
+ while (lpos > 0) {
+ Set4BitPixel(dst, *src, lpos, mColors);
+ ++src;
+ }
+ break;
+
+ case 8:
+ while (lpos > 0) {
+ SetPixel(dst, *src, mColors);
+ --lpos;
+ ++src;
+ }
+ break;
+
+ case 16:
+ if (mBitFields.IsR5G5B5()) {
+ // Specialize this common case.
+ while (lpos > 0) {
+ uint16_t val = LittleEndian::readUint16(src);
+ SetPixel(dst, mBitFields.mRed.Get5(val),
+ mBitFields.mGreen.Get5(val),
+ mBitFields.mBlue.Get5(val));
+ --lpos;
+ src += 2;
+ }
+ } else {
+ bool anyHasAlpha = false;
+ while (lpos > 0) {
+ uint16_t val = LittleEndian::readUint16(src);
+ SetPixel(dst, mBitFields.mRed.Get(val),
+ mBitFields.mGreen.Get(val),
+ mBitFields.mBlue.Get(val),
+ mBitFields.mAlpha.GetAlpha(val, anyHasAlpha));
+ --lpos;
+ src += 2;
+ }
+ if (anyHasAlpha) {
+ MOZ_ASSERT(mMayHaveTransparency);
+ mDoesHaveTransparency = true;
+ }
+ }
+ break;
+
+ case 24:
+ while (lpos > 0) {
+ SetPixel(dst, src[2], src[1], src[0]);
+ --lpos;
+ src += 3;
+ }
+ break;
+
+ case 32:
+ if (mH.mCompression == Compression::RGB && mIsWithinICO &&
+ mH.mBpp == 32) {
+ // This is a special case only used for 32bpp WinBMPv3-ICO files, which
+ // could be in either 0RGB or ARGB format. We start by assuming it's
+ // an 0RGB image. If we hit a non-zero alpha value, then we know it's
+ // actually an ARGB image, and change tack accordingly.
+ // (Note: a fully-transparent ARGB image is indistinguishable from a
+ // 0RGB image, and we will render such an image as a 0RGB image, i.e.
+ // opaquely. This is unlikely to be a problem in practice.)
+ while (lpos > 0) {
+ if (!mDoesHaveTransparency && src[3] != 0) {
+ // Up until now this looked like an 0RGB image, but we now know
+ // it's actually an ARGB image. Which means every pixel we've seen
+ // so far has been fully transparent. So we go back and redo them.
+
+ // Tell the Downscaler to go back to the start.
+ if (mDownscaler) {
+ mDownscaler->ResetForNextProgressivePass();
+ }
+
+ // Redo the complete rows we've already done.
+ MOZ_ASSERT(mCurrentPos == 0);
+ int32_t currentRow = mCurrentRow;
+ mCurrentRow = AbsoluteHeight();
+ while (mCurrentRow > currentRow) {
+ dst = RowBuffer();
+ for (int32_t i = 0; i < mH.mWidth; i++) {
+ SetPixel(dst, 0, 0, 0, 0);
+ }
+ FinishRow();
+ }
+
+ // Redo the part of this row we've already done.
+ dst = RowBuffer();
+ int32_t n = mH.mWidth - lpos;
+ for (int32_t i = 0; i < n; i++) {
+ SetPixel(dst, 0, 0, 0, 0);
+ }
+
+ MOZ_ASSERT(mMayHaveTransparency);
+ mDoesHaveTransparency = true;
+ }
+
+ // If mDoesHaveTransparency is false, treat this as an 0RGB image.
+ // Otherwise, treat this as an ARGB image.
+ SetPixel(dst, src[2], src[1], src[0],
+ mDoesHaveTransparency ? src[3] : 0xff);
+ src += 4;
+ --lpos;
+ }
+ } else if (mBitFields.IsR8G8B8()) {
+ // Specialize this common case.
+ while (lpos > 0) {
+ uint32_t val = LittleEndian::readUint32(src);
+ SetPixel(dst, mBitFields.mRed.Get8(val),
+ mBitFields.mGreen.Get8(val),
+ mBitFields.mBlue.Get8(val));
+ --lpos;
+ src += 4;
+ }
+ } else {
+ bool anyHasAlpha = false;
+ while (lpos > 0) {
+ uint32_t val = LittleEndian::readUint32(src);
+ SetPixel(dst, mBitFields.mRed.Get(val),
+ mBitFields.mGreen.Get(val),
+ mBitFields.mBlue.Get(val),
+ mBitFields.mAlpha.GetAlpha(val, anyHasAlpha));
+ --lpos;
+ src += 4;
+ }
+ if (anyHasAlpha) {
+ MOZ_ASSERT(mMayHaveTransparency);
+ mDoesHaveTransparency = true;
+ }
+ }
+ break;
+
+ default:
+ MOZ_CRASH("Unsupported color depth; earlier check didn't catch it?");
+ }
+
+ FinishRow();
+ return mCurrentRow == 0
+ ? Transition::TerminateSuccess()
+ : Transition::To(State::PIXEL_ROW, mPixelRowSize);
+}
+
+LexerTransition<nsBMPDecoder::State>
+nsBMPDecoder::ReadRLESegment(const char* aData)
+{
+ if (mCurrentRow == 0) {
+ return Transition::TerminateSuccess();
+ }
+
+ uint8_t byte1 = uint8_t(aData[0]);
+ uint8_t byte2 = uint8_t(aData[1]);
+
+ if (byte1 != RLE::ESCAPE) {
+ // Encoded mode consists of two bytes: byte1 specifies the number of
+ // consecutive pixels to be drawn using the color index contained in
+ // byte2.
+ //
+ // Work around bitmaps that specify too many pixels.
+ uint32_t pixelsNeeded =
+ std::min<uint32_t>(mH.mWidth - mCurrentPos, byte1);
+ if (pixelsNeeded) {
+ uint32_t* dst = RowBuffer();
+ mCurrentPos += pixelsNeeded;
+ if (mH.mCompression == Compression::RLE8) {
+ do {
+ SetPixel(dst, byte2, mColors);
+ pixelsNeeded --;
+ } while (pixelsNeeded);
+ } else {
+ do {
+ Set4BitPixel(dst, byte2, pixelsNeeded, mColors);
+ } while (pixelsNeeded);
+ }
+ }
+ return Transition::To(State::RLE_SEGMENT, RLE::SEGMENT_LENGTH);
+ }
+
+ if (byte2 == RLE::ESCAPE_EOL) {
+ mCurrentPos = 0;
+ FinishRow();
+ return mCurrentRow == 0
+ ? Transition::TerminateSuccess()
+ : Transition::To(State::RLE_SEGMENT, RLE::SEGMENT_LENGTH);
+ }
+
+ if (byte2 == RLE::ESCAPE_EOF) {
+ return Transition::TerminateSuccess();
+ }
+
+ if (byte2 == RLE::ESCAPE_DELTA) {
+ return Transition::To(State::RLE_DELTA, RLE::DELTA_LENGTH);
+ }
+
+ // Absolute mode. |byte2| gives the number of pixels. The length depends on
+ // whether it's 4-bit or 8-bit RLE. Also, the length must be even (and zero
+ // padding is used to achieve this when necessary).
+ MOZ_ASSERT(mAbsoluteModeNumPixels == 0);
+ mAbsoluteModeNumPixels = byte2;
+ uint32_t length = byte2;
+ if (mH.mCompression == Compression::RLE4) {
+ length = (length + 1) / 2; // halve, rounding up
+ }
+ if (length & 1) {
+ length++;
+ }
+ return Transition::To(State::RLE_ABSOLUTE, length);
+}
+
+LexerTransition<nsBMPDecoder::State>
+nsBMPDecoder::ReadRLEDelta(const char* aData)
+{
+ // Delta encoding makes it possible to skip pixels making part of the image
+ // transparent.
+ MOZ_ASSERT(mMayHaveTransparency);
+ mDoesHaveTransparency = true;
+
+ if (mDownscaler) {
+ // Clear the skipped pixels. (This clears to the end of the row,
+ // which is perfect if there's a Y delta and harmless if not).
+ mDownscaler->ClearRestOfRow(/* aStartingAtCol = */ mCurrentPos);
+ }
+
+ // Handle the XDelta.
+ mCurrentPos += uint8_t(aData[0]);
+ if (mCurrentPos > mH.mWidth) {
+ mCurrentPos = mH.mWidth;
+ }
+
+ // Handle the Y Delta.
+ int32_t yDelta = std::min<int32_t>(uint8_t(aData[1]), mCurrentRow);
+ mCurrentRow -= yDelta;
+
+ if (mDownscaler && yDelta > 0) {
+ // Commit the current row (the first of the skipped rows).
+ mDownscaler->CommitRow();
+
+ // Clear and commit the remaining skipped rows.
+ for (int32_t line = 1; line < yDelta; line++) {
+ mDownscaler->ClearRow();
+ mDownscaler->CommitRow();
+ }
+ }
+
+ return mCurrentRow == 0
+ ? Transition::TerminateSuccess()
+ : Transition::To(State::RLE_SEGMENT, RLE::SEGMENT_LENGTH);
+}
+
+LexerTransition<nsBMPDecoder::State>
+nsBMPDecoder::ReadRLEAbsolute(const char* aData, size_t aLength)
+{
+ uint32_t n = mAbsoluteModeNumPixels;
+ mAbsoluteModeNumPixels = 0;
+
+ if (mCurrentPos + n > uint32_t(mH.mWidth)) {
+ // Bad data. Stop decoding; at least part of the image may have been
+ // decoded.
+ return Transition::TerminateSuccess();
+ }
+
+ // In absolute mode, n represents the number of pixels that follow, each of
+ // which contains the color index of a single pixel.
+ uint32_t* dst = RowBuffer();
+ uint32_t iSrc = 0;
+ uint32_t* oldPos = dst;
+ if (mH.mCompression == Compression::RLE8) {
+ while (n > 0) {
+ SetPixel(dst, aData[iSrc], mColors);
+ n--;
+ iSrc++;
+ }
+ } else {
+ while (n > 0) {
+ Set4BitPixel(dst, aData[iSrc], n, mColors);
+ iSrc++;
+ }
+ }
+ mCurrentPos += dst - oldPos;
+
+ // We should read all the data (unless the last byte is zero padding).
+ MOZ_ASSERT(iSrc == aLength - 1 || iSrc == aLength);
+
+ return Transition::To(State::RLE_SEGMENT, RLE::SEGMENT_LENGTH);
+}
+
+} // namespace image
+} // namespace mozilla
diff --git a/image/decoders/nsBMPDecoder.h b/image/decoders/nsBMPDecoder.h
new file mode 100644
index 000000000..0cf2af689
--- /dev/null
+++ b/image/decoders/nsBMPDecoder.h
@@ -0,0 +1,235 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_image_decoders_nsBMPDecoder_h
+#define mozilla_image_decoders_nsBMPDecoder_h
+
+#include "BMPHeaders.h"
+#include "Decoder.h"
+#include "gfxColor.h"
+#include "StreamingLexer.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+namespace image {
+
+namespace bmp {
+
+/// This struct contains the fields from the file header and info header that
+/// we use during decoding. (Excluding bitfields fields, which are kept in
+/// BitFields.)
+struct Header {
+ uint32_t mDataOffset; // Offset to raster data.
+ uint32_t mBIHSize; // Header size.
+ int32_t mWidth; // Image width.
+ int32_t mHeight; // Image height.
+ uint16_t mBpp; // Bits per pixel.
+ uint32_t mCompression; // See struct Compression for valid values.
+ uint32_t mImageSize; // (compressed) image size. Can be 0 if
+ // mCompression==0.
+ uint32_t mNumColors; // Used colors.
+
+ Header()
+ : mDataOffset(0)
+ , mBIHSize(0)
+ , mWidth(0)
+ , mHeight(0)
+ , mBpp(0)
+ , mCompression(0)
+ , mImageSize(0)
+ , mNumColors(0)
+ {}
+};
+
+/// An entry in the color table.
+struct ColorTableEntry {
+ uint8_t mRed;
+ uint8_t mGreen;
+ uint8_t mBlue;
+};
+
+/// All the color-related bitfields for 16bpp and 32bpp images. We use this
+/// even for older format BMPs that don't have explicit bitfields.
+class BitFields {
+ class Value {
+ friend class BitFields;
+
+ uint32_t mMask; // The mask for the value.
+ uint8_t mRightShift; // The amount to right-shift after masking.
+ uint8_t mBitWidth; // The width (in bits) of the value.
+
+ /// Sets the mask (and thus the right-shift and bit-width as well).
+ void Set(uint32_t aMask);
+
+ public:
+ Value()
+ {
+ mMask = 0;
+ mRightShift = 0;
+ mBitWidth = 0;
+ }
+
+ /// Returns true if this channel is used. Only used for alpha.
+ bool IsPresent() const { return mMask != 0x0; }
+
+ /// Extracts the single color value from the multi-color value.
+ uint8_t Get(uint32_t aVal) const;
+
+ /// Like Get(), but specially for alpha.
+ uint8_t GetAlpha(uint32_t aVal, bool& aHasAlphaOut) const;
+
+ /// Specialized versions of Get() for when the bit-width is 5 or 8.
+ /// (They will assert if called and the bit-width is not 5 or 8.)
+ uint8_t Get5(uint32_t aVal) const;
+ uint8_t Get8(uint32_t aVal) const;
+ };
+
+public:
+ /// The individual color channels.
+ Value mRed;
+ Value mGreen;
+ Value mBlue;
+ Value mAlpha;
+
+ /// Set bitfields to the standard 5-5-5 16bpp values.
+ void SetR5G5B5();
+
+ /// Set bitfields to the standard 8-8-8 32bpp values.
+ void SetR8G8B8();
+
+ /// Test if bitfields have the standard 5-5-5 16bpp values.
+ bool IsR5G5B5() const;
+
+ /// Test if bitfields have the standard 8-8-8 32bpp values.
+ bool IsR8G8B8() const;
+
+ /// Read the bitfields from a header. The reading of the alpha mask is
+ /// optional.
+ void ReadFromHeader(const char* aData, bool aReadAlpha);
+
+ /// Length of the bitfields structure in the BMP file.
+ static const size_t LENGTH = 12;
+};
+
+} // namespace bmp
+
+class RasterImage;
+
+/// Decoder for BMP-Files, as used by Windows and OS/2.
+
+class nsBMPDecoder : public Decoder
+{
+public:
+ ~nsBMPDecoder();
+
+ /// Obtains the internal output image buffer.
+ uint32_t* GetImageData() { return reinterpret_cast<uint32_t*>(mImageData); }
+
+ /// Obtains the length of the internal output image buffer.
+ size_t GetImageDataLength() const { return mImageDataLength; }
+
+ /// Obtains the size of the compressed image resource.
+ int32_t GetCompressedImageSize() const;
+
+ /// Mark this BMP as being within an ICO file. Only used for testing purposes
+ /// because the ICO-specific constructor does this marking automatically.
+ void SetIsWithinICO() { mIsWithinICO = true; }
+
+ /// Did the BMP file have alpha data of any kind? (Only use this after the
+ /// bitmap has been fully decoded.)
+ bool HasTransparency() const { return mDoesHaveTransparency; }
+
+ LexerResult DoDecode(SourceBufferIterator& aIterator,
+ IResumable* aOnResume) override;
+ nsresult BeforeFinishInternal() override;
+ nsresult FinishInternal() override;
+
+private:
+ friend class DecoderFactory;
+
+ enum class State {
+ FILE_HEADER,
+ INFO_HEADER_SIZE,
+ INFO_HEADER_REST,
+ BITFIELDS,
+ COLOR_TABLE,
+ GAP,
+ AFTER_GAP,
+ PIXEL_ROW,
+ RLE_SEGMENT,
+ RLE_DELTA,
+ RLE_ABSOLUTE
+ };
+
+ // This is the constructor used for normal BMP images.
+ explicit nsBMPDecoder(RasterImage* aImage);
+
+ // This is the constructor used for BMP resources in ICO images.
+ nsBMPDecoder(RasterImage* aImage, uint32_t aDataOffset);
+
+ // Helper constructor called by the other two.
+ nsBMPDecoder(RasterImage* aImage, State aState, size_t aLength);
+
+ int32_t AbsoluteHeight() const { return abs(mH.mHeight); }
+
+ uint32_t* RowBuffer();
+
+ void FinishRow();
+
+ LexerTransition<State> ReadFileHeader(const char* aData, size_t aLength);
+ LexerTransition<State> ReadInfoHeaderSize(const char* aData, size_t aLength);
+ LexerTransition<State> ReadInfoHeaderRest(const char* aData, size_t aLength);
+ LexerTransition<State> ReadBitfields(const char* aData, size_t aLength);
+ LexerTransition<State> ReadColorTable(const char* aData, size_t aLength);
+ LexerTransition<State> SkipGap();
+ LexerTransition<State> AfterGap();
+ LexerTransition<State> ReadPixelRow(const char* aData);
+ LexerTransition<State> ReadRLESegment(const char* aData);
+ LexerTransition<State> ReadRLEDelta(const char* aData);
+ LexerTransition<State> ReadRLEAbsolute(const char* aData, size_t aLength);
+
+ StreamingLexer<State> mLexer;
+
+ bmp::Header mH;
+
+ // If the BMP is within an ICO file our treatment of it differs slightly.
+ bool mIsWithinICO;
+
+ bmp::BitFields mBitFields;
+
+ // Might the image have transparency? Determined from the headers during
+ // metadata decode. (Does not guarantee the image actually has transparency.)
+ bool mMayHaveTransparency;
+
+ // Does the image have transparency? Determined during full decoding, so only
+ // use this after that has been completed.
+ bool mDoesHaveTransparency;
+
+ uint32_t mNumColors; // The number of used colors, i.e. the number of
+ // entries in mColors, if it's present.
+ UniquePtr<bmp::ColorTableEntry[]> mColors; // The color table, if it's present.
+ uint32_t mBytesPerColor; // 3 or 4, depending on the format
+
+ // The number of bytes prior to the optional gap that have been read. This
+ // is used to find the start of the pixel data.
+ uint32_t mPreGapLength;
+
+ uint32_t mPixelRowSize; // The number of bytes per pixel row.
+
+ int32_t mCurrentRow; // Index of the row of the image that's currently
+ // being decoded: [height,1].
+ int32_t mCurrentPos; // Index into the current line. Used when
+ // doing RLE decoding and when filling in pixels
+ // for truncated files.
+
+ // Only used in RLE_ABSOLUTE state: the number of pixels to read.
+ uint32_t mAbsoluteModeNumPixels;
+};
+
+} // namespace image
+} // namespace mozilla
+
+#endif // mozilla_image_decoders_nsBMPDecoder_h
diff --git a/image/decoders/nsGIFDecoder2.cpp b/image/decoders/nsGIFDecoder2.cpp
new file mode 100644
index 000000000..7955438e4
--- /dev/null
+++ b/image/decoders/nsGIFDecoder2.cpp
@@ -0,0 +1,1085 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/*
+The Graphics Interchange Format(c) is the copyright property of CompuServe
+Incorporated. Only CompuServe Incorporated is authorized to define, redefine,
+enhance, alter, modify or change in any way the definition of the format.
+
+CompuServe Incorporated hereby grants a limited, non-exclusive, royalty-free
+license for the use of the Graphics Interchange Format(sm) in computer
+software; computer software utilizing GIF(sm) must acknowledge ownership of the
+Graphics Interchange Format and its Service Mark by CompuServe Incorporated, in
+User and Technical Documentation. Computer software utilizing GIF, which is
+distributed or may be distributed without User or Technical Documentation must
+display to the screen or printer a message acknowledging ownership of the
+Graphics Interchange Format and the Service Mark by CompuServe Incorporated; in
+this case, the acknowledgement may be displayed in an opening screen or leading
+banner, or a closing screen or trailing banner. A message such as the following
+may be used:
+
+ "The Graphics Interchange Format(c) is the Copyright property of
+ CompuServe Incorporated. GIF(sm) is a Service Mark property of
+ CompuServe Incorporated."
+
+For further information, please contact :
+
+ CompuServe Incorporated
+ Graphics Technology Department
+ 5000 Arlington Center Boulevard
+ Columbus, Ohio 43220
+ U. S. A.
+
+CompuServe Incorporated maintains a mailing list with all those individuals and
+organizations who wish to receive copies of this document when it is corrected
+or revised. This service is offered free of charge; please provide us with your
+mailing address.
+*/
+
+#include "nsGIFDecoder2.h"
+
+#include <stddef.h>
+
+#include "imgFrame.h"
+#include "mozilla/EndianUtils.h"
+#include "nsIInputStream.h"
+#include "RasterImage.h"
+#include "SurfacePipeFactory.h"
+
+#include "gfxColor.h"
+#include "gfxPlatform.h"
+#include "qcms.h"
+#include <algorithm>
+#include "mozilla/Telemetry.h"
+
+using namespace mozilla::gfx;
+
+using std::max;
+
+namespace mozilla {
+namespace image {
+
+//////////////////////////////////////////////////////////////////////
+// GIF Decoder Implementation
+
+static const size_t GIF_HEADER_LEN = 6;
+static const size_t GIF_SCREEN_DESCRIPTOR_LEN = 7;
+static const size_t BLOCK_HEADER_LEN = 1;
+static const size_t SUB_BLOCK_HEADER_LEN = 1;
+static const size_t EXTENSION_HEADER_LEN = 2;
+static const size_t GRAPHIC_CONTROL_EXTENSION_LEN = 4;
+static const size_t APPLICATION_EXTENSION_LEN = 11;
+static const size_t IMAGE_DESCRIPTOR_LEN = 9;
+
+// Masks for reading color table information from packed fields in the screen
+// descriptor and image descriptor blocks.
+static const uint8_t PACKED_FIELDS_COLOR_TABLE_BIT = 0x80;
+static const uint8_t PACKED_FIELDS_INTERLACED_BIT = 0x40;
+static const uint8_t PACKED_FIELDS_TABLE_DEPTH_MASK = 0x07;
+
+nsGIFDecoder2::nsGIFDecoder2(RasterImage* aImage)
+ : Decoder(aImage)
+ , mLexer(Transition::To(State::GIF_HEADER, GIF_HEADER_LEN),
+ Transition::TerminateSuccess())
+ , mOldColor(0)
+ , mCurrentFrameIndex(-1)
+ , mColorTablePos(0)
+ , mGIFOpen(false)
+ , mSawTransparency(false)
+{
+ // Clear out the structure, excluding the arrays.
+ memset(&mGIFStruct, 0, sizeof(mGIFStruct));
+
+ // Initialize as "animate once" in case no NETSCAPE2.0 extension is found.
+ mGIFStruct.loop_count = 1;
+}
+
+nsGIFDecoder2::~nsGIFDecoder2()
+{
+ free(mGIFStruct.local_colormap);
+}
+
+nsresult
+nsGIFDecoder2::FinishInternal()
+{
+ MOZ_ASSERT(!HasError(), "Shouldn't call FinishInternal after error!");
+
+ // If the GIF got cut off, handle it anyway
+ if (!IsMetadataDecode() && mGIFOpen) {
+ if (mCurrentFrameIndex == mGIFStruct.images_decoded) {
+ EndImageFrame();
+ }
+ PostDecodeDone(mGIFStruct.loop_count - 1);
+ mGIFOpen = false;
+ }
+
+ return NS_OK;
+}
+
+void
+nsGIFDecoder2::FlushImageData()
+{
+ Maybe<SurfaceInvalidRect> invalidRect = mPipe.TakeInvalidRect();
+ if (!invalidRect) {
+ return;
+ }
+
+ PostInvalidation(invalidRect->mInputSpaceRect,
+ Some(invalidRect->mOutputSpaceRect));
+}
+
+//******************************************************************************
+// GIF decoder callback methods. Part of public API for GIF2
+//******************************************************************************
+
+//******************************************************************************
+void
+nsGIFDecoder2::BeginGIF()
+{
+ if (mGIFOpen) {
+ return;
+ }
+
+ mGIFOpen = true;
+
+ PostSize(mGIFStruct.screen_width, mGIFStruct.screen_height);
+}
+
+bool
+nsGIFDecoder2::CheckForTransparency(const IntRect& aFrameRect)
+{
+ // Check if the image has a transparent color in its palette.
+ if (mGIFStruct.is_transparent) {
+ PostHasTransparency();
+ return true;
+ }
+
+ if (mGIFStruct.images_decoded > 0) {
+ return false; // We only care about first frame padding below.
+ }
+
+ // If we need padding on the first frame, that means we don't draw into part
+ // of the image at all. Report that as transparency.
+ IntRect imageRect(0, 0, mGIFStruct.screen_width, mGIFStruct.screen_height);
+ if (!imageRect.IsEqualEdges(aFrameRect)) {
+ PostHasTransparency();
+ mSawTransparency = true; // Make sure we don't optimize it away.
+ return true;
+ }
+
+ return false;
+}
+
+//******************************************************************************
+nsresult
+nsGIFDecoder2::BeginImageFrame(const IntRect& aFrameRect,
+ uint16_t aDepth,
+ bool aIsInterlaced)
+{
+ MOZ_ASSERT(HasSize());
+
+ bool hasTransparency = CheckForTransparency(aFrameRect);
+ gfx::SurfaceFormat format = hasTransparency ? SurfaceFormat::B8G8R8A8
+ : SurfaceFormat::B8G8R8X8;
+
+ // Make sure there's no animation if we're downscaling.
+ MOZ_ASSERT_IF(Size() != OutputSize(), !GetImageMetadata().HasAnimation());
+
+ SurfacePipeFlags pipeFlags = aIsInterlaced
+ ? SurfacePipeFlags::DEINTERLACE
+ : SurfacePipeFlags();
+
+ Maybe<SurfacePipe> pipe;
+ if (mGIFStruct.images_decoded == 0) {
+ // The first frame may be displayed progressively.
+ pipeFlags |= SurfacePipeFlags::PROGRESSIVE_DISPLAY;
+
+ // The first frame is always decoded into an RGB surface.
+ pipe =
+ SurfacePipeFactory::CreateSurfacePipe(this, mGIFStruct.images_decoded,
+ Size(), OutputSize(),
+ aFrameRect, format, pipeFlags);
+ } else {
+ // This is an animation frame (and not the first). To minimize the memory
+ // usage of animations, the image data is stored in paletted form.
+ MOZ_ASSERT(Size() == OutputSize());
+ pipe =
+ SurfacePipeFactory::CreatePalettedSurfacePipe(this, mGIFStruct.images_decoded,
+ Size(), aFrameRect, format,
+ aDepth, pipeFlags);
+ }
+
+ mCurrentFrameIndex = mGIFStruct.images_decoded;
+
+ if (!pipe) {
+ mPipe = SurfacePipe();
+ return NS_ERROR_FAILURE;
+ }
+
+ mPipe = Move(*pipe);
+ return NS_OK;
+}
+
+
+//******************************************************************************
+void
+nsGIFDecoder2::EndImageFrame()
+{
+ Opacity opacity = Opacity::SOME_TRANSPARENCY;
+
+ if (mGIFStruct.images_decoded == 0) {
+ // We need to send invalidations for the first frame.
+ FlushImageData();
+
+ // The first frame was preallocated with alpha; if it wasn't transparent, we
+ // should fix that. We can also mark it opaque unconditionally if we didn't
+ // actually see any transparent pixels - this test is only valid for the
+ // first frame.
+ if (!mGIFStruct.is_transparent && !mSawTransparency) {
+ opacity = Opacity::FULLY_OPAQUE;
+ }
+ }
+
+ // Unconditionally increment images_decoded, because we unconditionally
+ // append frames in BeginImageFrame(). This ensures that images_decoded
+ // always refers to the frame in mImage we're currently decoding,
+ // even if some of them weren't decoded properly and thus are blank.
+ mGIFStruct.images_decoded++;
+
+ // Tell the superclass we finished a frame
+ PostFrameStop(opacity,
+ DisposalMethod(mGIFStruct.disposal_method),
+ FrameTimeout::FromRawMilliseconds(mGIFStruct.delay_time));
+
+ // Reset the transparent pixel
+ if (mOldColor) {
+ mColormap[mGIFStruct.tpixel] = mOldColor;
+ mOldColor = 0;
+ }
+
+ mCurrentFrameIndex = -1;
+}
+
+template <typename PixelSize>
+PixelSize
+nsGIFDecoder2::ColormapIndexToPixel(uint8_t aIndex)
+{
+ MOZ_ASSERT(sizeof(PixelSize) == sizeof(uint32_t));
+
+ // Retrieve the next color, clamping to the size of the colormap.
+ uint32_t color = mColormap[aIndex & mColorMask];
+
+ // Check for transparency.
+ if (mGIFStruct.is_transparent) {
+ mSawTransparency = mSawTransparency || color == 0;
+ }
+
+ return color;
+}
+
+template <>
+uint8_t
+nsGIFDecoder2::ColormapIndexToPixel<uint8_t>(uint8_t aIndex)
+{
+ return aIndex & mColorMask;
+}
+
+template <typename PixelSize>
+NextPixel<PixelSize>
+nsGIFDecoder2::YieldPixel(const uint8_t* aData,
+ size_t aLength,
+ size_t* aBytesReadOut)
+{
+ MOZ_ASSERT(aData);
+ MOZ_ASSERT(aBytesReadOut);
+ MOZ_ASSERT(mGIFStruct.stackp >= mGIFStruct.stack);
+
+ // Advance to the next byte we should read.
+ const uint8_t* data = aData + *aBytesReadOut;
+
+ // If we don't have any decoded data to yield, try to read some input and
+ // produce some.
+ if (mGIFStruct.stackp == mGIFStruct.stack) {
+ while (mGIFStruct.bits < mGIFStruct.codesize && *aBytesReadOut < aLength) {
+ // Feed the next byte into the decoder's 32-bit input buffer.
+ mGIFStruct.datum += int32_t(*data) << mGIFStruct.bits;
+ mGIFStruct.bits += 8;
+ data += 1;
+ *aBytesReadOut += 1;
+ }
+
+ if (mGIFStruct.bits < mGIFStruct.codesize) {
+ return AsVariant(WriteState::NEED_MORE_DATA);
+ }
+
+ // Get the leading variable-length symbol from the data stream.
+ int code = mGIFStruct.datum & mGIFStruct.codemask;
+ mGIFStruct.datum >>= mGIFStruct.codesize;
+ mGIFStruct.bits -= mGIFStruct.codesize;
+
+ const int clearCode = ClearCode();
+
+ // Reset the dictionary to its original state, if requested
+ if (code == clearCode) {
+ mGIFStruct.codesize = mGIFStruct.datasize + 1;
+ mGIFStruct.codemask = (1 << mGIFStruct.codesize) - 1;
+ mGIFStruct.avail = clearCode + 2;
+ mGIFStruct.oldcode = -1;
+ return AsVariant(WriteState::NEED_MORE_DATA);
+ }
+
+ // Check for explicit end-of-stream code. It should only appear after all
+ // image data, but if that was the case we wouldn't be in this function, so
+ // this is always an error condition.
+ if (code == (clearCode + 1)) {
+ return AsVariant(WriteState::FAILURE);
+ }
+
+ if (mGIFStruct.oldcode == -1) {
+ if (code >= MAX_BITS) {
+ return AsVariant(WriteState::FAILURE); // The code's too big; something's wrong.
+ }
+
+ mGIFStruct.firstchar = mGIFStruct.oldcode = code;
+
+ // Yield a pixel at the appropriate index in the colormap.
+ mGIFStruct.pixels_remaining--;
+ return AsVariant(ColormapIndexToPixel<PixelSize>(mGIFStruct.suffix[code]));
+ }
+
+ int incode = code;
+ if (code >= mGIFStruct.avail) {
+ *mGIFStruct.stackp++ = mGIFStruct.firstchar;
+ code = mGIFStruct.oldcode;
+
+ if (mGIFStruct.stackp >= mGIFStruct.stack + MAX_BITS) {
+ return AsVariant(WriteState::FAILURE); // Stack overflow; something's wrong.
+ }
+ }
+
+ while (code >= clearCode) {
+ if ((code >= MAX_BITS) || (code == mGIFStruct.prefix[code])) {
+ return AsVariant(WriteState::FAILURE);
+ }
+
+ *mGIFStruct.stackp++ = mGIFStruct.suffix[code];
+ code = mGIFStruct.prefix[code];
+
+ if (mGIFStruct.stackp >= mGIFStruct.stack + MAX_BITS) {
+ return AsVariant(WriteState::FAILURE); // Stack overflow; something's wrong.
+ }
+ }
+
+ *mGIFStruct.stackp++ = mGIFStruct.firstchar = mGIFStruct.suffix[code];
+
+ // Define a new codeword in the dictionary.
+ if (mGIFStruct.avail < 4096) {
+ mGIFStruct.prefix[mGIFStruct.avail] = mGIFStruct.oldcode;
+ mGIFStruct.suffix[mGIFStruct.avail] = mGIFStruct.firstchar;
+ mGIFStruct.avail++;
+
+ // If we've used up all the codewords of a given length increase the
+ // length of codewords by one bit, but don't exceed the specified maximum
+ // codeword size of 12 bits.
+ if (((mGIFStruct.avail & mGIFStruct.codemask) == 0) &&
+ (mGIFStruct.avail < 4096)) {
+ mGIFStruct.codesize++;
+ mGIFStruct.codemask += mGIFStruct.avail;
+ }
+ }
+
+ mGIFStruct.oldcode = incode;
+ }
+
+ if (MOZ_UNLIKELY(mGIFStruct.stackp <= mGIFStruct.stack)) {
+ MOZ_ASSERT_UNREACHABLE("No decoded data but we didn't return early?");
+ return AsVariant(WriteState::FAILURE);
+ }
+
+ // Yield a pixel at the appropriate index in the colormap.
+ mGIFStruct.pixels_remaining--;
+ return AsVariant(ColormapIndexToPixel<PixelSize>(*--mGIFStruct.stackp));
+}
+
+/// Expand the colormap from RGB to Packed ARGB as needed by Cairo.
+/// And apply any LCMS transformation.
+static void
+ConvertColormap(uint32_t* aColormap, uint32_t aColors)
+{
+ // Apply CMS transformation if enabled and available
+ if (gfxPlatform::GetCMSMode() == eCMSMode_All) {
+ qcms_transform* transform = gfxPlatform::GetCMSRGBTransform();
+ if (transform) {
+ qcms_transform_data(transform, aColormap, aColormap, aColors);
+ }
+ }
+
+ // Convert from the GIF's RGB format to the Cairo format.
+ // Work from end to begin, because of the in-place expansion
+ uint8_t* from = ((uint8_t*)aColormap) + 3 * aColors;
+ uint32_t* to = aColormap + aColors;
+
+ // Convert color entries to Cairo format
+
+ // set up for loops below
+ if (!aColors) {
+ return;
+ }
+ uint32_t c = aColors;
+
+ // copy as bytes until source pointer is 32-bit-aligned
+ // NB: can't use 32-bit reads, they might read off the end of the buffer
+ for (; (NS_PTR_TO_UINT32(from) & 0x3) && c; --c) {
+ from -= 3;
+ *--to = gfxPackedPixel(0xFF, from[0], from[1], from[2]);
+ }
+
+ // bulk copy of pixels.
+ while (c >= 4) {
+ from -= 12;
+ to -= 4;
+ c -= 4;
+ GFX_BLOCK_RGB_TO_FRGB(from,to);
+ }
+
+ // copy remaining pixel(s)
+ // NB: can't use 32-bit reads, they might read off the end of the buffer
+ while (c--) {
+ from -= 3;
+ *--to = gfxPackedPixel(0xFF, from[0], from[1], from[2]);
+ }
+}
+
+LexerResult
+nsGIFDecoder2::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume)
+{
+ MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!");
+
+ return mLexer.Lex(aIterator, aOnResume,
+ [=](State aState, const char* aData, size_t aLength) {
+ switch(aState) {
+ case State::GIF_HEADER:
+ return ReadGIFHeader(aData);
+ case State::SCREEN_DESCRIPTOR:
+ return ReadScreenDescriptor(aData);
+ case State::GLOBAL_COLOR_TABLE:
+ return ReadGlobalColorTable(aData, aLength);
+ case State::FINISHED_GLOBAL_COLOR_TABLE:
+ return FinishedGlobalColorTable();
+ case State::BLOCK_HEADER:
+ return ReadBlockHeader(aData);
+ case State::EXTENSION_HEADER:
+ return ReadExtensionHeader(aData);
+ case State::GRAPHIC_CONTROL_EXTENSION:
+ return ReadGraphicControlExtension(aData);
+ case State::APPLICATION_IDENTIFIER:
+ return ReadApplicationIdentifier(aData);
+ case State::NETSCAPE_EXTENSION_SUB_BLOCK:
+ return ReadNetscapeExtensionSubBlock(aData);
+ case State::NETSCAPE_EXTENSION_DATA:
+ return ReadNetscapeExtensionData(aData);
+ case State::IMAGE_DESCRIPTOR:
+ return ReadImageDescriptor(aData);
+ case State::FINISH_IMAGE_DESCRIPTOR:
+ return FinishImageDescriptor(aData);
+ case State::LOCAL_COLOR_TABLE:
+ return ReadLocalColorTable(aData, aLength);
+ case State::FINISHED_LOCAL_COLOR_TABLE:
+ return FinishedLocalColorTable();
+ case State::IMAGE_DATA_BLOCK:
+ return ReadImageDataBlock(aData);
+ case State::IMAGE_DATA_SUB_BLOCK:
+ return ReadImageDataSubBlock(aData);
+ case State::LZW_DATA:
+ return ReadLZWData(aData, aLength);
+ case State::SKIP_LZW_DATA:
+ return Transition::ContinueUnbuffered(State::SKIP_LZW_DATA);
+ case State::FINISHED_LZW_DATA:
+ return Transition::To(State::IMAGE_DATA_SUB_BLOCK, SUB_BLOCK_HEADER_LEN);
+ case State::SKIP_SUB_BLOCKS:
+ return SkipSubBlocks(aData);
+ case State::SKIP_DATA_THEN_SKIP_SUB_BLOCKS:
+ return Transition::ContinueUnbuffered(State::SKIP_DATA_THEN_SKIP_SUB_BLOCKS);
+ case State::FINISHED_SKIPPING_DATA:
+ return Transition::To(State::SKIP_SUB_BLOCKS, SUB_BLOCK_HEADER_LEN);
+ default:
+ MOZ_CRASH("Unknown State");
+ }
+ });
+}
+
+LexerTransition<nsGIFDecoder2::State>
+nsGIFDecoder2::ReadGIFHeader(const char* aData)
+{
+ // We retrieve the version here but because many GIF encoders set header
+ // fields incorrectly, we barely use it; features which should only appear in
+ // GIF89a are always accepted.
+ if (strncmp(aData, "GIF87a", GIF_HEADER_LEN) == 0) {
+ mGIFStruct.version = 87;
+ } else if (strncmp(aData, "GIF89a", GIF_HEADER_LEN) == 0) {
+ mGIFStruct.version = 89;
+ } else {
+ return Transition::TerminateFailure();
+ }
+
+ return Transition::To(State::SCREEN_DESCRIPTOR, GIF_SCREEN_DESCRIPTOR_LEN);
+}
+
+LexerTransition<nsGIFDecoder2::State>
+nsGIFDecoder2::ReadScreenDescriptor(const char* aData)
+{
+ mGIFStruct.screen_width = LittleEndian::readUint16(aData + 0);
+ mGIFStruct.screen_height = LittleEndian::readUint16(aData + 2);
+
+ const uint8_t packedFields = aData[4];
+
+ // XXX: Should we be capturing these values even if there is no global color
+ // table?
+ mGIFStruct.global_colormap_depth =
+ (packedFields & PACKED_FIELDS_TABLE_DEPTH_MASK) + 1;
+ mGIFStruct.global_colormap_count = 1 << mGIFStruct.global_colormap_depth;
+
+ // We ignore several fields in the header. We don't care about the 'sort
+ // flag', which indicates if the global color table's entries are sorted in
+ // order of importance - if we need to render this image for a device with a
+ // narrower color gamut than GIF supports we'll handle that at a different
+ // layer. We have no use for the pixel aspect ratio as well. Finally, we
+ // intentionally ignore the background color index, as implementing that
+ // feature would not be web compatible - when a GIF image frame doesn't cover
+ // the entire area of the image, the area that's not covered should always be
+ // transparent.
+
+ if (packedFields & PACKED_FIELDS_COLOR_TABLE_BIT) {
+ MOZ_ASSERT(mColorTablePos == 0);
+
+ // We read the global color table in unbuffered mode since it can be quite
+ // large and it'd be preferable to avoid unnecessary copies.
+ const size_t globalColorTableSize = 3 * mGIFStruct.global_colormap_count;
+ return Transition::ToUnbuffered(State::FINISHED_GLOBAL_COLOR_TABLE,
+ State::GLOBAL_COLOR_TABLE,
+ globalColorTableSize);
+ }
+
+ return Transition::To(State::BLOCK_HEADER, BLOCK_HEADER_LEN);
+}
+
+LexerTransition<nsGIFDecoder2::State>
+nsGIFDecoder2::ReadGlobalColorTable(const char* aData, size_t aLength)
+{
+ uint8_t* dest = reinterpret_cast<uint8_t*>(mGIFStruct.global_colormap)
+ + mColorTablePos;
+ memcpy(dest, aData, aLength);
+ mColorTablePos += aLength;
+ return Transition::ContinueUnbuffered(State::GLOBAL_COLOR_TABLE);
+}
+
+LexerTransition<nsGIFDecoder2::State>
+nsGIFDecoder2::FinishedGlobalColorTable()
+{
+ ConvertColormap(mGIFStruct.global_colormap, mGIFStruct.global_colormap_count);
+ mColorTablePos = 0;
+ return Transition::To(State::BLOCK_HEADER, BLOCK_HEADER_LEN);
+}
+
+LexerTransition<nsGIFDecoder2::State>
+nsGIFDecoder2::ReadBlockHeader(const char* aData)
+{
+ // Determine what type of block we're dealing with.
+ switch (aData[0]) {
+ case GIF_EXTENSION_INTRODUCER:
+ return Transition::To(State::EXTENSION_HEADER, EXTENSION_HEADER_LEN);
+
+ case GIF_IMAGE_SEPARATOR:
+ return Transition::To(State::IMAGE_DESCRIPTOR, IMAGE_DESCRIPTOR_LEN);
+
+ case GIF_TRAILER:
+ FinishInternal();
+ return Transition::TerminateSuccess();
+
+ default:
+ // If we get anything other than GIF_IMAGE_SEPARATOR,
+ // GIF_EXTENSION_INTRODUCER, or GIF_TRAILER, there is extraneous data
+ // between blocks. The GIF87a spec tells us to keep reading until we find
+ // an image separator, but GIF89a says such a file is corrupt. We follow
+ // GIF89a and bail out.
+
+ if (mGIFStruct.images_decoded > 0) {
+ // The file is corrupt, but we successfully decoded some frames, so we
+ // may as well consider the decode successful and display them.
+ FinishInternal();
+ return Transition::TerminateSuccess();
+ }
+
+ // No images decoded; there is nothing to display.
+ return Transition::TerminateFailure();
+ }
+}
+
+LexerTransition<nsGIFDecoder2::State>
+nsGIFDecoder2::ReadExtensionHeader(const char* aData)
+{
+ const uint8_t label = aData[0];
+ const uint8_t extensionHeaderLength = aData[1];
+
+ // If the extension header is zero length, just treat it as a block terminator
+ // and move on to the next block immediately.
+ if (extensionHeaderLength == 0) {
+ return Transition::To(State::BLOCK_HEADER, BLOCK_HEADER_LEN);
+ }
+
+ switch (label) {
+ case GIF_GRAPHIC_CONTROL_LABEL:
+ // The GIF spec mandates that the Control Extension header block length is
+ // 4 bytes, and the parser for this block reads 4 bytes, so we must
+ // enforce that the buffer contains at least this many bytes. If the GIF
+ // specifies a different length, we allow that, so long as it's larger;
+ // the additional data will simply be ignored.
+ return Transition::To(State::GRAPHIC_CONTROL_EXTENSION,
+ max<uint8_t>(extensionHeaderLength,
+ GRAPHIC_CONTROL_EXTENSION_LEN));
+
+ case GIF_APPLICATION_EXTENSION_LABEL:
+ // Again, the spec specifies that an application extension header is 11
+ // bytes, but for compatibility with GIFs in the wild, we allow deviation
+ // from the spec. This is important for real-world compatibility, as GIFs
+ // in the wild exist with application extension headers that are both
+ // shorter and longer than 11 bytes. However, we only try to actually
+ // interpret the application extension if the length is correct;
+ // otherwise, we just skip the block unconditionally.
+ return extensionHeaderLength == APPLICATION_EXTENSION_LEN
+ ? Transition::To(State::APPLICATION_IDENTIFIER, extensionHeaderLength)
+ : Transition::ToUnbuffered(State::FINISHED_SKIPPING_DATA,
+ State::SKIP_DATA_THEN_SKIP_SUB_BLOCKS,
+ extensionHeaderLength);
+
+ default:
+ // Skip over any other type of extension block, including comment and
+ // plain text blocks.
+ return Transition::ToUnbuffered(State::FINISHED_SKIPPING_DATA,
+ State::SKIP_DATA_THEN_SKIP_SUB_BLOCKS,
+ extensionHeaderLength);
+ }
+}
+
+LexerTransition<nsGIFDecoder2::State>
+nsGIFDecoder2::ReadGraphicControlExtension(const char* aData)
+{
+ mGIFStruct.is_transparent = aData[0] & 0x1;
+ mGIFStruct.tpixel = uint8_t(aData[3]);
+ mGIFStruct.disposal_method = (aData[0] >> 2) & 0x7;
+
+ if (mGIFStruct.disposal_method == 4) {
+ // Some encoders (and apparently some specs) represent
+ // DisposalMethod::RESTORE_PREVIOUS as 4, but 3 is used in the canonical
+ // spec and is more popular, so we normalize to 3.
+ mGIFStruct.disposal_method = 3;
+ } else if (mGIFStruct.disposal_method > 4) {
+ // This GIF is using a disposal method which is undefined in the spec.
+ // Treat it as DisposalMethod::NOT_SPECIFIED.
+ mGIFStruct.disposal_method = 0;
+ }
+
+ DisposalMethod method = DisposalMethod(mGIFStruct.disposal_method);
+ if (method == DisposalMethod::CLEAR_ALL || method == DisposalMethod::CLEAR) {
+ // We may have to display the background under this image during animation
+ // playback, so we regard it as transparent.
+ PostHasTransparency();
+ }
+
+ mGIFStruct.delay_time = LittleEndian::readUint16(aData + 1) * 10;
+ if (mGIFStruct.delay_time > 0) {
+ PostIsAnimated(FrameTimeout::FromRawMilliseconds(mGIFStruct.delay_time));
+ }
+
+ return Transition::To(State::SKIP_SUB_BLOCKS, SUB_BLOCK_HEADER_LEN);
+}
+
+LexerTransition<nsGIFDecoder2::State>
+nsGIFDecoder2::ReadApplicationIdentifier(const char* aData)
+{
+ if ((strncmp(aData, "NETSCAPE2.0", 11) == 0) ||
+ (strncmp(aData, "ANIMEXTS1.0", 11) == 0)) {
+ // This is a Netscape application extension block.
+ return Transition::To(State::NETSCAPE_EXTENSION_SUB_BLOCK,
+ SUB_BLOCK_HEADER_LEN);
+ }
+
+ // This is an application extension we don't care about. Just skip it.
+ return Transition::To(State::SKIP_SUB_BLOCKS, SUB_BLOCK_HEADER_LEN);
+}
+
+LexerTransition<nsGIFDecoder2::State>
+nsGIFDecoder2::ReadNetscapeExtensionSubBlock(const char* aData)
+{
+ const uint8_t blockLength = aData[0];
+ if (blockLength == 0) {
+ // We hit the block terminator.
+ return Transition::To(State::BLOCK_HEADER, BLOCK_HEADER_LEN);
+ }
+
+ // We consume a minimum of 3 bytes in accordance with the specs for the
+ // Netscape application extension block, such as they are.
+ const size_t extensionLength = max<uint8_t>(blockLength, 3);
+ return Transition::To(State::NETSCAPE_EXTENSION_DATA, extensionLength);
+}
+
+LexerTransition<nsGIFDecoder2::State>
+nsGIFDecoder2::ReadNetscapeExtensionData(const char* aData)
+{
+ // Documentation for NETSCAPE2.0 / ANIMEXTS1.0 extensions can be found at:
+ // https://wiki.whatwg.org/wiki/GIF
+ static const uint8_t NETSCAPE_LOOPING_EXTENSION_SUB_BLOCK_ID = 1;
+ static const uint8_t NETSCAPE_BUFFERING_EXTENSION_SUB_BLOCK_ID = 2;
+
+ const uint8_t subBlockID = aData[0] & 7;
+ switch (subBlockID) {
+ case NETSCAPE_LOOPING_EXTENSION_SUB_BLOCK_ID:
+ // This is looping extension.
+ mGIFStruct.loop_count = LittleEndian::readUint16(aData + 1);
+ return Transition::To(State::NETSCAPE_EXTENSION_SUB_BLOCK,
+ SUB_BLOCK_HEADER_LEN);
+
+ case NETSCAPE_BUFFERING_EXTENSION_SUB_BLOCK_ID:
+ // We allow, but ignore, this extension.
+ return Transition::To(State::NETSCAPE_EXTENSION_SUB_BLOCK,
+ SUB_BLOCK_HEADER_LEN);
+
+ default:
+ return Transition::TerminateFailure();
+ }
+}
+
+LexerTransition<nsGIFDecoder2::State>
+nsGIFDecoder2::ReadImageDescriptor(const char* aData)
+{
+ // On the first frame, we don't need to yield, and none of the other checks
+ // below apply, so we can just jump right into FinishImageDescriptor().
+ if (mGIFStruct.images_decoded == 0) {
+ return FinishImageDescriptor(aData);
+ }
+
+ if (!HasAnimation()) {
+ // We should've already called PostIsAnimated(); this must be a corrupt
+ // animated image with a first frame timeout of zero. Signal that we're
+ // animated now, before the first-frame decode early exit below, so that
+ // RasterImage can detect that this happened.
+ PostIsAnimated(FrameTimeout::FromRawMilliseconds(0));
+ }
+
+ if (IsFirstFrameDecode()) {
+ // We're about to get a second frame, but we only want the first. Stop
+ // decoding now.
+ FinishInternal();
+ return Transition::TerminateSuccess();
+ }
+
+ MOZ_ASSERT(Size() == OutputSize(), "Downscaling an animated image?");
+
+ // Yield to allow access to the previous frame before we start a new one.
+ return Transition::ToAfterYield(State::FINISH_IMAGE_DESCRIPTOR);
+}
+
+LexerTransition<nsGIFDecoder2::State>
+nsGIFDecoder2::FinishImageDescriptor(const char* aData)
+{
+ IntRect frameRect;
+
+ // Get image offsets with respect to the screen origin.
+ frameRect.x = LittleEndian::readUint16(aData + 0);
+ frameRect.y = LittleEndian::readUint16(aData + 2);
+ frameRect.width = LittleEndian::readUint16(aData + 4);
+ frameRect.height = LittleEndian::readUint16(aData + 6);
+
+ if (!mGIFStruct.images_decoded) {
+ // Work around GIF files where
+ // * at least one of the logical screen dimensions is smaller than the
+ // same dimension in the first image, or
+ // * GIF87a files where the first image's dimensions do not match the
+ // logical screen dimensions.
+ if (mGIFStruct.screen_height < frameRect.height ||
+ mGIFStruct.screen_width < frameRect.width ||
+ mGIFStruct.version == 87) {
+ mGIFStruct.screen_height = frameRect.height;
+ mGIFStruct.screen_width = frameRect.width;
+ frameRect.MoveTo(0, 0);
+ }
+
+ // Create the image container with the right size.
+ BeginGIF();
+ if (HasError()) {
+ // Setting the size led to an error.
+ return Transition::TerminateFailure();
+ }
+
+ // If we're doing a metadata decode, we're done.
+ if (IsMetadataDecode()) {
+ CheckForTransparency(frameRect);
+ FinishInternal();
+ return Transition::TerminateSuccess();
+ }
+ }
+
+ // Work around broken GIF files that have zero frame width or height; in this
+ // case, we'll treat the frame as having the same size as the overall image.
+ if (frameRect.height == 0 || frameRect.width == 0) {
+ frameRect.height = mGIFStruct.screen_height;
+ frameRect.width = mGIFStruct.screen_width;
+
+ // If that still resulted in zero frame width or height, give up.
+ if (frameRect.height == 0 || frameRect.width == 0) {
+ return Transition::TerminateFailure();
+ }
+ }
+
+ // Determine |depth| (log base 2 of the number of colors in the palette).
+ bool haveLocalColorTable = false;
+ uint16_t depth = 0;
+ uint8_t packedFields = aData[8];
+
+ if (packedFields & PACKED_FIELDS_COLOR_TABLE_BIT) {
+ // Get the palette depth from the local color table.
+ depth = (packedFields & PACKED_FIELDS_TABLE_DEPTH_MASK) + 1;
+ haveLocalColorTable = true;
+ } else {
+ // Get the palette depth from the global color table.
+ depth = mGIFStruct.global_colormap_depth;
+ }
+
+ // If the transparent color index is greater than the number of colors in the
+ // color table, we may need a higher color depth than |depth| would specify.
+ // Our internal representation of the image will instead use |realDepth|,
+ // which is the smallest color depth that can accomodate the existing palette
+ // *and* the transparent color index.
+ uint16_t realDepth = depth;
+ while (mGIFStruct.tpixel >= (1 << realDepth) &&
+ realDepth < 8) {
+ realDepth++;
+ }
+
+ // Create a mask used to ensure that color values fit within the colormap.
+ mColorMask = 0xFF >> (8 - realDepth);
+
+ // Determine if this frame is interlaced or not.
+ const bool isInterlaced = packedFields & PACKED_FIELDS_INTERLACED_BIT;
+
+ // Create the SurfacePipe we'll use to write output for this frame.
+ if (NS_FAILED(BeginImageFrame(frameRect, realDepth, isInterlaced))) {
+ return Transition::TerminateFailure();
+ }
+
+ // Clear state from last image.
+ mGIFStruct.pixels_remaining = frameRect.width * frameRect.height;
+
+ if (haveLocalColorTable) {
+ // We have a local color table, so prepare to read it into the palette of
+ // the current frame.
+ mGIFStruct.local_colormap_size = 1 << depth;
+
+ if (mGIFStruct.images_decoded == 0) {
+ // The first frame has a local color table. Allocate space for it as we
+ // use a BGRA or BGRX surface for the first frame; such surfaces don't
+ // have their own palettes internally.
+ mColormapSize = sizeof(uint32_t) << realDepth;
+ if (!mGIFStruct.local_colormap) {
+ mGIFStruct.local_colormap =
+ static_cast<uint32_t*>(moz_xmalloc(mColormapSize));
+ }
+ mColormap = mGIFStruct.local_colormap;
+ }
+
+ const size_t size = 3 << depth;
+ if (mColormapSize > size) {
+ // Clear the part of the colormap which will be unused with this palette.
+ // If a GIF references an invalid palette entry, ensure the entry is opaque white.
+ // This is needed for Skia as if it isn't, RGBX surfaces will cause blending issues
+ // with Skia.
+ memset(reinterpret_cast<uint8_t*>(mColormap) + size, 0xFF,
+ mColormapSize - size);
+ }
+
+ MOZ_ASSERT(mColorTablePos == 0);
+
+ // We read the local color table in unbuffered mode since it can be quite
+ // large and it'd be preferable to avoid unnecessary copies.
+ return Transition::ToUnbuffered(State::FINISHED_LOCAL_COLOR_TABLE,
+ State::LOCAL_COLOR_TABLE,
+ size);
+ }
+
+ // There's no local color table; copy the global color table into the palette
+ // of the current frame.
+ if (mGIFStruct.images_decoded > 0) {
+ memcpy(mColormap, mGIFStruct.global_colormap, mColormapSize);
+ } else {
+ mColormap = mGIFStruct.global_colormap;
+ }
+
+ return Transition::To(State::IMAGE_DATA_BLOCK, BLOCK_HEADER_LEN);
+}
+
+LexerTransition<nsGIFDecoder2::State>
+nsGIFDecoder2::ReadLocalColorTable(const char* aData, size_t aLength)
+{
+ uint8_t* dest = reinterpret_cast<uint8_t*>(mColormap) + mColorTablePos;
+ memcpy(dest, aData, aLength);
+ mColorTablePos += aLength;
+ return Transition::ContinueUnbuffered(State::LOCAL_COLOR_TABLE);
+}
+
+LexerTransition<nsGIFDecoder2::State>
+nsGIFDecoder2::FinishedLocalColorTable()
+{
+ ConvertColormap(mColormap, mGIFStruct.local_colormap_size);
+ mColorTablePos = 0;
+ return Transition::To(State::IMAGE_DATA_BLOCK, BLOCK_HEADER_LEN);
+}
+
+LexerTransition<nsGIFDecoder2::State>
+nsGIFDecoder2::ReadImageDataBlock(const char* aData)
+{
+ // Make sure the transparent pixel is transparent in the colormap.
+ if (mGIFStruct.is_transparent) {
+ // Save the old value so we can restore it later.
+ if (mColormap == mGIFStruct.global_colormap) {
+ mOldColor = mColormap[mGIFStruct.tpixel];
+ }
+ mColormap[mGIFStruct.tpixel] = 0;
+ }
+
+ // Initialize the LZW decoder.
+ mGIFStruct.datasize = uint8_t(aData[0]);
+ const int clearCode = ClearCode();
+ if (mGIFStruct.datasize > MAX_LZW_BITS || clearCode >= MAX_BITS) {
+ return Transition::TerminateFailure();
+ }
+
+ mGIFStruct.avail = clearCode + 2;
+ mGIFStruct.oldcode = -1;
+ mGIFStruct.codesize = mGIFStruct.datasize + 1;
+ mGIFStruct.codemask = (1 << mGIFStruct.codesize) - 1;
+ mGIFStruct.datum = mGIFStruct.bits = 0;
+
+ // Initialize the tables.
+ for (int i = 0; i < clearCode; i++) {
+ mGIFStruct.suffix[i] = i;
+ }
+
+ mGIFStruct.stackp = mGIFStruct.stack;
+
+ // Begin reading image data sub-blocks.
+ return Transition::To(State::IMAGE_DATA_SUB_BLOCK, SUB_BLOCK_HEADER_LEN);
+}
+
+LexerTransition<nsGIFDecoder2::State>
+nsGIFDecoder2::ReadImageDataSubBlock(const char* aData)
+{
+ const uint8_t subBlockLength = aData[0];
+ if (subBlockLength == 0) {
+ // We hit the block terminator.
+ EndImageFrame();
+ return Transition::To(State::BLOCK_HEADER, BLOCK_HEADER_LEN);
+ }
+
+ if (mGIFStruct.pixels_remaining == 0) {
+ // We've already written to the entire image; we should've hit the block
+ // terminator at this point. This image is corrupt, but we'll tolerate it.
+
+ if (subBlockLength == GIF_TRAILER) {
+ // This GIF is missing the block terminator for the final block; we'll put
+ // up with it.
+ FinishInternal();
+ return Transition::TerminateSuccess();
+ }
+
+ // We're not at the end of the image, so just skip the extra data.
+ return Transition::ToUnbuffered(State::FINISHED_LZW_DATA,
+ State::SKIP_LZW_DATA,
+ subBlockLength);
+ }
+
+ // Handle the standard case: there's data in the sub-block and pixels left to
+ // fill in the image. We read the sub-block unbuffered so we can get pixels on
+ // the screen as soon as possible.
+ return Transition::ToUnbuffered(State::FINISHED_LZW_DATA,
+ State::LZW_DATA,
+ subBlockLength);
+}
+
+LexerTransition<nsGIFDecoder2::State>
+nsGIFDecoder2::ReadLZWData(const char* aData, size_t aLength)
+{
+ const uint8_t* data = reinterpret_cast<const uint8_t*>(aData);
+ size_t length = aLength;
+
+ while (mGIFStruct.pixels_remaining > 0 &&
+ (length > 0 || mGIFStruct.bits >= mGIFStruct.codesize)) {
+ size_t bytesRead = 0;
+
+ auto result = mGIFStruct.images_decoded == 0
+ ? mPipe.WritePixels<uint32_t>([&]{ return YieldPixel<uint32_t>(data, length, &bytesRead); })
+ : mPipe.WritePixels<uint8_t>([&]{ return YieldPixel<uint8_t>(data, length, &bytesRead); });
+
+ if (MOZ_UNLIKELY(bytesRead > length)) {
+ MOZ_ASSERT_UNREACHABLE("Overread?");
+ bytesRead = length;
+ }
+
+ // Advance our position in the input based upon what YieldPixel() consumed.
+ data += bytesRead;
+ length -= bytesRead;
+
+ switch (result) {
+ case WriteState::NEED_MORE_DATA:
+ continue;
+
+ case WriteState::FINISHED:
+ NS_WARNING_ASSERTION(mGIFStruct.pixels_remaining <= 0,
+ "too many pixels");
+ mGIFStruct.pixels_remaining = 0;
+ break;
+
+ case WriteState::FAILURE:
+ return Transition::TerminateFailure();
+ }
+ }
+
+ // We're done, but keep going until we consume all the data in the sub-block.
+ return Transition::ContinueUnbuffered(State::LZW_DATA);
+}
+
+LexerTransition<nsGIFDecoder2::State>
+nsGIFDecoder2::SkipSubBlocks(const char* aData)
+{
+ // In the SKIP_SUB_BLOCKS state we skip over data sub-blocks that we're not
+ // interested in. Blocks consist of a block header (which can be up to 255
+ // bytes in length) and a series of data sub-blocks. Each data sub-block
+ // consists of a single byte length value, followed by the data itself. A data
+ // sub-block with a length of zero terminates the overall block.
+ // SKIP_SUB_BLOCKS reads a sub-block length value. If it's zero, we've arrived
+ // at the next block. Otherwise, we enter the SKIP_DATA_THEN_SKIP_SUB_BLOCKS
+ // state to skip over the sub-block data and return to SKIP_SUB_BLOCKS at the
+ // start of the next sub-block.
+
+ const uint8_t nextSubBlockLength = aData[0];
+ if (nextSubBlockLength == 0) {
+ // We hit the block terminator, so the sequence of data sub-blocks is over;
+ // begin processing another block.
+ return Transition::To(State::BLOCK_HEADER, BLOCK_HEADER_LEN);
+ }
+
+ // Skip to the next sub-block length value.
+ return Transition::ToUnbuffered(State::FINISHED_SKIPPING_DATA,
+ State::SKIP_DATA_THEN_SKIP_SUB_BLOCKS,
+ nextSubBlockLength);
+}
+
+Maybe<Telemetry::ID>
+nsGIFDecoder2::SpeedHistogram() const
+{
+ return Some(Telemetry::IMAGE_DECODE_SPEED_GIF);
+}
+
+} // namespace image
+} // namespace mozilla
diff --git a/image/decoders/nsGIFDecoder2.h b/image/decoders/nsGIFDecoder2.h
new file mode 100644
index 000000000..c903ce890
--- /dev/null
+++ b/image/decoders/nsGIFDecoder2.h
@@ -0,0 +1,152 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_image_decoders_nsGIFDecoder2_h
+#define mozilla_image_decoders_nsGIFDecoder2_h
+
+#include "Decoder.h"
+#include "GIF2.h"
+#include "StreamingLexer.h"
+#include "SurfacePipe.h"
+
+namespace mozilla {
+namespace image {
+class RasterImage;
+
+//////////////////////////////////////////////////////////////////////
+// nsGIFDecoder2 Definition
+
+class nsGIFDecoder2 : public Decoder
+{
+public:
+ ~nsGIFDecoder2();
+
+protected:
+ LexerResult DoDecode(SourceBufferIterator& aIterator,
+ IResumable* aOnResume) override;
+ nsresult FinishInternal() override;
+
+ Maybe<Telemetry::ID> SpeedHistogram() const override;
+
+private:
+ friend class DecoderFactory;
+
+ // Decoders should only be instantiated via DecoderFactory.
+ explicit nsGIFDecoder2(RasterImage* aImage);
+
+ /// Called when we begin decoding the image.
+ void BeginGIF();
+
+ /**
+ * Called when we begin decoding a frame.
+ *
+ * @param aFrameRect The region of the image that contains data. The region
+ * outside this rect is transparent.
+ * @param aDepth The palette depth of this frame.
+ * @param aIsInterlaced If true, this frame is an interlaced frame.
+ */
+ nsresult BeginImageFrame(const gfx::IntRect& aFrameRect,
+ uint16_t aDepth,
+ bool aIsInterlaced);
+
+ /// Called when we finish decoding a frame.
+ void EndImageFrame();
+
+ /// Called when we finish decoding the entire image.
+ void FlushImageData();
+
+ /// Transforms a palette index into a pixel.
+ template <typename PixelSize> PixelSize
+ ColormapIndexToPixel(uint8_t aIndex);
+
+ /// A generator function that performs LZW decompression and yields pixels.
+ template <typename PixelSize> NextPixel<PixelSize>
+ YieldPixel(const uint8_t* aData, size_t aLength, size_t* aBytesReadOut);
+
+ /// Checks if we have transparency, either because the header indicates that
+ /// there's alpha, or because the frame rect doesn't cover the entire image.
+ bool CheckForTransparency(const gfx::IntRect& aFrameRect);
+
+ // @return the clear code used for LZW decompression.
+ int ClearCode() const { return 1 << mGIFStruct.datasize; }
+
+ enum class State
+ {
+ FAILURE,
+ SUCCESS,
+ GIF_HEADER,
+ SCREEN_DESCRIPTOR,
+ GLOBAL_COLOR_TABLE,
+ FINISHED_GLOBAL_COLOR_TABLE,
+ BLOCK_HEADER,
+ EXTENSION_HEADER,
+ GRAPHIC_CONTROL_EXTENSION,
+ APPLICATION_IDENTIFIER,
+ NETSCAPE_EXTENSION_SUB_BLOCK,
+ NETSCAPE_EXTENSION_DATA,
+ IMAGE_DESCRIPTOR,
+ FINISH_IMAGE_DESCRIPTOR,
+ LOCAL_COLOR_TABLE,
+ FINISHED_LOCAL_COLOR_TABLE,
+ IMAGE_DATA_BLOCK,
+ IMAGE_DATA_SUB_BLOCK,
+ LZW_DATA,
+ SKIP_LZW_DATA,
+ FINISHED_LZW_DATA,
+ SKIP_SUB_BLOCKS,
+ SKIP_DATA_THEN_SKIP_SUB_BLOCKS,
+ FINISHED_SKIPPING_DATA
+ };
+
+ LexerTransition<State> ReadGIFHeader(const char* aData);
+ LexerTransition<State> ReadScreenDescriptor(const char* aData);
+ LexerTransition<State> ReadGlobalColorTable(const char* aData, size_t aLength);
+ LexerTransition<State> FinishedGlobalColorTable();
+ LexerTransition<State> ReadBlockHeader(const char* aData);
+ LexerTransition<State> ReadExtensionHeader(const char* aData);
+ LexerTransition<State> ReadGraphicControlExtension(const char* aData);
+ LexerTransition<State> ReadApplicationIdentifier(const char* aData);
+ LexerTransition<State> ReadNetscapeExtensionSubBlock(const char* aData);
+ LexerTransition<State> ReadNetscapeExtensionData(const char* aData);
+ LexerTransition<State> ReadImageDescriptor(const char* aData);
+ LexerTransition<State> FinishImageDescriptor(const char* aData);
+ LexerTransition<State> ReadLocalColorTable(const char* aData, size_t aLength);
+ LexerTransition<State> FinishedLocalColorTable();
+ LexerTransition<State> ReadImageDataBlock(const char* aData);
+ LexerTransition<State> ReadImageDataSubBlock(const char* aData);
+ LexerTransition<State> ReadLZWData(const char* aData, size_t aLength);
+ LexerTransition<State> SkipSubBlocks(const char* aData);
+
+ // The StreamingLexer used to manage input. The initial size of the buffer is
+ // chosen as a little larger than the maximum size of any fixed-length data we
+ // have to read for a state. We read variable-length data in unbuffered mode
+ // so the buffer shouldn't have to be resized during decoding.
+ StreamingLexer<State, 16> mLexer;
+
+ uint32_t mOldColor; // The old value of the transparent pixel
+
+ // The frame number of the currently-decoding frame when we're in the middle
+ // of decoding it, and -1 otherwise.
+ int32_t mCurrentFrameIndex;
+
+ // When we're reading in the global or local color table, this records our
+ // current position - i.e., the offset into which the next byte should be
+ // written.
+ size_t mColorTablePos;
+
+ uint8_t mColorMask; // Apply this to the pixel to keep within colormap
+ bool mGIFOpen;
+ bool mSawTransparency;
+
+ gif_struct mGIFStruct;
+
+ SurfacePipe mPipe; /// The SurfacePipe used to write to the output surface.
+};
+
+} // namespace image
+} // namespace mozilla
+
+#endif // mozilla_image_decoders_nsGIFDecoder2_h
diff --git a/image/decoders/nsICODecoder.cpp b/image/decoders/nsICODecoder.cpp
new file mode 100644
index 000000000..633bb1255
--- /dev/null
+++ b/image/decoders/nsICODecoder.cpp
@@ -0,0 +1,673 @@
+/* vim:set tw=80 expandtab softtabstop=2 ts=2 sw=2: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* This is a Cross-Platform ICO Decoder, which should work everywhere, including
+ * Big-Endian machines like the PowerPC. */
+
+#include "nsICODecoder.h"
+
+#include <stdlib.h>
+
+#include "mozilla/EndianUtils.h"
+#include "mozilla/Move.h"
+
+#include "RasterImage.h"
+
+using namespace mozilla::gfx;
+
+namespace mozilla {
+namespace image {
+
+// Constants.
+static const uint32_t ICOHEADERSIZE = 6;
+static const uint32_t BITMAPINFOSIZE = bmp::InfoHeaderLength::WIN_ICO;
+
+// ----------------------------------------
+// Actual Data Processing
+// ----------------------------------------
+
+// Obtains the number of colors from the bits per pixel
+uint16_t
+nsICODecoder::GetNumColors()
+{
+ uint16_t numColors = 0;
+ if (mBPP <= 8) {
+ switch (mBPP) {
+ case 1:
+ numColors = 2;
+ break;
+ case 4:
+ numColors = 16;
+ break;
+ case 8:
+ numColors = 256;
+ break;
+ default:
+ numColors = (uint16_t)-1;
+ }
+ }
+ return numColors;
+}
+
+nsICODecoder::nsICODecoder(RasterImage* aImage)
+ : Decoder(aImage)
+ , mLexer(Transition::To(ICOState::HEADER, ICOHEADERSIZE),
+ Transition::TerminateSuccess())
+ , mBiggestResourceColorDepth(0)
+ , mBestResourceDelta(INT_MIN)
+ , mBestResourceColorDepth(0)
+ , mNumIcons(0)
+ , mCurrIcon(0)
+ , mBPP(0)
+ , mMaskRowSize(0)
+ , mCurrMaskLine(0)
+ , mIsCursor(false)
+ , mHasMaskAlpha(false)
+{ }
+
+nsresult
+nsICODecoder::FinishInternal()
+{
+ // We shouldn't be called in error cases
+ MOZ_ASSERT(!HasError(), "Shouldn't call FinishInternal after error!");
+
+ return GetFinalStateFromContainedDecoder();
+}
+
+nsresult
+nsICODecoder::FinishWithErrorInternal()
+{
+ return GetFinalStateFromContainedDecoder();
+}
+
+nsresult
+nsICODecoder::GetFinalStateFromContainedDecoder()
+{
+ if (!mContainedDecoder) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mContainedSourceBuffer,
+ "Should have a SourceBuffer if we have a decoder");
+
+ // Let the contained decoder finish up if necessary.
+ if (!mContainedSourceBuffer->IsComplete()) {
+ mContainedSourceBuffer->Complete(NS_OK);
+ mContainedDecoder->Decode();
+ }
+
+ // Make our state the same as the state of the contained decoder.
+ mDecodeDone = mContainedDecoder->GetDecodeDone();
+ mProgress |= mContainedDecoder->TakeProgress();
+ mInvalidRect.UnionRect(mInvalidRect, mContainedDecoder->TakeInvalidRect());
+ mCurrentFrame = mContainedDecoder->GetCurrentFrameRef();
+
+ // Propagate errors.
+ nsresult rv = HasError() || mContainedDecoder->HasError()
+ ? NS_ERROR_FAILURE
+ : NS_OK;
+
+ MOZ_ASSERT(NS_FAILED(rv) || !mCurrentFrame || mCurrentFrame->IsFinished());
+ return rv;
+}
+
+bool
+nsICODecoder::CheckAndFixBitmapSize(int8_t* aBIH)
+{
+ // Get the width from the BMP file information header. This is
+ // (unintuitively) a signed integer; see the documentation at:
+ //
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx
+ //
+ // However, we reject negative widths since they aren't meaningful.
+ const int32_t width = LittleEndian::readInt32(aBIH + 4);
+ if (width <= 0 || width > 256) {
+ return false;
+ }
+
+ // Verify that the BMP width matches the width we got from the ICO directory
+ // entry. If not, decoding fails, because if we were to allow it to continue
+ // the intrinsic size of the image wouldn't match the size of the decoded
+ // surface.
+ if (width != int32_t(GetRealWidth())) {
+ return false;
+ }
+
+ // Get the height from the BMP file information header. This is also signed,
+ // but in this case negative values are meaningful; see below.
+ int32_t height = LittleEndian::readInt32(aBIH + 8);
+ if (height == 0) {
+ return false;
+ }
+
+ // BMPs can be stored inverted by having a negative height.
+ // XXX(seth): Should we really be writing the absolute value into the BIH
+ // below? Seems like this could be problematic for inverted BMPs.
+ height = abs(height);
+
+ // The height field is double the actual height of the image to account for
+ // the AND mask. This is true even if the AND mask is not present.
+ height /= 2;
+ if (height > 256) {
+ return false;
+ }
+
+ // Verify that the BMP height matches the height we got from the ICO directory
+ // entry. If not, again, decoding fails.
+ if (height != int32_t(GetRealHeight())) {
+ return false;
+ }
+
+ // Fix the BMP height in the BIH so that the BMP decoder, which does not know
+ // about the AND mask that may follow the actual bitmap, can work properly.
+ LittleEndian::writeInt32(aBIH + 8, GetRealHeight());
+
+ return true;
+}
+
+LexerTransition<ICOState>
+nsICODecoder::ReadHeader(const char* aData)
+{
+ // If the third byte is 1, this is an icon. If 2, a cursor.
+ if ((aData[2] != 1) && (aData[2] != 2)) {
+ return Transition::TerminateFailure();
+ }
+ mIsCursor = (aData[2] == 2);
+
+ // The fifth and sixth bytes specify the number of resources in the file.
+ mNumIcons = LittleEndian::readUint16(aData + 4);
+ if (mNumIcons == 0) {
+ return Transition::TerminateSuccess(); // Nothing to do.
+ }
+
+ // Downscale-during-decode can end up decoding different resources in the ICO
+ // file depending on the target size. Since the resources are not necessarily
+ // scaled versions of the same image, some may be transparent and some may not
+ // be. We could be precise about transparency if we decoded the metadata of
+ // every resource, but for now we don't and it's safest to assume that
+ // transparency could be present.
+ PostHasTransparency();
+
+ return Transition::To(ICOState::DIR_ENTRY, ICODIRENTRYSIZE);
+}
+
+size_t
+nsICODecoder::FirstResourceOffset() const
+{
+ MOZ_ASSERT(mNumIcons > 0,
+ "Calling FirstResourceOffset before processing header");
+
+ // The first resource starts right after the directory, which starts right
+ // after the ICO header.
+ return ICOHEADERSIZE + mNumIcons * ICODIRENTRYSIZE;
+}
+
+LexerTransition<ICOState>
+nsICODecoder::ReadDirEntry(const char* aData)
+{
+ mCurrIcon++;
+
+ // Read the directory entry.
+ IconDirEntry e;
+ e.mWidth = aData[0];
+ e.mHeight = aData[1];
+ e.mColorCount = aData[2];
+ e.mReserved = aData[3];
+ e.mPlanes = LittleEndian::readUint16(aData + 4);
+ e.mBitCount = LittleEndian::readUint16(aData + 6);
+ e.mBytesInRes = LittleEndian::readUint32(aData + 8);
+ e.mImageOffset = LittleEndian::readUint32(aData + 12);
+
+ // If an explicit output size was specified, we'll try to select the resource
+ // that matches it best below.
+ const Maybe<IntSize> desiredSize = ExplicitOutputSize();
+
+ // Determine if this is the biggest resource we've seen so far. We always use
+ // the biggest resource for the intrinsic size, and if we don't have a
+ // specific desired size, we select it as the best resource as well.
+ IntSize entrySize(GetRealWidth(e), GetRealHeight(e));
+ if (e.mBitCount >= mBiggestResourceColorDepth &&
+ entrySize.width * entrySize.height >=
+ mBiggestResourceSize.width * mBiggestResourceSize.height) {
+ mBiggestResourceSize = entrySize;
+ mBiggestResourceColorDepth = e.mBitCount;
+ mBiggestResourceHotSpot = IntSize(e.mXHotspot, e.mYHotspot);
+
+ if (!desiredSize) {
+ mDirEntry = e;
+ }
+ }
+
+ if (desiredSize) {
+ // Calculate the delta between this resource's size and the desired size, so
+ // we can see if it is better than our current-best option. In the case of
+ // several equally-good resources, we use the last one. "Better" in this
+ // case is determined by |delta|, a measure of the difference in size
+ // between the entry we've found and the desired size. We will choose the
+ // smallest resource that is greater than or equal to the desired size (i.e.
+ // we assume it's better to downscale a larger icon than to upscale a
+ // smaller one).
+ int32_t delta = std::min(entrySize.width - desiredSize->width,
+ entrySize.height - desiredSize->height);
+ if (e.mBitCount >= mBestResourceColorDepth &&
+ ((mBestResourceDelta < 0 && delta >= mBestResourceDelta) ||
+ (delta >= 0 && delta <= mBestResourceDelta))) {
+ mBestResourceDelta = delta;
+ mBestResourceColorDepth = e.mBitCount;
+ mDirEntry = e;
+ }
+ }
+
+ if (mCurrIcon == mNumIcons) {
+ // Ensure the resource we selected has an offset past the ICO headers.
+ if (mDirEntry.mImageOffset < FirstResourceOffset()) {
+ return Transition::TerminateFailure();
+ }
+
+ // If this is a cursor, set the hotspot. We use the hotspot from the biggest
+ // resource since we also use that resource for the intrinsic size.
+ if (mIsCursor) {
+ mImageMetadata.SetHotspot(mBiggestResourceHotSpot.width,
+ mBiggestResourceHotSpot.height);
+ }
+
+ // We always report the biggest resource's size as the intrinsic size; this
+ // is necessary for downscale-during-decode to work since we won't even
+ // attempt to *upscale* while decoding.
+ PostSize(mBiggestResourceSize.width, mBiggestResourceSize.height);
+ if (IsMetadataDecode()) {
+ return Transition::TerminateSuccess();
+ }
+
+ // If the resource we selected matches the output size perfectly, we don't
+ // need to do any downscaling.
+ if (GetRealSize() == OutputSize()) {
+ MOZ_ASSERT_IF(desiredSize, GetRealSize() == *desiredSize);
+ MOZ_ASSERT_IF(!desiredSize, GetRealSize() == Size());
+ mDownscaler.reset();
+ }
+
+ size_t offsetToResource = mDirEntry.mImageOffset - FirstResourceOffset();
+ return Transition::ToUnbuffered(ICOState::FOUND_RESOURCE,
+ ICOState::SKIP_TO_RESOURCE,
+ offsetToResource);
+ }
+
+ return Transition::To(ICOState::DIR_ENTRY, ICODIRENTRYSIZE);
+}
+
+LexerTransition<ICOState>
+nsICODecoder::SniffResource(const char* aData)
+{
+ // We use the first PNGSIGNATURESIZE bytes to determine whether this resource
+ // is a PNG or a BMP.
+ bool isPNG = !memcmp(aData, nsPNGDecoder::pngSignatureBytes,
+ PNGSIGNATURESIZE);
+ if (isPNG) {
+ // Create a PNG decoder which will do the rest of the work for us.
+ mContainedSourceBuffer = new SourceBuffer();
+ mContainedSourceBuffer->ExpectLength(mDirEntry.mBytesInRes);
+ mContainedDecoder =
+ DecoderFactory::CreateDecoderForICOResource(DecoderType::PNG,
+ WrapNotNull(mContainedSourceBuffer),
+ WrapNotNull(this));
+
+ if (!WriteToContainedDecoder(aData, PNGSIGNATURESIZE)) {
+ return Transition::TerminateFailure();
+ }
+
+ if (mDirEntry.mBytesInRes <= PNGSIGNATURESIZE) {
+ return Transition::TerminateFailure();
+ }
+
+ // Read in the rest of the PNG unbuffered.
+ size_t toRead = mDirEntry.mBytesInRes - PNGSIGNATURESIZE;
+ return Transition::ToUnbuffered(ICOState::FINISHED_RESOURCE,
+ ICOState::READ_PNG,
+ toRead);
+ } else {
+ // Make sure we have a sane size for the bitmap information header.
+ int32_t bihSize = LittleEndian::readUint32(aData);
+ if (bihSize != static_cast<int32_t>(BITMAPINFOSIZE)) {
+ return Transition::TerminateFailure();
+ }
+
+ // Buffer the first part of the bitmap information header.
+ memcpy(mBIHraw, aData, PNGSIGNATURESIZE);
+
+ // Read in the rest of the bitmap information header.
+ return Transition::To(ICOState::READ_BIH,
+ BITMAPINFOSIZE - PNGSIGNATURESIZE);
+ }
+}
+
+LexerTransition<ICOState>
+nsICODecoder::ReadPNG(const char* aData, uint32_t aLen)
+{
+ if (!WriteToContainedDecoder(aData, aLen)) {
+ return Transition::TerminateFailure();
+ }
+
+ // Raymond Chen says that 32bpp only are valid PNG ICOs
+ // http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx
+ if (!static_cast<nsPNGDecoder*>(mContainedDecoder.get())->IsValidICO()) {
+ return Transition::TerminateFailure();
+ }
+
+ return Transition::ContinueUnbuffered(ICOState::READ_PNG);
+}
+
+LexerTransition<ICOState>
+nsICODecoder::ReadBIH(const char* aData)
+{
+ // Buffer the rest of the bitmap information header.
+ memcpy(mBIHraw + PNGSIGNATURESIZE, aData, BITMAPINFOSIZE - PNGSIGNATURESIZE);
+
+ // Extract the BPP from the BIH header; it should be trusted over the one
+ // we have from the ICO header which is usually set to 0.
+ mBPP = LittleEndian::readUint16(mBIHraw + 14);
+
+ // The ICO format when containing a BMP does not include the 14 byte
+ // bitmap file header. So we create the BMP decoder via the constructor that
+ // tells it to skip this, and pass in the required data (dataOffset) that
+ // would have been present in the header.
+ uint32_t dataOffset = bmp::FILE_HEADER_LENGTH + BITMAPINFOSIZE;
+ if (mDirEntry.mBitCount <= 8) {
+ // The color table is present only if BPP is <= 8.
+ uint16_t numColors = GetNumColors();
+ if (numColors == (uint16_t)-1) {
+ return Transition::TerminateFailure();
+ }
+ dataOffset += 4 * numColors;
+ }
+
+ // Create a BMP decoder which will do most of the work for us; the exception
+ // is the AND mask, which isn't present in standalone BMPs.
+ mContainedSourceBuffer = new SourceBuffer();
+ mContainedSourceBuffer->ExpectLength(mDirEntry.mBytesInRes);
+ mContainedDecoder =
+ DecoderFactory::CreateDecoderForICOResource(DecoderType::BMP,
+ WrapNotNull(mContainedSourceBuffer),
+ WrapNotNull(this),
+ Some(dataOffset));
+ RefPtr<nsBMPDecoder> bmpDecoder =
+ static_cast<nsBMPDecoder*>(mContainedDecoder.get());
+
+ // Verify that the BIH width and height values match the ICO directory entry,
+ // and fix the BIH height value to compensate for the fact that the underlying
+ // BMP decoder doesn't know about AND masks.
+ if (!CheckAndFixBitmapSize(reinterpret_cast<int8_t*>(mBIHraw))) {
+ return Transition::TerminateFailure();
+ }
+
+ // Write out the BMP's bitmap info header.
+ if (!WriteToContainedDecoder(mBIHraw, sizeof(mBIHraw))) {
+ return Transition::TerminateFailure();
+ }
+
+ // Check to make sure we have valid color settings.
+ uint16_t numColors = GetNumColors();
+ if (numColors == uint16_t(-1)) {
+ return Transition::TerminateFailure();
+ }
+
+ // Do we have an AND mask on this BMP? If so, we need to read it after we read
+ // the BMP data itself.
+ uint32_t bmpDataLength = bmpDecoder->GetCompressedImageSize() + 4 * numColors;
+ bool hasANDMask = (BITMAPINFOSIZE + bmpDataLength) < mDirEntry.mBytesInRes;
+ ICOState afterBMPState = hasANDMask ? ICOState::PREPARE_FOR_MASK
+ : ICOState::FINISHED_RESOURCE;
+
+ // Read in the rest of the BMP unbuffered.
+ return Transition::ToUnbuffered(afterBMPState,
+ ICOState::READ_BMP,
+ bmpDataLength);
+}
+
+LexerTransition<ICOState>
+nsICODecoder::ReadBMP(const char* aData, uint32_t aLen)
+{
+ if (!WriteToContainedDecoder(aData, aLen)) {
+ return Transition::TerminateFailure();
+ }
+
+ return Transition::ContinueUnbuffered(ICOState::READ_BMP);
+}
+
+LexerTransition<ICOState>
+nsICODecoder::PrepareForMask()
+{
+ RefPtr<nsBMPDecoder> bmpDecoder =
+ static_cast<nsBMPDecoder*>(mContainedDecoder.get());
+
+ uint16_t numColors = GetNumColors();
+ MOZ_ASSERT(numColors != uint16_t(-1));
+
+ // Determine the length of the AND mask.
+ uint32_t bmpLengthWithHeader =
+ BITMAPINFOSIZE + bmpDecoder->GetCompressedImageSize() + 4 * numColors;
+ MOZ_ASSERT(bmpLengthWithHeader < mDirEntry.mBytesInRes);
+ uint32_t maskLength = mDirEntry.mBytesInRes - bmpLengthWithHeader;
+
+ // If the BMP provides its own transparency, we ignore the AND mask. We can
+ // also obviously ignore it if the image has zero width or zero height.
+ if (bmpDecoder->HasTransparency() ||
+ GetRealWidth() == 0 || GetRealHeight() == 0) {
+ return Transition::ToUnbuffered(ICOState::FINISHED_RESOURCE,
+ ICOState::SKIP_MASK,
+ maskLength);
+ }
+
+ // Compute the row size for the mask.
+ mMaskRowSize = ((GetRealWidth() + 31) / 32) * 4; // + 31 to round up
+
+ // If the expected size of the AND mask is larger than its actual size, then
+ // we must have a truncated (and therefore corrupt) AND mask.
+ uint32_t expectedLength = mMaskRowSize * GetRealHeight();
+ if (maskLength < expectedLength) {
+ return Transition::TerminateFailure();
+ }
+
+ // If we're downscaling, the mask is the wrong size for the surface we've
+ // produced, so we need to downscale the mask into a temporary buffer and then
+ // combine the mask's alpha values with the color values from the image.
+ if (mDownscaler) {
+ MOZ_ASSERT(bmpDecoder->GetImageDataLength() ==
+ mDownscaler->TargetSize().width *
+ mDownscaler->TargetSize().height *
+ sizeof(uint32_t));
+ mMaskBuffer = MakeUnique<uint8_t[]>(bmpDecoder->GetImageDataLength());
+ nsresult rv = mDownscaler->BeginFrame(GetRealSize(), Nothing(),
+ mMaskBuffer.get(),
+ /* aHasAlpha = */ true,
+ /* aFlipVertically = */ true);
+ if (NS_FAILED(rv)) {
+ return Transition::TerminateFailure();
+ }
+ }
+
+ mCurrMaskLine = GetRealHeight();
+ return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize);
+}
+
+
+LexerTransition<ICOState>
+nsICODecoder::ReadMaskRow(const char* aData)
+{
+ mCurrMaskLine--;
+
+ uint8_t sawTransparency = 0;
+
+ // Get the mask row we're reading.
+ const uint8_t* mask = reinterpret_cast<const uint8_t*>(aData);
+ const uint8_t* maskRowEnd = mask + mMaskRowSize;
+
+ // Get the corresponding row of the mask buffer (if we're downscaling) or the
+ // decoded image data (if we're not).
+ uint32_t* decoded = nullptr;
+ if (mDownscaler) {
+ // Initialize the row to all white and fully opaque.
+ memset(mDownscaler->RowBuffer(), 0xFF, GetRealWidth() * sizeof(uint32_t));
+
+ decoded = reinterpret_cast<uint32_t*>(mDownscaler->RowBuffer());
+ } else {
+ RefPtr<nsBMPDecoder> bmpDecoder =
+ static_cast<nsBMPDecoder*>(mContainedDecoder.get());
+ uint32_t* imageData = bmpDecoder->GetImageData();
+ if (!imageData) {
+ return Transition::TerminateFailure();
+ }
+
+ decoded = imageData + mCurrMaskLine * GetRealWidth();
+ }
+
+ MOZ_ASSERT(decoded);
+ uint32_t* decodedRowEnd = decoded + GetRealWidth();
+
+ // Iterate simultaneously through the AND mask and the image data.
+ while (mask < maskRowEnd) {
+ uint8_t idx = *mask++;
+ sawTransparency |= idx;
+ for (uint8_t bit = 0x80; bit && decoded < decodedRowEnd; bit >>= 1) {
+ // Clear pixel completely for transparency.
+ if (idx & bit) {
+ *decoded = 0;
+ }
+ decoded++;
+ }
+ }
+
+ if (mDownscaler) {
+ mDownscaler->CommitRow();
+ }
+
+ // If any bits are set in sawTransparency, then we know at least one pixel was
+ // transparent.
+ if (sawTransparency) {
+ mHasMaskAlpha = true;
+ }
+
+ if (mCurrMaskLine == 0) {
+ return Transition::To(ICOState::FINISH_MASK, 0);
+ }
+
+ return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize);
+}
+
+LexerTransition<ICOState>
+nsICODecoder::FinishMask()
+{
+ // If we're downscaling, we now have the appropriate alpha values in
+ // mMaskBuffer. We just need to transfer them to the image.
+ if (mDownscaler) {
+ // Retrieve the image data.
+ RefPtr<nsBMPDecoder> bmpDecoder =
+ static_cast<nsBMPDecoder*>(mContainedDecoder.get());
+ uint8_t* imageData = reinterpret_cast<uint8_t*>(bmpDecoder->GetImageData());
+ if (!imageData) {
+ return Transition::TerminateFailure();
+ }
+
+ // Iterate through the alpha values, copying from mask to image.
+ MOZ_ASSERT(mMaskBuffer);
+ MOZ_ASSERT(bmpDecoder->GetImageDataLength() > 0);
+ for (size_t i = 3 ; i < bmpDecoder->GetImageDataLength() ; i += 4) {
+ imageData[i] = mMaskBuffer[i];
+ }
+ }
+
+ return Transition::To(ICOState::FINISHED_RESOURCE, 0);
+}
+
+LexerTransition<ICOState>
+nsICODecoder::FinishResource()
+{
+ // Make sure the actual size of the resource matches the size in the directory
+ // entry. If not, we consider the image corrupt.
+ if (mContainedDecoder->HasSize() &&
+ mContainedDecoder->Size() != GetRealSize()) {
+ return Transition::TerminateFailure();
+ }
+
+ return Transition::TerminateSuccess();
+}
+
+LexerResult
+nsICODecoder::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume)
+{
+ MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!");
+
+ return mLexer.Lex(aIterator, aOnResume,
+ [=](ICOState aState, const char* aData, size_t aLength) {
+ switch (aState) {
+ case ICOState::HEADER:
+ return ReadHeader(aData);
+ case ICOState::DIR_ENTRY:
+ return ReadDirEntry(aData);
+ case ICOState::SKIP_TO_RESOURCE:
+ return Transition::ContinueUnbuffered(ICOState::SKIP_TO_RESOURCE);
+ case ICOState::FOUND_RESOURCE:
+ return Transition::To(ICOState::SNIFF_RESOURCE, PNGSIGNATURESIZE);
+ case ICOState::SNIFF_RESOURCE:
+ return SniffResource(aData);
+ case ICOState::READ_PNG:
+ return ReadPNG(aData, aLength);
+ case ICOState::READ_BIH:
+ return ReadBIH(aData);
+ case ICOState::READ_BMP:
+ return ReadBMP(aData, aLength);
+ case ICOState::PREPARE_FOR_MASK:
+ return PrepareForMask();
+ case ICOState::READ_MASK_ROW:
+ return ReadMaskRow(aData);
+ case ICOState::FINISH_MASK:
+ return FinishMask();
+ case ICOState::SKIP_MASK:
+ return Transition::ContinueUnbuffered(ICOState::SKIP_MASK);
+ case ICOState::FINISHED_RESOURCE:
+ return FinishResource();
+ default:
+ MOZ_CRASH("Unknown ICOState");
+ }
+ });
+}
+
+bool
+nsICODecoder::WriteToContainedDecoder(const char* aBuffer, uint32_t aCount)
+{
+ MOZ_ASSERT(mContainedDecoder);
+ MOZ_ASSERT(mContainedSourceBuffer);
+
+ // Append the provided data to the SourceBuffer that the contained decoder is
+ // reading from.
+ mContainedSourceBuffer->Append(aBuffer, aCount);
+
+ bool succeeded = true;
+
+ // Write to the contained decoder. If we run out of data, the ICO decoder will
+ // get resumed when there's more data available, as usual, so we don't need
+ // the contained decoder to get resumed too. To avoid that, we provide an
+ // IResumable which just does nothing.
+ LexerResult result = mContainedDecoder->Decode();
+ if (result == LexerResult(TerminalState::FAILURE)) {
+ succeeded = false;
+ }
+
+ MOZ_ASSERT(result != LexerResult(Yield::OUTPUT_AVAILABLE),
+ "Unexpected yield");
+
+ // Make our state the same as the state of the contained decoder, and
+ // propagate errors.
+ mProgress |= mContainedDecoder->TakeProgress();
+ mInvalidRect.UnionRect(mInvalidRect, mContainedDecoder->TakeInvalidRect());
+ if (mContainedDecoder->HasError()) {
+ succeeded = false;
+ }
+
+ return succeeded;
+}
+
+} // namespace image
+} // namespace mozilla
diff --git a/image/decoders/nsICODecoder.h b/image/decoders/nsICODecoder.h
new file mode 100644
index 000000000..46e1377aa
--- /dev/null
+++ b/image/decoders/nsICODecoder.h
@@ -0,0 +1,137 @@
+/* vim:set tw=80 expandtab softtabstop=4 ts=4 sw=4: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifndef mozilla_image_decoders_nsICODecoder_h
+#define mozilla_image_decoders_nsICODecoder_h
+
+#include "StreamingLexer.h"
+#include "Decoder.h"
+#include "imgFrame.h"
+#include "mozilla/gfx/2D.h"
+#include "nsBMPDecoder.h"
+#include "nsPNGDecoder.h"
+#include "ICOFileHeaders.h"
+
+namespace mozilla {
+namespace image {
+
+class RasterImage;
+
+enum class ICOState
+{
+ HEADER,
+ DIR_ENTRY,
+ SKIP_TO_RESOURCE,
+ FOUND_RESOURCE,
+ SNIFF_RESOURCE,
+ READ_PNG,
+ READ_BIH,
+ READ_BMP,
+ PREPARE_FOR_MASK,
+ READ_MASK_ROW,
+ FINISH_MASK,
+ SKIP_MASK,
+ FINISHED_RESOURCE
+};
+
+class nsICODecoder : public Decoder
+{
+public:
+ virtual ~nsICODecoder() { }
+
+ /// @return the width of the icon directory entry @aEntry.
+ static uint32_t GetRealWidth(const IconDirEntry& aEntry)
+ {
+ return aEntry.mWidth == 0 ? 256 : aEntry.mWidth;
+ }
+
+ /// @return the width of the selected directory entry (mDirEntry).
+ uint32_t GetRealWidth() const { return GetRealWidth(mDirEntry); }
+
+ /// @return the height of the icon directory entry @aEntry.
+ static uint32_t GetRealHeight(const IconDirEntry& aEntry)
+ {
+ return aEntry.mHeight == 0 ? 256 : aEntry.mHeight;
+ }
+
+ /// @return the height of the selected directory entry (mDirEntry).
+ uint32_t GetRealHeight() const { return GetRealHeight(mDirEntry); }
+
+ /// @return the size of the selected directory entry (mDirEntry).
+ gfx::IntSize GetRealSize() const
+ {
+ return gfx::IntSize(GetRealWidth(), GetRealHeight());
+ }
+
+ /// @return The offset from the beginning of the ICO to the first resource.
+ size_t FirstResourceOffset() const;
+
+ LexerResult DoDecode(SourceBufferIterator& aIterator,
+ IResumable* aOnResume) override;
+ nsresult FinishInternal() override;
+ nsresult FinishWithErrorInternal() override;
+
+private:
+ friend class DecoderFactory;
+
+ // Decoders should only be instantiated via DecoderFactory.
+ explicit nsICODecoder(RasterImage* aImage);
+
+ // Writes to the contained decoder and sets the appropriate errors
+ // Returns true if there are no errors.
+ bool WriteToContainedDecoder(const char* aBuffer, uint32_t aCount);
+
+ // Gets decoder state from the contained decoder so it's visible externally.
+ nsresult GetFinalStateFromContainedDecoder();
+
+ /**
+ * Verifies that the width and height values in @aBIH are valid and match the
+ * values we read from the ICO directory entry. If everything looks OK, the
+ * height value in @aBIH is updated to compensate for the AND mask, which the
+ * underlying BMP decoder doesn't know about.
+ *
+ * @return true if the width and height values in @aBIH are valid and correct.
+ */
+ bool CheckAndFixBitmapSize(int8_t* aBIH);
+
+ // Obtains the number of colors from the BPP, mBPP must be filled in
+ uint16_t GetNumColors();
+
+ LexerTransition<ICOState> ReadHeader(const char* aData);
+ LexerTransition<ICOState> ReadDirEntry(const char* aData);
+ LexerTransition<ICOState> SniffResource(const char* aData);
+ LexerTransition<ICOState> ReadPNG(const char* aData, uint32_t aLen);
+ LexerTransition<ICOState> ReadBIH(const char* aData);
+ LexerTransition<ICOState> ReadBMP(const char* aData, uint32_t aLen);
+ LexerTransition<ICOState> PrepareForMask();
+ LexerTransition<ICOState> ReadMaskRow(const char* aData);
+ LexerTransition<ICOState> FinishMask();
+ LexerTransition<ICOState> FinishResource();
+
+ StreamingLexer<ICOState, 32> mLexer; // The lexer.
+ RefPtr<Decoder> mContainedDecoder; // Either a BMP or PNG decoder.
+ RefPtr<SourceBuffer> mContainedSourceBuffer; // SourceBuffer for mContainedDecoder.
+ UniquePtr<uint8_t[]> mMaskBuffer; // A temporary buffer for the alpha mask.
+ char mBIHraw[bmp::InfoHeaderLength::WIN_ICO]; // The bitmap information header.
+ IconDirEntry mDirEntry; // The dir entry for the selected resource.
+ gfx::IntSize mBiggestResourceSize; // Used to select the intrinsic size.
+ gfx::IntSize mBiggestResourceHotSpot; // Used to select the intrinsic size.
+ uint16_t mBiggestResourceColorDepth; // Used to select the intrinsic size.
+ int32_t mBestResourceDelta; // Used to select the best resource.
+ uint16_t mBestResourceColorDepth; // Used to select the best resource.
+ uint16_t mNumIcons; // Stores the number of icons in the ICO file.
+ uint16_t mCurrIcon; // Stores the current dir entry index we are processing.
+ uint16_t mBPP; // The BPP of the resource we're decoding.
+ uint32_t mMaskRowSize; // The size in bytes of each row in the BMP alpha mask.
+ uint32_t mCurrMaskLine; // The line of the BMP alpha mask we're processing.
+ bool mIsCursor; // Is this ICO a cursor?
+ bool mHasMaskAlpha; // Did the BMP alpha mask have any transparency?
+};
+
+} // namespace image
+} // namespace mozilla
+
+#endif // mozilla_image_decoders_nsICODecoder_h
diff --git a/image/decoders/nsIconDecoder.cpp b/image/decoders/nsIconDecoder.cpp
new file mode 100644
index 000000000..9ca63f5ad
--- /dev/null
+++ b/image/decoders/nsIconDecoder.cpp
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIconDecoder.h"
+#include "RasterImage.h"
+#include "SurfacePipeFactory.h"
+
+using namespace mozilla::gfx;
+
+namespace mozilla {
+namespace image {
+
+static const uint32_t ICON_HEADER_SIZE = 2;
+
+nsIconDecoder::nsIconDecoder(RasterImage* aImage)
+ : Decoder(aImage)
+ , mLexer(Transition::To(State::HEADER, ICON_HEADER_SIZE),
+ Transition::TerminateSuccess())
+ , mBytesPerRow() // set by ReadHeader()
+{
+ // Nothing to do
+}
+
+nsIconDecoder::~nsIconDecoder()
+{ }
+
+LexerResult
+nsIconDecoder::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume)
+{
+ MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!");
+
+ return mLexer.Lex(aIterator, aOnResume,
+ [=](State aState, const char* aData, size_t aLength) {
+ switch (aState) {
+ case State::HEADER:
+ return ReadHeader(aData);
+ case State::ROW_OF_PIXELS:
+ return ReadRowOfPixels(aData, aLength);
+ case State::FINISH:
+ return Finish();
+ default:
+ MOZ_CRASH("Unknown State");
+ }
+ });
+}
+
+LexerTransition<nsIconDecoder::State>
+nsIconDecoder::ReadHeader(const char* aData)
+{
+ // Grab the width and height.
+ uint8_t width = uint8_t(aData[0]);
+ uint8_t height = uint8_t(aData[1]);
+
+ // The input is 32bpp, so we expect 4 bytes of data per pixel.
+ mBytesPerRow = width * 4;
+
+ // Post our size to the superclass.
+ PostSize(width, height);
+
+ // Icons have alpha.
+ PostHasTransparency();
+
+ // If we're doing a metadata decode, we're done.
+ if (IsMetadataDecode()) {
+ return Transition::TerminateSuccess();
+ }
+
+ MOZ_ASSERT(!mImageData, "Already have a buffer allocated?");
+ Maybe<SurfacePipe> pipe =
+ SurfacePipeFactory::CreateSurfacePipe(this, 0, Size(), OutputSize(),
+ FullFrame(), SurfaceFormat::B8G8R8A8,
+ SurfacePipeFlags());
+ if (!pipe) {
+ return Transition::TerminateFailure();
+ }
+
+ mPipe = Move(*pipe);
+
+ MOZ_ASSERT(mImageData, "Should have a buffer now");
+
+ return Transition::To(State::ROW_OF_PIXELS, mBytesPerRow);
+}
+
+LexerTransition<nsIconDecoder::State>
+nsIconDecoder::ReadRowOfPixels(const char* aData, size_t aLength)
+{
+ MOZ_ASSERT(aLength % 4 == 0, "Rows should contain a multiple of four bytes");
+
+ auto result = mPipe.WritePixels<uint32_t>([&]() -> NextPixel<uint32_t> {
+ if (aLength == 0) {
+ return AsVariant(WriteState::NEED_MORE_DATA); // Done with this row.
+ }
+
+ uint32_t pixel;
+ memcpy(&pixel, aData, 4);
+ aData += 4;
+ aLength -= 4;
+
+ return AsVariant(pixel);
+ });
+
+ MOZ_ASSERT(result != WriteState::FAILURE);
+
+ Maybe<SurfaceInvalidRect> invalidRect = mPipe.TakeInvalidRect();
+ if (invalidRect) {
+ PostInvalidation(invalidRect->mInputSpaceRect,
+ Some(invalidRect->mOutputSpaceRect));
+ }
+
+ return result == WriteState::FINISHED
+ ? Transition::To(State::FINISH, 0)
+ : Transition::To(State::ROW_OF_PIXELS, mBytesPerRow);
+}
+
+LexerTransition<nsIconDecoder::State>
+nsIconDecoder::Finish()
+{
+ PostFrameStop();
+ PostDecodeDone();
+
+ return Transition::TerminateSuccess();
+}
+
+} // namespace image
+} // namespace mozilla
diff --git a/image/decoders/nsIconDecoder.h b/image/decoders/nsIconDecoder.h
new file mode 100644
index 000000000..69198315b
--- /dev/null
+++ b/image/decoders/nsIconDecoder.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_image_decoders_nsIconDecoder_h
+#define mozilla_image_decoders_nsIconDecoder_h
+
+#include "Decoder.h"
+#include "StreamingLexer.h"
+#include "SurfacePipe.h"
+
+namespace mozilla {
+namespace image {
+
+class RasterImage;
+
+////////////////////////////////////////////////////////////////////////////////
+// The icon decoder is a decoder specifically tailored for loading icons
+// from the OS. We've defined our own little format to represent these icons
+// and this decoder takes that format and converts it into 24-bit RGB with
+// alpha channel support. It was modeled a bit off the PPM decoder.
+//
+// The format of the incoming data is as follows:
+//
+// The first two bytes contain the width and the height of the icon.
+// The remaining bytes contain the icon data, 4 bytes per pixel, in
+// ARGB order (platform endianness, A in highest bits, B in lowest
+// bits), row-primary, top-to-bottom, left-to-right, with
+// premultiplied alpha.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+class nsIconDecoder : public Decoder
+{
+public:
+ virtual ~nsIconDecoder();
+
+ LexerResult DoDecode(SourceBufferIterator& aIterator,
+ IResumable* aOnResume) override;
+
+private:
+ friend class DecoderFactory;
+
+ // Decoders should only be instantiated via DecoderFactory.
+ explicit nsIconDecoder(RasterImage* aImage);
+
+ enum class State {
+ HEADER,
+ ROW_OF_PIXELS,
+ FINISH
+ };
+
+ LexerTransition<State> ReadHeader(const char* aData);
+ LexerTransition<State> ReadRowOfPixels(const char* aData, size_t aLength);
+ LexerTransition<State> Finish();
+
+ StreamingLexer<State> mLexer;
+ SurfacePipe mPipe;
+ uint32_t mBytesPerRow;
+};
+
+} // namespace image
+} // namespace mozilla
+
+#endif // mozilla_image_decoders_nsIconDecoder_h
diff --git a/image/decoders/nsJPEGDecoder.cpp b/image/decoders/nsJPEGDecoder.cpp
new file mode 100644
index 000000000..e76ffcbaf
--- /dev/null
+++ b/image/decoders/nsJPEGDecoder.cpp
@@ -0,0 +1,1006 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ImageLogging.h" // Must appear first.
+
+#include "nsJPEGDecoder.h"
+
+#include <cstdint>
+
+#include "imgFrame.h"
+#include "Orientation.h"
+#include "EXIF.h"
+
+#include "nsIInputStream.h"
+
+#include "nspr.h"
+#include "nsCRT.h"
+#include "gfxColor.h"
+
+#include "jerror.h"
+
+#include "gfxPlatform.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/Telemetry.h"
+
+extern "C" {
+#include "iccjpeg.h"
+}
+
+#if MOZ_BIG_ENDIAN
+#define MOZ_JCS_EXT_NATIVE_ENDIAN_XRGB JCS_EXT_XRGB
+#else
+#define MOZ_JCS_EXT_NATIVE_ENDIAN_XRGB JCS_EXT_BGRX
+#endif
+
+static void cmyk_convert_rgb(JSAMPROW row, JDIMENSION width);
+
+namespace mozilla {
+namespace image {
+
+static mozilla::LazyLogModule sJPEGLog("JPEGDecoder");
+
+static mozilla::LazyLogModule sJPEGDecoderAccountingLog("JPEGDecoderAccounting");
+
+static qcms_profile*
+GetICCProfile(struct jpeg_decompress_struct& info)
+{
+ JOCTET* profilebuf;
+ uint32_t profileLength;
+ qcms_profile* profile = nullptr;
+
+ if (read_icc_profile(&info, &profilebuf, &profileLength)) {
+ profile = qcms_profile_from_memory(profilebuf, profileLength);
+ free(profilebuf);
+ }
+
+ return profile;
+}
+
+METHODDEF(void) init_source (j_decompress_ptr jd);
+METHODDEF(boolean) fill_input_buffer (j_decompress_ptr jd);
+METHODDEF(void) skip_input_data (j_decompress_ptr jd, long num_bytes);
+METHODDEF(void) term_source (j_decompress_ptr jd);
+METHODDEF(void) my_error_exit (j_common_ptr cinfo);
+
+// Normal JFIF markers can't have more bytes than this.
+#define MAX_JPEG_MARKER_LENGTH (((uint32_t)1 << 16) - 1)
+
+nsJPEGDecoder::nsJPEGDecoder(RasterImage* aImage,
+ Decoder::DecodeStyle aDecodeStyle)
+ : Decoder(aImage)
+ , mLexer(Transition::ToUnbuffered(State::FINISHED_JPEG_DATA,
+ State::JPEG_DATA,
+ SIZE_MAX),
+ Transition::TerminateSuccess())
+ , mDecodeStyle(aDecodeStyle)
+ , mSampleSize(0)
+{
+ mState = JPEG_HEADER;
+ mReading = true;
+ mImageData = nullptr;
+
+ mBytesToSkip = 0;
+ memset(&mInfo, 0, sizeof(jpeg_decompress_struct));
+ memset(&mSourceMgr, 0, sizeof(mSourceMgr));
+ mInfo.client_data = (void*)this;
+
+ mSegment = nullptr;
+ mSegmentLen = 0;
+
+ mBackBuffer = nullptr;
+ mBackBufferLen = mBackBufferSize = mBackBufferUnreadLen = 0;
+
+ mInProfile = nullptr;
+ mTransform = nullptr;
+
+ mCMSMode = 0;
+
+ MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug,
+ ("nsJPEGDecoder::nsJPEGDecoder: Creating JPEG decoder %p",
+ this));
+}
+
+nsJPEGDecoder::~nsJPEGDecoder()
+{
+ // Step 8: Release JPEG decompression object
+ mInfo.src = nullptr;
+ jpeg_destroy_decompress(&mInfo);
+
+ PR_FREEIF(mBackBuffer);
+ if (mTransform) {
+ qcms_transform_release(mTransform);
+ }
+ if (mInProfile) {
+ qcms_profile_release(mInProfile);
+ }
+
+ MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug,
+ ("nsJPEGDecoder::~nsJPEGDecoder: Destroying JPEG decoder %p",
+ this));
+}
+
+Maybe<Telemetry::ID>
+nsJPEGDecoder::SpeedHistogram() const
+{
+ return Some(Telemetry::IMAGE_DECODE_SPEED_JPEG);
+}
+
+nsresult
+nsJPEGDecoder::InitInternal()
+{
+ mCMSMode = gfxPlatform::GetCMSMode();
+ if (GetSurfaceFlags() & SurfaceFlags::NO_COLORSPACE_CONVERSION) {
+ mCMSMode = eCMSMode_Off;
+ }
+
+ // We set up the normal JPEG error routines, then override error_exit.
+ mInfo.err = jpeg_std_error(&mErr.pub);
+ // mInfo.err = jpeg_std_error(&mErr.pub);
+ mErr.pub.error_exit = my_error_exit;
+ // Establish the setjmp return context for my_error_exit to use.
+ if (setjmp(mErr.setjmp_buffer)) {
+ // If we get here, the JPEG code has signaled an error, and initialization
+ // has failed.
+ return NS_ERROR_FAILURE;
+ }
+
+ // Step 1: allocate and initialize JPEG decompression object
+ jpeg_create_decompress(&mInfo);
+ // Set the source manager
+ mInfo.src = &mSourceMgr;
+
+ // Step 2: specify data source (eg, a file)
+
+ // Setup callback functions.
+ mSourceMgr.init_source = init_source;
+ mSourceMgr.fill_input_buffer = fill_input_buffer;
+ mSourceMgr.skip_input_data = skip_input_data;
+ mSourceMgr.resync_to_restart = jpeg_resync_to_restart;
+ mSourceMgr.term_source = term_source;
+
+ // Record app markers for ICC data
+ for (uint32_t m = 0; m < 16; m++) {
+ jpeg_save_markers(&mInfo, JPEG_APP0 + m, 0xFFFF);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsJPEGDecoder::FinishInternal()
+{
+ // If we're not in any sort of error case, force our state to JPEG_DONE.
+ if ((mState != JPEG_DONE && mState != JPEG_SINK_NON_JPEG_TRAILER) &&
+ (mState != JPEG_ERROR) &&
+ !IsMetadataDecode()) {
+ mState = JPEG_DONE;
+ }
+
+ return NS_OK;
+}
+
+LexerResult
+nsJPEGDecoder::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume)
+{
+ MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!");
+
+ return mLexer.Lex(aIterator, aOnResume,
+ [=](State aState, const char* aData, size_t aLength) {
+ switch (aState) {
+ case State::JPEG_DATA:
+ return ReadJPEGData(aData, aLength);
+ case State::FINISHED_JPEG_DATA:
+ return FinishedJPEGData();
+ }
+ MOZ_CRASH("Unknown State");
+ });
+}
+
+LexerTransition<nsJPEGDecoder::State>
+nsJPEGDecoder::ReadJPEGData(const char* aData, size_t aLength)
+{
+ mSegment = reinterpret_cast<const JOCTET*>(aData);
+ mSegmentLen = aLength;
+
+ // Return here if there is a fatal error within libjpeg.
+ nsresult error_code;
+ // This cast to nsresult makes sense because setjmp() returns whatever we
+ // passed to longjmp(), which was actually an nsresult.
+ if ((error_code = static_cast<nsresult>(setjmp(mErr.setjmp_buffer))) != NS_OK) {
+ if (error_code == NS_ERROR_FAILURE) {
+ // Error due to corrupt data. Make sure that we don't feed any more data
+ // to libjpeg-turbo.
+ mState = JPEG_SINK_NON_JPEG_TRAILER;
+ MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug,
+ ("} (setjmp returned NS_ERROR_FAILURE)"));
+ } else {
+ // Error for another reason. (Possibly OOM.)
+ mState = JPEG_ERROR;
+ MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug,
+ ("} (setjmp returned an error)"));
+ }
+
+ return Transition::TerminateFailure();
+ }
+
+ MOZ_LOG(sJPEGLog, LogLevel::Debug,
+ ("[this=%p] nsJPEGDecoder::Write -- processing JPEG data\n", this));
+
+ switch (mState) {
+ case JPEG_HEADER: {
+ LOG_SCOPE((mozilla::LogModule*)sJPEGLog, "nsJPEGDecoder::Write -- entering JPEG_HEADER"
+ " case");
+
+ // Step 3: read file parameters with jpeg_read_header()
+ if (jpeg_read_header(&mInfo, TRUE) == JPEG_SUSPENDED) {
+ MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug,
+ ("} (JPEG_SUSPENDED)"));
+ return Transition::ContinueUnbuffered(State::JPEG_DATA); // I/O suspension
+ }
+
+ // If we have a sample size specified for -moz-sample-size, use it.
+ if (mSampleSize > 0) {
+ mInfo.scale_num = 1;
+ mInfo.scale_denom = mSampleSize;
+ }
+
+ // Used to set up image size so arrays can be allocated
+ jpeg_calc_output_dimensions(&mInfo);
+
+ // Post our size to the superclass
+ PostSize(mInfo.output_width, mInfo.output_height,
+ ReadOrientationFromEXIF());
+ if (HasError()) {
+ // Setting the size led to an error.
+ mState = JPEG_ERROR;
+ return Transition::TerminateFailure();
+ }
+
+ // If we're doing a metadata decode, we're done.
+ if (IsMetadataDecode()) {
+ return Transition::TerminateSuccess();
+ }
+
+ // We're doing a full decode.
+ if (mCMSMode != eCMSMode_Off &&
+ (mInProfile = GetICCProfile(mInfo)) != nullptr) {
+ uint32_t profileSpace = qcms_profile_get_color_space(mInProfile);
+ bool mismatch = false;
+
+#ifdef DEBUG_tor
+ fprintf(stderr, "JPEG profileSpace: 0x%08X\n", profileSpace);
+#endif
+ switch (mInfo.jpeg_color_space) {
+ case JCS_GRAYSCALE:
+ if (profileSpace == icSigRgbData) {
+ mInfo.out_color_space = JCS_RGB;
+ } else if (profileSpace != icSigGrayData) {
+ mismatch = true;
+ }
+ break;
+ case JCS_RGB:
+ if (profileSpace != icSigRgbData) {
+ mismatch = true;
+ }
+ break;
+ case JCS_YCbCr:
+ if (profileSpace == icSigRgbData) {
+ mInfo.out_color_space = JCS_RGB;
+ } else {
+ // qcms doesn't support ycbcr
+ mismatch = true;
+ }
+ break;
+ case JCS_CMYK:
+ case JCS_YCCK:
+ // qcms doesn't support cmyk
+ mismatch = true;
+ break;
+ default:
+ mState = JPEG_ERROR;
+ MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug,
+ ("} (unknown colorpsace (1))"));
+ return Transition::TerminateFailure();
+ }
+
+ if (!mismatch) {
+ qcms_data_type type;
+ switch (mInfo.out_color_space) {
+ case JCS_GRAYSCALE:
+ type = QCMS_DATA_GRAY_8;
+ break;
+ case JCS_RGB:
+ type = QCMS_DATA_RGB_8;
+ break;
+ default:
+ mState = JPEG_ERROR;
+ MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug,
+ ("} (unknown colorpsace (2))"));
+ return Transition::TerminateFailure();
+ }
+#if 0
+ // We don't currently support CMYK profiles. The following
+ // code dealt with lcms types. Add something like this
+ // back when we gain support for CMYK.
+
+ // Adobe Photoshop writes YCCK/CMYK files with inverted data
+ if (mInfo.out_color_space == JCS_CMYK) {
+ type |= FLAVOR_SH(mInfo.saw_Adobe_marker ? 1 : 0);
+ }
+#endif
+
+ if (gfxPlatform::GetCMSOutputProfile()) {
+
+ // Calculate rendering intent.
+ int intent = gfxPlatform::GetRenderingIntent();
+ if (intent == -1) {
+ intent = qcms_profile_get_rendering_intent(mInProfile);
+ }
+
+ // Create the color management transform.
+ mTransform = qcms_transform_create(mInProfile,
+ type,
+ gfxPlatform::GetCMSOutputProfile(),
+ QCMS_DATA_RGB_8,
+ (qcms_intent)intent);
+ }
+ } else {
+#ifdef DEBUG_tor
+ fprintf(stderr, "ICM profile colorspace mismatch\n");
+#endif
+ }
+ }
+
+ if (!mTransform) {
+ switch (mInfo.jpeg_color_space) {
+ case JCS_GRAYSCALE:
+ case JCS_RGB:
+ case JCS_YCbCr:
+ // if we're not color managing we can decode directly to
+ // MOZ_JCS_EXT_NATIVE_ENDIAN_XRGB
+ if (mCMSMode != eCMSMode_All) {
+ mInfo.out_color_space = MOZ_JCS_EXT_NATIVE_ENDIAN_XRGB;
+ mInfo.out_color_components = 4;
+ } else {
+ mInfo.out_color_space = JCS_RGB;
+ }
+ break;
+ case JCS_CMYK:
+ case JCS_YCCK:
+ // libjpeg can convert from YCCK to CMYK, but not to RGB
+ mInfo.out_color_space = JCS_CMYK;
+ break;
+ default:
+ mState = JPEG_ERROR;
+ MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug,
+ ("} (unknown colorpsace (3))"));
+ return Transition::TerminateFailure();
+ }
+ }
+
+ // Don't allocate a giant and superfluous memory buffer
+ // when not doing a progressive decode.
+ mInfo.buffered_image = mDecodeStyle == PROGRESSIVE &&
+ jpeg_has_multiple_scans(&mInfo);
+
+ MOZ_ASSERT(!mImageData, "Already have a buffer allocated?");
+ nsresult rv = AllocateFrame(/* aFrameNum = */ 0, OutputSize(),
+ FullOutputFrame(), SurfaceFormat::B8G8R8X8);
+ if (NS_FAILED(rv)) {
+ mState = JPEG_ERROR;
+ MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug,
+ ("} (could not initialize image frame)"));
+ return Transition::TerminateFailure();
+ }
+
+ MOZ_ASSERT(mImageData, "Should have a buffer now");
+
+ if (mDownscaler) {
+ nsresult rv = mDownscaler->BeginFrame(Size(), Nothing(),
+ mImageData,
+ /* aHasAlpha = */ false);
+ if (NS_FAILED(rv)) {
+ mState = JPEG_ERROR;
+ return Transition::TerminateFailure();
+ }
+ }
+
+ MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug,
+ (" JPEGDecoderAccounting: nsJPEGDecoder::"
+ "Write -- created image frame with %ux%u pixels",
+ mInfo.output_width, mInfo.output_height));
+
+ mState = JPEG_START_DECOMPRESS;
+ MOZ_FALLTHROUGH; // to start decompressing.
+ }
+
+ case JPEG_START_DECOMPRESS: {
+ LOG_SCOPE((mozilla::LogModule*)sJPEGLog, "nsJPEGDecoder::Write -- entering"
+ " JPEG_START_DECOMPRESS case");
+ // Step 4: set parameters for decompression
+
+ // FIXME -- Should reset dct_method and dither mode
+ // for final pass of progressive JPEG
+
+ mInfo.dct_method = JDCT_ISLOW;
+ mInfo.dither_mode = JDITHER_FS;
+ mInfo.do_fancy_upsampling = TRUE;
+ mInfo.enable_2pass_quant = FALSE;
+ mInfo.do_block_smoothing = TRUE;
+
+ // Step 5: Start decompressor
+ if (jpeg_start_decompress(&mInfo) == FALSE) {
+ MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug,
+ ("} (I/O suspension after jpeg_start_decompress())"));
+ return Transition::ContinueUnbuffered(State::JPEG_DATA); // I/O suspension
+ }
+
+ // If this is a progressive JPEG ...
+ mState = mInfo.buffered_image ?
+ JPEG_DECOMPRESS_PROGRESSIVE : JPEG_DECOMPRESS_SEQUENTIAL;
+ MOZ_FALLTHROUGH; // to decompress sequential JPEG.
+ }
+
+ case JPEG_DECOMPRESS_SEQUENTIAL: {
+ if (mState == JPEG_DECOMPRESS_SEQUENTIAL) {
+ LOG_SCOPE((mozilla::LogModule*)sJPEGLog, "nsJPEGDecoder::Write -- "
+ "JPEG_DECOMPRESS_SEQUENTIAL case");
+
+ bool suspend;
+ OutputScanlines(&suspend);
+
+ if (suspend) {
+ MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug,
+ ("} (I/O suspension after OutputScanlines() - SEQUENTIAL)"));
+ return Transition::ContinueUnbuffered(State::JPEG_DATA); // I/O suspension
+ }
+
+ // If we've completed image output ...
+ NS_ASSERTION(mInfo.output_scanline == mInfo.output_height,
+ "We didn't process all of the data!");
+ mState = JPEG_DONE;
+ }
+ MOZ_FALLTHROUGH; // to decompress progressive JPEG.
+ }
+
+ case JPEG_DECOMPRESS_PROGRESSIVE: {
+ if (mState == JPEG_DECOMPRESS_PROGRESSIVE) {
+ LOG_SCOPE((mozilla::LogModule*)sJPEGLog,
+ "nsJPEGDecoder::Write -- JPEG_DECOMPRESS_PROGRESSIVE case");
+
+ int status;
+ do {
+ status = jpeg_consume_input(&mInfo);
+ } while ((status != JPEG_SUSPENDED) &&
+ (status != JPEG_REACHED_EOI));
+
+ for (;;) {
+ if (mInfo.output_scanline == 0) {
+ int scan = mInfo.input_scan_number;
+
+ // if we haven't displayed anything yet (output_scan_number==0)
+ // and we have enough data for a complete scan, force output
+ // of the last full scan
+ if ((mInfo.output_scan_number == 0) &&
+ (scan > 1) &&
+ (status != JPEG_REACHED_EOI))
+ scan--;
+
+ if (!jpeg_start_output(&mInfo, scan)) {
+ MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug,
+ ("} (I/O suspension after jpeg_start_output() -"
+ " PROGRESSIVE)"));
+ return Transition::ContinueUnbuffered(State::JPEG_DATA); // I/O suspension
+ }
+ }
+
+ if (mInfo.output_scanline == 0xffffff) {
+ mInfo.output_scanline = 0;
+ }
+
+ bool suspend;
+ OutputScanlines(&suspend);
+
+ if (suspend) {
+ if (mInfo.output_scanline == 0) {
+ // didn't manage to read any lines - flag so we don't call
+ // jpeg_start_output() multiple times for the same scan
+ mInfo.output_scanline = 0xffffff;
+ }
+ MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug,
+ ("} (I/O suspension after OutputScanlines() - PROGRESSIVE)"));
+ return Transition::ContinueUnbuffered(State::JPEG_DATA); // I/O suspension
+ }
+
+ if (mInfo.output_scanline == mInfo.output_height) {
+ if (!jpeg_finish_output(&mInfo)) {
+ MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug,
+ ("} (I/O suspension after jpeg_finish_output() -"
+ " PROGRESSIVE)"));
+ return Transition::ContinueUnbuffered(State::JPEG_DATA); // I/O suspension
+ }
+
+ if (jpeg_input_complete(&mInfo) &&
+ (mInfo.input_scan_number == mInfo.output_scan_number))
+ break;
+
+ mInfo.output_scanline = 0;
+ if (mDownscaler) {
+ mDownscaler->ResetForNextProgressivePass();
+ }
+ }
+ }
+
+ mState = JPEG_DONE;
+ }
+ MOZ_FALLTHROUGH; // to finish decompressing.
+ }
+
+ case JPEG_DONE: {
+ LOG_SCOPE((mozilla::LogModule*)sJPEGLog, "nsJPEGDecoder::ProcessData -- entering"
+ " JPEG_DONE case");
+
+ // Step 7: Finish decompression
+
+ if (jpeg_finish_decompress(&mInfo) == FALSE) {
+ MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug,
+ ("} (I/O suspension after jpeg_finish_decompress() - DONE)"));
+ return Transition::ContinueUnbuffered(State::JPEG_DATA); // I/O suspension
+ }
+
+ // Make sure we don't feed any more data to libjpeg-turbo.
+ mState = JPEG_SINK_NON_JPEG_TRAILER;
+
+ // We're done.
+ return Transition::TerminateSuccess();
+ }
+ case JPEG_SINK_NON_JPEG_TRAILER:
+ MOZ_LOG(sJPEGLog, LogLevel::Debug,
+ ("[this=%p] nsJPEGDecoder::ProcessData -- entering"
+ " JPEG_SINK_NON_JPEG_TRAILER case\n", this));
+
+ MOZ_ASSERT_UNREACHABLE("Should stop getting data after entering state "
+ "JPEG_SINK_NON_JPEG_TRAILER");
+
+ return Transition::TerminateSuccess();
+
+ case JPEG_ERROR:
+ MOZ_ASSERT_UNREACHABLE("Should stop getting data after entering state "
+ "JPEG_ERROR");
+
+ return Transition::TerminateFailure();
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Escaped the JPEG decoder state machine");
+ return Transition::TerminateFailure();
+}
+
+LexerTransition<nsJPEGDecoder::State>
+nsJPEGDecoder::FinishedJPEGData()
+{
+ // Since we set up an unbuffered read for SIZE_MAX bytes, if we actually read
+ // all that data something is really wrong.
+ MOZ_ASSERT_UNREACHABLE("Read the entire address space?");
+ return Transition::TerminateFailure();
+}
+
+Orientation
+nsJPEGDecoder::ReadOrientationFromEXIF()
+{
+ jpeg_saved_marker_ptr marker;
+
+ // Locate the APP1 marker, where EXIF data is stored, in the marker list.
+ for (marker = mInfo.marker_list ; marker != nullptr ; marker = marker->next) {
+ if (marker->marker == JPEG_APP0 + 1) {
+ break;
+ }
+ }
+
+ // If we're at the end of the list, there's no EXIF data.
+ if (!marker) {
+ return Orientation();
+ }
+
+ // Extract the orientation information.
+ EXIFData exif = EXIFParser::Parse(marker->data,
+ static_cast<uint32_t>(marker->data_length));
+ return exif.orientation;
+}
+
+void
+nsJPEGDecoder::NotifyDone()
+{
+ PostFrameStop(Opacity::FULLY_OPAQUE);
+ PostDecodeDone();
+}
+
+void
+nsJPEGDecoder::OutputScanlines(bool* suspend)
+{
+ *suspend = false;
+
+ const uint32_t top = mInfo.output_scanline;
+
+ while ((mInfo.output_scanline < mInfo.output_height)) {
+ uint32_t* imageRow = nullptr;
+ if (mDownscaler) {
+ imageRow = reinterpret_cast<uint32_t*>(mDownscaler->RowBuffer());
+ } else {
+ imageRow = reinterpret_cast<uint32_t*>(mImageData) +
+ (mInfo.output_scanline * mInfo.output_width);
+ }
+
+ MOZ_ASSERT(imageRow, "Should have a row buffer here");
+
+ if (mInfo.out_color_space == MOZ_JCS_EXT_NATIVE_ENDIAN_XRGB) {
+ // Special case: scanline will be directly converted into packed ARGB
+ if (jpeg_read_scanlines(&mInfo, (JSAMPARRAY)&imageRow, 1) != 1) {
+ *suspend = true; // suspend
+ break;
+ }
+ if (mDownscaler) {
+ mDownscaler->CommitRow();
+ }
+ continue; // all done for this row!
+ }
+
+ JSAMPROW sampleRow = (JSAMPROW)imageRow;
+ if (mInfo.output_components == 3) {
+ // Put the pixels at end of row to enable in-place expansion
+ sampleRow += mInfo.output_width;
+ }
+
+ // Request one scanline. Returns 0 or 1 scanlines.
+ if (jpeg_read_scanlines(&mInfo, &sampleRow, 1) != 1) {
+ *suspend = true; // suspend
+ break;
+ }
+
+ if (mTransform) {
+ JSAMPROW source = sampleRow;
+ if (mInfo.out_color_space == JCS_GRAYSCALE) {
+ // Convert from the 1byte grey pixels at begin of row
+ // to the 3byte RGB byte pixels at 'end' of row
+ sampleRow += mInfo.output_width;
+ }
+ qcms_transform_data(mTransform, source, sampleRow, mInfo.output_width);
+ // Move 3byte RGB data to end of row
+ if (mInfo.out_color_space == JCS_CMYK) {
+ memmove(sampleRow + mInfo.output_width,
+ sampleRow,
+ 3 * mInfo.output_width);
+ sampleRow += mInfo.output_width;
+ }
+ } else {
+ if (mInfo.out_color_space == JCS_CMYK) {
+ // Convert from CMYK to RGB
+ // We cannot convert directly to Cairo, as the CMSRGBTransform
+ // may wants to do a RGB transform...
+ // Would be better to have platform CMSenabled transformation
+ // from CMYK to (A)RGB...
+ cmyk_convert_rgb((JSAMPROW)imageRow, mInfo.output_width);
+ sampleRow += mInfo.output_width;
+ }
+ if (mCMSMode == eCMSMode_All) {
+ // No embedded ICC profile - treat as sRGB
+ qcms_transform* transform = gfxPlatform::GetCMSRGBTransform();
+ if (transform) {
+ qcms_transform_data(transform, sampleRow, sampleRow,
+ mInfo.output_width);
+ }
+ }
+ }
+
+ // counter for while() loops below
+ uint32_t idx = mInfo.output_width;
+
+ // copy as bytes until source pointer is 32-bit-aligned
+ for (; (NS_PTR_TO_UINT32(sampleRow) & 0x3) && idx; --idx) {
+ *imageRow++ = gfxPackedPixel(0xFF, sampleRow[0], sampleRow[1],
+ sampleRow[2]);
+ sampleRow += 3;
+ }
+
+ // copy pixels in blocks of 4
+ while (idx >= 4) {
+ GFX_BLOCK_RGB_TO_FRGB(sampleRow, imageRow);
+ idx -= 4;
+ sampleRow += 12;
+ imageRow += 4;
+ }
+
+ // copy remaining pixel(s)
+ while (idx--) {
+ // 32-bit read of final pixel will exceed buffer, so read bytes
+ *imageRow++ = gfxPackedPixel(0xFF, sampleRow[0], sampleRow[1],
+ sampleRow[2]);
+ sampleRow += 3;
+ }
+
+ if (mDownscaler) {
+ mDownscaler->CommitRow();
+ }
+ }
+
+ if (mDownscaler && mDownscaler->HasInvalidation()) {
+ DownscalerInvalidRect invalidRect = mDownscaler->TakeInvalidRect();
+ PostInvalidation(invalidRect.mOriginalSizeRect,
+ Some(invalidRect.mTargetSizeRect));
+ MOZ_ASSERT(!mDownscaler->HasInvalidation());
+ } else if (!mDownscaler && top != mInfo.output_scanline) {
+ PostInvalidation(nsIntRect(0, top,
+ mInfo.output_width,
+ mInfo.output_scanline - top));
+ }
+}
+
+// Override the standard error method in the IJG JPEG decoder code.
+METHODDEF(void)
+my_error_exit (j_common_ptr cinfo)
+{
+ decoder_error_mgr* err = (decoder_error_mgr*) cinfo->err;
+
+ // Convert error to a browser error code
+ nsresult error_code = err->pub.msg_code == JERR_OUT_OF_MEMORY
+ ? NS_ERROR_OUT_OF_MEMORY
+ : NS_ERROR_FAILURE;
+
+#ifdef DEBUG
+ char buffer[JMSG_LENGTH_MAX];
+
+ // Create the message
+ (*err->pub.format_message) (cinfo, buffer);
+
+ fprintf(stderr, "JPEG decoding error:\n%s\n", buffer);
+#endif
+
+ // Return control to the setjmp point. We pass an nsresult masquerading as
+ // an int, which works because the setjmp() caller casts it back.
+ longjmp(err->setjmp_buffer, static_cast<int>(error_code));
+}
+
+/*******************************************************************************
+ * This is the callback routine from the IJG JPEG library used to supply new
+ * data to the decompressor when its input buffer is exhausted. It juggles
+ * multiple buffers in an attempt to avoid unnecessary copying of input data.
+ *
+ * (A simpler scheme is possible: It's much easier to use only a single
+ * buffer; when fill_input_buffer() is called, move any unconsumed data
+ * (beyond the current pointer/count) down to the beginning of this buffer and
+ * then load new data into the remaining buffer space. This approach requires
+ * a little more data copying but is far easier to get right.)
+ *
+ * At any one time, the JPEG decompressor is either reading from the necko
+ * input buffer, which is volatile across top-level calls to the IJG library,
+ * or the "backtrack" buffer. The backtrack buffer contains the remaining
+ * unconsumed data from the necko buffer after parsing was suspended due
+ * to insufficient data in some previous call to the IJG library.
+ *
+ * When suspending, the decompressor will back up to a convenient restart
+ * point (typically the start of the current MCU). The variables
+ * next_input_byte & bytes_in_buffer indicate where the restart point will be
+ * if the current call returns FALSE. Data beyond this point must be
+ * rescanned after resumption, so it must be preserved in case the decompressor
+ * decides to backtrack.
+ *
+ * Returns:
+ * TRUE if additional data is available, FALSE if no data present and
+ * the JPEG library should therefore suspend processing of input stream
+ ******************************************************************************/
+
+/******************************************************************************/
+/* data source manager method */
+/******************************************************************************/
+
+/******************************************************************************/
+/* data source manager method
+ Initialize source. This is called by jpeg_read_header() before any
+ data is actually read. May leave
+ bytes_in_buffer set to 0 (in which case a fill_input_buffer() call
+ will occur immediately).
+*/
+METHODDEF(void)
+init_source (j_decompress_ptr jd)
+{
+}
+
+/******************************************************************************/
+/* data source manager method
+ Skip num_bytes worth of data. The buffer pointer and count should
+ be advanced over num_bytes input bytes, refilling the buffer as
+ needed. This is used to skip over a potentially large amount of
+ uninteresting data (such as an APPn marker). In some applications
+ it may be possible to optimize away the reading of the skipped data,
+ but it's not clear that being smart is worth much trouble; large
+ skips are uncommon. bytes_in_buffer may be zero on return.
+ A zero or negative skip count should be treated as a no-op.
+*/
+METHODDEF(void)
+skip_input_data (j_decompress_ptr jd, long num_bytes)
+{
+ struct jpeg_source_mgr* src = jd->src;
+ nsJPEGDecoder* decoder = (nsJPEGDecoder*)(jd->client_data);
+
+ if (num_bytes > (long)src->bytes_in_buffer) {
+ // Can't skip it all right now until we get more data from
+ // network stream. Set things up so that fill_input_buffer
+ // will skip remaining amount.
+ decoder->mBytesToSkip = (size_t)num_bytes - src->bytes_in_buffer;
+ src->next_input_byte += src->bytes_in_buffer;
+ src->bytes_in_buffer = 0;
+
+ } else {
+ // Simple case. Just advance buffer pointer
+
+ src->bytes_in_buffer -= (size_t)num_bytes;
+ src->next_input_byte += num_bytes;
+ }
+}
+
+/******************************************************************************/
+/* data source manager method
+ This is called whenever bytes_in_buffer has reached zero and more
+ data is wanted. In typical applications, it should read fresh data
+ into the buffer (ignoring the current state of next_input_byte and
+ bytes_in_buffer), reset the pointer & count to the start of the
+ buffer, and return TRUE indicating that the buffer has been reloaded.
+ It is not necessary to fill the buffer entirely, only to obtain at
+ least one more byte. bytes_in_buffer MUST be set to a positive value
+ if TRUE is returned. A FALSE return should only be used when I/O
+ suspension is desired.
+*/
+METHODDEF(boolean)
+fill_input_buffer (j_decompress_ptr jd)
+{
+ struct jpeg_source_mgr* src = jd->src;
+ nsJPEGDecoder* decoder = (nsJPEGDecoder*)(jd->client_data);
+
+ if (decoder->mReading) {
+ const JOCTET* new_buffer = decoder->mSegment;
+ uint32_t new_buflen = decoder->mSegmentLen;
+
+ if (!new_buffer || new_buflen == 0) {
+ return false; // suspend
+ }
+
+ decoder->mSegmentLen = 0;
+
+ if (decoder->mBytesToSkip) {
+ if (decoder->mBytesToSkip < new_buflen) {
+ // All done skipping bytes; Return what's left.
+ new_buffer += decoder->mBytesToSkip;
+ new_buflen -= decoder->mBytesToSkip;
+ decoder->mBytesToSkip = 0;
+ } else {
+ // Still need to skip some more data in the future
+ decoder->mBytesToSkip -= (size_t)new_buflen;
+ return false; // suspend
+ }
+ }
+
+ decoder->mBackBufferUnreadLen = src->bytes_in_buffer;
+
+ src->next_input_byte = new_buffer;
+ src->bytes_in_buffer = (size_t)new_buflen;
+ decoder->mReading = false;
+
+ return true;
+ }
+
+ if (src->next_input_byte != decoder->mSegment) {
+ // Backtrack data has been permanently consumed.
+ decoder->mBackBufferUnreadLen = 0;
+ decoder->mBackBufferLen = 0;
+ }
+
+ // Save remainder of netlib buffer in backtrack buffer
+ const uint32_t new_backtrack_buflen = src->bytes_in_buffer +
+ decoder->mBackBufferLen;
+
+ // Make sure backtrack buffer is big enough to hold new data.
+ if (decoder->mBackBufferSize < new_backtrack_buflen) {
+ // Check for malformed MARKER segment lengths, before allocating space
+ // for it
+ if (new_backtrack_buflen > MAX_JPEG_MARKER_LENGTH) {
+ my_error_exit((j_common_ptr)(&decoder->mInfo));
+ }
+
+ // Round up to multiple of 256 bytes.
+ const size_t roundup_buflen = ((new_backtrack_buflen + 255) >> 8) << 8;
+ JOCTET* buf = (JOCTET*)PR_REALLOC(decoder->mBackBuffer, roundup_buflen);
+ // Check for OOM
+ if (!buf) {
+ decoder->mInfo.err->msg_code = JERR_OUT_OF_MEMORY;
+ my_error_exit((j_common_ptr)(&decoder->mInfo));
+ }
+ decoder->mBackBuffer = buf;
+ decoder->mBackBufferSize = roundup_buflen;
+ }
+
+ // Copy remainder of netlib segment into backtrack buffer.
+ memmove(decoder->mBackBuffer + decoder->mBackBufferLen,
+ src->next_input_byte,
+ src->bytes_in_buffer);
+
+ // Point to start of data to be rescanned.
+ src->next_input_byte = decoder->mBackBuffer + decoder->mBackBufferLen -
+ decoder->mBackBufferUnreadLen;
+ src->bytes_in_buffer += decoder->mBackBufferUnreadLen;
+ decoder->mBackBufferLen = (size_t)new_backtrack_buflen;
+ decoder->mReading = true;
+
+ return false;
+}
+
+/******************************************************************************/
+/* data source manager method */
+/*
+ * Terminate source --- called by jpeg_finish_decompress() after all
+ * data has been read to clean up JPEG source manager. NOT called by
+ * jpeg_abort() or jpeg_destroy().
+ */
+METHODDEF(void)
+term_source (j_decompress_ptr jd)
+{
+ nsJPEGDecoder* decoder = (nsJPEGDecoder*)(jd->client_data);
+
+ // This function shouldn't be called if we ran into an error we didn't
+ // recover from.
+ MOZ_ASSERT(decoder->mState != JPEG_ERROR,
+ "Calling term_source on a JPEG with mState == JPEG_ERROR!");
+
+ // Notify using a helper method to get around protectedness issues.
+ decoder->NotifyDone();
+}
+
+} // namespace image
+} // namespace mozilla
+
+///*************** Inverted CMYK -> RGB conversion *************************
+/// Input is (Inverted) CMYK stored as 4 bytes per pixel.
+/// Output is RGB stored as 3 bytes per pixel.
+/// @param row Points to row buffer containing the CMYK bytes for each pixel
+/// in the row.
+/// @param width Number of pixels in the row.
+static void cmyk_convert_rgb(JSAMPROW row, JDIMENSION width)
+{
+ // Work from end to front to shrink from 4 bytes per pixel to 3
+ JSAMPROW in = row + width*4;
+ JSAMPROW out = in;
+
+ for (uint32_t i = width; i > 0; i--) {
+ in -= 4;
+ out -= 3;
+
+ // Source is 'Inverted CMYK', output is RGB.
+ // See: http://www.easyrgb.com/math.php?MATH=M12#text12
+ // Or: http://www.ilkeratalay.com/colorspacesfaq.php#rgb
+
+ // From CMYK to CMY
+ // C = ( C * ( 1 - K ) + K )
+ // M = ( M * ( 1 - K ) + K )
+ // Y = ( Y * ( 1 - K ) + K )
+
+ // From Inverted CMYK to CMY is thus:
+ // C = ( (1-iC) * (1 - (1-iK)) + (1-iK) ) => 1 - iC*iK
+ // Same for M and Y
+
+ // Convert from CMY (0..1) to RGB (0..1)
+ // R = 1 - C => 1 - (1 - iC*iK) => iC*iK
+ // G = 1 - M => 1 - (1 - iM*iK) => iM*iK
+ // B = 1 - Y => 1 - (1 - iY*iK) => iY*iK
+
+ // Convert from Inverted CMYK (0..255) to RGB (0..255)
+ const uint32_t iC = in[0];
+ const uint32_t iM = in[1];
+ const uint32_t iY = in[2];
+ const uint32_t iK = in[3];
+ out[0] = iC*iK/255; // Red
+ out[1] = iM*iK/255; // Green
+ out[2] = iY*iK/255; // Blue
+ }
+}
diff --git a/image/decoders/nsJPEGDecoder.h b/image/decoders/nsJPEGDecoder.h
new file mode 100644
index 000000000..260e91303
--- /dev/null
+++ b/image/decoders/nsJPEGDecoder.h
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_image_decoders_nsJPEGDecoder_h
+#define mozilla_image_decoders_nsJPEGDecoder_h
+
+#include "RasterImage.h"
+// On Windows systems, RasterImage.h brings in 'windows.h', which defines INT32.
+// But the jpeg decoder has its own definition of INT32. To avoid build issues,
+// we need to undefine the version from 'windows.h'.
+#undef INT32
+
+#include "Decoder.h"
+
+#include "nsIInputStream.h"
+#include "nsIPipe.h"
+#include "qcms.h"
+
+extern "C" {
+#include "jpeglib.h"
+}
+
+#include <setjmp.h>
+
+namespace mozilla {
+namespace image {
+
+typedef struct {
+ struct jpeg_error_mgr pub; // "public" fields for IJG library
+ jmp_buf setjmp_buffer; // For handling catastropic errors
+} decoder_error_mgr;
+
+typedef enum {
+ JPEG_HEADER, // Reading JFIF headers
+ JPEG_START_DECOMPRESS,
+ JPEG_DECOMPRESS_PROGRESSIVE, // Output progressive pixels
+ JPEG_DECOMPRESS_SEQUENTIAL, // Output sequential pixels
+ JPEG_DONE,
+ JPEG_SINK_NON_JPEG_TRAILER, // Some image files have a
+ // non-JPEG trailer
+ JPEG_ERROR
+} jstate;
+
+class RasterImage;
+struct Orientation;
+
+class nsJPEGDecoder : public Decoder
+{
+public:
+ virtual ~nsJPEGDecoder();
+
+ virtual void SetSampleSize(int aSampleSize) override
+ {
+ mSampleSize = aSampleSize;
+ }
+
+ void NotifyDone();
+
+protected:
+ nsresult InitInternal() override;
+ LexerResult DoDecode(SourceBufferIterator& aIterator,
+ IResumable* aOnResume) override;
+ nsresult FinishInternal() override;
+
+ Maybe<Telemetry::ID> SpeedHistogram() const override;
+
+protected:
+ Orientation ReadOrientationFromEXIF();
+ void OutputScanlines(bool* suspend);
+
+private:
+ friend class DecoderFactory;
+
+ // Decoders should only be instantiated via DecoderFactory.
+ nsJPEGDecoder(RasterImage* aImage, Decoder::DecodeStyle aDecodeStyle);
+
+ enum class State
+ {
+ JPEG_DATA,
+ FINISHED_JPEG_DATA
+ };
+
+ LexerTransition<State> ReadJPEGData(const char* aData, size_t aLength);
+ LexerTransition<State> FinishedJPEGData();
+
+ StreamingLexer<State> mLexer;
+
+public:
+ struct jpeg_decompress_struct mInfo;
+ struct jpeg_source_mgr mSourceMgr;
+ decoder_error_mgr mErr;
+ jstate mState;
+
+ uint32_t mBytesToSkip;
+
+ const JOCTET* mSegment; // The current segment we are decoding from
+ uint32_t mSegmentLen; // amount of data in mSegment
+
+ JOCTET* mBackBuffer;
+ uint32_t mBackBufferLen; // Offset of end of active backtrack data
+ uint32_t mBackBufferSize; // size in bytes what mBackBuffer was created with
+ uint32_t mBackBufferUnreadLen; // amount of data currently in mBackBuffer
+
+ JOCTET * mProfile;
+ uint32_t mProfileLength;
+
+ qcms_profile* mInProfile;
+ qcms_transform* mTransform;
+
+ bool mReading;
+
+ const Decoder::DecodeStyle mDecodeStyle;
+
+ uint32_t mCMSMode;
+
+ int mSampleSize;
+};
+
+} // namespace image
+} // namespace mozilla
+
+#endif // mozilla_image_decoders_nsJPEGDecoder_h
diff --git a/image/decoders/nsPNGDecoder.cpp b/image/decoders/nsPNGDecoder.cpp
new file mode 100644
index 000000000..0f385b339
--- /dev/null
+++ b/image/decoders/nsPNGDecoder.cpp
@@ -0,0 +1,1113 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ImageLogging.h" // Must appear first
+#include "nsPNGDecoder.h"
+
+#include <algorithm>
+#include <cstdint>
+
+#include "gfxColor.h"
+#include "gfxPlatform.h"
+#include "imgFrame.h"
+#include "nsColor.h"
+#include "nsIInputStream.h"
+#include "nsMemory.h"
+#include "nsRect.h"
+#include "nspr.h"
+#include "png.h"
+#include "RasterImage.h"
+#include "SurfacePipeFactory.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Telemetry.h"
+
+using namespace mozilla::gfx;
+
+using std::min;
+
+namespace mozilla {
+namespace image {
+
+static LazyLogModule sPNGLog("PNGDecoder");
+static LazyLogModule sPNGDecoderAccountingLog("PNGDecoderAccounting");
+
+// limit image dimensions (bug #251381, #591822, #967656, and #1283961)
+#ifndef MOZ_PNG_MAX_WIDTH
+# define MOZ_PNG_MAX_WIDTH 0x7fffffff // Unlimited
+#endif
+#ifndef MOZ_PNG_MAX_HEIGHT
+# define MOZ_PNG_MAX_HEIGHT 0x7fffffff // Unlimited
+#endif
+
+nsPNGDecoder::AnimFrameInfo::AnimFrameInfo()
+ : mDispose(DisposalMethod::KEEP)
+ , mBlend(BlendMethod::OVER)
+ , mTimeout(0)
+{ }
+
+#ifdef PNG_APNG_SUPPORTED
+
+int32_t GetNextFrameDelay(png_structp aPNG, png_infop aInfo)
+{
+ // Delay, in seconds, is delayNum / delayDen.
+ png_uint_16 delayNum = png_get_next_frame_delay_num(aPNG, aInfo);
+ png_uint_16 delayDen = png_get_next_frame_delay_den(aPNG, aInfo);
+
+ if (delayNum == 0) {
+ return 0; // SetFrameTimeout() will set to a minimum.
+ }
+
+ if (delayDen == 0) {
+ delayDen = 100; // So says the APNG spec.
+ }
+
+ // Need to cast delay_num to float to have a proper division and
+ // the result to int to avoid a compiler warning.
+ return static_cast<int32_t>(static_cast<double>(delayNum) * 1000 / delayDen);
+}
+
+nsPNGDecoder::AnimFrameInfo::AnimFrameInfo(png_structp aPNG, png_infop aInfo)
+ : mDispose(DisposalMethod::KEEP)
+ , mBlend(BlendMethod::OVER)
+ , mTimeout(0)
+{
+ png_byte dispose_op = png_get_next_frame_dispose_op(aPNG, aInfo);
+ png_byte blend_op = png_get_next_frame_blend_op(aPNG, aInfo);
+
+ if (dispose_op == PNG_DISPOSE_OP_PREVIOUS) {
+ mDispose = DisposalMethod::RESTORE_PREVIOUS;
+ } else if (dispose_op == PNG_DISPOSE_OP_BACKGROUND) {
+ mDispose = DisposalMethod::CLEAR;
+ } else {
+ mDispose = DisposalMethod::KEEP;
+ }
+
+ if (blend_op == PNG_BLEND_OP_SOURCE) {
+ mBlend = BlendMethod::SOURCE;
+ } else {
+ mBlend = BlendMethod::OVER;
+ }
+
+ mTimeout = GetNextFrameDelay(aPNG, aInfo);
+}
+#endif
+
+// First 8 bytes of a PNG file
+const uint8_t
+nsPNGDecoder::pngSignatureBytes[] = { 137, 80, 78, 71, 13, 10, 26, 10 };
+
+nsPNGDecoder::nsPNGDecoder(RasterImage* aImage)
+ : Decoder(aImage)
+ , mLexer(Transition::ToUnbuffered(State::FINISHED_PNG_DATA,
+ State::PNG_DATA,
+ SIZE_MAX),
+ Transition::TerminateSuccess())
+ , mNextTransition(Transition::ContinueUnbuffered(State::PNG_DATA))
+ , mLastChunkLength(0)
+ , mPNG(nullptr)
+ , mInfo(nullptr)
+ , mCMSLine(nullptr)
+ , interlacebuf(nullptr)
+ , mInProfile(nullptr)
+ , mTransform(nullptr)
+ , format(gfx::SurfaceFormat::UNKNOWN)
+ , mCMSMode(0)
+ , mChannels(0)
+ , mPass(0)
+ , mFrameIsHidden(false)
+ , mDisablePremultipliedAlpha(false)
+ , mNumFrames(0)
+{ }
+
+nsPNGDecoder::~nsPNGDecoder()
+{
+ if (mPNG) {
+ png_destroy_read_struct(&mPNG, mInfo ? &mInfo : nullptr, nullptr);
+ }
+ if (mCMSLine) {
+ free(mCMSLine);
+ }
+ if (interlacebuf) {
+ free(interlacebuf);
+ }
+ if (mInProfile) {
+ qcms_profile_release(mInProfile);
+
+ // mTransform belongs to us only if mInProfile is non-null
+ if (mTransform) {
+ qcms_transform_release(mTransform);
+ }
+ }
+}
+
+nsPNGDecoder::TransparencyType
+nsPNGDecoder::GetTransparencyType(SurfaceFormat aFormat,
+ const IntRect& aFrameRect)
+{
+ // Check if the image has a transparent color in its palette.
+ if (aFormat == SurfaceFormat::B8G8R8A8) {
+ return TransparencyType::eAlpha;
+ }
+ if (!aFrameRect.IsEqualEdges(FullFrame())) {
+ MOZ_ASSERT(HasAnimation());
+ return TransparencyType::eFrameRect;
+ }
+
+ return TransparencyType::eNone;
+}
+
+void
+nsPNGDecoder::PostHasTransparencyIfNeeded(TransparencyType aTransparencyType)
+{
+ switch (aTransparencyType) {
+ case TransparencyType::eNone:
+ return;
+
+ case TransparencyType::eAlpha:
+ PostHasTransparency();
+ return;
+
+ case TransparencyType::eFrameRect:
+ // If the first frame of animated image doesn't draw into the whole image,
+ // then record that it is transparent. For subsequent frames, this doesn't
+ // affect transparency, because they're composited on top of all previous
+ // frames.
+ if (mNumFrames == 0) {
+ PostHasTransparency();
+ }
+ return;
+ }
+}
+
+// CreateFrame() is used for both simple and animated images.
+nsresult
+nsPNGDecoder::CreateFrame(const FrameInfo& aFrameInfo)
+{
+ MOZ_ASSERT(HasSize());
+ MOZ_ASSERT(!IsMetadataDecode());
+
+ // Check if we have transparency, and send notifications if needed.
+ auto transparency = GetTransparencyType(aFrameInfo.mFormat,
+ aFrameInfo.mFrameRect);
+ PostHasTransparencyIfNeeded(transparency);
+ SurfaceFormat format = transparency == TransparencyType::eNone
+ ? SurfaceFormat::B8G8R8X8
+ : SurfaceFormat::B8G8R8A8;
+
+ // Make sure there's no animation or padding if we're downscaling.
+ MOZ_ASSERT_IF(Size() != OutputSize(), mNumFrames == 0);
+ MOZ_ASSERT_IF(Size() != OutputSize(), !GetImageMetadata().HasAnimation());
+ MOZ_ASSERT_IF(Size() != OutputSize(),
+ transparency != TransparencyType::eFrameRect);
+
+ // If this image is interlaced, we can display better quality intermediate
+ // results to the user by post processing them with ADAM7InterpolatingFilter.
+ SurfacePipeFlags pipeFlags = aFrameInfo.mIsInterlaced
+ ? SurfacePipeFlags::ADAM7_INTERPOLATE
+ : SurfacePipeFlags();
+
+ if (mNumFrames == 0) {
+ // The first frame may be displayed progressively.
+ pipeFlags |= SurfacePipeFlags::PROGRESSIVE_DISPLAY;
+ }
+
+ Maybe<SurfacePipe> pipe =
+ SurfacePipeFactory::CreateSurfacePipe(this, mNumFrames, Size(),
+ OutputSize(), aFrameInfo.mFrameRect,
+ format, pipeFlags);
+
+ if (!pipe) {
+ mPipe = SurfacePipe();
+ return NS_ERROR_FAILURE;
+ }
+
+ mPipe = Move(*pipe);
+
+ mFrameRect = aFrameInfo.mFrameRect;
+ mPass = 0;
+
+ MOZ_LOG(sPNGDecoderAccountingLog, LogLevel::Debug,
+ ("PNGDecoderAccounting: nsPNGDecoder::CreateFrame -- created "
+ "image frame with %dx%d pixels for decoder %p",
+ mFrameRect.width, mFrameRect.height, this));
+
+#ifdef PNG_APNG_SUPPORTED
+ if (png_get_valid(mPNG, mInfo, PNG_INFO_acTL)) {
+ mAnimInfo = AnimFrameInfo(mPNG, mInfo);
+
+ if (mAnimInfo.mDispose == DisposalMethod::CLEAR) {
+ // We may have to display the background under this image during
+ // animation playback, so we regard it as transparent.
+ PostHasTransparency();
+ }
+ }
+#endif
+
+ return NS_OK;
+}
+
+// set timeout and frame disposal method for the current frame
+void
+nsPNGDecoder::EndImageFrame()
+{
+ if (mFrameIsHidden) {
+ return;
+ }
+
+ mNumFrames++;
+
+ Opacity opacity = Opacity::SOME_TRANSPARENCY;
+ if (format == gfx::SurfaceFormat::B8G8R8X8) {
+ opacity = Opacity::FULLY_OPAQUE;
+ }
+
+ PostFrameStop(opacity, mAnimInfo.mDispose,
+ FrameTimeout::FromRawMilliseconds(mAnimInfo.mTimeout),
+ mAnimInfo.mBlend, Some(mFrameRect));
+}
+
+nsresult
+nsPNGDecoder::InitInternal()
+{
+ mCMSMode = gfxPlatform::GetCMSMode();
+ if (GetSurfaceFlags() & SurfaceFlags::NO_COLORSPACE_CONVERSION) {
+ mCMSMode = eCMSMode_Off;
+ }
+ mDisablePremultipliedAlpha =
+ bool(GetSurfaceFlags() & SurfaceFlags::NO_PREMULTIPLY_ALPHA);
+
+#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
+ static png_byte color_chunks[]=
+ { 99, 72, 82, 77, '\0', // cHRM
+ 105, 67, 67, 80, '\0'}; // iCCP
+ static png_byte unused_chunks[]=
+ { 98, 75, 71, 68, '\0', // bKGD
+ 104, 73, 83, 84, '\0', // hIST
+ 105, 84, 88, 116, '\0', // iTXt
+ 111, 70, 70, 115, '\0', // oFFs
+ 112, 67, 65, 76, '\0', // pCAL
+ 115, 67, 65, 76, '\0', // sCAL
+ 112, 72, 89, 115, '\0', // pHYs
+ 115, 66, 73, 84, '\0', // sBIT
+ 115, 80, 76, 84, '\0', // sPLT
+ 116, 69, 88, 116, '\0', // tEXt
+ 116, 73, 77, 69, '\0', // tIME
+ 122, 84, 88, 116, '\0'}; // zTXt
+#endif
+
+ // Initialize the container's source image header
+ // Always decode to 24 bit pixdepth
+
+ mPNG = png_create_read_struct(PNG_LIBPNG_VER_STRING,
+ nullptr, nsPNGDecoder::error_callback,
+ nsPNGDecoder::warning_callback);
+ if (!mPNG) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ mInfo = png_create_info_struct(mPNG);
+ if (!mInfo) {
+ png_destroy_read_struct(&mPNG, nullptr, nullptr);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
+ // Ignore unused chunks
+ if (mCMSMode == eCMSMode_Off || IsMetadataDecode()) {
+ png_set_keep_unknown_chunks(mPNG, 1, color_chunks, 2);
+ }
+
+ png_set_keep_unknown_chunks(mPNG, 1, unused_chunks,
+ (int)sizeof(unused_chunks)/5);
+#endif
+
+#ifdef PNG_SET_USER_LIMITS_SUPPORTED
+ png_set_user_limits(mPNG, MOZ_PNG_MAX_WIDTH, MOZ_PNG_MAX_HEIGHT);
+ if (mCMSMode != eCMSMode_Off) {
+ png_set_chunk_malloc_max(mPNG, 4000000L);
+ }
+#endif
+
+#ifdef PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED
+ // Disallow palette-index checking, for speed; we would ignore the warning
+ // anyhow. This feature was added at libpng version 1.5.10 and is disabled
+ // in the embedded libpng but enabled by default in the system libpng. This
+ // call also disables it in the system libpng, for decoding speed.
+ // Bug #745202.
+ png_set_check_for_invalid_index(mPNG, 0);
+#endif
+
+#ifdef PNG_SET_OPTION_SUPPORTED
+#if defined(PNG_sRGB_PROFILE_CHECKS) && PNG_sRGB_PROFILE_CHECKS >= 0
+ // Skip checking of sRGB ICC profiles
+ png_set_option(mPNG, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON);
+#endif
+
+#ifdef PNG_MAXIMUM_INFLATE_WINDOW
+ // Force a larger zlib inflate window as some images in the wild have
+ // incorrectly set metadata (specifically CMF bits) which prevent us from
+ // decoding them otherwise.
+ png_set_option(mPNG, PNG_MAXIMUM_INFLATE_WINDOW, PNG_OPTION_ON);
+#endif
+#endif
+
+ // use this as libpng "progressive pointer" (retrieve in callbacks)
+ png_set_progressive_read_fn(mPNG, static_cast<png_voidp>(this),
+ nsPNGDecoder::info_callback,
+ nsPNGDecoder::row_callback,
+ nsPNGDecoder::end_callback);
+
+ return NS_OK;
+}
+
+LexerResult
+nsPNGDecoder::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume)
+{
+ MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!");
+
+ return mLexer.Lex(aIterator, aOnResume,
+ [=](State aState, const char* aData, size_t aLength) {
+ switch (aState) {
+ case State::PNG_DATA:
+ return ReadPNGData(aData, aLength);
+ case State::FINISHED_PNG_DATA:
+ return FinishedPNGData();
+ }
+ MOZ_CRASH("Unknown State");
+ });
+}
+
+LexerTransition<nsPNGDecoder::State>
+nsPNGDecoder::ReadPNGData(const char* aData, size_t aLength)
+{
+ // If we were waiting until after returning from a yield to call
+ // CreateFrame(), call it now.
+ if (mNextFrameInfo) {
+ if (NS_FAILED(CreateFrame(*mNextFrameInfo))) {
+ return Transition::TerminateFailure();
+ }
+
+ MOZ_ASSERT(mImageData, "Should have a buffer now");
+ mNextFrameInfo = Nothing();
+ }
+
+ // libpng uses setjmp/longjmp for error handling.
+ if (setjmp(png_jmpbuf(mPNG))) {
+ return Transition::TerminateFailure();
+ }
+
+ // Pass the data off to libpng.
+ mLastChunkLength = aLength;
+ mNextTransition = Transition::ContinueUnbuffered(State::PNG_DATA);
+ png_process_data(mPNG, mInfo,
+ reinterpret_cast<unsigned char*>(const_cast<char*>((aData))),
+ aLength);
+
+ // Make sure that we've reached a terminal state if decoding is done.
+ MOZ_ASSERT_IF(GetDecodeDone(), mNextTransition.NextStateIsTerminal());
+ MOZ_ASSERT_IF(HasError(), mNextTransition.NextStateIsTerminal());
+
+ // Continue with whatever transition the callback code requested. We
+ // initialized this to Transition::ContinueUnbuffered(State::PNG_DATA) above,
+ // so by default we just continue the unbuffered read.
+ return mNextTransition;
+}
+
+LexerTransition<nsPNGDecoder::State>
+nsPNGDecoder::FinishedPNGData()
+{
+ // Since we set up an unbuffered read for SIZE_MAX bytes, if we actually read
+ // all that data something is really wrong.
+ MOZ_ASSERT_UNREACHABLE("Read the entire address space?");
+ return Transition::TerminateFailure();
+}
+
+// Sets up gamma pre-correction in libpng before our callback gets called.
+// We need to do this if we don't end up with a CMS profile.
+static void
+PNGDoGammaCorrection(png_structp png_ptr, png_infop info_ptr)
+{
+ double aGamma;
+
+ if (png_get_gAMA(png_ptr, info_ptr, &aGamma)) {
+ if ((aGamma <= 0.0) || (aGamma > 21474.83)) {
+ aGamma = 0.45455;
+ png_set_gAMA(png_ptr, info_ptr, aGamma);
+ }
+ png_set_gamma(png_ptr, 2.2, aGamma);
+ } else {
+ png_set_gamma(png_ptr, 2.2, 0.45455);
+ }
+}
+
+// Adapted from http://www.littlecms.com/pngchrm.c example code
+static qcms_profile*
+PNGGetColorProfile(png_structp png_ptr, png_infop info_ptr,
+ int color_type, qcms_data_type* inType, uint32_t* intent)
+{
+ qcms_profile* profile = nullptr;
+ *intent = QCMS_INTENT_PERCEPTUAL; // Our default
+
+ // First try to see if iCCP chunk is present
+ if (png_get_valid(png_ptr, info_ptr, PNG_INFO_iCCP)) {
+ png_uint_32 profileLen;
+ png_bytep profileData;
+ png_charp profileName;
+ int compression;
+
+ png_get_iCCP(png_ptr, info_ptr, &profileName, &compression,
+ &profileData, &profileLen);
+
+ profile = qcms_profile_from_memory((char*)profileData, profileLen);
+ if (profile) {
+ uint32_t profileSpace = qcms_profile_get_color_space(profile);
+
+ bool mismatch = false;
+ if (color_type & PNG_COLOR_MASK_COLOR) {
+ if (profileSpace != icSigRgbData) {
+ mismatch = true;
+ }
+ } else {
+ if (profileSpace == icSigRgbData) {
+ png_set_gray_to_rgb(png_ptr);
+ } else if (profileSpace != icSigGrayData) {
+ mismatch = true;
+ }
+ }
+
+ if (mismatch) {
+ qcms_profile_release(profile);
+ profile = nullptr;
+ } else {
+ *intent = qcms_profile_get_rendering_intent(profile);
+ }
+ }
+ }
+
+ // Check sRGB chunk
+ if (!profile && png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB)) {
+ profile = qcms_profile_sRGB();
+
+ if (profile) {
+ int fileIntent;
+ png_set_gray_to_rgb(png_ptr);
+ png_get_sRGB(png_ptr, info_ptr, &fileIntent);
+ uint32_t map[] = { QCMS_INTENT_PERCEPTUAL,
+ QCMS_INTENT_RELATIVE_COLORIMETRIC,
+ QCMS_INTENT_SATURATION,
+ QCMS_INTENT_ABSOLUTE_COLORIMETRIC };
+ *intent = map[fileIntent];
+ }
+ }
+
+ // Check gAMA/cHRM chunks
+ if (!profile &&
+ png_get_valid(png_ptr, info_ptr, PNG_INFO_gAMA) &&
+ png_get_valid(png_ptr, info_ptr, PNG_INFO_cHRM)) {
+ qcms_CIE_xyYTRIPLE primaries;
+ qcms_CIE_xyY whitePoint;
+
+ png_get_cHRM(png_ptr, info_ptr,
+ &whitePoint.x, &whitePoint.y,
+ &primaries.red.x, &primaries.red.y,
+ &primaries.green.x, &primaries.green.y,
+ &primaries.blue.x, &primaries.blue.y);
+ whitePoint.Y =
+ primaries.red.Y = primaries.green.Y = primaries.blue.Y = 1.0;
+
+ double gammaOfFile;
+
+ png_get_gAMA(png_ptr, info_ptr, &gammaOfFile);
+
+ profile = qcms_profile_create_rgb_with_gamma(whitePoint, primaries,
+ 1.0/gammaOfFile);
+
+ if (profile) {
+ png_set_gray_to_rgb(png_ptr);
+ }
+ }
+
+ if (profile) {
+ uint32_t profileSpace = qcms_profile_get_color_space(profile);
+ if (profileSpace == icSigGrayData) {
+ if (color_type & PNG_COLOR_MASK_ALPHA) {
+ *inType = QCMS_DATA_GRAYA_8;
+ } else {
+ *inType = QCMS_DATA_GRAY_8;
+ }
+ } else {
+ if (color_type & PNG_COLOR_MASK_ALPHA ||
+ png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
+ *inType = QCMS_DATA_RGBA_8;
+ } else {
+ *inType = QCMS_DATA_RGB_8;
+ }
+ }
+ }
+
+ return profile;
+}
+
+void
+nsPNGDecoder::info_callback(png_structp png_ptr, png_infop info_ptr)
+{
+ png_uint_32 width, height;
+ int bit_depth, color_type, interlace_type, compression_type, filter_type;
+ unsigned int channels;
+
+ png_bytep trans = nullptr;
+ int num_trans = 0;
+
+ nsPNGDecoder* decoder =
+ static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
+
+ // Always decode to 24-bit RGB or 32-bit RGBA
+ png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
+ &interlace_type, &compression_type, &filter_type);
+
+ const IntRect frameRect(0, 0, width, height);
+
+ // Post our size to the superclass
+ decoder->PostSize(frameRect.width, frameRect.height);
+ if (decoder->HasError()) {
+ // Setting the size led to an error.
+ png_error(decoder->mPNG, "Sizing error");
+ }
+
+ if (color_type == PNG_COLOR_TYPE_PALETTE) {
+ png_set_expand(png_ptr);
+ }
+
+ if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) {
+ png_set_expand(png_ptr);
+ }
+
+ if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
+ png_color_16p trans_values;
+ png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &trans_values);
+ // libpng doesn't reject a tRNS chunk with out-of-range samples
+ // so we check it here to avoid setting up a useless opacity
+ // channel or producing unexpected transparent pixels (bug #428045)
+ if (bit_depth < 16) {
+ png_uint_16 sample_max = (1 << bit_depth) - 1;
+ if ((color_type == PNG_COLOR_TYPE_GRAY &&
+ trans_values->gray > sample_max) ||
+ (color_type == PNG_COLOR_TYPE_RGB &&
+ (trans_values->red > sample_max ||
+ trans_values->green > sample_max ||
+ trans_values->blue > sample_max))) {
+ // clear the tRNS valid flag and release tRNS memory
+ png_free_data(png_ptr, info_ptr, PNG_FREE_TRNS, 0);
+ num_trans = 0;
+ }
+ }
+ if (num_trans != 0) {
+ png_set_expand(png_ptr);
+ }
+ }
+
+ if (bit_depth == 16) {
+ png_set_scale_16(png_ptr);
+ }
+
+ qcms_data_type inType = QCMS_DATA_RGBA_8;
+ uint32_t intent = -1;
+ uint32_t pIntent;
+ if (decoder->mCMSMode != eCMSMode_Off) {
+ intent = gfxPlatform::GetRenderingIntent();
+ decoder->mInProfile = PNGGetColorProfile(png_ptr, info_ptr,
+ color_type, &inType, &pIntent);
+ // If we're not mandating an intent, use the one from the image.
+ if (intent == uint32_t(-1)) {
+ intent = pIntent;
+ }
+ }
+ if (decoder->mInProfile && gfxPlatform::GetCMSOutputProfile()) {
+ qcms_data_type outType;
+
+ if (color_type & PNG_COLOR_MASK_ALPHA || num_trans) {
+ outType = QCMS_DATA_RGBA_8;
+ } else {
+ outType = QCMS_DATA_RGB_8;
+ }
+
+ decoder->mTransform = qcms_transform_create(decoder->mInProfile,
+ inType,
+ gfxPlatform::GetCMSOutputProfile(),
+ outType,
+ (qcms_intent)intent);
+ } else {
+ png_set_gray_to_rgb(png_ptr);
+
+ // only do gamma correction if CMS isn't entirely disabled
+ if (decoder->mCMSMode != eCMSMode_Off) {
+ PNGDoGammaCorrection(png_ptr, info_ptr);
+ }
+
+ if (decoder->mCMSMode == eCMSMode_All) {
+ if (color_type & PNG_COLOR_MASK_ALPHA || num_trans) {
+ decoder->mTransform = gfxPlatform::GetCMSRGBATransform();
+ } else {
+ decoder->mTransform = gfxPlatform::GetCMSRGBTransform();
+ }
+ }
+ }
+
+ // Let libpng expand interlaced images.
+ const bool isInterlaced = interlace_type == PNG_INTERLACE_ADAM7;
+ if (isInterlaced) {
+ png_set_interlace_handling(png_ptr);
+ }
+
+ // now all of those things we set above are used to update various struct
+ // members and whatnot, after which we can get channels, rowbytes, etc.
+ png_read_update_info(png_ptr, info_ptr);
+ decoder->mChannels = channels = png_get_channels(png_ptr, info_ptr);
+
+ //---------------------------------------------------------------//
+ // copy PNG info into imagelib structs (formerly png_set_dims()) //
+ //---------------------------------------------------------------//
+
+ if (channels == 1 || channels == 3) {
+ decoder->format = gfx::SurfaceFormat::B8G8R8X8;
+ } else if (channels == 2 || channels == 4) {
+ decoder->format = gfx::SurfaceFormat::B8G8R8A8;
+ } else {
+ png_error(decoder->mPNG, "Invalid number of channels");
+ }
+
+#ifdef PNG_APNG_SUPPORTED
+ bool isAnimated = png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL);
+ if (isAnimated) {
+ int32_t rawTimeout = GetNextFrameDelay(png_ptr, info_ptr);
+ decoder->PostIsAnimated(FrameTimeout::FromRawMilliseconds(rawTimeout));
+
+ if (decoder->Size() != decoder->OutputSize() &&
+ !decoder->IsFirstFrameDecode()) {
+ MOZ_ASSERT_UNREACHABLE("Doing downscale-during-decode "
+ "for an animated image?");
+ png_error(decoder->mPNG, "Invalid downscale attempt"); // Abort decode.
+ }
+ }
+#endif
+
+ if (decoder->IsMetadataDecode()) {
+ // If we are animated then the first frame rect is either:
+ // 1) the whole image if the IDAT chunk is part of the animation
+ // 2) the frame rect of the first fDAT chunk otherwise.
+ // If we are not animated then we want to make sure to call
+ // PostHasTransparency in the metadata decode if we need to. So it's
+ // okay to pass IntRect(0, 0, width, height) here for animated images;
+ // they will call with the proper first frame rect in the full decode.
+ auto transparency = decoder->GetTransparencyType(decoder->format,
+ frameRect);
+ decoder->PostHasTransparencyIfNeeded(transparency);
+
+ // We have the metadata we're looking for, so stop here, before we allocate
+ // buffers below.
+ return decoder->DoTerminate(png_ptr, TerminalState::SUCCESS);
+ }
+
+#ifdef PNG_APNG_SUPPORTED
+ if (isAnimated) {
+ png_set_progressive_frame_fn(png_ptr, nsPNGDecoder::frame_info_callback,
+ nullptr);
+ }
+
+ if (png_get_first_frame_is_hidden(png_ptr, info_ptr)) {
+ decoder->mFrameIsHidden = true;
+ } else {
+#endif
+ nsresult rv = decoder->CreateFrame(FrameInfo{ decoder->format,
+ frameRect,
+ isInterlaced });
+ if (NS_FAILED(rv)) {
+ png_error(decoder->mPNG, "CreateFrame failed");
+ }
+ MOZ_ASSERT(decoder->mImageData, "Should have a buffer now");
+#ifdef PNG_APNG_SUPPORTED
+ }
+#endif
+
+ if (decoder->mTransform && (channels <= 2 || isInterlaced)) {
+ uint32_t bpp[] = { 0, 3, 4, 3, 4 };
+ decoder->mCMSLine =
+ static_cast<uint8_t*>(malloc(bpp[channels] * frameRect.width));
+ if (!decoder->mCMSLine) {
+ png_error(decoder->mPNG, "malloc of mCMSLine failed");
+ }
+ }
+
+ if (interlace_type == PNG_INTERLACE_ADAM7) {
+ if (frameRect.height < INT32_MAX / (frameRect.width * int32_t(channels))) {
+ const size_t bufferSize = channels * frameRect.width * frameRect.height;
+ decoder->interlacebuf = static_cast<uint8_t*>(malloc(bufferSize));
+ }
+ if (!decoder->interlacebuf) {
+ png_error(decoder->mPNG, "malloc of interlacebuf failed");
+ }
+ }
+}
+
+void
+nsPNGDecoder::PostInvalidationIfNeeded()
+{
+ Maybe<SurfaceInvalidRect> invalidRect = mPipe.TakeInvalidRect();
+ if (!invalidRect) {
+ return;
+ }
+
+ PostInvalidation(invalidRect->mInputSpaceRect,
+ Some(invalidRect->mOutputSpaceRect));
+}
+
+static NextPixel<uint32_t>
+PackRGBPixelAndAdvance(uint8_t*& aRawPixelInOut)
+{
+ const uint32_t pixel =
+ gfxPackedPixel(0xFF, aRawPixelInOut[0], aRawPixelInOut[1],
+ aRawPixelInOut[2]);
+ aRawPixelInOut += 3;
+ return AsVariant(pixel);
+}
+
+static NextPixel<uint32_t>
+PackRGBAPixelAndAdvance(uint8_t*& aRawPixelInOut)
+{
+ const uint32_t pixel =
+ gfxPackedPixel(aRawPixelInOut[3], aRawPixelInOut[0],
+ aRawPixelInOut[1], aRawPixelInOut[2]);
+ aRawPixelInOut += 4;
+ return AsVariant(pixel);
+}
+
+static NextPixel<uint32_t>
+PackUnpremultipliedRGBAPixelAndAdvance(uint8_t*& aRawPixelInOut)
+{
+ const uint32_t pixel =
+ gfxPackedPixelNoPreMultiply(aRawPixelInOut[3], aRawPixelInOut[0],
+ aRawPixelInOut[1], aRawPixelInOut[2]);
+ aRawPixelInOut += 4;
+ return AsVariant(pixel);
+}
+
+void
+nsPNGDecoder::row_callback(png_structp png_ptr, png_bytep new_row,
+ png_uint_32 row_num, int pass)
+{
+ /* libpng comments:
+ *
+ * This function is called for every row in the image. If the
+ * image is interlacing, and you turned on the interlace handler,
+ * this function will be called for every row in every pass.
+ * Some of these rows will not be changed from the previous pass.
+ * When the row is not changed, the new_row variable will be
+ * nullptr. The rows and passes are called in order, so you don't
+ * really need the row_num and pass, but I'm supplying them
+ * because it may make your life easier.
+ *
+ * For the non-nullptr rows of interlaced images, you must call
+ * png_progressive_combine_row() passing in the row and the
+ * old row. You can call this function for nullptr rows (it will
+ * just return) and for non-interlaced images (it just does the
+ * memcpy for you) if it will make the code easier. Thus, you
+ * can just do this for all cases:
+ *
+ * png_progressive_combine_row(png_ptr, old_row, new_row);
+ *
+ * where old_row is what was displayed for previous rows. Note
+ * that the first pass (pass == 0 really) will completely cover
+ * the old row, so the rows do not have to be initialized. After
+ * the first pass (and only for interlaced images), you will have
+ * to pass the current row, and the function will combine the
+ * old row and the new row.
+ */
+ nsPNGDecoder* decoder =
+ static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
+
+ if (decoder->mFrameIsHidden) {
+ return; // Skip this frame.
+ }
+
+ MOZ_ASSERT_IF(decoder->IsFirstFrameDecode(), decoder->mNumFrames == 0);
+
+ while (pass > decoder->mPass) {
+ // Advance to the next pass. We may have to do this multiple times because
+ // libpng will skip passes if the image is so small that no pixels have
+ // changed on a given pass, but ADAM7InterpolatingFilter needs to be reset
+ // once for every pass to perform interpolation properly.
+ decoder->mPipe.ResetToFirstRow();
+ decoder->mPass++;
+ }
+
+ const png_uint_32 height =
+ static_cast<png_uint_32>(decoder->mFrameRect.height);
+
+ if (row_num >= height) {
+ // Bail if we receive extra rows. This is especially important because if we
+ // didn't, we might overflow the deinterlacing buffer.
+ MOZ_ASSERT_UNREACHABLE("libpng producing extra rows?");
+ return;
+ }
+
+ // Note that |new_row| may be null here, indicating that this is an interlaced
+ // image and |row_callback| is being called for a row that hasn't changed.
+ MOZ_ASSERT_IF(!new_row, decoder->interlacebuf);
+ uint8_t* rowToWrite = new_row;
+
+ if (decoder->interlacebuf) {
+ uint32_t width = uint32_t(decoder->mFrameRect.width);
+
+ // We'll output the deinterlaced version of the row.
+ rowToWrite = decoder->interlacebuf + (row_num * decoder->mChannels * width);
+
+ // Update the deinterlaced version of this row with the new data.
+ png_progressive_combine_row(png_ptr, rowToWrite, new_row);
+ }
+
+ decoder->WriteRow(rowToWrite);
+}
+
+void
+nsPNGDecoder::WriteRow(uint8_t* aRow)
+{
+ MOZ_ASSERT(aRow);
+
+ uint8_t* rowToWrite = aRow;
+ uint32_t width = uint32_t(mFrameRect.width);
+
+ // Apply color management to the row, if necessary, before writing it out.
+ if (mTransform) {
+ if (mCMSLine) {
+ qcms_transform_data(mTransform, rowToWrite, mCMSLine, width);
+
+ // Copy alpha over.
+ if (mChannels == 2 || mChannels == 4) {
+ for (uint32_t i = 0; i < width; ++i) {
+ mCMSLine[4 * i + 3] = rowToWrite[mChannels * i + mChannels - 1];
+ }
+ }
+
+ rowToWrite = mCMSLine;
+ } else {
+ qcms_transform_data(mTransform, rowToWrite, rowToWrite, width);
+ }
+ }
+
+ // Write this row to the SurfacePipe.
+ DebugOnly<WriteState> result = WriteState::FAILURE;
+ switch (format) {
+ case SurfaceFormat::B8G8R8X8:
+ result = mPipe.WritePixelsToRow<uint32_t>([&]{
+ return PackRGBPixelAndAdvance(rowToWrite);
+ });
+ break;
+
+ case SurfaceFormat::B8G8R8A8:
+ if (mDisablePremultipliedAlpha) {
+ result = mPipe.WritePixelsToRow<uint32_t>([&]{
+ return PackUnpremultipliedRGBAPixelAndAdvance(rowToWrite);
+ });
+ } else {
+ result = mPipe.WritePixelsToRow<uint32_t>([&]{
+ return PackRGBAPixelAndAdvance(rowToWrite);
+ });
+ }
+ break;
+
+ default:
+ png_error(mPNG, "Invalid SurfaceFormat");
+ }
+
+ MOZ_ASSERT(WriteState(result) != WriteState::FAILURE);
+
+ PostInvalidationIfNeeded();
+}
+
+void
+nsPNGDecoder::DoTerminate(png_structp aPNGStruct, TerminalState aState)
+{
+ // Stop processing data. Note that we intentionally ignore the return value of
+ // png_process_data_pause(), which tells us how many bytes of the data that
+ // was passed to png_process_data() have not been consumed yet, because now
+ // that we've reached a terminal state, we won't do any more decoding or call
+ // back into libpng anymore.
+ png_process_data_pause(aPNGStruct, /* save = */ false);
+
+ mNextTransition = aState == TerminalState::SUCCESS
+ ? Transition::TerminateSuccess()
+ : Transition::TerminateFailure();
+}
+
+void
+nsPNGDecoder::DoYield(png_structp aPNGStruct)
+{
+ // Pause data processing. png_process_data_pause() returns how many bytes of
+ // the data that was passed to png_process_data() have not been consumed yet.
+ // We use this information to tell StreamingLexer where to place us in the
+ // input stream when we come back from the yield.
+ png_size_t pendingBytes = png_process_data_pause(aPNGStruct,
+ /* save = */ false);
+
+ MOZ_ASSERT(pendingBytes < mLastChunkLength);
+ size_t consumedBytes = mLastChunkLength - min(pendingBytes, mLastChunkLength);
+
+ mNextTransition =
+ Transition::ContinueUnbufferedAfterYield(State::PNG_DATA, consumedBytes);
+}
+
+#ifdef PNG_APNG_SUPPORTED
+// got the header of a new frame that's coming
+void
+nsPNGDecoder::frame_info_callback(png_structp png_ptr, png_uint_32 frame_num)
+{
+ nsPNGDecoder* decoder =
+ static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
+
+ // old frame is done
+ decoder->EndImageFrame();
+
+ const bool previousFrameWasHidden = decoder->mFrameIsHidden;
+
+ if (!previousFrameWasHidden && decoder->IsFirstFrameDecode()) {
+ // We're about to get a second non-hidden frame, but we only want the first.
+ // Stop decoding now. (And avoid allocating the unnecessary buffers below.)
+ decoder->PostDecodeDone();
+ return decoder->DoTerminate(png_ptr, TerminalState::SUCCESS);
+ }
+
+ // Only the first frame can be hidden, so unhide unconditionally here.
+ decoder->mFrameIsHidden = false;
+
+ // Save the information necessary to create the frame; we'll actually create
+ // it when we return from the yield.
+ const IntRect frameRect(png_get_next_frame_x_offset(png_ptr, decoder->mInfo),
+ png_get_next_frame_y_offset(png_ptr, decoder->mInfo),
+ png_get_next_frame_width(png_ptr, decoder->mInfo),
+ png_get_next_frame_height(png_ptr, decoder->mInfo));
+ const bool isInterlaced = bool(decoder->interlacebuf);
+
+#ifndef MOZ_EMBEDDED_LIBPNG
+ // if using system library, check frame_width and height against 0
+ if (frameRect.width == 0) {
+ png_error(png_ptr, "Frame width must not be 0");
+ }
+ if (frameRect.height == 0) {
+ png_error(png_ptr, "Frame height must not be 0");
+ }
+#endif
+
+ const FrameInfo info { decoder->format, frameRect, isInterlaced };
+
+ // If the previous frame was hidden, skip the yield (which will mislead the
+ // caller, who will think the previous frame was real) and just allocate the
+ // new frame here.
+ if (previousFrameWasHidden) {
+ if (NS_FAILED(decoder->CreateFrame(info))) {
+ return decoder->DoTerminate(png_ptr, TerminalState::FAILURE);
+ }
+
+ MOZ_ASSERT(decoder->mImageData, "Should have a buffer now");
+ return; // No yield, so we'll just keep decoding.
+ }
+
+ // Yield to the caller to notify them that the previous frame is now complete.
+ decoder->mNextFrameInfo = Some(info);
+ return decoder->DoYield(png_ptr);
+}
+#endif
+
+void
+nsPNGDecoder::end_callback(png_structp png_ptr, png_infop info_ptr)
+{
+ /* libpng comments:
+ *
+ * this function is called when the whole image has been read,
+ * including any chunks after the image (up to and including
+ * the IEND). You will usually have the same info chunk as you
+ * had in the header, although some data may have been added
+ * to the comments and time fields.
+ *
+ * Most people won't do much here, perhaps setting a flag that
+ * marks the image as finished.
+ */
+
+ nsPNGDecoder* decoder =
+ static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
+
+ // We shouldn't get here if we've hit an error
+ MOZ_ASSERT(!decoder->HasError(), "Finishing up PNG but hit error!");
+
+ int32_t loop_count = 0;
+#ifdef PNG_APNG_SUPPORTED
+ if (png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL)) {
+ int32_t num_plays = png_get_num_plays(png_ptr, info_ptr);
+ loop_count = num_plays - 1;
+ }
+#endif
+
+ // Send final notifications.
+ decoder->EndImageFrame();
+ decoder->PostDecodeDone(loop_count);
+ return decoder->DoTerminate(png_ptr, TerminalState::SUCCESS);
+}
+
+
+void
+nsPNGDecoder::error_callback(png_structp png_ptr, png_const_charp error_msg)
+{
+ MOZ_LOG(sPNGLog, LogLevel::Error, ("libpng error: %s\n", error_msg));
+ png_longjmp(png_ptr, 1);
+}
+
+
+void
+nsPNGDecoder::warning_callback(png_structp png_ptr, png_const_charp warning_msg)
+{
+ MOZ_LOG(sPNGLog, LogLevel::Warning, ("libpng warning: %s\n", warning_msg));
+}
+
+Maybe<Telemetry::ID>
+nsPNGDecoder::SpeedHistogram() const
+{
+ return Some(Telemetry::IMAGE_DECODE_SPEED_PNG);
+}
+
+bool
+nsPNGDecoder::IsValidICO() const
+{
+ // Only 32-bit RGBA PNGs are valid ICO resources; see here:
+ // http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx
+
+ // If there are errors in the call to png_get_IHDR, the error_callback in
+ // nsPNGDecoder.cpp is called. In this error callback we do a longjmp, so
+ // we need to save the jump buffer here. Otherwise we'll end up without a
+ // proper callstack.
+ if (setjmp(png_jmpbuf(mPNG))) {
+ // We got here from a longjmp call indirectly from png_get_IHDR
+ return false;
+ }
+
+ png_uint_32
+ png_width, // Unused
+ png_height; // Unused
+
+ int png_bit_depth,
+ png_color_type;
+
+ if (png_get_IHDR(mPNG, mInfo, &png_width, &png_height, &png_bit_depth,
+ &png_color_type, nullptr, nullptr, nullptr)) {
+
+ return ((png_color_type == PNG_COLOR_TYPE_RGB_ALPHA ||
+ png_color_type == PNG_COLOR_TYPE_RGB) &&
+ png_bit_depth == 8);
+ } else {
+ return false;
+ }
+}
+
+} // namespace image
+} // namespace mozilla
diff --git a/image/decoders/nsPNGDecoder.h b/image/decoders/nsPNGDecoder.h
new file mode 100644
index 000000000..4b5c50ac5
--- /dev/null
+++ b/image/decoders/nsPNGDecoder.h
@@ -0,0 +1,158 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_image_decoders_nsPNGDecoder_h
+#define mozilla_image_decoders_nsPNGDecoder_h
+
+#include "Decoder.h"
+#include "png.h"
+#include "qcms.h"
+#include "StreamingLexer.h"
+#include "SurfacePipe.h"
+
+namespace mozilla {
+namespace image {
+class RasterImage;
+
+class nsPNGDecoder : public Decoder
+{
+public:
+ virtual ~nsPNGDecoder();
+
+ /// @return true if this PNG is a valid ICO resource.
+ bool IsValidICO() const;
+
+protected:
+ nsresult InitInternal() override;
+ LexerResult DoDecode(SourceBufferIterator& aIterator,
+ IResumable* aOnResume) override;
+
+ Maybe<Telemetry::ID> SpeedHistogram() const override;
+
+private:
+ friend class DecoderFactory;
+
+ // Decoders should only be instantiated via DecoderFactory.
+ explicit nsPNGDecoder(RasterImage* aImage);
+
+ /// The information necessary to create a frame.
+ struct FrameInfo
+ {
+ gfx::SurfaceFormat mFormat;
+ gfx::IntRect mFrameRect;
+ bool mIsInterlaced;
+ };
+
+ nsresult CreateFrame(const FrameInfo& aFrameInfo);
+ void EndImageFrame();
+
+ enum class TransparencyType
+ {
+ eNone,
+ eAlpha,
+ eFrameRect
+ };
+
+ TransparencyType GetTransparencyType(gfx::SurfaceFormat aFormat,
+ const gfx::IntRect& aFrameRect);
+ void PostHasTransparencyIfNeeded(TransparencyType aTransparencyType);
+
+ void PostInvalidationIfNeeded();
+
+ void WriteRow(uint8_t* aRow);
+
+ // Convenience methods to make interacting with StreamingLexer from inside
+ // a libpng callback easier.
+ void DoTerminate(png_structp aPNGStruct, TerminalState aState);
+ void DoYield(png_structp aPNGStruct);
+
+ enum class State
+ {
+ PNG_DATA,
+ FINISHED_PNG_DATA
+ };
+
+ LexerTransition<State> ReadPNGData(const char* aData, size_t aLength);
+ LexerTransition<State> FinishedPNGData();
+
+ StreamingLexer<State> mLexer;
+
+ // The next lexer state transition. We need to store it here because we can't
+ // directly return arbitrary values from libpng callbacks.
+ LexerTransition<State> mNextTransition;
+
+ // We yield to the caller every time we finish decoding a frame. When this
+ // happens, we need to allocate the next frame after returning from the yield.
+ // |mNextFrameInfo| is used to store the information needed to allocate the
+ // next frame.
+ Maybe<FrameInfo> mNextFrameInfo;
+
+ // The length of the last chunk of data passed to ReadPNGData(). We use this
+ // to arrange to arrive back at the correct spot in the data after yielding.
+ size_t mLastChunkLength;
+
+public:
+ png_structp mPNG;
+ png_infop mInfo;
+ nsIntRect mFrameRect;
+ uint8_t* mCMSLine;
+ uint8_t* interlacebuf;
+ qcms_profile* mInProfile;
+ qcms_transform* mTransform;
+
+ gfx::SurfaceFormat format;
+
+ // whether CMS or premultiplied alpha are forced off
+ uint32_t mCMSMode;
+
+ uint8_t mChannels;
+ uint8_t mPass;
+ bool mFrameIsHidden;
+ bool mDisablePremultipliedAlpha;
+
+ struct AnimFrameInfo
+ {
+ AnimFrameInfo();
+#ifdef PNG_APNG_SUPPORTED
+ AnimFrameInfo(png_structp aPNG, png_infop aInfo);
+#endif
+
+ DisposalMethod mDispose;
+ BlendMethod mBlend;
+ int32_t mTimeout;
+ };
+
+ AnimFrameInfo mAnimInfo;
+
+ SurfacePipe mPipe; /// The SurfacePipe used to write to the output surface.
+
+ // The number of frames we've finished.
+ uint32_t mNumFrames;
+
+ // libpng callbacks
+ // We put these in the class so that they can access protected members.
+ static void PNGAPI info_callback(png_structp png_ptr, png_infop info_ptr);
+ static void PNGAPI row_callback(png_structp png_ptr, png_bytep new_row,
+ png_uint_32 row_num, int pass);
+#ifdef PNG_APNG_SUPPORTED
+ static void PNGAPI frame_info_callback(png_structp png_ptr,
+ png_uint_32 frame_num);
+#endif
+ static void PNGAPI end_callback(png_structp png_ptr, png_infop info_ptr);
+ static void PNGAPI error_callback(png_structp png_ptr,
+ png_const_charp error_msg);
+ static void PNGAPI warning_callback(png_structp png_ptr,
+ png_const_charp warning_msg);
+
+ // This is defined in the PNG spec as an invariant. We use it to
+ // do manual validation without libpng.
+ static const uint8_t pngSignatureBytes[];
+};
+
+} // namespace image
+} // namespace mozilla
+
+#endif // mozilla_image_decoders_nsPNGDecoder_h