Coverage Report

Created: 2026-02-14 08:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibWeb/SVG/SVGDecodedImageData.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2023, Andreas Kling <kling@serenityos.org>
3
 *
4
 * SPDX-License-Identifier: BSD-2-Clause
5
 */
6
7
#include <LibGfx/Bitmap.h>
8
#include <LibWeb/Bindings/MainThreadVM.h>
9
#include <LibWeb/DOM/Document.h>
10
#include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
11
#include <LibWeb/HTML/BrowsingContext.h>
12
#include <LibWeb/HTML/DocumentState.h>
13
#include <LibWeb/HTML/NavigationParams.h>
14
#include <LibWeb/HTML/Parser/HTMLParser.h>
15
#include <LibWeb/HTML/TraversableNavigable.h>
16
#include <LibWeb/Layout/Viewport.h>
17
#include <LibWeb/Page/Page.h>
18
#include <LibWeb/Painting/DisplayListPlayerCPU.h>
19
#include <LibWeb/Painting/PaintContext.h>
20
#include <LibWeb/Painting/ViewportPaintable.h>
21
#include <LibWeb/SVG/SVGDecodedImageData.h>
22
#include <LibWeb/SVG/SVGSVGElement.h>
23
24
namespace Web::SVG {
25
26
JS_DEFINE_ALLOCATOR(SVGDecodedImageData);
27
JS_DEFINE_ALLOCATOR(SVGDecodedImageData::SVGPageClient);
28
29
ErrorOr<JS::NonnullGCPtr<SVGDecodedImageData>> SVGDecodedImageData::create(JS::Realm& realm, JS::NonnullGCPtr<Page> host_page, URL::URL const& url, ByteBuffer data)
30
0
{
31
0
    auto page_client = SVGPageClient::create(Bindings::main_thread_vm(), host_page);
32
0
    auto page = Page::create(Bindings::main_thread_vm(), *page_client);
33
0
    page_client->m_svg_page = page.ptr();
34
0
    page->set_top_level_traversable(MUST(Web::HTML::TraversableNavigable::create_a_new_top_level_traversable(*page, nullptr, {})));
35
0
    JS::NonnullGCPtr<HTML::Navigable> navigable = page->top_level_traversable();
36
0
    auto response = Fetch::Infrastructure::Response::create(navigable->vm());
37
0
    response->url_list().append(url);
38
0
    auto navigation_params = navigable->heap().allocate_without_realm<HTML::NavigationParams>();
39
0
    navigation_params->navigable = navigable;
40
0
    navigation_params->response = response;
41
0
    navigation_params->origin = URL::Origin {};
42
0
    navigation_params->policy_container = HTML::PolicyContainer {};
43
0
    navigation_params->final_sandboxing_flag_set = HTML::SandboxingFlagSet {};
44
0
    navigation_params->opener_policy = HTML::OpenerPolicy {};
45
46
    // FIXME: Use Navigable::navigate() instead of manually replacing the navigable's document.
47
0
    auto document = DOM::Document::create_and_initialize(DOM::Document::Type::HTML, "text/html"_string, navigation_params).release_value_but_fixme_should_propagate_errors();
48
0
    navigable->set_ongoing_navigation({});
49
0
    navigable->active_document()->destroy();
50
0
    navigable->active_session_history_entry()->document_state()->set_document(document);
51
52
0
    auto parser = HTML::HTMLParser::create_with_uncertain_encoding(document, data);
53
0
    parser->run(document->url());
54
55
    // Perform some DOM surgery to make the SVG root element be the first child of the Document.
56
    // FIXME: This is a huge hack until we figure out how to actually parse separate SVG files.
57
0
    auto* svg_root = document->body()->first_child_of_type<SVG::SVGSVGElement>();
58
0
    if (!svg_root)
59
0
        return Error::from_string_literal("SVGDecodedImageData: Invalid SVG input");
60
61
0
    svg_root->remove();
62
0
    document->remove_all_children();
63
64
0
    MUST(document->append_child(*svg_root));
65
66
0
    return realm.heap().allocate<SVGDecodedImageData>(realm, page, page_client, document, *svg_root);
67
0
}
68
69
SVGDecodedImageData::SVGDecodedImageData(JS::NonnullGCPtr<Page> page, JS::NonnullGCPtr<SVGPageClient> page_client, JS::NonnullGCPtr<DOM::Document> document, JS::NonnullGCPtr<SVG::SVGSVGElement> root_element)
70
0
    : m_page(page)
71
0
    , m_page_client(page_client)
72
0
    , m_document(document)
73
0
    , m_root_element(root_element)
74
0
{
75
0
}
76
77
0
SVGDecodedImageData::~SVGDecodedImageData() = default;
78
79
void SVGDecodedImageData::visit_edges(Cell::Visitor& visitor)
80
0
{
81
0
    Base::visit_edges(visitor);
82
0
    visitor.visit(m_page);
83
0
    visitor.visit(m_document);
84
0
    visitor.visit(m_page_client);
85
0
    visitor.visit(m_root_element);
86
0
}
87
88
RefPtr<Gfx::Bitmap> SVGDecodedImageData::render(Gfx::IntSize size) const
89
0
{
90
0
    auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, size).release_value_but_fixme_should_propagate_errors();
91
0
    VERIFY(m_document->navigable());
92
0
    m_document->navigable()->set_viewport_size(size.to_type<CSSPixels>());
93
0
    m_document->update_layout();
94
95
0
    auto display_list = Painting::DisplayList::create();
96
0
    Painting::DisplayListRecorder display_list_recorder(display_list);
97
98
0
    m_document->navigable()->record_display_list(display_list_recorder, {});
99
100
0
    auto painting_command_executor_type = m_page_client->display_list_player_type();
101
0
    switch (painting_command_executor_type) {
102
0
    case DisplayListPlayerType::CPU:
103
0
    case DisplayListPlayerType::CPUWithExperimentalTransformSupport:
104
0
    case DisplayListPlayerType::GPU: { // GPU painter does not have any path rasterization support so we always fall back to CPU painter
105
0
        Painting::DisplayListPlayerCPU display_list_player { *bitmap };
106
0
        display_list_player.execute(display_list);
107
0
        break;
108
0
    }
109
0
    default:
110
0
        VERIFY_NOT_REACHED();
111
0
    }
112
113
0
    return bitmap;
114
0
}
115
116
RefPtr<Gfx::ImmutableBitmap> SVGDecodedImageData::bitmap(size_t, Gfx::IntSize size) const
117
0
{
118
0
    if (size.is_empty())
119
0
        return nullptr;
120
121
0
    if (auto it = m_cached_rendered_bitmaps.find(size); it != m_cached_rendered_bitmaps.end())
122
0
        return it->value;
123
124
    // Prevent the cache from growing too big.
125
    // FIXME: Evict least used entries.
126
0
    if (m_cached_rendered_bitmaps.size() > 10)
127
0
        m_cached_rendered_bitmaps.remove(m_cached_rendered_bitmaps.begin());
128
129
0
    auto immutable_bitmap = Gfx::ImmutableBitmap::create(*render(size));
130
0
    m_cached_rendered_bitmaps.set(size, immutable_bitmap);
131
0
    return immutable_bitmap;
132
0
}
133
134
Optional<CSSPixels> SVGDecodedImageData::intrinsic_width() const
135
0
{
136
    // https://www.w3.org/TR/SVG2/coords.html#SizingSVGInCSS
137
0
    m_document->update_style();
138
0
    auto const* root_element_style = m_root_element->computed_css_values();
139
0
    VERIFY(root_element_style);
140
0
    auto const& width_value = root_element_style->size_value(CSS::PropertyID::Width);
141
0
    if (width_value.is_length() && width_value.length().is_absolute())
142
0
        return width_value.length().absolute_length_to_px();
143
0
    return {};
144
0
}
145
146
Optional<CSSPixels> SVGDecodedImageData::intrinsic_height() const
147
0
{
148
    // https://www.w3.org/TR/SVG2/coords.html#SizingSVGInCSS
149
0
    m_document->update_style();
150
0
    auto const* root_element_style = m_root_element->computed_css_values();
151
0
    VERIFY(root_element_style);
152
0
    auto const& height_value = root_element_style->size_value(CSS::PropertyID::Height);
153
0
    if (height_value.is_length() && height_value.length().is_absolute())
154
0
        return height_value.length().absolute_length_to_px();
155
0
    return {};
156
0
}
157
158
Optional<CSSPixelFraction> SVGDecodedImageData::intrinsic_aspect_ratio() const
159
0
{
160
    // https://www.w3.org/TR/SVG2/coords.html#SizingSVGInCSS
161
0
    auto width = intrinsic_width();
162
0
    auto height = intrinsic_height();
163
0
    if (height.has_value() && *height == 0)
164
0
        return {};
165
166
0
    if (width.has_value() && height.has_value())
167
0
        return *width / *height;
168
169
0
    if (auto const& viewbox = m_root_element->view_box(); viewbox.has_value()) {
170
0
        auto viewbox_width = CSSPixels::nearest_value_for(viewbox->width);
171
172
0
        if (viewbox_width == 0)
173
0
            return {};
174
175
0
        auto viewbox_height = CSSPixels::nearest_value_for(viewbox->height);
176
0
        if (viewbox_height == 0)
177
0
            return {};
178
179
0
        return viewbox_width / viewbox_height;
180
0
    }
181
0
    return {};
182
0
}
183
184
void SVGDecodedImageData::SVGPageClient::visit_edges(Visitor& visitor)
185
0
{
186
0
    Base::visit_edges(visitor);
187
0
    visitor.visit(m_host_page);
188
0
    visitor.visit(m_svg_page);
189
0
}
190
191
}