std/vp8: accept VP8X (but ignore any ALPH chunk)
diff --git a/doc/note/quirks.md b/doc/note/quirks.md
index 8ebdf18..6759f94 100644
--- a/doc/note/quirks.md
+++ b/doc/note/quirks.md
@@ -76,11 +76,12 @@
 
 Package-specific quirks:
 
-- [GIF image decoder quirks](/std/gif/decode_quirks.wuffs)
+- [GIF decoder quirks](/std/gif/decode_quirks.wuffs)
 - [JPEG decoder quirks](/std/jpeg/decode_quirks.wuffs)
 - [JSON decoder quirks](/std/json/decode_quirks.wuffs)
 - [LZMA decoder quirks](/std/lzma/decode_quirks.wuffs)
 - [LZW decoder quirks](/std/lzw/decode_quirks.wuffs)
-- [TH decoder quirks](/std/thumbhash/decode_quirks.wuffs)
+- [THUMBHASH decoder quirks](/std/thumbhash/decode_quirks.wuffs)
+- [VP8 decoder quirks](/std/vp8/decode_quirks.wuffs)
 - [XZ decoder quirks](/std/xz/decode_quirks.wuffs)
 - [ZLIB decoder quirks](/std/zlib/decode_quirks.wuffs)
diff --git a/doc/std/image-decoders.md b/doc/std/image-decoders.md
index 92a26b0..ac55351 100644
--- a/doc/std/image-decoders.md
+++ b/doc/std/image-decoders.md
@@ -132,8 +132,9 @@
 - [std/nie](/std/nie)
 - [std/png](/std/png)
 - [std/qoi](/std/qoi)
-- [std/tga](/std/tga)
+- [std/targa](/std/targa)
 - [std/thumbhash](/std/thumbhash)
+- [std/vp8](/std/vp8)
 - [std/wbmp](/std/wbmp)
 - [std/webp](/std/webp)
 
@@ -160,7 +161,8 @@
 
 - [GIF decoder quirks](/std/gif/decode_quirks.wuffs)
 - [JPEG decoder quirks](/std/jpeg/decode_quirks.wuffs)
-- [TH decoder quirks](/std/thumbhash/decode_quirks.wuffs)
+- [THUMBHASH decoder quirks](/std/thumbhash/decode_quirks.wuffs)
+- [VP8 decoder quirks](/std/vp8/decode_quirks.wuffs)
 
 
 ## Related Documentation
diff --git a/internal/cgen/expr.go b/internal/cgen/expr.go
index fae4007..8a09124 100644
--- a/internal/cgen/expr.go
+++ b/internal/cgen/expr.go
@@ -551,8 +551,16 @@
 		return fmt.Errorf("cannot generate user-defined method call %q for receiver type %q",
 			n.Str(g.tm), recv.MType().Str(g.tm))
 	}
+
 	qid := recvTyp.QID()
-	b.printf("%s%s__%s(", g.packagePrefix(qid), qid[1].Str(g.tm), method.Ident().Str(g.tm))
+	pkgStr := g.packagePrefix(qid)
+	typStr := qid[1].Str(g.tm)
+	methodStr := method.Ident().Str(g.tm)
+	if (qid[0] == t.IDBase) && qid[1].IsRangeType() && strings.HasPrefix(methodStr, "get_") {
+		pkgStr = "wuffs_private_impl__"
+	}
+	b.printf("%s%s__%s(", pkgStr, typStr, methodStr)
+
 	if !recvTyp.IsEtcUtilityType() {
 		b.writes(addr)
 		if err := g.writeExpr(b, recv, false, depth); err != nil {
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c
index aa66514..81444a6 100644
--- a/release/c/wuffs-unsupported-snapshot.c
+++ b/release/c/wuffs-unsupported-snapshot.c
@@ -15457,7 +15457,9 @@
 // ---------------- Status Codes
 
 extern const char wuffs_vp8__error__bad_quirk_source_length[];
+extern const char wuffs_vp8__error__bad_quirk_width_and_height[];
 extern const char wuffs_vp8__error__bad_header[];
+extern const char wuffs_vp8__error__inconsistent_width_and_height[];
 extern const char wuffs_vp8__error__missing_quirk_source_length[];
 extern const char wuffs_vp8__error__truncated_input[];
 extern const char wuffs_vp8__error__unsupported_quirk_source_length[];
@@ -15465,6 +15467,8 @@
 
 // ---------------- Public Consts
 
+#define WUFFS_VP8__QUIRK_WIDTH_AND_HEIGHT 1836840960u
+
 #define WUFFS_VP8__DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE 553648127u
 
 // ---------------- Struct Declarations
@@ -15638,6 +15642,7 @@
     bool f_is_key_frame;
     uint64_t f_frame_config_io_position;
     uint64_t f_quirk_source_length;
+    uint64_t f_quirk_width_and_height;
     uint32_t f_partitioned_data_length;
     uint32_t f_workbuf_yuv_y_stride;
     uint32_t f_workbuf_yuv_uv_stride;
@@ -16214,22 +16219,28 @@
 
 // ---------------- Status Codes
 
+extern const char wuffs_webp__error__bad_alph_vp8l_combination[];
 extern const char wuffs_webp__error__bad_huffman_code_over_subscribed[];
 extern const char wuffs_webp__error__bad_huffman_code_under_subscribed[];
 extern const char wuffs_webp__error__bad_huffman_code[];
+extern const char wuffs_webp__error__bad_vp8x_chunk[];
 extern const char wuffs_webp__error__bad_back_reference[];
+extern const char wuffs_webp__error__bad_chunk_order[];
 extern const char wuffs_webp__error__bad_color_cache[];
 extern const char wuffs_webp__error__bad_header[];
 extern const char wuffs_webp__error__bad_transform[];
 extern const char wuffs_webp__error__short_chunk[];
 extern const char wuffs_webp__error__truncated_input[];
-extern const char wuffs_webp__error__unsupported_number_of_huffman_groups[];
+extern const char wuffs_webp__error__unsupported_vp8x_vp8_chunk_length[];
 extern const char wuffs_webp__error__unsupported_webp_file[];
+extern const char wuffs_webp__error__unsupported_number_of_huffman_groups[];
 
 // ---------------- Public Consts
 
 #define WUFFS_WEBP__DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE 1275068416u
 
+#define WUFFS_WEBP__VP8X_MAX_INCL_VP8_CHUNK_LENGTH_DEFAULT 8388608u
+
 // ---------------- Struct Declarations
 
 typedef struct wuffs_webp__decoder__struct wuffs_webp__decoder;
@@ -16398,7 +16409,8 @@
     uint8_t f_call_sequence;
     uint8_t f_code_length_code_lengths[19];
     bool f_sub_chunk_has_padding;
-    bool f_is_vp8_lossy;
+    uint8_t f_variant;
+    uint8_t f_vp8x_header_flags;
     uint64_t f_frame_config_io_position;
     uint32_t f_riff_chunk_length;
     uint32_t f_sub_chunk_length;
@@ -16430,10 +16442,12 @@
     uint32_t p_do_decode_image_config;
     uint32_t p_do_decode_image_config_limited;
     uint32_t p_do_decode_image_config_limited_vp8l;
+    uint32_t p_do_decode_image_config_limited_vp8x;
     uint32_t p_decode_frame_config;
     uint32_t p_do_decode_frame_config;
     uint32_t p_decode_frame;
     uint32_t p_do_decode_frame;
+    uint32_t p_do_decode_frame_vp8l;
     uint32_t p_decode_transform;
     uint32_t p_decode_color_cache_parameters;
     uint32_t p_decode_hg_table;
@@ -16498,9 +16512,16 @@
       uint64_t scratch;
     } s_do_decode_image_config_limited_vp8l;
     struct {
-      uint32_t v_width;
+      uint64_t scratch;
+    } s_do_decode_image_config_limited_vp8x;
+    struct {
+      uint32_t v_chunk_type;
+      uint64_t scratch;
     } s_do_decode_frame;
     struct {
+      uint32_t v_width;
+    } s_do_decode_frame_vp8l;
+    struct {
       uint32_t v_transform_type;
       uint32_t v_tile_size_log2;
     } s_decode_transform;
@@ -80931,7 +80952,9 @@
 // ---------------- Status Codes Implementations
 
 const char wuffs_vp8__error__bad_quirk_source_length[] = "#vp8: bad QUIRK_SOURCE_LENGTH";
+const char wuffs_vp8__error__bad_quirk_width_and_height[] = "#vp8: bad QUIRK_WIDTH_AND_HEIGHT";
 const char wuffs_vp8__error__bad_header[] = "#vp8: bad header";
+const char wuffs_vp8__error__inconsistent_width_and_height[] = "#vp8: inconsistent width and height";
 const char wuffs_vp8__error__missing_quirk_source_length[] = "#vp8: missing QUIRK_SOURCE_LENGTH";
 const char wuffs_vp8__error__truncated_input[] = "#vp8: truncated input";
 const char wuffs_vp8__error__unsupported_quirk_source_length[] = "#vp8: unsupported QUIRK_SOURCE_LENGTH";
@@ -81826,6 +81849,8 @@
   9u, 12u, 13u, 10u, 7u, 11u, 14u, 15u,
 };
 
+#define WUFFS_VP8__QUIRKS_BASE 1836840960u
+
 // ---------------- Private Initializer Prototypes
 
 // ---------------- Private Function Prototypes
@@ -82134,6 +82159,11 @@
     wuffs_base__io_buffer* a_src);
 
 WUFFS_BASE__GENERATED_C_CODE
+static wuffs_base__empty_struct
+wuffs_vp8__decoder__calculate_mbw_mbh(
+    wuffs_vp8__decoder* self);
+
+WUFFS_BASE__GENERATED_C_CODE
 static wuffs_base__status
 wuffs_vp8__decoder__do_decode_frame_config(
     wuffs_vp8__decoder* self,
@@ -85191,6 +85221,8 @@
 
   if (a_key == 3u) {
     return self->private_impl.f_quirk_source_length;
+  } else if (a_key == 1836840960u) {
+    return self->private_impl.f_quirk_width_and_height;
   }
   return 0u;
 }
@@ -85213,8 +85245,38 @@
         : wuffs_base__error__initialize_not_called);
   }
 
