/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
const { HttpServer } = ChromeUtils.importESModule(
  "resource://testing-common/httpd.sys.mjs"
);

do_get_profile();
const server = new HttpServer();
server.registerDirectory("/", do_get_cwd());
server.start(-1);
const ROOT = `http://localhost:${server.identity.primaryPort}`;
const BASE = `${ROOT}/`;
const HEADLESS_URL = Services.io.newURI(`${BASE}/headless.html`);
const HEADLESS_BUTTON_URL = Services.io.newURI(`${BASE}/headless_button.html`);
registerCleanupFunction(() => {
  server.stop(() => {});
});

// Refrences to the progress listeners to keep them from being gc'ed
// before they are called.
const progressListeners = new Map();

function loadContentWindow(windowlessBrowser, uri) {
  return new Promise(resolve => {
    let loadURIOptions = {
      triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
    };
    windowlessBrowser.loadURI(uri, loadURIOptions);
    let docShell = windowlessBrowser.docShell;
    let webProgress = docShell
      .QueryInterface(Ci.nsIInterfaceRequestor)
      .getInterface(Ci.nsIWebProgress);
    let progressListener = {
      onLocationChange(progress, request, location, flags) {
        // Ignore inner-frame events
        if (progress != webProgress) {
          return;
        }
        // Ignore events that don't change the document
        if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
          return;
        }
        let contentWindow = docShell.domWindow;
        webProgress.removeProgressListener(progressListener);
        progressListeners.delete(progressListener);
        contentWindow.addEventListener(
          "load",
          () => {
            resolve(contentWindow);
          },
          { once: true }
        );
      },
      QueryInterface: ChromeUtils.generateQI([
        "nsIWebProgressListener",
        "nsISupportsWeakReference",
      ]),
    };
    progressListeners.set(progressListener, progressListener);
    webProgress.addProgressListener(
      progressListener,
      Ci.nsIWebProgress.NOTIFY_LOCATION
    );
  });
}

add_setup(function () {
  Services.prefs.setBoolPref("security.allow_unsafe_parent_loads", true);
});

registerCleanupFunction(function () {
  Services.prefs.clearUserPref("security.allow_unsafe_parent_loads");
});

add_task(async function test_snapshot() {
  let windowlessBrowser = Services.appShell.createWindowlessBrowser(false);
  let contentWindow = await loadContentWindow(windowlessBrowser, HEADLESS_URL);
  const contentWidth = 400;
  const contentHeight = 300;
  // Verify dimensions.
  contentWindow.resizeTo(contentWidth, contentHeight);
  equal(contentWindow.innerWidth, contentWidth);
  equal(contentWindow.innerHeight, contentHeight);

  // Snapshot the test page.
  let canvas = contentWindow.document.createElementNS(
    "http://www.w3.org/1999/xhtml",
    "html:canvas"
  );
  let context = canvas.getContext("2d");
  let width = contentWindow.innerWidth;
  let height = contentWindow.innerHeight;
  canvas.width = width;
  canvas.height = height;
  context.drawWindow(contentWindow, 0, 0, width, height, "rgb(255, 255, 255)");
  let imageData = context.getImageData(0, 0, width, height).data;
  ok(
    imageData[0] === 0 &&
      imageData[1] === 255 &&
      imageData[2] === 0 &&
      imageData[3] === 255,
    "Page is green."
  );

  // Search for a blue pixel (a quick and dirty check to see if the blue text is
  // on the page)
  let found = false;
  for (let i = 0; i < imageData.length; i += 4) {
    if (imageData[i + 2] === 255) {
      found = true;
      break;
    }
  }
  ok(found, "Found blue text on page.");

  windowlessBrowser.close();
});

add_task(async function test_snapshot_widget_layers() {
  let windowlessBrowser = Services.appShell.createWindowlessBrowser(false);
  // nsIWindowlessBrowser inherits from nsIWebNavigation.
  let contentWindow = await loadContentWindow(windowlessBrowser, HEADLESS_URL);
  const contentWidth = 1;
  const contentHeight = 2;
  // Verify dimensions.
  contentWindow.resizeTo(contentWidth, contentHeight);
  equal(contentWindow.innerWidth, contentWidth);
  equal(contentWindow.innerHeight, contentHeight);

  // Snapshot the test page.
  let canvas = contentWindow.document.createElementNS(
    "http://www.w3.org/1999/xhtml",
    "html:canvas"
  );
  let context = canvas.getContext("2d");
  let width = contentWindow.innerWidth;
  let height = contentWindow.innerHeight;
  canvas.width = width;
  canvas.height = height;
  context.drawWindow(
    contentWindow,
    0,
    0,
    width,
    height,
    "rgb(255, 255, 255)",
    context.DRAWWINDOW_DRAW_CARET |
      context.DRAWWINDOW_DRAW_VIEW |
      context.DRAWWINDOW_USE_WIDGET_LAYERS
  );
  ok(true, "Snapshot with widget layers didn't crash.");

  windowlessBrowser.close();
});

// Ensure keydown events are triggered on the windowless browser.
add_task(async function test_keydown() {
  let windowlessBrowser = Services.appShell.createWindowlessBrowser(false);
  // nsIWindowlessBrowser inherits from nsIWebNavigation.
  let contentWindow = await loadContentWindow(windowlessBrowser, HEADLESS_URL);

  let keydown = new Promise(resolve => {
    contentWindow.addEventListener(
      "keydown",
      () => {
        resolve();
      },
      { once: true }
    );
  });

  let tip = Cc["@mozilla.org/text-input-processor;1"].createInstance(
    Ci.nsITextInputProcessor
  );
  let begun = tip.beginInputTransactionForTests(contentWindow);
  ok(
    begun,
    "nsITextInputProcessor.beginInputTransactionForTests() should succeed"
  );
  tip.keydown(
    new contentWindow.KeyboardEvent("", {
      key: "a",
      code: "KeyA",
      keyCode: contentWindow.KeyboardEvent.DOM_VK_A,
    })
  );

  await keydown;
  ok(true, "Send keydown didn't crash");

  windowlessBrowser.close();
});

// Test dragging the mouse on a button to ensure the creation of the drag
// service doesn't crash in headless.
add_task(async function test_mouse_drag() {
  let windowlessBrowser = Services.appShell.createWindowlessBrowser(false);
  // nsIWindowlessBrowser inherits from nsIWebNavigation.
  let contentWindow = await loadContentWindow(
    windowlessBrowser,
    HEADLESS_BUTTON_URL
  );
  contentWindow.resizeTo(400, 400);

  let target = contentWindow.document.getElementById("btn");
  let rect = target.getBoundingClientRect();
  let left = rect.left;
  let top = rect.top;

  let utils = contentWindow.windowUtils;
  utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
  utils.sendMouseEvent("mousemove", left, top, 0, 1, 0, false, 0, 0);
  // Wait for a turn of the event loop since the synthetic mouse event
  // that creates the drag service is processed during the refresh driver.
  await new Promise(r => {
    executeSoon(r);
  });
  utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);

  ok(true, "Send mouse event didn't crash");

  windowlessBrowser.close();
});
