/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

add_task(async function auto_approve_optional_permissions() {
  // Auto-approve optional permission requests, without UI.
  await SpecialPowers.pushPrefEnv({
    set: [["extensions.webextOptionalPermissionPrompts", false]],
  });
  // TODO: Consider an observer for "webextension-optional-permission-prompt"
  // once bug 1493396 is fixed.
});

add_task(async function overrideContext_permissions() {
  function sidebarJs() {
    // If the extension has the right permissions, calling
    // menus.overrideContext with one of the following should not throw.
    const CONTEXT_OPTIONS_TAB = { context: "tab", tabId: 1 };
    const CONTEXT_OPTIONS_BOOKMARK = { context: "bookmark", bookmarkId: "x" };

    const E_PERM_TAB = /The "tab" context requires the "tabs" permission/;
    const E_PERM_BOOKMARK =
      /The "bookmark" context requires the "bookmarks" permission/;

    function assertAllowed(contextOptions) {
      try {
        let result = browser.menus.overrideContext(contextOptions);
        browser.test.assertEq(
          undefined,
          result,
          `Allowed menu for context=${contextOptions.context}`
        );
      } catch (e) {
        browser.test.fail(
          `Unexpected error for context=${contextOptions.context}: ${e}`
        );
      }
    }

    function assertNotAllowed(contextOptions, expectedError) {
      browser.test.assertThrows(
        () => {
          browser.menus.overrideContext(contextOptions);
        },
        expectedError,
        `Expected error for context=${contextOptions.context}`
      );
    }

    async function requestPermissions(permissions) {
      try {
        let permPromise;
        window.withHandlingUserInputForPermissionRequestTest(() => {
          permPromise = browser.permissions.request(permissions);
        });
        browser.test.assertTrue(
          await permPromise,
          `Should have granted ${JSON.stringify(permissions)}`
        );
      } catch (e) {
        browser.test.fail(
          `Failed to use permissions.request(${JSON.stringify(
            permissions
          )}): ${e}`
        );
      }
    }

    // The menus.overrideContext method can only be called during a
    // "contextmenu" event. So we use a generator to run tests, and yield
    // before we call overrideContext after an asynchronous operation.
    let testGenerator = (async function* () {
      browser.test.assertEq(
        undefined,
        browser.menus.overrideContext,
        "menus.overrideContext requires the 'menus.overrideContext' permission"
      );
      await requestPermissions({ permissions: ["menus.overrideContext"] });
      yield;

      // context without required property.
      browser.test.assertThrows(
        () => {
          browser.menus.overrideContext({ context: "tab" });
        },
        /Property "tabId" is required for context "tab"/,
        "Required property for context tab"
      );
      browser.test.assertThrows(
        () => {
          browser.menus.overrideContext({ context: "bookmark" });
        },
        /Property "bookmarkId" is required for context "bookmark"/,
        "Required property for context bookmarks"
      );

      // context with too many properties.
      browser.test.assertThrows(
        () => {
          browser.menus.overrideContext({
            context: "bookmark",
            bookmarkId: "x",
            tabId: 1,
          });
        },
        /Property "tabId" can only be used with context "tab"/,
        "Invalid property for context bookmarks"
      );
      browser.test.assertThrows(
        () => {
          browser.menus.overrideContext({
            context: "bookmark",
            bookmarkId: "x",
            showDefaults: true,
          });
        },
        /Property "showDefaults" cannot be used with context "bookmark"/,
        "showDefaults cannot be used with context bookmark"
      );

      // context with right properties, but missing permissions.
      assertNotAllowed(CONTEXT_OPTIONS_BOOKMARK, E_PERM_BOOKMARK);
      assertNotAllowed(CONTEXT_OPTIONS_TAB, E_PERM_TAB);

      await requestPermissions({ permissions: ["bookmarks"] });
      browser.test.log("Active permissions: bookmarks");
      yield;

      assertAllowed(CONTEXT_OPTIONS_BOOKMARK);
      assertNotAllowed(CONTEXT_OPTIONS_TAB, E_PERM_TAB);

      await requestPermissions({ permissions: ["tabs"] });
      await browser.permissions.remove({ permissions: ["bookmarks"] });
      browser.test.log("Active permissions: tabs");
      yield;

      assertNotAllowed(CONTEXT_OPTIONS_BOOKMARK, E_PERM_BOOKMARK);
      assertAllowed(CONTEXT_OPTIONS_TAB);
      await browser.permissions.remove({ permissions: ["tabs"] });
      browser.test.log("Active permissions: none");
      yield;

      assertNotAllowed(CONTEXT_OPTIONS_TAB, E_PERM_TAB);

      await browser.permissions.remove({
        permissions: ["menus.overrideContext"],
      });
      browser.test.assertEq(
        undefined,
        browser.menus.overrideContext,
        "menus.overrideContext is unavailable after revoking the permission"
      );
    })();

    // eslint-disable-next-line mozilla/balanced-listeners
    document.addEventListener("contextmenu", async event => {
      event.preventDefault();
      try {
        let { done } = await testGenerator.next();
        browser.test.sendMessage("continue_test", !done);
      } catch (e) {
        browser.test.fail(`Unexpected error: ${e} :: ${e.stack}`);
        browser.test.sendMessage("continue_test", false);
      }
    });
    browser.test.sendMessage("sidebar_ready");
  }
  let extension = ExtensionTestUtils.loadExtension({
    useAddonManager: "temporary", // To automatically show sidebar on load.
    manifest: {
      permissions: ["menus"],
      optional_permissions: ["menus.overrideContext", "tabs", "bookmarks"],
      sidebar_action: {
        default_panel: "sidebar.html",
      },
    },
    files: {
      "sidebar.html": `
        <!DOCTYPE html><meta charset="utf-8">
        <a href="http://example.com/">Link</a>
        <script src="sidebar.js"></script>
      `,
      "sidebar.js": sidebarJs,
    },
  });
  await extension.startup();
  await extension.awaitMessage("sidebar_ready");

  // permissions.request requires user input, export helper.
  await SpecialPowers.spawn(
    SidebarUI.browser.contentDocument.getElementById("webext-panels-browser"),
    [],
    () => {
      const { ExtensionCommon } = ChromeUtils.importESModule(
        "resource://gre/modules/ExtensionCommon.sys.mjs"
      );
      Cu.exportFunction(
        fn => {
          return ExtensionCommon.withHandlingUserInput(content, fn);
        },
        content,
        {
          defineAs: "withHandlingUserInputForPermissionRequestTest",
        }
      );
    }
  );

  do {
    info(`Going to trigger "contextmenu" event.`);
    await BrowserTestUtils.synthesizeMouseAtCenter(
      "a",
      { type: "contextmenu" },
      SidebarUI.browser.contentDocument.getElementById("webext-panels-browser")
    );
  } while (await extension.awaitMessage("continue_test"));

  await extension.unload();
});
