// META: global=worker,jsshell
// META: script=../resources/rs-utils.js
// META: script=../resources/test-utils.js
'use strict';

const error1 = new Error('error1');
error1.name = 'error1';

test(() => {
  assert_throws_js(TypeError, () => new ReadableStream().getReader({ mode: 'byob' }));
}, 'getReader({mode: "byob"}) throws on non-bytes streams');


test(() => {
  // Constructing ReadableStream with an empty underlying byte source object as parameter shouldn't throw.
  new ReadableStream({ type: 'bytes' }).getReader({ mode: 'byob' });
  // Constructor must perform ToString(type).
  new ReadableStream({ type: { toString() {return 'bytes';} } })
    .getReader({ mode: 'byob' });
  new ReadableStream({ type: { toString: null, valueOf() {return 'bytes';} } })
    .getReader({ mode: 'byob' });
}, 'ReadableStream with byte source can be constructed with no errors');

test(() => {
  const ReadableStreamBYOBReader = new ReadableStream({ type: 'bytes' }).getReader({ mode: 'byob' }).constructor;
  const rs = new ReadableStream({ type: 'bytes' });

  let reader = rs.getReader({ mode: { toString() { return 'byob'; } } });
  assert_true(reader instanceof ReadableStreamBYOBReader, 'must give a BYOB reader');
  reader.releaseLock();

  reader = rs.getReader({ mode: { toString: null, valueOf() {return 'byob';} } });
  assert_true(reader instanceof ReadableStreamBYOBReader, 'must give a BYOB reader');
  reader.releaseLock();

  reader = rs.getReader({ mode: 'byob', notmode: 'ignored' });
  assert_true(reader instanceof ReadableStreamBYOBReader, 'must give a BYOB reader');
}, 'getReader({mode}) must perform ToString()');

promise_test(() => {
  let startCalled = false;
  let startCalledBeforePull = false;
  let desiredSize;
  let controller;

  let resolveTestPromise;
  const testPromise = new Promise(resolve => {
    resolveTestPromise = resolve;
  });

  new ReadableStream({
    start(c) {
      controller = c;
      startCalled = true;
    },
    pull() {
      startCalledBeforePull = startCalled;
      desiredSize = controller.desiredSize;
      resolveTestPromise();
    },
    type: 'bytes'
  }, {
    highWaterMark: 256
  });

  return testPromise.then(() => {
    assert_true(startCalledBeforePull, 'start should be called before pull');
    assert_equals(desiredSize, 256, 'desiredSize should equal highWaterMark');
  });

}, 'ReadableStream with byte source: Construct and expect start and pull being called');

promise_test(() => {
  let pullCount = 0;
  let checkedNoPull = false;

  let resolveTestPromise;
  const testPromise = new Promise(resolve => {
    resolveTestPromise = resolve;
  });
  let resolveStartPromise;

  new ReadableStream({
    start() {
      return new Promise(resolve => {
        resolveStartPromise = resolve;
      });
    },
    pull() {
      if (checkedNoPull) {
        resolveTestPromise();
      }

      ++pullCount;
    },
    type: 'bytes'
  }, {
    highWaterMark: 256
  });

  Promise.resolve().then(() => {
    assert_equals(pullCount, 0);
    checkedNoPull = true;
    resolveStartPromise();
  });

  return testPromise;

}, 'ReadableStream with byte source: No automatic pull call if start doesn\'t finish');

promise_test(t => {
  new ReadableStream({
    pull: t.unreached_func('pull() should not be called'),
    type: 'bytes'
  }, {
    highWaterMark: 0
  });

  return Promise.resolve();
}, 'ReadableStream with byte source: Construct with highWaterMark of 0');

test(() => {
  new ReadableStream({
    start(c) {
      assert_equals(c.desiredSize, 10, 'desiredSize must start at the highWaterMark');
      c.close();
      assert_equals(c.desiredSize, 0, 'after closing, desiredSize must be 0');
    },
    type: 'bytes'
  }, {
    highWaterMark: 10
  });
}, 'ReadableStream with byte source: desiredSize when closed');

test(() => {
  new ReadableStream({
    start(c) {
      assert_equals(c.desiredSize, 10, 'desiredSize must start at the highWaterMark');
      c.error();
      assert_equals(c.desiredSize, null, 'after erroring, desiredSize must be null');
    },
    type: 'bytes'
  }, {
    highWaterMark: 10
  });
}, 'ReadableStream with byte source: desiredSize when errored');

promise_test(t => {
  const stream = new ReadableStream({
    type: 'bytes'
  });

  const reader = stream.getReader();
  reader.releaseLock();

  return promise_rejects(t, new TypeError(), reader.closed, 'closed must reject');
}, 'ReadableStream with byte source: getReader(), then releaseLock()');

promise_test(t => {
  const stream = new ReadableStream({
    type: 'bytes'
  });

  const reader = stream.getReader({ mode: 'byob' });
  reader.releaseLock();

  return promise_rejects(t, new TypeError(), reader.closed, 'closed must reject');
}, 'ReadableStream with byte source: getReader() with mode set to byob, then releaseLock()');

promise_test(t => {
  const stream = new ReadableStream({
    start(c) {
      c.close();
    },
    pull: t.unreached_func('pull() should not be called'),
    type: 'bytes'
  });

  const reader = stream.getReader();

  return reader.closed.then(() => {
    assert_throws_js(TypeError, () => stream.getReader(), 'getReader() must throw');
  });
}, 'ReadableStream with byte source: Test that closing a stream does not release a reader automatically');

promise_test(t => {
  const stream = new ReadableStream({
    start(c) {
      c.close();
    },
    pull: t.unreached_func('pull() should not be called'),
    type: 'bytes'
  });

  const reader = stream.getReader({ mode: 'byob' });

  return reader.closed.then(() => {
    assert_throws_js(TypeError, () => stream.getReader({ mode: 'byob' }), 'getReader() must throw');
  });
}, 'ReadableStream with byte source: Test that closing a stream does not release a BYOB reader automatically');

promise_test(t => {
  const stream = new ReadableStream({
    start(c) {
      c.error(error1);
    },
    pull: t.unreached_func('pull() should not be called'),
    type: 'bytes'
  });

  const reader = stream.getReader();

  return promise_rejects(t, error1, reader.closed, 'closed must reject').then(() => {
    assert_throws_js(TypeError, () => stream.getReader(), 'getReader() must throw');
  });
}, 'ReadableStream with byte source: Test that erroring a stream does not release a reader automatically');

promise_test(t => {
  const stream = new ReadableStream({
    start(c) {
      c.error(error1);
    },
    pull: t.unreached_func('pull() should not be called'),
    type: 'bytes'
  });

  const reader = stream.getReader({ mode: 'byob' });

  return promise_rejects(t, error1, reader.closed, 'closed must reject').then(() => {
    assert_throws_js(TypeError, () => stream.getReader({ mode: 'byob' }), 'getReader() must throw');
  });
}, 'ReadableStream with byte source: Test that erroring a stream does not release a BYOB reader automatically');

test(() => {
  const stream = new ReadableStream({
    type: 'bytes'
  });

  const reader = stream.getReader();
  reader.read();
  assert_throws_js(TypeError, () => reader.releaseLock(), 'reader.releaseLock() must throw');
}, 'ReadableStream with byte source: releaseLock() on ReadableStreamReader with pending read() must throw');

promise_test(() => {
  let pullCount = 0;

  const stream = new ReadableStream({
    pull() {
      ++pullCount;
    },
    type: 'bytes'
  }, {
    highWaterMark: 8
  });

  stream.getReader();

  assert_equals(pullCount, 0, 'No pull as start() just finished and is not yet reflected to the state of the stream');

  return Promise.resolve().then(() => {
    assert_equals(pullCount, 1, 'pull must be invoked');
  });
}, 'ReadableStream with byte source: Automatic pull() after start()');

promise_test(() => {
  let pullCount = 0;

  const stream = new ReadableStream({
    pull() {
      ++pullCount;
    },
    type: 'bytes'
  }, {
    highWaterMark: 0
  });

  const reader = stream.getReader();
  reader.read();

  assert_equals(pullCount, 0, 'No pull as start() just finished and is not yet reflected to the state of the stream');

  return Promise.resolve().then(() => {
    assert_equals(pullCount, 1, 'pull must be invoked');
  });
}, 'ReadableStream with byte source: Automatic pull() after start() and read()');

