/**
 * Tests that chromeFlags are set properly on windows that are
 * being opened from content.
 */

// The following features set chrome flags on new windows and are
// supported by web content. The schema for each property on this
// object is as follows:
//
// <feature string>: {
//   flag: <associated nsIWebBrowserChrome flag>,
//   defaults_to: <what this feature defaults to normally>
// }
const ALLOWED = {
  "toolbar": {
    flag: Ci.nsIWebBrowserChrome.CHROME_TOOLBAR,
    defaults_to: true,
  },
  "personalbar": {
    flag: Ci.nsIWebBrowserChrome.CHROME_PERSONAL_TOOLBAR,
    defaults_to: true,
  },
  "menubar": {
    flag: Ci.nsIWebBrowserChrome.CHROME_MENUBAR,
    defaults_to: true,
  },
  "scrollbars": {
    flag: Ci.nsIWebBrowserChrome.CHROME_SCROLLBARS,
    defaults_to: false,
  },
  "minimizable": {
    flag: Ci.nsIWebBrowserChrome.CHROME_WINDOW_MIN,
    defaults_to: true,
  },
};

// Construct a features string that flips all ALLOWED features
// to not be their defaults.
const ALLOWED_STRING = Object.keys(ALLOWED).map(feature => {
  let toValue = ALLOWED[feature].defaults_to ? "no" : "yes";
  return `${feature}=${toValue}`;
}).join(",");

// The following are not allowed from web content, at least
// in the default case (since some are disabled by default
// via the dom.disable_window_open_feature pref branch).
const DISALLOWED = {
  "location": {
    flag: Ci.nsIWebBrowserChrome.CHROME_LOCATIONBAR,
    defaults_to: true,
  },
  "chrome": {
    flag: Ci.nsIWebBrowserChrome.CHROME_OPENAS_CHROME,
    defaults_to: false,
  },
  "dialog": {
    flag: Ci.nsIWebBrowserChrome.CHROME_OPENAS_DIALOG,
    defaults_to: false,
  },
  "private": {
    flag: Ci.nsIWebBrowserChrome.CHROME_PRIVATE_WINDOW,
    defaults_to: false,
  },
  "non-private": {
    flag: Ci.nsIWebBrowserChrome.CHROME_NON_PRIVATE_WINDOW,
    defaults_to: false,
  },
  // "all":
  //   checked manually, since this is an aggregate
  //   flag.
  //
  // "remote":
  //   checked manually, since its default value will
  //   depend on whether or not e10s is enabled by default.
  "popup": {
    flag: Ci.nsIWebBrowserChrome.CHROME_WINDOW_POPUP,
    defaults_to: false,
  },
  "alwaysLowered": {
    flag: Ci.nsIWebBrowserChrome.CHROME_WINDOW_LOWERED,
    defaults_to: false,
  },
  "z-lock": {
    flag: Ci.nsIWebBrowserChrome.CHROME_WINDOW_LOWERED, // Renamed to alwaysLowered
    defaults_to: false,
  },
  "alwaysRaised": {
    flag: Ci.nsIWebBrowserChrome.CHROME_WINDOW_RAISED,
    defaults_to: false,
  },
  "suppressanimation": {
    flag: Ci.nsIWebBrowserChrome.CHROME_SUPPRESS_ANIMATION,
    defaults_to: false,
  },
  "extrachrome": {
    flag: Ci.nsIWebBrowserChrome.CHROME_EXTRA,
    defaults_to: false,
  },
  "centerscreen": {
    flag: Ci.nsIWebBrowserChrome.CHROME_CENTER_SCREEN,
    defaults_to: false,
  },
  "dependent": {
    flag: Ci.nsIWebBrowserChrome.CHROME_DEPENDENT,
    defaults_to: false,
  },
  "modal": {
    flag: Ci.nsIWebBrowserChrome.CHROME_MODAL,
    defaults_to: false,
  },
  "titlebar": {
    flag: Ci.nsIWebBrowserChrome.CHROME_TITLEBAR,
    defaults_to: true,
  },
  "close": {
    flag: Ci.nsIWebBrowserChrome.CHROME_WINDOW_CLOSE,
    defaults_to: true,
  },
  "resizable": {
    flag: Ci.nsIWebBrowserChrome.CHROME_WINDOW_RESIZE,
    defaults_to: true,
  },
  "status": {
    flag: Ci.nsIWebBrowserChrome.CHROME_STATUSBAR,
    defaults_to: true,
  },
};

