LibWeb: Add CanvasRenderingContext2D context attribute parsing

Makes `context.getContextAttributes()` work.
This commit is contained in:
Jelle Raaijmakers 2025-04-28 16:01:47 +02:00
commit fac0f82031
Notes: github-actions[bot] 2025-04-29 11:52:34 +00:00
6 changed files with 81 additions and 17 deletions

View file

@ -4,6 +4,7 @@
* Copyright (c) 2023, MacDue <macdue@dueutil.tech> * Copyright (c) 2023, MacDue <macdue@dueutil.tech>
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com> * Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
* Copyright (c) 2024, Lucien Fiorini <lucienfiorini@gmail.com> * Copyright (c) 2024, Lucien Fiorini <lucienfiorini@gmail.com>
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -26,7 +27,6 @@
#include <LibWeb/Infra/CharacterTypes.h> #include <LibWeb/Infra/CharacterTypes.h>
#include <LibWeb/Layout/TextNode.h> #include <LibWeb/Layout/TextNode.h>
#include <LibWeb/Painting/Paintable.h> #include <LibWeb/Painting/Paintable.h>
#include <LibWeb/Platform/FontPlugin.h>
#include <LibWeb/SVG/SVGImageElement.h> #include <LibWeb/SVG/SVGImageElement.h>
#include <LibWeb/WebIDL/ExceptionOr.h> #include <LibWeb/WebIDL/ExceptionOr.h>
@ -34,16 +34,18 @@ namespace Web::HTML {
GC_DEFINE_ALLOCATOR(CanvasRenderingContext2D); GC_DEFINE_ALLOCATOR(CanvasRenderingContext2D);
GC::Ref<CanvasRenderingContext2D> CanvasRenderingContext2D::create(JS::Realm& realm, HTMLCanvasElement& element) JS::ThrowCompletionOr<GC::Ref<CanvasRenderingContext2D>> CanvasRenderingContext2D::create(JS::Realm& realm, HTMLCanvasElement& element, JS::Value options)
{ {
return realm.create<CanvasRenderingContext2D>(realm, element); auto context_attributes = TRY(context_attributes_from_options(realm.vm(), options));
return realm.create<CanvasRenderingContext2D>(realm, element, context_attributes);
} }
CanvasRenderingContext2D::CanvasRenderingContext2D(JS::Realm& realm, HTMLCanvasElement& element) CanvasRenderingContext2D::CanvasRenderingContext2D(JS::Realm& realm, HTMLCanvasElement& element, CanvasRenderingContext2DSettings context_attributes)
: PlatformObject(realm) : PlatformObject(realm)
, CanvasPath(static_cast<Bindings::PlatformObject&>(*this), *this) , CanvasPath(static_cast<Bindings::PlatformObject&>(*this), *this)
, m_element(element) , m_element(element)
, m_size(element.bitmap_size_for_canvas()) , 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); visitor.visit(m_element);
} }
// https://html.spec.whatwg.org/multipage/canvas.html#canvasrenderingcontext2dsettings
JS::ThrowCompletionOr<CanvasRenderingContext2DSettings> CanvasRenderingContext2D::context_attributes_from_options(JS::VM& vm, JS::Value value)
{
if (!value.is_nullish() && !value.is_object())
return vm.throw_completion<JS::TypeError>(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::TypeError>(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::TypeError>(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() HTMLCanvasElement& CanvasRenderingContext2D::canvas_element()
{ {
return *m_element; return *m_element;
@ -221,6 +269,13 @@ void CanvasRenderingContext2D::allocate_painting_surface_if_needed()
{ {
if (m_surface || m_size.is_empty()) if (m_surface || m_size.is_empty())
return; 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(); 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); m_surface = Gfx::PaintingSurface::create_with_size(skia_backend_context, canvas_element().bitmap_size_for_canvas(), Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied);
} }

View file

@ -1,6 +1,7 @@
/* /*
* Copyright (c) 2020-2024, Andreas Kling <andreas@ladybird.org> * Copyright (c) 2020-2024, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org> * Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -8,9 +9,6 @@
#pragma once #pragma once
#include <AK/String.h> #include <AK/String.h>
#include <AK/Variant.h>
#include <LibGfx/AffineTransform.h>
#include <LibGfx/Color.h>
#include <LibGfx/Forward.h> #include <LibGfx/Forward.h>
#include <LibGfx/Painter.h> #include <LibGfx/Painter.h>
#include <LibGfx/Path.h> #include <LibGfx/Path.h>
@ -31,11 +29,18 @@
#include <LibWeb/HTML/Canvas/CanvasText.h> #include <LibWeb/HTML/Canvas/CanvasText.h>
#include <LibWeb/HTML/Canvas/CanvasTextDrawingStyles.h> #include <LibWeb/HTML/Canvas/CanvasTextDrawingStyles.h>
#include <LibWeb/HTML/Canvas/CanvasTransform.h> #include <LibWeb/HTML/Canvas/CanvasTransform.h>
#include <LibWeb/HTML/CanvasGradient.h>
#include <LibWeb/WebIDL/ExceptionOr.h> #include <LibWeb/WebIDL/ExceptionOr.h>
namespace Web::HTML { 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 class CanvasRenderingContext2D
: public Bindings::PlatformObject : public Bindings::PlatformObject
, public CanvasPath , public CanvasPath
@ -58,7 +63,7 @@ class CanvasRenderingContext2D
GC_DECLARE_ALLOCATOR(CanvasRenderingContext2D); GC_DECLARE_ALLOCATOR(CanvasRenderingContext2D);
public: public:
[[nodiscard]] static GC::Ref<CanvasRenderingContext2D> create(JS::Realm&, HTMLCanvasElement&); static JS::ThrowCompletionOr<GC::Ref<CanvasRenderingContext2D>> create(JS::Realm&, HTMLCanvasElement&, JS::Value options);
virtual ~CanvasRenderingContext2D() override; virtual ~CanvasRenderingContext2D() override;
virtual void fill_rect(float x, float y, float width, float height) override; virtual void fill_rect(float x, float y, float width, float height) override;
@ -86,6 +91,8 @@ public:
GC::Ref<HTMLCanvasElement> canvas_for_binding() const; GC::Ref<HTMLCanvasElement> canvas_for_binding() const;
CanvasRenderingContext2DSettings get_context_attributes() const { return m_context_attributes; }
virtual GC::Ref<TextMetrics> measure_text(StringView text) override; virtual GC::Ref<TextMetrics> measure_text(StringView text) override;
virtual void clip(StringView fill_rule) override; virtual void clip(StringView fill_rule) override;
@ -128,11 +135,13 @@ public:
void allocate_painting_surface_if_needed(); void allocate_painting_surface_if_needed();
private: private:
explicit CanvasRenderingContext2D(JS::Realm&, HTMLCanvasElement&); CanvasRenderingContext2D(JS::Realm&, HTMLCanvasElement&, CanvasRenderingContext2DSettings);
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override; virtual void visit_edges(Cell::Visitor&) override;
static JS::ThrowCompletionOr<CanvasRenderingContext2DSettings> context_attributes_from_options(JS::VM&, JS::Value);
virtual Gfx::Painter* painter_for_canvas_state() override { return painter(); } virtual Gfx::Painter* painter_for_canvas_state() override { return painter(); }
virtual Gfx::Path& path_for_canvas_state() override { return path(); } virtual Gfx::Path& path_for_canvas_state() override { return path(); }
@ -165,6 +174,7 @@ private:
Gfx::IntSize m_size; Gfx::IntSize m_size;
RefPtr<Gfx::PaintingSurface> m_surface; RefPtr<Gfx::PaintingSurface> m_surface;
CanvasRenderingContext2DSettings m_context_attributes;
}; };
enum class CanvasImageSourceUsability { enum class CanvasImageSourceUsability {

View file

@ -64,5 +64,5 @@ CanvasRenderingContext2D includes CanvasPath;
// https://html.spec.whatwg.org/multipage/canvas.html#canvassettings // https://html.spec.whatwg.org/multipage/canvas.html#canvassettings
interface mixin CanvasSettings { interface mixin CanvasSettings {
// settings // settings
[FIXME] CanvasRenderingContext2DSettings getContextAttributes(); CanvasRenderingContext2DSettings getContextAttributes();
}; };

View file

@ -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))); 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::HasOrCreatedContext> HTMLCanvasElement::create_2d_context(JS::Value options)
{ {
if (!m_context.has<Empty>()) if (!m_context.has<Empty>())
return m_context.has<GC::Ref<CanvasRenderingContext2D>>() ? HasOrCreatedContext::Yes : HasOrCreatedContext::No; return m_context.has<GC::Ref<CanvasRenderingContext2D>>() ? HasOrCreatedContext::Yes : HasOrCreatedContext::No;
m_context = CanvasRenderingContext2D::create(realm(), *this); m_context = TRY(CanvasRenderingContext2D::create(realm(), *this, options));
return HasOrCreatedContext::Yes; return HasOrCreatedContext::Yes;
} }
@ -244,7 +244,7 @@ JS::ThrowCompletionOr<HTMLCanvasElement::RenderingContext> 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: // 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. // NOTE: See the spec for the full table.
if (type == "2d"sv) { 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<GC::Ref<HTML::CanvasRenderingContext2D>>()); return GC::make_root(*m_context.get<GC::Ref<HTML::CanvasRenderingContext2D>>());
return Empty {}; return Empty {};

View file

@ -6,7 +6,6 @@
#pragma once #pragma once
#include <AK/ByteBuffer.h>
#include <LibGfx/Forward.h> #include <LibGfx/Forward.h>
#include <LibGfx/PaintingSurface.h> #include <LibGfx/PaintingSurface.h>
#include <LibWeb/HTML/HTMLElement.h> #include <LibWeb/HTML/HTMLElement.h>
@ -30,7 +29,7 @@ public:
No, No,
Yes, Yes,
}; };
HasOrCreatedContext create_2d_context(); JS::ThrowCompletionOr<HasOrCreatedContext> create_2d_context(JS::Value options);
WebIDL::UnsignedLong width() const; WebIDL::UnsignedLong width() const;
WebIDL::UnsignedLong height() const; WebIDL::UnsignedLong height() const;

View file

@ -43,7 +43,7 @@ ErrorOr<GC::Ref<HTML::HTMLCanvasElement>, WebDriver::Error> draw_bounding_box_fr
MUST(canvas.set_height(paint_height)); 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. // 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(); canvas.allocate_painting_surface_if_needed();
if (!canvas.surface()) if (!canvas.surface())
return Error::from_code(ErrorCode::UnableToCaptureScreen, "Failed to allocate painting surface"sv); return Error::from_code(ErrorCode::UnableToCaptureScreen, "Failed to allocate painting surface"sv);