/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

Services.scriptloader.loadSubScript(
  "chrome://mochitests/content/browser/toolkit/components/printing/tests/head.js",
  this
);

const PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"].getService(
  Ci.nsIPrintSettingsService
);

let mockCA = makeMockContentAnalysis();

add_setup(async function test_setup() {
  mockCA = await mockContentAnalysisService(mockCA);
});

const TEST_PAGE_URL =
  "https://example.com/browser/toolkit/components/printing/tests/simplifyArticleSample.html";
const TEST_PAGE_URL_2 =
  "https://example.com/browser/toolkit/components/printing/tests/longerArticle.html";

function addUniqueSuffix(prefix) {
  return `${prefix}-${Services.uuid
    .generateUUID()
    .toString()
    .slice(1, -1)}.pdf`;
}

async function printToDestination(aBrowser, aDestination) {
  let tmpDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
  let fileName = addUniqueSuffix(`printDestinationTest-${aDestination}`);
  let filePath = PathUtils.join(tmpDir.path, fileName);

  info(`Printing to ${filePath}`);

  let settings = PSSVC.createNewPrintSettings();
  settings.outputFormat = Ci.nsIPrintSettings.kOutputFormatPDF;
  settings.outputDestination = aDestination;

  settings.headerStrCenter = "";
  settings.headerStrLeft = "";
  settings.headerStrRight = "";
  settings.footerStrCenter = "";
  settings.footerStrLeft = "";
  settings.footerStrRight = "";

  settings.unwriteableMarginTop = 1; /* Just to ensure settings are respected on both */
  let outStream = null;
  if (aDestination == Ci.nsIPrintSettings.kOutputDestinationFile) {
    settings.toFileName = PathUtils.join(tmpDir.path, fileName);
  } else {
    is(aDestination, Ci.nsIPrintSettings.kOutputDestinationStream);
    outStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
      Ci.nsIFileOutputStream
    );
    let tmpFile = tmpDir.clone();
    tmpFile.append(fileName);
    outStream.init(tmpFile, -1, 0o666, 0);
    settings.outputStream = outStream;
  }

  await aBrowser.browsingContext.print(settings);

  return filePath;
}

function assertContentAnalysisRequest(request, expectedUrl) {
  is(request.url.spec, expectedUrl ?? TEST_PAGE_URL, "request has correct URL");
  is(
    request.analysisType,
    Ci.nsIContentAnalysisRequest.ePrint,
    "request has print analysisType"
  );
  is(
    request.reason,
    Ci.nsIContentAnalysisRequest.ePrintPreviewPrint,
    "request has correct reason"
  );
  is(
    request.operationTypeForDisplay,
    Ci.nsIContentAnalysisRequest.eOperationPrint,
    "request has print operationTypeForDisplay"
  );
  is(request.textContent, "", "request textContent should be empty");
  is(request.filePath, "", "request filePath should be empty");
  isnot(request.getPrintData().length, 0, "request should have print data");
  ok(!!request.requestToken.length, "request requestToken should not be empty");
}

// Printing to a stream is different than going through the print preview dialog because it
// doesn't make a static clone of the document before the print, which causes the
// Content Analysis code to go through a different code path. This is similar to what
// happens when various preferences are set to skip the print preview dialog, for example
// print.prefer_system_dialog.
add_task(
  async function testPrintToStreamWithContentAnalysisActiveAndAllowing() {
    await PrintHelper.withTestPage(
      async helper => {
        mockCA.setupForTest(true);

        let filePath = await printToDestination(
          helper.sourceBrowser,
          Ci.nsIPrintSettings.kOutputDestinationFile
        );
        is(
          mockCA.calls.length,
          1,
          "Correct number of calls to Content Analysis"
        );
        assertContentAnalysisRequest(mockCA.calls[0]);

        await waitForFileToAlmostMatchSize(
          filePath,
          mockCA.calls[0].getPrintData().length
        );

        await IOUtils.remove(filePath);
      },
      TEST_PAGE_URL,
      true
    );
  }
);

add_task(
  async function testPrintToStreamAfterNavigationWithContentAnalysisActiveAndAllowing() {
    await PrintHelper.withTestPage(
      async helper => {
        mockCA.setupForTest(true);

        let filePath = await printToDestination(
          helper.sourceBrowser,
          Ci.nsIPrintSettings.kOutputDestinationFile
        );
        is(
          mockCA.calls.length,
          1,
          "Correct number of calls to Content Analysis"
        );
        assertContentAnalysisRequest(mockCA.calls[0]);
        mockCA.clearCalls();

        await IOUtils.remove(filePath);

        BrowserTestUtils.startLoadingURIString(
          helper.sourceBrowser,
          TEST_PAGE_URL_2
        );
        await BrowserTestUtils.browserLoaded(helper.sourceBrowser);

        filePath = await printToDestination(
          helper.sourceBrowser,
          Ci.nsIPrintSettings.kOutputDestinationFile
        );
        is(
          mockCA.calls.length,
          1,
          "Correct number of calls to Content Analysis"
        );
        assertContentAnalysisRequest(mockCA.calls[0], TEST_PAGE_URL_2);
        await waitForFileToAlmostMatchSize(
          filePath,
          mockCA.calls[0].getPrintData().length
        );
      },
      TEST_PAGE_URL,
      true
    );
  }
);

