From 41ab0837fa50f8fe32d727e447672fefb37a9434 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Sat, 20 May 2023 17:19:11 +0200 Subject: LibWeb: Render SVG-as-image into an isolated top-level browsing context In order to separate the SVG content from the rest of the engine, it gets its very own Page, PageClient, top-level browsing context, etc. Unfortunately, we do have to get the palette and CSS/device pixel ratios from the host Page for now, maybe that's something we could refactor in the future. Note that this doesn't work visually yet, since we don't calculate the intrinsic sizes & ratio for SVG images. That comes next. :^) --- .../Libraries/LibWeb/HTML/HTMLImageElement.cpp | 44 ++++++--- .../Libraries/LibWeb/SVG/SVGDecodedImageData.cpp | 100 +++++++++++++++++++-- .../Libraries/LibWeb/SVG/SVGDecodedImageData.h | 14 ++- 3 files changed, 138 insertions(+), 20 deletions(-) (limited to 'Userland/Libraries/LibWeb') diff --git a/Userland/Libraries/LibWeb/HTML/HTMLImageElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLImageElement.cpp index 56537cd7ed..02b1b5b8da 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLImageElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLImageElement.cpp @@ -29,6 +29,7 @@ #include #include #include +#include namespace Web::HTML { @@ -545,21 +546,38 @@ void HTMLImageElement::handle_successful_fetch(AK::URL const& url_string, ImageR m_load_event_delayer.clear(); }; - auto result = Web::Platform::ImageCodecPlugin::the().decode_image(data.bytes()); - if (!result.has_value()) { - dispatch_event(DOM::Event::create(realm(), HTML::EventNames::error).release_value_but_fixme_should_propagate_errors()); - return; - } + // FIXME: Look at the MIME type instead! + bool is_svg_image = url_string.basename().ends_with(".svg"sv); - Vector frames; - for (auto& frame : result.value().frames) { - frames.append(AnimatedBitmapDecodedImageData::Frame { - .bitmap = frame.bitmap, - .duration = static_cast(frame.duration), - }); + RefPtr image_data; + + if (is_svg_image) { + VERIFY(document().page()); + auto result = SVG::SVGDecodedImageData::create(*document().page(), url_string, data); + if (result.is_error()) { + dbgln("Failed to decode SVG image: {}", result.error()); + dispatch_event(DOM::Event::create(realm(), HTML::EventNames::error).release_value_but_fixme_should_propagate_errors()); + return; + } + + image_data = result.release_value(); + } else { + auto result = Web::Platform::ImageCodecPlugin::the().decode_image(data.bytes()); + if (!result.has_value()) { + dispatch_event(DOM::Event::create(realm(), HTML::EventNames::error).release_value_but_fixme_should_propagate_errors()); + return; + } + + Vector frames; + for (auto& frame : result.value().frames) { + frames.append(AnimatedBitmapDecodedImageData::Frame { + .bitmap = frame.bitmap, + .duration = static_cast(frame.duration), + }); + } + image_data = AnimatedBitmapDecodedImageData::create(move(frames), result.value().loop_count, result.value().is_animated).release_value_but_fixme_should_propagate_errors(); } - auto image_data = AnimatedBitmapDecodedImageData::create(move(frames), result.value().loop_count, result.value().is_animated).release_value_but_fixme_should_propagate_errors(); image_request.set_image_data(image_data); ListOfAvailableImages::Key key; @@ -580,7 +598,7 @@ void HTMLImageElement::handle_successful_fetch(AK::URL const& url_string, ImageR image_request.set_state(ImageRequest::State::CompletelyAvailable); // 3. Add the image to the list of available images using the key key, with the ignore higher-layer caching flag set. - document().list_of_available_images().add(key, image_data, true).release_value_but_fixme_should_propagate_errors(); + document().list_of_available_images().add(key, *image_data, true).release_value_but_fixme_should_propagate_errors(); // 4. Fire an event named load at the img element. dispatch_event(DOM::Event::create(realm(), HTML::EventNames::load).release_value_but_fixme_should_propagate_errors()); diff --git a/Userland/Libraries/LibWeb/SVG/SVGDecodedImageData.cpp b/Userland/Libraries/LibWeb/SVG/SVGDecodedImageData.cpp index bda9e99561..f2e0074e78 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGDecodedImageData.cpp +++ b/Userland/Libraries/LibWeb/SVG/SVGDecodedImageData.cpp @@ -5,24 +5,114 @@ */ #include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include namespace Web::SVG { -ErrorOr> SVGDecodedImageData::create(ByteBuffer) +class SVGDecodedImageData::SVGPageClient final : public PageClient { +public: + explicit SVGPageClient(Page& host_page) + : m_host_page(host_page) + { + } + + virtual ~SVGPageClient() override = default; + + Page& m_host_page; + Page* m_svg_page { nullptr }; + + virtual Page& page() override { return *m_svg_page; } + virtual Page const& page() const override { return *m_svg_page; } + virtual bool is_connection_open() const override { return false; } + virtual Gfx::Palette palette() const override { return m_host_page.client().palette(); } + virtual DevicePixelRect screen_rect() const override { return {}; } + virtual float device_pixels_per_css_pixel() const override { return m_host_page.client().device_pixels_per_css_pixel(); } + virtual CSS::PreferredColorScheme preferred_color_scheme() const override { return m_host_page.client().preferred_color_scheme(); } + virtual void request_file(FileRequest) override { } + virtual void paint(DevicePixelRect const&, Gfx::Bitmap&) override { } +}; + +ErrorOr> SVGDecodedImageData::create(Page& host_page, AK::URL const& url, ByteBuffer data) { - return adopt_nonnull_ref_or_enomem(new (nothrow) SVGDecodedImageData()); + auto page_client = make(host_page); + auto page = make(*page_client); + page_client->m_svg_page = page.ptr(); + auto browsing_context = HTML::BrowsingContext::create_a_new_top_level_browsing_context(*page); + auto response = Fetch::Infrastructure::Response::create(browsing_context->vm()); + response->url_list().append(url); + HTML::NavigationParams navigation_params { + .id = {}, + .request = nullptr, + .response = response, + .origin = HTML::Origin {}, + .policy_container = HTML::PolicyContainer {}, + .final_sandboxing_flag_set = HTML::SandboxingFlagSet {}, + .cross_origin_opener_policy = HTML::CrossOriginOpenerPolicy {}, + .coop_enforcement_result = HTML::CrossOriginOpenerPolicyEnforcementResult {}, + .reserved_environment = {}, + .browsing_context = browsing_context, + .navigable = nullptr, + }; + auto document = DOM::Document::create_and_initialize(DOM::Document::Type::HTML, "text/html", move(navigation_params)).release_value_but_fixme_should_propagate_errors(); + browsing_context->set_active_document(document); + + auto parser = HTML::HTMLParser::create_with_uncertain_encoding(document, data); + parser->run(document->url()); + + // Perform some DOM surgery to make the SVG root element be the first child of the Document. + // FIXME: This is a huge hack until we figure out how to actually parse separate SVG files. + auto* svg_root = document->body()->first_child_of_type(); + svg_root->remove(); + document->remove_all_children(); + + MUST(document->append_child(*svg_root)); + + return adopt_nonnull_ref_or_enomem(new (nothrow) SVGDecodedImageData(move(page), move(page_client), move(document), move(svg_root))); } -SVGDecodedImageData::SVGDecodedImageData() +SVGDecodedImageData::SVGDecodedImageData(NonnullOwnPtr page, NonnullOwnPtr page_client, JS::Handle document, JS::Handle root_element) + : m_page(move(page)) + , m_page_client(move(page_client)) + , m_document(move(document)) + , m_root_element(move(root_element)) { } SVGDecodedImageData::~SVGDecodedImageData() = default; -RefPtr SVGDecodedImageData::bitmap(size_t, Gfx::IntSize) const +void SVGDecodedImageData::render(Gfx::IntSize size) const { - return nullptr; + m_document->browsing_context()->set_viewport_rect({ 0, 0, size.width(), size.height() }); + m_document->update_layout(); + + dump_tree(*m_document->layout_node()); + + Gfx::Painter painter(*m_bitmap); + PaintContext context(painter, m_page_client->palette(), m_page_client->device_pixels_per_css_pixel()); + + m_document->layout_node()->paint_all_phases(context); +} + +RefPtr SVGDecodedImageData::bitmap(size_t, Gfx::IntSize size) const +{ + if (size.is_empty()) + return nullptr; + + if (m_bitmap && m_bitmap->size() == size) + return m_bitmap; + + m_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, size).release_value_but_fixme_should_propagate_errors(); + render(size); + return m_bitmap; } Optional SVGDecodedImageData::intrinsic_width() const diff --git a/Userland/Libraries/LibWeb/SVG/SVGDecodedImageData.h b/Userland/Libraries/LibWeb/SVG/SVGDecodedImageData.h index 1adc482684..355e954614 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGDecodedImageData.h +++ b/Userland/Libraries/LibWeb/SVG/SVGDecodedImageData.h @@ -12,7 +12,7 @@ namespace Web::SVG { class SVGDecodedImageData final : public HTML::DecodedImageData { public: - static ErrorOr> create(ByteBuffer encoded_svg); + static ErrorOr> create(Page&, AK::URL const&, ByteBuffer encoded_svg); virtual ~SVGDecodedImageData() override; virtual RefPtr bitmap(size_t frame_index, Gfx::IntSize) const override; @@ -28,7 +28,17 @@ public: virtual bool is_animated() const override { return false; } private: - SVGDecodedImageData(); + class SVGPageClient; + SVGDecodedImageData(NonnullOwnPtr, NonnullOwnPtr, JS::Handle, JS::Handle); + + void render(Gfx::IntSize) const; + mutable RefPtr m_bitmap; + + NonnullOwnPtr m_page; + NonnullOwnPtr m_page_client; + + JS::Handle m_document; + JS::Handle m_root_element; }; } -- cgit v1.2.3