Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test has a WPT meta file that expects 1 subtest issues.
- This WPT test may be referenced by the following Test IDs:
- /old-tests/submission/Microsoft/history/history_000.htm - WPT Dashboard Interop Dashboard
<!doctype html>
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type" />
<title>HTML5 History Test Cases</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<div id="log"></div>
<!-- Use this iframe to test url changes so that the base url does not change. Their document does not matter. -->
<iframe id="testframe1" style="display:none" src="/common/blank.html"></iframe>
<iframe id="testframe2" style="display:none" src="/common/blank.html"></iframe>
<script type="text/javascript">
var testCollection;
var testIndex = 0;
var testframe1 = document.getElementById("testframe1");
var testframe2 = document.getElementById("testframe2");
setup(function()
{
testCollection = [
function() {
test(function() {
assert_own_property(window, "onpopstate", "window has 'onpopstate' own property");
}, "onpopstate in window");
},
function() {
test(function() {
assert_inherits(window.history, "pushState", "history inherits property 'pushState'");
assert_equals(window.history.pushState.constructor, Function, "pushState is a function");
}, "history.pushState is present");
},
function() {
test(function() {
assert_inherits(window.history, "replaceState", "history inherits property 'replaceState'");
assert_equals(window.history.replaceState.constructor, Function, "replaceState is a function");
}, "history.replaceState is present");
},
function() {
test(function() {
assert_inherits(window.history, "state", "history inherits property 'state'");
}, "history.state is present");
},
function() {
test(function() {
assert_equals(window.history.state, null, "history.state initialized to null");
}, "history.state is initialized to null");
},
function() {
test(function() {
var length = history.length;
history.pushState(null,null);
assert_equals(history.length, length+1, "history.length should be incremented by one");
}, "history.pushState increments history.length");
},
function() {
var t = async_test("history.pushState clears forward entries");
t.step(function() {
var length = history.length;
//push some extra entries into the session history
history.pushState(null,null);
history.pushState(null, null);
history.pushState(null, null);
//there should now be three extra
assert_equals(history.length, length+3, "Three additional travel entries add to history.length");
let popstateCount = 0;
onpopstate = t.step_func(function(e) {
if (++popstateCount == 3) {
onpopstate = null;
//once the .back navigations have completed, push again and verify length is one more than starting value
history.pushState(null, null);
assert_equals(history.length, length+1, "History.length should now only be one more than original value");
t.done();
}
});
//travel back to the entry that the test started on
history.back();
history.back();
history.back();
});
},
function() {
test(function() {
testframe1.contentWindow.history.pushState(null,null, "test-pushstate-url");
assert_equals(getPageName(testframe1.contentWindow.location.href), "test-pushstate-url", "iframe1 has the pushed url");
}, "history.pushState accepts a third parameter 'url' and uses it to alter location");
},
function() {
test(function() {
var oldurl = testframe1.contentWindow.location.href.toString();
var pagename = getPageName(oldurl);
//form a new absolute url (with protocol, host, etc) with "absolute-page" as the name of the page
var newurl = oldurl.replace(pagename, "absolute-page");
testframe1.contentWindow.history.pushState(null,null, newurl);
assert_equals(testframe1.contentWindow.location.href, newurl, "iframe1 has the pushed url correctly");
}, "history.pushState's url parameter can be an absolute url");
},
function() {
test(function() {
testframe1.contentWindow.history.pushState(null,null, "multiple-pushstate-url1");
testframe2.contentWindow.history.pushState(null,null, "multiple-pushstate-url2");
assert_equals(getPageName(testframe1.contentWindow.location.href), "multiple-pushstate-url1", "iframe1 has the pushed url");
assert_equals(getPageName(testframe2.contentWindow.location.href), "multiple-pushstate-url2", "iframe2 has the pushed url");
}, "history.pushState can modify location object in multiple frames");
},
function() {
test(function() {
//trigger a security error by replacing the host of the current url with a fake one that is cross-domain
var testurl = testframe1.contentWindow.location.href.toString().replace(testframe1.contentWindow.location.host, "fakelocation-push");
assert_throws_dom("SECURITY_ERR", function() { history.pushState(null, null, testurl); }, "Security_Err 18 should be thrown");
}, "history.pushState throws DOMException with code SECURITY_ERR (18)");
},
function() {
test(function() {
//trigger a data clone error by passing invalid SCA data into the function
assert_throws_dom("DATA_CLONE_ERR", function() { history.pushState(document.body, null); }, "pushState should throw an exception DATA_CLONE_ERR with code 25");
}, "history.pushState throws DATA_CLONE_ERR(25) for bad state parameter");
},
function() {
test(function() {
var length = history.length;
history.replaceState(null,null);
assert_equals(history.length, length, "history.length should not change");
}, "history.replaceState does not increment history.length");
},
function() {
var t = async_test("history.replaceState does not clear forward entries");
t.step(function() {
var length = history.length;
//push some extra entries into the session history
history.pushState(null,null);
history.pushState(null, null);
history.pushState(null, null);
//there should now be three extra
assert_equals(history.length, length+3, "Three additional travel entries add to history.length");
let popstateCount = 0;
onpopstate = t.step_func(function(e) {
if (++popstateCount == 2) {
onpopstate = null;
//once the .back navigations have fired, replace and verify the length has not changed since the last check
history.replaceState(null, null);
assert_equals(history.length, length+3, "History.length should still be three more than original value");
t.done();
}
});
//travel back two entries to land in the middle
history.back();
history.back();
});
},
function() {
test(function() {
testframe1.contentWindow.history.replaceState(null,null, "test-replaceState-url");
assert_equals(getPageName(testframe1.contentWindow.location.href), "test-replaceState-url", "iframe1 has the pushed url");
}, "history.replaceState accepts a third parameter 'url' and uses it to alter location");
},
function() {
test(function() {
var oldurl = testframe1.contentWindow.location.href.toString();
var pagename = getPageName(oldurl);
//form a new absolute url (with protocol, host, etc) with "absolute-page" as the name of the page
var newurl = oldurl.replace(pagename, "absolute-page");
testframe1.contentWindow.history.replaceState(null,null, newurl);
assert_equals(testframe1.contentWindow.location.href, newurl, "iframe1 has the pushed url correctly");
}, "history.replaceState's url parameter can be an absolute url");
},
function() {
test(function() {
testframe1.contentWindow.history.replaceState(null,null, "multiple-replaceState-url1");
testframe2.contentWindow.history.replaceState(null,null, "multiple-replaceState-url2");
assert_equals(getPageName(testframe1.contentWindow.location.href), "multiple-replaceState-url1", "iframe1 has the pushed url");
assert_equals(getPageName(testframe2.contentWindow.location.href), "multiple-replaceState-url2", "iframe2 has the pushed url");
}, "history.replaceState can modify location object in multiple frames");
},
function() {
test(function() {
//trigger a security error by replacing the host of the current url with a fake one that is cross-domain
var testurl = testframe1.contentWindow.location.href.toString().replace(testframe1.contentWindow.location.host, "fakelocation-replace");
assert_throws_dom("SECURITY_ERR", function() { history.replaceState(null, null, testurl); }, "Security_Err 18 should be thrown");
}, "history.replaceState throws DOMException with code SECURITY_ERR (18)");
},
function() {
test(function() {
//trigger a data clone error by passing invalid SCA data into the function
assert_throws_dom("DATA_CLONE_ERR", function() {history.replaceState(document.body, null);}, "replaceState should throw an exception DATA_CLONE_ERR with code 25");
}, "history.replaceState throws DATA_CLONE_ERR(25) for bad state parameter");
},
function() {
var t = async_test("PopStateEvent fires on Back navigation");
t.step(function() {
history.pushState(null, null);
history.pushState(null, null);
//prepare to end the test as soon as popstate fires
onpopstate = function(e) {
t.done();
}
//go back to fire the popstate event
history.back();
});
},
function() {
var t = async_test("PopStateEvent fires on Forward navigation");
t.step(function() {
onpopstate = null;
history.pushState(null, null);
history.pushState(null, null);
onpopstate = function(e) {
//prepare to end the test as soon as popstate fires
onpopstate = function(e) {
t.done();
};
//go forward to fire the popstate event
history.forward();
};
history.back();
});
},
function() {
var t = async_test("PopStateEvent receives state data on Back navigation");
t.step(function() {
history.pushState("popstate-data", null);
history.pushState(null, null);
//prepare the popstate event to validate the data
onpopstate = t.step_func(function(e) {
assert_equals(e.state, "popstate-data", "State data is passed to the event correctly");
t.done();
});
//go back to fire the popstate event
history.back();
});
},
function() {
test(function() {
history.pushState("pushstate-data", null);
//history.state should be set immediately
assert_equals(history.state, "pushstate-data", "State data is set correctly");
}, "history.state is set by history.pushState");
},
function() {
test(function() {
history.replaceState("replacestate-data", null);
//history.state should be set immediately
assert_equals(history.state, "replacestate-data", "State data is set correctly");
}, "history.state is set by history.replaceState");
},
function() {
var t = async_test("history.state changes on navigation");
t.step(function() {
history.pushState("state-back1", null);
history.pushState("state-back2", null);
//precondition
assert_equals(history.state, "state-back2", "Verify that history.state is set to a second value");
//set up the popstate event to verify that history.state was changed
onpopstate = t.step_func(function(e) {
assert_equals(e.state, "state-back1", "Verify that history.state reverted to the first value");
t.done();
});
history.back();
});
},
];
}, {explicit_done:true, timeout:8000});
//used to get the name of a page within a path
// to check correctness of url parameter
function getPageName(path) {
var path = path || location.pathname;
var segments = path.split('/');
return segments[segments.length-1];
}
//Callback for result_callback
//queues the next test in the array testCollection
//serves to make test execution sequential despite asynchronous behaviors
function testFinished(test) {
if(testIndex < testCollection.length - 1) {
//Run the function asynchronously so that the stack remains shallow
setTimeout(testCollection[++testIndex]);
} else {
//when the last test has run, explicitly end test suite
done();
}
}
add_result_callback(testFinished);
function startTestsWhenIframesLoaded() {
if (testframe1.contentWindow.document.readyState != 'complete' ||
testframe2.contentWindow.document.readyState != 'complete') {
return;
}
testframe1.removeEventListener('load', startTestsWhenIframesLoaded, false);
testframe2.removeEventListener('load', startTestsWhenIframesLoaded, false);
//start the first test
setTimeout(testCollection[testIndex]);
}
// add listeners to trigger the tests once the iframes are loaded,
// but also try to start it right away in case the load events have
// already fired and we missed them.
testframe1.addEventListener('load', startTestsWhenIframesLoaded, false);
testframe2.addEventListener('load', startTestsWhenIframesLoaded, false);
startTestsWhenIframesLoaded();
</script>
</body>
</html>