// View buffers are detached after pull() returns, so record the information at the time that pull() was called.
function extractViewInfo(view) {
  return {
    constructor: view.constructor,
    bufferByteLength: view.buffer.byteLength,
    byteOffset: view.byteOffset,
    byteLength: view.byteLength
  };
}

promise_test(() => {
  let pullCount = 0;
  let controller;
  const byobRequests = [];

  const stream = new ReadableStream({
    start(c) {
      controller = c;
    },
    pull() {
      const byobRequest = controller.byobRequest;
      const view = byobRequest.view;
      byobRequests[pullCount] = {
        defined: byobRequest !== undefined,
        viewDefined: view !== undefined,
        viewInfo: extractViewInfo(view)
      };
      if (pullCount === 0) {
        view[0] = 0x01;
        byobRequest.respond(1);
      } else if (pullCount === 1) {
        view[0] = 0x02;
        view[1] = 0x03;
        byobRequest.respond(2);
      }

      ++pullCount;
    },
    type: 'bytes',
    autoAllocateChunkSize: 16
  }, {
    highWaterMark: 0
  });

  const reader = stream.getReader();
  const p0 = reader.read();
  const p1 = reader.read();

  assert_equals(pullCount, 0, 'No pull() as start() just finished and is not yet reflected to the state of the stream');

  return Promise.resolve().then(() => {
    assert_equals(pullCount, 1, 'pull() must have been invoked once');
    const byobRequest = byobRequests[0];
    assert_true(byobRequest.defined, 'first byobRequest must not be undefined');
    assert_true(byobRequest.viewDefined, 'first byobRequest.view must not be undefined');
    const viewInfo = byobRequest.viewInfo;
    assert_equals(viewInfo.constructor, Uint8Array, 'first view.constructor should be Uint8Array');
    assert_equals(viewInfo.bufferByteLength, 16, 'first view.buffer.byteLength should be 16');
    assert_equals(viewInfo.byteOffset, 0, 'first view.byteOffset should be 0');
    assert_equals(viewInfo.byteLength, 16, 'first view.byteLength should be 16');

    return p0;
  }).then(result => {
    assert_equals(pullCount, 2, 'pull() must have been invoked twice');
    const value = result.value;
    assert_not_equals(value, undefined, 'first read should have a value');
    assert_equals(value.constructor, Uint8Array, 'first value should be a Uint8Array');
    assert_equals(value.buffer.byteLength, 16, 'first value.buffer.byteLength should be 16');
    assert_equals(value.byteOffset, 0, 'first value.byteOffset should be 0');
    assert_equals(value.byteLength, 1, 'first value.byteLength should be 1');
    assert_equals(value[0], 0x01, 'first value[0] should be 0x01');
    const byobRequest = byobRequests[1];
    assert_true(byobRequest.defined, 'second byobRequest must not be undefined');
    assert_true(byobRequest.viewDefined, 'second byobRequest.view must not be undefined');
    const viewInfo = byobRequest.viewInfo;
    assert_equals(viewInfo.constructor, Uint8Array, 'second view.constructor should be Uint8Array');
    assert_equals(viewInfo.bufferByteLength, 16, 'second view.buffer.byteLength should be 16');
    assert_equals(viewInfo.byteOffset, 0, 'second view.byteOffset should be 0');
    assert_equals(viewInfo.byteLength, 16, 'second view.byteLength should be 16');

    return p1;
  }).then(result => {
    assert_equals(pullCount, 2, 'pull() should only be invoked twice');
    const value = result.value;
    assert_not_equals(value, undefined, 'second read should have a value');
    assert_equals(value.constructor, Uint8Array, 'second value should be a Uint8Array');
    assert_equals(value.buffer.byteLength, 16, 'second value.buffer.byteLength should be 16');
    assert_equals(value.byteOffset, 0, 'second value.byteOffset should be 0');
    assert_equals(value.byteLength, 2, 'second value.byteLength should be 2');
    assert_equals(value[0], 0x02, 'second value[0] should be 0x02');
    assert_equals(value[1], 0x03, 'second value[1] should be 0x03');
  });
}, 'ReadableStream with byte source: autoAllocateChunkSize');

promise_test(() => {
  let pullCount = 0;
  let controller;
  const byobRequests = [];

  const stream = new ReadableStream({
    start(c) {
      controller = c;
    },
    pull() {
      const byobRequest = controller.byobRequest;
      const view = byobRequest.view;
      byobRequests[pullCount] = {
        defined: byobRequest !== undefined,
        viewDefined: view !== undefined,
        viewInfo: extractViewInfo(view)
      };
      if (pullCount === 0) {
        view[0] = 0x01;
        byobRequest.respond(1);
      } else if (pullCount === 1) {
        view[0] = 0x02;
        view[1] = 0x03;
        byobRequest.respond(2);
      }

      ++pullCount;
    },
    type: 'bytes',
    autoAllocateChunkSize: 16
  }, {
    highWaterMark: 0
  });

  const reader = stream.getReader();
  return reader.read().then(result => {
    const value = result.value;
    assert_not_equals(value, undefined, 'first read should have a value');
    assert_equals(value.constructor, Uint8Array, 'first value should be a Uint8Array');
    assert_equals(value.buffer.byteLength, 16, 'first value.buffer.byteLength should be 16');
    assert_equals(value.byteOffset, 0, 'first value.byteOffset should be 0');
    assert_equals(value.byteLength, 1, 'first value.byteLength should be 1');
    assert_equals(value[0], 0x01, 'first value[0] should be 0x01');
    const byobRequest = byobRequests[0];
    assert_true(byobRequest.defined, 'first byobRequest must not be undefined');
    assert_true(byobRequest.viewDefined, 'first byobRequest.view must not be undefined');
    const viewInfo = byobRequest.viewInfo;
    assert_equals(viewInfo.constructor, Uint8Array, 'first view.constructor should be Uint8Array');
    assert_equals(viewInfo.bufferByteLength, 16, 'first view.buffer.byteLength should be 16');
    assert_equals(viewInfo.byteOffset, 0, 'first view.byteOffset should be 0');
    assert_equals(viewInfo.byteLength, 16, 'first view.byteLength should be 16');

    reader.releaseLock();
    const byobReader = stream.getReader({ mode: 'byob' });
    return byobReader.read(new Uint8Array(32));
  }).then(result => {
    const value = result.value;
    assert_not_equals(value, undefined, 'second read should have a value');
    assert_equals(value.constructor, Uint8Array, 'second value should be a Uint8Array');
    assert_equals(value.buffer.byteLength, 32, 'second value.buffer.byteLength should be 32');
    assert_equals(value.byteOffset, 0, 'second value.byteOffset should be 0');
    assert_equals(value.byteLength, 2, 'second value.byteLength should be 2');
    assert_equals(value[0], 0x02, 'second value[0] should be 0x02');
    assert_equals(value[1], 0x03, 'second value[1] should be 0x03');
    const byobRequest = byobRequests[1];
    assert_true(byobRequest.defined, 'second byobRequest must not be undefined');
    assert_true(byobRequest.viewDefined, 'second byobRequest.view must not be undefined');
    const viewInfo = byobRequest.viewInfo;
    assert_equals(viewInfo.constructor, Uint8Array, 'second view.constructor should be Uint8Array');
    assert_equals(viewInfo.bufferByteLength, 32, 'second view.buffer.byteLength should be 32');
    assert_equals(viewInfo.byteOffset, 0, 'second view.byteOffset should be 0');
    assert_equals(viewInfo.byteLength, 32, 'second view.byteLength should be 32');
    assert_equals(pullCount, 2, 'pullCount should be 2');
  });
}, 'ReadableStream with byte source: Mix of auto allocate and BYOB');

promise_test(() => {
  let pullCount = 0;

  const stream = new ReadableStream({
    pull() {
      ++pullCount;
    },
    type: 'bytes'
  }, {
    highWaterMark: 0
  });

  const reader = stream.getReader();
  reader.read(new Uint8Array(8));

  assert_equals(pullCount, 0, 'No pull as start() just finished and is not yet reflected to the state of the stream');

  return Promise.resolve().then(() => {
    assert_equals(pullCount, 1, 'pull must be invoked');
  });
}, 'ReadableStream with byte source: Automatic pull() after start() and read(view)');

