"use strict";

const { ExtensionParent } = ChromeUtils.importESModule(
  "resource://gre/modules/ExtensionParent.sys.mjs"
);

add_task(async function testExtensionDebuggingUtilsCleanup() {
  const extension = ExtensionTestUtils.loadExtension({
    background() {
      browser.test.sendMessage("background.ready");
    },
  });

  const expectedEmptyDebugUtils = {
    hiddenXULWindow: null,
    cacheSize: 0,
  };

  let { hiddenXULWindow, debugBrowserPromises } = ExtensionParent.DebugUtils;

  deepEqual(
    { hiddenXULWindow, cacheSize: debugBrowserPromises.size },
    expectedEmptyDebugUtils,
    "No ExtensionDebugUtils resources has been allocated yet"
  );

  await extension.startup();

  await extension.awaitMessage("background.ready");

  hiddenXULWindow = ExtensionParent.DebugUtils.hiddenXULWindow;
  deepEqual(
    { hiddenXULWindow, cacheSize: debugBrowserPromises.size },
    expectedEmptyDebugUtils,
    "No debugging resources has been yet allocated once the extension is running"
  );

  const fakeAddonActor = {
    addonId: extension.id,
  };

  const anotherAddonActor = {
    addonId: extension.id,
  };

  const waitFirstBrowser =
    ExtensionParent.DebugUtils.getExtensionProcessBrowser(fakeAddonActor);
  const waitSecondBrowser =
    ExtensionParent.DebugUtils.getExtensionProcessBrowser(anotherAddonActor);

  const addonDebugBrowser = await waitFirstBrowser;
  equal(
    addonDebugBrowser.isRemoteBrowser,
    extension.extension.remote,
    "The addon debugging browser has the expected remote type"
  );

  equal(
    await waitSecondBrowser,
    addonDebugBrowser,
    "Two addon debugging actors related to the same addon get the same browser element "
  );

  equal(
    debugBrowserPromises.size,
    1,
    "The expected resources has been allocated"
  );

  const nonExistentAddonActor = {
    addonId: "non-existent-addon@test",
  };

  const waitRejection = ExtensionParent.DebugUtils.getExtensionProcessBrowser(
    nonExistentAddonActor
  );

  await Assert.rejects(
    waitRejection,
    /Extension not found/,
    "Reject with the expected message for non existent addons"
  );

  equal(
    debugBrowserPromises.size,
    1,
    "No additional debugging resources has been allocated"
  );

  await ExtensionParent.DebugUtils.releaseExtensionProcessBrowser(
    fakeAddonActor
  );

  equal(
    debugBrowserPromises.size,
    1,
    "The addon debugging browser is cached until all the related actors have released it"
  );

  await ExtensionParent.DebugUtils.releaseExtensionProcessBrowser(
    anotherAddonActor
  );

  hiddenXULWindow = ExtensionParent.DebugUtils.hiddenXULWindow;

  deepEqual(
    { hiddenXULWindow, cacheSize: debugBrowserPromises.size },
    expectedEmptyDebugUtils,
    "All the allocated debugging resources has been cleared"
  );

  await extension.unload();
});

