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