<!DOCTYPE html>
<meta charset=utf-8>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>

async_test(t => {
    let c1 = new BroadcastChannel('worker');
    let c2 = new BroadcastChannel('worker');
    let events = [];

    c1.onmessage = e => events.push(e);
    c2.onmessage = e => events.push(e);

    let doneCount = 0;
    c2.addEventListener('message', t.step_func(e => {
        if (e.data == 'from worker') {
          c2.postMessage('from c2');
          c1.postMessage('done');
        } else if (e.data == 'done') {
          assert_equals(events.length, 4);
          assert_equals(events[0].data, 'from worker');
          assert_equals(events[0].target, c1);
          assert_equals(events[1].data, 'from worker');
          assert_equals(events[1].target, c2);
          assert_equals(events[2].data, 'from c2');
          assert_equals(events[3].data, 'done');
          if (++doneCount == 2) t.done();
        }
      }));

    let worker = new Worker('resources/worker.js');
    worker.onmessage = t.step_func(e => {
        assert_array_equals(e.data, ['from c2', 'done']);
        if (++doneCount == 2) t.done();
      });
    worker.postMessage({channel: 'worker'});

  }, 'BroadcastChannel works in workers');

async_test(t => {
    let c1 = new BroadcastChannel('shared worker');
    let c2 = new BroadcastChannel('shared worker');
    let events = [];

    c1.onmessage = e => events.push(e);
    c2.onmessage = e => events.push(e);

    let doneCount = 0;
    c2.addEventListener('message', t.step_func(e => {
        if (e.data == 'from worker') {
          c2.postMessage('from c2');
          c1.postMessage('done');
        } else if (e.data == 'done') {
          assert_equals(events.length, 4);
          assert_equals(events[0].data, 'from worker');
          assert_equals(events[0].target, c1);
          assert_equals(events[1].data, 'from worker');
          assert_equals(events[1].target, c2);
          assert_equals(events[2].data, 'from c2');
          assert_equals(events[3].data, 'done');
          if (++doneCount == 2) t.done();
        }
      }));

    let worker = new SharedWorker('resources/worker.js');
    worker.port.onmessage = t.step_func(e => {
        assert_array_equals(e.data, ['from c2', 'done']);
        if (++doneCount == 2) t.done();
      });
    worker.port.postMessage({channel: 'shared worker'});

  }, 'BroadcastChannel works in shared workers');

async_test(t => {
    let c = new BroadcastChannel('worker-close');
    let events = [];

    c.onmessage = e => events.push('c1: ' + e.data);

    let worker = new Worker('resources/worker.js');
    worker.onmessage = t.step_func(e => {
        assert_array_equals(events,
                            ['c1: from worker', 'c2: ready', 'c2: echo'],
                            'messages in document');
        assert_array_equals(e.data, ['done'], 'messages in worker');
        t.done();
      });
    worker.onmessagerror =
        t.unreached_func('Worker\'s onmessageerror handler called');

    c.addEventListener('message', e => {
        if (e.data == 'from worker') {
          c.close();
          if (self.gc) self.gc();
          window.setTimeout(() => {
              let c2 = new BroadcastChannel('worker-close');
              c2.onmessage = e => {
                  events.push('c2: ' + e.data);
                  if (e.data === 'ready') {
                    worker.postMessage({ping: 'echo'});
                  } else {
                    c2.postMessage('done');
                    c2.close();
                  }
                };
              // For some implementations there may be a race condition between
              // when the BroadcastChannel instance above is created / ready to
              // receive messages and when the worker calls postMessage on it's
              // BroadcastChannel instance. To avoid this, confirm that our
              // instance can receive a message before indicating to the other
              // thread that we are ready. For more details, see:
              // https://github.com/whatwg/html/issues/7267
              let c3 = new BroadcastChannel('worker-close');
              c3.postMessage('ready');
              c3.close();
            }, 1);
        }
      });

    worker.postMessage({channel: 'worker-close'});
    t.add_cleanup(() => worker.terminate());

  }, 'Closing and re-opening a channel works.');

