<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
                 type="text/css"?>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1276553
-->
<window title="Mozilla Bug 1276553"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="run();">

  <!-- test code goes here -->
  <script type="application/javascript">
  <![CDATA[

  const {interfaces: Ci, classes: Cc, results: Cr, utils: Cu} = Components;
  Cu.import("resource://testing-common/TestUtils.jsm");
  Cu.import("resource://testing-common/ContentTask.jsm");
  Cu.import("resource://testing-common/BrowserTestUtils.jsm");
  Cu.import("resource://gre/modules/Task.jsm");
  ContentTask.setTestScope(window.opener.wrappedJSObject);

  let imports = ['SimpleTest', 'SpecialPowers', 'ok', 'is', 'info'];
  for (let name of imports) {
    window[name] = window.opener.wrappedJSObject[name];
  }

  /** Test for Bug 1276553 **/
  function run() {
    new Promise(resolve => SpecialPowers.pushPrefEnv(
      {'set' : [[ 'browser.groupedhistory.enabled', true ]]}, resolve))
    .then(() => test(false))
    .then(() => test(true))
    .then(() => {
      window.close();
      SimpleTest.finish();
    });
  }

  function test(remote) {
    let act, bg1, bg2;
    return Promise.resolve()

    // create first browser with 1 entry (which will always be the active one)
    .then(() => info('TEST-INFO | test create active browser, remote=' + remote))
    .then(() => createBrowser('pen', remote))
    .then(b => act = b)
    .then(() => verifyBrowser(act, 'pen'       /* title */,
                                    0          /* index */,
                                    1          /* length */,
                                    false      /* canGoBack */,
                                    false      /* canGoForward */,
                                    false      /* partial */ ))

    // create background browser 1 with 1 entry
    .then(() => info('TEST-INFO | test create background browser 1, remote=' + remote))
    .then(() => createBrowser('pineapple', remote))
    .then(b => bg1 = b)
    .then(() => verifyBrowser(bg1, 'pineapple' /* title */,
                                    0          /* index */,
                                    1          /* length */,
                                    false      /* canGoBack */,
                                    false      /* canGoForward */,
                                    false      /* partial */ ))

     // create background browser 2 with 2 entries
    .then(() => info('TEST-INFO | test create background browser 2, remote=' + remote))
    .then(() => createBrowser('apple', remote))
    .then(b => bg2 = b)
    .then(() => verifyBrowser(bg2, 'apple'     /* title */,
                                    0          /* index */,
                                    1          /* length */,
                                    false      /* canGoBack */,
                                    false      /* canGoForward */,
                                    false      /* partial */ ))
    .then(() => loadURI(bg2, getDummyHtml('pencil')))
    .then(() => verifyBrowser(bg2, 'pencil'    /* title */,
                                    1          /* index */,
                                    2          /* length */,
                                    true       /* canGoBack */,
                                    false      /* canGoForward */,
                                    false      /* partial */ ))

    // merge to 2 entries pen-pineapple
    .then(() => info('TEST-INFO | test merge history, remote=' + remote))
    .then(() => mergeHistory(act, bg1))
    .then(() => verifyBrowser(act, 'pineapple' /* title */,
                                    0          /* index */,
                                    1          /* length */,
                                    true       /* canGoBack */,
                                    false      /* canGoForward */,
                                    true       /* partial */,
                                    1          /* offset */,
                                    2          /* globalLength */ ))

    // merge to 4 entries pen-pineapple-apple-pencil
    .then(() => mergeHistory(act, bg2))
    .then(() => verifyBrowser(act, 'pencil'    /* title */,
                                    1          /* index */,
                                    2          /* length */,
                                    true       /* canGoBack */,
                                    false      /* canGoForward */,
                                    true       /* partial */,
                                    2          /* offset */,
                                    4          /* globalLength */ ))

    // test go back
    .then(() => info('TEST-INFO | test history go back, remote=' + remote))
    .then(() => wrapHistoryNavFn(act, act.goBack.bind(act)))
    .then(() => verifyBrowser(act, 'apple'     /* title */,
                                    0          /* index */,
                                    2          /* length */,
                                    true       /* canGoBack */,
                                    true       /* canGoForward */,
                                    true       /* partial */,
                                    2          /* offset */,
                                    4          /* globalLength */ ))
    // XXX The 2nd pageshow comes from reload as current index of the active
    // partial history remains the same
    .then(() => wrapHistoryNavFn(act, act.goBack.bind(act), true))
    .then(() => verifyBrowser(act, 'pineapple' /* title */,
                                    0          /* index */,
                                    1          /* length */,
                                    true       /* canGoBack */,
                                    true       /* canGoForward */,
                                    true       /* partial */,
                                    1          /* offset */,
                                    4          /* globalLength */ ))
    .then(() => wrapHistoryNavFn(act, act.goBack.bind(act), true))
    .then(() => verifyBrowser(act, 'pen'       /* title */,
                                    0          /* index */,
                                    1          /* length */,
                                    false      /* canGoBack */,
                                    true       /* canGoForward */,
                                    true       /* partial */,
                                    0          /* offset */,
                                    4          /* globalLength */ ))

    // test go forward
    .then(() => info('TEST-INFO | test history go forward, remote=' + remote))
    .then(() => wrapHistoryNavFn(act, act.goForward.bind(act), true))
    .then(() => verifyBrowser(act, 'pineapple' /* title */,
                                    0          /* index */,
                                    1          /* length */,
                                    true       /* canGoBack */,
                                    true       /* canGoForward */,
                                    true       /* partial */,
                                    1          /* offset */,
                                    4          /* globalLength */ ))
    .then(() => wrapHistoryNavFn(act, act.goForward.bind(act), true))
    .then(() => verifyBrowser(act, 'apple'     /* title */,
                                    0          /* index */,
                                    2          /* length */,
                                    true       /* canGoBack */,
                                    true       /* canGoForward */,
                                    true       /* partial */,
                                    2          /* offset */,
                                    4          /* globalLength */ ))
    .then(() => wrapHistoryNavFn(act, act.goForward.bind(act)))
    .then(() => verifyBrowser(act, 'pencil'    /* title */,
                                    1          /* index */,
                                    2          /* length */,
                                    true       /* canGoBack */,
                                    false      /* canGoForward */,
                                    true       /* partial */,
                                    2          /* offset */,
                                    4          /* globalLength */ ))

    // test goto index
    .then(() => info('TEST-INFO | test history goto index, remote=' + remote))
    .then(() => wrapHistoryNavFn(act, act.gotoIndex.bind(act, 0), true))
    .then(() => verifyBrowser(act, 'pen'       /* title */,
                                    0          /* index */,
                                    1          /* length */,
                                    false      /* canGoBack */,
                                    true       /* canGoForward */,
                                    true       /* partial */,
                                    0          /* offset */,
                                    4          /* globalLength */ ))
    // expect 2 pageshow since we're also changing mIndex of the partial history
    .then(() => wrapHistoryNavFn(act, act.gotoIndex.bind(act, 2), true, 2))
    .then(() => verifyBrowser(act, 'apple'     /* title */,
                                    0          /* index */,
                                    2          /* length */,
                                    true       /* canGoBack */,
                                    true       /* canGoForward */,
                                    true       /* partial */,
                                    2          /* offset */,
                                    4          /* globalLength */ ))
    .then(() => wrapHistoryNavFn(act, act.gotoIndex.bind(act, 1), true))
    .then(() => verifyBrowser(act, 'pineapple' /* title */,
                                    0          /* index */,
                                    1          /* length */,
                                    true       /* canGoBack */,
                                    true       /* canGoForward */,
                                    true       /* partial */,
                                    1          /* offset */,
                                    4          /* globalLength */ ))
    // expect 2 pageshow since we're also changing mIndex of the partial history
    .then(() => wrapHistoryNavFn(act, act.gotoIndex.bind(act, 3), true, 2))
    .then(() => verifyBrowser(act, 'pencil'    /* title */,
                                    1          /* index */,
                                    2          /* length */,
                                    true       /* canGoBack */,
                                    false      /* canGoForward */,
                                    true       /* partial */,
                                    2          /* offset */,
                                    4          /* globalLength */ ))

    // test history change to 3 entries pen-pineapple-banana
    .then(() => info('TEST-INFO | test history change, remote=' + remote))
    .then(() => wrapHistoryNavFn(act, act.gotoIndex.bind(act, 1), true))
    .then(() => verifyBrowser(act, 'pineapple' /* title */,
                                    0          /* index */,
                                    1          /* length */,
                                    true       /* canGoBack */,
                                    true       /* canGoForward */,
                                    true       /* partial */,
                                    1          /* offset */,
                                    4          /* globalLength */ ))
    .then(() => loadURI(act, getDummyHtml('banana')))
    .then(() => verifyBrowser(act, 'banana'    /* title */,
                                    1          /* index */,
                                    2          /* length */,
                                    true       /* canGoBack */,
                                    false      /* canGoForward */,
                                    true       /* partial */,
                                    1          /* offset */,
                                    3          /* globalLength */ ))
  }

  function getDummyHtml(title) {
    return 'data:text/html;charset=UTF-8,' +
     '<html><head><title>' + title + '</title></head></html>'
  }

  function createBrowser(title, remote) {
    let browser = document.createElement('browser');
    browser.setAttribute('type', 'content');
    browser.setAttribute('remote', remote);
    browser.setAttribute('src', getDummyHtml(title));
    document.getElementById('stack').appendChild(browser);
    return BrowserTestUtils.browserLoaded(browser)
           .then(() => {
             browser.messageManager.loadFrameScript('data:,' +
               'addEventListener("pageshow", () => sendAsyncMessage("test:pageshow", null), false);' +
               'addEventListener("pagehide", () => sendAsyncMessage("test:pagehide", null), false);',
               true);
           })
           .then(() => {
             // a trick to ensure webProgress object is created for e10s case
             ok(browser.webProgress, 'check browser.webProgress exists');
             return browser;
           });
  }

  function loadURI(browser, uri) {
    let promise = BrowserTestUtils.browserLoaded(browser, false, uri);
    browser.loadURI(uri);
    return promise;
  }

  function mergeHistory(b1, b2) {
    let promises = [];
    let pagehide1, pagehide2;

    // For swapping there should be a pagehide followed by a pageshow.
    promises.push(BrowserTestUtils.waitForMessage(b1.messageManager, 'test:pagehide', msg => pagehide1 = true));
    promises.push(BrowserTestUtils.waitForMessage(b2.messageManager, 'test:pagehide', msg => pagehide2 = true));
    promises.push(BrowserTestUtils.waitForMessage(b1.messageManager, 'test:pageshow', msg => pagehide1));
    promises.push(BrowserTestUtils.waitForMessage(b2.messageManager, 'test:pageshow', msg => pagehide2));

    // For swapping remote browsers, we'll also receive Content:LocationChange
    if (b1.isRemoteBrowser) {
      promises.push(BrowserTestUtils.waitForMessage(b1.messageManager, 'Content:LocationChange'));
    }

    promises.push(Promise.resolve().then(() => {
      let f1 = b1.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
      let f2 = b2.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
      f1.appendPartialSessionHistoryAndSwap(f2);
    }));

    return Promise.all(promises);
  }

  function wrapHistoryNavFn(browser, navFn, expectSwap = false, expectPageshowCount = 1) {
    let promises = [];
    let pagehide = false;
    let pageshowCount = 0;

    if (expectSwap) {
      // For swapping there should be a pagehide followed by a pageshow.
      promises.push(BrowserTestUtils.waitForMessage(browser.messageManager,
        'test:pagehide', msg => pagehide = true));

      // For swapping remote browsers, we'll also receive Content:LocationChange
      if (browser.isRemoteBrowser) {
        promises.push(BrowserTestUtils.waitForMessage(browser.messageManager,
          'Content:LocationChange'));
      }
    }
    promises.push(BrowserTestUtils.waitForMessage(browser.messageManager,
      'test:pageshow', msg => {
        // Only count events after pagehide for swapping case.
        if (!expectSwap || pagehide) {
          return !--expectPageshowCount;
        }
        return false;
      }));
    promises.push(Task.spawn(navFn));

    return Promise.all(promises);
  }

  function verifyBrowser(browser, title, index, length, canGoBack, canGoForward,
                         partial, offset = 0, globalLength = length) {
    is(browser.canGoBack, canGoBack, 'check browser.canGoBack');
    is(browser.canGoForward, canGoForward, 'check browser.canGoForward');
    if (partial) {
      let frameLoader = browser.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
      is(frameLoader.groupedSessionHistory.count, globalLength, 'check groupedSHistory.count');
    }

    return ContentTask.spawn(browser,
                             { title, index, length, canGoBack, canGoForward, partial, offset, globalLength },
                             ({ title, index, length, canGoBack, canGoForward, partial, offset, globalLength }) => {
      let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
      let shistory = webNav.sessionHistory;
      is(content.document.title, title, 'check title');
      is(webNav.canGoBack, canGoBack, 'check webNav.canGoBack');
      is(webNav.canGoForward, canGoForward, 'check webNav.canGoForward');
      is(shistory.index, index, 'check shistory.index');
      is(shistory.count, length, 'check shistory.count');
      is(shistory.isPartial, partial, 'check shistory.isPartial');
      is(shistory.globalIndexOffset, offset, 'check shistory.globalIndexOffset');
      is(shistory.globalCount, globalLength, 'check shistory.globalCount');
    });
  }

  ]]>
  </script>
  <stack id="stack" flex="1" />
</window>