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

"use strict";

// Tag every new WindowGlobalParent with an expando indicating whether or not
// they were an initial document when they were created for the duration of this
// test.
function wasInitialDocumentObserver(subject) {
  subject._test_wasInitialDocument = subject.isInitialDocument;
}
Services.obs.addObserver(wasInitialDocumentObserver, "window-global-created");
SimpleTest.registerCleanupFunction(function() {
  Services.obs.removeObserver(
    wasInitialDocumentObserver,
    "window-global-created"
  );
});

add_task(async function new_about_blank_tab() {
  await BrowserTestUtils.withNewTab("about:blank", async browser => {
    is(
      browser.browsingContext.currentWindowGlobal.isInitialDocument,
      false,
      "After loading an actual, final about:blank in the tab, the field is false"
    );
  });
});

add_task(async function iframe_initial_about_blank() {
  await BrowserTestUtils.withNewTab(
    "http://example.com/document-builder.sjs?html=com",
    async browser => {
      info("Create an iframe without any explicit location");
      await SpecialPowers.spawn(browser, [], async () => {
        const iframe = content.document.createElement("iframe");
        // Add the iframe to the DOM tree in order to be able to have its browsingContext
        content.document.body.appendChild(iframe);
        const { browsingContext } = iframe;

        is(
          iframe.contentDocument.isInitialDocument,
          true,
          "The field is true on just-created iframes"
        );
        let beforeLoadPromise = SpecialPowers.spawnChrome(
          [browsingContext],
          bc => [
            bc.currentWindowGlobal.isInitialDocument,
            bc.currentWindowGlobal._test_wasInitialDocument,
          ]
        );

        await new Promise(resolve => {
          iframe.addEventListener("load", resolve, { once: true });
        });
        is(
          iframe.contentDocument.isInitialDocument,
          false,
          "The field is false after having loaded the final about:blank document"
        );
        let afterLoadPromise = SpecialPowers.spawnChrome(
          [browsingContext],
          bc => [
            bc.currentWindowGlobal.isInitialDocument,
            bc.currentWindowGlobal._test_wasInitialDocument,
          ]
        );

        // Wait to await the parent process promises, so we can't miss the "load" event.
        let [beforeIsInitial, beforeWasInitial] = await beforeLoadPromise;
        is(beforeIsInitial, true, "before load is initial in parent");
        is(beforeWasInitial, true, "before load was initial in parent");
        let [afterIsInitial, afterWasInitial] = await afterLoadPromise;
        is(afterIsInitial, false, "after load is not initial in parent");
        is(afterWasInitial, true, "after load was initial in parent");
        iframe.remove();
      });

      info("Create an iframe with a cross origin location");
      const iframeBC = await SpecialPowers.spawn(browser, [], async () => {
        const iframe = content.document.createElement("iframe");
        await new Promise(resolve => {
          iframe.addEventListener("load", resolve, { once: true });
          iframe.src =
            "http://example.org/document-builder.sjs?html=org-iframe";
          content.document.body.appendChild(iframe);
        });

        return iframe.browsingContext;
      });

      is(
        iframeBC.currentWindowGlobal.isInitialDocument,
        false,
        "The field is true after having loaded the final document"
      );
    }
  );
});

add_task(async function window_open() {
  async function testWindowOpen({ browser, args, isCrossOrigin, willLoad }) {
    info(`Open popup with ${JSON.stringify(args)}`);
    const onNewTab = BrowserTestUtils.waitForNewTab(
      gBrowser,
      args[0] || "about:blank"
    );
    await SpecialPowers.spawn(
      browser,
      [args, isCrossOrigin, willLoad],
      async (args, crossOrigin, willLoad) => {
        const win = content.window.open(...args);
        is(
          win.document.isInitialDocument,
          true,
          "The field is true right after calling window.open()"
        );
        let beforeLoadPromise = SpecialPowers.spawnChrome(
          [win.browsingContext],
          bc => [
            bc.currentWindowGlobal.isInitialDocument,
            bc.currentWindowGlobal._test_wasInitialDocument,
          ]
        );

        // In cross origin, it is harder to watch for new document load, and if
        // no argument is passed no load will happen.
        if (!crossOrigin && willLoad) {
          await new Promise(r =>
            win.addEventListener("load", r, { once: true })
          );
          is(
            win.document.isInitialDocument,
            false,
            "The field becomes false right after the popup document is loaded"
          );
        }

        // Perform the await after the load to avoid missing it.
        let [beforeIsInitial, beforeWasInitial] = await beforeLoadPromise;
        is(beforeIsInitial, true, "before load is initial in parent");
        is(beforeWasInitial, true, "before load was initial in parent");
      }
    );
    const newTab = await onNewTab;
    const windowGlobal =
      newTab.linkedBrowser.browsingContext.currentWindowGlobal;
    if (willLoad) {
      is(
        windowGlobal.isInitialDocument,
        false,
        "The field is false in the parent process after having loaded the final document"
      );
    } else {
      is(
        windowGlobal.isInitialDocument,
        true,
        "The field remains true in the parent process as nothing will be loaded"
      );
    }
    BrowserTestUtils.removeTab(newTab);
  }

  await BrowserTestUtils.withNewTab(
    "http://example.com/document-builder.sjs?html=com",
    async browser => {
      info("Use window.open() with cross-origin document");
      await testWindowOpen({
        browser,
        args: ["http://example.org/document-builder.sjs?html=org-popup"],
        isCrossOrigin: true,
        willLoad: true,
      });

      info("Use window.open() with same-origin document");
      await testWindowOpen({
        browser,
        args: ["http://example.com/document-builder.sjs?html=com-popup"],
        isCrossOrigin: false,
        willLoad: true,
      });

      info("Use window.open() with final about:blank document");
      await testWindowOpen({
        browser,
        args: ["about:blank"],
        isCrossOrigin: false,
        willLoad: true,
      });

      info("Use window.open() with no argument");
      await testWindowOpen({
        browser,
        args: [],
        isCrossOrigin: false,
        willLoad: false,
      });
    }
  );
});

