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

"use strict";

const ORIGIN = "https://example.com";
const CROSS_SUBFRAME_PAGE =
  getRootDirectory(gTestPath).replace("chrome://mochitests/content", ORIGIN) +
  "temporary_permissions_subframe.html";

const CROSS_FRAME_PAGE =
  getRootDirectory(gTestPath).replace("chrome://mochitests/content", ORIGIN) +
  "temporary_permissions_frame.html";

const PromptResult = {
  ALLOW: "allow",
  DENY: "deny",
  PROMPT: "prompt",
};

var Perms = Services.perms;
var uri = NetUtil.newURI(ORIGIN);
var principal = Services.scriptSecurityManager.createContentPrincipal(uri, {});

async function checkNotificationBothOrigins(
  firstPartyOrigin,
  thirdPartyOrigin
) {
  // Notification is shown, check label and deny to clean
  let popuphidden = BrowserTestUtils.waitForEvent(
    PopupNotifications.panel,
    "popuphidden"
  );

  let notification = PopupNotifications.panel.firstElementChild;
  // Check the label of the notificaiton should be the first party
  is(
    PopupNotifications.getNotification("geolocation").options.name,
    firstPartyOrigin,
    "Use first party's origin"
  );

  // Check the second name of the notificaiton should be the third party
  is(
    PopupNotifications.getNotification("geolocation").options.secondName,
    thirdPartyOrigin,
    "Use third party's origin"
  );

  // Check remember checkbox is hidden
  let checkbox = notification.checkbox;
  ok(!!checkbox, "checkbox is present");
  ok(checkbox.hidden, "checkbox is not visible");
  ok(!checkbox.checked, "checkbox not checked");

  EventUtils.synthesizeMouseAtCenter(notification.secondaryButton, {});
  await popuphidden;
}

async function checkGeolocation(browser, frameId, expect) {
  let isPrompt = expect == PromptResult.PROMPT;
  let waitForPrompt;
  if (isPrompt) {
    waitForPrompt = BrowserTestUtils.waitForEvent(
      PopupNotifications.panel,
      "popupshown"
    );
  }

  await SpecialPowers.spawn(
    browser,
    [{ frameId, expect, isPrompt }],
    async args => {
      let frame = content.document.getElementById(args.frameId);

      let waitForNoPrompt = new Promise(resolve => {
        function onMessage(event) {
          // Check the result right here because there's no notification
          Assert.equal(
            event.data,
            args.expect,
            "Correct expectation for third party"
          );
          content.window.removeEventListener("message", onMessage);
          resolve();
        }

        if (!args.isPrompt) {
          content.window.addEventListener("message", onMessage);
        }
      });

      await content.SpecialPowers.spawn(frame, [], async () => {
        const { E10SUtils } = ChromeUtils.importESModule(
          "resource://gre/modules/E10SUtils.sys.mjs"
        );

        E10SUtils.wrapHandlingUserInput(this.content, true, function () {
          let frameDoc = this.content.document;
          frameDoc.getElementById("geo").click();
        });
      });

      if (!args.isPrompt) {
        await waitForNoPrompt;
      }
    }
  );

  if (isPrompt) {
    await waitForPrompt;
  }
}

add_setup(async function () {
  await new Promise(r => {
    SpecialPowers.pushPrefEnv(
      {
        set: [
          ["dom.security.featurePolicy.header.enabled", true],
          ["dom.security.featurePolicy.webidl.enabled", true],
          ["permissions.delegation.enabled", true],
          // This is the amount of time before the repeating
          // NetworkGeolocationProvider timer is stopped.
          // It needs to be less than 5000ms, or the timer will be
          // reported as left behind by the test.
          ["geo.timeout", 4000],
        ],
      },
      r
    );
  });
});

// Test that temp blocked permissions in first party affect the third party
// iframe.
add_task(async function testUseTempPermissionsFirstParty() {
  await BrowserTestUtils.withNewTab(
    CROSS_SUBFRAME_PAGE,
    async function (browser) {
      SitePermissions.setForPrincipal(
        principal,
        "geo",
        SitePermissions.BLOCK,
        SitePermissions.SCOPE_TEMPORARY,
        browser
      );

      await checkGeolocation(browser, "frame", PromptResult.DENY);

      SitePermissions.removeFromPrincipal(principal, "geo", browser);
    }
  );
});

