Report image metadata such as ICCP and XMP
diff --git a/doc/changelog.md b/doc/changelog.md
index 205ad84..3d9501c 100644
--- a/doc/changelog.md
+++ b/doc/changelog.md
@@ -37,6 +37,7 @@
 - Added a `reset` method.
 - Added `peek_uxx`, `skip_fast` and `write_fast_uxx` methods.
 - Renamed some `read_uxx` methods as `read_uxx_as_uyy`.
+- Report image metadata such as ICCP and XMP.
 - Added I/O positions.
 - Added extra fields (uninitialized internal buffers) to structs.
 - Tweaked how marks and limits work.
@@ -68,4 +69,4 @@
 
 ---
 
-Updated on March 2019.
+Updated on April 2019.
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c
index 901504e..091e922 100644
--- a/release/c/wuffs-unsupported-snapshot.c
+++ b/release/c/wuffs-unsupported-snapshot.c
@@ -3305,6 +3305,21 @@
                                         wuffs_base__image_config* a_dst,
                                         wuffs_base__io_reader a_src);
 
+WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct  //
+wuffs_gif__decoder__set_report_metadata(wuffs_gif__decoder* self,
+                                        uint32_t a_fourcc,
+                                        bool a_report);
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
+wuffs_gif__decoder__ack_metadata_chunk(wuffs_gif__decoder* self,
+                                       wuffs_base__io_reader a_src);
+
+WUFFS_BASE__MAYBE_STATIC uint32_t  //
+wuffs_gif__decoder__metadata_fourcc(const wuffs_gif__decoder* self);
+
+WUFFS_BASE__MAYBE_STATIC uint64_t  //
+wuffs_gif__decoder__metadata_chunk_length(const wuffs_gif__decoder* self);
+
 WUFFS_BASE__MAYBE_STATIC uint32_t  //
 wuffs_gif__decoder__num_animation_loops(const wuffs_gif__decoder* self);
 
@@ -3363,6 +3378,11 @@
     uint32_t f_width;
     uint32_t f_height;
     uint8_t f_call_sequence;
+    bool f_ignore_metadata;
+    bool f_report_metadata_xmp;
+    uint32_t f_metadata_fourcc_value;
+    uint64_t f_metadata_chunk_length_value;
+    uint64_t f_metadata_io_position;
     bool f_end_of_data;
     bool f_restarted;
     bool f_previous_lzw_decode_ended_abruptly;
@@ -3390,6 +3410,7 @@
     wuffs_base__pixel_swizzler f_swizzler;
 
     uint32_t p_decode_image_config[1];
+    uint32_t p_ack_metadata_chunk[1];
     uint32_t p_decode_frame_config[1];
     uint32_t p_skip_frame[1];
     uint32_t p_decode_frame[1];
@@ -3412,9 +3433,15 @@
     wuffs_lzw__decoder f_lzw;
 
     struct {
+      wuffs_base__status v_status;
+    } s_decode_image_config[1];
+    struct {
       uint64_t scratch;
     } s_skip_frame[1];
     struct {
+      wuffs_base__status v_status;
+    } s_decode_up_to_id_part1[1];
+    struct {
       uint8_t v_c[6];
       uint32_t v_i;
     } s_decode_header[1];
@@ -3425,12 +3452,16 @@
       uint64_t scratch;
     } s_decode_lsd[1];
     struct {
+      wuffs_base__status v_status;
+    } s_decode_extension[1];
+    struct {
       uint64_t scratch;
     } s_skip_blocks[1];
     struct {
       uint8_t v_block_size;
-      bool v_not_animexts;
-      bool v_not_netscape;
+      bool v_is_animexts;
+      bool v_is_netscape;
+      bool v_is_xmp;
       uint64_t scratch;
     } s_decode_ae[1];
     struct {
@@ -3490,6 +3521,26 @@
     return wuffs_gif__decoder__decode_image_config(this, a_dst, a_src);
   }
 
+  inline wuffs_base__empty_struct  //
+  set_report_metadata(uint32_t a_fourcc, bool a_report) {
+    return wuffs_gif__decoder__set_report_metadata(this, a_fourcc, a_report);
+  }
+
+  inline wuffs_base__status  //
+  ack_metadata_chunk(wuffs_base__io_reader a_src) {
+    return wuffs_gif__decoder__ack_metadata_chunk(this, a_src);
+  }
+
+  inline uint32_t  //
+  metadata_fourcc() const {
+    return wuffs_gif__decoder__metadata_fourcc(this);
+  }
+
+  inline uint64_t  //
+  metadata_chunk_length() const {
+    return wuffs_gif__decoder__metadata_chunk_length(this);
+  }
+
   inline uint32_t  //
   num_animation_loops() const {
     return wuffs_gif__decoder__num_animation_loops(this);
@@ -8469,6 +8520,12 @@
         78, 69, 84, 83, 67, 65, 80, 69, 50, 46, 48,
 };
 
+static const uint8_t           //
+    wuffs_gif__xmpdataxmp[11]  //
+    WUFFS_BASE__POTENTIALLY_UNUSED = {
+        88, 77, 80, 32, 68, 97, 116, 97, 88, 77, 80,
+};
+
 // ---------------- Private Initializer Prototypes
 
 // ---------------- Private Function Prototypes
@@ -8613,32 +8670,55 @@
   wuffs_base__status status = NULL;
 
   bool v_ffio = false;