+  uint32_t v_w = 0;
+  uint32_t v_h = 0;
+
   if (a_key == 3u) {
-    self->private_impl.f_quirk_source_length = a_value;
+    if ((a_value & 1u) == 0u) {
+      if (a_value != 0u) {
+        return wuffs_base__make_status(wuffs_vp8__error__bad_quirk_source_length);
+      }
+      self->private_impl.f_quirk_source_length = 0u;
+      self->private_impl.f_partitioned_data_length = 0u;
+    } else if ((a_value >> 1u) < 10u) {
+      return wuffs_base__make_status(wuffs_vp8__error__bad_quirk_source_length);
+    } else if ((a_value >> 1u) > 150994945u) {
+      return wuffs_base__make_status(wuffs_vp8__error__unsupported_quirk_source_length);
+    } else {
+      self->private_impl.f_quirk_source_length = a_value;
+      self->private_impl.f_partitioned_data_length = (((uint32_t)((a_value >> 1u))) - 10u);
+    }
+    return wuffs_base__make_status(NULL);
+  } else if (a_key == 1836840960u) {
+    v_w = ((uint32_t)(a_value));
+    v_h = ((uint32_t)(a_value));
+    if ((v_w < 1u) ||
+        (16383u < v_w) ||
+        (v_h < 1u) ||
+        (16383u < v_h)) {
+      return wuffs_base__make_status(wuffs_vp8__error__bad_quirk_width_and_height);
+    }
+    self->private_impl.f_quirk_width_and_height = a_value;
+    self->private_impl.f_width = v_w;
+    self->private_impl.f_height = v_h;
+    wuffs_vp8__decoder__calculate_mbw_mbh(self);
     return wuffs_base__make_status(NULL);
   }
   return wuffs_base__make_status(wuffs_base__error__unsupported_option);
@@ -85317,17 +85379,10 @@
       status = wuffs_base__make_status(wuffs_base__error__bad_call_sequence);
       goto exit;
     }
-    if ((self->private_impl.f_quirk_source_length & 1u) == 0u) {
+    if (self->private_impl.f_quirk_source_length == 0u) {
       status = wuffs_base__make_status(wuffs_vp8__error__missing_quirk_source_length);
       goto exit;
-    } else if ((self->private_impl.f_quirk_source_length >> 1u) < 10u) {
-      status = wuffs_base__make_status(wuffs_vp8__error__bad_quirk_source_length);
-      goto exit;
-    } else if ((self->private_impl.f_quirk_source_length >> 1u) > 150994945u) {
-      status = wuffs_base__make_status(wuffs_vp8__error__unsupported_quirk_source_length);
-      goto exit;
     }
-    self->private_impl.f_partitioned_data_length = (((uint32_t)((self->private_impl.f_quirk_source_length >> 1u))) - 10u);
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
       uint32_t t_0;
@@ -85431,13 +85486,14 @@
     }
     self->private_impl.f_width = (16383u & v_c32);
     self->private_impl.f_height = (16383u & (v_c32 >> 16u));