promise_test(() => {
  let pullCount = 0;

  let controller;
  let desiredSizeInStart;
  let desiredSizeInPull;

  const stream = new ReadableStream({
    start(c) {
      c.enqueue(new Uint8Array(16));
      desiredSizeInStart = c.desiredSize;
      controller = c;
    },
    pull() {
      ++pullCount;

      if (pullCount === 1) {
        desiredSizeInPull = controller.desiredSize;
      }
    },
    type: 'bytes'
  }, {
    highWaterMark: 8
  });

  return Promise.resolve().then(() => {
    assert_equals(pullCount, 0, 'No pull as the queue was filled by start()');
    assert_equals(desiredSizeInStart, -8, 'desiredSize after enqueue() in start()');

    const reader = stream.getReader();

    const promise = reader.read();
    assert_equals(pullCount, 1, 'The first pull() should be made on read()');
    assert_equals(desiredSizeInPull, 8, 'desiredSize in pull()');

    return promise.then(result => {
      assert_equals(result.done, false, 'result.done');

      const view = result.value;
      assert_equals(view.constructor, Uint8Array, 'view.constructor');
      assert_equals(view.buffer.byteLength, 16, 'view.buffer');
      assert_equals(view.byteOffset, 0, 'view.byteOffset');
      assert_equals(view.byteLength, 16, 'view.byteLength');
    });
  });
}, 'ReadableStream with byte source: enqueue(), getReader(), then read()');

promise_test(() => {
  let controller;

  const stream = new ReadableStream({
    start(c) {
      controller = c;
    },
    type: 'bytes'
  });

  const reader = stream.getReader();

  const promise = reader.read().then(result => {
    assert_equals(result.done, false);

    const view = result.value;
    assert_equals(view.constructor, Uint8Array);
    assert_equals(view.buffer.byteLength, 1);
    assert_equals(view.byteOffset, 0);
    assert_equals(view.byteLength, 1);
  });

  controller.enqueue(new Uint8Array(1));

  return promise;
}, 'ReadableStream with byte source: Push source that doesn\'t understand pull signal');

test(() => {
  assert_throws_js(TypeError, () => new ReadableStream({
    pull: 'foo',
    type: 'bytes'
  }), 'constructor should throw');
}, 'ReadableStream with byte source: pull() function is not callable');

promise_test(() => {
  const stream = new ReadableStream({
    start(c) {
      c.enqueue(new Uint16Array(16));
    },
    type: 'bytes'
  });

  const reader = stream.getReader();

  return reader.read().then(result => {
    assert_equals(result.done, false);

    const view = result.value;
    assert_equals(view.constructor, Uint8Array);
    assert_equals(view.buffer.byteLength, 32);
    assert_equals(view.byteOffset, 0);
    assert_equals(view.byteLength, 32);
  });
}, 'ReadableStream with byte source: enqueue() with Uint16Array, getReader(), then read()');

promise_test(t => {
  const stream = new ReadableStream({
    start(c) {
      const view = new Uint8Array(16);
      view[0] = 0x01;
      view[8] = 0x02;
      c.enqueue(view);
    },
    pull: t.unreached_func('pull() should not be called'),
    type: 'bytes'
  });

  const byobReader = stream.getReader({ mode: 'byob' });

  return byobReader.read(new Uint8Array(8)).then(result => {
    assert_equals(result.done, false, 'done');

    const view = result.value;
    assert_equals(view.constructor, Uint8Array, 'value.constructor');
    assert_equals(view.buffer.byteLength, 8, 'value.buffer.byteLength');
    assert_equals(view.byteOffset, 0, 'value.byteOffset');
    assert_equals(view.byteLength, 8, 'value.byteLength');
    assert_equals(view[0], 0x01);

    byobReader.releaseLock();

    const reader = stream.getReader();

    return reader.read();
  }).then(result => {
    assert_equals(result.done, false, 'done');

    const view = result.value;
    assert_equals(view.constructor, Uint8Array, 'value.constructor');
    assert_equals(view.buffer.byteLength, 16, 'value.buffer.byteLength');
    assert_equals(view.byteOffset, 8, 'value.byteOffset');
    assert_equals(view.byteLength, 8, 'value.byteLength');
    assert_equals(view[0], 0x02);
  });
}, 'ReadableStream with byte source: enqueue(), read(view) partially, then read()');

promise_test(t => {
  let controller;

  const stream = new ReadableStream({
    start(c) {
      controller = c;
    },
    pull: t.unreached_func('pull() should not be called'),
    type: 'bytes'
  });

  const reader = stream.getReader();

  controller.enqueue(new Uint8Array(16));
  controller.close();

  return reader.read().then(result => {
    assert_equals(result.done, false, 'done');

    const view = result.value;
    assert_equals(view.byteOffset, 0, 'byteOffset');
    assert_equals(view.byteLength, 16, 'byteLength');

    return reader.read();
  }).then(result => {
    assert_equals(result.done, true, 'done');
    assert_equals(result.value, undefined, 'value');
  });
}, 'ReadableStream with byte source: getReader(), enqueue(), close(), then read()');

promise_test(t => {
  const stream = new ReadableStream({
    start(c) {
      c.enqueue(new Uint8Array(16));
      c.close();
    },
    pull: t.unreached_func('pull() should not be called'),
    type: 'bytes'
  });

  const reader = stream.getReader();

  return reader.read().then(result => {
    assert_equals(result.done, false, 'done');

    const view = result.value;
    assert_equals(view.byteOffset, 0, 'byteOffset');
    assert_equals(view.byteLength, 16, 'byteLength');

    return reader.read();
  }).then(result => {
    assert_equals(result.done, true, 'done');
    assert_equals(result.value, undefined, 'value');
  });
}, 'ReadableStream with byte source: enqueue(), close(), getReader(), then read()');

promise_test(() => {
  let controller;
  let byobRequest;

  const stream = new ReadableStream({
    start(c) {
      controller = c;
    },
    pull() {
      controller.enqueue(new Uint8Array(16));
      byobRequest = controller.byobRequest;
    },
    type: 'bytes'
  });

  const reader = stream.getReader();

  return reader.read().then(result => {
    assert_equals(result.done, false, 'done');
    assert_equals(result.value.byteLength, 16, 'byteLength');
    assert_equals(byobRequest, undefined, 'byobRequest must be undefined');
  });
}, 'ReadableStream with byte source: Respond to pull() by enqueue()');

promise_test(() => {
  let pullCount = 0;

  let controller;
  let byobRequest;
  const desiredSizes = [];

  const stream = new ReadableStream({
    start(c) {
      controller = c;
    },
    pull() {
      byobRequest = controller.byobRequest;
      desiredSizes.push(controller.desiredSize);
      controller.enqueue(new Uint8Array(1));
      desiredSizes.push(controller.desiredSize);
      controller.enqueue(new Uint8Array(1));
      desiredSizes.push(controller.desiredSize);

      ++pullCount;
    },
    type: 'bytes'
  }, {
    highWaterMark: 0
  });

  const reader = stream.getReader();

  const p0 = reader.read();
  const p1 = reader.read();
  const p2 = reader.read();

  // Respond to the first pull call.
  controller.enqueue(new Uint8Array(1));

  assert_equals(pullCount, 0, 'pullCount after the enqueue() outside pull');

  return Promise.all([p0, p1, p2]).then(result => {
    assert_equals(pullCount, 1, 'pullCount after completion of all read()s');

    assert_equals(result[0].done, false, 'result[0].done');
    assert_equals(result[0].value.byteLength, 1, 'result[0].value.byteLength');
    assert_equals(result[1].done, false, 'result[1].done');
    assert_equals(result[1].value.byteLength, 1, 'result[1].value.byteLength');
    assert_equals(result[2].done, false, 'result[2].done');
    assert_equals(result[2].value.byteLength, 1, 'result[2].value.byteLength');
    assert_equals(byobRequest, undefined, 'byobRequest should be undefined');
    assert_equals(desiredSizes[0], 0, 'desiredSize on pull should be 0');
    assert_equals(desiredSizes[1], 0, 'desiredSize after 1st enqueue() should be 0');
    assert_equals(desiredSizes[2], 0, 'desiredSize after 2nd enqueue() should be 0');
    assert_equals(pullCount, 1, 'pull() should only be called once');
  });
}, 'ReadableStream with byte source: Respond to pull() by enqueue() asynchronously');

