diff --git a/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp b/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp
index 5da199de4ef..bea78210ce7 100644
--- a/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp
+++ b/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp
@@ -4,6 +4,7 @@
* Copyright (c) 2023, MacDue
* Copyright (c) 2024, Aliaksandr Kalenik
* Copyright (c) 2024, Lucien Fiorini
+ * Copyright (c) 2025, Jelle Raaijmakers
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -26,7 +27,6 @@
#include
#include
#include
-#include
#include
#include
@@ -34,16 +34,18 @@ namespace Web::HTML {
GC_DEFINE_ALLOCATOR(CanvasRenderingContext2D);
-GC::Ref CanvasRenderingContext2D::create(JS::Realm& realm, HTMLCanvasElement& element)
+JS::ThrowCompletionOr> CanvasRenderingContext2D::create(JS::Realm& realm, HTMLCanvasElement& element, JS::Value options)
{
- return realm.create(realm, element);
+ auto context_attributes = TRY(context_attributes_from_options(realm.vm(), options));
+ return realm.create(realm, element, context_attributes);
}
-CanvasRenderingContext2D::CanvasRenderingContext2D(JS::Realm& realm, HTMLCanvasElement& element)
+CanvasRenderingContext2D::CanvasRenderingContext2D(JS::Realm& realm, HTMLCanvasElement& element, CanvasRenderingContext2DSettings context_attributes)
: PlatformObject(realm)
, CanvasPath(static_cast(*this), *this)
, m_element(element)
, m_size(element.bitmap_size_for_canvas())
+ , m_context_attributes(move(context_attributes))
{
}
@@ -61,6 +63,52 @@ void CanvasRenderingContext2D::visit_edges(Cell::Visitor& visitor)
visitor.visit(m_element);
}
+// https://html.spec.whatwg.org/multipage/canvas.html#canvasrenderingcontext2dsettings
+JS::ThrowCompletionOr CanvasRenderingContext2D::context_attributes_from_options(JS::VM& vm, JS::Value value)
+{
+ if (!value.is_nullish() && !value.is_object())
+ return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "CanvasRenderingContext2DSettings");
+
+ CanvasRenderingContext2DSettings settings;
+ if (value.is_nullish())
+ return settings;
+
+ auto& value_object = value.as_object();
+
+ JS::Value alpha = TRY(value_object.get("alpha"_fly_string));
+ settings.alpha = alpha.is_undefined() ? true : alpha.to_boolean();
+
+ JS::Value desynchronized = TRY(value_object.get("desynchronized"_fly_string));
+ settings.desynchronized = desynchronized.is_undefined() ? false : desynchronized.to_boolean();
+
+ JS::Value color_space = TRY(value_object.get("colorSpace"_fly_string));
+ if (!color_space.is_undefined()) {
+ auto color_space_string = TRY(color_space.to_string(vm));
+ if (color_space_string == "srgb"sv)
+ settings.color_space = Bindings::PredefinedColorSpace::Srgb;
+ else if (color_space_string == "display-p3"sv)
+ settings.color_space = Bindings::PredefinedColorSpace::DisplayP3;
+ else
+ return vm.throw_completion(JS::ErrorType::InvalidEnumerationValue, color_space_string, "colorSpace");
+ }
+
+ JS::Value color_type = TRY(value_object.get("colorType"_fly_string));
+ if (!color_type.is_undefined()) {
+ auto color_type_string = TRY(color_type.to_string(vm));
+ if (color_type_string == "unorm8"sv)
+ settings.color_type = Bindings::CanvasColorType::Unorm8;
+ else if (color_type_string == "float16"sv)
+ settings.color_type = Bindings::CanvasColorType::Float16;
+ else
+ return vm.throw_completion(JS::ErrorType::InvalidEnumerationValue, color_type_string, "colorType");
+ }
+
+ JS::Value will_read_frequently = TRY(value_object.get("willReadFrequently"_fly_string));
+ settings.will_read_frequently = will_read_frequently.is_undefined() ? false : will_read_frequently.to_boolean();
+
+ return settings;
+}
+
HTMLCanvasElement& CanvasRenderingContext2D::canvas_element()
{
return *m_element;
@@ -221,6 +269,13 @@ void CanvasRenderingContext2D::allocate_painting_surface_if_needed()
{
if (m_surface || m_size.is_empty())
return;
+
+ // FIXME: implement context attribute .alpha
+ // FIXME: implement context attribute .color_space
+ // FIXME: implement context attribute .color_type
+ // FIXME: implement context attribute .desynchronized
+ // FIXME: implement context attribute .will_read_frequently
+
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);
}
diff --git a/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h b/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h
index d69d7bd8de3..8e459389707 100644
--- a/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h
+++ b/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h
@@ -1,6 +1,7 @@
/*
* Copyright (c) 2020-2024, Andreas Kling
* Copyright (c) 2021-2022, Linus Groh
+ * Copyright (c) 2025, Jelle Raaijmakers
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -8,9 +9,6 @@
#pragma once
#include
-#include
-#include
-#include
#include
#include
#include
@@ -31,11 +29,18 @@
#include
#include
#include
-#include
#include
namespace Web::HTML {
+struct CanvasRenderingContext2DSettings {
+ bool alpha { true };
+ bool desynchronized { false };
+ Bindings::PredefinedColorSpace color_space { Bindings::PredefinedColorSpace::Srgb };
+ Bindings::CanvasColorType color_type { Bindings::CanvasColorType::Unorm8 };
+ bool will_read_frequently { false };
+};
+
class CanvasRenderingContext2D
: public Bindings::PlatformObject
, public CanvasPath
@@ -58,7 +63,7 @@ class CanvasRenderingContext2D
GC_DECLARE_ALLOCATOR(CanvasRenderingContext2D);
public:
- [[nodiscard]] static GC::Ref create(JS::Realm&, HTMLCanvasElement&);
+ static JS::ThrowCompletionOr> create(JS::Realm&, HTMLCanvasElement&, JS::Value options);
virtual ~CanvasRenderingContext2D() override;
virtual void fill_rect(float x, float y, float width, float height) override;
@@ -86,6 +91,8 @@ public:
GC::Ref canvas_for_binding() const;
+ CanvasRenderingContext2DSettings get_context_attributes() const { return m_context_attributes; }
+
virtual GC::Ref measure_text(StringView text) override;
virtual void clip(StringView fill_rule) override;
@@ -128,11 +135,13 @@ public:
void allocate_painting_surface_if_needed();
private:
- explicit CanvasRenderingContext2D(JS::Realm&, HTMLCanvasElement&);
+ CanvasRenderingContext2D(JS::Realm&, HTMLCanvasElement&, CanvasRenderingContext2DSettings);
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
+ static JS::ThrowCompletionOr context_attributes_from_options(JS::VM&, JS::Value);
+
virtual Gfx::Painter* painter_for_canvas_state() override { return painter(); }
virtual Gfx::Path& path_for_canvas_state() override { return path(); }
@@ -165,6 +174,7 @@ private:
Gfx::IntSize m_size;
RefPtr m_surface;
+ CanvasRenderingContext2DSettings m_context_attributes;
};
enum class CanvasImageSourceUsability {
diff --git a/Libraries/LibWeb/HTML/CanvasRenderingContext2D.idl b/Libraries/LibWeb/HTML/CanvasRenderingContext2D.idl
index 54cb2b4663e..f8b80d845b7 100644
--- a/Libraries/LibWeb/HTML/CanvasRenderingContext2D.idl
+++ b/Libraries/LibWeb/HTML/CanvasRenderingContext2D.idl
@@ -64,5 +64,5 @@ CanvasRenderingContext2D includes CanvasPath;
// https://html.spec.whatwg.org/multipage/canvas.html#canvassettings
interface mixin CanvasSettings {
// settings
- [FIXME] CanvasRenderingContext2DSettings getContextAttributes();
+ CanvasRenderingContext2DSettings getContextAttributes();
};
\ No newline at end of file
diff --git a/Libraries/LibWeb/HTML/HTMLCanvasElement.cpp b/Libraries/LibWeb/HTML/HTMLCanvasElement.cpp
index f2ff0427ece..e426899c8db 100644
--- a/Libraries/LibWeb/HTML/HTMLCanvasElement.cpp
+++ b/Libraries/LibWeb/HTML/HTMLCanvasElement.cpp
@@ -208,12 +208,12 @@ void HTMLCanvasElement::adjust_computed_style(CSS::ComputedProperties& style)
style.set_property(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::None)));
}
-HTMLCanvasElement::HasOrCreatedContext HTMLCanvasElement::create_2d_context()
+JS::ThrowCompletionOr HTMLCanvasElement::create_2d_context(JS::Value options)
{
if (!m_context.has())
return m_context.has>() ? HasOrCreatedContext::Yes : HasOrCreatedContext::No;
- m_context = CanvasRenderingContext2D::create(realm(), *this);
+ m_context = TRY(CanvasRenderingContext2D::create(realm(), *this, options));
return HasOrCreatedContext::Yes;
}
@@ -244,7 +244,7 @@ JS::ThrowCompletionOr HTMLCanvasElement::ge
// 3. Run the steps in the cell of the following table whose column header matches this canvas element's canvas context mode and whose row header matches contextId:
// NOTE: See the spec for the full table.
if (type == "2d"sv) {
- if (create_2d_context() == HasOrCreatedContext::Yes)
+ if (TRY(create_2d_context(options)) == HasOrCreatedContext::Yes)
return GC::make_root(*m_context.get>());
return Empty {};
diff --git a/Libraries/LibWeb/HTML/HTMLCanvasElement.h b/Libraries/LibWeb/HTML/HTMLCanvasElement.h
index 49b58d2400b..dae44d9f836 100644
--- a/Libraries/LibWeb/HTML/HTMLCanvasElement.h
+++ b/Libraries/LibWeb/HTML/HTMLCanvasElement.h
@@ -6,7 +6,6 @@
#pragma once
-#include
#include
#include
#include
@@ -30,7 +29,7 @@ public:
No,
Yes,
};
- HasOrCreatedContext create_2d_context();
+ JS::ThrowCompletionOr create_2d_context(JS::Value options);
WebIDL::UnsignedLong width() const;
WebIDL::UnsignedLong height() const;
diff --git a/Libraries/LibWeb/WebDriver/Screenshot.cpp b/Libraries/LibWeb/WebDriver/Screenshot.cpp
index d741351efb1..7d786fe075f 100644
--- a/Libraries/LibWeb/WebDriver/Screenshot.cpp
+++ b/Libraries/LibWeb/WebDriver/Screenshot.cpp
@@ -43,7 +43,7 @@ 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.
- canvas.create_2d_context();
+ MUST(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);