| 
									
										
										
										
											2023-06-14 12:45:56 +02:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Copyright (c) 2023, Andreas Kling <kling@serenityos.org> | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * SPDX-License-Identifier: BSD-2-Clause | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <AK/HashTable.h>
 | 
					
						
							|  |  |  | #include <LibGfx/Bitmap.h>
 | 
					
						
							|  |  |  | #include <LibWeb/Fetch/Fetching/Fetching.h>
 | 
					
						
							|  |  |  | #include <LibWeb/Fetch/Infrastructure/FetchAlgorithms.h>
 | 
					
						
							|  |  |  | #include <LibWeb/Fetch/Infrastructure/FetchController.h>
 | 
					
						
							|  |  |  | #include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
 | 
					
						
							|  |  |  | #include <LibWeb/HTML/AnimatedBitmapDecodedImageData.h>
 | 
					
						
							|  |  |  | #include <LibWeb/HTML/DecodedImageData.h>
 | 
					
						
							|  |  |  | #include <LibWeb/HTML/SharedImageRequest.h>
 | 
					
						
							| 
									
										
										
										
											2023-12-04 21:59:00 +13:00
										 |  |  | #include <LibWeb/Page/Page.h>
 | 
					
						
							| 
									
										
										
										
											2023-06-14 12:45:56 +02:00
										 |  |  | #include <LibWeb/Platform/ImageCodecPlugin.h>
 | 
					
						
							|  |  |  | #include <LibWeb/SVG/SVGDecodedImageData.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | namespace Web::HTML { | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-19 19:47:52 +01:00
										 |  |  | JS_DEFINE_ALLOCATOR(SharedImageRequest); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 16:22:27 +13:00
										 |  |  | JS::NonnullGCPtr<SharedImageRequest> SharedImageRequest::get_or_create(JS::Realm& realm, JS::NonnullGCPtr<Page> page, URL::URL const& url) | 
					
						
							| 
									
										
										
										
											2023-06-14 12:45:56 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2023-10-17 16:52:16 +02:00
										 |  |  |     auto document = Bindings::host_defined_environment_settings_object(realm).responsible_document(); | 
					
						
							|  |  |  |     VERIFY(document); | 
					
						
							|  |  |  |     auto& shared_image_requests = document->shared_image_requests(); | 
					
						
							|  |  |  |     if (auto it = shared_image_requests.find(url); it != shared_image_requests.end()) | 
					
						
							| 
									
										
										
										
											2023-06-14 12:45:56 +02:00
										 |  |  |         return *it->value; | 
					
						
							| 
									
										
										
										
											2023-10-17 16:52:16 +02:00
										 |  |  |     auto request = realm.heap().allocate<SharedImageRequest>(realm, page, url, *document); | 
					
						
							|  |  |  |     shared_image_requests.set(url, request); | 
					
						
							| 
									
										
										
										
											2023-06-14 12:45:56 +02:00
										 |  |  |     return request; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 16:22:27 +13:00
										 |  |  | SharedImageRequest::SharedImageRequest(JS::NonnullGCPtr<Page> page, URL::URL url, JS::NonnullGCPtr<DOM::Document> document) | 
					
						
							| 
									
										
										
										
											2023-06-14 12:45:56 +02:00
										 |  |  |     : m_page(page) | 
					
						
							|  |  |  |     , m_url(move(url)) | 
					
						
							| 
									
										
										
										
											2023-10-17 16:52:16 +02:00
										 |  |  |     , m_document(document) | 
					
						
							| 
									
										
										
										
											2023-06-14 12:45:56 +02:00
										 |  |  | { | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-23 20:50:23 +01:00
										 |  |  | SharedImageRequest::~SharedImageRequest() = default; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void SharedImageRequest::finalize() | 
					
						
							| 
									
										
										
										
											2023-06-14 12:45:56 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2023-12-23 20:50:23 +01:00
										 |  |  |     Base::finalize(); | 
					
						
							| 
									
										
										
										
											2023-10-17 16:52:16 +02:00
										 |  |  |     auto& shared_image_requests = m_document->shared_image_requests(); | 
					
						
							|  |  |  |     shared_image_requests.remove(m_url); | 
					
						
							| 
									
										
										
										
											2023-06-14 12:45:56 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-18 14:11:55 +02:00
										 |  |  | void SharedImageRequest::visit_edges(JS::Cell::Visitor& visitor) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     Base::visit_edges(visitor); | 
					
						
							|  |  |  |     visitor.visit(m_fetch_controller); | 
					
						
							| 
									
										
										
										
											2023-10-17 16:52:16 +02:00
										 |  |  |     visitor.visit(m_document); | 
					
						
							| 
									
										
										
										
											2023-12-04 21:59:00 +13:00
										 |  |  |     visitor.visit(m_page); | 
					
						
							| 
									
										
										
										
											2023-09-25 14:27:11 +02:00
										 |  |  |     for (auto& callback : m_callbacks) { | 
					
						
							|  |  |  |         visitor.visit(callback.on_finish); | 
					
						
							|  |  |  |         visitor.visit(callback.on_fail); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-12-12 20:07:19 +01:00
										 |  |  |     visitor.visit(m_image_data); | 
					
						
							| 
									
										
										
										
											2023-08-18 14:11:55 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-12 20:07:19 +01:00
										 |  |  | JS::GCPtr<DecodedImageData> SharedImageRequest::image_data() const | 
					
						
							| 
									
										
										
										
											2023-06-14 12:45:56 +02:00
										 |  |  | { | 
					
						
							|  |  |  |     return m_image_data; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | JS::GCPtr<Fetch::Infrastructure::FetchController> SharedImageRequest::fetch_controller() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return m_fetch_controller.ptr(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void SharedImageRequest::set_fetch_controller(JS::GCPtr<Fetch::Infrastructure::FetchController> fetch_controller) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     m_fetch_controller = move(fetch_controller); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void SharedImageRequest::fetch_image(JS::Realm& realm, JS::NonnullGCPtr<Fetch::Infrastructure::Request> request) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     Fetch::Infrastructure::FetchAlgorithms::Input fetch_algorithms_input {}; | 
					
						
							|  |  |  |     fetch_algorithms_input.process_response = [this, &realm, request](JS::NonnullGCPtr<Fetch::Infrastructure::Response> response) { | 
					
						
							|  |  |  |         // FIXME: If the response is CORS cross-origin, we must use its internal response to query any of its data. See:
 | 
					
						
							|  |  |  |         //        https://github.com/whatwg/html/issues/9355
 | 
					
						
							|  |  |  |         response = response->unsafe_response(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-23 10:25:20 +02:00
										 |  |  |         auto process_body = JS::create_heap_function(heap(), [this, request, response](ByteBuffer data) { | 
					
						
							| 
									
										
										
										
											2024-04-26 13:24:20 -04:00
										 |  |  |             auto extracted_mime_type = response->header_list()->extract_mime_type(); | 
					
						
							| 
									
										
										
										
											2023-06-14 12:45:56 +02:00
										 |  |  |             auto mime_type = extracted_mime_type.has_value() ? extracted_mime_type.value().essence().bytes_as_string_view() : StringView {}; | 
					
						
							|  |  |  |             handle_successful_fetch(request->url(), mime_type, move(data)); | 
					
						
							| 
									
										
										
										
											2024-04-23 10:25:20 +02:00
										 |  |  |         }); | 
					
						
							|  |  |  |         auto process_body_error = JS::create_heap_function(heap(), [this](JS::GCPtr<WebIDL::DOMException>) { | 
					
						
							| 
									
										
										
										
											2023-06-14 12:45:56 +02:00
										 |  |  |             handle_failed_fetch(); | 
					
						
							| 
									
										
										
										
											2024-04-23 10:25:20 +02:00
										 |  |  |         }); | 
					
						
							| 
									
										
										
										
											2023-06-14 12:45:56 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-18 19:38:13 +02:00
										 |  |  |         if (response->body()) | 
					
						
							| 
									
										
										
										
											2024-04-26 14:57:40 -04:00
										 |  |  |             response->body()->fully_read(realm, process_body, process_body_error, JS::NonnullGCPtr { realm.global_object() }); | 
					
						
							| 
									
										
										
										
											2023-08-28 09:14:36 +02:00
										 |  |  |         else | 
					
						
							|  |  |  |             handle_failed_fetch(); | 
					
						
							| 
									
										
										
										
											2023-06-14 12:45:56 +02:00
										 |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m_state = State::Fetching; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     auto fetch_controller = Fetch::Fetching::fetch( | 
					
						
							|  |  |  |         realm, | 
					
						
							|  |  |  |         request, | 
					
						
							|  |  |  |         Fetch::Infrastructure::FetchAlgorithms::create(realm.vm(), move(fetch_algorithms_input))) | 
					
						
							|  |  |  |                                 .release_value_but_fixme_should_propagate_errors(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     set_fetch_controller(fetch_controller); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-25 14:27:11 +02:00
										 |  |  | void SharedImageRequest::add_callbacks(Function<void()> on_finish, Function<void()> on_fail) | 
					
						
							| 
									
										
										
										
											2023-06-14 12:45:56 +02:00
										 |  |  | { | 
					
						
							|  |  |  |     if (m_state == State::Finished) { | 
					
						
							|  |  |  |         if (on_finish) | 
					
						
							|  |  |  |             on_finish(); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (m_state == State::Failed) { | 
					
						
							|  |  |  |         if (on_fail) | 
					
						
							|  |  |  |             on_fail(); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-25 14:27:11 +02:00
										 |  |  |     Callbacks callbacks; | 
					
						
							|  |  |  |     if (on_finish) | 
					
						
							|  |  |  |         callbacks.on_finish = JS::create_heap_function(vm().heap(), move(on_finish)); | 
					
						
							|  |  |  |     if (on_fail) | 
					
						
							|  |  |  |         callbacks.on_fail = JS::create_heap_function(vm().heap(), move(on_fail)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m_callbacks.append(move(callbacks)); | 
					
						
							| 
									
										
										
										
											2023-06-14 12:45:56 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 16:22:27 +13:00
										 |  |  | void SharedImageRequest::handle_successful_fetch(URL::URL const& url_string, StringView mime_type, ByteBuffer data) | 
					
						
							| 
									
										
										
										
											2023-06-14 12:45:56 +02:00
										 |  |  | { | 
					
						
							|  |  |  |     // AD-HOC: At this point, things gets very ad-hoc.
 | 
					
						
							|  |  |  |     // FIXME: Bring this closer to spec.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     bool const is_svg_image = mime_type == "image/svg+xml"sv || url_string.basename().ends_with(".svg"sv); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-19 15:55:54 -06:00
										 |  |  |     auto handle_failed_decode = [strong_this = JS::Handle(*this)](Error&) -> void { | 
					
						
							|  |  |  |         strong_this->m_state = State::Failed; | 
					
						
							|  |  |  |         for (auto& callback : strong_this->m_callbacks) { | 
					
						
							| 
									
										
										
										
											2023-06-14 12:45:56 +02:00
										 |  |  |             if (callback.on_fail) | 
					
						
							| 
									
										
										
										
											2023-09-25 14:27:11 +02:00
										 |  |  |                 callback.on_fail->function()(); | 
					
						
							| 
									
										
										
										
											2023-06-14 12:45:56 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-19 15:55:54 -06:00
										 |  |  |     auto handle_successful_decode = [](SharedImageRequest& self) { | 
					
						
							|  |  |  |         self.m_state = State::Finished; | 
					
						
							|  |  |  |         for (auto& callback : self.m_callbacks) { | 
					
						
							|  |  |  |             if (callback.on_finish) | 
					
						
							|  |  |  |                 callback.on_finish->function()(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         self.m_callbacks.clear(); | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-14 12:45:56 +02:00
										 |  |  |     if (is_svg_image) { | 
					
						
							| 
									
										
										
										
											2023-12-12 20:07:19 +01:00
										 |  |  |         auto result = SVG::SVGDecodedImageData::create(m_document->realm(), m_page, url_string, data); | 
					
						
							| 
									
										
										
										
											2024-04-19 15:55:54 -06:00
										 |  |  |         if (result.is_error()) { | 
					
						
							|  |  |  |             handle_failed_decode(result.error()); | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             m_image_data = result.release_value(); | 
					
						
							|  |  |  |             handle_successful_decode(*this); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-06-14 12:45:56 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-19 15:55:54 -06:00
										 |  |  |     auto handle_successful_bitmap_decode = [strong_this = JS::Handle(*this), handle_successful_decode = move(handle_successful_decode)](Web::Platform::DecodedImage& result) -> ErrorOr<void> { | 
					
						
							| 
									
										
										
										
											2023-06-14 12:45:56 +02:00
										 |  |  |         Vector<AnimatedBitmapDecodedImageData::Frame> frames; | 
					
						
							| 
									
										
										
										
											2024-04-19 15:55:54 -06:00
										 |  |  |         for (auto& frame : result.frames) { | 
					
						
							| 
									
										
										
										
											2023-06-14 12:45:56 +02:00
										 |  |  |             frames.append(AnimatedBitmapDecodedImageData::Frame { | 
					
						
							| 
									
										
										
										
											2023-11-24 14:45:45 +01:00
										 |  |  |                 .bitmap = Gfx::ImmutableBitmap::create(*frame.bitmap), | 
					
						
							| 
									
										
										
										
											2023-06-14 12:45:56 +02:00
										 |  |  |                 .duration = static_cast<int>(frame.duration), | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-04-19 15:55:54 -06:00
										 |  |  |         strong_this->m_image_data = AnimatedBitmapDecodedImageData::create(strong_this->m_document->realm(), move(frames), result.loop_count, result.is_animated).release_value_but_fixme_should_propagate_errors(); | 
					
						
							|  |  |  |         handle_successful_decode(*strong_this); | 
					
						
							|  |  |  |         return {}; | 
					
						
							|  |  |  |     }; | 
					
						
							| 
									
										
										
										
											2023-06-14 12:45:56 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-19 15:55:54 -06:00
										 |  |  |     (void)Web::Platform::ImageCodecPlugin::the().decode_image(data.bytes(), move(handle_successful_bitmap_decode), move(handle_failed_decode)); | 
					
						
							| 
									
										
										
										
											2023-06-14 12:45:56 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void SharedImageRequest::handle_failed_fetch() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     m_state = State::Failed; | 
					
						
							|  |  |  |     for (auto& callback : m_callbacks) { | 
					
						
							|  |  |  |         if (callback.on_fail) | 
					
						
							| 
									
										
										
										
											2023-09-25 14:27:11 +02:00
										 |  |  |             callback.on_fail->function()(); | 
					
						
							| 
									
										
										
										
											2023-06-14 12:45:56 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-08-19 14:55:49 +02:00
										 |  |  |     m_callbacks.clear(); | 
					
						
							| 
									
										
										
										
											2023-06-14 12:45:56 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool SharedImageRequest::needs_fetching() const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return m_state == State::New; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool SharedImageRequest::is_fetching() const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return m_state == State::Fetching; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } |