/src/serenity/Userland/Libraries/LibWeb/HTML/NavigableContainer.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2020-2022, Andreas Kling <kling@serenityos.org> |
3 | | * Copyright (c) 2022, Linus Groh <linusg@serenityos.org> |
4 | | * Copyright (c) 2023, Sam Atkins <atkinssj@serenityos.org> |
5 | | * |
6 | | * SPDX-License-Identifier: BSD-2-Clause |
7 | | */ |
8 | | |
9 | | #include <LibURL/Origin.h> |
10 | | #include <LibWeb/Bindings/MainThreadVM.h> |
11 | | #include <LibWeb/DOM/Document.h> |
12 | | #include <LibWeb/DOM/Event.h> |
13 | | #include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h> |
14 | | #include <LibWeb/HTML/BrowsingContext.h> |
15 | | #include <LibWeb/HTML/BrowsingContextGroup.h> |
16 | | #include <LibWeb/HTML/DocumentState.h> |
17 | | #include <LibWeb/HTML/HTMLIFrameElement.h> |
18 | | #include <LibWeb/HTML/Navigable.h> |
19 | | #include <LibWeb/HTML/NavigableContainer.h> |
20 | | #include <LibWeb/HTML/NavigationParams.h> |
21 | | #include <LibWeb/HTML/Scripting/WindowEnvironmentSettingsObject.h> |
22 | | #include <LibWeb/HTML/TraversableNavigable.h> |
23 | | #include <LibWeb/HTML/Window.h> |
24 | | #include <LibWeb/HighResolutionTime/TimeOrigin.h> |
25 | | #include <LibWeb/Page/Page.h> |
26 | | |
27 | | namespace Web::HTML { |
28 | | |
29 | | HashTable<NavigableContainer*>& NavigableContainer::all_instances() |
30 | 0 | { |
31 | 0 | static HashTable<NavigableContainer*> set; |
32 | 0 | return set; |
33 | 0 | } |
34 | | |
35 | | NavigableContainer::NavigableContainer(DOM::Document& document, DOM::QualifiedName qualified_name) |
36 | 0 | : HTMLElement(document, move(qualified_name)) |
37 | 0 | { |
38 | 0 | all_instances().set(this); |
39 | 0 | } |
40 | | |
41 | | NavigableContainer::~NavigableContainer() |
42 | 0 | { |
43 | 0 | all_instances().remove(this); |
44 | 0 | } |
45 | | |
46 | | void NavigableContainer::visit_edges(Cell::Visitor& visitor) |
47 | 0 | { |
48 | 0 | Base::visit_edges(visitor); |
49 | 0 | visitor.visit(m_content_navigable); |
50 | 0 | } |
51 | | |
52 | | JS::GCPtr<NavigableContainer> NavigableContainer::navigable_container_with_content_navigable(JS::NonnullGCPtr<Navigable> navigable) |
53 | 0 | { |
54 | 0 | for (auto* navigable_container : all_instances()) { |
55 | 0 | if (navigable_container->content_navigable() == navigable) |
56 | 0 | return navigable_container; |
57 | 0 | } |
58 | 0 | return nullptr; |
59 | 0 | } |
60 | | |
61 | | // https://html.spec.whatwg.org/multipage/document-sequences.html#create-a-new-child-navigable |
62 | | WebIDL::ExceptionOr<void> NavigableContainer::create_new_child_navigable(JS::GCPtr<JS::HeapFunction<void()>> after_session_history_update) |
63 | 0 | { |
64 | | // 1. Let parentNavigable be element's node navigable. |
65 | 0 | auto parent_navigable = navigable(); |
66 | | |
67 | | // 2. Let group be element's node document's browsing context's top-level browsing context's group. |
68 | 0 | VERIFY(document().browsing_context()); |
69 | 0 | auto group = document().browsing_context()->top_level_browsing_context()->group(); |
70 | 0 | VERIFY(group); |
71 | | |
72 | | // 3. Let browsingContext and document be the result of creating a new browsing context and document given element's node document, element, and group. |
73 | 0 | auto& page = document().page(); |
74 | 0 | auto [browsing_context, document] = TRY(BrowsingContext::create_a_new_browsing_context_and_document(page, this->document(), *this, *group)); |
75 | | |
76 | | // 4. Let targetName be null. |
77 | 0 | Optional<String> target_name; |
78 | | |
79 | | // 5. If element has a name content attribute, then set targetName to the value of that attribute. |
80 | 0 | if (name().has_value()) |
81 | 0 | target_name = name().value().to_string(); |
82 | | |
83 | | // 6. Let documentState be a new document state, with |
84 | | // - document: document |
85 | | // - initiator origin: document's origin |
86 | | // - origin: document's origin |
87 | | // - navigable target name: targetName |
88 | | // - about base URL: document's about base URL |
89 | 0 | JS::NonnullGCPtr<DocumentState> document_state = *heap().allocate_without_realm<HTML::DocumentState>(); |
90 | 0 | document_state->set_document(document); |
91 | 0 | document_state->set_initiator_origin(document->origin()); |
92 | 0 | document_state->set_origin(document->origin()); |
93 | 0 | if (target_name.has_value()) |
94 | 0 | document_state->set_navigable_target_name(*target_name); |
95 | 0 | document_state->set_about_base_url(document->about_base_url()); |
96 | | |
97 | | // 7. Let navigable be a new navigable. |
98 | 0 | JS::NonnullGCPtr<Navigable> navigable = *heap().allocate_without_realm<Navigable>(page); |
99 | | |
100 | | // 8. Initialize the navigable navigable given documentState and parentNavigable. |
101 | 0 | TRY_OR_THROW_OOM(vm(), navigable->initialize_navigable(document_state, parent_navigable)); |
102 | | |
103 | | // 9. Set element's content navigable to navigable. |
104 | 0 | m_content_navigable = navigable; |
105 | | |
106 | | // 10. Let historyEntry be navigable's active session history entry. |
107 | 0 | auto history_entry = navigable->active_session_history_entry(); |
108 | | |
109 | | // 11. Let traversable be parentNavigable's traversable navigable. |
110 | 0 | auto traversable = parent_navigable->traversable_navigable(); |
111 | | |
112 | | // 12. Append the following session history traversal steps to traversable: |
113 | 0 | traversable->append_session_history_traversal_steps(JS::create_heap_function(heap(), [traversable, navigable, parent_navigable, history_entry, after_session_history_update] { |
114 | | // 1. Let parentDocState be parentNavigable's active session history entry's document state. |
115 | 0 | auto parent_doc_state = parent_navigable->active_session_history_entry()->document_state(); |
116 | | |
117 | | // 2. Let parentNavigableEntries be the result of getting session history entries for parentNavigable. |
118 | 0 | auto parent_navigable_entries = parent_navigable->get_session_history_entries(); |
119 | | |
120 | | // 3. Let targetStepSHE be the first session history entry in parentNavigableEntries whose document state equals parentDocState. |
121 | 0 | auto target_step_she = *parent_navigable_entries.find_if([parent_doc_state](auto& entry) { |
122 | 0 | return entry->document_state() == parent_doc_state; |
123 | 0 | }); |
124 | | |
125 | | // 4. Set historyEntry's step to targetStepSHE's step. |
126 | 0 | history_entry->set_step(target_step_she->step()); |
127 | | |
128 | | // 5. Let nestedHistory be a new nested history whose id is navigable's id and entries list is « historyEntry ». |
129 | 0 | DocumentState::NestedHistory nested_history { |
130 | 0 | .id = navigable->id(), |
131 | 0 | .entries { *history_entry }, |
132 | 0 | }; |
133 | | |
134 | | // 6. Append nestedHistory to parentDocState's nested histories. |
135 | 0 | parent_doc_state->nested_histories().append(move(nested_history)); |
136 | | |
137 | | // 7. Update for navigable creation/destruction given traversable |
138 | 0 | traversable->update_for_navigable_creation_or_destruction(); |
139 | |
|
140 | 0 | if (after_session_history_update) { |
141 | 0 | after_session_history_update->function()(); |
142 | 0 | } |
143 | 0 | })); |
144 | |
|
145 | 0 | return {}; |
146 | 0 | } |
147 | | |
148 | | // https://html.spec.whatwg.org/multipage/browsers.html#concept-bcc-content-document |
149 | | const DOM::Document* NavigableContainer::content_document() const |
150 | 0 | { |
151 | | // 1. If container's content navigable is null, then return null. |
152 | 0 | if (m_content_navigable == nullptr) |
153 | 0 | return nullptr; |
154 | | |
155 | | // 2. Let document be container's content navigable's active document. |
156 | 0 | auto document = m_content_navigable->active_document(); |
157 | | |
158 | | // 4. If document's origin and container's node document's origin are not same origin-domain, then return null. |
159 | 0 | if (!document->origin().is_same_origin_domain(m_document->origin())) |
160 | 0 | return nullptr; |
161 | | |
162 | | // 5. Return document. |
163 | 0 | return document; |
164 | 0 | } |
165 | | |
166 | | DOM::Document const* NavigableContainer::content_document_without_origin_check() const |
167 | 0 | { |
168 | 0 | if (!m_content_navigable) |
169 | 0 | return nullptr; |
170 | | |
171 | 0 | return m_content_navigable->active_document(); |
172 | 0 | } |
173 | | |
174 | | // https://html.spec.whatwg.org/multipage/embedded-content-other.html#dom-media-getsvgdocument |
175 | | const DOM::Document* NavigableContainer::get_svg_document() const |
176 | 0 | { |
177 | | // 1. Let document be this element's content document. |
178 | 0 | auto const* document = content_document(); |
179 | | |
180 | | // 2. If document is non-null and was created by the page load processing model for XML files section because the computed type of the resource in the navigate algorithm was image/svg+xml, then return document. |
181 | 0 | if (document && document->content_type() == "image/svg+xml"sv) |
182 | 0 | return document; |
183 | | // 3. Return null. |
184 | 0 | return nullptr; |
185 | 0 | } |
186 | | |
187 | | HTML::WindowProxy* NavigableContainer::content_window() |
188 | 0 | { |
189 | 0 | if (!m_content_navigable) |
190 | 0 | return nullptr; |
191 | 0 | return m_content_navigable->active_window_proxy(); |
192 | 0 | } |
193 | | |
194 | | // https://html.spec.whatwg.org/multipage/iframe-embed-object.html#shared-attribute-processing-steps-for-iframe-and-frame-elements |
195 | | Optional<URL::URL> NavigableContainer::shared_attribute_processing_steps_for_iframe_and_frame(bool initial_insertion) |
196 | 0 | { |
197 | | // 1. Let url be the URL record about:blank. |
198 | 0 | auto url = URL::URL("about:blank"); |
199 | | |
200 | | // 2. If element has a src attribute specified, and its value is not the empty string, |
201 | | // then parse the value of that attribute relative to element's node document. |
202 | | // If this is successful, then set url to the resulting URL record. |
203 | 0 | auto src_attribute_value = get_attribute_value(HTML::AttributeNames::src); |
204 | 0 | if (!src_attribute_value.is_empty()) { |
205 | 0 | auto parsed_src = document().parse_url(src_attribute_value); |
206 | 0 | if (parsed_src.is_valid()) |
207 | 0 | url = parsed_src; |
208 | 0 | } |
209 | | |
210 | | // 3. If the inclusive ancestor navigables of element's node navigable contains a navigable |
211 | | // whose active document's URL equals url with exclude fragments set to true, then return null. |
212 | 0 | if (m_content_navigable) { |
213 | 0 | for (auto const& navigable : document().inclusive_ancestor_navigables()) { |
214 | 0 | VERIFY(navigable->active_document()); |
215 | 0 | if (navigable->active_document()->url().equals(url, URL::ExcludeFragment::Yes)) |
216 | 0 | return {}; |
217 | 0 | } |
218 | 0 | } |
219 | | |
220 | | // 4. If url matches about:blank and initialInsertion is true, then perform the URL and history update steps given element's content navigable's active document and url. |
221 | 0 | if (url_matches_about_blank(url) && initial_insertion) { |
222 | 0 | auto& document = *m_content_navigable->active_document(); |
223 | 0 | perform_url_and_history_update_steps(document, url); |
224 | 0 | } |
225 | | |
226 | | // 5. Return url. |
227 | 0 | return url; |
228 | 0 | } |
229 | | |
230 | | // https://html.spec.whatwg.org/multipage/iframe-embed-object.html#navigate-an-iframe-or-frame |
231 | | void NavigableContainer::navigate_an_iframe_or_frame(URL::URL url, ReferrerPolicy::ReferrerPolicy referrer_policy, Optional<String> srcdoc_string) |
232 | 0 | { |
233 | | // 1. Let historyHandling be "auto". |
234 | 0 | auto history_handling = Bindings::NavigationHistoryBehavior::Auto; |
235 | | |
236 | | // 2. If element's content navigable's active document is not completely loaded, then set historyHandling to "replace". |
237 | 0 | if (m_content_navigable->active_document() && !m_content_navigable->active_document()->is_completely_loaded()) { |
238 | 0 | history_handling = Bindings::NavigationHistoryBehavior::Replace; |
239 | 0 | } |
240 | | |
241 | | // FIXME: 3. If element is an iframe, then set element's pending resource-timing start time to the current high resolution |
242 | | // time given element's node document's relevant global object. |
243 | | |
244 | | // 4. Navigate element's content navigable to url using element's node document, with historyHandling set to historyHandling, |
245 | | // referrerPolicy set to referrerPolicy, and documentResource set to scrdocString. |
246 | 0 | Variant<Empty, String, POSTResource> document_resource = Empty {}; |
247 | 0 | if (srcdoc_string.has_value()) |
248 | 0 | document_resource = srcdoc_string.value(); |
249 | 0 | MUST(m_content_navigable->navigate({ .url = url, |
250 | 0 | .source_document = document(), |
251 | 0 | .document_resource = document_resource, |
252 | 0 | .history_handling = history_handling, |
253 | 0 | .referrer_policy = referrer_policy })); |
254 | 0 | } |
255 | | |
256 | | // https://html.spec.whatwg.org/multipage/document-sequences.html#destroy-a-child-navigable |
257 | | void NavigableContainer::destroy_the_child_navigable() |
258 | 0 | { |
259 | | // 1. Let navigable be container's content navigable. |
260 | 0 | auto navigable = content_navigable(); |
261 | | |
262 | | // 2. If navigable is null, then return. |
263 | 0 | if (!navigable) |
264 | 0 | return; |
265 | | |
266 | | // Not in the spec: |
267 | | // Setting container's content navigable makes document *not* be "fully active". |
268 | | // Therefore, it is moved to run in afterAllDestruction callback of "destroy a document and its descendants" |
269 | | // when all queued tasks are done. |
270 | | // "Has been destroyed" flag is used instead to check whether navigable is already destroyed. |
271 | 0 | if (navigable->has_been_destroyed()) |
272 | 0 | return; |
273 | 0 | navigable->set_has_been_destroyed(); |
274 | | |
275 | | // FIXME: 4. Inform the navigation API about child navigable destruction given navigable. |
276 | | |
277 | | // 5. Destroy a document and its descendants given navigable's active document. |
278 | 0 | navigable->active_document()->destroy_a_document_and_its_descendants(JS::create_heap_function(heap(), [this, navigable] { |
279 | | // 3. Set container's content navigable to null. |
280 | 0 | m_content_navigable = nullptr; |
281 | | |
282 | | // Not in the spec: |
283 | 0 | HTML::all_navigables().remove(navigable); |
284 | | |
285 | | // 6. Let parentDocState be container's node navigable's active session history entry's document state. |
286 | 0 | auto parent_doc_state = this->navigable()->active_session_history_entry()->document_state(); |
287 | | |
288 | | // 7. Remove the nested history from parentDocState's nested histories whose id equals navigable's id. |
289 | 0 | parent_doc_state->nested_histories().remove_all_matching([&](auto& nested_history) { |
290 | 0 | return navigable->id() == nested_history.id; |
291 | 0 | }); |
292 | | |
293 | | // 8. Let traversable be container's node navigable's traversable navigable. |
294 | 0 | auto traversable = this->navigable()->traversable_navigable(); |
295 | | |
296 | | // 9. Append the following session history traversal steps to traversable: |
297 | 0 | traversable->append_session_history_traversal_steps(JS::create_heap_function(heap(), [traversable] { |
298 | | // 1. Update for navigable creation/destruction given traversable. |
299 | 0 | traversable->update_for_navigable_creation_or_destruction(); |
300 | 0 | })); |
301 | 0 | })); |
302 | 0 | } |
303 | | |
304 | | // https://html.spec.whatwg.org/multipage/iframe-embed-object.html#potentially-delays-the-load-event |
305 | | bool NavigableContainer::currently_delays_the_load_event() const |
306 | 0 | { |
307 | 0 | if (!m_content_navigable_initialized) |
308 | 0 | return true; |
309 | | |
310 | 0 | if (!m_potentially_delays_the_load_event) |
311 | 0 | return false; |
312 | | |
313 | | // If an element type potentially delays the load event, then for each element element of that type, |
314 | | // the user agent must delay the load event of element's node document if element's content navigable is non-null |
315 | | // and any of the following are true: |
316 | 0 | if (!m_content_navigable) |
317 | 0 | return false; |
318 | | |
319 | | // - element's content navigable's active document is not ready for post-load tasks; |
320 | 0 | if (!m_content_navigable->active_document()->ready_for_post_load_tasks()) |
321 | 0 | return true; |
322 | | |
323 | | // - element's content navigable's is delaying load events is true; or |
324 | 0 | if (m_content_navigable->is_delaying_load_events()) |
325 | 0 | return true; |
326 | | |
327 | | // - anything is delaying the load event of element's content navigable's active document. |
328 | 0 | if (m_content_navigable->active_document()->anything_is_delaying_the_load_event()) |
329 | 0 | return true; |
330 | | |
331 | 0 | return false; |
332 | 0 | } |
333 | | |
334 | | } |