<!DOCTYPE html>
<title>Test storage partitioning in fenced frames</title>
<meta name="timeout" content="long">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/utils.js"></script>
<script src="/common/dispatcher/dispatcher.js"></script>
<script src="resources/utils.js"></script>

<body>
<script>

// `getter(key)` : reads the value of `key`, null if not set
// `setter(key, value)`: sets `key` to `value`
async function runTest(getter, setter) {
  const key = "key";
  const outer_value = "outer";
  const inner_value = "inner";

  // Set the value in the top-level frame, and check that it worked.
  await setter(key, outer_value);
  assert_equals(await getter(key), outer_value,
      "Stored the value in the top-level frame.");

  // Attach a fenced frame.
  const frame = attachFencedFrameContext();

  // Check that the outer value isn't visible.
  const inner_before_set = await frame.execute(getter, [key]);
  assert_equals(inner_before_set, null,
      "The outer value isn't visible inside the fenced frame.");

  // Set the value inside the fenced frame, and check that it worked.
  await frame.execute(setter, [key, inner_value]);
  const inner_after_set = await frame.execute(getter, [key]);
  assert_equals(inner_after_set, inner_value,
      "Stored the value in the fenced frame.");

  // Check that the inner value isn't visible in the top-level frame.
  assert_equals(await getter(key), outer_value,
      "The inner value isn't visible outside the fenced frame.");

  // Perform an embedder-initiated navigation that will fail.
  const original_config = frame.config;
  frame.config = new FencedFrameConfig("resources/response-204.py");
  await step_timeout(() => {}, 1000);

  // Check that the failed navigation didn't change the storage partition.
  // (The partition nonce should be reinitialized on navigation commit.)
  const inner_after_failure = await frame.execute(getter, [key]);
  assert_equals(inner_after_failure, inner_value,
      "The inner value is still present after the failed navigation.");

  // Refresh the fenced frame from within.
  await frame.execute(() => {
    window.executor.suspend(() => { location.href = location.href; });
  });

  // Check that the storage partition is the same.
  const inner_after_inner_refresh = await frame.execute(getter, [key]);
  assert_equals(inner_after_inner_refresh, inner_value,
      "The inner value is the same after a fencedframe-initiated refresh.");

  // Refresh the fenced frame from the embedder.
  await frame.execute(() => window.executor.suspend(() => {}));
  frame.element.config = original_config;

  // Check that there is a blank slate.
  const inner_after_embedder_refresh = await frame.execute(getter, [key]);
  assert_equals(inner_after_embedder_refresh, null,
      "The inner value is gone after an embedder-initiated refresh.");
}

promise_test(async () => {
  return runTest(
    (_) => { return document.cookie || null; },
    (_, value) => { document.cookie = value;}
  );
}, 'document.cookie');

promise_test(async () => {
  return runTest(
    (key) => { return localStorage.getItem(key); },
    (key, value) => { return localStorage.setItem(key, value); }
  );
}, 'localStorage');

promise_test(async () => {
  return runTest(
    (key) => { return sessionStorage.getItem(key); },
    (key, value) => { return sessionStorage.setItem(key, value); }
  );
}, 'sessionStorage');

promise_test(async () => {
  return runTest(
    async (key) => {
      const newCache = await caches.open('test-cache');
      const response = await newCache.match(key);
      if (!response) {
        return null;
      }
      return response.text();
    },
    async (key, value) => {
      const newCache = await caches.open('test-cache');
      return newCache.put(key, new Response(value));
    }
  );
}, 'Cache API');

promise_test(async () => {
  return runTest(
    async (key) => {
      const root = await navigator.storage.getDirectory();
      const draftHandle = await root.getFileHandle(key, { create: true });
      const file = await draftHandle.getFile();
      const text = await file.text();
      return text || null;
    },
    async (key, value) => {
      const root = await navigator.storage.getDirectory();
      const draftHandle = await root.getFileHandle(key, { create: true });
      const writable = await draftHandle.createWritable()
      await writable.truncate(0);
      await writable.write(value);
      await writable.close();
    }
  );
}, 'File System Access API');

promise_test(async () => {
  return runTest(
    async (key) => {
      const openRequest = indexedDB.open('test-db', 2);
      const db = await new Promise((resolve) => {
        openRequest.onsuccess = (event) => {
          resolve(event.target.result);
        };
        openRequest.onupgradeneeded = (event) => {
          const db = event.target.result;
          const objStore = db.createObjectStore('test-tbl', {keyPath: 'key'});
          objStore.transaction.oncomplete = (event) => {
            resolve(db);
          };
        };
      });
      const readRequest = db.transaction(['test-tbl'])
                            .objectStore('test-tbl')
                            .get(key);
      return new Promise((resolve) => {
        readRequest.onsuccess = (event) => {
          if (!event.target.result) {
            resolve(null);
          } else {
            resolve(event.target.result['value']);
          }
        };
      });
    },
    async (key, value) => {
      const openRequest = indexedDB.open('test-db', 2);
      const db = await new Promise((resolve) => {
        openRequest.onsuccess = (event) => {
          resolve(event.target.result);
        };
        openRequest.onupgradeneeded = (event) => {
          const db = event.target.result;
          const objStore = db.createObjectStore('test-tbl', {keyPath: 'key'});
          objStore.transaction.oncomplete = (event) => {
            resolve(db);
          };
        };
      });
      const writeRequest = db.transaction(['test-tbl'], 'readwrite')
                             .objectStore('test-tbl')
                             .add({'key': key, 'value': value});
      return new Promise((resolve) => {
        writeRequest.onsuccess = (event) => {
          resolve(event.target.result);
        };
      });
    }
  );
}, 'IndexedDB');

</script>
</body>