-    self->private_impl.f_mbw = ((self->private_impl.f_width + 15u) / 16u);
-    self->private_impl.f_mbh = ((self->private_impl.f_height + 15u) / 16u);
-    self->private_impl.f_workbuf_yuv_y_stride = (self->private_impl.f_mbw * 16u);
-    self->private_impl.f_workbuf_yuv_uv_stride = (self->private_impl.f_mbw * 8u);
-    self->private_impl.f_workbuf_yuv_y_end = (((uint64_t)(self->private_impl.f_workbuf_yuv_y_stride)) * ((uint64_t)((self->private_impl.f_mbh * 16u))));
-    self->private_impl.f_workbuf_yuv_u_end = (self->private_impl.f_workbuf_yuv_y_end + (((uint64_t)(self->private_impl.f_workbuf_yuv_uv_stride)) * ((uint64_t)((self->private_impl.f_mbh * 8u)))));
-    self->private_impl.f_workbuf_yuv_v_end = (self->private_impl.f_workbuf_yuv_u_end + (((uint64_t)(self->private_impl.f_workbuf_yuv_uv_stride)) * ((uint64_t)((self->private_impl.f_mbh * 8u)))));
+    if ((self->private_impl.f_width == 0u) || (self->private_impl.f_height == 0u)) {
+      status = wuffs_base__make_status(wuffs_vp8__error__bad_header);
+      goto exit;
+    } else if ((self->private_impl.f_quirk_width_and_height != 0u) && (self->private_impl.f_quirk_width_and_height != (((uint64_t)(self->private_impl.f_width)) | (((uint64_t)(self->private_impl.f_height)) << 32u)))) {
+      status = wuffs_base__make_status(wuffs_vp8__error__inconsistent_width_and_height);
+      goto exit;
+    }
+    wuffs_vp8__decoder__calculate_mbw_mbh(self);
     self->private_impl.f_frame_config_io_position = wuffs_base__u64__sat_add((a_src ? a_src->meta.pos : 0), ((uint64_t)(iop_a_src - io0_a_src)));
     if (a_dst != NULL) {
       wuffs_base__image_config__set(
@@ -85470,6 +85526,22 @@
   return status;
 }
 
+// -------- func vp8.decoder.calculate_mbw_mbh
+
+WUFFS_BASE__GENERATED_C_CODE
+static wuffs_base__empty_struct
+wuffs_vp8__decoder__calculate_mbw_mbh(
+    wuffs_vp8__decoder* self) {
+  self->private_impl.f_mbw = ((self->private_impl.f_width + 15u) / 16u);
+  self->private_impl.f_mbh = ((self->private_impl.f_height + 15u) / 16u);
+  self->private_impl.f_workbuf_yuv_y_stride = (self->private_impl.f_mbw * 16u);
+  self->private_impl.f_workbuf_yuv_uv_stride = (self->private_impl.f_mbw * 8u);
+  self->private_impl.f_workbuf_yuv_y_end = (((uint64_t)(self->private_impl.f_workbuf_yuv_y_stride)) * ((uint64_t)((self->private_impl.f_mbh * 16u))));
+  self->private_impl.f_workbuf_yuv_u_end = (self->private_impl.f_workbuf_yuv_y_end + (((uint64_t)(self->private_impl.f_workbuf_yuv_uv_stride)) * ((uint64_t)((self->private_impl.f_mbh * 8u)))));
+  self->private_impl.f_workbuf_yuv_v_end = (self->private_impl.f_workbuf_yuv_u_end + (((uint64_t)(self->private_impl.f_workbuf_yuv_uv_stride)) * ((uint64_t)((self->private_impl.f_mbh * 8u)))));
+  return wuffs_base__make_empty_struct();
+}
+
 // -------- func vp8.decoder.decode_frame_config
 
 WUFFS_BASE__GENERATED_C_CODE
@@ -86922,17 +86994,21 @@
 
 // ---------------- Status Codes Implementations
 
+const char wuffs_webp__error__bad_alph_vp8l_combination[] = "#webp: bad ALPH+VP8L combination";
 const char wuffs_webp__error__bad_huffman_code_over_subscribed[] = "#webp: bad Huffman code (over-subscribed)";
 const char wuffs_webp__error__bad_huffman_code_under_subscribed[] = "#webp: bad Huffman code (under-subscribed)";
 const char wuffs_webp__error__bad_huffman_code[] = "#webp: bad Huffman code";
+const char wuffs_webp__error__bad_vp8x_chunk[] = "#webp: bad VP8X chunk";
 const char wuffs_webp__error__bad_back_reference[] = "#webp: bad back-reference";
+const char wuffs_webp__error__bad_chunk_order[] = "#webp: bad chunk order";
 const char wuffs_webp__error__bad_color_cache[] = "#webp: bad color cache";
 const char wuffs_webp__error__bad_header[] = "#webp: bad header";
 const char wuffs_webp__error__bad_transform[] = "#webp: bad transform";
 const char wuffs_webp__error__short_chunk[] = "#webp: short chunk";
 const char wuffs_webp__error__truncated_input[] = "#webp: truncated input";
-const char wuffs_webp__error__unsupported_number_of_huffman_groups[] = "#webp: unsupported number of Huffman groups";
+const char wuffs_webp__error__unsupported_vp8x_vp8_chunk_length[] = "#webp: unsupported VP8X/VP8 chunk length";
 const char wuffs_webp__error__unsupported_webp_file[] = "#webp: unsupported WebP file";
+const char wuffs_webp__error__unsupported_number_of_huffman_groups[] = "#webp: unsupported number of Huffman groups";
 const char wuffs_webp__error__internal_error_inconsistent_huffman_code[] = "#webp: internal error: inconsistent Huffman code";
 const char wuffs_webp__error__internal_error_inconsistent_dst_buffer[] = "#webp: internal error: inconsistent dst buffer";
 const char wuffs_webp__error__internal_error_inconsistent_n_bits[] = "#webp: internal error: inconsistent n_bits";
@@ -87116,6 +87192,12 @@
 
 WUFFS_BASE__GENERATED_C_CODE
 static wuffs_base__status
+wuffs_webp__decoder__do_decode_image_config_limited_vp8x(
+    wuffs_webp__decoder* self,
+    wuffs_base__io_buffer* a_src);
+
+WUFFS_BASE__GENERATED_C_CODE
+static wuffs_base__status
 wuffs_webp__decoder__do_decode_frame_config(
     wuffs_webp__decoder* self,
     wuffs_base__frame_config* a_dst,
@@ -87133,6 +87215,16 @@
 
 WUFFS_BASE__GENERATED_C_CODE
 static wuffs_base__status
+wuffs_webp__decoder__do_decode_frame_vp8l(
+    wuffs_webp__decoder* self,
+    wuffs_base__pixel_buffer* a_dst,
+    wuffs_base__io_buffer* a_src,
+    wuffs_base__pixel_blend a_blend,
+    wuffs_base__slice_u8 a_workbuf,
+    wuffs_base__decode_frame_options* a_opts);
+
+WUFFS_BASE__GENERATED_C_CODE
+static wuffs_base__status
 wuffs_webp__decoder__decode_transform(
     wuffs_webp__decoder* self,
     wuffs_base__io_buffer* a_src,
@@ -89288,7 +89380,7 @@
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(5);
     }
     self->private_impl.f_frame_config_io_position = wuffs_base__u64__sat_add((a_src ? a_src->meta.pos : 0), ((uint64_t)(iop_a_src - io0_a_src)));
-    if ( ! self->private_impl.f_is_vp8_lossy && (a_dst != NULL)) {
+    if ((self->private_impl.f_variant != 1u) && (a_dst != NULL)) {
       wuffs_base__image_config__set(
           a_dst,
           2164295816u,
@@ -89410,11 +89502,11 @@
       v_c32 = t_1;
     }
     if (v_c32 == 540561494u) {
-      self->private_impl.f_is_vp8_lossy = true;
+      self->private_impl.f_variant = 1u;
     } else if (v_c32 == 1278758998u) {
+      self->private_impl.f_variant = 2u;
     } else if (v_c32 == 1480085590u) {
-      status = wuffs_base__make_status(wuffs_webp__error__unsupported_webp_file);
-      goto exit;
+      self->private_impl.f_variant = 4u;
     } else {
       status = wuffs_base__make_status(wuffs_webp__error__bad_header);
       goto exit;
@@ -89453,8 +89545,14 @@
       goto exit;
     }
     self->private_impl.f_sub_chunk_has_padding = ((self->private_impl.f_sub_chunk_length & 1u) != 0u);
-    if (self->private_impl.f_is_vp8_lossy) {
+    if (self->private_impl.f_variant == 1u) {
       wuffs_vp8__decoder__set_quirk(&self->private_data.f_vp8, 3u, ((((uint64_t)(self->private_impl.f_sub_chunk_length)) << 1u) | 1u));
+    } else if (self->private_impl.f_variant == 2u) {
+    } else if (self->private_impl.f_sub_chunk_length != 10u) {
+      status = wuffs_base__make_status(wuffs_webp__error__bad_header);
+      goto exit;
+    } else {
+      wuffs_vp8__decoder__set_quirk(&self->private_data.f_vp8, 3u, 16777217u);
     }
     while (true) {
       {
@@ -89468,7 +89566,7 @@
           a_src->meta.wi = n;
         }
         v_r_mark = ((uint64_t)(iop_a_src - io0_a_src));
-        if (self->private_impl.f_is_vp8_lossy) {
+        if (self->private_impl.f_variant == 1u) {
           {
             if (a_src) {
               a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
@@ -89479,7 +89577,7 @@
               iop_a_src = a_src->data.ptr + a_src->meta.ri;
             }
           }
-        } else {
+        } else if (self->private_impl.f_variant == 2u) {
           {
             if (a_src) {
               a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
@@ -89490,6 +89588,17 @@
               iop_a_src = a_src->data.ptr + a_src->meta.ri;
             }
           }
+        } else {
+          {
+            if (a_src) {
+              a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+            }
+            wuffs_base__status t_5 = wuffs_webp__decoder__do_decode_image_config_limited_vp8x(self, a_src);
+            v_status = t_5;
+            if (a_src) {
+              iop_a_src = a_src->data.ptr + a_src->meta.ri;
+            }
+          }
         }
         wuffs_private_impl__u32__sat_sub_indirect(&self->private_impl.f_sub_chunk_length, ((uint32_t)(wuffs_private_impl__io__count_since(v_r_mark, ((uint64_t)(iop_a_src - io0_a_src))))));
         io2_a_src = o_0_io2_a_src;
@@ -89638,6 +89747,166 @@
   return status;
 }
 
+// -------- func webp.decoder.do_decode_image_config_limited_vp8x
+
+WUFFS_BASE__GENERATED_C_CODE
+static wuffs_base__status
+wuffs_webp__decoder__do_decode_image_config_limited_vp8x(
+    wuffs_webp__decoder* self,
+    wuffs_base__io_buffer* a_src) {
+  wuffs_base__status status = wuffs_base__make_status(NULL);
+
+  uint32_t v_c32 = 0;
+  uint32_t v_transform_size = 0;
+  wuffs_base__status v_status = wuffs_base__make_status(NULL);
+
+  const uint8_t* iop_a_src = NULL;
+  const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_src && a_src->data.ptr) {
+    io0_a_src = a_src->data.ptr;
+    io1_a_src = io0_a_src + a_src->meta.ri;
+    iop_a_src = io1_a_src;
+    io2_a_src = io0_a_src + a_src->meta.wi;
+  }
+
+  uint32_t coro_susp_point = self->private_impl.p_do_decode_image_config_limited_vp8x;
+  switch (coro_susp_point) {
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
+
+    {
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
+      uint32_t t_0;
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) {
+        t_0 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src);
+        iop_a_src += 4;
+      } else {
+        self->private_data.s_do_decode_image_config_limited_vp8x.scratch = 0;
+        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
+        while (true) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+            status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+            goto suspend;
+          }
+          uint64_t* scratch = &self->private_data.s_do_decode_image_config_limited_vp8x.scratch;
+          uint32_t num_bits_0 = ((uint32_t)(*scratch >> 56));
+          *scratch <<= 8;
+          *scratch >>= 8;
+          *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_0;
+          if (num_bits_0 == 24) {
+            t_0 = ((uint32_t)(*scratch));
+            break;
+          }
+          num_bits_0 += 8u;
+          *scratch |= ((uint64_t)(num_bits_0)) << 56;
+        }
+      }
+      v_c32 = t_0;
+    }
+    if ((v_c32 & 4294967233u) != 0u) {
+      status = wuffs_base__make_status(wuffs_webp__error__unsupported_webp_file);
+      goto exit;
+    }
+    self->private_impl.f_vp8x_header_flags = ((uint8_t)((v_c32 | 1u)));
+    {
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
+      uint32_t t_1;
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 3)) {
+        t_1 = ((uint32_t)(wuffs_base__peek_u24le__no_bounds_check(iop_a_src)));
+        iop_a_src += 3;
+      } else {
+        self->private_data.s_do_decode_image_config_limited_vp8x.scratch = 0;
+        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
+        while (true) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+            status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+            goto suspend;
+          }
+          uint64_t* scratch = &self->private_data.s_do_decode_image_config_limited_vp8x.scratch;
+          uint32_t num_bits_1 = ((uint32_t)(*scratch >> 56));
+          *scratch <<= 8;
+          *scratch >>= 8;
+          *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_1;
+          if (num_bits_1 == 16) {
+            t_1 = ((uint32_t)(*scratch));
+            break;
+          }
+          num_bits_1 += 8u;
+          *scratch |= ((uint64_t)(num_bits_1)) << 56;
+        }
+      }
+      v_c32 = t_1;
+    }
+    if (v_c32 >= 16383u) {
+      status = wuffs_base__make_status(wuffs_webp__error__unsupported_webp_file);
+      goto exit;
+    }
+    self->private_impl.f_width = ((v_c32 & 16383u) + 1u);
+    {
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5);
+      uint32_t t_2;
+      if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 3)) {
+        t_2 = ((uint32_t)(wuffs_base__peek_u24le__no_bounds_check(iop_a_src)));
+        iop_a_src += 3;
+      } else {
+        self->private_data.s_do_decode_image_config_limited_vp8x.scratch = 0;
+        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6);
+        while (true) {
+          if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+            status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+            goto suspend;
+          }
+          uint64_t* scratch = &self->private_data.s_do_decode_image_config_limited_vp8x.scratch;
+          uint32_t num_bits_2 = ((uint32_t)(*scratch >> 56));
+          *scratch <<= 8;
+          *scratch >>= 8;
+          *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_2;
+          if (num_bits_2 == 16) {
+            t_2 = ((uint32_t)(*scratch));
+            break;
+          }
+          num_bits_2 += 8u;
+          *scratch |= ((uint64_t)(num_bits_2)) << 56;
+        }
+      }
+      v_c32 = t_2;
+    }
+    if (v_c32 >= 16383u) {
+      status = wuffs_base__make_status(wuffs_webp__error__unsupported_webp_file);
+      goto exit;
+    }
+    self->private_impl.f_height = ((v_c32 & 16383u) + 1u);
+    v_transform_size = (4u * ((self->private_impl.f_width + 3u) >> 2u) * ((self->private_impl.f_height + 3u) >> 2u));
+    self->private_impl.f_workbuf_offset_for_transform[0u] = ((4u * self->private_impl.f_width * self->private_impl.f_height) + 0u);
+    self->private_impl.f_workbuf_offset_for_transform[1u] = ((4u * self->private_impl.f_width * self->private_impl.f_height) + v_transform_size);
+    self->private_impl.f_workbuf_offset_for_transform[2u] = ((4u * self->private_impl.f_width * self->private_impl.f_height) + (2u * v_transform_size));
+    self->private_impl.f_workbuf_offset_for_transform[3u] = ((4u * self->private_impl.f_width * self->private_impl.f_height) + (3u * v_transform_size));
+    v_status = wuffs_vp8__decoder__set_quirk(&self->private_data.f_vp8, 1836840960u, (((uint64_t)(self->private_impl.f_width)) | (((uint64_t)(self->private_impl.f_height)) << 32u)));
+    if ( ! wuffs_base__status__is_ok(&v_status)) {
+      status = wuffs_base__make_status(wuffs_webp__error__unsupported_webp_file);
+      goto exit;
+    }
+
+    goto ok;
+    ok:
+    self->private_impl.p_do_decode_image_config_limited_vp8x = 0;
+    goto exit;
+  }
+
+  goto suspend;
+  suspend:
+  self->private_impl.p_do_decode_image_config_limited_vp8x = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
+
+  goto exit;
+  exit:
+  if (a_src && a_src->data.ptr) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+  }
+
+  return status;
+}
+
 // -------- func webp.decoder.decode_frame_config
 
 WUFFS_BASE__GENERATED_C_CODE