add_task(
  async function testPrintToStreamWithContentAnalysisActiveAndBlocking() {
    await PrintHelper.withTestPage(
      async helper => {
        mockCA.setupForTest(false);

        try {
          await printToDestination(
            helper.sourceBrowser,
            Ci.nsIPrintSettings.kOutputDestinationFile
          );
          ok(false, "Content analysis should make this fail to print");
        } catch (e) {
          ok(
            /NS_ERROR_CONTENT_BLOCKED/.test(e.toString()),
            "Got content blocked error"
          );
        }
        is(
          mockCA.calls.length,
          1,
          "Correct number of calls to Content Analysis"
        );
        assertContentAnalysisRequest(mockCA.calls[0]);
      },
      TEST_PAGE_URL,
      true
    );
  }
);

add_task(
  async function testPrintToStreamWithContentAnalysisActiveAndBlockingButPrefOff() {
    await SpecialPowers.pushPrefEnv({
      set: [
        ["browser.contentanalysis.interception_point.print.enabled", false],
      ],
    });
    await PrintHelper.withTestPage(
      async helper => {
        mockCA.setupForTest(false);

        let filePath = await printToDestination(
          helper.sourceBrowser,
          Ci.nsIPrintSettings.kOutputDestinationFile
        );
        is(mockCA.calls.length, 0, "Should be no calls to Content Analysis");

        await IOUtils.remove(filePath);
      },
      TEST_PAGE_URL,
      true
    );
    await SpecialPowers.popPrefEnv();
  }
);

add_task(async function testPrintToStreamWithContentAnalysisReturningError() {
  await PrintHelper.withTestPage(
    async helper => {
      expectUncaughtException();
      mockCA.setupForTestWithError(Cr.NS_ERROR_NOT_AVAILABLE);

      try {
        await printToDestination(
          helper.sourceBrowser,
          Ci.nsIPrintSettings.kOutputDestinationFile
        );
        ok(false, "Content analysis should make this fail to print");
      } catch (e) {
        ok(
          /NS_ERROR_NOT_AVAILABLE/.test(e.toString()),
          "Error in mock CA was propagated out"
        );
      }
      is(mockCA.calls.length, 1, "Correct number of calls to Content Analysis");
      assertContentAnalysisRequest(mockCA.calls[0]);
    },
    TEST_PAGE_URL,
    true
  );
});

add_task(async function testPrintThroughDialogWithContentAnalysisActive() {
  await PrintHelper.withTestPage(
    async helper => {
      mockCA.setupForTest(true);

      await helper.startPrint();
      let fileName = addUniqueSuffix(`printDialogTest`);
      let file = helper.mockFilePicker(fileName);
      info(`Printing to ${file.path}`);
      await helper.assertPrintToFile(file, () => {
        EventUtils.sendKey("return", helper.win);
      });

      is(mockCA.calls.length, 1, "Correct number of calls to Content Analysis");
      assertContentAnalysisRequest(mockCA.calls[0]);

      await waitForFileToAlmostMatchSize(
        file.path,
        mockCA.calls[0].getPrintData().length
      );
    },
    TEST_PAGE_URL,
    true
  );
});

add_task(
  async function testPrintThroughDialogWithContentAnalysisActiveAndBlocking() {
    await PrintHelper.withTestPage(
      async helper => {
        mockCA.setupForTest(false);

        await helper.startPrint();
        let fileName = addUniqueSuffix(`printDialogTest`);
        let file = helper.mockFilePicker(fileName);
        info(`Printing to ${file.path}`);
        try {
          await helper.assertPrintToFile(file, () => {
            EventUtils.sendKey("return", helper.win);
          });
        } catch (e) {
          ok(
            /Wait for target file to get created/.test(e.toString()),
            "Target file should not get created"
          );
        }
        ok(!file.exists(), "File should not exist");

        is(
          mockCA.calls.length,
          1,
          "Correct number of calls to Content Analysis"
        );
        assertContentAnalysisRequest(mockCA.calls[0]);
      },
      TEST_PAGE_URL,
      true
    );
  }
);

add_task(
  async function testPrintThroughDialogWithContentAnalysisActiveAndBlockingButPrefOff() {
    await SpecialPowers.pushPrefEnv({
      set: [
        ["browser.contentanalysis.interception_point.print.enabled", false],
      ],
    });
    await PrintHelper.withTestPage(
      async helper => {
        mockCA.setupForTest(false);

        await helper.startPrint();
        let fileName = addUniqueSuffix(`printDialogTest`);
        let file = helper.mockFilePicker(fileName);
        info(`Printing to ${file.path}`);
        await helper.assertPrintToFile(file, () => {
          EventUtils.sendKey("return", helper.win);
        });

        is(mockCA.calls.length, 0, "Should be no calls to Content Analysis");

        await IOUtils.remove(file.path);
      },
      TEST_PAGE_URL,
      true
    );
    await SpecialPowers.popPrefEnv();
  }
);

add_task(
  async function testPrintThroughDialogWithContentAnalysisReturningError() {
    await PrintHelper.withTestPage(
      async helper => {
        expectUncaughtException();
        mockCA.setupForTestWithError(Cr.NS_ERROR_NOT_AVAILABLE);

        await helper.startPrint();
        let fileName = addUniqueSuffix(`printDialogTest`);
        let file = helper.mockFilePicker(fileName);
        info(`Printing to ${file.path}`);
        try {
          await helper.assertPrintToFile(file, () => {
            EventUtils.sendKey("return", helper.win);
          });
        } catch (e) {
          ok(
            /Wait for target file to get created/.test(e.toString()),
            "Target file should not get created"
          );
        }
        ok(!file.exists(), "File should not exist");

        is(
          mockCA.calls.length,
          1,
          "Correct number of calls to Content Analysis"
        );
        assertContentAnalysisRequest(mockCA.calls[0]);
      },
      TEST_PAGE_URL,
      true
    );
  }
);
