// 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')); 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')); promise_rejects_dom(t, "InvalidStateError", wt.createBidirectionalStream()); }, 'server initiated closure while opening bidirectional stream before ready');