forked from mirrors/gecko-dev
		
	Bug 1201590 - WebMIDI Mochitests; r=baku,padenot
				
					
				
			MozReview-Commit-ID: F6bSQ4oCRDq --HG-- extra : rebase_source : cb60759b5688de759e8e6aa9301b6f5d1f640a67
This commit is contained in:
		
							parent
							
								
									e4e6405a51
								
							
						
					
					
						commit
						10e49826b1
					
				
					 18 changed files with 703 additions and 8 deletions
				
			
		|  | @ -240,6 +240,7 @@ dom/mathml/** | ||||||
| dom/media/** | dom/media/** | ||||||
| !dom/media/*.js* | !dom/media/*.js* | ||||||
| dom/messagechannel/** | dom/messagechannel/** | ||||||
|  | dom/midi/** | ||||||
| dom/network/** | dom/network/** | ||||||
| dom/notification/Notification*.* | dom/notification/Notification*.* | ||||||
| dom/notification/test/browser/** | dom/notification/test/browser/** | ||||||
|  |  | ||||||
|  | @ -192,18 +192,14 @@ void TestMIDIPlatformService::ProcessMessages(const nsAString& aPortId) | ||||||
|         case 0x01: |         case 0x01: | ||||||
|         { |         { | ||||||
|           nsCOMPtr<nsIRunnable> r1(new AddPortRunnable(mStateTestInputPort)); |           nsCOMPtr<nsIRunnable> r1(new AddPortRunnable(mStateTestInputPort)); | ||||||
|           nsCOMPtr<nsIRunnable> r2(new AddPortRunnable(mStateTestOutputPort)); |  | ||||||
|           mBackgroundThread->Dispatch(r1, NS_DISPATCH_NORMAL); |           mBackgroundThread->Dispatch(r1, NS_DISPATCH_NORMAL); | ||||||
|           mBackgroundThread->Dispatch(r2, NS_DISPATCH_NORMAL); |  | ||||||
|           break; |           break; | ||||||
|         } |         } | ||||||
|         // Cause control test ports to disconnect
 |         // Cause control test ports to disconnect
 | ||||||
|         case 0x02: |         case 0x02: | ||||||
|         { |         { | ||||||
|           nsCOMPtr<nsIRunnable> r1(new RemovePortRunnable(mStateTestInputPort)); |           nsCOMPtr<nsIRunnable> r1(new RemovePortRunnable(mStateTestInputPort)); | ||||||
|           nsCOMPtr<nsIRunnable> r2(new RemovePortRunnable(mStateTestOutputPort)); |  | ||||||
|           mBackgroundThread->Dispatch(r1, NS_DISPATCH_NORMAL); |           mBackgroundThread->Dispatch(r1, NS_DISPATCH_NORMAL); | ||||||
|           mBackgroundThread->Dispatch(r2, NS_DISPATCH_NORMAL); |  | ||||||
|           break; |           break; | ||||||
|         } |         } | ||||||
|         // Test for packet timing
 |         // Test for packet timing
 | ||||||
|  | @ -224,7 +220,7 @@ void TestMIDIPlatformService::ProcessMessages(const nsAString& aPortId) | ||||||
|             // we're sorting correctly.
 |             // we're sorting correctly.
 | ||||||
|             newMsgs.AppendElement( |             newMsgs.AppendElement( | ||||||
|               MIDIMessage(msg, currentTime - |               MIDIMessage(msg, currentTime - | ||||||
|                           TimeDuration::FromMilliseconds(i))); |                           TimeDuration::FromMilliseconds(i * 2))); | ||||||
|           } |           } | ||||||
|           nsCOMPtr<nsIRunnable> r(new QueueMessagesRunnable(aPortId, newMsgs)); |           nsCOMPtr<nsIRunnable> r(new QueueMessagesRunnable(aPortId, newMsgs)); | ||||||
|           mBackgroundThread->Dispatch(r, NS_DISPATCH_NORMAL); |           mBackgroundThread->Dispatch(r, NS_DISPATCH_NORMAL); | ||||||
|  |  | ||||||
|  | @ -60,6 +60,4 @@ LOCAL_INCLUDES += [ | ||||||
|     '/dom/base', |     '/dom/base', | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| # TEST_DIRS += [ | MOCHITEST_MANIFESTS += ['tests/mochitest.ini'] | ||||||
| #     'test', |  | ||||||
| # ] |  | ||||||
|  |  | ||||||
							
								
								
									
										7
									
								
								dom/midi/tests/.eslintrc.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								dom/midi/tests/.eslintrc.js
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | ||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | module.exports = { | ||||||
|  |   "extends": [ | ||||||
|  |     "plugin:mozilla/mochitest-test", | ||||||
|  |   ] | ||||||
|  | }; | ||||||
							
								
								
									
										56
									
								
								dom/midi/tests/MIDITestUtils.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								dom/midi/tests/MIDITestUtils.js
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,56 @@ | ||||||
|  | /* 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; | ||||||
|  |   }, | ||||||
|  |   // This list needs to stay synced with the ports in
 | ||||||
|  |   // dom/midi/TestMIDIPlatformService.
 | ||||||
|  |   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."); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
							
								
								
									
										18
									
								
								dom/midi/tests/mochitest.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								dom/midi/tests/mochitest.ini
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | ||||||
|  | [DEFAULT] | ||||||
|  | support-files = | ||||||
|  |   MIDITestUtils.js | ||||||
|  | scheme = https | ||||||
|  | 
 | ||||||
|  | [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_sysex.html] | ||||||
|  | [test_midi_device_system_rt.html] | ||||||
|  | [test_midi_packet_timing_sorting.html] | ||||||
|  | [test_midi_device_connect_disconnect.html] | ||||||
|  | disabled = Bug 1437204 | ||||||
|  | [test_midi_device_pending.html] | ||||||
|  | disabled = Bug 1437204 | ||||||
							
								
								
									
										54
									
								
								dom/midi/tests/test_midi_device_connect_disconnect.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								dom/midi/tests/test_midi_device_connect_disconnect.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,54 @@ | ||||||
|  | <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; | ||||||
|  |        try { | ||||||
|  |          midi_access = await navigator.requestMIDIAccess({ "sysex": false }); | ||||||
|  |          ok(true, "MIDI Access Request successful"); | ||||||
|  |        } catch (e) { | ||||||
|  |          ok(false, "MIDI Access Request failed!"); | ||||||
|  |          SimpleTest.finish(); | ||||||
|  |          return; | ||||||
|  |        } | ||||||
|  |        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> | ||||||
							
								
								
									
										46
									
								
								dom/midi/tests/test_midi_device_enumeration.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								dom/midi/tests/test_midi_device_enumeration.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -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> | ||||||
							
								
								
									
										95
									
								
								dom/midi/tests/test_midi_device_explicit_open_close.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								dom/midi/tests/test_midi_device_explicit_open_close.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -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> | ||||||
							
								
								
									
										67
									
								
								dom/midi/tests/test_midi_device_implicit_open_close.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								dom/midi/tests/test_midi_device_implicit_open_close.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -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> | ||||||
							
								
								
									
										122
									
								
								dom/midi/tests/test_midi_device_pending.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								dom/midi/tests/test_midi_device_pending.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,122 @@ | ||||||
|  | <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; | ||||||
|  | 
 | ||||||
|  |        let accessRes; | ||||||
|  |        let accessRej; | ||||||
|  |        let accessPromise; | ||||||
|  |        let portRes; | ||||||
|  |        let portRej; | ||||||
|  |        let portPromise; | ||||||
|  | 
 | ||||||
|  |        function resetPromises() { | ||||||
|  |          accessPromise = new Promise((res, rej) => { accessRes = res; accessRej = rej; }); | ||||||
|  |          portPromise = new Promise((res, rej) => { portRes = res; portRej = rej; }); | ||||||
|  |        } | ||||||
|  | 
 | ||||||
|  |        function accessStateChangeHandler(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; | ||||||
|  |          } | ||||||
|  |          accessRes(event); | ||||||
|  |        } | ||||||
|  | 
 | ||||||
|  |        function portStateChangeHandler(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; | ||||||
|  |          } | ||||||
|  |          portRes(event); | ||||||
|  |        } | ||||||
|  | 
 | ||||||
|  |        // Part 1: Create MIDIAccess object, attach state change listener to list for new connections | ||||||
|  |        access = await navigator.requestMIDIAccess({ "sysex": false }); | ||||||
|  |        ok(true, "MIDI Access Request successful"); | ||||||
|  |        is(access.sysexEnabled, false, "Sysex should be false"); | ||||||
|  |        access.addEventListener("statechange", accessStateChangeHandler); | ||||||
|  | 
 | ||||||
|  |        // Part 2: open test device, make sure it connects, attach event handler to device object | ||||||
|  |        output = access.outputs.get(MIDITestUtils.outputInfo.id); | ||||||
|  |        resetPromises(); | ||||||
|  |        output.send([0x90, 0x01, 0x00]); | ||||||
|  |        let accessEvent = await accessPromise; | ||||||
|  |        let testPort = accessEvent.port; | ||||||
|  |        test_ports.push(testPort); | ||||||
|  |        testPort.addEventListener("statechange", portStateChangeHandler); | ||||||
|  |        is(testPort.state, "connected", "Device " + testPort.name + " connected"); | ||||||
|  | 
 | ||||||
|  |        // Part 3: Listen for port status change on open as both an access event | ||||||
|  |        // and a port event. | ||||||
|  |        resetPromises(); | ||||||
|  |        testPort.open(); | ||||||
|  |        accessEvent = await accessPromise; | ||||||
|  |        is(testPort.connection, "open", "Connection " + testPort.name + " opened"); | ||||||
|  |        let portEvent = await portPromise; | ||||||
|  |        is(testPort.connection, "open", "Connection " + testPort.name + " opened"); | ||||||
|  | 
 | ||||||
|  |        // Part 4: Disconnect port but don't close, check status to make sure we're pending. | ||||||
|  |        resetPromises(); | ||||||
|  |        output.send([0x90, 0x02, 0x00]); | ||||||
|  |        accessEvent = await accessPromise; | ||||||
|  |        is(testPort.connection, "pending", "Connection " + testPort.name + " pending"); | ||||||
|  |        is(access.inputs.has(testPort.id), false, "port removed from input map while pending"); | ||||||
|  |        portEvent = await portPromise; | ||||||
|  |        is(testPort.connection, "pending", "Connection " + testPort.name + " pending"); | ||||||
|  | 
 | ||||||
|  |        // Part 5: Connect ports again, make sure we return to the right status. The events will | ||||||
|  |        // fire because the device has been readded to the device maps in the access object. | ||||||
|  |        resetPromises(); | ||||||
|  |        output.send([0x90, 0x01, 0x00]); | ||||||
|  |        accessEvent = await accessPromise; | ||||||
|  |        var port = access.inputs.get(testPort.id); | ||||||
|  |        is(port, accessEvent.port, "port in map and port in event should be the same"); | ||||||
|  |        is(testPort.connection, "pending", "Connection " + testPort.name + " pending"); | ||||||
|  |        portEvent = await portPromise; | ||||||
|  |        is(testPort.connection, "pending", "Connection " + testPort.name + " pending"); | ||||||
|  | 
 | ||||||
|  |        // Part 6: Close out everything and clean up. | ||||||
|  |        resetPromises(); | ||||||
|  |        accessEvent = await accessPromise; | ||||||
|  |        is(accessEvent.port.connection, "open", "Connection " + testPort.name + " opened"); | ||||||
|  |        portEvent = await portPromise; | ||||||
|  |        is(portEvent.port.connection, "open", "Connection " + testPort.name + " opened"); | ||||||
|  | 
 | ||||||
|  |        /* for (let port of test_ports) { | ||||||
|  |         *   port.removeEventListener("statechange", checkDevices); | ||||||
|  |         * } | ||||||
|  |         * access.removeEventListener("statechange", checkDevices);*/ | ||||||
|  |        output.send([0x90, 0x02, 0x00]); | ||||||
|  |        testPort.removeEventListener("statechange", portStateChangeHandler); | ||||||
|  |        access.removeEventListener("statechange", accessStateChangeHandler); | ||||||
|  |        access = undefined; | ||||||
|  |        output = undefined; | ||||||
|  |        testPort = undefined; | ||||||
|  |        accessEvent = undefined; | ||||||
|  |        portEvent = undefined; | ||||||
|  |        SimpleTest.finish(); | ||||||
|  |      } | ||||||
|  |     </script> | ||||||
|  |   </body> | ||||||
|  | </html> | ||||||
							
								
								
									
										57
									
								
								dom/midi/tests/test_midi_device_sysex.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								dom/midi/tests/test_midi_device_sysex.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -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> | ||||||
							
								
								
									
										39
									
								
								dom/midi/tests/test_midi_device_system_rt.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								dom/midi/tests/test_midi_device_system_rt.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -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> | ||||||
							
								
								
									
										47
									
								
								dom/midi/tests/test_midi_packet_timing_sorting.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								dom/midi/tests/test_midi_packet_timing_sorting.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -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> | ||||||
							
								
								
									
										26
									
								
								dom/midi/tests/test_midi_permission_allow.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								dom/midi/tests/test_midi_permission_allow.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -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> | ||||||
							
								
								
									
										26
									
								
								dom/midi/tests/test_midi_permission_deny.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								dom/midi/tests/test_midi_permission_deny.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -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> | ||||||
							
								
								
									
										24
									
								
								dom/midi/tests/test_midi_permission_prompt.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								dom/midi/tests/test_midi_permission_prompt.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -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> | ||||||