// This magic value of 2 means that by default, when content tries
// to open a new window, it'll actually open in a new window instead
// of a new tab.
Services.prefs.setIntPref("browser.link.open_newwindow", 2);
registerCleanupFunction(() => {
  Services.prefs.clearUserPref("browser.link.open_newwindow");
});

/**
 * Given some nsIDOMWindow for a window running in the parent
 * process, return the nsIWebBrowserChrome chrome flags for
 * the associated XUL window.
 *
 * @param win (nsIDOMWindow)
 *        Some window in the parent process.
 * @returns int
 */
function getParentChromeFlags(win) {
  return win.QueryInterface(Ci.nsIInterfaceRequestor)
            .getInterface(Ci.nsIWebNavigation)
            .QueryInterface(Ci.nsIDocShellTreeItem)
            .treeOwner
            .QueryInterface(Ci.nsIInterfaceRequestor)
            .getInterface(Ci.nsIXULWindow)
            .chromeFlags;
}

/**
 * Given some nsIDOMWindow for a window running in the parent process,
 * asynchronously return the nsIWebBrowserChrome chrome flags for the
 * associated content window.
 *
 * @param win (nsIDOMWindow)
 * @returns int
 */
function getContentChromeFlags(win) {
  let b = win.gBrowser.selectedBrowser;
  return ContentTask.spawn(b, null, async function() {
    // Content scripts provide docShell as a global.
    /* global docShell */
    docShell.QueryInterface(Ci.nsIInterfaceRequestor);
    try {
      // This will throw if we're not a remote browser.
      return docShell.getInterface(Ci.nsITabChild)
                      .QueryInterface(Ci.nsIWebBrowserChrome)
                      .chromeFlags;
    } catch (e) {
      // This must be a non-remote browser...
      return docShell.QueryInterface(Ci.nsIDocShellTreeItem)
                      .treeOwner
                      .QueryInterface(Ci.nsIWebBrowserChrome)
                      .chromeFlags;
    }
  });
}

/**
 * For some chromeFlags, ensures that flags that are in the
 * ALLOWED group were modified, and that flags in the DISALLOWED
 * group were not modified.
 *
 * @param chromeFlags (int)
 *        Some chromeFlags to check.
 */
function assertContentFlags(chromeFlags) {
  for (let feature in ALLOWED) {
    let flag = ALLOWED[feature].flag;

    if (ALLOWED[feature].defaults_to) {
      // The feature is supposed to default to true, so we should
      // have been able to flip it off.
      Assert.ok(!(chromeFlags & flag),
                `Expected feature ${feature} to be disabled`);
    } else {
      // The feature is supposed to default to false, so we should
      // have been able to flip it on.
      Assert.ok((chromeFlags & flag),
                `Expected feature ${feature} to be enabled`);
    }
  }

  for (let feature in DISALLOWED) {
    let flag = DISALLOWED[feature].flag;
    Assert.ok(flag, "Expected flag to be a non-zeroish value");
    if (DISALLOWED[feature].defaults_to) {
      // The feature is supposed to default to true, so it should
      // stay true.
      Assert.ok((chromeFlags & flag),
                `Expected feature ${feature} to be unchanged`);
    } else {
      // The feature is supposed to default to false, so it should
      // stay false.
      Assert.ok(!(chromeFlags & flag),
                `Expected feature ${feature} to be unchanged`);
    }
  }
}

