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);