<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="resources/test-helpers.sub.js"></script>
<script>
// This is tentative because currently this feature only has an explainer and
// no formal spec.
// https://github.com/wanderview/fetchevent-worker-timing/blob/master/explainer.md

async function wait_for_performance_entries(url, performance, entry_type) {
  const entries = await performance.getEntriesByName(url);
  if (entries.length > 0) {
    return entries;
  }
  return new Promise((resolve) => {
    new PerformanceObserver((list, observer) => {
      const entries = list.getEntriesByName(url);
      if (entries.length > 0) {
        observer.disconnect();
        resolve(entries);
      }
    }).observe({ entryTypes: [entry_type] });
  });
}

function load_image(target_document, url) {
  return new Promise((resolve, reject) => {
    const image = target_document.createElement('img');
    image.onload = () => { resolve(image); }
    image.onerror = () => { reject(`Failed to load: ${url}`); };
    image.src = url;
  });
}

promise_test(async (t) => {
  const registration = await service_worker_unregister_and_register(
    t, 'resources/fetch-event-worker-timing.js', 'resources/');
  promise_test(async (t) => registration.unregister(),
    'Unregister service worker');
  await wait_for_state(t, registration.installing, 'activated');
}, 'Set up an active service worker');

promise_test(async (t) => {
  const frame = await with_iframe('resources/empty.html?fallback');
  t.add_cleanup(() => frame.remove());

  const performance = frame.contentWindow.performance;

  const entries = await wait_for_performance_entries(
    frame.src, performance, 'navigation');
  assert_equals(entries.length, 1, 'PerformanceNavigationTiming is observed');

  const worker_timings = await entries[0].workerTiming;
  const expected_worker_timings = [
    {
      name: "network-fallback mark 1",
      entryType: "mark",
      detail: { foo: 'foo' }
    },
    {
      name: "network-fallback mark 2",
      entryType: "mark",
      detail: { bar: 'bar' }
    },
    {
      name: "network-fallback measure",
      entryType: "measure",
      detail: { baz: 'baz' }
    }
  ];
  assert_equals(worker_timings.length, expected_worker_timings.length,
    'workerTiming is completed when PerformanceResourceTiming is observed');

  for (let i = 0; i < workerTimings.length; i++) {
    assert_equals(worker_timings[i].name,
      expected_worker_timings[i].name, 'entry name');
    assert_equals(worker_timings[i].entryType,
      expected_worker_timings[i].entryType, 'entry type');
    assert_object_equals(worker_timings[i].detail,
      expected_worker_timings[i].detail, 'entry detail');
  }
}, 'workerTiming for navigation in a frame with network fallback');

promise_test(async (t) => {
  const frame= await with_iframe('resources/empty.html?fetch-event');
  t.add_cleanup(() => frame.remove());

  const performance = frame.contentWindow.performance;

  const entries = await wait_for_performance_entries(
    frame.src, performance, 'navigation');
  assert_equals(entries.length, 1, 'PerformanceNavigationTiming is observed');

  const worker_timings = await entries[0].workerTiming;
  const expected_worker_timings = [
    {
      name: "fetch-event mark 1",
      entryType: "mark",
      detail: { foo: 'foo' }
    },
    {
      name: "fetch-event mark 2",
      entryType: "mark",
      detail: { bar: 'bar' }
    },
    {
      name: "fetch-event measure",
      entryType: "measure",
      detail: { baz: 'baz' }
    }
  ];
  assert_equals(worker_timings.length, expected_worker_timings.length,
    'workerTiming is completed when PerformanceResourceTiming is observed');

  for (let i = 0; i < workerTimings.length; i++) {
    assert_equals(worker_timings[i].name,
      expected_worker_timings[i].name, 'entry name');
    assert_equals(worker_timings[i].entryType,
      expected_worker_timings[i].entryType, 'entry type');
    assert_object_equals(worker_timings[i].detail,
      expected_worker_timings[i].detail, 'entry detail');
  }
}, 'workerTiming for navigation in a frame for a response from a fetch ' +
   'handler');

promise_test(async (t) => {
  const frame = await with_iframe('resources/empty.html');
  t.add_cleanup(() => frame.remove());

  const performance = frame.contentWindow.performance;

  const image_path = base_path() + 'resources/square.png?fallback';
  const image_url = get_host_info()['HTTPS_ORIGIN'] + image_path;

  let entries = await performance.getEntriesByName(image_url);
  assert_equals(entries.length, 0, 'No PerformanceResourceTiming');

  const image = await load_image(frame.contentDocument,
    'square.png?fetch-event');
  entries = await wait_for_performance_entries(
    image.src, performance, 'resource');
  assert_equals(entries.length, 1, 'PerformanceResourceTiming is observed');

  const worker_timings = await entries[0].workerTiming;
  const expected_worker_timings = [
    {
      name: "network-fallback mark 1",
      entryType: "mark",
      detail: { foo: 'foo' }
    },
    {
      name: "network-fallback mark 2",
      entryType: "mark",
      detail: { bar: 'bar' }
    },
    {
      name: "network-fallback measure",
      entryType: "measure",
      detail: { baz: 'baz' }
    }
  ];
  assert_equals(worker_timings.length, expected_worker_timings.length,
    'workerTiming is completed when PerformanceResourceTiming is observed');

  for (let i = 0; i < workerTimings.length; i++) {
    assert_equals(worker_timings[i].name,
      expected_worker_timings[i].name, 'entry name');
    assert_equals(worker_timings[i].entryType,
      expected_worker_timings[i].entryType, 'entry type');
    assert_object_equals(worker_timings[i].detail,
      expected_worker_timings[i].detail, 'entry detail');
  }
}, 'workerTiming for subresources in a frame with network fallback');

promise_test(async (t) => {
  const frame = await with_iframe('resources/empty.html');
  t.add_cleanup(() => frame.remove());

  const performance = frame.contentWindow.performance;

  const image_path = base_path() + 'resources/square.png?fetch-event';
  const image_url = get_host_info()['HTTPS_ORIGIN'] + image_path;

  let entries = await performance.getEntriesByName(image_url);
  assert_equals(entries.length, 0, 'No PerformanceResourceTiming');

  const image = await load_image(frame.contentDocument,
    'square.png?fetch-event');
  entries = await wait_for_performance_entries(
    image.src, performance, 'resource');
  assert_equals(entries.length, 1, 'PerformanceResourceTiming is observed');

  const worker_timings = await entries[0].workerTiming;
  const expected_worker_timings = [
    {
      name: "fetch-event mark 1",
      entryType: "mark",
      detail: { foo: 'foo' }
    },
    {
      name: "fetch-event mark 2",
      entryType: "mark",
      detail: { bar: 'bar' }
    },
    {
      name: "fetch-event measure",
      entryType: "measure",
      detail: { baz: 'baz' }
    }
  ];
  assert_equals(worker_timings.length, expected_worker_timings.length,
    'workerTiming is completed when PerformanceResourceTiming is observed');

  for (let i = 0; i < workerTimings.length; i++) {
    assert_equals(worker_timings[i].name,
      expected_worker_timings[i].name, 'entry name');
    assert_equals(worker_timings[i].entryType,
      expected_worker_timings[i].entryType, 'entry type');
    assert_object_equals(worker_timings[i].detail,
      expected_worker_timings[i].detail, 'entry detail');
  }
}, 'workerTiming for subresources in a frame for a response from a fetch' +
   'handler');
</script>
