Serenity Operating System
at master 353 lines 21 kB view raw
1/* 2 * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <LibGfx/Bitmap.h> 8#include <LibWeb/CSS/StyleComputer.h> 9#include <LibWeb/DOM/Document.h> 10#include <LibWeb/DOM/Event.h> 11#include <LibWeb/HTML/HTMLMediaElement.h> 12#include <LibWeb/HTML/HTMLObjectElement.h> 13#include <LibWeb/Layout/ImageBox.h> 14#include <LibWeb/Loader/ResourceLoader.h> 15#include <LibWeb/MimeSniff/MimeType.h> 16 17namespace Web::HTML { 18 19HTMLObjectElement::HTMLObjectElement(DOM::Document& document, DOM::QualifiedName qualified_name) 20 : BrowsingContextContainer(document, move(qualified_name)) 21{ 22 // https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-object-element 23 // Whenever one of the following conditions occur: 24 // - the element is created, 25 // ...the user agent must queue an element task on the DOM manipulation task source given 26 // the object element to run the following steps to (re)determine what the object element represents. 27 // This task being queued or actively running must delay the load event of the element's node document. 28 queue_element_task_to_run_object_representation_steps(); 29} 30 31HTMLObjectElement::~HTMLObjectElement() = default; 32 33JS::ThrowCompletionOr<void> HTMLObjectElement::initialize(JS::Realm& realm) 34{ 35 MUST_OR_THROW_OOM(Base::initialize(realm)); 36 set_prototype(&Bindings::ensure_web_prototype<Bindings::HTMLObjectElementPrototype>(realm, "HTMLObjectElement")); 37 38 return {}; 39} 40 41void HTMLObjectElement::parse_attribute(DeprecatedFlyString const& name, DeprecatedString const& value) 42{ 43 BrowsingContextContainer::parse_attribute(name, value); 44 45 // https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-object-element 46 // Whenever one of the following conditions occur: 47 if ( 48 // - the element's classid attribute is set, changed, or removed, 49 (name == HTML::AttributeNames::classid) || 50 // - the element's classid attribute is not present, and its data attribute is set, changed, or removed, 51 (!has_attribute(HTML::AttributeNames::classid) && name == HTML::AttributeNames::data) || 52 // - neither the element's classid attribute nor its data attribute are present, and its type attribute is set, changed, or removed, 53 (!has_attribute(HTML::AttributeNames::classid) && !has_attribute(HTML::AttributeNames::data) && name == HTML::AttributeNames::type)) { 54 55 // ...the user agent must queue an element task on the DOM manipulation task source given 56 // the object element to run the following steps to (re)determine what the object element represents. 57 // This task being queued or actively running must delay the load event of the element's node document. 58 queue_element_task_to_run_object_representation_steps(); 59 } 60} 61 62// https://html.spec.whatwg.org/multipage/iframe-embed-object.html#attr-object-data 63DeprecatedString HTMLObjectElement::data() const 64{ 65 auto data = attribute(HTML::AttributeNames::data); 66 return document().parse_url(data).to_deprecated_string(); 67} 68 69JS::GCPtr<Layout::Node> HTMLObjectElement::create_layout_node(NonnullRefPtr<CSS::StyleProperties> style) 70{ 71 switch (m_representation) { 72 case Representation::Children: 73 return BrowsingContextContainer::create_layout_node(move(style)); 74 case Representation::NestedBrowsingContext: 75 // FIXME: Actually paint the nested browsing context's document, similar to how iframes are painted with FrameBox and NestedBrowsingContextPaintable. 76 return nullptr; 77 case Representation::Image: 78 if (m_image_loader.has_value() && m_image_loader->has_image()) 79 return heap().allocate_without_realm<Layout::ImageBox>(document(), *this, move(style), *m_image_loader); 80 break; 81 default: 82 break; 83 } 84 85 return nullptr; 86} 87 88bool HTMLObjectElement::has_ancestor_media_element_or_object_element_not_showing_fallback_content() const 89{ 90 for (auto const* ancestor = parent(); ancestor; ancestor = ancestor->parent()) { 91 if (is<HTMLMediaElement>(*ancestor)) 92 return true; 93 94 if (is<HTMLObjectElement>(*ancestor)) { 95 auto& ancestor_object = static_cast<HTMLObjectElement const&>(*ancestor); 96 if (ancestor_object.m_representation != Representation::Children) 97 return true; 98 } 99 } 100 101 return false; 102} 103 104// https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-object-element:queue-an-element-task 105void HTMLObjectElement::queue_element_task_to_run_object_representation_steps() 106{ 107 queue_an_element_task(HTML::Task::Source::DOMManipulation, [&]() { 108 // 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. 109 110 // 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. 111 if (!document().browsing_context() || !document().is_fully_active()) 112 return run_object_representation_fallback_steps(); 113 if (has_ancestor_media_element_or_object_element_not_showing_fallback_content()) 114 return run_object_representation_fallback_steps(); 115 116 // FIXME: 3. If the classid attribute is present, and has a value that isn't the empty string, then: if the user agent can find a plugin suitable according to the value of the classid attribute, and plugins aren't being sandboxed, then that plugin should be used, and the value of the data attribute, if any, should be passed to the plugin. If no suitable plugin can be found, or if the plugin reports an error, jump to the step below labeled fallback. 117 118 // 4. If the data attribute is present and its value is not the empty string, then: 119 if (auto data = attribute(HTML::AttributeNames::data); !data.is_empty()) { 120 // 1. If the type attribute is present and its value is not a type that the user agent supports, and is not a type that the user agent can find a plugin for, then the user agent may jump to the step below labeled fallback without fetching the content to examine its real type. 121 122 // 2. Parse a URL given the data attribute, relative to the element's node document. 123 auto url = document().parse_url(data); 124 125 // 3. If that failed, fire an event named error at the element, then jump to the step below labeled fallback. 126 if (!url.is_valid()) { 127 dispatch_event(DOM::Event::create(realm(), HTML::EventNames::error).release_value_but_fixme_should_propagate_errors()); 128 return run_object_representation_fallback_steps(); 129 } 130 131 // 4. Let request be a new request whose URL is the resulting URL record, client is the element's node document's relevant settings object, destination is "object", credentials mode is "include", mode is "navigate", and whose use-URL-credentials flag is set. 132 auto request = LoadRequest::create_for_url_on_page(url, document().page()); 133 134 // 5. Fetch request, with processResponseEndOfBody given response res set to finalize and report timing with res, the element's node document's relevant global object, and "object". 135 // 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. 136 set_resource(ResourceLoader::the().load_resource(Resource::Type::Generic, request)); 137 138 // 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. 139 140 // NOTE: The request is always asynchronous, even if it is cached or succeeded/failed immediately. Allow the callbacks below to invoke 141 // the fallback steps. This prevents the fallback layout from flashing very briefly between here and the resource loading. 142 return; 143 } 144 145 // 5. If the data attribute is absent but the type attribute is present, and the user agent can find a plugin suitable according to the value of the type attribute, and plugins aren't being sandboxed, then that plugin should be used. If these conditions cannot be met, or if the plugin reports an error, jump to the step below labeled fallback. Otherwise return; once the plugin 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. 146 run_object_representation_fallback_steps(); 147 }); 148} 149 150// https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-object-element:concept-event-fire-2 151void HTMLObjectElement::resource_did_fail() 152{ 153 // 4.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. 154 dispatch_event(DOM::Event::create(realm(), HTML::EventNames::error).release_value_but_fixme_should_propagate_errors()); 155 run_object_representation_fallback_steps(); 156} 157 158// https://html.spec.whatwg.org/multipage/iframe-embed-object.html#object-type-detection 159void HTMLObjectElement::resource_did_load() 160{ 161 // 4.8. Determine the resource type, as follows: 162 163 // 1. Let the resource type be unknown. 164 Optional<DeprecatedString> resource_type; 165 166 // FIXME: 2. 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. 167 // FIXME: 3. If there is a type attribute present on the object element, and that attribute's value is not a type that the user agent supports, but it is a type that a plugin supports, then let the resource type be the type specified in that type attribute, and jump to the step below labeled handler. 168 169 // 4. Run the appropriate set of steps from the following list: 170 // * If the resource has associated Content-Type metadata 171 if (auto it = resource()->response_headers().find("Content-Type"sv); it != resource()->response_headers().end()) { 172 // 1. Let binary be false. 173 bool binary = false; 174 175 // FIXME: 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. 176 177 // 3. If the type specified in the resource's Content-Type metadata is "application/octet-stream", then set binary to true. 178 if (it->value == "application/octet-stream"sv) 179 binary = true; 180 181 // 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. 182 if (!binary) 183 return run_object_representation_handler_steps(it->value); 184 185 // 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: 186 if (auto type = this->type(); !type.is_empty() && (type != "application/octet-stream"sv)) { 187 // 1. If the attribute's value is a type that a plugin supports, or 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. 188 // FIXME: This only partially implements this step. 189 if (type.starts_with("image/"sv)) 190 resource_type = move(type); 191 192 // 2. Jump to the step below labeled handler. 193 } 194 } 195 // * Otherwise, if the resource does not have associated Content-Type metadata 196 else { 197 Optional<DeprecatedString> tentative_type; 198 199 // 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. 200 // Otherwise, let tentative type be the computed type of the resource. 201 if (auto type = this->type(); !type.is_empty()) 202 tentative_type = move(type); 203 204 // FIXME: For now, ignore application/ MIME types as we cannot render yet them anyways. We will need to implement the MIME type sniffing 205 // algorithm in order to map all unknown MIME types to "application/octet-stream". 206 else if (auto type = resource()->mime_type(); !type.starts_with("application/"sv)) 207 tentative_type = move(type); 208 209 // 2. If tentative type is not application/octet-stream, then let resource type be tentative type and jump to the step below labeled handler. 210 if (tentative_type.has_value() && tentative_type != "application/octet-stream"sv) 211 resource_type = move(tentative_type); 212 } 213 214 // FIXME: 5. If applying the URL parser algorithm to the URL of the specified resource (after any redirects) results in a URL record whose path component matches a pattern that a plugin supports, then let resource type be the type that that plugin can handle. 215 216 run_object_representation_handler_steps(move(resource_type)); 217} 218 219static bool is_xml_mime_type(StringView resource_type) 220{ 221 auto mime_type = MimeSniff::MimeType::parse(resource_type).release_value_but_fixme_should_propagate_errors(); 222 if (!mime_type.has_value()) 223 return false; 224 225 // An XML MIME type is any MIME type whose subtype ends in "+xml" or whose essence is "text/xml" or "application/xml". [RFC7303] 226 if (mime_type->subtype().ends_with_bytes("+xml"sv)) 227 return true; 228 229 return mime_type->essence().is_one_of("text/xml"sv, "application/xml"sv); 230} 231 232// https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-object-element:plugin-11 233void HTMLObjectElement::run_object_representation_handler_steps(Optional<DeprecatedString> resource_type) 234{ 235 // 4.9. Handler: Handle the content as given by the first of the following cases that matches: 236 237 // * FIXME: If the resource type is not a type that the user agent supports, but it is a type that a plugin supports 238 // If the object element's nested browsing context is non-null, then it must be discarded and then set to null. 239 // If plugins are being sandboxed, then jump to the step below labeled fallback. 240 // Otherwise, the user agent should use the plugin that supports resource type and pass the content of the resource to that plugin. If the plugin reports an error, then jump to the step below labeled fallback. 241 242 // * If the resource type is an XML MIME type, or if the resource type does not start with "image/" 243 if (resource_type.has_value() && (is_xml_mime_type(*resource_type) || !resource_type->starts_with("image/"sv))) { 244 // If the object element's nested browsing context is null, then create a new nested browsing context for the element. 245 if (!m_nested_browsing_context) 246 create_new_nested_browsing_context(); 247 248 // NOTE: Creating a new nested browsing context can fail if the document is not attached to a browsing context 249 if (!m_nested_browsing_context) 250 return; 251 252 // If the URL of the given resource does not match about:blank, then navigate the element's nested browsing context to that resource, with historyHandling set to "replace" and the source browsing context set to the object element's node document's browsing context. (The data attribute of the object element doesn't get updated if the browsing context gets further navigated to other locations.) 253 if (auto const& url = resource()->url(); url != "about:blank"sv) 254 m_nested_browsing_context->loader().load(url, FrameLoader::Type::IFrame); 255 256 // The object element represents its nested browsing context. 257 run_object_representation_completed_steps(Representation::NestedBrowsingContext); 258 } 259 260 // * If the resource type starts with "image/", and support for images has not been disabled 261 // FIXME: Handle disabling image support. 262 else if (resource_type.has_value() && resource_type->starts_with("image/"sv)) { 263 // If the object element's nested browsing context is non-null, then it must be discarded and then set to null. 264 if (m_nested_browsing_context) { 265 m_nested_browsing_context->discard(); 266 m_nested_browsing_context = nullptr; 267 } 268 269 // Apply the image sniffing rules to determine the type of the image. 270 // The object element represents the specified image. 271 // If the image cannot be rendered, e.g. because it is malformed or in an unsupported format, jump to the step below labeled fallback. 272 if (!resource()->has_encoded_data()) 273 return run_object_representation_fallback_steps(); 274 275 convert_resource_to_image(); 276 } 277 278 // * Otherwise 279 else { 280 // The given resource type is not supported. Jump to the step below labeled fallback. 281 run_object_representation_fallback_steps(); 282 } 283} 284 285// https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-object-element:the-object-element-19 286void HTMLObjectElement::run_object_representation_completed_steps(Representation representation) 287{ 288 // 4.10. The element's contents are not part of what the object element represents. 289 // 4.11. If the object element does not represent its nested browsing context, 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. 290 if (representation != Representation::NestedBrowsingContext) { 291 queue_an_element_task(HTML::Task::Source::DOMManipulation, [&]() { 292 dispatch_event(DOM::Event::create(realm(), HTML::EventNames::load).release_value_but_fixme_should_propagate_errors()); 293 }); 294 } 295 296 update_layout_and_child_objects(representation); 297 298 // 4.12. Return. 299} 300 301// https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-object-element:the-object-element-23 302void HTMLObjectElement::run_object_representation_fallback_steps() 303{ 304 // 6. Fallback: The object element represents the element's children, ignoring any leading param element children. This is the element's fallback content. If the element has an instantiated plugin, then unload it. If the element's nested browsing context is non-null, then it must be discarded and then set to null. 305 if (m_nested_browsing_context) { 306 m_nested_browsing_context->discard(); 307 m_nested_browsing_context = nullptr; 308 } 309 310 update_layout_and_child_objects(Representation::Children); 311} 312 313void HTMLObjectElement::convert_resource_to_image() 314{ 315 // FIXME: This is a bit awkward. We convert the Resource to an ImageResource here because we do not know 316 // until now that the resource is an image. ImageLoader then becomes responsible for handling 317 // encoding failures, animations, etc. It would be clearer if those features were split from 318 // ImageLoader into a purpose build class to be shared between here and ImageBox. 319 m_image_loader.emplace(*this); 320 321 m_image_loader->on_load = [this] { 322 run_object_representation_completed_steps(Representation::Image); 323 }; 324 m_image_loader->on_fail = [this] { 325 run_object_representation_fallback_steps(); 326 }; 327 328 m_image_loader->adopt_object_resource({}, *resource()); 329} 330 331void HTMLObjectElement::update_layout_and_child_objects(Representation representation) 332{ 333 if ((m_representation == Representation::Children && representation != Representation::Children) 334 || (m_representation != Representation::Children && representation == Representation::Children)) { 335 for_each_child_of_type<HTMLObjectElement>([](auto& object) { 336 object.queue_element_task_to_run_object_representation_steps(); 337 return IterationDecision::Continue; 338 }); 339 } 340 341 m_representation = representation; 342 set_needs_style_update(true); 343 document().set_needs_layout(); 344} 345 346// https://html.spec.whatwg.org/multipage/interaction.html#dom-tabindex 347i32 HTMLObjectElement::default_tab_index_value() const 348{ 349 // See the base function for the spec comments. 350 return 0; 351} 352 353}