├── .gitignore ├── images ├── forkme.png └── screenshot.jpg ├── test ├── resources │ ├── testharness.css │ ├── testharnessreport.js │ └── testharness.js └── index.html ├── index.html ├── js ├── voiceList.js ├── WebMIDIAPI.js ├── flatKeyboard.js ├── WebMIDIAPIWrapper.js └── ctrlWebMIDIAPIWrapper.js ├── README.md └── LICENSE.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /images/forkme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryoyakawai/WebMIDIAPIWrapper/HEAD/images/forkme.png -------------------------------------------------------------------------------- /images/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryoyakawai/WebMIDIAPIWrapper/HEAD/images/screenshot.jpg -------------------------------------------------------------------------------- /test/resources/testharness.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-family:DejaVu Sans, Bitstream Vera Sans, Arial, Sans; 3 | } 4 | 5 | #log .warning, 6 | #log .warning a { 7 | color: black; 8 | background: yellow; 9 | } 10 | 11 | #log .error, 12 | #log .error a { 13 | color: white; 14 | background: red; 15 | } 16 | 17 | #log pre { 18 | border: 1px solid black; 19 | padding: 1em; 20 | } 21 | 22 | section#summary { 23 | margin-bottom:1em; 24 | } 25 | 26 | table#results { 27 | border-collapse:collapse; 28 | table-layout:fixed; 29 | width:100%; 30 | } 31 | 32 | table#results th:first-child, 33 | table#results td:first-child { 34 | width:4em; 35 | } 36 | 37 | table#results th:last-child, 38 | table#results td:last-child { 39 | width:50%; 40 | } 41 | 42 | table#results.assertions th:last-child, 43 | table#results.assertions td:last-child { 44 | width:35%; 45 | } 46 | 47 | table#results th { 48 | padding:0; 49 | padding-bottom:0.5em; 50 | border-bottom:medium solid black; 51 | } 52 | 53 | table#results td { 54 | padding:1em; 55 | padding-bottom:0.5em; 56 | border-bottom:thin solid black; 57 | } 58 | 59 | tr.pass > td:first-child { 60 | color:green; 61 | } 62 | 63 | tr.fail > td:first-child { 64 | color:red; 65 | } 66 | 67 | tr.timeout > td:first-child { 68 | color:red; 69 | } 70 | 71 | tr.notrun > td:first-child { 72 | color:blue; 73 | } 74 | 75 | .pass > td:first-child, .fail > td:first-child, .timeout > td:first-child, .notrun > td:first-child { 76 | font-variant:small-caps; 77 | } 78 | 79 | table#results span { 80 | display:block; 81 | } 82 | 83 | table#results span.expected { 84 | font-family:DejaVu Sans Mono, Bitstream Vera Sans Mono, Monospace; 85 | white-space:pre; 86 | } 87 | 88 | table#results span.actual { 89 | font-family:DejaVu Sans Mono, Bitstream Vera Sans Mono, Monospace; 90 | white-space:pre; 91 | } 92 | 93 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Web MIDI API Wrapper Demo 5 | 8 | 9 | 10 |

Web MIDI API Wrapper Demo

11 | 12 |

[TO PARSE MIDI MESSAGE]

13 | 14 |
To test parse MIDI message
15 |
16 |
(Result Area)
17 |
18 | 19 |

[TO SEND MIDI MESSAGE]

20 | 21 | 22 | 23 | 24 |
25 |
26 |
27 | 28 | 29 |
30 |
31 | 32 | 33 | 34 | 35 | 36 |
37 | 38 |
39 |
40 |
41 |
42 |
43 |
44 | 45 | 46 |
47 |
48 | 49 | 50 |
51 |
52 |
53 | 54 | 55 |
56 |
57 |
58 |
59 | 60 |
61 |

Developed by Ryoya KAWAI.

