| <!doctype html> | |
| <meta charset=utf-8> | |
| <meta name="timeout" content="long"> | |
| <title>RTCDataChannel.prototype.bufferedAmount</title> | |
| <script src="/resources/testharness.js"></script> | |
| <script src="/resources/testharnessreport.js"></script> | |
| <script src="RTCPeerConnection-helper.js"></script> | |
| <script> | |
| 'use strict'; | |
| // Test is based on the following revision: | |
| // https://rawgit.com/w3c/webrtc-pc/1cc5bfc3ff18741033d804c4a71f7891242fb5b3/webrtc.html | |
| // The following helper functions are called from RTCPeerConnection-helper.js: | |
| // createDataChannelPair | |
| // awaitMessage | |
| /* | |
| 6.2. RTCDataChannel | |
| interface RTCDataChannel : EventTarget { | |
| ... | |
| readonly attribute unsigned long bufferedAmount; | |
| void send(USVString data); | |
| void send(Blob data); | |
| void send(ArrayBuffer data); | |
| void send(ArrayBufferView data); | |
| }; | |
| bufferedAmount | |
| The bufferedAmount attribute must return the number of bytes of application | |
| data (UTF-8 text and binary data) that have been queued using send() but that, | |
| as of the last time the event loop started executing a task, had not yet been | |
| transmitted to the network. (This thus includes any text sent during the | |
| execution of the current task, regardless of whether the user agent is able | |
| to transmit text asynchronously with script execution.) This does not include | |
| framing overhead incurred by the protocol, or buffering done by the operating | |
| system or network hardware. The value of the [[BufferedAmount]] slot will only | |
| increase with each call to the send() method as long as the [[ReadyState]] slot | |
| is open; however, the slot does not reset to zero once the channel closes. When | |
| the underlying data transport sends data from its queue, the user agent MUST | |
| queue a task that reduces [[BufferedAmount]] with the number of bytes that was | |
| sent. | |
| [WebMessaging] | |
| interface MessageEvent : Event { | |
| readonly attribute any data; | |
| ... | |
| }; | |
| */ | |
| // Simple ASCII encoded string | |
| const helloString = 'hello'; | |
| // ASCII encoded buffer representation of the string | |
| const helloBuffer = Uint8Array.of(0x68, 0x65, 0x6c, 0x6c, 0x6f); | |
| const helloBlob = new Blob([helloBuffer]); | |
| const emptyBuffer = Uint8Array.of(); | |
| const emptyBlob = new Blob([emptyBuffer]); | |
| // Unicode string with multiple code units | |
| const unicodeString = '世界你好'; | |
| // UTF-8 encoded buffer representation of the string | |
| const unicodeBuffer = Uint8Array.of( | |
| 0xe4, 0xb8, 0x96, 0xe7, 0x95, 0x8c, | |
| 0xe4, 0xbd, 0xa0, 0xe5, 0xa5, 0xbd); | |
| for (const options of [{}, {negotiated: true, id: 0}]) { | |
| const mode = `${options.negotiated? "negotiated " : ""}datachannel`; | |
| /* | |
| Ensure .bufferedAmount is 0 initially for both sides. | |
| */ | |
| promise_test(async (t) => { | |
| const [dc1, dc2] = await createDataChannelPair(t, options); | |
| assert_equals(dc1.bufferedAmount, 0, 'Expect bufferedAmount to be 0'); | |
| assert_equals(dc2.bufferedAmount, 0, 'Expect bufferedAmount to be 0'); | |
| }, `${mode} bufferedAmount initial value should be 0 for both peers`); | |
| /* | |
| 6.2. send() | |
| 3. Execute the sub step that corresponds to the type of the methods argument: | |
| string object | |
| Let data be the object and increase the bufferedAmount attribute | |
| by the number of bytes needed to express data as UTF-8. | |
| */ | |
| promise_test(async (t) => { | |
| const [dc1, dc2] = await createDataChannelPair(t, options); | |
| dc1.send(unicodeString); | |
| assert_equals(dc1.bufferedAmount, unicodeBuffer.byteLength, | |
| 'Expect bufferedAmount to be the byte length of the unicode string'); | |
| await awaitMessage(dc2); | |
| assert_equals(dc1.bufferedAmount, 0, | |
| 'Expect sender bufferedAmount to be reduced after message is sent'); | |
| }, `${mode} bufferedAmount should increase to byte length of encoded` + | |
| `unicode string sent`); | |
| promise_test(async (t) => { | |
| const [dc1, dc2] = await createDataChannelPair(t, options); | |
| dc1.send(""); | |
| assert_equals(dc1.bufferedAmount, 0, | |
| 'Expect bufferedAmount to stay at zero after sending empty string'); | |
| await awaitMessage(dc2); | |
| assert_equals(dc1.bufferedAmount, 0, 'Expect sender bufferedAmount unchanged'); | |
| }, `${mode} bufferedAmount should stay at zero for empty string sent`); | |
| /* | |
| 6.2. send() | |
| 3. Execute the sub step that corresponds to the type of the methods argument: | |
| ArrayBuffer object | |
| Let data be the data stored in the buffer described by the ArrayBuffer | |
| object and increase the bufferedAmount attribute by the length of the | |
| ArrayBuffer in bytes. | |
| */ | |
| promise_test(async (t) => { | |
| const [dc1, dc2] = await createDataChannelPair(t, options); | |
| dc1.send(helloBuffer.buffer); | |
| assert_equals(dc1.bufferedAmount, helloBuffer.byteLength, | |
| 'Expect bufferedAmount to increase to byte length of sent buffer'); | |
| await awaitMessage(dc2); | |
| assert_equals(dc1.bufferedAmount, 0, | |
| 'Expect sender bufferedAmount to be reduced after message is sent'); | |
| }, `${mode} bufferedAmount should increase to byte length of buffer sent`); | |
| promise_test(async (t) => { | |
| const [dc1, dc2] = await createDataChannelPair(t, options); | |
| dc1.send(emptyBuffer.buffer); | |
| assert_equals(dc1.bufferedAmount, 0, | |
| 'Expect bufferedAmount to stay at zero after sending empty buffer'); | |
| await awaitMessage(dc2); | |
| assert_equals(dc1.bufferedAmount, 0, | |
| 'Expect sender bufferedAmount unchanged'); | |
| }, `${mode} bufferedAmount should stay at zero for empty buffer sent`); | |
| /* | |
| 6.2. send() | |
| 3. Execute the sub step that corresponds to the type of the methods argument: | |
| Blob object | |
| Let data be the raw data represented by the Blob object and increase | |
| the bufferedAmount attribute by the size of data, in bytes. | |
| */ | |
| promise_test(async (t) => { | |
| const [dc1, dc2] = await createDataChannelPair(t, options); | |
| dc1.send(helloBlob); | |
| assert_equals(dc1.bufferedAmount, helloBlob.size, | |
| 'Expect bufferedAmount to increase to size of sent blob'); | |
| await awaitMessage(dc2); | |
| assert_equals(dc1.bufferedAmount, 0, | |
| 'Expect sender bufferedAmount to be reduced after message is sent'); | |
| }, `${mode} bufferedAmount should increase to size of blob sent`); | |
| promise_test(async (t) => { | |
| const [dc1, dc2] = await createDataChannelPair(t, options); | |
| dc1.send(emptyBlob); | |
| assert_equals(dc1.bufferedAmount, 0, | |
| 'Expect bufferedAmount to stay at zero after sending empty blob'); | |
| await awaitMessage(dc2); | |
| assert_equals(dc1.bufferedAmount, 0, | |
| 'Expect sender bufferedAmount unchanged'); | |
| }, `${mode} bufferedAmount should stay at zero for empty blob sent`); | |
| // Test sending 3 messages: helloBuffer, unicodeString, helloBlob | |
| promise_test(async (t) => { | |
| const resolver = new Resolver(); | |
| let messageCount = 0; | |
| const [dc1, dc2] = await createDataChannelPair(t, options); | |
| dc2.onmessage = t.step_func(() => { | |
| if (++messageCount === 3) { | |
| assert_equals(dc1.bufferedAmount, 0, | |
| 'Expect sender bufferedAmount to be reduced after message is sent'); | |
| resolver.resolve(); | |
| } | |
| }); | |
| dc1.send(helloBuffer); | |
| assert_equals(dc1.bufferedAmount, helloString.length, | |
| 'Expect bufferedAmount to be the total length of all messages queued to send'); | |
| dc1.send(unicodeString); | |
| assert_equals(dc1.bufferedAmount, | |
| helloString.length + unicodeBuffer.byteLength, | |
| 'Expect bufferedAmount to be the total length of all messages queued to send'); | |
| dc1.send(helloBlob); | |
| assert_equals(dc1.bufferedAmount, | |
| helloString.length*2 + unicodeBuffer.byteLength, | |
| 'Expect bufferedAmount to be the total length of all messages queued to send'); | |
| await resolver; | |
| }, `${mode} bufferedAmount should increase by byte length for each message sent`); | |
| promise_test(async (t) => { | |
| const [dc1] = await createDataChannelPair(t, options); | |
| dc1.send(helloBuffer.buffer); | |
| assert_equals(dc1.bufferedAmount, helloBuffer.byteLength, | |
| 'Expect bufferedAmount to increase to byte length of sent buffer'); | |
| dc1.close(); | |
| assert_equals(dc1.bufferedAmount, helloBuffer.byteLength, | |
| 'Expect bufferedAmount to not decrease immediately after closing the channel'); | |
| }, `${mode} bufferedAmount should not decrease immediately after initiating closure`); | |
| promise_test(async (t) => { | |
| const pc1 = new RTCPeerConnection(); | |
| t.add_cleanup(() => pc1.close()); | |
| const [dc1] = await createDataChannelPair(t, options, pc1); | |
| dc1.send(helloBuffer.buffer); | |
| assert_equals(dc1.bufferedAmount, helloBuffer.byteLength, | |
| 'Expect bufferedAmount to increase to byte length of sent buffer'); | |
| pc1.close(); | |
| assert_equals(dc1.bufferedAmount, helloBuffer.byteLength, | |
| 'Expect bufferedAmount to not decrease after closing the peer connection'); | |
| }, `${mode} bufferedAmount should not decrease after closing the peer connection`); | |
| promise_test(async t => { | |
| const [channel1, channel2] = await createDataChannelPair(t, options); | |
| channel1.addEventListener('bufferedamountlow', t.step_func_done(() => { | |
| assert_true(channel1.bufferedAmount <= channel1.bufferedAmountLowThreshold); | |
| })); | |
| const eventWatcher = new EventWatcher(t, channel1, ['bufferedamountlow']); | |
| channel1.send(helloString); | |
| await eventWatcher.wait_for(['bufferedamountlow']); | |
| }, `${mode} bufferedamountlow event fires after send() is complete`); | |
| promise_test(async t => { | |
| const [channel1, channel2] = await createDataChannelPair(t, options); | |
| channel1.send(helloString); | |
| assert_equals(channel1.bufferedAmount, helloString.length); | |
| await awaitMessage(channel2); | |
| assert_equals(channel1.bufferedAmount, 0); | |
| }, `${mode} bufferedamount is data.length on send(data)`); | |
| promise_test(async t => { | |
| const [channel1, channel2] = await createDataChannelPair(t, options); | |
| channel1.send(helloString); | |
| assert_equals(channel1.bufferedAmount, helloString.length); | |
| assert_equals(channel1.bufferedAmount, helloString.length); | |
| }, `${mode} bufferedamount returns the same amount if no more data is`); | |
| promise_test(async t => { | |
| const [channel1, channel2] = await createDataChannelPair(t, options); | |
| let eventFireCount = 0; | |
| channel1.addEventListener('bufferedamountlow', t.step_func(() => { | |
| assert_true(channel1.bufferedAmount <= channel1.bufferedAmountLowThreshold); | |
| assert_equals(++eventFireCount, 1); | |
| })); | |
| const eventWatcher = new EventWatcher(t, channel1, ['bufferedamountlow']); | |
| channel1.send(helloString); | |
| assert_equals(channel1.bufferedAmount, helloString.length); | |
| channel1.send(helloString); | |
| assert_equals(channel1.bufferedAmount, 2 * helloString.length); | |
| await eventWatcher.wait_for(['bufferedamountlow']); | |
| }, `${mode} bufferedamountlow event fires only once after multiple` + | |
| ` consecutive send() calls`); | |
| promise_test(async t => { | |
| const [channel1, channel2] = await createDataChannelPair(t, options); | |
| const eventWatcher = new EventWatcher(t, channel1, ['bufferedamountlow']); | |
| channel1.send(helloString); | |
| assert_equals(channel1.bufferedAmount, helloString.length); | |
| await eventWatcher.wait_for(['bufferedamountlow']); | |
| assert_equals(await awaitMessage(channel2), helloString); | |
| channel1.send(helloString); | |
| assert_equals(channel1.bufferedAmount, helloString.length); | |
| await eventWatcher.wait_for(['bufferedamountlow']); | |
| assert_equals(await awaitMessage(channel2), helloString); | |
| }, `${mode} bufferedamountlow event fires after each sent message`); | |
| } | |
| </script> |