Skip to content

Commit

Permalink
Align history.pushState/replaceState with latest spec
Browse files Browse the repository at this point in the history
Fixes #3513.
  • Loading branch information
domenic committed Dec 28, 2023
1 parent c88815c commit b2442a0
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 29 deletions.
65 changes: 40 additions & 25 deletions lib/jsdom/living/window/History-impl.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use strict";
const DOMException = require("../generated/DOMException");
const { documentBaseURLSerialized, parseURLToResultingURLRecord } = require("../helpers/document-base-url.js");
const { serializeURL } = require("whatwg-url");

// https://html.spec.whatwg.org/#history-3
exports.implementation = class HistoryImpl {
Expand Down Expand Up @@ -55,67 +56,57 @@ exports.implementation = class HistoryImpl {
this.go(+1);
}

pushState(data, title, url) {
this._sharedPushAndReplaceState(data, title, url, "pushState");
pushState(data, unused, url) {
this._sharedPushAndReplaceState(data, url, "push");
}
replaceState(data, title, url) {
this._sharedPushAndReplaceState(data, title, url, "replaceState");
replaceState(data, unused, url) {
this._sharedPushAndReplaceState(data, url, "replace");
}

// https://html.spec.whatwg.org/#dom-history-pushstate
_sharedPushAndReplaceState(data, title, url, methodName) {
// https://html.spec.whatwg.org/#shared-history-push/replace-state-steps
_sharedPushAndReplaceState(data, url, historyHandling) {
this._guardAgainstInactiveDocuments();

// TODO structured clone data

let newURL;
if (url !== null) {
// Not implemented: use of entry settings object's API base URL. Instead we just use the document base URL. The
// difference matters in the case of cross-frame calls.
let newURL = this._document._URL;
if (url !== null && url.length > 0) {
newURL = parseURLToResultingURLRecord(url, this._document);

if (newURL === null) {
throw DOMException.create(this._globalObject, [
`Could not parse url argument "${url}" to ${methodName} against base URL ` +
`Could not parse url argument "${url}" to ${historyHandling}State() against base URL ` +
`"${documentBaseURLSerialized(this._document)}".`,
"SecurityError"
]);
}

if (newURL.scheme !== this._document._URL.scheme ||
newURL.username !== this._document._URL.username ||
newURL.password !== this._document._URL.password ||
newURL.host !== this._document._URL.host ||
newURL.port !== this._document._URL.port) {
if (!canHaveItsURLRewritten(this._document, newURL)) {
throw DOMException.create(this._globalObject, [
`${methodName} cannot update history to a URL which differs in components other than in ` +
`path, query, or fragment.`,
`${historyHandling}State() cannot update history to the URL ${serializeURL(newURL)}.`,
"SecurityError"
]);
}

// Not implemented: origin check (seems to only apply to documents with weird origins, e.g. sandboxed ones)
} else {
newURL = this._window._sessionHistory.currentEntry.url;
}

if (methodName === "pushState") {
// What follows is very unlike the spec's URL and history update steps. Maybe if we implement real session
// history/navigation, we can fix that.

if (historyHandling === "push") {
this._window._sessionHistory.removeAllEntriesAfterCurrentEntry();

this._window._sessionHistory.clearHistoryTraversalTasks();

const newEntry = {
document: this._document,
stateObject: data,
title,
url: newURL
};
this._window._sessionHistory.addEntryAfterCurrentEntry(newEntry);
this._window._sessionHistory.updateCurrentEntry(newEntry);
} else {
const { currentEntry } = this._window._sessionHistory;
currentEntry.stateObject = data;
currentEntry.title = title;
currentEntry.url = newURL;
}

Expand All @@ -131,3 +122,27 @@ exports.implementation = class HistoryImpl {
this._document._latestEntry = this._window._sessionHistory.currentEntry;
}
};

function canHaveItsURLRewritten(document, targetURL) {
const documentURL = document._URL;

if (targetURL.scheme !== documentURL.scheme || targetURL.username !== documentURL.username ||
targetURL.password !== documentURL.password || targetURL.host !== documentURL.host ||
targetURL.port !== documentURL.port) {
return false;
}

if (targetURL.scheme === "https" || targetURL.scheme === "http") {
return true;
}

if (targetURL.scheme === "file" && targetURL.path !== documentURL.path) {
return false;
}

if (targetURL.path.join("/") !== documentURL.path.join("/") || targetURL.query !== documentURL.query) {
return false;
}

return true;
}
4 changes: 0 additions & 4 deletions test/web-platform-tests/to-run.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -467,10 +467,6 @@ history_pushstate_url_rewriting.html: [fail, needs URL.createObjectURL]
iframe_history_go_0.html: [timeout, Unknown]
joint_session_history/001.html: [timeout, Unknown]
joint_session_history/002.html: [timeout, Unknown]
pushstate-replacestate-empty-string/pushstate-base.html: [fail, Unknown]
pushstate-replacestate-empty-string/pushstate.html: [fail, Unknown]
pushstate-replacestate-empty-string/replacestate-base.html: [fail, Unknown]
pushstate-replacestate-empty-string/replacestate.html: [fail, Unknown]
traverse-during-beforeunload.html: [timeout, Unknown]
traverse-during-unload.html: [timeout, Unknown]
traverse_the_history_1.html: [timeout, Unknown]
Expand Down

0 comments on commit b2442a0

Please sign in to comment.