function loadSharedStorageImage(data) {
  let {key, value, hasSharedStorageWritableAttribute, isSameOrigin} = data;
  const encodedKey = encodeURIComponent(key);
  const encodedValue = encodeURIComponent(value);
  const sameOriginSrc = `/shared-storage/resources/` +
      `shared-storage-writable-pixel.png?key=${encodedKey}&value=${encodedValue}`;
  const crossOriginSrc =
      'https://{{domains[www]}}:{{ports[https][0]}}' + sameOriginSrc;

  let image = document.createElement('img');
  image.src = isSameOrigin ? sameOriginSrc : crossOriginSrc;
  if (hasSharedStorageWritableAttribute) {
    image.sharedStorageWritable = true;
  }

  const promise = new Promise((resolve, reject) => {
    image.addEventListener('load', () => {
      resolve(image);
    });
    image.addEventListener('error', () => {
      reject(new Error('Image load failed'));
    });
  });

  document.body.appendChild(image);
  return promise;
}

function navigateSharedStorageIframe(data) {
  let {
    hasSharedStorageWritableAttribute,
    rawWriteHeader,
    isSameOrigin,
    expectSharedStorageWritableHeader
  } = data;
  const writeHeader = encodeURIComponent(rawWriteHeader);
  const sameOriginSrc =
      `/shared-storage/resources/shared-storage-write-notify-parent.py` +
      `?write=${writeHeader}`;
  const crossOriginSrc =
      'https://{{domains[www]}}:{{ports[https][0]}}' + sameOriginSrc;

  let frame = document.createElement('iframe');
  frame.src = isSameOrigin ? sameOriginSrc : crossOriginSrc;
  if (hasSharedStorageWritableAttribute) {
    frame.sharedStorageWritable = true;
  }

  const expectedResult = expectSharedStorageWritableHeader ?
      '?1' :
      'NO_SHARED_STORAGE_WRITABLE_HEADER';

  function checkExpectedResult(data) {
    assert_equals(data.sharedStorageWritableHeader, expectedResult);
  }

  const promise = new Promise((resolve, reject) => {
    window.addEventListener('message', async function handler(evt) {
      if (evt.source === frame.contentWindow) {
        checkExpectedResult(evt.data);
        document.body.removeChild(frame);
        window.removeEventListener('message', handler);
        resolve();
      }
    });
    window.addEventListener('error', () => {
      reject(new Error('Navigation error'));
    });
  });

  document.body.appendChild(frame);
  return promise;
}

async function loadNestedSharedStorageFrameInNewFrame(data) {
  const SCOPE = '/shared-storage/resources/shared-storage-writ';
  const INTERMEDIATE_FRAME_SUFFIX =
      'able-fetch-request-fallback-to-network-iframe.https.html'
  const CROSS_ORIGIN = 'https://{{domains[www]}}:{{ports[https][0]}}';

  let {key, value, hasSharedStorageWritableAttribute, isSameOrigin} = data;

  const windowPromise = new Promise((resolve, reject) => {
    window.addEventListener('message', async function handler(evt) {
      if (evt.data.msg && evt.data.msg === 'iframe loaded') {
        window.removeEventListener('message', handler);
        resolve();
      }
    });
    window.addEventListener('error', () => {
      reject(new Error('Navigation error'));
    });
  });

  const framePromise = new Promise((resolve, reject) => {
    let frame = document.createElement('iframe');
    frame.src = SCOPE + INTERMEDIATE_FRAME_SUFFIX;
    frame.onload = function() {
      resolve(frame);
    };
    frame.onerror = function() {
      reject(new Error('Iframe load failed'));
    };
    document.body.appendChild(frame);
  });
  let frame = await framePromise;

  let rawWriteHeader = `set;key=${key};value=${value}`;
  let writeHeader = encodeURIComponent(rawWriteHeader);
  const sameOriginNestedSrc = `/shared-storage/resources/` +
      `shared-storage-write.py?write=${writeHeader}`;
  const nestedSrc =
      isSameOrigin ? sameOriginNestedSrc : CROSS_ORIGIN + sameOriginNestedSrc;

  let nestedFrame = frame.contentWindow.loadFrame(
      nestedSrc, hasSharedStorageWritableAttribute);
  await windowPromise;
  return {frame: frame, nestedFrame: nestedFrame, nestedFrameUrl: nestedSrc};
}

async function createWorkletAndVerifyDataOrigin(
    t, data_origin, script_origin, expect_success, error_type) {
  if (error_type) {
    assert_false(expect_success);
  }
  const key = 'key0';
  const value = 'value0';
  const sameOrigin = location.origin;
  const sameOriginScriptUrl = `/shared-storage/resources/simple-module.js`;
  const scriptUrl = (script_origin === location.origin) ?
      sameOriginScriptUrl :
      script_origin + sameOriginScriptUrl;
  let dataOrigin = sameOrigin;
  if (data_origin === 'script-origin') {
    dataOrigin = script_origin;
  } else if (data_origin !== '' && data_origin !== 'context-origin') {
    try {
      dataOrigin = new URL(data_origin).origin;
    } catch (e) {
      if (e.message !== 'Failed to construct \'URL\': Invalid URL') {
        throw e;
      }
      assert_false(expect_success);
    }
  }
  const options = (data_origin === '') ?
      {credentials: 'omit'} :
      {credentials: 'omit', dataOrigin: data_origin};
  let success = false;
  let error = null;

  try {
    const worklet = await sharedStorage.createWorklet(scriptUrl, options);

    const ancestor_key = token();
    let url0 =
        generateURL('/shared-storage/resources/frame0.html', [ancestor_key]);

    let select_url_result =
        await worklet.selectURL('test-url-selection-operation', [{url: url0}], {
          data: {'mockResult': 0, 'setKey': key, 'setValue': value},
          resolveToConfig: true,
          keepAlive: true
        });

    assert_true(validateSelectURLResult(select_url_result, true));
    attachFencedFrame(select_url_result, 'opaque-ads');
    const result0 = await nextValueFromServer(ancestor_key);
    assert_equals(result0, 'frame0_loaded');

    await verifyKeyValueForOrigin(key, value, dataOrigin);
    await deleteKeyForOrigin(key, dataOrigin);
    success = true;
  } catch (e) {
    error = e;
    assert_equals(e.name, error_type, e.message);
  } finally {
    assert_equals(
        expect_success, success,
        error ? 'expected success but error thrown: ' + error.toString() :
                'no error caught even though one was expected');
    t.done();
  }
}
