2020-03-19 19:07:56 +01:00
/*
2024-10-04 13:19:50 +02:00
* Copyright ( c ) 2018 - 2020 , Andreas Kling < andreas @ ladybird . org >
2020-03-19 19:07:56 +01:00
*
2021-04-22 01:24:48 -07:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-03-19 19:07:56 +01:00
*/
2021-04-19 23:47:29 +02:00
# include <AK/Base64.h>
2020-04-15 16:55:36 +02:00
# include <AK/Checked.h>
2023-08-03 10:26:13 +02:00
# include <AK/MemoryStream.h>
2020-03-19 19:07:56 +01:00
# include <LibGfx/Bitmap.h>
2023-08-03 10:26:13 +02:00
# include <LibGfx/ImageFormats/JPEGWriter.h>
2023-03-21 14:58:06 -04:00
# include <LibGfx/ImageFormats/PNGWriter.h>
2023-08-03 01:29:31 +02:00
# include <LibWeb/Bindings/ExceptionOrUtils.h>
2024-04-27 12:09:58 +12:00
# include <LibWeb/Bindings/HTMLCanvasElementPrototype.h>
2025-02-18 09:19:56 +01:00
# include <LibWeb/CSS/ComputedProperties.h>
2021-09-24 13:49:57 +02:00
# include <LibWeb/CSS/StyleComputer.h>
2024-08-14 11:46:56 +01:00
# include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
2024-11-08 20:14:37 +08:00
# include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
2023-08-31 19:22:17 +02:00
# include <LibWeb/CSS/StyleValues/RatioStyleValue.h>
# include <LibWeb/CSS/StyleValues/StyleValueList.h>
2020-03-19 19:07:56 +01:00
# include <LibWeb/DOM/Document.h>
2020-07-26 15:08:16 +02:00
# include <LibWeb/HTML/CanvasRenderingContext2D.h>
# include <LibWeb/HTML/HTMLCanvasElement.h>
2023-08-31 22:12:14 +02:00
# include <LibWeb/HTML/Numbers.h>
2023-08-03 01:29:31 +02:00
# include <LibWeb/HTML/Scripting/ExceptionReporter.h>
2024-09-25 15:44:58 +02:00
# include <LibWeb/HTML/TraversableNavigable.h>
2020-11-22 15:53:01 +01:00
# include <LibWeb/Layout/CanvasBox.h>
2023-08-03 01:29:31 +02:00
# include <LibWeb/Platform/EventLoopPlugin.h>
2024-12-05 20:56:18 -07:00
# include <LibWeb/WebGL/WebGL2RenderingContext.h>
2024-11-29 20:17:25 +01:00
# include <LibWeb/WebGL/WebGLRenderingContext.h>
2023-08-03 01:29:31 +02:00
# include <LibWeb/WebIDL/AbstractOperations.h>
2020-03-19 19:07:56 +01:00
2020-07-28 18:20:36 +02:00
namespace Web : : HTML {
2020-03-19 19:07:56 +01:00
2024-11-15 04:01:23 +13:00
GC_DEFINE_ALLOCATOR ( HTMLCanvasElement ) ;
2023-11-19 19:47:52 +01:00
2020-04-15 12:12:19 +02:00
static constexpr auto max_canvas_area = 16384 * 16384 ;
2022-02-18 21:00:52 +01:00
HTMLCanvasElement : : HTMLCanvasElement ( DOM : : Document & document , DOM : : QualifiedName qualified_name )
2021-02-07 11:20:15 +01:00
: HTMLElement ( document , move ( qualified_name ) )
2020-03-19 19:07:56 +01:00
{
}
2022-03-14 13:21:51 -06:00
HTMLCanvasElement : : ~ HTMLCanvasElement ( ) = default ;
2020-03-19 19:07:56 +01:00
2023-08-07 08:41:28 +02:00
void HTMLCanvasElement : : initialize ( JS : : Realm & realm )
2023-01-10 06:28:20 -05:00
{
2024-03-16 13:13:08 +01:00
WEB_SET_PROTOTYPE_FOR_INTERFACE ( HTMLCanvasElement ) ;
2025-04-20 16:22:57 +02:00
Base : : initialize ( realm ) ;
2023-01-10 06:28:20 -05:00
}
2022-09-02 15:53:02 +02:00
void HTMLCanvasElement : : visit_edges ( Cell : : Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
m_context . visit (
2024-11-15 04:01:23 +13:00
[ & ] ( GC : : Ref < CanvasRenderingContext2D > & context ) {
2023-11-19 16:18:00 +13:00
visitor . visit ( context ) ;
2022-09-02 15:53:02 +02:00
} ,
2024-11-15 04:01:23 +13:00
[ & ] ( GC : : Ref < WebGL : : WebGLRenderingContext > & context ) {
2023-11-19 16:18:00 +13:00
visitor . visit ( context ) ;
2022-09-02 15:53:02 +02:00
} ,
2024-12-05 20:56:18 -07:00
[ & ] ( GC : : Ref < WebGL : : WebGL2RenderingContext > & context ) {
visitor . visit ( context ) ;
} ,
2022-09-02 15:53:02 +02:00
[ ] ( Empty ) {
} ) ;
}
2024-12-23 17:51:10 +01:00
bool HTMLCanvasElement : : is_presentational_hint ( FlyString const & name ) const
{
if ( Base : : is_presentational_hint ( name ) )
return true ;
return first_is_one_of ( name ,
HTML : : AttributeNames : : width ,
HTML : : AttributeNames : : height ) ;
}
LibWeb: Split StyleComputer work into two phases with separate outputs
Before this change, StyleComputer would essentially take a DOM element,
find all the CSS rules that apply to it, and resolve the computed value
for each CSS property for that element.
This worked great, but it meant we had to do all the work of selector
matching and cascading every time.
To enable new optimizations, this change introduces a break in the
middle of this process where we've produced a "CascadedProperties".
This object contains the result of the cascade, before we've begun
turning cascaded values into computed values.
The cascaded properties are now stored with each element, which will
later allow us to do partial updates without re-running the full
StyleComputer machine. This will be particularly valuable for
re-implementing CSS inheritance, which is extremely heavy today.
Note that CSS animations and CSS transitions operate entirely on the
computed values, even though the cascade order would have you believe
they happen earlier. I'm not confident we have the right architecture
for this, but that's a separate issue.
2024-12-12 10:06:29 +01:00
void HTMLCanvasElement : : apply_presentational_hints ( GC : : Ref < CSS : : CascadedProperties > cascaded_properties ) const
2023-08-31 19:22:17 +02:00
{
// https://html.spec.whatwg.org/multipage/rendering.html#attributes-for-embedded-content-and-images
// The width and height attributes map to the aspect-ratio property on canvas elements.
// FIXME: Multiple elements have aspect-ratio presentational hints, make this into a helper function
// https://html.spec.whatwg.org/multipage/rendering.html#map-to-the-aspect-ratio-property
// if element has both attributes w and h, and parsing those attributes' values using the rules for parsing non-negative integers doesn't generate an error for either
2024-01-16 19:04:45 +01:00
auto w = parse_non_negative_integer ( get_attribute_value ( HTML : : AttributeNames : : width ) ) ;
auto h = parse_non_negative_integer ( get_attribute_value ( HTML : : AttributeNames : : height ) ) ;
2023-08-31 19:22:17 +02:00
if ( w . has_value ( ) & & h . has_value ( ) )
// then the user agent is expected to use the parsed integers as a presentational hint for the 'aspect-ratio' property of the form auto w / h.
LibWeb: Split StyleComputer work into two phases with separate outputs
Before this change, StyleComputer would essentially take a DOM element,
find all the CSS rules that apply to it, and resolve the computed value
for each CSS property for that element.
This worked great, but it meant we had to do all the work of selector
matching and cascading every time.
To enable new optimizations, this change introduces a break in the
middle of this process where we've produced a "CascadedProperties".
This object contains the result of the cascade, before we've begun
turning cascaded values into computed values.
The cascaded properties are now stored with each element, which will
later allow us to do partial updates without re-running the full
StyleComputer machine. This will be particularly valuable for
re-implementing CSS inheritance, which is extremely heavy today.
Note that CSS animations and CSS transitions operate entirely on the
computed values, even though the cascade order would have you believe
they happen earlier. I'm not confident we have the right architecture
for this, but that's a separate issue.
2024-12-12 10:06:29 +01:00
cascaded_properties - > set_property_from_presentational_hint ( CSS : : PropertyID : : AspectRatio ,
2023-08-31 19:22:17 +02:00
CSS : : StyleValueList : : create ( CSS : : StyleValueVector {
2024-08-14 14:06:03 +01:00
CSS : : CSSKeywordValue : : create ( CSS : : Keyword : : Auto ) ,
2023-08-31 19:22:17 +02:00
CSS : : RatioStyleValue : : create ( CSS : : Ratio { static_cast < double > ( w . value ( ) ) , static_cast < double > ( h . value ( ) ) } ) } ,
CSS : : StyleValueList : : Separator : : Space ) ) ;
}
2024-11-29 16:04:35 +00:00
// https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-width
WebIDL : : UnsignedLong HTMLCanvasElement : : width ( ) const
2020-03-19 19:07:56 +01:00
{
2024-11-29 16:04:35 +00:00
// The width and height IDL attributes must reflect the respective content attributes of the same name, with the same defaults.
2023-08-31 22:12:14 +02:00
// https://html.spec.whatwg.org/multipage/canvas.html#obtain-numeric-values
// The rules for parsing non-negative integers must be used to obtain their numeric values.
// If an attribute is missing, or if parsing its value returns an error, then the default value must be used instead.
// The width attribute defaults to 300
2024-11-29 16:04:35 +00:00
if ( auto width_string = get_attribute ( HTML : : AttributeNames : : width ) ; width_string . has_value ( ) ) {
if ( auto width = parse_non_negative_integer ( * width_string ) ; width . has_value ( ) & & * width < = 2147483647 )
return * width ;
}
return 300 ;
2020-03-19 19:07:56 +01:00
}
2024-11-29 16:04:35 +00:00
// https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-height
WebIDL : : UnsignedLong HTMLCanvasElement : : height ( ) const
2020-03-19 19:07:56 +01:00
{
2024-11-29 16:04:35 +00:00
// The width and height IDL attributes must reflect the respective content attributes of the same name, with the same defaults.
2023-08-31 22:12:14 +02:00
// https://html.spec.whatwg.org/multipage/canvas.html#obtain-numeric-values
// The rules for parsing non-negative integers must be used to obtain their numeric values.
// If an attribute is missing, or if parsing its value returns an error, then the default value must be used instead.
// the height attribute defaults to 150
2024-11-29 16:04:35 +00:00
if ( auto height_string = get_attribute ( HTML : : AttributeNames : : height ) ; height_string . has_value ( ) ) {
if ( auto height = parse_non_negative_integer ( * height_string ) ; height . has_value ( ) & & * height < = 2147483647 )
return * height ;
}
return 150 ;
2020-03-19 19:07:56 +01:00
}
2022-06-04 04:22:42 +01:00
void HTMLCanvasElement : : reset_context_to_default_state ( )
{
m_context . visit (
2024-11-15 04:01:23 +13:00
[ ] ( GC : : Ref < CanvasRenderingContext2D > & context ) {
2022-06-04 04:22:42 +01:00
context - > reset_to_default_state ( ) ;
} ,
2024-11-29 22:15:02 +01:00
[ ] ( GC : : Ref < WebGL : : WebGLRenderingContext > & context ) {
context - > reset_to_default_state ( ) ;
2022-06-04 04:22:42 +01:00
} ,
2024-12-05 20:56:18 -07:00
[ ] ( GC : : Ref < WebGL : : WebGL2RenderingContext > & context ) {
context - > reset_to_default_state ( ) ;
} ,
2022-06-04 04:22:42 +01:00
[ ] ( Empty ) {
// Do nothing.
} ) ;
}
2024-11-29 20:17:25 +01:00
void HTMLCanvasElement : : notify_context_about_canvas_size_change ( )
{
m_context . visit (
[ & ] ( GC : : Ref < CanvasRenderingContext2D > & context ) {
context - > set_size ( bitmap_size_for_canvas ( ) ) ;
} ,
2024-11-29 22:15:02 +01:00
[ & ] ( GC : : Ref < WebGL : : WebGLRenderingContext > & context ) {
context - > set_size ( bitmap_size_for_canvas ( ) ) ;
2024-11-29 20:17:25 +01:00
} ,
2024-12-05 20:56:18 -07:00
[ & ] ( GC : : Ref < WebGL : : WebGL2RenderingContext > & context ) {
context - > set_size ( bitmap_size_for_canvas ( ) ) ;
} ,
2024-11-29 20:17:25 +01:00
[ ] ( Empty ) {
// Do nothing.
} ) ;
}
WebIDL : : ExceptionOr < void > HTMLCanvasElement : : set_width ( unsigned value )
2021-11-13 00:54:21 +01:00
{
2024-11-29 16:04:35 +00:00
if ( value > 2147483647 )
value = 300 ;
2024-10-14 10:05:01 +02:00
TRY ( set_attribute ( HTML : : AttributeNames : : width , String : : number ( value ) ) ) ;
2024-11-29 20:17:25 +01:00
notify_context_about_canvas_size_change ( ) ;
2022-06-04 04:22:42 +01:00
reset_context_to_default_state ( ) ;
2023-05-25 20:37:57 +01:00
return { } ;
2021-11-13 00:54:21 +01:00
}
2024-11-29 16:04:35 +00:00
WebIDL : : ExceptionOr < void > HTMLCanvasElement : : set_height ( WebIDL : : UnsignedLong value )
2021-11-13 00:54:21 +01:00
{
2024-11-29 16:04:35 +00:00
if ( value > 2147483647 )
value = 150 ;
2024-10-14 10:05:01 +02:00
TRY ( set_attribute ( HTML : : AttributeNames : : height , String : : number ( value ) ) ) ;
2024-11-29 20:17:25 +01:00
notify_context_about_canvas_size_change ( ) ;
2022-06-04 04:22:42 +01:00
reset_context_to_default_state ( ) ;
2023-05-25 20:37:57 +01:00
return { } ;
2021-11-13 00:54:21 +01:00
}
2024-12-28 15:47:58 +11:00
void HTMLCanvasElement : : attribute_changed ( FlyString const & local_name , Optional < String > const & old_value , Optional < String > const & value , Optional < FlyString > const & namespace_ )
{
Base : : attribute_changed ( local_name , old_value , value , namespace_ ) ;
2025-06-04 14:34:04 +01:00
if ( local_name . is_one_of ( HTML : : AttributeNames : : width , HTML : : AttributeNames : : height ) ) {
2024-12-28 15:47:58 +11:00
notify_context_about_canvas_size_change ( ) ;
reset_context_to_default_state ( ) ;
2025-05-19 11:04:13 +02:00
if ( auto layout_node = this - > layout_node ( ) )
layout_node - > set_needs_layout_update ( DOM : : SetNeedsLayoutReason : : HTMLCanvasElementWidthOrHeightChange ) ;
2024-12-28 15:47:58 +11:00
}
}
2024-12-20 16:35:12 +01:00
GC : : Ptr < Layout : : Node > HTMLCanvasElement : : create_layout_node ( GC : : Ref < CSS : : ComputedProperties > style )
2020-03-19 19:07:56 +01:00
{
2024-11-14 06:13:46 +13:00
return heap ( ) . allocate < Layout : : CanvasBox > ( document ( ) , * this , move ( style ) ) ;
2020-03-19 19:07:56 +01:00
}
2024-12-20 11:32:17 +01:00
void HTMLCanvasElement : : adjust_computed_style ( CSS : : ComputedProperties & style )
2024-11-08 20:14:37 +08:00
{
// https://drafts.csswg.org/css-display-3/#unbox
if ( style . display ( ) . is_contents ( ) )
style . set_property ( CSS : : PropertyID : : Display , CSS : : DisplayStyleValue : : create ( CSS : : Display : : from_short ( CSS : : Display : : Short : : None ) ) ) ;
}
2025-04-28 16:01:47 +02:00
JS : : ThrowCompletionOr < HTMLCanvasElement : : HasOrCreatedContext > HTMLCanvasElement : : create_2d_context ( JS : : Value options )
2022-06-04 04:22:42 +01:00
{
if ( ! m_context . has < Empty > ( ) )
2024-11-15 04:01:23 +13:00
return m_context . has < GC : : Ref < CanvasRenderingContext2D > > ( ) ? HasOrCreatedContext : : Yes : HasOrCreatedContext : : No ;
2022-06-04 04:22:42 +01:00
2025-04-28 16:01:47 +02:00
m_context = TRY ( CanvasRenderingContext2D : : create ( realm ( ) , * this , options ) ) ;
2022-06-04 04:22:42 +01:00
return HasOrCreatedContext : : Yes ;
}
2024-12-05 20:56:18 -07:00
template < typename ContextType >
2022-06-04 04:22:42 +01:00
JS : : ThrowCompletionOr < HTMLCanvasElement : : HasOrCreatedContext > HTMLCanvasElement : : create_webgl_context ( JS : : Value options )
2020-03-19 19:07:56 +01:00
{
2022-06-04 04:22:42 +01:00
if ( ! m_context . has < Empty > ( ) )
2024-12-05 20:56:18 -07:00
return m_context . has < GC : : Ref < ContextType > > ( ) ? HasOrCreatedContext : : Yes : HasOrCreatedContext : : No ;
2022-06-04 04:22:42 +01:00
2024-12-05 20:56:18 -07:00
auto maybe_context = TRY ( ContextType : : create ( realm ( ) , * this , options ) ) ;
2022-06-04 04:22:42 +01:00
if ( ! maybe_context )
return HasOrCreatedContext : : No ;
2024-12-05 20:56:18 -07:00
m_context = GC : : Ref < ContextType > ( * maybe_context ) ;
2022-06-04 04:22:42 +01:00
return HasOrCreatedContext : : Yes ;
2020-03-19 19:07:56 +01:00
}
2022-06-04 04:22:42 +01:00
// https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-getcontext
2023-09-03 15:58:55 +12:00
JS : : ThrowCompletionOr < HTMLCanvasElement : : RenderingContext > HTMLCanvasElement : : get_context ( String const & type , JS : : Value options )
2020-04-15 12:12:19 +02:00
{
2022-06-04 04:22:42 +01:00
// 1. If options is not an object, then set options to null.
if ( ! options . is_object ( ) )
options = JS : : js_null ( ) ;
// 2. Set options to the result of converting options to a JavaScript value.
// NOTE: No-op.
// 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 ) {
2025-04-28 16:01:47 +02:00
if ( TRY ( create_2d_context ( options ) ) = = HasOrCreatedContext : : Yes )
2024-11-15 04:01:23 +13:00
return GC : : make_root ( * m_context . get < GC : : Ref < HTML : : CanvasRenderingContext2D > > ( ) ) ;
2022-06-04 04:22:42 +01:00
return Empty { } ;
}
// NOTE: The WebGL spec says "experimental-webgl" is also acceptable and must be equivalent to "webgl". Other engines accept this, so we do too.
if ( type . is_one_of ( " webgl " sv , " experimental-webgl " sv ) ) {
2024-12-05 20:56:18 -07:00
if ( TRY ( create_webgl_context < WebGL : : WebGLRenderingContext > ( options ) ) = = HasOrCreatedContext : : Yes )
2024-11-15 04:01:23 +13:00
return GC : : make_root ( * m_context . get < GC : : Ref < WebGL : : WebGLRenderingContext > > ( ) ) ;
2022-06-04 04:22:42 +01:00
return Empty { } ;
}
2024-12-05 20:56:18 -07:00
if ( type = = " webgl2 " sv ) {
if ( TRY ( create_webgl_context < WebGL : : WebGL2RenderingContext > ( options ) ) = = HasOrCreatedContext : : Yes )
return GC : : make_root ( * m_context . get < GC : : Ref < WebGL : : WebGL2RenderingContext > > ( ) ) ;
return Empty { } ;
}
2022-06-04 04:22:42 +01:00
return Empty { } ;
}
2024-11-29 20:17:25 +01:00
Gfx : : IntSize HTMLCanvasElement : : bitmap_size_for_canvas ( size_t minimum_width , size_t minimum_height ) const
2022-06-04 04:22:42 +01:00
{
2024-11-29 20:17:25 +01:00
auto width = max ( this - > width ( ) , minimum_width ) ;
auto height = max ( this - > height ( ) , minimum_height ) ;
2020-04-15 16:55:36 +02:00
Checked < size_t > area = width ;
area * = height ;
if ( area . has_overflow ( ) ) {
2021-01-09 14:02:45 +01:00
dbgln ( " Refusing to create {}x{} canvas (overflow) " , width , height ) ;
2020-04-15 12:12:19 +02:00
return { } ;
}
2020-04-15 16:55:36 +02:00
if ( area . value ( ) > max_canvas_area ) {
2021-01-09 14:02:45 +01:00
dbgln ( " Refusing to create {}x{} canvas (exceeds maximum size) " , width , height ) ;
2020-04-15 12:12:19 +02:00
return { } ;
}
2020-06-21 15:37:13 +02:00
return Gfx : : IntSize ( width , height ) ;
2020-04-15 12:12:19 +02:00
}
2023-08-03 10:12:11 +02:00
struct SerializeBitmapResult {
ByteBuffer buffer ;
StringView mime_type ;
} ;
// https://html.spec.whatwg.org/multipage/canvas.html#a-serialisation-of-the-bitmap-as-a-file
2024-11-08 09:13:54 +01:00
static ErrorOr < SerializeBitmapResult > serialize_bitmap ( Gfx : : Bitmap const & bitmap , StringView type , JS : : Value quality )
2023-08-03 10:12:11 +02:00
{
2023-08-03 10:26:13 +02:00
// If type is an image format that supports variable quality (such as "image/jpeg"), quality is given, and type is not "image/png", then,
2024-10-31 11:34:22 +00:00
// if quality is a Number in the range 0.0 to 1.0 inclusive, the user agent must treat quality as the desired quality level.
2023-08-03 10:26:13 +02:00
// Otherwise, the user agent must use its default quality value, as if the quality argument had not been given.
2024-11-08 09:13:54 +01:00
bool valid_quality = quality . is_number ( ) & & quality . as_double ( ) > = 0.0 & & quality . as_double ( ) < = 1.0 ;
2023-08-03 10:26:13 +02:00
if ( type . equals_ignoring_ascii_case ( " image/jpeg " sv ) ) {
AllocatingMemoryStream file ;
Gfx : : JPEGWriter : : Options jpeg_options ;
2024-11-08 09:13:54 +01:00
if ( valid_quality )
jpeg_options . quality = static_cast < int > ( quality . as_double ( ) * 100 ) ;
2023-08-03 10:26:13 +02:00
TRY ( Gfx : : JPEGWriter : : encode ( file , bitmap , jpeg_options ) ) ;
return SerializeBitmapResult { TRY ( file . read_until_eof ( ) ) , " image/jpeg " sv } ;
}
2023-08-03 10:12:11 +02:00
// User agents must support PNG ("image/png"). User agents may support other types.
// If the user agent does not support the requested type, then it must create the file using the PNG format. [PNG]
return SerializeBitmapResult { TRY ( Gfx : : PNGWriter : : encode ( bitmap ) ) , " image/png " sv } ;
}
// https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-todataurl
2024-11-08 09:13:54 +01:00
String HTMLCanvasElement : : to_data_url ( StringView type , JS : : Value quality )
2021-04-19 23:47:29 +02:00
{
2025-02-22 10:59:50 +08:00
// It is possible the canvas doesn't have a associated bitmap so create one
2024-11-29 20:17:25 +01:00
allocate_painting_surface_if_needed ( ) ;
auto surface = this - > surface ( ) ;
auto size = bitmap_size_for_canvas ( ) ;
2024-12-05 16:35:02 +01:00
if ( ! surface & & ! size . is_empty ( ) ) {
2024-11-29 20:17:25 +01:00
// 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 ) ;
2024-09-25 15:44:58 +02:00
}
2023-09-16 22:00:37 +02:00
2023-08-03 10:12:11 +02:00
// 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.)
2024-11-29 20:17:25 +01:00
if ( ! surface )
2023-09-03 15:58:55 +12:00
return " data:, " _string ;
2023-08-03 10:12:11 +02:00
// 3. Let file be a serialization of this canvas element's bitmap as a file, passing type and quality if given.
2024-11-29 20:17:25 +01:00
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 ) ;
2024-11-09 05:48:17 +01:00
auto file = serialize_bitmap ( bitmap , type , move ( quality ) ) ;
2023-08-03 10:12:11 +02:00
// 4. If file is null then return "data:,".
if ( file . is_error ( ) ) {
dbgln ( " HTMLCanvasElement: Failed to encode canvas bitmap to {}: {} " , type , file . error ( ) ) ;
2023-09-03 15:58:55 +12:00
return " data:, " _string ;
2022-12-05 20:34:27 +01:00
}
2023-08-03 10:12:11 +02:00
// 5. Return a data: URL representing file. [RFC2397]
auto base64_encoded_or_error = encode_base64 ( file . value ( ) . buffer ) ;
2022-12-19 00:23:47 +01:00
if ( base64_encoded_or_error . is_error ( ) ) {
2023-09-03 15:58:55 +12:00
return " data:, " _string ;
2022-12-19 00:23:47 +01:00
}
2024-12-03 22:31:33 +13:00
return URL : : create_with_data ( file . value ( ) . mime_type , base64_encoded_or_error . release_value ( ) , true ) . to_string ( ) ;
2021-04-19 23:47:29 +02:00
}
2023-08-03 01:29:31 +02:00
// https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-toblob
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < void > HTMLCanvasElement : : to_blob ( GC : : Ref < WebIDL : : CallbackType > callback , StringView type , JS : : Value quality )
2023-08-03 01:29:31 +02:00
{
2025-02-22 10:59:50 +08:00
// It is possible the canvas doesn't have a associated bitmap so create one
2024-11-29 20:17:25 +01:00
allocate_painting_surface_if_needed ( ) ;
auto surface = this - > surface ( ) ;
auto size = bitmap_size_for_canvas ( ) ;
2024-12-05 16:35:02 +01:00
if ( ! surface & & ! size . is_empty ( ) ) {
2024-11-29 20:17:25 +01:00
// 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 ) ;
2024-09-25 15:44:58 +02:00
}
2023-09-16 22:00:37 +02:00
2023-08-03 01:29:31 +02:00
// FIXME: 1. If this canvas element's bitmap's origin-clean flag is set to false, then throw a "SecurityError" DOMException.
// 2. Let result be null.
RefPtr < Gfx : : Bitmap > bitmap_result ;
// 3. If this canvas element's bitmap has pixels (i.e., neither its horizontal dimension nor its vertical dimension is zero),
// then set result to a copy of this canvas element's bitmap.
2024-11-29 20:17:25 +01:00
if ( surface ) {
bitmap_result = MUST ( Gfx : : Bitmap : : create ( Gfx : : BitmapFormat : : BGRA8888 , Gfx : : AlphaType : : Premultiplied , surface - > size ( ) ) ) ;
surface - > read_into_bitmap ( * bitmap_result ) ;
2024-09-25 15:44:58 +02:00
}
2023-08-03 01:29:31 +02:00
// 4. Run these steps in parallel:
2024-11-15 04:01:23 +13:00
Platform : : EventLoopPlugin : : the ( ) . deferred_invoke ( GC : : create_function ( heap ( ) , [ this , callback , bitmap_result , type , quality ] {
2023-08-03 01:29:31 +02:00
// 1. If result is non-null, then set result to a serialization of result as a file with type and quality if given.
Optional < SerializeBitmapResult > file_result ;
if ( bitmap_result ) {
if ( auto result = serialize_bitmap ( * bitmap_result , type , move ( quality ) ) ; ! result . is_error ( ) )
file_result = result . release_value ( ) ;
}
// 2. Queue an element task on the canvas blob serialization task source given the canvas element to run these steps:
queue_an_element_task ( Task : : Source : : CanvasBlobSerializationTask , [ this , callback , file_result = move ( file_result ) ] {
auto maybe_error = Bindings : : throw_dom_exception_if_needed ( vm ( ) , [ & ] ( ) - > WebIDL : : ExceptionOr < void > {
// 1. If result is non-null, then set result to a new Blob object, created in the relevant realm of this canvas element, representing result. [FILEAPI]
2024-11-15 04:01:23 +13:00
GC : : Ptr < FileAPI : : Blob > blob_result ;
2023-08-03 01:29:31 +02:00
if ( file_result . has_value ( ) )
2023-08-13 13:05:26 +02:00
blob_result = FileAPI : : Blob : : create ( realm ( ) , file_result - > buffer , TRY_OR_THROW_OOM ( vm ( ) , String : : from_utf8 ( file_result - > mime_type ) ) ) ;
2023-08-03 01:29:31 +02:00
2024-12-05 10:05:10 +00:00
// 2. Invoke callback with « result » and "report".
2025-04-15 20:56:03 -04:00
TRY ( WebIDL : : invoke_callback ( * callback , { } , WebIDL : : ExceptionBehavior : : Report , { { blob_result } } ) ) ;
2023-08-03 01:29:31 +02:00
return { } ;
} ) ;
if ( maybe_error . is_throw_completion ( ) )
report_exception ( maybe_error . throw_completion ( ) , realm ( ) ) ;
} ) ;
2024-10-31 02:39:29 +13:00
} ) ) ;
2023-08-03 01:29:31 +02:00
return { } ;
}
2022-06-04 04:22:42 +01:00
void HTMLCanvasElement : : present ( )
{
2024-11-29 20:17:25 +01:00
if ( auto surface = this - > surface ( ) )
surface - > flush ( ) ;
2024-09-25 15:44:58 +02:00
2022-06-04 04:22:42 +01:00
m_context . visit (
2024-11-15 04:01:23 +13:00
[ ] ( GC : : Ref < CanvasRenderingContext2D > & ) {
2022-06-04 04:22:42 +01:00
// Do nothing, CRC2D writes directly to the canvas bitmap.
} ,
2024-11-15 04:01:23 +13:00
[ ] ( GC : : Ref < WebGL : : WebGLRenderingContext > & context ) {
2022-06-04 04:22:42 +01:00
context - > present ( ) ;
} ,
2024-12-05 20:56:18 -07:00
[ ] ( GC : : Ref < WebGL : : WebGL2RenderingContext > & context ) {
context - > present ( ) ;
} ,
2022-06-04 04:22:42 +01:00
[ ] ( Empty ) {
// Do nothing.
} ) ;
}
2024-11-29 20:17:25 +01:00
RefPtr < Gfx : : PaintingSurface > HTMLCanvasElement : : surface ( ) const
{
return m_context . visit (
[ & ] ( GC : : Ref < CanvasRenderingContext2D > const & context ) {
return context - > surface ( ) ;
} ,
2024-11-29 22:15:02 +01:00
[ & ] ( GC : : Ref < WebGL : : WebGLRenderingContext > const & context ) - > RefPtr < Gfx : : PaintingSurface > {
return context - > surface ( ) ;
2024-11-29 20:17:25 +01:00
} ,
2024-12-05 20:56:18 -07:00
[ & ] ( GC : : Ref < WebGL : : WebGL2RenderingContext > const & context ) - > RefPtr < Gfx : : PaintingSurface > {
return context - > surface ( ) ;
} ,
2024-11-29 20:17:25 +01:00
[ ] ( Empty ) - > RefPtr < Gfx : : PaintingSurface > {
return { } ;
} ) ;
}
void HTMLCanvasElement : : allocate_painting_surface_if_needed ( )
{
m_context . visit (
[ & ] ( GC : : Ref < CanvasRenderingContext2D > & context ) {
context - > allocate_painting_surface_if_needed ( ) ;
} ,
2024-11-29 22:15:02 +01:00
[ & ] ( GC : : Ref < WebGL : : WebGLRenderingContext > & context ) {
context - > allocate_painting_surface_if_needed ( ) ;
2024-11-29 20:17:25 +01:00
} ,
2024-12-05 20:56:18 -07:00
[ & ] ( GC : : Ref < WebGL : : WebGL2RenderingContext > & context ) {
context - > allocate_painting_surface_if_needed ( ) ;
} ,
2024-11-29 20:17:25 +01:00
[ ] ( Empty ) {
// Do nothing.
} ) ;
}
2020-03-19 19:07:56 +01:00
}