Throw error if connections are made between different contexts

The WebAudio spec says that if a node is connected/disconnected to
another node or AudioParam that belongs to a different context, then
an InvalidStateError must be thrown.[1][2]  Add a check for this and throw
the required error.

This also requires a fix to audit.js which wasn't properly catching
DOM exceptions of the wrong type.

Finally, ctor-channelsplitter.html needed to be updated to specify the
error type because of the change in audit.js.

Also adds new WPT test, different-contexts.html, to more thoroughly test
connect/disconnects to different contexts.

[1] https://webaudio.github.io/web-audio-api/#dom-audionode-connect-destinationnode-output-input-destinationnode
[2] https://webaudio.github.io/web-audio-api/#dom-audionode-disconnect-destinationnode-destinationnode

Bug: 1206927
Change-Id: I320425268d1fd243e347dd36c79172c69dcc9733
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2965479
Reviewed-by: Hongchan Choi <hongchan@chromium.org>
Commit-Queue: Raymond Toy <rtoy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#895851}
diff --git a/webaudio/resources/audit.js b/webaudio/resources/audit.js
index 896b9d5..0acd340 100644
--- a/webaudio/resources/audit.js
+++ b/webaudio/resources/audit.js
@@ -320,11 +320,16 @@
           didThrowCorrectly = true;
           passDetail = '${actual} threw ' + error.name + errorMessage + '.';
         } else if (this._expected === DOMException &&
-                   (this._expectedDescription === undefined ||
-                    this._expectedDescription === error.name)) {
-          // Handles DOMException with the associated name.
-          didThrowCorrectly = true;
-          passDetail = '${actual} threw ${expected}' + errorMessage + '.';
+                   this._expectedDescription !== undefined) {
+          // Handles DOMException with an expected exception name.
+          if (this._expectedDescription === error.name) {
+            didThrowCorrectly = true;
+            passDetail = '${actual} threw ${expected}' + errorMessage + '.';
+          } else {
+            didThrowCorrectly = false;
+            failDetail =
+                '${actual} threw "' + error.name + '" instead of ${expected}.';
+          }
         } else if (this._expected == error.constructor) {
           // Handler other error types.
           didThrowCorrectly = true;
diff --git a/webaudio/the-audio-api/the-audiocontext-interface/audiocontextoptions.html b/webaudio/the-audio-api/the-audiocontext-interface/audiocontextoptions.html
index 0bf2cfb..136abed 100644
--- a/webaudio/the-audio-api/the-audiocontext-interface/audiocontextoptions.html
+++ b/webaudio/the-audio-api/the-audiocontext-interface/audiocontextoptions.html
@@ -170,7 +170,7 @@
                   context = new AudioContext({sampleRate: 1})
                 },
                 'context = new AudioContext({sampleRate: 1})')
-                .throw(DOMException);
+                .throw(DOMException, 'NotSupportedError');
 
             // A sampleRate of 1,000,000 is unlikely to be supported on any
             // browser, test that this rate is also rejected.
@@ -179,21 +179,21 @@
                   context = new AudioContext({sampleRate: 1000000})
                 },
                 'context = new AudioContext({sampleRate: 1000000})')
-                .throw(DOMException);
+                .throw(DOMException, 'NotSupportedError');
             // A negative sample rate should not be accepted
             should(
                 () => {
                   context = new AudioContext({sampleRate: -1})
                 },
                 'context = new AudioContext({sampleRate: -1})')
-                .throw(DOMException);
+                .throw(DOMException, 'NotSupportedError');
             // A null sample rate should not be accepted
             should(
                 () => {
                   context = new AudioContext({sampleRate: 0})
                 },
                 'context = new AudioContext({sampleRate: 0})')
