<!DOCTYPE HTML>
<html lang="en">
<head>
  <meta charset="utf8">
  <title>Test for the Console API and Service Workers</title>
  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
  <script type="text/javascript" src="common.js"></script>
  <!-- Any copyright is dedicated to the Public Domain.
     - http://creativecommons.org/publicdomain/zero/1.0/ -->
</head>
<body>
<p>Test for the Console API and Service Workers</p>

<script class="testbody" type="text/javascript">
"use strict";

SimpleTest.waitForExplicitFinish();

// Utils functions
function withFrame(url) {
  return new Promise(resolve => {
    const iframe = document.createElement("iframe");
    iframe.onload = function () {
      resolve(iframe);
    };
    iframe.src = url;
    document.body.appendChild(iframe);
  });
}

function navigateFrame(iframe, url) {
  return new Promise(resolve => {
    iframe.onload = function () {
      resolve(iframe);
    };
    iframe.src = url;
  });
}

function forceReloadFrame(iframe) {
  return new Promise(resolve => {
    iframe.onload = function () {
      resolve(iframe);
    };
    iframe.contentWindow.location.reload(true);
  });
}

function messageServiceWorker(win, scope, message) {
  return win.navigator.serviceWorker.getRegistration(scope).then(swr => {
    return new Promise(resolve => {
      win.navigator.serviceWorker.onmessage = evt => {
        resolve();
      };
      const sw = swr.active || swr.waiting || swr.installing;
      sw.postMessage({ type: "PING", message });
    });
  });
}

function unregisterServiceWorker(win) {
  return win.navigator.serviceWorker.ready.then(swr => {
    return swr.unregister();
  });
}

// Test
const BASE_URL = "https://example.com/chrome/devtools/shared/webconsole/test/chrome/";
const SERVICE_WORKER_URL = BASE_URL + "helper_serviceworker.js";
const SCOPE = BASE_URL + "foo/";
const NONSCOPE_FRAME_URL = BASE_URL + "sandboxed_iframe.html";
const SCOPE_FRAME_URL = SCOPE + "fake.html";
const SCOPE_FRAME_URL2 = SCOPE + "whatsit.html";
const MESSAGE = 'Tic Tock';

const expectedConsoleCalls = [
    {
      level: "log",
      filename: /helper_serviceworker/,
      arguments: ['script evaluation'],
    },
    {
      level: "log",
      filename: /helper_serviceworker/,
      arguments: ['install event'],
    },
    {
      level: "log",
      filename: /helper_serviceworker/,
      arguments: ['activate event'],
    },
    {
      level: "log",
      filename: /helper_serviceworker/,
      arguments: ['fetch event: ' + SCOPE_FRAME_URL],
    },
    {
      level: "log",
      filename: /helper_serviceworker/,
      arguments: ['fetch event: ' + SCOPE_FRAME_URL2],
    },
];
let consoleCalls = [];

const startTest = async function () {
  removeEventListener("load", startTest);

  await new Promise(resolve => {
    SpecialPowers.pushPrefEnv({"set": [
      ["dom.serviceWorkers.enabled", true],
      ["devtools.webconsole.filter.serviceworkers", true]
    ]}, resolve);
  });

  const {state, response} = await attachConsoleToTab(["ConsoleAPI"]);
  onAttach(state, response);
};
addEventListener("load", startTest);

const onAttach = async function (state, response) {
  onConsoleAPICall = onConsoleAPICall.bind(null, state);
  state.webConsoleFront.on("consoleAPICall", onConsoleAPICall);

  let currentFrame;
  try {
    // First, we need a frame from which to register our script.  This
    // will not trigger any console calls.
    info("Loading a non-scope frame from which to register a service worker.");
    currentFrame = await withFrame(NONSCOPE_FRAME_URL);

    // Now register the service worker and wait for it to become
    // activate.  This should trigger 3 console calls; 1 for script
    // evaluation, 1 for the install event, and 1 for the activate
    // event.  These console calls are received because we called
    // register(), not because we are in scope for the worker.
    info("Registering the service worker");
    await withActiveServiceWorker(currentFrame.contentWindow,
                                  SERVICE_WORKER_URL, SCOPE);
    ok(!currentFrame.contentWindow.navigator.serviceWorker.controller,
       'current frame should not be controlled');

    // Now that the service worker is activate, lets navigate our frame.
    // This will trigger 1 more console call for the fetch event.
    info("Service worker registered. Navigating frame.");
    await navigateFrame(currentFrame, SCOPE_FRAME_URL);
    ok(currentFrame.contentWindow.navigator.serviceWorker.controller,
       'navigated frame should be controlled');

    // We now have a controlled frame.  Lets perform a non-navigation fetch.
    // This should produce another console call for the fetch event.
    info("Frame navigated.  Calling fetch().");
    await currentFrame.contentWindow.fetch(SCOPE_FRAME_URL2);

    // Now force refresh our controlled frame.  This will cause the frame
    // to bypass the service worker and become an uncontrolled frame.  It
    // also happens to make the frame display a 404 message because the URL
    // does not resolve to a real resource.  This is ok, as we really only
    // care about the frame being non-controlled, but still having a location
    // that matches our service worker scope so we can provide its not
    // incorrectly getting console calls.
    info("Completed fetch().  Force refreshing to get uncontrolled frame.");
    await forceReloadFrame(currentFrame);
    ok(!currentFrame.contentWindow.navigator.serviceWorker.controller,
       'current frame should not be controlled after force refresh');
    is(currentFrame.contentWindow.location.toString(), SCOPE_FRAME_URL,
       'current frame should still have in-scope location URL even though it got 404');

    // Now postMessage() the service worker to trigger its message event
    // handler.  This will generate 1 or 2 to console.log() statements
    // depending on if the worker thread needs to spin up again.  In either
    // case, though, we should not get any console calls because we don't
    // have a controlled or registering document.
    info("Completed force refresh.  Messaging service worker.");
    await messageServiceWorker(currentFrame.contentWindow, SCOPE, MESSAGE);

    info("Done messaging service worker.  Unregistering service worker.");
    await unregisterServiceWorker(currentFrame.contentWindow);

    info('Service worker unregistered.  Checking console calls.');
    state.webConsoleFront.off("consoleAPICall", onConsoleAPICall);
    checkConsoleAPICalls(consoleCalls, expectedConsoleCalls);
  } catch(error) {
    ok(false, 'unexpected error: ' + error);
  } finally {
    if (currentFrame) {
      currentFrame.remove();
      currentFrame = null;
    }
    consoleCalls = [];
    closeDebugger(state, function() {
      SimpleTest.finish();
    });
  }
};

function onConsoleAPICall(state, packet) {
  info("received message level: " + packet.message.level);
  consoleCalls.push(packet.message);
}
</script>
</body>
</html>
