Skip to content

Commit 85abc02

Browse files
committed
LibWeb: Implement 'decoding' attribute for HTMLImageElement
This commit implements the 'decoding' attribute for HTMLImageElement, including parsing the attribute and implementing the decoding logic. The 'decoding' attribute can be set to 'auto', 'sync', or 'async' to control the image decoding behavior. This implementation respects the chosen mode, with 'sync' blocking the main thread and 'async' using a background task.
1 parent 35254d1 commit 85abc02

File tree

4 files changed

+149
-81
lines changed

4 files changed

+149
-81
lines changed

Libraries/LibWeb/HTML/HTMLImageElement.cpp

Lines changed: 129 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,17 @@ void HTMLImageElement::form_associated_element_attribute_changed(FlyString const
151151
}
152152

153153
if (name == HTML::AttributeNames::decoding) {
154-
if (value.has_value() && (value->equals_ignoring_ascii_case("sync"sv) || value->equals_ignoring_ascii_case("async"sv)))
155-
dbgln("FIXME: HTMLImageElement.decoding = '{}' is not implemented yet", value->to_ascii_lowercase());
154+
if (value.has_value()) {
155+
auto decoding_value = value -> to_ascii_lowercase();
156+
if (decoding_value == "sync"sv)
157+
m_decoding_mode = DecodingMode::Sync;
158+
else if (decoding_value == "async"sv)
159+
m_decoding_mode = DecodingMode::Async;
160+
else
161+
m_decoding_mode = DecodingMode::Auto;
162+
} else {
163+
m_decoding_mode = DecodingMode::Auto;
164+
}
156165
}
157166
}
158167

@@ -343,93 +352,134 @@ WebIDL::ExceptionOr<GC::Ref<WebIDL::Promise>> HTMLImageElement::decode() const
343352
// 1. Let promise be a new promise.
344353
auto promise = WebIDL::create_promise(realm);
345354