62 | 63 | Fork me on GitHub 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /js/voiceList.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 Ryoya KAWAI 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | var VoiceList = function() { 18 | this.gmVoiceList = { 19 | 'instruments' : [ 20 | 'Acoustic Grand Piano' ,'Bright Acoustic Piano' 21 | ,'Electric Grand Piano' ,'Honky-tonk Piano' 22 | ,'Rhodes Piano' ,'Chorused Piano' 23 | ,'Harpsichord' ,'Clavinet' 24 | ,'Celesta' ,'Glockenspiel' 25 | ,'Music Box' ,'Vibraphone' 26 | ,'Marimba' ,'Xylophone' 27 | ,'Tubular Bells' ,'Dulcimer' 28 | ,'Hammond Organ' ,'Percussive Organ' 29 | ,'Rock Organ' ,'Church Organ' 30 | ,'Reed Organ' ,'Accordion' 31 | ,'Harmonica' ,'Tango Accordion' 32 | ,'Acoustic Guitar (nylon)' ,'Acoustic Guitar (steel)' 33 | ,'Electric Guitar (jazz)' ,'Electric Guitar (clean)' 34 | ,'Electric Guitar (muted)' ,'Overdriven Guitar' 35 | ,'Distortion Guitar' ,'Guitar Harmonics' 36 | ,'Acoustic Bass' ,'Electric Bass (finger)' 37 | ,'Electric Bass (pick)' ,'Fretless Bass' 38 | ,'Slap Bass 1' ,'Slap Bass 2' 39 | ,'Synth Bass 1' ,'Synth Bass 2' 40 | ,'Violin' ,'Viola' 41 | ,'Cello' ,'Contrabass' 42 | ,'Tremelo Strings' ,'Pizzicato Strings' 43 | ,'Orchestral Harp' ,'Timpani' 44 | ,'String Ensemble 1' ,'String Ensemble 2' 45 | ,'SynthStrings 1' ,'SynthStrings 2' 46 | ,'Choir Aahs' ,'Voice Oohs' 47 | ,'Synth Voice' ,'Orchestra Hit' 48 | ,'Trumpet' ,'Trombone' 49 | ,'Tuba' ,'Muted Trumpet' 50 | ,'French Horn' ,'Brass Section' 51 | ,'Synth Brass 1' ,'Synth Brass 2' 52 | ,'Soprano Sax' ,'Alto Sax' 53 | ,'Tenor Sax' ,'Baritone Sax' 54 | ,'Oboe' ,'English Horn' 55 | ,'Bassoon' ,'Clarinet' 56 | ,'Piccolo' ,'Flute' 57 | ,'Recorder' ,'Pan Flute' 58 | ,'Bottle Blow' ,'Shakuhachi' 59 | ,'Whistle' ,'Whistle' 60 | ,'Ocarina' ,'Lead 2 (sawtooth)' 61 | ,'Lead 3 (calliope lead)' ,'Lead 4 (chiff lead)' 62 | ,'Lead 5 (charang)' ,'Lead 6 (voice)' 63 | ,'Lead 7 (fifths)' ,'Lead 8 (bass + lead)' 64 | ,'Pad 1 (new age)' ,'Pad 2 (warm)' 65 | ,'Pad 3 (polysynth)' ,'Pad 4 (choir)' 66 | ,'Pad 5 (bowed)' ,'Pad 6 (metallic)' 67 | ,'Pad 7 (halo)' ,'Pad 8 (sweep)' 68 | ,'FX 1 (rain)' ,'FX 2 (soundtrack)' 69 | ,'FX 3 (crystal)' ,'FX 4 (atmosphere)' 70 | ,'FX 5 (brightness)' ,'FX 6 (goblins)' 71 | ,'FX 7 (echoes)' ,'FX 8 (sci-fi)' 72 | ,'Sitar' ,'Banjo' 73 | ,'Shamisen' ,'Koto' 74 | ,'Kalimba' ,'Bagpipe' 75 | ,'Fiddle' ,'Shanai' 76 | ,'Tinkle Bell' ,'Agogo' 77 | ,'Steel Drums' ,'Woodblock' 78 | ,'Taiko Drum' ,'Melodic Tom' 79 | ,'Synth Drum' ,'Reverse Cymbal' 80 | ,'Guitar Fret Noise' ,'Breath Noise' 81 | ,'Seashore' ,'Bird Tweet' 82 | ,'Telephone Ring' ,'Helicopter' 83 | ,'Applause' ,'Gunshot' 84 | ], 85 | 'drums': [ 86 | ] 87 | }; 88 | }; 89 | 90 | VoiceList.prototype.getGMVoiceName = function(type, num) { 91 | var out=''; 92 | switch(type) { 93 | case 'instruments': 94 | default: 95 | out = this.gmVoiceList.instruments[num]; 96 | break; 97 | case 'drums': 98 | out = this.gmVoiceList.drums[num]; 99 | break; 100 | } 101 | return out; 102 | }; 103 | 104 | 105 | var voiceList = new VoiceList(); 106 | 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Web MIDI API Wrapper 2 | 3 | ## Live Demo 4 | [https://ryoyakawai.github.io/WebMIDIAPIWrapper/](https://ryoyakawai.github.io/WebMIDIAPIWrapper/) 5 | 6 | - Mac users: Select "Apple DLS Synth (0.Apple DLS Synth)" for output device. 7 | - Windows users: Select "Microsoft GS Wavetable Synth (0.Microsoft GS Wavetable Synth)" for output device. 8 | 9 | In the demo, js/ctrlctrlWebmidilib.js is the main JavaScript file. 10 | 11 | ![](https://raw.github.com/ryoyakawai/WebMIDIAPIWrapper/master/images/screenshot.jpg) 12 | 13 | ## What is this? 14 | This script is JavaScript wrapper for developers who wants to play with Musical Instruments and web browser!! 15 | 16 | - What is MIDI? -> [MIDI@wikipedia](http://en.wikipedia.org/wiki/MIDI) 17 | - What is Web MIDI API? -> [Web MIDI API@W3C](http://webaudio.github.io/web-midi-api/) 18 | 19 | ## Purpose of using this Wrapper 20 | MIDI is well defined protocol. But to use the protocol you must learn 7bit code, such as NoteOn: 9nH, NoteOff: 8nH. 21 | With using this wrapper you do NOT have to learn those 7bit code. And also, you do NOT have to know about method in Web MIDI API. A thing you must know is really basic JavaScript only!! 22 | Therefore, you can easily build MIDI application only with the wrapper and JavaScript !! 23 | 24 | ## Supported MIDI Message (as is 2013 Aug 19) 25 | 26 | - NoteOn 27 | - NoteOff 28 | - PitchBend 29 | - Sustain 30 | - Modulation 31 | - AllSoundOff 32 | - ResetAllController 33 | - AllNoteOff 34 | 35 | ## Requirements (as is 2013 Aug 19) 36 | Web MIDI API is NOT fully implemented in browser. Only [Chrome Canary](http://www.google.co.jp/intl/ja/chrome/browser/canary.html) accept MIDI input. 37 | So, please install [Jazz-Plugin](http://jazz-soft.net/) developed by Jazz-Soft.net. 38 | 39 | ## How to use 40 | - clone this repository 41 | - link to the wrapper and [Web MIDI API Shim](https://github.com/cwilso/WebMIDIAPIShim) 42 | 43 | ``` 44 | 45 | 46 | ``` 47 | 48 | - create constructor 49 | 50 | ``` 51 | var wmaw = new WebMIDIAPIWrapper( false ); 52 | ``` 53 | 54 | 55 | - Set an EventHandler for **setMidiInputSelect** and **setMidiOutputSelect** 56 | - **setMidiInputSelect** : display MIDI *input* ports, and EventHandler of when MIDI *input* port is specified 57 | - **setMidiOutputSelect** : display MIDI *output* ports, and EventHandler of when MIDI *output* port is specified 58 | - Set an EventHandler named **onmidimessage** inside of setMidiInputSelect EventHandler. 59 | 60 | - Do initialization. 61 | 62 | ``` 63 | wmaw.init(); 64 | ``` 65 | 66 | ## Method to send MIDI Messages 67 | 68 | **'sendNoteOn(portNo, ch, note, velocity, time)'** 69 | *description*: send noteOn message to ch of portNo. 70 | *values*: 71 | 72 | - portNo: port number to send message 73 | - ch: ch number to send message in the port 74 | - note: [noteNo](http://upload.wikimedia.org/wikipedia/commons/7/7a/NoteNamesFrequenciesAndMidiNumbers.svg) to noteOn 75 | - velocity : velocity of note 76 | - time: the time at which to begin sending the data to the port 77 | 78 | **'sendNoteOff(portNo, ch, note, velocity, time)'** 79 | *description*: send noteOff message to ch of portNo. 80 | *values*: 81 | 82 | - portNo: port number to send message 83 | - ch: ch number to send message in the port 84 | - note: [noteNo](http://upload.wikimedia.org/wikipedia/commons/7/7a/NoteNamesFrequenciesAndMidiNumbers.svg) to noteOff 85 | - velocity : velocity of note (some MIDI device accept the value as speed of noteOff) 86 | - time: the time at which to begin sending the data to the port 87 | 88 | **'sendProgramChange(portNo, ch, programNo, time)'** 89 | *description*: send programchange message to change voice 90 | *values*: 91 | 92 | - portNo: port number to send message 93 | - ch: ch number to send message in the port 94 | - programNo: voice number to change 95 | - time: the time at which to begin sending the data to the port 96 | 97 | 98 | **'setPitchBendValue(portNo, min, max, center)'** 99 | *description*: set the range of the pitch change value.(sometimes -8192 to 8191(center:0), 0 to 16383(center:8192). This value is depends on the MIDI device.) 100 | *values*: 101 | 102 | - portNo: port number to send message 103 | - min: minimum number of pitchbend value 104 | - max: maximum number of pitchbend value 105 | - center: center number of pitchbend value 106 | 107 | **'sendPitchBend(portNo, ch, value, time)'** 108 | *description*: send pitchbend message by value 109 | *values*: 110 | 111 | - portNo: port number to send message 112 | - ch: ch number to send message in the port 113 | - value: value to change 114 | - time: the time at which to begin sending the data to the port 115 | 116 | **'sendSustainStatus(portNo, ch, status, time)'** 117 | *description*: send sustain message by status 118 | *values*: 119 | 120 | - portNo: port number to send message 121 | - ch: ch number to send message in the port 122 | - status: "on" or "off" 123 | - time: the time at which to begin sending the data to the port 124 | 125 | **'sendModulationValue(portNo, ch, value, time)'** 126 | *description*: send modulation message by value 127 | *values*: 128 | 129 | - portNo: port number to send message 130 | - ch: ch number to send message in the port 131 | - value: value to change 132 | - time: the time at which to begin sending the data to the port 133 | 134 | **'sendAllSoundOff(portNo, ch, time)'** 135 | *description*: send AllSoundOff message to stop all sounds(even sustain and sound reflection) in channel. 136 | *values*: 137 | 138 | - portNo: port number to send message 139 | - ch: ch number to send message in the port 140 | - time: the time at which to begin sending the data to the port 141 | 142 | **'sendResetAllController(portNo, ch, time)'** 143 | *description*: send ResetAllController to reset all controller(pitchbend, after-touch and so on) to initial value in channel. 144 | *values*: 145 | 146 | - portNo: port number to send message 147 | - ch: ch number to send message in the port 148 | - time: the time at which to begin sending the data to the port 149 | 150 | **'sendAllNoteOff(portNo, ch, time)'** 151 | *description*: send AllNoteOff message to stop all sounds(sustain and sound reflection are NOTE affected) in channel. 152 | *values*: 153 | 154 | - portNo: port number to send message 155 | - ch: ch number to send message in the port 156 | - time: the time at which to begin sending the data to the port 157 | 158 | **'sendRaw(portNo, msg, time)'** 159 | *description*: send Raw MIDI message by array. 160 | *values*: 161 | 162 | - portNo: port number to send message 163 | - msg: set MIDI message as array. In the array, either hexadecimal or decimal (even mixture format) is allow to specify. e.g.) [0x80, 72, 120] 164 | - time: the time at which to begin sending the data to the port 165 | 166 | **'initializePerformanceNow()'** 167 | *description*: Initialize start time. 168 | *values*: no valuables 169 | 170 | ## License 171 | 172 | The Apache License Version 2.0 173 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | -------------------------------------------------------------------------------- /js/WebMIDIAPI.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2013 Chris Wilson 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | // Initialize the MIDI library. 17 | (function (global) { 18 | 'use strict'; 19 | var midiIO, _requestMIDIAccess, _getMIDIAccess, MIDIAccess, _onReady, MIDIPort, MIDIInput, MIDIOutput, _midiProc; 20 | 21 | //init: create plugin 22 | if (!window.navigator.requestMIDIAccess) { 23 | window.navigator.requestMIDIAccess = _requestMIDIAccess; 24 | if (!window.navigator.getMIDIAccess) 25 | window.navigator.getMIDIAccess = _getMIDIAccess; 26 | } 27 | 28 | function Promise() { 29 | 30 | } 31 | 32 | Promise.prototype.then = function(accept, reject) { 33 | this.accept = accept; 34 | this.reject = reject; 35 | } 36 | 37 | Promise.prototype.succeed = function(access) { 38 | if (this.accept) 39 | this.accept(access); 40 | } 41 | 42 | Promise.prototype.fail = function(error) { 43 | if (this.reject) 44 | this.reject(error); 45 | } 46 | 47 | function _JazzInstance() { 48 | this.inputInUse = false; 49 | this.outputInUse = false; 50 | 51 | // load the Jazz plugin 52 | var o1 = document.createElement("object"); 53 | o1.id = "_Jazz" + Math.random() + "ie"; 54 | o1.classid = "CLSID:1ACE1618-1C7D-4561-AEE1-34842AA85E90"; 55 | 56 | this.activeX = o1; 57 | 58 | var o2 = document.createElement("object"); 59 | o2.id = "_Jazz" + Math.random(); 60 | o2.type="audio/x-jazz"; 61 | o1.appendChild(o2); 62 | 63 | this.objRef = o2; 64 | 65 | var e = document.createElement("p"); 66 | e.appendChild(document.createTextNode("This page requires the ")); 67 | 68 | var a = document.createElement("a"); 69 | a.appendChild(document.createTextNode("Jazz plugin")); 70 | a.href = "http://jazz-soft.net/"; 71 | 72 | e.appendChild(a); 73 | e.appendChild(document.createTextNode(".")); 74 | o2.appendChild(e); 75 | 76 | var insertionPoint = document.getElementById("MIDIPlugin"); 77 | if (!insertionPoint) { 78 | // Create hidden element 79 | var insertionPoint = document.createElement("div"); 80 | insertionPoint.id = "MIDIPlugin"; 81 | insertionPoint.style.position = "absolute"; 82 | insertionPoint.style.visibility = "hidden"; 83 | insertionPoint.style.left = "-9999px"; 84 | insertionPoint.style.top = "-9999px"; 85 | document.body.appendChild(insertionPoint); 86 | } 87 | insertionPoint.appendChild(o1); 88 | 89 | if (this.objRef.isJazz) 90 | this._Jazz = this.objRef; 91 | else if (this.activeX.isJazz) 92 | this._Jazz = this.activeX; 93 | else 94 | this._Jazz = null; 95 | if (this._Jazz) { 96 | this._Jazz._jazzTimeZero = this._Jazz.Time(); 97 | this._Jazz._perfTimeZero = window.performance.now(); 98 | } 99 | } 100 | 101 | function _requestMIDIAccess() { 102 | var access = new MIDIAccess(); 103 | return access._promise; 104 | } 105 | 106 | // API Methods 107 | 108 | function MIDIAccess() { 109 | this._jazzInstances = new Array(); 110 | this._jazzInstances.push( new _JazzInstance() ); 111 | this._promise = new Promise; 112 | 113 | if (this._jazzInstances[0]._Jazz) { 114 | this._Jazz = this._jazzInstances[0]._Jazz; 115 | window.setTimeout( _onReady.bind(this), 3 ); 116 | } else { 117 | window.setTimeout( _onNotReady.bind(this), 3 ); 118 | } 119 | } 120 | 121 | function _onReady() { 122 | if (this._promise) 123 | this._promise.succeed(this); 124 | } 125 | 126 | function _onNotReady() { 127 | if (this._promise) 128 | this._promise.fail( { code: 1 } ); 129 | } 130 | 131 | MIDIAccess.prototype.inputs = function( ) { 132 | if (!this._Jazz) 133 | return null; 134 | var list=this._Jazz.MidiInList(); 135 | var inputs = new Array( list.length ); 136 | 137 | for ( var i=0; i1)) { 315 | var sendObj = new Object; 316 | sendObj.jazz = this._jazzInstance; 317 | sendObj.data = data; 318 | 319 | window.setTimeout( _sendLater.bind(sendObj), delayBeforeSend ); 320 | } else { 321 | this._jazzInstance.MidiOutLong( data ); 322 | } 323 | return true; 324 | } 325 | 326 | }(window)); 327 | 328 | // Polyfill window.performance.now() if necessary. 329 | (function (exports) { 330 | var perf = {}, 331 | props; 332 | 333 | function findAlt() { 334 | var prefix = "moz,webkit,opera,ms".split(","), 335 | i = prefix.length, 336 | //worst case, we use Date.now() 337 | props = { 338 | value: function (start) { 339 | return function () { 340 | return Date.now() - start; 341 | } 342 | }(Date.now()) 343 | }; 344 | 345 | //seach for vendor prefixed version 346 | for (; i >= 0; i--) { 347 | if ((prefix[i] + "Now") in exports.performance) { 348 | props.value = function (method) { 349 | return function () { 350 | exports.performance[method](); 351 | } 352 | }(prefix[i] + "Now"); 353 | return props; 354 | } 355 | } 356 | 357 | //otherwise, try to use connectionStart 358 | if ("timing" in exports.performance && "connectStart" in exports.performance.timing) { 359 | //this pretty much approximates performance.now() to the millisecond 360 | props.value = function (start) { 361 | return function() { 362 | Date.now() - start; 363 | } 364 | }(exports.performance.timing.connectStart); 365 | } 366 | return props; 367 | } 368 | 369 | //if already defined, bail 370 | if (("performance" in exports) && ("now" in exports.performance)) 371 | return; 372 | if (!("performance" in exports)) 373 | Object.defineProperty(exports, "performance", { 374 | get: function () { 375 | return perf; 376 | }}); 377 | //otherwise, performance is there, but not "now()" 378 | 379 | props = findAlt(); 380 | Object.defineProperty(exports.performance, "now", props); 381 | }(window)); 382 | 383 | 384 | 385 | -------------------------------------------------------------------------------- /test/resources/testharnessreport.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is intended for vendors to implement 3 | * code needed to integrate testharness.js tests with their own test systems. 4 | * 5 | * The default implementation extracts metadata from the tests and validates 6 | * it against the cached version that should be present in the test source 7 | * file. If the cache is not found or is out of sync, source code suitable for 8 | * caching the metadata is optionally generated. 9 | * 10 | * The cached metadata is present for extraction by test processing tools that 11 | * are unable to execute javascript. 12 | * 13 | * Metadata is attached to tests via the properties parameter in the test 14 | * constructor. See testharness.js for details. 15 | * 16 | * Typically test system integration will attach callbacks when each test has 17 | * run, using add_result_callback(callback(test)), or when the whole test file 18 | * has completed, using 19 | * add_completion_callback(callback(tests, harness_status)). 20 | * 21 | * For more documentation about the callback functions and the 22 | * parameters they are called with see testharness.js 23 | */ 24 | 25 | 26 | 27 | var metadata_generator = { 28 | 29 | currentMetadata: {}, 30 | cachedMetadata: false, 31 | metadataProperties: ['help', 'assert', 'author'], 32 | 33 | error: function(message) { 34 | var messageElement = document.createElement('p'); 35 | messageElement.setAttribute('class', 'error'); 36 | this.appendText(messageElement, message); 37 | 38 | var summary = document.getElementById('summary'); 39 | if (summary) { 40 | summary.parentNode.insertBefore(messageElement, summary); 41 | } 42 | else { 43 | document.body.appendChild(messageElement); 44 | } 45 | }, 46 | 47 | /** 48 | * Ensure property value has contact information 49 | */ 50 | validateContact: function(test, propertyName) { 51 | var result = true; 52 | var value = test.properties[propertyName]; 53 | var values = Array.isArray(value) ? value : [value]; 54 | for (var index = 0; index < values.length; index++) { 55 | value = values[index]; 56 | var re = /(\S+)(\s*)<(.*)>(.*)/; 57 | if (! re.test(value)) { 58 | re = /(\S+)(\s+)(http[s]?:\/\/)(.*)/ 59 | if (! re.test(value)) { 60 | this.error('Metadata property "' + propertyName + 61 | '" for test: "' + test.name + 62 | '" must have name and contact information ' + 63 | '("name " or "name http(s)://")'); 64 | result = false; 65 | } 66 | } 67 | } 68 | return result; 69 | }, 70 | 71 | /** 72 | * Extract metadata from test object 73 | */ 74 | extractFromTest: function(test) { 75 | var testMetadata = {}; 76 | // filter out metadata from other properties in test 77 | for (var metaIndex = 0; metaIndex < this.metadataProperties.length; 78 | metaIndex++) { 79 | var meta = this.metadataProperties[metaIndex]; 80 | if (test.properties.hasOwnProperty(meta)) { 81 | if ('author' == meta) { 82 | this.validateContact(test, meta); 83 | } 84 | testMetadata[meta] = test.properties[meta]; 85 | } 86 | } 87 | return testMetadata; 88 | }, 89 | 90 | /** 91 | * Compare cached metadata to extracted metadata 92 | */ 93 | validateCache: function() { 94 | for (var testName in this.currentMetadata) { 95 | if (! this.cachedMetadata.hasOwnProperty(testName)) { 96 | return false; 97 | } 98 | var testMetadata = this.currentMetadata[testName]; 99 | var cachedTestMetadata = this.cachedMetadata[testName]; 100 | delete this.cachedMetadata[testName]; 101 | 102 | for (var metaIndex = 0; metaIndex < this.metadataProperties.length; 103 | metaIndex++) { 104 | var meta = this.metadataProperties[metaIndex]; 105 | if (cachedTestMetadata.hasOwnProperty(meta) && 106 | testMetadata.hasOwnProperty(meta)) { 107 | if (Array.isArray(cachedTestMetadata[meta])) { 108 | if (! Array.isArray(testMetadata[meta])) { 109 | return false; 110 | } 111 | if (cachedTestMetadata[meta].length == 112 | testMetadata[meta].length) { 113 | for (var index = 0; 114 | index < cachedTestMetadata[meta].length; 115 | index++) { 116 | if (cachedTestMetadata[meta][index] != 117 | testMetadata[meta][index]) { 118 | return false; 119 | } 120 | } 121 | } 122 | else { 123 | return false; 124 | } 125 | } 126 | else { 127 | if (Array.isArray(testMetadata[meta])) { 128 | return false; 129 | } 130 | if (cachedTestMetadata[meta] != testMetadata[meta]) { 131 | return false; 132 | } 133 | } 134 | } 135 | else if (cachedTestMetadata.hasOwnProperty(meta) || 136 | testMetadata.hasOwnProperty(meta)) { 137 | return false; 138 | } 139 | } 140 | } 141 | for (var testName in this.cachedMetadata) { 142 | return false; 143 | } 144 | return true; 145 | }, 146 | 147 | appendText: function(elemement, text) { 148 | elemement.appendChild(document.createTextNode(text)); 149 | }, 150 | 151 | jsonifyArray: function(arrayValue, indent) { 152 | var output = '['; 153 | 154 | if (1 == arrayValue.length) { 155 | output += JSON.stringify(arrayValue[0]); 156 | } 157 | else { 158 | for (var index = 0; index < arrayValue.length; index++) { 159 | if (0 < index) { 160 | output += ',\n ' + indent; 161 | } 162 | output += JSON.stringify(arrayValue[index]); 163 | } 164 | } 165 | output += ']'; 166 | return output; 167 | }, 168 | 169 | jsonifyObject: function(objectValue, indent) { 170 | var output = '{'; 171 | 172 | var count = 0; 173 | for (var property in objectValue) { 174 | ++count; 175 | if (Array.isArray(objectValue[property]) || 176 | ('object' == typeof(value))) { 177 | ++count; 178 | } 179 | } 180 | if (1 == count) { 181 | for (var property in objectValue) { 182 | output += ' "' + property + '": ' 183 | + JSON.stringify(objectValue[property]) 184 | + ' '; 185 | } 186 | } 187 | else { 188 | var first = true; 189 | for (var property in objectValue) { 190 | if (! first) { 191 | output += ','; 192 | } 193 | first = false; 194 | output += '\n ' + indent + '"' + property + '": '; 195 | var value = objectValue[property]; 196 | if (Array.isArray(value)) { 197 | output += this.jsonifyArray(value, indent + 198 | ' '.substr(0, 5 + property.length)); 199 | } 200 | else if ('object' == typeof(value)) { 201 | output += this.jsonifyObject(value, indent + ' '); 202 | } 203 | else { 204 | output += JSON.stringify(value); 205 | } 206 | } 207 | if (1 < output.length) { 208 | output += '\n' + indent; 209 | } 210 | } 211 | output += '}'; 212 | return output; 213 | }, 214 | 215 | /** 216 | * Generate javascript source code for captured metadata 217 | * Metadata is in pretty-printed JSON format 218 | */ 219 | generateSource: function() { 220 | var source = 221 | '\n'; 224 | return source; 225 | }, 226 | 227 | /** 228 | * Add element containing metadata source code 229 | */ 230 | addSourceElement: function(event) { 231 | var sourceWrapper = document.createElement('div'); 232 | sourceWrapper.setAttribute('id', 'metadata_source'); 233 | 234 | var instructions = document.createElement('p'); 235 | if (this.cachedMetadata) { 236 | this.appendText(instructions, 237 | 'Replace the existing 8 | 9 | 10 | 11 |

