SharedImageRequest.cpp 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. /*
  2. * Copyright (c) 2023, Andreas Kling <kling@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/HashTable.h>
  7. #include <LibGfx/Bitmap.h>
  8. #include <LibWeb/Fetch/Fetching/Fetching.h>
  9. #include <LibWeb/Fetch/Infrastructure/FetchAlgorithms.h>
  10. #include <LibWeb/Fetch/Infrastructure/FetchController.h>
  11. #include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
  12. #include <LibWeb/HTML/AnimatedBitmapDecodedImageData.h>
  13. #include <LibWeb/HTML/DecodedImageData.h>
  14. #include <LibWeb/HTML/SharedImageRequest.h>
  15. #include <LibWeb/Page/Page.h>
  16. #include <LibWeb/Platform/ImageCodecPlugin.h>
  17. #include <LibWeb/SVG/SVGDecodedImageData.h>
  18. namespace Web::HTML {
  19. JS_DEFINE_ALLOCATOR(SharedImageRequest);
  20. JS::NonnullGCPtr<SharedImageRequest> SharedImageRequest::get_or_create(JS::Realm& realm, JS::NonnullGCPtr<Page> page, AK::URL const& url)
  21. {
  22. auto document = Bindings::host_defined_environment_settings_object(realm).responsible_document();
  23. VERIFY(document);
  24. auto& shared_image_requests = document->shared_image_requests();
  25. if (auto it = shared_image_requests.find(url); it != shared_image_requests.end())
  26. return *it->value;
  27. auto request = realm.heap().allocate<SharedImageRequest>(realm, page, url, *document);
  28. shared_image_requests.set(url, request);
  29. return request;
  30. }
  31. SharedImageRequest::SharedImageRequest(JS::NonnullGCPtr<Page> page, AK::URL url, JS::NonnullGCPtr<DOM::Document> document)
  32. : m_page(page)
  33. , m_url(move(url))
  34. , m_document(document)
  35. {
  36. }
  37. SharedImageRequest::~SharedImageRequest()
  38. {
  39. auto& shared_image_requests = m_document->shared_image_requests();
  40. shared_image_requests.remove(m_url);
  41. }
  42. void SharedImageRequest::visit_edges(JS::Cell::Visitor& visitor)
  43. {
  44. Base::visit_edges(visitor);
  45. visitor.visit(m_fetch_controller);
  46. visitor.visit(m_document);
  47. visitor.visit(m_page);
  48. for (auto& callback : m_callbacks) {
  49. visitor.visit(callback.on_finish);
  50. visitor.visit(callback.on_fail);
  51. }
  52. }
  53. RefPtr<DecodedImageData const> SharedImageRequest::image_data() const
  54. {
  55. return m_image_data;
  56. }
  57. JS::GCPtr<Fetch::Infrastructure::FetchController> SharedImageRequest::fetch_controller()
  58. {
  59. return m_fetch_controller.ptr();
  60. }
  61. void SharedImageRequest::set_fetch_controller(JS::GCPtr<Fetch::Infrastructure::FetchController> fetch_controller)
  62. {
  63. m_fetch_controller = move(fetch_controller);
  64. }
  65. void SharedImageRequest::fetch_image(JS::Realm& realm, JS::NonnullGCPtr<Fetch::Infrastructure::Request> request)
  66. {
  67. Fetch::Infrastructure::FetchAlgorithms::Input fetch_algorithms_input {};
  68. fetch_algorithms_input.process_response = [this, &realm, request](JS::NonnullGCPtr<Fetch::Infrastructure::Response> response) {
  69. // FIXME: If the response is CORS cross-origin, we must use its internal response to query any of its data. See:
  70. // https://github.com/whatwg/html/issues/9355
  71. response = response->unsafe_response();
  72. auto process_body = [this, request, response](ByteBuffer data) {
  73. auto extracted_mime_type = response->header_list()->extract_mime_type().release_value_but_fixme_should_propagate_errors();
  74. auto mime_type = extracted_mime_type.has_value() ? extracted_mime_type.value().essence().bytes_as_string_view() : StringView {};
  75. handle_successful_fetch(request->url(), mime_type, move(data));
  76. };
  77. auto process_body_error = [this](auto) {
  78. handle_failed_fetch();
  79. };
  80. if (response->body())
  81. response->body()->fully_read(realm, move(process_body), move(process_body_error), JS::NonnullGCPtr { realm.global_object() }).release_value_but_fixme_should_propagate_errors();
  82. else
  83. handle_failed_fetch();
  84. };
  85. m_state = State::Fetching;
  86. auto fetch_controller = Fetch::Fetching::fetch(
  87. realm,
  88. request,
  89. Fetch::Infrastructure::FetchAlgorithms::create(realm.vm(), move(fetch_algorithms_input)))
  90. .release_value_but_fixme_should_propagate_errors();
  91. set_fetch_controller(fetch_controller);
  92. }
  93. void SharedImageRequest::add_callbacks(Function<void()> on_finish, Function<void()> on_fail)
  94. {
  95. if (m_state == State::Finished) {
  96. if (on_finish)
  97. on_finish();
  98. return;
  99. }
  100. if (m_state == State::Failed) {
  101. if (on_fail)
  102. on_fail();
  103. return;
  104. }
  105. Callbacks callbacks;
  106. if (on_finish)
  107. callbacks.on_finish = JS::create_heap_function(vm().heap(), move(on_finish));
  108. if (on_fail)
  109. callbacks.on_fail = JS::create_heap_function(vm().heap(), move(on_fail));
  110. m_callbacks.append(move(callbacks));
  111. }
  112. void SharedImageRequest::handle_successful_fetch(AK::URL const& url_string, StringView mime_type, ByteBuffer data)
  113. {
  114. // AD-HOC: At this point, things gets very ad-hoc.
  115. // FIXME: Bring this closer to spec.
  116. bool const is_svg_image = mime_type == "image/svg+xml"sv || url_string.basename().ends_with(".svg"sv);
  117. RefPtr<DecodedImageData> image_data;
  118. auto handle_failed_decode = [&] {
  119. m_state = State::Failed;
  120. for (auto& callback : m_callbacks) {
  121. if (callback.on_fail)
  122. callback.on_fail->function()();
  123. }
  124. };
  125. if (is_svg_image) {
  126. auto result = SVG::SVGDecodedImageData::create(m_page, url_string, data);
  127. if (result.is_error())
  128. return handle_failed_decode();
  129. image_data = result.release_value();
  130. } else {
  131. auto result = Web::Platform::ImageCodecPlugin::the().decode_image(data.bytes());
  132. if (!result.has_value())
  133. return handle_failed_decode();
  134. Vector<AnimatedBitmapDecodedImageData::Frame> frames;
  135. for (auto& frame : result.value().frames) {
  136. frames.append(AnimatedBitmapDecodedImageData::Frame {
  137. .bitmap = Gfx::ImmutableBitmap::create(*frame.bitmap),
  138. .duration = static_cast<int>(frame.duration),
  139. });
  140. }
  141. image_data = AnimatedBitmapDecodedImageData::create(move(frames), result.value().loop_count, result.value().is_animated).release_value_but_fixme_should_propagate_errors();
  142. }
  143. m_image_data = move(image_data);
  144. m_state = State::Finished;
  145. for (auto& callback : m_callbacks) {
  146. if (callback.on_finish)
  147. callback.on_finish->function()();
  148. }
  149. m_callbacks.clear();
  150. }
  151. void SharedImageRequest::handle_failed_fetch()
  152. {
  153. m_state = State::Failed;
  154. for (auto& callback : m_callbacks) {
  155. if (callback.on_fail)
  156. callback.on_fail->function()();
  157. }
  158. m_callbacks.clear();
  159. }
  160. bool SharedImageRequest::needs_fetching() const
  161. {
  162. return m_state == State::New;
  163. }
  164. bool SharedImageRequest::is_fetching() const
  165. {
  166. return m_state == State::Fetching;
  167. }
  168. }