promise_test(() => {
  let pullCount = 0;

  let byobRequest;
  const desiredSizes = [];

  const stream = new ReadableStream({
    pull(c) {
      byobRequest = c.byobRequest;
      desiredSizes.push(c.desiredSize);

      if (pullCount < 3) {
        c.enqueue(new Uint8Array(1));
      } else {
        c.close();
      }

      ++pullCount;
    },
    type: 'bytes'
  }, {
    highWaterMark: 256
  });

  const reader = stream.getReader();

  const p0 = reader.read();
  const p1 = reader.read();
  const p2 = reader.read();

  assert_equals(pullCount, 0, 'No pull as start() just finished and is not yet reflected to the state of the stream');

  return Promise.all([p0, p1, p2]).then(result => {
    assert_equals(pullCount, 4, 'pullCount after completion of all read()s');

    assert_equals(result[0].done, false, 'result[0].done');
    assert_equals(result[0].value.byteLength, 1, 'result[0].value.byteLength');
    assert_equals(result[1].done, false, 'result[1].done');
    assert_equals(result[1].value.byteLength, 1, 'result[1].value.byteLength');
    assert_equals(result[2].done, false, 'result[2].done');
    assert_equals(result[2].value.byteLength, 1, 'result[2].value.byteLength');
    assert_equals(byobRequest, undefined, 'byobRequest should be undefined');
    assert_equals(desiredSizes[0], 256, 'desiredSize on pull should be 256');
    assert_equals(desiredSizes[1], 256, 'desiredSize after 1st enqueue() should be 256');
    assert_equals(desiredSizes[2], 256, 'desiredSize after 2nd enqueue() should be 256');
    assert_equals(desiredSizes[3], 256, 'desiredSize after 3rd enqueue() should be 256');
  });
}, 'ReadableStream with byte source: Respond to multiple pull() by separate enqueue()');

promise_test(() => {
  let controller;

  let pullCount = 0;
  const byobRequestDefined = [];

  const stream = new ReadableStream({
    start(c) {
      controller = c;
    },
    pull() {
      byobRequestDefined.push(controller.byobRequest !== undefined);

      const view = controller.byobRequest.view;
      view[0] = 0x01;
      controller.byobRequest.respond(1);

      byobRequestDefined.push(controller.byobRequest !== undefined);

      ++pullCount;
    },
    type: 'bytes'
  });

  const reader = stream.getReader({ mode: 'byob' });

  return reader.read(new Uint8Array(1)).then(result => {
    assert_equals(result.done, false, 'result.done');
    assert_equals(result.value.byteLength, 1, 'result.value.byteLength');
    assert_equals(result.value[0], 0x01, 'result.value[0]');
    assert_equals(pullCount, 1, 'pull() should be called only once');
    assert_true(byobRequestDefined[0], 'byobRequest must not be undefined before respond()');
    assert_false(byobRequestDefined[1], 'byobRequest must be undefined after respond()');
  });
}, 'ReadableStream with byte source: read(view), then respond()');

promise_test(() => {
  let controller;

  let pullCount = 0;
  const byobRequestDefined = [];

  const stream = new ReadableStream({
    start(c) {
      controller = c;
    },
    pull() {
      byobRequestDefined.push(controller.byobRequest !== undefined);

      // Emulate ArrayBuffer transfer by just creating a new ArrayBuffer and pass it. By checking the result of
      // read(view), we test that the respond()'s buffer argument is working correctly.
      //
      // A real implementation of the underlying byte source would transfer controller.byobRequest.view.buffer into
      // a new ArrayBuffer, then construct a view around it and write to it.
      const transferredView = new Uint8Array(1);
      transferredView[0] = 0x01;
      controller.byobRequest.respondWithNewView(transferredView);

      byobRequestDefined.push(controller.byobRequest !== undefined);

      ++pullCount;
    },
    type: 'bytes'
  });

  const reader = stream.getReader({ mode: 'byob' });

  return reader.read(new Uint8Array(1)).then(result => {
    assert_equals(result.done, false, 'result.done');
    assert_equals(result.value.byteLength, 1, 'result.value.byteLength');
    assert_equals(result.value[0], 0x01, 'result.value[0]');
    assert_equals(pullCount, 1, 'pull() should be called only once');
    assert_true(byobRequestDefined[0], 'byobRequest must not be undefined before respond()');
    assert_false(byobRequestDefined[1], 'byobRequest must be undefined after respond()');
  });
}, 'ReadableStream with byte source: read(view), then respond() with a transferred ArrayBuffer');

promise_test(() => {
  let controller;
  let byobRequestWasDefined;
  let incorrectRespondException;

  const stream = new ReadableStream({
    start(c) {
      controller = c;
    },
    pull() {
      byobRequestWasDefined = controller.byobRequest !== undefined;

      try {
        controller.byobRequest.respond(2);
      } catch (e) {
        incorrectRespondException = e;
      }

      controller.byobRequest.respond(1);
    },
    type: 'bytes'
  });

  const reader = stream.getReader({ mode: 'byob' });

  return reader.read(new Uint8Array(1)).then(() => {
    assert_true(byobRequestWasDefined, 'byobRequest should be defined');
    assert_not_equals(incorrectRespondException, undefined, 'respond() must throw');
    assert_equals(incorrectRespondException.name, 'RangeError', 'respond() must throw a RangeError');
  });
}, 'ReadableStream with byte source: read(view), then respond() with too big value');

promise_test(() => {
  let pullCount = 0;

  let controller;
  let byobRequest;
  let viewInfo;

  const stream = new ReadableStream({
    start(c) {
      controller = c;
    },
    pull() {
      ++pullCount;

      byobRequest = controller.byobRequest;
      const view = byobRequest.view;
      viewInfo = extractViewInfo(view);

      view[0] = 0x01;
      view[1] = 0x02;
      view[2] = 0x03;

      controller.byobRequest.respond(3);
    },
    type: 'bytes'
  });

  const reader = stream.getReader({ mode: 'byob' });

  return reader.read(new Uint16Array(2)).then(result => {
    assert_equals(pullCount, 1);

    assert_equals(result.done, false, 'done');

    const view = result.value;
    assert_equals(view.byteOffset, 0, 'byteOffset');
    assert_equals(view.byteLength, 2, 'byteLength');

    assert_equals(view[0], 0x0201);

    return reader.read(new Uint8Array(1));
  }).then(result => {
    assert_equals(pullCount, 1);
    assert_not_equals(byobRequest, undefined, 'byobRequest must not be undefined');
    assert_equals(viewInfo.constructor, Uint8Array, 'view.constructor should be Uint8Array');
    assert_equals(viewInfo.bufferByteLength, 4, 'view.buffer.byteLength should be 4');
    assert_equals(viewInfo.byteOffset, 0, 'view.byteOffset should be 0');
    assert_equals(viewInfo.byteLength, 4, 'view.byteLength should be 4');

    assert_equals(result.done, false, 'done');

    const view = result.value;
    assert_equals(view.byteOffset, 0, 'byteOffset');
    assert_equals(view.byteLength, 1, 'byteLength');

    assert_equals(view[0], 0x03);
  });
}, 'ReadableStream with byte source: respond(3) to read(view) with 2 element Uint16Array enqueues the 1 byte ' +
   'remainder');

promise_test(t => {
  const stream = new ReadableStream({
    start(controller) {
      const view = new Uint8Array(16);
      view[15] = 0x01;
      controller.enqueue(view);
    },
    pull: t.unreached_func('pull() should not be called'),
    type: 'bytes'
  });

  const reader = stream.getReader({ mode: 'byob' });

  return reader.read(new Uint8Array(16)).then(result => {
    assert_equals(result.done, false);

    const view = result.value;
    assert_equals(view.byteOffset, 0);
    assert_equals(view.byteLength, 16);
    assert_equals(view[15], 0x01);
  });
}, 'ReadableStream with byte source: enqueue(), getReader(), then read(view)');

promise_test(t => {
  let cancelCount = 0;
  let reason;

  const passedReason = new TypeError('foo');

  const stream = new ReadableStream({
    start(c) {
      c.enqueue(new Uint8Array(16));
    },
    pull: t.unreached_func('pull() should not be called'),
    cancel(r) {
      if (cancelCount === 0) {
        reason = r;
      }

      ++cancelCount;
    },
    type: 'bytes'
  });

  const reader = stream.getReader();

  return reader.cancel(passedReason).then(result => {
    assert_equals(result, undefined);
    assert_equals(cancelCount, 1);
    assert_equals(reason, passedReason, 'reason should equal the passed reason');
  });
}, 'ReadableStream with byte source: enqueue(), getReader(), then cancel() (mode = not BYOB)');