// Test that persistent permissions in first party affect the third party
// iframe.
add_task(async function testUsePersistentPermissionsFirstParty() {
  await BrowserTestUtils.withNewTab(
    CROSS_SUBFRAME_PAGE,
    async function (browser) {
      async function checkPermission(aPermission, aExpect) {
        PermissionTestUtils.add(uri, "geo", aPermission);
        await checkGeolocation(browser, "frame", aExpect);

        if (aExpect == PromptResult.PROMPT) {
          // Notification is shown, check label and deny to clean
          let popuphidden = BrowserTestUtils.waitForEvent(
            PopupNotifications.panel,
            "popuphidden"
          );

          let notification = PopupNotifications.panel.firstElementChild;
          // Check the label of the notificaiton should be the first party
          is(
            PopupNotifications.getNotification("geolocation").options.name,
            uri.host,
            "Use first party's origin"
          );

          EventUtils.synthesizeMouseAtCenter(notification.secondaryButton, {});

          await popuphidden;
          SitePermissions.removeFromPrincipal(null, "geo", browser);
        }

        PermissionTestUtils.remove(uri, "geo");
      }

      await checkPermission(Perms.PROMPT_ACTION, PromptResult.PROMPT);
      await checkPermission(Perms.DENY_ACTION, PromptResult.DENY);
      await checkPermission(Perms.ALLOW_ACTION, PromptResult.ALLOW);
    }
  );
});

// Test that we do not prompt for maybe unsafe permission delegation if the
// origin of the page is the original src origin.
add_task(async function testPromptInMaybeUnsafePermissionDelegation() {
  await BrowserTestUtils.withNewTab(
    CROSS_SUBFRAME_PAGE,
    async function (browser) {
      // Persistent allow top level origin
      PermissionTestUtils.add(uri, "geo", Perms.ALLOW_ACTION);

      await checkGeolocation(browser, "frameAllowsAll", PromptResult.ALLOW);

      SitePermissions.removeFromPrincipal(null, "geo", browser);
      PermissionTestUtils.remove(uri, "geo");
    }
  );
});

// Test that we should prompt if we are in unsafe permission delegation and
// change location to origin which is not explicitly trusted. The prompt popup
// should include both first and third party origin.
add_task(async function testPromptChangeLocationUnsafePermissionDelegation() {
  await BrowserTestUtils.withNewTab(
    CROSS_SUBFRAME_PAGE,
    async function (browser) {
      // Persistent allow top level origin
      PermissionTestUtils.add(uri, "geo", Perms.ALLOW_ACTION);

      let iframe = await SpecialPowers.spawn(browser, [], () => {
        return content.document.getElementById("frameAllowsAll")
          .browsingContext;
      });

      let otherURI =
        "https://test1.example.com/browser/browser/base/content/test/permissions/permissions.html";
      let loaded = BrowserTestUtils.browserLoaded(browser, true, otherURI);
      await SpecialPowers.spawn(iframe, [otherURI], async function (_otherURI) {
        content.location = _otherURI;
      });
      await loaded;

      await checkGeolocation(browser, "frameAllowsAll", PromptResult.PROMPT);
      await checkNotificationBothOrigins(uri.host, "test1.example.com");

      SitePermissions.removeFromPrincipal(null, "geo", browser);
      PermissionTestUtils.remove(uri, "geo");
    }
  );
});

// If we are in unsafe permission delegation and the origin is explicitly
// trusted in ancestor chain. Do not need prompt
add_task(async function testExplicitlyAllowedInChain() {
  await BrowserTestUtils.withNewTab(CROSS_FRAME_PAGE, async function (browser) {
    // Persistent allow top level origin
    PermissionTestUtils.add(uri, "geo", Perms.ALLOW_ACTION);

    let iframeAncestor = await SpecialPowers.spawn(browser, [], () => {
      return content.document.getElementById("frameAncestor").browsingContext;
    });

    let iframe = await SpecialPowers.spawn(iframeAncestor, [], () => {
      return content.document.getElementById("frameAllowsAll").browsingContext;
    });

    // Change location to check that we actually look at the ancestor chain
    // instead of just considering the "same origin as src" rule.
    let otherURI =
      "https://test2.example.com/browser/browser/base/content/test/permissions/permissions.html";
    let loaded = BrowserTestUtils.browserLoaded(browser, true, otherURI);
    await SpecialPowers.spawn(iframe, [otherURI], async function (_otherURI) {
      content.location = _otherURI;
    });
    await loaded;

    await checkGeolocation(
      iframeAncestor,
      "frameAllowsAll",
      PromptResult.ALLOW
    );

    PermissionTestUtils.remove(uri, "geo");
  });
});