+  wuffs_base__status v_status = NULL;
 
   uint32_t coro_susp_point = self->private_impl.p_decode_image_config[0];
   if (coro_susp_point) {
+    v_status = self->private_data.s_decode_image_config[0].v_status;
   }
   switch (coro_susp_point) {
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
 
-    if (self->private_impl.f_call_sequence >= 3) {
+    if (self->private_impl.f_call_sequence == 0) {
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
+      status = wuffs_gif__decoder__decode_header(self, a_src);
+      if (status) {
+        goto suspend;
+      }
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
+      status = wuffs_gif__decoder__decode_lsd(self, a_src);
+      if (status) {
+        goto suspend;
+      }
+    } else if (self->private_impl.f_call_sequence != 2) {
       status = wuffs_base__error__bad_call_sequence;
       goto exit;
     }
-    WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
-    status = wuffs_gif__decoder__decode_header(self, a_src);
-    if (status) {
-      goto suspend;
+  label_0_continue:;
+    while (true) {
+      {
+        wuffs_base__status t_0 =
+            wuffs_gif__decoder__decode_up_to_id_part1(self, a_src);
+        v_status = t_0;
+      }
+      if (wuffs_base__status__is_suspension(v_status)) {
+        status = v_status;
+        WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(3);
+        goto label_0_continue;
+      }
+      if (wuffs_base__status__is_ok(v_status)) {
+        goto label_0_break;
+      }
+      status = v_status;
+      if (wuffs_base__status__is_error(status)) {
+        goto exit;
+      } else if (wuffs_base__status__is_suspension(status)) {
+        status = wuffs_base__error__cannot_return_a_suspension;
+        goto exit;
+      }
+      goto ok;
     }
-    WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
-    status = wuffs_gif__decoder__decode_lsd(self, a_src);
-    if (status) {
-      goto suspend;
-    }
-    WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
-    status = wuffs_gif__decoder__decode_up_to_id_part1(self, a_src);
-    if (status) {
-      goto suspend;
-    }
+  label_0_break:;
     v_ffio =
         (!self->private_impl.f_gc_has_transparent_index &&
          (self->private_impl.f_frame_rect_x0 == 0) &&
@@ -8663,6 +8743,7 @@
 suspend:
   self->private_impl.p_decode_image_config[0] = coro_susp_point;
   self->private_impl.active_coroutine = 1;
+  self->private_data.s_decode_image_config[0].v_status = v_status;
 
   goto exit;
 exit:
@@ -8672,6 +8753,156 @@
   return status;
 }
 
+// -------- func gif.decoder.set_report_metadata
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct  //
+wuffs_gif__decoder__set_report_metadata(wuffs_gif__decoder* self,
+                                        uint32_t a_fourcc,
+                                        bool a_report) {
+  if (!self) {
+    return wuffs_base__make_empty_struct();
+  }
+  if (self->private_impl.magic != WUFFS_BASE__MAGIC) {
+    return wuffs_base__make_empty_struct();
+  }
+
+  if (a_fourcc == 1481461792) {
+    self->private_impl.f_report_metadata_xmp = a_report;
+  }
+  return wuffs_base__make_empty_struct();
+}
+
+// -------- func gif.decoder.ack_metadata_chunk
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
+wuffs_gif__decoder__ack_metadata_chunk(wuffs_gif__decoder* self,
+                                       wuffs_base__io_reader a_src) {
+  if (!self) {
+    return wuffs_base__error__bad_receiver;
+  }
+  if (self->private_impl.magic != WUFFS_BASE__MAGIC) {
+    return (self->private_impl.magic == WUFFS_BASE__DISABLED)
+               ? wuffs_base__error__disabled_by_previous_error
+               : wuffs_base__error__initialize_not_called;
+  }
+  if ((self->private_impl.active_coroutine != 0) &&
+      (self->private_impl.active_coroutine != 2)) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+    return wuffs_base__error__interleaved_coroutine_calls;
+  }
+  self->private_impl.active_coroutine = 0;
+  wuffs_base__status status = NULL;
+
+  uint8_t* iop_a_src = NULL;
+  uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_src.private_impl.buf) {
+    iop_a_src =
+        a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
+    if (!a_src.private_impl.mark) {
+      a_src.private_impl.mark = iop_a_src;
+      a_src.private_impl.limit =
+          a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.wi;
+    }
+    io0_a_src = a_src.private_impl.mark;
+    io1_a_src = a_src.private_impl.limit;
+  }
+
+  uint32_t coro_susp_point = self->private_impl.p_ack_metadata_chunk[0];
+  if (coro_susp_point) {
+  }
+  switch (coro_susp_point) {
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
+
+    if (self->private_impl.f_call_sequence != 1) {
+      status = wuffs_base__error__bad_call_sequence;
+      goto exit;
+    }
+    if ((a_src.private_impl.buf
+             ? wuffs_base__u64__sat_add(
+                   a_src.private_impl.buf->meta.pos,
+                   ((uint64_t)(iop_a_src - a_src.private_impl.buf->data.ptr)))
+             : 0) != self->private_impl.f_metadata_io_position) {
+      status = wuffs_base__error__bad_i_o_position;
+      goto exit;
+    }
+    while (((uint64_t)(io1_a_src - iop_a_src)) <= 0) {
+      status = wuffs_base__suspension__short_read;
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1);
+    }
+    self->private_impl.f_metadata_chunk_length_value =
+        (((uint64_t)(wuffs_base__load_u8be(iop_a_src))) + 1);
+    if (self->private_impl.f_metadata_chunk_length_value > 1) {
+      self->private_impl.f_metadata_io_position = wuffs_base__u64__sat_add(
+          (a_src.private_impl.buf
+               ? wuffs_base__u64__sat_add(
+                     a_src.private_impl.buf->meta.pos,
+                     ((uint64_t)(iop_a_src - a_src.private_impl.buf->data.ptr)))
+               : 0),
+          self->private_impl.f_metadata_chunk_length_value);
+      status = wuffs_base__warning__metadata_reported;
+      goto ok;
+    }
+    (iop_a_src += 1, wuffs_base__make_empty_struct());
+    self->private_impl.f_call_sequence = 2;
+    self->private_impl.f_metadata_fourcc_value = 0;
+    self->private_impl.f_metadata_io_position = 0;
+    status = NULL;
+    goto ok;
+    goto ok;
+  ok:
+    self->private_impl.p_ack_metadata_chunk[0] = 0;
+    goto exit;
+  }
+
+  goto suspend;
+suspend:
+  self->private_impl.p_ack_metadata_chunk[0] = coro_susp_point;
+  self->private_impl.active_coroutine = 2;
+
+  goto exit;
+exit:
+  if (a_src.private_impl.buf) {
+    a_src.private_impl.buf->meta.ri =
+        ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+  }
+
+  if (wuffs_base__status__is_error(status)) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+  }
+  return status;
+}
+
+// -------- func gif.decoder.metadata_fourcc
+
+WUFFS_BASE__MAYBE_STATIC uint32_t  //
+wuffs_gif__decoder__metadata_fourcc(const wuffs_gif__decoder* self) {
+  if (!self) {
+    return 0;
+  }
+  if ((self->private_impl.magic != WUFFS_BASE__MAGIC) &&
+      (self->private_impl.magic != WUFFS_BASE__DISABLED)) {
+    return 0;
+  }
+
+  return self->private_impl.f_metadata_fourcc_value;
+}
+
+// -------- func gif.decoder.metadata_chunk_length
+
+WUFFS_BASE__MAYBE_STATIC uint64_t  //
+wuffs_gif__decoder__metadata_chunk_length(const wuffs_gif__decoder* self) {
+  if (!self) {
+    return 0;
+  }
+  if ((self->private_impl.magic != WUFFS_BASE__MAGIC) &&
+      (self->private_impl.magic != WUFFS_BASE__DISABLED)) {
+    return 0;
+  }
+
+  return self->private_impl.f_metadata_chunk_length_value;
+}
+
 // -------- func gif.decoder.num_animation_loops
 
 WUFFS_BASE__MAYBE_STATIC uint32_t  //
@@ -8798,7 +9029,7 @@
                : wuffs_base__error__initialize_not_called;
   }
   if ((self->private_impl.active_coroutine != 0) &&
-      (self->private_impl.active_coroutine != 2)) {
+      (self->private_impl.active_coroutine != 3)) {
     self->private_impl.magic = WUFFS_BASE__DISABLED;
     return wuffs_base__error__interleaved_coroutine_calls;
   }