async_test(t => {
  function workerCode() {
    close();
    try {
      var bc = new BroadcastChannel('worker-create-after-close');
    } catch (e) {
      postMessage(e);
      return;
    }
    postMessage(true);
  }

  var workerBlob = new Blob(
      [workerCode.toString() + ';workerCode();'],
      {type: 'application/javascript'});

  var w = new Worker(URL.createObjectURL(workerBlob));
  w.onmessage = t.step_func_done(function(e) {
    assert_equals(
        e.data, true,
        'BroadcastChannel creation in closed worker triggered exception: ' +
            e.data.message);
  });
  t.add_cleanup(() => w.terminate());
}, 'BroadcastChannel created after a worker self.close()');


function postMessageFromWorkerWorkerCode(workerName, channelName) {
  if (workerName === 'close-before-create-worker') {
    close();
  }
  let bc = new BroadcastChannel(channelName);
  if (workerName === 'close-after-create-worker') {
    close();
  }
  bc.postMessage(workerName + ' done');
  postMessage(true);
}

function doPostMessageFromWorkerTest(t, workerName, channelName) {
  var bc = new BroadcastChannel(channelName);
  bc.onmessage = t.step_func_done(function(e) {
    assert_equals(
        e.data, 'done-worker done',
        'BroadcastChannel message should only be received from the second worker');
  });
  t.add_cleanup(() => bc.close());

  var testMessageHandler = t.step_func(function(e) {
    assert_equals(
        e.data, true,
        'Worker sent postMessage indicating it sent a BroadcastChannel message');

    var w = createWorker(
        postMessageFromWorkerWorkerCode, 'done-worker', channelName);
    t.add_cleanup(() => w.terminate());
  });
  createWorker(
      postMessageFromWorkerWorkerCode, workerName, channelName,
      testMessageHandler);

  // To avoid calling t.step_timeout here, have the worker postMessage(true)
  // once it is finished and then we'll instantiate another worker that
  // performs the same test steps but doesn't close. By the time the
  // BroadcastChannel message in that worker gets sent successfully it should
  // be safe to assume that any BroadcastChannel messages from the previous
  // worker would have been sent if they were going to be.
}

function createWorker(workerCode, workerName, channelName, handler = null) {
  var workerCodeStr = workerCode.toString() +
      `;${workerCode.name}("${workerName}", "${channelName}");`;
  var workerBlob = new Blob([workerCodeStr], {type: 'application/javascript'});
  var w = new Worker(URL.createObjectURL(workerBlob));
  if (handler !== null) {
    w.onmessage = handler;
  }
  return w;
}

async_test(t => {
  const workerName = 'close-after-create-worker';
  const channelName = workerName + '-postmessage-from-worker';
  doPostMessageFromWorkerTest(t, workerName, channelName);
}, 'BroadcastChannel messages from closed worker to parent should be ignored (BC created before closing)');

async_test(t => {
  const workerName = 'close-before-create-worker';
  const channelName = workerName + '-postmessage-from-worker';
  doPostMessageFromWorkerTest(t, workerName, channelName);
}, 'BroadcastChannel messages from closed worker to parent should be ignored (BC created after closing)');


function postMessageToWorkerWorkerCode(workerName, channelName) {
  self.addEventListener('message', () => {
    if (workerName === 'close-before-create-worker') {
      close();
    }
    try {
      let bc1 = new BroadcastChannel(channelName);
      bc1.onmessage = e => {
        if (e.data === 'ready') {
          postMessage(e.data);
        } else if (e.data === 'test') {
          postMessage(workerName + ' done');
        }
      };
      bc1.onmessageerror = () => {
        postMessage('onmessageerror called from worker BroadcastChannel');
      };
      if (workerName === 'close-after-create-worker') {
        close();
      }
    } catch (e) {
      postMessage(e);
      return;
    }

    if (workerName === 'done-worker') {
      // For some implementations there may be a race condition between when
      // the BroadcastChannel instance above is created / ready to receive
      // messages and when the parent calls postMessage on it's
      // BroadcastChannel instance. To avoid this, confirm that our instance
      // can receive a message before indicating to the other thread that we
      // are ready. For more details, see:
      // https://github.com/whatwg/html/issues/7267
      let bc2 = new BroadcastChannel(channelName);
      bc2.postMessage('ready');
      bc2.close();
    } else {
      // Since the worker has closed, it's not expected that the
      // BroadcastChannel will receive messages (there's a separate test for
      // that), so just indicate directly that it's ready to test receiving
      // a message from the parent dispite the possibility of a race condition.
      postMessage('ready');
    }
  });
  self.addEventListener('messageerror', () => {
    postMessage('onmessageerror called from worker');
  });
}

