"use strict";

// This is a test for PageActions.sys.mjs, specifically the context menus.

ChromeUtils.defineESModuleGetters(this, {
  ExtensionCommon: "resource://gre/modules/ExtensionCommon.sys.mjs",
});

// Initialization.  Must run first.
add_setup(async function () {
  // The page action urlbar button, and therefore the panel, is only shown when
  // the current tab is actionable -- i.e., a normal web page.  about:blank is
  // not, so open a new tab first thing, and close it when this test is done.
  let tab = await BrowserTestUtils.openNewForegroundTab({
    gBrowser,
    url: "http://example.com/",
  });
  registerCleanupFunction(async () => {
    BrowserTestUtils.removeTab(tab);
  });

  await initPageActionsTest();
});

// Opens the context menu on a non-built-in action.  (The context menu for
// built-in actions is tested in browser_page_action_menu.js.)
add_task(async function contextMenu() {
  // Add an extension with a page action so we can test its context menu.
  let extension = ExtensionTestUtils.loadExtension({
    manifest: {
      name: "Page action test",
      page_action: { show_matches: ["<all_urls>"] },
    },
    useAddonManager: "temporary",
  });
  await extension.startup();
  let actionId = ExtensionCommon.makeWidgetId(extension.id);

  // Open the main panel.
  await promiseOpenPageActionPanel();
  let panelButton = BrowserPageActions.panelButtonNodeForActionID(actionId);
  let cxmenu = document.getElementById("pageActionContextMenu");

  let contextMenuPromise;
  let menuItems;

  // Open the context menu again on the action's button in the panel. (The
  // panel should still be open.)
  contextMenuPromise = promisePanelShown("pageActionContextMenu");
  EventUtils.synthesizeMouseAtCenter(panelButton, {
    type: "contextmenu",
    button: 2,
  });
  await contextMenuPromise;
  menuItems = collectContextMenuItems();
  Assert.deepEqual(makeMenuItemSpecs(menuItems), makeContextMenuItemSpecs());

  // Click the "manage extension" context menu item. about:addons should open.
  let manageItemIndex = 0;
  contextMenuPromise = promisePanelHidden("pageActionContextMenu");
  let aboutAddonsPromise = BrowserTestUtils.waitForNewTab(
    gBrowser,
    "about:addons"
  );
  cxmenu.activateItem(menuItems[manageItemIndex]);
  let values = await Promise.all([aboutAddonsPromise, contextMenuPromise]);
  let aboutAddonsTab = values[0];
  BrowserTestUtils.removeTab(aboutAddonsTab);

  // Wait for the urlbar button to become visible again after about:addons is
  // closed and the test tab becomes selected.
  await BrowserTestUtils.waitForCondition(() => {
    return BrowserPageActions.urlbarButtonNodeForActionID(actionId);
  }, "Waiting for urlbar button to be added back");

  // Open the context menu on the action's urlbar button.
  let urlbarButton = BrowserPageActions.urlbarButtonNodeForActionID(actionId);
  contextMenuPromise = promisePanelShown("pageActionContextMenu");
  EventUtils.synthesizeMouseAtCenter(urlbarButton, {
    type: "contextmenu",
    button: 2,
  });
  await contextMenuPromise;
  menuItems = collectContextMenuItems();
  Assert.deepEqual(makeMenuItemSpecs(menuItems), makeContextMenuItemSpecs());

  // Click the "manage" context menu item. about:addons should open.
  contextMenuPromise = promisePanelHidden("pageActionContextMenu");
  aboutAddonsPromise = BrowserTestUtils.waitForNewTab(gBrowser, "about:addons");
  cxmenu.activateItem(menuItems[manageItemIndex]);
  values = await Promise.all([aboutAddonsPromise, contextMenuPromise]);
  aboutAddonsTab = values[0];
  BrowserTestUtils.removeTab(aboutAddonsTab);

  // Wait for the urlbar button to become visible again after about:addons is
  // closed and the test tab becomes selected.
  await BrowserTestUtils.waitForCondition(() => {
    return BrowserPageActions.urlbarButtonNodeForActionID(actionId);
  }, "Waiting for urlbar button to be added back");

  // Open the context menu on the action's urlbar button.
  urlbarButton = BrowserPageActions.urlbarButtonNodeForActionID(actionId);
  contextMenuPromise = promisePanelShown("pageActionContextMenu");
  EventUtils.synthesizeMouseAtCenter(urlbarButton, {
    type: "contextmenu",
    button: 2,
  });
  await contextMenuPromise;
  menuItems = collectContextMenuItems();
  Assert.deepEqual(makeMenuItemSpecs(menuItems), makeContextMenuItemSpecs());

  // Below we'll click the "remove extension" context menu item, which first
  // opens a prompt using the prompt service and requires confirming the prompt.
  // Set up a mock prompt service that returns 0 to indicate that the user
  // pressed the OK button.
  let { prompt } = Services;
  let promptService = {
    QueryInterface: ChromeUtils.generateQI(["nsIPromptService"]),
    confirmEx() {
      return 0;
    },
  };
  Services.prompt = promptService;
  registerCleanupFunction(() => {
    Services.prompt = prompt;
  });

  // Now click the "remove extension" context menu item.
  let removeItemIndex = manageItemIndex + 1;
  contextMenuPromise = promisePanelHidden("pageActionContextMenu");
  let promiseUninstalled = promiseAddonUninstalled(extension.id);
  cxmenu.activateItem(menuItems[removeItemIndex]);
  await contextMenuPromise;
  await promiseUninstalled;
  await extension.unload();
  Services.prompt = prompt;

  // urlbar tests that run after this one can break if the mouse is left over
  // the area where the urlbar popup appears, which seems to happen due to the
  // above synthesized mouse events.  Move it over the urlbar.
  EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, { type: "mousemove" });
  gURLBar.focus();
});

