| <!doctype html> |
| <meta charset=utf-8> |
| <title>RTCDataChannel.prototype.send</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 editor draft: |
| // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html |
| |
| // The following helper functions are called from RTCPeerConnection-helper.js: |
| // createDataChannelPair |
| // awaitMessage |
| // blobToArrayBuffer |
| // assert_equals_array_buffer |
| |
| /* |
| 6.2. RTCDataChannel |
| interface RTCDataChannel : EventTarget { |
| ... |
| readonly attribute RTCDataChannelState readyState; |
| readonly attribute unsigned long bufferedAmount; |
| attribute EventHandler onmessage; |
| attribute DOMString binaryType; |
| |
| void send(USVString data); |
| void send(Blob data); |
| void send(ArrayBuffer data); |
| void send(ArrayBufferView 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]); |
| |
| // 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); |
| |
| /* |
| 6.2. send() |
| 2. If channel's readyState attribute is connecting, throw an InvalidStateError. |
| */ |
| test(t => { |
| const pc = new RTCPeerConnection(); |
| const channel = pc.createDataChannel('test'); |
| assert_equals(channel.readyState, 'connecting'); |
| assert_throws('InvalidStateError', () => channel.send(helloString)); |
| }, 'Calling send() when data channel is in connecting state should throw InvalidStateError'); |
| |
| /* |
| 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. |
| |
| [WebSocket] |
| 5. Feedback from the protocol |
| When a WebSocket message has been received |
| 4. If type indicates that the data is Text, then initialize event's data |
| attribute to data. |
| */ |
| promise_test(t => { |
| return createDataChannelPair() |
| .then(([channel1, channel2]) => { |
| channel1.send(helloString); |
| return awaitMessage(channel2) |
| }).then(message => { |
| assert_equals(typeof message, 'string', |
| 'Expect message to be a string'); |
| |
| assert_equals(message, helloString); |
| }); |
| }, 'Data channel should be able to send simple string and receive as string'); |
| |
| promise_test(t => { |
| return createDataChannelPair() |
| .then(([channel1, channel2]) => { |
| channel1.send(unicodeString); |
| return awaitMessage(channel2) |
| }).then(message => { |
| assert_equals(typeof message, 'string', |
| 'Expect message to be a string'); |
| |
| assert_equals(message, unicodeString); |
| }); |
| }, 'Data channel should be able to send unicode string and receive as unicode string'); |
| promise_test(t => { |
| return createDataChannelPair() |
| .then(([channel1, channel2]) => { |
| channel2.binaryType = 'arraybuffer'; |
| channel1.send(helloString); |
| return awaitMessage(channel2); |
| }).then(message => { |
| assert_equals(typeof message, 'string', |
| 'Expect message to be a string'); |
| |
| assert_equals(message, helloString); |
| }); |
| }, 'Data channel should ignore binaryType and always receive string message as string'); |
| |
| /* |
| 6.2. send() |
| 3. Execute the sub step that corresponds to the type of the methods argument: |
| ArrayBufferView object |
| Let data be the data stored in the section of the buffer described |
| by the ArrayBuffer object that the ArrayBufferView object references |
| and increase the bufferedAmount attribute by the length of the |
| ArrayBufferView in bytes. |
| |
| [WebSocket] |
| 5. Feedback from the protocol |
| When a WebSocket message has been received |
| 4. If binaryType is set to "arraybuffer", then initialize event's data |
| attribute to a new read-only ArrayBuffer object whose contents are data. |
| |
| [WebIDL] |
| 4.1. ArrayBufferView |
| typedef (Int8Array or Int16Array or Int32Array or |
| Uint8Array or Uint16Array or Uint32Array or Uint8ClampedArray or |
| Float32Array or Float64Array or DataView) ArrayBufferView; |
| */ |
| promise_test(t => { |
| return createDataChannelPair() |
| .then(([channel1, channel2]) => { |
| channel2.binaryType = 'arraybuffer'; |
| channel1.send(helloBuffer); |
| return awaitMessage(channel2) |
| }).then(messageBuffer => { |
| assert_true(messageBuffer instanceof ArrayBuffer, |
| 'Expect messageBuffer to be an ArrayBuffer'); |
| |
| assert_equals_array_buffer(messageBuffer, helloBuffer.buffer); |
| }); |
| }, 'Data channel should be able to send Uint8Array message and receive as ArrayBuffer'); |
| |
| /* |
| 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(t => { |
| return createDataChannelPair() |
| .then(([channel1, channel2]) => { |
| channel2.binaryType = 'arraybuffer'; |
| channel1.send(helloBuffer.buffer); |
| return awaitMessage(channel2) |
| }).then(messageBuffer => { |
| assert_true(messageBuffer instanceof ArrayBuffer, |
| 'Expect messageBuffer to be an ArrayBuffer'); |
| |
| assert_equals_array_buffer(messageBuffer, helloBuffer.buffer); |
| }); |
| }, 'Data channel should be able to send ArrayBuffer message and receive as ArrayBuffer'); |
| |
| /* |
| 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(t => { |
| return createDataChannelPair() |
| .then(([channel1, channel2]) => { |
| channel2.binaryType = 'arraybuffer'; |
| channel1.send(helloBlob); |
| return awaitMessage(channel2); |
| }).then(messageBuffer => { |
| assert_true(messageBuffer instanceof ArrayBuffer, |
| 'Expect messageBuffer to be an ArrayBuffer'); |
| |
| assert_equals_array_buffer(messageBuffer, helloBuffer.buffer); |
| }); |
| }, 'Data channel should be able to send Blob message and receive as ArrayBuffer'); |
| |
| /* |
| [WebSocket] |
| 5. Feedback from the protocol |
| When a WebSocket message has been received |
| 4. If binaryType is set to "blob", then initialize event's data attribute |
| to a new Blob object that represents data as its raw data. |
| */ |
| promise_test(t => { |
| return createDataChannelPair() |
| .then(([channel1, channel2]) => { |
| channel2.binaryType = 'blob'; |
| channel1.send(helloBuffer); |
| return awaitMessage(channel2); |
| }) |
| .then(messageBlob => { |
| assert_true(messageBlob instanceof Blob, |
| 'Expect received messageBlob to be a Blob'); |
| |
| return blobToArrayBuffer(messageBlob); |
| }).then(messageBuffer => { |
| assert_true(messageBuffer instanceof ArrayBuffer, |
| 'Expect messageBuffer to be an ArrayBuffer'); |
| |
| assert_equals_array_buffer(messageBuffer, helloBuffer.buffer); |
| }); |
| }, 'Data channel should be able to send ArrayBuffer message and receive as Blob'); |
| |
| /* |
| 6.2. RTCDataChannel |
| binaryType |
| The binaryType attribute must, on getting, return the value to which it was |
| last set. On setting, the user agent must set the IDL attribute to the new |
| value. When a RTCDataChannel object is created, the binaryType attribute must |
| be initialized to the string "blob". |
| */ |
| promise_test(t => { |
| return createDataChannelPair() |
| .then(([channel1, channel2]) => { |
| assert_equals(channel2.binaryType, 'blob', |
| 'Expect initial binaryType value to be blob'); |
| |
| channel1.send(helloBuffer); |
| return awaitMessage(channel2); |
| }) |
| .then(messageBlob => { |
| assert_true(messageBlob instanceof Blob, |
| 'Expect received messageBlob to be a Blob'); |
| |
| return blobToArrayBuffer(messageBlob); |
| }).then(messageBuffer => { |
| assert_true(messageBuffer instanceof ArrayBuffer, |
| 'Expect messageBuffer to be an ArrayBuffer'); |
| |
| assert_equals_array_buffer(messageBuffer, helloBuffer.buffer); |
| }); |
| }, 'Data channel binaryType should receive message as Blob by default'); |
| |
| // Test sending 3 messages: helloBuffer, unicodeString, helloBlob |
| async_test(t => { |
| const receivedMessages = []; |
| |
| const onMessage = t.step_func(event => { |
| const { data } = event; |
| receivedMessages.push(data); |
| |
| if(receivedMessages.length === 3) { |
| assert_equals_array_buffer(receivedMessages[0], helloBuffer.buffer); |
| assert_equals(receivedMessages[1], unicodeString); |
| assert_equals_array_buffer(receivedMessages[2], helloBuffer.buffer); |
| |
| t.done(); |
| } |
| }); |
| |
| createDataChannelPair() |
| .then(([channel1, channel2]) => { |
| channel2.binaryType = 'arraybuffer'; |
| channel2.addEventListener('message', onMessage); |
| |
| channel1.send(helloBuffer); |
| channel1.send(unicodeString); |
| channel1.send(helloBlob); |
| |
| }).catch(t.step_func(err => |
| assert_unreached(`Unexpected promise rejection: ${err}`))); |
| }, 'Sending multiple messages with different types should succeed and be received'); |
| |
| /* |
| [Deferred] |
| 6.2. RTCDataChannel |
| The send() method is being amended in w3c/webrtc-pc#1209 to throw error instead |
| of closing data channel when buffer is full |
| |
| send() |
| 4. If channel's underlying data transport is not established yet, or if the |
| closing procedure has started, then abort these steps. |
| 5. Attempt to send data on channel's underlying data transport; if the data |
| cannot be sent, e.g. because it would need to be buffered but the buffer |
| is full, the user agent must abruptly close channel's underlying data |
| transport with an error. |
| |
| test(t => { |
| const pc = new RTCPeerConnection(); |
| const channel = pc.createDataChannel('test'); |
| channel.close(); |
| assert_equals(channel.readyState, 'closing'); |
| channel.send(helloString); |
| }, 'Calling send() when data channel is in closing state should succeed'); |
| */ |
| </script> |