|  | @ -664,6 +664,22 @@ var interfaceNamesInGlobalScope = | ||||||
|     {name: "MessageEvent", insecureContext: true}, |     {name: "MessageEvent", insecureContext: true}, | ||||||
| // IMPORTANT: Do not change this list without review from a DOM peer!
 | // IMPORTANT: Do not change this list without review from a DOM peer!
 | ||||||
|     {name: "MessagePort", insecureContext: true}, |     {name: "MessagePort", insecureContext: true}, | ||||||
|  | // IMPORTANT: Do not change this list without review from a DOM peer!
 | ||||||
|  |     {name: "MIDIAccess", disabled: true}, | ||||||
|  | // IMPORTANT: Do not change this list without review from a DOM peer!
 | ||||||
|  |     {name: "MIDIConnectionEvent", disabled: true}, | ||||||
|  | // IMPORTANT: Do not change this list without review from a DOM peer!
 | ||||||
|  |     {name: "MIDIInputMap", disabled: true}, | ||||||
|  | // IMPORTANT: Do not change this list without review from a DOM peer!
 | ||||||
|  |     {name: "MIDIInput", disabled: true}, | ||||||
|  | // IMPORTANT: Do not change this list without review from a DOM peer!
 | ||||||
|  |     {name: "MIDIMessageEvent", disabled: true}, | ||||||
|  | // IMPORTANT: Do not change this list without review from a DOM peer!
 | ||||||
|  |     {name: "MIDIOutputMap", disabled: true}, | ||||||
|  | // IMPORTANT: Do not change this list without review from a DOM peer!
 | ||||||
|  |     {name: "MIDIOutput", disabled: true}, | ||||||
|  | // IMPORTANT: Do not change this list without review from a DOM peer!
 | ||||||
|  |     {name: "MIDIPort", disabled: true}, | ||||||
| // IMPORTANT: Do not change this list without review from a DOM peer!
 | // IMPORTANT: Do not change this list without review from a DOM peer!
 | ||||||
|     {name: "MimeType", insecureContext: true}, |     {name: "MimeType", insecureContext: true}, | ||||||
| // IMPORTANT: Do not change this list without review from a DOM peer!
 | // IMPORTANT: Do not change this list without review from a DOM peer!
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 Kyle Machulis
						Kyle Machulis