"use strict";

// This is a test for PageActions.sys.mjs, specifically the generalized parts that
// add and remove page actions and toggle them in the urlbar.  This does not
// test the built-in page actions; browser_page_action_menu.js does that.

// Initialization.  Must run first.
add_setup(async function () {
  await initPageActionsTest();
});

// Makes sure that urlbar nodes appear in the correct order in a new window.
add_task(async function urlbarOrderNewWindow() {
  // Make some new actions.
  let actions = [0, 1, 2].map(i => {
    return PageActions.addAction(
      new PageActions.Action({
        id: `test-urlbarOrderNewWindow-${i}`,
        title: `Test urlbarOrderNewWindow ${i}`,
        pinnedToUrlbar: true,
      })
    );
  });

  // Make sure PageActions knows they're inserted before the bookmark action in
  // the urlbar.
  Assert.deepEqual(
    PageActions._persistedActions.idsInUrlbar.slice(
      PageActions._persistedActions.idsInUrlbar.length - (actions.length + 1)
    ),
    actions.map(a => a.id).concat([PageActions.ACTION_ID_BOOKMARK]),
    "PageActions._persistedActions.idsInUrlbar has new actions inserted"
  );
  Assert.deepEqual(
    PageActions.actionsInUrlbar(window)
      .slice(PageActions.actionsInUrlbar(window).length - (actions.length + 1))
      .map(a => a.id),
    actions.map(a => a.id).concat([PageActions.ACTION_ID_BOOKMARK]),
    "PageActions.actionsInUrlbar has new actions inserted"
  );

  // Reach into _persistedActions to move the new actions to the front of the
  // urlbar, same as if the user moved them.  That way we can test that insert-
  // before IDs are correctly non-null when the urlbar nodes are inserted in the
  // new window below.
  PageActions._persistedActions.idsInUrlbar.splice(
    PageActions._persistedActions.idsInUrlbar.length - (actions.length + 1),
    actions.length
  );
  for (let i = 0; i < actions.length; i++) {
    PageActions._persistedActions.idsInUrlbar.splice(i, 0, actions[i].id);
  }

  // Save the right-ordered IDs to use below, just in case they somehow get
  // changed when the new window opens, which shouldn't happen, but maybe
  // there's bugs.
  let ids = PageActions._persistedActions.idsInUrlbar.slice();

  // Make sure that worked.
  Assert.deepEqual(
    ids.slice(0, actions.length),
    actions.map(a => a.id),
    "PageActions._persistedActions.idsInUrlbar now has new actions at front"
  );

  // _persistedActions will contain the IDs of test actions added and removed
  // above (unless PageActions._purgeUnregisteredPersistedActions() was called
  // for all of them, which it wasn't).  Filter them out because they should
  // not appear in the new window (or any window at this point).
  ids = ids.filter(id => PageActions.actionForID(id));

  // Open the new window.
  let win = await BrowserTestUtils.openNewBrowserWindow();

  // Collect its urlbar nodes.
  let actualUrlbarNodeIDs = [];
  for (
    let node = win.BrowserPageActions.mainButtonNode.nextElementSibling;
    node;
    node = node.nextElementSibling
  ) {
    actualUrlbarNodeIDs.push(node.id);
  }

  // Now check that they're in the right order.
  Assert.deepEqual(
    actualUrlbarNodeIDs,
    ids.map(id => win.BrowserPageActions.urlbarButtonNodeIDForActionID(id)),
    "Expected actions in new window's urlbar"
  );

  // Done, clean up.
  await BrowserTestUtils.closeWindow(win);
  for (let action of actions) {
    action.remove();
  }
});

