/* * Copyright (c) 2018-2021, Andreas Kling * Copyright (c) 2022, networkException * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Web::HTML { GC_DEFINE_ALLOCATOR(HTMLScriptElement); HTMLScriptElement::HTMLScriptElement(DOM::Document& document, DOM::QualifiedName qualified_name) : HTMLElement(document, move(qualified_name)) { } HTMLScriptElement::~HTMLScriptElement() = default; void HTMLScriptElement::initialize(JS::Realm& realm) { WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLScriptElement); Base::initialize(realm); } void HTMLScriptElement::visit_edges(Cell::Visitor& visitor) { Base::visit_edges(visitor); if (auto* script = m_result.get_pointer>()) visitor.visit(*script); visitor.visit(m_parser_document); visitor.visit(m_preparation_time_document); } void HTMLScriptElement::attribute_changed(FlyString const& name, Optional const& old_value, Optional const& value, Optional const& namespace_) { Base::attribute_changed(name, old_value, value, namespace_); if (name == HTML::AttributeNames::crossorigin) { m_crossorigin = cors_setting_attribute_from_keyword(value); } else if (name == HTML::AttributeNames::referrerpolicy) { m_referrer_policy = ReferrerPolicy::from_string(value.value_or(""_string)).value_or(ReferrerPolicy::ReferrerPolicy::EmptyString); } else if (name == HTML::AttributeNames::src) { // https://html.spec.whatwg.org/multipage/scripting.html#script-processing-model:html-element-post-connection-steps-6 // 1. If namespace is not null, then return. if (namespace_.has_value()) return; // AD-HOC: This ensures that prepare_script() is not called when the src attribute is removed. // See: https://github.com/whatwg/html/pull/10188/files#r1685905457 for more information. if (!value.has_value()) return; // 2. If localName is src, then run the script HTML element post-connection steps, given element. post_connection(); } else if (name == HTML::AttributeNames::async) { // https://html.spec.whatwg.org/multipage/scripting.html#script-processing-model:script-force-async // When an async attribute is added to a script element el, the user agent must set el's force async to false. m_force_async = false; } } void HTMLScriptElement::begin_delaying_document_load_event(DOM::Document& document) { // https://html.spec.whatwg.org/multipage/scripting.html#concept-script-script // The user agent must delay the load event of the element's node document until the script is ready. m_document_load_event_delayer.emplace(document); } // https://html.spec.whatwg.org/multipage/scripting.html#execute-the-script-block void HTMLScriptElement::execute_script() { // https://html.spec.whatwg.org/multipage/document-lifecycle.html#read-html // Before any script execution occurs, the user agent must wait for scripts may run for the newly-created document to be true for document. if (!m_document->ready_to_run_scripts()) main_thread_event_loop().spin_until(GC::create_function(heap(), [&] { return m_document->ready_to_run_scripts(); })); // 1. Let document be el's node document. GC::Ref document = this->document(); // 2. If el's preparation-time document is not equal to document, then return. if (m_preparation_time_document.ptr() != document.ptr()) { dbgln("HTMLScriptElement: Refusing to run script because the preparation time document is not the same as the node document."); return; } // 3. Unblock rendering on el. unblock_rendering(); // 3. If el's result is null, then fire an event named error at el, and return. if (m_result.has()) { dbgln("HTMLScriptElement: Refusing to run script because the element's result is null."); dispatch_event(DOM::Event::create(realm(), HTML::EventNames::error)); return; } // 5. If el's from an external file is true, or el's type is "module", then increment document's ignore-destructive-writes counter. bool incremented_destructive_writes_counter = false; if (m_from_an_external_file || m_script_type == ScriptType::Module) { document->increment_ignore_destructive_writes_counter(); incremented_destructive_writes_counter = true; } // 5. Switch on el's type: // -> "classic" if (m_script_type == ScriptType::Classic) { // 1. Let oldCurrentScript be the value to which document's currentScript object was most recently set. auto old_current_script = document->current_script(); // 2. If el's root is not a shadow root, then set document's currentScript attribute to el. Otherwise, set it to null. if (!is(root())) document->set_current_script({}, this); else document->set_current_script({}, nullptr); if (m_from_an_external_file) dbgln_if(HTML_SCRIPT_DEBUG, "HTMLScriptElement: Running script {}", attribute(HTML::AttributeNames::src).value_or(String {})); else dbgln_if(HTML_SCRIPT_DEBUG, "HTMLScriptElement: Running inline script"); // 3. Run the classic script given by el's result. (void)as(*m_result.get>()).run(); // 4. Set document's currentScript attribute to oldCurrentScript. document->set_current_script({}, old_current_script); } // -> "module" else if (m_script_type == ScriptType::Module) { // 1. Assert: document's currentScript attribute is null. VERIFY(document->current_script() == nullptr); // 2. Run the module script given by el's result. (void)as(*m_result.get>()).run(); } // -> "importmap" else if (m_script_type == ScriptType::ImportMap) { // 1. Register an import map given el's relevant global object and el's result. m_result.get>()->register_import_map(as(relevant_global_object(*this))); } // 7. Decrement the ignore-destructive-writes counter of document, if it was incremented in the earlier step. if (incremented_destructive_writes_counter) document->decrement_ignore_destructive_writes_counter(); // 8. If el's from an external file is true, then fire an event named load at el. if (m_from_an_external_file) dispatch_event(DOM::Event::create(realm(), HTML::EventNames::load)); } // https://html.spec.whatwg.org/multipage/scripting.html#prepare-a-script // https://whatpr.org/html/9893/scripting.html#prepare-a-script void HTMLScriptElement::prepare_script() { // 1. If el's already started is true, then return. if (m_already_started) { dbgln("HTMLScriptElement: Refusing to run script because it has already started."); return; } // 2. Let parser document be el's parser document. GC::Ptr parser_document = m_parser_document; // 3. Set el's parser document to null. m_parser_document = nullptr; // 4. If parser document is non-null and el does not have an async attribute, then set el's force async to true. if (parser_document && !has_attribute(HTML::AttributeNames::async)) { m_force_async = true; } // 5. Let source text be el's child text content. auto source_text = child_text_content(); // 6. If el has no src attribute, and source text is the empty string, then return. if (!has_attribute(HTML::AttributeNames::src) && source_text.is_empty()) { return; } // 7. If el is not connected, then return. if (!is_connected()) { dbgln("HTMLScriptElement: Refusing to run script because the element is not connected."); return; } // 8. If any of the following are true: // - el has a type attribute whose value is the empty string; // - el has no type attribute but it has a language attribute and that attribute's value is the empty string; or // - el has neither a type attribute nor a language attribute String script_block_type; auto maybe_type_attribute = attribute(HTML::AttributeNames::type); auto maybe_language_attribute = attribute(HTML::AttributeNames::language); if ((maybe_type_attribute.has_value() && maybe_type_attribute->is_empty()) || (!maybe_type_attribute.has_value() && maybe_language_attribute.has_value() && maybe_language_attribute->is_empty()) || (!maybe_type_attribute.has_value() && !maybe_language_attribute.has_value())) { // then let the script block's type string for this script element be "text/javascript". script_block_type = "text/javascript"_string; } // Otherwise, if el has a type attribute, else if (maybe_type_attribute.has_value()) { // then let the script block's type string be the value of that attribute with leading and trailing ASCII whitespace stripped. script_block_type = MUST(maybe_type_attribute->trim(Infra::ASCII_WHITESPACE)); } // Otherwise, el has a non-empty language attribute; else if (maybe_language_attribute.has_value() && !maybe_language_attribute->is_empty()) { // let the script block's type string be the concatenation of "text/" and the value of el's language attribute. script_block_type = MUST(String::formatted("text/{}", maybe_language_attribute.value())); } // 9. If the script block's type string is a JavaScript MIME type essence match, if (MimeSniff::is_javascript_mime_type_essence_match(script_block_type)) { // then set el's type to "classic". m_script_type = ScriptType::Classic; } // 10. Otherwise, if the script block's type string is an ASCII case-insensitive match for the string "module", else if (script_block_type.equals_ignoring_ascii_case("module"sv)) { // then set el's type to "module". m_script_type = ScriptType::Module; } // 11. Otherwise, if the script block's type string is an ASCII case-insensitive match for the string "importmap", else if (script_block_type.equals_ignoring_ascii_case("importmap"sv)) { // then set el's type to "importmap". m_script_type = ScriptType::ImportMap; } // 12. Otherwise, return. (No script is executed, and el's type is left as null.) else { VERIFY(m_script_type == ScriptType::Null); return; } // 13. If parser document is non-null, then set el's parser document back to parser document and set el's force async to false. if (parser_document) { m_parser_document = parser_document; m_force_async = false; } // 14. Set el's already started to true. m_already_started = true; // 15. Set el's preparation-time document to its node document. m_preparation_time_document = &document(); // 16. If parser document is non-null, and parser document is not equal to el's preparation-time document, then return. if (parser_document != nullptr && parser_document != m_preparation_time_document) { dbgln("HTMLScriptElement: Refusing to run script because the parser document is not the same as the preparation time document."); return; } // 17. If scripting is disabled for el, then return. if (is_scripting_disabled()) { dbgln("HTMLScriptElement: Refusing to run script because scripting is disabled."); return; } // 18. If el has a nomodule content attribute and its type is "classic", then return. if (m_script_type == ScriptType::Classic && has_attribute(HTML::AttributeNames::nomodule)) { dbgln("HTMLScriptElement: Refusing to run classic script because it has the nomodule attribute."); return; } // 19. If el does not have a src content attribute, and the Should element's inline behavior be blocked by Content Security Policy? // algorithm returns "Blocked" when given el, "script", and source text, then return. [CSP] if (!has_attribute(AttributeNames::src) && ContentSecurityPolicy::should_elements_inline_type_behavior_be_blocked_by_content_security_policy(realm(), *this, ContentSecurityPolicy::Directives::Directive::InlineType::Script, source_text) == ContentSecurityPolicy::Directives::Directive::Result::Blocked) { dbgln("HTMLScriptElement: Refusing to run inline script because it violates the Content Security Policy."); return; } // 20. If el has an event attribute and a for attribute, and el's type is "classic", then: if (m_script_type == ScriptType::Classic && has_attribute(HTML::AttributeNames::event) && has_attribute(HTML::AttributeNames::for_)) { // 1. Let for be the value of el's' for attribute. auto for_ = get_attribute_value(HTML::AttributeNames::for_); // 2. Let event be the value of el's event attribute. auto event = get_attribute_value(HTML::AttributeNames::event); // 3. Strip leading and trailing ASCII whitespace from event and for. for_ = MUST(for_.trim(Infra::ASCII_WHITESPACE)); event = MUST(event.trim(Infra::ASCII_WHITESPACE)); // 4. If for is not an ASCII case-insensitive match for the string "window", then return. if (!for_.equals_ignoring_ascii_case("window"sv)) { dbgln("HTMLScriptElement: Refusing to run classic script because the provided 'for' attribute is not equal to 'window'"); return; } // 5. If event is not an ASCII case-insensitive match for either the string "onload" or the string "onload()", then return. if (!event.equals_ignoring_ascii_case("onload"sv) && !event.equals_ignoring_ascii_case("onload()"sv)) { dbgln("HTMLScriptElement: Refusing to run classic script because the provided 'event' attribute is not equal to 'onload' or 'onload()'"); return; } } // 21. If el has a charset attribute, then let encoding be the result of getting an encoding from the value of the charset attribute. // If el does not have a charset attribute, or if getting an encoding failed, then let encoding be el's node document's the encoding. Optional encoding; if (has_attribute(HTML::AttributeNames::charset)) { auto charset = TextCodec::get_standardized_encoding(get_attribute_value(HTML::AttributeNames::charset)); if (charset.has_value()) encoding = String::from_utf8(*charset).release_value_but_fixme_should_propagate_errors(); } if (!encoding.has_value()) { encoding = document().encoding_or_default(); } VERIFY(encoding.has_value()); // 22. Let classic script CORS setting be the current state of el's crossorigin content attribute. auto classic_script_cors_setting = m_crossorigin; // 23. Let module script credentials mode be the CORS settings attribute credentials mode for el's crossorigin content attribute. auto module_script_credential_mode = cors_settings_attribute_credentials_mode(m_crossorigin); // 24. Let cryptographic nonce be el's [[CryptographicNonce]] internal slot's value. auto cryptographic_nonce = m_cryptographic_nonce; // 25. If el has an integrity attribute, then let integrity metadata be that attribute's value. // Otherwise, let integrity metadata be the empty string. String integrity_metadata; if (auto maybe_integrity = attribute(HTML::AttributeNames::integrity); maybe_integrity.has_value()) { integrity_metadata = *maybe_integrity; } // 26. Let referrer policy be the current state of el's referrerpolicy content attribute. auto referrer_policy = m_referrer_policy; // 27. Let fetch priority be the current state of el's fetchpriority content attribute. auto fetch_priority = Fetch::Infrastructure::request_priority_from_string(get_attribute_value(HTML::AttributeNames::fetchpriority)).value_or(Fetch::Infrastructure::Request::Priority::Auto); // 28. Let parser metadata be "parser-inserted" if el is parser-inserted, and "not-parser-inserted" otherwise. auto parser_metadata = is_parser_inserted() ? Fetch::Infrastructure::Request::ParserMetadata::ParserInserted : Fetch::Infrastructure::Request::ParserMetadata::NotParserInserted; // 29. Let options be a script fetch options whose cryptographic nonce is cryptographic nonce, // integrity metadata is integrity metadata, parser metadata is parser metadata, // credentials mode is module script credentials mode, referrer policy is referrer policy, // and fetch priority is fetch priority. ScriptFetchOptions options { .cryptographic_nonce = move(cryptographic_nonce), .integrity_metadata = move(integrity_metadata), .parser_metadata = parser_metadata, .credentials_mode = module_script_credential_mode, .referrer_policy = move(referrer_policy), .fetch_priority = move(fetch_priority), }; // 30. Let settings object be el's node document's relevant settings object. auto& settings_object = document().relevant_settings_object(); // 31. If el has a src content attribute, then: if (has_attribute(HTML::AttributeNames::src)) { // 1. If el's type is "importmap", if (m_script_type == ScriptType::ImportMap) { // then queue an element task on the DOM manipulation task source given el to fire an event named error at el, and return. queue_an_element_task(HTML::Task::Source::DOMManipulation, [this] { dispatch_event(DOM::Event::create(realm(), HTML::EventNames::error)); }); return; } // 2. Let src be the value of el's src attribute. auto src = get_attribute_value(HTML::AttributeNames::src); // 3. If src is the empty string, then queue an element task on the DOM manipulation task source given el to fire an event named error at el, and return. if (src.is_empty()) { dbgln("HTMLScriptElement: Refusing to run script because the src attribute is empty."); queue_an_element_task(HTML::Task::Source::DOMManipulation, [this] { dispatch_event(DOM::Event::create(realm(), HTML::EventNames::error)); }); return; } // 4. Set el's from an external file to true. m_from_an_external_file = true; // 5. Let url be the result of encoding-parsing a URL given src, relative to el's node document. auto url = document().encoding_parse_url(src); // 6. If url is failure, then queue an element task on the DOM manipulation task source given el to fire an event named error at el, and return. if (!url.has_value()) { dbgln("HTMLScriptElement: Refusing to run script because the src URL '{}' is invalid.", url); queue_an_element_task(HTML::Task::Source::DOMManipulation, [this] { dispatch_event(DOM::Event::create(realm(), HTML::EventNames::error)); }); return; } // 7. If el is potentially render-blocking, then block rendering on el. if (is_potentially_render_blocking()) { block_rendering(); } // 8. Set el's delaying the load event to true. begin_delaying_document_load_event(*m_preparation_time_document); // FIXME: 9. If el is currently render-blocking, then set options's render-blocking to true. // 10. Let onComplete given result be the following steps: OnFetchScriptComplete on_complete = create_on_fetch_script_complete(heap(), [this](auto result) { // 1. Mark as ready el given result. if (result) mark_as_ready(Result { *result }); else mark_as_ready(ResultState::Null {}); }); // 11. Switch on el's type: // -> "classic" if (m_script_type == ScriptType::Classic) { // Fetch a classic script given url, settings object, options, classic script CORS setting, encoding, and onComplete. fetch_classic_script(*this, *url, settings_object, move(options), classic_script_cors_setting, encoding.release_value(), on_complete).release_value_but_fixme_should_propagate_errors(); } // -> "module" else if (m_script_type == ScriptType::Module) { // If el does not have an integrity attribute, then set options's integrity metadata to the result of resolving a module integrity metadata with url and settings object. if (!has_attribute(HTML::AttributeNames::integrity)) options.integrity_metadata = resolve_a_module_integrity_metadata(*url, settings_object); // Fetch an external module script graph given url, settings object, options, and onComplete. fetch_external_module_script_graph(realm(), *url, settings_object, options, on_complete); } } // 32. If el does not have a src content attribute: if (!has_attribute(HTML::AttributeNames::src)) { // Let base URL be el's node document's document base URL. auto base_url = document().base_url(); // 2. Switch on el's type: // -> "classic" if (m_script_type == ScriptType::Classic) { // 1. Let script be the result of creating a classic script using source text, settings object's realm, base URL, and options. // FIXME: Pass options. auto script = ClassicScript::create(m_document->url().to_byte_string(), source_text, settings_object.realm(), base_url, m_source_line_number); // 2. Mark as ready el given script. mark_as_ready(Result(move(script))); } // -> "module" else if (m_script_type == ScriptType::Module) { // 1. Set el's delaying the load event to true. begin_delaying_document_load_event(*m_preparation_time_document); auto steps = create_on_fetch_script_complete(heap(), [this](auto result) { // 1. Mark as ready el given result. if (!result) mark_as_ready(ResultState::Null {}); else mark_as_ready(Result(*result)); }); // 2. Fetch an inline module script graph, given source text, base URL, settings object, options, and with the following steps given result: // FIXME: Pass options fetch_inline_module_script_graph(realm(), m_document->url().to_byte_string(), source_text.to_byte_string(), base_url, document().relevant_settings_object(), steps); } // -> "importmap" else if (m_script_type == ScriptType::ImportMap) { // 1. Let result be the result of creating an import map parse result given source text and base URL. auto result = ImportMapParseResult::create(realm(), source_text.to_byte_string(), base_url); // 2. Mark as ready el given result. mark_as_ready(Result(move(result))); } } // 33. If el's type is "classic" and el has a src attribute, or el's type is "module": if ((m_script_type == ScriptType::Classic && has_attribute(HTML::AttributeNames::src)) || m_script_type == ScriptType::Module) { // 1. Assert: el's result is "uninitialized". // FIXME: I believe this step to be a spec bug, and it should be removed: https://github.com/whatwg/html/issues/8534 // 2. If el has an async attribute or el's force async is true: if (has_attribute(HTML::AttributeNames::async) || m_force_async) { // 1. Let scripts be el's preparation-time document's set of scripts that will execute as soon as possible. // 2. Append el to scripts. m_preparation_time_document->scripts_to_execute_as_soon_as_possible().append(*this); // 3. Set el's steps to run when the result is ready to the following: m_steps_to_run_when_the_result_is_ready = [this] { // 1. Execute the script element el. execute_script(); // 2. Remove el from scripts. m_preparation_time_document->scripts_to_execute_as_soon_as_possible().remove_first_matching([this](auto& entry) { return entry.ptr() == this; }); }; } // 3. Otherwise, if el is not parser-inserted: else if (!is_parser_inserted()) { // 1. Let scripts be el's preparation-time document's list of scripts that will execute in order as soon as possible. // 2. Append el to scripts. m_preparation_time_document->scripts_to_execute_in_order_as_soon_as_possible().append(*this); // 3. Set el's steps to run when the result is ready to the following: m_steps_to_run_when_the_result_is_ready = [this] { auto& scripts = m_preparation_time_document->scripts_to_execute_in_order_as_soon_as_possible(); // 1. If scripts[0] is not el, then abort these steps. if (scripts[0] != this) return; // 2. While scripts is not empty, and scripts[0]'s result is not "uninitialized": while (!scripts.is_empty() && !scripts[0]->m_result.has()) { // 1. Execute the script element scripts[0]. scripts[0]->execute_script(); // 2. Remove scripts[0]. scripts.take_first(); } }; } // 4. Otherwise, if el has a defer attribute or el's type is "module": else if (has_attribute(HTML::AttributeNames::defer) || m_script_type == ScriptType::Module) { // 1. Append el to its parser document's list of scripts that will execute when the document has finished parsing. m_parser_document->add_script_to_execute_when_parsing_has_finished({}, *this); // 2. Set el's steps to run when the result is ready to the following: m_steps_to_run_when_the_result_is_ready = [this] { // set el's ready to be parser-executed to true. (The parser will handle executing the script.) m_ready_to_be_parser_executed = true; }; } // 5. Otherwise: else { // 1. Set el's parser document's pending parsing-blocking script to el. m_parser_document->set_pending_parsing_blocking_script(this); // FIXME: 2. Block rendering on el. // 3. Set el's steps to run when the result is ready to the following: m_steps_to_run_when_the_result_is_ready = [this] { // set el's ready to be parser-executed to true. (The parser will handle executing the script.) m_ready_to_be_parser_executed = true; }; } } // 34. Otherwise: else { // 1. Assert: el's result is not "uninitialized". VERIFY(!m_result.has()); // 2. If all of the following are true: // - el's type is "classic"; // - el is parser-inserted; // - el's parser document has a style sheet that is blocking scripts; and // FIXME: - either the parser that created el is an XML parser, or it's an HTML parser whose script nesting level is not greater than one, // then: if (m_script_type == ScriptType::Classic && is_parser_inserted() && m_parser_document->has_a_style_sheet_that_is_blocking_scripts()) { // 1. Set el's parser document's pending parsing-blocking script to el. m_parser_document->set_pending_parsing_blocking_script(this); // 2. Set el's ready to be parser-executed to true. (The parser will handle executing the script.) m_ready_to_be_parser_executed = true; } // 3. Otherwise, else { // immediately execute the script element el, even if other scripts are already executing. execute_script(); } } } // https://html.spec.whatwg.org/multipage/scripting.html#script-processing-model:html-element-post-connection-steps-4 void HTMLScriptElement::children_changed(ChildrenChangedMetadata const* metadata) { Base::children_changed(metadata); // 1. Run the script HTML element post-connection steps, given the script element. post_connection(); } // https://html.spec.whatwg.org/multipage/scripting.html#script-processing-model:prepare-the-script-element-5 void HTMLScriptElement::post_connection() { // 1. If insertedNode is not connected, then return. if (!is_connected()) return; // 2. If insertedNode is parser-inserted, then return. if (is_parser_inserted()) return; // 3. Prepare the script element given insertedNode. prepare_script(); } // https://html.spec.whatwg.org/multipage/scripting.html#mark-as-ready void HTMLScriptElement::mark_as_ready(Result result) { // 1. Set el's result to result. m_result = move(result); // 2. If el's steps to run when the result is ready are not null, then run them. if (m_steps_to_run_when_the_result_is_ready) m_steps_to_run_when_the_result_is_ready(); // 3. Set el's steps to run when the result is ready to null. m_steps_to_run_when_the_result_is_ready = nullptr; // 4. Set el's delaying the load event to false. m_document_load_event_delayer.clear(); } void HTMLScriptElement::unmark_as_already_started(Badge) { m_already_started = false; } void HTMLScriptElement::unmark_as_parser_inserted(Badge) { m_parser_document = nullptr; } // https://html.spec.whatwg.org/multipage/scripting.html#dom-script-async bool HTMLScriptElement::async() const { // 1. If this's force async is true, then return true. if (m_force_async) return true; // 2. If this's async content attribute is present, then return true. if (has_attribute(HTML::AttributeNames::async)) return true; // 3. Return false. return false; } void HTMLScriptElement::set_async(bool async) { // 1. Set this's force async to false. m_force_async = false; // 2. If the given value is true, then set this's async content attribute to the empty string. if (async) { MUST(set_attribute(HTML::AttributeNames::async, ""_string)); } // 3. Otherwise, remove this's async content attribute. else { remove_attribute(HTML::AttributeNames::async); } } // https://html.spec.whatwg.org/multipage/scripting.html#script-processing-model:concept-node-clone-ext WebIDL::ExceptionOr HTMLScriptElement::cloned(Node& copy, bool subtree) const { TRY(Base::cloned(copy, subtree)); // The cloning steps for script elements given node, copy, and subtree are to set copy's already started to node's already started. auto& script_copy = as(copy); script_copy.m_already_started = m_already_started; return {}; } // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#implicitly-potentially-render-blocking bool HTMLScriptElement::is_implicitly_potentially_render_blocking() const { // A script element el is implicitly potentially render-blocking if el's type is "classic", el is parser-inserted, and el does not have an async or defer attribute. return m_script_type == ScriptType::Classic && is_parser_inserted() && !has_attribute(AttributeNames::async) && !has_attribute(AttributeNames::defer); } }