@@ -8813,6 +9044,7 @@
   switch (coro_susp_point) {
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
 
+    self->private_impl.f_ignore_metadata = true;
     (memset(&self->private_impl.f_dirty_y, 0, sizeof(wuffs_base__range_ie_u32)),
      wuffs_base__make_empty_struct());
     if (!self->private_impl.f_end_of_data) {
@@ -8875,7 +9107,7 @@
   goto suspend;
 suspend:
   self->private_impl.p_decode_frame_config[0] = coro_susp_point;
-  self->private_impl.active_coroutine = 2;
+  self->private_impl.active_coroutine = 3;
 
   goto exit;
 exit:
@@ -9002,7 +9234,7 @@
     return wuffs_base__error__bad_argument;
   }
   if ((self->private_impl.active_coroutine != 0) &&
-      (self->private_impl.active_coroutine != 3)) {
+      (self->private_impl.active_coroutine != 4)) {
     self->private_impl.magic = WUFFS_BASE__DISABLED;
     return wuffs_base__error__interleaved_coroutine_calls;
   }
@@ -9015,6 +9247,7 @@
   switch (coro_susp_point) {
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
 
+    self->private_impl.f_ignore_metadata = true;
     if (self->private_impl.f_call_sequence != 4) {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
       status = wuffs_gif__decoder__decode_frame_config(self, NULL, a_src);
@@ -9045,7 +9278,7 @@
   goto suspend;
 suspend:
   self->private_impl.p_decode_frame[0] = coro_susp_point;
-  self->private_impl.active_coroutine = 3;
+  self->private_impl.active_coroutine = 4;
 
   goto exit;
 exit:
@@ -9076,6 +9309,7 @@
   wuffs_base__status status = NULL;
 
   uint8_t v_block_type = 0;
+  wuffs_base__status v_status = NULL;
 
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
@@ -9094,17 +9328,21 @@
 
   uint32_t coro_susp_point = self->private_impl.p_decode_up_to_id_part1[0];
   if (coro_susp_point) {
+    v_status = self->private_data.s_decode_up_to_id_part1[0].v_status;
   }
   switch (coro_susp_point) {
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
 
     if (!self->private_impl.f_restarted) {
-      self->private_impl.f_frame_config_io_position =
-          (a_src.private_impl.buf
-               ? wuffs_base__u64__sat_add(
-                     a_src.private_impl.buf->meta.pos,
-                     ((uint64_t)(iop_a_src - a_src.private_impl.buf->data.ptr)))
-               : 0);
+      if (self->private_impl.f_call_sequence != 2) {
+        self->private_impl.f_frame_config_io_position =
+            (a_src.private_impl.buf
+                 ? wuffs_base__u64__sat_add(
+                       a_src.private_impl.buf->meta.pos,
+                       ((uint64_t)(iop_a_src -
+                                   a_src.private_impl.buf->data.ptr)))
+                 : 0);
+      }
     } else if (self->private_impl.f_frame_config_io_position !=
                (a_src.private_impl.buf
                     ? wuffs_base__u64__sat_add(
@@ -9128,19 +9366,39 @@
         v_block_type = t_0;
       }
       if (v_block_type == 33) {
-        if (a_src.private_impl.buf) {
-          a_src.private_impl.buf->meta.ri =
-              ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+      label_0_continue:;
+        while (true) {
+          {
+            if (a_src.private_impl.buf) {
+              a_src.private_impl.buf->meta.ri =
+                  ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+            }
+            wuffs_base__status t_1 =
+                wuffs_gif__decoder__decode_extension(self, a_src);
+            if (a_src.private_impl.buf) {
+              iop_a_src = a_src.private_impl.buf->data.ptr +
+                          a_src.private_impl.buf->meta.ri;
+            }
+            v_status = t_1;
+          }
+          if (wuffs_base__status__is_suspension(v_status)) {
+            status = v_status;
+            WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(2);
+            goto label_0_continue;
+          }
+          if (wuffs_base__status__is_ok(v_status)) {
+            goto label_0_break;
+          }
+          status = v_status;
+          if (wuffs_base__status__is_error(status)) {
+            goto exit;
+          } else if (wuffs_base__status__is_suspension(status)) {
+            status = wuffs_base__error__cannot_return_a_suspension;
+            goto exit;
+          }
+          goto ok;
         }
-        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
-        status = wuffs_gif__decoder__decode_extension(self, a_src);
-        if (a_src.private_impl.buf) {
-          iop_a_src = a_src.private_impl.buf->data.ptr +
-                      a_src.private_impl.buf->meta.ri;
-        }
-        if (status) {
-          goto suspend;
-        }
+      label_0_break:;
       } else if (v_block_type == 44) {
         if (a_src.private_impl.buf) {
           a_src.private_impl.buf->meta.ri =
@@ -9155,16 +9413,16 @@
         if (status) {
           goto suspend;
         }
-        goto label_0_break;
+        goto label_1_break;
       } else if (v_block_type == 59) {
         self->private_impl.f_end_of_data = true;
-        goto label_0_break;
+        goto label_1_break;
       } else {
         status = wuffs_gif__error__bad_block;
         goto exit;
       }
     }
-  label_0_break:;
+  label_1_break:;
 
     goto ok;
   ok:
@@ -9175,6 +9433,7 @@
   goto suspend;
 suspend:
   self->private_impl.p_decode_up_to_id_part1[0] = coro_susp_point;
+  self->private_data.s_decode_up_to_id_part1[0].v_status = v_status;
 
   goto exit;
 exit:
@@ -9459,6 +9718,7 @@
   wuffs_base__status status = NULL;
 
   uint8_t v_label = 0;
+  wuffs_base__status v_status = NULL;
 
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
@@ -9477,6 +9737,7 @@
 
   uint32_t coro_susp_point = self->private_impl.p_decode_extension[0];
   if (coro_susp_point) {
+    v_status = self->private_data.s_decode_extension[0].v_status;
   }
   switch (coro_susp_point) {
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
@@ -9507,19 +9768,38 @@
       status = NULL;
       goto ok;
     } else if (v_label == 255) {
-      if (a_src.private_impl.buf) {
-        a_src.private_impl.buf->meta.ri =
-            ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+    label_0_continue:;
+      while (true) {
+        {
+          if (a_src.private_impl.buf) {
+            a_src.private_impl.buf->meta.ri =
+                ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
+          }
+          wuffs_base__status t_1 = wuffs_gif__decoder__decode_ae(self, a_src);
+          if (a_src.private_impl.buf) {
+            iop_a_src = a_src.private_impl.buf->data.ptr +
+                        a_src.private_impl.buf->meta.ri;
+          }
+          v_status = t_1;
+        }
+        if (wuffs_base__status__is_suspension(v_status)) {
+          status = v_status;
+          WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(3);
+          goto label_0_continue;
+        }
+        if (wuffs_base__status__is_ok(v_status)) {
+          goto label_0_break;
+        }
+        status = v_status;
+        if (wuffs_base__status__is_error(status)) {
+          goto exit;
+        } else if (wuffs_base__status__is_suspension(status)) {
+          status = wuffs_base__error__cannot_return_a_suspension;
+          goto exit;
+        }
+        goto ok;
       }
-      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
-      status = wuffs_gif__decoder__decode_ae(self, a_src);
-      if (a_src.private_impl.buf) {
-        iop_a_src =
-            a_src.private_impl.buf->data.ptr + a_src.private_impl.buf->meta.ri;
-      }
-      if (status) {
-        goto suspend;
-      }
+    label_0_break:;
       status = NULL;
       goto ok;
     }
@@ -9546,6 +9826,7 @@
   goto suspend;
 suspend:
   self->private_impl.p_decode_extension[0] = coro_susp_point;
+  self->private_data.s_decode_extension[0].v_status = v_status;
 
   goto exit;
 exit:
@@ -9643,8 +9924,9 @@
 
   uint8_t v_c = 0;
   uint8_t v_block_size = 0;
-  bool v_not_animexts = false;
-  bool v_not_netscape = false;
+  bool v_is_animexts = false;
+  bool v_is_netscape = false;
+  bool v_is_xmp = false;
 
   uint8_t* iop_a_src = NULL;
   uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
@@ -9664,8 +9946,9 @@
   uint32_t coro_susp_point = self->private_impl.p_decode_ae[0];
   if (coro_susp_point) {
     v_block_size = self->private_data.s_decode_ae[0].v_block_size;
-    v_not_animexts = self->private_data.s_decode_ae[0].v_not_animexts;
-    v_not_netscape = self->private_data.s_decode_ae[0].v_not_netscape;
+    v_is_animexts = self->private_data.s_decode_ae[0].v_is_animexts;
+    v_is_netscape = self->private_data.s_decode_ae[0].v_is_netscape;
+    v_is_xmp = self->private_data.s_decode_ae[0].v_is_xmp;
   }
   switch (coro_susp_point) {
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
@@ -9698,8 +9981,9 @@
         iop_a_src += self->private_data.s_decode_ae[0].scratch;
         goto label_0_break;
       }
-      v_not_animexts = false;
-      v_not_netscape = false;
+      v_is_animexts = true;
+      v_is_netscape = true;
+      v_is_xmp = true;
       v_block_size = 0;
       while (v_block_size < 11) {
         {
@@ -9711,10 +9995,11 @@
           uint8_t t_1 = *iop_a_src++;
           v_c = t_1;
         }
-        v_not_animexts =
-            (v_not_animexts || (v_c != wuffs_gif__animexts1dot0[v_block_size]));
-        v_not_netscape =
-            (v_not_netscape || (v_c != wuffs_gif__netscape2dot0[v_block_size]));
+        v_is_animexts =
+            (v_is_animexts && (v_c == wuffs_gif__animexts1dot0[v_block_size]));
+        v_is_netscape =
+            (v_is_netscape && (v_c == wuffs_gif__netscape2dot0[v_block_size]));
+        v_is_xmp = (v_is_xmp && (v_c == wuffs_gif__xmpdataxmp[v_block_size]));
 #if defined(__GNUC__)
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wconversion"
@@ -9724,88 +10009,108 @@
 #pragma GCC diagnostic pop
 #endif
       }
-      if (v_not_animexts && v_not_netscape) {
-        goto label_0_break;
-      }
-      {
-        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
-        if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
-          status = wuffs_base__suspension__short_read;
-          goto suspend;
-        }
-        uint8_t t_2 = *iop_a_src++;
-        v_block_size = t_2;
-      }
-      if (v_block_size != 3) {
-        self->private_data.s_decode_ae[0].scratch = ((uint32_t)(v_block_size));
-        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5);
-        if (self->private_data.s_decode_ae[0].scratch >
-            ((uint64_t)(io1_a_src - iop_a_src))) {
-          self->private_data.s_decode_ae[0].scratch -=
-              ((uint64_t)(io1_a_src - iop_a_src));
-          iop_a_src = io1_a_src;
-          status = wuffs_base__suspension__short_read;
-          goto suspend;
-        }
-        iop_a_src += self->private_data.s_decode_ae[0].scratch;
-        goto label_0_break;
-      }
-      {
-        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6);
-        if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
-          status = wuffs_base__suspension__short_read;
-          goto suspend;
-        }
-        uint8_t t_3 = *iop_a_src++;
-        v_c = t_3;
-      }
-      if (v_c != 1) {
-        self->private_data.s_decode_ae[0].scratch = 2;
-        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7);
-        if (self->private_data.s_decode_ae[0].scratch >
-            ((uint64_t)(io1_a_src - iop_a_src))) {
-          self->private_data.s_decode_ae[0].scratch -=
-              ((uint64_t)(io1_a_src - iop_a_src));
-          iop_a_src = io1_a_src;
-          status = wuffs_base__suspension__short_read;
-          goto suspend;
-        }
-        iop_a_src += self->private_data.s_decode_ae[0].scratch;
-        goto label_0_break;
-      }
-      {
-        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8);
-        uint32_t t_4;
-        if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 2)) {
-          t_4 = ((uint32_t)(wuffs_base__load_u16le(iop_a_src)));
-          iop_a_src += 2;
-        } else {
-          self->private_data.s_decode_ae[0].scratch = 0;
-          WUFFS_BASE__COROUTINE_SUSPENSION_POINT(9);
-          while (true) {
-            if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
-              status = wuffs_base__suspension__short_read;
-              goto suspend;
-            }
-            uint64_t* scratch = &self->private_data.s_decode_ae[0].scratch;
-            uint32_t num_bits_4 = ((uint32_t)(*scratch >> 56));
-            *scratch <<= 8;
-            *scratch >>= 8;
-            *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_4;
-            if (num_bits_4 == 8) {
-              t_4 = ((uint32_t)(*scratch));
-              break;
-            }
-            num_bits_4 += 8;
-            *scratch |= ((uint64_t)(num_bits_4)) << 56;
+      if (v_is_animexts || v_is_netscape) {
+        {
+          WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+            status = wuffs_base__suspension__short_read;
+            goto suspend;
           }
+          uint8_t t_2 = *iop_a_src++;
+          v_block_size = t_2;
         }
-        self->private_impl.f_num_loops = t_4;
-      }
-      self->private_impl.f_seen_num_loops = true;
-      if ((0 < self->private_impl.f_num_loops) &&
-          (self->private_impl.f_num_loops <= 65535)) {
-        self->private_impl.f_num_loops += 1;
+        if (v_block_size != 3) {
+          self->private_data.s_decode_ae[0].scratch =
+              ((uint32_t)(v_block_size));
+          WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5);
+          if (self->private_data.s_decode_ae[0].scratch >
+              ((uint64_t)(io1_a_src - iop_a_src))) {
+            self->private_data.s_decode_ae[0].scratch -=
+                ((uint64_t)(io1_a_src - iop_a_src));
+            iop_a_src = io1_a_src;
+            status = wuffs_base__suspension__short_read;
+            goto suspend;
+          }
+          iop_a_src += self->private_data.s_decode_ae[0].scratch;
+          goto label_0_break;
+        }
+        {
+          WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6);
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+            status = wuffs_base__suspension__short_read;
+            goto suspend;
+          }
+          uint8_t t_3 = *iop_a_src++;
+          v_c = t_3;
+        }
+        if (v_c != 1) {
+          self->private_data.s_decode_ae[0].scratch = 2;
+          WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7);
+          if (self->private_data.s_decode_ae[0].scratch >
+              ((uint64_t)(io1_a_src - iop_a_src))) {
+            self->private_data.s_decode_ae[0].scratch -=
+                ((uint64_t)(io1_a_src - iop_a_src));
+            iop_a_src = io1_a_src;
+            status = wuffs_base__suspension__short_read;
+            goto suspend;
+          }
+          iop_a_src += self->private_data.s_decode_ae[0].scratch;
+          goto label_0_break;
+        }
+        {
+          WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8);
+          uint32_t t_4;
+          if (WUFFS_BASE__LIKELY(io1_a_src - iop_a_src >= 2)) {
+            t_4 = ((uint32_t)(wuffs_base__load_u16le(iop_a_src)));
+            iop_a_src += 2;
+          } else {
+            self->private_data.s_decode_ae[0].scratch = 0;
+            WUFFS_BASE__COROUTINE_SUSPENSION_POINT(9);
+            while (true) {
+              if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
+                status = wuffs_base__suspension__short_read;
+                goto suspend;
+              }
+              uint64_t* scratch = &self->private_data.s_decode_ae[0].scratch;
+              uint32_t num_bits_4 = ((uint32_t)(*scratch >> 56));
+              *scratch <<= 8;
+              *scratch >>= 8;
+              *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_4;
+              if (num_bits_4 == 8) {
+                t_4 = ((uint32_t)(*scratch));
+                break;
+              }
+              num_bits_4 += 8;
+              *scratch |= ((uint64_t)(num_bits_4)) << 56;
+            }
+          }
+          self->private_impl.f_num_loops = t_4;
+        }
+        self->private_impl.f_seen_num_loops = true;
+        if ((0 < self->private_impl.f_num_loops) &&
+            (self->private_impl.f_num_loops <= 65535)) {
+          self->private_impl.f_num_loops += 1;
+        }
+      } else if (self->private_impl.f_ignore_metadata) {
+      } else if (v_is_xmp && self->private_impl.f_report_metadata_xmp) {
+        while (((uint64_t)(io1_a_src - iop_a_src)) <= 0) {
+          status = wuffs_base__suspension__short_read;
+          WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(10);
+        }
+        self->private_impl.f_metadata_chunk_length_value =
+            (((uint64_t)(wuffs_base__load_u8be(iop_a_src))) + 1);
+        self->private_impl.f_metadata_fourcc_value = 1481461792;
+        self->private_impl.f_metadata_io_position = wuffs_base__u64__sat_add(
+            (a_src.private_impl.buf
+                 ? wuffs_base__u64__sat_add(
+                       a_src.private_impl.buf->meta.pos,
+                       ((uint64_t)(iop_a_src -
+                                   a_src.private_impl.buf->data.ptr)))
+                 : 0),
+            self->private_impl.f_metadata_chunk_length_value);
+        self->private_impl.f_call_sequence = 1;
+        status = wuffs_base__warning__metadata_reported;
+        goto ok;
       }
       goto label_0_break;
     }
