mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-01-23 09:46:04 -05:00
b981e6f7bc
Many times, attribute mutation doesn't necessitate a full style invalidation on the element. However, the conditions are pretty elaborate, so this first version has a lot of false positives. We only need to invalidate style when any of these things apply: 1. The change may affect the match state of a selector somewhere. 2. The change may affect presentational hints applied to the element. For (1) in this first version, we have a fixed list of attribute names that may affect selectors. We also collect all names referenced by attribute selectors anywhere in the document. For (2), we add a new Element::is_presentational_hint() virtual that tells us whether a given attribute name is a presentational hint. This drastically reduces style work on many websites. As an example, https://cnn.com/ is once again browseable.
597 lines
28 KiB
C++
597 lines
28 KiB
C++
/*
|
|
* Copyright (c) 2020-2024, Andreas Kling <andreas@ladybird.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <LibGfx/Bitmap.h>
|
|
#include <LibWeb/Bindings/HTMLObjectElementPrototype.h>
|
|
#include <LibWeb/CSS/StyleComputer.h>
|
|
#include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
|
|
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
|
|
#include <LibWeb/CSS/StyleValues/LengthStyleValue.h>
|
|
#include <LibWeb/DOM/Document.h>
|
|
#include <LibWeb/DOM/DocumentLoadEventDelayer.h>
|
|
#include <LibWeb/DOM/DocumentLoading.h>
|
|
#include <LibWeb/DOM/DocumentObserver.h>
|
|
#include <LibWeb/DOM/Event.h>
|
|
#include <LibWeb/Fetch/Fetching/Fetching.h>
|
|
#include <LibWeb/Fetch/Infrastructure/FetchAlgorithms.h>
|
|
#include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
|
|
#include <LibWeb/HTML/DecodedImageData.h>
|
|
#include <LibWeb/HTML/HTMLMediaElement.h>
|
|
#include <LibWeb/HTML/HTMLObjectElement.h>
|
|
#include <LibWeb/HTML/ImageRequest.h>
|
|
#include <LibWeb/HTML/Numbers.h>
|
|
#include <LibWeb/HTML/Parser/HTMLParser.h>
|
|
#include <LibWeb/HTML/PotentialCORSRequest.h>
|
|
#include <LibWeb/Layout/ImageBox.h>
|
|
#include <LibWeb/Layout/NavigableContainerViewport.h>
|
|
#include <LibWeb/Loader/ResourceLoader.h>
|
|
#include <LibWeb/MimeSniff/MimeType.h>
|
|
#include <LibWeb/MimeSniff/Resource.h>
|
|
|
|
namespace Web::HTML {
|
|
|
|
GC_DEFINE_ALLOCATOR(HTMLObjectElement);
|
|
|
|
HTMLObjectElement::HTMLObjectElement(DOM::Document& document, DOM::QualifiedName qualified_name)
|
|
: NavigableContainer(document, move(qualified_name))
|
|
{
|
|
// https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-object-element:potentially-delays-the-load-event
|
|
set_potentially_delays_the_load_event(true);
|
|
|
|
// https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-object-element
|
|
// Whenever one of the following conditions occur:
|
|
// - the element is created,
|
|
// ...the user agent must queue an element task on the DOM manipulation task source given the object element to run
|
|
// the following steps to (re)determine what the object element represents.
|
|
queue_element_task_to_run_object_representation_steps();
|
|
}
|
|
|
|
HTMLObjectElement::~HTMLObjectElement() = default;
|
|
|
|
void HTMLObjectElement::initialize(JS::Realm& realm)
|
|
{
|
|
Base::initialize(realm);
|
|
WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLObjectElement);
|
|
|
|
m_document_observer = realm.create<DOM::DocumentObserver>(realm, document());
|
|
|
|
// https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-object-element
|
|
// Whenever one of the following conditions occur:
|
|
// - the element's node document changes whether it is fully active,
|
|
// ...the user agent must queue an element task on the DOM manipulation task source given the object element to run
|
|
// the following steps to (re)determine what the object element represents.
|
|
m_document_observer->set_document_became_active([this]() {
|
|
queue_element_task_to_run_object_representation_steps();
|
|
});
|
|
m_document_observer->set_document_became_inactive([this]() {
|
|
queue_element_task_to_run_object_representation_steps();
|
|
});
|
|
}
|
|
|
|
void HTMLObjectElement::visit_edges(Cell::Visitor& visitor)
|
|
{
|
|
Base::visit_edges(visitor);
|
|
visitor.visit(m_resource_request);
|
|
visitor.visit(m_document_observer);
|
|
}
|
|
|
|
void HTMLObjectElement::form_associated_element_attribute_changed(FlyString const& name, Optional<String> const&, Optional<FlyString> const&)
|
|
{
|
|
// https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-object-element
|
|
// Whenever one of the following conditions occur:
|
|
if (
|
|
// - the element's classid attribute is set, changed, or removed,
|
|
(name == HTML::AttributeNames::classid) ||
|
|
// - the element's classid attribute is not present, and its data attribute is set, changed, or removed,
|
|
(!has_attribute(HTML::AttributeNames::classid) && name == HTML::AttributeNames::data) ||
|
|
// - neither the element's classid attribute nor its data attribute are present, and its type attribute is set, changed, or removed,
|
|
(!has_attribute(HTML::AttributeNames::classid) && !has_attribute(HTML::AttributeNames::data) && name == HTML::AttributeNames::type)) {
|
|
// ...the user agent must queue an element task on the DOM manipulation task source given the object element to run
|
|
// the following steps to (re)determine what the object element represents.
|
|
queue_element_task_to_run_object_representation_steps();
|
|
}
|
|
}
|
|
|
|
void HTMLObjectElement::form_associated_element_was_removed(DOM::Node*)
|
|
{
|
|
destroy_the_child_navigable();
|
|
}
|
|
|
|
bool HTMLObjectElement::is_presentational_hint(FlyString const& name) const
|
|
{
|
|
if (Base::is_presentational_hint(name))
|
|
return true;
|
|
|
|
return first_is_one_of(name,
|
|
HTML::AttributeNames::align,
|
|
HTML::AttributeNames::border,
|
|
HTML::AttributeNames::height,
|
|
HTML::AttributeNames::hspace,
|
|
HTML::AttributeNames::vspace,
|
|
HTML::AttributeNames::width);
|
|
}
|
|
|
|
void HTMLObjectElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
|
|
{
|
|
for_each_attribute([&](auto& name, auto& value) {
|
|
if (name == HTML::AttributeNames::align) {
|
|
if (value.equals_ignoring_ascii_case("center"sv))
|
|
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::TextAlign, CSS::CSSKeywordValue::create(CSS::Keyword::Center));
|
|
else if (value.equals_ignoring_ascii_case("middle"sv))
|
|
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::TextAlign, CSS::CSSKeywordValue::create(CSS::Keyword::Middle));
|
|
} else if (name == HTML::AttributeNames::border) {
|
|
if (auto parsed_value = parse_non_negative_integer(value); parsed_value.has_value()) {
|
|
auto width_style_value = CSS::LengthStyleValue::create(CSS::Length::make_px(*parsed_value));
|
|
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BorderTopWidth, width_style_value);
|
|
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BorderRightWidth, width_style_value);
|
|
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BorderBottomWidth, width_style_value);
|
|
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BorderLeftWidth, width_style_value);
|
|
|
|
auto border_style_value = CSS::CSSKeywordValue::create(CSS::Keyword::Solid);
|
|
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BorderTopStyle, border_style_value);
|
|
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BorderRightStyle, border_style_value);
|
|
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BorderBottomStyle, border_style_value);
|
|
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BorderLeftStyle, border_style_value);
|
|
}
|
|
}
|
|
// https://html.spec.whatwg.org/multipage/rendering.html#attributes-for-embedded-content-and-images:maps-to-the-dimension-property-3
|
|
else if (name == HTML::AttributeNames::height) {
|
|
if (auto parsed_value = parse_dimension_value(value)) {
|
|
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::Height, *parsed_value);
|
|
}
|
|
}
|
|
// https://html.spec.whatwg.org/multipage/rendering.html#attributes-for-embedded-content-and-images:maps-to-the-dimension-property
|
|
else if (name == HTML::AttributeNames::hspace) {
|
|
if (auto parsed_value = parse_dimension_value(value)) {
|
|
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::MarginLeft, *parsed_value);
|
|
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::MarginRight, *parsed_value);
|
|
}
|
|
} else if (name == HTML::AttributeNames::vspace) {
|
|
if (auto parsed_value = parse_dimension_value(value)) {
|
|
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::MarginTop, *parsed_value);
|
|
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::MarginBottom, *parsed_value);
|
|
}
|
|
} else if (name == HTML::AttributeNames::width) {
|
|
if (auto parsed_value = parse_dimension_value(value)) {
|
|
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::Width, *parsed_value);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/iframe-embed-object.html#attr-object-data
|
|
String HTMLObjectElement::data() const
|
|
{
|
|
auto data = get_attribute(HTML::AttributeNames::data);
|
|
if (!data.has_value())
|
|
return {};
|
|
|
|
return document().encoding_parse_url(*data).to_string();
|
|
}
|
|
|
|
GC::Ptr<Layout::Node> HTMLObjectElement::create_layout_node(GC::Ref<CSS::ComputedProperties> style)
|
|
{
|
|
switch (m_representation) {
|
|
case Representation::Children:
|
|
return NavigableContainer::create_layout_node(move(style));
|
|
case Representation::ContentNavigable:
|
|
return heap().allocate<Layout::NavigableContainerViewport>(document(), *this, move(style));
|
|
case Representation::Image:
|
|
if (image_data())
|
|
return heap().allocate<Layout::ImageBox>(document(), *this, move(style), *this);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void HTMLObjectElement::adjust_computed_style(CSS::ComputedProperties& style)
|
|
{
|
|
// https://drafts.csswg.org/css-display-3/#unbox
|
|
if (style.display().is_contents())
|
|
style.set_property(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::None)));
|
|
}
|
|
|
|
bool HTMLObjectElement::has_ancestor_media_element_or_object_element_not_showing_fallback_content() const
|
|
{
|
|
for (auto const* ancestor = parent(); ancestor; ancestor = ancestor->parent()) {
|
|
if (is<HTMLMediaElement>(*ancestor))
|
|
return true;
|
|
|
|
if (is<HTMLObjectElement>(*ancestor)) {
|
|
auto& ancestor_object = static_cast<HTMLObjectElement const&>(*ancestor);
|
|
if (ancestor_object.m_representation != Representation::Children)
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-object-element:queue-an-element-task
|
|
void HTMLObjectElement::queue_element_task_to_run_object_representation_steps()
|
|
{
|
|
// AD-HOC: If the document isn't fully active, this task will never run, and we will indefinitely delay the load event.
|
|
if (!document().is_fully_active())
|
|
return;
|
|
|
|
// This task being queued or actively running must delay the load event of the element's node document.
|
|
m_document_load_event_delayer_for_object_representation_task.emplace(document());
|
|
|
|
queue_an_element_task(HTML::Task::Source::DOMManipulation, [this]() {
|
|
ScopeGuard guard { [&]() { m_document_load_event_delayer_for_object_representation_task.clear(); } };
|
|
|
|
auto& realm = this->realm();
|
|
auto& vm = realm.vm();
|
|
|
|
// FIXME: 1. If the user has indicated a preference that this object element's fallback content be shown instead of the
|
|
// element's usual behavior, then jump to the step below labeled fallback.
|
|
|
|
// 2. If the element has an ancestor media element, or has an ancestor object element that is not showing its
|
|
// fallback content, or if the element is not in a document whose browsing context is non-null, or if the
|
|
// element's node document is not fully active, or if the element is still in the stack of open elements of
|
|
// an HTML parser or XML parser, or if the element is not being rendered, then jump to the step below labeled
|
|
// fallback.
|
|
// FIXME: Handle the element being in the stack of open elements.
|
|
// FIXME: Handle the element not being rendered.
|
|
if (!document().browsing_context() || !document().is_fully_active()) {
|
|
run_object_representation_fallback_steps();
|
|
return;
|
|
}
|
|
if (has_ancestor_media_element_or_object_element_not_showing_fallback_content()) {
|
|
run_object_representation_fallback_steps();
|
|
return;
|
|
}
|
|
|
|
// 3. If the data attribute is present and its value is not the empty string, then:
|
|
if (auto data = get_attribute(HTML::AttributeNames::data); data.has_value() && !data->is_empty()) {
|
|
// 1. If the type attribute is present and its value is not a type that the user agent supports, then the user
|
|
// agent may jump to the step below labeled fallback without fetching the content to examine its real type.
|
|
|
|
// 2. Let url be the result of encoding-parsing a URL given the data attribute's value, relative to the element's node document.
|
|
auto url = document().encoding_parse_url(*data);
|
|
|
|
// 3. If url is failure, then fire an event named error at the element and jump to the step below labeled fallback.
|
|
if (!url.is_valid()) {
|
|
dispatch_event(DOM::Event::create(realm, HTML::EventNames::error));
|
|
run_object_representation_fallback_steps();
|
|
return;
|
|
}
|
|
|
|
// 4. Let request be a new request whose URL is url, client is the element's node document's relevant settings
|
|
// object, destination is "object", credentials mode is "include", mode is "navigate", initiator type is
|
|
// "object", and whose use-URL-credentials flag is set.
|
|
auto request = Fetch::Infrastructure::Request::create(vm);
|
|
request->set_url(move(url));
|
|
request->set_client(&document().relevant_settings_object());
|
|
request->set_destination(Fetch::Infrastructure::Request::Destination::Object);
|
|
request->set_credentials_mode(Fetch::Infrastructure::Request::CredentialsMode::Include);
|
|
request->set_mode(Fetch::Infrastructure::Request::Mode::Navigate);
|
|
request->set_initiator_type(Fetch::Infrastructure::Request::InitiatorType::Object);
|
|
request->set_use_url_credentials(true);
|
|
|
|
Fetch::Infrastructure::FetchAlgorithms::Input fetch_algorithms_input {};
|
|
fetch_algorithms_input.process_response = [this](GC::Ref<Fetch::Infrastructure::Response> response) {
|
|
auto& realm = this->realm();
|
|
auto& global = document().realm().global_object();
|
|
|
|
if (response->is_network_error()) {
|
|
resource_did_fail();
|
|
return;
|
|
}
|
|
|
|
if (response->type() == Fetch::Infrastructure::Response::Type::Opaque || response->type() == Fetch::Infrastructure::Response::Type::OpaqueRedirect) {
|
|
auto& filtered_response = static_cast<Fetch::Infrastructure::FilteredResponse&>(*response);
|
|
response = filtered_response.internal_response();
|
|
}
|
|
|
|
auto on_data_read = GC::create_function(realm.heap(), [this, response](ByteBuffer data) {
|
|
resource_did_load(response, data);
|
|
});
|
|
auto on_error = GC::create_function(realm.heap(), [this](JS::Value) {
|
|
resource_did_fail();
|
|
});
|
|
|
|
VERIFY(response->body());
|
|
response->body()->fully_read(realm, on_data_read, on_error, GC::Ref { global });
|
|
};
|
|
|
|
// 5. Fetch request.
|
|
auto result = Fetch::Fetching::fetch(realm, request, Fetch::Infrastructure::FetchAlgorithms::create(vm, move(fetch_algorithms_input)));
|
|
if (result.is_error()) {
|
|
resource_did_fail();
|
|
return;
|
|
}
|
|
|
|
// Fetching the resource must delay the load event of the element's node document until the task that is
|
|
// queued by the networking task source once the resource has been fetched (defined next) has been run.
|
|
m_document_load_event_delayer_for_resource_load.emplace(document());
|
|
|
|
// 6. If the resource is not yet available (e.g. because the resource was not available in the cache, so that
|
|
// loading the resource required making a request over the network), then jump to the step below labeled
|
|
// fallback. The task that is queued by the networking task source once the resource is available must
|
|
// restart this algorithm from this step. Resources can load incrementally; user agents may opt to consider
|
|
// a resource "available" whenever enough data has been obtained to begin processing the resource.
|
|
|
|
// NOTE: The request is always asynchronous, even if it is cached or succeeded/failed immediately. Allow the
|
|
// response callback to invoke the fallback steps. This prevents the fallback layout from flashing very
|
|
// briefly between here and the resource loading.
|
|
}
|
|
});
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-object-element:concept-event-fire-2
|
|
void HTMLObjectElement::resource_did_fail()
|
|
{
|
|
ScopeGuard guard { [&]() { m_document_load_event_delayer_for_resource_load.clear(); } };
|
|
|
|
// 3.7. If the load failed (e.g. there was an HTTP 404 error, there was a DNS error), fire an event named error at
|
|
// the element, then jump to the step below labeled fallback.
|
|
dispatch_event(DOM::Event::create(realm(), HTML::EventNames::error));
|
|
run_object_representation_fallback_steps();
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/iframe-embed-object.html#object-type-detection
|
|
void HTMLObjectElement::resource_did_load(Fetch::Infrastructure::Response const& response, ReadonlyBytes data)
|
|
{
|
|
ScopeGuard guard { [&]() { m_document_load_event_delayer_for_resource_load.clear(); } };
|
|
|
|
// 3.8. Determine the resource type, as follows:
|
|
|
|
// 1. Let the resource type be unknown.
|
|
Optional<MimeSniff::MimeType> resource_type;
|
|
|
|
// FIXME: 3. If the user agent is configured to strictly obey Content-Type headers for this resource, and the resource has
|
|
// associated Content-Type metadata, then let the resource type be the type specified in the resource's Content-Type
|
|
// metadata, and jump to the step below labeled handler.
|
|
|
|
// 3. Run the appropriate set of steps from the following list:
|
|
// -> If the resource has associated Content-Type metadata
|
|
if (auto content_type = response.header_list()->extract_mime_type(); content_type.has_value()) {
|
|
// 1. Let binary be false.
|
|
bool binary = false;
|
|
|
|
// 2. If the type specified in the resource's Content-Type metadata is "text/plain", and the result of applying
|
|
// the rules for distinguishing if a resource is text or binary to the resource is that the resource is not
|
|
// text/plain, then set binary to true.
|
|
if (content_type->essence() == "text/plain"sv) {
|
|
auto computed_type = MimeSniff::Resource::sniff(
|
|
data,
|
|
MimeSniff::SniffingConfiguration {
|
|
.sniffing_context = MimeSniff::SniffingContext::TextOrBinary,
|
|
.supplied_type = content_type,
|
|
});
|
|
|
|
if (computed_type.essence() != "text/plain"sv)
|
|
binary = true;
|
|
}
|
|
|
|
// 3. If the type specified in the resource's Content-Type metadata is "application/octet-stream", then set binary to true.
|
|
else if (content_type->essence() == "application/octet-stream"sv) {
|
|
binary = true;
|
|
}
|
|
|
|
// 4. If binary is false, then let the resource type be the type specified in the resource's Content-Type metadata,
|
|
// and jump to the step below labeled handler.
|
|
if (!binary) {
|
|
resource_type = move(content_type);
|
|
}
|
|
|
|
// 5. If there is a type attribute present on the object element, and its value is not application/octet-stream,
|
|
// then run the following steps:
|
|
else if (auto type = this->type(); !type.is_empty() && (type != "application/octet-stream"sv)) {
|
|
// 1. If the attribute's value is a type that starts with "image/" that is not also an XML MIME type, then
|
|
// let the resource type be the type specified in that type attribute.
|
|
if (type.starts_with_bytes("image/"sv)) {
|
|
auto parsed_type = MimeSniff::MimeType::parse(type);
|
|
|
|
if (parsed_type.has_value() && !parsed_type->is_xml())
|
|
resource_type = move(parsed_type);
|
|
}
|
|
|
|
// 2. Jump to the step below labeled handler.
|
|
}
|
|
}
|
|
// -> Otherwise, if the resource does not have associated Content-Type metadata
|
|
else {
|
|
Optional<MimeSniff::MimeType> tentative_type;
|
|
|
|
// 1. If there is a type attribute present on the object element, then let the tentative type be the type specified in that type attribute.
|
|
// Otherwise, let tentative type be the computed type of the resource.
|
|
if (auto type = this->type(); !type.is_empty())
|
|
tentative_type = MimeSniff::MimeType::parse(type);
|
|
else
|
|
tentative_type = MimeSniff::Resource::sniff(data);
|
|
|
|
// 2. If tentative type is not application/octet-stream, then let resource type be tentative type and jump to the
|
|
// step below labeled handler.
|
|
if (tentative_type.has_value() && tentative_type->essence() != "application/octet-stream"sv)
|
|
resource_type = move(tentative_type);
|
|
}
|
|
|
|
if (resource_type.has_value())
|
|
run_object_representation_handler_steps(response, *resource_type, data);
|
|
else
|
|
run_object_representation_fallback_steps();
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-object-element:plugin-11
|
|
void HTMLObjectElement::run_object_representation_handler_steps(Fetch::Infrastructure::Response const& response, MimeSniff::MimeType const& resource_type, ReadonlyBytes data)
|
|
{
|
|
// 3.9. Handler: Handle the content as given by the first of the following cases that matches:
|
|
|
|
// -> If the resource type is an XML MIME type, or if the resource type does not start with "image/"
|
|
if (can_load_document_with_type(resource_type) && (resource_type.is_xml() || !resource_type.is_image())) {
|
|
// If the object element's content navigable is null, then create a new child navigable for the element.
|
|
if (!m_content_navigable && in_a_document_tree()) {
|
|
MUST(create_new_child_navigable());
|
|
set_content_navigable_initialized();
|
|
}
|
|
|
|
// NOTE: Creating a new nested browsing context can fail if the document is not attached to a browsing context
|
|
if (!m_content_navigable)
|
|
return;
|
|
|
|
// Let response be the response from fetch.
|
|
|
|
// If response's URL does not match about:blank, then navigate the element's content navigable to response's URL
|
|
// using the element's node document, with historyHandling set to "replace".
|
|
if (response.url().has_value() && !url_matches_about_blank(*response.url())) {
|
|
MUST(m_content_navigable->navigate({
|
|
.url = *response.url(),
|
|
.source_document = document(),
|
|
.history_handling = Bindings::NavigationHistoryBehavior::Replace,
|
|
}));
|
|
}
|
|
|
|
// The object element represents its content navigable.
|
|
run_object_representation_completed_steps(Representation::ContentNavigable);
|
|
}
|
|
|
|
// -> If the resource type starts with "image/", and support for images has not been disabled
|
|
// FIXME: Handle disabling image support.
|
|
else if (resource_type.is_image()) {
|
|
// Destroy a child navigable given the object element.
|
|
destroy_the_child_navigable();
|
|
|
|
// Apply the image sniffing rules to determine the type of the image.
|
|
// The object element represents the specified image.
|
|
// If the image cannot be rendered, e.g. because it is malformed or in an unsupported format, jump to the step
|
|
// below labeled fallback.
|
|
if (data.is_empty()) {
|
|
run_object_representation_fallback_steps();
|
|
return;
|
|
}
|
|
|
|
load_image();
|
|
}
|
|
|
|
// -> Otherwise
|
|
else {
|
|
// The given resource type is not supported. Jump to the step below labeled fallback.
|
|
run_object_representation_fallback_steps();
|
|
}
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-object-element:the-object-element-19
|
|
void HTMLObjectElement::run_object_representation_completed_steps(Representation representation)
|
|
{
|
|
// 3.10. The element's contents are not part of what the object element represents.
|
|
|
|
// 3.11. If the object element does not represent its content navigable, then once the resource is completely loaded,
|
|
// queue an element task on the DOM manipulation task source given the object element to fire an event named
|
|
// load at the element.
|
|
if (representation != Representation::ContentNavigable) {
|
|
queue_an_element_task(HTML::Task::Source::DOMManipulation, [&]() {
|
|
dispatch_event(DOM::Event::create(realm(), HTML::EventNames::load));
|
|
});
|
|
}
|
|
|
|
update_layout_and_child_objects(representation);
|
|
|
|
// 3.12. Return.
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-object-element:the-object-element-23
|
|
void HTMLObjectElement::run_object_representation_fallback_steps()
|
|
{
|
|
// 4. Fallback: The object element represents the element's children. This is the element's fallback content.
|
|
// Destroy a child navigable given the element.
|
|
destroy_the_child_navigable();
|
|
|
|
update_layout_and_child_objects(Representation::Children);
|
|
}
|
|
|
|
void HTMLObjectElement::load_image()
|
|
{
|
|
// FIXME: This currently reloads the image instead of reusing the resource we've already downloaded.
|
|
auto data = get_attribute_value(HTML::AttributeNames::data);
|
|
auto url = document().encoding_parse_url(data);
|
|
m_resource_request = HTML::SharedResourceRequest::get_or_create(realm(), document().page(), url);
|
|
m_resource_request->add_callbacks(
|
|
[this] {
|
|
run_object_representation_completed_steps(Representation::Image);
|
|
},
|
|
[this] {
|
|
run_object_representation_fallback_steps();
|
|
});
|
|
|
|
if (m_resource_request->needs_fetching()) {
|
|
auto request = HTML::create_potential_CORS_request(vm(), url, Fetch::Infrastructure::Request::Destination::Image, HTML::CORSSettingAttribute::NoCORS);
|
|
request->set_client(&document().relevant_settings_object());
|
|
m_resource_request->fetch_resource(realm(), request);
|
|
}
|
|
}
|
|
|
|
void HTMLObjectElement::update_layout_and_child_objects(Representation representation)
|
|
{
|
|
if (representation == Representation::Children) {
|
|
for_each_child_of_type<HTMLObjectElement>([](auto& object) {
|
|
object.queue_element_task_to_run_object_representation_steps();
|
|
return IterationDecision::Continue;
|
|
});
|
|
}
|
|
|
|
m_representation = representation;
|
|
invalidate_style(DOM::StyleInvalidationReason::HTMLObjectElementUpdateLayoutAndChildObjects);
|
|
document().invalidate_layout_tree();
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/interaction.html#dom-tabindex
|
|
i32 HTMLObjectElement::default_tab_index_value() const
|
|
{
|
|
// See the base function for the spec comments.
|
|
return 0;
|
|
}
|
|
|
|
GC::Ptr<DecodedImageData> HTMLObjectElement::image_data() const
|
|
{
|
|
if (!m_resource_request)
|
|
return nullptr;
|
|
return m_resource_request->image_data();
|
|
}
|
|
|
|
bool HTMLObjectElement::is_image_available() const
|
|
{
|
|
return image_data() != nullptr;
|
|
}
|
|
|
|
Optional<CSSPixels> HTMLObjectElement::intrinsic_width() const
|
|
{
|
|
if (auto image_data = this->image_data())
|
|
return image_data->intrinsic_width();
|
|
return {};
|
|
}
|
|
|
|
Optional<CSSPixels> HTMLObjectElement::intrinsic_height() const
|
|
{
|
|
if (auto image_data = this->image_data())
|
|
return image_data->intrinsic_height();
|
|
return {};
|
|
}
|
|
|
|
Optional<CSSPixelFraction> HTMLObjectElement::intrinsic_aspect_ratio() const
|
|
{
|
|
if (auto image_data = this->image_data())
|
|
return image_data->intrinsic_aspect_ratio();
|
|
return {};
|
|
}
|
|
|
|
RefPtr<Gfx::ImmutableBitmap> HTMLObjectElement::current_image_bitmap(Gfx::IntSize size) const
|
|
{
|
|
if (auto image_data = this->image_data())
|
|
return image_data->bitmap(0, size);
|
|
return nullptr;
|
|
}
|
|
|
|
void HTMLObjectElement::set_visible_in_viewport(bool)
|
|
{
|
|
// FIXME: Loosen grip on image data when it's not visible, e.g via volatile memory.
|
|
}
|
|
|
|
}
|