| 
									
										
										
										
											2023-05-20 16:34:52 +02:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Copyright (c) 2023, Andreas Kling <kling@serenityos.org> | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * SPDX-License-Identifier: BSD-2-Clause | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <LibGfx/Bitmap.h>
 | 
					
						
							| 
									
										
										
										
											2023-12-04 21:40:33 +13:00
										 |  |  | #include <LibWeb/Bindings/MainThreadVM.h>
 | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  | #include <LibWeb/DOM/Document.h>
 | 
					
						
							|  |  |  | #include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
 | 
					
						
							|  |  |  | #include <LibWeb/HTML/BrowsingContext.h>
 | 
					
						
							| 
									
										
										
										
											2023-06-23 03:19:34 +03:00
										 |  |  | #include <LibWeb/HTML/DocumentState.h>
 | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  | #include <LibWeb/HTML/NavigationParams.h>
 | 
					
						
							|  |  |  | #include <LibWeb/HTML/Parser/HTMLParser.h>
 | 
					
						
							| 
									
										
										
										
											2023-06-23 03:19:34 +03:00
										 |  |  | #include <LibWeb/HTML/TraversableNavigable.h>
 | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  | #include <LibWeb/Layout/Viewport.h>
 | 
					
						
							| 
									
										
										
										
											2023-09-19 19:16:50 +02:00
										 |  |  | #include <LibWeb/Page/Page.h>
 | 
					
						
							| 
									
										
										
										
											2024-02-15 15:00:43 +01:00
										 |  |  | #include <LibWeb/Painting/CommandExecutorCPU.h>
 | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  | #include <LibWeb/Painting/PaintContext.h>
 | 
					
						
							| 
									
										
										
										
											2023-08-19 08:38:51 +02:00
										 |  |  | #include <LibWeb/Painting/ViewportPaintable.h>
 | 
					
						
							| 
									
										
										
										
											2023-05-20 16:34:52 +02:00
										 |  |  | #include <LibWeb/SVG/SVGDecodedImageData.h>
 | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  | #include <LibWeb/SVG/SVGSVGElement.h>
 | 
					
						
							| 
									
										
										
										
											2023-05-20 16:34:52 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | namespace Web::SVG { | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-12 20:07:19 +01:00
										 |  |  | JS_DEFINE_ALLOCATOR(SVGDecodedImageData); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  | class SVGDecodedImageData::SVGPageClient final : public PageClient { | 
					
						
							| 
									
										
										
										
											2023-12-04 21:40:33 +13:00
										 |  |  |     JS_CELL(SVGDecodedImageData::SVGPageClient, PageClient); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  | public: | 
					
						
							| 
									
										
										
										
											2023-12-04 21:40:33 +13:00
										 |  |  |     static JS::NonnullGCPtr<SVGPageClient> create(JS::VM& vm, Page& page) | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2023-12-04 21:40:33 +13:00
										 |  |  |         return vm.heap().allocate_without_realm<SVGPageClient>(page); | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     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 {}; } | 
					
						
							| 
									
										
										
										
											2023-05-30 20:52:44 +02:00
										 |  |  |     virtual double device_pixels_per_css_pixel() const override { return 1.0; } | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  |     virtual CSS::PreferredColorScheme preferred_color_scheme() const override { return m_host_page.client().preferred_color_scheme(); } | 
					
						
							|  |  |  |     virtual void request_file(FileRequest) override { } | 
					
						
							| 
									
										
										
										
											2023-12-06 11:49:45 -05:00
										 |  |  |     virtual void paint(DevicePixelRect const&, Gfx::Bitmap&, Web::PaintOptions = {}) override { } | 
					
						
							| 
									
										
										
										
											2024-02-18 19:40:11 +01:00
										 |  |  |     virtual void schedule_repaint() override { } | 
					
						
							| 
									
										
										
										
											2023-12-04 21:40:33 +13:00
										 |  |  | 
 | 
					
						
							|  |  |  | private: | 
					
						
							|  |  |  |     explicit SVGPageClient(Page& host_page) | 
					
						
							|  |  |  |         : m_host_page(host_page) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-11 20:15:39 +13:00
										 |  |  | ErrorOr<JS::NonnullGCPtr<SVGDecodedImageData>> SVGDecodedImageData::create(JS::Realm& realm, JS::NonnullGCPtr<Page> host_page, URL const& url, ByteBuffer data) | 
					
						
							| 
									
										
										
										
											2023-05-20 16:34:52 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2023-12-04 21:40:33 +13:00
										 |  |  |     auto page_client = SVGPageClient::create(Bindings::main_thread_vm(), host_page); | 
					
						
							| 
									
										
										
										
											2023-12-04 21:57:13 +13:00
										 |  |  |     auto page = Page::create(Bindings::main_thread_vm(), *page_client); | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  |     page_client->m_svg_page = page.ptr(); | 
					
						
							| 
									
										
										
										
											2024-02-07 09:03:47 -07:00
										 |  |  |     page->set_top_level_traversable(MUST(Web::HTML::TraversableNavigable::create_a_new_top_level_traversable(*page, nullptr, {}))); | 
					
						
							| 
									
										
										
										
											2023-06-23 03:19:34 +03:00
										 |  |  |     JS::NonnullGCPtr<HTML::Navigable> navigable = page->top_level_traversable(); | 
					
						
							|  |  |  |     auto response = Fetch::Infrastructure::Response::create(navigable->vm()); | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  |     response->url_list().append(url); | 
					
						
							|  |  |  |     HTML::NavigationParams navigation_params { | 
					
						
							|  |  |  |         .id = {}, | 
					
						
							| 
									
										
										
										
											2023-09-21 13:47:19 -06:00
										 |  |  |         .navigable = navigable, | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  |         .request = nullptr, | 
					
						
							|  |  |  |         .response = response, | 
					
						
							| 
									
										
										
										
											2023-09-21 13:47:19 -06:00
										 |  |  |         .fetch_controller = nullptr, | 
					
						
							|  |  |  |         .commit_early_hints = nullptr, | 
					
						
							|  |  |  |         .coop_enforcement_result = HTML::CrossOriginOpenerPolicyEnforcementResult {}, | 
					
						
							|  |  |  |         .reserved_environment = {}, | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  |         .origin = HTML::Origin {}, | 
					
						
							|  |  |  |         .policy_container = HTML::PolicyContainer {}, | 
					
						
							|  |  |  |         .final_sandboxing_flag_set = HTML::SandboxingFlagSet {}, | 
					
						
							|  |  |  |         .cross_origin_opener_policy = HTML::CrossOriginOpenerPolicy {}, | 
					
						
							| 
									
										
										
										
											2023-09-21 13:47:19 -06:00
										 |  |  |         .about_base_url = {}, | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  |     }; | 
					
						
							| 
									
										
										
										
											2023-09-25 05:03:41 +02:00
										 |  |  |     // FIXME: Use Navigable::navigate() instead of manually replacing the navigable's document.
 | 
					
						
							| 
									
										
										
										
											2023-12-03 08:58:43 +13:00
										 |  |  |     auto document = DOM::Document::create_and_initialize(DOM::Document::Type::HTML, "text/html"_string, navigation_params).release_value_but_fixme_should_propagate_errors(); | 
					
						
							| 
									
										
										
										
											2023-06-23 03:19:34 +03:00
										 |  |  |     navigable->set_ongoing_navigation({}); | 
					
						
							| 
									
										
										
										
											2023-09-25 05:03:41 +02:00
										 |  |  |     navigable->active_document()->destroy(); | 
					
						
							| 
									
										
										
										
											2023-06-23 03:19:34 +03:00
										 |  |  |     navigable->active_session_history_entry()->document_state->set_document(document); | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     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::SVGSVGElement>(); | 
					
						
							| 
									
										
										
										
											2023-06-21 19:17:15 +03:00
										 |  |  |     if (!svg_root) | 
					
						
							|  |  |  |         return Error::from_string_literal("SVGDecodedImageData: Invalid SVG input"); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  |     svg_root->remove(); | 
					
						
							|  |  |  |     document->remove_all_children(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     MUST(document->append_child(*svg_root)); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-12 20:07:19 +01:00
										 |  |  |     return realm.heap().allocate<SVGDecodedImageData>(realm, page, page_client, document, *svg_root); | 
					
						
							| 
									
										
										
										
											2023-05-20 16:34:52 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-12 20:07:19 +01:00
										 |  |  | SVGDecodedImageData::SVGDecodedImageData(JS::NonnullGCPtr<Page> page, JS::NonnullGCPtr<SVGPageClient> page_client, JS::NonnullGCPtr<DOM::Document> document, JS::NonnullGCPtr<SVG::SVGSVGElement> root_element) | 
					
						
							|  |  |  |     : m_page(page) | 
					
						
							|  |  |  |     , m_page_client(page_client) | 
					
						
							|  |  |  |     , m_document(document) | 
					
						
							|  |  |  |     , m_root_element(root_element) | 
					
						
							| 
									
										
										
										
											2023-05-20 16:34:52 +02:00
										 |  |  | { | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | SVGDecodedImageData::~SVGDecodedImageData() = default; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-12 20:07:19 +01:00
										 |  |  | void SVGDecodedImageData::visit_edges(Cell::Visitor& visitor) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     Base::visit_edges(visitor); | 
					
						
							|  |  |  |     visitor.visit(m_page); | 
					
						
							|  |  |  |     visitor.visit(m_document); | 
					
						
							|  |  |  |     visitor.visit(m_page_client); | 
					
						
							|  |  |  |     visitor.visit(m_root_element); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 14:45:45 +01:00
										 |  |  | RefPtr<Gfx::Bitmap> SVGDecodedImageData::render(Gfx::IntSize size) const | 
					
						
							| 
									
										
										
										
											2023-05-20 16:34:52 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2023-11-24 14:45:45 +01:00
										 |  |  |     auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, size).release_value_but_fixme_should_propagate_errors(); | 
					
						
							| 
									
										
										
										
											2023-06-23 03:19:34 +03:00
										 |  |  |     VERIFY(m_document->navigable()); | 
					
						
							|  |  |  |     m_document->navigable()->set_viewport_rect({ 0, 0, size.width(), size.height() }); | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  |     m_document->update_layout(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-14 16:36:42 +01:00
										 |  |  |     Painting::CommandList painting_commands; | 
					
						
							|  |  |  |     Painting::RecordingPainter recording_painter(painting_commands); | 
					
						
							| 
									
										
										
										
											2023-10-15 04:27:48 +02:00
										 |  |  |     PaintContext context(recording_painter, m_page_client->palette(), m_page_client->device_pixels_per_css_pixel()); | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-19 09:29:04 +02:00
										 |  |  |     m_document->paintable()->paint_all_phases(context); | 
					
						
							| 
									
										
										
										
											2023-10-15 04:27:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-15 15:00:43 +01:00
										 |  |  |     Painting::CommandExecutorCPU executor { *bitmap }; | 
					
						
							| 
									
										
										
										
											2024-02-14 16:36:42 +01:00
										 |  |  |     painting_commands.execute(executor); | 
					
						
							| 
									
										
										
										
											2023-11-24 14:45:45 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     return bitmap; | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 14:45:45 +01:00
										 |  |  | RefPtr<Gfx::ImmutableBitmap> SVGDecodedImageData::bitmap(size_t, Gfx::IntSize size) const | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  | { | 
					
						
							|  |  |  |     if (size.is_empty()) | 
					
						
							|  |  |  |         return nullptr; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-24 08:34:49 +01:00
										 |  |  |     if (auto it = m_cached_rendered_bitmaps.find(size); it != m_cached_rendered_bitmaps.end()) | 
					
						
							|  |  |  |         return it->value; | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-24 08:34:49 +01:00
										 |  |  |     // Prevent the cache from growing too big.
 | 
					
						
							|  |  |  |     // FIXME: Evict least used entries.
 | 
					
						
							|  |  |  |     if (m_cached_rendered_bitmaps.size() > 10) | 
					
						
							|  |  |  |         m_cached_rendered_bitmaps.remove(m_cached_rendered_bitmaps.begin()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     auto immutable_bitmap = Gfx::ImmutableBitmap::create(*render(size)); | 
					
						
							|  |  |  |     m_cached_rendered_bitmaps.set(size, immutable_bitmap); | 
					
						
							|  |  |  |     return immutable_bitmap; | 
					
						
							| 
									
										
										
										
											2023-05-20 16:34:52 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Optional<CSSPixels> SVGDecodedImageData::intrinsic_width() const | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2023-05-20 17:33:48 +02:00
										 |  |  |     // https://www.w3.org/TR/SVG2/coords.html#SizingSVGInCSS
 | 
					
						
							|  |  |  |     m_document->update_style(); | 
					
						
							|  |  |  |     auto const* root_element_style = m_root_element->computed_css_values(); | 
					
						
							|  |  |  |     VERIFY(root_element_style); | 
					
						
							|  |  |  |     auto const& width_value = root_element_style->size_value(CSS::PropertyID::Width); | 
					
						
							|  |  |  |     if (width_value.is_length() && width_value.length().is_absolute()) | 
					
						
							|  |  |  |         return width_value.length().absolute_length_to_px(); | 
					
						
							|  |  |  |     return {}; | 
					
						
							| 
									
										
										
										
											2023-05-20 16:34:52 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Optional<CSSPixels> SVGDecodedImageData::intrinsic_height() const | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2023-05-20 17:33:48 +02:00
										 |  |  |     // https://www.w3.org/TR/SVG2/coords.html#SizingSVGInCSS
 | 
					
						
							|  |  |  |     m_document->update_style(); | 
					
						
							|  |  |  |     auto const* root_element_style = m_root_element->computed_css_values(); | 
					
						
							|  |  |  |     VERIFY(root_element_style); | 
					
						
							|  |  |  |     auto const& height_value = root_element_style->size_value(CSS::PropertyID::Height); | 
					
						
							|  |  |  |     if (height_value.is_length() && height_value.length().is_absolute()) | 
					
						
							|  |  |  |         return height_value.length().absolute_length_to_px(); | 
					
						
							|  |  |  |     return {}; | 
					
						
							| 
									
										
										
										
											2023-05-20 16:34:52 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-03 17:33:58 -05:00
										 |  |  | Optional<CSSPixelFraction> SVGDecodedImageData::intrinsic_aspect_ratio() const | 
					
						
							| 
									
										
										
										
											2023-05-20 16:34:52 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2023-05-20 17:33:48 +02:00
										 |  |  |     // https://www.w3.org/TR/SVG2/coords.html#SizingSVGInCSS
 | 
					
						
							|  |  |  |     auto width = intrinsic_width(); | 
					
						
							|  |  |  |     auto height = intrinsic_height(); | 
					
						
							| 
									
										
										
										
											2024-01-15 20:26:12 +01:00
										 |  |  |     if (height.has_value() && *height == 0) | 
					
						
							|  |  |  |         return {}; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-20 17:33:48 +02:00
										 |  |  |     if (width.has_value() && height.has_value()) | 
					
						
							| 
									
										
										
										
											2023-09-03 17:33:58 -05:00
										 |  |  |         return *width / *height; | 
					
						
							| 
									
										
										
										
											2023-05-20 17:33:48 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (auto const& viewbox = m_root_element->view_box(); viewbox.has_value()) | 
					
						
							| 
									
										
										
										
											2023-09-03 17:33:58 -05:00
										 |  |  |         return CSSPixels::nearest_value_for(viewbox->width) / CSSPixels::nearest_value_for(viewbox->height); | 
					
						
							| 
									
										
										
										
											2023-05-20 17:33:48 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     return {}; | 
					
						
							| 
									
										
										
										
											2023-05-20 16:34:52 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } |