| <!DOCTYPE html> |
| <html> |
| <head> |
| <meta charset='utf-8'> |
| <title>user-gesture-preserved-across-xmlhttprequest</title> |
| <script src='/media-resources/media-file.js'></script> |
| <script src='/media-resources/video-test.js'></script> |
| |
| <script> |
| let autoRun = true; |
| |
| if (window.testRunner) { |
| testRunner.dumpAsText(); |
| testRunner.waitUntilDone(); |
| } |
| |
| function doXHR(delay, timeout, completionHandler) |
| { |
| var xhr = new XMLHttpRequest(); |
| if (window.internals) |
| window.internals.setMaximumIntervalForUserGestureForwardingForFetch(timeout); |
| |
| xhr.open('GET', `/xmlhttprequest/resources/download-with-delay.py?iteration=1&delay=${delay}`, true); |
| |
| xhr.onload = (evt) => { |
| consoleWrite(`EVENT(load): readyState = ${xhr.readyState}`); |
| if (xhr.readyState === 4) { |
| if (xhr.status === 200) { |
| completionHandler(); |
| } else { |
| logResult(Failed, `xhr.onload, status = ${xhr.statusText}`); |
| } |
| } |
| }; |
| |
| xhr.onerror = (err) => { |
| logResult(Failed, `xhr.onerror, status = ${xhr.statusText}`); |
| }; |
| |
| xhr.send(null); |
| } |
| |
| function doFetch(delay, timeout, completionHandler) |
| { |
| if (window.internals) |
| window.internals.setMaximumIntervalForUserGestureForwardingForFetch(timeout); |
| |
| fetch(`/xmlhttprequest/resources/download-header-with-delay.py?delay=${delay}`, { method: 'GET', cache: "no-cache" }).then(response => { |
| consoleWrite(`FETCH(resolved): status = ${response.status}`); |
| completionHandler(); |
| }, error => { |
| consoleWrite(`FETCH(rejected): exception code = ${error.code}`); |
| }); |
| } |
| |
| function doFetchBlob(delay, timeout, completionHandler) |
| { |
| fetch(`/xmlhttprequest/resources/download-with-delay.py?iteration=1&delay=${delay}`, { method: 'GET', cache: "no-cache" }).then(response => { |
| consoleWrite(`FETCH(resolved): status = ${response.status}`); |
| if (window.internals) |
| window.internals.setMaximumIntervalForUserGestureForwardingForFetch(timeout); |
| return response.blob(); |
| }, error => { |
| consoleWrite(`FETCH(rejected): exception code = ${error.code}`); |
| }).then(response => { |
| consoleWrite(`FETCH(response resolved)`); |
| completionHandler(); |
| }, error => { |
| consoleWrite(`FETCH(response rejected): exception code = ${error.code}`); |
| }); |
| } |
| |
| function doPromise(completionHandler) |
| { |
| const promise = new Promise( (resolutionFunc) => { |
| resolutionFunc(); |
| }); |
| |
| promise.then(() => { |
| consoleWrite(`PROMISE(resolved)`); |
| completionHandler() |
| }); |
| } |
| |
| function doFetchThenPromise(completionHandler) |
| { |
| fetch(`/xmlhttprequest/resources/download-with-delay.py?iteration=1`, { method: 'GET', cache: "no-cache" }).then(response => { |
| consoleWrite(`FETCH(resolved): status = ${response.status}`); |
| return new Promise( (resolutionFunc) => resolutionFunc() ); |
| }).then(() => { |
| consoleWrite(`PROMISE(resolved)`); |
| completionHandler() |
| }); |
| } |
| |
| function doFetchThenCrypto(completionHandler) |
| { |
| fetch(`/xmlhttprequest/resources/download-with-delay.py?iteration=1`, { method: 'GET', cache: "no-cache" }).then(response => { |
| consoleWrite(`FETCH(resolved): status = ${response.status}`); |
| return crypto.subtle.generateKey({name: "RSA-OAEP", modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: "sha-1"}, false, ["decrypt", "encrypt", "wrapKey", "unwrapKey"]); |
| }).then(key => { |
| consoleWrite(`CRYPTO(key generated)`); |
| completionHandler() |
| }); |
| } |
| |
| function testComplete() |
| { |
| if (autoRun) |
| nextTest(); |
| } |
| |
| function runTest(test) |
| { |
| if (!test.sequence.length) { |
| switch (test.action) { |
| case 'play': |
| if (test.success) |
| run("shouldResolve(mediaElement.play()).then(testComplete, testComplete)"); |
| else |
| run("shouldReject(mediaElement.play()).then(testComplete, testComplete)"); |
| break; |
| |
| case 'popup': |
| if (window.internals) |
| internals.settings.setJavaScriptCanOpenWindowsAutomatically(false); |
| testExpected('window.open("about:blank")', null, test.success ? '!=' : '=='); |
| testComplete(); |
| break; |
| } |
| return; |
| } |
| |
| let command = test.sequence.shift().trim().split(' '); |
| let delay = command[0]; |
| let method = command[1].toLowerCase(); |
| |
| switch (method) { |
| case 'xhr': |
| consoleWrite(`sending XHR, delay = ${delay}`); |
| doXHR(delay, test.timeout || 1.0, () => runTest(test)); |
| break; |
| case 'timeout': |
| consoleWrite(`setting timeout, delay = ${delay}`); |
| setTimeout(() => { runTest(test); }, delay); |
| break; |
| case 'fetch': |
| consoleWrite(`fetching, delay = ${delay}`); |
| doFetch(delay, test.timeout || 1.0, () => runTest(test)); |
| break; |
| case 'fetchblob': |
| consoleWrite(`fetching blob, delay = ${delay}`); |
| doFetchBlob(delay, test.timeout || 1.0, () => runTest(test)); |
| break; |
| case 'promise': |
| consoleWrite(`promise`); |
| doPromise(() => runTest(test)); |
| break; |
| case 'fetchthenpromise': |
| consoleWrite(`fetch then promise`); |
| doFetchThenPromise(() => runTest(test)); |
| break; |
| case 'fetchthencrypto': |
| consoleWrite(`fetch then crypto`); |
| doFetchThenCrypto(() => runTest(test)); |
| break; |
| } |
| } |
| |
| let tests = [ |
| { title : 'gesture -> XHR -> timeout -> XHR -> window.open: should fail because XHR only propagates user gesture for media', |
| action : 'popup', withkey : true, success : false, sequence : ['100 XHR', '100 timeout', '100 xhr'] }, |
| |
| { title : 'gesture -> timeout -> XHR -> timeout -> window.open: should succeed', |
| action : 'popup', withkey : true, success : true, sequence : ['100 timeout', '100 xhr', '100 timeout'] }, |
| |
| { title : 'gesture -> timeout -> XHR -> timeout -> video playback: should succeed', |
| action : 'play', withkey : true, success : true, sequence : ['100 timeout', '100 XHR', '100 timeout'] }, |
| |
| { title : 'gesture -> XHR -> timeout -> XHR -> video playback: should succeed', |
| action : 'play', withkey : true, success : true, sequence : ['100 XHR', '100 timeout', '100 xhr'] }, |
| |
| { title : 'NO gesture -> XHR -> timeout -> video playback: should fail', |
| action : 'play', withkey : false, success : false, sequence : ['100 XHR', '100 timeout'] }, |
| |
| { title : 'gesture -> "long" XHR -> video playback: should fail', |
| action : 'play', withkey : true, success : false, timeout : 0.2, sequence : ['300 XHR'] }, |
| |
| { title : 'gesture -> fetch -> window.open: should fail because Fetch only propagates user gesture for media', |
| action : 'popup', withkey : true, success : false, sequence : ['100 Fetch'] }, |
| |
| { title : 'gesture -> fetch -> video playback: should succeed', |
| action : 'play', withkey : true, success : true, sequence : ['100 Fetch'] }, |
| |
| { title : 'gesture -> "long" Fetch -> video playback: should fail', |
| action : 'play', withkey : true, success : false, timeout : 0.2, sequence : ['300 Fetch'] }, |
| |
| { title : 'no gesture -> fetch -> video playback: should fail', |
| action : 'play', withkey : false, success : false, sequence : ['100 Fetch'] }, |
| |
| { title : 'gesture -> fetch -> blob -> window.open: should fail because Fetch only propagates user gesture for media', |
| action : 'popup', withkey : true, success : false, sequence : ['100 FetchBlob'] }, |
| |
| { title : 'gesture -> fetch -> blob -> video playback: should succeed', |
| action : 'play', withkey : true, success : true, sequence : ['100 FetchBlob'] }, |
| |
| { title : 'gesture -> fetch -> "long" blob -> video playback: should fail', |
| action : 'play', withkey : true, success : false, timeout : 0.2, sequence : ['300 FetchBlob'] }, |
| |
| { title : 'no gesture -> fetch -> blob -> video playback: should fail', |
| action : 'play', withkey : false, success : false, sequence : ['100 FetchBlob'] }, |
| |
| { title : 'no gesture -> promise -> video playback: should fail', |
| action : 'play', withkey : false, success : false, sequence : ['0 promise'] }, |
| |
| { title : 'gesture -> promise -> video playback: should fail because promise only propagates user gesture propagated through Fetch', |
| action : 'play', withkey : true, success : false, sequence : ['0 promise'] }, |
| |
| { title : 'gesture -> fetch -> promise -> video playback: should succeed', |
| action : 'play', withkey : true, success : true, sequence : ['0 FetchThenPromise'] }, |
| |
| { title : 'gesture -> fetch -> crypto -> video playback: should fail since the user gesture will not be propagated through other API even though it returns promises', |
| action : 'play', withkey : true, success : false, sequence : ['0 FetchThenCrypto'] }, |
| |
| ]; |
| |
| function nextTest() |
| { |
| if (!tests.length) { |
| consoleWrite(''); |
| endTest(); |
| return; |
| } |
| |
| let test = tests.shift(); |
| let action = () => { runTest(test) }; |
| consoleWrite(`<br>** ${test.title}`); |
| switch (test.action) { |
| case 'play': |
| let container = document.getElementById('parent'); |
| if (container.firstChild) |
| container.removeChild(container.firstChild); |
| |
| mediaElement = document.createElement('video'); |
| if (window.internals) |
| internals.setMediaElementRestrictions(mediaElement, 'RequireUserGestureForAudioRateChange'); |
| |
| mediaElement.src = "/media-resources/content/" + findMediaFile("video", "test"); |
| mediaElement.controls = 1; |
| container.appendChild(mediaElement); |
| waitForEvent('canplaythrough', event => { |
| if (test.withkey) |
| runWithKeyDown(action); |
| else |
| action(); |
| }); |
| |
| break; |
| |
| case 'popup': |
| if (test.withkey) |
| runWithKeyDown(action); |
| else |
| action(); |
| break; |
| } |
| } |
| |
| function start() |
| { |
| if (autoRun) |
| nextTest(); |
| } |
| |
| </script> |
| </head> |
| |
| <body onload="start()"> |
| <p>Test that a user gesture is preserved across XHR and setTimeout.</p> |
| <div id='parent'></div> |
| <button onclick="nextTest()">Run!</button><br> |
| <pre id='consoleLog'></pre> |
| </body> |
| </html> |
| |