Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | <html xmlns="http://www.w3.org/1999/xhtml"> |
michael@0 | 2 | <head> |
michael@0 | 3 | <title>Test for SMIL accessKey support</title> |
michael@0 | 4 | <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> |
michael@0 | 5 | <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> |
michael@0 | 6 | </head> |
michael@0 | 7 | <body> |
michael@0 | 8 | <a target="_blank" |
michael@0 | 9 | href="https://bugzilla.mozilla.org/show_bug.cgi?id=587910">Mozilla Bug |
michael@0 | 10 | 587910</a> |
michael@0 | 11 | <p id="display"></p> |
michael@0 | 12 | <div id="content" style="display: none"> |
michael@0 | 13 | <svg id="svg" xmlns="http://www.w3.org/2000/svg" width="120px" height="120px"> |
michael@0 | 14 | <circle cx="20" cy="20" r="15" fill="blue" id="circle"/> |
michael@0 | 15 | </svg> |
michael@0 | 16 | </div> |
michael@0 | 17 | <pre id="test"> |
michael@0 | 18 | <script class="testbody" type="text/javascript"> |
michael@0 | 19 | <![CDATA[ |
michael@0 | 20 | /** Test for SMIL accessKey support **/ |
michael@0 | 21 | |
michael@0 | 22 | const gSvgns = "http://www.w3.org/2000/svg"; |
michael@0 | 23 | var gSvg = document.getElementById("svg"); |
michael@0 | 24 | SimpleTest.waitForExplicitFinish(); |
michael@0 | 25 | |
michael@0 | 26 | function main() |
michael@0 | 27 | { |
michael@0 | 28 | gSvg.pauseAnimations(); |
michael@0 | 29 | |
michael@0 | 30 | // Basic syntax |
michael@0 | 31 | testOk('accessKey(a)', 'a'); |
michael@0 | 32 | testOk(' accessKey(a) ', 'a'); |
michael@0 | 33 | testNotOk('accessKey (a)', 'a'); |
michael@0 | 34 | testNotOk('accessKey( a)', 'a'); |
michael@0 | 35 | testNotOk('accessKey(a )', 'a'); |
michael@0 | 36 | testNotOk('accessKey(a)', 'b'); |
michael@0 | 37 | testNotOk('accessKey()', ' '); |
michael@0 | 38 | |
michael@0 | 39 | // Test the test framework itself |
michael@0 | 40 | testOk('accessKey(a)', 97); |
michael@0 | 41 | |
michael@0 | 42 | // Allow for either accessKey (SVG / SMIL Animation) or accesskey (SMIL2+) |
michael@0 | 43 | testOk('accesskey(a)', 'a'); |
michael@0 | 44 | |
michael@0 | 45 | // Offset |
michael@0 | 46 | testOk('accessKey(a)+0s', 'a'); |
michael@0 | 47 | testOk('accessKey(a) + 0min', 'a'); |
michael@0 | 48 | testOk('accessKey(a) -0h', 'a'); |
michael@0 | 49 | testOk('accessKey(a)+100ms', 'a', 0, 0.1); |
michael@0 | 50 | testOk('accessKey(a)-0.1s', 'a', 0, -0.1); |
michael@0 | 51 | |
michael@0 | 52 | // Id references are not allowed |
michael@0 | 53 | testNotOk('svg.accessKey(a)', 'a'); |
michael@0 | 54 | testNotOk('window.accessKey(a)', 'a'); |
michael@0 | 55 | |
michael@0 | 56 | // Case sensitivity |
michael@0 | 57 | testOk('accessKey(A)', 'A'); |
michael@0 | 58 | testNotOk('accessKey(a)', 'A'); |
michael@0 | 59 | testNotOk('accessKey(A)', 'a'); |
michael@0 | 60 | |
michael@0 | 61 | // Test unusual characters |
michael@0 | 62 | testOk('accessKey(-)', '-'); |
michael@0 | 63 | testOk('accessKey(\\)', '\\'); |
michael@0 | 64 | testOk('accessKey( )', ' '); |
michael@0 | 65 | testOk('accessKey(\x0D)', 0, KeyboardEvent.DOM_VK_RETURN); |
michael@0 | 66 | testOk('accessKey(\n)', 0, KeyboardEvent.DOM_VK_RETURN); // New line |
michael@0 | 67 | testOk('accessKey(\r)', 0, KeyboardEvent.DOM_VK_RETURN); // Carriage return |
michael@0 | 68 | testOk('accessKey(\x08)', 0, KeyboardEvent.DOM_VK_BACK_SPACE); |
michael@0 | 69 | testOk('accessKey(\x1B)', 0, KeyboardEvent.DOM_VK_ESCAPE); |
michael@0 | 70 | testOk('accessKey(\x7F)', 0, KeyboardEvent.DOM_VK_DELETE); |
michael@0 | 71 | |
michael@0 | 72 | // Check some disallowed keys |
michael@0 | 73 | // -- For now we don't allow tab since the interaction with focus causes |
michael@0 | 74 | // confusing results |
michael@0 | 75 | testNotOk('accessKey(\x09)', 0, 9); // Tab |
michael@0 | 76 | |
michael@0 | 77 | // Test setting the keyCode field |
michael@0 | 78 | testNotOk('accessKey(a)', 0, 97); |
michael@0 | 79 | testOk('accessKey(a)', 97, 66); // Give priority to charCode field |
michael@0 | 80 | testNotOk('accessKey(a)', 98, 97); // Give priority to charCode field |
michael@0 | 81 | |
michael@0 | 82 | // Test unicode |
michael@0 | 83 | testOk("accessKey(\u20AC)", 8364); // euro-symbol |
michael@0 | 84 | |
michael@0 | 85 | // Test an astral character just to make sure we don't crash |
michael@0 | 86 | testOk("accessKey(\uD835\uDC00)", 119808); // mathematical bold capital A |
michael@0 | 87 | // 0x1D400 |
michael@0 | 88 | // Test bad surrogate pairs don't confuse us either |
michael@0 | 89 | testNotOk("accessKey(\uD800\uD800)", 97); |
michael@0 | 90 | testNotOk("accessKey(\uD80020)", 97); |
michael@0 | 91 | testNotOk("accessKey(\uD800)", 97); |
michael@0 | 92 | |
michael@0 | 93 | // Test modifiers |
michael@0 | 94 | // -- When matching on charCode ignore shift and alt |
michael@0 | 95 | testNotOk('accessKey(a)', 'a', 0, 0, { ctrl: true }); |
michael@0 | 96 | testNotOk('accessKey(a)', 'a', 0, 0, { meta: true }); |
michael@0 | 97 | testOk('accessKey(a)', 'a', 0, 0, { alt: true }); |
michael@0 | 98 | testOk('accessKey(a)', 'a', 0, 0, { shift: true }); |
michael@0 | 99 | testNotOk('accessKey(a)', 'a', 0, 0, { shift: true, ctrl: true }); |
michael@0 | 100 | testNotOk('accessKey(a)', 'a', 0, 0, { alt: true, meta: true }); |
michael@0 | 101 | // -- When matching on keyCode ignore all |
michael@0 | 102 | testNotOk('accessKey(\x0D)', 0, 13, 0, { ctrl: true }); |
michael@0 | 103 | testNotOk('accessKey(\x0D)', 0, 13, 0, { meta: true }); |
michael@0 | 104 | testNotOk('accessKey(\x0D)', 0, 13, 0, { alt: true }); |
michael@0 | 105 | testNotOk('accessKey(\x0D)', 0, 13, 0, { shift: true }); |
michael@0 | 106 | testNotOk('accessKey(\x0D)', 0, 13, 0, { shift: true, ctrl: true }); |
michael@0 | 107 | |
michael@0 | 108 | testOpenEnd(); |
michael@0 | 109 | testPreventDefault(); |
michael@0 | 110 | testDispatchToWindow(); |
michael@0 | 111 | testAdoptNode(); |
michael@0 | 112 | testFauxEvent(); |
michael@0 | 113 | |
michael@0 | 114 | SimpleTest.finish(); |
michael@0 | 115 | } |
michael@0 | 116 | |
michael@0 | 117 | function testOk(spec, charCode, keyCode, offset, modifiers) |
michael@0 | 118 | { |
michael@0 | 119 | if (typeof offset == 'undefined') offset = 0; |
michael@0 | 120 | var msg = "No interval created for '" + spec + |
michael@0 | 121 | "' with input [charCode: " + charCode + "; keyCode: " + keyCode + "]" + |
michael@0 | 122 | getModifiersDescr(modifiers); |
michael@0 | 123 | ok(test(spec, charCode, keyCode, offset, modifiers), msg); |
michael@0 | 124 | } |
michael@0 | 125 | |
michael@0 | 126 | function testNotOk(spec, charCode, keyCode, offset, modifiers) |
michael@0 | 127 | { |
michael@0 | 128 | if (typeof offset == 'undefined') offset = 0; |
michael@0 | 129 | var msg = "Interval unexpectedly created for '" + spec + |
michael@0 | 130 | "' with input [charCode: " + charCode + "; keyCode: " + keyCode + "]" + |
michael@0 | 131 | getModifiersDescr(modifiers); |
michael@0 | 132 | ok(!test(spec, charCode, keyCode, offset, modifiers), msg); |
michael@0 | 133 | } |
michael@0 | 134 | |
michael@0 | 135 | function getModifiersDescr(modifiers) |
michael@0 | 136 | { |
michael@0 | 137 | if (typeof modifiers != 'object') |
michael@0 | 138 | return ''; |
michael@0 | 139 | var str = ' modifiers set:'; |
michael@0 | 140 | for (var key in modifiers) { |
michael@0 | 141 | if (modifiers[key]) str += ' ' + key; |
michael@0 | 142 | } |
michael@0 | 143 | return str; |
michael@0 | 144 | } |
michael@0 | 145 | |
michael@0 | 146 | function test(spec, charCode, keyCode, offset, modifiers) |
michael@0 | 147 | { |
michael@0 | 148 | gSvg.setCurrentTime(1); |
michael@0 | 149 | ok(gSvg.animationsPaused(), "Expected animations to be paused"); |
michael@0 | 150 | |
michael@0 | 151 | var anim = createAnim(spec); |
michael@0 | 152 | var evt = createEvent(charCode, keyCode, modifiers); |
michael@0 | 153 | |
michael@0 | 154 | document.getElementById('circle').dispatchEvent(evt); |
michael@0 | 155 | |
michael@0 | 156 | var gotStartTimeOk = true; |
michael@0 | 157 | try { |
michael@0 | 158 | var start = anim.getStartTime(); |
michael@0 | 159 | if (offset) { |
michael@0 | 160 | var expected = gSvg.getCurrentTime() + offset; |
michael@0 | 161 | ok(Math.abs(expected - start) <= 0.00001, |
michael@0 | 162 | "Unexpected start time for animation with begin: " + spec + |
michael@0 | 163 | " got " + start + ", expected " + expected); |
michael@0 | 164 | } else { |
michael@0 | 165 | is(start, gSvg.getCurrentTime() + offset, |
michael@0 | 166 | "Unexpected start time for animation with begin: " + spec); |
michael@0 | 167 | } |
michael@0 | 168 | } catch(e) { |
michael@0 | 169 | is(e.name, "InvalidStateError", |
michael@0 | 170 | "Unexpected exception: " + e.name); |
michael@0 | 171 | is(e.code, DOMException.INVALID_STATE_ERR, |
michael@0 | 172 | "Unexpected exception code: " + e.code); |
michael@0 | 173 | gotStartTimeOk = false; |
michael@0 | 174 | } |
michael@0 | 175 | |
michael@0 | 176 | anim.parentNode.removeChild(anim); |
michael@0 | 177 | |
michael@0 | 178 | return gotStartTimeOk; |
michael@0 | 179 | } |
michael@0 | 180 | |
michael@0 | 181 | function createAnim(beginSpec) |
michael@0 | 182 | { |
michael@0 | 183 | var anim = document.createElementNS(gSvgns, 'animate'); |
michael@0 | 184 | anim.setAttribute('attributeName', 'cx'); |
michael@0 | 185 | anim.setAttribute('values', '0; 100'); |
michael@0 | 186 | anim.setAttribute('dur', '10s'); |
michael@0 | 187 | anim.setAttribute('begin', beginSpec); |
michael@0 | 188 | return document.getElementById('circle').appendChild(anim); |
michael@0 | 189 | } |
michael@0 | 190 | |
michael@0 | 191 | function createEvent(charCode, keyCode, modifiers) |
michael@0 | 192 | { |
michael@0 | 193 | if (typeof charCode == 'string') { |
michael@0 | 194 | is(charCode.length, 1, |
michael@0 | 195 | "If charCode is a string it should be 1 character long"); |
michael@0 | 196 | charCode = charCode.charCodeAt(0); |
michael@0 | 197 | } else if (typeof charCode == 'undefined') { |
michael@0 | 198 | charCode = 0; |
michael@0 | 199 | } |
michael@0 | 200 | args = { ctrl: false, alt: false, shift: false, meta: false }; |
michael@0 | 201 | if (typeof modifiers == 'object') { |
michael@0 | 202 | for (var key in modifiers) |
michael@0 | 203 | args[key] = modifiers[key]; |
michael@0 | 204 | } |
michael@0 | 205 | if (typeof keyCode == 'undefined') keyCode = 0; |
michael@0 | 206 | var evt = document.createEvent("KeyboardEvent"); |
michael@0 | 207 | evt.initKeyEvent("keypress", true, true, window, |
michael@0 | 208 | args['ctrl'], |
michael@0 | 209 | args['alt'], |
michael@0 | 210 | args['shift'], |
michael@0 | 211 | args['meta'], |
michael@0 | 212 | keyCode, |
michael@0 | 213 | charCode); |
michael@0 | 214 | return evt; |
michael@0 | 215 | } |
michael@0 | 216 | |
michael@0 | 217 | function testOpenEnd() |
michael@0 | 218 | { |
michael@0 | 219 | // Test that an end specification with an accesskey value is treated as open |
michael@0 | 220 | // ended |
michael@0 | 221 | gSvg.setCurrentTime(0); |
michael@0 | 222 | ok(gSvg.animationsPaused(), "Expected animations to be paused"); |
michael@0 | 223 | |
michael@0 | 224 | var anim = createAnim('0s; 2s'); |
michael@0 | 225 | anim.setAttribute('end', '1s; accessKey(a)'); |
michael@0 | 226 | |
michael@0 | 227 | gSvg.setCurrentTime(2); |
michael@0 | 228 | |
michael@0 | 229 | try { |
michael@0 | 230 | is(anim.getStartTime(), 2, |
michael@0 | 231 | "Unexpected start time for second interval of open-ended animation"); |
michael@0 | 232 | } catch(e) { |
michael@0 | 233 | is(e.name, "InvalidStateError", |
michael@0 | 234 | "Unexpected exception:" + e.name); |
michael@0 | 235 | is(e.code, DOMException.INVALID_STATE_ERR, |
michael@0 | 236 | "Unexpected exception code:" + e.code); |
michael@0 | 237 | ok(false, "Failed to recognise accessKey as qualifying for creating an " + |
michael@0 | 238 | "open-ended interval"); |
michael@0 | 239 | } |
michael@0 | 240 | |
michael@0 | 241 | anim.parentNode.removeChild(anim); |
michael@0 | 242 | } |
michael@0 | 243 | |
michael@0 | 244 | function testPreventDefault() |
michael@0 | 245 | { |
michael@0 | 246 | // SVG/SMIL don't specify what should happen if preventDefault is called on |
michael@0 | 247 | // the keypress event. For now, for consistency with event timing we ignore |
michael@0 | 248 | // it. |
michael@0 | 249 | gSvg.setCurrentTime(1); |
michael@0 | 250 | ok(gSvg.animationsPaused(), "Expected animations to be paused"); |
michael@0 | 251 | |
michael@0 | 252 | var anim = createAnim('accessKey(a)'); |
michael@0 | 253 | var evt = createEvent('a'); |
michael@0 | 254 | |
michael@0 | 255 | var circle = document.getElementById('circle'); |
michael@0 | 256 | var func = function(evt) { evt.preventDefault(); } |
michael@0 | 257 | circle.addEventListener('keypress', func, false); |
michael@0 | 258 | circle.dispatchEvent(evt); |
michael@0 | 259 | |
michael@0 | 260 | try { |
michael@0 | 261 | var start = anim.getStartTime(); |
michael@0 | 262 | } catch(e) { |
michael@0 | 263 | ok(false, "preventDefault() cancelled accessKey handling"); |
michael@0 | 264 | } |
michael@0 | 265 | |
michael@0 | 266 | circle.removeEventListener('keypress', func, false); |
michael@0 | 267 | anim.parentNode.removeChild(anim); |
michael@0 | 268 | } |
michael@0 | 269 | |
michael@0 | 270 | function testDispatchToWindow() |
michael@0 | 271 | { |
michael@0 | 272 | gSvg.setCurrentTime(1); |
michael@0 | 273 | ok(gSvg.animationsPaused(), "Expected animations to be paused"); |
michael@0 | 274 | |
michael@0 | 275 | var anim = createAnim('accessKey(a)'); |
michael@0 | 276 | var evt = createEvent('a'); |
michael@0 | 277 | |
michael@0 | 278 | window.dispatchEvent(evt); |
michael@0 | 279 | |
michael@0 | 280 | try { |
michael@0 | 281 | var start = anim.getStartTime(); |
michael@0 | 282 | } catch(e) { |
michael@0 | 283 | ok(false, "Key event dispatched to the window failed to trigger " + |
michael@0 | 284 | "accesskey handling"); |
michael@0 | 285 | } |
michael@0 | 286 | |
michael@0 | 287 | anim.parentNode.removeChild(anim); |
michael@0 | 288 | } |
michael@0 | 289 | |
michael@0 | 290 | function testAdoptNode() |
michael@0 | 291 | { |
michael@0 | 292 | gSvg.setCurrentTime(1); |
michael@0 | 293 | ok(gSvg.animationsPaused(), "Expected animations to be paused"); |
michael@0 | 294 | |
michael@0 | 295 | // Create a new document with an animation element |
michael@0 | 296 | var newdoc = document.implementation.createDocument(gSvgns, 'svg', null); |
michael@0 | 297 | var anim = newdoc.createElementNS(gSvgns, 'animate'); |
michael@0 | 298 | anim.setAttribute('attributeName', 'cx'); |
michael@0 | 299 | anim.setAttribute('values', '0; 100'); |
michael@0 | 300 | anim.setAttribute('dur', '10s'); |
michael@0 | 301 | anim.setAttribute('begin', 'accesskey(a)'); |
michael@0 | 302 | newdoc.documentElement.appendChild(anim); |
michael@0 | 303 | |
michael@0 | 304 | // Adopt |
michael@0 | 305 | ok(anim.ownerDocument !== document, |
michael@0 | 306 | "Expected newly created animation to belong to a different doc"); |
michael@0 | 307 | document.adoptNode(anim); |
michael@0 | 308 | document.getElementById('circle').appendChild(anim); |
michael@0 | 309 | ok(anim.ownerDocument === document, |
michael@0 | 310 | "Expected newly created animation to belong to the same doc"); |
michael@0 | 311 | |
michael@0 | 312 | var evt = createEvent('a'); |
michael@0 | 313 | |
michael@0 | 314 | // Now fire an event at the original window and check nothing happens |
michael@0 | 315 | newdoc.dispatchEvent(evt); |
michael@0 | 316 | try { |
michael@0 | 317 | var start = anim.getStartTime(); |
michael@0 | 318 | ok(false, "Adopted node still receiving accesskey events from old doc"); |
michael@0 | 319 | } catch(e) { |
michael@0 | 320 | // Ok |
michael@0 | 321 | } |
michael@0 | 322 | |
michael@0 | 323 | // And then fire at our window |
michael@0 | 324 | document.dispatchEvent(evt); |
michael@0 | 325 | try { |
michael@0 | 326 | var start = anim.getStartTime(); |
michael@0 | 327 | } catch(e) { |
michael@0 | 328 | ok(false, "Adopted node failed to catch accesskey event"); |
michael@0 | 329 | } |
michael@0 | 330 | |
michael@0 | 331 | anim.parentNode.removeChild(anim); |
michael@0 | 332 | } |
michael@0 | 333 | |
michael@0 | 334 | function testFauxEvent() |
michael@0 | 335 | { |
michael@0 | 336 | // Test a non-KeyEvent labelled as a key event |
michael@0 | 337 | gSvg.setCurrentTime(0); |
michael@0 | 338 | ok(gSvg.animationsPaused(), "Expected animations to be paused"); |
michael@0 | 339 | |
michael@0 | 340 | var anim = createAnim('accessKey(a)'); |
michael@0 | 341 | var evt = document.createEvent("SVGEvents"); |
michael@0 | 342 | evt.initEvent("keypress", true, true); |
michael@0 | 343 | document.getElementById('circle').dispatchEvent(evt); |
michael@0 | 344 | |
michael@0 | 345 | // We're really just testing that the above didn't crash us, but while we're |
michael@0 | 346 | // at it, just do a sanity check that we didn't also create an interval |
michael@0 | 347 | try { |
michael@0 | 348 | var start = anim.getStartTime(); |
michael@0 | 349 | ok(false, "Faux event generated interval"); |
michael@0 | 350 | } catch(e) { |
michael@0 | 351 | // All is well |
michael@0 | 352 | } |
michael@0 | 353 | |
michael@0 | 354 | anim.parentNode.removeChild(anim); |
michael@0 | 355 | } |
michael@0 | 356 | |
michael@0 | 357 | window.addEventListener("load", main, false); |
michael@0 | 358 | ]]> |
michael@0 | 359 | </script> |
michael@0 | 360 | </pre> |
michael@0 | 361 | </body> |
michael@0 | 362 | </html> |