Implement VideoDecoder.isConfigSupported()

API and motivations described here:
https://github.com/WICG/web-codecs/issues/98

Spec PR here:
https://github.com/WICG/web-codecs/pull/120

Bug: 1141707
Test: new layout tests
Change-Id: I3e53da5961119b4b36ec76666ad1a67cca19b963
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2612684
Commit-Queue: Chrome Cunningham <chcunningham@chromium.org>
Reviewed-by: Thomas Guilbert <tguilbert@chromium.org>
Cr-Commit-Position: refs/heads/master@{#841376}
diff --git a/webcodecs/video-decoder.any.js b/webcodecs/video-decoder.any.js
index 2dd85ad..33ea2db 100644
--- a/webcodecs/video-decoder.any.js
+++ b/webcodecs/video-decoder.any.js
@@ -4,7 +4,7 @@
 // TODO(sandersd): Move metadata into a helper library.
 // TODO(sandersd): Add H.264 decode test once there is an API to query for
 // supported codecs.
-let h264 = {
+const h264 = {
   async buffer() { return (await fetch('h264.mp4')).arrayBuffer(); },
   codec: "avc1.64000c",
   description: {offset: 7229, size: 46},
@@ -20,7 +20,7 @@
            {offset: 6429, size: 281}]
 };
 
-let vp9 = {
+const vp9 = {
   async buffer() { return (await fetch('vp9.mp4')).arrayBuffer(); },
   // TODO(sandersd): Verify that the file is actually level 1.
   codec: "vp09.00.10.08",
@@ -36,6 +36,88 @@
            {offset: 5193, size: 159}]
 };
 
+const badCodecsList = [
+    '',                         // Empty codec
+    'bogus',                    // Non exsitent codec
+    'vorbis',                   // Audio codec
+    'vp9',                      // Ambiguous codec
+    'video/webm; codecs="vp9"'  // Codec with mime type
+  ]
+
+const invalidConfigs = [
+  {
+    comment: 'Emtpy codec',
+    config: {codec: ''},
+  },
+  {
+    comment: 'Unrecognized codec',
+    config: {codec: 'bogus'},
+  },
+  {
+    comment: 'Audio codec',
+    config: {codec: 'vorbis'},
+  },
+  {
+    comment: 'Ambiguous codec',
+    config: {codec: 'vp9'},
+  },
+  {
+    comment: 'Codec with MIME type',
+    config: {codec: 'video/webm; codecs="vp8"'},
+  },
+  {
+    comment: 'Zero coded size',
+    config: {
+      codec: h264.codec,
+      codedWidth: 0,
+      codedHeight: 0,
+    },
+  },
+  {
+    comment: 'Out of bounds crop size caused by left/top offset',
+    config: {
+      codec: h264.codec,
+      codedWidth: 1920,
+      codedHeight: 1088,
+      cropLeft: 10,
+      cropTop: 10,
+      // When unspecified, these default to coded dimensions
+      // cropWidth: 1920,
+      // cropHeight: 1088
+    },
+  },
+  {
+    comment: 'Out of bounds crop size',
+    config: {
+      codec: h264.codec,
+      codedWidth: 1920,
+      codedHeight: 1088,
+      cropLeft: 10,
+      cropTop: 10,
+      cropWidth: 1920,
+      cropHeight: 1088,
+    },
+  },
+  {
+    comment: 'Way out of bounds crop size',
+    config: {
+      codec: h264.codec,
+      codedWidth: 1920,
+      codedHeight: 1088,
+      cropWidth: 4000,
+      cropHeight: 5000,
+    },
+  },
+  {
+    comment: 'Invalid display size',
+    config: {
+      codec: h264.codec,
+      displayWidth: 0,
+      displayHeight: 0,
+    },
+  },
+] //  invalidConfigs
+
 function view(buffer, {offset, size}) {
   return new Uint8Array(buffer, offset, size);
 }
@@ -48,6 +130,40 @@
   });
 }
 
+invalidConfigs.forEach(entry => {
+  promise_test(t => {
+    return promise_rejects_js(t, TypeError, VideoDecoder.isConfigSupported(entry.config));
+  }, 'Test that VideoDecoder.isConfigSupported() rejects invalid config:' + entry.comment);
+});
+
+invalidConfigs.forEach(entry => {
+  async_test(t => {
+    let codec = new VideoDecoder(getDefaultCodecInit(t));
+    assert_throws_js(TypeError, () => { codec.configure(entry.config); });
+    t.done();
+  }, 'Test that VideoDecoder.configure() rejects invalid config:' + entry.comment);
+});
+
+promise_test(t => {
+  return VideoDecoder.isConfigSupported({codec: vp9.codec});
+}, 'Test VideoDecoder.isConfigSupported() with minimal valid config');
+
+promise_test(t => {
+  // This config specifies a slight crop. H264 1080p content always crops
+  // because H264 coded dimensions are a multiple of 16 (e.g. 1088).
+  return VideoDecoder.isConfigSupported({
+    codec: h264.codec,
+    codedWidth: 1920,
+    codedHeight: 1088,
+    cropLeft: 0,
+    cropTop: 0,
+    cropWidth: 1920,
+    cropHeight: 1080,
+    displayWidth: 1920,
+    displayHeight: 1080
+  });
+}, 'Test VideoDecoder.isConfigSupported() with valid expanded config');
+
 promise_test(t => {
   // VideoDecoderInit lacks required fields.
   assert_throws_js(TypeError, () => { new VideoDecoder({}); });
@@ -65,18 +181,12 @@
 promise_test(t => {
   let decoder = new VideoDecoder(getDefaultCodecInit(t));
 
-  let badCodecsList = [
-    '',                         // Empty codec
-    'bogus',                    // Non exsitent codec
-    'vorbis',                   // Audio codec
-    'vp9',                      // Ambiguous codec
-    'video/webm; codecs="vp9"'  // Codec with mime type
-  ]
-
+  // TODO(chcunningham): Remove badCodecsList testing. It's now covered more
+  // extensively by other tests.
   testConfigurations(decoder, { codec: vp9.codec }, badCodecsList);
 
   return endAfterEventLoopTurn();
-}, 'Test VideoDecoder.configure()');
+}, 'Test VideoDecoder.configure() with various codec strings');
 
 promise_test(async t => {
   let buffer = await vp9.buffer();