@@ -89674,7 +89943,7 @@
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
 
     while (true) {
-      if (self->private_impl.f_is_vp8_lossy) {
+      if (self->private_impl.f_variant == 1u) {
         {
           wuffs_base__status t_0 = wuffs_vp8__decoder__decode_frame_config(&self->private_data.f_vp8, a_dst, a_src);
           v_status = t_0;
@@ -89762,7 +90031,7 @@
       status = wuffs_base__make_status(wuffs_base__note__end_of_data);
       goto ok;
     }
-    if (a_dst != NULL) {
+    if ((self->private_impl.f_variant != 1u) && (a_dst != NULL)) {
       wuffs_base__frame_config__set(
           a_dst,
           wuffs_base__utility__make_rect_ie_u32(
@@ -89837,26 +90106,14 @@
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
 
     while (true) {
-      if (self->private_impl.f_is_vp8_lossy) {
-        {
-          wuffs_base__status t_0 = wuffs_vp8__decoder__decode_frame(&self->private_data.f_vp8,
-              a_dst,
-              a_src,
-              a_blend,
-              a_workbuf,
-              a_opts);
-          v_status = t_0;
-        }
-      } else {
-        {
-          wuffs_base__status t_1 = wuffs_webp__decoder__do_decode_frame(self,
-              a_dst,
-              a_src,
-              a_blend,
-              a_workbuf,
-              a_opts);
-          v_status = t_1;
-        }
+      {
+        wuffs_base__status t_0 = wuffs_webp__decoder__do_decode_frame(self,
+            a_dst,
+            a_src,
+            a_blend,
+            a_workbuf,
+            a_opts);
+        v_status = t_0;
       }
       if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) {
         status = wuffs_base__make_status(wuffs_webp__error__truncated_input);
@@ -89897,6 +90154,249 @@
     wuffs_base__decode_frame_options* a_opts) {
   wuffs_base__status status = wuffs_base__make_status(NULL);
 
+  uint32_t v_chunk_type = 0;
+  uint32_t v_chunk_length = 0;
+
+  const uint8_t* iop_a_src = NULL;
+  const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_src && a_src->data.ptr) {
+    io0_a_src = a_src->data.ptr;
+    io1_a_src = io0_a_src + a_src->meta.ri;
+    iop_a_src = io1_a_src;
+    io2_a_src = io0_a_src + a_src->meta.wi;
+  }
+
+  uint32_t coro_susp_point = self->private_impl.p_do_decode_frame;
+  if (coro_susp_point) {
+    v_chunk_type = self->private_data.s_do_decode_frame.v_chunk_type;
+  }
+  switch (coro_susp_point) {
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
+
+    if (self->private_impl.f_call_sequence == 64u) {
+    } else if (self->private_impl.f_call_sequence < 64u) {
+      if (a_src) {
+        a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+      }
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
+      status = wuffs_webp__decoder__do_decode_frame_config(self, NULL, a_src);
+      if (a_src) {
+        iop_a_src = a_src->data.ptr + a_src->meta.ri;
+      }
+      if (status.repr) {
+        goto suspend;
+      }
+    } else {
+      status = wuffs_base__make_status(wuffs_base__note__end_of_data);
+      goto ok;
+    }
+    while (((uint8_t)(self->private_impl.f_variant & 3u)) == 0u) {
+      {
+        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
+        uint32_t t_0;
+        if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) {
+          t_0 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src);
+          iop_a_src += 4;
+        } else {
+          self->private_data.s_do_decode_frame.scratch = 0;
+          WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
+          while (true) {
+            if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+              status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+              goto suspend;
+            }
+            uint64_t* scratch = &self->private_data.s_do_decode_frame.scratch;
+            uint32_t num_bits_0 = ((uint32_t)(*scratch >> 56));
+            *scratch <<= 8;
+            *scratch >>= 8;
+            *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_0;
+            if (num_bits_0 == 24) {
+              t_0 = ((uint32_t)(*scratch));
+              break;
+            }
+            num_bits_0 += 8u;
+            *scratch |= ((uint64_t)(num_bits_0)) << 56;
+          }
+        }
+        v_chunk_type = t_0;
+      }
+      {
+        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
+        uint32_t t_1;
+        if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) {
+          t_1 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src);
+          iop_a_src += 4;
+        } else {
+          self->private_data.s_do_decode_frame.scratch = 0;
+          WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5);
+          while (true) {
+            if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+              status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+              goto suspend;
+            }
+            uint64_t* scratch = &self->private_data.s_do_decode_frame.scratch;
+            uint32_t num_bits_1 = ((uint32_t)(*scratch >> 56));
+            *scratch <<= 8;
+            *scratch >>= 8;
+            *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_1;
+            if (num_bits_1 == 24) {
+              t_1 = ((uint32_t)(*scratch));
+              break;
+            }
+            num_bits_1 += 8u;
+            *scratch |= ((uint64_t)(num_bits_1)) << 56;
+          }
+        }
+        v_chunk_length = t_1;
+      }
+      if (v_chunk_type == 1346585417u) {
+        if (self->private_impl.f_variant > 4u) {
+          status = wuffs_base__make_status(wuffs_webp__error__bad_chunk_order);
+          goto exit;
+        } else if (((uint8_t)(self->private_impl.f_vp8x_header_flags & 32u)) == 0u) {
+          status = wuffs_base__make_status(wuffs_webp__error__bad_vp8x_chunk);
+          goto exit;
+        }
+        self->private_impl.f_variant = 8u;
+      } else if (v_chunk_type == 1296649793u) {
+        if (self->private_impl.f_variant > 8u) {
+          status = wuffs_base__make_status(wuffs_webp__error__bad_chunk_order);
+          goto exit;
+        } else if (((uint8_t)(self->private_impl.f_vp8x_header_flags & 2u)) == 0u) {
+          status = wuffs_base__make_status(wuffs_webp__error__bad_vp8x_chunk);
+          goto exit;
+        }
+        self->private_impl.f_variant = 16u;
+      } else if (v_chunk_type == 1179471425u) {
+        if (self->private_impl.f_variant > 16u) {
+          status = wuffs_base__make_status(wuffs_webp__error__bad_chunk_order);
+          goto exit;
+        } else if (((uint8_t)(self->private_impl.f_vp8x_header_flags & 2u)) == 0u) {
+          status = wuffs_base__make_status(wuffs_webp__error__bad_vp8x_chunk);
+          goto exit;
+        }
+        self->private_impl.f_variant = 32u;
+      } else if (v_chunk_type == 1213221953u) {
+        if (self->private_impl.f_variant > 32u) {
+          status = wuffs_base__make_status(wuffs_webp__error__bad_chunk_order);
+          goto exit;
+        } else if (((uint8_t)(self->private_impl.f_vp8x_header_flags & 16u)) == 0u) {
+          status = wuffs_base__make_status(wuffs_webp__error__bad_vp8x_chunk);
+          goto exit;
+        }
+        self->private_impl.f_variant = 64u;
+      } else if (v_chunk_type == 540561494u) {
+        if (((uint64_t)(v_chunk_length)) > 8388608u) {
+          status = wuffs_base__make_status(wuffs_webp__error__unsupported_vp8x_vp8_chunk_length);
+          goto exit;
+        }
+        wuffs_vp8__decoder__set_quirk(&self->private_data.f_vp8, 3u, ((((uint64_t)(v_chunk_length)) << 1u) | 1u));
+        self->private_impl.f_variant = 129u;
+        break;
+      } else if (v_chunk_type == 1278758998u) {
+        if (((uint8_t)(self->private_impl.f_vp8x_header_flags & 16u)) != 0u) {
+          status = wuffs_base__make_status(wuffs_webp__error__bad_alph_vp8l_combination);
+          goto exit;
+        }
+        self->private_impl.f_variant = 130u;
+        break;
+      } else if (v_chunk_type == 542133592u) {
+        if (((uint8_t)(self->private_impl.f_vp8x_header_flags & 4u)) == 0u) {
+          status = wuffs_base__make_status(wuffs_webp__error__bad_vp8x_chunk);
+          goto exit;
+        }
+      } else if (v_chunk_type == 1179211845u) {
+        if (((uint8_t)(self->private_impl.f_vp8x_header_flags & 8u)) == 0u) {
+          status = wuffs_base__make_status(wuffs_webp__error__bad_vp8x_chunk);
+          goto exit;
+        }
+      }
+      if (v_chunk_length > 2147483647u) {
+        status = wuffs_base__make_status(wuffs_webp__error__unsupported_webp_file);
+        goto exit;
+      } else if ((v_chunk_length & 1u) != 0u) {
+        v_chunk_length += 1u;
+      }
+      self->private_data.s_do_decode_frame.scratch = v_chunk_length;
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6);
+      if (self->private_data.s_do_decode_frame.scratch > ((uint64_t)(io2_a_src - iop_a_src))) {
+        self->private_data.s_do_decode_frame.scratch -= ((uint64_t)(io2_a_src - iop_a_src));
+        iop_a_src = io2_a_src;
+        status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+        goto suspend;
+      }
+      iop_a_src += self->private_data.s_do_decode_frame.scratch;
+    }
+    if (((uint8_t)(self->private_impl.f_variant & 1u)) != 0u) {
+      if (a_src) {
+        a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+      }
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7);
+      status = wuffs_vp8__decoder__decode_frame(&self->private_data.f_vp8,
+          a_dst,
+          a_src,
+          a_blend,
+          a_workbuf,
+          a_opts);
+      if (a_src) {
+        iop_a_src = a_src->data.ptr + a_src->meta.ri;
+      }
+      if (status.repr) {
+        goto suspend;
+      }
+    } else {
+      if (a_src) {
+        a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+      }
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8);
+      status = wuffs_webp__decoder__do_decode_frame_vp8l(self,
+          a_dst,
+          a_src,
+          a_blend,
+          a_workbuf,
+          a_opts);
+      if (a_src) {
+        iop_a_src = a_src->data.ptr + a_src->meta.ri;
+      }
+      if (status.repr) {
+        goto suspend;
+      }
+    }
+
+    ok:
+    self->private_impl.p_do_decode_frame = 0;
+    goto exit;
+  }
+
+  goto suspend;
+  suspend:
+  self->private_impl.p_do_decode_frame = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
+  self->private_data.s_do_decode_frame.v_chunk_type = v_chunk_type;
+
+  goto exit;
+  exit:
+  if (a_src && a_src->data.ptr) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+  }
+
+  return status;
+}
+
+// -------- func webp.decoder.do_decode_frame_vp8l
+
+WUFFS_BASE__GENERATED_C_CODE
+static wuffs_base__status
+wuffs_webp__decoder__do_decode_frame_vp8l(
+    wuffs_webp__decoder* self,
+    wuffs_base__pixel_buffer* a_dst,
+    wuffs_base__io_buffer* a_src,
+    wuffs_base__pixel_blend a_blend,
+    wuffs_base__slice_u8 a_workbuf,
+    wuffs_base__decode_frame_options* a_opts) {
+  wuffs_base__status status = wuffs_base__make_status(NULL);
+
   uint8_t v_c8 = 0;
   uint32_t v_has_more = 0;
   uint32_t v_width = 0;
@@ -89920,30 +90420,13 @@
     io2_a_src = io0_a_src + a_src->meta.wi;
   }
 