346-
// 2. Queue a microtask to perform the following steps:
347-
queue_a_microtask(&document(), GC::create_function(realm.heap(), [this, promise, &realm]() mutable {
348-
// 1. Let global be this's relevant global object.
349-
auto& global = relevant_global_object(*this);
350-
351-
auto reject_if_document_not_fully_active = [this, promise, &realm]() -> bool {
352-
if (this->document().is_fully_active())
353-
return false;
354-
355-
auto exception = WebIDL::EncodingError::create(realm, "Node document not fully active"_utf16);
356-
HTML::TemporaryExecutionContext context(realm);
357-
WebIDL::reject_promise(realm, promise, exception);
358-
return true;
359-
};
360-
361-
auto reject_if_current_request_state_broken = [this, promise, &realm]() {
362-
if (this->current_request().state() != ImageRequest::State::Broken)
363-
return false;
364-
365-
auto exception = WebIDL::EncodingError::create(realm, "Current request state is broken"_utf16);
366-
HTML::TemporaryExecutionContext context(realm);
367-
WebIDL::reject_promise(realm, promise, exception);
368-
return true;
369-
};
370-
371-
// 2. If any of the following are true:
372-
// - this's node document is not fully active;
373-
// - or this's current request's state is broken,
374-
// then reject promise with an "EncodingError" DOMException.
375-
if (reject_if_document_not_fully_active() || reject_if_current_request_state_broken()) {
376-
return;
355+
if (m_decoding_mode == DecodingMode::Sync) {
356+
// SYNC DECODING
357+
// Warning: This will block the main thread until the image is decoded.
358+
if (!document().is_fully_active()) {
359+
WebIDL::reject_promise(realm, promise, WebIDL::EncodingError::create(realm, "Node document not fully active"_utf16));
360+
return promise;
361+
}
362+
if (current_request().state() == ImageRequest::State::Broken) {
363+
WebIDL::reject_promise(realm, promise, WebIDL::EncodingError::create(realm, "Current request state is broken"_utf16));
364+
return promise;
365+
}
366+
if (current_request().state() != ImageRequest::State::CompletelyAvailable) {
367+
WebIDL::reject_promise(realm, promise, WebIDL::EncodingError::create(realm, "Image data not available"_utf16));
368+
return promise;
377369
}
378370

379-
// 3. Otherwise, in parallel wait for one of the following cases to occur, and perform the corresponding actions:
380-
Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(heap(), [this, promise, &realm, &global] {
381-
Platform::EventLoopPlugin::the().spin_until(GC::create_function(heap(), [this, promise, &realm, &global] {
382-
auto queue_reject_task = [promise, &realm, &global](Utf16String message) {
383-
queue_global_task(Task::Source::DOMManipulation, global, GC::create_function(realm.heap(), [&realm, promise, message = move(message)] {
384-
auto exception = WebIDL::EncodingError::create(realm, message);
385-
HTML::TemporaryExecutionContext context(realm);
386-
WebIDL::reject_promise(realm, promise, exception);
387-
}));
388-
};
389-
390-
// -> This img element's node document stops being fully active
391-
if (!document().is_fully_active()) {
392-
// Queue a global task on the DOM manipulation task source with global to reject promise with an "EncodingError" DOMException.
393-
queue_reject_task("Node document not fully active"_utf16);
394-
return true;
395-
}
371+
if (!current_request().shared_resource_request() || current_request().shared_resource_request()->encoded_data().is_empty()) {
372+
WebIDL::reject_promise(realm, promise, WebIDL::EncodingError::create(realm, "Image data not available"_utf16));
373+
return promise;
374+
}
396375

397-
auto state = this->current_request().state();
376+
bool success = false;
377+
bool failed = false;
398378

399-
// -> FIXME: This img element's current request changes or is mutated
400-
if (false) {
401-
// Queue a global task on the DOM manipulation task source with global to reject promise with an "EncodingError" DOMException.
402-
queue_reject_task("Current request changed or was mutated"_utf16);
403-
return true;
404-
}
379+
(void)Web::Platform::ImageCodecPlugin::the().decode_image(
380+
current_request().shared_resource_request()->encoded_data(),
381+
[&](Web::Platform::DecodedImage&) -> ErrorOr<void> {
382+
success = true;
383+
return {};
384+
},
385+
[&](auto&) {
386+
failed = true;
387+
});
405388

406-
// -> This img element's current request's state becomes broken
407-
if (state == ImageRequest::State::Broken) {
408-
// Queue a global task on the DOM manipulation task source with global to reject promise with an "EncodingError" DOMException.
409-
queue_reject_task("Current request state is broken"_utf16);
410-
return true;
411-
}
389+
Core::EventLoop::current().spin_until([&] {
390+
return success || failed;
391+
});
412392

413-
// -> This img element's current request's state becomes completely available
414-
if (state == ImageRequest::State::CompletelyAvailable) {
415-
// FIXME: Decode the image.
416-
// FIXME: If decoding does not need to be performed for this image (for example because it is a vector graphic) or the decoding process completes successfully, then queue a global task on the DOM manipulation task source with global to resolve promise with undefined.
417-
// FIXME: If decoding fails (for example due to invalid image data), then queue a global task on the DOM manipulation task source with global to reject promise with an "EncodingError" DOMException.
418-
419-
// NOTE: For now we just resolve it.
420-
queue_global_task(Task::Source::DOMManipulation, global, GC::create_function(realm.heap(), [&realm, promise] {
421-
HTML::TemporaryExecutionContext context(realm);
422-
WebIDL::resolve_promise(realm, promise, JS::js_undefined());
423-
}));
424-
return true;
425-
}
393+
if (success) {
394+
WebIDL::resolve_promise(realm, promise, JS::js_undefined());
395+
} else {
396+
WebIDL::reject_promise(realm, promise, WebIDL::EncodingError::create(realm, "Decoding failed"_utf16));
397+
}
398+
399+
} else { // ASYNC / AUTO DECODING
400+
queue_a_microtask(&document(), GC::create_function(realm.heap(), [this, promise, &realm]() mutable {
401+
auto& global = relevant_global_object(*this);
402+
403+
auto reject_if_document_not_fully_active = [this, promise, &realm]() -> bool {
404+
if (this->document().is_fully_active())
405+
return false;
406+
407+
auto exception = WebIDL::EncodingError::create(realm, "Node document not fully active"_utf16);
408+
HTML::TemporaryExecutionContext context(realm);
409+
WebIDL::reject_promise(realm, promise, exception);
410+
return true;
411+
};
412+
413+
auto reject_if_current_request_state_broken = [this, promise, &realm]() {
414+
if (this->current_request().state() != ImageRequest::State::Broken)
415+
return false;
416+
417+
auto exception = WebIDL::EncodingError::create(realm, "Current request state is broken"_utf16);
418+
HTML::TemporaryExecutionContext context(realm);
419+
WebIDL::reject_promise(realm, promise, exception);
420+
return true;
421+
};
426422

427-
return false;
423+
if (reject_if_document_not_fully_active() || reject_if_current_request_state_broken()) {
424+
return;
425+
}
426+
427+
Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(heap(), [this, promise, &realm, &global] {
428+
Platform::EventLoopPlugin::the().spin_until(GC::create_function(heap(), [this, promise, &realm, &global] {
429+
auto queue_reject_task = [promise, &realm, &global](Utf16String message) {
430+
queue_global_task(Task::Source::DOMManipulation, global, GC::create_function(realm.heap(), [&realm, promise, message = move(message)] {
431+
auto exception = WebIDL::EncodingError::create(realm, message);
432+
HTML::TemporaryExecutionContext context(realm);
433+
WebIDL::reject_promise(realm, promise, exception);
434+
}));
435+
};
436+
437+
if (!document().is_fully_active()) {
438+
queue_reject_task("Node document not fully active"_utf16);
439+
return true;
440+
}
441+
442+
auto state = this->current_request().state();
443+
444+
if (false) { // FIXME: This img element's current request changes or is mutated
445+
queue_reject_task("Current request changed or was mutated"_utf16);
446+
return true;
447+
}
448+
449+
if (state == ImageRequest::State::Broken) {
450+
queue_reject_task("Current request state is broken"_utf16);
451+
return true;
452+
}
453+
454+
if (state == ImageRequest::State::CompletelyAvailable) {
455+
auto& image_request = this->current_request();
456+
if (!image_request.shared_resource_request() || image_request.shared_resource_request()->encoded_data().is_empty()) {
457+
queue_reject_task("Image data not available"_utf16);
458+
return true;
459+
}
460+
461+
(void)Web::Platform::ImageCodecPlugin::the().decode_image(
462+
image_request.shared_resource_request()->encoded_data(),
463+
[promise, &realm, &global](Web::Platform::DecodedImage&) -> ErrorOr<void> {
464+
queue_global_task(Task::Source::DOMManipulation, global, GC::create_function(realm.heap(), [&realm, promise] {
465+
HTML::TemporaryExecutionContext context(realm);
466+
WebIDL::resolve_promise(realm, promise, JS::js_undefined());
467+
}));
468+
return {};
469+
},
470+
[promise, &realm, &global, &queue_reject_task](auto&) {
471+
queue_reject_task("Decoding failed"_utf16);
472+
});
473+
474+
return true;
475+
}
476+
477+
return false;
478+
}));
428479
}));
429480
}));
430-
}));
481+
}
431482

432-
// 3. Return promise.
433483
return promise;
434484
}
435485

Libraries/LibWeb/HTML/HTMLImageElement.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,15 @@ class HTMLImageElement final
163163
SourceSet m_source_set;
164164

165165
CSSPixelSize m_last_seen_viewport_size;
166+
167+
// https://html.spec.whatwg.org/multipage/embedded-content.html#attr-img-decoding
168+
enum class DecodingMode {
169+
Auto,
170+
Sync,
171+
Async,
172+
};
173+
174+
DecodingMode m_decoding_mode { DecodingMode::Auto };
166175
};
167176

168177
}

Libraries/LibWeb/HTML/SharedResourceRequest.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ void SharedResourceRequest::visit_edges(JS::Cell::Visitor& visitor)
6363
visitor.visit(m_image_data);
6464
}
6565

66+
ReadonlyBytes SharedResourceRequest::encoded_data() const
67+
{
68+
return m_encoded_data;
69+
}
70+
6671
GC::Ptr<DecodedImageData> SharedResourceRequest::image_data() const
6772
{
6873
return m_image_data;
@@ -140,13 +145,15 @@ void SharedResourceRequest::add_callbacks(Function<void()> on_finish, Function<v
140145

141146
void SharedResourceRequest::handle_successful_fetch(URL::URL const& url_string, StringView mime_type, ByteBuffer data)
142147
{
148+
m_encoded_data = move(data);
149+
143150
// AD-HOC: At this point, things gets very ad-hoc.
144151
// FIXME: Bring this closer to spec.
145152

146153
bool const is_svg_image = mime_type == "image/svg+xml"sv || url_string.basename().ends_with(".svg"sv);
147154

148155
if (is_svg_image) {
149-
auto result = SVG::SVGDecodedImageData::create(m_document->realm(), m_page, url_string, data);
156+
auto result = SVG::SVGDecodedImageData::create(m_document->realm(), m_page, url_string, m_encoded_data);
150157
if (result.is_error()) {
151158
handle_failed_fetch();
152159
} else {
@@ -173,7 +180,7 @@ void SharedResourceRequest::handle_successful_fetch(URL::URL const& url_string,
173180
strong_this->handle_failed_fetch();
174181
};
175182

176-
(void)Web::Platform::ImageCodecPlugin::the().decode_image(data.bytes(), move(handle_successful_bitmap_decode), move(handle_failed_decode));
183+
(void)Web::Platform::ImageCodecPlugin::the().decode_image(m_encoded_data, move(handle_successful_bitmap_decode), move(handle_failed_decode));
177184
}
178185

179186
void SharedResourceRequest::handle_failed_fetch()

Libraries/LibWeb/HTML/SharedResourceRequest.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class SharedResourceRequest final : public JS::Cell {
2424
virtual ~SharedResourceRequest() override;
2525

2626
URL::URL const& url() const { return m_url; }
27+
ReadonlyBytes encoded_data() const;
2728

2829
[[nodiscard]] GC::Ptr<DecodedImageData> image_data() const;
2930

@@ -65,6 +66,7 @@ class SharedResourceRequest final : public JS::Cell {
6566
Vector<Callbacks> m_callbacks;
6667

6768
URL::URL m_url;
69+
ByteBuffer m_encoded_data;
6870
GC::Ptr<DecodedImageData> m_image_data;
6971
GC::Ptr<Fetch::Infrastructure::FetchController> m_fetch_controller;
7072

0 commit comments

Comments
 (0)