function doPostMessageToWorkerTest(t, workerName, channelName) {
  var bc = new BroadcastChannel(channelName);
  t.add_cleanup(() => bc.close());

  var doneMessageHandler = t.step_func(function(e) {
    if (e.data === 'ready') {
      bc.postMessage('test');
    } else if (e.data === 'done-worker done') {
      t.done();
    } else {
      assert_unreached(
          'BroadcastChannel.postMessage triggered exception within second worker: ' +
          e.data.message);
    }
  });
  var testMessageHandler = t.step_func(function(e) {
    assert_equals(
        e.data, 'ready',
        'Worker sent postMessage indicating its BroadcastChannel instance is ready');
    bc.postMessage('test');

    var doneWorker = createWorker(
        postMessageToWorkerWorkerCode, 'done-worker', channelName,
        doneMessageHandler);
    t.add_cleanup(() => {
      doneWorker.terminate();
    });
    doneWorker.postMessage('start');
  });
  var testWorker = createWorker(
      postMessageToWorkerWorkerCode, workerName, channelName,
      testMessageHandler);
  testWorker.postMessage('start');
}

async_test(t => {
  const workerName = 'close-after-create-worker';
  const channelName = workerName + '-postmessage-to-worker';
  doPostMessageToWorkerTest(t, workerName, channelName);
}, 'BroadcastChannel messages from parent to closed worker should be ignored (BC created before closing)');

async_test(t => {
  const workerName = 'close-before-create-worker';
  const channelName = workerName + '-postmessage-to-worker';
  doPostMessageToWorkerTest(t, workerName, channelName);
}, 'BroadcastChannel messages from parent to closed worker should be ignored (BC created after closing)');


function postMessageWithinWorkerWorkerCode(workerName, channelName) {
  if (workerName === 'close-before-create-worker') {
    close();
  }
  try {
    let bc1 = new BroadcastChannel(channelName);
    let bc2 = new BroadcastChannel(channelName);
    bc1.onmessage = e => {
      postMessage(workerName + ' done')
    };
    if (workerName === 'close-after-create-worker') {
      close();
    }
    bc2.postMessage(true);
    postMessage(true);
  } catch (e) {
    postMessage(e);
  }
}

function doPostMessageWithinWorkerTest(t, workerName, channelName) {
  var doneMessageHandler = t.step_func(function(e) {
    if (e.data === true) {
      // Done worker has finished - no action needed
    } else if (e.data === 'done-worker done') {
      t.done();
    } else {
      assert_unreached(
          'BroadcastChannel.postMessage triggered exception within second worker: ' +
          e.data.message);
    }
  });
  var testMessageHandler = t.step_func(function(e) {
    assert_equals(
        e.data, true,
        'Worker indicated that the test procedures were executed successfully');

    var w = createWorker(
        postMessageWithinWorkerWorkerCode, 'done-worker', channelName,
        doneMessageHandler);
    t.add_cleanup(() => w.terminate());
  });
  createWorker(
      postMessageWithinWorkerWorkerCode, workerName, channelName,
      testMessageHandler);
}

async_test(t => {
  const workerName = 'close-after-create-worker';
  const channelName = workerName + '-postmessage-within-worker';
  doPostMessageWithinWorkerTest(t, workerName, channelName);
}, 'BroadcastChannel messages within closed worker should be ignored (BCs created before closing)');

async_test(t => {
  const workerName = 'close-before-create-worker';
  const channelName = workerName + '-postmessage-within-worker';
  doPostMessageWithinWorkerTest(t, workerName, channelName);
}, 'BroadcastChannel messages within closed worker should be ignored (BCs created after closing)');

</script>
