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

/**
 * Test for Bug 670318
 *
 * When LoadEntry() is called on a browser that has multiple duplicate history
 * entries, history.index can end up out of range (>= history.count).
 */

const URL =
  "http://mochi.test:8888/browser/docshell/test/browser/file_bug670318.html";

add_task(async function test() {
  await BrowserTestUtils.withNewTab(
    { gBrowser, url: "about:blank" },
    async function(browser) {
      if (!SpecialPowers.Services.appinfo.sessionHistoryInParent) {
        await ContentTask.spawn(browser, URL, async function(URL) {
          let history = docShell.QueryInterface(Ci.nsIWebNavigation)
            .sessionHistory;
          let count = 0;

          let testDone = {};
          testDone.promise = new Promise(resolve => {
            testDone.resolve = resolve;
          });

          // Since listener implements nsISupportsWeakReference, we are
          // responsible for keeping it alive so that the GC doesn't clear
          // it before the test completes. We do this by anchoring the listener
          // to the message manager, and clearing it just before the test
          // completes.
          this._testListener = {
            owner: this,
            OnHistoryNewEntry(aNewURI) {
              info("OnHistoryNewEntry " + aNewURI.spec + ", " + count);
              if (aNewURI.spec == URL && 5 == ++count) {
                addEventListener(
                  "load",
                  function onLoad() {
                    Assert.ok(
                      history.index < history.count,
                      "history.index is valid"
                    );
                    testDone.resolve();
                  },
                  { capture: true, once: true }
                );

                history.legacySHistory.removeSHistoryListener(
                  this.owner._testListener
                );
                delete this.owner._testListener;
                this.owner = null;
                content.setTimeout(() => {
                  content.location.reload();
                }, 0);
              }
            },

            OnHistoryReload: () => true,
            OnHistoryGotoIndex: () => {},
            OnHistoryPurge: () => {},
            OnHistoryReplaceEntry: () => {
              // The initial load of about:blank causes a transient entry to be
              // created, so our first navigation to a real page is a replace
              // instead of a new entry.
              ++count;
            },

            QueryInterface: ChromeUtils.generateQI([
              "nsISHistoryListener",
              "nsISupportsWeakReference",
            ]),
          };

          history.legacySHistory.addSHistoryListener(this._testListener);
          content.location = URL;

          await testDone.promise;
        });

        return;
      }

      let history = browser.browsingContext.sessionHistory;
      let count = 0;

      let testDone = {};
      testDone.promise = new Promise(resolve => {
        testDone.resolve = resolve;
      });

      let listener = {
        async OnHistoryNewEntry(aNewURI) {
          if (aNewURI.spec == URL && 5 == ++count) {
            history.removeSHistoryListener(listener);
            await ContentTask.spawn(browser, null, () => {
              return new Promise(resolve => {
                addEventListener(
                  "load",
                  evt => {
                    let history = docShell.QueryInterface(Ci.nsIWebNavigation)
                      .sessionHistory;
                    Assert.ok(
                      history.index < history.count,
                      "history.index is valid"
                    );
                    resolve();
                  },
                  { capture: true, once: true }
                );

                content.location.reload();
              });
            });
            testDone.resolve();
          }
        },

        OnHistoryReload: () => true,
        OnHistoryGotoIndex: () => {},
        OnHistoryPurge: () => {},
        OnHistoryReplaceEntry: () => {
          // The initial load of about:blank causes a transient entry to be
          // created, so our first navigation to a real page is a replace
          // instead of a new entry.
          ++count;
        },

        QueryInterface: ChromeUtils.generateQI([
          "nsISHistoryListener",
          "nsISupportsWeakReference",
        ]),
      };

      history.addSHistoryListener(listener);
      BrowserTestUtils.loadURI(browser, URL);

      await testDone.promise;
    }
  );
});
