Decode multiple graphic controls in a GIF
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c
index 14d3e60..71fbd24 100644
--- a/release/c/wuffs-unsupported-snapshot.c
+++ b/release/c/wuffs-unsupported-snapshot.c
@@ -3425,7 +3425,6 @@
     uint8_t f_interlace;
     bool f_seen_num_loops;
     uint32_t f_num_loops;
-    bool f_seen_graphic_control;
     bool f_gc_has_transparent_index;
     uint8_t f_gc_transparent_index;
     uint8_t f_gc_disposal;
@@ -9376,7 +9375,6 @@
 static wuffs_base__empty_struct  //
 wuffs_gif__decoder__reset_gc(wuffs_gif__decoder* self) {
   self->private_impl.f_call_sequence = 5;
-  self->private_impl.f_seen_graphic_control = false;
   self->private_impl.f_gc_has_transparent_index = false;
   self->private_impl.f_gc_transparent_index = 0;
   self->private_impl.f_gc_disposal = 0;
@@ -10256,10 +10254,6 @@
   switch (coro_susp_point) {
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
 
-    if (self->private_impl.f_seen_graphic_control) {
-      status = wuffs_gif__error__bad_graphic_control;
-      goto exit;
-    }
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
       if (WUFFS_BASE__UNLIKELY(iop_a_src == io1_a_src)) {
@@ -10344,7 +10338,6 @@
       status = wuffs_gif__error__bad_graphic_control;
       goto exit;
     }
-    self->private_impl.f_seen_graphic_control = true;
 
     goto ok;
   ok:
diff --git a/script/make-artificial.go b/script/make-artificial.go
index 5e2e96d..4c65088 100644
--- a/script/make-artificial.go
+++ b/script/make-artificial.go
@@ -513,9 +513,10 @@
 
 func stateGif(line string) (stateFunc, error) {
 	const (
-		cmdB  = "bytes "
-		cmdL  = "lzw "
-		cmdLC = "loopCount "
+		cmdB   = "bytes "
+		cmdGCD = "graphicControlDuration "
+		cmdL   = "lzw "
+		cmdLC  = "loopCount "
 	)
 outer:
 	switch {
@@ -547,6 +548,25 @@
 		}
 		return stateGif, nil
 
+	case strings.HasPrefix(line, cmdGCD):
+		s := line[len(cmdGCD):]
+		if !strings.HasSuffix(s, "ms") {
+			break
+		}
+		s = s[:len(s)-2]
+		duration, s, ok := parseNum(s)
+		if !ok || s != "" {
+			break
+		}
+		duration /= 10 // GIF's unit of time is 10ms.
+		out = append(out,
+			0x21, 0xF9, 0x04, 0x00,
+			uint8(duration>>0),
+			uint8(duration>>8),
+			0x00, 0x00,
+		)
+		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 debe38a..dee425c 100644
--- a/std/gif/decode_gif.wuffs
+++ b/std/gif/decode_gif.wuffs
@@ -146,7 +146,6 @@
 	seen_num_loops base.bool,
 	num_loops      base.u32,
 
-	seen_graphic_control     base.bool,
 	gc_has_transparent_index base.bool,
 	gc_transparent_index     base.u8,
 	gc_disposal              base.u8,
@@ -414,7 +413,6 @@
 	this.call_sequence = 5
 	// The Image Descriptor is mandatory, but the Graphic Control extension is
 	// optional. Reset the GC related fields for the next decode_frame call.
-	this.seen_graphic_control = false
 	this.gc_has_transparent_index = false
 	this.gc_transparent_index = 0
 	this.gc_disposal = 0
@@ -681,9 +679,6 @@
 	var flags                    base.u8
 	var gc_duration_centiseconds base.u16
 
-	if this.seen_graphic_control {
-		return "#bad graphic control"
-	}
 	c = args.src.read_u8?()
 	if c <> 4 {
 		return "#bad graphic control"
@@ -719,7 +714,6 @@
 	if c <> 0 {
 		return "#bad graphic control"
 	}
-	this.seen_graphic_control = true
 }
 
 // decode_id_partX reads an Image Descriptor. The Image Separator byte has
diff --git a/test/c/std/gif.c b/test/c/std/gif.c
index 52ff77a..42fca32 100644
--- a/test/c/std/gif.c
+++ b/test/c/std/gif.c
@@ -1245,6 +1245,40 @@
       src, 0, wuffs_base__suspension__short_read, false);
 }
 
+const char* test_wuffs_gif_decode_multiple_graphic_controls() {
+  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-multiple-graphic-controls.gif");
+  if (status) {
+    return status;
+  }
+
+  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);
+  }
+  wuffs_base__frame_config fc = ((wuffs_base__frame_config){});
+  wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(&src);
+  status = wuffs_gif__decoder__decode_frame_config(&dec, &fc, src_reader);
+  if (status) {
+    RETURN_FAIL("decode_frame_config: \"%s\"", status);
+  }
+
+  int got = wuffs_base__frame_config__duration(&fc) /
+            WUFFS_BASE__FLICKS_PER_MILLISECOND;
+  int want = 300;
+  if (got != want) {
+    RETURN_FAIL("duration: got %d, want %d", got, want);
+  }
+  return NULL;
+}
+
 const char* test_wuffs_gif_decode_multiple_loop_counts() {
   CHECK_FOCUS(__func__);
   wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
@@ -2025,6 +2059,7 @@
     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_graphic_controls,         //
     test_wuffs_gif_decode_multiple_loop_counts,              //
     test_wuffs_gif_decode_pixel_data_none,                   //
     test_wuffs_gif_decode_pixel_data_not_enough,             //
diff --git a/test/data/artificial/gif-multiple-graphic-controls.gif b/test/data/artificial/gif-multiple-graphic-controls.gif
new file mode 100644
index 0000000..ca49790
--- /dev/null
+++ b/test/data/artificial/gif-multiple-graphic-controls.gif
Binary files differ
diff --git a/test/data/artificial/gif-multiple-graphic-controls.gif.make-artificial.txt b/test/data/artificial/gif-multiple-graphic-controls.gif.make-artificial.txt
new file mode 100644
index 0000000..c2ec26a
--- /dev/null
+++ b/test/data/artificial/gif-multiple-graphic-controls.gif.make-artificial.txt
@@ -0,0 +1,31 @@
+# Feed this file to script/make-artificial.go
+
+# This GIF image contains multiple "Graphic Control Extensions" for a frame.
+#
+# The GIF89a specification says that "at most one Graphic Control Extension may
+# precede a graphic rendering block", but in practice, some encoders emit more
+# than one, and decoders ignore all but the last one.
+
+make gif
+
+header
+
+image {
+	imageWidthHeight 1 1
+	palette {
+		0x00 0x00 0xFF
+		0x11 0x00 0xFF
+		0x22 0x00 0xFF
+		0x33 0x00 0xFF
+	}
+}
+
+graphicControlDuration 200ms
+graphicControlDuration 300ms
+
+frame {
+	frameLeftTopWidthHeight 0 0 1 1
+}
+lzw 2 0x00
+
+trailer