/**
 * Opens a window from content using window.open with the
 * features computed from ALLOWED and DISALLOWED. The computed
 * feature string attempts to flip every feature away from their
 * default.
 */
add_task(async function test_new_remote_window_flags() {
  // Construct a features string that flips all DISALLOWED features
  // to not be their defaults.
  const DISALLOWED_STRING = Object.keys(DISALLOWED).map(feature => {
    let toValue = DISALLOWED[feature].defaults_to ? "no" : "yes";
    return `${feature}=${toValue}`;
  }).join(",");

  const FEATURES = [ALLOWED_STRING, DISALLOWED_STRING].join(",");

  const SCRIPT_PAGE = `data:text/html,<script>window.open("about:blank", "_blank", "${FEATURES}");</script>`;
  const SCRIPT_PAGE_FOR_CHROME_ALL = `data:text/html,<script>window.open("about:blank", "_blank", "all");</script>`;

  let newWinPromise = BrowserTestUtils.waitForNewWindow();

  await BrowserTestUtils.withNewTab({
    gBrowser,
    url: SCRIPT_PAGE,
  }, async function(browser) {
    let win = await newWinPromise;
    let parentChromeFlags = getParentChromeFlags(win);
    assertContentFlags(parentChromeFlags);

    if (win.gMultiProcessBrowser) {
      Assert.ok(parentChromeFlags &
                Ci.nsIWebBrowserChrome.CHROME_REMOTE_WINDOW,
                "Should be remote by default");
    } else {
      Assert.ok(!(parentChromeFlags &
                  Ci.nsIWebBrowserChrome.CHROME_REMOTE_WINDOW),
                "Should not be remote by default");
    }

    // Confusingly, chromeFlags also exist in the content process
    // as part of the TabChild, so we have to check those too.
    let contentChromeFlags = await getContentChromeFlags(win);
    assertContentFlags(contentChromeFlags);
    Assert.ok(!(contentChromeFlags &
                Ci.nsIWebBrowserChrome.CHROME_REMOTE_WINDOW),
              "Should not be remote in the content process.");

    await BrowserTestUtils.closeWindow(win);
  });

  // We check "all" manually, since that's an aggregate flag
  // and doesn't fit nicely into the ALLOWED / DISALLOWED scheme
  newWinPromise = BrowserTestUtils.waitForNewWindow();

  await BrowserTestUtils.withNewTab({
    gBrowser,
    url: SCRIPT_PAGE_FOR_CHROME_ALL,
  }, async function(browser) {
    let win = await newWinPromise;
    let parentChromeFlags = getParentChromeFlags(win);
    Assert.notEqual((parentChromeFlags & Ci.nsIWebBrowserChrome.CHROME_ALL),
                    Ci.nsIWebBrowserChrome.CHROME_ALL,
                    "Should not have been able to set CHROME_ALL");
    await BrowserTestUtils.closeWindow(win);
  });
});

/**
 * Opens a window with some chrome flags specified, which should not affect
 * scrollbars flag which defaults to true when not disabled explicitly.
 */
add_task(async function test_scrollbars_flag() {
  const SCRIPT = 'window.open("about:blank", "_blank", "toolbar=0");';
  const SCRIPT_PAGE = `data:text/html,<script>${SCRIPT}</script>`;

  let newWinPromise = BrowserTestUtils.waitForNewWindow();
  await BrowserTestUtils.withNewTab({
    gBrowser,
    url: SCRIPT_PAGE,
  }, async function(browser) {
    let win = await newWinPromise;

    let parentChromeFlags = getParentChromeFlags(win);
    Assert.ok(parentChromeFlags & Ci.nsIWebBrowserChrome.CHROME_SCROLLBARS,
              "Should have scrollbars when not disabled explicitly");

    let contentChromeFlags = await getContentChromeFlags(win);
    Assert.ok(contentChromeFlags & Ci.nsIWebBrowserChrome.CHROME_SCROLLBARS,
              "Should have scrollbars when not disabled explicitly");

    await BrowserTestUtils.closeWindow(win);
  });
});