@@ -9814,7 +10119,7 @@
       a_src.private_impl.buf->meta.ri =
           ((size_t)(iop_a_src - a_src.private_impl.buf->data.ptr));
     }
-    WUFFS_BASE__COROUTINE_SUSPENSION_POINT(10);
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT(11);
     status = wuffs_gif__decoder__skip_blocks(self, a_src);
     if (a_src.private_impl.buf) {
       iop_a_src =
@@ -9834,8 +10139,9 @@
 suspend:
   self->private_impl.p_decode_ae[0] = coro_susp_point;
   self->private_data.s_decode_ae[0].v_block_size = v_block_size;
-  self->private_data.s_decode_ae[0].v_not_animexts = v_not_animexts;
-  self->private_data.s_decode_ae[0].v_not_netscape = v_not_netscape;
+  self->private_data.s_decode_ae[0].v_is_animexts = v_is_animexts;
+  self->private_data.s_decode_ae[0].v_is_netscape = v_is_netscape;
+  self->private_data.s_decode_ae[0].v_is_xmp = v_is_xmp;
 
   goto exit;
 exit:
diff --git a/script/make-artificial.go b/script/make-artificial.go
index d436b99..40dee66 100644
--- a/script/make-artificial.go
+++ b/script/make-artificial.go
@@ -512,6 +512,7 @@
 
 func stateGif(line string) (stateFunc, error) {
 	const (
+		cmdB  = "bytes "
 		cmdL  = "lzw "
 		cmdLC = "loopCount "
 	)
@@ -532,6 +533,18 @@
 		out = append(out, 0x3B)
 		return stateGif, nil
 
+	case strings.HasPrefix(line, cmdB):
+		s := line[len(cmdB):]
+		for s != "" {
+			x, ok := uint32(0), false
+			x, s, ok = parseHex(s)
+			if !ok {
+				break outer
+			}
+			out = append(out, uint8(x))
+		}
+		return stateGif, nil
+
 	case strings.HasPrefix(line, cmdL):
 		s := line[len(cmdL):]
 		litWidth, s, ok := parseNum(s)
diff --git a/std/gif/decode_gif.wuffs b/std/gif/decode_gif.wuffs
index 50b6bd1..46bc404 100644
--- a/std/gif/decode_gif.wuffs
+++ b/std/gif/decode_gif.wuffs
@@ -49,6 +49,8 @@
 
 	// Call sequence states:
 	//  - 0: initial state.
+	//  - 1: metadata reported; image config decode is in progress.
+	//  - 2: metadata finished; image config decode is in progress.
 	//  - 3: image config decoded, including the first frame's bounds, but not
 	//       the first frame's pixels.
 	//  - 4: frame config decoded.
@@ -56,10 +58,16 @@
 	//
 	// State transitions:
 	//
-	//  - 0 -> 3: via IC
+	//  - 0 -> 1: via IC (metadata reported)
+	//  - 0 -> 3: via IC (metadata not reported)
 	//  - 0 -> 4: via FC with implicit IC
 	//  - 0 -> 5: via F  with implicit IC and FC
 	//
+	//  - 1 -> 2: via AMC
+	//
+	//  - 2 -> 1: via IC (metadata reported)
+	//  - 2 -> 3: via IC (metadata not reported)
+	//
 	//  - 3 -> 4: via FC
 	//  - 3 -> 5: via F  with implicit FC
 	//
@@ -70,11 +78,18 @@
 	//  - 5 -> 5: via F  with implicit FC
 	//
 	// Where:
-	//  - F  is decode_frame,        implicit means skip_frame
-	//  - FC is decode_frame_config, implicit means nullptr args.dst
-	//  - IC is decode_image_config, implicit means nullptr args.dst
+	//  - AMC is ack_metadata_chunk
+	//  - F   is decode_frame,        implicit means skip_frame
+	//  - FC  is decode_frame_config, implicit means nullptr args.dst
+	//  - IC  is decode_image_config, implicit means nullptr args.dst
 	call_sequence base.u8,
 
+	ignore_metadata             base.bool,
+	report_metadata_xmp         base.bool,
+	metadata_fourcc_value       base.u32,
+	metadata_chunk_length_value base.u64,
+	metadata_io_position        base.u64,
+
 	end_of_data                        base.bool,
 	restarted                          base.bool,
 	previous_lzw_decode_ended_abruptly base.bool,
@@ -128,15 +143,28 @@
 )
 
 pub func decoder.decode_image_config?(dst nptr base.image_config, src base.io_reader) {
-	var ffio base.bool
+	var ffio   base.bool
+	var status base.status  // TODO: this shouldn't be necessary.
 
-	if this.call_sequence >= 3 {
+	if this.call_sequence == 0 {
+		this.decode_header?(src:args.src)
+		this.decode_lsd?(src:args.src)
+	} else if this.call_sequence <> 2 {
 		return base."#bad call sequence"
 	}
 
-	this.decode_header?(src:args.src)
-	this.decode_lsd?(src:args.src)
-	this.decode_up_to_id_part1?(src:args.src)
+	// TODO: this should be just "this.decode_up_to_id_part1?(src:args.src)".
+	while true {
+		status =? this.decode_up_to_id_part1?(src:args.src)
+		if status.is_suspension() {
+			yield? status
+			continue
+		}
+		if status.is_ok() {
+			break
+		}
+		return status
+	}
 
 	// TODO: if this.end_of_data, return an error and/or set dst to zero?
 
@@ -161,6 +189,46 @@
 	this.call_sequence = 3
 }
 
+pub func decoder.set_report_metadata!(fourcc base.u32, report base.bool) {
+	if args.fourcc == 0x584D5020 {  // "XMP "
+		this.report_metadata_xmp = args.report
+	}
+}
+
+pub func decoder.ack_metadata_chunk?(src base.io_reader) {
+	if this.call_sequence <> 1 {
+		return base."#bad call sequence"
+	}
+	if args.src.position() <> this.metadata_io_position {
+		return base."#bad I/O position"
+	}
+	while args.src.available() <= 0,
+		post args.src.available() > 0,
+	{
+		yield? base."$short read"
+	}
+	// The +1 is because XMP metadata's encoding includes each block's leading
+	// byte (the block size) as part of the metadata passed to the caller.
+	this.metadata_chunk_length_value = args.src.peek_u8_as_u64() + 1
+	if this.metadata_chunk_length_value > 1 {
+		this.metadata_io_position = args.src.position() ~sat+ this.metadata_chunk_length_value
+		return base."@metadata reported"
+	}
+	args.src.skip_fast!(actual:1, worst_case:1)
+	this.call_sequence = 2
+	this.metadata_fourcc_value = 0
+	this.metadata_io_position = 0
+	return ok
+}
+
+pub func decoder.metadata_fourcc() base.u32 {
+	return this.metadata_fourcc_value
+}
+
+pub func decoder.metadata_chunk_length() base.u64 {
+	return this.metadata_chunk_length_value
+}
+
 pub func decoder.num_animation_loops() base.u32 {
 	if this.seen_num_loops {
 		return this.num_loops
@@ -212,6 +280,7 @@
 pub func decoder.decode_frame_config?(dst nptr base.frame_config, src base.io_reader) {
 	var blend base.u8
 
+	this.ignore_metadata = true
 	this.dirty_y.reset!()
 
 	if not this.end_of_data {
@@ -272,6 +341,7 @@
 
 // TODO: honor args.opts.
 pub func decoder.decode_frame?(dst ptr base.pixel_buffer, src base.io_reader, workbuf slice base.u8, opts nptr base.decode_frame_options) {
+	this.ignore_metadata = true
 	if this.call_sequence <> 4 {
 		this.decode_frame_config?(dst:nullptr, src:args.src)
 	}
@@ -295,9 +365,12 @@
 
 pri func decoder.decode_up_to_id_part1?(src base.io_reader) {
 	var block_type base.u8
+	var status     base.status  // TODO: this shouldn't be necessary.
 
 	if not this.restarted {
-		this.frame_config_io_position = args.src.position()
+		if this.call_sequence <> 2 {
+			this.frame_config_io_position = args.src.position()
+		}
 	} else if this.frame_config_io_position <> args.src.position() {
 		return base."#bad restart"
 	} else {
@@ -307,7 +380,18 @@
 	while true {
 		block_type = args.src.read_u8?()
 		if block_type == 0x21 {  // The spec calls 0x21 the "Extension Introducer".
-			this.decode_extension?(src:args.src)
+			// TODO: this should be just "this.decode_extension?(src:args.src)".
+			while true {
+				status =? this.decode_extension?(src:args.src)
+				if status.is_suspension() {
+					yield? status
+					continue
+				}
+				if status.is_ok() {
+					break
+				}
+				return status
+			}
 		} else if block_type == 0x2C {  // The spec calls 0x2C the "Image Separator".
 			this.decode_id_part0?(src:args.src)
 			break
@@ -388,14 +472,26 @@
 //  - section 25 "Plain Text Extension" on page 18.
 //  - section 26 "Application Extension" on page 21.
 pri func decoder.decode_extension?(src base.io_reader) {
-	var label base.u8
+	var label  base.u8
+	var status base.status  // TODO: this shouldn't be necessary.
 
 	label = args.src.read_u8?()
 	if label == 0xF9 {  // The spec calls 0xF9 the "Graphic Control Label".
 		this.decode_gc?(src:args.src)
 		return ok
 	} else if label == 0xFF {  // The spec calls 0xFF the "Application Extension Label".
-		this.decode_ae?(src:args.src)
+		// TODO: this should be just "this.decode_ae?(src:args.src)".
+		while true {
+			status =? this.decode_ae?(src:args.src)
+			if status.is_suspension() {
+				yield? status
+				continue
+			}
+			if status.is_ok() {
+				break
+			}
+			return status
+		}
 		return ok
 	}
 	// We skip over all other extensions, including 0x01 "Plain Text Label" and
@@ -425,12 +521,18 @@
 	0x4E, 0x45, 0x54, 0x53, 0x43, 0x41, 0x50, 0x45, 0x32, 0x2E, 0x30,
 ]
 
+// xmpdataxmp is "XMP DataXMP" as bytes.
+pri const xmpdataxmp array[11] base.u8 = [
+	0x58, 0x4D, 0x50, 0x20, 0x44, 0x61, 0x74, 0x61, 0x58, 0x4D, 0x50,
+]
+
 // decode_ae reads an Application Extension.
 pri func decoder.decode_ae?(src base.io_reader) {
-	var c            base.u8
-	var block_size   base.u8
-	var not_animexts base.bool
-	var not_netscape base.bool
+	var c           base.u8
+	var block_size  base.u8
+	var is_animexts base.bool
+	var is_netscape base.bool
+	var is_xmp      base.bool
 
 	// This "while true" always executes exactly once, as it ends with a
 	// "break", but using "break"s throughout simplifies the control flow.
@@ -440,54 +542,75 @@
 			return ok
 		}
 
-		// Look only for an 11 byte "ANIMEXTS1.0" or "NETSCAPE2.0" extension,
-		// as per:
+		// Look only for an 11 byte "ANIMEXTS1.0", "NETSCAPE2.0" or other
+		// extension, as per:
 		//  - http://www.vurdalakov.net/misc/gif/animexts-looping-application-extension
 		//  - http://www.vurdalakov.net/misc/gif/netscape-looping-application-extension
+		//
+		// Other extensions include XMP metadata.
 		if block_size <> 11 {
 			args.src.skip?(n:block_size as base.u32)
 			break
 		}
-		not_animexts = false
-		not_netscape = false
+		is_animexts = true
+		is_netscape = true
+		is_xmp = true
 		block_size = 0  // Re-purpose the block_size variable as a counter.
 		while block_size < 11 {
 			c = args.src.read_u8?()
-			not_animexts = not_animexts or (c <> animexts1dot0[block_size])
-			not_netscape = not_netscape or (c <> netscape2dot0[block_size])
+			is_animexts = is_animexts and (c == animexts1dot0[block_size])
+			is_netscape = is_netscape and (c == netscape2dot0[block_size])
+			is_xmp = is_xmp and (c == xmpdataxmp[block_size])
 			block_size += 1
 		}
-		if not_animexts and not_netscape {
-			break
-		}
 
-		// Those 11 bytes should be followed by 0x03, 0x01 and then the loop
-		// count.
-		block_size = args.src.read_u8?()
-		if block_size <> 3 {
-			args.src.skip?(n:block_size as base.u32)
-			break
-		}
-		c = args.src.read_u8?()
-		if c <> 0x01 {
-			args.src.skip?(n:2)
-			break
-		}
-		this.num_loops = args.src.read_u16le_as_u32?()
-		this.seen_num_loops = true
+		if is_animexts or is_netscape {
+			// Those 11 bytes should be followed by 0x03, 0x01 and then the loop
+			// count.
+			block_size = args.src.read_u8?()
+			if block_size <> 3 {
+				args.src.skip?(n:block_size as base.u32)
+				break
+			}
+			c = args.src.read_u8?()
+			if c <> 0x01 {
+				args.src.skip?(n:2)
+				break
+			}
+			this.num_loops = args.src.read_u16le_as_u32?()
+			this.seen_num_loops = true
 
-		// A loop count of N, in the wire format, actually means "repeat N
-		// times after the first play", if N is positive. A zero N means to
-		// loop forever. Playing the frames exactly once is denoted by the
-		// *absence* of this NETSCAPE2.0 application extension.
-		//
-		// For example, if there are four frames: A, B, C, D, and N is 2, then
-		// each frame is actually played N+1 or 3 times: ABCDABCDABCD.
-		//
-		// Thus, we increment N if it is positive. The comparison against
-		// 0xFFFF will never fail, but is necessary for the overflow checker.
-		if (0 < this.num_loops) and (this.num_loops <= 0xFFFF) {
-			this.num_loops += 1
+			// A loop count of N, in the wire format, actually means "repeat N
+			// times after the first play", if N is positive. A zero N means to
+			// loop forever. Playing the frames exactly once is denoted by the
+			// *absence* of this NETSCAPE2.0 application extension.
+			//
+			// For example, if there are four frames: A, B, C, D, and N is 2, then
+			// each frame is actually played N+1 or 3 times: ABCDABCDABCD.
+			//
+			// Thus, we increment N if it is positive. The comparison against
+			// 0xFFFF will never fail, but is necessary for the overflow checker.
+			if (0 < this.num_loops) and (this.num_loops <= 0xFFFF) {
+				this.num_loops += 1
+			}
+
+		} else if this.ignore_metadata {
+			// No-op.
+
+		} else if is_xmp and this.report_metadata_xmp {
+			while args.src.available() <= 0,
+				post args.src.available() > 0,
+			{
+				yield? base."$short read"
+			}
+			// The +1 is because XMP metadata's encoding includes each block's
+			// leading byte (the block size) as part of the metadata passed to
+			// the caller.
+			this.metadata_chunk_length_value = args.src.peek_u8_as_u64() + 1
+			this.metadata_fourcc_value = 0x584D5020  // "XMP "
+			this.metadata_io_position = args.src.position() ~sat+ this.metadata_chunk_length_value
+			this.call_sequence = 1
+			return base."@metadata reported"
 		}
 
 		break
diff --git a/test/c/std/gif.c b/test/c/std/gif.c
index 6ed3277..8b37259 100644
--- a/test/c/std/gif.c
+++ b/test/c/std/gif.c
@@ -930,6 +930,165 @@
   return NULL;
 }
 
+const char* test_wuffs_gif_decode_metadata() {
+  CHECK_FOCUS(__func__);
+
+  wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
+      .data = global_src_slice,
+  });
+
+  const char* status = read_file(&src, "test/data/artificial/gif-metadata.gif");
+  if (status) {
+    return status;
+  }
+
+  int iccp;
+  for (iccp = 0; iccp < 2; iccp++) {
+    int xmp;
+    for (xmp = 0; xmp < 2; xmp++) {
+      bool seen_iccp = false;
+      bool seen_xmp = false;
+
+      wuffs_gif__decoder dec;
+      status = wuffs_gif__decoder__initialize(
+          &dec, sizeof dec, WUFFS_VERSION,
+          WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED);
+      if (status) {
+        RETURN_FAIL("initialize: \"%s\"", status);
+      }
+
+      if (iccp) {
+        wuffs_gif__decoder__set_report_metadata(&dec, WUFFS_BASE__FOURCC__ICCP,
+                                                true);
+      }
+      if (xmp) {
+        wuffs_gif__decoder__set_report_metadata(&dec, WUFFS_BASE__FOURCC__XMP,
+                                                true);
+      }
+
+      wuffs_base__image_config ic = ((wuffs_base__image_config){});
+      src.meta.ri = 0;
+
+      while (true) {
+        status = wuffs_gif__decoder__decode_image_config(
+            &dec, &ic, wuffs_base__io_buffer__reader(&src));
+        if (!status) {
+          break;
+        } else if (status != wuffs_base__warning__metadata_reported) {
+          RETURN_FAIL(
+              "decode_image_config (iccp=%d, xmp=%d): got \"%s\", want \"%s\"",
+              iccp, xmp, status, wuffs_base__warning__metadata_reported);
+        }
+
+        const char* want = "";
+        char got_buffer[100];
+        int got_length = 0;
+        uint32_t got_fourcc = wuffs_gif__decoder__metadata_fourcc(&dec);
+
+        switch (wuffs_gif__decoder__metadata_fourcc(&dec)) {
+          case WUFFS_BASE__FOURCC__ICCP:
+            // TODO: want = "etc";
+            seen_iccp = true;
+            break;
+          case WUFFS_BASE__FOURCC__XMP:
+            want = "\x05\x17\x27\x37\x47\x57\x03\x77\x87\x97";
+            seen_xmp = true;
+            break;
+          default:
+            RETURN_FAIL(
+                "metadata_fourcc (iccp=%d, xmp=%d): unexpected FourCC "
+                "0x%08" PRIX32,
+                iccp, xmp, got_fourcc);
+        }
+
+        while (true) {
+          uint64_t n = wuffs_gif__decoder__metadata_chunk_length(&dec);
+          if ((n > 100) || (n + got_length > 100)) {
+            RETURN_FAIL(
+                "metadata_chunk_length (iccp=%d, xmp=%d): too much "
+                "metadata (vs buffer size)",
+                iccp, xmp);
+          }
+          if (n > wuffs_base__io_buffer__reader_available(&src)) {
+            RETURN_FAIL(
+                "metadata_chunk_length (iccp=%d, xmp=%d): too much "
+                "metadata (vs available)",
+                iccp, xmp);
+          }
+          memcpy(got_buffer + got_length, src.data.ptr + src.meta.ri, n);
+          got_length += n;
+          src.meta.ri += n;
+
+          status = wuffs_gif__decoder__ack_metadata_chunk(
+              &dec, wuffs_base__io_buffer__reader(&src));
+          if (!status) {
+            break;
+          } else if (status != wuffs_base__warning__metadata_reported) {
+            RETURN_FAIL(
+                "ack_metadata_chunk (iccp=%d, xmp=%d): got \"%s\", want \"%s\"",
+                iccp, xmp, status, wuffs_base__warning__metadata_reported);
+          }
+        }
+
+        int want_length = strlen(want);
+        if ((got_length != want_length) ||
+            strncmp(got_buffer, want, want_length)) {
+          RETURN_FAIL("metadata (iccp=%d, xmp=%d): fourcc=0x%08" PRIX32
+                      ": values differed",
+                      iccp, xmp, got_fourcc);
+        }
+      }
+
+      if (iccp != seen_iccp) {
+        // TODO.
+      }
+
+      if (xmp != seen_xmp) {
+        RETURN_FAIL("seen_xmp (iccp=%d, xmp=%d): got %d, want %d", iccp, xmp,
+                    seen_xmp, xmp);
+      }
+
+      {
+        uint64_t got = wuffs_base__image_config__first_frame_io_position(&ic);
+        uint64_t want = 25;
+        if (got != want) {
+          RETURN_FAIL("first_frame_io_position: got %" PRIu64 ", want %" PRIu64,
+                      got, want);
+        }
+      }
+
+      {
+        uint32_t got = wuffs_gif__decoder__num_animation_loops(&dec);
+        uint32_t want = 2001;
+        if (got != want) {
+          RETURN_FAIL("num_animation_loops: got %" PRIu32 ", want %" PRIu32,
+                      got, want);
+        }
+      }
+
+      {
+        wuffs_base__frame_config fc = ((wuffs_base__frame_config){});
+        status = wuffs_gif__decoder__decode_frame_config(
+            &dec, &fc, wuffs_base__io_buffer__reader(&src));
+        if (status) {
+          RETURN_FAIL("decode_frame_config (iccp=%d, xmp=%d): %s", iccp, xmp,
+                      status);
+        }
+        uint32_t got = wuffs_base__frame_config__width(&fc);
+        uint32_t want = 1;
+        if (got != want) {
+          RETURN_FAIL(
+              "decode_frame_config (iccp=%d, xmp=%d): width: got %" PRIu32
+              ", want %" PRIu32,
+              iccp, xmp, got, want);
+        }
+      }
+    }
+  }
+
+  return NULL;
+}
+
 const char* test_wuffs_gif_decode_missing_two_src_bytes() {
   CHECK_FOCUS(__func__);
 
@@ -1711,6 +1870,7 @@
     test_wuffs_gif_decode_input_is_a_gif_many_medium_reads,  //
     test_wuffs_gif_decode_input_is_a_gif_many_small_reads,   //
     test_wuffs_gif_decode_input_is_a_png,                    //
+    test_wuffs_gif_decode_metadata,                          //
     test_wuffs_gif_decode_missing_two_src_bytes,             //
     test_wuffs_gif_decode_multiple_loop_counts,              //
     test_wuffs_gif_decode_pixel_data_none,                   //
diff --git a/test/data/artificial/gif-metadata.gif b/test/data/artificial/gif-metadata.gif
new file mode 100644
index 0000000..8d01384
--- /dev/null
+++ b/test/data/artificial/gif-metadata.gif
Binary files differ
diff --git a/test/data/artificial/gif-metadata.gif.make-artificial.txt b/test/data/artificial/gif-metadata.gif.make-artificial.txt
new file mode 100644
index 0000000..197f857
--- /dev/null
+++ b/test/data/artificial/gif-metadata.gif.make-artificial.txt
@@ -0,0 +1,54 @@
+# Feed this file to script/make-artificial.go
+
+make gif
+
+header
+
+image {
+	imageWidthHeight 2 2
+	palette {
+		0x00 0x00 0xFF
+		0x11 0x00 0xFF
+		0x22 0x00 0xFF
+		0x33 0x00 0xFF
+	}
+}
+
+# ICCP metadata.
+#
+# Extension (Application Extension), 11 bytes for AI and AAC.
+bytes 0x21 0xFF 0x0B
+# Application Identifier "ICCRGBG1".
+bytes 0x49 0x43 0x43 0x52 0x47 0x42 0x47 0x31
+# Application Authentication Code "012".
+bytes 0x30 0x31 0x32
+# A block of arbitrary data.
+bytes 0x05 0x16 0x26 0x36 0x46 0x56
+# Another block of arbitrary data.
+bytes 0x03 0x76 0x86 0x96
+# Block Terminator.
+bytes 0x00
+
+# XMP metadata.
+#
+# Extension (Application Extension), 11 bytes for AI and AAC.
+bytes 0x21 0xFF 0x0B
+# Application Identifier "XMP Data".
+bytes 0x58 0x4D 0x50 0x20 0x44 0x61 0x74 0x61
+# Application Authentication Code "XMP".
+bytes 0x58 0x4D 0x50
+# A block of arbitrary data.
+bytes 0x05 0x17 0x27 0x37 0x47 0x57
+# Another block of arbitrary data.
+bytes 0x03 0x77 0x87 0x97
+# Block Terminator.
+bytes 0x00
+
+loopCount 2000
+
+frame {
+	frameLeftTopWidthHeight 1 0 1 1
+}
+lzw 4 0x01
+
+trailer