| // META: global=window,worker | |
| // META: script=/common/get-host-info.sub.js | |
| // META: script=resources/webtransport-test-helpers.sub.js | |
| // META: script=/common/utils.js | |
| promise_test(async t => { | |
| const id = token(); | |
| const wt = new WebTransport(webtransport_url(`client-close.py?token=${id}`)); | |
| add_completion_callback(() => wt.close()); | |
| await wt.ready; | |
| wt.close(); | |
| const close_info = await wt.closed; | |
| assert_equals(close_info.closeCode, 0, 'code'); | |
| assert_equals(close_info.reason, '', 'reason'); | |
| await wait(10); | |
| const data = await query(id); | |
| assert_own_property(data, 'session-close-info'); | |
| const info = data['session-close-info'] | |
| assert_false(info.abruptly, 'abruptly'); | |
| assert_equals(info.close_info.code, 0, 'code'); | |
| assert_equals(info.close_info.reason, '', 'reason'); | |
| }, 'close'); | |
| promise_test(async t => { | |
| const wt = new WebTransport(webtransport_url('echo.py')); | |
| wt.close(); | |
| try { | |
| await wt.closed; | |
| } catch(e) { | |
| await promise_rejects_exactly(t, e, wt.ready, 'ready promise should be rejected'); | |
| assert_true(e instanceof WebTransportError); | |
| assert_equals(e.source, 'session', 'source'); | |
| assert_equals(e.streamErrorCode, null, 'streamErrorCode'); | |
| } | |
| }, 'close without waiting for ready'); | |
| promise_test(async t => { | |
| const id = token(); | |
| const wt = new WebTransport(webtransport_url(`client-close.py?token=${id}`)); | |
| add_completion_callback(() => wt.close()); | |
| await wt.ready; | |
| wt.close({closeCode: 99, reason: 'reason X'}); | |
| const close_info = await wt.closed; | |
| assert_equals(close_info.closeCode, 99, 'code'); | |
| assert_equals(close_info.reason, 'reason X', 'reason'); | |
| await wait(10); | |
| const data = await query(id); | |
| assert_own_property(data, 'session-close-info'); | |
| const info = data['session-close-info'] | |
| assert_false(info.abruptly, 'abruptly'); | |
| assert_equals(info.close_info.code, 99, 'code'); | |
| assert_equals(info.close_info.reason, 'reason X', 'reason'); | |
| }, 'close with code and reason'); | |
| promise_test(async t => { | |
| const id = token(); | |
| const wt = new WebTransport(webtransport_url(`client-close.py?token=${id}`)); | |
| add_completion_callback(() => wt.close()); | |
| await wt.ready; | |
| const reason = 'あいうえお'.repeat(1000); | |
| wt.close({closeCode: 11, reason}); | |
| const close_info = await wt.closed; | |
| assert_equals(close_info.closeCode, 11, 'code'); | |
| // `close_info.reason` should report the original, non-truncated reason as | |
| // step 9 of https://w3c.github.io/webtransport/#dom-webtransport-close | |
| // uses the original `closeInfo` to perform `Cleanup`. | |
| assert_equals(close_info.reason, reason, 'reason'); | |
| await wait(10); | |
| const data = await query(id); | |
| assert_own_property(data, 'session-close-info'); | |
| const info = data['session-close-info'] | |
| // Server should have received truncated reason as step 6 of | |
| // https://w3c.github.io/webtransport/#dom-webtransport-close specifies. | |
| const expected_reason = | |
| new TextDecoder().decode( | |
| new TextEncoder().encode(reason).slice(0, 1024)).replaceAll('\ufffd', ''); | |
| assert_false(info.abruptly, 'abruptly'); | |
| assert_equals(info.close_info.code, 11, 'code'); | |
| assert_equals(info.close_info.reason, expected_reason, 'reason'); | |
| }, 'close with code and long reason'); | |
| promise_test(async t => { | |
| const wt = new WebTransport(webtransport_url('server-close.py')); | |
| const close_info = await wt.closed; | |
| assert_equals(close_info.closeCode, 0, 'code'); | |
| assert_equals(close_info.reason, '', 'reason'); | |
| }, 'server initiated closure without code and reason'); | |
| promise_test(async t => { | |
| const code = 32; | |
| const reason = 'abc'; | |
| const wt = new WebTransport( | |
| webtransport_url(`server-close.py?code=${code}&reason=${reason}`)); | |
| add_completion_callback(() => wt.close()); | |
| const close_info = await wt.closed; | |
| assert_equals(close_info.closeCode, code, 'code'); | |
| assert_equals(close_info.reason, reason, 'reason'); | |
| }, 'server initiated closure with code and reason'); | |
| promise_test(async t => { | |
| const wt = new WebTransport(webtransport_url('server-connection-close.py')); | |
| add_completion_callback(() => wt.close()); | |
| const streams_reader = wt.incomingBidirectionalStreams.getReader(); | |
| const { value: bidi } = await streams_reader.read(); | |
| const writer = bidi.writable.getWriter(); | |
| const reader = bidi.readable.getReader(); | |
| try { | |
| writer.write(new Uint8Array([65])); | |
| } catch (e) { | |
| } | |
| // Sadly we cannot use promise_rejects_dom as the error constructor is | |
| // WebTransportError rather than DOMException. | |
| // We get a possible error, and then make sure wt.closed is rejected with it. | |
| const e = await wt.closed.catch(e => e); | |
| await promise_rejects_exactly(t, e, wt.closed, 'wt.closed'); | |
| await promise_rejects_exactly(t, e, writer.closed, 'writer.closed'); | |
| await promise_rejects_exactly(t, e, reader.closed, 'reader.closed'); | |
| assert_true(e instanceof WebTransportError); | |
| assert_equals(e.source, 'session', 'source'); | |
| assert_equals(e.streamErrorCode, null, 'streamErrorCode'); | |
| }, 'server initiated connection closure'); | |
| promise_test(async t => { | |
| const wt = new WebTransport(webtransport_url('echo.py')); | |
| const stream = await wt.createUnidirectionalStream(); | |
| await wt.ready; | |
| }, 'opening unidirectional stream before ready'); | |
| promise_test(async t => { | |
| const wt = new WebTransport(webtransport_url('echo.py')); | |
| const stream = await wt.createBidirectionalStream(); | |
| await wt.ready; | |
| }, 'opening bidirectional stream before ready'); | |
| promise_test(async t => { | |
| const wt = new WebTransport(webtransport_url('server-close.py')); | |
| await promise_rejects_dom(t, "InvalidStateError", | |
| wt.createUnidirectionalStream()); | |
| }, 'server initiated closure while opening unidirectional stream before ready'); | |
| promise_test(async t => { | |
| const wt = new WebTransport(webtransport_url('server-close.py')); | |
| await promise_rejects_dom(t, "InvalidStateError", | |
| wt.createBidirectionalStream()); | |
| }, 'server initiated closure while opening bidirectional stream before ready'); | |
| // Regression test for https://crbug.com/347710668. | |
| promise_test(async t => { | |
| const wt = new WebTransport(webtransport_url('server-read-then-close.py')); | |
| add_completion_callback(() => wt.close()); | |
| await wt.ready; | |
| const bidi_reader = wt.incomingBidirectionalStreams.getReader(); | |
| const { value: bidi } = await bidi_reader.read(); | |
| bidi.writable.getWriter().write(new TextEncoder().encode('some data')); | |
| const reader = bidi.readable.getReader(); | |
| await reader.closed.catch(t.step_func( | |
| e => assert_true(e instanceof WebTransportError))); | |
| // The WebTransport session will already be closed. | |
| const {reason, closeCode} = await wt.closed; | |
| assert_equals(reason, '', 'reason should be default'); | |
| assert_equals(closeCode, 0, 'closeCode should be default'); | |
| }, 'reading closed property after close should work'); |