promise_test(t => {
  let cancelCount = 0;
  let reason;

  const passedReason = new TypeError('foo');

  const stream = new ReadableStream({
    start(c) {
      c.enqueue(new Uint8Array(16));
    },
    pull: t.unreached_func('pull() should not be called'),
    cancel(r) {
      if (cancelCount === 0) {
        reason = r;
      }

      ++cancelCount;
    },
    type: 'bytes'
  });

  const reader = stream.getReader({ mode: 'byob' });

  return reader.cancel(passedReason).then(result => {
    assert_equals(result, undefined);
    assert_equals(cancelCount, 1);
    assert_equals(reason, passedReason, 'reason should equal the passed reason');
  });
}, 'ReadableStream with byte source: enqueue(), getReader(), then cancel() (mode = BYOB)');

promise_test(t => {
  let cancelCount = 0;
  let reason;

  const passedReason = new TypeError('foo');

  let controller;

  const stream = new ReadableStream({
    start(c) {
      controller = c;
    },
    pull: t.unreached_func('pull() should not be called'),
    cancel(r) {
      if (cancelCount === 0) {
        reason = r;
        controller.byobRequest.respond(0);
      }

      ++cancelCount;

      return 'bar';
    },
    type: 'bytes'
  });

  const reader = stream.getReader({ mode: 'byob' });

  const readPromise = reader.read(new Uint8Array(1)).then(result => {
    assert_equals(result.done, true);
  });

  const cancelPromise = reader.cancel(passedReason).then(result => {
    assert_equals(result, undefined);
    assert_equals(cancelCount, 1);
    assert_equals(reason, passedReason, 'reason should equal the passed reason');
  });

  return Promise.all([readPromise, cancelPromise]);
}, 'ReadableStream with byte source: getReader(), read(view), then cancel()');

promise_test(() => {
  let pullCount = 0;

  let controller;
  let byobRequest;
  const viewInfos = [];

  const stream = new ReadableStream({
    start(c) {
      controller = c;
    },
    pull() {
      byobRequest = controller.byobRequest;

      viewInfos.push(extractViewInfo(controller.byobRequest.view));
      controller.enqueue(new Uint8Array(1));
      viewInfos.push(extractViewInfo(controller.byobRequest.view));

      ++pullCount;
    },
    type: 'bytes'
  });

  return Promise.resolve().then(() => {
    assert_equals(pullCount, 0, 'No pull() as no read(view) yet');

    const reader = stream.getReader({ mode: 'byob' });

    const promise = reader.read(new Uint16Array(1)).then(result => {
      assert_equals(result.done, true, 'result.done');
      assert_equals(result.value.constructor, Uint16Array, 'result.value');
    });

    assert_equals(pullCount, 1, '1 pull() should have been made in response to partial fill by enqueue()');
    assert_not_equals(byobRequest, undefined, 'byobRequest should not be undefined');
    assert_equals(viewInfos[0].byteLength, 2, 'byteLength before enqueue() shouild be 2');
    assert_equals(viewInfos[1].byteLength, 1, 'byteLength after enqueue() should be 1');


    reader.cancel();

    // Tell that the buffer given via pull() is returned.
    controller.byobRequest.respond(0);

    assert_equals(pullCount, 1, 'pull() should only be called once');
    return promise;
  });
}, 'ReadableStream with byte source: cancel() with partially filled pending pull() request');

promise_test(() => {
  let controller;
  let byobRequest;

  const stream = new ReadableStream({
    start(c) {
      const view = new Uint8Array(8);
      view[7] = 0x01;
      c.enqueue(view);

      controller = c;
    },
    pull() {
      byobRequest = controller.byobRequest;
    },
    type: 'bytes'
  });

  const reader = stream.getReader({ mode: 'byob' });

  const buffer = new ArrayBuffer(16);

  return reader.read(new Uint8Array(buffer, 8, 8)).then(result => {
    assert_equals(result.done, false);

    assert_equals(byobRequest, undefined, 'byobRequest must be undefined');

    const view = result.value;
    assert_equals(view.constructor, Uint8Array);
    assert_equals(view.buffer.byteLength, 16);
    assert_equals(view.byteOffset, 8);
    assert_equals(view.byteLength, 8);
    assert_equals(view[7], 0x01);
  });
}, 'ReadableStream with byte source: enqueue(), getReader(), then read(view) where view.buffer is not fully ' +
   'covered by view');

promise_test(() => {
  let controller;
  let byobRequest;

  const stream = new ReadableStream({
    start(c) {
      let view;

      view = new Uint8Array(16);
      view[15] = 123;
      c.enqueue(view);

      view = new Uint8Array(8);
      view[7] = 111;
      c.enqueue(view);

      controller = c;
    },
    pull() {
      byobRequest = controller.byobRequest;
    },
    type: 'bytes'
  });

  const reader = stream.getReader({ mode: 'byob' });

  return reader.read(new Uint8Array(24)).then(result => {
    assert_equals(result.done, false, 'done');

    assert_equals(byobRequest, undefined, 'byobRequest must be undefined');

    const view = result.value;
    assert_equals(view.byteOffset, 0, 'byteOffset');
    assert_equals(view.byteLength, 24, 'byteLength');
    assert_equals(view[15], 123, 'Contents are set from the first chunk');
    assert_equals(view[23], 111, 'Contents are set from the second chunk');
  });
}, 'ReadableStream with byte source: Multiple enqueue(), getReader(), then read(view)');

promise_test(() => {
  let byobRequest;

  const stream = new ReadableStream({
    start(c) {
      const view = new Uint8Array(16);
      view[15] = 0x01;
      c.enqueue(view);
    },
    pull(controller) {
      byobRequest = controller.byobRequest;
    },
    type: 'bytes'
  });

  const reader = stream.getReader({ mode: 'byob' });

  return reader.read(new Uint8Array(24)).then(result => {
    assert_equals(result.done, false);

    assert_equals(byobRequest, undefined, 'byobRequest must be undefined');

    const view = result.value;
    assert_equals(view.byteOffset, 0);
    assert_equals(view.byteLength, 16);
    assert_equals(view[15], 0x01);
  });
}, 'ReadableStream with byte source: enqueue(), getReader(), then read(view) with a bigger view');

promise_test(() => {
  let byobRequest;

  const stream = new ReadableStream({
    start(c) {
      const view = new Uint8Array(16);
      view[7] = 0x01;
      view[15] = 0x02;
      c.enqueue(view);
    },
    pull(controller) {
      byobRequest = controller.byobRequest;
    },
    type: 'bytes'
  });

  const reader = stream.getReader({ mode: 'byob' });

  return reader.read(new Uint8Array(8)).then(result => {
    assert_equals(result.done, false, 'done');

    const view = result.value;
    assert_equals(view.byteOffset, 0);
    assert_equals(view.byteLength, 8);
    assert_equals(view[7], 0x01);

    return reader.read(new Uint8Array(8));
  }).then(result => {
    assert_equals(result.done, false, 'done');

    assert_equals(byobRequest, undefined, 'byobRequest must be undefined');

    const view = result.value;
    assert_equals(view.byteOffset, 0);
    assert_equals(view.byteLength, 8);
    assert_equals(view[7], 0x02);
  });
}, 'ReadableStream with byte source: enqueue(), getReader(), then read(view) with a smaller views');

promise_test(() => {
  let controller;
  let viewInfo;

  const stream = new ReadableStream({
    start(c) {
      const view = new Uint8Array(1);
      view[0] = 0xff;
      c.enqueue(view);

      controller = c;
    },
    pull() {
      if (controller.byobRequest === undefined) {
        return;
      }

      const view = controller.byobRequest.view;
      viewInfo = extractViewInfo(view);

      view[0] = 0xaa;
      controller.byobRequest.respond(1);
    },
    type: 'bytes'
  });

  const reader = stream.getReader({ mode: 'byob' });

  return reader.read(new Uint16Array(1)).then(result => {
    assert_equals(result.done, false);

    const view = result.value;
    assert_equals(view.byteOffset, 0);
    assert_equals(view.byteLength, 2);
    assert_equals(view[0], 0xaaff);

    assert_equals(viewInfo.constructor, Uint8Array, 'view.constructor should be Uint8Array');
    assert_equals(viewInfo.bufferByteLength, 2, 'view.buffer.byteLength should be 2');
    assert_equals(viewInfo.byteOffset, 1, 'view.byteOffset should be 1');
    assert_equals(viewInfo.byteLength, 1, 'view.byteLength should be 1');
  });
}, 'ReadableStream with byte source: enqueue() 1 byte, getReader(), then read(view) with Uint16Array');

