/**
 * When "privacy.resistFingerprinting" is set to true, user permission is
 * required for canvas data extraction.
 * This tests whether the site permission prompt for canvas data extraction
 * works properly.
 * Canvas data extraction should result in random data.
 */
"use strict";

const kUrl = "https://example.com/";
const kPrincipal = Services.scriptSecurityManager.createContentPrincipal(
  Services.io.newURI(kUrl),
  {}
);
const kPermission = "canvas";

function initTab() {
  let contentWindow = content.wrappedJSObject;

  let drawCanvas = (fillStyle, id) => {
    let contentDocument = contentWindow.document;
    let width = 64,
      height = 64;
    let canvas = contentDocument.createElement("canvas");
    if (id) {
      canvas.setAttribute("id", id);
    }
    canvas.setAttribute("width", width);
    canvas.setAttribute("height", height);
    contentDocument.body.appendChild(canvas);

    let context = canvas.getContext("2d");
    context.fillStyle = fillStyle;
    context.fillRect(0, 0, width, height);

    if (id) {
      let button = contentDocument.createElement("button");
      button.addEventListener("click", function () {
        canvas.toDataURL();
      });
      button.setAttribute("id", "clickme");
      button.innerHTML = "Click Me!";
      contentDocument.body.appendChild(button);
    }

    return canvas;
  };

  let canvas = drawCanvas("cyan", "canvas-id-canvas");
  contentWindow.kPlacedData = canvas.toDataURL();
  is(
    canvas.toDataURL(),
    contentWindow.kPlacedData,
    "privacy.resistFingerprinting = false, canvas data == placed data"
  );
}

function enableResistFingerprinting(autoDeclineNoInput) {
  return SpecialPowers.pushPrefEnv({
    set: [
      ["privacy.fingerprintingProtection", true],
      [
        "privacy.fingerprintingProtection.overrides",
        "+CanvasRandomization,+CanvasImageExtractionPrompt,+CanvasExtractionFromThirdPartiesIsBlocked" +
          (autoDeclineNoInput
            ? ",+CanvasExtractionBeforeUserInputIsBlocked"
            : ""),
      ],
    ],
  });
}

function promisePopupShown() {
  return BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
}

function promisePopupHidden() {
  return BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden");
}

function extractCanvasData(grantPermission) {
  let contentWindow = content.wrappedJSObject;
  let canvas = contentWindow.document.getElementById("canvas-id-canvas");
  let canvasData = canvas.toDataURL();
  if (grantPermission) {
    is(
      canvasData,
      contentWindow.kPlacedData,
      "privacy.resistFingerprinting = true, permission granted, canvas data == placed data"
    );
  } else if (grantPermission === false) {
    isnot(
      canvasData,
      contentWindow.kPlacedData,
      "privacy.resistFingerprinting = true, permission denied, canvas data != placed data"
    );
  } else {
    isnot(
      canvasData,
      contentWindow.kPlacedData,
      "privacy.resistFingerprinting = true, requesting permission, canvas data != placed data"
    );
  }
}

function triggerCommand(button) {
  let notifications = PopupNotifications.panel.children;
  let notification = notifications[0];
  EventUtils.synthesizeMouseAtCenter(notification[button], {});
}

function triggerMainCommand() {
  triggerCommand("button");
}

function triggerSecondaryCommand() {
  triggerCommand("secondaryButton");
}

function testPermission() {
  return Services.perms.testPermissionFromPrincipal(kPrincipal, kPermission);
}

async function withNewTabNoInput(grantPermission, browser) {
  await SpecialPowers.spawn(browser, [], initTab);
  await enableResistFingerprinting(false);
  let popupShown = promisePopupShown();
  await SpecialPowers.spawn(browser, [], extractCanvasData);
  await popupShown;
  let popupHidden = promisePopupHidden();
  if (grantPermission) {
    triggerMainCommand();
    await popupHidden;
    is(testPermission(), Services.perms.ALLOW_ACTION, "permission granted");
  } else {
    triggerSecondaryCommand();
    await popupHidden;
    is(testPermission(), Services.perms.DENY_ACTION, "permission denied");
  }
  await SpecialPowers.spawn(browser, [grantPermission], extractCanvasData);
  await SpecialPowers.popPrefEnv();
}