// Stores version-0 (unversioned actually) persisted actions and makes sure that
// migrating to version 1 works.
add_task(async function migrate1() {
  // Add a test action so we can test a non-built-in action below.
  let actionId = "test-migrate1";
  PageActions.addAction(
    new PageActions.Action({
      id: actionId,
      title: "Test migrate1",
      pinnedToUrlbar: true,
    })
  );

  // Add the bookmark action first to make sure it ends up last after migration.
  // Also include a non-default action to make sure we're not accidentally
  // testing default behavior.
  let ids = [PageActions.ACTION_ID_BOOKMARK, actionId];
  let persisted = ids.reduce(
    (memo, id) => {
      memo.ids[id] = true;
      memo.idsInUrlbar.push(id);
      return memo;
    },
    { ids: {}, idsInUrlbar: [] }
  );

  Services.prefs.setStringPref(
    PageActions.PREF_PERSISTED_ACTIONS,
    JSON.stringify(persisted)
  );

  // Migrate.
  PageActions._loadPersistedActions();

  Assert.equal(PageActions._persistedActions.version, 1, "Correct version");

  // expected order
  let orderedIDs = [actionId, PageActions.ACTION_ID_BOOKMARK];

  // Check the ordering.
  Assert.deepEqual(
    PageActions._persistedActions.idsInUrlbar,
    orderedIDs,
    "PageActions._persistedActions.idsInUrlbar has right order"
  );
  Assert.deepEqual(
    PageActions.actionsInUrlbar(window).map(a => a.id),
    orderedIDs,
    "PageActions.actionsInUrlbar has right order"
  );

  // Open a new window.
  let win = await BrowserTestUtils.openNewBrowserWindow();
  await BrowserTestUtils.openNewForegroundTab({
    gBrowser: win.gBrowser,
    url: "http://example.com/",
  });

  // Collect its urlbar nodes.
  let actualUrlbarNodeIDs = [];
  for (
    let node = win.BrowserPageActions.mainButtonNode.nextElementSibling;
    node;
    node = node.nextElementSibling
  ) {
    actualUrlbarNodeIDs.push(node.id);
  }

  // Now check that they're in the right order.
  Assert.deepEqual(
    actualUrlbarNodeIDs,
    orderedIDs.map(id =>
      win.BrowserPageActions.urlbarButtonNodeIDForActionID(id)
    ),
    "Expected actions in new window's urlbar"
  );

  // Done, clean up.
  await BrowserTestUtils.closeWindow(win);
  Services.prefs.clearUserPref(PageActions.PREF_PERSISTED_ACTIONS);
  PageActions.actionForID(actionId).remove();
});

// Opens a new browser window and makes sure per-window state works right.
add_task(async function perWindowState() {
  // Add a test action.
  let title = "Test perWindowState";
  let action = PageActions.addAction(
    new PageActions.Action({
      iconURL: "chrome://browser/skin/mail.svg",
      id: "test-perWindowState",
      pinnedToUrlbar: true,
      title,
    })
  );

  let actionsInUrlbar = PageActions.actionsInUrlbar(window);

  // Open a new browser window and load an actionable page so that the action
  // shows up in it.
  let newWindow = await BrowserTestUtils.openNewBrowserWindow();
  await BrowserTestUtils.openNewForegroundTab({
    gBrowser: newWindow.gBrowser,
    url: "http://example.com/",
  });

  // Set a new title globally.
  let newGlobalTitle = title + " new title";
  action.setTitle(newGlobalTitle);
  Assert.equal(action.getTitle(), newGlobalTitle, "Title: global");
  Assert.equal(action.getTitle(window), newGlobalTitle, "Title: old window");
  Assert.equal(action.getTitle(newWindow), newGlobalTitle, "Title: new window");

  // Initialize panel nodes in the windows
  document.getElementById("pageActionButton").click();
  await BrowserTestUtils.waitForEvent(document, "popupshowing", true);
  newWindow.document.getElementById("pageActionButton").click();
  await BrowserTestUtils.waitForEvent(newWindow.document, "popupshowing", true);

  // The action's panel button nodes should be updated in both windows.
  let panelButtonID = BrowserPageActions.panelButtonNodeIDForActionID(
    action.id
  );
  for (let win of [window, newWindow]) {
    win.BrowserPageActions.placeLazyActionsInPanel();
    let panelButtonNode = win.document.getElementById(panelButtonID);
    Assert.equal(
      panelButtonNode.getAttribute("label"),
      newGlobalTitle,
      "Panel button label should be global title"
    );
  }

  // Set a new title in the new window.
  let newPerWinTitle = title + " new title in new window";
  action.setTitle(newPerWinTitle, newWindow);
  Assert.equal(
    action.getTitle(),
    newGlobalTitle,
    "Title: global should remain same"
  );
  Assert.equal(
    action.getTitle(window),
    newGlobalTitle,
    "Title: old window should remain same"
  );
  Assert.equal(
    action.getTitle(newWindow),
    newPerWinTitle,
    "Title: new window should be new"
  );

  // The action's panel button node should be updated in the new window but the
  // same in the old window.
  let panelButtonNode1 = document.getElementById(panelButtonID);
  Assert.equal(
    panelButtonNode1.getAttribute("label"),
    newGlobalTitle,
    "Panel button label in old window"
  );
  let panelButtonNode2 = newWindow.document.getElementById(panelButtonID);
  Assert.equal(
    panelButtonNode2.getAttribute("label"),
    newPerWinTitle,
    "Panel button label in new window"
  );

  // Disable the action in the new window.
  action.setDisabled(true, newWindow);
  Assert.equal(
    action.getDisabled(),
    false,
    "Disabled: global should remain false"
  );
  Assert.equal(
    action.getDisabled(window),
    false,
    "Disabled: old window should remain false"
  );
  Assert.equal(
    action.getDisabled(newWindow),
    true,
    "Disabled: new window should be true"
  );

  // Check PageActions.actionsInUrlbar for each window.
  Assert.deepEqual(
    PageActions.actionsInUrlbar(window).map(a => a.id),
    actionsInUrlbar.map(a => a.id),
    "PageActions.actionsInUrlbar: old window should have all actions in urlbar"
  );
  Assert.deepEqual(
    PageActions.actionsInUrlbar(newWindow).map(a => a.id),
    actionsInUrlbar.map(a => a.id).filter(id => id != action.id),
    "PageActions.actionsInUrlbar: new window should have all actions in urlbar except the test action"
  );

  // Check the urlbar nodes for the old window.
  let actualUrlbarNodeIDs = [];
  for (
    let node = BrowserPageActions.mainButtonNode.nextElementSibling;
    node;
    node = node.nextElementSibling
  ) {
    actualUrlbarNodeIDs.push(node.id);
  }
  Assert.deepEqual(
    actualUrlbarNodeIDs,
    actionsInUrlbar.map(a =>
      BrowserPageActions.urlbarButtonNodeIDForActionID(a.id)
    ),
    "Old window should have all nodes in urlbar"
  );

  // Check the urlbar nodes for the new window.
  actualUrlbarNodeIDs = [];
  for (
    let node = newWindow.BrowserPageActions.mainButtonNode.nextElementSibling;
    node;
    node = node.nextElementSibling
  ) {
    actualUrlbarNodeIDs.push(node.id);
  }
  Assert.deepEqual(
    actualUrlbarNodeIDs,
    actionsInUrlbar
      .filter(a => a.id != action.id)
      .map(a => BrowserPageActions.urlbarButtonNodeIDForActionID(a.id)),
    "New window should have all nodes in urlbar except for the test action's"
  );

  // Done, clean up.
  await BrowserTestUtils.closeWindow(newWindow);
  action.remove();
});

add_task(async function action_disablePrivateBrowsing() {
  let id = "testWidget";
  let action = PageActions.addAction(
    new PageActions.Action({
      id,
      disablePrivateBrowsing: true,
      title: "title",
      disabled: false,
      pinnedToUrlbar: true,
    })
  );
  // Open an actionable page so that the main page action button appears.
  let url = "http://example.com/";
  let privateWindow = await BrowserTestUtils.openNewBrowserWindow({
    private: true,
  });
  await BrowserTestUtils.openNewForegroundTab(
    privateWindow.gBrowser,
    url,
    true,
    true
  );

  Assert.ok(action.canShowInWindow(window), "should show in default window");
  Assert.ok(
    !action.canShowInWindow(privateWindow),
    "should not show in private browser"
  );
  Assert.ok(action.shouldShowInUrlbar(window), "should show in default urlbar");
  Assert.ok(
    !action.shouldShowInUrlbar(privateWindow),
    "should not show in default urlbar"
  );
  Assert.ok(action.shouldShowInPanel(window), "should show in default urlbar");
  Assert.ok(
    !action.shouldShowInPanel(privateWindow),
    "should not show in default urlbar"
  );

  action.remove();

  privateWindow.close();
});
