Serenity Operating System
at master 2400 lines 96 kB view raw
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}