[TEST] method: parseMIDIMessage

12 |
13 | 14 | 15 | 16 | 436 | 437 |
438 |
439 |
440 | 441 | 452 | 453 | 454 | 455 | 456 | -------------------------------------------------------------------------------- /js/ctrlWebMIDIAPIWrapper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 Ryoya KAWAI 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | try { 18 | if(typeof navigator.requestMIDIAccess!=="function") { 19 | throw new Error("navigator.requestMIDIAccess is NOT a function."); 20 | } 21 | document.getElementById("errorText").style.setProperty("display", "none"); 22 | 23 | var wmaw = new WebMIDIAPIWrapper( true ); 24 | var config = { "programNo": 0 }; 25 | var timerId; 26 | var disp=false; 27 | var fKey = new FlatKeyboard("keyboard"); 28 | 29 | // to parse midi message 30 | var parseMIDIInput=document.createElement("input"); 31 | parseMIDIInput.id="midiMsg"; 32 | parseMIDIInput.size="50"; 33 | parseMIDIInput.style.setProperty("border-radius", "4px"); 34 | parseMIDIInput.value="0x90 0x4f 0x7f"; 35 | var parseMIDIInputB=document.createElement("input"); 36 | parseMIDIInputB.id="midiMsgB"; parseMIDIInputB.type="button"; 37 | parseMIDIInputB.value="Parse MIDI Message"; 38 | document.querySelector("#midiMsgTextBox").appendChild(parseMIDIInput); 39 | document.querySelector("#midiMsgTextBox").appendChild(parseMIDIInputB); 40 | document.querySelector("#midiMsgB").addEventListener("click", function(){ 41 | var midiMsg=document.querySelector("#midiMsg").value; 42 | if(midiMsg=="") { 43 | document.querySelector("#midiMsg").style.setProperty("border", "2px solid #dc143c"); 44 | document.querySelector("#midiMsg").style.setProperty("background-color", "#db99a6"); 45 | setTimeout(function(){ 46 | document.querySelector("#midiMsg").style.removeProperty("border"); 47 | document.querySelector("#midiMsg").style.removeProperty("background-color"); 48 | }, 400); 49 | } else { 50 | var mm=midiMsg.split(" "); 51 | for(var i=0; i"); 57 | if(typeof result.subType!="undefined") tmp.push("[subType] " + result.subType + "
"); 58 | if(typeof result.event.channel!="undefined") tmp.push("[channel] " + result.event.channel + "
"); 59 | if(typeof result.event.ctrlName!="undefined") tmp.push("[ctrlName] " + result.event.ctrlName + "
"); 60 | if(typeof result.event.ctrlStatus!="undefined") tmp.push("[ctrlStatus] " + result.event.ctrlStatus + "
"); 61 | if(typeof result.event.programNumber!="undefined") tmp.push("[programNo] " + result.event.programNumber + "
"); 62 | if(typeof result.event.valueType!="undefined") tmp.push("[Type] " + result.event.valueType + "
"); 63 | if(typeof result.event.noteNumber!="undefined") tmp.push("[noteNum] " + result.event.noteNumber + "
"); 64 | if(typeof result.event.velocity!="undefined") tmp.push("[velocity] " + result.event.velocity + "
"); 65 | if(typeof result.event.value!="undefined") tmp.push("[value] " + result.event.value + "
"); 66 | if(typeof result.event.amount!="undefined") tmp.push("[amount] " + result.event.amount + "
"); 67 | console.log(result); 68 | var raw=""; 69 | for(var i=0; iwmaw.ports.out[0].pitchBendValue.max) { 221 | d=-1; 222 | } else if(val<=wmaw.ports.out[0].pitchBendValue.min) { 223 | d=1; 224 | clearInterval(t); 225 | t=false; 226 | } 227 | val = val + d*128; 228 | var ch=document.querySelector("#changeChValue").value-1;; 229 | wmaw.sendPitchBend(0, ch, val, 0); 230 | if(t==false) { 231 | wmaw.sendNoteOff(0, ch, 72, 120, 0); 232 | wmaw.sendPitchBend(0, ch, wmaw.ports.out[0].pitchBendValue.center, 0); 233 | } 234 | document.getElementById("bendvalue").innerHTML=val; 235 | }, 10); 236 | }); 237 | 238 | var prgChange = document.createElement("input"); 239 | prgChange.id="prgChange"; prgChange.type="range"; 240 | prgChange.min=0, prgChange.max=127, prgChange.value=0; 241 | prgChange.style.setProperty("width", "200px"); 242 | prgChange.addEventListener("change", function() { 243 | document.getElementById("voicename").innerHTML=this.value+". "+voiceList.getGMVoiceName("instruments", this.value); 244 | var ch=document.querySelector("#changeChValue").value-1;; 245 | wmaw.sendProgramChange(0, ch, this.value, 0); 246 | config.programNo=this.value; 247 | }); 248 | document.getElementById("prgChange").appendChild(prgChange); 249 | document.getElementById("voicename").innerHTML="0. "+voiceList.getGMVoiceName("instruments", 0); 250 | document.getElementById("prgChange").appendChild(prgChange); 251 | document.getElementById("prgChangeText").style.removeProperty("visibility"); 252 | document.getElementById("prgChangeText01").style.removeProperty("visibility"); 253 | document.getElementById("prgChangeText02").style.removeProperty("visibility"); 254 | 255 | var changeCh = document.createElement("input"); 256 | changeCh.id="changeChValue"; changeCh.type="number"; 257 | changeCh.style.setProperty("width", "40px"); 258 | changeCh.min=1, changeCh.max=16, changeCh.value=1; 259 | document.getElementById("changeCh").appendChild(changeCh); 260 | document.getElementById("changeCh").style.setProperty("padding", "3px"); 261 | document.getElementById("changeCh").style.setProperty("margin", "0px 0px 10px 0px"); 262 | document.getElementById("control").style.removeProperty("visibility"); 263 | 264 | 265 | var fireMod = document.createElement("input"); 266 | fireMod.id="fireMod"; fireMod.type="button"; 267 | fireMod.value="Fire MIDI (Mod)"; 268 | document.getElementById("modFireButton").appendChild(fireMod); 269 | document.getElementById("fireMod").addEventListener("click", function() { 270 | wmaw.setPitchBendValue(0, 0, 16384, 8192); 271 | //wmaw.sendProgramChange(0, 0, 34, 0); 272 | 273 | var ch=document.querySelector("#changeChValue").value-1;; 274 | 275 | wmaw.sendNoteOn(0, ch, 72, 120, 0); 276 | var val=0; 277 | var d=1; 278 | var t=null; 279 | t=setInterval(function() { 280 | val++; 281 | if(val>=127) { 282 | d=-1; 283 | } else if(val<=0) { 284 | clearInterval(t); 285 | t=false; 286 | } 287 | if(t==false) { 288 | wmaw.sendNoteOff(0, ch, 72, 120, 0); 289 | val=0; 290 | } else { 291 | val=val+d*2; 292 | } 293 | wmaw.sendModulationValue(0, ch, val, 0); 294 | document.getElementById("modvalue").innerHTML=val; 295 | }, 50); 296 | 297 | }); 298 | 299 | 300 | var fireTri = document.createElement("input"); 301 | fireTri.id="fireSustain"; fireTri.type="button"; 302 | fireTri.value="Fire Tritone"; 303 | document.getElementById("fireTri").appendChild(fireTri); 304 | document.getElementById("fireTriText").style.removeProperty("visibility"); 305 | document.getElementById("fireTri").addEventListener("click", function() { 306 | wmaw.initializePerformanceNow(); 307 | 308 | var ch=document.querySelector("#changeChValue").value-1;; 309 | 310 | wmaw.sendProgramChange(0, ch, 12, 0); 311 | wmaw.sendProgramChange(0, ch, config.programNo, 370); 312 | 313 | wmaw.sendNoteOn(0, ch, 62, 127, 0); 314 | wmaw.sendNoteOff(0, ch, 62, 0, 120); 315 | 316 | wmaw.sendNoteOn(0, ch, 69, 127, 120); 317 | wmaw.sendNoteOff(0, ch, 69, 0, 240); 318 | 319 | wmaw.sendNoteOn(0, ch, 74, 127, 240); 320 | wmaw.sendNoteOff(0, ch, 74, 0, 360); 321 | 322 | }); 323 | 324 | var fireSustain2 = document.createElement("input"); 325 | fireSustain2.id="fireSustain2"; fireSustain2.type="button"; 326 | fireSustain2.value="Fire MIDI (Sustain one Note)"; 327 | document.getElementById("sustain2FireButton").appendChild(fireSustain2); 328 | document.getElementById("fireSustain2").addEventListener("click", function() { 329 | wmaw.initializePerformanceNow(); 330 | 331 | var ch=document.querySelector("#changeChValue").value-1;; 332 | 333 | wmaw.sendSustainStatus(0, ch, "on", 0); 334 | wmaw.sendSustainStatus(0, ch, "off", 5000); 335 | 336 | wmaw.sendNoteOn(0, ch, 72, 127, 0); 337 | wmaw.sendNoteOff(0, ch, 72, 127, 5000); 338 | }); 339 | 340 | document.getElementById("allOffText").style.removeProperty("visibility"); 341 | var allSndOff = document.createElement("input"); 342 | allSndOff.id="allSnfOff"; allSndOff.type="button"; 343 | allSndOff.value="AllSoundOff"; 344 | document.getElementById("allSndOff").appendChild(allSndOff); 345 | document.getElementById("allSndOff").addEventListener("click", function() { 346 | wmaw.initializePerformanceNow(); 347 | for(var i=0; i<16; i++) { 348 | wmaw.sendAllSoundOff(0, i, 0); 349 | } 350 | }); 351 | 352 | 353 | var allNoteOff = document.createElement("input"); 354 | allNoteOff.id="allSnfOff"; allNoteOff.type="button"; 355 | allNoteOff.value="AllNoteOff"; 356 | document.getElementById("allNoteOff").appendChild(allNoteOff); 357 | document.getElementById("allNoteOff").addEventListener("click", function() { 358 | wmaw.initializePerformanceNow(); 359 | wmaw.sendAllNoteOff(0, 0, 0); 360 | }); 361 | 362 | document.getElementById("sendRaw01").style.removeProperty("visibility"); 363 | var sendRaw = document.createElement("input"); 364 | sendRaw.id="sendRaw"; sendRaw.type="button"; 365 | sendRaw.value="Fire MIDI (sendRaw())"; 366 | document.getElementById("sendRaw").appendChild(sendRaw); 367 | document.getElementById("sendRaw").addEventListener("click", function() { 368 | wmaw.initializePerformanceNow(); 369 | var ch=document.querySelector("#changeChValue").value-1;; 370 | var chH=ch.toString(16); 371 | console.log(chH); 372 | 373 | wmaw.sendRaw(0, ["0x9"+chH, 72, 60], 0); 374 | wmaw.sendRaw(0, ["0x8"+chH, 72, 60], 500); 375 | 376 | wmaw.sendRaw(0, ["0x9"+chH, 74, 80], 500); 377 | wmaw.sendRaw(0, ["0x8"+chH, 74, 80], 1000); 378 | 379 | wmaw.sendRaw(0, ["0x9"+chH, 76, 100], 1000); 380 | wmaw.sendRaw(0, ["0x8"+chH, 76, 100], 1500); 381 | 382 | wmaw.sendRaw(0, ["0x9"+chH, 77, 120], 1500); 383 | wmaw.sendRaw(0, ["0x8"+chH, 77, 120], 2000); 384 | 385 | wmaw.sendRaw(0, ["0x9"+chH, 79, 127], 2000); 386 | wmaw.sendRaw(0, ["0x8"+chH, 79, 127], 2500); 387 | 388 | }); 389 | } 390 | }); 391 | 392 | }; 393 | 394 | wmaw.initMidi(); 395 | 396 | } catch (e) { 397 | document.getElementById("errorText").style.removeProperty("visibility"); 398 | document.getElementById("errorText").innerHTML='Something went wrong....
I think you might need to install Jazz-Plugin.'; 399 | console.log(e.message); 400 | } 401 | 402 | 403 | 404 | -------------------------------------------------------------------------------- /test/resources/testharness.js: -------------------------------------------------------------------------------- 1 | /* 2 | Distributed under both the W3C Test Suite License [1] and the W3C 3 | 3-clause BSD License [2]. To contribute to a W3C Test Suite, see the 4 | policies and contribution forms [3]. 5 | 6 | [1] http://www.w3.org/Consortium/Legal/2008/04-testsuite-license 7 | [2] http://www.w3.org/Consortium/Legal/2008/03-bsd-license 8 | [3] http://www.w3.org/2004/10/27-testcases 9 | */ 10 | 11 | /* 12 | * == Introduction == 13 | * 14 | * This file provides a framework for writing testcases. It is intended to 15 | * provide a convenient API for making common assertions, and to work both 16 | * for testing synchronous and asynchronous DOM features in a way that 17 | * promotes clear, robust, tests. 18 | * 19 | * == Basic Usage == 20 | * 21 | * To use this file, import the script and the testharnessreport script into 22 | * the test document: 23 | * 24 | * 25 | * 26 | * Within each file one may define one or more tests. Each test is atomic 27 | * in the sense that a single test has a single result (pass/fail/timeout). 28 | * Within each test one may have a number of asserts. The test fails at the 29 | * first failing assert, and the remainder of the test is (typically) not run. 30 | * 31 | * If the file containing the tests is a HTML file with an element of id "log" 32 | * this will be populated with a table containing the test results after all 33 | * the tests have run. 34 | * 35 | * NOTE: By default tests must be created before the load event fires. For ways 36 | * to create tests after the load event, see "Determining when all tests 37 | * are complete", below 38 | * 39 | * == Synchronous Tests == 40 | * 41 | * To create a synchronous test use the test() function: 42 | * 43 | * test(test_function, name, properties) 44 | * 45 | * test_function is a function that contains the code to test. For example a 46 | * trivial passing test would be: 47 | * 48 | * test(function() {assert_true(true)}, "assert_true with true") 49 | * 50 | * The function passed in is run in the test() call. 51 | * 52 | * properties is an object that overrides default test properties. The 53 | * recognised properties are: 54 | * timeout - the test timeout in ms 55 | * 56 | * e.g. 57 | * test(test_function, "Sample test", {timeout:1000}) 58 | * 59 | * would run test_function with a timeout of 1s. 60 | * 61 | * Additionally, test-specific metadata can be passed in the properties. These 62 | * are used when the individual test has different metadata from that stored 63 | * in the . 64 | * The recognized metadata properties are: 65 | * 66 | * help - The url of the part of the specification being tested 67 | * 68 | * assert - A human readable description of what the test is attempting 69 | * to prove 70 | * 71 | * author - Name and contact information for the author of the test in the 72 | * format: "Name " or "Name http://contact/url" 73 | * 74 | * == Asynchronous Tests == 75 | * 76 | * Testing asynchronous features is somewhat more complex since the result of 77 | * a test may depend on one or more events or other callbacks. The API provided 78 | * for testing these features is indended to be rather low-level but hopefully 79 | * applicable to many situations. 80 | * 81 | * To create a test, one starts by getting a Test object using async_test: 82 | * 83 | * async_test(name, properties) 84 | * 85 | * e.g. 86 | * var t = async_test("Simple async test") 87 | * 88 | * Assertions can be added to the test by calling the step method of the test 89 | * object with a function containing the test assertions: 90 | * 91 | * t.step(function() {assert_true(true)}); 92 | * 93 | * When all the steps are complete, the done() method must be called: 94 | * 95 | * t.done(); 96 | * 97 | * As a convenience, async_test can also takes a function as first argument. 98 | * This function is called with the test object as both its `this` object and 99 | * first argument. The above example can be rewritten as: 100 | * 101 | * async_test(function(t) { 102 | * object.some_event = function() { 103 | * t.step(function (){assert_true(true); t.done();}); 104 | * }; 105 | * }, "Simple async test"); 106 | * 107 | * which avoids cluttering the global scope with references to async 108 | * tests instances. 109 | * 110 | * The properties argument is identical to that for test(). 111 | * 112 | * In many cases it is convenient to run a step in response to an event or a 113 | * callback. A convenient method of doing this is through the step_func method 114 | * which returns a function that, when called runs a test step. For example 115 | * 116 | * object.some_event = t.step_func(function(e) {assert_true(e.a)}); 117 | * 118 | * == Making assertions == 119 | * 120 | * Functions for making assertions start assert_ 121 | * The best way to get a list is to look in this file for functions names 122 | * matching that pattern. The general signature is 123 | * 124 | * assert_something(actual, expected, description) 125 | * 126 | * although not all assertions precisely match this pattern e.g. assert_true 127 | * only takes actual and description as arguments. 128 | * 129 | * The description parameter is used to present more useful error messages when 130 | * a test fails 131 | * 132 | * NOTE: All asserts must be located in a test() or a step of an async_test(). 133 | * asserts outside these places won't be detected correctly by the harness 134 | * and may cause a file to stop testing. 135 | * 136 | * == Setup == 137 | * 138 | * Sometimes tests require non-trivial setup that may fail. For this purpose 139 | * there is a setup() function, that may be called with one or two arguments. 140 | * The two argument version is: 141 | * 142 | * setup(func, properties) 143 | * 144 | * The one argument versions may omit either argument. 145 | * func is a function to be run synchronously. setup() becomes a no-op once 146 | * any tests have returned results. Properties are global properties of the test 147 | * harness. Currently recognised properties are: 148 | * 149 | * timeout - The time in ms after which the harness should stop waiting for 150 | * tests to complete (this is different to the per-test timeout 151 | * because async tests do not start their timer until .step is called) 152 | * 153 | * explicit_done - Wait for an explicit call to done() before declaring all 154 | * tests complete (see below) 155 | * 156 | * output_document - The document to which results should be logged. By default 157 | * this is the current document but could be an ancestor 158 | * document in some cases e.g. a SVG test loaded in an HTML 159 | * wrapper 160 | * 161 | * explicit_timeout - disable file timeout; only stop waiting for results 162 | * when the timeout() function is called (typically for 163 | * use when integrating with some existing test framework 164 | * that has its own timeout mechanism). 165 | * 166 | * == Determining when all tests are complete == 167 | * 168 | * By default the test harness will assume there are no more results to come 169 | * when: 170 | * 1) There are no Test objects that have been created but not completed 171 | * 2) The load event on the document has fired 172 | * 173 | * This behaviour can be overridden by setting the explicit_done property to 174 | * true in a call to setup(). If explicit_done is true, the test harness will 175 | * not assume it is done until the global done() function is called. Once done() 176 | * is called, the two conditions above apply like normal. 177 | * 178 | * == Generating tests == 179 | * 180 | * NOTE: this functionality may be removed 181 | * 182 | * There are scenarios in which is is desirable to create a large number of 183 | * (synchronous) tests that are internally similar but vary in the parameters 184 | * used. To make this easier, the generate_tests function allows a single 185 | * function to be called with each set of parameters in a list: 186 | * 187 | * generate_tests(test_function, parameter_lists, properties) 188 | * 189 | * For example: 190 | * 191 | * generate_tests(assert_equals, [ 192 | * ["Sum one and one", 1+1, 2], 193 | * ["Sum one and zero", 1+0, 1] 194 | * ]) 195 | * 196 | * Is equivalent to: 197 | * 198 | * test(function() {assert_equals(1+1, 2)}, "Sum one and one") 199 | * test(function() {assert_equals(1+0, 1)}, "Sum one and zero") 200 | * 201 | * Note that the first item in each parameter list corresponds to the name of 202 | * the test. 203 | * 204 | * The properties argument is identical to that for test(). This may be a 205 | * single object (used for all generated tests) or an array. 206 | * 207 | * == Callback API == 208 | * 209 | * The framework provides callbacks corresponding to 3 events: 210 | * 211 | * start - happens when the first Test is created 212 | * result - happens when a test result is recieved 213 | * complete - happens when all results are recieved 214 | * 215 | * The page defining the tests may add callbacks for these events by calling 216 | * the following methods: 217 | * 218 | * add_start_callback(callback) - callback called with no arguments 219 | * add_result_callback(callback) - callback called with a test argument 220 | * add_completion_callback(callback) - callback called with an array of tests 221 | * and an status object 222 | * 223 | * tests have the following properties: 224 | * status: A status code. This can be compared to the PASS, FAIL, TIMEOUT and 225 | * NOTRUN properties on the test object 226 | * message: A message indicating the reason for failure. In the future this 227 | * will always be a string 228 | * 229 | * The status object gives the overall status of the harness. It has the 230 | * following properties: 231 | * status: Can be compared to the OK, ERROR and TIMEOUT properties 232 | * message: An error message set when the status is ERROR 233 | * 234 | * == External API == 235 | * 236 | * In order to collect the results of multiple pages containing tests, the test 237 | * harness will, when loaded in a nested browsing context, attempt to call 238 | * certain functions in each ancestor and opener browsing context: 239 | * 240 | * start - start_callback 241 | * result - result_callback 242 | * complete - completion_callback 243 | * 244 | * These are given the same arguments as the corresponding internal callbacks 245 | * described above. 246 | * 247 | * == External API through cross-document messaging == 248 | * 249 | * Where supported, the test harness will also send messages using 250 | * cross-document messaging to each ancestor and opener browsing context. Since 251 | * it uses the wildcard keyword (*), cross-origin communication is enabled and 252 | * script on different origins can collect the results. 253 | * 254 | * This API follows similar conventions as those described above only slightly 255 | * modified to accommodate message event API. Each message is sent by the harness 256 | * is passed a single vanilla object, available as the `data` property of the 257 | * event object. These objects are structures as follows: 258 | * 259 | * start - { type: "start" } 260 | * result - { type: "result", test: Test } 261 | * complete - { type: "complete", tests: [Test, ...], status: TestsStatus } 262 | * 263 | * == List of assertions == 264 | * 265 | * assert_true(actual, description) 266 | * asserts that /actual/ is strictly true 267 | * 268 | * assert_false(actual, description) 269 | * asserts that /actual/ is strictly false 270 | * 271 | * assert_equals(actual, expected, description) 272 | * asserts that /actual/ is the same value as /expected/ 273 | * 274 | * assert_not_equals(actual, expected, description) 275 | * asserts that /actual/ is a different value to /expected/. Yes, this means 276 | * that "expected" is a misnomer 277 | * 278 | * assert_in_array(actual, expected, description) 279 | * asserts that /expected/ is an Array, and /actual/ is equal to one of the 280 | * members -- expected.indexOf(actual) != -1 281 | * 282 | * assert_array_equals(actual, expected, description) 283 | * asserts that /actual/ and /expected/ have the same length and the value of 284 | * each indexed property in /actual/ is the strictly equal to the corresponding 285 | * property value in /expected/ 286 | * 287 | * assert_approx_equals(actual, expected, epsilon, description) 288 | * asserts that /actual/ is a number within +/- /epsilon/ of /expected/ 289 | * 290 | * assert_less_than(actual, expected, description) 291 | * asserts that /actual/ is a number less than /expected/ 292 | * 293 | * assert_greater_than(actual, expected, description) 294 | * asserts that /actual/ is a number greater than /expected/ 295 | * 296 | * assert_less_than_equal(actual, expected, description) 297 | * asserts that /actual/ is a number less than or equal to /expected/ 298 | * 299 | * assert_greater_than_equal(actual, expected, description) 300 | * asserts that /actual/ is a number greater than or equal to /expected/ 301 | * 302 | * assert_regexp_match(actual, expected, description) 303 | * asserts that /actual/ matches the regexp /expected/ 304 | * 305 | * assert_class_string(object, class_name, description) 306 | * asserts that the class string of /object/ as returned in 307 | * Object.prototype.toString is equal to /class_name/. 308 | * 309 | * assert_own_property(object, property_name, description) 310 | * assert that object has own property property_name 311 | * 312 | * assert_inherits(object, property_name, description) 313 | * assert that object does not have an own property named property_name 314 | * but that property_name is present in the prototype chain for object 315 | * 316 | * assert_idl_attribute(object, attribute_name, description) 317 | * assert that an object that is an instance of some interface has the 318 | * attribute attribute_name following the conditions specified by WebIDL 319 | * 320 | * assert_readonly(object, property_name, description) 321 | * assert that property property_name on object is readonly 322 | * 323 | * assert_throws(code, func, description) 324 | * code - the expected exception: 325 | * o string: the thrown exception must be a DOMException with the given 326 | * name, e.g., "TimeoutError" (for compatibility with existing 327 | * tests, a constant is also supported, e.g., "TIMEOUT_ERR") 328 | * o object: the thrown exception must have a property called "name" that 329 | * matches code.name 330 | * o null: allow any exception (in general, one of the options above 331 | * should be used) 332 | * func - a function that should throw 333 | * 334 | * assert_unreached(description) 335 | * asserts if called. Used to ensure that some codepath is *not* taken e.g. 336 | * an event does not fire. 337 | * 338 | * assert_any(assert_func, actual, expected_array, extra_arg_1, ... extra_arg_N) 339 | * asserts that one assert_func(actual, expected_array_N, extra_arg1, ..., extra_arg_N) 340 | * is true for some expected_array_N in expected_array. This only works for assert_func 341 | * with signature assert_func(actual, expected, args_1, ..., args_N). Note that tests 342 | * with multiple allowed pass conditions are bad practice unless the spec specifically 343 | * allows multiple behaviours. Test authors should not use this method simply to hide 344 | * UA bugs. 345 | * 346 | * assert_exists(object, property_name, description) 347 | * *** deprecated *** 348 | * asserts that object has an own property property_name 349 | * 350 | * assert_not_exists(object, property_name, description) 351 | * *** deprecated *** 352 | * assert that object does not have own property property_name 353 | */ 354 | 355 | (function () 356 | { 357 | var debug = false; 358 | // default timeout is 5 seconds, test can override if needed 359 | var settings = { 360 | output:true, 361 | timeout:5000, 362 | test_timeout:2000 363 | }; 364 | 365 | var xhtml_ns = "http://www.w3.org/1999/xhtml"; 366 | 367 | // script_prefix is used by Output.prototype.show_results() to figure out 368 | // where to get testharness.css from. It's enclosed in an extra closure to 369 | // not pollute the library's namespace with variables like "src". 370 | var script_prefix = null; 371 | (function () 372 | { 373 | var scripts = document.getElementsByTagName("script"); 374 | for (var i = 0; i < scripts.length; i++) 375 | { 376 | if (scripts[i].src) 377 | { 378 | var src = scripts[i].src; 379 | } 380 | else if (scripts[i].href) 381 | { 382 | //SVG case 383 | var src = scripts[i].href.baseVal; 384 | } 385 | if (src && src.slice(src.length - "testharness.js".length) === "testharness.js") 386 | { 387 | script_prefix = src.slice(0, src.length - "testharness.js".length); 388 | break; 389 | } 390 | } 391 | })(); 392 | 393 | /* 394 | * API functions 395 | */ 396 | 397 | var name_counter = 0; 398 | function next_default_name() 399 | { 400 | //Don't use document.title to work around an Opera bug in XHTML documents 401 | var title = document.getElementsByTagName("title")[0]; 402 | var prefix = (title && title.firstChild && title.firstChild.data) || "Untitled"; 403 | var suffix = name_counter > 0 ? " " + name_counter : ""; 404 | name_counter++; 405 | return prefix + suffix; 406 | } 407 | 408 | function test(func, name, properties) 409 | { 410 | var test_name = name ? name : next_default_name(); 411 | properties = properties ? properties : {}; 412 | var test_obj = new Test(test_name, properties); 413 | test_obj.step(func); 414 | if (test_obj.status === test_obj.NOTRUN) { 415 | test_obj.done(); 416 | } 417 | } 418 | 419 | function async_test(func, name, properties) 420 | { 421 | if (typeof func !== "function") { 422 | properties = name; 423 | name = func; 424 | func = null; 425 | } 426 | var test_name = name ? name : next_default_name(); 427 | properties = properties ? properties : {}; 428 | var test_obj = new Test(test_name, properties); 429 | if (func) { 430 | test_obj.step(func, test_obj, test_obj); 431 | } 432 | return test_obj; 433 | } 434 | 435 | function setup(func_or_properties, maybe_properties) 436 | { 437 | var func = null; 438 | var properties = {}; 439 | if (arguments.length === 2) { 440 | func = func_or_properties; 441 | properties = maybe_properties; 442 | } else if (func_or_properties instanceof Function){ 443 | func = func_or_properties; 444 | } else { 445 | properties = func_or_properties; 446 | } 447 | tests.setup(func, properties); 448 | output.setup(properties); 449 | } 450 | 451 | function done() { 452 | tests.end_wait(); 453 | } 454 | 455 | function generate_tests(func, args, properties) { 456 | forEach(args, function(x, i) 457 | { 458 | var name = x[0]; 459 | test(function() 460 | { 461 | func.apply(this, x.slice(1)); 462 | }, 463 | name, 464 | Array.isArray(properties) ? properties[i] : properties); 465 | }); 466 | } 467 | 468 | function on_event(object, event, callback) 469 | { 470 | object.addEventListener(event, callback, false); 471 | } 472 | 473 | expose(test, 'test'); 474 | expose(async_test, 'async_test'); 475 | expose(generate_tests, 'generate_tests'); 476 | expose(setup, 'setup'); 477 | expose(done, 'done'); 478 | expose(on_event, 'on_event'); 479 | 480 | /* 481 | * Return a string truncated to the given length, with ... added at the end 482 | * if it was longer. 483 | */ 484 | function truncate(s, len) 485 | { 486 | if (s.length > len) { 487 | return s.substring(0, len - 3) + "..."; 488 | } 489 | return s; 490 | } 491 | 492 | /* 493 | * Convert a value to a nice, human-readable string 494 | */ 495 | function format_value(val, seen) 496 | { 497 | if (!seen) { 498 | seen = []; 499 | } 500 | if (typeof val === "object" && val !== null) 501 | { 502 | if (seen.indexOf(val) >= 0) 503 | { 504 | return "[...]"; 505 | } 506 | seen.push(val); 507 | } 508 | if (Array.isArray(val)) 509 | { 510 | return "[" + val.map(function(x) {return format_value(x, seen)}).join(", ") + "]"; 511 | } 512 | 513 | switch (typeof val) 514 | { 515 | case "string": 516 | val = val.replace("\\", "\\\\"); 517 | for (var i = 0; i < 32; i++) 518 | { 519 | var replace = "\\"; 520 | switch (i) { 521 | case 0: replace += "0"; break; 522 | case 1: replace += "x01"; break; 523 | case 2: replace += "x02"; break; 524 | case 3: replace += "x03"; break; 525 | case 4: replace += "x04"; break; 526 | case 5: replace += "x05"; break; 527 | case 6: replace += "x06"; break; 528 | case 7: replace += "x07"; break; 529 | case 8: replace += "b"; break; 530 | case 9: replace += "t"; break; 531 | case 10: replace += "n"; break; 532 | case 11: replace += "v"; break; 533 | case 12: replace += "f"; break; 534 | case 13: replace += "r"; break; 535 | case 14: replace += "x0e"; break; 536 | case 15: replace += "x0f"; break; 537 | case 16: replace += "x10"; break; 538 | case 17: replace += "x11"; break; 539 | case 18: replace += "x12"; break; 540 | case 19: replace += "x13"; break; 541 | case 20: replace += "x14"; break; 542 | case 21: replace += "x15"; break; 543 | case 22: replace += "x16"; break; 544 | case 23: replace += "x17"; break; 545 | case 24: replace += "x18"; break; 546 | case 25: replace += "x19"; break; 547 | case 26: replace += "x1a"; break; 548 | case 27: replace += "x1b"; break; 549 | case 28: replace += "x1c"; break; 550 | case 29: replace += "x1d"; break; 551 | case 30: replace += "x1e"; break; 552 | case 31: replace += "x1f"; break; 553 | } 554 | val = val.replace(RegExp(String.fromCharCode(i), "g"), replace); 555 | } 556 | return '"' + val.replace(/"/g, '\\"') + '"'; 557 | case "boolean": 558 | case "undefined": 559 | return String(val); 560 | case "number": 561 | // In JavaScript, -0 === 0 and String(-0) == "0", so we have to 562 | // special-case. 563 | if (val === -0 && 1/val === -Infinity) 564 | { 565 | return "-0"; 566 | } 567 | return String(val); 568 | case "object": 569 | if (val === null) 570 | { 571 | return "null"; 572 | } 573 | 574 | // Special-case Node objects, since those come up a lot in my tests. I 575 | // ignore namespaces. I use duck-typing instead of instanceof, because 576 | // instanceof doesn't work if the node is from another window (like an 577 | // iframe's contentWindow): 578 | // http://www.w3.org/Bugs/Public/show_bug.cgi?id=12295 579 | if ("nodeType" in val 580 | && "nodeName" in val 581 | && "nodeValue" in val 582 | && "childNodes" in val) 583 | { 584 | switch (val.nodeType) 585 | { 586 | case Node.ELEMENT_NODE: 587 | var ret = "<" + val.tagName.toLowerCase(); 588 | for (var i = 0; i < val.attributes.length; i++) 589 | { 590 | ret += " " + val.attributes[i].name + '="' + val.attributes[i].value + '"'; 591 | } 592 | ret += ">" + val.innerHTML + ""; 593 | return "Element node " + truncate(ret, 60); 594 | case Node.TEXT_NODE: 595 | return 'Text node "' + truncate(val.data, 60) + '"'; 596 | case Node.PROCESSING_INSTRUCTION_NODE: 597 | return "ProcessingInstruction node with target " + format_value(truncate(val.target, 60)) + " and data " + format_value(truncate(val.data, 60)); 598 | case Node.COMMENT_NODE: 599 | return "Comment node "; 600 | case Node.DOCUMENT_NODE: 601 | return "Document node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children"); 602 | case Node.DOCUMENT_TYPE_NODE: 603 | return "DocumentType node"; 604 | case Node.DOCUMENT_FRAGMENT_NODE: 605 | return "DocumentFragment node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children"); 606 | default: 607 | return "Node object of unknown type"; 608 | } 609 | } 610 | 611 | // Fall through to default 612 | default: 613 | return typeof val + ' "' + truncate(String(val), 60) + '"'; 614 | } 615 | } 616 | expose(format_value, "format_value"); 617 | 618 | /* 619 | * Assertions 620 | */ 621 | 622 | function assert_true(actual, description) 623 | { 624 | assert(actual === true, "assert_true", description, 625 | "expected true got ${actual}", {actual:actual}); 626 | }; 627 | expose(assert_true, "assert_true"); 628 | 629 | function assert_false(actual, description) 630 | { 631 | assert(actual === false, "assert_false", description, 632 | "expected false got ${actual}", {actual:actual}); 633 | }; 634 | expose(assert_false, "assert_false"); 635 | 636 | function same_value(x, y) { 637 | if (y !== y) 638 | { 639 | //NaN case 640 | return x !== x; 641 | } 642 | else if (x === 0 && y === 0) { 643 | //Distinguish +0 and -0 644 | return 1/x === 1/y; 645 | } 646 | else 647 | { 648 | //typical case 649 | return x === y; 650 | } 651 | } 652 | 653 | function assert_equals(actual, expected, description) 654 | { 655 | /* 656 | * Test if two primitives are equal or two objects 657 | * are the same object 658 | */ 659 | if (typeof actual != typeof expected) 660 | { 661 | assert(false, "assert_equals", description, 662 | "expected (" + typeof expected + ") ${expected} but got (" + typeof actual + ") ${actual}", 663 | {expected:expected, actual:actual}); 664 | return; 665 | } 666 | assert(same_value(actual, expected), "assert_equals", description, 667 | "expected ${expected} but got ${actual}", 668 | {expected:expected, actual:actual}); 669 | }; 670 | expose(assert_equals, "assert_equals"); 671 | 672 | function assert_not_equals(actual, expected, description) 673 | { 674 | /* 675 | * Test if two primitives are unequal or two objects 676 | * are different objects 677 | */ 678 | assert(!same_value(actual, expected), "assert_not_equals", description, 679 | "got disallowed value ${actual}", 680 | {actual:actual}); 681 | }; 682 | expose(assert_not_equals, "assert_not_equals"); 683 | 684 | function assert_in_array(actual, expected, description) 685 | { 686 | assert(expected.indexOf(actual) != -1, "assert_in_array", description, 687 | "value ${actual} not in array ${expected}", 688 | {actual:actual, expected:expected}); 689 | } 690 | expose(assert_in_array, "assert_in_array"); 691 | 692 | function assert_object_equals(actual, expected, description) 693 | { 694 | //This needs to be improved a great deal 695 | function check_equal(actual, expected, stack) 696 | { 697 | stack.push(actual); 698 | 699 | var p; 700 | for (p in actual) 701 | { 702 | assert(expected.hasOwnProperty(p), "assert_object_equals", description, 703 | "unexpected property ${p}", {p:p}); 704 | 705 | if (typeof actual[p] === "object" && actual[p] !== null) 706 | { 707 | if (stack.indexOf(actual[p]) === -1) 708 | { 709 | check_equal(actual[p], expected[p], stack); 710 | } 711 | } 712 | else 713 | { 714 | assert(same_value(actual[p], expected[p]), "assert_object_equals", description, 715 | "property ${p} expected ${expected} got ${actual}", 716 | {p:p, expected:expected, actual:actual}); 717 | } 718 | } 719 | for (p in expected) 720 | { 721 | assert(actual.hasOwnProperty(p), 722 | "assert_object_equals", description, 723 | "expected property ${p} missing", {p:p}); 724 | } 725 | stack.pop(); 726 | } 727 | check_equal(actual, expected, []); 728 | }; 729 | expose(assert_object_equals, "assert_object_equals"); 730 | 731 | function assert_array_equals(actual, expected, description) 732 | { 733 | assert(actual.length === expected.length, 734 | "assert_array_equals", description, 735 | "lengths differ, expected ${expected} got ${actual}", 736 | {expected:expected.length, actual:actual.length}); 737 | 738 | for (var i=0; i < actual.length; i++) 739 | { 740 | assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i), 741 | "assert_array_equals", description, 742 | "property ${i}, property expected to be $expected but was $actual", 743 | {i:i, expected:expected.hasOwnProperty(i) ? "present" : "missing", 744 | actual:actual.hasOwnProperty(i) ? "present" : "missing"}); 745 | assert(same_value(expected[i], actual[i]), 746 | "assert_array_equals", description, 747 | "property ${i}, expected ${expected} but got ${actual}", 748 | {i:i, expected:expected[i], actual:actual[i]}); 749 | } 750 | } 751 | expose(assert_array_equals, "assert_array_equals"); 752 | 753 | function assert_approx_equals(actual, expected, epsilon, description) 754 | { 755 | /* 756 | * Test if two primitive numbers are equal withing +/- epsilon 757 | */ 758 | assert(typeof actual === "number", 759 | "assert_approx_equals", description, 760 | "expected a number but got a ${type_actual}", 761 | {type_actual:typeof actual}); 762 | 763 | assert(Math.abs(actual - expected) <= epsilon, 764 | "assert_approx_equals", description, 765 | "expected ${expected} +/- ${epsilon} but got ${actual}", 766 | {expected:expected, actual:actual, epsilon:epsilon}); 767 | }; 768 | expose(assert_approx_equals, "assert_approx_equals"); 769 | 770 | function assert_less_than(actual, expected, description) 771 | { 772 | /* 773 | * Test if a primitive number is less than another 774 | */ 775 | assert(typeof actual === "number", 776 | "assert_less_than", description, 777 | "expected a number but got a ${type_actual}", 778 | {type_actual:typeof actual}); 779 | 780 | assert(actual < expected, 781 | "assert_less_than", description, 782 | "expected a number less than ${expected} but got ${actual}", 783 | {expected:expected, actual:actual}); 784 | }; 785 | expose(assert_less_than, "assert_less_than"); 786 | 787 | function assert_greater_than(actual, expected, description) 788 | { 789 | /* 790 | * Test if a primitive number is greater than another 791 | */ 792 | assert(typeof actual === "number", 793 | "assert_greater_than", description, 794 | "expected a number but got a ${type_actual}", 795 | {type_actual:typeof actual}); 796 | 797 | assert(actual > expected, 798 | "assert_greater_than", description, 799 | "expected a number greater than ${expected} but got ${actual}", 800 | {expected:expected, actual:actual}); 801 | }; 802 | expose(assert_greater_than, "assert_greater_than"); 803 | 804 | function assert_less_than_equal(actual, expected, description) 805 | { 806 | /* 807 | * Test if a primitive number is less than or equal to another 808 | */ 809 | assert(typeof actual === "number", 810 | "assert_less_than_equal", description, 811 | "expected a number but got a ${type_actual}", 812 | {type_actual:typeof actual}); 813 | 814 | assert(actual <= expected, 815 | "assert_less_than", description, 816 | "expected a number less than or equal to ${expected} but got ${actual}", 817 | {expected:expected, actual:actual}); 818 | }; 819 | expose(assert_less_than_equal, "assert_less_than_equal"); 820 | 821 | function assert_greater_than_equal(actual, expected, description) 822 | { 823 | /* 824 | * Test if a primitive number is greater than or equal to another 825 | */ 826 | assert(typeof actual === "number", 827 | "assert_greater_than_equal", description, 828 | "expected a number but got a ${type_actual}", 829 | {type_actual:typeof actual}); 830 | 831 | assert(actual >= expected, 832 | "assert_greater_than_equal", description, 833 | "expected a number greater than or equal to ${expected} but got ${actual}", 834 | {expected:expected, actual:actual}); 835 | }; 836 | expose(assert_greater_than_equal, "assert_greater_than_equal"); 837 | 838 | function assert_regexp_match(actual, expected, description) { 839 | /* 840 | * Test if a string (actual) matches a regexp (expected) 841 | */ 842 | assert(expected.test(actual), 843 | "assert_regexp_match", description, 844 | "expected ${expected} but got ${actual}", 845 | {expected:expected, actual:actual}); 846 | } 847 | expose(assert_regexp_match, "assert_regexp_match"); 848 | 849 | function assert_class_string(object, class_string, description) { 850 | assert_equals({}.toString.call(object), "[object " + class_string + "]", 851 | description); 852 | } 853 | expose(assert_class_string, "assert_class_string"); 854 | 855 | 856 | function _assert_own_property(name) { 857 | return function(object, property_name, description) 858 | { 859 | assert(object.hasOwnProperty(property_name), 860 | name, description, 861 | "expected property ${p} missing", {p:property_name}); 862 | }; 863 | } 864 | expose(_assert_own_property("assert_exists"), "assert_exists"); 865 | expose(_assert_own_property("assert_own_property"), "assert_own_property"); 866 | 867 | function assert_not_exists(object, property_name, description) 868 | { 869 | assert(!object.hasOwnProperty(property_name), 870 | "assert_not_exists", description, 871 | "unexpected property ${p} found", {p:property_name}); 872 | }; 873 | expose(assert_not_exists, "assert_not_exists"); 874 | 875 | function _assert_inherits(name) { 876 | return function (object, property_name, description) 877 | { 878 | assert(typeof object === "object", 879 | name, description, 880 | "provided value is not an object"); 881 | 882 | assert("hasOwnProperty" in object, 883 | name, description, 884 | "provided value is an object but has no hasOwnProperty method"); 885 | 886 | assert(!object.hasOwnProperty(property_name), 887 | name, description, 888 | "property ${p} found on object expected in prototype chain", 889 | {p:property_name}); 890 | 891 | assert(property_name in object, 892 | name, description, 893 | "property ${p} not found in prototype chain", 894 | {p:property_name}); 895 | }; 896 | } 897 | expose(_assert_inherits("assert_inherits"), "assert_inherits"); 898 | expose(_assert_inherits("assert_idl_attribute"), "assert_idl_attribute"); 899 | 900 | function assert_readonly(object, property_name, description) 901 | { 902 | var initial_value = object[property_name]; 903 | try { 904 | //Note that this can have side effects in the case where 905 | //the property has PutForwards 906 | object[property_name] = initial_value + "a"; //XXX use some other value here? 907 | assert(same_value(object[property_name], initial_value), 908 | "assert_readonly", description, 909 | "changing property ${p} succeeded", 910 | {p:property_name}); 911 | } 912 | finally 913 | { 914 | object[property_name] = initial_value; 915 | } 916 | }; 917 | expose(assert_readonly, "assert_readonly"); 918 | 919 | function assert_throws(code, func, description) 920 | { 921 | try 922 | { 923 | func.call(this); 924 | assert(false, "assert_throws", description, 925 | "${func} did not throw", {func:func}); 926 | } 927 | catch(e) 928 | { 929 | if (e instanceof AssertionError) { 930 | throw(e); 931 | } 932 | if (code === null) 933 | { 934 | return; 935 | } 936 | if (typeof code === "object") 937 | { 938 | assert(typeof e == "object" && "name" in e && e.name == code.name, 939 | "assert_throws", description, 940 | "${func} threw ${actual} (${actual_name}) expected ${expected} (${expected_name})", 941 | {func:func, actual:e, actual_name:e.name, 942 | expected:code, 943 | expected_name:code.name}); 944 | return; 945 | } 946 | 947 | var code_name_map = { 948 | INDEX_SIZE_ERR: 'IndexSizeError', 949 | HIERARCHY_REQUEST_ERR: 'HierarchyRequestError', 950 | WRONG_DOCUMENT_ERR: 'WrongDocumentError', 951 | INVALID_CHARACTER_ERR: 'InvalidCharacterError', 952 | NO_MODIFICATION_ALLOWED_ERR: 'NoModificationAllowedError', 953 | NOT_FOUND_ERR: 'NotFoundError', 954 | NOT_SUPPORTED_ERR: 'NotSupportedError', 955 | INVALID_STATE_ERR: 'InvalidStateError', 956 | SYNTAX_ERR: 'SyntaxError', 957 | INVALID_MODIFICATION_ERR: 'InvalidModificationError', 958 | NAMESPACE_ERR: 'NamespaceError', 959 | INVALID_ACCESS_ERR: 'InvalidAccessError', 960 | TYPE_MISMATCH_ERR: 'TypeMismatchError', 961 | SECURITY_ERR: 'SecurityError', 962 | NETWORK_ERR: 'NetworkError', 963 | ABORT_ERR: 'AbortError', 964 | URL_MISMATCH_ERR: 'URLMismatchError', 965 | QUOTA_EXCEEDED_ERR: 'QuotaExceededError', 966 | TIMEOUT_ERR: 'TimeoutError', 967 | INVALID_NODE_TYPE_ERR: 'InvalidNodeTypeError', 968 | DATA_CLONE_ERR: 'DataCloneError' 969 | }; 970 | 971 | var name = code in code_name_map ? code_name_map[code] : code; 972 | 973 | var name_code_map = { 974 | IndexSizeError: 1, 975 | HierarchyRequestError: 3, 976 | WrongDocumentError: 4, 977 | InvalidCharacterError: 5, 978 | NoModificationAllowedError: 7, 979 | NotFoundError: 8, 980 | NotSupportedError: 9, 981 | InvalidStateError: 11, 982 | SyntaxError: 12, 983 | InvalidModificationError: 13, 984 | NamespaceError: 14, 985 | InvalidAccessError: 15, 986 | TypeMismatchError: 17, 987 | SecurityError: 18, 988 | NetworkError: 19, 989 | AbortError: 20, 990 | URLMismatchError: 21, 991 | QuotaExceededError: 22, 992 | TimeoutError: 23, 993 | InvalidNodeTypeError: 24, 994 | DataCloneError: 25, 995 | 996 | UnknownError: 0, 997 | ConstraintError: 0, 998 | DataError: 0, 999 | TransactionInactiveError: 0, 1000 | ReadOnlyError: 0, 1001 | VersionError: 0 1002 | }; 1003 | 1004 | if (!(name in name_code_map)) 1005 | { 1006 | throw new AssertionError('Test bug: unrecognized DOMException code "' + code + '" passed to assert_throws()'); 1007 | } 1008 | 1009 | var required_props = { code: name_code_map[name] }; 1010 | 1011 | if (required_props.code === 0 1012 | || ("name" in e && e.name !== e.name.toUpperCase() && e.name !== "DOMException")) 1013 | { 1014 | // New style exception: also test the name property. 1015 | required_props.name = name; 1016 | } 1017 | 1018 | //We'd like to test that e instanceof the appropriate interface, 1019 | //but we can't, because we don't know what window it was created 1020 | //in. It might be an instanceof the appropriate interface on some 1021 | //unknown other window. TODO: Work around this somehow? 1022 | 1023 | assert(typeof e == "object", 1024 | "assert_throws", description, 1025 | "${func} threw ${e} with type ${type}, not an object", 1026 | {func:func, e:e, type:typeof e}); 1027 | 1028 | for (var prop in required_props) 1029 | { 1030 | assert(typeof e == "object" && prop in e && e[prop] == required_props[prop], 1031 | "assert_throws", description, 1032 | "${func} threw ${e} that is not a DOMException " + code + ": property ${prop} is equal to ${actual}, expected ${expected}", 1033 | {func:func, e:e, prop:prop, actual:e[prop], expected:required_props[prop]}); 1034 | } 1035 | } 1036 | } 1037 | expose(assert_throws, "assert_throws"); 1038 | 1039 | function assert_unreached(description) { 1040 | assert(false, "assert_unreached", description, 1041 | "Reached unreachable code"); 1042 | } 1043 | expose(assert_unreached, "assert_unreached"); 1044 | 1045 | function assert_any(assert_func, actual, expected_array) 1046 | { 1047 | var args = [].slice.call(arguments, 3) 1048 | var errors = [] 1049 | var passed = false; 1050 | forEach(expected_array, 1051 | function(expected) 1052 | { 1053 | try { 1054 | assert_func.apply(this, [actual, expected].concat(args)) 1055 | passed = true; 1056 | } catch(e) { 1057 | errors.push(e.message); 1058 | } 1059 | }); 1060 | if (!passed) { 1061 | throw new AssertionError(errors.join("\n\n")); 1062 | } 1063 | } 1064 | expose(assert_any, "assert_any"); 1065 | 1066 | function Test(name, properties) 1067 | { 1068 | this.name = name; 1069 | this.status = this.NOTRUN; 1070 | this.timeout_id = null; 1071 | this.is_done = false; 1072 | 1073 | this.properties = properties; 1074 | this.timeout_length = properties.timeout ? properties.timeout : settings.test_timeout; 1075 | 1076 | this.message = null; 1077 | 1078 | var this_obj = this; 1079 | this.steps = []; 1080 | 1081 | tests.push(this); 1082 | } 1083 | 1084 | Test.statuses = { 1085 | PASS:0, 1086 | FAIL:1, 1087 | TIMEOUT:2, 1088 | NOTRUN:3 1089 | }; 1090 | 1091 | Test.prototype = merge({}, Test.statuses); 1092 | 1093 | Test.prototype.structured_clone = function() 1094 | { 1095 | if(!this._structured_clone) 1096 | { 1097 | var msg = this.message; 1098 | msg = msg ? String(msg) : msg; 1099 | this._structured_clone = merge({ 1100 | name:String(this.name), 1101 | status:this.status, 1102 | message:msg 1103 | }, Test.statuses); 1104 | } 1105 | return this._structured_clone; 1106 | }; 1107 | 1108 | Test.prototype.step = function(func, this_obj) 1109 | { 1110 | //In case the test has already failed 1111 | if (this.status !== this.NOTRUN) 1112 | { 1113 | return; 1114 | } 1115 | 1116 | tests.started = true; 1117 | 1118 | if (this.timeout_id === null) { 1119 | this.set_timeout(); 1120 | } 1121 | 1122 | this.steps.push(func); 1123 | 1124 | if (arguments.length === 1) 1125 | { 1126 | this_obj = this; 1127 | } 1128 | 1129 | try 1130 | { 1131 | return func.apply(this_obj, Array.prototype.slice.call(arguments, 2)); 1132 | } 1133 | catch(e) 1134 | { 1135 | //This can happen if something called synchronously invoked another 1136 | //step 1137 | if (this.status !== this.NOTRUN) 1138 | { 1139 | return; 1140 | } 1141 | this.status = this.FAIL; 1142 | this.message = (typeof e === "object" && e !== null) ? e.message : e; 1143 | if (typeof e.stack != "undefined" && typeof e.message == "string") { 1144 | //Try to make it more informative for some exceptions, at least 1145 | //in Gecko and WebKit. This results in a stack dump instead of 1146 | //just errors like "Cannot read property 'parentNode' of null" 1147 | //or "root is null". Makes it a lot longer, of course. 1148 | this.message += "(stack: " + e.stack + ")"; 1149 | } 1150 | this.done(); 1151 | if (debug && e.constructor !== AssertionError) { 1152 | throw e; 1153 | } 1154 | } 1155 | }; 1156 | 1157 | Test.prototype.step_func = function(func, this_obj) 1158 | { 1159 | var test_this = this; 1160 | 1161 | if (arguments.length === 1) 1162 | { 1163 | this_obj = test_this; 1164 | } 1165 | 1166 | return function() 1167 | { 1168 | test_this.step.apply(test_this, [func, this_obj].concat( 1169 | Array.prototype.slice.call(arguments))); 1170 | }; 1171 | }; 1172 | 1173 | Test.prototype.step_func_done = function(func, this_obj) 1174 | { 1175 | var test_this = this; 1176 | 1177 | if (arguments.length === 1) 1178 | { 1179 | this_obj = test_this; 1180 | } 1181 | 1182 | return function() 1183 | { 1184 | test_this.step.apply(test_this, [func, this_obj].concat( 1185 | Array.prototype.slice.call(arguments))); 1186 | test_this.done(); 1187 | }; 1188 | }; 1189 | 1190 | Test.prototype.set_timeout = function() 1191 | { 1192 | var this_obj = this; 1193 | this.timeout_id = setTimeout(function() 1194 | { 1195 | this_obj.timeout(); 1196 | }, this.timeout_length); 1197 | }; 1198 | 1199 | Test.prototype.timeout = function() 1200 | { 1201 | this.status = this.TIMEOUT; 1202 | this.timeout_id = null; 1203 | this.message = "Test timed out"; 1204 | this.done(); 1205 | }; 1206 | 1207 | Test.prototype.done = function() 1208 | { 1209 | if (this.is_done) { 1210 | return; 1211 | } 1212 | clearTimeout(this.timeout_id); 1213 | if (this.status === this.NOTRUN) 1214 | { 1215 | this.status = this.PASS; 1216 | } 1217 | this.is_done = true; 1218 | tests.result(this); 1219 | }; 1220 | 1221 | 1222 | /* 1223 | * Harness 1224 | */ 1225 | 1226 | function TestsStatus() 1227 | { 1228 | this.status = null; 1229 | this.message = null; 1230 | } 1231 | 1232 | TestsStatus.statuses = { 1233 | OK:0, 1234 | ERROR:1, 1235 | TIMEOUT:2 1236 | }; 1237 | 1238 | TestsStatus.prototype = merge({}, TestsStatus.statuses); 1239 | 1240 | TestsStatus.prototype.structured_clone = function() 1241 | { 1242 | if(!this._structured_clone) 1243 | { 1244 | var msg = this.message; 1245 | msg = msg ? String(msg) : msg; 1246 | this._structured_clone = merge({ 1247 | status:this.status, 1248 | message:msg 1249 | }, TestsStatus.statuses); 1250 | } 1251 | return this._structured_clone; 1252 | }; 1253 | 1254 | function Tests() 1255 | { 1256 | this.tests = []; 1257 | this.num_pending = 0; 1258 | 1259 | this.phases = { 1260 | INITIAL:0, 1261 | SETUP:1, 1262 | HAVE_TESTS:2, 1263 | HAVE_RESULTS:3, 1264 | COMPLETE:4 1265 | }; 1266 | this.phase = this.phases.INITIAL; 1267 | 1268 | this.properties = {}; 1269 | 1270 | //All tests can't be done until the load event fires 1271 | this.all_loaded = false; 1272 | this.wait_for_finish = false; 1273 | this.processing_callbacks = false; 1274 | 1275 | this.timeout_length = settings.timeout; 1276 | this.timeout_id = null; 1277 | 1278 | this.start_callbacks = []; 1279 | this.test_done_callbacks = []; 1280 | this.all_done_callbacks = []; 1281 | 1282 | this.status = new TestsStatus(); 1283 | 1284 | var this_obj = this; 1285 | 1286 | on_event(window, "load", 1287 | function() 1288 | { 1289 | this_obj.all_loaded = true; 1290 | if (this_obj.all_done()) 1291 | { 1292 | this_obj.complete(); 1293 | } 1294 | }); 1295 | 1296 | this.set_timeout(); 1297 | } 1298 | 1299 | Tests.prototype.setup = function(func, properties) 1300 | { 1301 | if (this.phase >= this.phases.HAVE_RESULTS) 1302 | { 1303 | return; 1304 | } 1305 | if (this.phase < this.phases.SETUP) 1306 | { 1307 | this.phase = this.phases.SETUP; 1308 | } 1309 | 1310 | for (var p in properties) 1311 | { 1312 | if (properties.hasOwnProperty(p)) 1313 | { 1314 | this.properties[p] = properties[p]; 1315 | } 1316 | } 1317 | 1318 | if (properties.timeout) 1319 | { 1320 | this.timeout_length = properties.timeout; 1321 | } 1322 | if (properties.explicit_done) 1323 | { 1324 | this.wait_for_finish = true; 1325 | } 1326 | if (properties.explicit_timeout) { 1327 | this.timeout_length = null; 1328 | } 1329 | 1330 | if (func) 1331 | { 1332 | try 1333 | { 1334 | func(); 1335 | } catch(e) 1336 | { 1337 | this.status.status = this.status.ERROR; 1338 | this.status.message = e; 1339 | }; 1340 | } 1341 | this.set_timeout(); 1342 | }; 1343 | 1344 | Tests.prototype.set_timeout = function() 1345 | { 1346 | var this_obj = this; 1347 | clearTimeout(this.timeout_id); 1348 | if (this.timeout_length !== null) 1349 | { 1350 | this.timeout_id = setTimeout(function() { 1351 | this_obj.timeout(); 1352 | }, this.timeout_length); 1353 | } 1354 | }; 1355 | 1356 | Tests.prototype.timeout = function() { 1357 | this.status.status = this.status.TIMEOUT; 1358 | this.complete(); 1359 | }; 1360 | 1361 | Tests.prototype.end_wait = function() 1362 | { 1363 | this.wait_for_finish = false; 1364 | if (this.all_done()) { 1365 | this.complete(); 1366 | } 1367 | }; 1368 | 1369 | Tests.prototype.push = function(test) 1370 | { 1371 | if (this.phase < this.phases.HAVE_TESTS) { 1372 | this.start(); 1373 | } 1374 | this.num_pending++; 1375 | this.tests.push(test); 1376 | }; 1377 | 1378 | Tests.prototype.all_done = function() { 1379 | return (this.all_loaded && this.num_pending === 0 && 1380 | !this.wait_for_finish && !this.processing_callbacks); 1381 | }; 1382 | 1383 | Tests.prototype.start = function() { 1384 | this.phase = this.phases.HAVE_TESTS; 1385 | this.notify_start(); 1386 | }; 1387 | 1388 | Tests.prototype.notify_start = function() { 1389 | var this_obj = this; 1390 | forEach (this.start_callbacks, 1391 | function(callback) 1392 | { 1393 | callback(this_obj.properties); 1394 | }); 1395 | forEach_windows( 1396 | function(w, is_same_origin) 1397 | { 1398 | if(is_same_origin && w.start_callback) 1399 | { 1400 | try 1401 | { 1402 | w.start_callback(this_obj.properties); 1403 | } 1404 | catch(e) 1405 | { 1406 | if (debug) 1407 | { 1408 | throw(e); 1409 | } 1410 | } 1411 | } 1412 | if (supports_post_message(w) && w !== self) 1413 | { 1414 | w.postMessage({ 1415 | type: "start", 1416 | properties: this_obj.properties 1417 | }, "*"); 1418 | } 1419 | }); 1420 | }; 1421 | 1422 | Tests.prototype.result = function(test) 1423 | { 1424 | if (this.phase > this.phases.HAVE_RESULTS) 1425 | { 1426 | return; 1427 | } 1428 | this.phase = this.phases.HAVE_RESULTS; 1429 | this.num_pending--; 1430 | this.notify_result(test); 1431 | }; 1432 | 1433 | Tests.prototype.notify_result = function(test) { 1434 | var this_obj = this; 1435 | this.processing_callbacks = true; 1436 | forEach(this.test_done_callbacks, 1437 | function(callback) 1438 | { 1439 | callback(test, this_obj); 1440 | }); 1441 | 1442 | forEach_windows( 1443 | function(w, is_same_origin) 1444 | { 1445 | if(is_same_origin && w.result_callback) 1446 | { 1447 | try 1448 | { 1449 | w.result_callback(test); 1450 | } 1451 | catch(e) 1452 | { 1453 | if(debug) { 1454 | throw e; 1455 | } 1456 | } 1457 | } 1458 | if (supports_post_message(w) && w !== self) 1459 | { 1460 | w.postMessage({ 1461 | type: "result", 1462 | test: test.structured_clone() 1463 | }, "*"); 1464 | } 1465 | }); 1466 | this.processing_callbacks = false; 1467 | if (this_obj.all_done()) 1468 | { 1469 | this_obj.complete(); 1470 | } 1471 | }; 1472 | 1473 | Tests.prototype.complete = function() { 1474 | if (this.phase === this.phases.COMPLETE) { 1475 | return; 1476 | } 1477 | this.phase = this.phases.COMPLETE; 1478 | var this_obj = this; 1479 | this.tests.forEach( 1480 | function(x) 1481 | { 1482 | if(x.status === x.NOTRUN) 1483 | { 1484 | this_obj.notify_result(x); 1485 | } 1486 | } 1487 | ); 1488 | this.notify_complete(); 1489 | }; 1490 | 1491 | Tests.prototype.notify_complete = function() 1492 | { 1493 | clearTimeout(this.timeout_id); 1494 | var this_obj = this; 1495 | var tests = map(this_obj.tests, 1496 | function(test) 1497 | { 1498 | return test.structured_clone(); 1499 | }); 1500 | if (this.status.status === null) 1501 | { 1502 | this.status.status = this.status.OK; 1503 | } 1504 | 1505 | forEach (this.all_done_callbacks, 1506 | function(callback) 1507 | { 1508 | callback(this_obj.tests, this_obj.status); 1509 | }); 1510 | 1511 | forEach_windows( 1512 | function(w, is_same_origin) 1513 | { 1514 | if(is_same_origin && w.completion_callback) 1515 | { 1516 | try 1517 | { 1518 | w.completion_callback(this_obj.tests, this_obj.status); 1519 | } 1520 | catch(e) 1521 | { 1522 | if (debug) 1523 | { 1524 | throw e; 1525 | } 1526 | } 1527 | } 1528 | if (supports_post_message(w) && w !== self) 1529 | { 1530 | w.postMessage({ 1531 | type: "complete", 1532 | tests: tests, 1533 | status: this_obj.status.structured_clone() 1534 | }, "*"); 1535 | } 1536 | }); 1537 | }; 1538 | 1539 | var tests = new Tests(); 1540 | 1541 | function timeout() { 1542 | if (tests.timeout_length === null) 1543 | { 1544 | tests.timeout(); 1545 | } 1546 | } 1547 | expose(timeout, 'timeout'); 1548 | 1549 | function add_start_callback(callback) { 1550 | tests.start_callbacks.push(callback); 1551 | } 1552 | 1553 | function add_result_callback(callback) 1554 | { 1555 | tests.test_done_callbacks.push(callback); 1556 | } 1557 | 1558 | function add_completion_callback(callback) 1559 | { 1560 | tests.all_done_callbacks.push(callback); 1561 | } 1562 | 1563 | expose(add_start_callback, 'add_start_callback'); 1564 | expose(add_result_callback, 'add_result_callback'); 1565 | expose(add_completion_callback, 'add_completion_callback'); 1566 | 1567 | /* 1568 | * Output listener 1569 | */ 1570 | 1571 | function Output() { 1572 | this.output_document = document; 1573 | this.output_node = null; 1574 | this.done_count = 0; 1575 | this.enabled = settings.output; 1576 | this.phase = this.INITIAL; 1577 | } 1578 | 1579 | Output.prototype.INITIAL = 0; 1580 | Output.prototype.STARTED = 1; 1581 | Output.prototype.HAVE_RESULTS = 2; 1582 | Output.prototype.COMPLETE = 3; 1583 | 1584 | Output.prototype.setup = function(properties) { 1585 | if (this.phase > this.INITIAL) { 1586 | return; 1587 | } 1588 | 1589 | //If output is disabled in testharnessreport.js the test shouldn't be 1590 | //able to override that 1591 | this.enabled = this.enabled && (properties.hasOwnProperty("output") ? 1592 | properties.output : settings.output); 1593 | }; 1594 | 1595 | Output.prototype.init = function(properties) 1596 | { 1597 | if (this.phase >= this.STARTED) { 1598 | return; 1599 | } 1600 | if (properties.output_document) { 1601 | this.output_document = properties.output_document; 1602 | } else { 1603 | this.output_document = document; 1604 | } 1605 | this.phase = this.STARTED; 1606 | }; 1607 | 1608 | Output.prototype.resolve_log = function() 1609 | { 1610 | var output_document; 1611 | if (typeof this.output_document === "function") 1612 | { 1613 | output_document = this.output_document.apply(undefined); 1614 | } else 1615 | { 1616 | output_document = this.output_document; 1617 | } 1618 | if (!output_document) 1619 | { 1620 | return; 1621 | } 1622 | var node = output_document.getElementById("log"); 1623 | if (node) 1624 | { 1625 | this.output_document = output_document; 1626 | this.output_node = node; 1627 | } 1628 | }; 1629 | 1630 | Output.prototype.show_status = function(test) 1631 | { 1632 | if (this.phase < this.STARTED) 1633 | { 1634 | this.init(); 1635 | } 1636 | if (!this.enabled) 1637 | { 1638 | return; 1639 | } 1640 | if (this.phase < this.HAVE_RESULTS) 1641 | { 1642 | this.resolve_log(); 1643 | this.phase = this.HAVE_RESULTS; 1644 | } 1645 | this.done_count++; 1646 | if (this.output_node) 1647 | { 1648 | if (this.done_count < 100 1649 | || (this.done_count < 1000 && this.done_count % 100 == 0) 1650 | || this.done_count % 1000 == 0) { 1651 | this.output_node.textContent = "Running, " 1652 | + this.done_count + " complete, " 1653 | + tests.num_pending + " remain"; 1654 | } 1655 | } 1656 | }; 1657 | 1658 | Output.prototype.show_results = function (tests, harness_status) 1659 | { 1660 | if (this.phase >= this.COMPLETE) { 1661 | return; 1662 | } 1663 | if (!this.enabled) 1664 | { 1665 | return; 1666 | } 1667 | if (!this.output_node) { 1668 | this.resolve_log(); 1669 | } 1670 | this.phase = this.COMPLETE; 1671 | 1672 | var log = this.output_node; 1673 | if (!log) 1674 | { 1675 | return; 1676 | } 1677 | var output_document = this.output_document; 1678 | 1679 | while (log.lastChild) 1680 | { 1681 | log.removeChild(log.lastChild); 1682 | } 1683 | 1684 | if (script_prefix != null) { 1685 | var stylesheet = output_document.createElementNS(xhtml_ns, "link"); 1686 | stylesheet.setAttribute("rel", "stylesheet"); 1687 | stylesheet.setAttribute("href", script_prefix + "testharness.css"); 1688 | var heads = output_document.getElementsByTagName("head"); 1689 | if (heads.length) { 1690 | heads[0].appendChild(stylesheet); 1691 | } 1692 | } 1693 | 1694 | var status_text = {}; 1695 | status_text[Test.prototype.PASS] = "Pass"; 1696 | status_text[Test.prototype.FAIL] = "Fail"; 1697 | status_text[Test.prototype.TIMEOUT] = "Timeout"; 1698 | status_text[Test.prototype.NOTRUN] = "Not Run"; 1699 | 1700 | var status_number = {}; 1701 | forEach(tests, function(test) { 1702 | var status = status_text[test.status]; 1703 | if (status_number.hasOwnProperty(status)) 1704 | { 1705 | status_number[status] += 1; 1706 | } else { 1707 | status_number[status] = 1; 1708 | } 1709 | }); 1710 | 1711 | function status_class(status) 1712 | { 1713 | return status.replace(/\s/g, '').toLowerCase(); 1714 | } 1715 | 1716 | var summary_template = ["section", {"id":"summary"}, 1717 | ["h2", {}, "Summary"], 1718 | ["p", {}, "Found ${num_tests} tests"], 1719 | function(vars) { 1720 | var rv = [["div", {}]]; 1721 | var i=0; 1722 | while (status_text.hasOwnProperty(i)) { 1723 | if (status_number.hasOwnProperty(status_text[i])) { 1724 | var status = status_text[i]; 1725 | rv[0].push(["div", {"class":status_class(status)}, 1726 | ["label", {}, 1727 | ["input", {type:"checkbox", checked:"checked"}], 1728 | status_number[status] + " " + status]]); 1729 | } 1730 | i++; 1731 | } 1732 | return rv; 1733 | }]; 1734 | 1735 | log.appendChild(render(summary_template, {num_tests:tests.length}, output_document)); 1736 | 1737 | forEach(output_document.querySelectorAll("section#summary label"), 1738 | function(element) 1739 | { 1740 | on_event(element, "click", 1741 | function(e) 1742 | { 1743 | if (output_document.getElementById("results") === null) 1744 | { 1745 | e.preventDefault(); 1746 | return; 1747 | } 1748 | var result_class = element.parentNode.getAttribute("class"); 1749 | var style_element = output_document.querySelector("style#hide-" + result_class); 1750 | var input_element = element.querySelector("input"); 1751 | if (!style_element && !input_element.checked) { 1752 | style_element = output_document.createElementNS(xhtml_ns, "style"); 1753 | style_element.id = "hide-" + result_class; 1754 | style_element.textContent = "table#results > tbody > tr."+result_class+"{display:none}"; 1755 | output_document.body.appendChild(style_element); 1756 | } else if (style_element && input_element.checked) { 1757 | style_element.parentNode.removeChild(style_element); 1758 | } 1759 | }); 1760 | }); 1761 | 1762 | // This use of innerHTML plus manual escaping is not recommended in 1763 | // general, but is necessary here for performance. Using textContent 1764 | // on each individual adds tens of seconds of execution time for 1765 | // large test suites (tens of thousands of tests). 1766 | function escape_html(s) 1767 | { 1768 | return s.replace(/\&/g, "&") 1769 | .replace(/" 1798 | + "ResultTest Name" 1799 | + (assertions ? "Assertion" : "") 1800 | + "Message" 1801 | + ""; 1802 | for (var i = 0; i < tests.length; i++) { 1803 | html += '' 1806 | + escape_html(status_text[tests[i].status]) 1807 | + "" 1808 | + escape_html(tests[i].name) 1809 | + "" 1810 | + (assertions ? escape_html(get_assertion(tests[i])) + "" : "") 1811 | + escape_html(tests[i].message ? tests[i].message : " ") 1812 | + ""; 1813 | } 1814 | html += ""; 1815 | try { 1816 | log.lastChild.innerHTML = html; 1817 | } catch (e) { 1818 | log.appendChild(document.createElementNS(xhtml_ns, "p")) 1819 | .textContent = "Setting innerHTML for the log threw an exception."; 1820 | log.appendChild(document.createElementNS(xhtml_ns, "pre")) 1821 | .textContent = html; 1822 | } 1823 | }; 1824 | 1825 | var output = new Output(); 1826 | add_start_callback(function (properties) {output.init(properties);}); 1827 | add_result_callback(function (test) {output.show_status(tests);}); 1828 | add_completion_callback(function (tests, harness_status) {output.show_results(tests, harness_status);}); 1829 | 1830 | /* 1831 | * Template code 1832 | * 1833 | * A template is just a javascript structure. An element is represented as: 1834 | * 1835 | * [tag_name, {attr_name:attr_value}, child1, child2] 1836 | * 1837 | * the children can either be strings (which act like text nodes), other templates or 1838 | * functions (see below) 1839 | * 1840 | * A text node is represented as 1841 | * 1842 | * ["{text}", value] 1843 | * 1844 | * String values have a simple substitution syntax; ${foo} represents a variable foo. 1845 | * 1846 | * It is possible to embed logic in templates by using a function in a place where a 1847 | * node would usually go. The function must either return part of a template or null. 1848 | * 1849 | * In cases where a set of nodes are required as output rather than a single node 1850 | * with children it is possible to just use a list 1851 | * [node1, node2, node3] 1852 | * 1853 | * Usage: 1854 | * 1855 | * render(template, substitutions) - take a template and an object mapping 1856 | * variable names to parameters and return either a DOM node or a list of DOM nodes 1857 | * 1858 | * substitute(template, substitutions) - take a template and variable mapping object, 1859 | * make the variable substitutions and return the substituted template 1860 | * 1861 | */ 1862 | 1863 | function is_single_node(template) 1864 | { 1865 | return typeof template[0] === "string"; 1866 | } 1867 | 1868 | function substitute(template, substitutions) 1869 | { 1870 | if (typeof template === "function") { 1871 | var replacement = template(substitutions); 1872 | if (replacement) 1873 | { 1874 | var rv = substitute(replacement, substitutions); 1875 | return rv; 1876 | } 1877 | else 1878 | { 1879 | return null; 1880 | } 1881 | } 1882 | else if (is_single_node(template)) 1883 | { 1884 | return substitute_single(template, substitutions); 1885 | } 1886 | else 1887 | { 1888 | return filter(map(template, function(x) { 1889 | return substitute(x, substitutions); 1890 | }), function(x) {return x !== null;}); 1891 | } 1892 | } 1893 | 1894 | function substitute_single(template, substitutions) 1895 | { 1896 | var substitution_re = /\${([^ }]*)}/g; 1897 | 1898 | function do_substitution(input) { 1899 | var components = input.split(substitution_re); 1900 | var rv = []; 1901 | for (var i=0; i