/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

let checkState = Task.async(function*(browser) {
  // Go back and then forward, and make sure that the state objects received
  // from the popState event are as we expect them to be.
  //
  // We also add a node to the document's body when after going back and make
  // sure it's still there after we go forward -- this is to test that the two
  // history entries correspond to the same document.

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

  let popStateCount = 0;

  browser.addEventListener("popstate", function(aEvent) {
    if (popStateCount == 0) {
      popStateCount++;

      ok(aEvent.state, "Event should have a state property.");

      ContentTask.spawn(browser, null, function() {
        is(content.testState, "foo",
           "testState after going back");
        is(JSON.stringify(content.history.state), JSON.stringify({obj1:1}),
           "first popstate object.");

        // Add a node with id "new-elem" to the document.
        let doc = content.document;
        ok(!doc.getElementById("new-elem"),
           "doc shouldn't contain new-elem before we add it.");
        let elem = doc.createElement("div");
        elem.id = "new-elem";
        doc.body.appendChild(elem);
      }).then(() => {
        browser.goForward();
      });
    } else if (popStateCount == 1) {
      popStateCount++;
      // When content fires a PopStateEvent and we observe it from a chrome event
      // listener (as we do here, and, thankfully, nowhere else in the tree), the
      // state object will be a cross-compartment wrapper to an object that was
      // deserialized in the content scope. And in this case, since RegExps are
      // not currently Xrayable (see bug 1014991), trying to pull |obj3| (a RegExp)
      // off of an Xrayed Object won't work. So we need to waive.
      ContentTask.spawn(browser, aEvent.state, function(state) {
        Assert.equal(Cu.waiveXrays(state).obj3.toString(),
          "/^a$/", "second popstate object.");

        // Make sure that the new-elem node is present in the document.  If it's
        // not, then this history entry has a different doc identifier than the
        // previous entry, which is bad.
        let doc = content.document;
        let newElem = doc.getElementById("new-elem");
        ok(newElem, "doc should contain new-elem.");
        newElem.parentNode.removeChild(newElem);
        ok(!doc.getElementById("new-elem"), "new-elem should be removed.");
      }).then(() => {
        browser.removeEventListener("popstate", arguments.callee, true);
        deferred.resolve();
      });
    }
  });

  // Set some state in the page's window.  When we go back(), the page should
  // be retrieved from bfcache, and this state should still be there.
  yield ContentTask.spawn(browser, null, function() {
    content.testState = "foo";
  });

  // Now go back.  This should trigger the popstate event handler above.
  browser.goBack();

  yield deferred.promise;
});

add_task(function* test() {
  // Tests session restore functionality of history.pushState and
  // history.replaceState().  (Bug 500328)

  // We open a new blank window, let it load, and then load in
  // http://example.com.  We need to load the blank window first, otherwise the
  // docshell gets confused and doesn't have a current history entry.
  let state;
  yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, function* (browser) {
    BrowserTestUtils.loadURI(browser, "http://example.com");
    yield BrowserTestUtils.browserLoaded(browser);

    // After these push/replaceState calls, the window should have three
    // history entries:
    //   testURL        (state object: null)          <-- oldest
    //   testURL        (state object: {obj1:1})
    //   testURL?page2  (state object: {obj3:/^a$/})  <-- newest
    function contentTest() {
      let history = content.window.history;
      history.pushState({obj1:1}, "title-obj1");
      history.pushState({obj2:2}, "title-obj2", "?page2");
      history.replaceState({obj3:/^a$/}, "title-obj3");
    }
    yield ContentTask.spawn(browser, null, contentTest);
    yield TabStateFlusher.flush(browser);

    state = ss.getTabState(gBrowser.getTabForBrowser(browser));
  });

  // Restore the state into a new tab.  Things don't work well when we
  // restore into the old tab, but that's not a real use case anyway.
  yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, function* (browser) {
    let tab2 = gBrowser.getTabForBrowser(browser);

    let tabRestoredPromise = promiseTabRestored(tab2);
    ss.setTabState(tab2, state, true);

    // Run checkState() once the tab finishes loading its restored state.
    yield tabRestoredPromise;
    yield checkState(browser);
  });
});