async function doTestNoInput(grantPermission) {
  await BrowserTestUtils.withNewTab(
    kUrl,
    withNewTabNoInput.bind(null, grantPermission)
  );
  Services.perms.removeFromPrincipal(kPrincipal, kPermission);
}

// With auto-declining disabled (not the default)
// Tests clicking "Don't Allow" button of the permission prompt.
add_task(doTestNoInput.bind(null, false));

// Tests clicking "Allow" button of the permission prompt.
add_task(doTestNoInput.bind(null, true));

async function withNewTabAutoBlockNoInput(browser) {
  await SpecialPowers.spawn(browser, [], initTab);
  await enableResistFingerprinting(true);

  let noShowHandler = () => {
    ok(false, "The popup notification should not show in this case.");
  };
  PopupNotifications.panel.addEventListener("popupshown", noShowHandler, {
    once: true,
  });

  let promisePopupObserver = TestUtils.topicObserved(
    "PopupNotifications-updateNotShowing"
  );

  // Try to extract canvas data without user inputs.
  await SpecialPowers.spawn(browser, [], extractCanvasData);

  await promisePopupObserver;
  info("There should be no popup shown on the panel.");

  // Check that the icon of canvas permission is shown.
  let canvasNotification = PopupNotifications.getNotification(
    "canvas-permissions-prompt",
    browser
  );

  is(
    canvasNotification.anchorElement.getAttribute("showing"),
    "true",
    "The canvas permission icon is correctly shown."
  );
  PopupNotifications.panel.removeEventListener("popupshown", noShowHandler);

  await SpecialPowers.popPrefEnv();
}

async function doTestAutoBlockNoInput() {
  await BrowserTestUtils.withNewTab(
    kUrl,
    withNewTabAutoBlockNoInput.bind(null)
  );
}

add_task(doTestAutoBlockNoInput.bind(null));

function extractCanvasDataUserInput(grantPermission) {
  let contentWindow = content.wrappedJSObject;
  let canvas = contentWindow.document.getElementById("canvas-id-canvas");
  let canvasData = canvas.toDataURL();
  if (grantPermission) {
    is(
      canvasData,
      contentWindow.kPlacedData,
      "privacy.resistFingerprinting = true, permission granted, canvas data == placed data"
    );
  } else if (grantPermission === false) {
    isnot(
      canvasData,
      contentWindow.kPlacedData,
      "privacy.resistFingerprinting = true, permission denied, canvas data != placed data"
    );
  } else {
    isnot(
      canvasData,
      contentWindow.kPlacedData,
      "privacy.resistFingerprinting = true, requesting permission, canvas data != placed data"
    );
  }
}

async function withNewTabInput(grantPermission, browser) {
  await SpecialPowers.spawn(browser, [], initTab);
  await enableResistFingerprinting(true);
  let popupShown = promisePopupShown();
  await SpecialPowers.spawn(browser, [], function () {
    E10SUtils.wrapHandlingUserInput(content, true, function () {
      var button = content.document.getElementById("clickme");
      button.click();
    });
  });
  await popupShown;
  let popupHidden = promisePopupHidden();
  if (grantPermission) {
    triggerMainCommand();
    await popupHidden;
    is(testPermission(), Services.perms.ALLOW_ACTION, "permission granted");
  } else {
    triggerSecondaryCommand();
    await popupHidden;
    is(testPermission(), Services.perms.DENY_ACTION, "permission denied");
  }
  await SpecialPowers.spawn(
    browser,
    [grantPermission],
    extractCanvasDataUserInput
  );
  await SpecialPowers.popPrefEnv();
}

async function doTestInput(grantPermission) {
  await BrowserTestUtils.withNewTab(
    kUrl,
    withNewTabInput.bind(null, grantPermission)
  );
  Services.perms.removeFromPrincipal(kPrincipal, kPermission);
}

// With auto-declining enabled (the default)
// Tests clicking "Don't Allow" button of the permission prompt.
add_task(doTestInput.bind(null, false));

// Tests clicking "Allow" button of the permission prompt.
add_task(doTestInput.bind(null, true));
