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

/* eslint max-nested-callbacks: ["warn", 12] */

/**
 * Tests that history navigation works for the add-ons manager.
 */

// Request a longer timeout, because this tests run twice
// (once on XUL views and once on the HTML views).
requestLongerTimeout(4);

const { AddonTestUtils } = ChromeUtils.importESModule(
  "resource://testing-common/AddonTestUtils.sys.mjs"
);

AddonTestUtils.initMochitest(this);

const DISCOAPI_URL = `http://example.com/${RELATIVE_DIR}/discovery/api_response_empty.json`;

SpecialPowers.pushPrefEnv({
  set: [["browser.navigation.requireUserInteraction", false]],
});

var gProvider = new MockProvider();
gProvider.createAddons([
  {
    id: "test1@tests.mozilla.org",
    name: "Test add-on 1",
    description: "foo",
  },
  {
    id: "test2@tests.mozilla.org",
    name: "Test add-on 2",
    description: "bar",
  },
  {
    id: "test3@tests.mozilla.org",
    name: "Test add-on 3",
    type: "theme",
    description: "bar",
  },
]);

function go_back() {
  gBrowser.goBack();
}

const goBackKeyModifier =
  AppConstants.platform == "macosx" ? { metaKey: true } : { altKey: true };

function go_back_key() {
  EventUtils.synthesizeKey("KEY_ArrowLeft", goBackKeyModifier);
}

function go_forward_key() {
  EventUtils.synthesizeKey("KEY_ArrowRight", goBackKeyModifier);
}

function go_forward() {
  gBrowser.goForward();
}

function check_state(canGoBack, canGoForward) {
  is(gBrowser.canGoBack, canGoBack, "canGoBack should be correct");
  is(gBrowser.canGoForward, canGoForward, "canGoForward should be correct");
}

function is_in_list(aManager, view, canGoBack, canGoForward) {
  var categoryUtils = new CategoryUtilities(aManager);

  is(
    categoryUtils.getSelectedViewId(),
    view,
    "Should be on the right category"
  );

  ok(
    aManager.document.querySelector("addon-list"),
    "Got a list-view in about:addons"
  );

  check_state(canGoBack, canGoForward);
}

function is_in_detail(aManager, view, canGoBack, canGoForward) {
  var categoryUtils = new CategoryUtilities(aManager);

  is(
    categoryUtils.getSelectedViewId(),
    view,
    "Should be on the right category"
  );

  is(
    aManager.document.querySelectorAll("addon-card").length,
    1,
    "Got a detail-view in about:addons"
  );

  check_state(canGoBack, canGoForward);
}

function is_in_discovery(aManager, canGoBack, canGoForward) {
  ok(
    aManager.document.querySelector("discovery-pane"),
    "Got a discovery panel in the HTML about:addons browser"
  );

  check_state(canGoBack, canGoForward);
}

async function expand_addon_element(aManagerWin, aId) {
  var addon = getAddonCard(aManagerWin, aId);
  // Ensure that we send a click on the control that is accessible (while a
  // mouse user could also activate a card by clicking on the entire container):
  const addonLink = addon.querySelector(".addon-name-link");
  addonLink.click();
}

function wait_for_page_load(browser) {
  return BrowserTestUtils.browserLoaded(browser, false, "http://example.com/");
}

// Tests simple forward and back navigation and that the right heading and
// category is selected
add_task(async function test_navigate_history() {
  let aManager = await open_manager("addons://list/extension");
  let categoryUtils = new CategoryUtilities(aManager);
  info("Part 1");
  is_in_list(aManager, "addons://list/extension", false, false);

  EventUtils.synthesizeMouseAtCenter(categoryUtils.get("plugin"), {}, aManager);

  aManager = await wait_for_view_load(aManager);
  info("Part 2");
  is_in_list(aManager, "addons://list/plugin", true, false);

  go_back();

  aManager = await wait_for_view_load(aManager);
  info("Part 3");
  is_in_list(aManager, "addons://list/extension", false, true);

  go_forward();

  aManager = await wait_for_view_load(aManager);
  info("Part 4");
  is_in_list(aManager, "addons://list/plugin", true, false);

  go_back();

  aManager = await wait_for_view_load(aManager);
  info("Part 5");
  is_in_list(aManager, "addons://list/extension", false, true);

  await expand_addon_element(aManager, "test1@tests.mozilla.org");

  aManager = await wait_for_view_load(aManager);
  info("Part 6");
  is_in_detail(aManager, "addons://list/extension", true, false);

  go_back();

  aManager = await wait_for_view_load(aManager);
  info("Part 7");
  is_in_list(aManager, "addons://list/extension", false, true);

  await close_manager(aManager);
});