-                .throw(DOMException);
+                .throw(DOMException, 'NotSupportedError');
 
             should(
                 () => {
diff --git a/webaudio/the-audio-api/the-audionode-interface/different-contexts.html b/webaudio/the-audio-api/the-audionode-interface/different-contexts.html
new file mode 100644
index 0000000..f763d34
--- /dev/null
+++ b/webaudio/the-audio-api/the-audionode-interface/different-contexts.html
@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Connections and disconnections with different contexts
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script>
+      let audit = Audit.createTaskRunner();
+
+      // Different contexts to be used for testing.
+      let c1;
+      let c2;
+
+      audit.define(
+          {label: 'setup', description: 'Contexts for testing'},
+          (task, should) => {
+            should(() => {c1 = new AudioContext()}, 'c1 = new AudioContext()')
+                .notThrow();
+            should(() => {c2 = new AudioContext()}, 'c2 = new AudioContext()')
+                .notThrow();
+            task.done();
+          });
+
+      audit.define(
+          {label: 'Test 1', description: 'Connect nodes between contexts'},
+          (task, should) => {
+            let g1;
+            let g2;
+            should(
+                () => {g1 = new GainNode(c1)}, 'Test 1: g1 = new GainNode(c1)')
+                .notThrow();
+            should(
+                () => {g2 = new GainNode(c2)}, 'Test 1: g2 = new GainNode(c2)')
+                .notThrow();
+            should(() => {g2.connect(g1)}, 'Test 1: g2.connect(g1)')
+                .throw(DOMException, 'InvalidAccessError');
+            task.done();
+          });
+
+      audit.define(
+          {label: 'Test 2', description: 'Connect AudioParam between contexts'},
+          (task, should) => {
+            let g1;
+            let g2;
+            should(
+                () => {g1 = new GainNode(c1)}, 'Test 2: g1 = new GainNode(c1)')
+                .notThrow();
+            should(
+                () => {g2 = new GainNode(c2)}, 'Test 2: g2 = new GainNode(c2)')
+                .notThrow();
+            should(() => {g2.connect(g1.gain)}, 'Test 2: g2.connect(g1.gain)')
+                .throw(DOMException, 'InvalidAccessError');
+            task.done();
+          });
+
+      audit.define(
+          {label: 'Test 3', description: 'Disconnect nodes between contexts'},
+          (task, should) => {
+            let g1;
+            let g2;
+            should(
+                () => {g1 = new GainNode(c1)}, 'Test 3: g1 = new GainNode(c1)')
+                .notThrow();
+            should(
+                () => {g2 = new GainNode(c2)}, 'Test 3: g2 = new GainNode(c2)')
+                .notThrow();
+            should(() => {g2.disconnect(g1)}, 'Test 3: g2.disconnect(g1)')
+                .throw(DOMException, 'InvalidAccessError');
+            task.done();
+          });
+
+      audit.define(
+          {
+            label: 'Test 4',
+            description: 'Disconnect AudioParam between contexts'
+          },
+          (task, should) => {
+            let g1;
+            let g2;
+            should(
+                () => {g1 = new GainNode(c1)}, 'Test 4: g1 = new GainNode(c1)')
+                .notThrow();
+            should(
+                () => {g2 = new GainNode(c2)}, 'Test 4: g2 = new GainNode(c2)')
+                .notThrow();
+            should(
+                () => {g2.disconnect(g1.gain)}, 'Test 4: g2.connect(g1.gain)')
+                .throw(DOMException, 'InvalidAccessError');
+            task.done();
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-channelsplitternode-interface/ctor-channelsplitter.html b/webaudio/the-audio-api/the-channelsplitternode-interface/ctor-channelsplitter.html
index 9cbb46b..b7165ba 100644
--- a/webaudio/the-audio-api/the-channelsplitternode-interface/ctor-channelsplitter.html
+++ b/webaudio/the-audio-api/the-channelsplitternode-interface/ctor-channelsplitter.html
@@ -49,6 +49,7 @@
           channelCountMode: {
             value: 'explicit',
             isFixed: true,
+            exceptionType: 'InvalidStateError'
           },
           channelInterpretation: {
             value: 'discrete',