Serenity Operating System
1/*
2 * Copyright (c) 2018-2023, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
4 * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
5 * Copyright (c) 2021, Sam Atkins <atkinssj@serenityos.org>
6 *
7 * SPDX-License-Identifier: BSD-2-Clause
8 */
9
10#include <AK/CharacterTypes.h>
11#include <AK/Debug.h>
12#include <AK/StringBuilder.h>
13#include <AK/Utf8View.h>
14#include <LibJS/Interpreter.h>
15#include <LibJS/Runtime/FunctionObject.h>
16#include <LibWeb/Bindings/MainThreadVM.h>
17#include <LibWeb/CSS/MediaQueryList.h>
18#include <LibWeb/CSS/MediaQueryListEvent.h>
19#include <LibWeb/CSS/StyleComputer.h>
20#include <LibWeb/Cookie/ParsedCookie.h>
21#include <LibWeb/DOM/Comment.h>
22#include <LibWeb/DOM/CustomEvent.h>
23#include <LibWeb/DOM/DOMImplementation.h>
24#include <LibWeb/DOM/Document.h>
25#include <LibWeb/DOM/DocumentFragment.h>
26#include <LibWeb/DOM/DocumentType.h>
27#include <LibWeb/DOM/Element.h>
28#include <LibWeb/DOM/ElementFactory.h>
29#include <LibWeb/DOM/Event.h>
30#include <LibWeb/DOM/HTMLCollection.h>
31#include <LibWeb/DOM/NodeIterator.h>
32#include <LibWeb/DOM/ProcessingInstruction.h>
33#include <LibWeb/DOM/Range.h>
34#include <LibWeb/DOM/ShadowRoot.h>
35#include <LibWeb/DOM/Text.h>
36#include <LibWeb/DOM/TreeWalker.h>
37#include <LibWeb/Dump.h>
38#include <LibWeb/HTML/AttributeNames.h>
39#include <LibWeb/HTML/BrowsingContext.h>
40#include <LibWeb/HTML/EventLoop/EventLoop.h>
41#include <LibWeb/HTML/EventNames.h>
42#include <LibWeb/HTML/HTMLAnchorElement.h>
43#include <LibWeb/HTML/HTMLAreaElement.h>
44#include <LibWeb/HTML/HTMLBaseElement.h>
45#include <LibWeb/HTML/HTMLBodyElement.h>
46#include <LibWeb/HTML/HTMLEmbedElement.h>
47#include <LibWeb/HTML/HTMLFormElement.h>
48#include <LibWeb/HTML/HTMLFrameSetElement.h>
49#include <LibWeb/HTML/HTMLHeadElement.h>
50#include <LibWeb/HTML/HTMLHtmlElement.h>
51#include <LibWeb/HTML/HTMLIFrameElement.h>
52#include <LibWeb/HTML/HTMLImageElement.h>
53#include <LibWeb/HTML/HTMLLinkElement.h>
54#include <LibWeb/HTML/HTMLScriptElement.h>
55#include <LibWeb/HTML/HTMLTitleElement.h>
56#include <LibWeb/HTML/Location.h>
57#include <LibWeb/HTML/MessageEvent.h>
58#include <LibWeb/HTML/NavigationParams.h>
59#include <LibWeb/HTML/Origin.h>
60#include <LibWeb/HTML/Parser/HTMLParser.h>
61#include <LibWeb/HTML/Scripting/ExceptionReporter.h>
62#include <LibWeb/HTML/Scripting/WindowEnvironmentSettingsObject.h>
63#include <LibWeb/HTML/Window.h>
64#include <LibWeb/HTML/WindowProxy.h>
65#include <LibWeb/HighResolutionTime/TimeOrigin.h>
66#include <LibWeb/Infra/Strings.h>
67#include <LibWeb/Layout/BlockFormattingContext.h>
68#include <LibWeb/Layout/TreeBuilder.h>
69#include <LibWeb/Layout/Viewport.h>
70#include <LibWeb/Namespace.h>
71#include <LibWeb/Page/Page.h>
72#include <LibWeb/Platform/Timer.h>
73#include <LibWeb/SVG/TagNames.h>
74#include <LibWeb/Selection/Selection.h>
75#include <LibWeb/UIEvents/EventNames.h>
76#include <LibWeb/UIEvents/FocusEvent.h>
77#include <LibWeb/UIEvents/KeyboardEvent.h>
78#include <LibWeb/UIEvents/MouseEvent.h>
79#include <LibWeb/WebIDL/DOMException.h>
80#include <LibWeb/WebIDL/ExceptionOr.h>
81
82namespace Web::DOM {
83
84// https://html.spec.whatwg.org/multipage/origin.html#obtain-browsing-context-navigation
85static JS::NonnullGCPtr<HTML::BrowsingContext> obtain_a_browsing_context_to_use_for_a_navigation_response(
86 HTML::BrowsingContext& browsing_context,
87 HTML::SandboxingFlagSet sandbox_flags,
88 HTML::CrossOriginOpenerPolicy navigation_coop,
89 HTML::CrossOriginOpenerPolicyEnforcementResult coop_enforcement_result)
90{
91 // 1. If browsingContext is not a top-level browsing context, return browsingContext.
92 if (!browsing_context.is_top_level())
93 return browsing_context;
94
95 // 2. If coopEnforcementResult's needs a browsing context group switch is false, then:
96 if (!coop_enforcement_result.needs_a_browsing_context_group_switch) {
97 // 1. If coopEnforcementResult's would need a browsing context group switch due to report-only is true,
98 if (coop_enforcement_result.would_need_a_browsing_context_group_switch_due_to_report_only) {
99 // FIXME: set browsing context's virtual browsing context group ID to a new unique identifier.
100 }
101 // 2. Return browsingContext.
102 return browsing_context;
103 }
104
105 // 3. Let newBrowsingContext be the result of creating a new top-level browsing context.
106 VERIFY(browsing_context.page());
107 auto new_browsing_context = HTML::BrowsingContext::create_a_new_top_level_browsing_context(*browsing_context.page());
108
109 // FIXME: 4. If navigationCOOP's value is "same-origin-plurs-COEP", then set newBrowsingContext's group's
110 // cross-origin isolation mode to either "logical" or "concrete". The choice of which is implementation-defined.
111
112 // 5. If sandboxFlags is not empty, then:
113 if (!sandbox_flags.is_empty()) {
114 // 1. Assert navigationCOOP's value is "unsafe-none".
115 VERIFY(navigation_coop.value == HTML::CrossOriginOpenerPolicyValue::UnsafeNone);
116
117 // 2. Assert: newBrowsingContext's popup sandboxing flag set is empty.
118
119 // 3. Set newBrowsingContext's popup sandboxing flag set to a clone of sandboxFlags.
120 }
121
122 // 6. Discard browsingContext.
123 browsing_context.discard();
124
125 // 7. Return newBrowsingContext.
126 return new_browsing_context;
127}
128
129// https://html.spec.whatwg.org/multipage/browsing-the-web.html#initialise-the-document-object
130WebIDL::ExceptionOr<JS::NonnullGCPtr<Document>> Document::create_and_initialize(Type type, DeprecatedString content_type, HTML::NavigationParams navigation_params)
131{
132 // 1. Let browsingContext be the result of the obtaining a browsing context to use for a navigation response
133 // given navigationParams's browsing context, navigationParams's final sandboxing flag set,
134 // navigationParams's cross-origin opener policy, and navigationParams's COOP enforcement result.
135 auto browsing_context = obtain_a_browsing_context_to_use_for_a_navigation_response(
136 *navigation_params.browsing_context,
137 navigation_params.final_sandboxing_flag_set,
138 navigation_params.cross_origin_opener_policy,
139 navigation_params.coop_enforcement_result);
140
141 // FIXME: 2. Let permissionsPolicy be the result of creating a permissions policy from a response
142 // given browsingContext, navigationParams's origin, and navigationParams's response.
143
144 // 3. Let creationURL be navigationParams's response's URL.
145 auto creation_url = navigation_params.response->url();
146
147 // 4. If navigationParams's request is non-null, then set creationURL to navigationParams's request's current URL.
148 if (navigation_params.request) {
149 creation_url = navigation_params.request->current_url();
150 }
151
152 JS::GCPtr<HTML::Window> window;
153
154 // 5. If browsingContext is still on its initial about:blank Document,
155 // and navigationParams's history handling is "replace",
156 // and browsingContext's active document's origin is same origin-domain with navigationParams's origin,
157 // then do nothing.
158 if (browsing_context->still_on_its_initial_about_blank_document()
159 && navigation_params.history_handling == HTML::HistoryHandlingBehavior::Replace
160 && (browsing_context->active_document() && browsing_context->active_document()->origin().is_same_origin(navigation_params.origin))) {
161 // Do nothing.
162 // NOTE: This means that both the initial about:blank Document, and the new Document that is about to be created, will share the same Window object.
163 window = browsing_context->active_window();
164 }
165
166 // 6. Otherwise:
167 else {
168 // FIXME: 1. Let oacHeader be the result of getting a structured field value given `Origin-Agent-Cluster` and "item" from response's header list.
169
170 // FIXME: 2. Let requestsOAC be true if oacHeader is not null and oacHeader[0] is the boolean true; otherwise false.
171 [[maybe_unused]] auto requests_oac = false;
172
173 // FIXME: 3. If navigationParams's reserved environment is a non-secure context, then set requestsOAC to false.
174
175 // FIXME: 4. Let agent be the result of obtaining a similar-origin window agent given navigationParams's origin, browsingContext's group, and requestsOAC.
176
177 // 5. Let realm execution context be the result of creating a new JavaScript realm given agent and the following customizations:
178 auto realm_execution_context = Bindings::create_a_new_javascript_realm(
179 Bindings::main_thread_vm(),
180 [&](JS::Realm& realm) -> JS::Object* {
181 // - For the global object, create a new Window object.
182 window = HTML::Window::create(realm).release_value_but_fixme_should_propagate_errors();
183 return window;
184 },
185 [&](JS::Realm&) -> JS::Object* {
186 // - For the global this binding, use browsingContext's WindowProxy object.
187 return browsing_context->window_proxy();
188 });
189
190 // 6. Let topLevelCreationURL be creationURL.
191 auto top_level_creation_url = creation_url;
192
193 // 7. Let topLevelOrigin be navigationParams's origin.
194 auto top_level_origin = navigation_params.origin;
195
196 // 8. If browsingContext is not a top-level browsing context, then:
197 if (!browsing_context->is_top_level()) {
198 // 1. Let parentEnvironment be browsingContext's container's relevant settings object.
199 VERIFY(browsing_context->container());
200 auto& parent_environment = HTML::relevant_settings_object(*browsing_context->container());
201
202 // 2. Set topLevelCreationURL to parentEnvironment's top-level creation URL.
203 top_level_creation_url = parent_environment.top_level_creation_url;
204
205 // 3. Set topLevelOrigin to parentEnvironment's top-level origin.
206 top_level_origin = parent_environment.top_level_origin;
207 }
208
209 // 9. Set up a window environment settings object with creationURL, realm execution context,
210 // navigationParams's reserved environment, topLevelCreationURL, and topLevelOrigin.
211
212 // FIXME: Why do we assume `creation_url` is non-empty here? Is this a spec bug?
213 // FIXME: Why do we assume `top_level_creation_url` is non-empty here? Is this a spec bug?
214 TRY(HTML::WindowEnvironmentSettingsObject::setup(
215 creation_url.value(),
216 move(realm_execution_context),
217 navigation_params.reserved_environment,
218 top_level_creation_url.value(),
219 top_level_origin));
220 }
221
222 // FIXME: 7. Let loadTimingInfo be a new document load timing info with its navigation start time set to response's timing info's start time.
223
224 // 8. Let document be a new Document,
225 // whose type is type,
226 // content type is contentType,
227 // origin is navigationParams's origin,
228 // policy container is navigationParams's policy container,
229 // FIXME: permissions policy is permissionsPolicy,
230 // active sandboxing flag set is navigationParams's final sandboxing flag set,
231 // FIXME: and cross-origin opener policy is navigationParams's cross-origin opener policy,
232 // FIXME: load timing info is loadTimingInfo,
233 // and navigation id is navigationParams's id.
234 auto document = TRY(Document::create(window->realm()));
235 document->m_type = type;
236 document->m_content_type = move(content_type);
237 document->set_origin(navigation_params.origin);
238 document->m_policy_container = navigation_params.policy_container;
239 document->m_active_sandboxing_flag_set = navigation_params.final_sandboxing_flag_set;
240 document->m_navigation_id = navigation_params.id;
241
242 document->m_window = window;
243 window->set_associated_document(*document);
244
245 // 9. Set document's URL to creationURL.
246 document->m_url = creation_url.value();
247
248 // 10. Set document's current document readiness to "loading".
249 document->m_readiness = HTML::DocumentReadyState::Loading;
250
251 // FIXME: 11. Run CSP initialization for a Document given document.
252
253 // 12. If navigationParams's request is non-null, then:
254 if (navigation_params.request) {
255 // 1. Set document's referrer to the empty string.
256 document->m_referrer = DeprecatedString::empty();
257
258 // 2. Let referrer be navigationParams's request's referrer.
259 auto& referrer = navigation_params.request->referrer();
260
261 // 3. If referrer is a URL record, then set document's referrer to the serialization of referrer.
262 if (referrer.has<AK::URL>()) {
263 document->m_referrer = referrer.get<AK::URL>().serialize();
264 }
265 }
266
267 // FIXME: 13. Let historyHandling be navigationParams's history handling.
268
269 // FIXME: 14: Let navigationTimingType be the result of switching on navigationParams's history handling...
270
271 // FIXME: 15. Let redirectCount be 0 if navigationParams's has cross-origin redirects is true;
272 // otherwise navigationParams's request's redirect count.
273
274 // FIXME: 16. Create the navigation timing entry for document, with navigationParams's response's timing info,
275 // redirectCount, navigationTimingType, and navigationParams's response's service worker timing info.
276
277 // FIXME: 17. If navigationParams's response has a `Refresh` header, then...
278
279 // FIXME: 18. If navigationParams's commit early hints is not null, then call navigationParams's commit early hints with document.
280
281 // FIXME: 19. Process link headers given document, navigationParams's response, and "pre-media".
282
283 // 20. Return document.
284 return document;
285}
286
287WebIDL::ExceptionOr<JS::NonnullGCPtr<Document>> Document::construct_impl(JS::Realm& realm)
288{
289 return Document::create(realm);
290}
291
292WebIDL::ExceptionOr<JS::NonnullGCPtr<Document>> Document::create(JS::Realm& realm, AK::URL const& url)
293{
294 return MUST_OR_THROW_OOM(realm.heap().allocate<Document>(realm, realm, url));
295}
296
297Document::Document(JS::Realm& realm, const AK::URL& url)
298 : ParentNode(realm, *this, NodeType::DOCUMENT_NODE)
299 , m_style_computer(make<CSS::StyleComputer>(*this))
300 , m_url(url)
301{
302 HTML::main_thread_event_loop().register_document({}, *this);
303
304 m_style_update_timer = Platform::Timer::create_single_shot(0, [this] {
305 update_style();
306 });
307
308 m_layout_update_timer = Platform::Timer::create_single_shot(0, [this] {
309 force_layout();
310 });
311}
312
313Document::~Document()
314{
315 HTML::main_thread_event_loop().unregister_document({}, *this);
316}
317
318JS::ThrowCompletionOr<void> Document::initialize(JS::Realm& realm)
319{
320 MUST_OR_THROW_OOM(Base::initialize(realm));
321 set_prototype(&Bindings::ensure_web_prototype<Bindings::DocumentPrototype>(realm, "Document"));
322
323 m_selection = MUST_OR_THROW_OOM(heap().allocate<Selection::Selection>(realm, realm, *this));
324
325 return {};
326}
327
328void Document::visit_edges(Cell::Visitor& visitor)
329{
330 Base::visit_edges(visitor);
331 visitor.visit(m_window.ptr());
332 visitor.visit(m_style_sheets.ptr());
333 visitor.visit(m_hovered_node.ptr());
334 visitor.visit(m_inspected_node.ptr());
335 visitor.visit(m_active_favicon.ptr());
336 visitor.visit(m_focused_element.ptr());
337 visitor.visit(m_active_element.ptr());
338 visitor.visit(m_implementation.ptr());
339 visitor.visit(m_current_script.ptr());
340 visitor.visit(m_associated_inert_template_document.ptr());
341 visitor.visit(m_appropriate_template_contents_owner_document);
342 visitor.visit(m_pending_parsing_blocking_script.ptr());
343 visitor.visit(m_history.ptr());
344
345 visitor.visit(m_browsing_context);
346
347 visitor.visit(m_applets);
348 visitor.visit(m_anchors);
349 visitor.visit(m_images);
350 visitor.visit(m_embeds);
351 visitor.visit(m_links);
352 visitor.visit(m_forms);
353 visitor.visit(m_scripts);
354 visitor.visit(m_all);
355 visitor.visit(m_selection);
356 visitor.visit(m_first_base_element_with_href_in_tree_order);
357 visitor.visit(m_parser);
358
359 for (auto& script : m_scripts_to_execute_when_parsing_has_finished)
360 visitor.visit(script.ptr());
361 for (auto& script : m_scripts_to_execute_as_soon_as_possible)
362 visitor.visit(script.ptr());
363
364 for (auto& node_iterator : m_node_iterators)
365 visitor.visit(node_iterator);
366
367 for (auto& target : m_pending_scroll_event_targets)
368 visitor.visit(target.ptr());
369 for (auto& target : m_pending_scrollend_event_targets)
370 visitor.visit(target.ptr());
371}
372
373// https://w3c.github.io/selection-api/#dom-document-getselection
374JS::GCPtr<Selection::Selection> Document::get_selection() const
375{
376 // The method must return the selection associated with this if this has an associated browsing context,
377 // and it must return null otherwise.
378 if (!browsing_context())
379 return {};
380 return m_selection;
381}
382
383// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-document-write
384WebIDL::ExceptionOr<void> Document::write(Vector<DeprecatedString> const& strings)
385{
386 StringBuilder builder;
387 builder.join(""sv, strings);
388
389 return run_the_document_write_steps(builder.to_deprecated_string());
390}
391
392// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-document-writeln
393WebIDL::ExceptionOr<void> Document::writeln(Vector<DeprecatedString> const& strings)
394{
395 StringBuilder builder;
396 builder.join(""sv, strings);
397 builder.append("\n"sv);
398
399 return run_the_document_write_steps(builder.to_deprecated_string());
400}
401
402// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-write-steps
403WebIDL::ExceptionOr<void> Document::run_the_document_write_steps(DeprecatedString input)
404{
405 // 1. If document is an XML document, then throw an "InvalidStateError" DOMException.
406 if (m_type == Type::XML)
407 return WebIDL::InvalidStateError::create(realm(), "write() called on XML document.");
408
409 // 2. If document's throw-on-dynamic-markup-insertion counter is greater than 0, then throw an "InvalidStateError" DOMException.
410 if (m_throw_on_dynamic_markup_insertion_counter > 0)
411 return WebIDL::InvalidStateError::create(realm(), "throw-on-dynamic-markup-insertion-counter greater than zero.");
412
413 // 3. If document's active parser was aborted is true, then return.
414 if (m_active_parser_was_aborted)
415 return {};
416
417 // 4. If the insertion point is undefined, then:
418 if (!(m_parser && m_parser->tokenizer().is_insertion_point_defined())) {
419 // 1. If document's unload counter is greater than 0 or document's ignore-destructive-writes counter is greater than 0, then return.
420 if (m_unload_counter > 0 || m_ignore_destructive_writes_counter > 0)
421 return {};
422
423 // 2. Run the document open steps with document.
424 TRY(open());
425 }
426
427 // 5. Insert input into the input stream just before the insertion point.
428 m_parser->tokenizer().insert_input_at_insertion_point(input);
429
430 // 6. If there is no pending parsing-blocking script, have the HTML parser process input, one code point at a time, processing resulting tokens as they are emitted, and stopping when the tokenizer reaches the insertion point or when the processing of the tokenizer is aborted by the tree construction stage (this can happen if a script end tag token is emitted by the tokenizer).
431 if (!pending_parsing_blocking_script())
432 m_parser->run();
433
434 return {};
435}
436
437// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-document-open
438WebIDL::ExceptionOr<Document*> Document::open(DeprecatedString const&, DeprecatedString const&)
439{
440 // 1. If document is an XML document, then throw an "InvalidStateError" DOMException exception.
441 if (m_type == Type::XML)
442 return WebIDL::InvalidStateError::create(realm(), "open() called on XML document.");
443
444 // 2. If document's throw-on-dynamic-markup-insertion counter is greater than 0, then throw an "InvalidStateError" DOMException.
445 if (m_throw_on_dynamic_markup_insertion_counter > 0)
446 return WebIDL::InvalidStateError::create(realm(), "throw-on-dynamic-markup-insertion-counter greater than zero.");
447
448 // FIXME: 3. Let entryDocument be the entry global object's associated Document.
449 auto& entry_document = *this;
450
451 // 4. If document's origin is not same origin to entryDocument's origin, then throw a "SecurityError" DOMException.
452 if (origin() != entry_document.origin())
453 return WebIDL::SecurityError::create(realm(), "Document.origin() not the same as entryDocument's.");
454
455 // 5. If document has an active parser whose script nesting level is greater than 0, then return document.
456 if (m_parser && m_parser->script_nesting_level() > 0)
457 return this;
458
459 // 6. Similarly, if document's unload counter is greater than 0, then return document.
460 if (m_unload_counter > 0)
461 return this;
462
463 // 7. If document's active parser was aborted is true, then return document.
464 if (m_active_parser_was_aborted)
465 return this;
466
467 // FIXME: 8. If document's browsing context is non-null and there is an existing attempt to navigate document's browsing context, then stop document loading given document.
468
469 // FIXME: 9. For each shadow-including inclusive descendant node of document, erase all event listeners and handlers given node.
470
471 // FIXME 10. If document is the associated Document of document's relevant global object, then erase all event listeners and handlers given document's relevant global object.
472
473 // 11. Replace all with null within document, without firing any mutation events.
474 replace_all(nullptr);
475
476 // 12. If document is fully active, then:
477 if (is_fully_active()) {
478 // 1. Let newURL be a copy of entryDocument's URL.
479 auto new_url = entry_document.url();
480 // 2. If entryDocument is not document, then set newURL's fragment to null.
481 if (&entry_document != this)
482 new_url.set_fragment("");
483
484 // FIXME: 3. Run the URL and history update steps with document and newURL.
485 }
486
487 // 13. Set document's is initial about:blank to false.
488 set_is_initial_about_blank(false);
489
490 // FIXME: 14. If document's iframe load in progress flag is set, then set document's mute iframe load flag.
491
492 // 15. Set document to no-quirks mode.
493 set_quirks_mode(QuirksMode::No);
494
495 // 16. Create a new HTML parser and associate it with document. This is a script-created parser (meaning that it can be closed by the document.open() and document.close() methods, and that the tokenizer will wait for an explicit call to document.close() before emitting an end-of-file token). The encoding confidence is irrelevant.
496 m_parser = HTML::HTMLParser::create_for_scripting(*this);
497
498 // 17. Set the insertion point to point at just before the end of the input stream (which at this point will be empty).
499 m_parser->tokenizer().update_insertion_point();
500
501 // 18. Update the current document readiness of document to "loading".
502 update_readiness(HTML::DocumentReadyState::Loading);
503
504 // 19. Return document.
505 return this;
506}
507
508// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-document-open-window
509WebIDL::ExceptionOr<JS::GCPtr<HTML::WindowProxy>> Document::open(DeprecatedString const& url, DeprecatedString const& name, DeprecatedString const& features)
510{
511 // 1. If this is not fully active, then throw an "InvalidAccessError" DOMException exception.
512 if (!is_fully_active())
513 return WebIDL::InvalidAccessError::create(realm(), "Cannot perform open on a document that isn't fully active."sv);
514
515 // 2. Return the result of running the window open steps with url, name, and features.
516 return window().open_impl(url, name, features);
517}
518
519// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#closing-the-input-stream
520WebIDL::ExceptionOr<void> Document::close()
521{
522 // 1. If document is an XML document, then throw an "InvalidStateError" DOMException exception.
523 if (m_type == Type::XML)
524 return WebIDL::InvalidStateError::create(realm(), "close() called on XML document.");
525
526 // 2. If document's throw-on-dynamic-markup-insertion counter is greater than 0, then throw an "InvalidStateError" DOMException.
527 if (m_throw_on_dynamic_markup_insertion_counter > 0)
528 return WebIDL::InvalidStateError::create(realm(), "throw-on-dynamic-markup-insertion-counter greater than zero.");
529
530 // 3. If there is no script-created parser associated with the document, then return.
531 if (!m_parser)
532 return {};
533
534 // FIXME: 4. Insert an explicit "EOF" character at the end of the parser's input stream.
535 m_parser->tokenizer().insert_eof();
536
537 // 5. If there is a pending parsing-blocking script, then return.
538 if (pending_parsing_blocking_script())
539 return {};
540
541 // FIXME: 6. Run the tokenizer, processing resulting tokens as they are emitted, and stopping when the tokenizer reaches the explicit "EOF" character or spins the event loop.
542 m_parser->run();
543
544 return {};
545}
546
547HTML::Origin Document::origin() const
548{
549 return m_origin;
550}
551
552void Document::set_origin(HTML::Origin const& origin)
553{
554 m_origin = origin;
555}
556
557void Document::schedule_style_update()
558{
559 if (m_style_update_timer->is_active())
560 return;
561 m_style_update_timer->start();
562}
563
564void Document::schedule_layout_update()
565{
566 if (m_layout_update_timer->is_active())
567 return;
568 m_layout_update_timer->start();
569}
570
571bool Document::is_child_allowed(Node const& node) const
572{
573 switch (node.type()) {
574 case NodeType::DOCUMENT_NODE:
575 case NodeType::TEXT_NODE:
576 return false;
577 case NodeType::COMMENT_NODE:
578 return true;
579 case NodeType::DOCUMENT_TYPE_NODE:
580 return !first_child_of_type<DocumentType>();
581 case NodeType::ELEMENT_NODE:
582 return !first_child_of_type<Element>();
583 default:
584 return false;
585 }
586}
587
588Element* Document::document_element()
589{
590 return first_child_of_type<Element>();
591}
592
593Element const* Document::document_element() const
594{
595 return first_child_of_type<Element>();
596}
597
598HTML::HTMLHtmlElement* Document::html_element()
599{
600 auto* html = document_element();
601 if (is<HTML::HTMLHtmlElement>(html))
602 return verify_cast<HTML::HTMLHtmlElement>(html);
603 return nullptr;
604}
605
606HTML::HTMLHeadElement* Document::head()
607{
608 auto* html = html_element();
609 if (!html)
610 return nullptr;
611 return html->first_child_of_type<HTML::HTMLHeadElement>();
612}
613
614HTML::HTMLElement* Document::body()
615{
616 auto* html = html_element();
617 if (!html)
618 return nullptr;
619 auto* first_body = html->first_child_of_type<HTML::HTMLBodyElement>();
620 if (first_body)
621 return first_body;
622 auto* first_frameset = html->first_child_of_type<HTML::HTMLFrameSetElement>();
623 if (first_frameset)
624 return first_frameset;
625 return nullptr;
626}
627
628// https://html.spec.whatwg.org/multipage/dom.html#dom-document-body
629WebIDL::ExceptionOr<void> Document::set_body(HTML::HTMLElement* new_body)
630{
631 if (!is<HTML::HTMLBodyElement>(new_body) && !is<HTML::HTMLFrameSetElement>(new_body))
632 return WebIDL::HierarchyRequestError::create(realm(), "Invalid document body element, must be 'body' or 'frameset'");
633
634 auto* existing_body = body();
635 if (existing_body) {
636 (void)TRY(existing_body->parent()->replace_child(*new_body, *existing_body));
637 return {};
638 }
639
640 auto* document_element = this->document_element();
641 if (!document_element)
642 return WebIDL::HierarchyRequestError::create(realm(), "Missing document element");
643
644 (void)TRY(document_element->append_child(*new_body));
645 return {};
646}
647
648DeprecatedString Document::title() const
649{
650 auto* head_element = head();
651 if (!head_element)
652 return {};
653
654 auto* title_element = head_element->first_child_of_type<HTML::HTMLTitleElement>();
655 if (!title_element)
656 return {};
657
658 auto raw_title = title_element->text_content();
659
660 StringBuilder builder;
661 bool last_was_space = false;
662 for (auto code_point : Utf8View(raw_title)) {
663 if (is_ascii_space(code_point)) {
664 last_was_space = true;
665 } else {
666 if (last_was_space && !builder.is_empty())
667 builder.append(' ');
668 builder.append_code_point(code_point);
669 last_was_space = false;
670 }
671 }
672 return builder.to_deprecated_string();
673}
674
675void Document::set_title(DeprecatedString const& title)
676{
677 auto* head_element = const_cast<HTML::HTMLHeadElement*>(head());
678 if (!head_element)
679 return;
680
681 JS::GCPtr<HTML::HTMLTitleElement> title_element = head_element->first_child_of_type<HTML::HTMLTitleElement>();
682 if (!title_element) {
683 title_element = &static_cast<HTML::HTMLTitleElement&>(*create_element(HTML::TagNames::title).release_value());
684 MUST(head_element->append_child(*title_element));
685 }
686
687 title_element->remove_all_children(true);
688 MUST(title_element->append_child(heap().allocate<Text>(realm(), *this, title).release_allocated_value_but_fixme_should_propagate_errors()));
689
690 if (auto* page = this->page()) {
691 if (browsing_context() == &page->top_level_browsing_context())
692 page->client().page_did_change_title(title);
693 }
694}
695
696void Document::tear_down_layout_tree()
697{
698 if (!m_layout_root)
699 return;
700
701 // Gather up all the layout nodes in a vector and detach them from parents
702 // while the vector keeps them alive.
703
704 Vector<JS::Handle<Layout::Node>> layout_nodes;
705
706 m_layout_root->for_each_in_inclusive_subtree([&](auto& layout_node) {
707 layout_nodes.append(layout_node);
708 return IterationDecision::Continue;
709 });
710
711 for (auto& layout_node : layout_nodes) {
712 if (layout_node->parent())
713 layout_node->parent()->remove_child(*layout_node);
714 }
715
716 m_layout_root = nullptr;
717}
718
719Color Document::background_color(Gfx::Palette const& palette) const
720{
721 // CSS2 says we should use the HTML element's background color unless it's transparent...
722 if (auto* html_element = this->html_element(); html_element && html_element->layout_node()) {
723 auto color = html_element->layout_node()->computed_values().background_color();
724 if (color.alpha())
725 return color;
726 }
727
728 // ...in which case we use the BODY element's background color.
729 if (auto* body_element = body(); body_element && body_element->layout_node()) {
730 auto color = body_element->layout_node()->computed_values().background_color();
731 if (color.alpha())
732 return color;
733 }
734
735 // If both HTML and BODY are transparent, we fall back to the system's "base" palette color.
736 return palette.base();
737}
738
739Vector<CSS::BackgroundLayerData> const* Document::background_layers() const
740{
741 auto* body_element = body();
742 if (!body_element)
743 return {};
744
745 auto* body_layout_node = body_element->layout_node();
746 if (!body_layout_node)
747 return {};
748
749 return &body_layout_node->background_layers();
750}
751
752void Document::update_base_element(Badge<HTML::HTMLBaseElement>)
753{
754 JS::GCPtr<HTML::HTMLBaseElement const> base_element;
755
756 for_each_in_subtree_of_type<HTML::HTMLBaseElement>([&base_element](HTML::HTMLBaseElement const& base_element_in_tree) {
757 if (base_element_in_tree.has_attribute(HTML::AttributeNames::href)) {
758 base_element = &base_element_in_tree;
759 return IterationDecision::Break;
760 }
761
762 return IterationDecision::Continue;
763 });
764
765 m_first_base_element_with_href_in_tree_order = base_element;
766}
767
768JS::GCPtr<HTML::HTMLBaseElement const> Document::first_base_element_with_href_in_tree_order() const
769{
770 return m_first_base_element_with_href_in_tree_order;
771}
772
773// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fallback-base-url
774AK::URL Document::fallback_base_url() const
775{
776 // FIXME: 1. If document is an iframe srcdoc document, then return the document base URL of document's browsing context's container document.
777
778 // 2. If document's URL is about:blank, and document's browsing context's creator base URL is non-null, then return that creator base URL.
779 if (m_url == "about:blank"sv && browsing_context() && browsing_context()->creator_url().has_value())
780 return browsing_context()->creator_url().value();
781
782 // 3. Return document's URL.
783 return m_url;
784}
785
786// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#document-base-url
787AK::URL Document::base_url() const
788{
789 // 1. If there is no base element that has an href attribute in the Document, then return the Document's fallback base URL.
790 auto base_element = first_base_element_with_href_in_tree_order();
791 if (!base_element)
792 return fallback_base_url();
793
794 // 2. Otherwise, return the frozen base URL of the first base element in the Document that has an href attribute, in tree order.
795 return base_element->frozen_base_url();
796}
797
798// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#parse-a-url
799AK::URL Document::parse_url(DeprecatedString const& url) const
800{
801 // FIXME: Pass in document's character encoding.
802 return base_url().complete_url(url);
803}
804
805void Document::set_needs_layout()
806{
807 if (m_needs_layout)
808 return;
809 m_needs_layout = true;
810 schedule_layout_update();
811}
812
813void Document::force_layout()
814{
815 tear_down_layout_tree();
816 update_layout();
817}
818
819void Document::invalidate_layout()
820{
821 tear_down_layout_tree();
822 schedule_layout_update();
823}
824
825void Document::update_layout()
826{
827 // NOTE: If our parent document needs a relayout, we must do that *first*.
828 // This is necessary as the parent layout may cause our viewport to change.
829 if (browsing_context() && browsing_context()->container())
830 browsing_context()->container()->document().update_layout();
831
832 update_style();
833
834 if (!m_needs_layout && m_layout_root)
835 return;
836
837 // NOTE: If this is a document hosting <template> contents, layout is unnecessary.
838 if (m_created_for_appropriate_template_contents)
839 return;
840
841 if (!browsing_context())
842 return;
843
844 auto viewport_rect = browsing_context()->viewport_rect();
845
846 if (!m_layout_root) {
847 m_next_layout_node_serial_id = 0;
848 Layout::TreeBuilder tree_builder;
849 m_layout_root = verify_cast<Layout::Viewport>(*tree_builder.build(*this));
850 }
851
852 Layout::LayoutState layout_state;
853 layout_state.used_values_per_layout_node.resize(layout_node_count());
854
855 {
856 Layout::BlockFormattingContext root_formatting_context(layout_state, *m_layout_root, nullptr);
857
858 auto& viewport = static_cast<Layout::Viewport&>(*m_layout_root);
859 auto& viewport_state = layout_state.get_mutable(viewport);
860 viewport_state.set_content_width(viewport_rect.width());
861 viewport_state.set_content_height(viewport_rect.height());
862
863 root_formatting_context.run(
864 *m_layout_root,
865 Layout::LayoutMode::Normal,
866 Layout::AvailableSpace(
867 Layout::AvailableSize::make_definite(viewport_rect.width()),
868 Layout::AvailableSize::make_definite(viewport_rect.height())));
869 }
870
871 layout_state.commit();
872
873 browsing_context()->set_needs_display();
874
875 if (browsing_context()->is_top_level() && browsing_context()->active_document() == this) {
876 if (auto* page = this->page())
877 page->client().page_did_layout();
878 }
879
880 m_layout_root->recompute_selection_states();
881
882 m_needs_layout = false;
883 m_layout_update_timer->stop();
884}
885
886[[nodiscard]] static bool update_style_recursively(DOM::Node& node)
887{
888 bool const needs_full_style_update = node.document().needs_full_style_update();
889 bool needs_relayout = false;
890
891 if (is<Element>(node)) {
892 needs_relayout |= static_cast<Element&>(node).recompute_style() == Element::NeedsRelayout::Yes;
893 }
894 node.set_needs_style_update(false);
895
896 if (needs_full_style_update || node.child_needs_style_update()) {
897 if (node.is_element()) {
898 if (auto* shadow_root = static_cast<DOM::Element&>(node).shadow_root_internal()) {
899 if (needs_full_style_update || shadow_root->needs_style_update() || shadow_root->child_needs_style_update())
900 needs_relayout |= update_style_recursively(*shadow_root);
901 }
902 }
903 node.for_each_child([&](auto& child) {
904 if (needs_full_style_update || child.needs_style_update() || child.child_needs_style_update())
905 needs_relayout |= update_style_recursively(child);
906 return IterationDecision::Continue;
907 });
908 }
909
910 node.set_child_needs_style_update(false);
911 return needs_relayout;
912}
913
914void Document::update_style()
915{
916 if (!browsing_context())
917 return;
918 if (!needs_full_style_update() && !needs_style_update() && !child_needs_style_update())
919 return;
920
921 // NOTE: If this is a document hosting <template> contents, style update is unnecessary.
922 if (m_created_for_appropriate_template_contents)
923 return;
924
925 evaluate_media_rules();
926 if (update_style_recursively(*this))
927 invalidate_layout();
928 m_needs_full_style_update = false;
929 m_style_update_timer->stop();
930}
931
932void Document::set_link_color(Color color)
933{
934 m_link_color = color;
935}
936
937void Document::set_active_link_color(Color color)
938{
939 m_active_link_color = color;
940}
941
942void Document::set_visited_link_color(Color color)
943{
944 m_visited_link_color = color;
945}
946
947Layout::Viewport const* Document::layout_node() const
948{
949 return static_cast<Layout::Viewport const*>(Node::layout_node());
950}
951
952Layout::Viewport* Document::layout_node()
953{
954 return static_cast<Layout::Viewport*>(Node::layout_node());
955}
956
957void Document::set_inspected_node(Node* node)
958{
959 if (m_inspected_node.ptr() == node)
960 return;
961
962 if (m_inspected_node && m_inspected_node->layout_node())
963 m_inspected_node->layout_node()->set_needs_display();
964
965 m_inspected_node = node;
966
967 if (m_inspected_node && m_inspected_node->layout_node())
968 m_inspected_node->layout_node()->set_needs_display();
969}
970
971static Node* find_common_ancestor(Node* a, Node* b)
972{
973 if (!a || !b)
974 return nullptr;
975
976 if (a == b)
977 return a;
978
979 HashTable<Node*> ancestors;
980 for (auto* node = a; node; node = node->parent_or_shadow_host())
981 ancestors.set(node);
982
983 for (auto* node = b; node; node = node->parent_or_shadow_host()) {
984 if (ancestors.contains(node))
985 return node;
986 }
987
988 return nullptr;
989}
990
991void Document::set_hovered_node(Node* node)
992{
993 if (m_hovered_node.ptr() == node)
994 return;
995
996 JS::GCPtr<Node> old_hovered_node = move(m_hovered_node);
997 m_hovered_node = node;
998
999 auto* common_ancestor = find_common_ancestor(old_hovered_node, m_hovered_node);
1000 if (common_ancestor)
1001 common_ancestor->invalidate_style();
1002 else
1003 invalidate_style();
1004
1005 // https://w3c.github.io/uievents/#mouseleave
1006 if (old_hovered_node && (!m_hovered_node || !m_hovered_node->is_descendant_of(*old_hovered_node))) {
1007 // FIXME: Check if we need to dispatch these events in a specific order.
1008 for (auto target = old_hovered_node; target && target.ptr() != common_ancestor; target = target->parent()) {
1009 // FIXME: Populate the event with mouse coordinates, etc.
1010 target->dispatch_event(UIEvents::MouseEvent::create(realm(), UIEvents::EventNames::mouseleave).release_value_but_fixme_should_propagate_errors());
1011 }
1012 }
1013
1014 // https://w3c.github.io/uievents/#mouseenter
1015 if (m_hovered_node && (!old_hovered_node || !m_hovered_node->is_ancestor_of(*old_hovered_node))) {
1016 // FIXME: Check if we need to dispatch these events in a specific order.
1017 for (auto target = m_hovered_node; target && target.ptr() != common_ancestor; target = target->parent()) {
1018 // FIXME: Populate the event with mouse coordinates, etc.
1019 target->dispatch_event(UIEvents::MouseEvent::create(realm(), UIEvents::EventNames::mouseenter).release_value_but_fixme_should_propagate_errors());
1020 }
1021 }
1022}
1023
1024JS::NonnullGCPtr<HTMLCollection> Document::get_elements_by_name(DeprecatedString const& name)
1025{
1026 return HTMLCollection::create(*this, [name](Element const& element) {
1027 return element.name() == name;
1028 }).release_value_but_fixme_should_propagate_errors();
1029}
1030
1031JS::NonnullGCPtr<HTMLCollection> Document::get_elements_by_class_name(DeprecatedFlyString const& class_names)
1032{
1033 Vector<FlyString> list_of_class_names;
1034 for (auto& name : class_names.view().split_view(' ')) {
1035 list_of_class_names.append(FlyString::from_utf8(name).release_value_but_fixme_should_propagate_errors());
1036 }
1037 return HTMLCollection::create(*this, [list_of_class_names = move(list_of_class_names), quirks_mode = document().in_quirks_mode()](Element const& element) {
1038 for (auto& name : list_of_class_names) {
1039 if (!element.has_class(name, quirks_mode ? CaseSensitivity::CaseInsensitive : CaseSensitivity::CaseSensitive))
1040 return false;
1041 }
1042 return true;
1043 }).release_value_but_fixme_should_propagate_errors();
1044}
1045
1046// https://html.spec.whatwg.org/multipage/obsolete.html#dom-document-applets
1047JS::NonnullGCPtr<HTMLCollection> Document::applets()
1048{
1049 if (!m_applets)
1050 m_applets = HTMLCollection::create(*this, [](auto&) { return false; }).release_value_but_fixme_should_propagate_errors();
1051 return *m_applets;
1052}
1053
1054// https://html.spec.whatwg.org/multipage/obsolete.html#dom-document-anchors
1055JS::NonnullGCPtr<HTMLCollection> Document::anchors()
1056{
1057 if (!m_anchors) {
1058 m_anchors = HTMLCollection::create(*this, [](Element const& element) {
1059 return is<HTML::HTMLAnchorElement>(element) && element.has_attribute(HTML::AttributeNames::name);
1060 }).release_value_but_fixme_should_propagate_errors();
1061 }
1062 return *m_anchors;
1063}
1064
1065// https://html.spec.whatwg.org/multipage/dom.html#dom-document-images
1066JS::NonnullGCPtr<HTMLCollection> Document::images()
1067{
1068 if (!m_images) {
1069 m_images = HTMLCollection::create(*this, [](Element const& element) {
1070 return is<HTML::HTMLImageElement>(element);
1071 }).release_value_but_fixme_should_propagate_errors();
1072 }
1073 return *m_images;
1074}
1075
1076// https://html.spec.whatwg.org/multipage/dom.html#dom-document-embeds
1077JS::NonnullGCPtr<HTMLCollection> Document::embeds()
1078{
1079 if (!m_embeds) {
1080 m_embeds = HTMLCollection::create(*this, [](Element const& element) {
1081 return is<HTML::HTMLEmbedElement>(element);
1082 }).release_value_but_fixme_should_propagate_errors();
1083 }
1084 return *m_embeds;
1085}
1086
1087// https://html.spec.whatwg.org/multipage/dom.html#dom-document-plugins
1088JS::NonnullGCPtr<HTMLCollection> Document::plugins()
1089{
1090 return embeds();
1091}
1092
1093// https://html.spec.whatwg.org/multipage/dom.html#dom-document-links
1094JS::NonnullGCPtr<HTMLCollection> Document::links()
1095{
1096 if (!m_links) {
1097 m_links = HTMLCollection::create(*this, [](Element const& element) {
1098 return (is<HTML::HTMLAnchorElement>(element) || is<HTML::HTMLAreaElement>(element)) && element.has_attribute(HTML::AttributeNames::href);
1099 }).release_value_but_fixme_should_propagate_errors();
1100 }
1101 return *m_links;
1102}
1103
1104// https://html.spec.whatwg.org/multipage/dom.html#dom-document-forms
1105JS::NonnullGCPtr<HTMLCollection> Document::forms()
1106{
1107 if (!m_forms) {
1108 m_forms = HTMLCollection::create(*this, [](Element const& element) {
1109 return is<HTML::HTMLFormElement>(element);
1110 }).release_value_but_fixme_should_propagate_errors();
1111 }
1112 return *m_forms;
1113}
1114
1115// https://html.spec.whatwg.org/multipage/dom.html#dom-document-scripts
1116JS::NonnullGCPtr<HTMLCollection> Document::scripts()
1117{
1118 if (!m_scripts) {
1119 m_scripts = HTMLCollection::create(*this, [](Element const& element) {
1120 return is<HTML::HTMLScriptElement>(element);
1121 }).release_value_but_fixme_should_propagate_errors();
1122 }
1123 return *m_scripts;
1124}
1125
1126// https://html.spec.whatwg.org/multipage/dom.html#dom-document-all
1127JS::NonnullGCPtr<HTMLCollection> Document::all()
1128{
1129 if (!m_all) {
1130 m_all = HTMLCollection::create(*this, [](Element const&) {
1131 return true;
1132 }).release_value_but_fixme_should_propagate_errors();
1133 }
1134 return *m_all;
1135}
1136
1137Color Document::link_color() const
1138{
1139 if (m_link_color.has_value())
1140 return m_link_color.value();
1141 if (!page())
1142 return Color::Blue;
1143 return page()->palette().link();
1144}
1145
1146Color Document::active_link_color() const
1147{
1148 if (m_active_link_color.has_value())
1149 return m_active_link_color.value();
1150 if (!page())
1151 return Color::Red;
1152 return page()->palette().active_link();
1153}
1154
1155Color Document::visited_link_color() const
1156{
1157 if (m_visited_link_color.has_value())
1158 return m_visited_link_color.value();
1159 if (!page())
1160 return Color::Magenta;
1161 return page()->palette().visited_link();
1162}
1163
1164// https://html.spec.whatwg.org/multipage/webappapis.html#relevant-settings-object
1165HTML::EnvironmentSettingsObject& Document::relevant_settings_object()
1166{
1167 // Then, the relevant settings object for a platform object o is the environment settings object of the relevant Realm for o.
1168 return Bindings::host_defined_environment_settings_object(realm());
1169}
1170
1171JS::Value Document::run_javascript(StringView source, StringView filename)
1172{
1173 // FIXME: The only user of this function now is javascript: URLs. Refactor them to follow the spec: https://html.spec.whatwg.org/multipage/browsing-the-web.html#javascript-protocol
1174 auto interpreter = JS::Interpreter::create_with_existing_realm(realm());
1175 auto script_or_error = JS::Script::parse(source, realm(), filename);
1176 if (script_or_error.is_error()) {
1177 // FIXME: Add error logging back.
1178 return JS::js_undefined();
1179 }
1180
1181 auto result = interpreter->run(script_or_error.value());
1182
1183 if (result.is_error()) {
1184 // FIXME: I'm sure the spec could tell us something about error propagation here!
1185 HTML::report_exception(result, realm());
1186
1187 return {};
1188 }
1189 return result.value();
1190}
1191
1192// https://dom.spec.whatwg.org/#dom-document-createelement
1193WebIDL::ExceptionOr<JS::NonnullGCPtr<Element>> Document::create_element(DeprecatedFlyString const& a_local_name)
1194{
1195 auto local_name = a_local_name;
1196
1197 // 1. If localName does not match the Name production, then throw an "InvalidCharacterError" DOMException.
1198 if (!is_valid_name(local_name))
1199 return WebIDL::InvalidCharacterError::create(realm(), "Invalid character in tag name.");
1200
1201 // 2. If this is an HTML document, then set localName to localName in ASCII lowercase.
1202 if (document_type() == Type::HTML)
1203 local_name = local_name.to_lowercase();
1204
1205 // FIXME: 3. Let is be null.
1206 // FIXME: 4. If options is a dictionary and options["is"] exists, then set is to it.
1207
1208 // 5. Let namespace be the HTML namespace, if this is an HTML document or this’s content type is "application/xhtml+xml"; otherwise null.
1209 DeprecatedFlyString namespace_;
1210 if (document_type() == Type::HTML || content_type() == "application/xhtml+xml"sv)
1211 namespace_ = Namespace::HTML;
1212
1213 // 6. Return the result of creating an element given this, localName, namespace, null, is, and with the synchronous custom elements flag set.
1214 return TRY(DOM::create_element(*this, local_name, namespace_));
1215}
1216
1217// https://dom.spec.whatwg.org/#dom-document-createelementns
1218// https://dom.spec.whatwg.org/#internal-createelementns-steps
1219// FIXME: This only implements step 4 of the algorithm and does not take in options.
1220WebIDL::ExceptionOr<JS::NonnullGCPtr<Element>> Document::create_element_ns(DeprecatedString const& namespace_, DeprecatedString const& qualified_name)
1221{
1222 // 1. Let namespace, prefix, and localName be the result of passing namespace and qualifiedName to validate and extract.
1223 auto extracted_qualified_name = TRY(validate_and_extract(realm(), namespace_, qualified_name));
1224
1225 // FIXME: 2. Let is be null.
1226 // FIXME: 3. If options is a dictionary and options["is"] exists, then set is to it.
1227
1228 // 4. Return the result of creating an element given document, localName, namespace, prefix, is, and with the synchronous custom elements flag set.
1229 return TRY(DOM::create_element(*this, extracted_qualified_name.local_name(), extracted_qualified_name.namespace_(), extracted_qualified_name.prefix()));
1230}
1231
1232JS::NonnullGCPtr<DocumentFragment> Document::create_document_fragment()
1233{
1234 return heap().allocate<DocumentFragment>(realm(), *this).release_allocated_value_but_fixme_should_propagate_errors();
1235}
1236
1237JS::NonnullGCPtr<Text> Document::create_text_node(DeprecatedString const& data)
1238{
1239 return heap().allocate<Text>(realm(), *this, data).release_allocated_value_but_fixme_should_propagate_errors();
1240}
1241
1242JS::NonnullGCPtr<Comment> Document::create_comment(DeprecatedString const& data)
1243{
1244 return heap().allocate<Comment>(realm(), *this, data).release_allocated_value_but_fixme_should_propagate_errors();
1245}
1246
1247// https://dom.spec.whatwg.org/#dom-document-createprocessinginstruction
1248WebIDL::ExceptionOr<JS::NonnullGCPtr<ProcessingInstruction>> Document::create_processing_instruction(DeprecatedString const& target, DeprecatedString const& data)
1249{
1250 // FIXME: 1. If target does not match the Name production, then throw an "InvalidCharacterError" DOMException.
1251
1252 // FIXME: 2. If data contains the string "?>", then throw an "InvalidCharacterError" DOMException.
1253
1254 // 3. Return a new ProcessingInstruction node, with target set to target, data set to data, and node document set to this.
1255 return MUST_OR_THROW_OOM(heap().allocate<ProcessingInstruction>(realm(), *this, data, target));
1256}
1257
1258JS::NonnullGCPtr<Range> Document::create_range()
1259{
1260 return Range::create(*this).release_value_but_fixme_should_propagate_errors();
1261}
1262
1263// https://dom.spec.whatwg.org/#dom-document-createevent
1264WebIDL::ExceptionOr<JS::NonnullGCPtr<Event>> Document::create_event(DeprecatedString const& interface)
1265{
1266 auto& realm = this->realm();
1267
1268 // NOTE: This is named event here, since we do step 5 and 6 as soon as possible for each case.
1269 // 1. Let constructor be null.
1270 JS::GCPtr<Event> event;
1271
1272 // 2. If interface is an ASCII case-insensitive match for any of the strings in the first column in the following table,
1273 // then set constructor to the interface in the second column on the same row as the matching string:
1274 if (Infra::is_ascii_case_insensitive_match(interface, "beforeunloadevent"sv)) {
1275 event = TRY(Event::create(realm, "")); // FIXME: Create BeforeUnloadEvent
1276 } else if (Infra::is_ascii_case_insensitive_match(interface, "compositionevent"sv)) {
1277 event = TRY(Event::create(realm, "")); // FIXME: Create CompositionEvent
1278 } else if (Infra::is_ascii_case_insensitive_match(interface, "customevent"sv)) {
1279 event = TRY(CustomEvent::create(realm, ""));
1280 } else if (Infra::is_ascii_case_insensitive_match(interface, "devicemotionevent"sv)) {
1281 event = TRY(Event::create(realm, "")); // FIXME: Create DeviceMotionEvent
1282 } else if (Infra::is_ascii_case_insensitive_match(interface, "deviceorientationevent"sv)) {
1283 event = TRY(Event::create(realm, "")); // FIXME: Create DeviceOrientationEvent
1284 } else if (Infra::is_ascii_case_insensitive_match(interface, "dragevent"sv)) {
1285 event = TRY(Event::create(realm, "")); // FIXME: Create DragEvent
1286 } else if (Infra::is_ascii_case_insensitive_match(interface, "event"sv)
1287 || Infra::is_ascii_case_insensitive_match(interface, "events"sv)) {
1288 event = TRY(Event::create(realm, ""));
1289 } else if (Infra::is_ascii_case_insensitive_match(interface, "focusevent"sv)) {
1290 event = TRY(UIEvents::FocusEvent::create(realm, ""));
1291 } else if (Infra::is_ascii_case_insensitive_match(interface, "hashchangeevent"sv)) {
1292 event = TRY(Event::create(realm, "")); // FIXME: Create HashChangeEvent
1293 } else if (Infra::is_ascii_case_insensitive_match(interface, "htmlevents"sv)) {
1294 event = TRY(Event::create(realm, ""));
1295 } else if (Infra::is_ascii_case_insensitive_match(interface, "keyboardevent"sv)) {
1296 event = TRY(UIEvents::KeyboardEvent::create(realm, ""));
1297 } else if (Infra::is_ascii_case_insensitive_match(interface, "messageevent"sv)) {
1298 event = TRY(HTML::MessageEvent::create(realm, String {}));
1299 } else if (Infra::is_ascii_case_insensitive_match(interface, "mouseevent"sv)
1300 || Infra::is_ascii_case_insensitive_match(interface, "mouseevents"sv)) {
1301 event = TRY(UIEvents::MouseEvent::create(realm, ""));
1302 } else if (Infra::is_ascii_case_insensitive_match(interface, "storageevent"sv)) {
1303 event = TRY(Event::create(realm, "")); // FIXME: Create StorageEvent
1304 } else if (Infra::is_ascii_case_insensitive_match(interface, "svgevents"sv)) {
1305 event = TRY(Event::create(realm, ""));
1306 } else if (Infra::is_ascii_case_insensitive_match(interface, "textevent"sv)) {
1307 event = TRY(Event::create(realm, "")); // FIXME: Create CompositionEvent
1308 } else if (Infra::is_ascii_case_insensitive_match(interface, "touchevent"sv)) {
1309 event = TRY(Event::create(realm, "")); // FIXME: Create TouchEvent
1310 } else if (Infra::is_ascii_case_insensitive_match(interface, "uievent"sv)
1311 || Infra::is_ascii_case_insensitive_match(interface, "uievents"sv)) {
1312 event = TRY(UIEvents::UIEvent::create(realm, ""));
1313 }
1314
1315 // 3. If constructor is null, then throw a "NotSupportedError" DOMException.
1316 if (!event) {
1317 return WebIDL::NotSupportedError::create(realm, "No constructor for interface found");
1318 }
1319
1320 // FIXME: 4. If the interface indicated by constructor is not exposed on the relevant global object of this, then throw a "NotSupportedError" DOMException.
1321
1322 // NOTE: These are done in the if-chain above
1323 // 5. Let event be the result of creating an event given constructor.
1324 // 6. Initialize event’s type attribute to the empty string.
1325 // NOTE: This is handled by each constructor.
1326
1327 // FIXME: 7. Initialize event’s timeStamp attribute to the result of calling current high resolution time with this’s relevant global object.
1328
1329 // 8. Initialize event’s isTrusted attribute to false.
1330 event->set_is_trusted(false);
1331
1332 // 9. Unset event’s initialized flag.
1333 event->set_initialized(false);
1334
1335 // 10. Return event.
1336 return JS::NonnullGCPtr(*event);
1337}
1338
1339void Document::set_pending_parsing_blocking_script(Badge<HTML::HTMLScriptElement>, HTML::HTMLScriptElement* script)
1340{
1341 m_pending_parsing_blocking_script = script;
1342}
1343
1344JS::NonnullGCPtr<HTML::HTMLScriptElement> Document::take_pending_parsing_blocking_script(Badge<HTML::HTMLParser>)
1345{
1346 VERIFY(m_pending_parsing_blocking_script);
1347 auto script = m_pending_parsing_blocking_script;
1348 m_pending_parsing_blocking_script = nullptr;
1349 return *script;
1350}
1351
1352void Document::add_script_to_execute_when_parsing_has_finished(Badge<HTML::HTMLScriptElement>, HTML::HTMLScriptElement& script)
1353{
1354 m_scripts_to_execute_when_parsing_has_finished.append(JS::make_handle(script));
1355}
1356
1357Vector<JS::Handle<HTML::HTMLScriptElement>> Document::take_scripts_to_execute_when_parsing_has_finished(Badge<HTML::HTMLParser>)
1358{
1359 return move(m_scripts_to_execute_when_parsing_has_finished);
1360}
1361
1362void Document::add_script_to_execute_as_soon_as_possible(Badge<HTML::HTMLScriptElement>, HTML::HTMLScriptElement& script)
1363{
1364 m_scripts_to_execute_as_soon_as_possible.append(JS::make_handle(script));
1365}
1366
1367Vector<JS::Handle<HTML::HTMLScriptElement>> Document::take_scripts_to_execute_as_soon_as_possible(Badge<HTML::HTMLParser>)
1368{
1369 return move(m_scripts_to_execute_as_soon_as_possible);
1370}
1371
1372void Document::add_script_to_execute_in_order_as_soon_as_possible(Badge<HTML::HTMLScriptElement>, HTML::HTMLScriptElement& script)
1373{
1374 m_scripts_to_execute_in_order_as_soon_as_possible.append(JS::make_handle(script));
1375}
1376
1377Vector<JS::Handle<HTML::HTMLScriptElement>> Document::take_scripts_to_execute_in_order_as_soon_as_possible(Badge<HTML::HTMLParser>)
1378{
1379 return move(m_scripts_to_execute_in_order_as_soon_as_possible);
1380}
1381
1382// https://dom.spec.whatwg.org/#dom-document-importnode
1383WebIDL::ExceptionOr<JS::NonnullGCPtr<Node>> Document::import_node(JS::NonnullGCPtr<Node> node, bool deep)
1384{
1385 // 1. If node is a document or shadow root, then throw a "NotSupportedError" DOMException.
1386 if (is<Document>(*node) || is<ShadowRoot>(*node))
1387 return WebIDL::NotSupportedError::create(realm(), "Cannot import a document or shadow root.");
1388
1389 // 2. Return a clone of node, with this and the clone children flag set if deep is true.
1390 return node->clone_node(this, deep);
1391}
1392
1393// https://dom.spec.whatwg.org/#concept-node-adopt
1394void Document::adopt_node(Node& node)
1395{
1396 auto& old_document = node.document();
1397 if (node.parent())
1398 node.remove();
1399
1400 if (&old_document != this) {
1401 node.for_each_shadow_including_descendant([&](auto& inclusive_descendant) {
1402 inclusive_descendant.set_document({}, *this);
1403 // FIXME: If inclusiveDescendant is an element, then set the node document of each attribute in inclusiveDescendant’s attribute list to document.
1404 return IterationDecision::Continue;
1405 });
1406
1407 // FIXME: For each inclusiveDescendant in node’s shadow-including inclusive descendants that is custom,
1408 // enqueue a custom element callback reaction with inclusiveDescendant, callback name "adoptedCallback",
1409 // and an argument list containing oldDocument and document.
1410
1411 node.for_each_shadow_including_descendant([&](auto& inclusive_descendant) {
1412 inclusive_descendant.adopted_from(old_document);
1413 return IterationDecision::Continue;
1414 });
1415
1416 // Transfer NodeIterators rooted at `node` from old_document to this document.
1417 Vector<NodeIterator&> node_iterators_to_transfer;
1418 for (auto node_iterator : old_document.m_node_iterators) {
1419 if (node_iterator->root().ptr() == &node)
1420 node_iterators_to_transfer.append(*node_iterator);
1421 }
1422
1423 for (auto& node_iterator : node_iterators_to_transfer) {
1424 old_document.m_node_iterators.remove(&node_iterator);
1425 m_node_iterators.set(&node_iterator);
1426 }
1427 }
1428}
1429
1430// https://dom.spec.whatwg.org/#dom-document-adoptnode
1431WebIDL::ExceptionOr<JS::NonnullGCPtr<Node>> Document::adopt_node_binding(JS::NonnullGCPtr<Node> node)
1432{
1433 if (is<Document>(*node))
1434 return WebIDL::NotSupportedError::create(realm(), "Cannot adopt a document into a document");
1435
1436 if (is<ShadowRoot>(*node))
1437 return WebIDL::HierarchyRequestError::create(realm(), "Cannot adopt a shadow root into a document");
1438
1439 if (is<DocumentFragment>(*node) && verify_cast<DocumentFragment>(*node).host())
1440 return node;
1441
1442 adopt_node(*node);
1443
1444 return node;
1445}
1446
1447DocumentType const* Document::doctype() const
1448{
1449 return first_child_of_type<DocumentType>();
1450}
1451
1452DeprecatedString const& Document::compat_mode() const
1453{
1454 static DeprecatedString back_compat = "BackCompat";
1455 static DeprecatedString css1_compat = "CSS1Compat";
1456
1457 if (m_quirks_mode == QuirksMode::Yes)
1458 return back_compat;
1459
1460 return css1_compat;
1461}
1462
1463bool Document::is_editable() const
1464{
1465 return m_editable;
1466}
1467
1468void Document::set_focused_element(Element* element)
1469{
1470 if (m_focused_element.ptr() == element)
1471 return;
1472
1473 if (m_focused_element) {
1474 m_focused_element->did_lose_focus();
1475 m_focused_element->set_needs_style_update(true);
1476 }
1477
1478 m_focused_element = element;
1479
1480 if (m_focused_element) {
1481 m_focused_element->did_receive_focus();
1482 m_focused_element->set_needs_style_update(true);
1483 }
1484
1485 if (m_layout_root)
1486 m_layout_root->set_needs_display();
1487
1488 // Scroll the viewport if necessary to make the newly focused element visible.
1489 if (m_focused_element)
1490 (void)m_focused_element->scroll_into_view();
1491}
1492
1493void Document::set_active_element(Element* element)
1494{
1495 if (m_active_element.ptr() == element)
1496 return;
1497
1498 m_active_element = element;
1499
1500 if (m_layout_root)
1501 m_layout_root->set_needs_display();
1502}
1503
1504DeprecatedString Document::ready_state() const
1505{
1506 switch (m_readiness) {
1507 case HTML::DocumentReadyState::Loading:
1508 return "loading"sv;
1509 case HTML::DocumentReadyState::Interactive:
1510 return "interactive"sv;
1511 case HTML::DocumentReadyState::Complete:
1512 return "complete"sv;
1513 }
1514 VERIFY_NOT_REACHED();
1515}
1516
1517// https://html.spec.whatwg.org/multipage/dom.html#update-the-current-document-readiness
1518void Document::update_readiness(HTML::DocumentReadyState readiness_value)
1519{
1520 // 1. If document's current document readiness equals readinessValue, then return.
1521 if (m_readiness == readiness_value)
1522 return;
1523
1524 // 2. Set document's current document readiness to readinessValue.
1525 m_readiness = readiness_value;
1526
1527 // 3. If document is associated with an HTML parser, then:
1528 if (m_parser) {
1529 // 1. Let now be the current high resolution time given document's relevant global object.
1530 auto now = HighResolutionTime::unsafe_shared_current_time();
1531
1532 // 2. If readinessValue is "complete", and document's load timing info's DOM complete time is 0,
1533 // then set document's load timing info's DOM complete time to now.
1534 if (readiness_value == HTML::DocumentReadyState::Complete && m_load_timing_info.dom_complete_time == 0) {
1535 m_load_timing_info.dom_complete_time = now;
1536 }
1537 // 3. Otherwise, if readinessValue is "interactive", and document's load timing info's DOM interactive time is 0,
1538 // then set document's load timing info's DOM interactive time to now.
1539 else if (readiness_value == HTML::DocumentReadyState::Interactive && m_load_timing_info.dom_interactive_time == 0) {
1540 m_load_timing_info.dom_interactive_time = now;
1541 }
1542 }
1543
1544 // 4. Fire an event named readystatechange at document.
1545 dispatch_event(Event::create(realm(), HTML::EventNames::readystatechange).release_value_but_fixme_should_propagate_errors());
1546}
1547
1548Page* Document::page()
1549{
1550 return m_browsing_context ? m_browsing_context->page() : nullptr;
1551}
1552
1553Page const* Document::page() const
1554{
1555 return m_browsing_context ? m_browsing_context->page() : nullptr;
1556}
1557
1558EventTarget* Document::get_parent(Event const& event)
1559{
1560 if (event.type() == HTML::EventNames::load)
1561 return nullptr;
1562
1563 return m_window;
1564}
1565
1566// https://html.spec.whatwg.org/#completely-loaded
1567bool Document::is_completely_loaded() const
1568{
1569 return m_completely_loaded_time.has_value();
1570}
1571
1572// https://html.spec.whatwg.org/multipage/browsing-the-web.html#completely-finish-loading
1573void Document::completely_finish_loading()
1574{
1575 // 1. Assert: document's browsing context is non-null.
1576 VERIFY(browsing_context());
1577
1578 // 2. Set document's completely loaded time to the current time.
1579 m_completely_loaded_time = AK::Time::now_realtime();
1580
1581 // 3. Let container be document's browsing context's container.
1582 auto container = JS::make_handle(browsing_context()->container());
1583
1584 // 4. If container is an iframe element, then queue an element task on the DOM manipulation task source given container to run the iframe load event steps given container.
1585 if (container && is<HTML::HTMLIFrameElement>(*container)) {
1586 container->queue_an_element_task(HTML::Task::Source::DOMManipulation, [container] {
1587 run_iframe_load_event_steps(static_cast<HTML::HTMLIFrameElement&>(*container));
1588 });
1589 }
1590 // 5. Otherwise, if container is non-null, then queue an element task on the DOM manipulation task source given container to fire an event named load at container.
1591 else if (container) {
1592 container->queue_an_element_task(HTML::Task::Source::DOMManipulation, [container] {
1593 container->dispatch_event(DOM::Event::create(container->realm(), HTML::EventNames::load).release_value_but_fixme_should_propagate_errors());
1594 });
1595 }
1596}
1597
1598DeprecatedString Document::cookie(Cookie::Source source)
1599{
1600 if (auto* page = this->page())
1601 return page->client().page_did_request_cookie(m_url, source);
1602 return {};
1603}
1604
1605void Document::set_cookie(DeprecatedString const& cookie_string, Cookie::Source source)
1606{
1607 auto cookie = Cookie::parse_cookie(cookie_string);
1608 if (!cookie.has_value())
1609 return;
1610
1611 if (auto* page = this->page())
1612 page->client().page_did_set_cookie(m_url, cookie.value(), source);
1613}
1614
1615DeprecatedString Document::dump_dom_tree_as_json() const
1616{
1617 StringBuilder builder;
1618 auto json = MUST(JsonObjectSerializer<>::try_create(builder));
1619 serialize_tree_as_json(json);
1620
1621 MUST(json.finish());
1622 return builder.to_deprecated_string();
1623}
1624
1625// https://html.spec.whatwg.org/multipage/semantics.html#has-a-style-sheet-that-is-blocking-scripts
1626bool Document::has_a_style_sheet_that_is_blocking_scripts() const
1627{
1628 // A Document has a style sheet that is blocking scripts if its script-blocking style sheet counter is greater than 0,
1629 if (m_script_blocking_style_sheet_counter > 0)
1630 return true;
1631
1632 // ...or if that Document has a non-null browsing context whose container document is non-null and has a script-blocking style sheet counter greater than 0.
1633 if (!browsing_context() || !browsing_context()->container_document())
1634 return false;
1635
1636 return browsing_context()->container_document()->m_script_blocking_style_sheet_counter > 0;
1637}
1638
1639DeprecatedString Document::referrer() const
1640{
1641 return m_referrer;
1642}
1643
1644void Document::set_referrer(DeprecatedString referrer)
1645{
1646 m_referrer = referrer;
1647}
1648
1649// https://html.spec.whatwg.org/multipage/browsers.html#fully-active
1650bool Document::is_fully_active() const
1651{
1652 // A Document d is said to be fully active when d's browsing context is non-null, d's browsing context's active document is d,
1653 // and either d's browsing context is a top-level browsing context, or d's browsing context's container document is fully active.
1654 auto* browsing_context = this->browsing_context();
1655 if (!browsing_context)
1656 return false;
1657 if (browsing_context->active_document() != this)
1658 return false;
1659 if (browsing_context->is_top_level())
1660 return true;
1661 if (auto* browsing_context_container_document = browsing_context->container_document()) {
1662 if (browsing_context_container_document->is_fully_active())
1663 return true;
1664 }
1665 return false;
1666}
1667
1668// https://html.spec.whatwg.org/multipage/browsers.html#active-document
1669bool Document::is_active() const
1670{
1671 // A browsing context's active document is its active window's associated Document.
1672 return browsing_context() && browsing_context()->active_document() == this;
1673}
1674
1675// https://html.spec.whatwg.org/multipage/history.html#dom-document-location
1676WebIDL::ExceptionOr<JS::GCPtr<HTML::Location>> Document::location()
1677{
1678 // The Document object's location attribute's getter must return this Document object's relevant global object's Location object,
1679 // if this Document object is fully active, and null otherwise.
1680
1681 if (!is_fully_active())
1682 return nullptr;
1683
1684 return TRY(window().location());
1685}
1686
1687// https://html.spec.whatwg.org/multipage/interaction.html#dom-document-hidden
1688bool Document::hidden() const
1689{
1690 return visibility_state() == "hidden";
1691}
1692
1693// https://html.spec.whatwg.org/multipage/interaction.html#dom-document-visibilitystate
1694DeprecatedString Document::visibility_state() const
1695{
1696 switch (m_visibility_state) {
1697 case HTML::VisibilityState::Hidden:
1698 return "hidden"sv;
1699 case HTML::VisibilityState::Visible:
1700 return "visible"sv;
1701 }
1702 VERIFY_NOT_REACHED();
1703}
1704
1705void Document::set_visibility_state(Badge<HTML::BrowsingContext>, HTML::VisibilityState visibility_state)
1706{
1707 m_visibility_state = visibility_state;
1708}
1709
1710// https://html.spec.whatwg.org/multipage/interaction.html#update-the-visibility-state
1711void Document::update_the_visibility_state(HTML::VisibilityState visibility_state)
1712{
1713 // 1. If document's visibility state equals visibilityState, then return.
1714 if (m_visibility_state == visibility_state)
1715 return;
1716
1717 // 2. Set document's visibility state to visibilityState.
1718 m_visibility_state = visibility_state;
1719
1720 // FIXME: 3. Run any page visibility change steps which may be defined in other specifications, with visibility state and document.
1721
1722 // 4. Fire an event named visibilitychange at document, with its bubbles attribute initialized to true.
1723 auto event = DOM::Event::create(realm(), HTML::EventNames::visibilitychange).release_value_but_fixme_should_propagate_errors();
1724 event->set_bubbles(true);
1725 dispatch_event(event);
1726}
1727
1728// https://drafts.csswg.org/cssom-view/#run-the-resize-steps
1729void Document::run_the_resize_steps()
1730{
1731 // 1. If doc’s viewport has had its width or height changed
1732 // (e.g. as a result of the user resizing the browser window, or changing the page zoom scale factor,
1733 // or an iframe element’s dimensions are changed) since the last time these steps were run,
1734 // fire an event named resize at the Window object associated with doc.
1735
1736 if (!browsing_context())
1737 return;
1738
1739 auto viewport_size = browsing_context()->viewport_rect().size().to_type<float>().to_type<int>();
1740 if (m_last_viewport_size == viewport_size)
1741 return;
1742 m_last_viewport_size = viewport_size;
1743
1744 window().dispatch_event(DOM::Event::create(realm(), UIEvents::EventNames::resize).release_value_but_fixme_should_propagate_errors());
1745
1746 schedule_layout_update();
1747}
1748
1749// https://w3c.github.io/csswg-drafts/cssom-view-1/#document-run-the-scroll-steps
1750void Document::run_the_scroll_steps()
1751{
1752 // 1. For each item target in doc’s pending scroll event targets, in the order they were added to the list, run these substeps:
1753 for (auto& target : m_pending_scroll_event_targets) {
1754 // 1. If target is a Document, fire an event named scroll that bubbles at target and fire an event named scroll at the VisualViewport that is associated with target.
1755 if (is<Document>(*target)) {
1756 auto event = DOM::Event::create(realm(), HTML::EventNames::scroll).release_value_but_fixme_should_propagate_errors();
1757 event->set_bubbles(true);
1758 target->dispatch_event(event);
1759 // FIXME: Fire at the associated VisualViewport
1760 }
1761 // 2. Otherwise, fire an event named scroll at target.
1762 else {
1763 auto event = DOM::Event::create(realm(), HTML::EventNames::scroll).release_value_but_fixme_should_propagate_errors();
1764 target->dispatch_event(event);
1765 }
1766 }
1767
1768 // 2. Empty doc’s pending scroll event targets.
1769 m_pending_scroll_event_targets.clear();
1770}
1771
1772void Document::add_media_query_list(JS::NonnullGCPtr<CSS::MediaQueryList> media_query_list)
1773{
1774 m_media_query_lists.append(*media_query_list);
1775}
1776
1777// https://drafts.csswg.org/cssom-view/#evaluate-media-queries-and-report-changes
1778void Document::evaluate_media_queries_and_report_changes()
1779{
1780 // NOTE: Not in the spec, but we take this opportunity to prune null WeakPtrs.
1781 m_media_query_lists.remove_all_matching([](auto& it) {
1782 return it.is_null();
1783 });
1784
1785 // 1. For each MediaQueryList object target that has doc as its document,
1786 // in the order they were created, oldest first, run these substeps:
1787 for (auto& media_query_list_ptr : m_media_query_lists) {
1788 // 1. If target’s matches state has changed since the last time these steps
1789 // were run, fire an event at target using the MediaQueryListEvent constructor,
1790 // with its type attribute initialized to change, its isTrusted attribute
1791 // initialized to true, its media attribute initialized to target’s media,
1792 // and its matches attribute initialized to target’s matches state.
1793 if (media_query_list_ptr.is_null())
1794 continue;
1795 JS::GCPtr<CSS::MediaQueryList> media_query_list = media_query_list_ptr.ptr();
1796 bool did_match = media_query_list->matches();
1797 bool now_matches = media_query_list->evaluate();
1798
1799 if (did_match != now_matches) {
1800 CSS::MediaQueryListEventInit init;
1801 init.media = String::from_deprecated_string(media_query_list->media()).release_value_but_fixme_should_propagate_errors();
1802 init.matches = now_matches;
1803 auto event = CSS::MediaQueryListEvent::create(realm(), HTML::EventNames::change, init).release_value_but_fixme_should_propagate_errors();
1804 event->set_is_trusted(true);
1805 media_query_list->dispatch_event(*event);
1806 }
1807 }
1808
1809 // Also not in the spec, but this is as good a place as any to evaluate @media rules!
1810 evaluate_media_rules();
1811}
1812
1813void Document::evaluate_media_rules()
1814{
1815 bool any_media_queries_changed_match_state = false;
1816 for (auto& style_sheet : style_sheets().sheets()) {
1817 if (style_sheet->evaluate_media_queries(window()))
1818 any_media_queries_changed_match_state = true;
1819 }
1820
1821 if (any_media_queries_changed_match_state) {
1822 style_computer().invalidate_rule_cache();
1823 invalidate_style();
1824 }
1825}
1826
1827DOMImplementation* Document::implementation()
1828{
1829 if (!m_implementation)
1830 m_implementation = DOMImplementation::create(*this).release_value_but_fixme_should_propagate_errors();
1831 return m_implementation;
1832}
1833
1834bool Document::has_focus() const
1835{
1836 // FIXME: Return whether we actually have focus.
1837 return true;
1838}
1839
1840void Document::set_parser(Badge<HTML::HTMLParser>, HTML::HTMLParser& parser)
1841{
1842 m_parser = parser;
1843}
1844
1845void Document::detach_parser(Badge<HTML::HTMLParser>)
1846{
1847 m_parser = nullptr;
1848}
1849
1850// https://www.w3.org/TR/xml/#NT-NameStartChar
1851static bool is_valid_name_start_character(u32 code_point)
1852{
1853 return code_point == ':'
1854 || (code_point >= 'A' && code_point <= 'Z')
1855 || code_point == '_'
1856 || (code_point >= 'a' && code_point <= 'z')
1857 || (code_point >= 0xc0 && code_point <= 0xd6)
1858 || (code_point >= 0xd8 && code_point <= 0xf6)
1859 || (code_point >= 0xf8 && code_point <= 0x2ff)
1860 || (code_point >= 0x370 && code_point <= 0x37d)
1861 || (code_point >= 0x37f && code_point <= 0x1fff)
1862 || (code_point >= 0x200c && code_point <= 0x200d)
1863 || (code_point >= 0x2070 && code_point <= 0x218f)
1864 || (code_point >= 0x2c00 && code_point <= 0x2fef)
1865 || (code_point >= 0x3001 && code_point <= 0xD7ff)
1866 || (code_point >= 0xf900 && code_point <= 0xfdcf)
1867 || (code_point >= 0xfdf0 && code_point <= 0xfffd)
1868 || (code_point >= 0x10000 && code_point <= 0xeffff);
1869}
1870
1871// https://www.w3.org/TR/xml/#NT-NameChar
1872static inline bool is_valid_name_character(u32 code_point)
1873{
1874 return is_valid_name_start_character(code_point)
1875 || code_point == '-'
1876 || code_point == '.'
1877 || (code_point >= '0' && code_point <= '9')
1878 || code_point == 0xb7
1879 || (code_point >= 0x300 && code_point <= 0x36f)
1880 || (code_point >= 0x203f && code_point <= 0x2040);
1881}
1882
1883bool Document::is_valid_name(DeprecatedString const& name)
1884{
1885 if (name.is_empty())
1886 return false;
1887
1888 if (!is_valid_name_start_character(name[0]))
1889 return false;
1890
1891 for (size_t i = 1; i < name.length(); ++i) {
1892 if (!is_valid_name_character(name[i]))
1893 return false;
1894 }
1895
1896 return true;
1897}
1898
1899// https://dom.spec.whatwg.org/#validate
1900WebIDL::ExceptionOr<Document::PrefixAndTagName> Document::validate_qualified_name(JS::Realm& realm, DeprecatedString const& qualified_name)
1901{
1902 if (qualified_name.is_empty())
1903 return WebIDL::InvalidCharacterError::create(realm, "Empty string is not a valid qualified name.");
1904
1905 Utf8View utf8view { qualified_name };
1906 if (!utf8view.validate())
1907 return WebIDL::InvalidCharacterError::create(realm, "Invalid qualified name.");
1908
1909 Optional<size_t> colon_offset;
1910
1911 bool at_start_of_name = true;
1912
1913 for (auto it = utf8view.begin(); it != utf8view.end(); ++it) {
1914 auto code_point = *it;
1915 if (code_point == ':') {
1916 if (colon_offset.has_value())
1917 return WebIDL::InvalidCharacterError::create(realm, "More than one colon (:) in qualified name.");
1918 colon_offset = utf8view.byte_offset_of(it);
1919 at_start_of_name = true;
1920 continue;
1921 }
1922 if (at_start_of_name) {
1923 if (!is_valid_name_start_character(code_point))
1924 return WebIDL::InvalidCharacterError::create(realm, "Invalid start of qualified name.");
1925 at_start_of_name = false;
1926 continue;
1927 }
1928 if (!is_valid_name_character(code_point))
1929 return WebIDL::InvalidCharacterError::create(realm, "Invalid character in qualified name.");
1930 }
1931
1932 if (!colon_offset.has_value())
1933 return Document::PrefixAndTagName {
1934 .prefix = {},
1935 .tag_name = qualified_name,
1936 };
1937
1938 if (*colon_offset == 0)
1939 return WebIDL::InvalidCharacterError::create(realm, "Qualified name can't start with colon (:).");
1940
1941 if (*colon_offset >= (qualified_name.length() - 1))
1942 return WebIDL::InvalidCharacterError::create(realm, "Qualified name can't end with colon (:).");
1943
1944 return Document::PrefixAndTagName {
1945 .prefix = qualified_name.substring_view(0, *colon_offset),
1946 .tag_name = qualified_name.substring_view(*colon_offset + 1),
1947 };
1948}
1949
1950// https://dom.spec.whatwg.org/#dom-document-createnodeiterator
1951JS::NonnullGCPtr<NodeIterator> Document::create_node_iterator(Node& root, unsigned what_to_show, JS::GCPtr<NodeFilter> filter)
1952{
1953 return NodeIterator::create(root, what_to_show, filter).release_value_but_fixme_should_propagate_errors();
1954}
1955
1956// https://dom.spec.whatwg.org/#dom-document-createtreewalker
1957JS::NonnullGCPtr<TreeWalker> Document::create_tree_walker(Node& root, unsigned what_to_show, JS::GCPtr<NodeFilter> filter)
1958{
1959 return TreeWalker::create(root, what_to_show, filter).release_value_but_fixme_should_propagate_errors();
1960}
1961
1962void Document::register_node_iterator(Badge<NodeIterator>, NodeIterator& node_iterator)
1963{
1964 auto result = m_node_iterators.set(&node_iterator);
1965 VERIFY(result == AK::HashSetResult::InsertedNewEntry);
1966}
1967
1968void Document::unregister_node_iterator(Badge<NodeIterator>, NodeIterator& node_iterator)
1969{
1970 bool was_removed = m_node_iterators.remove(&node_iterator);
1971 VERIFY(was_removed);
1972}
1973
1974void Document::increment_number_of_things_delaying_the_load_event(Badge<DocumentLoadEventDelayer>)
1975{
1976 ++m_number_of_things_delaying_the_load_event;
1977
1978 if (auto* page = this->page())
1979 page->client().page_did_update_resource_count(m_number_of_things_delaying_the_load_event);
1980}
1981
1982void Document::decrement_number_of_things_delaying_the_load_event(Badge<DocumentLoadEventDelayer>)
1983{
1984 VERIFY(m_number_of_things_delaying_the_load_event);
1985 --m_number_of_things_delaying_the_load_event;
1986
1987 if (auto* page = this->page())
1988 page->client().page_did_update_resource_count(m_number_of_things_delaying_the_load_event);
1989}
1990
1991void Document::invalidate_stacking_context_tree()
1992{
1993 if (auto* paint_box = this->paint_box())
1994 const_cast<Painting::PaintableBox*>(paint_box)->invalidate_stacking_context();
1995}
1996
1997void Document::check_favicon_after_loading_link_resource()
1998{
1999 // https://html.spec.whatwg.org/multipage/links.html#rel-icon
2000 // NOTE: firefox also load favicons outside the head tag, which is against spec (see table 4.6.7)
2001 auto* head_element = head();
2002 if (!head_element)
2003 return;
2004
2005 auto favicon_link_elements = HTMLCollection::create(*head_element, [](Element const& element) {
2006 if (!is<HTML::HTMLLinkElement>(element))
2007 return false;
2008
2009 return static_cast<HTML::HTMLLinkElement const&>(element).has_loaded_icon();
2010 }).release_value_but_fixme_should_propagate_errors();
2011
2012 if (favicon_link_elements->length() == 0) {
2013 dbgln_if(SPAM_DEBUG, "No favicon found to be used");
2014 return;
2015 }
2016
2017 // 4.6.7.8 Link type "icon"
2018 //
2019 // If there are multiple equally appropriate icons, user agents must use the last one declared
2020 // in tree order at the time that the user agent collected the list of icons.
2021 //
2022 // If multiple icons are provided, the user agent must select the most appropriate icon
2023 // according to the type, media, and sizes attributes.
2024 //
2025 // FIXME: There is no selective behavior yet for favicons.
2026 for (auto i = favicon_link_elements->length(); i-- > 0;) {
2027 auto favicon_element = favicon_link_elements->item(i);
2028
2029 if (favicon_element == m_active_element.ptr())
2030 return;
2031
2032 // If the user agent tries to use an icon but that icon is determined, upon closer examination,
2033 // to in fact be inappropriate (...), then the user agent must try the next-most-appropriate icon
2034 // as determined by the attributes.
2035 if (static_cast<HTML::HTMLLinkElement*>(favicon_element)->load_favicon_and_use_if_window_is_active()) {
2036 m_active_favicon = favicon_element;
2037 return;
2038 }
2039 }
2040
2041 dbgln_if(SPAM_DEBUG, "No favicon found to be used");
2042}
2043
2044void Document::set_window(Badge<HTML::BrowsingContext>, HTML::Window& window)
2045{
2046 m_window = &window;
2047}
2048
2049CSS::StyleSheetList& Document::style_sheets()
2050{
2051 if (!m_style_sheets)
2052 m_style_sheets = CSS::StyleSheetList::create(*this).release_value_but_fixme_should_propagate_errors();
2053 return *m_style_sheets;
2054}
2055
2056CSS::StyleSheetList const& Document::style_sheets() const
2057{
2058 return const_cast<Document*>(this)->style_sheets();
2059}
2060
2061JS::NonnullGCPtr<HTML::History> Document::history()
2062{
2063 if (!m_history)
2064 m_history = HTML::History::create(realm(), *this).release_value_but_fixme_should_propagate_errors();
2065 return *m_history;
2066}
2067
2068JS::NonnullGCPtr<HTML::History> Document::history() const
2069{
2070 return const_cast<Document*>(this)->history();
2071}
2072
2073// https://html.spec.whatwg.org/multipage/origin.html#dom-document-domain
2074DeprecatedString Document::domain() const
2075{
2076 // 1. Let effectiveDomain be this's origin's effective domain.
2077 auto effective_domain = origin().effective_domain();
2078
2079 // 2. If effectiveDomain is null, then return the empty string.
2080 if (!effective_domain.has_value())
2081 return DeprecatedString::empty();
2082
2083 // 3. Return effectiveDomain, serialized.
2084 // FIXME: Implement host serialization.
2085 return effective_domain.release_value();
2086}
2087
2088void Document::set_domain(DeprecatedString const& domain)
2089{
2090 dbgln("(STUBBED) Document::set_domain(domain='{}')", domain);
2091}
2092
2093void Document::set_navigation_id(Optional<AK::DeprecatedString> navigation_id)
2094{
2095 m_navigation_id = move(navigation_id);
2096}
2097
2098Optional<DeprecatedString> Document::navigation_id() const
2099{
2100 return m_navigation_id;
2101}
2102
2103HTML::SandboxingFlagSet Document::active_sandboxing_flag_set() const
2104{
2105 return m_active_sandboxing_flag_set;
2106}
2107
2108HTML::PolicyContainer Document::policy_container() const
2109{
2110 return m_policy_container;
2111}
2112
2113// https://html.spec.whatwg.org/multipage/browsers.html#list-of-the-descendant-browsing-contexts
2114Vector<JS::Handle<HTML::BrowsingContext>> Document::list_of_descendant_browsing_contexts() const
2115{
2116 // 1. Let list be an empty list.
2117 Vector<JS::Handle<HTML::BrowsingContext>> list;
2118
2119 // 2. For each browsing context container container,
2120 // whose nested browsing context is non-null and whose shadow-including root is d, in shadow-including tree order:
2121
2122 // NOTE: We already store our browsing contexts in a tree structure, so we can simply collect all the descendants
2123 // of this document's browsing context.
2124 if (browsing_context()) {
2125 browsing_context()->for_each_in_subtree([&](auto& context) {
2126 list.append(JS::make_handle(context));
2127 return IterationDecision::Continue;
2128 });
2129 }
2130
2131 return list;
2132}
2133
2134// https://html.spec.whatwg.org/multipage/window-object.html#discard-a-document
2135void Document::discard()
2136{
2137 // 1. Set document's salvageable state to false.
2138 m_salvageable = false;
2139
2140 // FIXME: 2. Run any unloading document cleanup steps for document that are defined by this specification and other applicable specifications.
2141
2142 // 3. Abort document.
2143 abort();
2144
2145 // 4. Remove any tasks associated with document in any task source, without running those tasks.
2146 HTML::main_thread_event_loop().task_queue().remove_tasks_matching([this](auto& task) {
2147 return task.document() == this;
2148 });
2149
2150 // 5. Discard all the child browsing contexts of document.
2151 if (browsing_context()) {
2152 browsing_context()->for_each_child([](HTML::BrowsingContext& child_browsing_context) {
2153 child_browsing_context.discard();
2154 });
2155 }
2156
2157 // FIXME: 6. For each session history entry entry whose document is equal to document, set entry's document to null.
2158
2159 // 7. Set document's browsing context to null.
2160 tear_down_layout_tree();
2161 m_browsing_context = nullptr;
2162
2163 // FIXME: 8. Remove document from the owner set of each WorkerGlobalScope object whose set contains document.
2164
2165 // FIXME: 9. For each workletGlobalScope in document's worklet global scopes, terminate workletGlobalScope.
2166}
2167
2168// https://html.spec.whatwg.org/multipage/browsing-the-web.html#abort-a-document
2169void Document::abort()
2170{
2171 // 1. Abort the active documents of every child browsing context.
2172 // If this results in any of those Document objects having their salvageable state set to false,
2173 // then set document's salvageable state to false also.
2174 if (browsing_context()) {
2175 browsing_context()->for_each_child([this](HTML::BrowsingContext& child_browsing_context) {
2176 if (auto* child_document = child_browsing_context.active_document()) {
2177 child_document->abort();
2178 if (!child_document->m_salvageable)
2179 m_salvageable = false;
2180 }
2181 });
2182 }
2183
2184 // FIXME: 2. Cancel any instances of the fetch algorithm in the context of document,
2185 // discarding any tasks queued for them, and discarding any further data received from the network for them.
2186 // If this resulted in any instances of the fetch algorithm being canceled
2187 // or any queued tasks or any network data getting discarded,
2188 // then set document's salvageable state to false.
2189
2190 // 3. If document's navigation id is non-null, then:
2191 if (m_navigation_id.has_value()) {
2192 // 1. FIXME: Invoke WebDriver BiDi navigation aborted with document's browsing context,
2193 // and new WebDriver BiDi navigation status whose whose id is document's navigation id,
2194 // status is "canceled", and url is document's URL.
2195
2196 // 2. Set document's navigation id to null.
2197 m_navigation_id = {};
2198 }
2199
2200 // 4. If document has an active parser, then:
2201 if (auto parser = active_parser()) {
2202 // 1. Set document's active parser was aborted to true.
2203 m_active_parser_was_aborted = true;
2204
2205 // 2. Abort that parser.
2206 parser->abort();
2207
2208 // 3. Set document's salvageable state to false.
2209 m_salvageable = false;
2210 }
2211}
2212
2213// https://html.spec.whatwg.org/multipage/dom.html#active-parser
2214JS::GCPtr<HTML::HTMLParser> Document::active_parser()
2215{
2216 if (!m_parser)
2217 return nullptr;
2218
2219 if (m_parser->aborted() || m_parser->stopped())
2220 return nullptr;
2221
2222 return m_parser;
2223}
2224
2225void Document::set_browsing_context(HTML::BrowsingContext* browsing_context)
2226{
2227 m_browsing_context = browsing_context;
2228}
2229
2230// https://html.spec.whatwg.org/multipage/browsing-the-web.html#unload-a-document
2231void Document::unload(bool recursive_flag, Optional<DocumentUnloadTimingInfo> unload_timing_info)
2232{
2233 // 1. Increase the event loop's termination nesting level by one.
2234 HTML::main_thread_event_loop().increment_termination_nesting_level();
2235
2236 // 2. Increase document's unload counter by 1.
2237 m_unload_counter += 1;
2238
2239 // 3. If the user agent does not intend to keep document alive in a session history entry
2240 // (such that it can be reused later on history traversal), set document's salvageable state to false.
2241 // FIXME: If we want to implement fast back/forward cache, this has to change.
2242 m_salvageable = false;
2243
2244 // 4. If document's page showing flag is true:
2245 if (m_page_showing) {
2246 // 1. Set document's page showing flag to false.
2247 m_page_showing = false;
2248
2249 // 2. Fire a page transition event named pagehide at document's relevant global object with document's salvageable state.
2250 global_object().fire_a_page_transition_event(HTML::EventNames::pagehide, m_salvageable);
2251
2252 // 3. Update the visibility state of newDocument to "hidden".
2253 update_the_visibility_state(HTML::VisibilityState::Hidden);
2254 }
2255
2256 // 5. If unloadTimingInfo is not null,
2257 if (unload_timing_info.has_value()) {
2258 // then set unloadTimingInfo's unload event start time to the current high resolution time given newGlobal,
2259 // coarsened given document's relevant settings object's cross-origin isolated capability.
2260 unload_timing_info->unload_event_start_time = HighResolutionTime::coarsen_time(
2261 HighResolutionTime::unsafe_shared_current_time(),
2262 relevant_settings_object().cross_origin_isolated_capability() == HTML::CanUseCrossOriginIsolatedAPIs::Yes);
2263 }
2264
2265 // 6. If document's salvageable state is false,
2266 if (!m_salvageable) {
2267 // then fire an event named unload at document's relevant global object, with legacy target override flag set.
2268 // FIXME: The legacy target override flag is currently set by a virtual override of dispatch_event()
2269 // We should reorganize this so that the flag appears explicitly here instead.
2270 auto event = DOM::Event::create(realm(), HTML::EventNames::unload).release_value_but_fixme_should_propagate_errors();
2271 global_object().dispatch_event(event);
2272 }
2273
2274 // 7. If unloadTimingInfo is not null,
2275 if (unload_timing_info.has_value()) {
2276 // then set unloadTimingInfo's unload event end time to the current high resolution time given newGlobal,
2277 // coarsened given document's relevant settings object's cross-origin isolated capability.
2278 unload_timing_info->unload_event_end_time = HighResolutionTime::coarsen_time(
2279 HighResolutionTime::unsafe_shared_current_time(),
2280 relevant_settings_object().cross_origin_isolated_capability() == HTML::CanUseCrossOriginIsolatedAPIs::Yes);
2281 }
2282
2283 // 8. Decrease the event loop's termination nesting level by one.
2284 HTML::main_thread_event_loop().decrement_termination_nesting_level();
2285
2286 // FIXME: 9. Set document's suspension time to the current high resolution time given document's relevant global object.
2287
2288 // FIXME: 10. Set document's suspended timer handles to the result of getting the keys for the map of active timers.
2289
2290 // FIXME: 11. Run any unloading document cleanup steps for document that are defined by this specification and other applicable specifications.
2291
2292 // 12. If the recursiveFlag is not set, then:
2293 if (!recursive_flag) {
2294 // 1. Let descendants be the list of the descendant browsing contexts of document.
2295 auto descendants = list_of_descendant_browsing_contexts();
2296
2297 // 2. For each browsingContext in descendants:
2298 for (auto browsing_context : descendants) {
2299 JS::GCPtr<Document> active_document = browsing_context->active_document();
2300 if (!active_document)
2301 continue;
2302
2303 // 1. Unload the active document of browsingContext with the recursiveFlag set.
2304 active_document->unload(true);
2305
2306 // 2. If the salvageable state of the active document of browsingContext is false,
2307 // then set the salvageable state of document to false also.
2308 if (!active_document->m_salvageable)
2309 m_salvageable = false;
2310 }
2311
2312 // 3. If document's salvageable state is false, then discard document.
2313 if (!m_salvageable)
2314 discard();
2315 }
2316
2317 // 13. Decrease document's unload counter by 1.
2318 m_unload_counter -= 1;
2319}
2320
2321void Document::did_stop_being_active_document_in_browsing_context(Badge<HTML::BrowsingContext>)
2322{
2323 tear_down_layout_tree();
2324}
2325
2326// https://w3c.github.io/editing/docs/execCommand/#querycommandsupported()
2327bool Document::query_command_supported(DeprecatedString const& command) const
2328{
2329 dbgln("(STUBBED) Document::query_command_supported(command='{}')", command);
2330 return false;
2331}
2332
2333// https://html.spec.whatwg.org/multipage/scripting.html#appropriate-template-contents-owner-document
2334JS::NonnullGCPtr<DOM::Document> Document::appropriate_template_contents_owner_document()
2335{
2336 // 1. If doc is not a Document created by this algorithm, then:
2337 if (!created_for_appropriate_template_contents()) {
2338 // 1. If doc does not yet have an associated inert template document, then:
2339 if (!m_associated_inert_template_document) {
2340 // 1. Let new doc be a new Document (whose browsing context is null). This is "a Document created by this algorithm" for the purposes of the step above.
2341 auto new_document = DOM::Document::create(realm()).release_value_but_fixme_should_propagate_errors();
2342 new_document->m_created_for_appropriate_template_contents = true;
2343
2344 // 2. If doc is an HTML document, mark new doc as an HTML document also.
2345 if (document_type() == Type::HTML)
2346 new_document->set_document_type(Type::HTML);
2347
2348 // 3. Let doc's associated inert template document be new doc.
2349 m_associated_inert_template_document = new_document;
2350 }
2351 // 2. Set doc to doc's associated inert template document.
2352 return *m_associated_inert_template_document;
2353 }
2354 // 2. Return doc.
2355 return *this;
2356}
2357
2358DeprecatedString Document::dump_accessibility_tree_as_json()
2359{
2360 StringBuilder builder;
2361 auto accessibility_tree = AccessibilityTreeNode::create(this, nullptr).release_value_but_fixme_should_propagate_errors();
2362 build_accessibility_tree(*&accessibility_tree);
2363 auto json = MUST(JsonObjectSerializer<>::try_create(builder));
2364
2365 // Empty document
2366 if (!accessibility_tree->value()) {
2367 MUST(json.add("type"sv, "element"sv));
2368 MUST(json.add("role"sv, "document"sv));
2369 } else {
2370 accessibility_tree->serialize_tree_as_json(json, *this);
2371 }
2372
2373 MUST(json.finish());
2374 return builder.to_deprecated_string();
2375}
2376
2377// https://dom.spec.whatwg.org/#dom-document-createattribute
2378WebIDL::ExceptionOr<JS::NonnullGCPtr<Attr>> Document::create_attribute(DeprecatedString const& local_name)
2379{
2380 // 1. If localName does not match the Name production in XML, then throw an "InvalidCharacterError" DOMException.
2381 if (!is_valid_name(local_name))
2382 return WebIDL::InvalidCharacterError::create(realm(), "Invalid character in attribute name.");
2383
2384 // 2. If this is an HTML document, then set localName to localName in ASCII lowercase.
2385 // 3. Return a new attribute whose local name is localName and node document is this.
2386 return Attr::create(*this, is_html_document() ? local_name.to_lowercase() : local_name);
2387}
2388
2389// https://dom.spec.whatwg.org/#dom-document-createattributens
2390WebIDL::ExceptionOr<JS::NonnullGCPtr<Attr>> Document::create_attribute_ns(DeprecatedString const& namespace_, DeprecatedString const& qualified_name)
2391{
2392 // 1. Let namespace, prefix, and localName be the result of passing namespace and qualifiedName to validate and extract.
2393 auto extracted_qualified_name = TRY(validate_and_extract(realm(), namespace_, qualified_name));
2394
2395 // 2. Return a new attribute whose namespace is namespace, namespace prefix is prefix, local name is localName, and node document is this.
2396
2397 return Attr::create(*this, extracted_qualified_name);
2398}
2399
2400}