-  uint32_t coro_susp_point = self->private_impl.p_do_decode_frame;
+  uint32_t coro_susp_point = self->private_impl.p_do_decode_frame_vp8l;
   if (coro_susp_point) {
-    v_width = self->private_data.s_do_decode_frame.v_width;
+    v_width = self->private_data.s_do_decode_frame_vp8l.v_width;
   }
   switch (coro_susp_point) {
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
 
-    if (self->private_impl.f_call_sequence == 64u) {
-    } else if (self->private_impl.f_call_sequence < 64u) {
-      if (a_src) {
-        a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
-      }
-      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
-      status = wuffs_webp__decoder__do_decode_frame_config(self, NULL, a_src);
-      if (a_src) {
-        iop_a_src = a_src->data.ptr + a_src->meta.ri;
-      }
-      if (status.repr) {
-        goto suspend;
-      }
-    } else {
-      status = wuffs_base__make_status(wuffs_base__note__end_of_data);
-      goto ok;
-    }
     self->private_impl.f_seen_transform[0u] = false;
     self->private_impl.f_seen_transform[1u] = false;
     self->private_impl.f_seen_transform[2u] = false;
@@ -89952,7 +90435,7 @@
     while (true) {
       if (self->private_impl.f_n_bits < 1u) {
         {
-          WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
+          WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
           if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__make_status(wuffs_base__suspension__short_read);
             goto suspend;
@@ -89972,7 +90455,7 @@
       if (a_src) {
         a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
       }
-      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
       status = wuffs_webp__decoder__decode_transform(self, a_src, a_workbuf);
       if (a_src) {
         iop_a_src = a_src->data.ptr + a_src->meta.ri;
@@ -89988,7 +90471,7 @@
     if (a_src) {
       a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
     }
-    WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
     status = wuffs_webp__decoder__decode_color_cache_parameters(self, a_src);
     if (a_src) {
       iop_a_src = a_src->data.ptr + a_src->meta.ri;
@@ -90000,7 +90483,7 @@
     if (a_src) {
       a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
     }
-    WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5);
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
     status = wuffs_webp__decoder__decode_hg_table(self, a_src, v_width, a_workbuf);
     if (a_src) {
       iop_a_src = a_src->data.ptr + a_src->meta.ri;
@@ -90012,7 +90495,7 @@
     if (a_src) {
       a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
     }
-    WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6);
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5);
     status = wuffs_webp__decoder__decode_huffman_groups(self, a_src, self->private_impl.f_overall_n_huffman_groups);
     if (a_src) {
       iop_a_src = a_src->data.ptr + a_src->meta.ri;
@@ -90051,7 +90534,7 @@
         break;
       }
       status = v_status;
-      WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(7);
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(6);
     }
     if (((uint64_t)(self->private_impl.f_workbuf_offset_for_transform[0u])) > ((uint64_t)(a_workbuf.len))) {
       status = wuffs_base__make_status(wuffs_base__error__bad_workbuf_length);
@@ -90101,14 +90584,14 @@
     self->private_impl.f_call_sequence = 96u;
 
     ok:
-    self->private_impl.p_do_decode_frame = 0;
+    self->private_impl.p_do_decode_frame_vp8l = 0;
     goto exit;
   }
 
   goto suspend;
   suspend:
-  self->private_impl.p_do_decode_frame = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
-  self->private_data.s_do_decode_frame.v_width = v_width;
+  self->private_impl.p_do_decode_frame_vp8l = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
+  self->private_data.s_do_decode_frame_vp8l.v_width = v_width;
 
   goto exit;
   exit:
@@ -90775,7 +91258,7 @@
     return wuffs_base__utility__empty_rect_ie_u32();
   }
 
-  if (self->private_impl.f_is_vp8_lossy) {
+  if (self->private_impl.f_variant == 1u) {
     return wuffs_vp8__decoder__frame_dirty_rect(&self->private_data.f_vp8);
   }
   return wuffs_base__utility__make_rect_ie_u32(
@@ -90816,7 +91299,7 @@
     return 0;
   }
 
-  if (self->private_impl.f_is_vp8_lossy) {
+  if (self->private_impl.f_variant == 1u) {
     return wuffs_vp8__decoder__num_decoded_frame_configs(&self->private_data.f_vp8);
   }
   if (self->private_impl.f_call_sequence > 32u) {
@@ -90839,7 +91322,7 @@
     return 0;
   }
 
-  if (self->private_impl.f_is_vp8_lossy) {
+  if (self->private_impl.f_variant == 1u) {
     return wuffs_vp8__decoder__num_decoded_frames(&self->private_data.f_vp8);
   }
   if (self->private_impl.f_call_sequence > 64u) {
@@ -90868,7 +91351,7 @@
 
   wuffs_base__status v_status = wuffs_base__make_status(NULL);
 
-  if (self->private_impl.f_is_vp8_lossy) {
+  if (self->private_impl.f_variant == 1u) {
     v_status = wuffs_vp8__decoder__restart_frame(&self->private_data.f_vp8, a_index, a_io_position);
     return wuffs_private_impl__status__ensure_not_a_suspension(v_status);
   }
@@ -90950,10 +91433,18 @@
     return wuffs_base__utility__empty_range_ii_u64();
   }
 
-  if (self->private_impl.f_is_vp8_lossy) {
+  wuffs_base__range_ii_u64 v_r = {0};
+  wuffs_base__range_ii_u64 v_s = {0};
+
+  if (self->private_impl.f_variant == 1u) {
     return wuffs_vp8__decoder__workbuf_len(&self->private_data.f_vp8);
   }
-  return wuffs_base__utility__make_range_ii_u64(((uint64_t)(self->private_impl.f_workbuf_offset_for_transform[3u])), ((uint64_t)(self->private_impl.f_workbuf_offset_for_transform[3u])));
+  v_r = wuffs_base__utility__make_range_ii_u64(((uint64_t)(self->private_impl.f_workbuf_offset_for_transform[3u])), ((uint64_t)(self->private_impl.f_workbuf_offset_for_transform[3u])));
+  if (self->private_impl.f_variant != 2u) {
+    v_s = wuffs_vp8__decoder__workbuf_len(&self->private_data.f_vp8);
+    v_r = wuffs_base__utility__make_range_ii_u64(wuffs_base__u64__max(wuffs_private_impl__range_ii_u64__get_min_incl(&v_s), wuffs_private_impl__range_ii_u64__get_min_incl(&v_r)), wuffs_base__u64__max(wuffs_private_impl__range_ii_u64__get_max_incl(&v_s), wuffs_private_impl__range_ii_u64__get_max_incl(&v_r)));
+  }
+  return v_r;
 }
 
 #endif  // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__WEBP)
diff --git a/std/vp8/decode_quirks.wuffs b/std/vp8/decode_quirks.wuffs
new file mode 100644
index 0000000..ed13f79
--- /dev/null
+++ b/std/vp8/decode_quirks.wuffs
@@ -0,0 +1,38 @@
+// Copyright 2026 The Wuffs Authors.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+//
+// SPDX-License-Identifier: Apache-2.0 OR MIT
+
+// --------
+
+// Quirks are discussed in (/doc/note/quirks.md).
+//
+// The base38 encoding of "vp8." is 0x1B_5EFE. Left shifting by 10 gives
+// 0x6D7B_F800.
+pri const QUIRKS_BASE : base.u32 = 0x6D7B_F800
+
+// --------
+
+// Gives the image width and height out-of-band, in the u64 quirk value. The
+// low 32 bits are the width and the high 32 bits are the height. Both
+// dimensions should range within 0x0001 ..= 0x3FFF.
+//
+// A simple WebP file could contain one chunk, a VP8 chunk, that gives the
+// image width and height. A complex WebP file could contain three chunks:
+// VP8X, ALPH and VP8. The image dimensions are given in the VP8X chunk and are
+// then repeated in the VP8 chunk.
+//
+// After a webp.decoder decode_image_config call, the caller might next want to
+// query the workbuf length, to know how much memory to dynamically allocate,
+// especially before attempting to decode the ALPH chunk. The outer
+// webp.decoder workbuf_len method can delegate to the inner vp8.decoder
+// workbuf_len method, but the latter needs to know the image dimensions.
+//
+// This quirk mechanism lets the outer webp decoder tell the inner vp8 decoder
+// those dimensions, after parsing the VP8X chunk but before ALPH or VP8.
+pub const QUIRK_WIDTH_AND_HEIGHT : base.u32 = 0x6D7B_F800 | 0x00
diff --git a/std/vp8/decode_vp8.wuffs b/std/vp8/decode_vp8.wuffs
index 026010a..cdc0d1d 100644
--- a/std/vp8/decode_vp8.wuffs
+++ b/std/vp8/decode_vp8.wuffs
@@ -9,7 +9,9 @@
 // SPDX-License-Identifier: Apache-2.0 OR MIT
 
 pub status "#bad QUIRK_SOURCE_LENGTH"
+pub status "#bad QUIRK_WIDTH_AND_HEIGHT"
 pub status "#bad header"
+pub status "#inconsistent width and height"
 pub status "#missing QUIRK_SOURCE_LENGTH"
 pub status "#truncated input"
 pub status "#unsupported QUIRK_SOURCE_LENGTH"
@@ -37,6 +39,8 @@
         // ((value_as_u63 << 1) | has_value_as_u1).
         quirk_source_length : base.u64,
 
+        quirk_width_and_height : base.u64,
+
         // Just under 144 MiB, for 9 partitions each up to 0xFF_FFFF bytes.
         partitioned_data_length : base.u32[..= 0x08FF_FFF7],
 
@@ -205,15 +209,46 @@
 pub func decoder.get_quirk(key: base.u32) base.u64 {
     if args.key == base.QUIRK_SOURCE_LENGTH {
         return this.quirk_source_length
+    } else if args.key == QUIRK_WIDTH_AND_HEIGHT {
+        return this.quirk_width_and_height
     }
     return 0
 }
 
 pub func decoder.set_quirk!(key: base.u32, value: base.u64) base.status {
+    var w : base.u32
+    var h : base.u32
+
     if args.key == base.QUIRK_SOURCE_LENGTH {
-        this.quirk_source_length = args.value
+        if (args.value & 1) == 0 {
+            if args.value <> 0 {
+                return "#bad QUIRK_SOURCE_LENGTH"
+            }
+            this.quirk_source_length = 0
+            this.partitioned_data_length = 0
+        } else if (args.value >> 1) < 10 {
+            return "#bad QUIRK_SOURCE_LENGTH"
+        } else if (args.value >> 1) > (0x08FF_FFF7 + 10) {
+            return "#unsupported QUIRK_SOURCE_LENGTH"
+        } else {
+            this.quirk_source_length = args.value
+            this.partitioned_data_length = ((args.value >> 1) as base.u32) - 10
+        }
+        return ok
+
+    } else if args.key == QUIRK_WIDTH_AND_HEIGHT {
+        w = (args.value & 0xFFFF_FFFF) as base.u32
+        h = (args.value & 0xFFFF_FFFF) as base.u32
+        if (w < 1) or (0x3FFF < w) or (h < 1) or (0x3FFF < h) {
+            return "#bad QUIRK_WIDTH_AND_HEIGHT"
+        }
+        this.quirk_width_and_height = args.value
+        this.width = w
+        this.height = h
+        this.calculate_mbw_mbh!()
         return ok
     }
+
     return base."#unsupported option"
 }
 
@@ -236,14 +271,9 @@
         return base."#bad call sequence"
     }
 
-    if (this.quirk_source_length & 1) == 0 {
+    if this.quirk_source_length == 0 {
         return "#missing QUIRK_SOURCE_LENGTH"
-    } else if (this.quirk_source_length >> 1) < 10 {
-        return "#bad QUIRK_SOURCE_LENGTH"
-    } else if (this.quirk_source_length >> 1) > (0x08FF_FFF7 + 10) {
-        return "#unsupported QUIRK_SOURCE_LENGTH"
     }
-    this.partitioned_data_length = ((this.quirk_source_length >> 1) as base.u32) - 10
 
     // RFC 6386 Section 9. Frame Header, up to and including 9.1. Uncompressed
     // Data Chunk. decode_header_partition handles the rest of section 9.
@@ -267,17 +297,13 @@
     c32 = args.src.read_u32le?()
     this.width = 0x3FFF & (c32 >> 0)
     this.height = 0x3FFF & (c32 >> 16)
-    this.mbw = (this.width + 15) / 16
-    this.mbh = (this.height + 15) / 16
-
-    this.workbuf_yuv_y_stride = this.mbw * 16
-    this.workbuf_yuv_uv_stride = this.mbw * 8
-    this.workbuf_yuv_y_end =
-            ((this.workbuf_yuv_y_stride as base.u64) * ((this.mbh * 16) as base.u64))
-    this.workbuf_yuv_u_end = this.workbuf_yuv_y_end +
-            ((this.workbuf_yuv_uv_stride as base.u64) * ((this.mbh * 8) as base.u64))
-    this.workbuf_yuv_v_end = this.workbuf_yuv_u_end +
-            ((this.workbuf_yuv_uv_stride as base.u64) * ((this.mbh * 8) as base.u64))
+    if (this.width == 0) or (this.height == 0) {
+        return "#bad header"
+    } else if (this.quirk_width_and_height <> 0) and
+            (this.quirk_width_and_height <> ((this.width as base.u64) | ((this.height as base.u64) << 32))) {
+        return "#inconsistent width and height"
+    }
+    this.calculate_mbw_mbh!()
 
     this.frame_config_io_position = args.src.position()
 
@@ -294,6 +320,20 @@
     this.call_sequence = 0x20
 }
 
+pri func decoder.calculate_mbw_mbh!() {
+    this.mbw = (this.width + 15) / 16
+    this.mbh = (this.height + 15) / 16
+
+    this.workbuf_yuv_y_stride = this.mbw * 16
+    this.workbuf_yuv_uv_stride = this.mbw * 8
+    this.workbuf_yuv_y_end =
+            ((this.workbuf_yuv_y_stride as base.u64) * ((this.mbh * 16) as base.u64))
+    this.workbuf_yuv_u_end = this.workbuf_yuv_y_end +
+            ((this.workbuf_yuv_uv_stride as base.u64) * ((this.mbh * 8) as base.u64))
+    this.workbuf_yuv_v_end = this.workbuf_yuv_u_end +
+            ((this.workbuf_yuv_uv_stride as base.u64) * ((this.mbh * 8) as base.u64))
+}
+
 pub func decoder.decode_frame_config?(dst: nptr base.frame_config, src: base.io_reader) {
     var status : base.status
 
diff --git a/std/webp/decode_webp.wuffs b/std/webp/decode_webp.wuffs
index 781b773..187bad3 100644
--- a/std/webp/decode_webp.wuffs
+++ b/std/webp/decode_webp.wuffs
@@ -10,17 +10,21 @@
 
 use "std/vp8"
 
+pub status "#bad ALPH+VP8L combination"
 pub status "#bad Huffman code (over-subscribed)"
 pub status "#bad Huffman code (under-subscribed)"
 pub status "#bad Huffman code"
+pub status "#bad VP8X chunk"
 pub status "#bad back-reference"
+pub status "#bad chunk order"
 pub status "#bad color cache"
 pub status "#bad header"
 pub status "#bad transform"
 pub status "#short chunk"
 pub status "#truncated input"
-pub status "#unsupported number of Huffman groups"
+pub status "#unsupported VP8X/VP8 chunk length"
 pub status "#unsupported WebP file"
+pub status "#unsupported number of Huffman groups"
 
 pri status "#internal error: inconsistent Huffman code"
 pri status "#internal error: inconsistent dst buffer"
@@ -30,6 +34,63 @@
 // worst case for VP8L is bigger than for VP8.
 pub const DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE : base.u64 = 0x4C00_0000
 
+// The default value of the largest supported VP8-inside-VP8X chunk length.
+// This limitation is a consequence of combining Wuffs' "decode an image" API
+// with WebP/VP8X/VP8's file format design.
+//
+// Wuffs' API only assumes sequential I/O access, not random I/O access, to the
+// encoded image's bytes. It also is incapable (in the security capability
+// sense) of dynamically allocating memory, so any O(width × height) pixel
+// buffers, or any other buffers that aren't O(1) in terms of width and height,
+// must be allocated by the caller, not the callee (Wuffs code).
+//
+// "How much dynamic memory does the caller need to allocate?" is answered by
+// the decoder.workbuf_len method, which is valid to call as soon as
+// decoder.decode_image_config returns (and decode_image_config, unlike
+// decode_frame, does not take a variable-length slice-of-bytes workbuf
+// argument). workbuf_len must return a worst case estimate, as it has only one
+// shot at answering "how much dynamic memory?"
+//
+// On the other hand, an VP8 image file can have multiple "partitions", which
+// means that a VP8 decoder must support random I/O access. Wuffs supports this
+// by, in the worst case, copying the VP8's chunk payload (the encoded image
+// data) into the workbuf. (Copying into a contiguous buffer is the same as
+// what the libwebp C library's WebPIAppend "incremental API" does.) But doing
+// so requires knowing how large the VP8 chunk is, so that the caller can
+// allocate a sufficiently large workbuf.
+//
+// We cannot answer that question precisely until we parse the VP8 chunk, but
+// in the WebP/VP8X file format, the VP8 chunk must occur after any ALPH chunk.
+// For Wuffs to parse an ALPH chunk, it must have already "allocated" enough
+// memory, more than O(1), by answering "how much dynamic memory?"
+//
+// As we cannot answer that question precisely (whilst still satisfying Wuffs'
+// "decode an image" API, which works across numerous image file formats, not
+// just WebP), we use a made-up upper bound, which is this number.
+//
+// Its value is arbitrary, but there is a trade-off. The larger it is, the more
+// workbuf memory Wuffs will say it needs (and callers will allocate), even if
+// it later discovers that it over-budgeted. The smaller it is, Wuffs will
+// reject (as unsupported) more large-but-valid WebP/VP8X/VP8 files.
+//
+// This default value is 8 MiB.
+pub const VP8X_MAX_INCL_VP8_CHUNK_LENGTH_DEFAULT : base.u64 = 8_388608
+
+// TODO: in the future, regarding VP8X_MAX_INCL_VP8_CHUNK_LENGTH_DEFAULT, we
+// could make this set by a quirk instead of hard-coded.
+//
+// Or, we could change the semantics of the wuffs_base__image_decoder API
+// (which applies to multiple image formats, not just WebP). The API is that
+// you can call workbuf_len as soon as decode_image_config returns. workbuf_len
+// returns a range and any value within that range should be a valid length for
+// the "dynamically allocated, not O(1) in terms of width and height" workbuf
+// memory passed to decode_frame.
+//
+// We could relax this so that decode_frame could return a "$short workbuf"
+// suspension, if the workbuf length is unknown at the time of the first
+// decode_frame call, and the caller is expected to essentially realloc the
+// workbuf, keeping any existing contents when growing it.
+
 pub struct decoder? implements base.image_decoder(
         width  : base.u32[..= 0x4000],
         height : base.u32[..= 0x4000],
@@ -42,7 +103,35 @@
 
         sub_chunk_has_padding : base.bool,
 
-        is_vp8_lossy : base.bool,
+        // 0x01 = VP8  lossy
+        // 0x02 = VP8L lossless
+        // 0x04 = VP8X and seen VP8X  chunks
+        // 0x08 = VP8X and seen VP8X, ICCP   chunks
+        // 0x10 = VP8X and seen VP8X, ICCP†, ANIM   chunks
+        // 0x20 = VP8X and seen VP8X, ICCP†, ANIM†, ANMF   chunks
+        // 0x40 = VP8X and seen VP8X, ICCP†, ANIM†, ANMF†, ALPH   chunks
+        // 0x81 = VP8X and seen VP8X, ICCP†, ANIM†, ANMF†, ALPH†, VP8  chunks
+        // 0x82 = VP8X and seen VP8X, ICCP†, ANIM†, ANMF†, ALPH†, VP8L chunks
+        //
+        // Chunks marked with a † are optional.
+        variant : base.u8,
+
+        // The "Rsv|I|L|E|X|A|R" bits from
+        // https://developers.google.com/speed/webp/docs/riff_container#extended_file_format
+        //
+        // We also re-purpose the otherwise-unused 0x01 bit to mean VP8X-ness,
+        // so that (vp8x_header_flags <> 0) is an easy check for VP8X versus
+        // not-VP8X. Similarly, ((vp8x_header_flags & 0x11) == 0x01) is an easy
+        // check for "VP8X without an ALPH chunk".
+        //
+        // See also libwebp's src/webp/mux_types.h:
+        //
+        // ANIMATION_FLAG = 0x00000002,
+        // XMP_FLAG       = 0x00000004,
+        // EXIF_FLAG      = 0x00000008,
+        // ALPHA_FLAG     = 0x00000010,
+        // ICCP_FLAG      = 0x00000020,
+        vp8x_header_flags : base.u8,
 
         frame_config_io_position : base.u64,
 
@@ -199,7 +288,7 @@
 
     this.frame_config_io_position = args.src.position()
 
-    if (not this.is_vp8_lossy) and (args.dst <> nullptr) {
+    if (this.variant <> 0x01) and (args.dst <> nullptr) {
         args.dst.set!(
                 pixfmt: base.PIXEL_FORMAT__BGRA_NONPREMUL,
                 pixsub: 0,
@@ -224,11 +313,11 @@
 
     c32 = args.src.read_u32le?()
     if c32 == 'VP8 'le {
-        this.is_vp8_lossy = true
+        this.variant = 0x01
     } else if c32 == 'VP8L'le {
-        // No-op.
+        this.variant = 0x02
     } else if c32 == 'VP8X'le {
-        return "#unsupported WebP file"
+        this.variant = 0x04
     } else {
         return "#bad header"
     }
@@ -239,20 +328,29 @@
     }
     this.sub_chunk_has_padding = (this.sub_chunk_length & 1) <> 0
 
-    if this.is_vp8_lossy {
+    if this.variant == 0x01 {
         this.vp8.set_quirk!(
                 key: base.QUIRK_SOURCE_LENGTH,
                 value: ((this.sub_chunk_length as base.u64) << 1) | 1)
-
+    } else if this.variant == 0x02 {
+        // No-op.
+    } else if this.sub_chunk_length <> 10 {
+        return "#bad header"
+    } else {
+        this.vp8.set_quirk!(
+                key: base.QUIRK_SOURCE_LENGTH,
+                value: (VP8X_MAX_INCL_VP8_CHUNK_LENGTH_DEFAULT << 1) | 1)
     }
 
     while true {
         io_limit (io: args.src, limit: this.sub_chunk_length as base.u64) {
             r_mark = args.src.mark()
-            if this.is_vp8_lossy {
+            if this.variant == 0x01 {
                 status =? this.vp8.decode_image_config?(dst: args.dst, src: args.src)
-            } else {
+            } else if this.variant == 0x02 {
                 status =? this.do_decode_image_config_limited_vp8l?(src: args.src)
+            } else {
+                status =? this.do_decode_image_config_limited_vp8x?(src: args.src)
             }
             this.sub_chunk_length ~sat-=
                     (args.src.count_since(mark: r_mark) & 0xFFFF_FFFF) as base.u32
@@ -298,11 +396,65 @@
     this.workbuf_offset_for_transform[3] = (4 * this.width * this.height) + (3 * transform_size)
 }
 
+pri func decoder.do_decode_image_config_limited_vp8x?(src: base.io_reader) {
+    var c32            : base.u32
+    var transform_size : base.u32[..= 0x400_0000]
+    var status         : base.status
+
+    c32 = args.src.read_u32le?()
+    if (c32 & 0xFFFF_FFC1) <> 0 {
+        return "#unsupported WebP file"
+    }
+    this.vp8x_header_flags = ((c32 | 0x01) & 0xFF) as base.u8
+
+    c32 = args.src.read_u24le_as_u32?()
+    if c32 >= 0x3FFF {
+        return "#unsupported WebP file"
+    }
+    this.width = (c32 & 0x3FFF) + 1
+
+    c32 = args.src.read_u24le_as_u32?()
+    if c32 >= 0x3FFF {
+        return "#unsupported WebP file"
+    }
+    this.height = (c32 & 0x3FFF) + 1
+
+    // It's valid to call this.workbuf_len after this.decode_image_config
+    // returns, for the caller to know how much dynamically-sized memory
+    // (called workbuf memory) is needed to decode the image file.
+    //
+    // Unfortunately, having only seen the VP8X chunk, we don't know yet
+    // whether it's ultimately a VP8 or VP8L image, but we wouldn't know until
+    // after we've processed the ALPH chunk (if present), and processing the
+    // ALPH chunk requires workbuf memory (as the amount of memory depends on
+    // this.width and this.height).
+    //
+    // Wuffs' "decode an image" API assumes that the caller, not callee, is
+    // responsible for dynamically allocating memory. But also, it only assumes
+    // sequential access (not random access) to the io_reader.
+    //
+    // So, here, we have to cater for the worst case and set aside workbuf
+    // memory just like plain VP8L, even if it might end up being unused, for
+    // example: VP8X+VP8 instead of VP8X+VP8L or VP8X+ALPH+VP8.
+    transform_size = 4 * ((this.width + 3) >> 2) * ((this.height + 3) >> 2)
+    this.workbuf_offset_for_transform[0] = (4 * this.width * this.height) + (0 * transform_size)
+    this.workbuf_offset_for_transform[1] = (4 * this.width * this.height) + (1 * transform_size)
+    this.workbuf_offset_for_transform[2] = (4 * this.width * this.height) + (2 * transform_size)
+    this.workbuf_offset_for_transform[3] = (4 * this.width * this.height) + (3 * transform_size)
+
+    status = this.vp8.set_quirk!(
+            key: vp8.QUIRK_WIDTH_AND_HEIGHT,
+            value: ((this.width as base.u64) | ((this.height as base.u64) << 32)))
+    if not status.is_ok() {
+        return "#unsupported WebP file"
+    }
+}
+
 pub func decoder.decode_frame_config?(dst: nptr base.frame_config, src: base.io_reader) {
     var status : base.status
 
     while true {
-        if this.is_vp8_lossy {
+        if this.variant == 0x01 {
             status =? this.vp8.decode_frame_config?(dst: args.dst, src: args.src)
         } else {
             status =? this.do_decode_frame_config?(dst: args.dst, src: args.src)
@@ -330,7 +482,7 @@
         return base."@end of data"
     }
 
-    if args.dst <> nullptr {
+    if (this.variant <> 0x01) and (args.dst <> nullptr) {
         args.dst.set!(bounds: this.util.make_rect_ie_u32(
                 min_incl_x: 0,
                 min_incl_y: 0,
@@ -352,11 +504,7 @@
     var status : base.status
 
     while true {
-        if this.is_vp8_lossy {
-            status =? this.vp8.decode_frame?(dst: args.dst, src: args.src, blend: args.blend, workbuf: args.workbuf, opts: args.opts)
-        } else {
-            status =? this.do_decode_frame?(dst: args.dst, src: args.src, blend: args.blend, workbuf: args.workbuf, opts: args.opts)
-        }
+        status =? this.do_decode_frame?(dst: args.dst, src: args.src, blend: args.blend, workbuf: args.workbuf, opts: args.opts)
         if (status == base."$short read") and args.src.is_closed() {
             return "#truncated input"
         }
@@ -365,6 +513,97 @@
 }
 
 pri func decoder.do_decode_frame?(dst: ptr base.pixel_buffer, src: base.io_reader, blend: base.pixel_blend, workbuf: slice base.u8, opts: nptr base.decode_frame_options) {
+    var chunk_type   : base.u32
+    var chunk_length : base.u32
+
+    if this.call_sequence == 0x40 {
+        // No-op.
+    } else if this.call_sequence < 0x40 {
+        this.do_decode_frame_config?(dst: nullptr, src: args.src)
+    } else {
+        return base."@end of data"
+    }
+
+    while (this.variant & 0x03) == 0 {
+        chunk_type = args.src.read_u32le?()
+        chunk_length = args.src.read_u32le?()
+
+        if chunk_type == 'ICCP'le {
+            if this.variant > 0x04 {
+                return "#bad chunk order"
+            } else if (this.vp8x_header_flags & 0x20) == 0 {
+                return "#bad VP8X chunk"
+            }
+            this.variant = 0x08
+
+        } else if chunk_type == 'ANIM'le {
+            if this.variant > 0x08 {
+                return "#bad chunk order"
+            } else if (this.vp8x_header_flags & 0x02) == 0 {
+                return "#bad VP8X chunk"
+            }
+            this.variant = 0x10
+
+        } else if chunk_type == 'ANMF'le {
+            if this.variant > 0x10 {
+                return "#bad chunk order"
+            } else if (this.vp8x_header_flags & 0x02) == 0 {
+                return "#bad VP8X chunk"
+            }
+            this.variant = 0x20
+
+        } else if chunk_type == 'ALPH'le {
+            if this.variant > 0x20 {
+                return "#bad chunk order"
+            } else if (this.vp8x_header_flags & 0x10) == 0 {
+                return "#bad VP8X chunk"
+            }
+            this.variant = 0x40
+
+        } else if chunk_type == 'VP8 'le {
+            if (chunk_length as base.u64) > VP8X_MAX_INCL_VP8_CHUNK_LENGTH_DEFAULT {
+                return "#unsupported VP8X/VP8 chunk length"
+            }
+            this.vp8.set_quirk!(
+                    key: base.QUIRK_SOURCE_LENGTH,
+                    value: ((chunk_length as base.u64) << 1) | 1)
+            this.variant = 0x81
+            break
+
+        } else if chunk_type == 'VP8L'le {
+            if (this.vp8x_header_flags & 0x10) <> 0 {
+                return "#bad ALPH+VP8L combination"
+            }
+            this.variant = 0x82
+            break
+
+        } else if chunk_type == 'XMP 'le {
+            if (this.vp8x_header_flags & 0x04) == 0 {
+                return "#bad VP8X chunk"
+            }
+
+        } else if chunk_type == 'EXIF'le {
+            if (this.vp8x_header_flags & 0x08) == 0 {
+                return "#bad VP8X chunk"
+            }
+        }
+
+        if chunk_length > 0x7FFF_FFFF {
+            return "#unsupported WebP file"
+        } else if (chunk_length & 1) <> 0 {
+            chunk_length += 1
+        }
+        args.src.skip_u32?(n: chunk_length)
+    }
+
+    if (this.variant & 0x01) <> 0 {
+        this.vp8.decode_frame?(dst: args.dst, src: args.src, blend: args.blend, workbuf: args.workbuf, opts: args.opts)
+    } else {
+        this.do_decode_frame_vp8l?(dst: args.dst, src: args.src, blend: args.blend, workbuf: args.workbuf, opts: args.opts)
+    }
+}
+
+pri func decoder.do_decode_frame_vp8l?(dst: ptr base.pixel_buffer, src: base.io_reader, blend: base.pixel_blend, workbuf: slice base.u8, opts: nptr base.decode_frame_options) {
     var c8             : base.u8
     var has_more       : base.u32[..= 1]
     var width          : base.u32[..= 0x4000]
@@ -377,14 +616,6 @@
     var ti             : base.u64
     var tj             : base.u64
 
-    if this.call_sequence == 0x40 {
-        // No-op.
-    } else if this.call_sequence < 0x40 {
-        this.do_decode_frame_config?(dst: nullptr, src: args.src)
-    } else {
-        return base."@end of data"
-    }
-
     this.seen_transform[0] = false
     this.seen_transform[1] = false
     this.seen_transform[2] = false
@@ -844,7 +1075,7 @@
 }
 
 pub func decoder.frame_dirty_rect() base.rect_ie_u32 {
-    if this.is_vp8_lossy {
+    if this.variant == 0x01 {
         return this.vp8.frame_dirty_rect()
     }
     return this.util.make_rect_ie_u32(
@@ -859,7 +1090,7 @@
 }
 
 pub func decoder.num_decoded_frame_configs() base.u64 {
-    if this.is_vp8_lossy {
+    if this.variant == 0x01 {
         return this.vp8.num_decoded_frame_configs()
     }
     if this.call_sequence > 0x20 {
@@ -869,7 +1100,7 @@
 }
 
 pub func decoder.num_decoded_frames() base.u64 {
-    if this.is_vp8_lossy {
+    if this.variant == 0x01 {
         return this.vp8.num_decoded_frames()
     }
     if this.call_sequence > 0x40 {
@@ -881,7 +1112,7 @@
 pub func decoder.restart_frame!(index: base.u64, io_position: base.u64) base.status {
     var status : base.status
 
-    if this.is_vp8_lossy {
+    if this.variant == 0x01 {
         status = this.vp8.restart_frame!(index: args.index, io_position: args.io_position)
         return status
     }
@@ -904,12 +1135,25 @@
 }
 
 pub func decoder.workbuf_len() base.range_ii_u64 {
-    if this.is_vp8_lossy {
+    var r : base.range_ii_u64
+    var s : base.range_ii_u64
+
+    if this.variant == 0x01 {
         return this.vp8.workbuf_len()
     }
-    return this.util.make_range_ii_u64(
+
+    r = this.util.make_range_ii_u64(
             min_incl: this.workbuf_offset_for_transform[3] as base.u64,
             max_incl: this.workbuf_offset_for_transform[3] as base.u64)
+
+    if this.variant <> 0x02 {
+        s = this.vp8.workbuf_len()
+        r = this.util.make_range_ii_u64(
+                min_incl: s.get_min_incl().max(no_less_than: r.get_min_incl()),
+                max_incl: s.get_max_incl().max(no_less_than: r.get_max_incl()))
+    }
+
+    return r
 }
 
 pri const DISTANCE_MAP : roarray[120] base.u8 = [
diff --git a/test/3pdata/nia-checksums-of-blinksuite.txt b/test/3pdata/nia-checksums-of-blinksuite.txt
index eb72b89..a69a1c5 100644
--- a/test/3pdata/nia-checksums-of-blinksuite.txt
+++ b/test/3pdata/nia-checksums-of-blinksuite.txt
@@ -30,6 +30,7 @@
 OK. 267741e0 test/3pdata/blinksuite/blue-100.png
 OK. 89449c01 test/3pdata/blinksuite/blue-wheel-srgb-color-profile.jpg
 OK. 8fce70a9 test/3pdata/blinksuite/blue-wheel-srgb-color-profile.png
+OK. 426a5623 test/3pdata/blinksuite/blue-wheel-srgb-color-profile.webp
 OK. 2d7bad9f test/3pdata/blinksuite/border-image-srgb-color-profile.png
 OK. 9ca73f21 test/3pdata/blinksuite/border-image.png
 OK. 277600f9 test/3pdata/blinksuite/boston.gif
@@ -42,6 +43,8 @@
 OK. b3389858 test/3pdata/blinksuite/color-checker-srgb-color-profile.png
 OK. 10721531 test/3pdata/blinksuite/count-down-color-test.gif
 OK. 1abc4dc6 test/3pdata/blinksuite/count-down-color-test.png
+BAD d663b0e6 test/3pdata/blinksuite/count-down-color-test.webp
+OK. 83ea9bce test/3pdata/blinksuite/crbug.364830.webp
 OK. 24ab1efd test/3pdata/blinksuite/crbug779261.gif
 OK. ae034abe test/3pdata/blinksuite/crbug807324.png
 OK. 4f50fdb0 test/3pdata/blinksuite/crbug827754.png
@@ -66,6 +69,7 @@
 OK. a2a940bc test/3pdata/blinksuite/cs-uma-ycck.jpg
 OK. b6000be6 test/3pdata/blinksuite/dice.png
 BAD 1023c20a test/3pdata/blinksuite/empty-frame.png
+OK. 4a495d9f test/3pdata/blinksuite/flip.webp
 OK. eff36075 test/3pdata/blinksuite/flowchart.jpg
 OK. 20ff1df8 test/3pdata/blinksuite/full2loop.gif
 OK. dc814e29 test/3pdata/blinksuite/gif-loop-count.gif
@@ -89,6 +93,11 @@
 OK. 93f21c2a test/3pdata/blinksuite/icc-v2-gbr-420-width-not-whole-mcu.jpg
 OK. f37cea44 test/3pdata/blinksuite/icc-v2-gbr-422-whole-mcus.jpg
 OK. 1de5a906 test/3pdata/blinksuite/icc-v2-gbr.jpg
+BAD 7c80a001 test/3pdata/blinksuite/invalid-animated-webp.webp
+BAD 1e37d000 test/3pdata/blinksuite/invalid-animated-webp2.webp
+BAD 97b2b706 test/3pdata/blinksuite/invalid-animated-webp3.webp
+BAD 145962c5 test/3pdata/blinksuite/invalid-animated-webp4.webp
+BAD 67a2ece0 test/3pdata/blinksuite/invalid_vp8_vp8x.webp
 OK. 0da5f37e test/3pdata/blinksuite/jpeg-height-exif-orientation.jpg
 OK. 4409dca6 test/3pdata/blinksuite/large-gif-checkerboard.gif
 OK. d67648b6 test/3pdata/blinksuite/large.webp
@@ -171,9 +180,24 @@
 OK. 6b85cdb9 test/3pdata/blinksuite/stripes-small.png
 OK. 16c2c0fe test/3pdata/blinksuite/test-load.jpg
 OK. 6fbda804 test/3pdata/blinksuite/test.webp
+OK. b7690880 test/3pdata/blinksuite/test2.webp
 OK. 19d3a207 test/3pdata/blinksuite/test3.webp
+OK. 17bda738 test/3pdata/blinksuite/truncated.webp
 BAD 1dfa35c6 test/3pdata/blinksuite/truncated2.webp
+BAD b16eb1a2 test/3pdata/blinksuite/webp-animated-icc-xmp.webp
+BAD 45030792 test/3pdata/blinksuite/webp-animated-large.webp
+BAD 4519c83d test/3pdata/blinksuite/webp-animated-no-blend.webp
+BAD 4519c83d test/3pdata/blinksuite/webp-animated-opaque.webp
+BAD d26b2bd9 test/3pdata/blinksuite/webp-animated-semitransparent1.webp
+BAD 73e0b22f test/3pdata/blinksuite/webp-animated-semitransparent2.webp
+BAD d26b2bd9 test/3pdata/blinksuite/webp-animated-semitransparent3.webp
+BAD 0dba213a test/3pdata/blinksuite/webp-animated-semitransparent4.webp
+BAD ae7f1fcf test/3pdata/blinksuite/webp-animated.webp
 OK. eb191e45 test/3pdata/blinksuite/webp-color-no-profile-lossy.webp
+OK. 60116c2c test/3pdata/blinksuite/webp-color-profile-crash.webp
+BAD f234aa8a test/3pdata/blinksuite/webp-color-profile-lossless.webp
+OK. b861d3c2 test/3pdata/blinksuite/webp-color-profile-lossy-alpha.webp
+OK. 684e3635 test/3pdata/blinksuite/webp-color-profile-lossy.webp
 BAD ed67a9a5 test/3pdata/blinksuite/wrong-block-length.gif
 OK. 577c5c4c test/3pdata/blinksuite/ycbcr-420-fast-int-progressive.jpg
 OK. bf4cd8e2 test/3pdata/blinksuite/ycbcr-420-float-progressive.jpg
diff --git a/test/3pdata/nia-checksums-of-webpsuite.txt b/test/3pdata/nia-checksums-of-webpsuite.txt
index b55a527..5257a89 100644
--- a/test/3pdata/nia-checksums-of-webpsuite.txt
+++ b/test/3pdata/nia-checksums-of-webpsuite.txt
@@ -1,5 +1,19 @@
 # Generated by script/print-nia-checksums.sh
+OK. 651f0caa test/3pdata/webpsuite/alpha_color_cache.webp
+OK. 935f9fe1 test/3pdata/webpsuite/alpha_filter_0_method_0.webp
+OK. 935f9fe1 test/3pdata/webpsuite/alpha_filter_0_method_1.webp
+OK. 28779e98 test/3pdata/webpsuite/alpha_filter_1.webp
+OK. 935f9fe1 test/3pdata/webpsuite/alpha_filter_1_method_0.webp
+OK. 935f9fe1 test/3pdata/webpsuite/alpha_filter_1_method_1.webp
+OK. 28779e98 test/3pdata/webpsuite/alpha_filter_2.webp
+OK. 935f9fe1 test/3pdata/webpsuite/alpha_filter_2_method_0.webp
+OK. 935f9fe1 test/3pdata/webpsuite/alpha_filter_2_method_1.webp
+OK. 28779e98 test/3pdata/webpsuite/alpha_filter_3.webp
+OK. 935f9fe1 test/3pdata/webpsuite/alpha_filter_3_method_0.webp
+OK. 935f9fe1 test/3pdata/webpsuite/alpha_filter_3_method_1.webp
+OK. 28779e98 test/3pdata/webpsuite/alpha_no_compression.webp
 OK. 0ecfd841 test/3pdata/webpsuite/bad_palette_index.webp
+OK. 4cb35952 test/3pdata/webpsuite/big_endian_bug_393.webp
 OK. 3dd065ab test/3pdata/webpsuite/bryce.webp
 OK. 7794c8b2 test/3pdata/webpsuite/bug3.webp
 OK. 6fbda804 test/3pdata/webpsuite/color_cache_bits_11.webp
@@ -49,6 +63,10 @@
 OK. 6706e719 test/3pdata/webpsuite/lossless_vec_2_7.webp
 OK. 6706e719 test/3pdata/webpsuite/lossless_vec_2_8.webp
 OK. 6706e719 test/3pdata/webpsuite/lossless_vec_2_9.webp
+OK. f553cdb9 test/3pdata/webpsuite/lossy_alpha1.webp
+OK. 2707a535 test/3pdata/webpsuite/lossy_alpha2.webp
+OK. be4ce0da test/3pdata/webpsuite/lossy_alpha3.webp
+OK. 5dce2606 test/3pdata/webpsuite/lossy_alpha4.webp
 OK. ac62b1e6 test/3pdata/webpsuite/lossy_extreme_probabilities.webp
 OK. 84ff1fbf test/3pdata/webpsuite/lossy_q0_f100.webp
 OK. 6957defa test/3pdata/webpsuite/near_lossless_75.webp
diff --git a/test/c/std/webp.c b/test/c/std/webp.c
index be4f25f..6889c23 100644
--- a/test/c/std/webp.c
+++ b/test/c/std/webp.c
@@ -115,6 +115,20 @@
 }
 
 const char*  //
+test_wuffs_webp_decode_interface_vp8x_alpha_lossy() {
+  CHECK_FOCUS(__func__);
+  wuffs_webp__decoder* dec = &g_webp_decoder;
+  CHECK_STATUS("initialize",
+               wuffs_webp__decoder__initialize(
+                   dec, sizeof *dec, WUFFS_VERSION,
+                   WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED));
+  return do_test__wuffs_base__image_decoder(
+      wuffs_webp__decoder__upcast_as__wuffs_base__image_decoder(dec),
+      "test/data/hippopotamus.masked-with-muybridge.lossy.webp", 0, SIZE_MAX,
+      36, 28, 0xFFF5F5F5);  // TODO: apply the alpha channel.
+}
+
+const char*  //
 test_wuffs_webp_decode_many_small_reads() {
   CHECK_FOCUS(__func__);
   wuffs_webp__decoder* dec = &g_webp_decoder;
@@ -520,6 +534,7 @@
 
     test_wuffs_webp_decode_interface_lossless,
     test_wuffs_webp_decode_interface_lossy,
+    test_wuffs_webp_decode_interface_vp8x_alpha_lossy,
     test_wuffs_webp_decode_many_small_reads,
 
 #ifdef WUFFS_MIMIC
diff --git a/test/nia-checksums-of-data.txt b/test/nia-checksums-of-data.txt
index 6ddb13d..8da570e 100644
--- a/test/nia-checksums-of-data.txt
+++ b/test/nia-checksums-of-data.txt
@@ -134,6 +134,7 @@
 OK. 2535637e test/data/hippopotamus.masked-with-muybridge.etc2.bgra-binary.pkm
 OK. 720180ee test/data/hippopotamus.masked-with-muybridge.etc2.bgra-nonpremul.pkm
 OK. d3bbed27 test/data/hippopotamus.masked-with-muybridge.gif
+OK. c41410be test/data/hippopotamus.masked-with-muybridge.lossy.webp
 OK. 7e6acf01 test/data/hippopotamus.masked-with-muybridge.png
 OK. dcbb225a test/data/hippopotamus.nie
 OK. 57d9a4dd test/data/hippopotamus.pgm