<!DOCTYPE html>
<meta charset="utf-8">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/helpers.js"></script>
<script src="../resources/test-utils.js"></script>
<script src="../resources/recording-streams.js"></script>
<script>
'use strict';

promise_test(t => {
  const orig = new WritableStream();
  const promise = new Promise(resolve => {
    addEventListener('message', t.step_func(evt => {
      const transferred = evt.data;
      assert_equals(transferred.constructor, WritableStream,
                    'transferred should be a WritableStream in this realm');
      assert_true(transferred instanceof WritableStream,
                  'instanceof check should pass');

      // Perform a brand-check on |transferred|.
      const writer = WritableStream.prototype.getWriter.call(transferred);
      resolve();
    }), {once: true});
  });
  postMessage(orig, '*', [orig]);
  assert_true(orig.locked, 'the original stream should be locked');
  return promise;
}, 'window.postMessage should be able to transfer a WritableStream');

test(() => {
  const ws = new WritableStream();
  const writer = ws.getWriter();
  assert_throws_dom('DataCloneError', () => postMessage(ws, '*', [ws]),
                    'postMessage should throw');
}, 'a locked WritableStream should not be transferable');

promise_test(t => {
  const {writable, readable} = new TransformStream();
  const promise = new Promise(resolve => {
    addEventListener('message', t.step_func(async evt => {
      const {writable, readable} = evt.data;
      const reader = readable.getReader();
      const writer = writable.getWriter();
      const writerPromises = Promise.all([
        writer.write('hi'),
        writer.close(),
      ]);
      const {value, done} = await reader.read();
      assert_false(done, 'we should not be done');
      assert_equals(value, 'hi', 'chunk should have been delivered');
      const readResult = await reader.read();
      assert_true(readResult.done, 'readable should be closed');
      await writerPromises;
      resolve();
    }), {once: true});
  });
  postMessage({writable, readable}, '*', [writable, readable]);
  return promise;
}, 'window.postMessage should be able to transfer a {readable, writable} pair');

function transfer(stream) {
  return new Promise(resolve => {
    addEventListener('message', evt => resolve(evt.data), { once: true });
    postMessage(stream, '*', [stream]);
  });
}

promise_test(async () => {
  const orig = new WritableStream(
    {}, new ByteLengthQueuingStrategy({ highWaterMark: 65536 }));
  const transferred = await transfer(orig);
  const writer = transferred.getWriter();
  assert_equals(writer.desiredSize, 1, 'desiredSize should be 1');
}, 'desiredSize for a newly-transferred stream should be 1');

promise_test(async () => {
  const orig = new WritableStream({
    write() {
      return new Promise(() => {});
    }
  });
  const transferred = await transfer(orig);
  const writer = transferred.getWriter();
  await writer.write('a');
  assert_equals(writer.desiredSize, 1, 'desiredSize should be 1');
}, 'effective queue size of a transferred writable should be 2');

promise_test(async () => {
  const [writeCalled, resolveWriteCalled] = makePromiseAndResolveFunc();
  let resolveWrite;
  const orig = new WritableStream({
    write() {
      resolveWriteCalled();
      return new Promise(resolve => {
        resolveWrite = resolve;
      });
    }
  });
  const transferred = await transfer(orig);
  const writer = transferred.getWriter();
  await writer.write('a');
  let writeDone = false;
  const writePromise = writer.write('b').then(() => {
    writeDone = true;
  });
  await writeCalled;
  assert_false(writeDone, 'second write should not have resolved yet');
  resolveWrite();
  await writePromise; // (makes sure this resolves)
}, 'second write should wait for first underlying write to complete');

async function transferredWritableStreamWithAbortPromise() {
  const [abortCalled, resolveAbortCalled] = makePromiseAndResolveFunc();
  const orig = recordingWritableStream({
    abort() {
      resolveAbortCalled();
    }
  });
  const transferred = await transfer(orig);
  return { orig, transferred, abortCalled };
}

promise_test(async t => {
  const { orig, transferred, abortCalled } = await transferredWritableStreamWithAbortPromise();
  transferred.abort('p');
  await abortCalled;
  assert_array_equals(orig.events, ['abort', 'p'],
                      'abort() should have been called');
}, 'abort() should work');

promise_test(async t => {
  const { orig, transferred, abortCalled } = await transferredWritableStreamWithAbortPromise();
  const writer = transferred.getWriter();
  // A WritableStream object cannot be cloned.
  await promise_rejects_dom(t, 'DataCloneError', writer.write(new WritableStream()),
                            'the write should reject');
  await promise_rejects_dom(t, 'DataCloneError', writer.closed,
                            'the stream should be errored');
  await abortCalled;
  assert_equals(orig.events.length, 2, 'abort should have been called');
  assert_equals(orig.events[0], 'abort', 'first event should be abort');
  assert_equals(orig.events[1].name, 'DataCloneError',
                'reason should be a DataCloneError');
}, 'writing a unclonable object should error the stream');
</script>
