2020-03-19 15:07:56 -03:00
/*
2024-10-04 08:19:50 -03:00
* Copyright ( c ) 2018 - 2020 , Andreas Kling < andreas @ ladybird . org >
2020-03-19 15:07:56 -03:00
*
2021-04-22 05:24:48 -03:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-03-19 15:07:56 -03:00
*/
2021-04-19 18:47:29 -03:00
# include <AK/Base64.h>
2020-04-15 11:55:36 -03:00
# include <AK/Checked.h>
2020-03-19 15:07:56 -03:00
# include <LibGfx/Bitmap.h>
2026-05-05 09:13:30 -03:00
# include <LibGfx/DecodedImageFrame.h>
2023-08-02 20:29:31 -03:00
# include <LibWeb/Bindings/ExceptionOrUtils.h>
2026-04-18 05:54:06 -03:00
# include <LibWeb/Bindings/HTMLCanvasElement.h>
2025-02-18 05:19:56 -03:00
# include <LibWeb/CSS/ComputedProperties.h>
2021-09-24 08:49:57 -03:00
# include <LibWeb/CSS/StyleComputer.h>
2024-11-08 09:14:37 -03:00
# include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
2025-08-08 06:28:41 -03:00
# include <LibWeb/CSS/StyleValues/KeywordStyleValue.h>
2026-03-23 05:43:45 -03:00
# include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
2023-08-31 14:22:17 -03:00
# include <LibWeb/CSS/StyleValues/RatioStyleValue.h>
# include <LibWeb/CSS/StyleValues/StyleValueList.h>
2020-03-19 15:07:56 -03:00
# include <LibWeb/DOM/Document.h>
2025-03-06 19:37:13 -03:00
# include <LibWeb/HTML/Canvas/SerializeBitmap.h>
2020-07-26 10:08:16 -03:00
# include <LibWeb/HTML/CanvasRenderingContext2D.h>
# include <LibWeb/HTML/HTMLCanvasElement.h>
2026-04-04 01:29:11 -03:00
# include <LibWeb/HTML/Navigable.h>
2023-08-31 17:12:14 -03:00
# include <LibWeb/HTML/Numbers.h>
2023-08-02 20:29:31 -03:00
# include <LibWeb/HTML/Scripting/ExceptionReporter.h>
2020-11-22 11:53:01 -03:00
# include <LibWeb/Layout/CanvasBox.h>
2026-02-19 15:31:20 -03:00
# include <LibWeb/Page/Page.h>
2023-08-02 20:29:31 -03:00
# include <LibWeb/Platform/EventLoopPlugin.h>
2026-04-04 01:29:11 -03:00
# include <LibWeb/Platform/FontPlugin.h>
2024-12-06 00:56:18 -03:00
# include <LibWeb/WebGL/WebGL2RenderingContext.h>
2024-11-29 16:17:25 -03:00
# include <LibWeb/WebGL/WebGLRenderingContext.h>
2023-08-02 20:29:31 -03:00
# include <LibWeb/WebIDL/AbstractOperations.h>
2020-03-19 15:07:56 -03:00
2020-07-28 13:20:36 -03:00
namespace Web : : HTML {
2020-03-19 15:07:56 -03:00
2024-11-14 12:01:23 -03:00
GC_DEFINE_ALLOCATOR ( HTMLCanvasElement ) ;
2023-11-19 15:47:52 -03:00
2020-04-15 07:12:19 -03:00
static constexpr auto max_canvas_area = 16384 * 16384 ;
2022-02-18 17:00:52 -03:00
HTMLCanvasElement : : HTMLCanvasElement ( DOM : : Document & document , DOM : : QualifiedName qualified_name )
2021-02-07 07:20:15 -03:00
: HTMLElement ( document , move ( qualified_name ) )
2020-03-19 15:07:56 -03:00
{
}
2022-03-14 16:21:51 -03:00
HTMLCanvasElement : : ~ HTMLCanvasElement ( ) = default ;
2020-03-19 15:07:56 -03:00
2023-08-07 03:41:28 -03:00
void HTMLCanvasElement : : initialize ( JS : : Realm & realm )
2023-01-10 08:28:20 -03:00
{
2024-03-16 09:13:08 -03:00
WEB_SET_PROTOTYPE_FOR_INTERFACE ( HTMLCanvasElement ) ;
2025-04-20 11:22:57 -03:00
Base : : initialize ( realm ) ;
2026-02-19 15:31:20 -03:00
document ( ) . page ( ) . register_canvas_element ( { } , unique_id ( ) ) ;
}
void HTMLCanvasElement : : finalize ( )
{
Base : : finalize ( ) ;
document ( ) . page ( ) . unregister_canvas_element ( { } , unique_id ( ) ) ;
2023-01-10 08:28:20 -03:00
}
2022-09-02 10:53:02 -03:00
void HTMLCanvasElement : : visit_edges ( Cell : : Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
m_context . visit (
2024-11-14 12:01:23 -03:00
[ & ] ( GC : : Ref < CanvasRenderingContext2D > & context ) {
2023-11-19 00:18:00 -03:00
visitor . visit ( context ) ;
2022-09-02 10:53:02 -03:00
} ,
2024-11-14 12:01:23 -03:00
[ & ] ( GC : : Ref < WebGL : : WebGLRenderingContext > & context ) {
2023-11-19 00:18:00 -03:00
visitor . visit ( context ) ;
2022-09-02 10:53:02 -03:00
} ,
2024-12-06 00:56:18 -03:00
[ & ] ( GC : : Ref < WebGL : : WebGL2RenderingContext > & context ) {
visitor . visit ( context ) ;
} ,
2022-09-02 10:53:02 -03:00
[ ] ( Empty ) {
} ) ;
}
2024-12-23 13:51:10 -03: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 ) ;
}
2026-04-30 06:44:26 -03:00
void HTMLCanvasElement : : apply_presentational_hints ( Vector < CSS : : StyleProperty > & properties ) const
2023-08-31 14:22:17 -03:00
{
2026-04-30 06:44:26 -03:00
Base : : apply_presentational_hints ( properties ) ;
2023-08-31 14:22:17 -03: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 15:04:45 -03: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 14:22:17 -03:00
2026-04-30 06:44:26 -03:00
// 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.
if ( w . has_value ( ) & & h . has_value ( ) ) {
auto aspect_ratio = CSS : : StyleValueList : : create (
CSS : : StyleValueVector {
CSS : : KeywordStyleValue : : create ( CSS : : Keyword : : Auto ) ,
CSS : : RatioStyleValue : : create ( CSS : : NumberStyleValue : : create ( w . value ( ) ) , CSS : : NumberStyleValue : : create ( h . value ( ) ) ) ,
} ,
CSS : : StyleValueList : : Separator : : Space ) ;
properties . append ( { . property_id = CSS : : PropertyID : : AspectRatio , . value = aspect_ratio } ) ;
}
2023-08-31 14:22:17 -03:00
}
2024-11-29 13:04:35 -03:00
// https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-width
WebIDL : : UnsignedLong HTMLCanvasElement : : width ( ) const
2020-03-19 15:07:56 -03:00
{
2024-11-29 13:04:35 -03:00
// The width and height IDL attributes must reflect the respective content attributes of the same name, with the same defaults.
2023-08-31 17:12:14 -03: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 13:04:35 -03: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 15:07:56 -03:00
}
2024-11-29 13:04:35 -03:00
// https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-height
WebIDL : : UnsignedLong HTMLCanvasElement : : height ( ) const
2020-03-19 15:07:56 -03:00
{
2024-11-29 13:04:35 -03:00
// The width and height IDL attributes must reflect the respective content attributes of the same name, with the same defaults.
2023-08-31 17:12:14 -03: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 13:04:35 -03: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 15:07:56 -03:00
}
2026-02-19 15:31:20 -03:00
Painting : : ExternalContentSource & HTMLCanvasElement : : ensure_external_content_source ( )
{
if ( ! m_external_content_source )
m_external_content_source = Painting : : ExternalContentSource : : create ( ) ;
return * m_external_content_source ;
}
2022-06-04 00:22:42 -03:00
void HTMLCanvasElement : : reset_context_to_default_state ( )
{
2026-02-19 15:31:20 -03:00
if ( m_external_content_source )
m_external_content_source - > clear ( ) ;
2022-06-04 00:22:42 -03:00
m_context . visit (
2024-11-14 12:01:23 -03:00
[ ] ( GC : : Ref < CanvasRenderingContext2D > & context ) {
2022-06-04 00:22:42 -03:00
context - > reset_to_default_state ( ) ;
} ,
2024-11-29 18:15:02 -03:00
[ ] ( GC : : Ref < WebGL : : WebGLRenderingContext > & context ) {
context - > reset_to_default_state ( ) ;
2022-06-04 00:22:42 -03:00
} ,
2024-12-06 00:56:18 -03:00
[ ] ( GC : : Ref < WebGL : : WebGL2RenderingContext > & context ) {
context - > reset_to_default_state ( ) ;
} ,
2022-06-04 00:22:42 -03:00
[ ] ( Empty ) {
// Do nothing.
} ) ;
}
2026-04-04 01:29:11 -03:00
CSS : : ComputationContext HTMLCanvasElement : : canvas_font_computation_context ( )
{
DOM : : AbstractElement abstract_element { * this } ;
Optional < CSS : : Length : : ResolutionContext > length_resolution_context ;
if ( is_connected ( ) & & this - > navigable ( ) ) {
length_resolution_context = CSS : : Length : : ResolutionContext : : for_element ( abstract_element ) ;
} else {
// NB: This is similar to the document's LRC but using the default canvas context font size of 10px
CSS : : Length : : FontMetrics font_metrics { 10 , Platform : : FontPlugin : : the ( ) . default_font ( 8 ) - > pixel_metrics ( ) , CSS : : InitialValues : : line_height ( ) } ;
CSSPixelRect viewport_rect ;
if ( auto navigable = this - > navigable ( ) )
viewport_rect = navigable - > viewport_rect ( ) ;
length_resolution_context = {
. viewport_rect = viewport_rect ,
. font_metrics = font_metrics ,
. root_font_metrics = font_metrics
} ;
}
return CSS : : ComputationContext {
. length_resolution_context = length_resolution_context . value ( ) ,
// NB: We require a abstract element here since tree counting functions are allowed in font values unlike for
// OffscreenCanvas
. abstract_element = abstract_element ,
// FIXME: Do we require a color scheme to resolve light-dark()?
. color_scheme = { }
} ;
}
2024-11-29 16:17:25 -03: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 18:15:02 -03:00
[ & ] ( GC : : Ref < WebGL : : WebGLRenderingContext > & context ) {
context - > set_size ( bitmap_size_for_canvas ( ) ) ;
2024-11-29 16:17:25 -03:00
} ,
2024-12-06 00:56:18 -03:00
[ & ] ( GC : : Ref < WebGL : : WebGL2RenderingContext > & context ) {
context - > set_size ( bitmap_size_for_canvas ( ) ) ;
} ,
2024-11-29 16:17:25 -03:00
[ ] ( Empty ) {
// Do nothing.
} ) ;
}
2025-10-31 09:30:47 -03:00
void HTMLCanvasElement : : set_width ( unsigned value )
2021-11-12 20:54:21 -03:00
{
2024-11-29 13:04:35 -03:00
if ( value > 2147483647 )
value = 300 ;
2025-10-31 09:30:47 -03:00
set_attribute_value ( HTML : : AttributeNames : : width , String : : number ( value ) ) ;
2024-11-29 16:17:25 -03:00
notify_context_about_canvas_size_change ( ) ;
2022-06-04 00:22:42 -03:00
reset_context_to_default_state ( ) ;
2021-11-12 20:54:21 -03:00
}
2025-10-31 09:30:47 -03:00
void HTMLCanvasElement : : set_height ( WebIDL : : UnsignedLong value )
2021-11-12 20:54:21 -03:00
{
2024-11-29 13:04:35 -03:00
if ( value > 2147483647 )
value = 150 ;
2025-10-31 09:30:47 -03:00
set_attribute_value ( HTML : : AttributeNames : : height , String : : number ( value ) ) ;
2024-11-29 16:17:25 -03:00
notify_context_about_canvas_size_change ( ) ;
2022-06-04 00:22:42 -03:00
reset_context_to_default_state ( ) ;
2021-11-12 20:54:21 -03:00
}
2024-12-28 01:47:58 -03: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 10:34:04 -03:00
if ( local_name . is_one_of ( HTML : : AttributeNames : : width , HTML : : AttributeNames : : height ) ) {
2024-12-28 01:47:58 -03:00
notify_context_about_canvas_size_change ( ) ;
reset_context_to_default_state ( ) ;
2026-02-26 07:57:29 -03:00
set_needs_layout_update ( DOM : : SetNeedsLayoutReason : : HTMLCanvasElementWidthOrHeightChange ) ;
2024-12-28 01:47:58 -03:00
}
}
2024-12-20 12:35:12 -03:00
GC : : Ptr < Layout : : Node > HTMLCanvasElement : : create_layout_node ( GC : : Ref < CSS : : ComputedProperties > style )
2020-03-19 15:07:56 -03:00
{
2024-11-13 14:13:46 -03:00
return heap ( ) . allocate < Layout : : CanvasBox > ( document ( ) , * this , move ( style ) ) ;
2020-03-19 15:07:56 -03:00
}
2024-12-20 07:32:17 -03:00
void HTMLCanvasElement : : adjust_computed_style ( CSS : : ComputedProperties & style )
2024-11-08 09:14:37 -03: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 11:01:47 -03:00
JS : : ThrowCompletionOr < HTMLCanvasElement : : HasOrCreatedContext > HTMLCanvasElement : : create_2d_context ( JS : : Value options )
2022-06-04 00:22:42 -03:00
{
if ( ! m_context . has < Empty > ( ) )
2024-11-14 12:01:23 -03:00
return m_context . has < GC : : Ref < CanvasRenderingContext2D > > ( ) ? HasOrCreatedContext : : Yes : HasOrCreatedContext : : No ;
2022-06-04 00:22:42 -03:00
2025-04-28 11:01:47 -03:00
m_context = TRY ( CanvasRenderingContext2D : : create ( realm ( ) , * this , options ) ) ;
2022-06-04 00:22:42 -03:00
return HasOrCreatedContext : : Yes ;
}
2024-12-06 00:56:18 -03:00
template < typename ContextType >
2022-06-04 00:22:42 -03:00
JS : : ThrowCompletionOr < HTMLCanvasElement : : HasOrCreatedContext > HTMLCanvasElement : : create_webgl_context ( JS : : Value options )
2020-03-19 15:07:56 -03:00
{
2022-06-04 00:22:42 -03:00
if ( ! m_context . has < Empty > ( ) )
2024-12-06 00:56:18 -03:00
return m_context . has < GC : : Ref < ContextType > > ( ) ? HasOrCreatedContext : : Yes : HasOrCreatedContext : : No ;
2022-06-04 00:22:42 -03:00
2024-12-06 00:56:18 -03:00
auto maybe_context = TRY ( ContextType : : create ( realm ( ) , * this , options ) ) ;
2022-06-04 00:22:42 -03:00
if ( ! maybe_context )
return HasOrCreatedContext : : No ;
2024-12-06 00:56:18 -03:00
m_context = GC : : Ref < ContextType > ( * maybe_context ) ;
2022-06-04 00:22:42 -03:00
return HasOrCreatedContext : : Yes ;
2020-03-19 15:07:56 -03:00
}
2022-06-04 00:22:42 -03:00
// https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-getcontext
2023-09-03 00:58:55 -03:00
JS : : ThrowCompletionOr < HTMLCanvasElement : : RenderingContext > HTMLCanvasElement : : get_context ( String const & type , JS : : Value options )
2020-04-15 07:12:19 -03:00
{
2022-06-04 00:22:42 -03: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 11:01:47 -03:00
if ( TRY ( create_2d_context ( options ) ) = = HasOrCreatedContext : : Yes )
2024-11-14 12:01:23 -03:00
return GC : : make_root ( * m_context . get < GC : : Ref < HTML : : CanvasRenderingContext2D > > ( ) ) ;
2022-06-04 00:22:42 -03: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-06 00:56:18 -03:00
if ( TRY ( create_webgl_context < WebGL : : WebGLRenderingContext > ( options ) ) = = HasOrCreatedContext : : Yes )
2024-11-14 12:01:23 -03:00
return GC : : make_root ( * m_context . get < GC : : Ref < WebGL : : WebGLRenderingContext > > ( ) ) ;
2022-06-04 00:22:42 -03:00
return Empty { } ;
}
2024-12-06 00:56:18 -03: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 00:22:42 -03:00
return Empty { } ;
}
2024-11-29 16:17:25 -03:00
Gfx : : IntSize HTMLCanvasElement : : bitmap_size_for_canvas ( size_t minimum_width , size_t minimum_height ) const
2022-06-04 00:22:42 -03:00
{
2024-11-29 16:17:25 -03:00
auto width = max ( this - > width ( ) , minimum_width ) ;
auto height = max ( this - > height ( ) , minimum_height ) ;
2020-04-15 11:55:36 -03:00
Checked < size_t > area = width ;
area * = height ;
if ( area . has_overflow ( ) ) {
2021-01-09 10:02:45 -03:00
dbgln ( " Refusing to create {}x{} canvas (overflow) " , width , height ) ;
2020-04-15 07:12:19 -03:00
return { } ;
}
2020-04-15 11:55:36 -03:00
if ( area . value ( ) > max_canvas_area ) {
2021-01-09 10:02:45 -03:00
dbgln ( " Refusing to create {}x{} canvas (exceeds maximum size) " , width , height ) ;
2020-04-15 07:12:19 -03:00
return { } ;
}
2020-06-21 10:37:13 -03:00
return Gfx : : IntSize ( width , height ) ;
2020-04-15 07:12:19 -03:00
}
2023-08-03 05:12:11 -03:00
// https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-todataurl
2025-03-06 19:37:13 -03:00
String HTMLCanvasElement : : to_data_url ( StringView type , JS : : Value js_quality )
2021-04-19 18:47:29 -03:00
{
2026-03-16 20:55:40 -03:00
// It is possible the canvas doesn't have an associated bitmap so create one
2024-11-29 16:17:25 -03:00
allocate_painting_surface_if_needed ( ) ;
auto surface = this - > surface ( ) ;
auto size = bitmap_size_for_canvas ( ) ;
2024-12-05 12:35:02 -03:00
if ( ! surface & & ! size . is_empty ( ) ) {
2024-11-29 16:17:25 -03:00
// If the context is not initialized yet, we need to allocate transparent surface for serialization
2026-03-16 20:55:40 -03:00
surface = Gfx : : PaintingSurface : : create_with_size ( size , Gfx : : BitmapFormat : : BGRA8888 , Gfx : : AlphaType : : Premultiplied ) ;
2024-09-25 10:44:58 -03:00
}
2023-09-16 17:00:37 -03:00
2023-08-03 05:12:11 -03:00
// FIXME: 1. If this canvas element's bitmap's origin-clean flag is set to false, then throw a "SecurityError" DOMException.
2025-06-24 07:18:14 -03:00
// 2. If this canvas element's bitmap has no pixels (i.e. either its horizontal dimension or its vertical dimension is zero),
2023-08-03 05:12:11 -03:00
// then return the string "data:,". (This is the shortest data: URL; it represents the empty string in a text/plain resource.)
2024-11-29 16:17:25 -03:00
if ( ! surface )
2023-09-03 00:58:55 -03:00
return " data:, " _string ;
2023-08-03 05:12:11 -03:00
// 3. Let file be a serialization of this canvas element's bitmap as a file, passing type and quality if given.
2026-05-05 07:36:43 -03:00
auto bitmap = surface - > snapshot_bitmap ( ) ;
2025-03-06 19:37:13 -03:00
Optional < double > quality = js_quality . is_number ( ) ? js_quality . as_double ( ) : Optional < double > ( ) ;
auto file = serialize_bitmap ( bitmap , type , quality ) ;
2023-08-03 05:12:11 -03:00
2025-06-24 07:18:14 -03:00
// 4. If file is null, then return "data:,".
2023-08-03 05:12:11 -03:00
if ( file . is_error ( ) ) {
dbgln ( " HTMLCanvasElement: Failed to encode canvas bitmap to {}: {} " , type , file . error ( ) ) ;
2023-09-03 00:58:55 -03:00
return " data:, " _string ;
2022-12-05 16:34:27 -03:00
}
2023-08-03 05:12:11 -03:00
// 5. Return a data: URL representing file. [RFC2397]
auto base64_encoded_or_error = encode_base64 ( file . value ( ) . buffer ) ;
2022-12-18 20:23:47 -03:00
if ( base64_encoded_or_error . is_error ( ) ) {
2023-09-03 00:58:55 -03:00
return " data:, " _string ;
2022-12-18 20:23:47 -03:00
}
2024-12-03 06:31:33 -03:00
return URL : : create_with_data ( file . value ( ) . mime_type , base64_encoded_or_error . release_value ( ) , true ) . to_string ( ) ;
2021-04-19 18:47:29 -03:00
}
2023-08-02 20:29:31 -03:00
// https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-toblob
2025-03-06 19:37:13 -03:00
WebIDL : : ExceptionOr < void > HTMLCanvasElement : : to_blob ( GC : : Ref < WebIDL : : CallbackType > callback , StringView type , JS : : Value js_quality )
2023-08-02 20:29:31 -03: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.
// 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.
2025-07-14 16:24:22 -03:00
auto bitmap_result = get_bitmap_from_surface ( ) ;
2023-08-02 20:29:31 -03:00
2025-03-06 19:37:13 -03:00
Optional < double > quality = js_quality . is_number ( ) ? js_quality . as_double ( ) : Optional < double > ( ) ;
2023-08-02 20:29:31 -03:00
// 4. Run these steps in parallel:
2024-11-14 12:01:23 -03:00
Platform : : EventLoopPlugin : : the ( ) . deferred_invoke ( GC : : create_function ( heap ( ) , [ this , callback , bitmap_result , type , quality ] {
2023-08-02 20:29:31 -03: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 ) {
2025-03-06 19:37:13 -03:00
if ( auto result = serialize_bitmap ( * bitmap_result , type , quality ) ; ! result . is_error ( ) )
2023-08-02 20:29:31 -03:00
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-14 12:01:23 -03:00
GC : : Ptr < FileAPI : : Blob > blob_result ;
2023-08-02 20:29:31 -03:00
if ( file_result . has_value ( ) )
2023-08-13 08:05:26 -03:00
blob_result = FileAPI : : Blob : : create ( realm ( ) , file_result - > buffer , TRY_OR_THROW_OOM ( vm ( ) , String : : from_utf8 ( file_result - > mime_type ) ) ) ;
2023-08-02 20:29:31 -03:00
2024-12-05 07:05:10 -03:00
// 2. Invoke callback with « result » and "report".
2025-04-15 21:56:03 -03:00
TRY ( WebIDL : : invoke_callback ( * callback , { } , WebIDL : : ExceptionBehavior : : Report , { { blob_result } } ) ) ;
2023-08-02 20:29:31 -03:00
return { } ;
} ) ;
if ( maybe_error . is_throw_completion ( ) )
report_exception ( maybe_error . throw_completion ( ) , realm ( ) ) ;
} ) ;
2024-10-30 10:39:29 -03:00
} ) ) ;
2023-08-02 20:29:31 -03:00
return { } ;
}
2025-07-14 16:24:22 -03:00
RefPtr < Gfx : : Bitmap > HTMLCanvasElement : : get_bitmap_from_surface ( )
{
// It is possible the canvas doesn't have an associated bitmap so create one
allocate_painting_surface_if_needed ( ) ;
auto surface = this - > surface ( ) ;
if ( auto const size = bitmap_size_for_canvas ( ) ; ! surface & & ! size . is_empty ( ) ) {
// If the context is not initialized yet, we need to allocate transparent surface for serialization
2026-03-16 20:55:40 -03:00
surface = Gfx : : PaintingSurface : : create_with_size ( size , Gfx : : BitmapFormat : : BGRA8888 , Gfx : : AlphaType : : Premultiplied ) ;
2025-07-14 16:24:22 -03:00
}
RefPtr < Gfx : : Bitmap > bitmap ;
if ( surface ) {
2026-05-05 07:36:43 -03:00
bitmap = surface - > snapshot_bitmap ( ) ;
2025-07-14 16:24:22 -03:00
}
return bitmap ;
}
2026-02-19 15:31:20 -03:00
void HTMLCanvasElement : : set_canvas_content_dirty ( )
{
m_canvas_content_dirty = true ;
}
2022-06-04 00:22:42 -03:00
void HTMLCanvasElement : : present ( )
{
2026-02-19 15:31:20 -03:00
if ( ! m_canvas_content_dirty )
return ;
m_canvas_content_dirty = false ;
2024-09-25 10:44:58 -03:00
2022-06-04 00:22:42 -03:00
m_context . visit (
2026-05-04 12:40:34 -03:00
[ ] ( GC : : Ref < CanvasRenderingContext2D > & context ) {
context - > present ( ) ;
2022-06-04 00:22:42 -03:00
} ,
2024-11-14 12:01:23 -03:00
[ ] ( GC : : Ref < WebGL : : WebGLRenderingContext > & context ) {
2022-06-04 00:22:42 -03:00
context - > present ( ) ;
} ,
2024-12-06 00:56:18 -03:00
[ ] ( GC : : Ref < WebGL : : WebGL2RenderingContext > & context ) {
context - > present ( ) ;
} ,
2022-06-04 00:22:42 -03:00
[ ] ( Empty ) {
// Do nothing.
} ) ;
2026-02-19 15:31:20 -03:00
if ( auto surface = this - > surface ( ) ) {
surface - > flush ( ) ;
2026-05-07 09:39:50 -03:00
auto snapshot = Gfx : : DecodedImageFrame { * surface - > snapshot_bitmap ( ) } ;
2026-02-19 15:31:20 -03:00
ensure_external_content_source ( ) . update ( snapshot ) ;
}
2022-06-04 00:22:42 -03:00
}
2024-11-29 16:17:25 -03:00
RefPtr < Gfx : : PaintingSurface > HTMLCanvasElement : : surface ( ) const
{
return m_context . visit (
[ & ] ( GC : : Ref < CanvasRenderingContext2D > const & context ) {
return context - > surface ( ) ;
} ,
2024-11-29 18:15:02 -03:00
[ & ] ( GC : : Ref < WebGL : : WebGLRenderingContext > const & context ) - > RefPtr < Gfx : : PaintingSurface > {
return context - > surface ( ) ;
2024-11-29 16:17:25 -03:00
} ,
2024-12-06 00:56:18 -03:00
[ & ] ( GC : : Ref < WebGL : : WebGL2RenderingContext > const & context ) - > RefPtr < Gfx : : PaintingSurface > {
return context - > surface ( ) ;
} ,
2024-11-29 16:17:25 -03: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 18:15:02 -03:00
[ & ] ( GC : : Ref < WebGL : : WebGLRenderingContext > & context ) {
context - > allocate_painting_surface_if_needed ( ) ;
2024-11-29 16:17:25 -03:00
} ,
2024-12-06 00:56:18 -03:00
[ & ] ( GC : : Ref < WebGL : : WebGL2RenderingContext > & context ) {
context - > allocate_painting_surface_if_needed ( ) ;
} ,
2024-11-29 16:17:25 -03:00
[ ] ( Empty ) {
// Do nothing.
} ) ;
}
2020-03-19 15:07:56 -03:00
}