promise_test(() => {
  let pullCount = 0;

  let controller;
  let byobRequest;
  let viewInfo;
  let desiredSize;

  const stream = new ReadableStream({
    start(c) {
      const view = new Uint8Array(3);
      view[0] = 0x01;
      view[2] = 0x02;
      c.enqueue(view);

      controller = c;
    },
    pull() {
      byobRequest = controller.byobRequest;

      const view = controller.byobRequest.view;

      viewInfo = extractViewInfo(view);

      view[0] = 0x03;
      controller.byobRequest.respond(1);

      desiredSize = controller.desiredSize;

      ++pullCount;
    },
    type: 'bytes'
  });

  // Wait for completion of the start method to be reflected.
  return Promise.resolve().then(() => {
    const reader = stream.getReader({ mode: 'byob' });

    const promise = reader.read(new Uint16Array(2)).then(result => {
      assert_equals(result.done, false, 'done');

      const view = result.value;
      assert_equals(view.constructor, Uint16Array, 'constructor');
      assert_equals(view.buffer.byteLength, 4, 'buffer.byteLength');
      assert_equals(view.byteOffset, 0, 'byteOffset');
      assert_equals(view.byteLength, 2, 'byteLength');
      assert_equals(view[0], 0x0001, 'Contents are set');

      const p = reader.read(new Uint16Array(1));

      assert_equals(pullCount, 1);

      return p;
    }).then(result => {
      assert_equals(result.done, false, 'done');

      const view = result.value;
      assert_equals(view.buffer.byteLength, 2, 'buffer.byteLength');
      assert_equals(view.byteOffset, 0, 'byteOffset');
      assert_equals(view.byteLength, 2, 'byteLength');
      assert_equals(view[0], 0x0302, 'Contents are set');

      assert_not_equals(byobRequest, undefined, 'byobRequest must not be undefined');
      assert_equals(viewInfo.constructor, Uint8Array, 'view.constructor should be Uint8Array');
      assert_equals(viewInfo.bufferByteLength, 2, 'view.buffer.byteLength should be 2');
      assert_equals(viewInfo.byteOffset, 1, 'view.byteOffset should be 1');
      assert_equals(viewInfo.byteLength, 1, 'view.byteLength should be 1');
      assert_equals(desiredSize, 0, 'desiredSize should be zero');
    });

    assert_equals(pullCount, 0);

    return promise;
  });
}, 'ReadableStream with byte source: enqueue() 3 byte, getReader(), then read(view) with 2-element Uint16Array');

promise_test(t => {
  const stream = new ReadableStream({
    start(c) {
      const view = new Uint8Array(1);
      view[0] = 0xff;
      c.enqueue(view);
      c.close();
    },
    pull: t.unreached_func('pull() should not be called'),
    type: 'bytes'
  });

  const reader = stream.getReader({ mode: 'byob' });


  return promise_rejects(t, new TypeError(), reader.read(new Uint16Array(1)), 'read(view) must fail')
      .then(() => promise_rejects(t, new TypeError(), reader.closed, 'reader.closed should reject'));
}, 'ReadableStream with byte source: read(view) with Uint16Array on close()-d stream with 1 byte enqueue()-d must ' +
   'fail');

promise_test(t => {
  let controller;

  const stream = new ReadableStream({
    start(c) {
      const view = new Uint8Array(1);
      view[0] = 0xff;
      c.enqueue(view);

      controller = c;
    },
    pull: t.unreached_func('pull() should not be called'),
    type: 'bytes'
  });

  const reader = stream.getReader({ mode: 'byob' });

  const readPromise = reader.read(new Uint16Array(1));

  assert_throws_js(TypeError, () => controller.close(), 'controller.close() must throw');

  return promise_rejects(t, new TypeError(), readPromise, 'read(view) must fail')
      .then(() => promise_rejects(t, new TypeError(), reader.closed, 'reader.closed must reject'));
}, 'ReadableStream with byte source: A stream must be errored if close()-d before fulfilling read(view) with ' +
   'Uint16Array');

test(() => {
  let controller;

  new ReadableStream({
    start(c) {
      controller = c;
    },
    type: 'bytes'
  });

  // Enqueue a chunk so that the stream doesn't get closed. This is to check duplicate close() calls are rejected
  // even if the stream has not yet entered the closed state.
  const view = new Uint8Array(1);
  controller.enqueue(view);
  controller.close();

  assert_throws_js(TypeError, () => controller.close(), 'controller.close() must throw');
}, 'ReadableStream with byte source: Throw if close()-ed more than once');

test(() => {
  let controller;

  new ReadableStream({
    start(c) {
      controller = c;
    },
    type: 'bytes'
  });

  // Enqueue a chunk so that the stream doesn't get closed. This is to check enqueue() after close() is  rejected
  // even if the stream has not yet entered the closed state.
  const view = new Uint8Array(1);
  controller.enqueue(view);
  controller.close();

  assert_throws_js(TypeError, () => controller.enqueue(view), 'controller.close() must throw');
}, 'ReadableStream with byte source: Throw on enqueue() after close()');

promise_test(() => {
  let controller;
  let byobRequest;
  let viewInfo;

  const stream = new ReadableStream({
    start(c) {
      controller = c;
    },
    pull() {
      byobRequest = controller.byobRequest;
      const view = controller.byobRequest.view;
      viewInfo = extractViewInfo(view);

      view[15] = 0x01;
      controller.byobRequest.respond(16);
      controller.close();
    },
    type: 'bytes'
  });

  const reader = stream.getReader({ mode: 'byob' });

  return reader.read(new Uint8Array(16)).then(result => {
    assert_equals(result.done, false);

    const view = result.value;
    assert_equals(view.byteOffset, 0);
    assert_equals(view.byteLength, 16);
    assert_equals(view[15], 0x01);

    return reader.read(new Uint8Array(16));
  }).then(result => {
    assert_equals(result.done, true);

    const view = result.value;
    assert_equals(view.byteOffset, 0);
    assert_equals(view.byteLength, 0);

    assert_not_equals(byobRequest, undefined, 'byobRequest must not be undefined');
    assert_equals(viewInfo.constructor, Uint8Array, 'view.constructor should be Uint8Array');
    assert_equals(viewInfo.bufferByteLength, 16, 'view.buffer.byteLength should be 16');
    assert_equals(viewInfo.byteOffset, 0, 'view.byteOffset should be 0');
    assert_equals(viewInfo.byteLength, 16, 'view.byteLength should be 16');
  });
}, 'ReadableStream with byte source: read(view), then respond() and close() in pull()');

promise_test(() => {
  let pullCount = 0;

  let controller;
  const viewInfos = [];

  const stream = new ReadableStream({
    start(c) {
      controller = c;
    },
    pull() {
      if (controller.byobRequest === undefined) {
        return;
      }

      for (let i = 0; i < 4; ++i) {
        const view = controller.byobRequest.view;
        viewInfos.push(extractViewInfo(view));

        view[0] = 0x01;
        controller.byobRequest.respond(1);
      }

      ++pullCount;
    },
    type: 'bytes'
  });

  const reader = stream.getReader({ mode: 'byob' });

  return reader.read(new Uint32Array(1)).then(result => {
    assert_equals(result.done, false);

    const view = result.value;
    assert_equals(view.byteOffset, 0);
    assert_equals(view.byteLength, 4);
    assert_equals(view[0], 0x01010101);

    assert_equals(pullCount, 1, 'pull() should only be called once');

    for (let i = 0; i < 4; ++i) {
      assert_equals(viewInfos[i].constructor, Uint8Array, 'view.constructor should be Uint8Array');
      assert_equals(viewInfos[i].bufferByteLength, 4, 'view.buffer.byteLength should be 4');

      assert_equals(viewInfos[i].byteOffset, i, 'view.byteOffset should be i');
      assert_equals(viewInfos[i].byteLength, 4 - i, 'view.byteLength should be 4 - i');
    }
  });
}, 'ReadableStream with byte source: read(view) with Uint32Array, then fill it by multiple respond() calls');

