2020-03-19 21:07:56 +03:00
/*
* Copyright ( c ) 2018 - 2020 , Andreas Kling < kling @ serenityos . org >
*
2021-04-22 11:24:48 +03:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-03-19 21:07:56 +03:00
*/
2021-04-20 00:47:29 +03:00
# include <AK/Base64.h>
2020-04-15 17:55:36 +03:00
# include <AK/Checked.h>
2020-03-19 21:07:56 +03:00
# include <LibGfx/Bitmap.h>
2023-03-21 21:58:06 +03:00
# include <LibGfx/ImageFormats/PNGWriter.h>
2021-09-24 14:49:57 +03:00
# include <LibWeb/CSS/StyleComputer.h>
2020-03-19 21:07:56 +03:00
# include <LibWeb/DOM/Document.h>
2020-07-26 16:08:16 +03:00
# include <LibWeb/HTML/CanvasRenderingContext2D.h>
# include <LibWeb/HTML/HTMLCanvasElement.h>
2020-11-22 17:53:01 +03:00
# include <LibWeb/Layout/CanvasBox.h>
2020-03-19 21:07:56 +03:00
2020-07-28 19:20:36 +03:00
namespace Web : : HTML {
2020-03-19 21:07:56 +03:00
2020-04-15 13:12:19 +03:00
static constexpr auto max_canvas_area = 16384 * 16384 ;
2022-02-18 23:00:52 +03:00
HTMLCanvasElement : : HTMLCanvasElement ( DOM : : Document & document , DOM : : QualifiedName qualified_name )
2021-02-07 13:20:15 +03:00
: HTMLElement ( document , move ( qualified_name ) )
2020-03-19 21:07:56 +03:00
{
}
2022-03-14 22:21:51 +03:00
HTMLCanvasElement : : ~ HTMLCanvasElement ( ) = default ;
2020-03-19 21:07:56 +03:00
2023-01-28 20:33:35 +03:00
JS : : ThrowCompletionOr < void > HTMLCanvasElement : : initialize ( JS : : Realm & realm )
2023-01-10 14:28:20 +03:00
{
2023-01-28 20:33:35 +03:00
MUST_OR_THROW_OOM ( Base : : initialize ( realm ) ) ;
2023-01-10 14:28:20 +03:00
set_prototype ( & Bindings : : ensure_web_prototype < Bindings : : HTMLCanvasElementPrototype > ( realm , " HTMLCanvasElement " ) ) ;
2023-01-28 20:33:35 +03:00
return { } ;
2023-01-10 14:28:20 +03:00
}
2022-09-02 16:53:02 +03:00
void HTMLCanvasElement : : visit_edges ( Cell : : Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
m_context . visit (
[ & ] ( JS : : NonnullGCPtr < CanvasRenderingContext2D > & context ) {
visitor . visit ( context . ptr ( ) ) ;
} ,
[ & ] ( JS : : NonnullGCPtr < WebGL : : WebGLRenderingContext > & context ) {
visitor . visit ( context . ptr ( ) ) ;
} ,
[ ] ( Empty ) {
} ) ;
}
2020-06-21 16:37:13 +03:00
unsigned HTMLCanvasElement : : width ( ) const
2020-03-19 21:07:56 +03:00
{
2020-06-21 16:37:13 +03:00
return attribute ( HTML : : AttributeNames : : width ) . to_uint ( ) . value_or ( 300 ) ;
2020-03-19 21:07:56 +03:00
}
2020-06-21 16:37:13 +03:00
unsigned HTMLCanvasElement : : height ( ) const
2020-03-19 21:07:56 +03:00
{
2020-06-21 16:37:13 +03:00
return attribute ( HTML : : AttributeNames : : height ) . to_uint ( ) . value_or ( 150 ) ;
2020-03-19 21:07:56 +03:00
}
2022-06-04 06:22:42 +03:00
void HTMLCanvasElement : : reset_context_to_default_state ( )
{
m_context . visit (
2022-09-02 16:53:02 +03:00
[ ] ( JS : : NonnullGCPtr < CanvasRenderingContext2D > & context ) {
2022-06-04 06:22:42 +03:00
context - > reset_to_default_state ( ) ;
} ,
2022-09-02 16:53:02 +03:00
[ ] ( JS : : NonnullGCPtr < WebGL : : WebGLRenderingContext > & ) {
2022-06-04 06:22:42 +03:00
TODO ( ) ;
} ,
[ ] ( Empty ) {
// Do nothing.
} ) ;
}
2021-11-13 02:54:21 +03:00
void HTMLCanvasElement : : set_width ( unsigned value )
{
2022-12-04 21:02:33 +03:00
MUST ( set_attribute ( HTML : : AttributeNames : : width , DeprecatedString : : number ( value ) ) ) ;
2022-04-11 04:05:19 +03:00
m_bitmap = nullptr ;
2022-06-04 06:22:42 +03:00
reset_context_to_default_state ( ) ;
2021-11-13 02:54:21 +03:00
}
void HTMLCanvasElement : : set_height ( unsigned value )
{
2022-12-04 21:02:33 +03:00
MUST ( set_attribute ( HTML : : AttributeNames : : height , DeprecatedString : : number ( value ) ) ) ;
2022-04-11 04:05:19 +03:00
m_bitmap = nullptr ;
2022-06-04 06:22:42 +03:00
reset_context_to_default_state ( ) ;
2021-11-13 02:54:21 +03:00
}
2022-10-17 15:41:50 +03:00
JS : : GCPtr < Layout : : Node > HTMLCanvasElement : : create_layout_node ( NonnullRefPtr < CSS : : StyleProperties > style )
2020-03-19 21:07:56 +03:00
{
2022-10-17 15:41:50 +03:00
return heap ( ) . allocate_without_realm < Layout : : CanvasBox > ( document ( ) , * this , move ( style ) ) ;
2020-03-19 21:07:56 +03:00
}
2022-06-04 06:22:42 +03:00
HTMLCanvasElement : : HasOrCreatedContext HTMLCanvasElement : : create_2d_context ( )
{
if ( ! m_context . has < Empty > ( ) )
2022-09-02 16:53:02 +03:00
return m_context . has < JS : : NonnullGCPtr < CanvasRenderingContext2D > > ( ) ? HasOrCreatedContext : : Yes : HasOrCreatedContext : : No ;
2022-06-04 06:22:42 +03:00
2023-02-15 20:48:20 +03:00
m_context = CanvasRenderingContext2D : : create ( realm ( ) , * this ) . release_value_but_fixme_should_propagate_errors ( ) ;
2022-06-04 06:22:42 +03:00
return HasOrCreatedContext : : Yes ;
}
JS : : ThrowCompletionOr < HTMLCanvasElement : : HasOrCreatedContext > HTMLCanvasElement : : create_webgl_context ( JS : : Value options )
2020-03-19 21:07:56 +03:00
{
2022-06-04 06:22:42 +03:00
if ( ! m_context . has < Empty > ( ) )
2022-09-02 16:53:02 +03:00
return m_context . has < JS : : NonnullGCPtr < WebGL : : WebGLRenderingContext > > ( ) ? HasOrCreatedContext : : Yes : HasOrCreatedContext : : No ;
2022-06-04 06:22:42 +03:00
2022-09-26 03:12:50 +03:00
auto maybe_context = TRY ( WebGL : : WebGLRenderingContext : : create ( realm ( ) , * this , options ) ) ;
2022-06-04 06:22:42 +03:00
if ( ! maybe_context )
return HasOrCreatedContext : : No ;
2022-09-02 16:53:02 +03:00
m_context = JS : : NonnullGCPtr < WebGL : : WebGLRenderingContext > ( * maybe_context ) ;
2022-06-04 06:22:42 +03:00
return HasOrCreatedContext : : Yes ;
2020-03-19 21:07:56 +03:00
}
2022-06-04 06:22:42 +03:00
// https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-getcontext
2022-12-04 21:02:33 +03:00
JS : : ThrowCompletionOr < HTMLCanvasElement : : RenderingContext > HTMLCanvasElement : : get_context ( DeprecatedString const & type , JS : : Value options )
2020-04-15 13:12:19 +03:00
{
2022-06-04 06: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 ) {
if ( create_2d_context ( ) = = HasOrCreatedContext : : Yes )
2022-09-02 16:53:02 +03:00
return JS : : make_handle ( * m_context . get < JS : : NonnullGCPtr < HTML : : CanvasRenderingContext2D > > ( ) ) ;
2022-06-04 06: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 ) ) {
if ( TRY ( create_webgl_context ( options ) ) = = HasOrCreatedContext : : Yes )
2022-09-02 16:53:02 +03:00
return JS : : make_handle ( * m_context . get < JS : : NonnullGCPtr < WebGL : : WebGLRenderingContext > > ( ) ) ;
2022-06-04 06:22:42 +03:00
return Empty { } ;
}
return Empty { } ;
}
static Gfx : : IntSize bitmap_size_for_canvas ( HTMLCanvasElement const & canvas , size_t minimum_width , size_t minimum_height )
{
auto width = max ( canvas . width ( ) , minimum_width ) ;
auto height = max ( canvas . height ( ) , minimum_height ) ;
2020-04-15 17:55:36 +03:00
Checked < size_t > area = width ;
area * = height ;
if ( area . has_overflow ( ) ) {
2021-01-09 16:02:45 +03:00
dbgln ( " Refusing to create {}x{} canvas (overflow) " , width , height ) ;
2020-04-15 13:12:19 +03:00
return { } ;
}
2020-04-15 17:55:36 +03:00
if ( area . value ( ) > max_canvas_area ) {
2021-01-09 16:02:45 +03:00
dbgln ( " Refusing to create {}x{} canvas (exceeds maximum size) " , width , height ) ;
2020-04-15 13:12:19 +03:00
return { } ;
}
2020-06-21 16:37:13 +03:00
return Gfx : : IntSize ( width , height ) ;
2020-04-15 13:12:19 +03:00
}
2022-06-04 06:22:42 +03:00
bool HTMLCanvasElement : : create_bitmap ( size_t minimum_width , size_t minimum_height )
2020-03-19 21:07:56 +03:00
{
2022-06-04 06:22:42 +03:00
auto size = bitmap_size_for_canvas ( * this , minimum_width , minimum_height ) ;
2020-04-15 13:12:19 +03:00
if ( size . is_empty ( ) ) {
m_bitmap = nullptr ;
return false ;
2020-03-19 21:07:56 +03:00
}
2021-11-06 21:30:59 +03:00
if ( ! m_bitmap | | m_bitmap - > size ( ) ! = size ) {
2023-01-20 22:06:05 +03:00
auto bitmap_or_error = Gfx : : Bitmap : : create ( Gfx : : BitmapFormat : : BGRA8888 , size ) ;
2021-11-06 21:30:59 +03:00
if ( bitmap_or_error . is_error ( ) )
return false ;
m_bitmap = bitmap_or_error . release_value_but_fixme_should_propagate_errors ( ) ;
}
2020-04-15 17:55:36 +03:00
return m_bitmap ;
2020-03-19 21:07:56 +03:00
}
2022-12-04 21:02:33 +03:00
DeprecatedString HTMLCanvasElement : : to_data_url ( DeprecatedString const & type , [[maybe_unused]] Optional < double > quality ) const
2021-04-20 00:47:29 +03:00
{
if ( ! m_bitmap )
return { } ;
if ( type ! = " image/png " )
return { } ;
2022-12-05 22:34:27 +03:00
auto encoded_bitmap_or_error = Gfx : : PNGWriter : : encode ( * m_bitmap ) ;
if ( encoded_bitmap_or_error . is_error ( ) ) {
dbgln ( " Gfx::PNGWriter failed to encode the HTMLCanvasElement: {} " , encoded_bitmap_or_error . error ( ) ) ;
return { } ;
}
2022-12-19 02:23:47 +03:00
auto base64_encoded_or_error = encode_base64 ( encoded_bitmap_or_error . value ( ) ) ;
if ( base64_encoded_or_error . is_error ( ) ) {
// FIXME: propagate error
return { } ;
}
return AK : : URL : : create_with_data ( type , base64_encoded_or_error . release_value ( ) . to_deprecated_string ( ) , true ) . to_deprecated_string ( ) ;
2021-04-20 00:47:29 +03:00
}
2022-06-04 06:22:42 +03:00
void HTMLCanvasElement : : present ( )
{
m_context . visit (
2022-09-02 16:53:02 +03:00
[ ] ( JS : : NonnullGCPtr < CanvasRenderingContext2D > & ) {
2022-06-04 06:22:42 +03:00
// Do nothing, CRC2D writes directly to the canvas bitmap.
} ,
2022-09-02 16:53:02 +03:00
[ ] ( JS : : NonnullGCPtr < WebGL : : WebGLRenderingContext > & context ) {
2022-06-04 06:22:42 +03:00
context - > present ( ) ;
} ,
[ ] ( Empty ) {
// Do nothing.
} ) ;
}
2020-03-19 21:07:56 +03:00
}