add_task(async function document_open() {
  await BrowserTestUtils.withNewTab(
    "http://example.com/document-builder.sjs?html=com",
    async browser => {
      is(browser.browsingContext.currentWindowGlobal.isInitialDocument, false);
      await SpecialPowers.spawn(browser, [], async () => {
        const iframe = content.document.createElement("iframe");
        // Add the iframe to the DOM tree in order to be able to have its browsingContext
        content.document.body.appendChild(iframe);
        const { browsingContext } = iframe;

        // Check the state before the call in both parent and content.
        is(
          iframe.contentDocument.isInitialDocument,
          true,
          "Is an initial document before calling document.open"
        );
        let beforeOpenParentPromise = SpecialPowers.spawnChrome(
          [browsingContext],
          bc => [
            bc.currentWindowGlobal.isInitialDocument,
            bc.currentWindowGlobal._test_wasInitialDocument,
            bc.currentWindowGlobal.innerWindowId,
          ]
        );

        // Run the `document.open` call with reduced permissions.
        iframe.contentWindow.eval(`
          document.open();
          document.write("new document");
          document.close();
        `);

        is(
          iframe.contentDocument.isInitialDocument,
          false,
          "Is no longer an initial document after calling document.open"
        );
        let [
          afterIsInitial,
          afterWasInitial,
          afterID,
        ] = await SpecialPowers.spawnChrome([browsingContext], bc => [
          bc.currentWindowGlobal.isInitialDocument,
          bc.currentWindowGlobal._test_wasInitialDocument,
          bc.currentWindowGlobal.innerWindowId,
        ]);
        let [
          beforeIsInitial,
          beforeWasInitial,
          beforeID,
        ] = await beforeOpenParentPromise;
        is(beforeIsInitial, true, "Should be initial before in the parent");
        is(beforeWasInitial, true, "Was initial before in the parent");
        is(afterIsInitial, false, "Should not be initial after in the parent");
        is(afterWasInitial, true, "Was initial after in the parent");
        is(beforeID, afterID, "Should be the same WindowGlobalParent");
      });
    }
  );
});

add_task(async function windowless_browser() {
  info("Create a Windowless browser");
  const browser = Services.appShell.createWindowlessBrowser(false);
  const { browsingContext } = browser;
  is(
    browsingContext.currentWindowGlobal.isInitialDocument,
    true,
    "The field is true for a freshly created WindowlessBrowser"
  );
  is(
    browser.currentURI.spec,
    "about:blank",
    "The location is immediately set to about:blank"
  );

  const principal = Services.scriptSecurityManager.getSystemPrincipal();
  browser.docShell.createAboutBlankContentViewer(principal, principal);
  is(
    browsingContext.currentWindowGlobal.isInitialDocument,
    false,
    "The field becomes false when creating an artificial blank document"
  );

  info("Load a final about:blank document in it");
  const onLocationChange = new Promise(resolve => {
    let wpl = {
      QueryInterface: ChromeUtils.generateQI([
        "nsIWebProgressListener",
        "nsISupportsWeakReference",
      ]),
      onLocationChange() {
        browsingContext.webProgress.removeProgressListener(
          wpl,
          Ci.nsIWebProgress.NOTIFY_ALL
        );
        resolve();
      },
    };
    browsingContext.webProgress.addProgressListener(
      wpl,
      Ci.nsIWebProgress.NOTIFY_ALL
    );
  });
  browser.loadURI("about:blank", { triggeringPrincipal: principal });
  info("Wait for the location change");
  await onLocationChange;
  is(
    browsingContext.currentWindowGlobal.isInitialDocument,
    false,
    "The field is false after the location change event"
  );
  browser.close();
});