// Tests that browsing to the add-ons manager from a website and going back works
add_task(async function test_navigate_between_webpage_and_aboutaddons() {
  info("Part 1");
  await BrowserTestUtils.openNewForegroundTab(
    gBrowser,
    "http://example.com/",
    true,
    true
  );

  info("Part 2");
  ok(!gBrowser.canGoBack, "Should not be able to go back");
  ok(!gBrowser.canGoForward, "Should not be able to go forward");

  BrowserTestUtils.startLoadingURIString(
    gBrowser.selectedBrowser,
    "about:addons"
  );
  await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);

  let manager = await wait_for_manager_load(
    gBrowser.selectedBrowser.contentWindow
  );

  info("Part 3");
  is_in_list(manager, "addons://list/extension", true, false);

  // XXX: This is less than ideal, as it's currently difficult to deal with
  // the browser frame switching between remote/non-remote in e10s mode.
  let promiseLoaded;
  if (gMultiProcessBrowser) {
    promiseLoaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
  } else {
    promiseLoaded = BrowserTestUtils.waitForEvent(
      gBrowser.selectedBrowser,
      "pageshow"
    );
  }

  go_back(manager);
  await promiseLoaded;

  info("Part 4");
  is(
    gBrowser.currentURI.spec,
    "http://example.com/",
    "Should be showing the webpage"
  );
  ok(!gBrowser.canGoBack, "Should not be able to go back");
  ok(gBrowser.canGoForward, "Should be able to go forward");

  promiseLoaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
  go_forward(manager);
  await promiseLoaded;

  manager = gBrowser.selectedBrowser.contentWindow;
  info("Part 5");
  await TestUtils.waitForCondition(
    () => manager.document.querySelector("addon-list"),
    "The add-on list should render."
  );

  is_in_list(manager, "addons://list/extension", true, false);

  await close_manager(manager);
});

// Tests simple forward and back navigation and that the right heading and
// category is selected -- Keyboard navigation [Bug 565359]
add_task(async function test_keyboard_history_navigation() {
  let aManager = await open_manager("addons://list/extension");
  let categoryUtils = new CategoryUtilities(aManager);
  info("Part 1");
  is_in_list(aManager, "addons://list/extension", false, false);

  EventUtils.synthesizeMouseAtCenter(categoryUtils.get("plugin"), {}, aManager);

  aManager = await wait_for_view_load(aManager);
  info("Part 2");
  is_in_list(aManager, "addons://list/plugin", true, false);

  // Backspace should not navigate back. We should still be on the same view.
  is(
    Services.prefs.getIntPref("browser.backspace_action"),
    2,
    "Backspace should not navigate back"
  );
  EventUtils.synthesizeKey("KEY_Backspace");
  aManager = await wait_for_view_load(aManager);
  info("Part 2b");
  is_in_list(aManager, "addons://list/plugin", true, false);

  go_back_key();

  aManager = await wait_for_view_load(aManager);
  info("Part 3");
  is_in_list(aManager, "addons://list/extension", false, true);

  go_forward_key();

  aManager = await wait_for_view_load(aManager);
  info("Part 4");
  is_in_list(aManager, "addons://list/plugin", true, false);

  go_back_key();

  aManager = await wait_for_view_load(aManager);
  info("Part 5");
  is_in_list(aManager, "addons://list/extension", false, true);

  await expand_addon_element(aManager, "test1@tests.mozilla.org");

  aManager = await wait_for_view_load(aManager);
  info("Part 6");
  is_in_detail(aManager, "addons://list/extension", true, false);

  go_back_key();

  aManager = await wait_for_view_load(aManager);
  info("Part 7");
  is_in_list(aManager, "addons://list/extension", false, true);

  await close_manager(aManager);
});

// Tests that opening a custom first view only stores a single history entry
add_task(async function test_single_history_entry() {
  let aManager = await open_manager("addons://list/plugin");
  let categoryUtils = new CategoryUtilities(aManager);
  info("Part 1");
  is_in_list(aManager, "addons://list/plugin", false, false);

  EventUtils.synthesizeMouseAtCenter(
    categoryUtils.get("extension"),
    {},
    aManager
  );

  aManager = await wait_for_view_load(aManager);
  info("Part 2");
  is_in_list(aManager, "addons://list/extension", true, false);

  go_back();

  aManager = await wait_for_view_load(aManager);
  info("Part 3");
  is_in_list(aManager, "addons://list/plugin", false, true);

  await close_manager(aManager);
});