add_task(async function testExtensionDebuggingUtilsAddonReloaded() {
  let extension = ExtensionTestUtils.loadExtension({
    manifest: {
      browser_specific_settings: {
        gecko: {
          id: "test-reloaded@test.mozilla.com",
        },
      },
    },
    background() {
      browser.test.sendMessage("background.ready");
    },
  });

  await extension.startup();
  await extension.awaitMessage("background.ready");

  let fakeAddonActor = {
    addonId: extension.id,
  };

  const addonDebugBrowser =
    await ExtensionParent.DebugUtils.getExtensionProcessBrowser(fakeAddonActor);
  equal(
    addonDebugBrowser.isRemoteBrowser,
    extension.extension.remote,
    "The addon debugging browser has the expected remote type"
  );
  equal(
    ExtensionParent.DebugUtils.debugBrowserPromises.size,
    1,
    "Got the expected number of requested debug browsers"
  );

  const { chromeDocument } = ExtensionParent.DebugUtils.hiddenXULWindow;

  Assert.strictEqual(
    addonDebugBrowser.parentElement,
    chromeDocument.documentElement,
    "The addon debugging browser is part of the hiddenXULWindow chromeDocument"
  );

  await extension.unload();

  // Install an extension with the same id to recreate for the DebugUtils
  // conditions similar to an addon reloaded while the Addon Debugger is opened.
  extension = ExtensionTestUtils.loadExtension({
    manifest: {
      browser_specific_settings: {
        gecko: {
          id: "test-reloaded@test.mozilla.com",
        },
      },
    },
    background() {
      browser.test.sendMessage("background.ready");
    },
  });

  await extension.startup();
  await extension.awaitMessage("background.ready");

  equal(
    ExtensionParent.DebugUtils.debugBrowserPromises.size,
    1,
    "Got the expected number of requested debug browsers"
  );

  const newAddonDebugBrowser =
    await ExtensionParent.DebugUtils.getExtensionProcessBrowser(fakeAddonActor);

  equal(
    addonDebugBrowser,
    newAddonDebugBrowser,
    "The existent debugging browser has been reused"
  );

  equal(
    newAddonDebugBrowser.isRemoteBrowser,
    extension.extension.remote,
    "The addon debugging browser has the expected remote type"
  );

  await ExtensionParent.DebugUtils.releaseExtensionProcessBrowser(
    fakeAddonActor
  );

  equal(
    ExtensionParent.DebugUtils.debugBrowserPromises.size,
    0,
    "All the addon debugging browsers has been released"
  );

  await extension.unload();
});

add_task(async function testExtensionDebuggingUtilsWithMultipleAddons() {
  let extension = ExtensionTestUtils.loadExtension({
    manifest: {
      browser_specific_settings: {
        gecko: {
          id: "test-addon-1@test.mozilla.com",
        },
      },
    },
    background() {
      browser.test.sendMessage("background.ready");
    },
  });
  let anotherExtension = ExtensionTestUtils.loadExtension({
    manifest: {
      browser_specific_settings: {
        gecko: {
          id: "test-addon-2@test.mozilla.com",
        },
      },
    },
    background() {
      browser.test.sendMessage("background.ready");
    },
  });

  await extension.startup();
  await extension.awaitMessage("background.ready");

  await anotherExtension.startup();
  await anotherExtension.awaitMessage("background.ready");

  const fakeAddonActor = {
    addonId: extension.id,
  };

  const anotherFakeAddonActor = {
    addonId: anotherExtension.id,
  };

  const { DebugUtils } = ExtensionParent;
  const debugBrowser =
    await DebugUtils.getExtensionProcessBrowser(fakeAddonActor);
  const anotherDebugBrowser = await DebugUtils.getExtensionProcessBrowser(
    anotherFakeAddonActor
  );

  const chromeDocument = DebugUtils.hiddenXULWindow.chromeDocument;

  equal(
    ExtensionParent.DebugUtils.debugBrowserPromises.size,
    2,
    "Got the expected number of debug browsers requested"
  );
  Assert.strictEqual(
    debugBrowser.parentElement,
    chromeDocument.documentElement,
    "The first debug browser is part of the hiddenXUL chromeDocument"
  );
  Assert.strictEqual(
    anotherDebugBrowser.parentElement,
    chromeDocument.documentElement,
    "The second debug browser is part of the hiddenXUL chromeDocument"
  );

  await ExtensionParent.DebugUtils.releaseExtensionProcessBrowser(
    fakeAddonActor
  );

  equal(
    ExtensionParent.DebugUtils.debugBrowserPromises.size,
    1,
    "Got the expected number of debug browsers requested"
  );

  Assert.strictEqual(
    anotherDebugBrowser.parentElement,
    chromeDocument.documentElement,
    "The second debug browser is still part of the hiddenXUL chromeDocument"
  );

  Assert.equal(
    debugBrowser.parentElement,
    null,
    "The first debug browser has been removed from the hiddenXUL chromeDocument"
  );

  await ExtensionParent.DebugUtils.releaseExtensionProcessBrowser(
    anotherFakeAddonActor
  );

  Assert.equal(
    anotherDebugBrowser.parentElement,
    null,
    "The second debug browser has been removed from the hiddenXUL chromeDocument"
  );
  equal(
    ExtensionParent.DebugUtils.debugBrowserPromises.size,
    0,
    "All the addon debugging browsers has been released"
  );

  await extension.unload();
  await anotherExtension.unload();
});
