'use strict';

// This script depends on the following script:
//    /fs/resources/test-helpers.js
//    /service-workers/service-worker/resources/test-helpers.sub.js

// Define the URL constants used for each type of message target, including
// iframes and workers.
const kDocumentMessageTarget = 'resources/message-target.html';
const kSharedWorkerMessageTarget = 'resources/message-target-shared-worker.js';
const kServiceWorkerMessageTarget =
  'resources/message-target-service-worker.js';
const kDedicatedWorkerMessageTarget =
  'resources/message-target-dedicated-worker.js';

function create_dedicated_worker(test, url) {
  const dedicated_worker = new Worker(url);
  test.add_cleanup(() => {
    dedicated_worker.terminate();
  });
  return dedicated_worker;
}

async function create_service_worker(test, script_url, scope) {
  const registration = await service_worker_unregister_and_register(
    test, script_url, scope);
  test.add_cleanup(() => {
    return registration.unregister();
  });
  return registration;
}

// Creates an iframe and waits to receive a message from the iframe.
// Valid |options| include src, srcdoc and sandbox, which mirror the
// corresponding iframe element properties.
async function add_iframe(test, options) {
  const iframe = document.createElement('iframe');

  if (options.sandbox !== undefined) {
    iframe.sandbox = options.sandbox;
  }

  if (options.src !== undefined) {
    iframe.src = options.src;
  }

  if (options.srcdoc !== undefined) {
    iframe.srcdoc = options.srcdoc;
  }

  document.body.appendChild(iframe);
  test.add_cleanup(() => {
    iframe.remove();
  });

  await wait_for_loaded_message(self);
  return iframe;
}

// Creates a child window using window.open() and waits to receive a message
// from the child window.
async function open_window(test, url) {
  const child_window = window.open(url);
  test.add_cleanup(() => {
    child_window.close();
  });
  await wait_for_loaded_message(self);
  return child_window;
}

// Wait until |receiver| gets a message event with the data set to 'LOADED'.
// The postMessage() tests use messaging instead of the loaded event because
// cross-origin child windows from window.open() do not dispatch the loaded
// event to the parent window.
async function wait_for_loaded_message(receiver) {
  const message_promise = new Promise((resolve, reject) => {
    receiver.addEventListener('message', message_event => {
      if (message_event.data === 'LOADED') {
        resolve();
      } else {
        reject('The message target must receive a "LOADED" message response.');
      }
    });
  });
  await message_promise;
}

// Sets up a new message channel. Sends one port to |target| and then returns
// the other port.
function create_message_channel(target, target_origin) {
  const message_channel = new MessageChannel();

  const message_data =
    { type: 'receive-message-port', message_port: message_channel.port2 };
  target.postMessage(
    message_data,
    {
      transfer: [message_channel.port2],
      targetOrigin: target_origin
    });
  message_channel.port1.start();
  return message_channel.port1;
}

// Creates a variety of different FileSystemFileHandles for testing.
async function create_file_system_handles(test, root) {
  // Create some files to use with postMessage().
  const empty_file = await createEmptyFile(test, 'empty-file', root);
  const first_file = await createFileWithContents(
    test, 'first-file-with-contents', 'first-text-content', root);
  const second_file = await createFileWithContents(
    test, 'second-file-with-contents', 'second-text-content', root);

  // Create an empty directory to use with postMessage().
  const empty_directory = await createDirectory(test, 'empty-directory', root);

  // Create a directory containing both files and subdirectories to use
  // with postMessage().
  const directory_with_files =
    await createDirectory(test, 'directory-with-files', root);
  await createFileWithContents(test, 'first-file-in-directory',
    'first-directory-text-content', directory_with_files);
  await createFileWithContents(test, 'second-file-in-directory',
    'second-directory-text-content', directory_with_files);
  const subdirectory =
    await createDirectory(test, 'subdirectory', directory_with_files);
  await createFileWithContents(test, 'first-file-in-subdirectory',
    'first-subdirectory-text-content', subdirectory);

  return [
    empty_file,
    first_file,
    second_file,
    // Include the same FileSystemFileHandle twice.
    second_file,
    empty_directory,
    // Include the Same FileSystemDirectoryHandle object twice.
    empty_directory,
    directory_with_files
  ];
}

// Tests sending an array of FileSystemHandles to |target| with postMessage().
// The array includes both FileSystemFileHandles and FileSystemDirectoryHandles.
// After receiving the message, |target| accesses all cloned handles by
// serializing the properties of each handle to a JavaScript object.
//
// |target| then responds with the resulting array of serialized handles. The
// response also includes the array of cloned handles, which creates more
// clones. After receiving the response, this test runner verifies that both
// the serialized handles and the cloned handles contain the expected properties.
async function do_post_message_test(
  test, root_dir, receiver, target, target_origin) {
  // Create and send the handles to |target|.
  const handles =
    await create_file_system_handles(test, root_dir, target, target_origin);
  target.postMessage(
    { type: 'receive-file-system-handles', cloned_handles: handles },
    { targetOrigin: target_origin });

  // Wait for |target| to respond with results.
  const event_watcher = new EventWatcher(test, receiver, 'message');
  const message_event = await event_watcher.wait_for('message');
  const response = message_event.data;

  assert_equals(response.type, 'receive-serialized-file-system-handles',
    'The test runner must receive a "serialized-file-system-handles" ' +
    `message response. Actual response: ${response}`);

  // Verify the results.
  const expected_serialized_handles = await serialize_handles(handles);

  assert_equals_serialized_handles(
    response.serialized_handles, expected_serialized_handles);

  await assert_equals_cloned_handles(response.cloned_handles, handles);
}

// Runs the same test as do_post_message_test(), but uses a MessagePort.
// This test starts by establishing a message channel between the test runner
// and |target|. Afterwards, the test sends FileSystemHandles through the
// message port channel.
async function do_message_port_test(test, root_dir, target, target_origin) {
  const message_port = create_message_channel(target, target_origin);
  await do_post_message_test(
      test, root_dir, /*receiver=*/ message_port, /*target=*/ message_port);
}
