<!DOCTYPE HTML>
<html>
<head>
    <meta charset="utf-8">
    <title>Test for Async Auth Prompt</title>
    <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
    <script src="/tests/SimpleTest/SimpleTest.js"></script>
    <script type="text/javascript" src="pwmgr_common.js"></script>
    <script type="text/javascript" src="../../../prompts/test/prompt_common.js"></script>
    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />

    <script class="testbody" type="text/javascript">
      const { NetUtil } = SpecialPowers.ChromeUtils.import(
        "resource://gre/modules/NetUtil.jsm"
      );
      const { TestUtils } = SpecialPowers.ChromeUtils.import(
        "resource://testing-common/TestUtils.jsm"
      );
      const EXAMPLE_COM = "http://example.com/tests/toolkit/components/passwordmgr/test/mochitest/";
      const EXAMPLE_ORG = "http://example.org/tests/toolkit/components/passwordmgr/test/mochitest/";
      let mozproxyOrigin;

      // Let prompt_common know what kind of modal type is enabled for auth prompts.
      modalType = authPromptModalType;

      // These are magically defined on the window due to the iframe IDs
      /* global iframe1, iframe2a, iframe2b */

      /**
       * Add a listener to add some logins to be autofilled in the HTTP/proxy auth. prompts later.
       */
      let pwmgrParent = runInParent(() => {
        Services.prefs.setIntPref("network.auth.subresource-http-auth-allow", 2);
        Services.prefs.setIntPref("prompts.authentication_dialog_abuse_limit", -1);

        addMessageListener("initLogins", async function onMessage(msg) {
          const loginsData = [
            [msg.mozproxyOrigin, "proxy_realm", "proxy_user", "proxy_pass"],
            [msg.mozproxyOrigin, "proxy_realm2", "proxy_user2", "proxy_pass2"],
            [msg.mozproxyOrigin, "proxy_realm3", "proxy_user3", "proxy_pass3"],
            [msg.mozproxyOrigin, "proxy_realm4", "proxy_user4", "proxy_pass4"],
            [msg.mozproxyOrigin, "proxy_realm5", "proxy_user5", "proxy_pass5"],
            ["http://example.com", "mochirealm", "user1name", "user1pass"],
            ["http://example.org", "mochirealm2", "user2name", "user2pass"],
            ["http://example.com", "mochirealm3", "user3name", "user3pass"],
            ["http://example.com", "mochirealm4", "user4name", "user4pass"],
            ["http://example.com", "mochirealm5", "user5name", "user5pass"],
            ["http://example.com", "mochirealm6", "user6name", "user6pass"]
          ];
          const logins = loginsData.map(([host, realm, user, pass]) => {
            const login = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(Ci.nsILoginInfo);
            login.init(host, null, realm, user, pass, "", "");
            return login
          })
          await Services.logins.addLogins(logins);
        });
      }); // end runInParent

      function promiseLoadedContentDoc(frame) {
        return new Promise(resolve => {
          frame.addEventListener("load", function onLoad(evt) {
            resolve(SpecialPowers.wrap(frame).contentDocument);
          }, { once: true });
        });
      }

      function promiseProxyErrorLoad(frame) {
        return TestUtils.waitForCondition(async function checkForProxyConnectFailure() {
          try {
            return await SpecialPowers.spawn(frame, [], function() {
              return this.content.document.documentURI.includes("proxyConnectFailure");
            })
          } catch (e) {
            // The frame may not be ready for the 'spawn' task right after setting
            // iframe.src, which will throw an exception when that happens.
            // Since this test is testing error load, we can't wait until the iframe
            // is 'loaded' either. So we simply catch the exception here and retry the task
            // later since we are in the waitForCondition loop.
            return false;
          }
        }, "Waiting for proxyConnectFailure documentURI");
      }

      /**
       * Make a channel to get the ProxyInfo used by the test harness so that we
       * can add logins for the correct proxy origin.
       */
      add_task(async function setup_getProxyInfoForHarness() {
        await new Promise(resolve => {
          let resolveCallback = SpecialPowers.wrapCallbackObject({
            // eslint-disable-next-line mozilla/use-chromeutils-generateqi
            QueryInterface(iid) {
              const interfaces = [Ci.nsIProtocolProxyCallback, Ci.nsISupports];

              if (!interfaces.some(v => iid.equals(v))) {
                throw SpecialPowers.Cr.NS_ERROR_NO_INTERFACE;
              }
              return this;
            },

            onProxyAvailable(req, uri, pi, status) {
              // Add logins using the proxy host and port used by the mochitest harness.
              mozproxyOrigin = "moz-proxy://" + SpecialPowers.wrap(pi).host + ":" +
                SpecialPowers.wrap(pi).port;

              pwmgrParent.sendQuery("initLogins", {mozproxyOrigin}).then(resolve)
            },
          });

          // Need to allow for arbitrary network servers defined in PAC instead of a hardcoded moz-proxy.
          let channel = NetUtil.newChannel({
            uri: "http://example.com",
            loadUsingSystemPrincipal: true,
          });

          let pps = SpecialPowers.Cc["@mozilla.org/network/protocol-proxy-service;1"]
                                 .getService();

          pps.asyncResolve(channel, 0, resolveCallback);
        });
      });

      add_task(async function test_proxyAuthThenTwoHTTPAuth() {
        // Load through a single proxy with authentication required 3 different
        // pages, first with one login, other two with their own different login.
        // We expect to show just a single dialog for proxy authentication and
        // then two dialogs to authenticate to login 1 and then login 2.

        let iframe1DocPromise = promiseLoadedContentDoc(iframe1);
        let iframe2aDocPromise = promiseLoadedContentDoc(iframe2a);
        let iframe2bDocPromise = promiseLoadedContentDoc(iframe2b);

        iframe1.src = EXAMPLE_COM + "authenticate.sjs?" +
          "r=1&" +
          "user=user1name&" +
          "pass=user1pass&" +
          "realm=mochirealm&" +
          "proxy_user=proxy_user&" +
          "proxy_pass=proxy_pass&" +
          "proxy_realm=proxy_realm";
        iframe2a.src = EXAMPLE_ORG + "authenticate.sjs?" +
          "r=2&" +
          "user=user2name&" +
          "pass=user2pass&" +
          "realm=mochirealm2&" +
          "proxy_user=proxy_user&" +
          "proxy_pass=proxy_pass&" +
          "proxy_realm=proxy_realm";
        iframe2b.src = EXAMPLE_ORG + "authenticate.sjs?" +
          "r=3&" +
          "user=user2name&" +
          "pass=user2pass&" +
          "realm=mochirealm2&" +
          "proxy_user=proxy_user&" +
          "proxy_pass=proxy_pass&" +
          "proxy_realm=proxy_realm";

        let state = {
          msg: `The proxy ${mozproxyOrigin} is requesting a username and password. The site says: “proxy_realm”`,
          title: "Authentication Required",
          textValue: "proxy_user",
          passValue: "proxy_pass",
          iconClass: "authentication-icon question-icon",
          titleHidden: true,
          textHidden: false,
          passHidden: false,
          checkHidden: true,
          checkMsg: "",
          checked: false,
          focused: "textField",
          defButton: "button0",
        };
        let action = {
          buttonClick: "ok",
        };
        await handlePrompt(state, action);

        // We don't know what order these prompts appear in so get both states and check them.
        // We can't use Promise.all here since we can't start the 2nd timer in chromeScript.js until
        // the first timer is done since the timer variable gets clobbered, plus we don't want
        // different actions racing each other.
        let promptStates = [
          await handlePromptWithoutChecks(action),
          await handlePromptWithoutChecks(action),
        ];

        let expected1 = Object.assign({}, state, {
          msg: "This site is asking you to sign in. Warning: Your login information will be shared with example.com, not the website you are currently visiting.",
          textValue: "user1name",
          passValue: "user1pass",
        });

        let expected2 = Object.assign({}, state, {
          msg: "This site is asking you to sign in. Warning: Your login information will be shared with example.org, not the website you are currently visiting.",
          textValue: "user2name",
          passValue: "user2pass",
        });

        // The order isn't important.
        let expectedPromptStates = [
          expected1,
          expected2,
        ];

        is(promptStates.length, expectedPromptStates.length,
           "Check we handled the right number of prompts");
        for (let promptState of promptStates) {
          let expectedStateIndexForMessage = expectedPromptStates.findIndex(eps => {
            return eps.msg == promptState.msg;
          });
          isnot(expectedStateIndexForMessage, -1, "Check state message was found in expected array");
          let expectedPromptState = expectedPromptStates.splice(expectedStateIndexForMessage, 1)[0];
          checkPromptState(promptState, expectedPromptState);
        }

        await iframe1DocPromise;
        await iframe2aDocPromise;
        await iframe2bDocPromise;

        await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [], function() {
          let doc = this.content.document;
          let authok1 = doc.getElementById("ok").textContent;
          let proxyok1 = doc.getElementById("proxy").textContent;
          Assert.equal(authok1, "PASS", "WWW Authorization OK, frame1");
          Assert.equal(proxyok1, "PASS", "Proxy Authorization OK, frame1");
        });

        await SpecialPowers.spawn(getIframeBrowsingContext(window, 1), [], function() {
          let doc = this.content.document;
          let authok2a = doc.getElementById("ok").textContent;
          let proxyok2a = doc.getElementById("proxy").textContent;
          Assert.equal(authok2a, "PASS", "WWW Authorization OK, frame2a");
          Assert.equal(proxyok2a, "PASS", "Proxy Authorization OK, frame2a");
        });

        await SpecialPowers.spawn(getIframeBrowsingContext(window, 2), [], function() {
          let doc = this.content.document;
          let authok2b = doc.getElementById("ok").textContent;
          let proxyok2b = doc.getElementById("proxy").textContent;
          Assert.equal(authok2b, "PASS", "WWW Authorization OK, frame2b");
          Assert.equal(proxyok2b, "PASS", "Proxy Authorization OK, frame2b");
        });
      });

      add_task(async function test_threeSubframesWithSameProxyAndHTTPAuth() {
        // Load an iframe with 3 subpages all requiring the same login through
        // an authenticated proxy. We expect 2 dialogs, proxy authentication
        // and web authentication.

        let iframe1DocPromise = promiseLoadedContentDoc(iframe1);

        iframe1.src = EXAMPLE_COM + "subtst_prompt_async.html";
        iframe2a.src = "about:blank";
        iframe2b.src = "about:blank";

        let state = {
          msg: `The proxy ${mozproxyOrigin} is requesting a username and password. The site says: “proxy_realm2”`,
          title: "Authentication Required",
          textValue: "proxy_user2",
          passValue: "proxy_pass2",
          iconClass: "authentication-icon question-icon",
          titleHidden: true,
          textHidden: false,
          passHidden: false,
          checkHidden: true,
          checkMsg: "",
          checked: false,
          focused: "textField",
          defButton: "button0",
        };
        let action = {
          buttonClick: "ok",
        };
        await handlePrompt(state, action);

        Object.assign(state, {
          msg: "This site is asking you to sign in. Warning: Your login information will be shared with example.com, not the website you are currently visiting.",
          textValue: "user3name",
          passValue: "user3pass",
        });
        await handlePrompt(state, action);

        await iframe1DocPromise;

        function checkIframe(frameid) {
          let doc = this.content.document;
          let authok = doc.getElementById("ok").textContent;
          let proxyok = doc.getElementById("proxy").textContent;

          Assert.equal(authok, "PASS", "WWW Authorization OK, " + frameid);
          Assert.equal(proxyok, "PASS", "Proxy Authorization OK, " + frameid);
        }

        let parentIFrameBC = SpecialPowers.wrap(window).windowGlobalChild
                                          .browsingContext.children[0];

        let childIFrame = SpecialPowers.unwrap(parentIFrameBC.children[0]);
        await SpecialPowers.spawn(childIFrame, ["iframe1"], checkIframe);
        childIFrame = SpecialPowers.unwrap(parentIFrameBC.children[1]);
        await SpecialPowers.spawn(childIFrame, ["iframe2"], checkIframe);
        childIFrame = SpecialPowers.unwrap(parentIFrameBC.children[2]);
        await SpecialPowers.spawn(childIFrame, ["iframe3"], checkIframe);
      });

      add_task(async function test_oneFrameWithUnauthenticatedProxy() {
        // Load in the iframe page through unauthenticated proxy
        // and discard the proxy authentication. We expect to see
        // unauthenticated page content and just a single dialog.

        iframe1.src = EXAMPLE_COM + "authenticate.sjs?" +
          "user=user4name&" +
          "pass=user4pass&" +
          "realm=mochirealm4&" +
          "proxy_user=proxy_user3&" +
          "proxy_pass=proxy_pass3&" +
          "proxy_realm=proxy_realm3";

        let state = {
          msg: `The proxy ${mozproxyOrigin} is requesting a username and password. The site says: “proxy_realm3”`,
          title: "Authentication Required",
          textValue: "proxy_user3",
          passValue: "proxy_pass3",
          iconClass: "authentication-icon question-icon",
          titleHidden: true,
          textHidden: false,
          passHidden: false,
          checkHidden: true,
          checkMsg: "",
          checked: false,
          focused: "textField",
          defButton: "button0",
        };
        let action = {
          buttonClick: "cancel",
        };
        await handlePrompt(state, action);

        await promiseProxyErrorLoad(iframe1);
      });

      add_task(async function test_reloadReusingProxyAuthButCancellingHTTPAuth() {
        // Reload the frame from previous step and pass the proxy authentication
        // but cancel the WWW authentication. We should get the proxy=ok and WWW=fail
        // content as a result.
        let iframe1DocPromise = promiseLoadedContentDoc(iframe1);

        iframe1.src = EXAMPLE_COM + "authenticate.sjs?" +
          "user=user4name&" +
          "pass=user4pass&" +
          "realm=mochirealm4&" +
          "proxy_user=proxy_user3&" +
          "proxy_pass=proxy_pass3&" +
          "proxy_realm=proxy_realm3";

        let state = {
          msg: `The proxy ${mozproxyOrigin} is requesting a username and password. The site says: “proxy_realm3”`,
          title: "Authentication Required",
          textValue: "proxy_user3",
          passValue: "proxy_pass3",
          iconClass: "authentication-icon question-icon",
          titleHidden: true,
          textHidden: false,
          passHidden: false,
          checkHidden: true,
          checkMsg: "",
          checked: false,
          focused: "textField",
          defButton: "button0",
        };
        let action = {
          buttonClick: "ok",
        };
        await handlePrompt(state, action);

        Object.assign(state, {
          msg: "This site is asking you to sign in. Warning: Your login information will be shared with example.com, not the website you are currently visiting.",
          textValue: "user4name",
          passValue: "user4pass",
        });
        action = {
          buttonClick: "cancel",
        };
        await handlePrompt(state, action);

        await iframe1DocPromise;

        await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [], function() {
          let doc = this.content.document;
          let authok1 = doc.getElementById("ok").textContent;
          let proxyok1 = doc.getElementById("proxy").textContent;

          Assert.equal(authok1, "FAIL", "WWW Authorization FAILED, frame1");
          Assert.equal(proxyok1, "PASS", "Proxy Authorization OK, frame1");
        });
      });

      add_task(async function test_hugePayloadCancelled() {
        // Same as the previous two steps but let the server generate
        // huge content load to check http channel is capable to handle
        // case when auth dialog is canceled or accepted before unauthenticated
        // content data is load from the server. (This would be better to
        // implement using delay of server response).
        iframe1.src = EXAMPLE_COM + "authenticate.sjs?" +
          "user=user5name&" +
          "pass=user5pass&" +
          "realm=mochirealm5&" +
          "proxy_user=proxy_user4&" +
          "proxy_pass=proxy_pass4&" +
          "proxy_realm=proxy_realm4&" +
          "huge=1";

        let state = {
          msg: `The proxy ${mozproxyOrigin} is requesting a username and password. The site says: “proxy_realm4”`,
          title: "Authentication Required",
          textValue: "proxy_user4",
          passValue: "proxy_pass4",
          iconClass: "authentication-icon question-icon",
          titleHidden: true,
          textHidden: false,
          passHidden: false,
          checkHidden: true,
          checkMsg: "",
          checked: false,
          focused: "textField",
          defButton: "button0",
        };
        let action = {
          buttonClick: "cancel",
        };
        await handlePrompt(state, action);

        await promiseProxyErrorLoad(iframe1);
      });

      add_task(async function test_hugeProxySuccessWWWFail() {
        // Reload the frame from the previous step and let the proxy
        // authentication pass but WWW fail. We expect two dialogs
        // and an unauthenticated page content load.

        let iframe1DocPromise = promiseLoadedContentDoc(iframe1);
        iframe1.src = EXAMPLE_COM + "authenticate.sjs?" +
          "user=user5name&" +
          "pass=user5pass&" +
          "realm=mochirealm5&" +
          "proxy_user=proxy_user4&" +
          "proxy_pass=proxy_pass4&" +
          "proxy_realm=proxy_realm4&" +
          "huge=1";

        let state = {
          msg: `The proxy ${mozproxyOrigin} is requesting a username and password. The site says: “proxy_realm4”`,
          title: "Authentication Required",
          textValue: "proxy_user4",
          passValue: "proxy_pass4",
          iconClass: "authentication-icon question-icon",
          titleHidden: true,
          textHidden: false,
          passHidden: false,
          checkHidden: true,
          checkMsg: "",
          checked: false,
          focused: "textField",
          defButton: "button0",
        };
        let action = {
          buttonClick: "ok",
        };
        await handlePrompt(state, action);

        Object.assign(state, {
          msg: "This site is asking you to sign in. Warning: Your login information will be shared with example.com, not the website you are currently visiting.",
          textValue: "user5name",
          passValue: "user5pass",
        });
        action = {
          buttonClick: "cancel",
        };
        await handlePrompt(state, action);

        await iframe1DocPromise;

        await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [], function() {
          let doc = this.content.document;
          let authok1 = doc.getElementById("ok").textContent;
          let proxyok1 = doc.getElementById("proxy").textContent;
          let footnote = doc.getElementById("footnote").textContent;

          Assert.equal(authok1, "FAIL", "WWW Authorization FAILED, frame1");
          Assert.equal(proxyok1, "PASS", "Proxy Authorization OK, frame1");
          Assert.equal(footnote, "This is a footnote after the huge content fill",
             "Footnote present and loaded completely");
        });
      });

      add_task(async function test_hugeProxySuccessWWWSuccess() {
        // Reload again and let pass all authentication dialogs.
        // Check we get the authenticated content not broken by
        // the unauthenticated content.

        let iframe1DocPromise = promiseLoadedContentDoc(iframe1);
        await SpecialPowers.spawn(iframe1, [], function() {
          this.content.document.location.reload();
        });

        let state = {
          msg: "This site is asking you to sign in. Warning: Your login information will be shared with example.com, not the website you are currently visiting.",
          title: "Authentication Required",
          textValue: "user5name",
          passValue: "user5pass",
          iconClass: "authentication-icon question-icon",
          titleHidden: true,
          textHidden: false,
          passHidden: false,
          checkHidden: true,
          checkMsg: "",
          checked: false,
          focused: "textField",
          defButton: "button0",
        };
        let action = {
          buttonClick: "ok",
        };
        await handlePrompt(state, action);

        await iframe1DocPromise;

        await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [], function() {
          let doc = this.content.document;
          let authok1 = doc.getElementById("ok").textContent;
          let proxyok1 = doc.getElementById("proxy").textContent;
          let footnote = doc.getElementById("footnote").textContent;

          Assert.equal(authok1, "PASS", "WWW Authorization OK, frame1");
          Assert.equal(proxyok1, "PASS", "Proxy Authorization OK, frame1");
          Assert.equal(footnote, "This is a footnote after the huge content fill",
             "Footnote present and loaded completely");
        });
      });

      add_task(async function test_cancelSome() {
        // Check we process all challenges sent by server when
        // user cancels prompts
        let iframe1DocPromise = promiseLoadedContentDoc(iframe1);
        iframe1.src = EXAMPLE_COM + "authenticate.sjs?" +
          "user=user6name&" +
          "pass=user6pass&" +
          "realm=mochirealm6&" +
          "proxy_user=proxy_user5&" +
          "proxy_pass=proxy_pass5&" +
          "proxy_realm=proxy_realm5&" +
          "huge=1&" +
          "multiple=3";

        let state = {
          msg: `The proxy ${mozproxyOrigin} is requesting a username and password. The site says: “proxy_realm5”`,
          title: "Authentication Required",
          textValue: "proxy_user5",
          passValue: "proxy_pass5",
          iconClass: "authentication-icon question-icon",
          titleHidden: true,
          textHidden: false,
          passHidden: false,
          checkHidden: true,
          checkMsg: "",
          checked: false,
          focused: "textField",
          defButton: "button0",
        };
        let action = {
          buttonClick: "cancel",
        };
        await handlePrompt(state, action);

        action = {
          buttonClick: "cancel",
        };
        await handlePrompt(state, action);

        action = {
          buttonClick: "ok",
        };
        await handlePrompt(state, action);

        Object.assign(state, {
          msg: "This site is asking you to sign in. Warning: Your login information will be shared with example.com, not the website you are currently visiting.",
          textValue: "user6name",
          passValue: "user6pass",
        });

        action = {
          buttonClick: "cancel",
        };
        await handlePrompt(state, action);

        action = {
          buttonClick: "ok",
        };
        await handlePrompt(state, action);

        await iframe1DocPromise;
        await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [], function() {
          let doc = this.content.document;
          let authok1 = doc.getElementById("ok").textContent;
          let proxyok1 = doc.getElementById("proxy").textContent;
          let footnote = doc.getElementById("footnote").textContent;

          Assert.equal(authok1, "PASS", "WWW Authorization OK, frame1");
          Assert.equal(proxyok1, "PASS", "Proxy Authorization OK, frame1");
          Assert.equal(footnote, "This is a footnote after the huge content fill",
             "Footnote present and loaded completely");
        });

      });
    </script>
</head>
<body>
    <iframe id="iframe1"></iframe>
    <iframe id="iframe2a"></iframe>
    <iframe id="iframe2b"></iframe>
</body>
</html>
