Serenity Operating System
at master 217 lines 8.3 kB view raw
1/* 2 * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <LibGfx/Bitmap.h> 8#include <LibWeb/ARIA/Roles.h> 9#include <LibWeb/CSS/Parser/Parser.h> 10#include <LibWeb/CSS/StyleComputer.h> 11#include <LibWeb/DOM/Document.h> 12#include <LibWeb/DOM/Event.h> 13#include <LibWeb/HTML/EventNames.h> 14#include <LibWeb/HTML/HTMLImageElement.h> 15#include <LibWeb/HTML/Parser/HTMLParser.h> 16#include <LibWeb/Layout/ImageBox.h> 17#include <LibWeb/Loader/ResourceLoader.h> 18#include <LibWeb/Painting/PaintableBox.h> 19 20namespace Web::HTML { 21 22HTMLImageElement::HTMLImageElement(DOM::Document& document, DOM::QualifiedName qualified_name) 23 : HTMLElement(document, move(qualified_name)) 24 , m_image_loader(*this) 25{ 26 m_image_loader.on_load = [this] { 27 set_needs_style_update(true); 28 this->document().set_needs_layout(); 29 queue_an_element_task(HTML::Task::Source::DOMManipulation, [this] { 30 dispatch_event(DOM::Event::create(this->realm(), EventNames::load).release_value_but_fixme_should_propagate_errors()); 31 }); 32 }; 33 34 m_image_loader.on_fail = [this] { 35 dbgln("HTMLImageElement: Resource did fail: {}", src()); 36 set_needs_style_update(true); 37 this->document().set_needs_layout(); 38 queue_an_element_task(HTML::Task::Source::DOMManipulation, [this] { 39 dispatch_event(DOM::Event::create(this->realm(), EventNames::error).release_value_but_fixme_should_propagate_errors()); 40 }); 41 }; 42 43 m_image_loader.on_animate = [this] { 44 if (layout_node()) 45 layout_node()->set_needs_display(); 46 }; 47} 48 49HTMLImageElement::~HTMLImageElement() = default; 50 51JS::ThrowCompletionOr<void> HTMLImageElement::initialize(JS::Realm& realm) 52{ 53 MUST_OR_THROW_OOM(Base::initialize(realm)); 54 set_prototype(&Bindings::ensure_web_prototype<Bindings::HTMLImageElementPrototype>(realm, "HTMLImageElement")); 55 56 return {}; 57} 58 59void HTMLImageElement::apply_presentational_hints(CSS::StyleProperties& style) const 60{ 61 for_each_attribute([&](auto& name, auto& value) { 62 if (name == HTML::AttributeNames::width) { 63 if (auto parsed_value = parse_dimension_value(value)) 64 style.set_property(CSS::PropertyID::Width, parsed_value.release_nonnull()); 65 } else if (name == HTML::AttributeNames::height) { 66 if (auto parsed_value = parse_dimension_value(value)) 67 style.set_property(CSS::PropertyID::Height, parsed_value.release_nonnull()); 68 } else if (name == HTML::AttributeNames::hspace) { 69 if (auto parsed_value = parse_dimension_value(value)) { 70 style.set_property(CSS::PropertyID::MarginLeft, *parsed_value); 71 style.set_property(CSS::PropertyID::MarginRight, *parsed_value); 72 } 73 } else if (name == HTML::AttributeNames::vspace) { 74 if (auto parsed_value = parse_dimension_value(value)) { 75 style.set_property(CSS::PropertyID::MarginTop, *parsed_value); 76 style.set_property(CSS::PropertyID::MarginBottom, *parsed_value); 77 } 78 } 79 }); 80} 81 82void HTMLImageElement::parse_attribute(DeprecatedFlyString const& name, DeprecatedString const& value) 83{ 84 HTMLElement::parse_attribute(name, value); 85 86 if (name == HTML::AttributeNames::src && !value.is_empty()) 87 m_image_loader.load(document().parse_url(value)); 88 89 if (name == HTML::AttributeNames::alt) { 90 if (layout_node()) 91 verify_cast<Layout::ImageBox>(*layout_node()).dom_node_did_update_alt_text({}); 92 } 93} 94 95JS::GCPtr<Layout::Node> HTMLImageElement::create_layout_node(NonnullRefPtr<CSS::StyleProperties> style) 96{ 97 return heap().allocate_without_realm<Layout::ImageBox>(document(), *this, move(style), m_image_loader); 98} 99 100Gfx::Bitmap const* HTMLImageElement::bitmap() const 101{ 102 return m_image_loader.bitmap(m_image_loader.current_frame_index()); 103} 104 105// https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-width 106unsigned HTMLImageElement::width() const 107{ 108 const_cast<DOM::Document&>(document()).update_layout(); 109 110 // Return the rendered width of the image, in CSS pixels, if the image is being rendered. 111 if (auto* paint_box = this->paint_box()) 112 return paint_box->content_width().value(); 113 114 // NOTE: This step seems to not be in the spec, but all browsers do it. 115 auto width_attr = get_attribute(HTML::AttributeNames::width); 116 if (auto converted = width_attr.to_uint(); converted.has_value()) 117 return *converted; 118 119 // ...or else the density-corrected intrinsic width and height of the image, in CSS pixels, 120 // if the image has intrinsic dimensions and is available but not being rendered. 121 if (m_image_loader.has_image()) 122 return m_image_loader.width(); 123 124 // ...or else 0, if the image is not available or does not have intrinsic dimensions. 125 return 0; 126} 127 128void HTMLImageElement::set_width(unsigned width) 129{ 130 MUST(set_attribute(HTML::AttributeNames::width, DeprecatedString::number(width))); 131} 132 133// https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-height 134unsigned HTMLImageElement::height() const 135{ 136 const_cast<DOM::Document&>(document()).update_layout(); 137 138 // Return the rendered height of the image, in CSS pixels, if the image is being rendered. 139 if (auto* paint_box = this->paint_box()) 140 return paint_box->content_height().value(); 141 142 // NOTE: This step seems to not be in the spec, but all browsers do it. 143 auto height_attr = get_attribute(HTML::AttributeNames::height); 144 if (auto converted = height_attr.to_uint(); converted.has_value()) 145 return *converted; 146 147 // ...or else the density-corrected intrinsic height and height of the image, in CSS pixels, 148 // if the image has intrinsic dimensions and is available but not being rendered. 149 if (m_image_loader.has_image()) 150 return m_image_loader.height(); 151 152 // ...or else 0, if the image is not available or does not have intrinsic dimensions. 153 return 0; 154} 155 156void HTMLImageElement::set_height(unsigned height) 157{ 158 MUST(set_attribute(HTML::AttributeNames::height, DeprecatedString::number(height))); 159} 160 161// https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-naturalwidth 162unsigned HTMLImageElement::natural_width() const 163{ 164 // Return the density-corrected intrinsic width of the image, in CSS pixels, 165 // if the image has intrinsic dimensions and is available. 166 if (m_image_loader.has_image()) 167 return m_image_loader.width(); 168 169 // ...or else 0. 170 return 0; 171} 172 173// https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-naturalheight 174unsigned HTMLImageElement::natural_height() const 175{ 176 // Return the density-corrected intrinsic height of the image, in CSS pixels, 177 // if the image has intrinsic dimensions and is available. 178 if (m_image_loader.has_image()) 179 return m_image_loader.height(); 180 181 // ...or else 0. 182 return 0; 183} 184 185// https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-complete 186bool HTMLImageElement::complete() const 187{ 188 // The IDL attribute complete must return true if any of the following conditions is true: 189 190 // - Both the src attribute and the srcset attribute are omitted. 191 if (!has_attribute(HTML::AttributeNames::src) && !has_attribute(HTML::AttributeNames::srcset)) 192 return true; 193 194 // - The srcset attribute is omitted and the src attribute's value is the empty string. 195 if (!has_attribute(HTML::AttributeNames::srcset) && attribute(HTML::AttributeNames::src) == ""sv) 196 return true; 197 198 // - The img element's current request's state is completely available and its pending request is null. 199 // - The img element's current request's state is broken and its pending request is null. 200 // FIXME: This is ad-hoc and should be updated once we are loading images via the Fetch mechanism. 201 if (m_image_loader.has_loaded_or_failed()) 202 return true; 203 204 return false; 205} 206 207Optional<ARIA::Role> HTMLImageElement::default_role() const 208{ 209 // https://www.w3.org/TR/html-aria/#el-img 210 // https://www.w3.org/TR/html-aria/#el-img-no-alt 211 if (alt().is_null() || !alt().is_empty()) 212 return ARIA::Role::img; 213 // https://www.w3.org/TR/html-aria/#el-img-empty-alt 214 return ARIA::Role::presentation; 215} 216 217}