// Tests that opening a view while the manager is already open adds a new
// history entry
add_task(async function test_new_history_entry_while_opened() {
  let aManager = await open_manager("addons://list/extension");
  info("Part 1");
  is_in_list(aManager, "addons://list/extension", false, false);

  aManager.loadView("addons://list/plugin");

  aManager = await wait_for_view_load(aManager);
  info("Part 2");
  is_in_list(aManager, "addons://list/plugin", true, false);

  go_back();

  aManager = await wait_for_view_load(aManager);
  info("Part 3");
  is_in_list(aManager, "addons://list/extension", false, true);

  go_forward();

  aManager = await wait_for_view_load(aManager);
  info("Part 4");
  is_in_list(aManager, "addons://list/plugin", true, false);

  await close_manager(aManager);
});

// Tests than navigating to a website and then going back returns to the
// previous view
add_task(async function test_navigate_back_from_website() {
  await SpecialPowers.pushPrefEnv({
    set: [["security.allow_eval_with_system_principal", true]],
  });

  let aManager = await open_manager("addons://list/plugin");
  info("Part 1");
  is_in_list(aManager, "addons://list/plugin", false, false);

  BrowserTestUtils.startLoadingURIString(gBrowser, "http://example.com/");
  await wait_for_page_load(gBrowser.selectedBrowser);

  info("Part 2");

  await new Promise(resolve =>
    executeSoon(function () {
      ok(gBrowser.canGoBack, "Should be able to go back");
      ok(!gBrowser.canGoForward, "Should not be able to go forward");

      go_back();

      gBrowser.addEventListener("pageshow", async function listener(event) {
        if (event.target.location != "about:addons") {
          return;
        }
        gBrowser.removeEventListener("pageshow", listener);

        aManager = await wait_for_view_load(
          gBrowser.contentWindow.wrappedJSObject
        );
        info("Part 3");
        is_in_list(aManager, "addons://list/plugin", false, true);

        executeSoon(() => go_forward());
        wait_for_page_load(gBrowser.selectedBrowser).then(() => {
          info("Part 4");

          executeSoon(function () {
            ok(gBrowser.canGoBack, "Should be able to go back");
            ok(!gBrowser.canGoForward, "Should not be able to go forward");

            go_back();

            gBrowser.addEventListener(
              "pageshow",
              async function listener(event) {
                if (event.target.location != "about:addons") {
                  return;
                }
                gBrowser.removeEventListener("pageshow", listener);
                aManager = await wait_for_view_load(
                  gBrowser.contentWindow.wrappedJSObject
                );
                info("Part 5");
                is_in_list(aManager, "addons://list/plugin", false, true);

                resolve();
              }
            );
          });
        });
      });
    })
  );

  await close_manager(aManager);
  await SpecialPowers.popPrefEnv();
});

// Tests that refreshing a list view does not affect the history
add_task(async function test_refresh_listview_donot_add_history_entries() {
  let aManager = await open_manager("addons://list/extension");
  let categoryUtils = new CategoryUtilities(aManager);
  info("Part 1");
  is_in_list(aManager, "addons://list/extension", false, false);

  EventUtils.synthesizeMouseAtCenter(categoryUtils.get("plugin"), {}, aManager);

  aManager = await wait_for_view_load(aManager);
  info("Part 2");
  is_in_list(aManager, "addons://list/plugin", true, false);

  await new Promise(resolve => {
    gBrowser.reload();
    gBrowser.addEventListener("pageshow", async function listener(event) {
      if (event.target.location != "about:addons") {
        return;
      }
      gBrowser.removeEventListener("pageshow", listener);

      aManager = await wait_for_view_load(
        gBrowser.contentWindow.wrappedJSObject
      );
      info("Part 3");
      is_in_list(aManager, "addons://list/plugin", true, false);

      go_back();
      aManager = await wait_for_view_load(aManager);
      info("Part 4");
      is_in_list(aManager, "addons://list/extension", false, true);
      resolve();
    });
  });

  await close_manager(aManager);
});

