/* * Copyright (c) 2023, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include #include namespace Web::HTML { JS_DEFINE_ALLOCATOR(SharedImageRequest); JS::NonnullGCPtr SharedImageRequest::get_or_create(JS::Realm& realm, JS::NonnullGCPtr page, AK::URL const& url) { 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()) return *it->value; auto request = realm.heap().allocate(realm, page, url, *document); shared_image_requests.set(url, request); return request; } SharedImageRequest::SharedImageRequest(JS::NonnullGCPtr page, AK::URL url, JS::NonnullGCPtr document) : m_page(page) , m_url(move(url)) , m_document(document) { } SharedImageRequest::~SharedImageRequest() = default; void SharedImageRequest::finalize() { Base::finalize(); auto& shared_image_requests = m_document->shared_image_requests(); shared_image_requests.remove(m_url); } void SharedImageRequest::visit_edges(JS::Cell::Visitor& visitor) { Base::visit_edges(visitor); visitor.visit(m_fetch_controller); visitor.visit(m_document); visitor.visit(m_page); for (auto& callback : m_callbacks) { visitor.visit(callback.on_finish); visitor.visit(callback.on_fail); } visitor.visit(m_image_data); } JS::GCPtr SharedImageRequest::image_data() const { return m_image_data; } JS::GCPtr SharedImageRequest::fetch_controller() { return m_fetch_controller.ptr(); } void SharedImageRequest::set_fetch_controller(JS::GCPtr fetch_controller) { m_fetch_controller = move(fetch_controller); } void SharedImageRequest::fetch_image(JS::Realm& realm, JS::NonnullGCPtr request) { Fetch::Infrastructure::FetchAlgorithms::Input fetch_algorithms_input {}; fetch_algorithms_input.process_response = [this, &realm, request](JS::NonnullGCPtr 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(); auto process_body = [this, request, response](ByteBuffer data) { auto extracted_mime_type = response->header_list()->extract_mime_type().release_value_but_fixme_should_propagate_errors(); 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)); }; auto process_body_error = [this](auto) { handle_failed_fetch(); }; if (response->body()) response->body()->fully_read(realm, move(process_body), move(process_body_error), JS::NonnullGCPtr { realm.global_object() }).release_value_but_fixme_should_propagate_errors(); else handle_failed_fetch(); }; 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); } void SharedImageRequest::add_callbacks(Function on_finish, Function on_fail) { if (m_state == State::Finished) { if (on_finish) on_finish(); return; } if (m_state == State::Failed) { if (on_fail) on_fail(); return; } 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)); } void SharedImageRequest::handle_successful_fetch(AK::URL const& url_string, StringView mime_type, ByteBuffer data) { // 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); JS::GCPtr image_data; auto handle_failed_decode = [&] { m_state = State::Failed; for (auto& callback : m_callbacks) { if (callback.on_fail) callback.on_fail->function()(); } }; if (is_svg_image) { auto result = SVG::SVGDecodedImageData::create(m_document->realm(), m_page, url_string, data); if (result.is_error()) return handle_failed_decode(); image_data = result.release_value(); } else { auto result = Web::Platform::ImageCodecPlugin::the().decode_image(data.bytes()); if (!result.has_value()) return handle_failed_decode(); Vector frames; for (auto& frame : result.value().frames) { frames.append(AnimatedBitmapDecodedImageData::Frame { .bitmap = Gfx::ImmutableBitmap::create(*frame.bitmap), .duration = static_cast(frame.duration), }); } image_data = AnimatedBitmapDecodedImageData::create(m_document->realm(), move(frames), result.value().loop_count, result.value().is_animated).release_value_but_fixme_should_propagate_errors(); } m_image_data = image_data; m_state = State::Finished; for (auto& callback : m_callbacks) { if (callback.on_finish) callback.on_finish->function()(); } m_callbacks.clear(); } void SharedImageRequest::handle_failed_fetch() { m_state = State::Failed; for (auto& callback : m_callbacks) { if (callback.on_fail) callback.on_fail->function()(); } m_callbacks.clear(); } bool SharedImageRequest::needs_fetching() const { return m_state == State::New; } bool SharedImageRequest::is_fetching() const { return m_state == State::Fetching; } }