// The context menu shouldn't open on separators in the panel.
add_task(async function contextMenuOnSeparator() {
  // Add a non-built-in action so the built-in separator will appear in the
  // panel.
  let action = PageActions.addAction(
    new PageActions.Action({
      id: "contextMenuOnSeparator",
      title: "contextMenuOnSeparator",
      pinnedToUrlbar: true,
    })
  );

  // Open the panel and get the built-in separator.
  await promiseOpenPageActionPanel();
  let separator = BrowserPageActions.panelButtonNodeForActionID(
    PageActions.ACTION_ID_BUILT_IN_SEPARATOR
  );
  Assert.ok(separator, "The built-in separator should be in the panel");

  // Context-click it.  popupshowing should be fired, but by the time the event
  // reaches this listener, preventDefault should have been called on it.
  let showingPromise = BrowserTestUtils.waitForEvent(
    document.getElementById("pageActionContextMenu"),
    "popupshowing",
    false
  );
  EventUtils.synthesizeMouseAtCenter(separator, {
    type: "contextmenu",
    button: 2,
  });
  let event = await showingPromise;
  Assert.ok(
    event.defaultPrevented,
    "defaultPrevented should be true on popupshowing event"
  );

  // Click the main button to hide the main panel.
  EventUtils.synthesizeMouseAtCenter(BrowserPageActions.mainButtonNode, {});
  await promisePageActionPanelHidden();

  action.remove();

  // urlbar tests that run after this one can break if the mouse is left over
  // the area where the urlbar popup appears, which seems to happen due to the
  // above synthesized mouse events.  Move it over the urlbar.
  EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, { type: "mousemove" });
  gURLBar.focus();
});

function collectContextMenuItems() {
  let contextMenu = document.getElementById("pageActionContextMenu");
  return Array.prototype.filter.call(contextMenu.children, node => {
    return window.getComputedStyle(node).visibility == "visible";
  });
}

function makeMenuItemSpecs(elements) {
  return elements.map(e =>
    e.localName == "menuseparator" ? {} : { label: e.label }
  );
}

function makeContextMenuItemSpecs() {
  let items = [
    { label: "Manage Extension\u2026" },
    { label: "Remove Extension" },
  ];
  return items;
}

function promiseAddonUninstalled(addonId) {
  return new Promise(resolve => {
    let listener = {};
    listener.onUninstalled = addon => {
      if (addon.id == addonId) {
        AddonManager.removeAddonListener(listener);
        resolve();
      }
    };
    AddonManager.addAddonListener(listener);
  });
}
