Serenity Operating System
1/*
2 * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <AK/Debug.h>
8#include <LibGfx/Bitmap.h>
9#include <LibWeb/DOM/Document.h>
10#include <LibWeb/DOM/Element.h>
11#include <LibWeb/Loader/ImageLoader.h>
12#include <LibWeb/Loader/ResourceLoader.h>
13#include <LibWeb/Platform/Timer.h>
14
15namespace Web {
16
17ImageLoader::ImageLoader(DOM::Element& owner_element)
18 : m_owner_element(owner_element)
19 , m_timer(Platform::Timer::create())
20{
21}
22
23void ImageLoader::adopt_object_resource(Badge<HTML::HTMLObjectElement>, Resource& resource)
24{
25 auto image_resource = ImageResource::convert_from_resource(resource);
26 set_resource(image_resource);
27}
28
29void ImageLoader::load(const AK::URL& url)
30{
31 m_redirects_count = 0;
32 load_without_resetting_redirect_counter(url);
33}
34
35void ImageLoader::load_without_resetting_redirect_counter(AK::URL const& url)
36{
37 m_loading_state = LoadingState::Loading;
38
39 auto request = LoadRequest::create_for_url_on_page(url, m_owner_element.document().page());
40 set_resource(ResourceLoader::the().load_resource(Resource::Type::Image, request));
41}
42
43void ImageLoader::set_visible_in_viewport(bool visible_in_viewport) const
44{
45 if (m_visible_in_viewport == visible_in_viewport)
46 return;
47 m_visible_in_viewport = visible_in_viewport;
48
49 // FIXME: Don't update volatility every time. If we're here, we're probably scanning through
50 // the whole document, updating "is visible in viewport" flags, and this could lead
51 // to the same bitmap being marked volatile back and forth unnecessarily.
52 if (resource())
53 const_cast<ImageResource*>(resource())->update_volatility();
54}
55
56void ImageLoader::resource_did_load()
57{
58 VERIFY(resource());
59
60 // For 3xx (Redirection) responses, the Location value refers to the preferred target resource for automatically redirecting the request.
61 auto status_code = resource()->status_code();
62 if (status_code.has_value() && *status_code >= 300 && *status_code <= 399) {
63 auto location = resource()->response_headers().get("Location");
64 if (location.has_value()) {
65 if (m_redirects_count > maximum_redirects_allowed) {
66 m_redirects_count = 0;
67 m_loading_state = LoadingState::Failed;
68 if (on_fail)
69 on_fail();
70 return;
71 }
72 m_redirects_count++;
73 load_without_resetting_redirect_counter(resource()->url().complete_url(location.value()));
74 return;
75 }
76 }
77 m_redirects_count = 0;
78
79 if (!resource()->mime_type().starts_with("image/"sv)) {
80 m_loading_state = LoadingState::Failed;
81 if (on_fail)
82 on_fail();
83 return;
84 }
85
86 m_loading_state = LoadingState::Loaded;
87
88 if constexpr (IMAGE_LOADER_DEBUG) {
89 if (!resource()->has_encoded_data()) {
90 dbgln("ImageLoader: Resource did load, no encoded data. URL: {}", resource()->url());
91 } else {
92 dbgln("ImageLoader: Resource did load, has encoded data. URL: {}", resource()->url());
93 }
94 }
95
96 if (resource()->is_animated() && resource()->frame_count() > 1) {
97 m_timer->set_interval(resource()->frame_duration(0));
98 m_timer->on_timeout = [this] { animate(); };
99 m_timer->start();
100 }
101
102 if (on_load)
103 on_load();
104}
105
106void ImageLoader::animate()
107{
108 if (!m_visible_in_viewport)
109 return;
110
111 m_current_frame_index = (m_current_frame_index + 1) % resource()->frame_count();
112 auto current_frame_duration = resource()->frame_duration(m_current_frame_index);
113
114 if (current_frame_duration != m_timer->interval()) {
115 m_timer->restart(current_frame_duration);
116 }
117
118 if (m_current_frame_index == resource()->frame_count() - 1) {
119 ++m_loops_completed;
120 if (m_loops_completed > 0 && m_loops_completed == resource()->loop_count()) {
121 m_timer->stop();
122 }
123 }
124
125 if (on_animate)
126 on_animate();
127}
128
129void ImageLoader::resource_did_fail()
130{
131 dbgln("ImageLoader: Resource did fail. URL: {}", resource()->url());
132 m_loading_state = LoadingState::Failed;
133 if (on_fail)
134 on_fail();
135}
136
137bool ImageLoader::has_image() const
138{
139 if (!resource())
140 return false;
141 return bitmap(0);
142}
143
144unsigned ImageLoader::width() const
145{
146 if (!resource())
147 return 0;
148 return bitmap(0) ? bitmap(0)->width() : 0;
149}
150
151unsigned ImageLoader::height() const
152{
153 if (!resource())
154 return 0;
155 return bitmap(0) ? bitmap(0)->height() : 0;
156}
157
158Gfx::Bitmap const* ImageLoader::bitmap(size_t frame_index) const
159{
160 if (!resource())
161 return nullptr;
162 return resource()->bitmap(frame_index);
163}
164
165}