promise_test(() => {
  let pullCount = 0;

  let controller;
  let byobRequest;

  const stream = new ReadableStream({
    start(c) {
      controller = c;
    },
    pull() {
      byobRequest = controller.byobRequest;

      ++pullCount;
    },
    type: 'bytes'
  });

  const reader = stream.getReader();

  const p0 = reader.read().then(result => {
    assert_equals(pullCount, 1);

    controller.enqueue(new Uint8Array(2));

    // Since the queue has data no less than HWM, no more pull.
    assert_equals(pullCount, 1);

    assert_equals(result.done, false);

    const view = result.value;
    assert_equals(view.constructor, Uint8Array);
    assert_equals(view.buffer.byteLength, 1);
    assert_equals(view.byteOffset, 0);
    assert_equals(view.byteLength, 1);
  });

  assert_equals(pullCount, 0, 'No pull should have been made since the startPromise has not yet been handled');

  const p1 = reader.read().then(result => {
    assert_equals(pullCount, 1);

    assert_equals(result.done, false);

    const view = result.value;
    assert_equals(view.constructor, Uint8Array);
    assert_equals(view.buffer.byteLength, 2);
    assert_equals(view.byteOffset, 0);
    assert_equals(view.byteLength, 2);

    assert_equals(byobRequest, undefined, 'byobRequest must be undefined');
  });

  assert_equals(pullCount, 0, 'No pull should have been made since the startPromise has not yet been handled');

  controller.enqueue(new Uint8Array(1));

  assert_equals(pullCount, 0, 'No pull should have been made since the startPromise has not yet been handled');

  return Promise.all([p0, p1]);
}, 'ReadableStream with byte source: read() twice, then enqueue() twice');

promise_test(t => {
  let controller;

  const stream = new ReadableStream({
    start(c) {
      controller = c;
    },
    pull: t.unreached_func('pull() should not be called'),
    type: 'bytes'
  });

  const reader = stream.getReader({ mode: 'byob' });

  const p0 = reader.read(new Uint8Array(16)).then(result => {
    assert_equals(result.done, true, '1st read: done');

    const view = result.value;
    assert_equals(view.buffer.byteLength, 16, '1st read: buffer.byteLength');
    assert_equals(view.byteOffset, 0, '1st read: byteOffset');
    assert_equals(view.byteLength, 0, '1st read: byteLength');
  });

  const p1 = reader.read(new Uint8Array(32)).then(result => {
    assert_equals(result.done, true, '2nd read: done');

    const view = result.value;
    assert_equals(view.buffer.byteLength, 32, '2nd read: buffer.byteLength');
    assert_equals(view.byteOffset, 0, '2nd read: byteOffset');
    assert_equals(view.byteLength, 0, '2nd read: byteLength');
  });

  controller.close();
  controller.byobRequest.respond(0);

  return Promise.all([p0, p1]);
}, 'ReadableStream with byte source: Multiple read(view), close() and respond()');

promise_test(t => {
  let controller;

  const stream = new ReadableStream({
    start(c) {
      controller = c;
    },
    pull: t.unreached_func('pull() should not be called'),
    type: 'bytes'
  });

  const reader = stream.getReader({ mode: 'byob' });

  const p0 = reader.read(new Uint8Array(16)).then(result => {
    assert_equals(result.done, false, '1st read: done');

    const view = result.value;
    assert_equals(view.buffer.byteLength, 16, '1st read: buffer.byteLength');
    assert_equals(view.byteOffset, 0, '1st read: byteOffset');
    assert_equals(view.byteLength, 16, '1st read: byteLength');
  });

  const p1 = reader.read(new Uint8Array(16)).then(result => {
    assert_equals(result.done, false, '2nd read: done');

    const view = result.value;
    assert_equals(view.buffer.byteLength, 16, '2nd read: buffer.byteLength');
    assert_equals(view.byteOffset, 0, '2nd read: byteOffset');
    assert_equals(view.byteLength, 8, '2nd read: byteLength');
  });

  controller.enqueue(new Uint8Array(24));

  return Promise.all([p0, p1]);
}, 'ReadableStream with byte source: Multiple read(view), big enqueue()');

promise_test(t => {
  let controller;

  const stream = new ReadableStream({
    start(c) {
      controller = c;
    },
    pull: t.unreached_func('pull() should not be called'),
    type: 'bytes'
  });

  const reader = stream.getReader({ mode: 'byob' });

  let bytesRead = 0;

  function pump() {
    return reader.read(new Uint8Array(7)).then(result => {
      if (result.done) {
        assert_equals(bytesRead, 1024);
        return undefined;
      }

      bytesRead += result.value.byteLength;

      return pump();
    });
  }
  const promise = pump();

  controller.enqueue(new Uint8Array(512));
  controller.enqueue(new Uint8Array(512));
  controller.close();

  return promise;
}, 'ReadableStream with byte source: Multiple read(view) and multiple enqueue()');

promise_test(t => {
  let byobRequest;
  const stream = new ReadableStream({
    pull(controller) {
      byobRequest = controller.byobRequest;
    },
    type: 'bytes'
  });

  const reader = stream.getReader({ mode: 'byob' });

  return promise_rejects(t, new TypeError(), reader.read(), 'read() must fail')
      .then(() => assert_equals(byobRequest, undefined, 'byobRequest must be undefined'));
}, 'ReadableStream with byte source: read(view) with passing undefined as view must fail');

promise_test(t => {
  let byobRequest;
  const stream = new ReadableStream({
    pull(controller) {
      byobRequest = controller.byobRequest;
    },
    type: 'bytes'
  });

  const reader = stream.getReader({ mode: 'byob' });

  return promise_rejects(t, new TypeError(), reader.read(new Uint8Array(0)), 'read(view) must fail')
      .then(() => assert_equals(byobRequest, undefined, 'byobRequest must be undefined'));
}, 'ReadableStream with byte source: read(view) with zero-length view must fail');

promise_test(t => {
  const stream = new ReadableStream({
    type: 'bytes'
  });

  const reader = stream.getReader({ mode: 'byob' });

  return promise_rejects(t, new TypeError(), reader.read({}), 'read(view) must fail');
}, 'ReadableStream with byte source: read(view) with passing an empty object as view must fail');

promise_test(t => {
  const stream = new ReadableStream({
    type: 'bytes'
  });

  const reader = stream.getReader({ mode: 'byob' });

  return promise_rejects(t, new TypeError(),
                         reader.read({ buffer: new ArrayBuffer(10), byteOffset: 0, byteLength: 10 }),
                         'read(view) must fail');
}, 'ReadableStream with byte source: Even read(view) with passing ArrayBufferView like object as view must fail');

promise_test(t => {
  const stream = new ReadableStream({
    start(c) {
      c.error(error1);
    },
    pull: t.unreached_func('pull() should not be called'),
    type: 'bytes'
  });

  const reader = stream.getReader();

  return promise_rejects(t, error1, reader.read(), 'read() must fail');
}, 'ReadableStream with byte source: read() on an errored stream');

promise_test(t => {
  let controller;

  const stream = new ReadableStream({
    start(c) {
      controller = c;
    },
    type: 'bytes'
  });

  const reader = stream.getReader();

  const promise = promise_rejects(t, error1, reader.read(), 'read() must fail');

  controller.error(error1);

  return promise;
}, 'ReadableStream with byte source: read(), then error()');

promise_test(t => {
  const stream = new ReadableStream({
    start(c) {
      c.error(error1);
    },
    pull: t.unreached_func('pull() should not be called'),
    type: 'bytes'
  });

  const reader = stream.getReader({ mode: 'byob' });

  return promise_rejects(t, error1, reader.read(new Uint8Array(1)), 'read() must fail');
}, 'ReadableStream with byte source: read(view) on an errored stream');

promise_test(t => {
  let controller;

  const stream = new ReadableStream({
    start(c) {
      controller = c;
    },
    type: 'bytes'
  });

  const reader = stream.getReader({ mode: 'byob' });

  const promise = promise_rejects(t, error1, reader.read(new Uint8Array(1)), 'read() must fail');

  controller.error(error1);

  return promise;
}, 'ReadableStream with byte source: read(view), then error()');

