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

"use strict";

const URL = HTTPROOT + "browser_frametree_sample.html";
const URL_FRAMESET = HTTPROOT + "browser_frametree_sample_frameset.html";

/**
 * This ensures that loading a page normally, aborting a page load, reloading
 * a page, navigating using the bfcache, and ignoring frames that were
 * created dynamically work as expect. We expect the frame tree to be reset
 * when a page starts loading and we also expect a valid frame tree to exist
 * when it has stopped loading.
 */
add_task(async function test_frametree() {
  const FRAME_TREE_SINGLE = { href: URL };
  const FRAME_TREE_FRAMESET = {
    href: URL_FRAMESET,
    children: [{href: URL}, {href: URL}, {href: URL}]
  };

  // Create a tab with a single frame.
  let tab = BrowserTestUtils.addTab(gBrowser, URL);
  let browser = tab.linkedBrowser;
  await promiseNewFrameTree(browser);
  await checkFrameTree(browser, FRAME_TREE_SINGLE,
    "loading a page resets and creates the frame tree correctly");

  // Load the frameset and create two frames dynamically, the first on
  // DOMContentLoaded and the second on load.
  await sendMessage(browser, "ss-test:createDynamicFrames", {id: "frames", url: URL});
  browser.loadURI(URL_FRAMESET);
  await promiseNewFrameTree(browser);
  await checkFrameTree(browser, FRAME_TREE_FRAMESET,
    "dynamic frames created on or after the load event are ignored");

  // Go back to the previous single-frame page. There will be no load event as
  // the page is still in the bfcache. We thus make sure this type of navigation
  // resets the frame tree.
  browser.goBack();
  await promiseNewFrameTree(browser);
  await checkFrameTree(browser, FRAME_TREE_SINGLE,
    "loading from bfache resets and creates the frame tree correctly");

  // Load the frameset again but abort the load early.
  // The frame tree should still be reset and created.
  browser.loadURI(URL_FRAMESET);
  executeSoon(() => browser.stop());
  await promiseNewFrameTree(browser);

  // Load the frameset and check the tree again.
  await sendMessage(browser, "ss-test:createDynamicFrames", {id: "frames", url: URL});
  browser.loadURI(URL_FRAMESET);
  await promiseNewFrameTree(browser);
  await checkFrameTree(browser, FRAME_TREE_FRAMESET,
    "reloading a page resets and creates the frame tree correctly");

  // Cleanup.
  gBrowser.removeTab(tab);
});

/**
 * This test ensures that we ignore frames that were created dynamically at or
 * after the load event. SessionStore can't handle these and will not restore
 * or collect any data for them.
 */
add_task(async function test_frametree_dynamic() {
  // The frame tree as expected. The first two frames are static
  // and the third one was created on DOMContentLoaded.
  const FRAME_TREE = {
    href: URL_FRAMESET,
    children: [{href: URL}, {href: URL}, {href: URL}]
  };
  const FRAME_TREE_REMOVED = {
    href: URL_FRAMESET,
    children: [{href: URL}, {href: URL}]
  };

  // Add an empty tab for a start.
  let tab = BrowserTestUtils.addTab(gBrowser, "about:blank");
  let browser = tab.linkedBrowser;
  await promiseBrowserLoaded(browser);

  // Create dynamic frames on "DOMContentLoaded" and on "load".
  await sendMessage(browser, "ss-test:createDynamicFrames", {id: "frames", url: URL});
  browser.loadURI(URL_FRAMESET);
  await promiseNewFrameTree(browser);

  // Check that the frame tree does not contain the frame created on "load".
  // The two static frames and the one created on DOMContentLoaded must be in
  // the tree.
  await checkFrameTree(browser, FRAME_TREE,
    "frame tree contains first four frames");

  // Remove the last frame in the frameset.
  await sendMessage(browser, "ss-test:removeLastFrame", {id: "frames"});
  // Check that the frame tree didn't change.
  await checkFrameTree(browser, FRAME_TREE,
    "frame tree contains first four frames");

  // Remove the last frame in the frameset.
  await sendMessage(browser, "ss-test:removeLastFrame", {id: "frames"});
  // Check that the frame tree excludes the removed frame.
  await checkFrameTree(browser, FRAME_TREE_REMOVED,
    "frame tree contains first three frames");

  // Cleanup.
  gBrowser.removeTab(tab);
});

/**
 * Checks whether the current frame hierarchy of a given |browser| matches the
 * |expected| frame hierarchy.
 */
function checkFrameTree(browser, expected, msg) {
  return sendMessage(browser, "ss-test:mapFrameTree").then(tree => {
    is(JSON.stringify(tree), JSON.stringify(expected), msg);
  });
}

/**
 * Returns a promise that will be resolved when the given |browser| has loaded
 * and we received messages saying that its frame tree has been reset and
 * recollected.
 */
function promiseNewFrameTree(browser) {
  let reset = promiseContentMessage(browser, "ss-test:onFrameTreeCollected");
  let collect = promiseContentMessage(browser, "ss-test:onFrameTreeCollected");
  return Promise.all([reset, collect]);
}
