<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/test-helpers.sub.js"></script>
<script>
function resourceUrl(path) {
  return "https://{{host}}:{{ports[https][0]}}" + base_path() + path;
}

function crossOriginUrl(path) {
  return "https://{{hosts[alt][]}}:{{ports[https][0]}}" + base_path() + path;
}

// Verify existance of a PerformanceEntry and the order between the timings.
//
// |options| has these properties:
// performance: Performance interface to verify existance of the entry.
// resource: the path to the resource.
// mode: 'cross-origin' to load the resource from third-party origin.
// description: the description passed to each assertion.
// should_no_performance_entry: no entry is expected to be recorded when it's
//                              true.
function verify(options) {
  const url = options.mode === 'cross-origin' ? crossOriginUrl(options.resource)
    : resourceUrl(options.resource);
  const entryList = options.performance.getEntriesByName(url, 'resource');
  if (options.should_no_performance_entry) {
    // The performance timeline may not have an entry for a resource
    // which failed to load.
    assert_equals(entryList.length, 0, options.description);
    return;
  }

  assert_equals(entryList.length, 1, options.description);
  const entry = entryList[0];
  assert_equals(entry.entryType, 'resource', options.description);

  // workerStart is recorded between startTime and fetchStart.
  assert_greater_than(entry.workerStart, 0, options.description);
  assert_greater_than_equal(entry.workerStart, entry.startTime, options.description);
  assert_less_than_equal(entry.workerStart, entry.fetchStart, options.description);

  if (options.mode === 'cross-origin') {
    assert_equals(entry.responseStart, 0, options.description);
    assert_greater_than_equal(entry.responseEnd, entry.fetchStart, options.description);
  } else {
    assert_greater_than_equal(entry.responseStart, entry.fetchStart, options.description);
    assert_greater_than_equal(entry.responseEnd, entry.responseStart, options.description);
  }

  // responseEnd follows fetchStart.
  assert_greater_than(entry.responseEnd, entry.fetchStart, options.description);
  // duration always has some value.
  assert_greater_than(entry.duration, 0, options.description);

  if (options.resource.indexOf('redirect.py') != -1) {
    assert_less_than_equal(entry.workerStart, entry.redirectStart,
      options.description);
  } else {
    assert_equals(entry.redirectStart, 0, options.description);
  }
}

promise_test(async (t) => {
  const worker_url = 'resources/resource-timing-worker.js';
  const scope = 'resources/resource-timing-iframe.sub.html';

  const registration = await service_worker_unregister_and_register(t, worker_url, scope);
  t.add_cleanup(() => registration.unregister());
  await wait_for_state(t, registration.installing, 'activated');
  const frame = await with_iframe(scope);
  t.add_cleanup(() => frame.remove());

  const performance = frame.contentWindow.performance;
  verify({
    performance: performance,
    resource: 'resources/sample.js',
    mode: 'same-origin',
    description: 'Generated response',
  });
  verify({
    performance: performance,
    resource: 'resources/empty.js',
    mode: 'same-origin',
    description: 'Network fallback',
  });
  verify({
    performance: performance,
    resource: 'resources/redirect.py?Redirect=empty.js',
    mode: 'same-origin',
    description: 'Redirect',
  });
  verify({
    performance: performance,
    resource: 'resources/square.png',
    mode: 'same-origin',
    description: 'Network fallback image',
  });
  // Test that worker start is available on cross-origin no-cors
  // subresources.
  verify({
    performance: performance,
    resource: 'resources/square.png',
    mode: 'cross-origin',
    description: 'Network fallback cross-origin image',
  });

  // Tests for resouces which failed to load.
  verify({
    performance: performance,
    resource: 'resources/missing.jpg',
    mode: 'same-origin',
    description: 'Network fallback load failure',
  });
  verify({
    performance: performance,
    resource: 'resources/missing.jpg',
    mode: 'cross-origin',
    description: 'Network fallback cross-origin load failure',
  });
  // Tests for respondWith(fetch()).
  verify({
    performance: performance,
    resource: 'resources/missing.jpg?SWRespondsWithFetch',
    mode: 'same-origin',
    description: 'Resource in iframe, nonexistent but responded with fetch to another.',
  });
  verify({
    performance: performance,
    resource: 'resources/sample.txt?SWFetched',
    mode: 'same-origin',
    description: 'Resource fetched as response from missing.jpg?SWRespondsWithFetch.',
    should_no_performance_entry: true,
  });
  // Test for a normal resource that is unaffected by the Service Worker.
  verify({
    performance: performance,
    resource: 'resources/empty-worker.js',
    mode: 'same-origin',
    description: 'Resource untouched by the Service Worker.',
  });
}, 'Controlled resource loads');

test(() => {
  const url = resourceUrl('resources/test-helpers.sub.js');
  const entry = window.performance.getEntriesByName(url, 'resource')[0];
  assert_equals(entry.workerStart, 0, 'Non-controlled');
}, 'Non-controlled resource loads');

</script>
