mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-05-11 09:27:00 +02:00
WPT reftests capture screenshots through WebDriver::draw_bounding_box_from_the_framebuffer(), which creates a scratch 2D canvas outside normal script execution. This regressed when CanvasRenderingContext2DSettings started using the generated bindings conversion path. That path now looks at the current realm even for this codepath, whereas the previous handwritten conversion did not, so WebDriver screenshot capture could hit a missing running execution context and crash. Push a TemporaryExecutionContext while creating the scratch canvas so the generated conversion has the realm it expects and WPT reftests no longer crash.
102 lines
5.0 KiB
C++
102 lines
5.0 KiB
C++
/*
|
||
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@ladybird.org>
|
||
*
|
||
* 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/Scripting/TemporaryExecutionContext.h>
|
||
#include <LibWeb/HTML/TagNames.h>
|
||
#include <LibWeb/HTML/TraversableNavigable.h>
|
||
#include <LibWeb/Namespace.h>
|
||
#include <LibWeb/Page/Page.h>
|
||
#include <LibWeb/WebDriver/Screenshot.h>
|
||
|
||
namespace Web::WebDriver {
|
||
|
||
// https://w3c.github.io/webdriver/#dfn-draw-a-bounding-box-from-the-framebuffer
|
||
ErrorOr<GC::Ref<HTML::HTMLCanvasElement>, WebDriver::Error> draw_bounding_box_from_the_framebuffer(HTML::BrowsingContext& browsing_context, DOM::Element& element, Gfx::IntRect rect)
|
||
{
|
||
HTML::TemporaryExecutionContext execution_context { element.realm() };
|
||
|
||
// 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();
|
||
auto& canvas = as<HTML::HTMLCanvasElement>(*canvas_element);
|
||
|
||
// FIXME: Handle DevicePixelRatio in HiDPI mode.
|
||
canvas.set_width(paint_width);
|
||
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.
|
||
MUST(canvas.create_2d_context({}));
|
||
canvas.allocate_painting_surface_if_needed();
|
||
if (!canvas.surface())
|
||
return Error::from_code(ErrorCode::UnableToCaptureScreen, "Failed to allocate painting surface"sv);
|
||
|
||
// 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 };
|
||
|
||
auto bitmap = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied, canvas.surface()->size()));
|
||
auto painting_surface = Gfx::PaintingSurface::wrap_bitmap(bitmap);
|
||
IGNORE_USE_IN_ESCAPING_LAMBDA bool did_paint = false;
|
||
HTML::PaintConfig paint_config { .canvas_fill_rect = paint_rect };
|
||
browsing_context.active_document()->navigable()->render_screenshot(painting_surface, paint_config, [&did_paint] {
|
||
did_paint = true;
|
||
});
|
||
HTML::main_thread_event_loop().spin_until(GC::create_function(HTML::main_thread_event_loop().heap(), [&] {
|
||
return did_paint;
|
||
}));
|
||
|
||
canvas.surface()->write_from_bitmap(*bitmap);
|
||
|
||
// 7. Return success with canvas.
|
||
return canvas;
|
||
}
|
||
|
||
// https://w3c.github.io/webdriver/#dfn-encoding-a-canvas-as-base64
|
||
Response encode_canvas_element(HTML::HTMLCanvasElement& canvas)
|
||
{
|
||
// 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.
|
||
if (canvas.surface()->size().is_empty())
|
||
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]
|
||
auto data_url = canvas.to_data_url("image/png"sv, JS::js_undefined());
|
||
|
||
// 5. Let index be the index of "," in data url.
|
||
auto index = data_url.find_byte_offset(',');
|
||
VERIFY(index.has_value());
|
||
|
||
// 6. Let encoded string be a substring of data url using (index + 1) as the start argument.
|
||
auto encoded_string = MUST(data_url.substring_from_byte_offset(*index + 1));
|
||
|
||
// 7. Return success with data encoded string.
|
||
return JsonValue { move(encoded_string) };
|
||
}
|
||
|
||
}
|