/src/serenity/Userland/Libraries/LibWeb/HTML/Location.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2020-2023, Andreas Kling <kling@serenityos.org> |
3 | | * Copyright (c) 2022-2023, Linus Groh <linusg@serenityos.org> |
4 | | * |
5 | | * SPDX-License-Identifier: BSD-2-Clause |
6 | | */ |
7 | | |
8 | | #include <AK/String.h> |
9 | | #include <AK/StringBuilder.h> |
10 | | #include <LibJS/Heap/MarkedVector.h> |
11 | | #include <LibJS/Runtime/Completion.h> |
12 | | #include <LibJS/Runtime/PropertyDescriptor.h> |
13 | | #include <LibJS/Runtime/PropertyKey.h> |
14 | | #include <LibURL/Parser.h> |
15 | | #include <LibWeb/Bindings/LocationPrototype.h> |
16 | | #include <LibWeb/DOM/Document.h> |
17 | | #include <LibWeb/HTML/CrossOrigin/AbstractOperations.h> |
18 | | #include <LibWeb/HTML/Location.h> |
19 | | #include <LibWeb/HTML/Navigation.h> |
20 | | #include <LibWeb/HTML/Window.h> |
21 | | #include <LibWeb/WebIDL/DOMException.h> |
22 | | |
23 | | namespace Web::HTML { |
24 | | |
25 | | JS_DEFINE_ALLOCATOR(Location); |
26 | | |
27 | | // https://html.spec.whatwg.org/multipage/history.html#the-location-interface |
28 | | Location::Location(JS::Realm& realm) |
29 | 0 | : PlatformObject(realm, MayInterfereWithIndexedPropertyAccess::Yes) |
30 | 0 | { |
31 | 0 | } |
32 | | |
33 | 0 | Location::~Location() = default; |
34 | | |
35 | | void Location::visit_edges(Cell::Visitor& visitor) |
36 | 0 | { |
37 | 0 | Base::visit_edges(visitor); |
38 | 0 | visitor.visit(m_default_properties); |
39 | 0 | } |
40 | | |
41 | | // https://html.spec.whatwg.org/multipage/nav-history-apis.html#the-location-interface |
42 | | void Location::initialize(JS::Realm& realm) |
43 | 0 | { |
44 | 0 | Base::initialize(realm); |
45 | 0 | WEB_SET_PROTOTYPE_FOR_INTERFACE(Location); |
46 | |
|
47 | 0 | auto& vm = this->vm(); |
48 | | |
49 | | // Step 2: Let valueOf be location's relevant realm.[[Intrinsics]].[[%Object.prototype.valueOf%]]. |
50 | 0 | auto& intrinsics = realm.intrinsics(); |
51 | 0 | auto value_of_function = intrinsics.object_prototype()->get_without_side_effects(vm.names.valueOf); |
52 | | |
53 | | // Step 3: Perform ! location.[[DefineOwnProperty]]("valueOf", { [[Value]]: valueOf, [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false }). |
54 | 0 | auto value_of_property_descriptor = JS::PropertyDescriptor { |
55 | 0 | .value = value_of_function, |
56 | 0 | .writable = false, |
57 | 0 | .enumerable = false, |
58 | 0 | .configurable = false, |
59 | 0 | }; |
60 | 0 | MUST(internal_define_own_property(vm.names.valueOf, value_of_property_descriptor)); |
61 | | |
62 | | // Step 4: Perform ! location.[[DefineOwnProperty]](%Symbol.toPrimitive%, { [[Value]]: undefined, [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false }). |
63 | 0 | auto to_primitive_property_descriptor = JS::PropertyDescriptor { |
64 | 0 | .value = JS::js_undefined(), |
65 | 0 | .writable = false, |
66 | 0 | .enumerable = false, |
67 | 0 | .configurable = false, |
68 | 0 | }; |
69 | 0 | MUST(internal_define_own_property(vm.well_known_symbol_to_primitive(), to_primitive_property_descriptor)); |
70 | | |
71 | | // 5. Set the value of the [[DefaultProperties]] internal slot of location to location.[[OwnPropertyKeys]](). |
72 | | // NOTE: In LibWeb this happens before the ESO is set up, so we must avoid location's custom [[OwnPropertyKeys]]. |
73 | 0 | m_default_properties.extend(MUST(Object::internal_own_property_keys())); |
74 | 0 | } |
75 | | |
76 | | // https://html.spec.whatwg.org/multipage/history.html#relevant-document |
77 | | JS::GCPtr<DOM::Document> Location::relevant_document() const |
78 | 0 | { |
79 | | // A Location object has an associated relevant Document, which is this Location object's |
80 | | // relevant global object's browsing context's active document, if this Location object's |
81 | | // relevant global object's browsing context is non-null, and null otherwise. |
82 | 0 | auto* browsing_context = verify_cast<HTML::Window>(HTML::relevant_global_object(*this)).browsing_context(); |
83 | 0 | return browsing_context ? browsing_context->active_document() : nullptr; |
84 | 0 | } |
85 | | |
86 | | // https://html.spec.whatwg.org/multipage/nav-history-apis.html#location-object-navigate |
87 | | WebIDL::ExceptionOr<void> Location::navigate(URL::URL url, Bindings::NavigationHistoryBehavior history_handling) |
88 | 0 | { |
89 | | // 1. Let navigable be location's relevant global object's navigable. |
90 | 0 | auto navigable = verify_cast<HTML::Window>(HTML::relevant_global_object(*this)).navigable(); |
91 | | |
92 | | // 2. Let sourceDocument be the incumbent global object's associated Document. |
93 | 0 | auto& source_document = verify_cast<HTML::Window>(incumbent_global_object()).associated_document(); |
94 | | |
95 | | // 3. If location's relevant Document is not yet completely loaded, and the incumbent global object does not have transient activation, then set historyHandling to "replace". |
96 | 0 | if (!relevant_document()->is_completely_loaded() && !verify_cast<HTML::Window>(incumbent_global_object()).has_transient_activation()) { |
97 | 0 | history_handling = Bindings::NavigationHistoryBehavior::Replace; |
98 | 0 | } |
99 | | |
100 | | // 4. Navigate navigable to url using sourceDocument, with exceptionsEnabled set to true and historyHandling set to historyHandling. |
101 | 0 | TRY(navigable->navigate({ .url = url, |
102 | 0 | .source_document = source_document, |
103 | 0 | .exceptions_enabled = true, |
104 | 0 | .history_handling = history_handling })); |
105 | |
|
106 | 0 | return {}; |
107 | 0 | } |
108 | | |
109 | | // https://html.spec.whatwg.org/multipage/history.html#concept-location-url |
110 | | URL::URL Location::url() const |
111 | 0 | { |
112 | | // A Location object has an associated url, which is this Location object's relevant Document's URL, |
113 | | // if this Location object's relevant Document is non-null, and about:blank otherwise. |
114 | 0 | auto const relevant_document = this->relevant_document(); |
115 | 0 | return relevant_document ? relevant_document->url() : "about:blank"sv; |
116 | 0 | } |
117 | | |
118 | | // https://html.spec.whatwg.org/multipage/history.html#dom-location-href |
119 | | WebIDL::ExceptionOr<String> Location::href() const |
120 | 0 | { |
121 | 0 | auto& vm = this->vm(); |
122 | | |
123 | | // 1. If this's relevant Document is non-null and its origin is not same origin-domain with the entry settings object's origin, then throw a "SecurityError" DOMException. |
124 | 0 | auto const relevant_document = this->relevant_document(); |
125 | 0 | if (relevant_document && !relevant_document->origin().is_same_origin_domain(entry_settings_object().origin())) |
126 | 0 | return WebIDL::SecurityError::create(realm(), "Location's relevant document is not same origin-domain with the entry settings object's origin"_string); |
127 | | |
128 | | // 2. Return this's url, serialized. |
129 | 0 | return TRY_OR_THROW_OOM(vm, String::from_byte_string(url().serialize())); |
130 | 0 | } |
131 | | |
132 | | // https://html.spec.whatwg.org/multipage/history.html#the-location-interface:dom-location-href-2 |
133 | | WebIDL::ExceptionOr<void> Location::set_href(String const& new_href) |
134 | 0 | { |
135 | 0 | auto& realm = this->realm(); |
136 | 0 | auto& window = verify_cast<HTML::Window>(HTML::current_global_object()); |
137 | | |
138 | | // 1. If this's relevant Document is null, then return. |
139 | 0 | auto const relevant_document = this->relevant_document(); |
140 | 0 | if (!relevant_document) |
141 | 0 | return {}; |
142 | | |
143 | | // FIXME: 2. Let url be the result of encoding-parsing a URL given the given value, relative to the entry settings object. |
144 | 0 | auto href_url = window.associated_document().parse_url(new_href.to_byte_string()); |
145 | | |
146 | | // 3. If url is failure, then throw a "SyntaxError" DOMException. |
147 | 0 | if (!href_url.is_valid()) |
148 | 0 | return WebIDL::SyntaxError::create(realm, MUST(String::formatted("Invalid URL '{}'", new_href))); |
149 | | |
150 | | // 4. Location-object navigate this to url. |
151 | 0 | TRY(navigate(href_url)); |
152 | |
|
153 | 0 | return {}; |
154 | 0 | } |
155 | | |
156 | | // https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-location-origin |
157 | | WebIDL::ExceptionOr<String> Location::origin() const |
158 | 0 | { |
159 | 0 | auto& vm = this->vm(); |
160 | | |
161 | | // 1. If this's relevant Document is non-null and its origin is not same origin-domain with the entry settings object's origin, then throw a "SecurityError" DOMException. |
162 | 0 | auto const relevant_document = this->relevant_document(); |
163 | 0 | if (relevant_document && !relevant_document->origin().is_same_origin_domain(entry_settings_object().origin())) |
164 | 0 | return WebIDL::SecurityError::create(realm(), "Location's relevant document is not same origin-domain with the entry settings object's origin"_string); |
165 | | |
166 | | // 2. Return the serialization of this's url's origin. |
167 | 0 | return TRY_OR_THROW_OOM(vm, String::from_byte_string(url().origin().serialize())); |
168 | 0 | } |
169 | | |
170 | | // https://html.spec.whatwg.org/multipage/history.html#dom-location-protocol |
171 | | WebIDL::ExceptionOr<String> Location::protocol() const |
172 | 0 | { |
173 | 0 | auto& vm = this->vm(); |
174 | | |
175 | | // 1. If this's relevant Document is non-null and its origin is not same origin-domain with the entry settings object's origin, then throw a "SecurityError" DOMException. |
176 | 0 | auto const relevant_document = this->relevant_document(); |
177 | 0 | if (relevant_document && !relevant_document->origin().is_same_origin_domain(entry_settings_object().origin())) |
178 | 0 | return WebIDL::SecurityError::create(realm(), "Location's relevant document is not same origin-domain with the entry settings object's origin"_string); |
179 | | |
180 | | // 2. Return this's url's scheme, followed by ":". |
181 | 0 | return TRY_OR_THROW_OOM(vm, String::formatted("{}:", url().scheme())); |
182 | 0 | } |
183 | | |
184 | | // https://html.spec.whatwg.org/multipage/history.html#dom-location-protocol |
185 | | WebIDL::ExceptionOr<void> Location::set_protocol(String const& value) |
186 | 0 | { |
187 | 0 | auto relevant_document = this->relevant_document(); |
188 | | |
189 | | // 1. If this's relevant Document is null, then return. |
190 | 0 | if (!relevant_document) |
191 | 0 | return {}; |
192 | | |
193 | | // 2. If this's relevant Document's origin is not same origin-domain with the entry settings object's origin, then throw a "SecurityError" DOMException. |
194 | 0 | if (!relevant_document->origin().is_same_origin_domain(entry_settings_object().origin())) |
195 | 0 | return WebIDL::SecurityError::create(realm(), "Location's relevant document is not same origin-domain with the entry settings object's origin"_string); |
196 | | |
197 | | // 3. Let copyURL be a copy of this's url. |
198 | 0 | auto copy_url = this->url(); |
199 | | |
200 | | // 4. Let possibleFailure be the result of basic URL parsing the given value, followed by ":", with copyURL as url and scheme start state as state override. |
201 | 0 | auto possible_failure = URL::Parser::basic_parse(value, {}, ©_url, URL::Parser::State::SchemeStart); |
202 | | |
203 | | // 5. If possibleFailure is failure, then throw a "SyntaxError" DOMException. |
204 | 0 | if (!possible_failure.is_valid()) |
205 | 0 | return WebIDL::SyntaxError::create(realm(), MUST(String::formatted("Failed to set protocol. '{}' is an invalid protocol", value))); |
206 | | |
207 | | // 6. if copyURL's scheme is not an HTTP(S) scheme, then terminate these steps. |
208 | 0 | if (!(copy_url.scheme() == "http"sv || copy_url.scheme() == "https"sv)) |
209 | 0 | return {}; |
210 | | |
211 | | // 7. Location-object navigate this to copyURL. |
212 | 0 | TRY(navigate(copy_url)); |
213 | |
|
214 | 0 | return {}; |
215 | 0 | } |
216 | | |
217 | | // https://html.spec.whatwg.org/multipage/history.html#dom-location-host |
218 | | WebIDL::ExceptionOr<String> Location::host() const |
219 | 0 | { |
220 | 0 | auto& vm = this->vm(); |
221 | | |
222 | | // 1. If this's relevant Document is non-null and its origin is not same origin-domain with the entry settings object's origin, then throw a "SecurityError" DOMException. |
223 | 0 | auto const relevant_document = this->relevant_document(); |
224 | 0 | if (relevant_document && !relevant_document->origin().is_same_origin_domain(entry_settings_object().origin())) |
225 | 0 | return WebIDL::SecurityError::create(realm(), "Location's relevant document is not same origin-domain with the entry settings object's origin"_string); |
226 | | |
227 | | // 2. Let url be this's url. |
228 | 0 | auto url = this->url(); |
229 | | |
230 | | // 3. If url's host is null, return the empty string. |
231 | 0 | if (url.host().has<Empty>()) |
232 | 0 | return String {}; |
233 | | |
234 | | // 4. If url's port is null, return url's host, serialized. |
235 | 0 | if (!url.port().has_value()) |
236 | 0 | return TRY_OR_THROW_OOM(vm, url.serialized_host()); |
237 | | |
238 | | // 5. Return url's host, serialized, followed by ":" and url's port, serialized. |
239 | 0 | return TRY_OR_THROW_OOM(vm, String::formatted("{}:{}", TRY_OR_THROW_OOM(vm, url.serialized_host()), *url.port())); |
240 | 0 | } |
241 | | |
242 | | WebIDL::ExceptionOr<void> Location::set_host(String const&) |
243 | 0 | { |
244 | 0 | auto& vm = this->vm(); |
245 | 0 | return vm.throw_completion<JS::InternalError>(JS::ErrorType::NotImplemented, "Location.host setter"); |
246 | 0 | } |
247 | | |
248 | | // https://html.spec.whatwg.org/multipage/history.html#dom-location-hostname |
249 | | WebIDL::ExceptionOr<String> Location::hostname() const |
250 | 0 | { |
251 | 0 | auto& vm = this->vm(); |
252 | | |
253 | | // 1. If this's relevant Document is non-null and its origin is not same origin-domain with the entry settings object's origin, then throw a "SecurityError" DOMException. |
254 | 0 | auto const relevant_document = this->relevant_document(); |
255 | 0 | if (relevant_document && !relevant_document->origin().is_same_origin_domain(entry_settings_object().origin())) |
256 | 0 | return WebIDL::SecurityError::create(realm(), "Location's relevant document is not same origin-domain with the entry settings object's origin"_string); |
257 | | |
258 | 0 | auto url = this->url(); |
259 | | |
260 | | // 2. If this's url's host is null, return the empty string. |
261 | 0 | if (url.host().has<Empty>()) |
262 | 0 | return String {}; |
263 | | |
264 | | // 3. Return this's url's host, serialized. |
265 | 0 | return TRY_OR_THROW_OOM(vm, url.serialized_host()); |
266 | 0 | } |
267 | | |
268 | | WebIDL::ExceptionOr<void> Location::set_hostname(String const&) |
269 | 0 | { |
270 | 0 | auto& vm = this->vm(); |
271 | 0 | return vm.throw_completion<JS::InternalError>(JS::ErrorType::NotImplemented, "Location.hostname setter"); |
272 | 0 | } |
273 | | |
274 | | // https://html.spec.whatwg.org/multipage/history.html#dom-location-port |
275 | | WebIDL::ExceptionOr<String> Location::port() const |
276 | 0 | { |
277 | | // 1. If this's relevant Document is non-null and its origin is not same origin-domain with the entry settings object's origin, then throw a "SecurityError" DOMException. |
278 | 0 | auto const relevant_document = this->relevant_document(); |
279 | 0 | if (relevant_document && !relevant_document->origin().is_same_origin_domain(entry_settings_object().origin())) |
280 | 0 | return WebIDL::SecurityError::create(realm(), "Location's relevant document is not same origin-domain with the entry settings object's origin"_string); |
281 | | |
282 | 0 | auto url = this->url(); |
283 | | |
284 | | // 2. If this's url's port is null, return the empty string. |
285 | 0 | if (!url.port().has_value()) |
286 | 0 | return String {}; |
287 | | |
288 | | // 3. Return this's url's port, serialized. |
289 | 0 | return String::number(*url.port()); |
290 | 0 | } |
291 | | |
292 | | WebIDL::ExceptionOr<void> Location::set_port(String const&) |
293 | 0 | { |
294 | 0 | auto& vm = this->vm(); |
295 | 0 | return vm.throw_completion<JS::InternalError>(JS::ErrorType::NotImplemented, "Location.port setter"); |
296 | 0 | } |
297 | | |
298 | | // https://html.spec.whatwg.org/multipage/history.html#dom-location-pathname |
299 | | WebIDL::ExceptionOr<String> Location::pathname() const |
300 | 0 | { |
301 | | // 1. If this's relevant Document is non-null and its origin is not same origin-domain with the entry settings object's origin, then throw a "SecurityError" DOMException. |
302 | 0 | auto const relevant_document = this->relevant_document(); |
303 | 0 | if (relevant_document && !relevant_document->origin().is_same_origin_domain(entry_settings_object().origin())) |
304 | 0 | return WebIDL::SecurityError::create(realm(), "Location's relevant document is not same origin-domain with the entry settings object's origin"_string); |
305 | | |
306 | | // 2. Return the result of URL path serializing this Location object's url. |
307 | 0 | return url().serialize_path(); |
308 | 0 | } |
309 | | |
310 | | WebIDL::ExceptionOr<void> Location::set_pathname(String const&) |
311 | 0 | { |
312 | 0 | auto& vm = this->vm(); |
313 | 0 | return vm.throw_completion<JS::InternalError>(JS::ErrorType::NotImplemented, "Location.pathname setter"); |
314 | 0 | } |
315 | | |
316 | | // https://html.spec.whatwg.org/multipage/history.html#dom-location-search |
317 | | WebIDL::ExceptionOr<String> Location::search() const |
318 | 0 | { |
319 | 0 | auto& vm = this->vm(); |
320 | | |
321 | | // 1. If this's relevant Document is non-null and its origin is not same origin-domain with the entry settings object's origin, then throw a "SecurityError" DOMException. |
322 | 0 | auto const relevant_document = this->relevant_document(); |
323 | 0 | if (relevant_document && !relevant_document->origin().is_same_origin_domain(entry_settings_object().origin())) |
324 | 0 | return WebIDL::SecurityError::create(realm(), "Location's relevant document is not same origin-domain with the entry settings object's origin"_string); |
325 | | |
326 | 0 | auto url = this->url(); |
327 | | |
328 | | // 2. If this's url's query is either null or the empty string, return the empty string. |
329 | 0 | if (!url.query().has_value() || url.query()->is_empty()) |
330 | 0 | return String {}; |
331 | | |
332 | | // 3. Return "?", followed by this's url's query. |
333 | 0 | return TRY_OR_THROW_OOM(vm, String::formatted("?{}", url.query())); |
334 | 0 | } |
335 | | |
336 | | // https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-location-search |
337 | | WebIDL::ExceptionOr<void> Location::set_search(String const& value) |
338 | 0 | { |
339 | | // The search setter steps are: |
340 | 0 | auto const relevant_document = this->relevant_document(); |
341 | | |
342 | | // 1. If this's relevant Document is null, then return. |
343 | 0 | if (!relevant_document) |
344 | 0 | return {}; |
345 | | |
346 | | // 2. If this's relevant Document's origin is not same origin-domain with the entry settings object's origin, then throw a "SecurityError" DOMException. |
347 | 0 | if (!relevant_document->origin().is_same_origin_domain(entry_settings_object().origin())) |
348 | 0 | return WebIDL::SecurityError::create(realm(), "Location's relevant document is not same origin-domain with the entry settings object's origin"_string); |
349 | | |
350 | | // 3. Let copyURL be a copy of this's url. |
351 | 0 | auto copy_url = this->url(); |
352 | | |
353 | | // 4. If the given value is the empty string, set copyURL's query to null. |
354 | 0 | if (value.is_empty()) { |
355 | 0 | copy_url.set_query({}); |
356 | 0 | } |
357 | | // 5. Otherwise, run these substeps: |
358 | 0 | else { |
359 | | // 5.1. Let input be the given value with a single leading "?" removed, if any. |
360 | 0 | auto value_as_string_view = value.bytes_as_string_view(); |
361 | 0 | auto input = value_as_string_view.substring_view(value_as_string_view.starts_with('?')); |
362 | | |
363 | | // 5.2. Set copyURL's query to the empty string. |
364 | 0 | copy_url.set_query(String {}); |
365 | | |
366 | | // 5.3. Basic URL parse input, with null, the relevant Document's document's character encoding, copyURL as url, and query state as state override. |
367 | 0 | (void)URL::Parser::basic_parse(input, {}, ©_url, URL::Parser::State::Query); |
368 | 0 | } |
369 | | |
370 | | // 6. Location-object navigate this to copyURL. |
371 | 0 | TRY(navigate(copy_url)); |
372 | |
|
373 | 0 | return {}; |
374 | 0 | } |
375 | | |
376 | | // https://html.spec.whatwg.org/multipage/history.html#dom-location-hash |
377 | | WebIDL::ExceptionOr<String> Location::hash() const |
378 | 0 | { |
379 | 0 | auto& vm = this->vm(); |
380 | | |
381 | | // 1. If this's relevant Document is non-null and its origin is not same origin-domain with the entry settings object's origin, then throw a "SecurityError" DOMException. |
382 | 0 | auto const relevant_document = this->relevant_document(); |
383 | 0 | if (relevant_document && !relevant_document->origin().is_same_origin_domain(entry_settings_object().origin())) |
384 | 0 | return WebIDL::SecurityError::create(realm(), "Location's relevant document is not same origin-domain with the entry settings object's origin"_string); |
385 | | |
386 | 0 | auto url = this->url(); |
387 | | |
388 | | // 2. If this's url's fragment is either null or the empty string, return the empty string. |
389 | 0 | if (!url.fragment().has_value() || url.fragment()->is_empty()) |
390 | 0 | return String {}; |
391 | | |
392 | | // 3. Return "#", followed by this's url's fragment. |
393 | 0 | return TRY_OR_THROW_OOM(vm, String::formatted("#{}", *url.fragment())); |
394 | 0 | } |
395 | | |
396 | | // https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-location-hash |
397 | | WebIDL::ExceptionOr<void> Location::set_hash(String const& value) |
398 | 0 | { |
399 | | // The hash setter steps are: |
400 | 0 | auto const relevant_document = this->relevant_document(); |
401 | | |
402 | | // 1. If this's relevant Document is null, then return. |
403 | 0 | if (!relevant_document) |
404 | 0 | return {}; |
405 | | |
406 | | // 2. If this's relevant Document's origin is not same origin-domain with the entry settings object's origin, then throw a "SecurityError" DOMException. |
407 | 0 | if (!relevant_document->origin().is_same_origin_domain(entry_settings_object().origin())) |
408 | 0 | return WebIDL::SecurityError::create(realm(), "Location's relevant document is not same origin-domain with the entry settings object's origin"_string); |
409 | | |
410 | | // 3. Let copyURL be a copy of this's url. |
411 | 0 | auto copy_url = this->url(); |
412 | | |
413 | | // 4. Let input be the given value with a single leading "#" removed, if any. |
414 | 0 | auto input = value.bytes_as_string_view().trim("#"sv, TrimMode::Left); |
415 | | |
416 | | // 5. Set copyURL's fragment to the empty string. |
417 | 0 | copy_url.set_fragment(String {}); |
418 | | |
419 | | // 6. Basic URL parse input, with copyURL as url and fragment state as state override. |
420 | 0 | (void)URL::Parser::basic_parse(input, {}, ©_url, URL::Parser::State::Fragment); |
421 | | |
422 | | // 7. If copyURL's fragment is this's url's fragment, then return. |
423 | 0 | if (copy_url.fragment() == this->url().fragment()) |
424 | 0 | return {}; |
425 | | |
426 | | // 8. Location-object navigate this to copyURL. |
427 | 0 | TRY(navigate(copy_url)); |
428 | |
|
429 | 0 | return {}; |
430 | 0 | } |
431 | | |
432 | | // https://html.spec.whatwg.org/multipage/history.html#dom-location-reload |
433 | | void Location::reload() const |
434 | 0 | { |
435 | | // 1. Let document be this's relevant Document. |
436 | 0 | auto document = relevant_document(); |
437 | | |
438 | | // 2. If document is null, then return. |
439 | 0 | if (!document) |
440 | 0 | return; |
441 | | |
442 | | // FIXME: 3. If document's origin is not same origin-domain with the entry settings object's origin, then throw a "SecurityError" DOMException. |
443 | | |
444 | | // 4. Reload document's node navigable. |
445 | 0 | document->navigable()->reload(); |
446 | 0 | } |
447 | | |
448 | | // https://html.spec.whatwg.org/multipage/history.html#dom-location-replace |
449 | | WebIDL::ExceptionOr<void> Location::replace(String const& url) |
450 | 0 | { |
451 | | // 1. If this's relevant Document is null, then return. |
452 | 0 | if (!relevant_document()) |
453 | 0 | return {}; |
454 | | |
455 | | // 2. Parse url relative to the entry settings object. If that failed, throw a "SyntaxError" DOMException. |
456 | 0 | auto replace_url = entry_settings_object().parse_url(url); |
457 | 0 | if (!replace_url.is_valid()) |
458 | 0 | return WebIDL::SyntaxError::create(realm(), MUST(String::formatted("Invalid URL '{}'", url))); |
459 | | |
460 | | // 3. Location-object navigate this to the resulting URL record given "replace". |
461 | 0 | TRY(navigate(replace_url, Bindings::NavigationHistoryBehavior::Replace)); |
462 | |
|
463 | 0 | return {}; |
464 | 0 | } |
465 | | |
466 | | // https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-location-assign |
467 | | WebIDL::ExceptionOr<void> Location::assign(String const& url) |
468 | 0 | { |
469 | | // 1. If this's relevant Document is null, then return. |
470 | 0 | auto const relevant_document = this->relevant_document(); |
471 | 0 | if (!relevant_document) |
472 | 0 | return {}; |
473 | | |
474 | | // 2. If this's relevant Document's origin is not same origin-domain with the entry settings object's origin, then throw a "SecurityError" DOMException. |
475 | 0 | if (!relevant_document->origin().is_same_origin_domain(entry_settings_object().origin())) |
476 | 0 | return WebIDL::SecurityError::create(realm(), "Location's relevant document is not same origin-domain with the entry settings object's origin"_string); |
477 | | |
478 | | // 3. Parse url relative to the entry settings object. If that failed, throw a "SyntaxError" DOMException. |
479 | 0 | auto assign_url = entry_settings_object().parse_url(url); |
480 | 0 | if (!assign_url.is_valid()) |
481 | 0 | return WebIDL::SyntaxError::create(realm(), MUST(String::formatted("Invalid URL '{}'", url))); |
482 | | |
483 | | // 4. Location-object navigate this to the resulting URL record. |
484 | 0 | TRY(navigate(assign_url)); |
485 | |
|
486 | 0 | return {}; |
487 | 0 | } |
488 | | |
489 | | // 7.10.5.1 [[GetPrototypeOf]] ( ), https://html.spec.whatwg.org/multipage/history.html#location-getprototypeof |
490 | | JS::ThrowCompletionOr<JS::Object*> Location::internal_get_prototype_of() const |
491 | 0 | { |
492 | | // 1. If IsPlatformObjectSameOrigin(this) is true, then return ! OrdinaryGetPrototypeOf(this). |
493 | 0 | if (HTML::is_platform_object_same_origin(*this)) |
494 | 0 | return MUST(JS::Object::internal_get_prototype_of()); |
495 | | |
496 | | // 2. Return null. |
497 | 0 | return nullptr; |
498 | 0 | } |
499 | | |
500 | | // 7.10.5.2 [[SetPrototypeOf]] ( V ), https://html.spec.whatwg.org/multipage/history.html#location-setprototypeof |
501 | | JS::ThrowCompletionOr<bool> Location::internal_set_prototype_of(Object* prototype) |
502 | 0 | { |
503 | | // 1. Return ! SetImmutablePrototype(this, V). |
504 | 0 | return MUST(set_immutable_prototype(prototype)); |
505 | 0 | } |
506 | | |
507 | | // 7.10.5.3 [[IsExtensible]] ( ), https://html.spec.whatwg.org/multipage/history.html#location-isextensible |
508 | | JS::ThrowCompletionOr<bool> Location::internal_is_extensible() const |
509 | 0 | { |
510 | | // 1. Return true. |
511 | 0 | return true; |
512 | 0 | } |
513 | | |
514 | | // 7.10.5.4 [[PreventExtensions]] ( ), https://html.spec.whatwg.org/multipage/history.html#location-preventextensions |
515 | | JS::ThrowCompletionOr<bool> Location::internal_prevent_extensions() |
516 | 0 | { |
517 | | // 1. Return false. |
518 | 0 | return false; |
519 | 0 | } |
520 | | |
521 | | // 7.10.5.5 [[GetOwnProperty]] ( P ), https://html.spec.whatwg.org/multipage/history.html#location-getownproperty |
522 | | JS::ThrowCompletionOr<Optional<JS::PropertyDescriptor>> Location::internal_get_own_property(JS::PropertyKey const& property_key) const |
523 | 0 | { |
524 | 0 | auto& vm = this->vm(); |
525 | | |
526 | | // 1. If IsPlatformObjectSameOrigin(this) is true, then: |
527 | 0 | if (HTML::is_platform_object_same_origin(*this)) { |
528 | | // 1. Let desc be OrdinaryGetOwnProperty(this, P). |
529 | 0 | auto descriptor = MUST(Object::internal_get_own_property(property_key)); |
530 | | |
531 | | // 2. If the value of the [[DefaultProperties]] internal slot of this contains P, then set desc.[[Configurable]] to true. |
532 | | // FIXME: This doesn't align with what the other browsers do. Spec issue: https://github.com/whatwg/html/issues/4157 |
533 | 0 | auto property_key_value = property_key.is_symbol() |
534 | 0 | ? JS::Value { property_key.as_symbol() } |
535 | 0 | : JS::PrimitiveString::create(vm, property_key.to_string()); |
536 | 0 | if (m_default_properties.contains_slow(property_key_value)) |
537 | 0 | descriptor->configurable = true; |
538 | | |
539 | | // 3. Return desc. |
540 | 0 | return descriptor; |
541 | 0 | } |
542 | | |
543 | | // 2. Let property be CrossOriginGetOwnPropertyHelper(this, P). |
544 | 0 | auto property = HTML::cross_origin_get_own_property_helper(const_cast<Location*>(this), property_key); |
545 | | |
546 | | // 3. If property is not undefined, then return property. |
547 | 0 | if (property.has_value()) |
548 | 0 | return property; |
549 | | |
550 | | // 4. Return ? CrossOriginPropertyFallback(P). |
551 | 0 | return TRY(HTML::cross_origin_property_fallback(vm, property_key)); |
552 | 0 | } |
553 | | |
554 | | // 7.10.5.6 [[DefineOwnProperty]] ( P, Desc ), https://html.spec.whatwg.org/multipage/history.html#location-defineownproperty |
555 | | JS::ThrowCompletionOr<bool> Location::internal_define_own_property(JS::PropertyKey const& property_key, JS::PropertyDescriptor const& descriptor, Optional<JS::PropertyDescriptor>* precomputed_get_own_property) |
556 | 0 | { |
557 | | // 1. If IsPlatformObjectSameOrigin(this) is true, then: |
558 | 0 | if (HTML::is_platform_object_same_origin(*this)) { |
559 | | // 1. If the value of the [[DefaultProperties]] internal slot of this contains P, then return false. |
560 | | // 2. Return ? OrdinaryDefineOwnProperty(this, P, Desc). |
561 | 0 | return JS::Object::internal_define_own_property(property_key, descriptor, precomputed_get_own_property); |
562 | 0 | } |
563 | | |
564 | | // 2. Throw a "SecurityError" DOMException. |
565 | 0 | return throw_completion(WebIDL::SecurityError::create(realm(), MUST(String::formatted("Can't define property '{}' on cross-origin object", property_key)))); |
566 | 0 | } |
567 | | |
568 | | // 7.10.5.7 [[Get]] ( P, Receiver ), https://html.spec.whatwg.org/multipage/history.html#location-get |
569 | | JS::ThrowCompletionOr<JS::Value> Location::internal_get(JS::PropertyKey const& property_key, JS::Value receiver, JS::CacheablePropertyMetadata* cacheable_metadata, PropertyLookupPhase phase) const |
570 | 0 | { |
571 | 0 | auto& vm = this->vm(); |
572 | | |
573 | | // 1. If IsPlatformObjectSameOrigin(this) is true, then return ? OrdinaryGet(this, P, Receiver). |
574 | 0 | if (HTML::is_platform_object_same_origin(*this)) |
575 | 0 | return JS::Object::internal_get(property_key, receiver, cacheable_metadata, phase); |
576 | | |
577 | | // 2. Return ? CrossOriginGet(this, P, Receiver). |
578 | 0 | return HTML::cross_origin_get(vm, static_cast<JS::Object const&>(*this), property_key, receiver); |
579 | 0 | } |
580 | | |
581 | | // 7.10.5.8 [[Set]] ( P, V, Receiver ), https://html.spec.whatwg.org/multipage/history.html#location-set |
582 | | JS::ThrowCompletionOr<bool> Location::internal_set(JS::PropertyKey const& property_key, JS::Value value, JS::Value receiver, JS::CacheablePropertyMetadata* cacheable_metadata) |
583 | 0 | { |
584 | 0 | auto& vm = this->vm(); |
585 | | |
586 | | // 1. If IsPlatformObjectSameOrigin(this) is true, then return ? OrdinarySet(this, P, V, Receiver). |
587 | 0 | if (HTML::is_platform_object_same_origin(*this)) |
588 | 0 | return JS::Object::internal_set(property_key, value, receiver, cacheable_metadata); |
589 | | |
590 | | // 2. Return ? CrossOriginSet(this, P, V, Receiver). |
591 | 0 | return HTML::cross_origin_set(vm, static_cast<JS::Object&>(*this), property_key, value, receiver); |
592 | 0 | } |
593 | | |
594 | | // 7.10.5.9 [[Delete]] ( P ), https://html.spec.whatwg.org/multipage/history.html#location-delete |
595 | | JS::ThrowCompletionOr<bool> Location::internal_delete(JS::PropertyKey const& property_key) |
596 | 0 | { |
597 | | // 1. If IsPlatformObjectSameOrigin(this) is true, then return ? OrdinaryDelete(this, P). |
598 | 0 | if (HTML::is_platform_object_same_origin(*this)) |
599 | 0 | return JS::Object::internal_delete(property_key); |
600 | | |
601 | | // 2. Throw a "SecurityError" DOMException. |
602 | 0 | return throw_completion(WebIDL::SecurityError::create(realm(), MUST(String::formatted("Can't delete property '{}' on cross-origin object", property_key)))); |
603 | 0 | } |
604 | | |
605 | | // 7.10.5.10 [[OwnPropertyKeys]] ( ), https://html.spec.whatwg.org/multipage/history.html#location-ownpropertykeys |
606 | | JS::ThrowCompletionOr<JS::MarkedVector<JS::Value>> Location::internal_own_property_keys() const |
607 | 0 | { |
608 | | // 1. If IsPlatformObjectSameOrigin(this) is true, then return OrdinaryOwnPropertyKeys(this). |
609 | 0 | if (HTML::is_platform_object_same_origin(*this)) |
610 | 0 | return JS::Object::internal_own_property_keys(); |
611 | | |
612 | | // 2. Return CrossOriginOwnPropertyKeys(this). |
613 | 0 | return HTML::cross_origin_own_property_keys(this); |
614 | 0 | } |
615 | | |
616 | | } |