diff --git a/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp b/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp index 75768f6a94a..629562a16cc 100644 --- a/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp +++ b/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +42,7 @@ CanvasRenderingContext2D::CanvasRenderingContext2D(JS::Realm& realm, HTMLCanvasE : PlatformObject(realm) , CanvasPath(static_cast(*this), *this) , m_element(element) + , m_size(element.bitmap_size_for_canvas()) { } @@ -198,14 +200,29 @@ void CanvasRenderingContext2D::did_draw(Gfx::FloatRect const&) Gfx::Painter* CanvasRenderingContext2D::painter() { if (!canvas_element().surface()) { - if (!canvas_element().allocate_painting_surface()) - return nullptr; + allocate_painting_surface_if_needed(); canvas_element().document().invalidate_display_list(); m_painter = make(*canvas_element().surface()); } return m_painter.ptr(); } +void CanvasRenderingContext2D::set_size(Gfx::IntSize const& size) +{ + if (m_size == size) + return; + m_size = size; + m_surface = nullptr; +} + +void CanvasRenderingContext2D::allocate_painting_surface_if_needed() +{ + if (m_surface) + return; + auto skia_backend_context = canvas_element().navigable()->traversable_navigable()->skia_backend_context(); + m_surface = Gfx::PaintingSurface::create_with_size(skia_backend_context, canvas_element().bitmap_size_for_canvas(), Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied); +} + Gfx::Path CanvasRenderingContext2D::text_path(StringView text, float x, float y, Optional max_width) { if (max_width.has_value() && max_width.value() <= 0) diff --git a/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h b/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h index 52dfb48994b..858e3cc34cc 100644 --- a/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h +++ b/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h @@ -113,6 +113,11 @@ public: [[nodiscard]] Gfx::Painter* painter(); + void set_size(Gfx::IntSize const&); + + RefPtr surface() { return m_surface; } + void allocate_painting_surface_if_needed(); + private: explicit CanvasRenderingContext2D(JS::Realm&, HTMLCanvasElement&); @@ -148,6 +153,9 @@ private: // https://html.spec.whatwg.org/multipage/canvas.html#concept-canvas-origin-clean bool m_origin_clean { true }; + + Gfx::IntSize m_size; + RefPtr m_surface; }; enum class CanvasImageSourceUsability { diff --git a/Libraries/LibWeb/HTML/HTMLCanvasElement.cpp b/Libraries/LibWeb/HTML/HTMLCanvasElement.cpp index f5bcb806a08..a18dc3ca0bc 100644 --- a/Libraries/LibWeb/HTML/HTMLCanvasElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLCanvasElement.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include namespace Web::HTML { @@ -128,13 +129,27 @@ void HTMLCanvasElement::reset_context_to_default_state() }); } -WebIDL::ExceptionOr HTMLCanvasElement::set_width(WebIDL::UnsignedLong value) +void HTMLCanvasElement::notify_context_about_canvas_size_change() +{ + m_context.visit( + [&](GC::Ref& context) { + context->set_size(bitmap_size_for_canvas()); + }, + [&](GC::Ref&) { + TODO(); + }, + [](Empty) { + // Do nothing. + }); +} + +WebIDL::ExceptionOr HTMLCanvasElement::set_width(unsigned value) { if (value > 2147483647) value = 300; TRY(set_attribute(HTML::AttributeNames::width, String::number(value))); - m_surface = nullptr; + notify_context_about_canvas_size_change(); reset_context_to_default_state(); return {}; } @@ -145,7 +160,7 @@ WebIDL::ExceptionOr HTMLCanvasElement::set_height(WebIDL::UnsignedLong val value = 150; TRY(set_attribute(HTML::AttributeNames::height, String::number(value))); - m_surface = nullptr; + notify_context_about_canvas_size_change(); reset_context_to_default_state(); return {}; } @@ -214,10 +229,10 @@ JS::ThrowCompletionOr HTMLCanvasElement::ge return Empty {}; } -static Gfx::IntSize bitmap_size_for_canvas(HTMLCanvasElement const& canvas, size_t minimum_width, size_t minimum_height) +Gfx::IntSize HTMLCanvasElement::bitmap_size_for_canvas(size_t minimum_width, size_t minimum_height) const { - auto width = max(canvas.width(), minimum_width); - auto height = max(canvas.height(), minimum_height); + auto width = max(this->width(), minimum_width); + auto height = max(this->height(), minimum_height); Checked area = width; area *= height; @@ -233,25 +248,6 @@ static Gfx::IntSize bitmap_size_for_canvas(HTMLCanvasElement const& canvas, size return Gfx::IntSize(width, height); } -bool HTMLCanvasElement::allocate_painting_surface(size_t minimum_width, size_t minimum_height) -{ - if (m_surface) - return true; - - auto traversable = document().navigable()->traversable_navigable(); - VERIFY(traversable); - - auto size = bitmap_size_for_canvas(*this, minimum_width, minimum_height); - if (size.is_empty()) { - m_surface = nullptr; - return false; - } - if (!m_surface || m_surface->size() != size) { - m_surface = Gfx::PaintingSurface::create_with_size(traversable->skia_backend_context(), size, Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied); - } - return m_surface; -} - struct SerializeBitmapResult { ByteBuffer buffer; StringView mime_type; @@ -283,21 +279,26 @@ static ErrorOr serialize_bitmap(Gfx::Bitmap const& bitmap String HTMLCanvasElement::to_data_url(StringView type, JS::Value quality) { // It is possible the the canvas doesn't have a associated bitmap so create one - if (!m_surface) { - allocate_painting_surface(); + allocate_painting_surface_if_needed(); + auto surface = this->surface(); + auto size = bitmap_size_for_canvas(); + if (!size.is_empty()) { + // If the context is not initialized yet, we need to allocate transparent surface for serialization + auto skia_backend_context = navigable()->traversable_navigable()->skia_backend_context(); + surface = Gfx::PaintingSurface::create_with_size(skia_backend_context, size, Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied); } // FIXME: 1. If this canvas element's bitmap's origin-clean flag is set to false, then throw a "SecurityError" DOMException. // 2. If this canvas element's bitmap has no pixels (i.e. either its horizontal dimension or its vertical dimension is zero) // then return the string "data:,". (This is the shortest data: URL; it represents the empty string in a text/plain resource.) - if (!m_surface) + if (!surface) return "data:,"_string; // 3. Let file be a serialization of this canvas element's bitmap as a file, passing type and quality if given. - auto snapshot = Gfx::ImmutableBitmap::create_snapshot_from_painting_surface(*m_surface); - auto bitmap = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied, m_surface->size())); - m_surface->read_into_bitmap(*bitmap); + auto snapshot = Gfx::ImmutableBitmap::create_snapshot_from_painting_surface(*surface); + auto bitmap = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied, surface->size())); + surface->read_into_bitmap(*bitmap); auto file = serialize_bitmap(bitmap, type, move(quality)); // 4. If file is null then return "data:,". @@ -318,8 +319,13 @@ String HTMLCanvasElement::to_data_url(StringView type, JS::Value quality) WebIDL::ExceptionOr HTMLCanvasElement::to_blob(GC::Ref callback, StringView type, JS::Value quality) { // It is possible the the canvas doesn't have a associated bitmap so create one - if (!m_surface) { - allocate_painting_surface(); + allocate_painting_surface_if_needed(); + auto surface = this->surface(); + auto size = bitmap_size_for_canvas(); + if (!size.is_empty()) { + // If the context is not initialized yet, we need to allocate transparent surface for serialization + auto skia_backend_context = navigable()->traversable_navigable()->skia_backend_context(); + surface = Gfx::PaintingSurface::create_with_size(skia_backend_context, size, Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied); } // FIXME: 1. If this canvas element's bitmap's origin-clean flag is set to false, then throw a "SecurityError" DOMException. @@ -329,9 +335,9 @@ WebIDL::ExceptionOr HTMLCanvasElement::to_blob(GC::Refsize())); - m_surface->read_into_bitmap(*bitmap_result); + if (surface) { + bitmap_result = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied, surface->size())); + surface->read_into_bitmap(*bitmap_result); } // 4. Run these steps in parallel: @@ -364,9 +370,8 @@ WebIDL::ExceptionOr HTMLCanvasElement::to_blob(GC::Refflush(); - } + if (auto surface = this->surface()) + surface->flush(); m_context.visit( [](GC::Ref&) { @@ -380,4 +385,32 @@ void HTMLCanvasElement::present() }); } +RefPtr HTMLCanvasElement::surface() const +{ + return m_context.visit( + [&](GC::Ref const& context) { + return context->surface(); + }, + [&](GC::Ref const&) -> RefPtr { + TODO(); + }, + [](Empty) -> RefPtr { + return {}; + }); +} + +void HTMLCanvasElement::allocate_painting_surface_if_needed() +{ + m_context.visit( + [&](GC::Ref& context) { + context->allocate_painting_surface_if_needed(); + }, + [&](GC::Ref&) { + TODO(); + }, + [](Empty) { + // Do nothing. + }); +} + } diff --git a/Libraries/LibWeb/HTML/HTMLCanvasElement.h b/Libraries/LibWeb/HTML/HTMLCanvasElement.h index 730aff74769..ca1ee76fc86 100644 --- a/Libraries/LibWeb/HTML/HTMLCanvasElement.h +++ b/Libraries/LibWeb/HTML/HTMLCanvasElement.h @@ -24,11 +24,14 @@ public: virtual ~HTMLCanvasElement() override; - bool allocate_painting_surface(size_t minimum_width = 0, size_t minimum_height = 0); - RefPtr surface() { return m_surface; } - RefPtr surface() const { return m_surface; } + Gfx::IntSize bitmap_size_for_canvas(size_t minimum_width = 0, size_t minimum_height = 0) const; JS::ThrowCompletionOr get_context(String const& type, JS::Value options); + enum class HasOrCreatedContext { + No, + Yes, + }; + HasOrCreatedContext create_2d_context(); WebIDL::UnsignedLong width() const; WebIDL::UnsignedLong height() const; @@ -41,6 +44,9 @@ public: void present(); + RefPtr surface() const; + void allocate_painting_surface_if_needed(); + private: HTMLCanvasElement(DOM::Document&, DOM::QualifiedName); @@ -52,16 +58,9 @@ private: virtual GC::Ptr create_layout_node(CSS::StyleProperties) override; virtual void adjust_computed_style(CSS::StyleProperties&) override; - enum class HasOrCreatedContext { - No, - Yes, - }; - - HasOrCreatedContext create_2d_context(); JS::ThrowCompletionOr create_webgl_context(JS::Value options); void reset_context_to_default_state(); - - RefPtr m_surface; + void notify_context_about_canvas_size_change(); Variant, GC::Ref, Empty> m_context; }; diff --git a/Libraries/LibWeb/WebDriver/Screenshot.cpp b/Libraries/LibWeb/WebDriver/Screenshot.cpp index 6a69aed4b62..ccdaa34db46 100644 --- a/Libraries/LibWeb/WebDriver/Screenshot.cpp +++ b/Libraries/LibWeb/WebDriver/Screenshot.cpp @@ -42,8 +42,10 @@ ErrorOr, WebDriver::Error> draw_bounding_box_fr MUST(canvas.set_height(paint_height)); // FIXME: 5. Let context, a canvas context mode, be the result of invoking the 2D context creation algorithm given canvas as the target. - if (!canvas.allocate_painting_surface(paint_width, paint_height)) - return Error::from_code(ErrorCode::UnableToCaptureScreen, "Unable to create a screenshot bitmap"sv); + canvas.create_2d_context(); + canvas.allocate_painting_surface_if_needed(); + if (!canvas.surface()) + return Error::from_code(ErrorCode::UnableToCaptureScreen, "Failed to allocate painting surface"sv); // 6. Complete implementation specific steps equivalent to drawing the region of the framebuffer specified by the following coordinates onto context: // - X coordinate: rectangle x coordinate diff --git a/Libraries/LibWeb/WebGL/OpenGLContext.cpp b/Libraries/LibWeb/WebGL/OpenGLContext.cpp index ac3cfff8f7a..d967293015b 100644 --- a/Libraries/LibWeb/WebGL/OpenGLContext.cpp +++ b/Libraries/LibWeb/WebGL/OpenGLContext.cpp @@ -5,15 +5,12 @@ */ #include -#include -#include #include namespace Web::WebGL { -OwnPtr OpenGLContext::create(Gfx::PaintingSurface& bitmap) +OwnPtr OpenGLContext::create() { - (void)bitmap; return {}; } diff --git a/Libraries/LibWeb/WebGL/OpenGLContext.h b/Libraries/LibWeb/WebGL/OpenGLContext.h index 5578b3985a6..6336fb36877 100644 --- a/Libraries/LibWeb/WebGL/OpenGLContext.h +++ b/Libraries/LibWeb/WebGL/OpenGLContext.h @@ -13,7 +13,7 @@ namespace Web::WebGL { class OpenGLContext { public: - static OwnPtr create(Gfx::PaintingSurface&); + static OwnPtr create(); virtual void present() = 0; void clear_buffer_to_default_values(); diff --git a/Libraries/LibWeb/WebGL/WebGLRenderingContext.cpp b/Libraries/LibWeb/WebGL/WebGLRenderingContext.cpp index f53732fd34f..4c304c259ec 100644 --- a/Libraries/LibWeb/WebGL/WebGLRenderingContext.cpp +++ b/Libraries/LibWeb/WebGL/WebGLRenderingContext.cpp @@ -39,14 +39,7 @@ JS::ThrowCompletionOr> WebGLRenderingContext::cre // We should be coming here from getContext being called on a wrapped element. auto context_attributes = TRY(convert_value_to_context_attributes_dictionary(canvas_element.vm(), options)); - bool created_surface = canvas_element.allocate_painting_surface(/* minimum_width= */ 1, /* minimum_height= */ 1); - if (!created_surface) { - fire_webgl_context_creation_error(canvas_element); - return GC::Ptr { nullptr }; - } - - VERIFY(canvas_element.surface()); - auto context = OpenGLContext::create(*canvas_element.surface()); + auto context = OpenGLContext::create(); if (!context) { fire_webgl_context_creation_error(canvas_element);