2022-04-03 16:15:36 +00:00
/*
* Copyright ( c ) 2022 , Dex ♪ < dexes . ttp @ gmail . com >
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# include <AK/Assertions.h>
# include <AK/ByteBuffer.h>
# include <AK/Format.h>
# include <AK/HashTable.h>
2022-07-18 18:23:19 +00:00
# include <AK/LexicalPath.h>
2022-04-03 16:15:36 +00:00
# include <AK/NonnullOwnPtr.h>
# include <AK/StringBuilder.h>
# include <AK/Types.h>
# include <LibCore/ArgsParser.h>
2022-09-30 06:59:37 +00:00
# include <LibCore/ConfigFile.h>
2022-04-03 16:15:36 +00:00
# include <LibCore/EventLoop.h>
# include <LibCore/File.h>
2022-04-03 16:20:05 +00:00
# include <LibCore/MemoryStream.h>
# include <LibCore/Stream.h>
2022-02-26 16:50:31 +00:00
# include <LibCore/System.h>
2022-11-22 01:03:45 +00:00
# include <LibCore/SystemServerTakeover.h>
2022-04-03 16:15:36 +00:00
# include <LibCore/Timer.h>
2022-04-03 16:20:05 +00:00
# include <LibGemini/GeminiRequest.h>
# include <LibGemini/GeminiResponse.h>
# include <LibGemini/Job.h>
2022-04-03 16:15:36 +00:00
# include <LibGfx/Bitmap.h>
# include <LibGfx/Font/FontDatabase.h>
# include <LibGfx/ImageDecoder.h>
# include <LibGfx/PNGWriter.h>
# include <LibGfx/Rect.h>
2022-04-03 16:20:05 +00:00
# include <LibHTTP/HttpRequest.h>
# include <LibHTTP/HttpResponse.h>
# include <LibHTTP/HttpsJob.h>
# include <LibHTTP/Job.h>
2022-04-03 16:15:36 +00:00
# include <LibMain/Main.h>
# include <LibWeb/Cookie/ParsedCookie.h>
# include <LibWeb/DOM/Document.h>
# include <LibWeb/HTML/BrowsingContext.h>
# include <LibWeb/Layout/InitialContainingBlock.h>
# include <LibWeb/Loader/ResourceLoader.h>
# include <LibWeb/Page/Page.h>
# include <LibWeb/Painting/PaintableBox.h>
2022-09-21 16:22:53 +00:00
# include <LibWeb/Platform/EventLoopPluginSerenity.h>
# include <LibWeb/Platform/FontPluginSerenity.h>
2022-09-16 13:01:47 +00:00
# include <LibWeb/Platform/ImageCodecPlugin.h>
2022-04-03 16:15:36 +00:00
# include <LibWeb/WebSockets/WebSocket.h>
2022-04-03 16:20:05 +00:00
# include <LibWebSocket/ConnectionInfo.h>
# include <LibWebSocket/Message.h>
# include <LibWebSocket/WebSocket.h>
2022-11-22 01:03:45 +00:00
# include <WebContent/WebDriverConnection.h>
2022-04-03 16:15:36 +00:00
class HeadlessBrowserPageClient final : public Web : : PageClient {
public :
static NonnullOwnPtr < HeadlessBrowserPageClient > create ( )
{
return adopt_own ( * new HeadlessBrowserPageClient ( ) ) ;
}
2022-11-21 20:22:26 +00:00
virtual Web : : Page & page ( ) override { return * m_page ; }
virtual Web : : Page const & page ( ) const override { return * m_page ; }
2022-04-03 16:15:36 +00:00
Web : : Layout : : InitialContainingBlock * layout_root ( )
{
auto * document = page ( ) . top_level_browsing_context ( ) . active_document ( ) ;
if ( ! document )
return nullptr ;
return document - > layout_node ( ) ;
}
void load ( AK : : URL const & url )
{
page ( ) . load ( url ) ;
}
2022-11-25 17:07:19 +00:00
virtual void paint ( Web : : DevicePixelRect const & content_rect , Gfx : : Bitmap & target ) override
2022-04-03 16:15:36 +00:00
{
Gfx : : Painter painter ( target ) ;
2022-11-25 17:07:19 +00:00
Gfx : : IntRect int_content_rect { content_rect . x ( ) . value ( ) , content_rect . y ( ) . value ( ) , content_rect . width ( ) . value ( ) , content_rect . height ( ) . value ( ) } ;
2022-04-03 16:15:36 +00:00
if ( auto * document = page ( ) . top_level_browsing_context ( ) . active_document ( ) )
document - > update_layout ( ) ;
2022-11-25 17:07:19 +00:00
painter . fill_rect ( { { } , int_content_rect . size ( ) } , palette ( ) . base ( ) ) ;
2022-04-03 16:15:36 +00:00
auto * layout_root = this - > layout_root ( ) ;
if ( ! layout_root ) {
return ;
}
2022-11-25 17:07:19 +00:00
Web : : PaintContext context ( painter , palette ( ) , int_content_rect . top_left ( ) ) ;
2022-04-03 16:15:36 +00:00
context . set_should_show_line_box_borders ( false ) ;
2022-11-25 17:07:19 +00:00
context . set_viewport_rect ( int_content_rect ) ;
2022-04-03 16:15:36 +00:00
context . set_has_focus ( true ) ;
layout_root - > paint_all_phases ( context ) ;
}
void setup_palette ( Core : : AnonymousBuffer theme_buffer )
{
m_palette_impl = Gfx : : PaletteImpl : : create_with_anonymous_buffer ( theme_buffer ) ;
}
2022-07-18 16:44:22 +00:00
void set_viewport_rect ( Gfx : : IntRect viewport_rect )
{
page ( ) . top_level_browsing_context ( ) . set_viewport_rect ( viewport_rect ) ;
}
2022-11-25 17:07:19 +00:00
void set_screen_rect ( Web : : DevicePixelRect screen_rect )
2022-04-03 16:15:36 +00:00
{
m_screen_rect = screen_rect ;
}
2022-11-22 01:03:45 +00:00
ErrorOr < void > connect_to_webdriver ( StringView webdriver_ipc_path )
{
VERIFY ( ! m_webdriver ) ;
m_webdriver = TRY ( WebContent : : WebDriverConnection : : connect ( * this , webdriver_ipc_path ) ) ;
return { } ;
}
ErrorOr < void > connect_to_webdriver ( int webdriver_fd_passing_socket )
{
VERIFY ( ! m_webdriver ) ;
VERIFY ( webdriver_fd_passing_socket > = 0 ) ;
auto socket = TRY ( Core : : take_over_socket_from_system_server ( " WebDriver " sv ) ) ;
m_webdriver = TRY ( WebContent : : WebDriverConnection : : try_create ( move ( socket ) , * this ) ) ;
m_webdriver - > set_fd_passing_socket ( TRY ( Core : : Stream : : LocalSocket : : adopt_fd ( webdriver_fd_passing_socket ) ) ) ;
return { } ;
}
2022-04-03 16:15:36 +00:00
// ^Web::PageClient
2022-11-21 20:18:42 +00:00
virtual bool is_connection_open ( ) const override
{
2022-11-22 01:03:45 +00:00
if ( m_webdriver )
return m_webdriver - > is_open ( ) ;
2022-11-21 20:18:42 +00:00
return true ;
}
2022-04-03 16:15:36 +00:00
virtual Gfx : : Palette palette ( ) const override
{
return Gfx : : Palette ( * m_palette_impl ) ;
}
2022-11-25 17:07:19 +00:00
virtual Web : : DevicePixelRect screen_rect ( ) const override
2022-04-03 16:15:36 +00:00
{
return m_screen_rect ;
}
2022-11-25 17:07:19 +00:00
virtual float device_pixels_per_css_pixel ( ) const override
{
return 1.0f ;
}
2022-04-03 16:15:36 +00:00
virtual Web : : CSS : : PreferredColorScheme preferred_color_scheme ( ) const override
{
return m_preferred_color_scheme ;
}
2022-12-04 18:02:33 +00:00
virtual void page_did_change_title ( DeprecatedString const & ) override
2022-04-03 16:15:36 +00:00
{
}
2022-11-09 23:29:35 +00:00
virtual void page_did_start_loading ( AK : : URL const & , bool ) override
2022-04-03 16:15:36 +00:00
{
}
virtual void page_did_finish_loading ( AK : : URL const & ) override
{
}
virtual void page_did_change_selection ( ) override
{
}
virtual void page_did_request_cursor_change ( Gfx : : StandardCursor ) override
{
}
2022-12-06 20:27:44 +00:00
virtual void page_did_request_context_menu ( Gfx : : IntPoint ) override
2022-04-03 16:15:36 +00:00
{
}
2022-12-06 20:27:44 +00:00
virtual void page_did_request_link_context_menu ( Gfx : : IntPoint , AK : : URL const & , DeprecatedString const & , unsigned ) override
2022-04-03 16:15:36 +00:00
{
}
2022-12-06 20:27:44 +00:00
virtual void page_did_request_image_context_menu ( Gfx : : IntPoint , AK : : URL const & , DeprecatedString const & , unsigned , Gfx : : Bitmap const * ) override
2022-04-03 16:15:36 +00:00
{
}
2022-12-04 18:02:33 +00:00
virtual void page_did_click_link ( AK : : URL const & , DeprecatedString const & , unsigned ) override
2022-04-03 16:15:36 +00:00
{
}
2022-12-04 18:02:33 +00:00
virtual void page_did_middle_click_link ( AK : : URL const & , DeprecatedString const & , unsigned ) override
2022-04-03 16:15:36 +00:00
{
}
2022-12-06 20:27:44 +00:00
virtual void page_did_enter_tooltip_area ( Gfx : : IntPoint , DeprecatedString const & ) override
2022-04-03 16:15:36 +00:00
{
}
virtual void page_did_leave_tooltip_area ( ) override
{
}
virtual void page_did_hover_link ( AK : : URL const & ) override
{
}
virtual void page_did_unhover_link ( ) override
{
}
virtual void page_did_invalidate ( Gfx : : IntRect const & ) override
{
}
virtual void page_did_change_favicon ( Gfx : : Bitmap const & ) override
{
}
virtual void page_did_layout ( ) override
{
}
virtual void page_did_request_scroll_into_view ( Gfx : : IntRect const & ) override
{
}
2022-12-04 18:02:33 +00:00
virtual void page_did_request_alert ( DeprecatedString const & ) override
2022-04-03 16:15:36 +00:00
{
}
2022-12-04 18:02:33 +00:00
virtual void page_did_request_confirm ( DeprecatedString const & ) override
2022-04-03 16:15:36 +00:00
{
}
2022-12-04 18:02:33 +00:00
virtual void page_did_request_prompt ( DeprecatedString const & , DeprecatedString const & ) override
2022-04-03 16:15:36 +00:00
{
}
2022-12-04 18:02:33 +00:00
virtual DeprecatedString page_did_request_cookie ( AK : : URL const & , Web : : Cookie : : Source ) override
2022-04-03 16:15:36 +00:00
{
2022-12-04 18:02:33 +00:00
return DeprecatedString : : empty ( ) ;
2022-04-03 16:15:36 +00:00
}
virtual void page_did_set_cookie ( AK : : URL const & , Web : : Cookie : : ParsedCookie const & , Web : : Cookie : : Source ) override
{
}
2022-02-26 16:50:31 +00:00
void request_file ( NonnullRefPtr < Web : : FileRequest > & request ) override
{
auto const file = Core : : System : : open ( request - > path ( ) , O_RDONLY ) ;
request - > on_file_request_finish ( file ) ;
}
2022-04-03 16:15:36 +00:00
private :
HeadlessBrowserPageClient ( )
: m_page ( make < Web : : Page > ( * this ) )
{
}
NonnullOwnPtr < Web : : Page > m_page ;
RefPtr < Gfx : : PaletteImpl > m_palette_impl ;
2022-11-25 17:07:19 +00:00
Web : : DevicePixelRect m_screen_rect { 0 , 0 , 800 , 600 } ;
2022-04-03 16:15:36 +00:00
Web : : CSS : : PreferredColorScheme m_preferred_color_scheme { Web : : CSS : : PreferredColorScheme : : Auto } ;
2022-11-22 01:03:45 +00:00
RefPtr < WebContent : : WebDriverConnection > m_webdriver ;
2022-04-03 16:15:36 +00:00
} ;
2022-09-16 13:01:47 +00:00
class ImageCodecPluginHeadless : public Web : : Platform : : ImageCodecPlugin {
2022-04-03 16:15:36 +00:00
public :
2022-09-16 13:01:47 +00:00
ImageCodecPluginHeadless ( ) = default ;
virtual ~ ImageCodecPluginHeadless ( ) override = default ;
2022-04-03 16:15:36 +00:00
2022-09-16 13:01:47 +00:00
virtual Optional < Web : : Platform : : DecodedImage > decode_image ( ReadonlyBytes data ) override
2022-04-03 16:15:36 +00:00
{
2022-04-03 16:17:42 +00:00
auto decoder = Gfx : : ImageDecoder : : try_create ( data ) ;
if ( ! decoder )
2022-09-16 13:01:47 +00:00
return Web : : Platform : : DecodedImage { false , 0 , Vector < Web : : Platform : : Frame > { } } ;
2022-04-03 16:17:42 +00:00
if ( ! decoder - > frame_count ( ) )
2022-09-16 13:01:47 +00:00
return Web : : Platform : : DecodedImage { false , 0 , Vector < Web : : Platform : : Frame > { } } ;
2022-04-03 16:17:42 +00:00
2022-09-16 13:01:47 +00:00
Vector < Web : : Platform : : Frame > frames ;
2022-04-03 16:17:42 +00:00
for ( size_t i = 0 ; i < decoder - > frame_count ( ) ; + + i ) {
auto frame_or_error = decoder - > frame ( i ) ;
if ( frame_or_error . is_error ( ) ) {
frames . append ( { { } , 0 } ) ;
} else {
auto frame = frame_or_error . release_value ( ) ;
frames . append ( { move ( frame . image ) , static_cast < size_t > ( frame . duration ) } ) ;
}
}
2022-09-16 13:01:47 +00:00
return Web : : Platform : : DecodedImage {
2022-04-03 16:17:42 +00:00
decoder - > is_animated ( ) ,
static_cast < u32 > ( decoder - > loop_count ( ) ) ,
frames ,
} ;
2022-04-03 16:15:36 +00:00
}
} ;
2022-04-03 16:20:05 +00:00
static HashTable < RefPtr < Web : : ResourceLoaderConnectorRequest > > s_all_requests ;
2022-04-03 16:15:36 +00:00
class HeadlessRequestServer : public Web : : ResourceLoaderConnector {
public :
2022-04-03 16:20:05 +00:00
class HTTPHeadlessRequest
: public Web : : ResourceLoaderConnectorRequest
, public Weakable < HTTPHeadlessRequest > {
public :
2022-12-04 18:02:33 +00:00
static ErrorOr < NonnullRefPtr < HTTPHeadlessRequest > > create ( DeprecatedString const & method , AK : : URL const & url , HashMap < DeprecatedString , DeprecatedString > const & request_headers , ReadonlyBytes request_body , Core : : ProxyData const & )
2022-04-03 16:20:05 +00:00
{
auto stream_backing_buffer = TRY ( ByteBuffer : : create_uninitialized ( 1 * MiB ) ) ;
auto underlying_socket = TRY ( Core : : Stream : : TCPSocket : : connect ( url . host ( ) , url . port ( ) . value_or ( 80 ) ) ) ;
TRY ( underlying_socket - > set_blocking ( false ) ) ;
auto socket = TRY ( Core : : Stream : : BufferedSocket < Core : : Stream : : TCPSocket > : : create ( move ( underlying_socket ) ) ) ;
HTTP : : HttpRequest request ;
if ( method . equals_ignoring_case ( " head " sv ) )
request . set_method ( HTTP : : HttpRequest : : HEAD ) ;
else if ( method . equals_ignoring_case ( " get " sv ) )
request . set_method ( HTTP : : HttpRequest : : GET ) ;
else if ( method . equals_ignoring_case ( " post " sv ) )
request . set_method ( HTTP : : HttpRequest : : POST ) ;
else
request . set_method ( HTTP : : HttpRequest : : Invalid ) ;
request . set_url ( move ( url ) ) ;
request . set_headers ( request_headers ) ;
request . set_body ( TRY ( ByteBuffer : : copy ( request_body ) ) ) ;
return adopt_ref ( * new HTTPHeadlessRequest ( move ( request ) , move ( socket ) , move ( stream_backing_buffer ) ) ) ;
}
virtual ~ HTTPHeadlessRequest ( ) override
{
}
virtual void set_should_buffer_all_input ( bool ) override
{
}
virtual bool stop ( ) override
{
return false ;
}
virtual void stream_into ( Core : : Stream : : Stream & ) override
{
}
private :
HTTPHeadlessRequest ( HTTP : : HttpRequest & & request , NonnullOwnPtr < Core : : Stream : : BufferedSocketBase > socket , ByteBuffer & & stream_backing_buffer )
: m_stream_backing_buffer ( move ( stream_backing_buffer ) )
, m_output_stream ( Core : : Stream : : MemoryStream : : construct ( m_stream_backing_buffer . bytes ( ) ) . release_value_but_fixme_should_propagate_errors ( ) )
, m_socket ( move ( socket ) )
, m_job ( HTTP : : Job : : construct ( move ( request ) , * m_output_stream ) )
{
2022-11-19 01:09:53 +00:00
m_job - > on_headers_received = [ weak_this = make_weak_ptr ( ) ] ( auto & response_headers , auto response_code ) {
2022-04-03 16:20:05 +00:00
if ( auto strong_this = weak_this . strong_ref ( ) ) {
strong_this - > m_response_code = response_code ;
for ( auto & header : response_headers ) {
strong_this - > m_response_headers . set ( header . key , header . value ) ;
}
}
} ;
2022-11-19 01:09:53 +00:00
m_job - > on_finish = [ weak_this = make_weak_ptr ( ) ] ( bool success ) {
Core : : deferred_invoke ( [ weak_this , success ] {
2022-04-03 16:20:05 +00:00
if ( auto strong_this = weak_this . strong_ref ( ) ) {
ReadonlyBytes response_bytes { strong_this - > m_output_stream - > bytes ( ) . data ( ) , strong_this - > m_output_stream - > offset ( ) } ;
auto response_buffer = ByteBuffer : : copy ( response_bytes ) . release_value_but_fixme_should_propagate_errors ( ) ;
strong_this - > on_buffered_request_finish ( success , strong_this - > m_output_stream - > offset ( ) , strong_this - > m_response_headers , strong_this - > m_response_code , response_buffer ) ;
}
} ) ;
} ;
m_job - > start ( * m_socket ) ;
}
Optional < u32 > m_response_code ;
ByteBuffer m_stream_backing_buffer ;
NonnullOwnPtr < Core : : Stream : : MemoryStream > m_output_stream ;
NonnullOwnPtr < Core : : Stream : : BufferedSocketBase > m_socket ;
NonnullRefPtr < HTTP : : Job > m_job ;
2022-12-04 18:02:33 +00:00
HashMap < DeprecatedString , DeprecatedString , CaseInsensitiveStringTraits > m_response_headers ;
2022-04-03 16:20:05 +00:00
} ;
class HTTPSHeadlessRequest
: public Web : : ResourceLoaderConnectorRequest
, public Weakable < HTTPSHeadlessRequest > {
public :
2022-12-04 18:02:33 +00:00
static ErrorOr < NonnullRefPtr < HTTPSHeadlessRequest > > create ( DeprecatedString const & method , AK : : URL const & url , HashMap < DeprecatedString , DeprecatedString > const & request_headers , ReadonlyBytes request_body , Core : : ProxyData const & )
2022-04-03 16:20:05 +00:00
{
auto stream_backing_buffer = TRY ( ByteBuffer : : create_uninitialized ( 1 * MiB ) ) ;
2022-07-18 16:39:09 +00:00
auto underlying_socket = TRY ( TLS : : TLSv12 : : connect ( url . host ( ) , url . port ( ) . value_or ( 443 ) ) ) ;
2022-04-03 16:20:05 +00:00
TRY ( underlying_socket - > set_blocking ( false ) ) ;
auto socket = TRY ( Core : : Stream : : BufferedSocket < TLS : : TLSv12 > : : create ( move ( underlying_socket ) ) ) ;
HTTP : : HttpRequest request ;
if ( method . equals_ignoring_case ( " head " sv ) )
request . set_method ( HTTP : : HttpRequest : : HEAD ) ;
else if ( method . equals_ignoring_case ( " get " sv ) )
request . set_method ( HTTP : : HttpRequest : : GET ) ;
else if ( method . equals_ignoring_case ( " post " sv ) )
request . set_method ( HTTP : : HttpRequest : : POST ) ;
else
request . set_method ( HTTP : : HttpRequest : : Invalid ) ;
request . set_url ( move ( url ) ) ;
request . set_headers ( request_headers ) ;
request . set_body ( TRY ( ByteBuffer : : copy ( request_body ) ) ) ;
return adopt_ref ( * new HTTPSHeadlessRequest ( move ( request ) , move ( socket ) , move ( stream_backing_buffer ) ) ) ;
}
virtual ~ HTTPSHeadlessRequest ( ) override
{
}
virtual void set_should_buffer_all_input ( bool ) override
{
}
virtual bool stop ( ) override
{
return false ;
}
virtual void stream_into ( Core : : Stream : : Stream & ) override
{
}
private :
HTTPSHeadlessRequest ( HTTP : : HttpRequest & & request , NonnullOwnPtr < Core : : Stream : : BufferedSocketBase > socket , ByteBuffer & & stream_backing_buffer )
: m_stream_backing_buffer ( move ( stream_backing_buffer ) )
, m_output_stream ( Core : : Stream : : MemoryStream : : construct ( m_stream_backing_buffer . bytes ( ) ) . release_value_but_fixme_should_propagate_errors ( ) )
, m_socket ( move ( socket ) )
, m_job ( HTTP : : HttpsJob : : construct ( move ( request ) , * m_output_stream ) )
{
2022-11-19 01:09:53 +00:00
m_job - > on_headers_received = [ weak_this = make_weak_ptr ( ) ] ( auto & response_headers , auto response_code ) {
2022-04-03 16:20:05 +00:00
if ( auto strong_this = weak_this . strong_ref ( ) ) {
strong_this - > m_response_code = response_code ;
for ( auto & header : response_headers ) {
strong_this - > m_response_headers . set ( header . key , header . value ) ;
}
}
} ;
2022-11-19 01:09:53 +00:00
m_job - > on_finish = [ weak_this = make_weak_ptr ( ) ] ( bool success ) {
Core : : deferred_invoke ( [ weak_this , success ] {
2022-04-03 16:20:05 +00:00
if ( auto strong_this = weak_this . strong_ref ( ) ) {
ReadonlyBytes response_bytes { strong_this - > m_output_stream - > bytes ( ) . data ( ) , strong_this - > m_output_stream - > offset ( ) } ;
auto response_buffer = ByteBuffer : : copy ( response_bytes ) . release_value_but_fixme_should_propagate_errors ( ) ;
strong_this - > on_buffered_request_finish ( success , strong_this - > m_output_stream - > offset ( ) , strong_this - > m_response_headers , strong_this - > m_response_code , response_buffer ) ;
}
} ) ;
} ;
m_job - > start ( * m_socket ) ;
}
Optional < u32 > m_response_code ;
ByteBuffer m_stream_backing_buffer ;
NonnullOwnPtr < Core : : Stream : : MemoryStream > m_output_stream ;
NonnullOwnPtr < Core : : Stream : : BufferedSocketBase > m_socket ;
NonnullRefPtr < HTTP : : HttpsJob > m_job ;
2022-12-04 18:02:33 +00:00
HashMap < DeprecatedString , DeprecatedString , CaseInsensitiveStringTraits > m_response_headers ;
2022-04-03 16:20:05 +00:00
} ;
class GeminiHeadlessRequest
: public Web : : ResourceLoaderConnectorRequest
, public Weakable < GeminiHeadlessRequest > {
public :
2022-12-04 18:02:33 +00:00
static ErrorOr < NonnullRefPtr < GeminiHeadlessRequest > > create ( DeprecatedString const & , AK : : URL const & url , HashMap < DeprecatedString , DeprecatedString > const & , ReadonlyBytes , Core : : ProxyData const & )
2022-04-03 16:20:05 +00:00
{
auto stream_backing_buffer = TRY ( ByteBuffer : : create_uninitialized ( 1 * MiB ) ) ;
auto underlying_socket = TRY ( Core : : Stream : : TCPSocket : : connect ( url . host ( ) , url . port ( ) . value_or ( 80 ) ) ) ;
TRY ( underlying_socket - > set_blocking ( false ) ) ;
auto socket = TRY ( Core : : Stream : : BufferedSocket < Core : : Stream : : TCPSocket > : : create ( move ( underlying_socket ) ) ) ;
Gemini : : GeminiRequest request ;
request . set_url ( url ) ;
return adopt_ref ( * new GeminiHeadlessRequest ( move ( request ) , move ( socket ) , move ( stream_backing_buffer ) ) ) ;
}
virtual ~ GeminiHeadlessRequest ( ) override
{
}
virtual void set_should_buffer_all_input ( bool ) override
{
}
virtual bool stop ( ) override
{
return false ;
}
virtual void stream_into ( Core : : Stream : : Stream & ) override
{
}
private :
GeminiHeadlessRequest ( Gemini : : GeminiRequest & & request , NonnullOwnPtr < Core : : Stream : : BufferedSocketBase > socket , ByteBuffer & & stream_backing_buffer )
: m_stream_backing_buffer ( move ( stream_backing_buffer ) )
, m_output_stream ( Core : : Stream : : MemoryStream : : construct ( m_stream_backing_buffer . bytes ( ) ) . release_value_but_fixme_should_propagate_errors ( ) )
, m_socket ( move ( socket ) )
, m_job ( Gemini : : Job : : construct ( move ( request ) , * m_output_stream ) )
{
2022-11-19 01:09:53 +00:00
m_job - > on_headers_received = [ weak_this = make_weak_ptr ( ) ] ( auto & response_headers , auto response_code ) {
2022-04-03 16:20:05 +00:00
if ( auto strong_this = weak_this . strong_ref ( ) ) {
strong_this - > m_response_code = response_code ;
for ( auto & header : response_headers ) {
strong_this - > m_response_headers . set ( header . key , header . value ) ;
}
}
} ;
2022-11-19 01:09:53 +00:00
m_job - > on_finish = [ weak_this = make_weak_ptr ( ) ] ( bool success ) {
Core : : deferred_invoke ( [ weak_this , success ] {
2022-04-03 16:20:05 +00:00
if ( auto strong_this = weak_this . strong_ref ( ) ) {
ReadonlyBytes response_bytes { strong_this - > m_output_stream - > bytes ( ) . data ( ) , strong_this - > m_output_stream - > offset ( ) } ;
auto response_buffer = ByteBuffer : : copy ( response_bytes ) . release_value_but_fixme_should_propagate_errors ( ) ;
strong_this - > on_buffered_request_finish ( success , strong_this - > m_output_stream - > offset ( ) , strong_this - > m_response_headers , strong_this - > m_response_code , response_buffer ) ;
}
} ) ;
} ;
m_job - > start ( * m_socket ) ;
}
Optional < u32 > m_response_code ;
ByteBuffer m_stream_backing_buffer ;
NonnullOwnPtr < Core : : Stream : : MemoryStream > m_output_stream ;
NonnullOwnPtr < Core : : Stream : : BufferedSocketBase > m_socket ;
NonnullRefPtr < Gemini : : Job > m_job ;
2022-12-04 18:02:33 +00:00
HashMap < DeprecatedString , DeprecatedString , CaseInsensitiveStringTraits > m_response_headers ;
2022-04-03 16:20:05 +00:00
} ;
2022-04-03 16:15:36 +00:00
static NonnullRefPtr < HeadlessRequestServer > create ( )
{
return adopt_ref ( * new HeadlessRequestServer ( ) ) ;
}
virtual ~ HeadlessRequestServer ( ) override { }
virtual void prefetch_dns ( AK : : URL const & ) override { }
virtual void preconnect ( AK : : URL const & ) override { }
2022-12-04 18:02:33 +00:00
virtual RefPtr < Web : : ResourceLoaderConnectorRequest > start_request ( DeprecatedString const & method , AK : : URL const & url , HashMap < DeprecatedString , DeprecatedString > const & request_headers , ReadonlyBytes request_body , Core : : ProxyData const & proxy ) override
2022-04-03 16:15:36 +00:00
{
2022-04-03 16:20:05 +00:00
RefPtr < Web : : ResourceLoaderConnectorRequest > request ;
2022-09-28 23:30:58 +00:00
if ( url . scheme ( ) . equals_ignoring_case ( " http " sv ) ) {
2022-04-03 16:20:05 +00:00
auto request_or_error = HTTPHeadlessRequest : : create ( method , url , request_headers , request_body , proxy ) ;
if ( request_or_error . is_error ( ) )
return { } ;
request = request_or_error . release_value ( ) ;
}
2022-09-28 23:30:58 +00:00
if ( url . scheme ( ) . equals_ignoring_case ( " https " sv ) ) {
2022-04-03 16:20:05 +00:00
auto request_or_error = HTTPSHeadlessRequest : : create ( method , url , request_headers , request_body , proxy ) ;
if ( request_or_error . is_error ( ) )
return { } ;
request = request_or_error . release_value ( ) ;
}
2022-09-28 23:30:58 +00:00
if ( url . scheme ( ) . equals_ignoring_case ( " gemini " sv ) ) {
2022-04-03 16:20:05 +00:00
auto request_or_error = GeminiHeadlessRequest : : create ( method , url , request_headers , request_body , proxy ) ;
if ( request_or_error . is_error ( ) )
return { } ;
request = request_or_error . release_value ( ) ;
}
if ( request )
s_all_requests . set ( request ) ;
return request ;
2022-04-03 16:15:36 +00:00
}
private :
HeadlessRequestServer ( ) { }
} ;
class HeadlessWebSocketClientManager : public Web : : WebSockets : : WebSocketClientManager {
public :
2022-04-03 16:20:05 +00:00
class HeadlessWebSocket
: public Web : : WebSockets : : WebSocketClientSocket
, public Weakable < HeadlessWebSocket > {
public :
static NonnullRefPtr < HeadlessWebSocket > create ( NonnullRefPtr < WebSocket : : WebSocket > underlying_socket )
{
return adopt_ref ( * new HeadlessWebSocket ( move ( underlying_socket ) ) ) ;
}
virtual ~ HeadlessWebSocket ( ) override
{
}
virtual Web : : WebSockets : : WebSocket : : ReadyState ready_state ( ) override
{
switch ( m_websocket - > ready_state ( ) ) {
case WebSocket : : ReadyState : : Connecting :
return Web : : WebSockets : : WebSocket : : ReadyState : : Connecting ;
case WebSocket : : ReadyState : : Open :
return Web : : WebSockets : : WebSocket : : ReadyState : : Open ;
case WebSocket : : ReadyState : : Closing :
return Web : : WebSockets : : WebSocket : : ReadyState : : Closing ;
case WebSocket : : ReadyState : : Closed :
return Web : : WebSockets : : WebSocket : : ReadyState : : Closed ;
}
VERIFY_NOT_REACHED ( ) ;
}
virtual void send ( ByteBuffer binary_or_text_message , bool is_text ) override
{
m_websocket - > send ( WebSocket : : Message ( binary_or_text_message , is_text ) ) ;
}
virtual void send ( StringView message ) override
{
m_websocket - > send ( WebSocket : : Message ( message ) ) ;
}
2022-12-04 18:02:33 +00:00
virtual void close ( u16 code , DeprecatedString reason ) override
2022-04-03 16:20:05 +00:00
{
m_websocket - > close ( code , reason ) ;
}
private :
HeadlessWebSocket ( NonnullRefPtr < WebSocket : : WebSocket > underlying_socket )
: m_websocket ( move ( underlying_socket ) )
{
m_websocket - > on_open = [ weak_this = make_weak_ptr ( ) ] {
if ( auto strong_this = weak_this . strong_ref ( ) )
if ( strong_this - > on_open )
strong_this - > on_open ( ) ;
} ;
m_websocket - > on_message = [ weak_this = make_weak_ptr ( ) ] ( auto message ) {
if ( auto strong_this = weak_this . strong_ref ( ) ) {
if ( strong_this - > on_message ) {
strong_this - > on_message ( Web : : WebSockets : : WebSocketClientSocket : : Message {
. data = move ( message . data ( ) ) ,
. is_text = message . is_text ( ) ,
} ) ;
}
}
} ;
m_websocket - > on_error = [ weak_this = make_weak_ptr ( ) ] ( auto error ) {
if ( auto strong_this = weak_this . strong_ref ( ) ) {
if ( strong_this - > on_error ) {
switch ( error ) {
case WebSocket : : WebSocket : : Error : : CouldNotEstablishConnection :
strong_this - > on_error ( Web : : WebSockets : : WebSocketClientSocket : : Error : : CouldNotEstablishConnection ) ;
return ;
case WebSocket : : WebSocket : : Error : : ConnectionUpgradeFailed :
strong_this - > on_error ( Web : : WebSockets : : WebSocketClientSocket : : Error : : ConnectionUpgradeFailed ) ;
return ;
case WebSocket : : WebSocket : : Error : : ServerClosedSocket :
strong_this - > on_error ( Web : : WebSockets : : WebSocketClientSocket : : Error : : ServerClosedSocket ) ;
return ;
}
VERIFY_NOT_REACHED ( ) ;
}
}
} ;
2022-12-04 18:02:33 +00:00
m_websocket - > on_close = [ weak_this = make_weak_ptr ( ) ] ( u16 code , DeprecatedString reason , bool was_clean ) {
2022-04-03 16:20:05 +00:00
if ( auto strong_this = weak_this . strong_ref ( ) )
if ( strong_this - > on_close )
strong_this - > on_close ( code , move ( reason ) , was_clean ) ;
} ;
}
NonnullRefPtr < WebSocket : : WebSocket > m_websocket ;
} ;
2022-04-03 16:15:36 +00:00
static NonnullRefPtr < HeadlessWebSocketClientManager > create ( )
{
return adopt_ref ( * new HeadlessWebSocketClientManager ( ) ) ;
}
virtual ~ HeadlessWebSocketClientManager ( ) override { }
2022-12-04 18:02:33 +00:00
virtual RefPtr < Web : : WebSockets : : WebSocketClientSocket > connect ( AK : : URL const & url , DeprecatedString const & origin ) override
2022-04-03 16:15:36 +00:00
{
2022-04-03 16:20:05 +00:00
WebSocket : : ConnectionInfo connection_info ( url ) ;
connection_info . set_origin ( origin ) ;
auto connection = HeadlessWebSocket : : create ( WebSocket : : WebSocket : : create ( move ( connection_info ) ) ) ;
return connection ;
2022-04-03 16:15:36 +00:00
}
private :
HeadlessWebSocketClientManager ( ) { }
} ;
2022-11-22 01:03:45 +00:00
static void load_page_for_screenshot_and_exit ( HeadlessBrowserPageClient & page_client , int take_screenshot_after )
{
dbgln ( " Taking screenshot after {} seconds " , take_screenshot_after ) ;
auto timer = Core : : Timer : : create_single_shot (
take_screenshot_after * 1000 ,
[ & ] ( ) {
// FIXME: Allow passing the output path as argument
2022-12-04 18:02:33 +00:00
DeprecatedString output_file_path = " output.png " ;
2022-11-22 01:03:45 +00:00
dbgln ( " Saving to {} " , output_file_path ) ;
if ( Core : : File : : exists ( output_file_path ) )
MUST ( Core : : File : : remove ( output_file_path , Core : : File : : RecursionMode : : Disallowed , true ) ) ;
auto output_file = MUST ( Core : : Stream : : File : : open ( output_file_path , Core : : Stream : : OpenMode : : Write ) ) ;
auto output_rect = page_client . screen_rect ( ) ;
2022-11-25 17:07:19 +00:00
auto output_bitmap = MUST ( Gfx : : Bitmap : : try_create ( Gfx : : BitmapFormat : : BGRx8888 , output_rect . size ( ) . to_type < int > ( ) ) ) ;
2022-11-22 01:03:45 +00:00
page_client . paint ( output_rect , output_bitmap ) ;
2022-12-05 19:34:27 +00:00
auto image_buffer = MUST ( Gfx : : PNGWriter : : encode ( output_bitmap ) ) ;
2022-11-22 01:03:45 +00:00
MUST ( output_file - > write ( image_buffer . bytes ( ) ) ) ;
exit ( 0 ) ;
} ) ;
timer - > start ( ) ;
}
2022-04-03 16:15:36 +00:00
ErrorOr < int > serenity_main ( Main : : Arguments arguments )
{
int take_screenshot_after = 1 ;
StringView url ;
2022-07-18 18:23:19 +00:00
StringView resources_folder ;
2022-04-03 16:15:36 +00:00
StringView error_page_url ;
2022-09-30 06:59:37 +00:00
StringView ca_certs_path ;
2022-11-22 01:03:45 +00:00
StringView webdriver_ipc_path ;
int webdriver_fd_passing_socket { - 1 } ;
2022-04-03 16:15:36 +00:00
Core : : EventLoop event_loop ;
Core : : ArgsParser args_parser ;
args_parser . set_general_help ( " This utility runs the Browser in headless mode. " ) ;
args_parser . add_option ( take_screenshot_after , " Take a screenshot after [n] seconds (default: 1) " , " screenshot " , ' s ' , " n " ) ;
2022-07-18 18:23:19 +00:00
args_parser . add_option ( resources_folder , " Path of the base resources folder (defaults to /res) " , " resources " , ' r ' , " resources-root-path " ) ;
args_parser . add_option ( error_page_url , " URL for the error page (defaults to file:///res/html/error.html) " , " error-page " , ' e ' , " error-page-url " ) ;
2022-09-30 06:59:37 +00:00
args_parser . add_option ( ca_certs_path , " The bundled ca certificates file " , " certs " , ' c ' , " ca-certs-path " ) ;
2022-11-22 01:03:45 +00:00
args_parser . add_option ( webdriver_ipc_path , " Path to the WebDriver IPC socket " , " webdriver-ipc-path " , 0 , " path " ) ;
args_parser . add_option ( webdriver_fd_passing_socket , " File descriptor of the passing socket for the WebDriver connection " , " webdriver-fd-passing-socket " , ' d ' , " webdriver_fd_passing_socket " ) ;
2022-04-03 16:15:36 +00:00
args_parser . add_positional_argument ( url , " URL to open " , " url " , Core : : ArgsParser : : Required : : Yes ) ;
args_parser . parse ( arguments ) ;
2022-11-22 01:03:45 +00:00
if ( ! webdriver_ipc_path . is_empty ( ) & & webdriver_fd_passing_socket > = 0 )
return Error : : from_string_view ( " Only one of --webdriver-ipc-path and --webdriver-fd-passing-socket may be used " sv ) ;
2022-09-21 16:22:53 +00:00
Web : : Platform : : EventLoopPlugin : : install ( * new Web : : Platform : : EventLoopPluginSerenity ) ;
Web : : Platform : : FontPlugin : : install ( * new Web : : Platform : : FontPluginSerenity ) ;
2022-09-16 13:01:47 +00:00
Web : : Platform : : ImageCodecPlugin : : install ( * new ImageCodecPluginHeadless ) ;
2022-04-03 16:15:36 +00:00
Web : : ResourceLoader : : initialize ( HeadlessRequestServer : : create ( ) ) ;
Web : : WebSockets : : WebSocketClientManager : : initialize ( HeadlessWebSocketClientManager : : create ( ) ) ;
2022-07-18 18:23:19 +00:00
if ( ! resources_folder . is_empty ( ) ) {
Web : : FrameLoader : : set_default_favicon_path ( LexicalPath : : join ( resources_folder , " icons/16x16/app-browser.png " sv ) . string ( ) ) ;
Gfx : : FontDatabase : : set_default_fonts_lookup_path ( LexicalPath : : join ( resources_folder , " fonts " sv ) . string ( ) ) ;
}
2022-09-30 06:59:37 +00:00
if ( ! ca_certs_path . is_empty ( ) ) {
auto config_result = Core : : ConfigFile : : open ( ca_certs_path ) ;
if ( config_result . is_error ( ) ) {
dbgln ( " Failed to load CA Certificates: {} " , config_result . error ( ) ) ;
} else {
auto config = config_result . release_value ( ) ;
DefaultRootCACertificates : : the ( ) . reload_certificates ( config ) ;
}
}
2022-04-03 16:15:36 +00:00
Gfx : : FontDatabase : : set_default_font_query ( " Katica 10 400 0 " ) ;
2022-07-31 16:41:07 +00:00
Gfx : : FontDatabase : : set_window_title_font_query ( " Katica 10 700 0 " ) ;
2022-04-03 16:15:36 +00:00
Gfx : : FontDatabase : : set_fixed_width_font_query ( " Csilla 10 400 0 " ) ;
if ( ! error_page_url . is_empty ( ) )
Web : : FrameLoader : : set_error_page_url ( error_page_url ) ;
auto page_client = HeadlessBrowserPageClient : : create ( ) ;
2022-07-18 18:23:19 +00:00
if ( ! resources_folder . is_empty ( ) )
page_client - > setup_palette ( Gfx : : load_system_theme ( LexicalPath : : join ( resources_folder , " themes/Default.ini " sv ) . string ( ) ) ) ;
2022-04-03 16:15:36 +00:00
else
page_client - > setup_palette ( Gfx : : load_system_theme ( " /res/themes/Default.ini " ) ) ;
dbgln ( " Loading {} " , url ) ;
page_client - > load ( AK : : URL ( url ) ) ;
// FIXME: Allow passing these values as arguments
2022-07-18 16:44:22 +00:00
page_client - > set_viewport_rect ( { 0 , 0 , 800 , 600 } ) ;
2022-04-03 16:15:36 +00:00
page_client - > set_screen_rect ( { 0 , 0 , 800 , 600 } ) ;
2022-11-22 01:03:45 +00:00
if ( ! webdriver_ipc_path . is_empty ( ) )
TRY ( page_client - > connect_to_webdriver ( webdriver_ipc_path ) ) ;
else if ( webdriver_fd_passing_socket > = 0 )
TRY ( page_client - > connect_to_webdriver ( webdriver_fd_passing_socket ) ) ;
else
load_page_for_screenshot_and_exit ( * page_client , take_screenshot_after ) ;
2022-04-03 16:15:36 +00:00
return event_loop . exec ( ) ;
}