2022-11-10 14:57:16 -05:00
/*
2024-10-30 20:29:26 -04:00
* Copyright ( c ) 2022 - 2024 , Tim Flynn < trflynn89 @ ladybird . org >
2022-11-10 14:57:16 -05:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# include <LibGfx/Bitmap.h>
# include <LibWeb/DOM/Document.h>
# include <LibWeb/DOM/ElementFactory.h>
# include <LibWeb/HTML/BrowsingContext.h>
# include <LibWeb/HTML/HTMLCanvasElement.h>
# include <LibWeb/HTML/TagNames.h>
2023-08-22 16:00:42 +02:00
# include <LibWeb/HTML/TraversableNavigable.h>
2022-11-10 14:57:16 -05:00
# include <LibWeb/Namespace.h>
# include <LibWeb/Page/Page.h>
# include <LibWeb/WebDriver/Screenshot.h>
namespace Web : : WebDriver {
2024-10-30 20:29:26 -04:00
// https://w3c.github.io/webdriver/#dfn-draw-a-bounding-box-from-the-framebuffer
2024-11-15 04:01:23 +13:00
ErrorOr < GC : : Ref < HTML : : HTMLCanvasElement > , WebDriver : : Error > draw_bounding_box_from_the_framebuffer ( HTML : : BrowsingContext & browsing_context , DOM : : Element & element , Gfx : : IntRect rect )
2024-10-30 20:29:26 -04:00
{
// 1. If either the initial viewport's width or height is 0 CSS pixels, return error with error code unable to capture screen.
auto viewport_rect = browsing_context . top_level_traversable ( ) - > viewport_rect ( ) ;
if ( viewport_rect . is_empty ( ) )
return Error : : from_code ( ErrorCode : : UnableToCaptureScreen , " Viewport is empty " sv ) ;
auto viewport_device_rect = browsing_context . page ( ) . enclosing_device_rect ( viewport_rect ) . to_type < int > ( ) ;
// 2. Let paint width be the initial viewport's width – min(rectangle x coordinate, rectangle x coordinate + rectangle width dimension).
auto paint_width = viewport_device_rect . width ( ) - min ( rect . x ( ) , rect . x ( ) + rect . width ( ) ) ;
// 3. Let paint height be the initial viewport's height – min(rectangle y coordinate, rectangle y coordinate + rectangle height dimension).
auto paint_height = viewport_device_rect . height ( ) - min ( rect . y ( ) , rect . y ( ) + rect . height ( ) ) ;
// 4. Let canvas be a new canvas element, and set its width and height to paint width and paint height, respectively.
auto canvas_element = DOM : : create_element ( element . document ( ) , HTML : : TagNames : : canvas , Namespace : : HTML ) . release_value_but_fixme_should_propagate_errors ( ) ;
2025-01-21 09:12:05 -05:00
auto & canvas = as < HTML : : HTMLCanvasElement > ( * canvas_element ) ;
2024-10-30 20:29:26 -04:00
// FIXME: Handle DevicePixelRatio in HiDPI mode.
MUST ( canvas . set_width ( paint_width ) ) ;
MUST ( canvas . set_height ( paint_height ) ) ;
// FIXME: 5. Let context, a canvas context mode, be the result of invoking the 2D context creation algorithm given canvas as the target.
2025-04-28 16:01:47 +02:00
MUST ( canvas . create_2d_context ( { } ) ) ;
2024-11-29 20:17:25 +01:00
canvas . allocate_painting_surface_if_needed ( ) ;
if ( ! canvas . surface ( ) )
return Error : : from_code ( ErrorCode : : UnableToCaptureScreen , " Failed to allocate painting surface " sv ) ;
2024-10-30 20:29:26 -04:00
// 6. Complete implementation specific steps equivalent to drawing the region of the framebuffer specified by the following coordinates onto context:
// - X coordinate: rectangle x coordinate
// - Y coordinate: rectangle y coordinate
// - Width: paint width
// - Height: paint height
Gfx : : IntRect paint_rect { rect . x ( ) , rect . y ( ) , paint_width , paint_height } ;
2024-11-09 05:48:17 +01:00
auto bitmap = MUST ( Gfx : : Bitmap : : create ( Gfx : : BitmapFormat : : BGRA8888 , Gfx : : AlphaType : : Premultiplied , canvas . surface ( ) - > size ( ) ) ) ;
2025-06-27 04:39:16 +02:00
auto painting_surface = Gfx : : PaintingSurface : : wrap_bitmap ( bitmap ) ;
2025-02-25 04:07:53 +01:00
IGNORE_USE_IN_ESCAPING_LAMBDA bool did_paint = false ;
2025-06-26 22:33:58 +02:00
HTML : : PaintConfig paint_config { . canvas_fill_rect = paint_rect } ;
2025-06-27 04:39:16 +02:00
browsing_context . active_document ( ) - > navigable ( ) - > start_display_list_rendering ( painting_surface , paint_config , [ & did_paint ] {
2025-02-25 04:07:53 +01:00
did_paint = true ;
} ) ;
HTML : : main_thread_event_loop ( ) . spin_until ( GC : : create_function ( HTML : : main_thread_event_loop ( ) . heap ( ) , [ & ] {
return did_paint ;
} ) ) ;
2024-11-13 10:50:58 +00:00
canvas . surface ( ) - > write_from_bitmap ( * bitmap ) ;
2024-10-30 20:29:26 -04:00
// 7. Return success with canvas.
return canvas ;
}
2022-11-10 14:57:16 -05:00
// https://w3c.github.io/webdriver/#dfn-encoding-a-canvas-as-base64
2024-10-30 20:29:26 -04:00
Response encode_canvas_element ( HTML : : HTMLCanvasElement & canvas )
2022-11-10 14:57:16 -05:00
{
// FIXME: 1. If the canvas element’ s bitmap’ s origin-clean flag is set to false, return error with error code unable to capture screen.
// 2. If the canvas element’ s bitmap has no pixels (i.e. either its horizontal dimension or vertical dimension is zero) then return error with error code unable to capture screen.
2024-09-25 15:44:58 +02:00
if ( canvas . surface ( ) - > size ( ) . is_empty ( ) )
2022-11-10 14:57:16 -05:00
return Error : : from_code ( ErrorCode : : UnableToCaptureScreen , " Captured screenshot is empty " sv ) ;
// 3. Let file be a serialization of the canvas element’ s bitmap as a file, using "image/png" as an argument.
// 4. Let data url be a data: URL representing file. [RFC2397]
2024-11-08 09:13:54 +01:00
auto data_url = canvas . to_data_url ( " image/png " sv , JS : : js_undefined ( ) ) ;
2022-11-10 14:57:16 -05:00
// 5. Let index be the index of "," in data url.
2023-09-03 15:58:55 +12:00
auto index = data_url . find_byte_offset ( ' , ' ) ;
2022-11-10 14:57:16 -05:00
VERIFY ( index . has_value ( ) ) ;
// 6. Let encoded string be a substring of data url using (index + 1) as the start argument.
2023-09-03 15:58:55 +12:00
auto encoded_string = MUST ( data_url . substring_from_byte_offset ( * index + 1 ) ) ;
2022-11-10 14:57:16 -05:00
// 7. Return success with data encoded string.
2025-02-17 13:58:21 -05:00
return JsonValue { move ( encoded_string ) } ;
2022-11-10 14:57:16 -05:00
}
}