Skip to content

Commit ca233b5

Browse files
committed
LibWeb: Implement more of the shared history push/replace state steps
Add the seralization and URL validation steps, but skip the actual navigation for now. This might cause more pages to throw exceptions when trying to push state that contains objects that we don't know how to serialize.
1 parent eb40d16 commit ca233b5

File tree

1 file changed

+79
-5
lines changed

1 file changed

+79
-5
lines changed

Userland/Libraries/LibWeb/HTML/History.cpp

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <LibWeb/Bindings/Intrinsics.h>
88
#include <LibWeb/DOM/Document.h>
99
#include <LibWeb/HTML/History.h>
10+
#include <LibWeb/HTML/StructuredSerialize.h>
1011

1112
namespace Web::HTML {
1213

@@ -109,21 +110,94 @@ WebIDL::ExceptionOr<void> History::forward()
109110
return go(1);
110111
}
111112

113+
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#can-have-its-url-rewritten
114+
static bool can_have_its_url_rewritten(DOM::Document const& document, AK::URL const& target_url)
115+
{
116+
// 1. Let documentURL be document's URL.
117+
auto document_url = document.url();
118+
119+
// 2. If targetURL and documentURL differ in their scheme, username, password, host, or port components,
120+
// then return false.
121+
if (target_url.scheme() != document_url.scheme()
122+
|| target_url.raw_username() != document_url.raw_username()
123+
|| target_url.raw_password() != document_url.raw_password()
124+
|| target_url.host() != document_url.host()
125+
|| target_url.port() != document_url.port())
126+
return false;
127+
128+
// 3. If targetURL's scheme is an HTTP(S) scheme, then return true.
129+
// (Differences in path, query, and fragment are allowed for http: and https: URLs.)
130+
if (target_url.scheme() == "http"sv || target_url.scheme() == "https"sv)
131+
return true;
132+
133+
// 4. If targetURL's scheme is "file", and targetURL and documentURL differ in their path component,
134+
// then return false. (Differences in query and fragment are allowed for file: URLs.)
135+
// FIXME: Don't create temporary strings to compare paths
136+
auto target_url_path = target_url.serialize_path();
137+
auto document_url_path = document_url.serialize_path();
138+
if (target_url.scheme() == "file"sv
139+
&& target_url_path != document_url_path)
140+
return false;
141+
142+
// 5. If targetURL and documentURL differ in their path component or query components, then return false.
143+
// (Only differences in fragment are allowed for other types of URLs.)
144+
if (target_url_path != document_url_path
145+
|| target_url.query() != document_url.query())
146+
return false;
147+
148+
// 6. Return true.
149+
return true;
150+
}
151+
112152
// https://html.spec.whatwg.org/multipage/history.html#shared-history-push/replace-state-steps
113-
WebIDL::ExceptionOr<void> History::shared_history_push_replace_state(JS::Value, DeprecatedString const&, IsPush)
153+
WebIDL::ExceptionOr<void> History::shared_history_push_replace_state(JS::Value value, DeprecatedString const& url, IsPush)
114154
{
115-
// 1. Let document be history's associated Document. (NOTE: Not necessary)
155+
// 1. Let document be history's associated Document.
156+
auto& document = m_associated_document;
116157

117158
// 2. If document is not fully active, then throw a "SecurityError" DOMException.
118-
if (!m_associated_document->is_fully_active())
159+
if (!document->is_fully_active())
119160
return WebIDL::SecurityError::create(realm(), "Cannot perform pushState or replaceState on a document that isn't fully active."sv);
120161

121162
// 3. Optionally, return. (For example, the user agent might disallow calls to these methods that are invoked on a timer,
122163
// or from event listeners that are not triggered in response to a clear user action, or that are invoked in rapid succession.)
164+
165+
// 4. Let serializedData be StructuredSerializeForStorage(data). Rethrow any exceptions.
166+
// FIXME: Actually rethrow exceptions here once we start using the serialized data.
167+
// Throwing here on data types we don't yet serialize will regress sites that use push/replaceState.
168+
[[maybe_unused]] auto serialized_data_or_error = structured_serialize_for_storage(vm(), value);
169+
170+
// 5. Let newURL be document's URL.
171+
auto new_url = document->url();
172+
173+
// 6. If url is not null or the empty string, then:
174+
if (!url.is_empty() && !url.is_null()) {
175+
176+
// 1. Parse url, relative to the relevant settings object of history.
177+
auto parsed_url = relevant_settings_object(*this).parse_url(url);
178+
179+
// 2. If that fails, then throw a "SecurityError" DOMException.
180+
if (!parsed_url.is_valid())
181+
return WebIDL::SecurityError::create(realm(), "Cannot pushState or replaceState to incompatible URL"sv);
182+
183+
// 3. Set newURL to the resulting URL record.
184+
new_url = parsed_url;
185+
186+
// 4. If document cannot have its URL rewritten to newURL, then throw a "SecurityError" DOMException.
187+
if (!can_have_its_url_rewritten(document, new_url))
188+
return WebIDL::SecurityError::create(realm(), "Cannot pushState or replaceState to incompatible URL"sv);
189+
}
190+
191+
// FIXME: 7. Let navigation be history's relevant global object's navigation API.
192+
// FIXME: 8. Let continue be the result of firing a push/replace/reload navigate event at navigation
193+
/// with navigationType set to historyHandling, isSameDocument set to true, destinationURL set to newURL,
194+
// and classicHistoryAPIState set to serializedData.
195+
// FIXME: 9. If continue is false, then return.
196+
// FIXME: 10. Run the URL and history update steps given document and newURL, with serializedData set to
197+
// serializedData and historyHandling set to historyHandling.
198+
123199
dbgln("FIXME: Implement shared_history_push_replace_state.");
124200
return {};
125-
126-
// FIXME: Add the rest of the spec steps once they're added.
127201
}
128202

129203
}

0 commit comments

Comments
 (0)