From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../tests/encoding/streams/backpressure.any.js | 60 ++++ .../encoding/streams/decode-attributes.any.js | 71 +++++ .../encoding/streams/decode-bad-chunks.any.js | 38 +++ .../encoding/streams/decode-ignore-bom.any.js | 38 +++ .../streams/decode-incomplete-input.any.js | 24 ++ .../tests/encoding/streams/decode-non-utf8.any.js | 83 ++++++ .../encoding/streams/decode-split-character.any.js | 50 ++++ .../tests/encoding/streams/decode-utf8.any.js | 76 ++++++ .../encoding/streams/decode-utf8.any.js.headers | 2 + .../encoding/streams/encode-bad-chunks.any.js | 63 +++++ .../tests/encoding/streams/encode-utf8.any.js | 144 ++++++++++ .../tests/encoding/streams/invalid-realm.window.js | 37 +++ .../streams/readable-writable-properties.any.js | 22 ++ .../tests/encoding/streams/realms.window.js | 304 +++++++++++++++++++++ .../resources/readable-stream-from-array.js | 12 + .../streams/resources/readable-stream-to-array.js | 11 + .../encoding/streams/stringification-crash.html | 20 ++ 17 files changed, 1055 insertions(+) create mode 100644 testing/web-platform/tests/encoding/streams/backpressure.any.js create mode 100644 testing/web-platform/tests/encoding/streams/decode-attributes.any.js create mode 100644 testing/web-platform/tests/encoding/streams/decode-bad-chunks.any.js create mode 100644 testing/web-platform/tests/encoding/streams/decode-ignore-bom.any.js create mode 100644 testing/web-platform/tests/encoding/streams/decode-incomplete-input.any.js create mode 100644 testing/web-platform/tests/encoding/streams/decode-non-utf8.any.js create mode 100644 testing/web-platform/tests/encoding/streams/decode-split-character.any.js create mode 100644 testing/web-platform/tests/encoding/streams/decode-utf8.any.js create mode 100644 testing/web-platform/tests/encoding/streams/decode-utf8.any.js.headers create mode 100644 testing/web-platform/tests/encoding/streams/encode-bad-chunks.any.js create mode 100644 testing/web-platform/tests/encoding/streams/encode-utf8.any.js create mode 100644 testing/web-platform/tests/encoding/streams/invalid-realm.window.js create mode 100644 testing/web-platform/tests/encoding/streams/readable-writable-properties.any.js create mode 100644 testing/web-platform/tests/encoding/streams/realms.window.js create mode 100644 testing/web-platform/tests/encoding/streams/resources/readable-stream-from-array.js create mode 100644 testing/web-platform/tests/encoding/streams/resources/readable-stream-to-array.js create mode 100644 testing/web-platform/tests/encoding/streams/stringification-crash.html (limited to 'testing/web-platform/tests/encoding/streams') diff --git a/testing/web-platform/tests/encoding/streams/backpressure.any.js b/testing/web-platform/tests/encoding/streams/backpressure.any.js new file mode 100644 index 0000000000..0f67e33584 --- /dev/null +++ b/testing/web-platform/tests/encoding/streams/backpressure.any.js @@ -0,0 +1,60 @@ +// META: global=window,worker,shadowrealm + +'use strict'; + +const classes = [ + { + name: 'TextDecoderStream', + input: new Uint8Array([65]) + }, + { + name: 'TextEncoderStream', + input: 'A' + } +]; + +const microtasksRun = () => new Promise(resolve => step_timeout(resolve, 0)); + +for (const streamClass of classes) { + promise_test(async () => { + const stream = new self[streamClass.name](); + const writer = stream.writable.getWriter(); + const reader = stream.readable.getReader(); + const events = []; + await microtasksRun(); + const writePromise = writer.write(streamClass.input); + writePromise.then(() => events.push('write')); + await microtasksRun(); + events.push('paused'); + await reader.read(); + events.push('read'); + await writePromise; + assert_array_equals(events, ['paused', 'read', 'write'], + 'write should happen after read'); + }, 'write() should not complete until read relieves backpressure for ' + + `${streamClass.name}`); + + promise_test(async () => { + const stream = new self[streamClass.name](); + const writer = stream.writable.getWriter(); + const reader = stream.readable.getReader(); + const events = []; + await microtasksRun(); + const readPromise1 = reader.read(); + readPromise1.then(() => events.push('read1')); + const writePromise1 = writer.write(streamClass.input); + const writePromise2 = writer.write(streamClass.input); + writePromise1.then(() => events.push('write1')); + writePromise2.then(() => events.push('write2')); + await microtasksRun(); + events.push('paused'); + const readPromise2 = reader.read(); + readPromise2.then(() => events.push('read2')); + await Promise.all([writePromise1, writePromise2, + readPromise1, readPromise2]); + assert_array_equals(events, ['read1', 'write1', 'paused', 'read2', + 'write2'], + 'writes should not happen before read2'); + }, 'additional writes should wait for backpressure to be relieved for ' + + `class ${streamClass.name}`); +} diff --git a/testing/web-platform/tests/encoding/streams/decode-attributes.any.js b/testing/web-platform/tests/encoding/streams/decode-attributes.any.js new file mode 100644 index 0000000000..f64f45b56a --- /dev/null +++ b/testing/web-platform/tests/encoding/streams/decode-attributes.any.js @@ -0,0 +1,71 @@ +// META: global=window,worker,shadowrealm + +'use strict'; + +// Verify that constructor arguments are correctly reflected in the attributes. + +// Mapping of the first argument to TextDecoderStream to the expected value of +// the encoding attribute. We assume that if this subset works correctly, the +// rest probably work too. +const labelToName = { + 'unicode-1-1-utf-8': 'utf-8', + 'iso-8859-2': 'iso-8859-2', + 'ascii': 'windows-1252', + 'utf-16': 'utf-16le' +}; + +for (const label of Object.keys(labelToName)) { + test(() => { + const stream = new TextDecoderStream(label); + assert_equals(stream.encoding, labelToName[label], 'encoding should match'); + }, `encoding attribute should have correct value for '${label}'`); +} + +for (const falseValue of [false, 0, '', undefined, null]) { + test(() => { + const stream = new TextDecoderStream('utf-8', { fatal: falseValue }); + assert_false(stream.fatal, 'fatal should be false'); + }, `setting fatal to '${falseValue}' should set the attribute to false`); + + test(() => { + const stream = new TextDecoderStream('utf-8', { ignoreBOM: falseValue }); + assert_false(stream.ignoreBOM, 'ignoreBOM should be false'); + }, `setting ignoreBOM to '${falseValue}' should set the attribute to false`); +} + +for (const trueValue of [true, 1, {}, [], 'yes']) { + test(() => { + const stream = new TextDecoderStream('utf-8', { fatal: trueValue }); + assert_true(stream.fatal, 'fatal should be true'); + }, `setting fatal to '${trueValue}' should set the attribute to true`); + + test(() => { + const stream = new TextDecoderStream('utf-8', { ignoreBOM: trueValue }); + assert_true(stream.ignoreBOM, 'ignoreBOM should be true'); + }, `setting ignoreBOM to '${trueValue}' should set the attribute to true`); +} + +test(() => { + assert_throws_js(RangeError, () => new TextDecoderStream(''), + 'the constructor should throw'); +}, 'constructing with an invalid encoding should throw'); + +test(() => { + assert_throws_js(TypeError, () => new TextDecoderStream({ + toString() { return {}; } + }), 'the constructor should throw'); +}, 'constructing with a non-stringifiable encoding should throw'); + +test(() => { + assert_throws_js(Error, + () => new TextDecoderStream('utf-8', { + get fatal() { throw new Error(); } + }), 'the constructor should throw'); +}, 'a throwing fatal member should cause the constructor to throw'); + +test(() => { + assert_throws_js(Error, + () => new TextDecoderStream('utf-8', { + get ignoreBOM() { throw new Error(); } + }), 'the constructor should throw'); +}, 'a throwing ignoreBOM member should cause the constructor to throw'); diff --git a/testing/web-platform/tests/encoding/streams/decode-bad-chunks.any.js b/testing/web-platform/tests/encoding/streams/decode-bad-chunks.any.js new file mode 100644 index 0000000000..21d61427a2 --- /dev/null +++ b/testing/web-platform/tests/encoding/streams/decode-bad-chunks.any.js @@ -0,0 +1,38 @@ +// META: global=window,worker,shadowrealm + +'use strict'; + +const badChunks = [ + { + name: 'undefined', + value: undefined + }, + { + name: 'null', + value: null + }, + { + name: 'numeric', + value: 3.14 + }, + { + name: 'object, not BufferSource', + value: {} + }, + { + name: 'array', + value: [65] + } +]; + +for (const chunk of badChunks) { + promise_test(async t => { + const tds = new TextDecoderStream(); + const reader = tds.readable.getReader(); + const writer = tds.writable.getWriter(); + const writePromise = writer.write(chunk.value); + const readPromise = reader.read(); + await promise_rejects_js(t, TypeError, writePromise, 'write should reject'); + await promise_rejects_js(t, TypeError, readPromise, 'read should reject'); + }, `chunk of type ${chunk.name} should error the stream`); +} diff --git a/testing/web-platform/tests/encoding/streams/decode-ignore-bom.any.js b/testing/web-platform/tests/encoding/streams/decode-ignore-bom.any.js new file mode 100644 index 0000000000..92f89c8015 --- /dev/null +++ b/testing/web-platform/tests/encoding/streams/decode-ignore-bom.any.js @@ -0,0 +1,38 @@ +// META: global=window,worker +// META: script=resources/readable-stream-from-array.js +// META: script=resources/readable-stream-to-array.js + +const cases = [ + {encoding: 'utf-8', bytes: [0xEF, 0xBB, 0xBF, 0x61, 0x62, 0x63]}, + {encoding: 'utf-16le', bytes: [0xFF, 0xFE, 0x61, 0x00, 0x62, 0x00, 0x63, 0x00]}, + {encoding: 'utf-16be', bytes: [0xFE, 0xFF, 0x00, 0x61, 0x00, 0x62, 0x00, 0x63]} +]; +const BOM = '\uFEFF'; + +// |inputChunks| is an array of chunks, each represented by an array of +// integers. |ignoreBOM| is true or false. The result value is the output of the +// pipe, concatenated into a single string. +async function pipeAndAssemble(inputChunks, encoding, ignoreBOM) { + const chunksAsUint8 = inputChunks.map(values => new Uint8Array(values)); + const readable = readableStreamFromArray(chunksAsUint8); + const outputArray = await readableStreamToArray(readable.pipeThrough( + new TextDecoderStream(encoding, {ignoreBOM}))); + return outputArray.join(''); +} + +for (const testCase of cases) { + for (let splitPoint = 0; splitPoint < 4; ++splitPoint) { + promise_test(async () => { + const inputChunks = [testCase.bytes.slice(0, splitPoint), + testCase.bytes.slice(splitPoint)]; + const withIgnoreBOM = + await pipeAndAssemble(inputChunks, testCase.encoding, true); + assert_equals(withIgnoreBOM, BOM + 'abc', 'BOM should be preserved'); + + const withoutIgnoreBOM = + await pipeAndAssemble(inputChunks, testCase.encoding, false); + assert_equals(withoutIgnoreBOM, 'abc', 'BOM should be stripped') + }, `ignoreBOM should work for encoding ${testCase.encoding}, split at ` + + `character ${splitPoint}`); + } +} diff --git a/testing/web-platform/tests/encoding/streams/decode-incomplete-input.any.js b/testing/web-platform/tests/encoding/streams/decode-incomplete-input.any.js new file mode 100644 index 0000000000..3add74336d --- /dev/null +++ b/testing/web-platform/tests/encoding/streams/decode-incomplete-input.any.js @@ -0,0 +1,24 @@ +// META: global=window,worker +// META: script=resources/readable-stream-from-array.js +// META: script=resources/readable-stream-to-array.js + +'use strict'; + +const inputBytes = [229]; + +promise_test(async () => { + const input = readableStreamFromArray([new Uint8Array(inputBytes)]); + const output = input.pipeThrough(new TextDecoderStream()); + const array = await readableStreamToArray(output); + assert_array_equals(array, ['\uFFFD'], 'array should have one element'); +}, 'incomplete input with error mode "replacement" should end with a ' + + 'replacement character'); + +promise_test(async t => { + const input = readableStreamFromArray([new Uint8Array(inputBytes)]); + const output = input.pipeThrough(new TextDecoderStream( + 'utf-8', {fatal: true})); + const reader = output.getReader(); + await promise_rejects_js(t, TypeError, reader.read(), + 'read should reject'); +}, 'incomplete input with error mode "fatal" should error the stream'); diff --git a/testing/web-platform/tests/encoding/streams/decode-non-utf8.any.js b/testing/web-platform/tests/encoding/streams/decode-non-utf8.any.js new file mode 100644 index 0000000000..effdee3b43 --- /dev/null +++ b/testing/web-platform/tests/encoding/streams/decode-non-utf8.any.js @@ -0,0 +1,83 @@ +// META: global=window,worker,shadowrealm + +'use strict'; + +// The browser is assumed to use the same implementation as for TextDecoder, so +// this file don't replicate the exhaustive checks it has. It is just a smoke +// test that non-UTF-8 encodings work at all. + +const encodings = [ + { + name: 'UTF-16BE', + value: [108, 52], + expected: "\u{6c34}", + invalid: [0xD8, 0x00] + }, + { + name: 'UTF-16LE', + value: [52, 108], + expected: "\u{6c34}", + invalid: [0x00, 0xD8] + }, + { + name: 'Shift_JIS', + value: [144, 133], + expected: "\u{6c34}", + invalid: [255] + }, + { + name: 'ISO-2022-JP', + value: [65, 66, 67, 0x1B, 65, 66, 67], + expected: "ABC\u{fffd}ABC", + invalid: [0x0E] + }, + { + name: 'ISO-8859-14', + value: [100, 240, 114], + expected: "d\u{0175}r", + invalid: undefined // all bytes are treated as valid + } +]; + +for (const encoding of encodings) { + promise_test(async () => { + const stream = new TextDecoderStream(encoding.name); + const reader = stream.readable.getReader(); + const writer = stream.writable.getWriter(); + const writePromise = writer.write(new Uint8Array(encoding.value)); + const {value, done} = await reader.read(); + assert_false(done, 'readable should not be closed'); + assert_equals(value, encoding.expected, 'chunk should match expected'); + await writePromise; + }, `TextDecoderStream should be able to decode ${encoding.name}`); + + if (!encoding.invalid) + continue; + + promise_test(async t => { + const stream = new TextDecoderStream(encoding.name); + const reader = stream.readable.getReader(); + const writer = stream.writable.getWriter(); + const writePromise = writer.write(new Uint8Array(encoding.invalid)); + const closePromise = writer.close(); + const {value, done} = await reader.read(); + assert_false(done, 'readable should not be closed'); + assert_equals(value, '\u{FFFD}', 'output should be replacement character'); + await Promise.all([writePromise, closePromise]); + }, `TextDecoderStream should be able to decode invalid sequences in ` + + `${encoding.name}`); + + promise_test(async t => { + const stream = new TextDecoderStream(encoding.name, {fatal: true}); + const reader = stream.readable.getReader(); + const writer = stream.writable.getWriter(); + const writePromise = writer.write(new Uint8Array(encoding.invalid)); + const closePromise = writer.close(); + await promise_rejects_js(t, TypeError, reader.read(), + 'readable should be errored'); + await promise_rejects_js(t, TypeError, + Promise.all([writePromise, closePromise]), + 'writable should be errored'); + }, `TextDecoderStream should be able to reject invalid sequences in ` + + `${encoding.name}`); +} diff --git a/testing/web-platform/tests/encoding/streams/decode-split-character.any.js b/testing/web-platform/tests/encoding/streams/decode-split-character.any.js new file mode 100644 index 0000000000..d589694fce --- /dev/null +++ b/testing/web-platform/tests/encoding/streams/decode-split-character.any.js @@ -0,0 +1,50 @@ +// META: global=window,worker +// META: script=resources/readable-stream-from-array.js +// META: script=resources/readable-stream-to-array.js + +'use strict'; + +const inputBytes = [73, 32, 240, 159, 146, 153, 32, 115, 116, 114, 101, + 97, 109, 115]; +for (const splitPoint of [2, 3, 4, 5]) { + promise_test(async () => { + const input = readableStreamFromArray( + [new Uint8Array(inputBytes.slice(0, splitPoint)), + new Uint8Array(inputBytes.slice(splitPoint))]); + const expectedOutput = ['I ', '\u{1F499} streams']; + const output = input.pipeThrough(new TextDecoderStream()); + const array = await readableStreamToArray(output); + assert_array_equals(array, expectedOutput, + 'the split code point should be in the second chunk ' + + 'of the output'); + }, 'a code point split between chunks should not be emitted until all ' + + 'bytes are available; split point = ' + splitPoint); +} + +promise_test(async () => { + const splitPoint = 6; + const input = readableStreamFromArray( + [new Uint8Array(inputBytes.slice(0, splitPoint)), + new Uint8Array(inputBytes.slice(splitPoint))]); + const output = input.pipeThrough(new TextDecoderStream()); + const array = await readableStreamToArray(output); + assert_array_equals(array, ['I \u{1F499}', ' streams'], + 'the multibyte character should be in the first chunk ' + + 'of the output'); +}, 'a code point should be emitted as soon as all bytes are available'); + +for (let splitPoint = 1; splitPoint < 7; ++splitPoint) { + promise_test(async () => { + const input = readableStreamFromArray( + [new Uint8Array(inputBytes.slice(0, splitPoint)), + new Uint8Array([]), + new Uint8Array(inputBytes.slice(splitPoint))]); + const concatenatedOutput = 'I \u{1F499} streams'; + const output = input.pipeThrough(new TextDecoderStream()); + const array = await readableStreamToArray(output); + assert_equals(array.length, 2, 'two chunks should be output'); + assert_equals(array[0].concat(array[1]), concatenatedOutput, + 'output should be unchanged by the empty chunk'); + }, 'an empty chunk inside a code point split between chunks should not ' + + 'change the output; split point = ' + splitPoint); +} diff --git a/testing/web-platform/tests/encoding/streams/decode-utf8.any.js b/testing/web-platform/tests/encoding/streams/decode-utf8.any.js new file mode 100644 index 0000000000..f6fceb299b --- /dev/null +++ b/testing/web-platform/tests/encoding/streams/decode-utf8.any.js @@ -0,0 +1,76 @@ +// META: global=window,worker +// META: script=resources/readable-stream-from-array.js +// META: script=resources/readable-stream-to-array.js +// META: script=/common/sab.js +'use strict'; + +["ArrayBuffer", "SharedArrayBuffer"].forEach((arrayBufferOrSharedArrayBuffer) => { + const inputChunkData = [73, 32, 240, 159, 146, 153, 32, 115, 116, 114, 101, 97, 109, 115]; + + const emptyChunk = new Uint8Array(createBuffer(arrayBufferOrSharedArrayBuffer, 0)); + const inputChunk = new Uint8Array(createBuffer(arrayBufferOrSharedArrayBuffer, inputChunkData.length)); + + inputChunk.set(inputChunkData); + + const expectedOutputString = 'I \u{1F499} streams'; + + promise_test(async () => { + const input = readableStreamFromArray([inputChunk]); + const output = input.pipeThrough(new TextDecoderStream()); + const array = await readableStreamToArray(output); + assert_array_equals(array, [expectedOutputString], + 'the output should be in one chunk'); + }, 'decoding one UTF-8 chunk should give one output string - ' + arrayBufferOrSharedArrayBuffer); + + promise_test(async () => { + const input = readableStreamFromArray([emptyChunk]); + const output = input.pipeThrough(new TextDecoderStream()); + const array = await readableStreamToArray(output); + assert_array_equals(array, [], 'no chunks should be output'); + }, 'decoding an empty chunk should give no output chunks - ' + arrayBufferOrSharedArrayBuffer); + + promise_test(async () => { + const input = readableStreamFromArray([emptyChunk, inputChunk]); + const output = input.pipeThrough(new TextDecoderStream()); + const array = await readableStreamToArray(output); + assert_array_equals(array, [expectedOutputString], + 'the output should be in one chunk'); + }, 'an initial empty chunk should be ignored - ' + arrayBufferOrSharedArrayBuffer); + + promise_test(async () => { + const input = readableStreamFromArray([inputChunk, emptyChunk]); + const output = input.pipeThrough(new TextDecoderStream()); + const array = await readableStreamToArray(output); + assert_array_equals(array, [expectedOutputString], + 'the output should be in one chunk'); + }, 'a trailing empty chunk should be ignored - ' + arrayBufferOrSharedArrayBuffer); + + promise_test(async () => { + const chunk = new Uint8Array(createBuffer(arrayBufferOrSharedArrayBuffer, 3)); + chunk.set([0xF0, 0x9F, 0x92]); + const input = readableStreamFromArray([chunk]); + const output = input.pipeThrough(new TextDecoderStream()); + const array = await readableStreamToArray(output); + assert_array_equals(array, ['\uFFFD']); + }, 'UTF-8 EOF handling - ' + arrayBufferOrSharedArrayBuffer); +}); + +promise_test(async () => { + const buffer = new ArrayBuffer(3); + const view = new Uint8Array(buffer, 1, 1); + view[0] = 65; + new MessageChannel().port1.postMessage(buffer, [buffer]); + const input = readableStreamFromArray([view]); + const output = input.pipeThrough(new TextDecoderStream()); + const array = await readableStreamToArray(output); + assert_array_equals(array, [], 'no chunks should be output'); +}, 'decoding a transferred Uint8Array chunk should give no output'); + +promise_test(async () => { + const buffer = new ArrayBuffer(1); + new MessageChannel().port1.postMessage(buffer, [buffer]); + const input = readableStreamFromArray([buffer]); + const output = input.pipeThrough(new TextDecoderStream()); + const array = await readableStreamToArray(output); + assert_array_equals(array, [], 'no chunks should be output'); +}, 'decoding a transferred ArrayBuffer chunk should give no output'); diff --git a/testing/web-platform/tests/encoding/streams/decode-utf8.any.js.headers b/testing/web-platform/tests/encoding/streams/decode-utf8.any.js.headers new file mode 100644 index 0000000000..4fff9d9fba --- /dev/null +++ b/testing/web-platform/tests/encoding/streams/decode-utf8.any.js.headers @@ -0,0 +1,2 @@ +Cross-Origin-Opener-Policy: same-origin +Cross-Origin-Embedder-Policy: require-corp \ No newline at end of file diff --git a/testing/web-platform/tests/encoding/streams/encode-bad-chunks.any.js b/testing/web-platform/tests/encoding/streams/encode-bad-chunks.any.js new file mode 100644 index 0000000000..4a926a67d2 --- /dev/null +++ b/testing/web-platform/tests/encoding/streams/encode-bad-chunks.any.js @@ -0,0 +1,63 @@ +// META: global=window,worker +// META: script=resources/readable-stream-from-array.js +// META: script=resources/readable-stream-to-array.js + +'use strict'; + +const error1 = new Error('error1'); +error1.name = 'error1'; + +promise_test(t => { + const ts = new TextEncoderStream(); + const writer = ts.writable.getWriter(); + const reader = ts.readable.getReader(); + const writePromise = writer.write({ + toString() { throw error1; } + }); + const readPromise = reader.read(); + return Promise.all([ + promise_rejects_exactly(t, error1, readPromise, 'read should reject with error1'), + promise_rejects_exactly(t, error1, writePromise, 'write should reject with error1'), + promise_rejects_exactly(t, error1, reader.closed, 'readable should be errored with error1'), + promise_rejects_exactly(t, error1, writer.closed, 'writable should be errored with error1'), + ]); +}, 'a chunk that cannot be converted to a string should error the streams'); + +const oddInputs = [ + { + name: 'undefined', + value: undefined, + expected: 'undefined' + }, + { + name: 'null', + value: null, + expected: 'null' + }, + { + name: 'numeric', + value: 3.14, + expected: '3.14' + }, + { + name: 'object', + value: {}, + expected: '[object Object]' + }, + { + name: 'array', + value: ['hi'], + expected: 'hi' + } +]; + +for (const input of oddInputs) { + promise_test(async () => { + const outputReadable = readableStreamFromArray([input.value]) + .pipeThrough(new TextEncoderStream()) + .pipeThrough(new TextDecoderStream()); + const output = await readableStreamToArray(outputReadable); + assert_equals(output.length, 1, 'output should contain one chunk'); + assert_equals(output[0], input.expected, 'output should be correct'); + }, `input of type ${input.name} should be converted correctly to string`); +} diff --git a/testing/web-platform/tests/encoding/streams/encode-utf8.any.js b/testing/web-platform/tests/encoding/streams/encode-utf8.any.js new file mode 100644 index 0000000000..a5ba8f91ea --- /dev/null +++ b/testing/web-platform/tests/encoding/streams/encode-utf8.any.js @@ -0,0 +1,144 @@ +// META: global=window,worker +// META: script=resources/readable-stream-from-array.js +// META: script=resources/readable-stream-to-array.js + +'use strict'; +const inputString = 'I \u{1F499} streams'; +const expectedOutputBytes = [0x49, 0x20, 0xf0, 0x9f, 0x92, 0x99, 0x20, 0x73, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73]; +// This is a character that must be represented in two code units in a string, +// ie. it is not in the Basic Multilingual Plane. +const astralCharacter = '\u{1F499}'; // BLUE HEART +const astralCharacterEncoded = [0xf0, 0x9f, 0x92, 0x99]; +const leading = astralCharacter[0]; +const trailing = astralCharacter[1]; +const replacementEncoded = [0xef, 0xbf, 0xbd]; + +// These tests assume that the implementation correctly classifies leading and +// trailing surrogates and treats all the code units in each set equivalently. + +const testCases = [ + { + input: [inputString], + output: [expectedOutputBytes], + description: 'encoding one string of UTF-8 should give one complete chunk' + }, + { + input: [leading, trailing], + output: [astralCharacterEncoded], + description: 'a character split between chunks should be correctly encoded' + }, + { + input: [leading, trailing + astralCharacter], + output: [astralCharacterEncoded.concat(astralCharacterEncoded)], + description: 'a character following one split between chunks should be ' + + 'correctly encoded' + }, + { + input: [leading, trailing + leading, trailing], + output: [astralCharacterEncoded, astralCharacterEncoded], + description: 'two consecutive astral characters each split down the ' + + 'middle should be correctly reassembled' + }, + { + input: [leading, trailing + leading + leading, trailing], + output: [astralCharacterEncoded.concat(replacementEncoded), astralCharacterEncoded], + description: 'two consecutive astral characters each split down the ' + + 'middle with an invalid surrogate in the middle should be correctly ' + + 'encoded' + }, + { + input: [leading], + output: [replacementEncoded], + description: 'a stream ending in a leading surrogate should emit a ' + + 'replacement character as a final chunk' + }, + { + input: [leading, astralCharacter], + output: [replacementEncoded.concat(astralCharacterEncoded)], + description: 'an unmatched surrogate at the end of a chunk followed by ' + + 'an astral character in the next chunk should be replaced with ' + + 'the replacement character at the start of the next output chunk' + }, + { + input: [leading, 'A'], + output: [replacementEncoded.concat([65])], + description: 'an unmatched surrogate at the end of a chunk followed by ' + + 'an ascii character in the next chunk should be replaced with ' + + 'the replacement character at the start of the next output chunk' + }, + { + input: [leading, leading, trailing], + output: [replacementEncoded, astralCharacterEncoded], + description: 'an unmatched surrogate at the end of a chunk followed by ' + + 'a plane 1 character split into two chunks should result in ' + + 'the encoded plane 1 character appearing in the last output chunk' + }, + { + input: [leading, leading], + output: [replacementEncoded, replacementEncoded], + description: 'two leading chunks should result in two replacement ' + + 'characters' + }, + { + input: [leading + leading, trailing], + output: [replacementEncoded, astralCharacterEncoded], + description: 'a non-terminal unpaired leading surrogate should ' + + 'immediately be replaced' + }, + { + input: [trailing, astralCharacter], + output: [replacementEncoded, astralCharacterEncoded], + description: 'a terminal unpaired trailing surrogate should ' + + 'immediately be replaced' + }, + { + input: [leading, '', trailing], + output: [astralCharacterEncoded], + description: 'a leading surrogate chunk should be carried past empty chunks' + }, + { + input: [leading, ''], + output: [replacementEncoded], + description: 'a leading surrogate chunk should error when it is clear ' + + 'it didn\'t form a pair' + }, + { + input: [''], + output: [], + description: 'an empty string should result in no output chunk' + }, + { + input: ['', inputString], + output: [expectedOutputBytes], + description: 'a leading empty chunk should be ignored' + }, + { + input: [inputString, ''], + output: [expectedOutputBytes], + description: 'a trailing empty chunk should be ignored' + }, + { + input: ['A'], + output: [[65]], + description: 'a plain ASCII chunk should be converted' + }, + { + input: ['\xff'], + output: [[195, 191]], + description: 'characters in the ISO-8859-1 range should be encoded correctly' + }, +]; + +for (const {input, output, description} of testCases) { + promise_test(async () => { + const inputStream = readableStreamFromArray(input); + const outputStream = inputStream.pipeThrough(new TextEncoderStream()); + const chunkArray = await readableStreamToArray(outputStream); + assert_equals(chunkArray.length, output.length, + 'number of chunks should match'); + for (let i = 0; i < output.length; ++i) { + assert_array_equals(chunkArray[i], output[i], `chunk ${i} should match`); + } + }, description); +} diff --git a/testing/web-platform/tests/encoding/streams/invalid-realm.window.js b/testing/web-platform/tests/encoding/streams/invalid-realm.window.js new file mode 100644 index 0000000000..beaec42641 --- /dev/null +++ b/testing/web-platform/tests/encoding/streams/invalid-realm.window.js @@ -0,0 +1,37 @@ +// Text*Stream should still work even if the realm is detached. + +// Adds an iframe to the document and returns it. +function addIframe() { + const iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + return iframe; +} + +promise_test(async t => { + const iframe = addIframe(); + const stream = new iframe.contentWindow.TextDecoderStream(); + const readPromise = stream.readable.getReader().read(); + const writer = stream.writable.getWriter(); + await writer.ready; + iframe.remove(); + return Promise.all([writer.write(new Uint8Array([65])),readPromise]); +}, 'TextDecoderStream: write in detached realm should succeed'); + +promise_test(async t => { + const iframe = addIframe(); + const stream = new iframe.contentWindow.TextEncoderStream(); + const readPromise = stream.readable.getReader().read(); + const writer = stream.writable.getWriter(); + await writer.ready; + iframe.remove(); + return Promise.all([writer.write('A'), readPromise]); +}, 'TextEncoderStream: write in detached realm should succeed'); + +for (const type of ['TextEncoderStream', 'TextDecoderStream']) { + promise_test(async t => { + const iframe = addIframe(); + const stream = new iframe.contentWindow[type](); + iframe.remove(); + return stream.writable.close(); + }, `${type}: close in detached realm should succeed`); +} diff --git a/testing/web-platform/tests/encoding/streams/readable-writable-properties.any.js b/testing/web-platform/tests/encoding/streams/readable-writable-properties.any.js new file mode 100644 index 0000000000..8084cb9942 --- /dev/null +++ b/testing/web-platform/tests/encoding/streams/readable-writable-properties.any.js @@ -0,0 +1,22 @@ +// META: global=window,worker,shadowrealm + +// This just tests that the "readable" and "writable" properties pass the brand +// checks. All other relevant attributes are covered by the IDL tests. + +'use strict'; + +test(() => { + const te = new TextEncoderStream(); + assert_equals(typeof ReadableStream.prototype.getReader.call(te.readable), + 'object', 'readable property must pass brand check'); + assert_equals(typeof WritableStream.prototype.getWriter.call(te.writable), + 'object', 'writable property must pass brand check'); +}, 'TextEncoderStream readable and writable properties must pass brand checks'); + +test(() => { + const td = new TextDecoderStream(); + assert_equals(typeof ReadableStream.prototype.getReader.call(td.readable), + 'object', 'readable property must pass brand check'); + assert_equals(typeof WritableStream.prototype.getWriter.call(td.writable), + 'object', 'writable property must pass brand check'); +}, 'TextDecoderStream readable and writable properties must pass brand checks'); diff --git a/testing/web-platform/tests/encoding/streams/realms.window.js b/testing/web-platform/tests/encoding/streams/realms.window.js new file mode 100644 index 0000000000..ca9ce21abc --- /dev/null +++ b/testing/web-platform/tests/encoding/streams/realms.window.js @@ -0,0 +1,304 @@ +'use strict'; + +// Test that objects created by the TextEncoderStream and TextDecoderStream APIs +// are created in the correct realm. The tests work by creating an iframe for +// each realm and then posting Javascript to them to be evaluated. Inputs and +// outputs are passed around via global variables in each realm's scope. + +// Async setup is required before creating any tests, so require done() to be +// called. +setup({explicit_done: true}); + +function createRealm() { + let iframe = document.createElement('iframe'); + const scriptEndTag = '<' + '/script>'; + iframe.srcdoc = ` + + -- cgit v1.2.3