promise_test(t => {
  let controller;
  let byobRequest;

  const testError = new TypeError('foo');

  const stream = new ReadableStream({
    start(c) {
      controller = c;
    },
    pull() {
      byobRequest = controller.byobRequest;
      throw testError;
    },
    type: 'bytes'
  });

  const reader = stream.getReader();

  const promise = promise_rejects(t, testError, reader.read(), 'read() must fail');
  return promise_rejects(t, testError, promise.then(() => reader.closed))
      .then(() => assert_equals(byobRequest, undefined, 'byobRequest must be undefined'));
}, 'ReadableStream with byte source: Throwing in pull function must error the stream');

promise_test(t => {
  let byobRequest;

  const stream = new ReadableStream({
    pull(controller) {
      byobRequest = controller.byobRequest;
      controller.error(error1);
      throw new TypeError('foo');
    },
    type: 'bytes'
  });

  const reader = stream.getReader();

  return promise_rejects(t, error1, reader.read(), 'read() must fail')
      .then(() => promise_rejects(t, error1, reader.closed, 'closed must fail'))
      .then(() => assert_equals(byobRequest, undefined, 'byobRequest must be undefined'));
}, 'ReadableStream with byte source: Throwing in pull in response to read() must be ignored if the stream is ' +
   'errored in it');

promise_test(t => {
  let byobRequest;

  const testError = new TypeError('foo');

  const stream = new ReadableStream({
    pull(controller) {
      byobRequest = controller.byobRequest;
      throw testError;
    },
    type: 'bytes'
  });

  const reader = stream.getReader({ mode: 'byob' });

  return promise_rejects(t, testError, reader.read(new Uint8Array(1)), 'read(view) must fail')
      .then(() => promise_rejects(t, testError, reader.closed, 'reader.closed must reject'))
      .then(() => assert_not_equals(byobRequest, undefined, 'byobRequest must not be undefined'));
}, 'ReadableStream with byte source: Throwing in pull in response to read(view) function must error the stream');

promise_test(t => {
  let byobRequest;

  const stream = new ReadableStream({
    pull(controller) {
      byobRequest = controller.byobRequest;
      controller.error(error1);
      throw new TypeError('foo');
    },
    type: 'bytes'
  });

  const reader = stream.getReader({ mode: 'byob' });

  return promise_rejects(t, error1, reader.read(new Uint8Array(1)), 'read(view) must fail')
      .then(() => promise_rejects(t, error1, reader.closed, 'closed must fail'))
      .then(() => assert_not_equals(byobRequest, undefined, 'byobRequest must not be undefined'));
}, 'ReadableStream with byte source: Throwing in pull in response to read(view) must be ignored if the stream is ' +
   'errored in it');

promise_test(() => {
  let byobRequest;
  const rs = new ReadableStream({
    pull(controller) {
      byobRequest = controller.byobRequest;
      byobRequest.respond(4);
    },
    type: 'bytes'
  });
  const reader = rs.getReader({ mode: 'byob' });
  const view = new Uint8Array(16);
  return reader.read(view).then(() => {
    assert_throws_js(TypeError, () => byobRequest.respond(4), 'respond() should throw a TypeError');
  });
}, 'calling respond() twice on the same byobRequest should throw');

promise_test(() => {
  let byobRequest;
  const newView = () => new Uint8Array(16);
  const rs = new ReadableStream({
    pull(controller) {
      byobRequest = controller.byobRequest;
      byobRequest.respondWithNewView(newView());
    },
    type: 'bytes'
  });
  const reader = rs.getReader({ mode: 'byob' });
  return reader.read(newView()).then(() => {
    assert_throws_js(TypeError, () => byobRequest.respondWithNewView(newView()),
                     'respondWithNewView() should throw a TypeError');
  });
}, 'calling respondWithNewView() twice on the same byobRequest should throw');

promise_test(() => {
  let byobRequest;
  let resolvePullCalledPromise;
  const pullCalledPromise = new Promise(resolve => {
    resolvePullCalledPromise = resolve;
  });
  let resolvePull;
  const rs = new ReadableStream({
    pull(controller) {
      byobRequest = controller.byobRequest;
      resolvePullCalledPromise();
      return new Promise(resolve => {
        resolvePull = resolve;
      });
    },
    type: 'bytes'
  });
  const reader = rs.getReader({ mode: 'byob' });
  const readPromise = reader.read(new Uint8Array(16));
  return pullCalledPromise.then(() => {
    const cancelPromise = reader.cancel('meh');
    resolvePull();
    byobRequest.respond(0);
    return Promise.all([readPromise, cancelPromise]).then(() => {
      assert_throws_js(TypeError, () => byobRequest.respond(0), 'respond() should throw');
    });
  });
}, 'calling respond(0) twice on the same byobRequest should throw even when closed');

promise_test(() => {
  let resolvePullCalledPromise;
  const pullCalledPromise = new Promise(resolve => {
    resolvePullCalledPromise = resolve;
  });
  let resolvePull;
  const rs = new ReadableStream({
    pull() {
      resolvePullCalledPromise();
      return new Promise(resolve => {
        resolvePull = resolve;
      });
    },
    type: 'bytes'
  });
  const reader = rs.getReader({ mode: 'byob' });
  reader.read(new Uint8Array(16));
  return pullCalledPromise.then(() => {
    resolvePull();
    return delay(0).then(() => {
      assert_throws_js(TypeError, () => reader.releaseLock(), 'releaseLock() should throw');
    });
  });
}, 'pull() resolving should not make releaseLock() possible');

promise_test(() => {
  // Tests https://github.com/whatwg/streams/issues/686

  let controller;
  const rs = new ReadableStream({
    autoAllocateChunkSize: 128,
    start(c) {
      controller = c;
    },
    type: 'bytes'
  });

  const readPromise = rs.getReader().read();

  const br = controller.byobRequest;
  controller.close();

  br.respond(0);

  return readPromise;
}, 'ReadableStream with byte source: default reader + autoAllocateChunkSize + byobRequest interaction');

test(() => {
  const ReadableStreamBYOBReader = new ReadableStream({ type: 'bytes' }).getReader({ mode: 'byob' }).constructor;
  const stream = new ReadableStream({ type: 'bytes' });
  new ReadableStreamBYOBReader(stream);
}, 'ReadableStreamBYOBReader can be constructed directly');

test(() => {
  const ReadableStreamBYOBReader = new ReadableStream({ type: 'bytes' }).getReader({ mode: 'byob' }).constructor;
  assert_throws_js(TypeError, () => new ReadableStreamBYOBReader({}), 'constructor must throw');
}, 'ReadableStreamBYOBReader constructor requires a ReadableStream argument');

test(() => {
  const ReadableStreamBYOBReader = new ReadableStream({ type: 'bytes' }).getReader({ mode: 'byob' }).constructor;
  const stream = new ReadableStream({ type: 'bytes' });
  stream.getReader();
  assert_throws_js(TypeError, () => new ReadableStreamBYOBReader(stream), 'constructor must throw');
}, 'ReadableStreamBYOBReader constructor requires an unlocked ReadableStream');

test(() => {
  const ReadableStreamBYOBReader = new ReadableStream({ type: 'bytes' }).getReader({ mode: 'byob' }).constructor;
  const stream = new ReadableStream();
  assert_throws_js(TypeError, () => new ReadableStreamBYOBReader(stream), 'constructor must throw');
}, 'ReadableStreamBYOBReader constructor requires a ReadableStream with type "bytes"');

test(() => {
  assert_throws_js(RangeError, () => new ReadableStream({ type: 'bytes' }, {
    size() {
      return 1;
    }
  }), 'constructor should throw for size function');

  assert_throws_js(RangeError, () => new ReadableStream({ type: 'bytes' }, { size: null }),
                   'constructor should throw for size defined');

  assert_throws_js(RangeError,
                   () => new ReadableStream({ type: 'bytes' }, new CountQueuingStrategy({ highWaterMark: 1 })),
                   'constructor should throw when strategy is CountQueuingStrategy');

  assert_throws_js(RangeError,
                   () => new ReadableStream({ type: 'bytes' }, new ByteLengthQueuingStrategy({ highWaterMark: 512 })),
                   'constructor should throw when strategy is ByteLengthQueuingStrategy');

  class HasSizeMethod {
    size() {}
 }

  assert_throws_js(RangeError, () => new ReadableStream({ type: 'bytes' }, new HasSizeMethod()),
                   'constructor should throw when size on the prototype chain');
}, 'ReadableStream constructor should not accept a strategy with a size defined if type is "bytes"');
