Bug 1201590 - WebMIDI Mochitests; r=baku r=padenot draft
authorKyle Machulis <kyle@nonpolynomial.com>
Tue, 21 Jul 2015 14:28:30 -0700
changeset 707270 d82b899b6113eecd34203f72be93faeb9da9c469
parent 707269 cc91595a1aeeb6d1bdf5078ec0aa979d7f3ebc70
child 742894 c7f4a5029f68fe81fc0fc9f7119f8a79962340c7
push id92062
push userbmo:kyle@nonpolynomial.com
push dateTue, 05 Dec 2017 01:36:25 +0000
reviewersbaku, padenot
bugs1201590
milestone59.0a1
Bug 1201590 - WebMIDI Mochitests; r=baku r=padenot MozReview-Commit-ID: F6bSQ4oCRDq
.eslintignore
browser/modules/PermissionUI.jsm
dom/midi/moz.build
dom/midi/tests/.eslintrc.js
dom/midi/tests/MIDITestUtils.js
dom/midi/tests/mochitest.ini
dom/midi/tests/test_midi_device_connect_disconnect.html
dom/midi/tests/test_midi_device_enumeration.html
dom/midi/tests/test_midi_device_explicit_open_close.html
dom/midi/tests/test_midi_device_implicit_open_close.html
dom/midi/tests/test_midi_device_pending.html
dom/midi/tests/test_midi_device_sysex.html
dom/midi/tests/test_midi_device_system_rt.html
dom/midi/tests/test_midi_packet_timing_sorting.html
dom/midi/tests/test_midi_permission_allow.html
dom/midi/tests/test_midi_permission_deny.html
dom/midi/tests/test_midi_permission_prompt.html
--- a/.eslintignore
+++ b/.eslintignore
@@ -231,16 +231,17 @@ dom/ipc/**
 dom/json/**
 dom/jsurl/**
 dom/locales/**
 dom/manifest/**
 dom/mathml/**
 dom/media/**
 !dom/media/*.js*
 dom/messagechannel/**
+dom/midi/**
 dom/network/**
 dom/notification/Notification*.*
 dom/notification/test/browser/**
 dom/notification/test/unit/**
 dom/offline/**
 dom/payments/**
 dom/performance/**
 dom/permission/**
--- a/browser/modules/PermissionUI.jsm
+++ b/browser/modules/PermissionUI.jsm
@@ -679,19 +679,19 @@ PermissionUI.PersistentStoragePermission
  *        The request for a permission from content.
  */
 function MIDIPermissionPrompt(request) {
   this.request = request;
   let types = request.types.QueryInterface(Ci.nsIArray);
   let perm = types.queryElementAt(0, Ci.nsIContentPermissionType);
   this.isSysexPerm = (perm.options.length > 0 &&
                       perm.options.queryElementAt(0, Ci.nsISupportsString) == "sysex");
-  let permName = 'midi';
+  let permName = "midi";
   if (this.isSysexPerm) {
-    permName = 'midi-sysex';
+    permName = "midi-sysex";
   }
 }
 
 MIDIPermissionPrompt.prototype = {
   __proto__: PermissionPromptForRequestPrototype,
 
   get permissionKey() {
     return "midi-sysex";
@@ -706,35 +706,30 @@ MIDIPermissionPrompt.prototype = {
     return "midi";
   },
 
   get anchorID() {
     return "";
   },
 
   get message() {
-    let message;
     if (this.isSysexPerm) {
       if (this.principal.URI.schemeIs("file")) {
-        message = gBrowserBundle.formatStringFromName("midi.shareSysexWithFile",
-                                                      [this.principal.URI.path], 1);
-      } else {
-        message = gBrowserBundle.formatStringFromName("midi.shareSysexWithSite",
-                                                      [this.principal.URI.host], 1);
+        return gBrowserBundle.formatStringFromName("midi.shareSysexWithFile",
+                                                   [this.principal.URI.path], 1);
       }
-    } else {
-      if (this.principal.URI.schemeIs("file")) {
-        message = gBrowserBundle.formatStringFromName("midi.shareWithFile",
-                                                      [this.principal.URI.path], 1);
-      } else {
-        message = gBrowserBundle.formatStringFromName("midi.shareWithSite",
-                                                      [this.principal.URI.host], 1);
-      }
+      return gBrowserBundle.formatStringFromName("midi.shareSysexWithSite",
+                                                 [this.principal.URI.host], 1);
     }
-    return message;
+    if (this.principal.URI.schemeIs("file")) {
+      return gBrowserBundle.formatStringFromName("midi.shareWithFile",
+                                                 [this.principal.URI.path], 1);
+    }
+    return gBrowserBundle.formatStringFromName("midi.shareWithSite",
+                                               [this.principal.URI.host], 1);
   },
 
   get promptActions() {
     let actions = [];
     if (this.isSysexPerm) {
       actions = [{
         label: gBrowserBundle.GetStringFromName("midi.shareDevicesWithSysex"),
         accessKey: gBrowserBundle.GetStringFromName("midi.shareDevicesWithSysex.accesskey"),
--- a/dom/midi/moz.build
+++ b/dom/midi/moz.build
@@ -62,11 +62,9 @@ UNIFIED_SOURCES = [
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '/dom/base',
 ]
 
-# TEST_DIRS += [
-#     'test',
-# ]
+MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
new file mode 100644
--- /dev/null
+++ b/dom/midi/tests/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+  "extends": [
+    "plugin:mozilla/mochitest-test",
+  ]
+};
new file mode 100644
--- /dev/null
+++ b/dom/midi/tests/MIDITestUtils.js
@@ -0,0 +1,54 @@
+/* eslint-env mozilla/frame-script */
+var MIDITestUtils = {
+  permissionSetup: (allow) => {
+    let permPromiseRes;
+    let permPromise = new Promise((res, rej) => { permPromiseRes = res; });
+    SpecialPowers.pushPrefEnv({"set": [["dom.webmidi.enabled", true],
+                                       ["midi.testing", true],
+                                       ["midi.prompt.testing", true],
+                                       ["media.navigator.permission.disabled", allow]]},
+                              () => {
+                                permPromiseRes();
+                              });
+    return permPromise;
+  },
+  inputInfo: {
+    id: "b744eebe-f7d8-499b-872b-958f63c8f522",
+    name: "Test Control MIDI Device Input Port",
+    manufacturer: "Test Manufacturer",
+    version: "1.0.0"
+  },
+  outputInfo: {
+    id: "ab8e7fe8-c4de-436a-a960-30898a7c9a3d",
+    name: "Test Control MIDI Device Output Port",
+    manufacturer: "Test Manufacturer",
+    version: "1.0.0"
+  },
+  stateTestInputInfo: {
+    id: "a9329677-8588-4460-a091-9d4a7f629a48",
+    name: "Test State MIDI Device Input Port",
+    manufacturer: "Test Manufacturer",
+    version: "1.0.0"
+  },
+  stateTestOutputInfo: {
+    id: "478fa225-b5fc-4fa6-a543-d32d9cb651e7",
+    name: "Test State MIDI Device Output Port",
+    manufacturer: "Test Manufacturer",
+    version: "1.0.0"
+  },
+  alwaysClosedTestOutputInfo: {
+    id: "f87d0c76-3c68-49a9-a44f-700f1125c07a",
+    name: "Always Closed MIDI Device Output Port",
+    manufacturer: "Test Manufacturer",
+    version: "1.0.0"
+  },
+  checkPacket: (expected, actual) => {
+    if (expected.length != actual.length) {
+      ok(false, "Packet " + expected + " length not same as packet " + actual);
+    }
+    for (var i = 0; i < expected.length; ++i) {
+      is(expected[i], actual[i], "Packet value " + expected[i] + " matches.");
+    }
+  }
+};
+
new file mode 100644
--- /dev/null
+++ b/dom/midi/tests/mochitest.ini
@@ -0,0 +1,15 @@
+[DEFAULT]
+support-files =
+  MIDITestUtils.js
+
+[test_midi_permission_prompt.html]
+[test_midi_permission_allow.html]
+[test_midi_permission_deny.html]
+[test_midi_device_enumeration.html]
+[test_midi_device_implicit_open_close.html]
+[test_midi_device_explicit_open_close.html]
+[test_midi_device_connect_disconnect.html]
+[test_midi_device_pending.html]
+[test_midi_device_sysex.html]
+[test_midi_device_system_rt.html]
+[test_midi_packet_timing_sorting.html]
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/midi/tests/test_midi_device_connect_disconnect.html
@@ -0,0 +1,47 @@
+<html>
+  <head>
+    <title>WebMIDI Listener Test</title>
+    <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+    <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+    <script type="application/javascript" src="MIDITestUtils.js"></script>
+  </head>
+
+  <body onload="runTests()">
+    <script class="testbody" type="application/javascript">
+     SimpleTest.waitForExplicitFinish();
+
+     async function runTests() {
+       await MIDITestUtils.permissionSetup(true);
+       let output;
+
+       let midi_access = await navigator.requestMIDIAccess({ "sysex": false });
+       ok(true, "MIDI Access Request successful");
+       is(midi_access.sysexEnabled, false, "Sysex should be false");
+       output = midi_access.outputs.get(MIDITestUtils.outputInfo.id);
+       let statePromiseRes;
+       let statePromise = new Promise((res) => { statePromiseRes = res; });
+       await output.open();
+       let stateChangeHandler = (event) => {
+         if (event.port == output) {
+           return;
+         }
+         statePromiseRes(event.port);
+       };
+       midi_access.addEventListener("statechange", stateChangeHandler);
+       // Send command to connect new port.
+       output.send([0x90, 0x01, 0x00]);
+       let p = await statePromise;
+       is(p.state, "connected", "Device " + p.name + " connected");
+
+       // Rebuild our promise, we'll need to await another one.
+       statePromise = new Promise((res) => { statePromiseRes = res; });
+       output.send([0x90, 0x02, 0x00]);
+       p = await statePromise;
+       is(p.state, "disconnected", "Device " + p.name + " disconnected");
+       midi_access.removeEventListener("statechange", stateChangeHandler);
+       SimpleTest.finish();
+     }
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/midi/tests/test_midi_device_enumeration.html
@@ -0,0 +1,46 @@
+<html>
+  <head>
+    <title>WebMIDI Listener Test</title>
+    <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+    <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+    <script type="application/javascript" src="MIDITestUtils.js"></script>
+  </head>
+
+  <body onload="runTests()">
+    <script class="testbody" type="application/javascript">
+     SimpleTest.waitForExplicitFinish();
+
+     let objectCompare = (type, props, obj) => {
+       for (var prop in props) {
+         is(props[prop], obj[prop], type + " property value " + prop + " is " + props[prop]);
+       }
+     };
+     let failOnCall = (event) => {
+       ok(false, "No connect/state events should be received on startup!");
+     };
+     async function runTests () {
+       await MIDITestUtils.permissionSetup(true);
+       // Request access without sysex.
+       let access = await navigator.requestMIDIAccess({ "sysex": false });
+       ok(true, "MIDI Access Request successful");
+       is(access.sysexEnabled, false, "Sysex should be false");
+       access.addEventListener("statechange", failOnCall);
+       var input_id = MIDITestUtils.inputInfo.id;
+       var output_id = MIDITestUtils.outputInfo.id;
+       var inputs = access.inputs;
+       var outputs = access.outputs;
+       is(inputs.size, 1, "Should have one input");
+       is(outputs.size, 2, "Should have two outputs");
+       ok(inputs.has(input_id), "input list should contain input id");
+       ok(outputs.has(output_id), "output list should contain output id");
+       var input = access.inputs.get(input_id);
+       var output = access.outputs.get(output_id);
+       objectCompare("input", MIDITestUtils.inputInfo, input);
+       objectCompare("output", MIDITestUtils.outputInfo, output);
+       access.removeEventListener("statechange", failOnCall);
+       SimpleTest.finish();
+     };
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/midi/tests/test_midi_device_explicit_open_close.html
@@ -0,0 +1,95 @@
+<html>
+  <head>
+    <title>WebMIDI Device Open/Close Test</title>
+    <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+    <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+    <script type="application/javascript" src="MIDITestUtils.js"></script>
+  </head>
+
+  <body onload="runTests()">
+    <script class="testbody" type="application/javascript">
+     SimpleTest.waitForExplicitFinish();
+
+     async function runTests() {
+       await MIDITestUtils.permissionSetup(true);
+
+       let access;
+       try {
+         access = await navigator.requestMIDIAccess({ "sysex": false })
+       } catch (e) {
+         ok(false, "MIDI Access Request Failed!");
+         SimpleTest.finish();
+       }
+
+       ok(true, "MIDI Access Request successful");
+       let input = access.inputs.get(MIDITestUtils.inputInfo.id);
+       let portEventRes;
+       let accessEventRes;
+       let portEventPromise = new Promise((resolve, reject) => { portEventRes = resolve; });
+       let accessEventPromise = new Promise((resolve, reject) => { accessEventRes = resolve; });
+       let shouldClose = false;
+       let checkPort = (event) => {
+         ok(input === event.port, "input port object and event port object are same object");
+         ok(true, "port connection event fired");
+         ok(event.port.connection === (!shouldClose ? "open" : "closed"), "connection registered correctly");
+       };
+       let inputEventHandler = (event) => {
+         checkPort(event);
+         portEventRes();
+       };
+       let accessEventHandler = (event) => {
+         checkPort(event);
+         accessEventRes();
+       };
+       input.addEventListener("statechange", inputEventHandler);
+       access.addEventListener("statechange", accessEventHandler);
+       await input.open();
+       ok(true, "connection successful");
+       ok(input.connection === "open", "connection registered as open");
+       await Promise.all([portEventPromise, accessEventPromise]);
+       input.removeEventListener("statechange", inputEventHandler);
+       access.removeEventListener("statechange", accessEventHandler);
+       ok(true, "MIDI Port Open Test finished.");
+       ok(true, "Testing open failure");
+       let out_access;
+       try {
+         out_access = await navigator.requestMIDIAccess({ "sysex": false });
+       } catch (e) {
+         ok(false, "MIDI Access Request Failed!");
+         SimpleTest.finish();
+       }
+       let outputEventRes;
+       let outputEventHandler = (event) => {
+         ok(output_opened === event.port, "output port object and event port object are same object");
+         ok(true, "access connection event fired");
+         ok(event.port.connection === "closed", "connection registered as closed");
+       };
+       out_access.addEventListener("statechange", outputEventHandler);
+       let output_opened = out_access.outputs.get(MIDITestUtils.alwaysClosedTestOutputInfo.id);
+       try {
+         await output_opened.open();
+         ok(false, "Should've failed to open port!");
+       } catch(err) {
+         is(err.name, "InvalidAccessError", "error name " + err.name + " should be InvalidAccessError");
+         ok(output_opened.connection == "closed", "connection registered as closed");
+         ok(true, "Port not opened, test succeeded");
+       } finally {
+         out_access.removeEventListener("statechange", outputEventHandler);
+       }
+       ok(true, "Starting MIDI port closing test");
+       portEventPromise = new Promise((resolve, reject) => { portEventRes = resolve; });
+       accessEventPromise = new Promise((resolve, reject) => { accessEventRes = resolve; });
+       input.addEventListener("statechange", inputEventHandler);
+       access.addEventListener("statechange", accessEventHandler);
+       shouldClose = true;
+       await input.close();
+       ok(input.connection === "closed", "connection registered as closed");
+       await Promise.all([portEventPromise, accessEventPromise]);
+       input.removeEventListener("statechange", inputEventHandler);
+       access.removeEventListener("statechange", accessEventHandler);
+       SimpleTest.finish();
+     }
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/midi/tests/test_midi_device_implicit_open_close.html
@@ -0,0 +1,67 @@
+<html>
+  <head>
+    <title>WebMIDI Listener Test</title>
+    <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+    <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+    <script type="application/javascript" src="MIDITestUtils.js"></script>
+  </head>
+
+  <body onload="runTests()">
+    <script class="testbody" type="application/javascript">
+     SimpleTest.waitForExplicitFinish();
+
+     async function runTests() {
+       await MIDITestUtils.permissionSetup(true);
+       let access = await navigator.requestMIDIAccess({ "sysex": false });
+       ok(true, "MIDI Access Request successful");
+       is(access.sysexEnabled, false, "Sysex should be false");
+
+       var checkCount = 0;
+       var reopened = false;
+       var input;
+       var output;
+       function checkCallbacks(port) {
+         ok(true, "Got port " + port.connection + " for " + port.name);
+         if (port.connection == "open") {
+           checkCount++;
+         } else {
+           if (!reopened) {
+             reopened = true;
+             // Ports are closed. Fire rest of tests.
+             input.onmidimessage = checkReturn;
+             output.send([0x90, 0x00, 0x7F]);
+           }
+         }
+         if (checkCount == 3) {
+           input.onstatechange = undefined;
+           output.onstatechange = undefined;
+           input.close();
+           output.close();
+           SimpleTest.finish();
+         }
+       }
+       function checkReturn(event) {
+         checkCount++;
+         ok(true, "Got echo message back");
+         MIDITestUtils.checkPacket(event.data, [0x90, 0x00, 0x7f]);
+         if (checkCount == 3) {
+           input.onstatechange = undefined;
+           output.onstatechange = undefined;
+           input.close();
+           output.close();
+           SimpleTest.finish();
+         }
+       }
+
+       input = access.inputs.get(MIDITestUtils.inputInfo.id);
+       output = access.outputs.get(MIDITestUtils.outputInfo.id);
+       // We automatically open ports, so close them first.
+       input.onstatechange = (event) => { checkCallbacks(event.port); };
+       output.onstatechange = (event) => { checkCallbacks(event.port); };
+       input.close();
+       output.close();
+     }
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/midi/tests/test_midi_device_pending.html
@@ -0,0 +1,87 @@
+<html>
+  <head>
+    <title>WebMIDI Listener Test</title>
+    <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+    <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+    <script type="application/javascript" src="MIDITestUtils.js"></script>
+  </head>
+
+  <body onload="runTests()">
+    <script class="testbody" type="application/javascript">
+     SimpleTest.waitForExplicitFinish();
+
+     async function runTests() {
+       await MIDITestUtils.permissionSetup(true);
+
+
+       var checkCount = 0;
+       var state = "connecting";
+       var output;
+       var test_ports = [];
+       let access;
+
+       function checkDevices(event) {
+         var p = event.port;
+         // We'll get an open event for the output control port. Ignore it.
+         if (p.name == MIDITestUtils.outputInfo.name) {
+           return;
+         }
+         checkCount++;
+         if (state == "connecting") {
+           test_ports.push(p);
+           p.addEventListener("statechange", checkDevices);
+           is(p.state, "connected", "Device " + p.name + " connected");
+           p.open();
+         } else if (state == "opening" || state == "reopening") {
+           is(p.connection, "open", "Connection " + p.name + " opened");
+         } else if (state == "pending" || state == "reconnecting") {
+           is(p.connection, "pending", "Connection " + p.name + " pending");
+         }
+         // Make sure we've removed devices from access map.
+         if (state == "pending") {
+           is(access.inputs.has(p.id), false, "port removed from input map while pending");
+         }
+         // Make sure we've readded the same devices.
+         if (state == "reconnecting") {
+           var port = access.inputs.get(p.id);
+           is(port, p, "port in map and port in event should be the same");
+         }
+         // While connecting, we'll only get 1 event, from the MIDIAccess object.
+         // After that, we'll always get 2 events, 1 from MIDIAccess, 1 from the
+         // input device.
+         if (state == "connecting" && checkCount == 1) {
+           state = "opening";
+           checkCount = 0;
+         } else if (checkCount == 2) {
+           if (state == "opening") {
+             state = "pending";
+             checkCount = 0;
+             output.send([0x90, 0x02, 0x00]);
+           } else if (state == "pending") {
+             state = "reconnecting";
+             checkCount = 0;
+             output.send([0x90, 0x01, 0x00]);
+           } else if (state == "reconnecting") {
+             state = "reopening";
+             checkCount = 0;
+           } else {
+             for (let port of test_ports) {
+               port.removeEventListener("statechange", checkDevices);
+             }
+             access.removeEventListener("statechange", checkDevices);
+             SimpleTest.finish();
+           }
+         }
+       }
+
+       access = await navigator.requestMIDIAccess({ "sysex": false });
+       ok(true, "MIDI Access Request successful");
+       is(access.sysexEnabled, false, "Sysex should be false");
+       access.addEventListener("statechange", checkDevices);
+       output = access.outputs.get(MIDITestUtils.outputInfo.id);
+       output.send([0x90, 0x01, 0x00]);
+     }
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/midi/tests/test_midi_device_sysex.html
@@ -0,0 +1,57 @@
+<html>
+  <head>
+    <title>WebMIDI Listener Test</title>
+    <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+    <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+    <script type="application/javascript" src="MIDITestUtils.js"></script>
+  </head>
+
+  <body onload="runTests()">
+    <script class="testbody" type="application/javascript">
+     SimpleTest.waitForExplicitFinish();
+
+     async function runTests() {
+       await MIDITestUtils.permissionSetup(true);
+       var sysexCheckCount = 0;
+       var checkCount = 0;
+       var input;
+       var output;
+       function checkSysexReceive(event) {
+         checkCount++;
+         sysexCheckCount++;
+         if (sysexCheckCount == 1) {
+           is(event.data[0], 0xF0, "Echoed sysex message via sysex port");
+         } else {
+           is(event.data[0], 0x90, "Echoed regular message via sysex port");
+         }
+         if (checkCount == 5) {
+           SimpleTest.finish();
+         }
+       }
+
+       function checkNoSysexReceive(event) {
+         checkCount++;
+         is(event.data[0], 0x90, "Echoed regular message via non-sysex port");
+         if (checkCount == 5) {
+           SimpleTest.finish()
+         }
+       }
+
+       // Request access without sysex.
+       let access_regular = await navigator.requestMIDIAccess({ "sysex": false });
+       let access_sysex = await navigator.requestMIDIAccess({ "sysex": true });
+       ok(true, "MIDI Access Request successful");
+       ok(true, "Check for sysex message drop");
+       input = access_regular.inputs.get(MIDITestUtils.inputInfo.id);
+       output = access_sysex.outputs.get(MIDITestUtils.outputInfo.id);
+       input_sysex = access_sysex.inputs.get(MIDITestUtils.inputInfo.id);
+       input_sysex.onmidimessage = checkSysexReceive;
+       input.onmidimessage = checkNoSysexReceive;
+       output.send([0xF0, 0x00, 0xF7]);
+       output.send([0x90, 0x00, 0x01]);
+       output.send([0x90, 0x00, 0x01]);
+     }
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/midi/tests/test_midi_device_system_rt.html
@@ -0,0 +1,39 @@
+<html>
+  <head>
+    <title>WebMIDI Listener Test</title>
+    <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+    <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+    <script type="application/javascript" src="MIDITestUtils.js"></script>
+  </head>
+
+  <body onload="runTests()">
+    <script class="testbody" type="application/javascript">
+     SimpleTest.waitForExplicitFinish();
+
+     async function runTests() {
+       await MIDITestUtils.permissionSetup(true);
+       var checkCount = 0;
+
+       function checkReturn(msg) {
+         checkCount++;
+         if (checkCount == 1) {
+           MIDITestUtils.checkPacket(msg.data, [0xF8]);
+         } else if (checkCount == 2) {
+           MIDITestUtils.checkPacket(msg.data, [0xF9]);
+         } else if (checkCount == 3) {
+           MIDITestUtils.checkPacket(msg.data, [0xF0, 0x01, 0x02, 0x03, 0x04, 0x05, 0xF7]);
+           SimpleTest.finish();
+         }
+       }
+
+       // Request access without sysex.
+       let access_sysex = await navigator.requestMIDIAccess({ "sysex": true });
+       let input_sysex = access_sysex.inputs.get(MIDITestUtils.inputInfo.id);
+       input_sysex.onmidimessage = checkReturn;
+       let output_sysex = access_sysex.outputs.get(MIDITestUtils.outputInfo.id);
+       output_sysex.send([0xF0, 0x01, 0xF7]);
+     }
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/midi/tests/test_midi_packet_timing_sorting.html
@@ -0,0 +1,47 @@
+<html>
+  <head>
+    <title>WebMIDI Listener Test</title>
+    <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+    <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+    <script type="application/javascript" src="MIDITestUtils.js"></script>
+  </head>
+
+  <body onload="runTests()">
+    <script class="testbody" type="application/javascript">
+     SimpleTest.waitForExplicitFinish();
+
+     async function runTests() {
+       await MIDITestUtils.permissionSetup(true);
+       var checkCount = 0;
+       var lastTime = 0;
+       var reopened = false;
+       var input;
+       var output;
+       function checkReturn(event) {
+         ok(event.timeStamp > lastTime, "Received timestamp " + event.timeStamp + " should be greater than " + lastTime);
+         lastTime = event.timeStamp;
+         checkCount++;
+
+         if (checkCount == 6) {
+           input.close();
+           output.close();
+           SimpleTest.finish();
+         }
+       }
+       ok("Testing MIDI packet reordering based on timestamps");
+       // Request access without sysex.
+       let access = await navigator.requestMIDIAccess({ "sysex": false });
+       ok(true, "MIDI Access Request successful");
+       is(access.sysexEnabled, false, "Sysex should be false");
+
+       input = access.inputs.get(MIDITestUtils.inputInfo.id);
+       output = access.outputs.get(MIDITestUtils.outputInfo.id);
+       input.onmidimessage = checkReturn;
+       // trigger the packet timing sorting tests
+       output.send([0x90, 0x03, 0x00], 0);
+       ok(true, "Waiting on packets");
+     }
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/midi/tests/test_midi_permission_allow.html
@@ -0,0 +1,26 @@
+<html>
+  <head>
+    <title>WebMIDI Listener Test</title>
+    <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+    <script type="application/javascript" src="MIDITestUtils.js"></script>
+  </head>
+
+  <body onload="runTests()">
+    <script class="testbody" type="application/javascript">
+     SimpleTest.waitForExplicitFinish();
+
+     async function runTests() {
+       await MIDITestUtils.permissionSetup(true);
+       // Request access without sysex.
+       try {
+         await navigator.requestMIDIAccess({ "sysex": false })
+         ok(true, "MIDI Access Request successful");
+         SimpleTest.finish();
+       } catch {
+         ok(false, "MIDI Access Request Failed!");
+         SimpleTest.finish();
+       }
+     }
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/midi/tests/test_midi_permission_deny.html
@@ -0,0 +1,26 @@
+<html>
+  <head>
+    <title>WebMIDI Listener Test</title>
+    <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+    <script type="application/javascript" src="MIDITestUtils.js"></script>
+  </head>
+
+  <body onload="runTests()">
+    <script class="testbody" type="application/javascript">
+     SimpleTest.waitForExplicitFinish();
+
+     async function runTests() {
+       await MIDITestUtils.permissionSetup(false);
+       // Request access without sysex.
+       try {
+         await navigator.requestMIDIAccess({ "sysex": false });
+         ok(false, "MIDI Access Request Deny failed");
+         SimpleTest.finish();
+       } catch {
+         ok(true, "MIDI Access Request Deny successful!");
+         SimpleTest.finish();
+       }
+     }
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/midi/tests/test_midi_permission_prompt.html
@@ -0,0 +1,24 @@
+<html>
+  <head>
+    <title>WebMIDI Listener Test</title>
+    <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+    <script type="application/javascript" src="MIDITestUtils.js"></script>
+  </head>
+
+  <body onload="runTests()">
+    <script class="testbody" type="application/javascript">
+     SimpleTest.waitForExplicitFinish();
+
+     async function runTests() {
+       await MIDITestUtils.permissionSetup(true);
+       try {
+         await navigator.requestMIDIAccess({ "sysex": false });
+         ok(true, "Prompting for permissions succeeded!");
+       } catch (e) {
+         ok(false, "Prompting for permissions failed!");
+       }
+       SimpleTest.finish();
+     }
+    </script>
+  </body>
+</html>