// Tests that refreshing a detail view does not affect the history
add_task(async function test_refresh_detailview_donot_add_history_entries() {
  let aManager = await open_manager(null);
  info("Part 1");
  is_in_list(aManager, "addons://list/extension", false, false);

  await expand_addon_element(aManager, "test1@tests.mozilla.org");

  aManager = await wait_for_view_load(aManager);
  info("Part 2");
  is_in_detail(aManager, "addons://list/extension", true, false);

  await new Promise(resolve => {
    gBrowser.reload();
    gBrowser.addEventListener("pageshow", async function listener(event) {
      if (event.target.location != "about:addons") {
        return;
      }
      gBrowser.removeEventListener("pageshow", listener);

      aManager = await wait_for_view_load(
        gBrowser.contentWindow.wrappedJSObject
      );
      info("Part 3");
      is_in_detail(aManager, "addons://list/extension", true, false);

      go_back();
      aManager = await wait_for_view_load(aManager);
      info("Part 4");
      is_in_list(aManager, "addons://list/extension", false, true);
      resolve();
    });
  });

  await close_manager(aManager);
});

// Tests that removing an extension from the detail view goes back and doesn't
// allow you to go forward again.
add_task(async function test_history_on_detailview_extension_removed() {
  let aManager = await open_manager("addons://list/extension");

  info("Part 1");
  is_in_list(aManager, "addons://list/extension", false, false);

  await expand_addon_element(aManager, "test1@tests.mozilla.org");

  aManager = await wait_for_view_load(aManager);
  info("Part 2");
  is_in_detail(aManager, "addons://list/extension", true, false);

  const addonCard = aManager.document.querySelector(
    'addon-card[addon-id="test1@tests.mozilla.org"]'
  );
  const promptService = mockPromptService();
  promptService._response = 0;
  addonCard.querySelector("[action=remove]").click();

  await wait_for_view_load(aManager);
  await TestUtils.waitForCondition(
    () => aManager.document.querySelector("addon-list"),
    "The add-on list should render."
  );
  is_in_list(aManager, "addons://list/extension", true, false);

  const addon = await AddonManager.getAddonByID("test1@tests.mozilla.org");
  addon.cancelUninstall();

  await close_manager(aManager);
});

// Tests that opening the manager opens the last view
add_task(async function test_open_last_view() {
  let aManager = await open_manager("addons://list/plugin");
  info("Part 1");
  is_in_list(aManager, "addons://list/plugin", false, false);

  await close_manager(aManager);
  aManager = await open_manager(null);
  info("Part 2");
  is_in_list(aManager, "addons://list/plugin", false, false);

  await close_manager(aManager);
});

// Tests that navigating the discovery page works when that was the first view
add_task(async function test_discopane_first_history_entry() {
  await SpecialPowers.pushPrefEnv({
    set: [["extensions.getAddons.discovery.api_url", DISCOAPI_URL]],
  });

  let aManager = await open_manager("addons://discover/");
  let categoryUtils = new CategoryUtilities(aManager);
  info("1");
  is_in_discovery(aManager, false, false);

  EventUtils.synthesizeMouseAtCenter(categoryUtils.get("plugin"), {}, aManager);

  aManager = await wait_for_view_load(aManager);
  is_in_list(aManager, "addons://list/plugin", true, false);

  go_back();
  aManager = await wait_for_view_load(aManager);

  is_in_discovery(aManager, false, true);

  await close_manager(aManager);
});

// Tests that navigating the discovery page works when that was the second view
add_task(async function test_discopane_second_history_entry() {
  let aManager = await open_manager("addons://list/plugin");
  let categoryUtils = new CategoryUtilities(aManager);
  is_in_list(aManager, "addons://list/plugin", false, false);

  EventUtils.synthesizeMouseAtCenter(
    categoryUtils.get("discover"),
    {},
    aManager
  );

  aManager = await wait_for_view_load(aManager);
  is_in_discovery(aManager, true, false);

  EventUtils.synthesizeMouseAtCenter(categoryUtils.get("plugin"), {}, aManager);

  aManager = await wait_for_view_load(aManager);
  is_in_list(aManager, "addons://list/plugin", true, false);

  go_back();

  aManager = await wait_for_view_load(aManager);
  is_in_discovery(aManager, true, true);

  go_back();

  aManager = await wait_for_view_load(aManager);
  is_in_list(aManager, "addons://list/plugin", false, true);

  await close_manager(aManager);
});

add_task(async function test_initialSelectedView_on_aboutaddons_reload() {
  let managerWindow = await open_manager("addons://list/extension");
  isnot(
    managerWindow.gViewController.currentViewId,
    null,
    "Got a non null currentViewId on first load"
  );

  managerWindow.location.reload();
  await wait_for_manager_load(managerWindow);
  await wait_for_view_load(managerWindow);

  isnot(
    managerWindow.gViewController.currentViewId,
    null,
    "Got a non null currentViewId on reload"
  );

  await close_manager(managerWindow);
});
