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>
2020-03-19 19:07:56 +01:00
# include <LibGfx/Bitmap.h>
2026-05-16 22:58:31 +02:00
# include <LibGfx/SharedImage.h>
2023-08-03 01:29:31 +02:00
# include <LibWeb/Bindings/ExceptionOrUtils.h>
2026-04-18 10:54:06 +02:00
# include <LibWeb/Bindings/HTMLCanvasElement.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-11-08 20:14:37 +08:00
# include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
2025-08-08 10:28:41 +01:00
# include <LibWeb/CSS/StyleValues/KeywordStyleValue.h>
2026-03-23 21:43:45 +13:00
# include <LibWeb/CSS/StyleValues/NumberStyleValue.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>
2025-03-06 23:37:13 +01:00
# include <LibWeb/HTML/Canvas/SerializeBitmap.h>
2020-07-26 15:08:16 +02:00
# include <LibWeb/HTML/CanvasRenderingContext2D.h>
# include <LibWeb/HTML/HTMLCanvasElement.h>
2026-04-04 17:29:11 +13:00
# include <LibWeb/HTML/Navigable.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>
2020-11-22 15:53:01 +01:00
# include <LibWeb/Layout/CanvasBox.h>
2026-02-19 19:31:20 +01:00
# include <LibWeb/Page/Page.h>
2023-08-03 01:29:31 +02:00
# include <LibWeb/Platform/EventLoopPlugin.h>
2026-04-04 17:29:11 +13:00
# include <LibWeb/Platform/FontPlugin.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 ) ;
2026-02-19 19:31:20 +01:00
document ( ) . page ( ) . register_canvas_element ( { } , unique_id ( ) ) ;
}
void HTMLCanvasElement : : finalize ( )
{
2026-05-16 22:58:31 +02:00
clear_compositor_surface ( ) ;
2026-02-19 19:31:20 +01:00
Base : : finalize ( ) ;
document ( ) . page ( ) . unregister_canvas_element ( { } , unique_id ( ) ) ;
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 ) ;
2026-04-14 18:03:06 +01:00
visitor . visit ( m_context ) ;
2022-09-02 15:53:02 +02:00
}
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 ) ;
}
2026-04-30 10:44:26 +01:00
void HTMLCanvasElement : : apply_presentational_hints ( Vector < CSS : : StyleProperty > & properties ) const
2023-08-31 19:22:17 +02:00
{
2026-04-30 10:44:26 +01:00
Base : : apply_presentational_hints ( properties ) ;
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
2026-04-30 10:44:26 +01: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 19:22:17 +02:00
}
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
}
2026-05-16 22:58:31 +02:00
Painting : : CompositorSurfaceId HTMLCanvasElement : : ensure_compositor_surface_id ( )
2026-02-19 19:31:20 +01:00
{
2026-05-16 22:58:31 +02:00
if ( ! m_compositor_surface_id . has_value ( ) )
m_compositor_surface_id = Painting : : allocate_compositor_surface_id ( ) ;
return * m_compositor_surface_id ;
2026-02-19 19:31:20 +01:00
}
2022-06-04 04:22:42 +01:00
void HTMLCanvasElement : : reset_context_to_default_state ( )
{
2026-05-16 22:58:31 +02:00
clear_compositor_surface ( ) ;
2022-06-04 04:22:42 +01:00
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.
} ) ;
}
2026-04-04 17:29:11 +13: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 ,
2026-04-19 00:41:38 +12:00
// NB: We don't require a color scheme since this is only used for resolving font values, not colors
2026-04-04 17:29:11 +13:00
. color_scheme = { }
} ;
}
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.
} ) ;
}
2025-10-31 12:30:47 +00:00
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 ;
2025-10-31 12:30:47 +00:00
set_attribute_value ( 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 ( ) ;
2021-11-13 00:54:21 +01:00
}
2025-10-31 12:30:47 +00:00
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 ;
2025-10-31 12:30:47 +00:00
set_attribute_value ( 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 ( ) ;
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 ( ) ;
2026-02-26 11:57:29 +01:00
set_needs_layout_update ( DOM : : SetNeedsLayoutReason : : HTMLCanvasElementWidthOrHeightChange ) ;
2024-12-28 15:47:58 +11:00
}
}
LibWeb: Make layout nodes refcounted
Move the layout tree from GC allocation to refcounted ownership so
removed layout and paint subtrees are destroyed synchronously instead
of waiting for the next GC sweep. This dramatically reduces GC memory
usage peaks after layout tree churn and makes it easier for memory use
to fall back after large document updates.
Update layout factories, tree traversal, SVG layout node creation,
paintable back-pointers, and pseudo-element layout links to use RefPtr
ownership.
Make display: contents follow the same shape as Blink and WebKit: the
element itself does not create a layout node, and its children are
flattened into the nearest layout parent. Wrap direct non-whitespace
text in an anonymous inline node when the boxless element contributes
inherited style to that text.
Use an internal inline wrapper for display: contents pseudo-elements
so generated content can still participate in layout, painting, hit
testing, and pseudo-element queries. Keep CSSOM reporting the computed
display value from the pseudo style, not the internal wrapper.
Remove the retained out-of-tree layout node list and its testing hook,
since the flattened model does not need a side owner for boxless
elements. Add coverage for inherited text style, dynamic insertion
order, pseudo-element hit testing, and computed style queries.
2026-06-07 17:50:33 +02:00
RefPtr < Layout : : Node > HTMLCanvasElement : : create_layout_node ( CSS : : ComputedProperties const & style )
2020-03-19 19:07:56 +01:00
{
LibWeb: Make layout nodes refcounted
Move the layout tree from GC allocation to refcounted ownership so
removed layout and paint subtrees are destroyed synchronously instead
of waiting for the next GC sweep. This dramatically reduces GC memory
usage peaks after layout tree churn and makes it easier for memory use
to fall back after large document updates.
Update layout factories, tree traversal, SVG layout node creation,
paintable back-pointers, and pseudo-element layout links to use RefPtr
ownership.
Make display: contents follow the same shape as Blink and WebKit: the
element itself does not create a layout node, and its children are
flattened into the nearest layout parent. Wrap direct non-whitespace
text in an anonymous inline node when the boxless element contributes
inherited style to that text.
Use an internal inline wrapper for display: contents pseudo-elements
so generated content can still participate in layout, painting, hit
testing, and pseudo-element queries. Keep CSSOM reporting the computed
display value from the pseudo style, not the internal wrapper.
Remove the retained out-of-tree layout node list and its testing hook,
since the flattened model does not need a side owner for boxless
elements. Add coverage for inherited text style, dynamic insertion
order, pseudo-element hit testing, and computed style queries.
2026-06-07 17:50:33 +02:00
return make_ref_counted < Layout : : CanvasBox > ( document ( ) , * this , 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 )
2026-05-20 20:58:46 +02:00
return 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 )
2026-05-20 20:58:46 +02:00
return 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 )
2026-05-20 20:58:46 +02:00
return m_context . get < GC : : Ref < WebGL : : WebGL2RenderingContext > > ( ) ;
2024-12-05 20:56:18 -07:00
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
// https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-todataurl
2026-05-20 20:58:46 +02:00
String HTMLCanvasElement : : to_data_url ( StringView type , Optional < JS : : Value > js_quality )
2021-04-19 23:47:29 +02:00
{
2026-03-16 23:55:40 +00:00
// It is possible the canvas doesn't have an 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
2026-03-16 23:55:40 +00:00
surface = Gfx : : PaintingSurface : : create_with_size ( 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.
2025-06-24 11:18:14 +01: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 10:12:11 +02: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 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.
2026-05-05 12:36:43 +02:00
auto bitmap = surface - > snapshot_bitmap ( ) ;
2026-05-20 20:58:46 +02:00
Optional < double > quality = js_quality . has_value ( ) & & js_quality - > is_number ( ) ? js_quality - > as_double ( ) : Optional < double > ( ) ;
2025-03-06 23:37:13 +01:00
auto file = serialize_bitmap ( bitmap , type , quality ) ;
2023-08-03 10:12:11 +02:00
2025-06-24 11:18:14 +01:00
// 4. If file is null, then return "data:,".
2023-08-03 10:12:11 +02:00
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
2026-05-20 20:58:46 +02:00
WebIDL : : ExceptionOr < void > HTMLCanvasElement : : to_blob ( GC : : Ref < WebIDL : : CallbackType > callback , StringView type , Optional < JS : : Value > js_quality )
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.
// 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 21:24:22 +02:00
auto bitmap_result = get_bitmap_from_surface ( ) ;
2023-08-03 01:29:31 +02:00
2026-05-20 20:58:46 +02:00
Optional < double > quality = js_quality . has_value ( ) & & js_quality - > is_number ( ) ? js_quality - > as_double ( ) : Optional < double > ( ) ;
2025-03-06 23:37:13 +01: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 ) {
2025-03-06 23:37:13 +01:00
if ( auto result = serialize_bitmap ( * bitmap_result , type , quality ) ; ! result . is_error ( ) )
2023-08-03 01:29:31 +02: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-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 { } ;
}
2025-07-14 21:24:22 +02: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 23:55:40 +00:00
surface = Gfx : : PaintingSurface : : create_with_size ( size , Gfx : : BitmapFormat : : BGRA8888 , Gfx : : AlphaType : : Premultiplied ) ;
2025-07-14 21:24:22 +02:00
}
RefPtr < Gfx : : Bitmap > bitmap ;
if ( surface ) {
2026-05-05 12:36:43 +02:00
bitmap = surface - > snapshot_bitmap ( ) ;
2025-07-14 21:24:22 +02:00
}
return bitmap ;
}
2026-02-19 19:31:20 +01:00
void HTMLCanvasElement : : set_canvas_content_dirty ( )
{
m_canvas_content_dirty = true ;
}
2022-06-04 04:22:42 +01:00
void HTMLCanvasElement : : present ( )
{
2026-02-19 19:31:20 +01:00
if ( ! m_canvas_content_dirty )
return ;
m_canvas_content_dirty = false ;
2024-09-25 15:44:58 +02:00
2022-06-04 04:22:42 +01:00
m_context . visit (
2026-05-04 17:40:34 +02:00
[ ] ( GC : : Ref < CanvasRenderingContext2D > & context ) {
context - > present ( ) ;
2022-06-04 04:22:42 +01:00
} ,
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.
} ) ;
2026-02-19 19:31:20 +01:00
LibWeb+LibWebView+WebContent: Recover after Compositor process crashes
The browser previously treated the out-of-process Compositor as fatal.
Restart the shared Compositor from the browser process, reconnect
process-backed WebContent clients, recreate compositor contexts, restore
viewport state, and ask WebContent to repaint and republish canvas and
media resources. WebContent now marks its compositor connection lost,
returns conservative values for synchronous compositor queries while
reconnecting, and drops outgoing updates until the replacement transport
arrives.
Synchronous input queries through the compositor control connection now
use fallible IPC. If the Compositor exits after the open check or before
the sync reply arrives, scroll and mouse handling report that the
Compositor did not handle the event and let the normal WebContent
fallback run.
Mouse events queued while the Compositor is unavailable now fall back to
direct WebContent dispatch. This keeps input completion in step with the
pending-event queue.
Recovery is capped at three automatic restarts. If the restart limit is
exceeded, if restart, reconnect, or context recreation fails, or if the
replacement Compositor exits during active recovery, the browser crashes
instead of switching process-backed views to a fallback path.
2026-05-24 03:36:24 +02:00
update_compositor_surface ( ) ;
}
void HTMLCanvasElement : : republish_compositor_surface ( )
{
if ( m_canvas_content_dirty ) {
present ( ) ;
return ;
}
update_compositor_surface ( ) ;
}
void HTMLCanvasElement : : update_compositor_surface ( )
{
2026-02-19 19:31:20 +01:00
if ( auto surface = this - > surface ( ) ) {
surface - > flush ( ) ;
2026-05-18 16:00:28 +02:00
if ( auto navigable = document ( ) . navigable ( ) ; navigable & & navigable - > has_compositor_context ( ) )
navigable - > compositor_context ( ) . update_compositor_surface ( ensure_compositor_surface_id ( ) , surface - > snapshot_into_shared_image ( ) ) ;
2026-02-19 19:31:20 +01:00
}
2022-06-04 04:22:42 +01:00
}
2026-05-16 22:58:31 +02:00
void HTMLCanvasElement : : clear_compositor_surface ( )
{
if ( ! m_compositor_surface_id . has_value ( ) )
return ;
2026-05-18 16:00:28 +02:00
if ( auto navigable = document ( ) . navigable ( ) ; navigable & & navigable - > has_compositor_context ( ) )
navigable - > compositor_context ( ) . clear_compositor_surface ( * m_compositor_surface_id ) ;
2026-05-16 22:58:31 +02:00
}
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
}